From dc9baea8fdc3336696c4b954f4c97e8664f465b4 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Wed, 24 Jul 2024 14:25:53 +0100 Subject: [PATCH 01/18] first dockerfile --- Dockerfile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3afbd82 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:18.20-alpine + +RUN apk update && apk add --no-cache python3 py3-pip g++ make + +COPY /source /source + +WORKDIR /source + +RUN yarn install + +RUN yarn build + +CMD yarn config-validator ../config + From b5ff77cb4fae566809401409a62118f26cd4faca Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Wed, 24 Jul 2024 14:58:03 +0100 Subject: [PATCH 02/18] correcting CMD --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3afbd82..569ef76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,5 +10,5 @@ RUN yarn install RUN yarn build -CMD yarn config-validator ../config +CMD yarn validate-config ../config From 5a32f353e7538943ad7303cad348e592dade502c Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Thu, 25 Jul 2024 09:32:31 +0100 Subject: [PATCH 03/18] crating multi stage build --- Dockerfile | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 569ef76..c96cac5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,39 @@ -FROM node:18.20-alpine +FROM node:18.20-alpine AS build RUN apk update && apk add --no-cache python3 py3-pip g++ make - COPY /source /source +WORKDIR /source +RUN yarn install +RUN yarn build +FROM node:18.20-alpine WORKDIR /source +COPY --from=build /source/node_modules /source/node_modules +COPY --from=build /source/package.json /source/package.json +COPY --from=build /source/packages/@aws-accelerator/accelerator/dist /source/packages/@aws-accelerator/accelerator/dist +COPY --from=build /source/packages/@aws-accelerator/accelerator/node_modules /source/packages/@aws-accelerator/accelerator/node_modules +COPY --from=build /source/packages/@aws-accelerator/config/dist /source/packages/@aws-accelerator/config/dist +COPY --from=build /source/packages/@aws-accelerator/config/node_modules /source/packages/@aws-accelerator/config/node_modules +COPY --from=build /source/packages/@aws-accelerator/constructs/node_modules /source/packages/@aws-accelerator/constructs/node_modules +COPY --from=build /source/packages/@aws-accelerator/govcloud-account-vending/dist /source/packages/@aws-accelerator/govcloud-account-vending/dist +COPY --from=build /source/packages/@aws-accelerator/govcloud-account-vending/node_modules /source/packages/@aws-accelerator/govcloud-account-vending/node_modules +COPY --from=build /source/packages/@aws-accelerator/installer/dist /source/packages/@aws-accelerator/installer/dist +COPY --from=build /source/packages/@aws-accelerator/installer/node_modules /source/packages/@aws-accelerator/installer/node_modules +COPY --from=build /source/packages/@aws-accelerator/modules/dist /source/packages/@aws-accelerator/modules/dist +COPY --from=build /source/packages/@aws-accelerator/modules/node_modules /source/packages/@aws-accelerator/modules/node_modules +COPY --from=build /source/packages/@aws-accelerator/tester/dist /source/packages/@aws-accelerator/tester/dist +COPY --from=build /source/packages/@aws-accelerator/tester/node_modules /source/packages/@aws-accelerator/tester/node_modules +COPY --from=build /source/packages/@aws-accelerator/tools/dist /source/packages/@aws-accelerator/tools/dist +COPY --from=build /source/packages/@aws-accelerator/tools/node_modules /source/packages/@aws-accelerator/tools/node_modules +COPY --from=build /source/packages/@aws-accelerator/utils/dist /source/packages/@aws-accelerator/utils/dist +COPY --from=build /source/packages/@aws-accelerator/utils/node_modules /source/packages/@aws-accelerator/utils/node_modules -RUN yarn install +COPY --from=build /source/packages/@aws-cdk-extensions/cdk-extensions/dist /source/packages/@aws-cdk-extensions/cdk-extensions/dist +COPY --from=build /source/packages/@aws-cdk-extensions/cdk-extensions/node_modules /source/packages/@aws-cdk-extensions/cdk-extensions/node_modules +COPY --from=build /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/dist /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/dist +COPY --from=build /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/node_modules /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/node_modules -RUN yarn build +COPY --from=build /source/packages/@aws-accelerator/accelerator/lib/config-validator.ts /source/packages/@aws-accelerator/accelerator/lib/config-validator.ts -CMD yarn validate-config ../config +CMD ts-node /source/packages/@aws-accelerator/accelerator/dist/@aws-accelerator/accelerator/lib/ ../config From d09aae8607b639758e81019999fc5c450262c088 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Thu, 25 Jul 2024 10:50:53 +0100 Subject: [PATCH 04/18] working AMD image --- Dockerfile | 53 +++++++++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index c96cac5..f6f89ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,24 @@ -FROM node:18.20-alpine AS build +# FROM node:18.20-alpine -RUN apk update && apk add --no-cache python3 py3-pip g++ make -COPY /source /source -WORKDIR /source -RUN yarn install -RUN yarn build +# RUN apk update && apk add --no-cache python3 py3-pip g++ make +# COPY /source /source +# WORKDIR /source +# RUN export NODE_OPTIONS=--max_old_space_size=8192 +# RUN yarn install +# RUN yarn build +# RUN yarn cache clean -FROM node:18.20-alpine -WORKDIR /source -COPY --from=build /source/node_modules /source/node_modules -COPY --from=build /source/package.json /source/package.json -COPY --from=build /source/packages/@aws-accelerator/accelerator/dist /source/packages/@aws-accelerator/accelerator/dist -COPY --from=build /source/packages/@aws-accelerator/accelerator/node_modules /source/packages/@aws-accelerator/accelerator/node_modules -COPY --from=build /source/packages/@aws-accelerator/config/dist /source/packages/@aws-accelerator/config/dist -COPY --from=build /source/packages/@aws-accelerator/config/node_modules /source/packages/@aws-accelerator/config/node_modules -COPY --from=build /source/packages/@aws-accelerator/constructs/node_modules /source/packages/@aws-accelerator/constructs/node_modules -COPY --from=build /source/packages/@aws-accelerator/govcloud-account-vending/dist /source/packages/@aws-accelerator/govcloud-account-vending/dist -COPY --from=build /source/packages/@aws-accelerator/govcloud-account-vending/node_modules /source/packages/@aws-accelerator/govcloud-account-vending/node_modules -COPY --from=build /source/packages/@aws-accelerator/installer/dist /source/packages/@aws-accelerator/installer/dist -COPY --from=build /source/packages/@aws-accelerator/installer/node_modules /source/packages/@aws-accelerator/installer/node_modules -COPY --from=build /source/packages/@aws-accelerator/modules/dist /source/packages/@aws-accelerator/modules/dist -COPY --from=build /source/packages/@aws-accelerator/modules/node_modules /source/packages/@aws-accelerator/modules/node_modules -COPY --from=build /source/packages/@aws-accelerator/tester/dist /source/packages/@aws-accelerator/tester/dist -COPY --from=build /source/packages/@aws-accelerator/tester/node_modules /source/packages/@aws-accelerator/tester/node_modules -COPY --from=build /source/packages/@aws-accelerator/tools/dist /source/packages/@aws-accelerator/tools/dist -COPY --from=build /source/packages/@aws-accelerator/tools/node_modules /source/packages/@aws-accelerator/tools/node_modules -COPY --from=build /source/packages/@aws-accelerator/utils/dist /source/packages/@aws-accelerator/utils/dist -COPY --from=build /source/packages/@aws-accelerator/utils/node_modules /source/packages/@aws-accelerator/utils/node_modules +FROM --platform=linux/amd64 node:lts-alpine3.17 +WORKDIR /lza +COPY . . +# COPY lza-validator.sh ./lza-validator.sh -COPY --from=build /source/packages/@aws-cdk-extensions/cdk-extensions/dist /source/packages/@aws-cdk-extensions/cdk-extensions/dist -COPY --from=build /source/packages/@aws-cdk-extensions/cdk-extensions/node_modules /source/packages/@aws-cdk-extensions/cdk-extensions/node_modules -COPY --from=build /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/dist /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/dist -COPY --from=build /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/node_modules /source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/node_modules +RUN mkdir config +RUN cd source \ + && export NODE_OPTIONS=--max_old_space_size=8192 \ + && yarn install \ + && yarn build \ + && yarn cache clean -COPY --from=build /source/packages/@aws-accelerator/accelerator/lib/config-validator.ts /source/packages/@aws-accelerator/accelerator/lib/config-validator.ts - -CMD ts-node /source/packages/@aws-accelerator/accelerator/dist/@aws-accelerator/accelerator/lib/ ../config +CMD yarn validate-config ../config From ecb1202171a23a04bb1b5e5b741d611e62491d2b Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 11:48:34 +0100 Subject: [PATCH 05/18] removing lza files and creating build script and workflow --- .github/workflows/docker-build.yml | 38 + .gitignore | 82 +- .viperlightignore | 239 - .viperlightrc | 4 - CHANGELOG.md | 758 - CODE_OF_CONDUCT.md | 5 - CONTRIBUTING.md | 69 - DEVELOPING.md | 3 - Dockerfile | 19 +- FAQ.md | 3 - LICENSE.txt | 175 - NOTICE.txt | 125 - README.md | 128 - build.sh | 16 + codescan-postbuild-custom.sh | 123 - codescan-prebuild-custom.sh | 117 - deployment/build-docs.sh | 71 - deployment/build-open-source-dist.sh | 157 - deployment/build-s3-dist.sh | 434 - deployment/cdk-solution-helper/README.md | 152 - deployment/cdk-solution-helper/index.js | 105 - deployment/cdk-solution-helper/package.json | 16 - deployment/solution_config | 3 - deployment/update-snapshots.sh | 55 - reference/sample-configurations/README.md | 20 - .../lza-sample-config-cccs-medium/README.md | 5 - .../lza-sample-config-cn/accounts-config.yaml | 25 - .../cfn-templates/iam-analyzer.yaml | 9 - ...h-ec2-instance-profile-detection-role.json | 16 - ...ec2-instance-profile-remediation-role.json | 14 - .../attach-ec2-instance-profile.zip | Bin 987 -> 0 bytes .../bucket-sse-enabled-remediation-role.json | 10 - ...ce-profile-permissions-detection-role.json | 13 - ...-profile-permissions-remediation-role.json | 13 - .../ec2-instance-profile-permissions.zip | Bin 1292 -> 0 bytes .../elb-logging-enabled-remediation-role.json | 14 - .../customizations-config.yaml | 14 - .../domain-list-1.txt | 1 - .../domain-list-2.txt | 1 - .../dynamic-partitioning/log-filters.json | 3 - .../lza-sample-config-cn/global-config.yaml | 140 - .../lza-sample-config-cn/iam-config.yaml | 30 - .../lza-sample-config-cn/network-config.yaml | 486 - .../organization-config.yaml | 9 - .../lza-sample-config-cn/security-config.yaml | 555 - .../attach-iam-instance-profile.yaml | 19 - .../ssm-documents/attach-iam-role-policy.yaml | 50 - .../ssm-documents/s3-encryption.yaml | 28 - .../ssm-documents/ssm-elb-enable-logging.yaml | 44 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../lza-sample-config-education/README.md | 5 - .../lza-sample-config-finance-tax/README.md | 143 - .../accounts-config.yaml | 54 - .../images/lza_tax_network_diagram.png | Bin 1764995 -> 0 bytes .../images/tax_lza_network_diagram.png | Bin 146939 -> 0 bytes .../images/tax_multi_account_ref_arch.png | Bin 296045 -> 0 bytes .../ip-cidr-mapping-tax.csv | 96 - .../network-config.yaml | 1180 - .../organization-config.yaml | 67 - .../scp-base-root.json | 48 - .../scp-only-us-regions.json | 67 - .../tagging-policies/tax-org-tag-policy.json | 39 - .../lza-sample-config-govcloud-us/README.md | 3 - .../commercial-config/accounts-config.yaml | 48 - .../commercial-config/global-config.yaml | 50 - .../commercial-config/iam-config.yaml | 5 - .../commercial-config/network-config.yaml | 53 - .../organization-config.yaml | 34 - .../commercial-config/security-config.yaml | 56 - .../lockdown-govCloud-accounts.json | 24 - .../service-control-policies/quarantine.json | 21 - .../govcloud-us-config/accounts-config.yaml | 47 - .../bucket-policies/central-log-bucket.json | 22 - .../dynamic-partitioning/log-filters.json | 6 - .../govcloud-us-config/global-config.yaml | 72 - .../govcloud-us-config/iam-config.yaml | 83 - .../govcloud-us-config/network-config.yaml | 283 - .../organization-config.yaml | 59 - .../govcloud-us-config/security-config.yaml | 364 - .../guardrails-1.json | 134 - .../guardrails-2.json | 122 - .../service-control-policies/quarantine.json | 21 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../lza-sample-config-healthcare/README.md | 155 - .../accounts-config.yaml | 36 - .../images/.gitkeep | 0 ...ZA_EGA_Healthcare_Network_Diagram_v2.1.png | Bin 560646 -> 0 bytes .../LZA_EGA_Healthcare_Org_Structure.png | Bin 77133 -> 0 bytes .../images/LZAforHealthcare_2022-08-30.png | Bin 637748 -> 0 bytes .../network-config.yaml | 516 - .../organization-config.yaml | 72 - .../service-control-policies/Readme.md | 147 - .../scp-hlc-base-root.json | 48 - .../scp-hlc-hipaa-service.json | 168 - .../healthcare-org-tag-policy.json | 56 - .../lza-sample-config-tse-se/README.md | 5 - .../README.md | 242 - .../accounts-config.yaml | 37 - .../images/EGA-StateIT-LZA-EGA-TGW.png | Bin 191005 -> 0 bytes .../images/EGA-StateIT-LZA-OU-EGA.png | Bin 57388 -> 0 bytes .../LZAforStateCentralIT_2022-10-18.png | Bin 1513457 -> 0 bytes .../network-config.yaml | 719 - .../organization-config.yaml | 75 - .../service-control-policies/READEME.md | 147 - .../guardrails-3.json | 48 - .../scp-hlc-hipaa-service.json | 169 - .../healthcare-org-tag-policy.json | 56 - .../lza-sample-config/README.md | 3 - .../lza-sample-config/accounts-config.yaml | 28 - .../AD-connector-permissions-setup.ps1 | 19 - .../AD-group-grant-permissions-setup.ps1 | 16 - .../ad-config-scripts/AD-group-setup.ps1 | 32 - .../ad-config-scripts/AD-user-group-setup.ps1 | 31 - .../ad-config-scripts/AD-user-setup.ps1 | 45 - .../ad-config-scripts/AWSQuickStart.psm1 | 345 - .../Configure-password-policy.ps1 | 51 - .../ad-config-scripts/Join-Domain.ps1 | 29 - .../backup-policies/backup-plan.json | 156 - .../bucket-policies/central-log-bucket.json | 22 - .../domain-list-1.txt | 1 - .../domain-list-2.txt | 1 - .../dynamic-partitioning/log-filters.json | 3 - .../firewall-rules/rules.txt | 16 - .../lza-sample-config/global-config.yaml | 183 - .../lza-sample-config/iam-config.yaml | 39 - .../lza-sample-config/network-config.yaml | 341 - .../organization-config.yaml | 58 - .../lza-sample-config/security-config.yaml | 492 - .../guardrails-1.json | 134 - .../guardrails-2.json | 151 - .../service-control-policies/quarantine.json | 21 - .../tagging-policies/org-tag-policy.json | 15 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - solution-manifest.yaml | 9 - source/.eslintrc.json | 25 - source/.husky/pre-commit | 15 - source/.prettierrc.json | 7 - source/lerna.json | 24 - source/log-scanner.sh | 14 - .../docs/developer-guide/dependencies.md | 44 - source/mkdocs/docs/developer-guide/design.md | 51 - .../docs/developer-guide/doc-guidelines.md | 225 - .../mkdocs/docs/developer-guide/features.md | 118 - source/mkdocs/docs/developer-guide/index.md | 13 - source/mkdocs/docs/developer-guide/scripts.md | 74 - source/mkdocs/docs/faq/architecture.md | 19 - source/mkdocs/docs/faq/ct-cfct.md | 183 - source/mkdocs/docs/faq/customizations.md | 9 - source/mkdocs/docs/faq/general.md | 33 - source/mkdocs/docs/faq/index.md | 19 - source/mkdocs/docs/faq/logging/cwl.md | 24 - .../docs/faq/networking/direct-connect.md | 51 - source/mkdocs/docs/faq/networking/dpi.md | 19 - source/mkdocs/docs/faq/networking/general.md | 57 - source/mkdocs/docs/faq/networking/gwlb.md | 25 - .../docs/faq/networking/network-firewall.md | 24 - source/mkdocs/docs/faq/operations.md | 71 - source/mkdocs/docs/faq/security.md | 5 - source/mkdocs/docs/index.md | 45 - source/mkdocs/docs/installation.md | 87 - .../govcloud-us/considerations.md | 15 - .../govcloud-us/images/image1.png | Bin 467537 -> 0 bytes .../govcloud-us/images/image2.png | Bin 158478 -> 0 bytes .../govcloud-us/index.md | 13 - .../govcloud-us/networking.md | 10 - .../govcloud-us/org-structure.md | 15 - .../govcloud-us/overview.md | 10 - .../govcloud-us/security-controls.md | 37 - .../docs/sample-configurations/index.md | 10 - .../standard/authn-authz.md | 74 - .../standard/images/cloudwatch_logs.jpg | Bin 321695 -> 0 bytes .../standard/images/default_ou_structure.jpg | Bin 9774 -> 0 bytes .../images/lza-centralized-logging.png | Bin 294390 -> 0 bytes .../standard/images/mandatory_accounts.jpg | Bin 154288 -> 0 bytes .../standard/images/organization_legend.jpg | Bin 24970 -> 0 bytes .../images/organization_structure.png | Bin 70556 -> 0 bytes .../standard/images/scp_inheritance.jpg | Bin 57112 -> 0 bytes .../standard/images/standard_network.jpg | Bin 320395 -> 0 bytes .../sample-configurations/standard/index.md | 13 - .../standard/logging-monitoring.md | 56 - .../standard/networking.md | 31 - .../standard/org-structure.md | 68 - .../standard/overview.md | 68 - source/mkdocs/docs/typedocs/index.md | 6 - source/mkdocs/docs/user-guide/config.md | 128 - source/mkdocs/docs/user-guide/index.md | 12 - source/mkdocs/docs/user-guide/logging.md | 61 - .../docs/user-guide/securityhub-findings.md | 31 - source/mkdocs/mkdocs.yml | 144 - source/package.json | 97 - .../@aws-accelerator/accelerator/.npmignore | 2 - .../@aws-accelerator/accelerator/README.md | 1 - .../@aws-accelerator/accelerator/bin/app.ts | 265 - .../@aws-accelerator/accelerator/cdk.json | 7 - .../@aws-accelerator/accelerator/cdk.ts | 135 - .../@aws-accelerator/accelerator/index.ts | 33 - .../accelerator/jest.config.js | 78 - .../accelerator/jest/setEnvVars.js | 1 - .../accelerator/lib/accelerator-aspects.ts | 330 - .../lib/accelerator-resource-names.ts | 329 - .../accelerator/lib/accelerator-stage.ts | 52 - .../accelerator/lib/accelerator.ts | 1500 -- .../application-load-balancers.ts | 79 - .../lib/asea-resources/firewall-resources.ts | 80 - .../lib/asea-resources/iam-groups.ts | 125 - .../lib/asea-resources/iam-roles.ts | 246 - .../lib/asea-resources/iam-users.ts | 121 - .../asea-resources/managed-ad-resources.ts | 69 - .../lib/asea-resources/managed-policies.ts | 144 - .../lib/asea-resources/resource.ts | 121 - .../route-53-query-logging-association.ts | 81 - .../asea-resources/route-53-query-logging.ts | 90 - .../route-53-resolver-endpoint.ts | 100 - .../asea-resources/shared-security-groups.ts | 68 - .../lib/asea-resources/ssm-inventory.ts | 75 - .../tgw-cross-account-resources.ts | 230 - .../asea-resources/transit-gateway-routes.ts | 331 - .../lib/asea-resources/transit-gateways.ts | 88 - .../lib/asea-resources/vpc-endpoints.ts | 191 - .../asea-resources/vpc-peering-connection.ts | 40 - .../lib/asea-resources/vpc-resources.ts | 1384 -- .../accelerator/lib/config-repository.ts | 144 - .../accelerator/lib/config-validator.ts | 376 - .../accelerator/lib/detach-quarantine-scp.ts | 101 - .../lambdas/attach-quarantine-scp/index.ts | 116 - .../attach-quarantine-scp/package.json | 44 - .../attach-quarantine-scp/tsconfig.json | 8 - .../index.ts | 62 - .../package.json | 44 - .../tsconfig.json | 8 - .../control-tower-notifications/index.ts | 75 - .../control-tower-notifications/package.json | 44 - .../control-tower-notifications/tsconfig.json | 8 - .../lambdas/control-tower-ou-events/index.ts | 153 - .../control-tower-ou-events/package.json | 48 - .../control-tower-ou-events/tsconfig.json | 8 - .../lambdas/detach-quarantine-scp/index.ts | 95 - .../detach-quarantine-scp/package.json | 44 - .../detach-quarantine-scp/tsconfig.json | 8 - .../lib/lambdas/diagnostic-pack/index.ts | 288 - .../lib/lambdas/diagnostic-pack/package.json | 52 - .../diagnostic-pack/resources/functions.ts | 705 - .../diagnostic-pack/resources/types.ts | 43 - .../lib/lambdas/diagnostic-pack/tsconfig.json | 8 - .../lib/lambdas/load-config-table/index.ts | 389 - .../lambdas/load-config-table/package.json | 51 - .../lambdas/load-config-table/tsconfig.json | 9 - .../lib/lambdas/sns-topic-forwarder/index.ts | 52 - .../lambdas/sns-topic-forwarder/package.json | 44 - .../lambdas/sns-topic-forwarder/tsconfig.json | 8 - .../lib/lambdas/validate-environment/index.ts | 931 - .../lambdas/validate-environment/package.json | 50 - .../validate-environment/tsconfig.json | 8 - .../accelerator/lib/load-config-table.ts | 172 - .../accelerator/lib/pipeline.ts | 927 - .../accelerator/lib/prerequisites.ts | 167 - .../lib/resources/kms-key-resource.ts | 124 - .../accelerator/lib/resources/scp-resource.ts | 400 - .../lib/stacks/accelerator-stack.ts | 1912 -- .../accelerator/lib/stacks/accounts-stack.ts | 164 - .../lib/stacks/applications-stack.ts | 756 - .../accelerator/lib/stacks/bootstrap-stack.ts | 687 - .../accelerator/lib/stacks/custom-stack.ts | 281 - .../lib/stacks/customizations-stack.ts | 602 - .../dependencies-stack/dependencies-stack.ts | 107 - .../dependencies-stack/diagnostics-pack.ts | 94 - .../dependencies-stack/identity-center.ts | 76 - .../lib/stacks/diagnostics-pack-stack.ts | 268 - .../accelerator/lib/stacks/finalize-stack.ts | 73 - .../lib/stacks/identity-center-stack.ts | 346 - .../lib/stacks/import-asea-resources-stack.ts | 405 - .../accelerator/lib/stacks/key-stack.ts | 283 - .../accelerator/lib/stacks/logging-stack.ts | 3050 --- .../firewall-vpn-resources.ts | 1196 - .../network-associations-gwlb-stack.ts | 1242 - .../network-associations-stack.ts | 3822 --- .../shared-resources.ts | 254 - .../central-network-resources.ts | 47 - .../network-prep-stack/dx-resources.ts | 314 - .../network-prep-stack/fms-resources.ts | 92 - .../network-prep-stack/ipam-resources.ts | 329 - .../network-prep-stack/mad-resources.ts | 90 - .../network-prep-stack/network-prep-stack.ts | 72 - .../network-prep-stack/nfw-resources.ts | 282 - .../network-prep-stack/resolver-resources.ts | 346 - .../network-prep-stack/tgw-resources.ts | 331 - .../network-prep-stack/vpn-resources.ts | 421 - .../stacks/network-stacks/network-stack.ts | 1325 - .../network-vpc-dns-stack.ts | 463 - .../network-vpc-endpoints-stack.ts | 1032 - .../network-vpc-stack/acm-resources.ts | 132 - .../network-vpc-stack/dhcp-resources.ts | 64 - .../network-vpc-stack/ipam-resources.ts | 68 - .../load-balancer-resources.ts | 483 - .../network-vpc-stack/nacl-resources.ts | 255 - .../network-vpc-stack/nat-gw-resources.ts | 90 - .../network-vpc-stack/network-vpc-stack.ts | 338 - .../prefix-list-resources.ts | 205 - .../route-entry-resources.ts | 324 - .../route-table-resources.ts | 174 - .../security-group-resources.ts | 32 - .../network-vpc-stack/subnet-resources.ts | 362 - .../network-vpc-stack/tgw-resources.ts | 452 - .../network-vpc-stack/vpc-resources.ts | 921 - .../network-stacks/utils/getter-utils.ts | 379 - .../utils/security-group-utils.ts | 847 - .../network-stacks/utils/setter-utils.ts | 51 - .../network-stacks/utils/validation-utils.ts | 133 - .../lib/stacks/operations-stack.ts | 1415 -- .../lib/stacks/organizations-stack.ts | 818 - .../accelerator/lib/stacks/pipeline-stack.ts | 193 - .../accelerator/lib/stacks/prepare-stack.ts | 890 - .../resource-policy-enforcement-stack.ts | 396 - .../lib/stacks/security-audit-stack.ts | 656 - .../lib/stacks/security-resources-stack.ts | 1538 -- .../accelerator/lib/stacks/security-stack.ts | 469 - .../lib/stacks/tester-pipeline-stack.ts | 93 - .../accelerator/lib/tester-pipeline.ts | 274 - .../accelerator/lib/toolkit.ts | 912 - .../lib/validate-environment-config.ts | 205 - .../@aws-accelerator/accelerator/package.json | 86 - .../@aws-accelerator/accelerator/test.ts | 0 .../__snapshots__/accounts-stack.test.ts.snap | 2001 -- .../applications-stack.test.ts.snap | 459 - .../bootstrap-stack.test.ts.snap | 1224 - .../customizations-stack.test.ts.snap | 1133 - .../dependencies-stack.test.ts.snap | 220 - .../diagnostics-pack-stack.test.ts.snap | 511 - .../__snapshots__/finalize-stack.test.ts.snap | 990 - .../identity-center-stack.test.ts.snap | 714 - .../test/__snapshots__/key-stack.test.ts.snap | 421 - .../__snapshots__/logging-stack.test.ts.snap | 13010 ---------- ...twork-associations-gwlb-stack.test.ts.snap | 2940 --- .../network-associations-stack.test.ts.snap | 5430 ---- .../network-prep-stack.test.ts.snap | 2983 --- .../network-vpc-dns-stack.test.ts.snap | 1339 - .../network-vpc-endpoints-stack.test.ts.snap | 3076 --- .../network-vpc-stack.test.ts.snap | 5797 ----- .../operations-stack.test.ts.snap | 1748 -- .../organizations-stack.test.ts.snap | 9930 -------- .../__snapshots__/pipeline-stack.test.ts.snap | 2598 -- .../__snapshots__/prepare-stack.test.ts.snap | 4867 ---- ...urce-policy-enforcement-stack.test.ts.snap | 893 - .../security-audit-stack.test.ts.snap | 5101 ---- .../security-resources-stack.test.ts.snap | 7982 ------ .../__snapshots__/security-stack.test.ts.snap | 2363 -- .../tester-pipeline-stack.test.ts.snap | 1180 - .../test/accelerator-synth-stacks.ts | 816 - .../accelerator/test/accelerator.test.ts | 97 - .../accelerator/test/accounts-stack.test.ts | 32 - .../accelerator/test/app-utils.test.ts | 285 - .../test/applications-stack.test.ts | 26 - .../accelerator/test/bootstrap-stack.test.ts | 26 - .../test/config-repository.test.ts | 49 - .../accounts-config.yaml | 39 - .../AD-connector-permissions-setup.ps1 | 19 - .../AD-group-grant-permissions-setup.ps1 | 16 - .../ad-config-scripts/AD-group-setup.ps1 | 32 - .../ad-config-scripts/AD-user-group-setup.ps1 | 31 - .../ad-config-scripts/AD-user-setup.ps1 | 45 - .../ad-config-scripts/AWSQuickStart.psm1 | 345 - .../Configure-password-policy.ps1 | 51 - .../ad-config-scripts/Join-Domain.ps1 | 29 - .../appA/launchTemplate/userData.sh | 10 - .../backup-policies/org-backup-policies.json | 267 - .../custom-s3-bucket.yaml | 37 - ...h-ec2-instance-profile-detection-role.json | 16 - ...ec2-instance-profile-remediation-role.json | 14 - .../attach-ec2-instance-profile.zip | Bin 987 -> 0 bytes .../bucket-sse-enabled-remediation-role.json | 15 - ...ce-profile-permissions-detection-role.json | 13 - ...-profile-permissions-remediation-role.json | 13 - .../ec2-instance-profile-permissions.zip | Bin 1292 -> 0 bytes .../elb-logging-enabled-remediation-role.json | 14 - .../customizations-config.yaml | 334 - .../domain-list-1.txt | 2 - .../dynamic-partitioning/log-filters.json | 3 - .../firewall-rules/rules.txt | 16 - .../global-config.yaml | 120 - .../iam-config.yaml | 194 - .../iam-policies/boundary-policy.json | 35 - .../kms/kms-policy-01.json | 13 - .../network-config.yaml | 1088 - .../organization-config.yaml | 61 - .../security-config.yaml | 678 - .../guardrails-1.json | 91 - .../guardrails-2.json | 144 - .../service-control-policies/quarantine.json | 21 - .../attach-iam-instance-profile.yaml | 19 - .../ssm-documents/attach-iam-role-policy.yaml | 50 - .../ssm-documents/s3-encryption.yaml | 28 - .../ssm-documents/ssm-elb-enable-logging.yaml | 44 - .../tagging-policies/org-tag-policy.json | 15 - .../test-configuration/config.yaml | 22 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../accounts-config.yaml | 33 - .../appA/launchTemplate/userData.sh | 10 - .../backup-policies/org-backup-policies.json | 267 - .../custom-s3-bucket.yaml | 37 - ...h-ec2-instance-profile-detection-role.json | 16 - ...ec2-instance-profile-remediation-role.json | 14 - .../attach-ec2-instance-profile.zip | Bin 987 -> 0 bytes .../bucket-sse-enabled-remediation-role.json | 15 - ...ce-profile-permissions-detection-role.json | 13 - ...-profile-permissions-remediation-role.json | 13 - .../ec2-instance-profile-permissions.zip | Bin 1292 -> 0 bytes .../elb-logging-enabled-remediation-role.json | 14 - .../customizations-config.yaml | 334 - .../domain-list-1.txt | 2 - .../dynamic-partitioning/log-filters.json | 3 - .../firewall-rules/rules.txt | 16 - .../all-enabled-ou-targets/global-config.yaml | 113 - .../all-enabled-ou-targets/iam-config.yaml | 51 - .../iam-policies/boundary-policy.json | 35 - .../kms/kms-policy-01.json | 13 - .../network-config.yaml | 1075 - .../organization-config.yaml | 63 - .../security-config.yaml | 678 - .../guardrails-1.json | 91 - .../guardrails-2.json | 144 - .../service-control-policies/quarantine.json | 21 - .../attach-iam-instance-profile.yaml | 19 - .../ssm-documents/attach-iam-role-policy.yaml | 50 - .../ssm-documents/s3-encryption.yaml | 28 - .../ssm-documents/ssm-elb-enable-logging.yaml | 44 - .../tagging-policies/org-tag-policy.json | 15 - .../test-configuration/config.yaml | 22 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../configs/all-enabled/accounts-config.yaml | 40 - .../AD-connector-permissions-setup.ps1 | 19 - .../AD-group-grant-permissions-setup.ps1 | 16 - .../ad-config-scripts/AD-group-setup.ps1 | 32 - .../ad-config-scripts/AD-user-group-setup.ps1 | 31 - .../ad-config-scripts/AD-user-setup.ps1 | 45 - .../ad-config-scripts/AWSQuickStart.psm1 | 345 - .../Configure-password-policy.ps1 | 51 - .../ad-config-scripts/Join-Domain.ps1 | 29 - .../appA/launchTemplate/userData.sh | 10 - .../backup-policies/org-backup-policies.json | 267 - .../infrastructure-vault-policy.json | 17 - .../bucket-policies/access-logs-bucket.json | 22 - .../bucket-policies/assets-bucket.json | 22 - .../bucket-policies/central-log-bucket.json | 41 - .../bucket-policies/elb-logs-bucket.json | 24 - .../full-access-log-bucket.json | 36 - .../full-central-log-bucket.json | 199 - .../bucket-policies/full-elb-log-bucket.json | 68 - .../custom-s3-bucket.yaml | 55 - ...h-ec2-instance-profile-detection-role.json | 16 - ...ec2-instance-profile-remediation-role.json | 14 - .../attach-ec2-instance-profile.zip | Bin 987 -> 0 bytes .../bucket-sse-enabled-remediation-role.json | 15 - ...ce-profile-permissions-detection-role.json | 13 - ...-profile-permissions-remediation-role.json | 13 - .../ec2-instance-profile-permissions.zip | Bin 1292 -> 0 bytes .../elb-logging-enabled-remediation-role.json | 14 - .../enable-s3-encryption.json | 12 - .../targetDocumentLambda.zip | Bin 274 -> 0 bytes .../waf-logging-enabled-detection-role.json | 22 - .../waf-logging-enabled-remediation-role.json | 33 - .../waf-logging-enabled.zip | Bin 916 -> 0 bytes .../all-enabled/customizations-config.yaml | 898 - .../domain-list-1.txt | 2 - .../domain-list-2.txt | 2 - .../dynamic-partitioning/log-filters.json | 3 - .../all-enabled/firewall-rules/rules.txt | 16 - .../configs/all-enabled/global-config.yaml | 360 - .../test/configs/all-enabled/iam-config.yaml | 247 - ...ceConfigurationCollectorPolicy-policy.json | 48 - .../iam-policies/boundary-policy.json | 35 - .../sso-permissionSet1-inline-policy.json | 15 - .../all-enabled/kms/applicationEbs.json | 39 - .../kms/central-logs-bucket-key-policy.json | 20 - .../all-enabled/kms/elb-logs-bucket.json | 24 - .../full-central-logs-bucket-key-policy.json | 156 - .../all-enabled/kms/kms-policy-01.json | 13 - .../configs/all-enabled/network-config.yaml | 2270 -- .../all-enabled/organization-config.yaml | 100 - .../all-enabled/replacements-config.yaml | 16 - .../resource-policies/apigateway.json | 47 - .../resource-policies/backup-vault.json | 69 - .../codeartifact-repository.json | 47 - .../all-enabled/resource-policies/ecr.json | 45 - .../resource-policies/efs-file-system.json | 47 - .../eventbridge-eventbus.json | 47 - .../resource-policies/glue-catalog.json | 47 - .../all-enabled/resource-policies/iam.json | 49 - .../all-enabled/resource-policies/kms.json | 47 - .../resource-policies/lex-bot.json | 47 - .../resource-policies/opensearch.json | 47 - .../all-enabled/resource-policies/s3.json | 43 - .../resource-policies/secrets-manager.json | 41 - .../all-enabled/resource-policies/sns.json | 65 - .../all-enabled/resource-policies/sqs.json | 47 - .../configs/all-enabled/security-config.yaml | 1047 - .../allow-ec2-only.json | 11 - .../guardrails-1.json | 91 - .../guardrails-2.json | 129 - .../service-control-policies/quarantine.json | 21 - .../attach-iam-instance-profile.yaml | 19 - .../ssm-documents/attach-iam-role-policy.yaml | 50 - .../ssm-documents/s3-encryption.yaml | 28 - .../ssm-documents/ssm-elb-enable-logging.yaml | 44 - .../ssm-documents/waf-enable-logging.yaml | 120 - .../tagging-policies/org-tag-policy.json | 15 - .../test-configuration/config.yaml | 22 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../no-org-config/accounts-config.yaml | 39 - .../configs/no-org-config/global-config.yaml | 16 - .../configs/no-org-config/iam-config.yaml | 5 - .../configs/no-org-config/network-config.yaml | 45 - .../no-org-config/organization-config.yaml | 5 - .../no-org-config/security-config.yaml | 51 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../snapshot-only/accounts-config.yaml | 40 - .../AD-connector-permissions-setup.ps1 | 19 - .../AD-group-grant-permissions-setup.ps1 | 16 - .../ad-config-scripts/AD-group-setup.ps1 | 32 - .../ad-config-scripts/AD-user-group-setup.ps1 | 31 - .../ad-config-scripts/AD-user-setup.ps1 | 45 - .../ad-config-scripts/AWSQuickStart.psm1 | 345 - .../Configure-password-policy.ps1 | 51 - .../ad-config-scripts/Join-Domain.ps1 | 29 - .../appA/launchTemplate/userData.sh | 10 - .../backup-policies/org-backup-policies.json | 267 - .../infrastructure-vault-policy.json | 17 - .../bucket-policies/access-logs-bucket.json | 22 - .../bucket-policies/central-log-bucket.json | 41 - .../bucket-policies/elb-logs-bucket.json | 24 - .../full-access-log-bucket.json | 36 - .../full-central-log-bucket.json | 199 - .../bucket-policies/full-elb-log-bucket.json | 68 - .../snapshot-only/certificates/certA/cert.crt | 0 .../certificates/certA/privKey.key | 0 .../custom-s3-bucket.yaml | 56 - ...h-ec2-instance-profile-detection-role.json | 16 - ...ec2-instance-profile-remediation-role.json | 14 - .../attach-ec2-instance-profile.zip | Bin 987 -> 0 bytes .../bucket-sse-enabled-remediation-role.json | 15 - ...ce-profile-permissions-detection-role.json | 13 - ...-profile-permissions-remediation-role.json | 13 - .../ec2-instance-profile-permissions.zip | Bin 1292 -> 0 bytes .../elb-logging-enabled-remediation-role.json | 14 - .../enable-s3-encryption.json | 12 - .../targetDocumentLambda.zip | Bin 274 -> 0 bytes .../waf-logging-enabled-detection-role.json | 22 - .../waf-logging-enabled-remediation-role.json | 33 - .../waf-logging-enabled.zip | Bin 916 -> 0 bytes .../snapshot-only/customizations-config.yaml | 791 - .../domain-list-1.txt | 2 - .../domain-list-2.txt | 2 - .../dynamic-partitioning/log-filters.json | 3 - .../snapshot-only/firewall-rules/rules.txt | 16 - .../configs/snapshot-only/global-config.yaml | 341 - .../configs/snapshot-only/iam-config.yaml | 264 - ...ceConfigurationCollectorPolicy-policy.json | 48 - .../iam-policies/boundary-policy.json | 35 - .../sso-permissionSet1-inline-policy.json | 15 - .../kms/central-logs-bucket-key-policy.json | 36 - .../full-central-logs-bucket-key-policy.json | 156 - .../snapshot-only/kms/kms-policy-01.json | 13 - .../configs/snapshot-only/network-config.yaml | 2274 -- .../snapshot-only/organization-config.yaml | 95 - .../snapshot-only/replacements-config.yaml | 19 - .../resource-policies/apigateway.json | 47 - .../resource-policies/backup-vault.json | 69 - .../codeartifact-repository.json | 47 - .../snapshot-only/resource-policies/ecr.json | 45 - .../resource-policies/efs-file-system.json | 47 - .../eventbridge-eventbus.json | 47 - .../resource-policies/glue-catalog.json | 47 - .../snapshot-only/resource-policies/iam.json | 49 - .../snapshot-only/resource-policies/kms.json | 47 - .../resource-policies/lex-bot.json | 47 - .../resource-policies/opensearch.json | 47 - .../snapshot-only/resource-policies/s3.json | 49 - .../resource-policies/secrets-manager.json | 41 - .../snapshot-only/resource-policies/sns.json | 65 - .../snapshot-only/resource-policies/sqs.json | 47 - .../snapshot-only/security-config.yaml | 1035 - .../allow-ec2-only.json | 11 - .../data-perimeter.json | 25 - .../guardrails-1.json | 91 - .../guardrails-2.json | 129 - .../service-control-policies/quarantine.json | 21 - .../attach-iam-instance-profile.yaml | 19 - .../ssm-documents/attach-iam-role-policy.yaml | 50 - .../ssm-documents/s3-encryption.yaml | 28 - .../ssm-documents/ssm-elb-enable-logging.yaml | 44 - .../ssm-documents/waf-enable-logging.yaml | 120 - .../tagging-policies/org-tag-policy.json | 15 - .../test-configuration/config.yaml | 22 - .../vpc-endpoint-policies/default.json | 32 - .../vpc-endpoint-policies/ec2.json | 10 - .../test/customizations-stack.test.ts | 30 - .../test/dependencies-stack.test.ts | 26 - .../test/diagnostics-pack-stack.test.ts | 41 - .../accelerator/test/finalize-stack.test.ts | 26 - .../test/identity-center-stack.test.ts | 29 - .../test/integ-test-aspects/integ.aspects.ts | 117 - .../accelerator/test/key-stack.test.ts | 26 - .../accelerator/test/logging-stack.test.ts | 45 - .../network-associations-gwlb-stack.test.ts | 31 - .../test/network-associations-stack.test.ts | 37 - .../test/network-prep-stack.test.ts | 26 - .../test/network-vpc-dns-stack.test.ts | 29 - .../test/network-vpc-endpoints-stack.test.ts | 32 - .../test/network-vpc-stack.test.ts | 41 - .../accelerator/test/operations-stack.test.ts | 29 - .../test/organizations-stack.test.ts | 93 - .../accelerator/test/pipeline-stack.test.ts | 66 - .../accelerator/test/prepare-stack.test.ts | 26 - .../accelerator/test/prerequisites.test.ts | 174 - .../resource-policy-enforcement-stack.test.ts | 33 - .../test/security-audit-stack.test.ts | 38 - .../test/security-resources-stack.test.ts | 41 - .../accelerator/test/security-stack.test.ts | 41 - .../accelerator/test/snapshot-test.ts | 63 - .../test/tester-pipeline-stack.test.ts | 43 - .../accelerator/tsconfig.json | 8 - .../accelerator/utils/app-utils.ts | 1104 - .../utils/import-stack-resources.ts | 185 - .../accelerator/utils/stack-utils.ts | 1403 -- .../packages/@aws-accelerator/config/index.ts | 36 - .../@aws-accelerator/config/jest.config.js | 76 - .../config/lib/accounts-config.ts | 380 - .../config/lib/common/index.ts | 15 - .../config/lib/common/parse.ts | 235 - .../config/lib/common/types.ts | 803 - .../config/lib/customizations-config.ts | 386 - .../config/lib/global-config.ts | 987 - .../@aws-accelerator/config/lib/iam-config.ts | 356 - .../config/lib/models/accounts-config.ts | 117 - .../lib/models/customizations-config.ts | 2608 -- .../config/lib/models/global-config.ts | 2371 -- .../config/lib/models/iam-config.ts | 1510 -- .../config/lib/models/network-config.ts | 7312 ------ .../config/lib/models/organization-config.ts | 337 - .../config/lib/models/replacements-config.ts | 110 - .../config/lib/models/security-config.ts | 2336 -- .../config/lib/network-config.ts | 952 - .../config/lib/organization-config.ts | 255 - .../config/lib/replacements-config.ts | 189 - .../config/lib/schemas/accounts-config.json | 156 - .../lib/schemas/customizations-config.json | 1703 -- .../config/lib/schemas/global-config.json | 1618 -- .../config/lib/schemas/iam-config.json | 937 - .../config/lib/schemas/network-config.json | 5187 ---- .../lib/schemas/organization-config.json | 262 - .../lib/schemas/replacements-config.json | 96 - .../config/lib/schemas/security-config.json | 1682 -- .../config/lib/security-config.ts | 427 - .../@aws-accelerator/config/package.json | 64 - .../config/test/accounts-config.test.ts | 215 - .../config/test/common-types.test.ts | 44 - .../config/test/customizations-config.test.ts | 114 - .../config/test/global-config.test.ts | 149 - .../config/test/iam-config.test.ts | 96 - .../config/test/network-config.test.ts | 192 - .../config/test/organization-config.test.ts | 53 - .../config/test/replacements-config.test.ts | 135 - .../config/test/security-config.test.ts | 114 - .../duplicate-emails/duplicate-emails.test.ts | 22 - .../no-org-config/accounts-config.yaml | 43 - .../no-org-config/global-config.yaml | 16 - .../no-org-config/iam-config.yaml | 5 - .../no-org-config/network-config.yaml | 45 - .../no-org-config/organization-config.yaml | 5 - .../no-org-config/security-config.yaml | 51 - .../vpc-endpoint-policies/default.json | 10 - .../vpc-endpoint-policies/ec2.json | 10 - .../@aws-accelerator/config/tsconfig.json | 8 - .../validator/accounts-config-validator.ts | 198 - .../common/common-validator-functions.ts | 196 - .../customizations-config-validator.ts | 1670 -- .../validator/global-config-validator.ts | 1282 - .../config/validator/iam-config-validator.ts | 1213 - .../central-network-validator.ts | 59 - .../certificates-validator.ts | 66 - .../customer-gateways-validator.ts | 512 - .../dhcp-options-validator.ts | 192 - .../direct-connect-gateways-validator.ts | 128 - .../endpoint-policies-validator.ts | 92 - .../firewall-manager-validator.ts | 52 - .../gateway-load-balancers-validator.ts | 318 - .../ipam-validator.ts | 298 - .../network-config-validator.ts | 135 - .../network-firewall-validator.ts | 1440 -- .../network-validator-functions.ts | 293 - .../prefix-list-validator.ts | 121 - .../route53-resolver-validator.ts | 920 - .../transit-gateway-validator.ts | 700 - .../network-config-validator/vpc-validator.ts | 3170 --- .../organization-config-validator.ts | 143 - .../replacements-config-validator.ts | 187 - .../validator/security-config-validator.ts | 1422 -- .../utils/common-validator-functions.ts | 21 - .../@aws-accelerator/constructs/.gitignore | 9 - .../@aws-accelerator/constructs/.npmignore | 6 - .../@aws-accelerator/constructs/README.md | 1 - .../@aws-accelerator/constructs/index.ts | 153 - .../constructs/jest.config.js | 76 - .../get-accelerator-metadata.ts | 246 - .../get-accelerator-metadata/index.ts | 472 - .../get-accelerator-metadata/package.json | 42 - .../get-accelerator-metadata/tsconfig.json | 8 - ...auditmanager-organization-admin-account.ts | 145 - .../auditmanager-reports-destination.ts | 104 - .../create-reports-destination/index.ts | 64 - .../create-reports-destination/package.json | 42 - .../create-reports-destination/tsconfig.json | 8 - .../index.ts | 103 - .../package.json | 43 - .../tsconfig.json | 9 - .../create-autoscaling-group.ts | 92 - .../lib/aws-budgets/budget-definition.ts | 253 - .../aws-budgets/cross-region-budget/index.ts | 226 - .../cross-region-budget/package.json | 45 - .../cross-region-budget/tsconfig.json | 8 - .../aws-certificate-manager/certificate.ts | 133 - .../create-certificates/index.ts | 274 - .../create-certificates/package.json | 43 - .../create-certificates/tsconfig.json | 8 - .../aws-cloudformation/get-resource-type.ts | 116 - .../get-resource-type/index.ts | 78 - .../get-resource-type/package.json | 44 - .../get-resource-type/tsconfig.json | 8 - .../cloudwatch-destination.ts | 152 - .../cloudwatch-log-data-protection.ts | 101 - .../cloudwatch-log-group.ts | 180 - .../cloudwatch-logs-subscription-filter.ts | 159 - .../create-log-groups/index.ts | 285 - .../create-log-groups/jest.config.js | 76 - .../create-log-groups/package.json | 50 - .../create-log-groups/test/index.test.ts | 454 - .../create-log-groups/test/static-input.ts | 110 - .../put-account-policy/index.ts | 179 - .../put-account-policy/jest.config.js | 76 - .../put-account-policy/package.json | 49 - .../put-account-policy/test/index.test.ts | 296 - .../put-account-policy/test/static-input.ts | 134 - .../update-subscription-filter/index.ts | 368 - .../update-subscription-filter/package.json | 39 - .../update-subscription-filter/tsconfig.json | 8 - .../aws-configservice/config-aggregation.ts | 42 - .../lib/aws-configservice/config-recorder.ts | 146 - .../config-recorder/index.ts | 289 - .../config-recorder/package.json | 45 - .../test/config-recorder.test.ts | 442 - .../config-recorder/tsconfig.json | 8 - .../lib/aws-configservice/config-tags.ts | 87 - .../aws-configservice/update-tags/index.ts | 111 - .../update-tags/package.json | 45 - .../update-tags/tsconfig.json | 8 - .../create-accounts-status/index.ts | 302 - .../create-accounts-status/jest.config.js | 76 - .../create-accounts-status/package.json | 51 - .../create-accounts-status/test/index.test.ts | 423 - .../test/static-input.ts | 34 - .../create-accounts-status/tsconfig.json | 8 - .../lib/aws-controltower/create-accounts.ts | 180 - .../aws-controltower/create-accounts/index.ts | 41 - .../create-accounts/package.json | 42 - .../create-accounts/tsconfig.json | 8 - .../cross-region-report-definition/index.ts | 114 - .../package.json | 44 - .../tsconfig.json | 8 - .../lib/aws-cur/report-definition.ts | 252 - .../lib/aws-detective/create-members/index.ts | 116 - .../aws-detective/create-members/package.json | 43 - .../create-members/tsconfig.json | 8 - .../aws-detective/detective-graph-config.ts | 112 - .../lib/aws-detective/detective-members.ts | 104 - .../detective-organization-admin-account.ts | 128 - .../index.ts | 113 - .../package.json | 44 - .../tsconfig.json | 8 - .../update-graph-config/index.ts | 68 - .../update-graph-config/package.json | 44 - .../update-graph-config/tsconfig.json | 8 - .../direct-connect-gateway.ts | 101 - .../direct-connect-gateway/index.ts | 91 - .../direct-connect-gateway/package.json | 43 - .../direct-connect-gateway/tsconfig.json | 8 - .../gateway-association-proposal/index.ts | 81 - .../gateway-association-proposal/package.json | 43 - .../tsconfig.json | 8 - .../aws-directconnect/gateway-association.ts | 147 - .../gateway-association/index.ts | 312 - .../gateway-association/package.json | 43 - .../gateway-association/tsconfig.json | 8 - .../attributes.ts | 105 - .../virtual-interface-allocation/index.ts | 289 - .../virtual-interface-allocation/package.json | 43 - .../tsconfig.json | 8 - .../aws-directconnect/virtual-interface.ts | 209 - .../virtual-interface/attributes.ts | 104 - .../virtual-interface/index.ts | 494 - .../virtual-interface/package.json | 43 - .../virtual-interface/tsconfig.json | 8 - .../active-directory-configuration.ts | 428 - .../active-directory-log-subscription.ts | 121 - .../active-directory-resolver-rule.ts | 106 - .../aws-directory-service/active-directory.ts | 109 - .../create-log-subscription/index.ts | 107 - .../create-log-subscription/package.json | 43 - .../create-log-subscription/tsconfig.json | 8 - .../share-active-directory.ts | 114 - .../share-directory/index.ts | 302 - .../share-directory/package.json | 43 - .../share-directory/tsconfig.json | 8 - .../update-resolver-role/index.ts | 179 - .../update-resolver-role/package.json | 43 - .../update-resolver-role/tsconfig.json | 8 - .../index.ts | 533 - .../package.json | 43 - .../tsconfig.json | 8 - .../aws-ec2/account-warming-status/index.ts | 159 - .../account-warming-status/package.json | 45 - .../account-warming-status/tsconfig.json | 8 - .../constructs/lib/aws-ec2/account-warming.ts | 252 - .../lib/aws-ec2/account-warming/index.ts | 296 - .../lib/aws-ec2/account-warming/package.json | 44 - .../lib/aws-ec2/account-warming/tsconfig.json | 8 - .../lib/aws-ec2/create-launch-template.ts | 161 - .../cross-account-customer-gateway/index.ts | 276 - .../package.json | 45 - .../tsconfig.json | 9 - .../lib/aws-ec2/cross-account-route.ts | 209 - .../lib/aws-ec2/cross-account-route/index.ts | 123 - .../aws-ec2/cross-account-route/package.json | 44 - .../aws-ec2/cross-account-route/tsconfig.json | 8 - .../index.ts | 251 - .../package.json | 45 - .../tsconfig.json | 9 - .../aws-ec2/custom-vpn-connection/index.ts | 575 - .../custom-vpn-connection/package.json | 47 - .../custom-vpn-connection/tsconfig.json | 9 - .../custom-vpn-connection/vpn-types.ts | 274 - .../lib/aws-ec2/customer-gateway.ts | 123 - .../index.ts | 175 - .../package.json | 43 - .../lib/aws-ec2/delete-default-vpc.ts | 94 - .../lib/aws-ec2/delete-default-vpc/index.ts | 241 - .../aws-ec2/delete-default-vpc/package.json | 43 - .../aws-ec2/delete-default-vpc/tsconfig.json | 8 - .../constructs/lib/aws-ec2/dhcp-options.ts | 90 - .../aws-ec2/ebs-default-encryption/index.ts | 62 - .../ebs-default-encryption/package.json | 43 - .../ebs-default-encryption/tsconfig.json | 8 - .../constructs/lib/aws-ec2/ebs-encryption.ts | 99 - .../enable-ipam-organization-admin/index.ts | 70 - .../package.json | 44 - .../tsconfig.json | 8 - .../constructs/lib/aws-ec2/firewall-asg.ts | 75 - .../aws-ec2/firewall-config-replacements.ts | 63 - .../firewall-config-replacements/index.ts | 323 - .../firewall-config-replacements/package.json | 47 - .../replacements.ts | 1620 -- .../tsconfig.json | 8 - .../lib/aws-ec2/firewall-instance.ts | 123 - .../constructs/lib/aws-ec2/firewall.ts | 243 - .../lib/aws-ec2/get-ipam-subnet-cidr/index.ts | 117 - .../aws-ec2/get-ipam-subnet-cidr/package.json | 44 - .../lib/aws-ec2/get-subnet-id/index.ts | 81 - .../lib/aws-ec2/get-subnet-id/package.json | 44 - .../lib/aws-ec2/get-subnet-id/tsconfig.json | 8 - .../get-transit-gateway-attachment/index.ts | 344 - .../package.json | 45 - .../tsconfig.json | 8 - .../lib/aws-ec2/get-vpc-id/index.ts | 76 - .../lib/aws-ec2/get-vpc-id/package.json | 44 - .../lib/aws-ec2/get-vpc-id/tsconfig.json | 8 - .../ipam-organization-admin-account.ts | 111 - .../constructs/lib/aws-ec2/ipam-pool.ts | 150 - .../constructs/lib/aws-ec2/ipam-scope.ts | 81 - .../constructs/lib/aws-ec2/ipam-subnet.ts | 248 - .../lib/aws-ec2/ipam-subnet/index.ts | 275 - .../lib/aws-ec2/ipam-subnet/package.json | 45 - .../lib/aws-ec2/ipam-subnet/tsconfig.json | 8 - .../constructs/lib/aws-ec2/ipam-subnet/vpc.ts | 450 - .../constructs/lib/aws-ec2/ipam.ts | 105 - .../lib/aws-ec2/prefix-list-route.ts | 147 - .../lib/aws-ec2/prefix-list-route/index.ts | 74 - .../aws-ec2/prefix-list-route/package.json | 44 - .../aws-ec2/prefix-list-route/tsconfig.json | 8 - .../constructs/lib/aws-ec2/prefix-list.ts | 76 - .../constructs/lib/aws-ec2/route-table.ts | 350 - .../lib/aws-ec2/subnet-id-lookup.ts | 96 - .../transit-gateway-association/index.ts | 237 - .../transit-gateway-association/package.json | 45 - .../transit-gateway-association/tsconfig.json | 8 - .../lib/aws-ec2/transit-gateway-connect.ts | 53 - .../lib/aws-ec2/transit-gateway-peering.ts | 193 - .../transit-gateway-prefix-list-reference.ts | 125 - .../index.ts | 212 - .../package.json | 45 - .../tsconfig.json | 8 - .../transit-gateway-propagation/index.ts | 236 - .../transit-gateway-propagation/package.json | 45 - .../transit-gateway-propagation/tsconfig.json | 8 - .../aws-ec2/transit-gateway-route-table.ts | 63 - .../aws-ec2/transit-gateway-static-route.ts | 88 - .../constructs/lib/aws-ec2/transit-gateway.ts | 530 - .../constructs/lib/aws-ec2/vpc-endpoint.ts | 161 - .../constructs/lib/aws-ec2/vpc-id-lookup.ts | 90 - .../constructs/lib/aws-ec2/vpc-peering.ts | 184 - .../constructs/lib/aws-ec2/vpc.ts | 1065 - .../constructs/lib/aws-ec2/vpn-connection.ts | 215 - .../application-load-balancer.ts | 282 - .../gateway-load-balancer.ts | 126 - .../network-load-balancer.ts | 189 - .../nlb-addresses.ts | 151 - .../nlb-ip-lookup/index.ts | 114 - .../nlb-ip-lookup/package.json | 43 - .../nlb-ip-lookup/tsconfig.json | 8 - .../target-group.ts | 245 - .../lib/aws-events/move-account-rule.ts | 157 - .../lib/aws-events/move-account/index.ts | 299 - .../lib/aws-events/move-account/package.json | 43 - .../lib/aws-events/move-account/tsconfig.json | 8 - .../new-cloudwatch-log-event-rule.ts | 169 - .../put-subscription-policy/index.ts | 193 - .../put-subscription-policy/package.json | 43 - .../put-subscription-policy/tsconfig.json | 8 - .../lib/aws-events/revert-scp-changes.ts | 219 - .../aws-events/revert-scp-changes/index.ts | 295 - .../revert-scp-changes/package.json | 44 - .../revert-scp-changes/tsconfig.json | 8 - .../security-hub-event-log/index.ts | 97 - .../security-hub-event-log/jest.config.js | 76 - .../security-hub-event-log/package.json | 49 - .../security-hub-event-log/test/index.test.ts | 131 - .../test/static-input.ts | 6 - .../security-hub-event-log/tsconfig.json | 8 - .../lib/aws-events/security-hub-events-log.ts | 168 - .../aws-firehose/cloudwatch-to-s3-firehose.ts | 418 - .../firehose-record-processing/index.ts | 308 - .../firehose-record-processing/package.json | 43 - .../firehose-record-processing/tsconfig.json | 8 - .../index.ts | 168 - .../package.json | 43 - .../tsconfig.json | 8 - .../lib/aws-fms/fms-notification-channel.ts | 55 - .../aws-fms/fms-organization-admin-account.ts | 153 - .../lib/aws-guardduty/create-members/index.ts | 302 - .../aws-guardduty/create-members/package.json | 44 - .../create-members/tsconfig.json | 8 - .../create-publishing-destination/index.ts | 284 - .../package.json | 42 - .../tsconfig.json | 8 - .../index.ts | 127 - .../package.json | 43 - .../tsconfig.json | 8 - .../guardduty-detector-config.ts | 110 - .../lib/aws-guardduty/guardduty-members.ts | 132 - .../guardduty-organization-admin-account.ts | 121 - .../guardduty-publishing-destination.ts | 116 - .../update-detector-config/index.ts | 247 - .../update-detector-config/package.json | 43 - .../update-detector-config/tsconfig.json | 8 - .../create-service-linked-role/index.ts | 154 - .../create-service-linked-role/package.json | 44 - .../create-service-linked-role/tsconfig.json | 8 - .../constructs/lib/aws-iam/password-policy.ts | 98 - .../lib/aws-iam/service-linked-role.ts | 107 - .../update-account-password-policy/index.ts | 63 - .../package.json | 44 - .../tsconfig.json | 8 - .../index.ts | 475 - .../package.json | 46 - .../tsconfig.json | 8 - .../index.ts | 373 - .../package.json | 44 - .../tsconfig.json | 8 - .../index.ts | 67 - .../package.json | 44 - .../tsconfig.json | 8 - .../get-permission-set-role-arn/index.ts | 96 - .../get-permission-set-role-arn/package.json | 44 - .../get-permission-set-role-arn/tsconfig.json | 8 - .../identity-center-assignments.ts | 150 - ...tity-center-get-permission-set-role-arn.ts | 143 - .../identity-center-instance.ts | 72 - ...ntity-center-organization-admin-account.ts | 141 - .../constructs/lib/aws-kms/key-encryption.ts | 100 - .../constructs/lib/aws-kms/key-lookup.ts | 90 - .../lib/aws-kms/put-key-policy/index.ts | 89 - .../lib/aws-kms/put-key-policy/package.json | 44 - .../lib/aws-kms/put-key-policy/tsconfig.json | 8 - .../lib/aws-macie/create-member/index.ts | 186 - .../lib/aws-macie/create-member/package.json | 43 - .../lib/aws-macie/create-member/tsconfig.json | 8 - .../lib/aws-macie/enable-macie/index.ts | 128 - .../lib/aws-macie/enable-macie/package.json | 43 - .../lib/aws-macie/enable-macie/tsconfig.json | 8 - .../index.ts | 167 - .../package.json | 44 - .../tsconfig.json | 8 - .../macie-export-config-classification.ts | 102 - .../constructs/lib/aws-macie/macie-members.ts | 109 - .../macie-organization-admin-account.ts | 137 - .../constructs/lib/aws-macie/macie-session.ts | 109 - .../put-export-config-classification/index.ts | 103 - .../package.json | 43 - .../tsconfig.json | 8 - .../lib/aws-networkfirewall/firewall.ts | 218 - .../get-network-firewall-endpoint.ts | 97 - .../get-network-firewall-endpoint/index.ts | 112 - .../package.json | 44 - .../tsconfig.json | 8 - .../lib/aws-networkfirewall/policy.ts | 148 - .../lib/aws-networkfirewall/rule-group.ts | 148 - .../lib/aws-networkfirewall/utils.ts | 201 - .../lib/aws-organizations/account.ts | 111 - .../aws-organizations/attach-policy/index.ts | 237 - .../attach-policy/jest.config.js | 76 - .../attach-policy/package.json | 49 - .../attach-policy/test/index.test.ts | 256 - .../attach-policy/test/static-input.ts | 65 - .../attach-policy/tsconfig.json | 8 - .../create-accounts-status/index.ts | 315 - .../create-accounts-status/package.json | 44 - .../create-accounts-status/tsconfig.json | 8 - .../lib/aws-organizations/create-accounts.ts | 146 - .../create-accounts/index.ts | 39 - .../create-accounts/package.json | 42 - .../create-accounts/tsconfig.json | 8 - .../create-organizational-units/index.ts | 266 - .../create-organizational-units/package.json | 47 - .../create-organizational-units/tsconfig.json | 8 - .../aws-organizations/create-policy/index.ts | 259 - .../create-policy/jest.config.js | 76 - .../create-policy/package.json | 51 - .../create-policy/test/index.test.ts | 135 - .../create-policy/test/static-input.ts | 30 - .../create-policy/tsconfig.json | 8 - .../enable-aws-service-access.ts | 85 - .../enable-aws-service-access/index.ts | 66 - .../enable-aws-service-access/package.json | 44 - .../enable-aws-service-access/tsconfig.json | 8 - .../aws-organizations/enable-policy-type.ts | 112 - .../enable-policy-type/index.ts | 81 - .../enable-policy-type/package.json | 44 - .../enable-policy-type/tsconfig.json | 8 - .../invite-account-to-organization/index.ts | 302 - .../package.json | 49 - .../tsconfig.json | 8 - .../list-policy-for-target/index.ts | 161 - .../list-policy-for-target/package.json | 44 - .../list-policy-for-target/tsconfig.json | 8 - .../aws-organizations/move-account/index.ts | 397 - .../move-account/package.json | 44 - .../move-account/tsconfig.json | 8 - .../lib/aws-organizations/move-accounts.ts | 143 - .../aws-organizations/organizational-units.ts | 105 - .../aws-organizations/policy-attachment.ts | 135 - .../lib/aws-organizations/policy.ts | 211 - .../register-delegated-administrator.ts | 88 - .../register-delegated-administrator/index.ts | 87 - .../package.json | 44 - .../tsconfig.json | 8 - .../aws-organizations/validate-scp-count.ts | 104 - .../enable-sharing-with-aws-organization.ts | 84 - .../index.ts | 48 - .../package.json | 44 - .../tsconfig.json | 8 - .../aws-ram/get-resource-share-item/index.ts | 82 - .../get-resource-share-item/package.json | 44 - .../get-resource-share-item/tsconfig.json | 8 - .../lib/aws-ram/get-resource-share/index.ts | 134 - .../aws-ram/get-resource-share/package.json | 44 - .../aws-ram/get-resource-share/tsconfig.json | 8 - .../constructs/lib/aws-ram/resource-share.ts | 231 - .../lib/aws-ram/share-subnet-tags.ts | 115 - .../lib/aws-ram/share-subnet-tags/index.ts | 295 - .../aws-ram/share-subnet-tags/package.json | 44 - .../aws-ram/share-subnet-tags/tsconfig.json | 8 - .../endpoint-addresses.ts | 84 - .../firewall-domain-list.ts | 150 - .../firewall-rule-group.ts | 112 - .../get-domain-lists/index.ts | 73 - .../get-domain-lists/package.json | 44 - .../get-domain-lists/tsconfig.json | 8 - .../get-endpoint-addresses/index.ts | 82 - .../get-endpoint-addresses/package.json | 44 - .../get-endpoint-addresses/tsconfig.json | 8 - .../log-resource-policy/index.ts | 71 - .../log-resource-policy/package.json | 44 - .../log-resource-policy/tsconfig.json | 8 - .../query-logging-config-association/index.ts | 74 - .../package.json | 44 - .../tsconfig.json | 8 - .../query-logging-config.ts | 337 - .../query-logging-config/index.ts | 87 - .../query-logging-config/package.json | 44 - .../query-logging-config/tsconfig.json | 8 - .../resolver-endpoint.ts | 88 - .../aws-route-53-resolver/resolver-rule.ts | 149 - .../aws-route-53/associate-hosted-zones.ts | 100 - .../associate-hosted-zones/index.ts | 345 - .../associate-hosted-zones/interfaces.ts | 82 - .../associate-hosted-zones/package.json | 46 - .../associate-hosted-zones/tsconfig.json | 8 - .../lib/aws-route-53/hosted-zone.ts | 126 - .../constructs/lib/aws-route-53/record-set.ts | 62 - .../lib/aws-s3/bucket-encryption.ts | 83 - .../constructs/lib/aws-s3/bucket-policy.ts | 140 - .../constructs/lib/aws-s3/bucket-prefix.ts | 113 - .../lib/aws-s3/bucket-replication.ts | 236 - .../constructs/lib/aws-s3/bucket.ts | 386 - .../lib/aws-s3/central-logs-bucket.ts | 342 - .../lib/aws-s3/public-access-block.ts | 91 - .../lib/aws-s3/put-bucket-encryption/index.ts | 71 - .../aws-s3/put-bucket-encryption/package.json | 44 - .../put-bucket-encryption/tsconfig.json | 8 - .../lib/aws-s3/put-bucket-policy/index.ts | 393 - .../lib/aws-s3/put-bucket-policy/package.json | 44 - .../aws-s3/put-bucket-policy/tsconfig.json | 8 - .../lib/aws-s3/put-bucket-prefix/index.ts | 79 - .../lib/aws-s3/put-bucket-prefix/package.json | 44 - .../aws-s3/put-bucket-prefix/tsconfig.json | 8 - .../aws-s3/put-bucket-replication/index.ts | 110 - .../put-bucket-replication/package.json | 44 - .../put-bucket-replication/tsconfig.json | 8 - .../aws-s3/put-public-access-block/index.ts | 69 - .../put-public-access-block/package.json | 44 - .../put-public-access-block/tsconfig.json | 8 - .../aws-s3/validate-bucket-config/index.ts | 125 - .../validate-bucket-config/package.json | 44 - .../validate-bucket-config/tsconfig.json | 8 - .../constructs/lib/aws-s3/validate-bucket.ts | 90 - .../batch-enable-standards/index.ts | 343 - .../batch-enable-standards/package.json | 45 - .../batch-enable-standards/tsconfig.json | 8 - .../aws-securityhub/create-members/index.ts | 230 - .../create-members/package.json | 45 - .../create-members/tsconfig.json | 8 - .../index.ts | 185 - .../package.json | 44 - .../tsconfig.json | 8 - .../region-aggregation/index.ts | 90 - .../region-aggregation/package.json | 44 - .../region-aggregation/tsconfig.json | 8 - .../aws-securityhub/securityhub-members.ts | 112 - .../securityhub-organization-admin-account.ts | 135 - .../securityhub-region-aggregation.ts | 91 - .../aws-securityhub/securityhub-standards.ts | 107 - .../aws-service-quota/create-limits/index.ts | 101 - .../create-limits/jest.config.js | 76 - .../create-limits/package.json | 50 - .../create-limits/test/index.test.ts | 77 - .../create-limits/test/static-input.ts | 7 - .../create-limits/tsconfig.json | 8 - .../limits-service-quota-definition.ts | 179 - .../aws-servicecatalog/get-portfolio-id.ts | 84 - .../get-portfolio-id/index.ts | 73 - .../get-portfolio-id/jest.config.js | 76 - .../get-portfolio-id/package.json | 49 - .../get-portfolio-id/test/index.test.ts | 57 - .../get-portfolio-id/test/static-input.ts | 8 - .../get-portfolio-id/tsconfig.json | 8 - .../propagate-portfolio-associations.ts | 104 - .../propagate-portfolio-associations/index.ts | 343 - .../jest.config.js | 76 - .../package.json | 50 - .../test/index.test.ts | 466 - .../test/static-input.ts | 66 - .../tsconfig.json | 8 - .../share-portfolio-with-org.ts | 126 - .../share-portfolio-with-org/index.ts | 239 - .../share-portfolio-with-org/jest.config.js | 76 - .../share-portfolio-with-org/package.json | 49 - .../test/index.test.ts | 132 - .../test/static-input.ts | 22 - .../share-portfolio-with-org/tsconfig.json | 8 - .../constructs/lib/aws-ssm/document.ts | 105 - .../lib/aws-ssm/get-param-value/index.ts | 75 - .../aws-ssm/get-param-value/jest.config.js | 76 - .../lib/aws-ssm/get-param-value/package.json | 50 - .../get-param-value/test/index.test.ts | 67 - .../get-param-value/test/static-input.ts | 19 - .../lib/aws-ssm/get-param-value/tsconfig.json | 8 - .../constructs/lib/aws-ssm/inventory.ts | 49 - .../lib/aws-ssm/policy-attachment.ts | 244 - .../lib/aws-ssm/put-param-value/index.ts | 354 - .../aws-ssm/put-param-value/jest.config.js | 76 - .../lib/aws-ssm/put-param-value/package.json | 50 - .../put-param-value/test/index.test.ts | 131 - .../put-param-value/test/static-input.ts | 63 - .../lib/aws-ssm/put-param-value/tsconfig.json | 8 - .../lib/aws-ssm/put-ssm-parameter.ts | 130 - .../lib/aws-ssm/session-manager-settings.ts | 133 - .../aws-ssm/session-manager-settings/index.ts | 105 - .../session-manager-settings/jest.config.js | 76 - .../session-manager-settings/package.json | 49 - .../test/index.test.ts | 84 - .../test/static-input.ts | 49 - .../session-manager-settings/tsconfig.json | 8 - .../lib/aws-ssm/share-document/index.ts | 137 - .../lib/aws-ssm/share-document/jest.config.js | 76 - .../lib/aws-ssm/share-document/package.json | 50 - .../aws-ssm/share-document/test/index.test.ts | 64 - .../share-document/test/static-input.ts | 22 - .../lib/aws-ssm/share-document/tsconfig.json | 8 - .../lib/aws-ssm/ssm-parameter-lookup.ts | 123 - .../constructs/lib/common-functions.ts | 20 - .../attach-resource-based-policy.yaml | 22 - .../data-perimeter/detect-resource-policy.ts | 142 - .../lambda-handler/package.json | 55 - .../common/aws-resource-policy-strategy.ts | 27 - .../src/common/common-resources.ts | 81 - .../allowed-only-policy-strategy.ts | 124 - .../apigateway-repository-policy-strategy.ts | 73 - .../backup-valut-policy-strategy.ts | 79 - ...ode-artifact-repository-policy-strategy.ts | 83 - .../ecr-repository-policy-strategy.ts | 68 - .../efs-file-system-policy-strategy.ts | 70 - .../eventbridge-eventbus-policy-strategy.ts | 62 - .../strategies/iam-role-policy-strategy.ts | 67 - .../strategies/kms-key-policy-strategy.ts | 74 - .../strategies/lambda-policy-strategy.ts | 114 - .../strategies/lex-bot-policy-strategy.ts | 86 - .../opensearch-domain-policy-strategy.ts | 60 - .../common/strategies/pca-policy-strategy.ts | 92 - .../strategies/s3-bucket-policy-strategy.ts | 63 - .../secrets-manager-policy-streategy.ts | 73 - .../common/strategies/sns-policy-strategy.ts | 61 - .../common/strategies/sqs-policy-strategy.ts | 67 - .../lambda-handler/src/common/strategy.ts | 45 - .../lambda-handler/src/common/utils.ts | 157 - .../src/detect-resource-policy/index.ts | 101 - .../src/remediate-resource-policy/index.ts | 107 - .../lambda-handler/tsconfig.json | 8 - .../remediate-resource-policy.ts | 193 - .../remediation-ssm-document.ts | 58 - .../constructs/lib/lza-custom-resource.ts | 345 - .../constructs/lib/lza-lambda.ts | 185 - .../@aws-accelerator/constructs/package.json | 54 - .../get-accelerator-metadata.test.ts.snap | 304 - .../get-accelerator-metadata.test.ts | 43 - ...er-create-reports-destination.test.ts.snap | 208 - ...er-organization-admin-account.test.ts.snap | 232 - ...manager-create-reports-destination.test.ts | 36 - ...manager-organization-admin-account.test.ts | 35 - .../create-autoscaling-group.test.ts.snap | 346 - .../create-autoscaling-group.test.ts | 47 - .../budget-definition.test.ts.snap | 278 - .../aws-budgets/budget-definition.test.ts | 109 - .../__snapshots__/certificate.test.ts.snap | 903 - .../certificate.test.ts | 56 - .../get-resource-type.test.ts.snap | 348 - .../get-resource-type.test.ts | 39 - .../cloudwatch-destination.test.ts.snap | 512 - ...loudwatch-log-data-protection.test.ts.snap | 391 - .../cloudwatch-log-group.test.ts.snap | 353 - ...atch-logs-subscription-filter.test.ts.snap | 345 - .../cloudwatch-destination.test.ts | 59 - .../cloudwatch-log-data-protection.test.ts | 38 - .../cloudwatch-log-group.test.ts | 43 - ...loudwatch-logs-subscription-filter.test.ts | 49 - .../config-aggregation.test.ts.snap | 55 - .../config-recorder.test.ts.snap | 364 - .../__snapshots__/config-tags.test.ts.snap | 165 - .../detect-resource-policy.test.ts.snap | 385 - .../remediate-resource-policy.test.ts.snap | 574 - .../config-aggregation.test.ts | 35 - .../aws-configservice/config-recorder.test.ts | 42 - .../aws-configservice/config-tags.test.ts | 50 - .../detect-resource-policy.test.ts | 55 - .../remediate-resource-policy.test.ts | 55 - .../create-accounts.test.ts.snap | 965 - .../aws-controltower/create-accounts.test.ts | 47 - .../report-definition.test.ts.snap | 417 - .../test/aws-cur/report-definition.test.ts | 71 - .../detective-graph-config.test.ts.snap | 200 - .../detective-members.test.ts.snap | 180 - ...ve-organization-admin-account.test.ts.snap | 215 - .../detective-graph-config.test.ts | 33 - .../aws-detective/detective-members.test.ts | 33 - ...tective-organization-admin-account.test.ts | 34 - .../direct-connect-gateway.test.ts.snap | 152 - .../gateway-association.test.ts.snap | 296 - .../virtual-interface.test.ts.snap | 310 - .../direct-connect-gateway.test.ts | 36 - .../gateway-association.test.ts | 51 - .../virtual-interface.test.ts | 154 - ...ctive-directory-configuration.test.ts.snap | 689 - ...ve-directory-log-subscription.test.ts.snap | 373 - ...ctive-directory-resolver-rule.test.ts.snap | 347 - .../active-directory.test.ts.snap | 373 - .../share-active-directory.test.ts.snap | 348 - .../active-directory-configuration.test.ts | 81 - .../active-directory-log-subscription.test.ts | 42 - .../active-directory-resolver-rule.test.ts | 36 - .../active-directory.test.ts | 42 - .../share-active-directory.test.ts | 36 - .../account-warming.test.ts.snap | 1334 - .../create-launch-template.test.ts.snap | 49 - .../cross-account-route.test.ts.snap | 334 - .../customer-gateway.test.ts.snap | 212 - ...-default-security-group-rules.test.ts.snap | 151 - .../delete-default-vpc.test.ts.snap | 159 - .../__snapshots__/dhcp-options.test.ts.snap | 30 - .../__snapshots__/ebs-encryption.test.ts.snap | 192 - .../__snapshots__/firewall-asg.test.ts.snap | 387 - .../firewall-config-replacements.test.ts.snap | 296 - .../firewall-instance.test.ts.snap | 97 - ...am-organization-admin-account.test.ts.snap | 184 - .../__snapshots__/ipam-pool.test.ts.snap | 31 - .../__snapshots__/ipam-scope.test.ts.snap | 21 - .../__snapshots__/ipam-subnet.test.ts.snap | 470 - .../aws-ec2/__snapshots__/ipam.test.ts.snap | 28 - .../prefix-list-route.test.ts.snap | 154 - .../__snapshots__/prefix-list.test.ts.snap | 27 - .../__snapshots__/route-table.test.ts.snap | 585 - .../subnet-id-lookup.test.ts.snap | 313 - .../transit-gateway-connect.test.ts.snap | 23 - .../transit-gateway-peering.test.ts.snap | 235 - ...gateway-prefix-list-reference.test.ts.snap | 155 - .../transit-gateway-route-table.test.ts.snap | 24 - .../transit-gateway-static-route.test.ts.snap | 756 - .../transit-gateway.test.ts.snap | 2993 --- .../__snapshots__/vpc-endpoint.test.ts.snap | 226 - .../__snapshots__/vpc-id-lookup.test.ts.snap | 312 - .../__snapshots__/vpc-import.test.ts.snap | 504 - .../__snapshots__/vpc-peering.test.ts.snap | 598 - .../aws-ec2/__snapshots__/vpc.test.ts.snap | 1226 - .../__snapshots__/vpn-connection.test.ts.snap | 270 - .../test/aws-ec2/account-warming.test.ts | 37 - .../aws-ec2/account-warming/index.test.ts | 230 - .../aws-ec2/create-launch-template.test.ts | 46 - .../test/aws-ec2/cross-account-route.test.ts | 68 - .../aws-ec2/cross-account-route/index.test.ts | 347 - .../test/aws-ec2/customer-gateway.test.ts | 46 - ...elete-default-security-group-rules.test.ts | 36 - .../test/aws-ec2/delete-default-vpc.test.ts | 33 - .../test/aws-ec2/dhcp-options.test.ts | 38 - .../test/aws-ec2/ebs-encryption.test.ts | 37 - .../test/aws-ec2/ebs-encryption/index.test.ts | 117 - .../test/aws-ec2/firewall-asg.test.ts | 87 - .../firewall-config-replacements.test.ts | 41 - .../test/aws-ec2/firewall-instance.test.ts | 66 - .../integ.account-warming.ts | 120 - .../integ.ebs-encryption.ts | 210 - .../integ.transit-gateway.ts | 267 - .../ipam-organization-admin-account.test.ts | 34 - .../constructs/test/aws-ec2/ipam-pool.test.ts | 37 - .../test/aws-ec2/ipam-scope.test.ts | 34 - .../test/aws-ec2/ipam-subnet.test.ts | 68 - .../constructs/test/aws-ec2/ipam.test.ts | 34 - .../launchTemplateFiles/firewallUserData.txt | 1 - .../launchTemplateFiles/launchTemplate.yaml | 160 - .../launchTemplateFiles/testUserData.sh | 10 - .../test/aws-ec2/prefix-list-route.test.ts | 36 - .../test/aws-ec2/prefix-list.test.ts | 36 - .../test/aws-ec2/route-table.test.ts | 182 - .../test/aws-ec2/subnet-id-lookup.test.ts | 37 - .../aws-ec2/transit-gateway-connect.test.ts | 36 - .../aws-ec2/transit-gateway-peering.test.ts | 50 - ...nsit-gateway-prefix-list-reference.test.ts | 36 - .../transit-gateway-route-table.test.ts | 33 - .../transit-gateway-static-route.test.ts | 86 - .../test/aws-ec2/transit-gateway.test.ts | 167 - .../test/aws-ec2/vpc-endpoint.test.ts | 218 - .../test/aws-ec2/vpc-id-lookup.test.ts | 36 - .../test/aws-ec2/vpc-import.test.ts | 144 - .../test/aws-ec2/vpc-peering.test.ts | 125 - .../constructs/test/aws-ec2/vpc.test.ts | 224 - .../test/aws-ec2/vpn-connection.test.ts | 85 - .../application-load-balancer.test.ts.snap | 183 - .../gateway-load-balancer.test.ts.snap | 66 - .../network-load-balancer.test.ts.snap | 144 - .../__snapshots__/nlb-addresses.test.ts.snap | 321 - .../__snapshots__/target-group.test.ts.snap | 73 - .../application-load-balancer.test.ts | 102 - .../gateway-load-balancer.test.ts | 35 - .../network-load-balancer.test.ts | 61 - .../nlb-addresses.test.ts | 40 - .../target-group.test.ts | 51 - .../move-account-rule.test.ts.snap | 428 - ...new-cloudwatch-log-event-rule.test.ts.snap | 604 - .../revert-scp-changes.test.ts.snap | 662 - .../security-hub-events-log.test.ts.snap | 498 - .../test/aws-events/move-account-rule.test.ts | 40 - .../new-cloudwatch-log-event-rule.test.ts | 51 - .../integ.new-cloudwatch-log-event.ts | 196 - .../aws-events/revert-scp-changes.test.ts | 78 - .../security-hub-events-log.test.ts | 37 - .../integ.security-hub-events-log.ts | 174 - .../cloudwatch-to-s3-firehose.test.ts.snap | 5604 ----- .../cloudwatch-to-s3-firehose.test.ts | 107 - .../dynamicPartition1.json | 3 - .../dynamicPartition2.json | 3 - .../dynamicPartition3.json | 3 - .../dynamicPartition4.json | 3 - .../firehose-record-processing/index.test.ts | 445 - .../fms-notification-channel.test.ts.snap | 41 - ...ms-organization-admin-account.test.ts.snap | 323 - .../aws-fms/fms-notification-channel.test.ts | 34 - .../fms-organization-admin-account.test.ts | 36 - .../guardduty-detector-config.test.ts.snap | 154 - .../guardduty-members.test.ts.snap | 188 - ...ty-organization-admin-account.test.ts.snap | 203 - ...rdduty-publishing-destination.test.ts.snap | 266 - .../guardduty-detector-config.test.ts | 36 - .../aws-guardduty/guardduty-members.test.ts | 37 - ...ardduty-organization-admin-account.test.ts | 34 - .../guardduty-publishing-destination.test.ts | 37 - .../password-policy.test.ts.snap | 156 - .../service-linked-role.test.ts.snap | 318 - .../test/aws-iam/password-policy.test.ts | 42 - .../test/aws-iam/service-linked-role.test.ts | 38 - .../identity-center-assignment.test.ts.snap | 223 - ...r-get-permission-set-role-arn.test.ts.snap | 247 - .../identity-center-instance.test.ts.snap | 352 - ...er-organization-admin-account.test.ts.snap | 266 - .../identity-center-assignment.test.ts | 40 - ...center-get-permission-set-role-arn.test.ts | 40 - .../identity-center-instance.test.ts | 34 - ...-center-organization-admin-account.test.ts | 34 - .../__snapshots__/key-encryption.test.ts.snap | 360 - .../__snapshots__/key-lookup.test.ts.snap | 156 - .../test/aws-kms/key-encryption.test.ts | 39 - .../test/aws-kms/key-lookup.test.ts | 36 - ...-export-config-classification.test.ts.snap | 197 - .../__snapshots__/macie-members.test.ts.snap | 179 - ...ie-organization-admin-account.test.ts.snap | 223 - .../__snapshots__/macie-session.test.ts.snap | 172 - ...macie-export-config-classification.test.ts | 36 - .../test/aws-macie/macie-members.test.ts | 33 - .../macie-organization-admin-account.test.ts | 33 - .../test/aws-macie/macie-session.test.ts | 34 - .../__snapshots__/firewall.test.ts.snap | 297 - ...get-network-firewall-endpoint.test.ts.snap | 151 - .../__snapshots__/policy.test.ts.snap | 89 - .../__snapshots__/rule-group.test.ts.snap | 285 - .../test/aws-networkfirewall/firewall.test.ts | 78 - .../get-network-firewall-endpoint.test.ts | 38 - .../includedStacks/firewall-stack.json | 26 - .../test/aws-networkfirewall/policy.test.ts | 95 - .../aws-networkfirewall/rule-group.test.ts | 147 - .../__snapshots__/account.test.ts.snap | 217 - .../create-accounts.test.ts.snap | 1134 - .../enable-aws-service-access.test.ts.snap | 152 - .../enable-policy-type.test.ts.snap | 175 - .../__snapshots__/move-accounts.test.ts.snap | 382 - .../organizational-units.test.ts.snap | 200 - .../policy-attachment.test.ts.snap | 163 - .../__snapshots__/policy.test.ts.snap | 197 - ...ister-delegated-administrator.test.ts.snap | 155 - .../validate-scp-count.test.ts.snap | 184 - .../test/aws-organizations/account.test.ts | 38 - .../aws-organizations/create-accounts.test.ts | 55 - .../enable-aws-service-access.test.ts | 34 - .../enable-policy-type.test.ts | 33 - .../enable-policy-type/index.test.ts | 194 - .../integ.enable-policy-type.ts | 109 - .../aws-organizations/move-accounts.test.ts | 40 - .../organizational-units.test.ts | 39 - .../policy-attachment.test.ts | 42 - .../test/aws-organizations/policy.test.ts | 46 - .../register-delegated-administrator.test.ts | 35 - .../validate-scp-count.test.ts | 37 - ...sharing-with-aws-organization.test.ts.snap | 151 - .../__snapshots__/resource-share.test.ts.snap | 193 - .../share-subnet-tags.test.ts.snap | 209 - ...able-sharing-with-aws-organization.test.ts | 33 - .../test/aws-ram/resource-share.test.ts | 70 - .../test/aws-ram/share-subnet-tags.test.ts | 40 - .../aws-ram/share-subnet-tags/index.test.ts | 202 - .../endpoint-addresses.test.ts.snap | 151 - .../firewall-domain-list.test.ts.snap | 200 - .../firewall-rule-group.test.ts.snap | 37 - .../query-logging-config.test.ts.snap | 703 - .../resolver-endpoint.test.ts.snap | 32 - .../__snapshots__/resolver-rule.test.ts.snap | 154 - .../endpoint-addresses.test.ts | 33 - .../firewall-domain-list.test.ts | 48 - .../firewall-rule-group.test.ts | 46 - .../query-logging-config.test.ts | 92 - .../resolver-endpoint.test.ts | 32 - .../resolver-rule.test.ts | 92 - .../associate-hosted-zones.test.ts.snap | 174 - .../__snapshots__/hosted-zone.test.ts.snap | 358 - .../__snapshots__/record-set.test.ts.snap | 154 - .../associate-hosted-zones.test.ts | 47 - .../test/aws-route-53/hosted-zone.test.ts | 115 - .../test/aws-route-53/record-set.test.ts | 91 - .../bucket-encryption.test.ts.snap | 405 - .../__snapshots__/bucket-policy.test.ts.snap | 390 - .../__snapshots__/bucket-prefix.test.ts.snap | 334 - .../bucket-replication.test.ts.snap | 486 - .../aws-s3/__snapshots__/bucket.test.ts.snap | 2731 -- .../central-logs-buckets.test.ts.snap | 749 - .../public-access-block.test.ts.snap | 154 - .../validate-bucket.test.ts.snap | 1459 -- .../test/aws-s3/bucket-encryption.test.ts | 36 - .../test/aws-s3/bucket-policy.test.ts | 46 - .../test/aws-s3/bucket-prefix.test.ts | 37 - .../test/aws-s3/bucket-replication.test.ts | 107 - .../constructs/test/aws-s3/bucket.test.ts | 298 - .../test/aws-s3/central-logs-buckets.test.ts | 47 - .../test/aws-s3/public-access-block.test.ts | 38 - .../test/aws-s3/validate-bucket.test.ts | 55 - .../securityhub-members.test.ts.snap | 176 - ...ub-organization-admin-account.test.ts.snap | 219 - ...ecurityhub-region-aggregation.test.ts.snap | 159 - .../securityhub-standards.test.ts.snap | 190 - .../securityhub-members.test.ts | 35 - ...rityhub-organization-admin-account.test.ts | 34 - .../securityhub-region-aggregation.test.ts | 33 - .../securityhub-standards.test.ts | 45 - ...mits-service-quota-definition.test.ts.snap | 259 - .../limits-service-quota-definition.test.ts | 36 - .../get-portfolio-id.test.ts.snap | 151 - ...pagate-portfolio-associations.test.ts.snap | 156 - .../share-portfolio-with-org.test.ts.snap | 200 - .../get-portfolio-id.test.ts | 36 - .../propagate-portfolio-associations.test.ts | 40 - .../share-portfolio-with-org.test.ts | 38 - .../__snapshots__/document.test.ts.snap | 250 - .../__snapshots__/inventory.test.ts.snap | 35 - .../policy-attachment.test.ts.snap | 290 - .../put-ssm-parameter.test.ts.snap | 187 - .../session-manager-settings.test.ts.snap | 249 - .../ssm-parameter-lookup.test.ts.snap | 154 - .../constructs/test/aws-ssm/document.test.ts | 48 - .../constructs/test/aws-ssm/inventory.test.ts | 31 - .../test/aws-ssm/policy-attachment.test.ts | 56 - .../test/aws-ssm/put-ssm-parameter.test.ts | 45 - .../aws-ssm/session-manager-settings.test.ts | 47 - .../test/aws-ssm/ssm-parameter-lookup.test.ts | 37 - .../constructs/test/snapshot-test.ts | 51 - .../test/unit-test/accelerator-unit-test.ts | 135 - .../test/unit-test/common/resources.ts | 80 - .../@aws-accelerator/constructs/tsconfig.json | 6 - .../govcloud-account-vending/.gitignore | 10 - .../govcloud-account-vending/.npmignore | 6 - .../govcloud-account-vending/README.md | 25 - .../bin/govcloud-avm.ts | 31 - .../govcloud-account-vending/cdk.json | 42 - .../govcloud-account-vending/jest.config.js | 8 - .../lib/govcloud-avm-product-stack.ts | 97 - .../lib/govcloud-avm-stack.ts | 129 - .../lambdas/create-govcloud-account/index.js | 80 - .../govcloud-account-vending/package.json | 77 - .../govcloud-account-vending.test.ts.snap | 231 - .../test/govcloud-account-vending.test.ts | 31 - .../test/snapshot-test.ts | 55 - .../govcloud-account-vending/tsconfig.json | 8 - .../@aws-accelerator/installer/.npmignore | 6 - .../@aws-accelerator/installer/README.md | 1 - .../installer/bin/installer.ts | 77 - .../@aws-accelerator/installer/cdk.json | 5 - .../@aws-accelerator/installer/index.ts | 14 - .../@aws-accelerator/installer/jest.config.js | 76 - .../installer/lib/installer-stack.ts | 1160 - .../update-pipeline-github-token/index.js | 107 - .../installer/lib/resource-name-prefixes.ts | 245 - .../installer/lib/solutions-helper.ts | 203 - .../installer/lib/validate.ts | 78 - .../@aws-accelerator/installer/package.json | 55 - .../test/__snapshots__/installer.test.ts.snap | 20736 ---------------- .../installer/test/installer.test.ts | 94 - .../@aws-accelerator/installer/tsconfig.json | 8 - .../@aws-accelerator/modules/README.md | 12 - .../@aws-accelerator/modules/bin/runner.ts | 87 - .../common/accelerator-config-loader.ts | 76 - .../modules/common/functions.ts | 504 - .../modules/common/resources.ts | 156 - .../@aws-accelerator/modules/jest.config.js | 81 - .../modules/lib/accelerator-module.ts | 28 - .../modules/lib/aws-organization/index.ts | 1247 - .../modules/lib/control-tower/index.ts | 490 - .../control-tower/prerequisites/iam-role.ts | 235 - .../control-tower/prerequisites/kms-key.ts | 188 - .../prerequisites/organization.ts | 377 - .../prerequisites/shared-account.ts | 190 - .../lib/control-tower/utils/resources.ts | 351 - .../modules/lib/module-runner.ts | 37 - .../@aws-accelerator/modules/package.json | 85 - .../test/aws-organization/index.test.ts | 20 - .../test/control-tower/iam-role.test.ts | 136 - .../test/control-tower/kms-key.test.ts | 79 - .../test/control-tower/organization.test.ts | 277 - .../test/control-tower/shared-account.test.ts | 192 - .../modules/test/utils/test-resources.ts | 220 - .../@aws-accelerator/modules/tsconfig.json | 9 - .../@aws-accelerator/tester/.npmignore | 6 - .../@aws-accelerator/tester/README.md | 1 - .../@aws-accelerator/tester/bin/app.ts | 80 - .../packages/@aws-accelerator/tester/cdk.json | 7 - .../packages/@aws-accelerator/tester/index.ts | 14 - .../@aws-accelerator/tester/jest.config.js | 76 - .../@aws-accelerator/tester/lambdas/index.ts | 118 - .../tester/lambdas/package.json | 44 - .../validate-transit-gateway.ts | 280 - .../tester/lambdas/tsconfig.json | 11 - .../tester/lib/tester-stack.ts | 150 - .../@aws-accelerator/tester/package.json | 58 - .../tester/test/configs/config.yaml | 22 - ...rnal-pipeline-account-tester-stack.test.ts | 332 - .../tester/test/tester-stack.test.ts | 330 - .../@aws-accelerator/tester/tsconfig.json | 10 - .../packages/@aws-accelerator/tools/index.ts | 15 - .../@aws-accelerator/tools/jest.config.js | 76 - .../tools/lib/classes/accelerator-tool.ts | 2102 -- .../@aws-accelerator/tools/package.json | 65 - .../tools/test/uninstaller.test.ts | 22 - .../@aws-accelerator/tools/tsconfig.json | 8 - .../@aws-accelerator/tools/uninstaller.ts | 268 - .../packages/@aws-accelerator/utils/README.md | 1 - .../packages/@aws-accelerator/utils/index.ts | 28 - .../@aws-accelerator/utils/jest.config.js | 76 - .../utils/lib/common-functions.ts | 208 - .../utils/lib/common-resources.ts | 146 - .../utils/lib/common-types.ts | 304 - .../utils/lib/control-tower.ts | 37 - .../@aws-accelerator/utils/lib/diff-stack.ts | 108 - .../utils/lib/evaluate-limits.ts | 106 - .../utils/lib/get-template.ts | 132 - .../utils/lib/load-organization-config.ts | 143 - .../@aws-accelerator/utils/lib/logger.ts | 35 - .../utils/lib/policy-replacements.ts | 252 - .../@aws-accelerator/utils/lib/regions.ts | 130 - .../utils/lib/set-organizations-client.ts | 43 - .../utils/lib/set-token-preferences.ts | 52 - .../utils/lib/ssm-parameter-path.ts | 463 - .../@aws-accelerator/utils/lib/throttle.ts | 77 - .../@aws-accelerator/utils/package.json | 54 - .../utils/test/evaluate-limits.test.ts | 87 - .../utils/test/get-template.test.ts | 251 - .../set-token-preferences.integ.ts | 90 - .../utils/test/set-token-preferences.test.ts | 122 - .../utils/test/throttle.test.ts | 25 - .../@aws-accelerator/utils/tsconfig.json | 8 - .../cdk-extensions/.gitignore | 8 - .../cdk-extensions/.npmignore | 6 - .../cdk-extensions/README.md | 1 - .../cdk-extensions/index.ts | 15 - .../cdk-extensions/jest.config.js | 76 - .../cdk-extensions/lib/repository.ts | 64 - .../cdk-extensions/lib/trail.ts | 42 - .../cdk-extensions/package.json | 51 - .../repository-snapshot.test.ts.snap | 21 - .../__snapshots__/repository.test.ts.snap | 21 - .../__snapshots__/trail-snapshot.test.ts.snap | 128 - .../test/repository-fine-grained.test.ts | 36 - .../test/repository-snapshot.test.ts | 27 - .../cdk-extensions/test/repository.test.ts | 62 - .../cdk-extensions/test/test-config.ts | 35 - .../test/trail-snapshot.test.ts | 27 - .../cdk-extensions/tsconfig.json | 8 - .../cdk-plugin-assume-role/.gitignore | 1 - .../cdk-plugin-assume-role/index.ts | 15 - .../lib/assume-role-plugin.ts | 56 - .../lib/assume-role-provider-source.ts | 109 - .../cdk-plugin-assume-role/lib/backoff.ts | 44 - .../cdk-plugin-assume-role/package.json | 46 - .../cdk-plugin-assume-role/tsconfig.json | 8 - source/run-all-tests.sh | 154 - source/tsconfig.json | 23 - source/yarn.lock | 13491 ---------- yarn.lock | 4 - 1672 files changed, 62 insertions(+), 388908 deletions(-) create mode 100644 .github/workflows/docker-build.yml mode change 100755 => 100644 .gitignore delete mode 100755 .viperlightignore delete mode 100755 .viperlightrc delete mode 100755 CHANGELOG.md delete mode 100755 CODE_OF_CONDUCT.md delete mode 100755 CONTRIBUTING.md delete mode 100644 DEVELOPING.md delete mode 100644 FAQ.md delete mode 100755 LICENSE.txt delete mode 100755 NOTICE.txt delete mode 100755 README.md create mode 100644 build.sh delete mode 100644 codescan-postbuild-custom.sh delete mode 100644 codescan-prebuild-custom.sh delete mode 100755 deployment/build-docs.sh delete mode 100755 deployment/build-open-source-dist.sh delete mode 100755 deployment/build-s3-dist.sh delete mode 100755 deployment/cdk-solution-helper/README.md delete mode 100755 deployment/cdk-solution-helper/index.js delete mode 100755 deployment/cdk-solution-helper/package.json delete mode 100644 deployment/solution_config delete mode 100755 deployment/update-snapshots.sh delete mode 100644 reference/sample-configurations/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-cccs-medium/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-cn/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/cfn-templates/iam-analyzer.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-detection-role.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-remediation-role.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile.zip delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/bucket-sse-enabled-remediation-role.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-detection-role.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions.zip delete mode 100644 reference/sample-configurations/lza-sample-config-cn/custom-config-rules/elb-logging-enabled-remediation-role.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/customizations-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/dns-firewall-domain-lists/domain-list-1.txt delete mode 100644 reference/sample-configurations/lza-sample-config-cn/dns-firewall-domain-lists/domain-list-2.txt delete mode 100644 reference/sample-configurations/lza-sample-config-cn/dynamic-partitioning/log-filters.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/global-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/iam-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/security-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-instance-profile.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-role-policy.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/ssm-documents/s3-encryption.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/ssm-documents/ssm-elb-enable-logging.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/default.json delete mode 100644 reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/ec2.json delete mode 100644 reference/sample-configurations/lza-sample-config-education/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/images/lza_tax_network_diagram.png delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/images/tax_lza_network_diagram.png delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/images/tax_multi_account_ref_arch.png delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/ip-cidr-mapping-tax.csv delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-base-root.json delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-only-us-regions.json delete mode 100644 reference/sample-configurations/lza-sample-config-finance-tax/tagging-policies/tax-org-tag-policy.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/global-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/iam-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/security-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/lockdown-govCloud-accounts.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/quarantine.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/bucket-policies/central-log-bucket.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/dynamic-partitioning/log-filters.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/global-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/iam-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/security-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-1.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-2.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/quarantine.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/default.json delete mode 100644 reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/ec2.json delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/images/.gitkeep delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/images/LZA_EGA_Healthcare_Network_Diagram_v2.1.png delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/images/LZA_EGA_Healthcare_Org_Structure.png delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/images/LZAforHealthcare_2022-08-30.png delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/Readme.md delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-base-root.json delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-hipaa-service.json delete mode 100644 reference/sample-configurations/lza-sample-config-healthcare/tagging-policies/healthcare-org-tag-policy.json delete mode 100644 reference/sample-configurations/lza-sample-config-tse-se/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/README.md delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/images/EGA-StateIT-LZA-EGA-TGW.png delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/images/EGA-StateIT-LZA-OU-EGA.png delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/images/LZAforStateCentralIT_2022-10-18.png delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/READEME.md delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/guardrails-3.json delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/scp-hlc-hipaa-service.json delete mode 100644 reference/sample-configurations/lza-sample-config-us-slg-central-it/tagging-policies/healthcare-org-tag-policy.json delete mode 100644 reference/sample-configurations/lza-sample-config/README.md delete mode 100644 reference/sample-configurations/lza-sample-config/accounts-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-connector-permissions-setup.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-grant-permissions-setup.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-setup.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-group-setup.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-setup.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/AWSQuickStart.psm1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/Configure-password-policy.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/ad-config-scripts/Join-Domain.ps1 delete mode 100644 reference/sample-configurations/lza-sample-config/backup-policies/backup-plan.json delete mode 100644 reference/sample-configurations/lza-sample-config/bucket-policies/central-log-bucket.json delete mode 100644 reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-1.txt delete mode 100644 reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-2.txt delete mode 100644 reference/sample-configurations/lza-sample-config/dynamic-partitioning/log-filters.json delete mode 100644 reference/sample-configurations/lza-sample-config/firewall-rules/rules.txt delete mode 100644 reference/sample-configurations/lza-sample-config/global-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config/iam-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config/network-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config/organization-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config/security-config.yaml delete mode 100644 reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-1.json delete mode 100644 reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-2.json delete mode 100644 reference/sample-configurations/lza-sample-config/service-control-policies/quarantine.json delete mode 100644 reference/sample-configurations/lza-sample-config/tagging-policies/org-tag-policy.json delete mode 100644 reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/default.json delete mode 100644 reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/ec2.json delete mode 100644 solution-manifest.yaml delete mode 100644 source/.eslintrc.json delete mode 100755 source/.husky/pre-commit delete mode 100644 source/.prettierrc.json delete mode 100644 source/lerna.json delete mode 100755 source/log-scanner.sh delete mode 100644 source/mkdocs/docs/developer-guide/dependencies.md delete mode 100644 source/mkdocs/docs/developer-guide/design.md delete mode 100644 source/mkdocs/docs/developer-guide/doc-guidelines.md delete mode 100644 source/mkdocs/docs/developer-guide/features.md delete mode 100644 source/mkdocs/docs/developer-guide/index.md delete mode 100644 source/mkdocs/docs/developer-guide/scripts.md delete mode 100644 source/mkdocs/docs/faq/architecture.md delete mode 100644 source/mkdocs/docs/faq/ct-cfct.md delete mode 100644 source/mkdocs/docs/faq/customizations.md delete mode 100644 source/mkdocs/docs/faq/general.md delete mode 100644 source/mkdocs/docs/faq/index.md delete mode 100644 source/mkdocs/docs/faq/logging/cwl.md delete mode 100644 source/mkdocs/docs/faq/networking/direct-connect.md delete mode 100644 source/mkdocs/docs/faq/networking/dpi.md delete mode 100644 source/mkdocs/docs/faq/networking/general.md delete mode 100644 source/mkdocs/docs/faq/networking/gwlb.md delete mode 100644 source/mkdocs/docs/faq/networking/network-firewall.md delete mode 100644 source/mkdocs/docs/faq/operations.md delete mode 100644 source/mkdocs/docs/faq/security.md delete mode 100644 source/mkdocs/docs/index.md delete mode 100644 source/mkdocs/docs/installation.md delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/considerations.md delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/images/image1.png delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/images/image2.png delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/index.md delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/networking.md delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/org-structure.md delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/overview.md delete mode 100644 source/mkdocs/docs/sample-configurations/govcloud-us/security-controls.md delete mode 100644 source/mkdocs/docs/sample-configurations/index.md delete mode 100644 source/mkdocs/docs/sample-configurations/standard/authn-authz.md delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/cloudwatch_logs.jpg delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/default_ou_structure.jpg delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/lza-centralized-logging.png delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/mandatory_accounts.jpg delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/organization_legend.jpg delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/organization_structure.png delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/scp_inheritance.jpg delete mode 100644 source/mkdocs/docs/sample-configurations/standard/images/standard_network.jpg delete mode 100644 source/mkdocs/docs/sample-configurations/standard/index.md delete mode 100644 source/mkdocs/docs/sample-configurations/standard/logging-monitoring.md delete mode 100644 source/mkdocs/docs/sample-configurations/standard/networking.md delete mode 100644 source/mkdocs/docs/sample-configurations/standard/org-structure.md delete mode 100644 source/mkdocs/docs/sample-configurations/standard/overview.md delete mode 100644 source/mkdocs/docs/typedocs/index.md delete mode 100644 source/mkdocs/docs/user-guide/config.md delete mode 100644 source/mkdocs/docs/user-guide/index.md delete mode 100644 source/mkdocs/docs/user-guide/logging.md delete mode 100644 source/mkdocs/docs/user-guide/securityhub-findings.md delete mode 100644 source/mkdocs/mkdocs.yml delete mode 100644 source/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/.npmignore delete mode 100644 source/packages/@aws-accelerator/accelerator/README.md delete mode 100644 source/packages/@aws-accelerator/accelerator/bin/app.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/cdk.json delete mode 100644 source/packages/@aws-accelerator/accelerator/cdk.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/jest.config.js delete mode 100644 source/packages/@aws-accelerator/accelerator/jest/setEnvVars.js delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/accelerator-aspects.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/accelerator-resource-names.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/accelerator-stage.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/accelerator.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/application-load-balancers.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/firewall-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-groups.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-roles.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-users.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-ad-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-policies.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/resource.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging-association.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-resolver-endpoint.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/shared-security-groups.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/ssm-inventory.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/tgw-cross-account-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateway-routes.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateways.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-endpoints.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-peering-connection.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/config-repository.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/config-validator.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/detach-quarantine-scp.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/types.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/index.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/load-config-table.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/pipeline.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/prerequisites.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/resources/kms-key-resource.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/resources/scp-resource.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/accelerator-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/accounts-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/applications-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/bootstrap-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/custom-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/customizations-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/dependencies-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/diagnostics-pack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/identity-center.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/diagnostics-pack-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/finalize-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/identity-center-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/import-asea-resources-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/key-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/logging-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/firewall-vpn-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/network-associations-gwlb-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/network-associations-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/shared-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/central-network-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/dx-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/fms-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/ipam-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/mad-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/network-prep-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/nfw-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/resolver-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/tgw-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/vpn-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-dns-stack/network-vpc-dns-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-endpoints-stack/network-vpc-endpoints-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/acm-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/dhcp-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/ipam-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/load-balancer-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nacl-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nat-gw-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/prefix-list-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-entry-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-table-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/security-group-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/subnet-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/tgw-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/vpc-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/getter-utils.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/security-group-utils.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/setter-utils.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/validation-utils.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/operations-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/organizations-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/pipeline-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/prepare-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/resource-policy-enforcement-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/security-audit-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/security-resources-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/security-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/stacks/tester-pipeline-stack.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/tester-pipeline.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/toolkit.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/lib/validate-environment-config.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/package.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/accounts-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/applications-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/bootstrap-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/customizations-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/dependencies-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/diagnostics-pack-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/finalize-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/identity-center-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/key-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/logging-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-gwlb-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-prep-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-dns-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-endpoints-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/operations-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/organizations-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/pipeline-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/prepare-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/resource-policy-enforcement-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-audit-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-resources-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/__snapshots__/tester-pipeline-stack.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/accelerator/test/accelerator-synth-stacks.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/accelerator.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/accounts-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/app-utils.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/applications-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/bootstrap-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/config-repository.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/accounts-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-connector-permissions-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-grant-permissions-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-group-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AWSQuickStart.psm1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Configure-password-policy.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Join-Domain.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/appConfigs/appA/launchTemplate/userData.sh delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/backup-policies/org-backup-policies.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/cloudformation-templates/custom-s3-bucket.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/bucket-sse-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/elb-logging-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/customizations-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/dns-firewall-domain-lists/domain-list-1.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/dynamic-partitioning/log-filters.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/firewall-rules/rules.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/global-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-policies/boundary-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/kms/kms-policy-01.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/network-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/organization-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/security-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-1.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/quarantine.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-instance-profile.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-role-policy.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/s3-encryption.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/ssm-elb-enable-logging.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/tagging-policies/org-tag-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/test-configuration/config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/default.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/ec2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/accounts-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/appConfigs/appA/launchTemplate/userData.sh delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/backup-policies/org-backup-policies.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/cloudformation-templates/custom-s3-bucket.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/bucket-sse-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/elb-logging-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/customizations-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/dns-firewall-domain-lists/domain-list-1.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/dynamic-partitioning/log-filters.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/firewall-rules/rules.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/global-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-policies/boundary-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/kms/kms-policy-01.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/network-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/organization-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/security-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-1.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/quarantine.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-instance-profile.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-role-policy.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/s3-encryption.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/ssm-elb-enable-logging.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/tagging-policies/org-tag-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/test-configuration/config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/default.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/ec2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/accounts-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-connector-permissions-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-grant-permissions-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-group-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AWSQuickStart.psm1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Configure-password-policy.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Join-Domain.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/appConfigs/appA/launchTemplate/userData.sh delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-policies/org-backup-policies.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-vault-policies/infrastructure-vault-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/access-logs-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/assets-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/central-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/elb-logs-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-access-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-central-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-elb-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/cloudformation-templates/custom-s3-bucket.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/bucket-sse-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/elb-logging-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/enable-s3-encryption.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/targetDocumentLambda.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/customizations-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-1.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-2.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dynamic-partitioning/log-filters.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/firewall-rules/rules.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/global-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/ResourceConfigurationCollectorPolicy-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/boundary-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/sso-permissionSet1-inline-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/applicationEbs.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/central-logs-bucket-key-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/elb-logs-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/full-central-logs-bucket-key-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/kms-policy-01.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/network-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/organization-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/replacements-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/apigateway.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/backup-vault.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/codeartifact-repository.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/ecr.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/efs-file-system.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/eventbridge-eventbus.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/glue-catalog.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/iam.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/kms.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/lex-bot.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/opensearch.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/s3.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/secrets-manager.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sns.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sqs.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/security-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/allow-ec2-only.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-1.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/quarantine.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-instance-profile.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-role-policy.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/s3-encryption.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/ssm-elb-enable-logging.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/waf-enable-logging.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/tagging-policies/org-tag-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/test-configuration/config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/default.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/ec2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/accounts-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/global-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/iam-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/network-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/organization-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/security-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/default.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/ec2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/accounts-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-connector-permissions-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-grant-permissions-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-group-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-setup.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AWSQuickStart.psm1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Configure-password-policy.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Join-Domain.ps1 delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/appConfigs/appA/launchTemplate/userData.sh delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-policies/org-backup-policies.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-vault-policies/infrastructure-vault-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/access-logs-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/central-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/elb-logs-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-access-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-central-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-elb-log-bucket.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/certificates/certA/cert.crt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/certificates/certA/privKey.key delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/cloudformation-templates/custom-s3-bucket.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/bucket-sse-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/elb-logging-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/enable-s3-encryption.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/targetDocumentLambda.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-detection-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-remediation-role.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled.zip delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/customizations-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-1.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-2.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dynamic-partitioning/log-filters.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/firewall-rules/rules.txt delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/global-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/ResourceConfigurationCollectorPolicy-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/boundary-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/sso-permissionSet1-inline-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/central-logs-bucket-key-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/full-central-logs-bucket-key-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/kms-policy-01.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/network-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/organization-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/replacements-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/apigateway.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/backup-vault.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/codeartifact-repository.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/ecr.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/efs-file-system.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/eventbridge-eventbus.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/glue-catalog.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/iam.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/kms.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/lex-bot.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/opensearch.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/s3.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/secrets-manager.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sns.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sqs.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/security-config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/allow-ec2-only.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/data-perimeter.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-1.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/quarantine.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-instance-profile.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-role-policy.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/s3-encryption.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/ssm-elb-enable-logging.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/waf-enable-logging.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/tagging-policies/org-tag-policy.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/test-configuration/config.yaml delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/default.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/ec2.json delete mode 100644 source/packages/@aws-accelerator/accelerator/test/customizations-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/dependencies-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/diagnostics-pack-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/finalize-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/identity-center-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/integ-test-aspects/integ.aspects.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/key-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/logging-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/network-associations-gwlb-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/network-associations-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/network-prep-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/network-vpc-dns-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/network-vpc-endpoints-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/network-vpc-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/operations-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/organizations-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/pipeline-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/prepare-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/prerequisites.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/resource-policy-enforcement-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/security-audit-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/security-resources-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/security-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/snapshot-test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/test/tester-pipeline-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/accelerator/utils/app-utils.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/utils/import-stack-resources.ts delete mode 100644 source/packages/@aws-accelerator/accelerator/utils/stack-utils.ts delete mode 100644 source/packages/@aws-accelerator/config/index.ts delete mode 100644 source/packages/@aws-accelerator/config/jest.config.js delete mode 100644 source/packages/@aws-accelerator/config/lib/accounts-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/common/index.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/common/parse.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/common/types.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/customizations-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/global-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/iam-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/accounts-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/customizations-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/global-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/iam-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/network-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/organization-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/replacements-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/models/security-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/network-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/organization-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/replacements-config.ts delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/accounts-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/customizations-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/global-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/iam-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/network-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/organization-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/replacements-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/schemas/security-config.json delete mode 100644 source/packages/@aws-accelerator/config/lib/security-config.ts delete mode 100644 source/packages/@aws-accelerator/config/package.json delete mode 100644 source/packages/@aws-accelerator/config/test/accounts-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/common-types.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/customizations-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/global-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/iam-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/network-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/organization-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/replacements-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/security-config.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/duplicate-emails.test.ts delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/accounts-config.yaml delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/global-config.yaml delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/iam-config.yaml delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/network-config.yaml delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/organization-config.yaml delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/security-config.yaml delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/default.json delete mode 100644 source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/ec2.json delete mode 100644 source/packages/@aws-accelerator/config/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/config/validator/accounts-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/common/common-validator-functions.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/customizations-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/global-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/iam-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/central-network-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/certificates-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/customer-gateways-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/dhcp-options-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/direct-connect-gateways-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/endpoint-policies-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/firewall-manager-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/gateway-load-balancers-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/ipam-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/network-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/network-firewall-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/network-validator-functions.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/prefix-list-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/route53-resolver-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/transit-gateway-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/network-config-validator/vpc-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/organization-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/replacements-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/security-config-validator.ts delete mode 100644 source/packages/@aws-accelerator/config/validator/utils/common-validator-functions.ts delete mode 100644 source/packages/@aws-accelerator/constructs/.gitignore delete mode 100644 source/packages/@aws-accelerator/constructs/.npmignore delete mode 100644 source/packages/@aws-accelerator/constructs/README.md delete mode 100644 source/packages/@aws-accelerator/constructs/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-reports-destination.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-autoscaling/create-autoscaling-group.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-budgets/budget-definition.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/certificate.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-destination.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-data-protection.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-group.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-aggregation.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/test/config-recorder.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-tags.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-cur/report-definition.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-graph-config.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-members.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/attributes.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/attributes.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-configuration.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-log-subscription.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-resolver-rule.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-active-directory.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/create-launch-template.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/vpn-types.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/customer-gateway.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/dhcp-options.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-encryption.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-asg.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/replacements.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-instance.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-pool.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-scope.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/vpc.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/route-table.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/subnet-id-lookup.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-connect.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-peering.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-route-table.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-static-route.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-endpoint.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-id-lookup.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-peering.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpn-connection.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/application-load-balancer.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/gateway-load-balancer.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/network-load-balancer.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-addresses.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/target-group.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/move-account-rule.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/new-cloudwatch-log-event-rule.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-events-log.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-firehose/cloudwatch-to-s3-firehose.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-notification-channel.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-detector-config.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-members.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-publishing-destination.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/password-policy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/service-linked-role.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-assignments.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-get-permission-set-role-arn.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-instance.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-kms/key-encryption.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-kms/key-lookup.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-export-config-classification.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-members.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-session.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/firewall.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/policy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/rule-group.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/utils.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-accounts.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/organizational-units.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy-attachment.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-organizations/validate-scp-count.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/resource-share.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/endpoint-addresses.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-domain-list.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-rule-group.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-endpoint.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-rule.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/interfaces.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/hosted-zone.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-route-53/record-set.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-encryption.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-policy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-prefix.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-replication.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/central-logs-bucket.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/public-access-block.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-members.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-organization-admin-account.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-region-aggregation.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-standards.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-service-quota/limits-service-quota-definition.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/document.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/inventory.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/policy-attachment.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-ssm-parameter.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/jest.config.js delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/static-input.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/aws-ssm/ssm-parameter-lookup.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/common-functions.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/attach-resource-based-policy.yaml delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/detect-resource-policy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/aws-resource-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/common-resources.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/allowed-only-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/apigateway-repository-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/backup-valut-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/code-artifact-repository-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/ecr-repository-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/efs-file-system-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/eventbridge-eventbus-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/iam-role-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/kms-key-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lambda-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lex-bot-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/opensearch-domain-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/pca-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/s3-bucket-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/secrets-manager-policy-streategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sns-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sqs-policy-strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/utils.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/detect-resource-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/remediate-resource-policy/index.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediate-resource-policy.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediation-ssm-document.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/lza-custom-resource.ts delete mode 100644 source/packages/@aws-accelerator/constructs/lib/lza-lambda.ts delete mode 100644 source/packages/@aws-accelerator/constructs/package.json delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-accelerator/__snapshots__/get-accelerator-metadata.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-accelerator/get-accelerator-metadata.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-create-reports-destination.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-create-reports-destination.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-autoscaling/__snapshots__/create-autoscaling-group.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-autoscaling/create-autoscaling-group.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-budgets/__snapshots__/budget-definition.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-budgets/budget-definition.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/__snapshots__/certificate.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/certificate.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudformation/__snapshots__/get-resource-type.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudformation/get-resource-type.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-destination.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-data-protection.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-group.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-logs-subscription-filter.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-destination.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-data-protection.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-group.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-aggregation.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-recorder.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-tags.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/detect-resource-policy.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/remediate-resource-policy.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/config-aggregation.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/config-recorder.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/config-tags.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/detect-resource-policy.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-configservice/remediate-resource-policy.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-controltower/__snapshots__/create-accounts.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-controltower/create-accounts.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cur/__snapshots__/report-definition.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-cur/report-definition.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-graph-config.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-members.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-detective/detective-graph-config.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-detective/detective-members.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-detective/detective-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/direct-connect-gateway.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/gateway-association.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/virtual-interface.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directconnect/direct-connect-gateway.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directconnect/gateway-association.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directconnect/virtual-interface.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-configuration.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-log-subscription.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-resolver-rule.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/share-active-directory.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-configuration.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-log-subscription.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-resolver-rule.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-directory-service/share-active-directory.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/account-warming.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/create-launch-template.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/cross-account-route.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/customer-gateway.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-security-group-rules.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-vpc.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/dhcp-options.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ebs-encryption.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-asg.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-config-replacements.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-instance.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-pool.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-scope.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-subnet.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list-route.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/route-table.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/subnet-id-lookup.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-connect.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-peering.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-prefix-list-reference.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-route-table.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-static-route.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-endpoint.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-id-lookup.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-import.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-peering.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpn-connection.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/create-launch-template.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/customer-gateway.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-security-group-rules.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-vpc.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/dhcp-options.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-asg.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-config-replacements.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-instance.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-account-warming/integ.account-warming.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-ebs-encryption/integ.ebs-encryption.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-transit-gateway/integ.transit-gateway.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-pool.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-scope.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-subnet.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/firewallUserData.txt delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/launchTemplate.yaml delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/testUserData.sh delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list-route.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/route-table.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/subnet-id-lookup.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-connect.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-peering.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-prefix-list-reference.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-route-table.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-static-route.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-endpoint.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-id-lookup.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-import.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-peering.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ec2/vpn-connection.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/application-load-balancer.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/gateway-load-balancer.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/network-load-balancer.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/nlb-addresses.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/target-group.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/application-load-balancer.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/gateway-load-balancer.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/network-load-balancer.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/nlb-addresses.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/target-group.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/move-account-rule.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/new-cloudwatch-log-event-rule.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/revert-scp-changes.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/security-hub-events-log.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/move-account-rule.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event-rule.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event/integ.new-cloudwatch-log-event.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/revert-scp-changes.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log/integ.security-hub-events-log.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/__snapshots__/cloudwatch-to-s3-firehose.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/cloudwatch-to-s3-firehose.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition1.json delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition2.json delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition3.json delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition4.json delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-notification-channel.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-fms/fms-notification-channel.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-fms/fms-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-detector-config.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-members.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-publishing-destination.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-detector-config.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-members.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-publishing-destination.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/password-policy.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/service-linked-role.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-iam/password-policy.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-iam/service-linked-role.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-assignment.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-get-permission-set-role-arn.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-instance.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-assignment.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-get-permission-set-role-arn.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-instance.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-encryption.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-lookup.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-kms/key-encryption.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-kms/key-lookup.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-export-config-classification.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-members.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-session.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/macie-export-config-classification.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/macie-members.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/macie-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-macie/macie-session.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/firewall.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/get-network-firewall-endpoint.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/policy.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/rule-group.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/firewall.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/get-network-firewall-endpoint.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/includedStacks/firewall-stack.json delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/policy.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/rule-group.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/create-accounts.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-aws-service-access.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-policy-type.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/move-accounts.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/organizational-units.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy-attachment.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/register-delegated-administrator.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/validate-scp-count.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/create-accounts.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-aws-service-access.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/integ-test-enable-policy-type/integ.enable-policy-type.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/move-accounts.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/organizational-units.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/policy-attachment.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/policy.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/register-delegated-administrator.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-organizations/validate-scp-count.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/enable-sharing-with-aws-organization.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/resource-share.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/share-subnet-tags.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/enable-sharing-with-aws-organization.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/resource-share.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags/index.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/endpoint-addresses.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-domain-list.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-rule-group.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/query-logging-config.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-endpoint.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-rule.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/endpoint-addresses.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-domain-list.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-rule-group.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/query-logging-config.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-endpoint.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-rule.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/associate-hosted-zones.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/hosted-zone.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/record-set.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53/associate-hosted-zones.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53/hosted-zone.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-route-53/record-set.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-encryption.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-policy.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-prefix.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-replication.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/central-logs-buckets.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/public-access-block.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/validate-bucket.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-encryption.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-policy.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-prefix.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-replication.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/bucket.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/central-logs-buckets.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/public-access-block.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-s3/validate-bucket.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-members.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-organization-admin-account.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-region-aggregation.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-standards.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-members.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-organization-admin-account.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-region-aggregation.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-standards.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-service-quota/__snapshots__/limits-service-quota-definition.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-service-quota/limits-service-quota-definition.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/get-portfolio-id.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/propagate-portfolio-associations.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/share-portfolio-with-org.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/get-portfolio-id.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/propagate-portfolio-associations.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/share-portfolio-with-org.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/document.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/inventory.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/policy-attachment.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/put-ssm-parameter.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/session-manager-settings.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/ssm-parameter-lookup.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/document.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/inventory.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/policy-attachment.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/put-ssm-parameter.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/session-manager-settings.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/aws-ssm/ssm-parameter-lookup.test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/snapshot-test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/unit-test/accelerator-unit-test.ts delete mode 100644 source/packages/@aws-accelerator/constructs/test/unit-test/common/resources.ts delete mode 100644 source/packages/@aws-accelerator/constructs/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/.gitignore delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/.npmignore delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/README.md delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/bin/govcloud-avm.ts delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/cdk.json delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/jest.config.js delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-product-stack.ts delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-stack.ts delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/lib/lambdas/create-govcloud-account/index.js delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/package.json delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/test/__snapshots__/govcloud-account-vending.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/test/govcloud-account-vending.test.ts delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/test/snapshot-test.ts delete mode 100644 source/packages/@aws-accelerator/govcloud-account-vending/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/installer/.npmignore delete mode 100644 source/packages/@aws-accelerator/installer/README.md delete mode 100644 source/packages/@aws-accelerator/installer/bin/installer.ts delete mode 100644 source/packages/@aws-accelerator/installer/cdk.json delete mode 100644 source/packages/@aws-accelerator/installer/index.ts delete mode 100644 source/packages/@aws-accelerator/installer/jest.config.js delete mode 100644 source/packages/@aws-accelerator/installer/lib/installer-stack.ts delete mode 100644 source/packages/@aws-accelerator/installer/lib/lambdas/update-pipeline-github-token/index.js delete mode 100644 source/packages/@aws-accelerator/installer/lib/resource-name-prefixes.ts delete mode 100644 source/packages/@aws-accelerator/installer/lib/solutions-helper.ts delete mode 100644 source/packages/@aws-accelerator/installer/lib/validate.ts delete mode 100644 source/packages/@aws-accelerator/installer/package.json delete mode 100644 source/packages/@aws-accelerator/installer/test/__snapshots__/installer.test.ts.snap delete mode 100644 source/packages/@aws-accelerator/installer/test/installer.test.ts delete mode 100644 source/packages/@aws-accelerator/installer/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/modules/README.md delete mode 100644 source/packages/@aws-accelerator/modules/bin/runner.ts delete mode 100644 source/packages/@aws-accelerator/modules/common/accelerator-config-loader.ts delete mode 100644 source/packages/@aws-accelerator/modules/common/functions.ts delete mode 100644 source/packages/@aws-accelerator/modules/common/resources.ts delete mode 100644 source/packages/@aws-accelerator/modules/jest.config.js delete mode 100644 source/packages/@aws-accelerator/modules/lib/accelerator-module.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/aws-organization/index.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/control-tower/index.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/iam-role.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/kms-key.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/organization.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/shared-account.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/control-tower/utils/resources.ts delete mode 100644 source/packages/@aws-accelerator/modules/lib/module-runner.ts delete mode 100644 source/packages/@aws-accelerator/modules/package.json delete mode 100644 source/packages/@aws-accelerator/modules/test/aws-organization/index.test.ts delete mode 100644 source/packages/@aws-accelerator/modules/test/control-tower/iam-role.test.ts delete mode 100644 source/packages/@aws-accelerator/modules/test/control-tower/kms-key.test.ts delete mode 100644 source/packages/@aws-accelerator/modules/test/control-tower/organization.test.ts delete mode 100644 source/packages/@aws-accelerator/modules/test/control-tower/shared-account.test.ts delete mode 100644 source/packages/@aws-accelerator/modules/test/utils/test-resources.ts delete mode 100644 source/packages/@aws-accelerator/modules/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/tester/.npmignore delete mode 100644 source/packages/@aws-accelerator/tester/README.md delete mode 100644 source/packages/@aws-accelerator/tester/bin/app.ts delete mode 100644 source/packages/@aws-accelerator/tester/cdk.json delete mode 100644 source/packages/@aws-accelerator/tester/index.ts delete mode 100644 source/packages/@aws-accelerator/tester/jest.config.js delete mode 100644 source/packages/@aws-accelerator/tester/lambdas/index.ts delete mode 100644 source/packages/@aws-accelerator/tester/lambdas/package.json delete mode 100644 source/packages/@aws-accelerator/tester/lambdas/test-target-functions/validate-transit-gateway.ts delete mode 100644 source/packages/@aws-accelerator/tester/lambdas/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/tester/lib/tester-stack.ts delete mode 100644 source/packages/@aws-accelerator/tester/package.json delete mode 100644 source/packages/@aws-accelerator/tester/test/configs/config.yaml delete mode 100644 source/packages/@aws-accelerator/tester/test/external-pipeline-account-tester-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/tester/test/tester-stack.test.ts delete mode 100644 source/packages/@aws-accelerator/tester/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/tools/index.ts delete mode 100644 source/packages/@aws-accelerator/tools/jest.config.js delete mode 100644 source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts delete mode 100644 source/packages/@aws-accelerator/tools/package.json delete mode 100644 source/packages/@aws-accelerator/tools/test/uninstaller.test.ts delete mode 100644 source/packages/@aws-accelerator/tools/tsconfig.json delete mode 100644 source/packages/@aws-accelerator/tools/uninstaller.ts delete mode 100644 source/packages/@aws-accelerator/utils/README.md delete mode 100644 source/packages/@aws-accelerator/utils/index.ts delete mode 100644 source/packages/@aws-accelerator/utils/jest.config.js delete mode 100644 source/packages/@aws-accelerator/utils/lib/common-functions.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/common-resources.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/common-types.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/control-tower.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/diff-stack.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/evaluate-limits.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/get-template.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/load-organization-config.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/logger.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/policy-replacements.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/regions.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/set-organizations-client.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/set-token-preferences.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/ssm-parameter-path.ts delete mode 100644 source/packages/@aws-accelerator/utils/lib/throttle.ts delete mode 100644 source/packages/@aws-accelerator/utils/package.json delete mode 100644 source/packages/@aws-accelerator/utils/test/evaluate-limits.test.ts delete mode 100644 source/packages/@aws-accelerator/utils/test/get-template.test.ts delete mode 100644 source/packages/@aws-accelerator/utils/test/integration/set-token-preferences.integ.ts delete mode 100644 source/packages/@aws-accelerator/utils/test/set-token-preferences.test.ts delete mode 100644 source/packages/@aws-accelerator/utils/test/throttle.test.ts delete mode 100644 source/packages/@aws-accelerator/utils/tsconfig.json delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/.gitignore delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/.npmignore delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/README.md delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/index.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/jest.config.js delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/lib/repository.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/lib/trail.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/package.json delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository-snapshot.test.ts.snap delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository.test.ts.snap delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/trail-snapshot.test.ts.snap delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-fine-grained.test.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-snapshot.test.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/repository.test.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/test-config.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/test/trail-snapshot.test.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-extensions/tsconfig.json delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/.gitignore delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/index.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-plugin.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-provider-source.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/backoff.ts delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/package.json delete mode 100644 source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/tsconfig.json delete mode 100755 source/run-all-tests.sh delete mode 100644 source/tsconfig.json delete mode 100644 source/yarn.lock delete mode 100644 yarn.lock diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..c5a0b2b --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,38 @@ +name: Build and Publish Docker Image + +on: + pull_request + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + REPO_NAME: ${{ github.event.repository.name }} + +jobs: + build-and-publish: + permissions: + packages: write + contents: read + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run build script + run: | + bash build.sh + + + + diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index ca36762..09058a0 --- a/.gitignore +++ b/.gitignore @@ -1,80 +1,2 @@ -*.js -*.js.map -!deployment/.typescript/cdk-solution-helper/index.js -!**/create-govcloud-account/index.js -package-lock.json -!jest.config.js -!setEnvVars.js -*.d.ts -*.tsbuildinfo -node_modules -dist -!source/.typescript/lambda/**/*.js -!source/lambda/**/*.js -source/packages/@aws-accelerator/accelerator/lib/logger.js.map -asea-assets - -### log files -*.log - -### CDK asset staging directory -.cdk.staging -cdk.out - -!**/cdk-solution-helper/index.js -!**/update-pipeline-github-token/index.js - -deployment/global-s3-assets -deployment/regional-s3-assets -open-source/ - -### Temporary folders -tmp/ -temp/ - -### yarn files -yarn-error.log - -### jest coverage folder -coverage/ - -### VisualStudioCode ### -.vscode/* - -### macOS ### -.DS_Store - -### IntelliJ ### -.idea -*.iml - -### Exclude Documentation ### -docs -!source/mkdocs/docs - -### Exclude Jest test report directory -test-reports -source/lerna-debug.log - -### Logger files -*.log - -### Debugging -development -.vscode - -### Viperlight -viperlight*.zip -/.idea/ - -### Sonarqube -.scannerwork - -*.js.map - -### Integ testing -**/integ.*.ts.snapshot -**/integ.*.ts.snapshot/** - -# ASEA Assets -asea-assets \ No newline at end of file +*.DS_Store +landing-zone-accelerator-on-aws \ No newline at end of file diff --git a/.viperlightignore b/.viperlightignore deleted file mode 100755 index 9db9e8e..0000000 --- a/.viperlightignore +++ /dev/null @@ -1,239 +0,0 @@ -Config - -# Use of opensource-codeofconduct email for amazon is expected - -CODE_OF_CONDUCT.md:4 - -cdk.out/ -^dist/ -node_modules/ -dist/ - -.html -.js -.d.ts -.snap - -yarn.lock - -# Ignore typescript documentation - -docs - -# Ignore test reports - -source/test-reports - -# Ignore runtime logs - -combined.log -error.log - -### Ignore Development which is for debugging - -development - -# Ignore reference custom rules lambda zip files - -.*/attach-ec2-instance-profile.zip -.*/ec2-instance-profile-permissions.zip -.*/targetDocumentLambda.zip -.*/custom-config-rules/cognito-password-complexity.zip -.*/custom-config-rules/emr-cluster-unencrypted-transport.zip -.*/custom-config-rules/iam-required-policy.zip -.*/custom-config-rules/rds-mssql-postgres-unencrypted-transport.zip -.*/custom-config-rules/rds-mysql-mariadb-unencrypted-transport.zip -.*/custom-config-rules/ssm-unencrypted-parameter.zip -.*/custom-config-rules/workdocs-user-domain-detector.zip -.*/custom-config-rules/rds-oracle-unencrypted-transport.zip -.*/custom-config-rules/waf-logging-enabled.zip - -# - -# Line level file ignores (keep in alphabetical order) - -# - -# ELB Root Account References https://docs.aws.amazon.com/elasticloadbalancing/latest/application/enable-access-logging.html#attach-bucket-policy - -source/packages/@aws-accelerator/utils/lib/regions.ts:64 -source/packages/@aws-accelerator/utils/lib/regions.ts:65 -source/packages/@aws-accelerator/utils/lib/regions.ts:66 -source/packages/@aws-accelerator/utils/lib/regions.ts:67 -source/packages/@aws-accelerator/utils/lib/regions.ts:68 -source/packages/@aws-accelerator/utils/lib/regions.ts:69 -source/packages/@aws-accelerator/utils/lib/regions.ts:70 -source/packages/@aws-accelerator/utils/lib/regions.ts:71 -source/packages/@aws-accelerator/utils/lib/regions.ts:72 -source/packages/@aws-accelerator/utils/lib/regions.ts:73 -source/packages/@aws-accelerator/utils/lib/regions.ts:74 -source/packages/@aws-accelerator/utils/lib/regions.ts:75 -source/packages/@aws-accelerator/utils/lib/regions.ts:76 -source/packages/@aws-accelerator/utils/lib/regions.ts:77 -source/packages/@aws-accelerator/utils/lib/regions.ts:78 -source/packages/@aws-accelerator/utils/lib/regions.ts:79 -source/packages/@aws-accelerator/utils/lib/regions.ts:80 -source/packages/@aws-accelerator/utils/lib/regions.ts:81 -source/packages/@aws-accelerator/utils/lib/regions.ts:82 -source/packages/@aws-accelerator/utils/lib/regions.ts:83 -source/packages/@aws-accelerator/utils/lib/regions.ts:84 -source/packages/@aws-accelerator/utils/lib/regions.ts:85 -source/packages/@aws-accelerator/utils/lib/regions.ts:86 -source/packages/@aws-accelerator/utils/lib/regions.ts:87 -source/packages/@aws-accelerator/utils/lib/regions.ts:88 -source/packages/@aws-accelerator/utils/lib/regions.ts:89 -source/packages/@aws-accelerator/utils/lib/regions.ts:90 -source/packages/@aws-accelerator/utils/lib/regions.ts:91 -source/packages/@aws-accelerator/utils/lib/regions.ts:92 -source/packages/@aws-accelerator/utils/lib/regions.ts:93 -source/packages/@aws-accelerator/utils/lib/regions.ts:94 -source/packages/@aws-accelerator/utils/lib/regions.ts:95 -source/packages/@aws-accelerator/utils/lib/regions.ts:96 -source/packages/@aws-accelerator/utils/lib/regions.ts:97 -source/packages/@aws-accelerator/utils/lib/regions.ts:98 -source/packages/@aws-accelerator/utils/lib/regions.ts:99 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/accelerator/lib/accelerator.ts:396 -source/packages/@aws-accelerator/accelerator/lib/accelerator.ts:397 -source/packages/@aws-accelerator/accelerator/lib/accelerator.ts:1309 -source/packages/@aws-accelerator/accelerator/lib/accelerator.ts:1310 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/config/lib/global-config.ts:980 -source/packages/@aws-accelerator/config/lib/global-config.ts:981 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/index.ts:144 -source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/index.ts:145 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/index.ts:71 -source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/index.ts:72 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/index.ts:57 -source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/index.ts:58 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/index.ts:133 -source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/index.ts:134 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1281 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1282 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1290 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1291 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1300 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1301 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1309 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1310 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1318 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1319 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1327 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1328 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1948 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:1949 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:2031 -source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts:2032 - -# Sample configuration READMEs - -reference/sample-configurations/lza-sample-config-healthcare/README.md:48 -reference/sample-configurations/lza-sample-config-us-slg-central-it/README.md:102 -reference/sample-configurations/lza-sample-config-us-slg-central-it/README.md:109 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts:47 -source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts:48 -source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts:57 -source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts:58 -source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts:64 -source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts:65 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts:167 -source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts:170 -source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts:176 -source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts:179 - -# Accelerator specific assume role declaration - -source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts:422 -source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts:425 -source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts:432 -source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts:434 -source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts:451 -source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts:453 - -# empty file to simulate certs - -source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/certificates/certA/privKey.key -source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/certificates/certA/cert.crt - -# Ignore password policy construct files - -source/packages/@aws-accelerator/constructs/lib/aws-iam/password-policy.ts -source/packages/@aws-accelerator/constructs/test/aws-iam/password-policy.test.ts - -# Assume role declaration for cross account - -source/packages/@aws-accelerator/utils/lib/get-template.ts:73 -source/packages/@aws-accelerator/utils/lib/get-template.ts:74 - -# Masked email address - -source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts:91 -source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts:269 -source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts:594 - - -# Assume role declaration for cross account - -source/packages/@aws-accelerator/utils/lib/evaluate-limits.ts:100 -source/packages/@aws-accelerator/utils/lib/evaluate-limits.ts:101 - -# Assume role declaration for cross account -source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/index.ts:268 -source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/index.ts:269 - -# Assume role declaration for cross account -source/packages/@aws-config-converter/config/global-config.ts:2305 -source/packages/@aws-config-converter/config/global-config.ts:2306 - -source/packages/@aws-config-converter/src/config/global-config.ts:2305 -source/packages/@aws-config-converter/src/config/global-config.ts:2306 - -# Assume role declaration -source/packages/@aws-accelerator/utils/test/get-template.test.ts:67 -source/packages/@aws-accelerator/utils/test/get-template.test.ts:74 diff --git a/.viperlightrc b/.viperlightrc deleted file mode 100755 index 34036e5..0000000 --- a/.viperlightrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "all": true, - "failOn": "medium" -} diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100755 index 3cab8e2..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,758 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [1.8.1] - 07-03-2024 - -### Fixed - -- fix(networking): Fix undefined condition for transitGatewayCidrBlocks property for Transit Gateway. -- bug(pipeline): Suppress mapping bucket results from build log -- fix(pipeline): "find: ‘./cdk.out’: No such file or directory" error in diff stage -- fix(config): update global config cdkoptions and control tower settings -- fix(security-hub): Fixed SecurityHub error "exceeds maximum number of members can be created in a single request" - -## [1.8.0] - 06-28-2024 - -### Added - -- feat(networking): Add transit gateway static CIDR blocks and Transit Gateway Connect attachments -- feat(autoScalingGroup): Add option to set maxInstanceLifetime property for AutoScalingGroups -- feat(securityHub): Allow SecurityHub to be enabled when AwsConfig is enabled with deploymentTargets option -- feat(customizations): Add option to set maxInstanceLifetime property for AutoScalingGroups - -### Changed - -- chore(github): added automated testing to GitHub repo for external PRs -- chore(networking): update function signatures for vpc resources in network vpc stack - -### Fixed - -- fix(organizations): throttling on ListAccounts call -- fix(control-tower): The baseline 'AWSControlTowerBaseline' cannot be enabled on renamed OUs -- fix(control-tower): change organizations module execution condition -- fix(diff): "Unexpected end of JSON input" error closes [#497](https://github.com/awslabs/landing-zone-accelerator-on-aws/issues/497) -- fix(configrule): Update config rule remediation validation when using KMSMasterKey replacement value -- fix(construct): LZA fails with AWS::Logs::LogGroup already exists - - -## [1.7.1] - 06-14-2024 - -### Added - -- feat(security-hub): enable cisv3 standard - -### Fixed - -- fix(logging): CreateServiceLinkedRole fails with LogGroup already exists -- fix(organizations): Update number of retries when using SDKV3 retry strategy -- fix(replacements): add check for undefined accountName - -## [1.7.0] - 05-31-2024 - -### Added - -- feat(control-tower): integrate lz management api -- feat(control-tower): integrate lz baseline api -- feat(control-tower): add global region into the Control Tower governed region list -- feat(network): add IPv6 support for DHCP options sets -- feat(network): Provide static IPv6 support for VPC and Subnets -- feat(network): extend IPv6 support to VPC peering, ENI, and TGW static routes -- feat(network): support vpc peering for vpcs created by vpcTemplates -- feat(network): add resolver config to vpc object -- feat(network): add tag property for interface endpoints -- feat(network): add route53 query logging and resolver endpoint handlers -- feat(logging): wildcards in dynamic partitioning -- feat(logging): add cloudwatch log group data protection policy -- feat(ssm): add targetType to documents -- feat(config): update to use json schema -- feat(replacements): add support for ACCOUNT_NAME in user data -- feat(pipeline): move assets to local directory -- feat(pipeline): validate accelerator version in build stage -- feat(regions): add ca-west-1 support -- feat(securityhub): add custom cloudwatch log group for security hub -- feat(iam): allow IAM Principal Arn as well as externalId for trust policy with IAM Roles -- feat(config): added deploymentTargets for awsConfig -- feat(guardduty): added deploymentTargets for GuardDuty - -### Changed - -- chore(lambda): upgrade to node18 runtime -- chore(sdkv3): remove references to aws-lambda -- chore(sdkv3): remove aws-lambda reference in batch enable standards -- chore(package): tree shake util import to reduce package size -- chore(docs): added docs for local zone subnet creation - -### Fixed - -- fix(replacements): retrieve mgmt credentials during every config validation -- fix(replacements): throw error for undefined replacement -- fix(replacements): updated logic for ignored replacements -- fix(replacements): updated validation pattern -- fix(replacements): updated EmailAddress type to support replacement strings -- fix(route53): revert getHostedZoneNameForService changes -- fix(identity-center): address identity center resource metadata lookup resources -- fix(identity-center): added permission to create assignments for mgmt -- fix(identity-center): removed custom resource for SSM parameters -- fix(diagnostic-pack): assume role name prefix for external deployment -- fix(logging): refactored logging of Security Hub events -- fix(diff): customizations template lookup -- fix(diff): dependent stack lookup -- fix(diff): added error logging to detect file diff errors -- fix(applications): only lookup shared subnet ids for apps in shared vpcs -- fix(toolkit): fixed deployment behavior for non-customization stage -- fix(toolkit): change asset copy files to syn -- fix(toolkit): move asset processing into main -- fix(organizations): unable to create ou with same name under different parent -- fix(organizations): delete policies based on event -- fix(organizations): Resolve issue where policies are not being updated -- fix(pipeline): send UUID on exception of central logs bucket kms key -- fix(config): Update SSM automation document match string -- fix(config): validate regions in customizations -- fix(service-quotas): check existing limit before request -- fix(idc): explicitly set management account for CDK env -- fix(move-accounts): retry strategy and increase timeout -- fix(alb): Update target types to include lambda -- fix(validation): check for duplicate emails in accounts-config -- fix(validation) Update KMS key lookup validation in security-config - -### Configuration Changes - -- chore(sample-config): remove breakglass user from the sample configurations -- chore(sample-config): add alerting for breakglass user account usage - -## [1.6.4] - 05-23-2024 - -### Added - -- feat(validation): add option to skip scp validation during prepare stage - -### Fixed - -- fix(toolkit): move custom stack queue out of toolkit - -## [1.6.3] - 05-09-2024 - -### Fixed - -- fix(organizations): ignore deletion for policies that do not exist -- fix(organizations): resolve issue where existing policies were not being updated - -## [1.6.2] - 03-27-2024 - -### Fixed - -- fix(replacements): throw error for undefined replacements -- fix(diff): dependent CloudFormation stacks not included in diff review stage -- fix(diff): customizations templates are not included in diff review stage -- fix(networking): ca-central-1 physical AZ subnet incorrect -- fix: metadata updates should execute on pipeline completion - -### Changed - -- chore(documentation): improvements to installation.md - -## [1.6.1] - 02-21-2024 - -### Fixed - -- fix(docs): resolve broken links to appropriate pages -- fix(networking): resolve duplicate construct error for endpoint security groups -- fix(networking): Fix Canada region physical AZ Subnet lookup -- fix(docs): broken links in documentation -- fix(route53): associate hosted zones timeout - -### Changed - -- chore(diagnostics-pack): cleanup - -## [1.6.0] - 01-10-2024 - -### Added - -- feat(budgets): Budget notifications accept array of email addresses -- feat(cloudwatch): provide the ability to use CloudWatch service key for LogGroup encryption -- feat(config-service): allow reference of public ssm documents -- feat(customizations): Enhance custom applications to deploy in shared VPC -- feat(firewalls): load firewall configuration from directory and support secret replacement -- feat(lambda): Allow option to use service key for AWS Lambda function environment variables encryption -- feat(networking): add support for targeting network interfaces -- feat(pipeline): use v2 tokens for sts -- feat(regions) Add il-central-1 region -- feat(replacements): added check for commented out replacements-config.yaml -- feat(replacements): extend dynamic parameter lookups -- feat(resource-policies): Support additional AWS services in resource based policies -- feat(s3): make the creation of access log buckets and S3 encryption CMK optional -- feat(ssm): add aggregated ssm region policy construct -- feat(support): add Diagnostic Pack support -- feat(validation): adds configuration validation for cmk replacement in the AWS config remediation lambda. -- feat(validation): add option to skip static validation - -### Changed - -- chore(documentation): added SBOM instructions to FAQ -- chore(documentation): added Architecture and Design Philosophy section to DEVELOPING.md -- chore(documentation): Update security hub cis 1.4.0 control examples -- chore(esbuild): update build target from node16 to node18 -- enhancement(ebs): Add deployment targets to ebs encryption options -- enhancement(iam): added prefix condition to trust policies -- enhancement(logging): Add validation for s3 resource policy attachments against public block access -- enhancement(networking): allow ability to define static replacements for EC2 firewall configurations -- enhancement(networking): allow ability to deploy EC2 firewall in RAM shared VPC account -- enhancement(pipeline): optimize CodeBuild memory for over 1000 stacks -- enhancement(validation): Managed active directory secret config account validation - -### Fixed - -- fix(aspects): saml lookup for console login to non-standard partitions fails -- fix(budget): sns topic arn for budgets notifications -- fix(config-service): modify public ssm document name validation -- fix(guardduty): export findings frequency and exclude region settings for protections are ignored -- fix(iam): update the iam role for systems manager -- fix(logging): refactored CloudWatch Log exclusion filter to use regex -- fix(networking): Allow for Target Groups with type IP to be created within VPC without targets specified -- fix(networking): added explicit dependency between vpc creation and deletion of default vpc -- fix(networking): create network interface route for firewall in shared vpc -- fix(networking): reverted role name to VpcPeeringRole -- fix(networking): share subnets with tags causes SSM parameter race condition -- fix(networking): add dependency between networkAssociations and GWLB stages -- fix(operations): account warming fails -- fix(organizations): enablePolicyType function blocks tag and backup policy creation in GovCloud -- fix(pipeline): consolidate customizations into single app -- fix(pipeline): exit pipeline upon synth failure -- fix(pipeline): evaluate limits before deploying workloads -- fix(scp): Catch PolicyNotAttachedException when SCP is allow-list strategy -- fix(scp): Add organization_enabled variable to revertSCP Lambda function -- fix(ssm): intermittent failure in OperationsStack, added missing dependency -- fix(toolkit): enforce runOrder for custom stacks in customizations stage -- fix(validation): allow OUs and accounts for MAD shares -- fix(validation): Fix max concurrent stacks validation -- fix(validation): Add validation on static parameters for policy templates -- fix(validation): validate kmsKey and subnet deployment targets - -### Configuration Changes - -- chore(aws-best-practices-tse-se): migrated to new GitHub repository -- chore(aws-best-practices-cccs-medium): migrated to new GitHub repository - -## [1.5.2] - 2023-11-15 - -### Fixed - -- fix(toolkit): enforce runOrder for custom stacks in customizations stage -- fix(aspects): saml lookup for console login to non-standard partitions fails -- fix(pipeline): exit pipeline upon synth failure -- fix(pipeline): consolidate customizations into single app - -### Changed - -- chore: update libs per audit findings - -### Configuration Changes - -- chore: migrate cccs and tse-se configuration - -## [1.5.1] - 2023-10-19 - -### Fixed - -- fix(iam): Security_Resource stack failure to assume role into suspended and un-enrolled account -- fix(identity-center): operation stack AcceleratorLambdaKey construct already exists -- fix(customizations): Could not load credentials from any providers - -## [1.5.0] - 2023-10-05 - -### Added - -- feat(backup) add Backup vault policy -- feat(config): allow users to set stack concurrency -- feat(config) M2131 WAF logging enabled -- feat(control-tower): add control tower controls -- feat(identity-center): add IdentityCenter extended permission set and assignment -- feat(logging): enable non-accelerator subscription filter destination replacement -- feat(logging): move larger CloudWatch logs payloads back into kinesis stream for re-ingestion -- feat(networking): add ability to reference dynamic configuration file replacements and license files for EC2 firewalls -- feat(networking): add dynamic EC2 firewall site-to-site VPN connections and configuration replacements -- feat(networking): add exclude regions for default VPC -- feat(networking): allow gateway and interface endpoint service customizations -- feat(networking): Created Shared ALB and supporting resources (ACM, Target Groups) -- feat(replacements): support Policy Replacements in VPC Endpoint policies -- feat(s3): allow import of S3 buckets -- feat(s3): support lifecycle rules for given prefix -- feat(security-hub): allow customers to disable Security Hub CloudWatch logs -- feat(service-catalog): support service catalog product constraints -- feat(ssm): allow SSM replacements through replacements-config.yaml -- feat(ssm): allow creation of custom SSM parameters -- feat(tags): Support Customer Tags - -### Changed - -- enhancement(docs): add script to generate versioned TypeDocs -- enhancement(iam): make managed AD resolverRuleName property optional -- enhancement(networking): add ability to define advanced VPN tunnel configuration parameters -- enhancement(networking): add ability to dynamically reference same-VPC subnets as a route destination -- enhancement(networking): add ability to reference physical IDs for subnet availability zones and for Network Firewall endpoint lookups -- enhancement(networking): add AWSManagedAggregateThreatList to supported DNS firewall managed domain lists -- enhancement(pipeline): allow synth and deploy to write to stack specific directories -- enhancement(validation): Add config rule name validation -- enhancement(validation): add name uniqueness check for IAM policies and roles -- enhancement(validation): add validation for security delegated admin account -- chore(deps): bump semver to 7.5.2 -- chore(deps): bump lerna to 7.2.0 -- chore(deps): bump proxy-agent to 6.3.0 -- chore(deps): bump aws-cdk to 2.93.0 -- chore(docs): added instructions for validations and tests -- chore(docs): added documentation for excluded regions in audit manager -- chore(docs): document dynamic partitioning format in TypeDocs -- chore(docs): remove invalid targets for routeTableEntry -- chore(docs): update TransitGatewayAttachmentConfig docs to reflect subnet update behavior -- chore(docs): updated typedoc example for budget notifications -- chore(docs): update maxAggregationInterval to match appropriate unit -- chore(docs): VPC Flow Logs central logging method indicated service-native S3 logging -- chore(logging): add accelerator roles to central bucket policy -- chore(organizations): Moved getOrgId function to config -- chore(organizations): Removed Check for Tag and Backup policies in AWS GovCloud -- chore(test): update test pipeline lambda functions to Node.js 16 runtime -- chore(utils): moved chunkArray to utils -- chore(validation): Remove let from config validation -- chore: license file updates -- chore: refactor engine to reduce complexity -- chore: updated dependencies for aws-sdk - -### Fixed - -- fix(accelerator-prefix): accelerator prefix remains hardcoded in some constructs -- fix(accounts): allow Control Tower account enrollment in GovCloud -- fix(acm): Duplicate certificate imported on CR update -- fix(applications): allow launchTemplates without userData, remove securityGroup checks -- fix(audit-manager): excluded regions list ignored in security audit stack -- fix(bootstrap): synth large environments runs out of memory -- fix(cdk): fixed promise bug for parallel deployments -- fix(cloudwatch): log replication with exclusion times out -- fix(cloudwatch): Updated logic to deploy CW log groups to OUs -- fix(customizations): make security groups optional in launch templates -- fix(deployment) - Enforce IMDS v2 for Managed Active Directory controlling EC2 instance -- fix(guardduty): create guardduty prefix in s3 destination when prefix deleted by life cycle policy -- fix(guardduty): support account create and delete actions for more than 50 accounts -- fix(guardduty): Delete publishing destination when enabled is false -- fix(guardduty): Updated createMembers function to use SDKv3 -- fix(iam): remove permissive runInstance from policy -- fix(iam): add IAM validation for roles, groups, users to Policies -- fix(iam): failed to assume role with static partition -- fix(iam): Added error handling for service linked role already existing -- fix(iam): update boundary control policy IAM get user actions -- fix(identity-center): incorrect sso regional endpoint -- fix(identity-center): fix api rate exceeded issue -- fix(limits): Allow service quota limits to be defined with regions -- fix(logging): change kms key lookup for central bucket -- fix(logging): fixed logging stack deployment order -- fix(logging): central log bucket cmk role exists when centralized logging changed -- fix(logging): enable CloudWatch logging on Firehose -- fix(logging): Add prefix creation for imported central log buckets -- fix(logging): add firehose records processor to exclusion list default -- fix(logging): compress logs within lambda and set firehose transform to uncompressed -- fix(MAD): Remove key pair from MAD instance -- fix(networking): duplicate construct error when creating GWLB endpoints in multiple VPCs under the same account -- fix(networking): fix underscore subnet names -- fix(networking): Transit gateway peering fails when multiple accepter tgw has multiple requester -- fix(networking): Fixed IPv6 validation for Prefix Lists -- fix(networking): incorrect private hosted zones created for interface endpoint services with specific API subdomains -- fix(networking): AZ not defined error when outpost subnet is configured -- fix(networking): fixed isTarget conditions for target groups -- fix(networking): update regional conditions for shared ALBs -- fix(networking): EC2 firewall config replacements incorrectly matches multiple variables on a single line -- fix(networking): EC2 firewall config replacements missing hostname lookup -- fix(organizations): load ou units asynchronously -- fix(pipeline): useManagementAccessRole optional -- fix(pipeline): time out in CodePipeline Review stage -- fix(pipeline): change assume role behavior on management account -- fix(pipeline): add nagSupression to firewall service linked role -- fix(pipeline): toolkit does not use prefix variable -- fix(replacements): Updated generatePolicyReplacements arguments to include organization id -- fix(roles): add UUID to service linked role to prevent accidental deletion -- fix(roles): make security audit stack partition aware -- fix(roles): add delay on service linked role creation -- fix(roles): create service linked role in custom resource -- fix(saml): SAML login is hardcoded -- fix(s3): access logs bucket external policy fix -- fix(scp): scpRevertChanges should use accelerator prefix -- fix(security): bring your own KMS key cannot reference service-linked roles in key policy file -- fix(security): Increased memory for GuardDuty custom resource -- fix(security): custom config rule discarding triggering resource types -- fix(ssm): PutSsmParameter upgrade from v1.3.x to v1.4.2+ fails -- fix(ssm): Added check to see if roles exist before policy attachment -- fix(sso): Added validation to flag permission set assignments created for management account -- fix(tagging): Accel-P tag is appropriately set on resources -- fix(uninstaller) detach customer policies prior to delete -- fix(validation): Add config rule name validation -- fix(validation): validate certificate deployment target -- fix(validation): undefined Config remediation target account name causes false positive - -### Configuration Changes - -- enhancement(aws-best-practices): Added README for Best Practices -- enhancement(aws-best-practices): Update Macie Permissions -- enhancement(aws-best-practices): apply SCPs to security OU -- enhancement(aws-best-practices-govcloud):update AWS GovCloud(US) configuration per FedRAMP assessment -- chore(education): migrate EDU sample configuration directory to external repository -- chore(elections): remove election sample directory -- chore(config): cccs/tse Config updates - -## [1.4.3] - 2023-07-19 - -### Fixed - -- fix(logging): cloudwatch logging, change log format in firehose to json -- fix(organizations): large OU organizations fail to load during prepare stage -- fix(networking): cannot provision new IPAM subnets when VPC has CIDRs from non-contiguous CIDR blocks -- fix(networking): Modify Transit Gateway resource lookup construct ids -- fix(validate-config): ValidateEnvironmentConfig improperly evaluates enrolled CT accounts as not enrolled - -### Configuration Changes - -- chore(aws-best-practices-tse-se): include granular billing SCP permission updates -- chore(aws-best-practices-cccs-medium): include granular billing SCP permission updates - -## [1.4.2] - 2023-06-16 - -### Fixed - -- fix(ssm): PutSsmParameters custom resource ignores new accounts -- chore(organizations): moved getOrganizationId to organizations-config -- fix(iam): service linked roles fail to create in multi-region deployment -- fix(validation): TGW route validation fails when prefixList deployment targets do not have excluded regions -- fix(validation): incorrectly configured security delegated admin account isn’t caught by validation -- fix(docs): README indicates S3 server access logs are replicated to central logs bucket - -## [1.4.1] - 2023-05-18 - -### Fixed - -- fix(route53): route53 resolver configuration depends on Network Firewall configuration -- fix(config): AWS Config recorder failure when enabled in new installation -- fix(installer): set default value for existing config repository parameters -- fix(networking): non-wildcard record missing in hosted zone for centralized S3 interface endpoints -- chore(bootstrap): update CDK version to 2.79.1 -- chore(lambda): Increased memory size of custom resources - -## [1.4.0] - 2023-05-03 - -### Added - -- feat(config): Utilize existing AWS Config Service Delivery Channel -- feat(installer): Support custom prefix for LZA resources -- feat(logging) Add S3 prefix to Config Recorder delivery channel -- feat(networking): Added deploymentTargets property for prefix lists -- feat(networking): add ability to reference same-account IPAM subnets in Security Groups and NACLs -- feat(scp): Implement SCP allow-list strategy -- feat(security-config) Add ability to define CloudWatch Log Groups -- feat(security hub): allow definition of deploymentTargets for Security Hub standards -- feat(validation): verify no ignored OU accounts are included in accounts-config file - -### Changed - -- chore(app): Update AWS CDK version to 2.70.0 -- chore(docs): adding optional flags and replacement warnings to SecurityConfig and NetworkConfig -- chore(network): network stack refactor to assist in development efforts -- enhancement(cdk): Configure CDK to use managementAccountAccessRole for all actions -- enhancement(logging): Reduce logging in firehose processor to optimize cost -- enhancement(networking): replicate Security Groups to Accounts with RAM shared subnets -- enhancement(network): make vpcFlowLogs property optional - -### Fixed - -- fix(accounts): methods used to retrieve Account IDs for Root OU targets return ignored accounts -- fix(bootstrap): Forced bootstrap update for non-centralized CDK buckets -- fix(budgets): unable to deploy AWS Budgets in Regions without vpc endpoint -- fix(ebs): EBS encryption policy references Account instead of Region -- fix(logging): remove nested looping for additional statements -- fix(networking): fix IPAM SSM lookup role name mismatch -- fix(networking): VPC-level ALBs and NLBs may reference incorrect logging bucket region -- fix(networking): replicating shared VPC/subnet tags to consumer account fails if sharing subnets from multiple owner accounts -- fix(networking): default VPCs are not deleted if the excludedAccounts property is not included -- fix(pipeline): Credential timeout for long running stages -- fix(sso): permission sets and assignments created outside of LZA cause pipeline failure -- chore(application-stack): refactor application stack to reduce complexity - -### Configuration Changes - -- feat(lza-sample-config-education): Added additional security-config controls -- feat(lza-sample-config-tse-se): Added AWS Control Tower installation instructions -- enhancement(lza-sample-config): Replace hard-coded management role in guardrail SCPs with a variable -- enhancement(lza-sample-config-cccs-medium): updated configuration to utilize accelerator prefix feature -- enhancement(lza-sample-config-tse-se): updated install instructions for GitHub personal access token - -## [1.3.2] - 2023-03-02 - -### Changed - -- enhancement(securityhub): enable nist 800-53 rev5 standard -- fix(network): allow -1:-1 port range in NACL config -- fix(validation): fix OU validation -- fix: conflicting logical id for org lookup in createIpamSsmRole - -### Configuration Changes - -- chore: update sample config to use nist 800-53 security hub standard - -## [1.3.1] - 2023-02-28 - -### Added - -- feat: add region support for me-central-1 -- feat: add region support for ap-south-2, ap-southeast-3, ap-southeast-4 -- feat: add region support for eu-central-2, eu-south-2 -- feat(controltower): create up to 5 ControlTower accounts accounts concurrently -- feat(servicecatalog): add ability to define Service Catalog portfolios and products -- feat(servicecatalog): enable principal association with existing IAM resources -- feat(servicecatalog): add option to propagate principal associations for Service Catalog portfolios -- feat(servicecatalog): add support for AWS Identity Center (formerly SSO) principal associations with Service Catalog portfolios -- feat(installer): allow installer stack to use an existing config repository -- feat(network): remove default Security Group ingress and egress rules of VPC -- feat(network): elastic IP address allocation for NAT gateway -- feat(network): add support for referencing cross-account and cross-region subnets in network ACLs -- feat(iam): allow account lookups for IAM trust policies -- feat(identitycenter): add support for overriding delegated admin in Identity Center -- feat(account): add account warming -- feat(logs): add S3 prefixes for GuardDuty, Config and ELB -- feat(customizations): add capability to pass parameters to Stacks and StackSets -- feat(config): add support to enable config aggregation -- feat(docs): added FAQ - -### Changed - -- enhancement(network): add validation for route table names -- enhancement(network): GWLB VPC type and delegated admin account validation checks -- enhancement(network): add ability to define private NAT gateway connectivity type -- enhancement(network): modularize network validation classes -- enhancement(network): improve VPC validation -- enhancement(network): improve transitGateways validation -- enhancement(network): add validation for dhcpOptions and prefixLists -- enhancement(network): improve centralNetworkServices validation -- enhancement(network): update NFW config objects for enhanced error checking -- enhancement(network): allow specification of TGW attachment options in GovCloud -- enhancement(cloudformation): upload StackSet template as asset before deployment -- enhancement(builds): disable privileged mode in Code Build -- chore(logger): move logger to accelerator utils -- chore(logger): improved logger usage -- fix(app): throw error at app-level try/catch -- fix(installer): github token not properly updating in Code Pipeline -- fix(sts): assume role plugin uses regional sts endpoints -- fix(logging): use correct region for organization trail centralized logging -- fix(network): allow TGW route table associations/propagations for separate attachments to the same VPC -- fix(network): cannot create a STRICT_ORDER rule group when using rulesFile -- fix(network): ALB/NLB bucket region correction for accessLogs -- fix(network): fix cross-account nacl entry construct name -- fix(network): fix IPAM CIDR Role -- fix(network): fix security group enum typo from MYSQL to MSSQL -- fix(network): VPC using IPAM not creating cross-region -- fix(network): S2S VPN resource reference fails in GovCloud -- fix(network): inter-region tgw peering unable to find SSM parameter in second region -- fix(securityhub): failure disabling SecurityHub standards -- fix:(guardduty): issue configuring GuardDuty for opt-in regions -- fix(uninstaller): delete termination protected config repo -- fix(uninstaller): ecr delete error handling -- fix(uninstaller): ecr cleanups with full uninstall option -- fix(logging): ignore CloudWatch logs retention when existing log retention is higher than specified in global config -- fix(logging): fix organization trail centralized logging region parameter -- fix(config): VPC route validation fails when no route specified -- fix(cloudtrail): check for cloudtrail.enable property before creating account trails - -### Configuration Changes - -- chore: consolidate finance configs to lza-sample-config -- chore: remove default limits increase from lza-sample-config config -- chore: update education config -- chore: add lifecycle rules to lza-sample-config -- fix: update the readme file name in AWS GovCloud (US) configurations -- fix: update lock down scp with control tower role -- enhancement: enabled versioning on sample template s3 buckets - -## [1.3.0] - 2022-12-21 - -### Added - -- feat(installer): add support for organization only install -- feat(network): add ability to create site-to-site vpn to tgw -- feat(network): add ability to specify file with list of suricata rules for network firewall -- feat(network): add ability to specify transit gateway peering -- feat(network): add ability to create routes for vpc peering connections -- feat(network): add ability to create and reference VGWs for VPNs, subnet routes, and gateway route table associations -- feat(network): add ability to create third-party firewalls -- feat(network): add ability to configure firewall manager -- feat(network): add ability to define ALBs and NLBs -- feat(logs): allow specification of centralized logging bucket region independent of home region -- feat(iam): add ability for IAM policy replacements -- feat(organizations): add support to ignore organizational units -- feat(organizations): add functionality to move accounts between ous (orgs-only install) -- feat(security): add centralized and configurable sns topics -- feat(security): add ability to create ACM from s3 and integrate that with ELBv2 -- feat(guardduty): enable S3 export config override -- feat(guardduty): provide functionality to enable EKS protection -- feat(ssm): enable SSM Inventory -- feat(securityhub): add support for CIS 1.4.0 controls in SecurityHub -- feat(cloudformation): Create custom CloudFormation stacks -- feat(s3): add ability to define policy statements to s3 buckets and keys -- feat(quotas): limits increase for services -- feat(sso): add ability to configure iam identity center -- feat(mad): add ability to configure managed ad -- feat(kms): allow parameter replacement in key files - -### Changed - -- enhancement(network): add use of static CIDR property for VPC templates -- enhancement(network): update Direct Connect custom resource logic to handle asynchronous actions -- enhancement(network): add Resolver endpoint name to deployed endpoints -- enhancement(logging): transform cloudwatch logs data to allow query from athena -- enhancement(organizations): move replacements to stack level -- enhancement(organizations): added checks for scps with no OUs or accounts -- enhancement(organizations): validate scp count -- enhancement(configs): add config rules and ssm auto remediation in AWS GovCloud (US) reference config -- fix(logging): update central log key lookup set log bucket to central log region -- fix(logging): move account CloudTrail S3 logs to central log bucket -- fix(organizations): add cases for null organizations and accounts in SCP -- fix(pipeline): force bootstraping to run in global region and home region if missing -- fix(ssm) limit api calls to 20 accounts per invocation -- fix(sns): update sns policies -- fix(sns): added account check on sns kms key policy -- fix(kms): add ebs kms policy for cloud9 -- fix(security): updated sns topic to use home region rather than global region - -### New Configurations - -- [US Aerospace](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/aerospace.html) -- [US State and Local Government Central IT](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/central-it.html) -- [Canadian Centre for Cyber Security (CCCS) Cloud Medium](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/canadian-centre-for-cyber-security-cccs-cloud-medium.html) -- [Trusted Secure Enclaves Sensitive Edition (TSE-SE) for National Security, Defence, and National Law Enforcement](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/trusted-secure-enclaves-sensitive-edition-for-national-security-defence-and-national-law-enforcement.html) -- [Elections](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/elections.html) -- [Finance (Tax)](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/finance-tax.html) - -## [1.2.2] - 2022-11-04 - -### Changed - -- fix(budgets) budgets causing operations stack to fail -- fix(app) wrap execution in try/catch to surface errors - -## [1.2.1] - 2022-10-13 - -### Added - -- feat(govcloud): add updated govcloud config files -- feat(govcloud): add govcloud account vending service catalog product -- feat(configs): add healthcare sample config files -- feat(configs): add support aws-cn and config files - -### Changed - -- fix(cloudwatch): change security config to support CT organization-level cloudtrail log metrics creation -- fix(logging): cloudwatch log replication in aws-us-gov partition -- fix(config): syntax error AWS GovCloud (US) config -- fix(bootstrap): cdk centralization bug fix -- fix(logging): move session manager principal access -- fix(security): update package dependencies -- fix(installer): solution-helper is emitting delete event -- fix(installer): remove installer kms key from loggroup -- fix(logging): log replication KMS created in log receiving account only -- fix(config): update network config to align with diagrams -- fix(logging): set resource dependence for accountTrail CloudWatch log group. -- fix (pipeline): fix issue with changeset creation and bootstrap - -## [1.2.0] - 2022-09-22 - -### Added - -- feat(iam): add path property to IAM RoleSets -- feat(logging): Allow configuration of CloudTrail Insights and configuration of Organization Trail -- feat(logging): Centralized Logging -- feat(network): add ability to configure Gateway Load Balancer -- feat(network): AWS Outpost Support -- feat(network): Add ability to configure Direct Connect -- feat(network): add ability to define gateway route tables -- feat(organizations): Update guardrail scp to include CloudTrail and CloudWatch Logs -- feat(partition): add support for aws-iso-b -- feat(s3): Apply Lifecycle Rules to Central Log Bucket -- feat(security): localize KMS key for every environment and service -- feat(security): Add Custom KMS CMKs -- enhancement(network): Add tags to RAM shared subnets/vpc - -### Changed - -- fix(budgets): Budget reports deployment targets bug -- fix(config): add checks for OU presence in organization config file from other config files where OUs are referred -- fix(config): Fix issues in network-config.yaml reference -- fix(iam): iam user password is not set properly -- fix(iam): Cross Account SSM parameter role creates in every region -- fix(installer): Updating git Personal Access Token not working once it's expired -- fix(installer): Fix duplicate execution of pipeline -- fix(logging):Update sessionmanager logging -- fix(logging): Existing organization trail fails in organization stack -- fix(logging) - lambdaKey lookup only in homeRegion -- fix(network): VPC templates rework -- fix(network): Fix bug with tcpFlags and source/destination bug with network firewall -- fix(network): move endpoint creation to new GWLB-specific stack -- fix(network): allow multiple VPCs to fetch a RAM share ID for the same IPAM pool or network firewall policy -- fix(network): VPC flowlog bucket exists failure when network-vpc stack updates with new vpc with s3 flow log destination -- fix(s3): added error logic for expiration values -- fix(security) AWS Macie ExportConfigClassification fails when new account added -- fix(security): Check keyManagementService for undefined -- fix(security): permissions for CrossAccountAcceleratorSsmParamAccessRole -- fix(security): When excluded in config, do not enable the automatically enabled standards for security hub -- fix(security): Fix issue with GuardDuty S3 protection not enabled in all accounts -- fix(security): Empty EBS encryption key in default config file causes pipeline failure -- fix(installer): Enable pipeline notification only for the regions that support AWS CodeStar -- chore(build): upgrade to cdk v2.28.0 - -## [1.1.0] - 2022-08-22 - -### Added - -- feat(auditmanager): add support to enable AWS Audit Manager -- feat(cloudformation): enable termination protection for all stacks -- feat(config): Add the ability to add tags to AWS Config rules -- feat(controltower): add drift detection for AWS Control Tower -- feat(detective): add support to enable Amazon Detective -- feat(installer): add ability to launch the accelerator pipeline at completion of installer pipeline -- feat(network): add managed prefix list as a destination in subnet and tgw route tables -- feat(network): add ability to define Amazon Route 53 resolver SYSTEM rules -- feat(vpc): add ability to use IPAM address pools -- enhancement: add AWS GovCloud (US) sample configuration - -### Changed - -- fix(organizations): security services Amazon GuardDuty, Amazon Macie, and AWS Security Hub failing when multiple new regions registered -- fix(organizations): fix organizational unit creation and GovCloud account add to organization -- fix(iam): fix failing pipeline tests due to service linked role descriptions -- fix(network): vpc interface endpoints workflows for GovCloud -- fix(network): outbound NACL entries causing duplicate entry error -- fix(network): Add check for route entry types in network-vpc stack -- fix(route53): add uuid to r53association custom resource to force reevaluation -- enhancement(network): make route table target property optional -- enhancement(budget): budgets scope based on account or ou -- enhancement(backup): update backup vaults to use the accelerator key -- enhancement(pipeline): move config lint checks to build stage -- enhancement(organizations): add pitr to config table -- chore(build): update to javascript sdk v2.1152.0 -- chore(build): upgrade to cdk v2.25.0 -- chore(build): update lerna to 5.1.8 -- chore(readme): update installer stack instructions -- chore(iam): Update default boundary policy to require MFA -- chore(installer): Added email constraints for installer stack - -## [1.0.1] - 2022-06-03 - -### Changed - -- fix(installer): require branch param in installer -- fix(accounts): accounts stack fails in GovCloud when enabling SERVICE_CONTROL_POLICY type -- enhancement: added more explicit error message in account config -- fix(controltower): support creation of new account in nested OU with Control Tower - -## [1.0.0] - 2022-05-23 - -### Added - -- All files, initial version diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100755 index ec98f2b..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,5 +0,0 @@ -## Code of Conduct - -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100755 index be8dbbd..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,69 +0,0 @@ -# Contributing Guidelines - -Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional -documentation, we greatly value feedback and contributions from our community. - -Please read through this document before submitting any issues or pull requests to ensure we have all the necessary -information to effectively respond to your bug report or contribution. - - -## Project Governance - -The Landing Zone Accelerator on AWS will use GitHub [Issues](https://docs.github.com/en/github/managing-your-work-on-github/creating-an-issue) and [Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) mechanisms for community engagement. AWS employees, AWS partners, customers and the general public, can create Issue(s) for the repo, such as: bugs, feature requests, or questions on the code itself. You should not communicate [security](#security-issue-notifications) issues through GitHub. - -Stakeholders are also encouraged to create Pull Requests that could potentially address issues or add functionality. Stakeholders can also express sentiments on issues such as upvote/downvote in order to influence prioritization. The Product Manager, working with all stakeholders, will use a GitHub Kanban board to publicly document the ongoing project work and prioritization using the available feedback. - -## Reporting Bugs/Feature Requests - -We welcome you to use the GitHub issue tracker to report bugs or suggest features. - -When filing an issue, please check [existing open](https://github.com/awslabs/landing-zone-accelerator-on-aws/issues), or [recently closed](https://github.com/awslabs/landing-zone-accelerator-on-aws/issues?q=is%3Aissue+is%3Aclosed), issues to make sure somebody else hasn't already -reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: - -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment - - -## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: - -1. You are working against the latest source on the *main* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. - -To send us a pull request, please: - -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure all build processes execute successfully (see README.md for additional guidance). -4. Ensure all unit, integration, and/or snapshot tests pass, as applicable (see DEVELOPING.md for additional guidance). -5. Commit to your fork using clear commit messages. -6. Send us a pull request, answering any default questions in the pull request interface. -7. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. - -GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and -[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). - - -## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/landing-zone-accelerator-on-aws/labels/help%20wanted) issues is a great place to start. - - -## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. - - -## Security issue notifications -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue. - - -## Licensing - -See the [LICENSE](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/LICENSE.txt) file for our project's licensing. We will ask you to confirm the licensing of your contribution. - -We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. - diff --git a/DEVELOPING.md b/DEVELOPING.md deleted file mode 100644 index f905f88..0000000 --- a/DEVELOPING.md +++ /dev/null @@ -1,3 +0,0 @@ -# Developing - -Details for contributing to the development of the solution have been moved to the [Developer Guide](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/developer-guide) section of our [GitHub Pages website](https://awslabs.github.io/landing-zone-accelerator-on-aws). diff --git a/Dockerfile b/Dockerfile index f6f89ac..33c736b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,17 @@ -# FROM node:18.20-alpine - -# RUN apk update && apk add --no-cache python3 py3-pip g++ make -# COPY /source /source -# WORKDIR /source -# RUN export NODE_OPTIONS=--max_old_space_size=8192 -# RUN yarn install -# RUN yarn build -# RUN yarn cache clean - -FROM --platform=linux/amd64 node:lts-alpine3.17 +FROM node:18-alpine3.20 WORKDIR /lza -COPY . . +COPY landing-zone-accelerator-on-aws . # COPY lza-validator.sh ./lza-validator.sh RUN mkdir config +RUN VERSION=$(echo $release | tr -cd [0-9]) RUN cd source \ && export NODE_OPTIONS=--max_old_space_size=8192 \ && yarn install \ && yarn build \ && yarn cache clean -CMD yarn validate-config ../config +# ENTRYPOINT ["/lza/lza-validator.sh"] +# CMD ["/lza/config/"] +CMD yarn validate config ../config diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index 460249c..0000000 --- a/FAQ.md +++ /dev/null @@ -1,3 +0,0 @@ -# Frequently Asked Questions (FAQ) - -The solution's FAQ topics have been moved to the [FAQ](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/faq) section of our [GitHub Pages website](https://awslabs.github.io/landing-zone-accelerator-on-aws). diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100755 index 19dc35b..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100755 index 2578c2e..0000000 --- a/NOTICE.txt +++ /dev/null @@ -1,125 +0,0 @@ -Landing Zone Accelerator on AWS - -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -Licensed under the Apache Version 2.0 (the "License"). You may not use this file except -in compliance with the. A copy of the is located at http://www.apache.org/licenses/ -or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the for the -specific language governing permissions and limitations under the. - -********************** -THIRD PARTY COMPONENTS -********************** -This software includes third party software subject to the following copyrights: - -@aws-accelerator/accelerator - Apache-2.0 -@aws-accelerator/config - Apache-2.0 -@aws-accelerator/constructs - Apache-2.0 -@aws-accelerator/utils - Apache-2.0 -@aws-cdk-extensions/cdk-extensions - Apache-2.0 -@aws-cdk-extensions/cdk-plugin-assume-role - Apache-2.0 -@aws-cdk/assert - Apache-2.0 -@aws-cdk/cloud-assembly - Apache-2.0 -@aws-cdk/cloud-assembly-schema - Apache-2.0 -@aws-cdk/cx-api - Apache-2.0 -@aws-cdk/region-info - Apache-2.0 -@aws-sdk/client-backup - Apache-2.0 -@aws-sdk/client-cloudformation - Apache-2.0 -@aws-sdk/client-cloudwatch-logs - Apache-2.0 -@aws-sdk/client-codebuild - Apache-2.0 -@aws-sdk/client-codecommit - Apache-2.0 -@aws-sdk/client-codepipeline - Apache-2.0 -@aws-sdk/client-config-service - Apache-2.0 -@aws-sdk/client-detective - Apache-2.0 -@aws-sdk/client-dynamodb - Apache-2.0 -@aws-sdk/client-ec2 - Apache-2.0 -@aws-sdk/client-ecr - Apache-2.0 -@aws-sdk/client-guardduty - Apache-2.0 -@aws-sdk/client-iam - Apache-2.0 -@aws-sdk/client-kms - Apache-2.0 -@aws-sdk/client-network-firewall - Apache-2.0 -@aws-sdk/client-organizations - Apache-2.0 -@aws-sdk/client-s3 - Apache-2.0 -@aws-sdk/client-s3 - Apache-2.0 -@aws-sdk/client-secrets-manager - Apache-2.0 -@aws-sdk/client-service-quotas - Apache-2.0 -@aws-sdk/client-sns - Apache-2.0 -@aws-sdk/client-ssm - - Apache-2.0 -@aws-sdk/client-sts - Apache-2.0 -@aws-sdk/lib-dynamodb - Apache-2.0 -@aws-sdk/smithy-client - Apache-2.0 -@aws-sdk/types - Apache-2.0 -@aws-sdk/util-retry - Apache-2.0 -@babel/core - MIT -@babel/preset-env - MIT -@babel/preset-typescript - MIT -@types/aws-lambda - MIT -@types/diff - MIT -@types/fs-extra - MIT -@types/jest - MIT -@types/js-yaml - MIT -@types/mri - MIT -@types/node - MIT -@types/promptly - MIT -@types/semver - MIT -@types/uuid - MIT -@typescript-eslint/eslint-plugin - MIT -@typescript-eslint/parser - BSD-2-Clause -ajv - MIT -aws-cdk - Apache-2.0 -aws-cdk-lib - Apache-2.0 -aws-lambda - MIT -aws-sdk - Apache-2.0 -aws-sdk-client-mock - MIT -cdk-assets - Apache-2.0 -cdk-nag - Apache-2.0 -change-case - MIT -chokidar - MIT -colors - MIT -constructs - Apache-2.0 -email-validator - Unlicense -es-lint - MIT -esbuild - MIT -eslint - MIT -eslint-config-prettier - MIT -eslint-config-standard - MIT -eslint-import-resolver-node - MIT -eslint-import-resolver-typescript - MIT -eslint-plugin-import - MIT -eslint-plugin-jest - MIT -eslint-plugin-license-header - MIT -eslint-plugin-node - MIT -eslint-plugin-prettier - MIT -eslint-plugin-promise - ISC -exponential-backoff - Apache-2.0 -fs-extra - MIT -hash-sum - MIT -husky - MIT -husky-init - MIT -ip-num - MIT -jest - MIT -jest-junit - Apache-2.0 -jest-sonar-reporter - Apache-2.0 -js-yaml - MIT -jsii - Apache-2.0 -jsii-pacmak - Apache-2.0 -lerna - MIT -lint-staged - MIT -minimatch - ISC -monocle-ts - MIT -mri - MIT -newtype-ts - MIT -node - MIT -pascal-case - MIT -prettier - MIT -promptly - MIT -proxy-agent - MIT -semver - MIT -tempy - MIT -ts-jest - MIT -ts-json-schema-generator - MIT -ts-node - MIT -typedoc - Apache-2.0 -typescript - Apache-2.0 -winston - MIT -yargs - MIT \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100755 index 08a2211..0000000 --- a/README.md +++ /dev/null @@ -1,128 +0,0 @@ -- [Landing Zone Accelerator on AWS](#landing-zone-accelerator-on-aws) - - [Documentation](#documentation) - - [Package Structure](#package-structure) - - [@aws-accelerator/accelerator](#aws-acceleratoraccelerator) - - [@aws-accelerator/config](#aws-acceleratorconfig) - - [@aws-accelerator/constructs](#aws-acceleratorconstructs) - - [@aws-accelerator/installer](#aws-acceleratorinstaller) - - [@aws-accelerator/modules](#aws-acceleratormodules) - - [@aws-accelerator/ui (future)](#aws-acceleratorui-future) - - [@aws-accelerator/utils](#aws-acceleratorutils) - - [@aws-cdk-extensions/cdk-extensions](#aws-cdk-extensionscdk-extensions) - - [@aws-cdk-extensions/tester](#aws-cdk-extensionstester) - -# Landing Zone Accelerator on AWS - -The Landing Zone Accelerator on AWS (LZA) is architected to align with AWS best practices -and in conformance with multiple, global compliance frameworks. We recommend customers -deploy AWS Control Tower as the foundational landing zone and enhance their landing zone -capabilities with Landing Zone Accelerator. These complementary capabilities provides a -comprehensive low-code solution across 35+ AWS services to manage and govern a multi-account -environment built to support customers with highly-regulated workloads and complex compliance -requirements. AWS Control Tower and Landing Zone Accelerator help you establish platform -readiness with security, compliance, and operational capabilities. - -Landing Zone Accelerator is provided as an open-source project that is built using the AWS -Cloud Development Kit (CDK). You install directly into your environment to -get full access to the infrastructure as code (IaC) solution. Through a -simplified set of configuration files, you are able to configure additional -functionality, controls and security services (eg. AWS Managed Config Rules, -and AWS Security Hub), manage your foundational networking topology (eg. VPCs, -Transit Gateways, and Network Firewall), and generate additional workload -accounts using the AWS Control Tower Account Factory. - -There are no additional charges or upfront commitments required to use Landing -Zone Accelerator on AWS. You pay only for AWS services enabled in order to set -up your platform and operate your controls. This solution can also support -non-standard AWS partitions, including AWS GovCloud (US), and the US Secret and -Top Secret regions. - -For an overview and solution deployment guide, please visit -[Landing Zone Accelerator on AWS](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) - ---- - -IMPORTANT: This solution will not, by itself, make you compliant. It provides -the foundational infrastructure from which additional complementary solutions -can be integrated. The information contained in this solution implementation -guide is not exhaustive. You must be review, evaluate, assess, and approve the -solution in compliance with your organization’s particular security features, -tools, and configurations. It is the sole responsibility of you and your -organization to determine which regulatory requirements are applicable and to -ensure that you comply with all requirements. Although this solution discusses -both the technical and administrative requirements, this solution does not help -you comply with the non-technical administrative requirements. - ---- - -This solution collects anonymized operational metrics to help AWS improve the -quality of features of the solution. For more information, including how to -disable this capability, please see the [implementation guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/collection-of-operational-metrics.html). - -## Documentation - -Additional documentation for the solution is hosted on [GitHub Pages](https://awslabs.github.io/landing-zone-accelerator-on-aws). We strongly recommend reviewing this resource as well as the [Implementation Guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws) for important details on deployment, customization, and maintenance of the solution and its included sample configuration files. - -> **NOTE:** The installation and configuration reference documentation that was previously hosted in this README has been migrated to the new GitHub Pages location. - -## Package Structure - -### @aws-accelerator/accelerator - -A CDK Application. The core of the accelerator solution. Contains all the stack -definitions and deployment pipeline for the accelerator. This also includes the -CDK Toolkit orchestration. - -### @aws-accelerator/config - -A pure typescript library containing modules to manage the accelerator config -files. - -### @aws-accelerator/constructs - -Contains L2/L3 constructs that have been built to support accelerator actions, -such as creating an AWS Organizational Unit or VPC. These constructs are -intended to be fully reusable, independent of the accelerator, and do not -directly access the accelerator configuration files. Example: CentralLogsBucket, -an S3 bucket that is configured with a CMK with the proper key and bucket -policies to allow services and accounts in the organization to publish logs to -the bucket. - -### @aws-accelerator/installer - -Contains a CDK Application that defines the accelerator Installer stack. - -### @aws-accelerator/modules - -Contains various accelerator modules, deployed by the solution. - -### @aws-accelerator/ui (future) - -A web application that utilizes the aws-ui-components library to present a -console to configure the accelerator - -### @aws-accelerator/utils - -Contains common utilities and types that are needed by @aws-accelerator/\* -packages. For example, throttling and backoff for AWS SDK calls - -### @aws-cdk-extensions/cdk-extensions - -Contains L2 constructs that extend the functionality of the CDK repo. The CDK -repo is an actively developed project. As the accelerator team identifies -missing features of the CDK, those features will be initially developed locally -within this repo and submitted to the CDK project as a pull request. - -### @aws-cdk-extensions/tester - -Accelerator tester CDK app. This package creates AWS Config custom rules for every test cases defined in test case manifest file. - ---- - -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - -Licensed under the Apache License Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at - - http://www.apache.org/licenses/ - -or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..e831c74 --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ +#!bin/bash + +set -e -u -o pipefail + +if [ ! -d landing-zone-accelerator-on-aws ]; then + git clone https://github.com/awslabs/landing-zone-accelerator-on-aws.git +fi +cd landing-zone-accelerator-on-aws +git checkout main +git pull +release=$(git describe --tags --abbrev=0) +git -c advice.detachedHead=false checkout $release +cd .. +echo $release +docker buildx build --platform linux/amd64 --tag cc-lza-validator:$release . +docker images diff --git a/codescan-postbuild-custom.sh b/codescan-postbuild-custom.sh deleted file mode 100644 index b410d40..0000000 --- a/codescan-postbuild-custom.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -#-------------------------------------------------------------------- -# Usage: this script must exit with a non-zero return code if the -# Viperlight scan fails. -#-------------------------------------------------------------------- -. ./codescan-funcs.sh - -echo ================================================================ -echo ====== Viperlight Script `codescan-postbuild-custom.sh` -echo ================================================================ -source_dir='./source' -solution_dir=`pwd` - -# Create a temp folder for working data -viperlight_temp=/tmp/viperlight_scan # should work in most environments -if [ -d $viperlight_temp ]; then - rm $viperlight_temp/* - rmdir $viperlight_temp -fi -mkdir $viperlight_temp - -export PATH=${PATH}:../viperlight/bin - -failed_scans=0 - -if [ .${PIPELINE_TYPE} == . ]; then - echo Pipeline type not set. Defaulting to \"feature\" - PIPELINE_TYPE='feature' -fi -echo Pipeline type is ${PIPELINE_TYPE} - -scan_npm() { - echo ----------------------------------------------------------- - echo NPM Scanning ${1} - echo ----------------------------------------------------------- - folder_path=$(dirname ${1}) - viperlight scan -t ${folder_path} -m node-npmoutdated -m node-yarnoutdated - rc=$? - if [ ${rc} -eq 0 ]; then - echo SUCCESS - elif [ ${rc} -eq 42 ]; then - echo NOTHING TO SCAN - else - echo FAILED rc=${rc} - # Disabled until cdk v2 is implemented in our solutions or - # we have a better way to ignore at the finding level - # ((failed_scans=failed_scans+1)) - fi -} - -scan_py() { - echo ----------------------------------------------------------- - echo Python Scanning $1 - echo ----------------------------------------------------------- - folder_path=`dirname $1` - viperlight scan -t $folder_path -m notice-py - rc=$? - if [ $rc -eq 0 ]; then - echo SUCCESS - elif [ $rc -eq 42 ]; then - echo NOTHING TO SCAN - else - echo FAILED rc=$rc - ((failed_scans=failed_scans+1)) - fi -} - -echo ----------------------------------------------------------- -echo Scanning all Nodejs projects -echo ----------------------------------------------------------- -find_all_node_projects ${viperlight_temp} -if [[ -e ${viperlight_temp}/scan_npm_list.txt ]]; then - while read folder - do - scan_npm $folder - done < $viperlight_temp/scan_npm_list.txt -else - echo No node projects found -fi - -echo ----------------------------------------------------------- -echo Set up python virtual environment for pubcheck scan -echo ----------------------------------------------------------- -tear_down_python_virtual_env ../ -# Create a list of python folders in ${viperlight_temp}/scan_python_lists.txt -find_all_python_requirements ${viperlight_temp} -setup_python_virtual_env ../ - -# Install modules -if [[ -e ${viperlight_temp}/scan_python_list.txt ]]; then - pip install bandit pip-licenses pip-audit -U - while read folder - do - pip install -r $folder - done < $viperlight_temp/scan_python_list.txt -else - echo No python projects found -fi - -echo ----------------------------------------------------------- -echo Running publisher checks -echo ----------------------------------------------------------- -viperlight pubcheck -# Uncomment to have failed pubcheck fail the build -# rc=$? -# if [ $rc -gt 0 ]; then -# ((failed_scans=failed_scans+1)) -# fi - -if [ ${failed_scans} == 0 ] -then - echo Scan completed successfully -else - echo ${failed_scans} scans failed. Check previous messages for findings. -fi - -# Do not fail on feature pipelines -if [ ${PIPELINE_TYPE} == 'feature' ]; then - echo ${failed_scans} scans failed in Feature pipeline. Setting exit code to passing. - failed_scans=0 -fi - -exit ${failed_scans} diff --git a/codescan-prebuild-custom.sh b/codescan-prebuild-custom.sh deleted file mode 100644 index 2199dc3..0000000 --- a/codescan-prebuild-custom.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash -#-------------------------------------------------------------------- -# Usage: this script must exit with a non-zero return code if the -# Viperlight scan fails. -#-------------------------------------------------------------------- -. ./codescan-funcs.sh - -echo ================================================================ -echo ====== Viperlight Script `basename $0` -echo ================================================================ -source_dir='./source' -solution_dir=`pwd` - -# Create a temp folder for working data -viperlight_temp=/tmp/viperlight_scan # should work in most environments -if [ -d $viperlight_temp ]; then - rm $viperlight_temp/* - rmdir $viperlight_temp -fi -mkdir $viperlight_temp - -export PATH=$PATH:../viperlight/bin - -failed_scans=0 - -if [ .${PIPELINE_TYPE} == . ]; then - echo Pipeline type not set. Defaulting to \"feature\" - PIPELINE_TYPE='feature' -fi -echo Pipeline type is ${PIPELINE_TYPE} - -scan_npm() { - echo ----------------------------------------------------------- - echo NPM / YARN Scanning $1 - echo ----------------------------------------------------------- - folder_path=`dirname $1` - viperlight scan -t $folder_path -m node-npmaudit -m node-npm6audit -m node-npmoutdated - rc=$? - if [ $rc -eq 0 ]; then - echo SUCCESS - elif [ $rc -eq 42 ]; then - echo NOTHING TO SCAN - else - echo FAILED rc=$rc - ((failed_scans=failed_scans+1)) - fi -} - -scan_py() { - echo ----------------------------------------------------------- - echo Scanning Python Environment - echo ----------------------------------------------------------- - viperlight scan -m python-piprot -m python-safety -m python-pipoutdated - rc=$? - if [ $rc -eq 0 ]; then - echo SUCCESS - elif [ $rc -eq 42 ]; then - echo NOTHING TO SCAN - else - echo FAILED rc=$rc - ((failed_scans=failed_scans+1)) - fi -} - -echo ----------------------------------------------------------- -echo Scanning all Nodejs projects -echo ----------------------------------------------------------- -find_all_node_projects ${viperlight_temp} -if [[ -e ${viperlight_temp}/scan_npm_list.txt ]]; then - while read folder - do - scan_npm $folder - done < $viperlight_temp/scan_npm_list.txt -else - echo No node projects found -fi - -echo ----------------------------------------------------------- -echo Scanning all python projects -echo ----------------------------------------------------------- -tear_down_python_virtual_env ../ -find_all_python_requirements ${viperlight_temp} -setup_python_virtual_env ../ -pip install piprot safety pip-licenses bandit pip-audit - -# Runs python scans if there is any requirements.txt -if [[ -e ${viperlight_temp}/scan_python_list.txt ]]; then - while read folder - do - echo "-----------------------------------------------------" - echo "pip install -r ${folder}" - echo "-----------------------------------------------------" - pip install -r ${folder} - done < ${viperlight_temp}/scan_python_list.txt - scan_py ${folder} -else - echo No python projects found -fi - -echo ----------------------------------------------------------- -echo Scanning everywhere else -echo ----------------------------------------------------------- -cd ${solution_dir} -viperlight scan -rc=$? -if [ $rc -gt 0 ]; then - ((failed_scans=failed_scans+1)) -fi - -if [ $failed_scans == 0 ] -then - echo Scan completed successfully -else - echo $failed_scans scans failed. Check previous messages for findings. -fi - -exit $failed_scans diff --git a/deployment/build-docs.sh b/deployment/build-docs.sh deleted file mode 100755 index be2e568..0000000 --- a/deployment/build-docs.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -eo pipefail -# This script builds TypeDoc documentation pages for each release branch -# of Landing Zone Accelerator. The documents are stored in the ./source/docs directory, -# with the latest version's doc pages being the root of the tree. -RED=$(tput setaf 1) -NORMAL=$(tput sgr0) - -function setup_docs_dir () { - ## Create docs directory if it doesn't exist - if [ ! -d "./docs" ]; then - mkdir -p ./docs - fi - - ## Remove latest directory if it exists - if [ -L "./docs/latest" ]; then - rm ./docs/latest - fi - - ## Remove latest directory if it exists - if [ -d "./docs/latest" ]; then - rm -rf ./docs/latest - fi -} - -function create_versioned_docs () { - ## Create versioned doc pages - for tag in $(git tag | grep v1) - do - if [ -d "./docs/${tag}" ]; then - echo "Skipping ${tag} because it already exists" - continue - fi - git checkout $tag - yarn install - yarn build - yarn docs --out ./docs/${tag} - yarn cleanup - yarn cache clean - # Create a symbolic link to the latest version - if [ $tag == $latest ]; then - cd ./docs - ln -s ${tag} latest - fi - done -} - -function main () { - ## See if we are in the correct working directory - if [ ! -d "./packages/@aws-accelerator" ]; then - printf "\n${RED}ERROR${NORMAL}: Please run this script from the repository's source directory\n\n" - exit 1 - fi - - ## Get latest git tag - git checkout main - latest=$(git tag --sort=-refname | awk 'BEGIN{ RS = "" ; FS = "\n" }{print $1}') - - ## Set up documentation directory - setup_docs_dir - - ## Clean up if needed - if [ -d "./node_modules" ]; then - yarn cleanup - yarn cache clean - fi - - ## Create versioned docs - create_versioned_docs -} - -main \ No newline at end of file diff --git a/deployment/build-open-source-dist.sh b/deployment/build-open-source-dist.sh deleted file mode 100755 index 0ce32a3..0000000 --- a/deployment/build-open-source-dist.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/bash -# -# This script packages your project into an open-source solution distributable -# that can be published to sites like GitHub. -# -# Important notes and prereq's: -# 1. The initialize-repo.sh script must have been run in order for this script to -# function properly. -# 2. This script should be run from the repo's /deployment folder. -# -# This script will perform the following tasks: -# 1. Remove any old dist files from previous runs. -# 2. Package the GitHub contribution and pull request templates (typically -# found in the /.github folder). -# 3. Package the /source folder along with the necessary root-level -# open-source artifacts (i.e. CHANGELOG, etc.). -# 4. Remove any unecessary artifacts from the /open-source folder (i.e. -# node_modules, package-lock.json, etc.). -# 5. Zip up the /open-source folder and create the distributable. -# 6. Remove any temporary files used for staging. -# -# Parameters: -# - solution-name: name of the solution for consistency - -# Check to see if the required parameters have been provided: -if [ -z "$1" ]; then - echo "Please provide the trademark approved solution name for the open source package." - echo "For example: ./build-open-source-dist.sh trademarked-solution-name" - exit 1 -fi - -# Get reference for all important folders -source_template_dir="$PWD" -dist_dir="$source_template_dir/open-source" -source_dir="$source_template_dir/../source" -github_dir="$source_template_dir/../.github" -deployment_dir="$source_template_dir/../deployment" -reference_dir="$source_template_dir/../reference" - -echo "------------------------------------------------------------------------------" -echo "[Init] Remove any old dist files from previous runs" -echo "------------------------------------------------------------------------------" - -echo "rm -rf $dist_dir" -rm -rf $dist_dir -echo "mkdir -p $dist_dir" -mkdir -p $dist_dir - -echo "------------------------------------------------------------------------------" -echo "[Packing] GitHub templates" -echo "------------------------------------------------------------------------------" - -echo "cp -r $github_dir $dist_dir" -cp -r $github_dir $dist_dir - -echo "------------------------------------------------------------------------------" -echo "[Packing] Source folder" -echo "------------------------------------------------------------------------------" - -echo "cp -r $source_dir $dist_dir" -cp -r $source_dir $dist_dir - - -echo "------------------------------------------------------------------------------" -echo "[Packing] Deployment folder" -echo "------------------------------------------------------------------------------" - -echo "mkdir -p $dist_dir/deployment/" -mkdir -p $dist_dir/deployment/ - -echo "cp -r $deployment_dir/cdk-solution-helper $dist_dir/deployment/cdk-solution-helper" -cp -r $deployment_dir/cdk-solution-helper $dist_dir/deployment/cdk-solution-helper - -echo "cp $deployment_dir/build-open-source-dist.sh $dist_dir/deployment/" -cp $deployment_dir/build-open-source-dist.sh $dist_dir/deployment/ - -echo "cp $deployment_dir/build-s3-dist.sh $dist_dir/deployment/" -cp $deployment_dir/build-s3-dist.sh $dist_dir/deployment/ - -echo "cp $deployment_dir/solution_config $dist_dir/deployment/" -cp $deployment_dir/solution_config $dist_dir/deployment/ - -echo "------------------------------------------------------------------------------" -echo "[Packing] Reference folder" -echo "------------------------------------------------------------------------------" - -echo "cp -r $reference_dir $dist_dir" -cp -r $reference_dir $dist_dir - -echo "------------------------------------------------------------------------------" -echo "[Packing] Files from the root level of the project" -echo "------------------------------------------------------------------------------" - -echo "cp $source_template_dir/../LICENSE.txt $dist_dir" -cp $source_template_dir/../LICENSE.txt $dist_dir - -echo "cp $source_template_dir/../NOTICE.txt $dist_dir" -cp $source_template_dir/../NOTICE.txt $dist_dir - -echo "cp $source_template_dir/../README.md $dist_dir" -cp $source_template_dir/../README.md $dist_dir - -echo "cp $source_template_dir/../CODE_OF_CONDUCT.md $dist_dir" -cp $source_template_dir/../CODE_OF_CONDUCT.md $dist_dir - -echo "cp $source_template_dir/../CONTRIBUTING.md $dist_dir" -cp $source_template_dir/../CONTRIBUTING.md $dist_dir - -echo "cp $source_template_dir/../CHANGELOG.md $dist_dir" -cp $source_template_dir/../CHANGELOG.md $dist_dir - -echo "cp $source_template_dir/../DEVELOPING.md $dist_dir" -cp $source_template_dir/../DEVELOPING.md $dist_dir - -echo "cp $source_template_dir/../FAQ.md $dist_dir" -cp $source_template_dir/../FAQ.md $dist_dir - -echo "cp $source_template_dir/../.viperlightignore $dist_dir" -cp $source_template_dir/../.viperlightignore $dist_dir - -echo "cp $source_template_dir/../.gitignore $dist_dir" -cp $source_template_dir/../.gitignore $dist_dir - -echo "------------------------------------------------------------------------------" -echo "[Packing] Clean up the open-source distributable" -echo "------------------------------------------------------------------------------" -echo $dist_dir -# General cleanup of node_modules and package-lock.json files -echo "find $dist_dir -iname "node_modules" -type d -exec rm -rf "{}" \; 2> /dev/null" -find $dist_dir -iname "node_modules" -type d -exec rm -rf "{}" \; 2> /dev/null -echo "find $dist_dir -iname "dist" -type d -exec rm -rf "{}" \; 2> /dev/null" -find $dist_dir -iname "dist" -type d -exec rm -rf "{}" \; 2> /dev/null -echo "find $dist_dir -iname "package-lock.json" -type f -exec rm -f "{}" \; 2> /dev/null" -find $dist_dir -iname "package-lock.json" -type f -exec rm -f "{}" \; 2> /dev/null -echo "find $dist_dir -iname "*.log" -type f -exec rm -f "{}" \; 2> /dev/null" -find $dist_dir -iname "*.log" -type f -exec rm -f "{}" \; 2> /dev/null - -echo "------------------------------------------------------------------------------" -echo "[Packing] Create GitHub (open-source) zip file" -echo "------------------------------------------------------------------------------" - -# Create the zip file -echo "cd $dist_dir" -cd $dist_dir -echo "zip -q -r9 ../$1.zip ." -zip -q -r9 ../$1.zip . - -# Cleanup any temporary/unnecessary files -echo "Clean up open-source folder" -echo "rm -rf * .*" -rm -rf * .* - -# Place final zip file in $dist_dir -echo "mv ../$1.zip ." -mv ../$1.zip . - -echo "Completed building $1.zip dist" diff --git a/deployment/build-s3-dist.sh b/deployment/build-s3-dist.sh deleted file mode 100755 index 7e04395..0000000 --- a/deployment/build-s3-dist.sh +++ /dev/null @@ -1,434 +0,0 @@ -#!/bin/bash -# -# This script packages your project into a solution distributable that can be -# used as an input to the solution builder validation pipeline. -# -# Important notes and prereq's: -# 1. The initialize-repo.sh script must have been run in order for this script to -# function properly. -# 2. This script should be run from the repo's /deployment folder. -# -# This script will perform the following tasks: -# 1. Remove any old dist files from previous runs. -# 2. Install dependencies for the cdk-solution-helper; responsible for -# converting standard 'cdk synth' output into solution assets. -# 3. Build and synthesize your CDK project. -# 4. Run the cdk-solution-helper on template outputs and organize -# those outputs into the /global-s3-assets folder. -# 5. Organize source code artifacts into the /regional-s3-assets folder. -# 6. Remove any temporary files used for staging. -# -# Parameters: -# - source-bucket-base-name: Name for the S3 bucket location where the template will source the Lambda -# code from. The template will append '-[region_name]' to this bucket name. -# For example: ./build-s3-dist.sh solutions v1.0.0 -# The template will then expect the source code to be located in the solutions-[region_name] bucket -# - solution-name: name of the solution for consistency -# - version-code: version of the package -#----------------------- -# Formatting -bold=$(tput bold) -normal=$(tput sgr0) -#------------------------------------------------------------------------------ -# SETTINGS -#------------------------------------------------------------------------------ -template_format="json" -run_helper="true" - -# run_helper is false for yaml - not supported -[[ $template_format == "yaml" ]] && { - run_helper="false" - echo "${bold}Solution_helper disabled:${normal} template format is yaml" -} - -#------------------------------------------------------------------------------ -# DISABLE OVERRIDE WARNINGS -#------------------------------------------------------------------------------ -# Use with care: disables the warning for overridden properties on -# AWS Solutions Constructs -export overrideWarningsEnabled=false - -#------------------------------------------------------------------------------ -# Build Functions -#------------------------------------------------------------------------------ -# Echo, execute, and check the return code for a command. Exit if rc > 0 -# ex. do_cmd npm run build -usage() -{ - echo "Usage: $0 bucket solution-name version" - echo "Please provide the base source bucket name, trademarked solution name, and version." - echo "For example: ./build-s3-dist.sh mybucket my-solution v1.0.0" - exit 1 -} - -do_cmd() -{ - echo "------ EXEC $*" - $* - rc=$? - if [ $rc -gt 0 ] - then - echo "Aborted - rc=$rc" - exit $rc - fi -} - -sedi() -{ - # cross-platform for sed -i - sed -i $* 2>/dev/null || sed -i "" $* -} - -# use sed to perform token replacement -# ex. do_replace myfile.json %%VERSION%% v1.1.1 -do_replace() -{ - replace="s/$2/$3/g" - file=$1 - do_cmd sedi $replace $file -} - -create_template_json() -{ - # Run 'cdk synth' to generate raw solution outputs - do_cmd yarn run cdk synth --output=$staging_dist_dir - - # Remove unnecessary output files - do_cmd cd $staging_dist_dir - # ignore return code - can be non-zero if any of these does not exist - rm tree.json manifest.json cdk.out - - # Move outputs from staging to template_dist_dir - echo "Move outputs from staging to template_dist_dir" - do_cmd mv $staging_dist_dir/*.template.json $template_dist_dir/ - - # Rename all *.template.json files to *.template - echo "Rename all *.template.json to *.template" - echo "copy templates and rename" - for f in $template_dist_dir/*.template.json; do - mv -- "$f" "${f%.template.json}.template" - done -} -create_template_installer_json() -{ - # Run 'cdk synth' to generate raw solution outputs - do_cmd yarn run cdk synth --output=$staging_dist_dir --context use-permission-boundary=true - do_cmd find $staging_dist_dir -name '*InstallerStack.template.json' -exec mv {} {}-CustomBp.template.json \; - - do_cmd yarn run cdk synth --output=$staging_dist_dir --context use-external-pipeline-account=true - do_cmd find $staging_dist_dir -name '*InstallerStack.template.json' -exec mv {} {}-ExternalPipeline.template.json \; - - do_cmd yarn run cdk synth --output=$staging_dist_dir - # Remove unnecessary output files - do_cmd cd $staging_dist_dir - # ignore return code - can be non-zero if any of these does not exist - rm tree.json manifest.json cdk.out - - # Move outputs from staging to template_dist_dir - echo "Move outputs from staging to template_dist_dir" - do_cmd mv $staging_dist_dir/*.template.json $template_dist_dir/ - - # Rename all *.template.json files to *.template - echo "Rename all *.template.json to *.template" - echo "copy templates and rename" - for f in $template_dist_dir/*.template.json; do - mv -- "$f" "${f%.template.json}.template" - done -} - -create_template_yaml() -{ - # Assumes current working directory is where the CDK is defined - # Output YAML - this is currently the only way to do this for multiple templates - maxrc=0 - for template in `cdk list`; do - echo Create template $template - do_cmd yarn run cdk synth $template > ${template_dist_dir}/${template}.template - if [[ $? > $maxrc ]]; then - maxrc=$? - fi - done -} - -cleanup_temporary_generted_files() -{ - echo "------------------------------------------------------------------------------" - echo "${bold}[Cleanup] Remove temporary files${normal}" - echo "------------------------------------------------------------------------------" - - # Delete generated files: CDK Consctruct typescript transcompiled generted files - do_cmd cd $source_dir/ - do_cmd yarn run cleanup:tsc - - # Delete the temporary /staging folder - do_cmd rm -rf $staging_dist_dir -} - -fn_exists() -{ - exists=`LC_ALL=C type $1` - return $? -} - -#------------------------------------------------------------------------------ -# INITIALIZATION -#------------------------------------------------------------------------------ -# solution_config must exist in the deployment folder (same folder as this -# file) . It is the definitive source for solution ID, name, and trademarked -# name. -# -# Example: -# -# SOLUTION_ID='SO0111' -# SOLUTION_NAME='AWS Security Hub Automated Response & Remediation' -# SOLUTION_TRADEMARKEDNAME='aws-security-hub-automated-response-and-remediation' -# SOLUTION_VERSION='v1.1.1' # optional -if [[ -e './solution_config' ]]; then - source ./solution_config -else - echo "solution_config is missing from the solution root." - exit 1 -fi - -if [[ -z $SOLUTION_ID ]]; then - echo "SOLUTION_ID is missing from ../solution_config" - exit 1 -else - export SOLUTION_ID -fi - -if [[ -z $SOLUTION_NAME ]]; then - echo "SOLUTION_NAME is missing from ../solution_config" - exit 1 -else - export SOLUTION_NAME -fi - -if [[ -z $SOLUTION_TRADEMARKEDNAME ]]; then - echo "SOLUTION_TRADEMARKEDNAME is missing from ../solution_config" - exit 1 -else - export SOLUTION_TRADEMARKEDNAME -fi - -if [[ ! -z $SOLUTION_VERSION ]]; then - export SOLUTION_VERSION -fi - -#------------------------------------------------------------------------------ -# Validate command line parameters -#------------------------------------------------------------------------------ -# Validate command line input - must provide bucket -[[ -z $1 ]] && { usage; exit 1; } || { SOLUTION_BUCKET=$1; } - -# Environmental variables for use in CDK -export DIST_OUTPUT_BUCKET=$SOLUTION_BUCKET - -# Version from the command line is definitive. Otherwise, use, in order of precedence: -# - SOLUTION_VERSION from solution_config -# - version.txt -# -# Note: Solutions Pipeline sends bucket, name, version. Command line expects bucket, version -# if there is a 3rd parm then version is $3, else $2 -# -# If confused, use build-s3-dist.sh -if [ ! -z $3 ]; then - version="$3" -elif [ ! -z "$2" ]; then - version=$2 -elif [ ! -z $SOLUTION_VERSION ]; then - version=$SOLUTION_VERSION -elif [ -e ../source/version.txt ]; then - version=`cat ../source/version.txt` -else - echo "Version not found. Version must be passed as an argument or in version.txt in the format vn.n.n" - exit 1 -fi -SOLUTION_VERSION=$version - -# SOLUTION_VERSION should be vn.n.n -if [[ $SOLUTION_VERSION != v* ]]; then - echo prepend v to $SOLUTION_VERSION - SOLUTION_VERSION=v${SOLUTION_VERSION} -fi - -export SOLUTION_VERSION=$version - -#----------------------------------------------------------------------------------- -# Get reference for all important folders -#----------------------------------------------------------------------------------- -template_dir="$PWD" -staging_dist_dir="$template_dir/staging" -template_dist_dir="$template_dir/global-s3-assets" -build_dist_dir="$template_dir/regional-s3-assets" -source_dir="$template_dir/../source" -installer_dir="$source_dir/packages/@aws-accelerator/installer" -govcloud_vending_dir="$source_dir/packages/@aws-accelerator/govcloud-account-vending" - -echo "------------------------------------------------------------------------------" -echo "${bold}[Init] Remove any old dist files from previous runs${normal}" -echo "------------------------------------------------------------------------------" - -do_cmd rm -rf $template_dist_dir -do_cmd mkdir -p $template_dist_dir -do_cmd rm -rf $build_dist_dir -do_cmd mkdir -p $build_dist_dir -do_cmd rm -rf $staging_dist_dir -do_cmd mkdir -p $staging_dist_dir - - -echo "------------------------------------------------------------------------------" -echo "${bold}[Init] Install dependencies for the cdk-solution-helper${normal}" -echo "------------------------------------------------------------------------------" - -do_cmd cd $template_dir/cdk-solution-helper -do_cmd npm install - -echo "------------------------------------------------------------------------------" -echo "${bold}[Synth] CDK Project${normal}" -echo "------------------------------------------------------------------------------" - -do_cmd cd $source_dir - -# Add local install to PATH -export PATH=$(yarn bin):$PATH - -do_cmd yarn install -do_cmd yarn build # build javascript from typescript to validate the code - # cdk synth doesn't always detect issues in the typescript - # and may succeed using old build files. This ensures we - # have fresh javascript from a successful build - - -echo "------------------------------------------------------------------------------" -echo "${bold}[Create] Templates${normal}" -echo "------------------------------------------------------------------------------" - -if fn_exists create_template_${template_format}; then - do_cmd cd $installer_dir - create_template_installer_${template_format} - do_cmd cd $source_dir - do_cmd cd $govcloud_vending_dir - create_template_${template_format} - do_cmd cd $source_dir -else - echo "Invalid setting for \$template_format: $template_format" - exit 255 -fi - -echo "------------------------------------------------------------------------------" -echo "${bold}[Packing] Template artifacts${normal}" -echo "------------------------------------------------------------------------------" - -# Run the helper to clean-up the templates and remove unnecessary CDK elements -echo "Run the helper to clean-up the templates and remove unnecessary CDK elements" -[[ $run_helper == "true" ]] && { - echo "node $template_dir/cdk-solution-helper/index" - node $template_dir/cdk-solution-helper/index - if [ "$?" = "1" ]; then - echo "(cdk-solution-helper) ERROR: there is likely output above." 1>&2 - exit 1 - fi -} || echo "${bold}Solution Helper skipped: ${normal}run_helper=false" - -# Find and replace bucket_name, solution_name, and version -echo "Find and replace bucket_name, solution_name, and version" -cd $template_dist_dir -do_replace "*.template" %%BUCKET_NAME%% ${SOLUTION_BUCKET} -do_replace "*.template" %%SOLUTION_NAME%% ${SOLUTION_TRADEMARKEDNAME} -do_replace "*.template" %%VERSION%% ${SOLUTION_VERSION} -do_replace "*.template" %%PRODUCT_BUCKET%% ${TEMPLATE_OUTPUT_BUCKET} - -echo "------------------------------------------------------------------------------" -echo "${bold}[Packing] Source code artifacts${normal}" -echo "------------------------------------------------------------------------------" - -# General cleanup of node_modules files -echo "find $staging_dist_dir -iname "node_modules" -type d -exec rm -rf "{}" \; 2> /dev/null" -find $staging_dist_dir -iname "node_modules" -type d -exec rm -rf "{}" \; 2> /dev/null - -# ... For each asset.* source code artifact in the temporary /staging folder... -cd $staging_dist_dir -for d in `find . -mindepth 1 -maxdepth 1 -type d`; do - - # Rename the artifact, removing the period for handler compatibility - pfname="$(basename -- $d)" - fname="$(echo $pfname | sed -e 's/\.//g')" - echo "zip -r $fname.zip $fname" - mv $d $fname - - # Build the artifacts - if test -f $fname/requirements.txt; then - echo "====================================" - echo "This is Python runtime" - echo "====================================" - cd $fname - venv_folder="./venv-prod/" - rm -fr .venv-test - rm -fr .venv-prod - echo "Initiating virtual environment" - python3 -m venv $venv_folder - source $venv_folder/bin/activate - pip3 install -q -r requirements.txt --target . - deactivate - cd $staging_dist_dir/$fname/$venv_folder/lib/python3.*/site-packages - echo "zipping the artifact" - zip -qr9 $staging_dist_dir/$fname.zip . - cd $staging_dist_dir/$fname - zip -gq $staging_dist_dir/$fname.zip *.py util/* - cd $staging_dist_dir - elif test -f $fname/package.json; then - echo "====================================" - echo "This is Node runtime" - echo "====================================" - cd $fname - echo "Clean and rebuild artifacts" - npm run clean - npm ci - if [ "$?" = "1" ]; then - echo "ERROR: Seems like package-lock.json does not exists or is out of sync with package.json. Trying npm install instead" 1>&2 - npm install - fi - cd $staging_dist_dir - # Zip the artifact - echo "zip -r $fname.zip $fname" - zip -rq $fname.zip $fname - else - echo "====================================" - echo "This is a Directory Asset" - echo "====================================" - - echo "zip -r $fname.zip $fname" - zip -rq $fname.zip $fname - fi - - if test -f $fname.zip; then - # Copy the zipped artifact from /staging to /regional-s3-assets - echo "cp $fname.zip $build_dist_dir" - cp $fname.zip $build_dist_dir - - # Remove the old, unzipped artifact from /staging - echo "rm -rf $fname" - rm -rf $fname - - # Remove the old, zipped artifact from /staging - echo "rm $fname.zip" - rm $fname.zip - # ... repeat until all source code artifacts are zipped and placed in the - # ... /regional-s3-assets folder - else - echo "ERROR: $fname.zip not found" - exit 1 - fi - -done - -# This solution does not generate any assets, need to make a file to move the -# pipeline forward -touch $build_dist_dir/temp-asset.file - -# cleanup temporary generated files that are not needed for later stages of the build pipeline -cleanup_temporary_generted_files - -# Return to original directory from when we started the build -cd $template_dir diff --git a/deployment/cdk-solution-helper/README.md b/deployment/cdk-solution-helper/README.md deleted file mode 100755 index 35ef250..0000000 --- a/deployment/cdk-solution-helper/README.md +++ /dev/null @@ -1,152 +0,0 @@ -# cdk-solution-helper - -A lightweight helper function that cleans-up synthesized templates from the AWS Cloud Development Kit (CDK) and prepares -them for use with the AWS Solutions publishing pipeline. This function performs the following tasks: - -#### Lambda function preparation - -Replaces the AssetParameter-style properties that identify source code for Lambda functions with the common variables -used by the AWS Solutions publishing pipeline. - -- `Code.S3Bucket` is assigned the `%%BUCKET_NAME%%` placeholder value. -- `Code.S3Key` is assigned the `%%SOLUTION_NAME%%`/`%%VERSION%%` placeholder value. -- `Handler` is given a prefix identical to the artifact hash, enabling the Lambda function to properly find the handler in the extracted source code package. - -These placeholders are then replaced with the appropriate values using the default find/replace operation run by the pipeline. - -Before: -``` -"examplefunction67F55935": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7S3Bucket54E71A95" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7S3VersionKeyC789D8B1" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7S3VersionKeyC789D8B1" - } - ] - } - ] - } - ] - ] - } - }, ... - Handler: "index.handler", ... -``` - -After helper function run: -``` -"examplefunction67F55935": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "%%BUCKET_NAME%%", - "S3Key": "%%SOLUTION_NAME%%/%%VERSION%%/assetd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7.zip" - }, ... - "Handler": "assetd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7/index.handler" -``` - -After build script run: -``` -"examplefunction67F55935": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "solutions", - "S3Key": "trademarked-solution-name/v1.0.0/asset.d513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7.zip" - }, ... - "Handler": "assetd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7/index.handler" -``` - -After CloudFormation deployment: -``` -"examplefunction67F55935": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "solutions-us-east-1", - "S3Key": "trademarked-solution-name/v1.0.0/asset.d513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7.zip" - }, ... - "Handler": "assetd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7/index.handler" -``` - -#### Template cleanup - -Cleans-up the parameters section and improves readability by removing the AssetParameter-style fields that would have -been used to specify Lambda source code properties. This allows solution-specific parameters to be highlighted and -removes unnecessary clutter. - -Before: -``` -"Parameters": { - "AssetParametersd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7S3Bucket54E71A95": { - "Type": "String", - "Description": "S3 bucket for asset \"d513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7\"" - }, - "AssetParametersd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7S3VersionKeyC789D8B1": { - "Type": "String", - "Description": "S3 key for asset version \"d513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7\"" - }, - "AssetParametersd513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7ArtifactHash7AA751FE": { - "Type": "String", - "Description": "Artifact hash for asset \"d513e93e266931de36e1c7e79c27b196f84ab928fce63d364d9152ca501551f7\"" - }, - "CorsEnabled" : { - "Description" : "Would you like to enable Cross-Origin Resource Sharing (CORS) for the image handler API? Select 'Yes' if so.", - "Default" : "No", - "Type" : "String", - "AllowedValues" : [ "Yes", "No" ] - }, - "CorsOrigin" : { - "Description" : "If you selected 'Yes' above, please specify an origin value here. A wildcard (*) value will support any origin.", - "Default" : "*", - "Type" : "String" - } - } - ``` - -After: -``` -"Parameters": { - "CorsEnabled" : { - "Description" : "Would you like to enable Cross-Origin Resource Sharing (CORS) for the image handler API? Select 'Yes' if so.", - "Default" : "No", - "Type" : "String", - "AllowedValues" : [ "Yes", "No" ] - }, - "CorsOrigin" : { - "Description" : "If you selected 'Yes' above, please specify an origin value here. A wildcard (*) value will support any origin.", - "Default" : "*", - "Type" : "String" - } - } - ``` - -*** -© Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/deployment/cdk-solution-helper/index.js b/deployment/cdk-solution-helper/index.js deleted file mode 100755 index 6569edf..0000000 --- a/deployment/cdk-solution-helper/index.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -// Imports -const fs = require('fs'); -const _regex = /[\w]*AssetParameters/g; //this regular express also takes into account lambda functions defined in nested stacks - -// Paths -const global_s3_assets = '../deployment/global-s3-assets'; - -// For each template in global_s3_assets ... -fs.readdirSync(global_s3_assets).forEach(file => { - if (file.includes('.product.template')) { - fs.renameSync(`${global_s3_assets}/${file}`, `${global_s3_assets}/AWSAccelerator-GovCloudAccountVendingProduct.template`); - console.log(`Renamed file: ${file} to AWSAccelerator-GovCloudAccountVendingProduct.template`) - file = 'AWSAccelerator-GovCloudAccountVendingProduct.template'; - } - - // Import and parse template file - const raw_template = fs.readFileSync(`${global_s3_assets}/${file}`); - let template = JSON.parse(raw_template); - - // Clean-up Lambda function code dependencies - const resources = (template.Resources) ? template.Resources : {}; - const lambdaFunctions = Object.keys(resources).filter(function(key) { - return resources[key].Type === "AWS::Lambda::Function"; - }); - lambdaFunctions.forEach(function(f) { - const fn = template.Resources[f]; - if (fn.Properties.Code.hasOwnProperty('S3Bucket')) { - // Set the S3 key reference - let artifactHash = Object.assign(fn.Properties.Code.S3Bucket.Ref); - artifactHash = artifactHash.replace(_regex, ''); - artifactHash = artifactHash.substring(0, artifactHash.indexOf('S3Bucket')); - const assetPath = `asset${artifactHash}`; - fn.Properties.Code.S3Key = `%%SOLUTION_NAME%%/%%VERSION%%/${assetPath}.zip`; - // Set the S3 bucket reference - fn.Properties.Code.S3Bucket = { - 'Fn::Sub': '%%BUCKET_NAME%%-${AWS::Region}' - }; - // Set the handler - const handler = fn.Properties.Handler; - fn.Properties.Handler = `${assetPath}/${handler}`; - } - }); - - // Clean-up Lambda Layer code dependencies - const lambdaLayers = Object.keys(resources).filter(function (key) { - return resources[key].Type === "AWS::Lambda::LayerVersion"; - }) - lambdaLayers.forEach(function (l) { - const layer = template.Resources[l]; - if (layer.Properties.Content.hasOwnProperty('S3Bucket')) { - let s3Key = Object.assign(layer.Properties.Content.S3Key); - layer.Properties.Content.S3Key = `%%SOLUTION_NAME%%/%%VERSION%%/${s3Key}`; - layer.Properties.Content.S3Bucket = { - 'Fn::Sub': '%%BUCKET_NAME%%-${AWS::Region}' - } - } - }) - // Lookup all service catalog products - const serviceCatalogProducts = Object.keys(resources).filter(function(key) { - return resources[key].Type === "AWS::ServiceCatalog::CloudFormationProduct"; - }); - - serviceCatalogProducts.forEach(function(f) { - const productArray = template.Resources[f].Properties.ProvisioningArtifactParameters; - let i = 0; - while (productArray[i]){ - // Filter out the AWS GovCloud Account vending machine - if (productArray[i].Description.includes('AWS GovCloud (US) Account Vending Product.')){ - - productArray[i].Info.LoadTemplateFromURL = 'https://s3.amazonaws.com/%%PRODUCT_BUCKET%%/%%SOLUTION_NAME%%/%%VERSION%%/AWSAccelerator-GovCloudAccountVendingProduct.template'; - console.log(productArray[i].Info.LoadTemplateFromURL); - i++; - } - } - }) - - // Clean-up parameters section - const parameters = (template.Parameters) ? template.Parameters : {}; - const assetParameters = Object.keys(parameters).filter(function(key) { - if (key.search(_regex) > -1) { - return true; - } - return false; - }); - assetParameters.forEach(function(a) { - template.Parameters[a] = undefined; - }); - - // Output modified template file - const output_template = JSON.stringify(template, null, 2); - fs.writeFileSync(`${global_s3_assets}/${file}`, output_template); -}); diff --git a/deployment/cdk-solution-helper/package.json b/deployment/cdk-solution-helper/package.json deleted file mode 100755 index 05f220f..0000000 --- a/deployment/cdk-solution-helper/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "cdk-solution-helper", - "version": "0.1.0", - "description": "CDK solution helper", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "dependencies": { - "fs": "0.0.1-security" - }, - "devDependencies": { - "fs": "0.0.1-security" - } -} diff --git a/deployment/solution_config b/deployment/solution_config deleted file mode 100644 index 72b8483..0000000 --- a/deployment/solution_config +++ /dev/null @@ -1,3 +0,0 @@ -SOLUTION_ID='SO0199' -SOLUTION_NAME='Landing Zone Accelerator on AWS' -SOLUTION_TRADEMARKEDNAME='landing-zone-accelerator-on-aws' diff --git a/deployment/update-snapshots.sh b/deployment/update-snapshots.sh deleted file mode 100755 index d9ddd80..0000000 --- a/deployment/update-snapshots.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -eo pipefail -# This script updates the package version for a release -# and all snapshots for Landing Zone Accelerator. Run this -# as part of the changes in the release commit. - -# Set line color values -shw_succ () { - echo $(tput bold)$(tput setaf 2) $@ $(tput sgr 0) -} -shw_info () { - echo $(tput bold)$(tput setaf 0) $@ $(tput sgr 0) -} -shw_err () { - echo $(tput bold)$(tput setaf 1) $@ $(tput sgr 0) -} - -# Extract Version from source/package.json -PACKAGE_VERSION=$(cat package.json \ - | grep version \ - | head -1 \ - | awk -F: '{ print $2 }' \ - | sed 's/[",]//g') - -# Request user to confirm the appropriate version is set before proceeding -while true; do - read -p "Is this release version correct:$PACKAGE_VERSION? [Y/N]" yn - case $yn in - [Yy]* ) shw_succ "Great! Continuing with the updating of snapshots."; break;; - [Nn]* ) shw_err "Please update source/package.json version and rerun"; exit;; - * ) shw_err "Please answer yes or no.";; - esac -done - -# Change into @aws-accelerator dir -cd "./packages/@aws-accelerator" - -# Iterate through updating snapshots with new version -for dir in `find . -depth -maxdepth 1 -mindepth 1 -type d` -do - shw_succ "Switching to $dir" - cd $dir - shw_succ "Updating snapshots for $dir" - # Updating snapshots - yarn test -u - - # Switch back to source directory - shw_succ "######################################################" - shw_succ "######################################################" - shw_succ "Snapshot Update complete for $dir" - shw_succ "######################################################" - shw_succ "######################################################" - cd .. -done - -shw_succ "Successfully updated all snapshots for version:$PACKAGE_VERSION" diff --git a/reference/sample-configurations/README.md b/reference/sample-configurations/README.md deleted file mode 100644 index 4f95b96..0000000 --- a/reference/sample-configurations/README.md +++ /dev/null @@ -1,20 +0,0 @@ -## Support for specific regions and industries - -The following configurations are provided as a starter configuration to help meet specific regional and industry requirements. These configurations are frequently updated as AWS services and features evolve. If you are adopting one of these samples, we highly recommend that you continue to review the updates to the respective sample configuration and apply the enhancements that are relevant to your environment. - -- Configurations - - [Standard Configuration](lza-sample-config/README.md) -- Regional Configurations - - [Canadian Centre for Cyber Security (CCCS) Cloud Medium](https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-cccs-medium/blob/main/README.md) - - [AWS GovCloud (US)](lza-sample-config-govcloud-us/README.md) - - National security, defence, and national law enforcement (outside the US) - - [Trusted Secure Enclaves Sensitive Edition (TSE-SE)](https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-tse-se/blob/main/README.md) -- Industry Configurations - - [Education](https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-education/blob/main/README.md) - - [Finance](lza-sample-config-finance-tax/README.md) - - [Healthcare](lza-sample-config-healthcare/README.md) - - [State and Local Government Central IT](lza-sample-config-us-slg-central-it/README.md) - -Important: These assets aren't intended to be feature complete or fully compliant, but rather to help accelerate cloud migrations and cloud refactoring efforts by entities required to meet region- or industry-specific security requirements. While these assets can help you reduce the effort required to manually build a production-ready infrastructure, you will still need to tailor them to your unique business needs. For more information about how to use AWS in compliance with specific requirements, see [AWS Compliance Programs](https://aws.amazon.com/compliance/programs/). Consult with your AWS team to understand controls to meet your requirements. - -For more information on how to manage your configuration files please see [Configuration File Best Practices](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/configuration-file-best-practices.html) diff --git a/reference/sample-configurations/lza-sample-config-cccs-medium/README.md b/reference/sample-configurations/lza-sample-config-cccs-medium/README.md deleted file mode 100644 index 47e1dbe..0000000 --- a/reference/sample-configurations/lza-sample-config-cccs-medium/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Canadian Centre for Cyber Security (CCCS) Cloud Medium (CCCS Medium) - -This sample configuration has been migrated to the GitHub repository below:
- -[Landing Zone Accelerator on AWS for Canadian Centre for Cyber Security (CCCS) Cloud Medium](https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-cccs-medium) diff --git a/reference/sample-configurations/lza-sample-config-cn/accounts-config.yaml b/reference/sample-configurations/lza-sample-config-cn/accounts-config.yaml deleted file mode 100644 index d6b3c8c..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/accounts-config.yaml +++ /dev/null @@ -1,25 +0,0 @@ -mandatoryAccounts: - - name: Management - description: >- - The management (primary) account. Do not change the name field for this mandatory account. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: >- - The log archive account. Do not change the name field for this mandatory account. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: >- - The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - - name: SharedServices - description: The SharedServices account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure diff --git a/reference/sample-configurations/lza-sample-config-cn/cfn-templates/iam-analyzer.yaml b/reference/sample-configurations/lza-sample-config-cn/cfn-templates/iam-analyzer.yaml deleted file mode 100644 index 27fbad5..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/cfn-templates/iam-analyzer.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -AWSTemplateFormatVersion: 2010-09-09 -Description: Enable IAM Access Analyzer -Resources: - Analyzer: - Type: 'AWS::AccessAnalyzer::Analyzer' - Properties: - AnalyzerName: IAM-Access-Analyzer - Type: ACCOUNT \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-detection-role.json b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-detection-role.json deleted file mode 100644 index db821df..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-detection-role.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-remediation-role.json b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-remediation-role.json deleted file mode 100644 index 52701c4..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile-remediation-role.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile.zip b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/attach-ec2-instance-profile.zip deleted file mode 100644 index 1de00bf06d02573ed8890d48da07d3c50ed785dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmWIWW@Zs#-~hs}B^f~sQ1F+70VtxtkeQc~TA`O!92&yQz<&LuYr;(+2GOMz+zgB? zq7NO*Sk6>47j6wZm?vc@u=lg5@XAw7F6KdfA(jD$8nqfZR#bH?dQic>VN-T~u0D(F zpX%e;(^4A+LblF1V4R}~FRwR-!${OMK~i1d4x^c&R)1FH?mAy5u>2*L=IrOQWFLCFF_mpk zTpd;MxlyY1^FEb*3(hJmpQ(Fzmang~No3CPSE8xhdmMJzWIeO9ULj`l%zZ=Vir-Ug zG-sSLIUaY#O6B#VyftTqBh;U2ZrLn3%Yq{>7<#t{oL2L{{QrZl|Oi`P~s=;Bwwr0?Z0pQ5l}FFIxo7y-e_C) ztOIv8-sta67UzE}-@lo|_istR^wqgHU!DDKCHIB>@pGfVfPggXy)%2BbKHt`o9Lv> z%&p`5STusw)r&cc>)P6n)4yMCU8`<))G9vtz>S1+-*^PwLjuzNxCWlNt$QrPkn5k~ z-2)fbs<1y~i85lI=$gq(BwSQb`SYegdz0$Xv-@{` zIluId1^@*MvS$DQ diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/bucket-sse-enabled-remediation-role.json b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/bucket-sse-enabled-remediation-role.json deleted file mode 100644 index c0f53cd..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/bucket-sse-enabled-remediation-role.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["s3:GetEncryptionConfiguration", "s3:PutEncryptionConfiguration"], - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-detection-role.json b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-detection-role.json deleted file mode 100644 index e612dc8..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-detection-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "iam:Get*", - "iam:List*" - ], - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json deleted file mode 100644 index 83c5a8d..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy" - ], - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions.zip b/reference/sample-configurations/lza-sample-config-cn/custom-config-rules/ec2-instance-profile-permissions.zip deleted file mode 100644 index 9d64fe687967e4fa3cf01897db666d68356cb1af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1292 zcmWIWW@Zs#-~hr?Ejd99P~gSS02EPR$jnPgtcVx~UR-qK+7Txoqf#cY`$ghLj~zm}!V^8GE_ovn z`S&}Yr>*YMNS2oi_uiay{QKF%#*aM;mL~qWF0{_!(tcyLmG37u|B5`?A6H=M;3>hm zFKxDg;B4NhE86c*^?30#K_D_)-JNkc`(v#+H`-OFR*MDNuMj!Def#yzCx3i@{qpbK zlAS+3=NXyqm~U$$)tmNs_Qe-`veoeyn(UT3Wr^l-_6u`+UzzB>;atU%nGYUZwtZTa zaly3q5~B_Of+cA?e(_KC$=ukyTW8@0F`-4ey>gQG53KcYIkJLjk?Fzf5?dHr*gLmP z(5yL8(X+rf#ka!cVOW=`^Zvvi4G*^z{)^Ciw(ydW?dGo=mmJ-!B6(*$FlJ zJdG;@4|c3c^;)v%oK}uzh{HJpCABwK4VPxz4B{yd(cHAqE$p@j&+lm-n}TaSrcTMy z60P~ocCYwdq{lY4|LwP|jy$>}(DyI-jP&Np-yaxfN$Fh-nYUu+B!(AjMc8l41Z%c_ z*{gXu`pi4m>z`Q@=IX_LC}(uJw8=YJ-{E=X!S5!H;o%Q-eN!i<*oe$yj~GN&RZ^+BwtZ_wV4Z3=r|`k_|3f z@hsx_9EFew%~`ypDUAoI!}zSW@=FD zx|Oki@h)$bDVpXfr_%no2+vuv@q6FHDz!Ue76!(X`CJ^|JP5FNo2t{v`1XM|YYu~$ z@Zlwr_ZDb8)joK0=f5sKv3qZiB&o(3FTHP_vAOH4B7@J(9e0e>{@jQ>ac_24#-keN z8Hr9m-STrE*8g08H0G0T*;V0V&B4SvPJtDi!u)yMj>Ar4xp(^M2X~c(Ze?O!&FVi-my!R0;)nGct)VBT6V_IZz1& d14|k~EMm*60B=?{ka|WS3},${ACCEL_LOOKUP::CustomerManagedPolicy:} - ResourceId: RESOURCE_ID - customRule: - lambda: - sourceFilePath: custom-config-rules/ec2-instance-profile-permissions.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::IAM::Role - remediation: - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-remediation-role.json - automatic: true - targetId: Attach-IAM-Role-Policy - targetAccountName: Audit - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: ResourceId - value: RESOURCE_ID - type: String - - name: AWSManagedPolicies - value: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - type: StringList - # - name: CustomerManagedPolicies - # value: ${ACCEL_LOOKUP::CustomerManagedPolicy:policy-00},${ACCEL_LOOKUP::CustomerManagedPolicy:policy-01} - # type: StringList - - name: accelerator-s3-bucket-server-side-encryption-enabled - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - complianceResourceTypes: - - AWS::S3::Bucket - remediation: - rolePolicyFile: custom-config-rules/bucket-sse-enabled-remediation-role.json - automatic: true - targetId: Put-S3-Encryption - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: KMSMasterKey - value: ${ACCEL_LOOKUP::KMS} - type: StringList - - name: accelerator-elb-logging-enabled - identifier: ELB_LOGGING_ENABLED - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - - AWS::ElasticLoadBalancingV2::LoadBalancer - inputParameters: - s3BucketNames: ${ACCEL_LOOKUP::Bucket:elbLogs} - remediation: - rolePolicyFile: custom-config-rules/elb-logging-enabled-remediation-role.json - automatic: true - targetId: SSM-ELB-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: LoadBalancerArn - value: RESOURCE_ID - type: String - - name: LogDestination - value: ${ACCEL_LOOKUP::Bucket:elbLogs} - type: StringList - - name: accelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: accelerator-cloudtrail-enabled - identifier: CLOUD_TRAIL_ENABLED - - name: accelerator-cloudwatch-alarm-action-check - complianceResourceTypes: - - AWS::CloudWatch::Alarm - inputParameters: - alarmActionRequired: "TRUE" - insufficientDataActionRequired: "TRUE" - okActionRequired: "FALSE" - identifier: CLOUDWATCH_ALARM_ACTION_CHECK - - name: accelerator-redshift-cluster-configuration-check - inputParameters: - clusterDbEncrypted: "TRUE" - loggingEnabled: "TRUE" - complianceResourceTypes: - - AWS::Redshift::Cluster - identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK - - name: accelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: accelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: accelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: accelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - - name: accelerator-ec2-instances-in-vpc - complianceResourceTypes: - - AWS::EC2::Instance - identifier: INSTANCES_IN_VPC - - name: accelerator-vpc-sg-open-only-to-authorized-ports - inputParameters: - authorizedTcpPorts: "443" - authorizedUdpPorts: "1020-1025" - complianceResourceTypes: - - AWS::EC2::SecurityGroup - identifier: VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS - - name: accelerator-ec2-instance-no-public-ip - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_NO_PUBLIC_IP - - name: accelerator-elasticsearch-in-vpc-only - identifier: ELASTICSEARCH_IN_VPC_ONLY - - name: accelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: accelerator-iam-no-inline-policy-check - complianceResourceTypes: - - AWS::IAM::User - - AWS::IAM::Role - - AWS::IAM::Group - identifier: IAM_NO_INLINE_POLICY_CHECK - - name: accelerator-elb-acm-certificate-required - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELBV2_ACM_CERTIFICATE_REQUIRED - - name: accelerator-alb-http-drop-invalid-header-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ALB_HTTP_DROP_INVALID_HEADER_ENABLED - - name: accelerator-elb-tls-https-listeners-only - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_TLS_HTTPS_LISTENERS_ONLY - - name: accelerator-api-gw-execution-logging-enabled - complianceResourceTypes: - - AWS::ApiGateway::Stage - - AWS::ApiGatewayV2::Stage - identifier: API_GW_EXECUTION_LOGGING_ENABLED - - name: accelerator-s3-bucket-replication-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_REPLICATION_ENABLED - - name: accelerator-cw-loggroup-retention-period-check - identifier: CW_LOGGROUP_RETENTION_PERIOD_CHECK - - name: accelerator-ec2-instance-detailed-monitoring-enabled - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: accelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: "TRUE" - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: accelerator-elb-deletion-protection-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ELB_DELETION_PROTECTION_ENABLED - - name: accelerator-cloudtrail-security-trail-enabled - identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: accelerator-elasticache-redis-cluster-automatic-backup-check - identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - - name: accelerator-s3-bucket-versioning-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_VERSIONING_ENABLED - - name: accelerator-elb-cross-zone-load-balancing-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED - - name: accelerator-iam-user-mfa-enabled - identifier: IAM_USER_MFA_ENABLED - - name: accelerator-kms-cmk-not-scheduled-for-deletion - complianceResourceTypes: - - AWS::KMS::Key - identifier: KMS_CMK_NOT_SCHEDULED_FOR_DELETION - - name: accelerator-api-gw-cache-enabled-and-encrypted - complianceResourceTypes: - - AWS::ApiGateway::Stage - identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED - - name: accelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - name: accelerator-s3-bucket-default-lock-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_DEFAULT_LOCK_ENABLED -cloudWatch: - metricSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - metrics: - # CIS 4.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}" - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: "1" - # CIS 4.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}" - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: "1" - # CIS 4.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: "1" - # CIS 4.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}" - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: "1" - # CIS 4.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}" - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: "1" - # CIS 4.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}" - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: "1" - # CIS 4.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}" - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: "1" - # CIS 4.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}" - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: "1" - # CIS 4.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}" - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: "1" - # CIS 4.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}" - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: "1" - # CIS 4.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AccountTrail - filterPattern: "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}" - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: "1" - alarmSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - alarms: - # CIS 4.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-4.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 4.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-4.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-4.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-4.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-4.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 4.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-4.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-4.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-4.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-4.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-4.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 4.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching diff --git a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-instance-profile.yaml b/reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-instance-profile.yaml deleted file mode 100644 index e9c99d8..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-instance-profile.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: Associate AWS Iam Instance Profile to EC2 Instance -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - IamInstanceProfile: - type: String - InstanceId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: associateIamProfile - action: 'aws:executeAwsApi' - inputs: - Service: ec2 - Api: associate_iam_instance_profile - IamInstanceProfile: - Name: '{{ IamInstanceProfile }}' - InstanceId: '{{ InstanceId }}' \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-role-policy.yaml b/reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-role-policy.yaml deleted file mode 100644 index 58cc557..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/attach-iam-role-policy.yaml +++ /dev/null @@ -1,50 +0,0 @@ -description: IAM Role Policy -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - ResourceId: - type: String - AWSManagedPolicies: - type: StringList - CustomerManagedPolicies: - type: StringList - minItems: 0 - default: [] - AutomationAssumeRole: - type: String -mainSteps: - - name: attachPolicy - action: 'aws:executeScript' - inputs: - Runtime: python3.7 - Handler: script_handler - Script: |- - import boto3 - partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] - iam = boto3.client("iam") - config = boto3.client("config") - def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - ) - InputPayload: - ResourceId: '{{ ResourceId }}' - AWSManagedPolicies: '{{ AWSManagedPolicies }}' - CustomerManagedPolicies: '{{ CustomerManagedPolicies }}' \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/s3-encryption.yaml b/reference/sample-configurations/lza-sample-config-cn/ssm-documents/s3-encryption.yaml deleted file mode 100644 index 79bc510..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/s3-encryption.yaml +++ /dev/null @@ -1,28 +0,0 @@ -description: Enables Encryption on S3 Bucket -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - BucketName: - type: String - description: (Required) The name of the S3 Bucket whose content will be encrypted. - KMSMasterKey: - type: String - description: (Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption. - AutomationAssumeRole: - type: String - description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. - default: "" -mainSteps: -- name: PutBucketEncryption - action: aws:executeAwsApi - inputs: - Service: s3 - Api: PutBucketEncryption - Bucket: "{{BucketName}}" - ServerSideEncryptionConfiguration: - Rules: - - - ApplyServerSideEncryptionByDefault: - SSEAlgorithm: "aws:kms" - KMSMasterKeyID: "{{KMSMasterKey}}" - isEnd: true \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/ssm-elb-enable-logging.yaml b/reference/sample-configurations/lza-sample-config-cn/ssm-documents/ssm-elb-enable-logging.yaml deleted file mode 100644 index 00af3e2..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/ssm-documents/ssm-elb-enable-logging.yaml +++ /dev/null @@ -1,44 +0,0 @@ -description: Enable logging on Elastic Load-Balancer -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - LogDestination: - type: String - LoadBalancerArn: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: getAccount - action: 'aws:executeAwsApi' - inputs: - Service: sts - Api: get_caller_identity - outputs: - - Name: Id - Selector: $.Account - Type: String - - name: getLoadBalancer - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: describe_load_balancers - LoadBalancerArns: - - '{{ LoadBalancerArn }}' - outputs: - - Name: Name - Selector: $.LoadBalancers[0].LoadBalancerName - Type: String - - name: enableLogging - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: modify_load_balancer_attributes - LoadBalancerArn: '{{ LoadBalancerArn }}' - Attributes: - - Key: access_logs.s3.enabled - Value: 'true' - - Key: access_logs.s3.bucket - Value: '{{ LogDestination }}' - - Key: access_logs.s3.prefix - Value: 'elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}' diff --git a/reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/default.json b/reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/ec2.json b/reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/reference/sample-configurations/lza-sample-config-cn/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-education/README.md b/reference/sample-configurations/lza-sample-config-education/README.md deleted file mode 100644 index a3ada81..0000000 --- a/reference/sample-configurations/lza-sample-config-education/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Landing Zone Accelerator on AWS for Education - -This sample configuration has been migrated to the GitHub repository below:
- -[Landing Zone Accelerator on AWS for Education](https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-education) \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/README.md b/reference/sample-configurations/lza-sample-config-finance-tax/README.md deleted file mode 100644 index 013dd61..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# Landing Zone Accelerator on AWS for Finance(Tax) - -## What is Finance (Tax)? - -The Finance (Tax) config aims to deploy an account structure commonly used with Tax workloads along with security controls and network configurations to secure Federal Tax Information (FTI) data. - -The config provides the capability to easily enable additional security services, such as Amazon Macie. Detective guardrails are established through the use of Security Hub and Config, which deploy managed Config rules. These rules evaluate whether the configuration settings of your AWS resources comply with common best practices, such as CIS. As an example these align with IRS cloud computing [Computer Security Evaluation Matrix (SCSEM)]. The accelerator deploys the account structure for Tax deployments along with security controls and network configurations to secure FTI data. - -This project builds around best practices suitable for any organization. This LZA is not meant to be feature complete, but rather is intended to help accelerate cloud migrations and cloud refactoring efforts by organizations focused on tax workloads. You will still need to tailor it to your unique business needs, however it does reduce much of the effort required to manually build a production-ready infrastrcture. - -## Deployment Overview - -Use the following steps to deploy the industry guidance. For detailed instructions, follow the links for each step. - -[Step 1. Launch the stack](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-1.-launch-the-stack.html) - -- Launch the AWS CloudFormation template into your AWS account. -- Review the templates parameters and enter or adjust the default values as needed. - -[Step 2. Await initial environment deployment](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-2.-await-initial-environment-deployment.html) - -- Await successful completion of `AWSAccelerator-Pipeline` pipeline. - -Step 3. Copy the configuration files - -- Clone the `aws-accelerator-config` AWS CodeCommit repository. -- Clone the [landing-zone-accelerator-on-aws](https://github.com/awslabs/landing-zone-accelerator-on-aws) repo -- Copy the configs and all the contents from the `lza-sample-config` folder under `reference/sample-configurations` to your local `aws-accelerator-config` repo. -- Copy the contents from the `lza-sample-config-finance-tax` folder under `reference/sample-configurations` to your local `aws-accelerator-config` repo. You may be prompted to over-write duplicate configs, such as `accounts-config.yaml`. - -Step 4. Update the configuration files and release a change. - -- Using the IDE of your choice. Update the variables at the top of each config, such as `homeRegion`, to match where you deployed the solution to. -- Add another region under the `enabledRegions` secion of the `global-config.yaml`, besides the `homeRegion`, that will be used for disaster recovery. -- Update the configuration files to match the desired state of your environment. Look for the `UPDATE` comments for areas requiring updates, such as e-mail addresses in your `accounts-config.yaml` -- Review the contents in the `Security Controls` section below to understand if any changes need to be made to meet organizational requirements, such as applying SCPs to the various OUs. -- Commit and push all your change to the `aws-accelerator-config` AWS CodeCommit repository. -- Release a change manually to the AWSAccelerator-Pipeline pipeline. - -### Tax Reference Architecture Diagram - -![Tax Multi-Account/Control Tower deployment](./images/tax_multi_account_ref_arch.png) - -## Organizational Structure - -Tax LZA accounts are generated and organized under Organizational Units (OUs) as follows: - -- Root - - Security - - Log Archive - - Audit - - Tax - - Production - - Control - - DevTest - - Staging - - DisasterRecovery - - Infrastructure - - Network - - Shared Services - - Management Account - -By default the Tax LZA builds the above organizational structure, with the exception of the `Management Account` and `Security` OU, which are predefined by you prior to launching the LZA. The below architecture diagram highlights the key deployments: - -- A `Tax` OU - - Contains multiple AWS accounts based on reference architecture for deploying Tax workloads - - Each contains a single VPC in `us-east-1` - - Each VPC uses a /21 CIDR block in the 10.0.0.0/8 RFC-1918 range -- An `Infrastructure` OU - - Contains one `Network` and one `SharedServices` Account - - The `Network` account also contains a Transit Gateway for infrastructure routing - - Each contains a single VPC in `us-east-2` - - The `Network` VPC uses a /22 CIDR block and SharedServices a /21 in the 10.0.0.0/8 RFC-1918 range - -There is an included [CSV Document](./ip-cidr-mapping-tax.csv) that provides a breakdown of the default CIDRs and subnet assignments. - -## Tax LZA Network Diagram - -![Tax LZA Network Diagram](./images/lza_tax_network_diagram.png) - -## Network Considerations - -- This LZA does not create DNS resources. You can create Private Hosted Zone for customer domain and associate it with workload VPCs for inter-VPC DNS resolution. Customer can also deploy inbound and outbound Route53 Resolvers for hybrid DNS resolution. - -## Security Considerations - -All of the Finance LZA configuration files initially align with the default LZA sample configuration for AWS. The Finance LZA configuration deviates from the best practices by defining the organization structure. It does this by leveraging a Tax OU with various accounts that align with the reference architecture and where FTI data will be expected to reside. - -The Finance LZA provides the capability to easily enable additional security services, such as Amazon Macie. Detective guardrails are established through the use of Security Hub and Config, which deploy managed Config rules. These rules evaluate whether the configuration settings of your AWS resources comply with common best practices, such as CIS, that align with the cloud computing SCSEM - -In the network-config.yaml file the configuration establishes the use of multiple network services such as AWS Transit Gateway. The Finance configuration leverages a central egress VPC. - -Some additional considerations: in case a 3rd party (such as AMS) manages customer Tax environments, the CMK used to encrypt FTI data needs to be created in separate customer owned account to encrypt S3, EBS volumes and FSx shares or any other data stores storing FTI data in the Tax OU. Additionally, all the resources such as EC2 instances, EBS volumes, S3 buckets storing FTI data should be assigned a unique tag for access control purposes through IAM and SCP policies. The Application Load Balancers (ALB) deployed for the Tax application also needs to be enabled for FIPS 140-2 mode, please consult with your AWS team to meet your requirements. - -### CloudWatch Metrics and Logging - -CloudWatch metrics and alarms are deployed via the Tax LZA that align to IRS Pub 1075 requirements and the related CIS controls below. These can be reviewed and modified to meet your specific needs within the [Security Configuration] file. - -- CIS 1.1 – Avoid the use of the "root" account -- CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls -- CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA -- CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account -- CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes -- CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes -- CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes -- CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways -- CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes -- CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - -### Config rules implemented - -The Tax LZA deploys AWS Config rules with automatic remidiation for ensuring S3 buckets are encrypted with the default AWS-managed SSE KMS key and ensures bucket versioning is enabled. You may want to consider customizing this rule to use a customer managed KMS key (CMK) in the event you have third parties with access to your account. Reach out to your AWS team to discuss further. - -Additionally, the LZA for Tax also deploys a rule for checking to ensure EC2 instances have instances profile permissions applied for use with AWS Systems Manager (SSM) and SSM Session Manager. - -## How to use the accelerator? - -The Tax LZA can be deployed in a greenfield Control Tower deployment where a customer first deploys AWS Control Tower. Alternatively, customers can customize the accelerator configuration to deploy in an existing Control Tower environment or deploy specific Tax accelerator configurations based on their requirements. - -### Further considerations - -Although the Tax LZA aims to be prescriptive in applying best practices for our customers, it intentionally avoids being _overly prescriptive_ out of deference to the unique realities for each individual organization. Consider the baseline Tax LZA as a good starting point related to the Tax reference architecture, but bear in mind your objectives as you begin to tailor it for your specific business requirements. From this perspective AWS provides resources that you should consult as you begin customizing your deployment of the Tax LZA: - -1. This set of configuration files was tested with AWS Control Tower verions 3.0. AWS Control Tower 3.0 supports the use of an AWS CloudTrail Organization Trail. The global-config.yaml file shows organizationTail set to false because it is enabled through the AWS Control Tower setup. -2. Refer to the [Best Practices] for Organizational Units with AWS Organizations blog post for an overview. -3. [Recommended OUs and accounts]. This section of the `Organizing your AWS Environment Using Multiple Accounts` Whitepaper discusses the deployment of specific-purpose OUs in addition to the foundational ones established by the LZA. For example, you may wish to establish a `Sandbox` OU for experimentation, a `Policy Staging` OU to safely test policy changes before deploying them more broadly, or a `Suspended` OU to hold, constrain, and eventually retire accounts that you no longer need. -4. [AWS Security Reference Architecture] (SRA). The SRA "is a holistic set of guidelines for deploying the full complement of AWS security services in a multi-account environment." This document is aimed at helping you to explore the "big picture" of AWS security and security-related services in order to determine the architectures most suited to your organization's unique security requirements. -5. Transit Gateway Flow logs are not enabled by default, work AWS team to determine if enabling TGW Flow logs help you meet your regulatory and organizational requirements. -6. The Tax LZA does not implement MFA, but does deploy complimenting controls to require MFA for certain actions. -7. [IAM Best Practices]. This best practices document for planning and managing users/roles and policies is a good source for understanding how to establish a least-privilege permission posture for controlling access to your environment. - - - -[Security Configuration]: ./security-config.yaml -[IAM Best Practices]: https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html -[Best Practices]: https://aws.amazon.com/blogs/mt/best-practices-for-organizational-units-with-aws-organizations/ -[Recommended OUs and accounts]: https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/recommended-ous-and-accounts.html -[AWS Security Reference Architecture]: https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/welcome.html -[Implementation Guide]: https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/landing-zone-accelerator-on-aws.pdf -[LZA Accelerator]: https://github.com/awslabs/landing-zone-accelerator-on-aws -[Operational Best Practices for HIPAA Security]: https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html -[VPC Sharing: key considerations and best practices]: https://aws.amazon.com/blogs/networking-and-content-delivery/vpc-sharing-key-considerations-and-best-practices/ -[Computer Security Evaluation Matrix (SCSEM)]: https://www.irs.gov/privacy-disclosure/computer-security-compliance-references-and-related-topics diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/accounts-config.yaml b/reference/sample-configurations/lza-sample-config-finance-tax/accounts-config.yaml deleted file mode 100644 index 4b23b60..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/accounts-config.yaml +++ /dev/null @@ -1,54 +0,0 @@ -mandatoryAccounts: - # We recommend you do not change mandatory account names. These are used within Landing Zone Accelerator to reference the accounts from other config files. - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - # The "name" will be used to set the AWS Account name - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: SharedServices - description: The SharedServices account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Production - description: >- - The Prod account for Tax. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tax - - name: Control - description: >- - The Control account for Tax. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tax - - name: Staging - description: >- - The Staging account for Tax. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tax - - name: DevTest - description: >- - The Dev and Test account for Tax. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tax - - name: DisasterRecovery - description: >- - The DR account for Tax. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tax - \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/images/lza_tax_network_diagram.png b/reference/sample-configurations/lza-sample-config-finance-tax/images/lza_tax_network_diagram.png deleted file mode 100644 index 8be7269c768a80c0fc7229c707fd1b5220007b1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1764995 zcmaI8xz7A<(PP~ zoC5*n-~WA?Z$>bB{B*m$+`iYUtFEf@|74qwfBSF#`+xJdzy0mss*3pf+u#1X|MPEu z`)~j6|K`7jBme9FT>dZc^#%(Pj`Q$a?bo7(Z#WE} zmJ1&d{G-T!K*|3=|BtZCjbmGcMf$APyA+)M7o38_|L&=HhW>X^=3ix^ySu@|cm~Vg5R?o@4Qk&MWpRg9l=N$0g|Ayh=WE0-c9c|LC(c?@sG6<Uxm;q9%t*SwkJ)QDnXR4BpbC{`g81KXPKo<9=wWU!_O}# z%+JGnH;g7k(Ere>d}EKFmo{^bCu<*XdCpI0Hnc|O)ld7a%a*+dr2drs1zjoUSr?Y@ z&0K6CZu`OY@3K_b!&c53RoN1HS%&L)f|&}+8EkG!Xa;*YHtL1v4kvk=?cvLVYbu>y z;&M@QmOVO-EP@vEUYpY@wQ2EgQX5Vj2N^-dc+-wtm;kchi{Bmz&#!z} zD2M;#2dB}t6kq85jN?B3WT2a%q4L8iy}T}MAzDT_vgjBIN45Kwc_lQBh8;#>H@RE$ zHHbVKMou1=y<$>H@o9XUH*=@1J*9W*SD@G?&v1cDz|hIs{ea?u=R{ZLG+Ly0OPLEw zymv12?v;B+!iMcML9*q36*&Iak@@_`4qnaG5z_c8p*3cL9+Eo@OVI{y<754m%rN=o zlO=OjW*ptJ)8CS>{oPZUo6m9?GZf|TP#AyTOWr-gG-dy}+X>s1mDPsr(|S_KemC3J z50QvUsW^@@!HH6lk(#FcerAxK=0D8#vEV8TU&*md$tzQB5_*)UO{xp=OQXG-z*@`h zWrf@uHQQtcf1uF=KNILWe!d@jjd*!wwu{bEO0$=ejy9ZH8{fNvmAwoG+w8jmd7dVl zv}|z8%-;jXBosZ}6G@*(_8tjgW`BKptb4JP9UJM}eyJDTq1oNTn01c~an0Lt-%_n-pXOcP^&IYJPWPaORAQ6f z-t9<#Uu>B$ihPdrDAn)%p1nk-$6~72yvf!Mzi4)J+urwL$ut5R(EXMl$QpRtx-)0M zCOazyS)wPei^HirDxu0e%8DQSecWqNfyQ8CpI#A--)ogVmYJnpx}cuu%EAHI>}#Z9gyl`YyZdRG1xfe)eRqoInfD9ADMs72 zRr|28qp$PwsDZt@H%|CigMKNttH;r{ER^_hr;QNZ43= zD2XMQwPz;QVh6UIs;qzS#cizZw){f6)Up+O!-4j?`)K)Muq5uD^vnA)e1Q+`-Z_hV zg`0V?hrf`@nzAlt!(MyFmEbgN$;dhE@^!$ME0F zsCN7p5SnvBA;p8KhNk9 z7)9*j?wjhH&c~IG`Bte~AJ^CS?9N>nbee{Jvp3K$ql|p0ELHlGTQtBTvh7K(BEgcX ze`Q@eugd9ng{!HcO-IpyN6Xiz$=H$b@8#?E=@`OYs)7k}#H8~ud?-0p`>3aTt9BTF z*y*o&GzVW+?q$L>XMOCR9IeH&Cdid-?FT18KElSI@2|hJ%|_iLjb!DL<5Fo-e(%-= zK4X2`NL5sa9*WSQ3S{47^~oC_#5d+>=IMXV-W%Sr7KohvG{38|@wdh(oM@*KdS#<7 zfV;?Gid_5auR;`U_&i0yhrYXg{?c=qznQ@3tgC$NVfxxK87s)qe@Vr+xR*s#VV@#@ zpu+B82`48ik-F$`iv*lZ=19-oJdW?!3zz}&j&<0B)=P4X7V8-kyB$$(s^2r1 zMHqw$cI^ZEli)r--&71u z8mbAJF@~NI`ZG##{R4^xW+z`nzUrQit}S+Z%wix@Cy&2tn1~ZXeQb7PzfI4hkc5ro0PgdNA14DnW zu|dIwi$Z?MDpq|+o*(8KKmBZa4HZRlbKE2QKySTFq`%hVX*A#c^V^Voaf^KK7vb&J zSJhQE`s<=r4J|?2^EQa+B&s@nKN+7MN&fd`=|2F=?=KJcP9EyW*UQ1K_hk9bBKZi- z0nIwl{yY-fhU7=nI{%Sp&*I%x-==5yV`w}gI_!IW;03~t^r^KO?eI#ZVWRe-rB!HEYWnBL>FClm$X1G&-%r-f8H(Z9Ua zrD=jY5dH%?LvdD<^i3`;)?)U`@s^z_os8|8XU1wx`s_TthN<4MBV0?KqN)*VQ>$xo z&5*nKf6v;V)B*WSBL-X(mUaa2~*O`+Q=?_;! zvhBlNn`ax-4BEiE{97%;R#@)QSF&V8{35d9SwAJyue`DP&7^lfjhv0ppPWT!Gi8wG z6Ald~d{CTdGKE<%U-{Og=O(%GuB7{@nM{PuH zM#3~555#9P=lDwu+J{e(qe~$J8h3cW5!8rD$n zWgza}&`qCB1{We|Gy= zCiEP5lH_ya@Xb}OH7i$r;|Y1{61r>{U|!wJsEb}Ot0^PW)g9|MTseD6a5We8pL1#x!n2WWhsW zYxF3BX`rb0RcQ}jsauv1XO_WrD`ZfQR73HWko}nOe1#9`@#f}3kuqe006Ye3A{ z+_EL#Uv=Ne<#5Po@pY#v_cuG^0D$n z?q>&EWaqV?`k&%@9{gq|JA&hj;R$+B52TcT)^aBpfL};^k175|BRBLN{)L?_(yrw* z{i7Ye+L(U>qRE@Ot|ClT%kD3x`7QYQUooQ{7{YhT8RdB6>oyNl69&< zK1@{#SAHS`8XZ)a;bjD5&28;N4FqL0x43E3;CFo;f8GkRjPrZ=$gGH8RA1C zqq4V#%{hDQl=k28wd9|co{CUB!!|hlMbsShrH2lYz66?kwRw$Wm3nta{lx7a`?i*h zCXpB8-`ofQjruGVwq zlXATmjdb3-N%7@I==)S!npCo1%9^@}5Y*KmvIMiO+yu(gSg=>%!VSLCq{Y;%SMgh< zN^t$BAt3!efWWo3pjP&{5j$rooOKRFE5yeaZPW-|)EY}0O{PDZf9!Y2EsihhM@u&1 zo*z@V=g(VcFOnT%;OuB)@2Qlgl!K%V0o_<*ky664zj^gYe}Czh&zQ3ZOoImkhEI}N zKO3PR$WW+w;>7Mf@^y17Q>43P%*%k9>G6Z%d{_js(w2^h54UCiCS6kk2#bim=-P9_ z_d0j$7c5&o1c})ZX2cG6=x{&J&V(Dre5t;pY z2EBnaJ;^lv0cV3TLQRp+V~3`j z24=`B0mvIS$CY@<^TVt5j~YTfMfoT=f8n@Xe8gt9GgydO4{R=%@a=?7u168mj#HUE zt!a45?NrKnI$_bmdR>OU;;Gw&(bth}R{U7M*GZbByyoB5 zt3@s`H2q*Kr>P{oNH_Dw`G0L$xKjnWu4Nb8a5W?yAu9zU7{l`w@%a)j&AwOTKKTn4 z4mQw4>_tRQ%{Hm~e)fk(ZvBa2Q`-8`MNcJIV|i*jEjs#gE)s_rbG4SYI6HvZFUR*Z zyG`@7nacJ-ZV?LxV=Qe6(#<>$y)Q>14js)?2RSKV^X#wZfx8l@m)|`PWj$x35Y$T` zWOMXBRWbiI`8hv%cib)oH_XSn8O~B?Q7{?g{Eokcjul@!sU=OVu}6x;ojW#~)}ewU zFKU%Pe%I0{H(scFL=v^ed9;t{8@88*&go@5)igUwMo(h^vI`+heWy`4%No*o#Vzi3#>ps&(l|ilmQcZ>6EL^siu7f7>Jd$ZU+Y z58&%~1^qsw--t4w$#ZIIP%gD#v8FHUHhPZaidD*Hd|%5(zd~I-K3<`}wYO4gTa3&} z-AIn)cwwUyms9*6n&PwM>;j7_8}4@Z49TLE09JAz6*|q=pGRYU#HhiopS$Bsso2`j z0=+^MY4#p^%p+H1`lD%pL$%_ojfcJgd+0|T^XL3L!-tlxzlHQe7q#5n2Yem@s~}0} z^N)Txk)qOGsv&jiw~&^Vdc1ORB*N!GKc~@ZM*HhrsCrG(6_~7?Ey)t6uiN1JNbD6! z&$1<~qi87U5z?!*#1`Yn)RYmCf7_Q|sygIH0k$9TI!{7uT&%Hfo6=vOgD?&c_d9c; zKmKlvPrF-2N%A1NwY@p?K;;yb61|g$GJ|FB>H5RjU$%Ei$S78;WdHcRCZuPj9S%=O z^iz64#nJ?)?qROv?f1(LR9o3~s`q8WESADM^;CxP2@6I(jF>gQh8xy0e)cQ#UufVv4>y~L?H*A#a?I!Q zDcLQx%j2%V?Lm=c62)oSTyKh>-|d;E!J(wQ)xtG6r&wAEA#g$5wA1A^!8IBi53I`Zptxmfl1!l<^j>^(^Lz=cqp9u|pD&ovHx^%-FDpa$uJYVr zzlWWG$Rtq5ah~QL(rh!6djETfPpvTv{=i?YhPTz^;dfuV1cOr6;uz%qeSVn4{0-0X zt92DOqeO_@Yx1F1jGm-zo_#5Wx!nZ}+Y@LPxvQ$-SP^tZ&7LMB!_jZ9geJx)j5%IGIO`a!#r@4^lu}*Z1|}&WEbI{Mqhn& zqx`g$I%EnG%$C+*C`r*_URL$92lkZp#>|CazZVbONxew2n6p28zka;!=q`?}?`6jH z)Dh_j1D;G>Q0vwlZ@5#S*&u$HfH;=eAjwvEC+HwPqBowJujBaUmuA-WcfFf#kjIrJ z1%K8Uk@G_ER_wyXTquU$qO|}5Pqwui?reEmz*iWieIBhgJpGT&oZBVN{$e=OM)CXc zcIoALm>e;Cw&iRC19MAutLZtrZJ4+V+rqe6qv>~h8b337=-FXf#fG?2+XP?1OM|)2 z|0}!R6i|o6_k$4DwAYdON7y`w6TL~K!QCaztz%k$IJ)`UV^}Ah zFVH2G-gXdncZoHVoME2bYu=Z;7vXp-GeH(@gf{r5?dJqF9L2h1ds>b}7kUTrXTY34 z7t4T+XTmn8orVxUJ^iklBW!#b}x?+nW%HeGI!%Y$tv37o3ao=y=Lg1PADV|~vA zRZ7m1>?m!2ox}6L-Vk5*5-cB8f|BKG5MJURpJj6bBWj5-KgEege){JJr{|ip3#hPr zb6O{6i+m)#*}6}NkE(g4x z2fIL8yJXa`eqDCY3qnppB3%lC_HnS zqS1th_P1@t9G}oFDZ$A=!Zq`OPo?Rw!P1+zEPkxitaQlZ{bDDjGs`bMM!gw)_7n{@ z=UUy)8U8q=^GX(fDos9?oGbQcU&I3q{Gq2`$nkd0hDUOg_|~d%`fJ*O3p5l|{zWLD%D=zU^#Xp>l|18UKZbaIy?PYh2;sTc> zzf!~oQdGW)3^HmgzCTzZ9?K5`Kupb$ut6fL^aiJPSeuT^uw&&*o6o1thMDV#Hlr2c zKzCVnYtUlrI3Z?qFpcD%=}0ZW#tn|E()@XHYF7P|4Rtv3_a)vZV5HpmH6KGX(W`QQ zZvuUAlJp{_>o0C%rj@?k5e8gD9`{sH=}e4YzhDZu8O#B=CbOvu2Ut_?@Oj_}c3}eF zl+ssDxEQXPzb$%F^B7DakzZ`p`X<|R^AM#W84pznN$6g0O?3puU|q)qBB=89_tdCf zii$!wOb<}PufW=preB}ds4;HWFAsN}{@PdST}~UGJEnIL7i4B3Qt)mK`$iQoDkf-o znR~sYr!(l9$k-!WK3}wxlP&R={r!vc4-dRl!LB~Fs2!$_G?V%5wO6W2VB%Xj*ZvVc zN*O813lywOO2R5$D>#w>b+N#kzQ#?oKkJ*AI5=GxUhh12IjKam3H&xb+Jgntzt=5; zU%}5K^4cFnbO9o2sN3ZOWYaj$d=L7NBDLwNpGDT1c`W5#^ghho2X6<^bOE1>JU5W; zu}gE!d=8+rM&`_Kk)17Xq7u9qyJ`y^reQm#f);{_H5e`>3eY3#GveFxfYDO)c`|}* zd><*OmhX-p_x@r1x+~}(N{qh8C15Je|HfFSk-C3Nl`KYUHyXY)CYDWhbl+)aU#Gft z;OB7Hp7I)}h!-M8oAe_+va!gynp?;dlQ6rjpU@TVf0=6_9!FfSCoSUy5ZP}k&hesQ z3lu>C^ZI?Vk%V|Hsr=e2Mfl3v3N&^cENp)D`Z;i`3F%!_7`y$q>IGoyk+mV;Jw(W@@lv!StU z^OAC4(98`5Ve?hL0DFkv&S+$@Hestxez+u7jXu#Yii+!1g$IIpkAdwoE!7Y?{m2xT#l#Nc(j6o%pVtGsJZVycT z;2n8h%)JD;#=mS!$4N84YcpTB>S88_k%PT`gZY+fbNj>$%8#Nk!euwg=2)ULHY1sI z|3Rb%3CRCkWw36G{*-Z>L8FbuCpY=5q7&0^o~V4J!YFve5S7^h-!~-A4JTuN{Fh8|vaIM-5Rxe&*z~20T!b^0I2sU<2D+lfJJSPF z8*Cy_`X?=l2S;nrZn^w{WmkV$uzZ66vsw-yC(pmZU+T;Dtx52L$j5pu{`gCaF|?^? zaUer_b`BOk&x3AkInB+h#qrZL*>85Q|5`GG(uuDL_H{~pl!L#NI2snfZ`1N8;C%?} zB0h;niRK`Zh+WXGw;$8q1A{~(FIrTaRNqkPh#!VPBkyl86bwdo?%Ju#SIWMNnlRlD zcz@8_vcJ8Ajzflv`C7Em7h50YmzsX?Jf}@MsXBAH#O>hU4ojQjAJ)_8ZI%yKf{jjn zBP9z<&6~IAuvVf-M!(sI<)7@saLhgSaX^7wv-rh(Yvm{HVy+;{n;>iPZ9FiBXzjc= z6t(hQ4@BzQtK&5MG`&L<7$)3j%%EH2Vm_Xq*b^L^aOpO250x{98t%3Eye zL7hK9QkBgS#SIPNo)F(73aK?TBby!5njzwiOJAnokn~GnIFA4>u!7)4tyle(N$iZ` z%vwt?@3O5Qj=nCKE_z|_Nt%rw0C`@kcZd2IMab&f^TraCy*op&~>!%hTws}MiX(LO)EdRp+e)LH{81O-$ zAfO6gzTP%wWCZ=mb7NGUI!de9S;y4NLpQYT15}UD;A|zC{^NrOL}(4-K`P4m)Zz~# z&V*`ARJQNYCNMVAa1ASPx=R#P@lB^xY3Z;b+HVpC&sgE`?)vky?*5K?(=7T z8Lu!~FO&Fb>W|fAK@maFeLt`8<^H~!#9Oo&<3Df;TooWS-iz{mVz67vK6jfBL^6DQ z4eWGDYyDcSj8Xx6_$@|ZnuXhdiy!n)2Povpxh3nyP4YxInx^yfEc4r*Favtesiqrr z4&e%n%hhI8*KMC<95>2uiD!X9jnNCP<;gMJhZgDlH347dAM<27@D9uqGMsZt#xHOU z@cmL7{C>DM4hYa0X9K5yqM#T#9IU}?axIyr1*7Wla9pfV^?&CptsCu_X++cg>@^zG zk1Rfn>*vtQmm*7_BqTs$>9>-yYg`x{`ibknOizZ|REL*d-IF#yzAP!K5^NUGhafv{ z9eR)-g|(%cy0M3*azC0XDc66Q9L64$o7g}#=-|grt*}Og` zJcy_1DjfT821E+7Jf?ju@H-`f3m}~Cz^YM3Yi_L9J?T&tREjvdY=AH^^Mi=`lzHRN zg#5a!!_hkBEvz;Z)d@T&nYdOt!h(bQ9K8C@dbY;TRGf9{WvFST=tYt=LF|K*#z9_s z;*jYJ=%VSeKKjc{IE1*Uegbdm*0xs9aLGb+fPrrjaJLsa>#N4+Lw{!Z2fXOuU?Krf z)r0`p-;;mHpm1JK^_|ajIBrch+4q|@ozU3vINRG6W&ZdY592l|`Tx}07?b9C_%~*w z1o8b2V*Q^I_z(;EGZfT1aFlB_aa#c1Q@$;L5sN=ptK%OcMdM%FM|>Yo zxODVZD2vO*BYRRUq|swqKaOYx4xNuj6PHgSG`OT=f4dCQx(!;W*o!Ev%K-8z@~G@U zRG0l45H;?M@Ap1AIRVyATao*l-)^N5$?%JoXdyMjbjV?HJdWrWy}JmQ@c~R&OW0x3 z9#-$d|>uyv2!qZ!Qpt}RJ`(kmw1 zV<`^G^AA7{`k&zpRvsS+)wXmLerGwHuVBntS6uzsmDRog8(ziZZ)R~4{_u@ZpKqp7 z-ZDfj+lu{ah9}vsRX~8&OVD)IZg^Lu?0asBf*y_bdVZ`G zBZ0h>1&ejlnyxdk5Bc-;=+nq}eZ5%W2)54c<@Yp{>CNeEqthGkMD+Q!Z=z0srff{j z?AV=V@tM|Jz{7S^Z9uLbnctzQMI1NyWa`WBQ_g!z+wP^g$kz>I+ zyI$iT3~fel@#m@;;Vuo5o*5X6{Ff(mVxpd}Y&WC5paZrGm{!5Qj@Dk6SJFWZS3x>V zKhh}m`XKO&eG4z;k&=7OUU9xZuJftfllRZ|XCk=wA$rI;Vd z)fRo?U%DXesbKmY>`TTwi!a-quTNEf35R|)KBL#PUrEq?RA2vv14O!56Sx+?yKq!-&F^%_zY zI$2aX+b-YXa&kPNNtq*R=Z@d{5&9RRM-7oT439S`A(TLkDrq((&Sh)vXB&ro{QRi?EpORZ!(K4c;wm`jdw zs{PXEScZS$mwD@*d3liEL(DhYgQ<>OL>@yDS!_6apPI(idcpwSCE^lXS@@2Z)`79e zp^~m9&quE|=DW^5s$dl(I2&)9I|q?m^8I$>cQRxx0uk4BF4*@XVhL3J;>p(tfkHmJE}PX(0(h;e zhX8acFG;KRtgTjeau;*z3T!$j48f-J6;l7+vW|xY2^501^cMS6)8lE@zL3oCdQQ!b z(>2wX+go_v@v?IAYS!oHWaATJr2*nWZ5}@!bPkgfe2D;n-Kp7fPtHE3My}|%++UgHW-64){p}=)6h?dcFpJNC!GUrvR6`w4G zPX8>3Y(~j2MN&H~HB3MIPUj2-g43gRvJRkg*W(Gn4ud2!ldUf zud7<<>%_9h_$jw%zg+huKM#muLs6*3_4%meiP)_PiDtLUuD5qA z%+mrQ`1(gIYAo7M_4IpM*~Yy!rp14}K~z7DZQH`upbJtU(T&;RAP;qV`E!<5|L zbn6{A4D~OYyugt_f>6&jF1%yB%!Ub-h-f?ff*6jFL3l`NB`E7(R4dG|KM}-Yo{$hhyb@D-M-VT6MG&J9L44d1 z#4G8XcZDH>m~U3l6=w);dgbS)GaI(^_=@a0eN(Ss%Y8*`=>T=oY5R4H!*TlDQ~o7G zh&K<~kJr6`4u;U~$0v4-ZE&N37vT6CU~z(*WIOx)(Vs;Z;ehfoGVvk)sL!uT>Q6Df z`oQMvB#RiF^&wFPDA62Ftlg*YFuGl+zIP8Vc`Ag>vynKPJ5 zxc*Is)OmsN3-Mho)~yiVN{72Qu>J=>9ZEI?iTyJkE=z^^&i&-~Mkz1V z9Pk8~+J!QaYp~kvs=wDu*KYA8evc~((PA-Ad=w9QxI>q+J{ibm0(%!n)Ja0w-?-r5 zyMtt&o|3p7Xh8Nao(+14E3I9GV7{NB98a&;qA2jmbJ?}}HTcI>d>YH~_eOf1@VFXz z?5D03vEJky^06YEv=hB+#`|RPoO;1^dyxixCH5fx9f*=&LwQED z-+!>SZ%ppW#1Av{A?>x<4O*ym(lU>xe1gRUo=#&on0p#|;toL#OaU0i;vkY8Bxd-B zdrwtk`E$Vx&4LZGo9gAH&mZb8-p<{cMVW|r`F3n9$a*bP4<-M^kQ)O`&O5z5fcO3v zJb+%%J(LV(ZV#YvdjLc50CLDJHTDPb>1pR~Ea??f>gY~czowx#e#ipCOt)iT z(g*V(QBW^pA)eG)RWA1|I`C-yBX@OebJqwn=k4dS0M;^U^VD7QK!eYAa&$ZLmc88Bt9;$fqbSAx!_+K;qDs< zFS)9UZ^~Lvw+&jFedL&;GtwOMx6*qH`1uYFqElBZ(rW%4aPKnUe^{x8?|}ahm$WGd zgfgpmPB!pX2Sy|hfVJ{Sckn6=Fl25?$AST&^Nd~tTcOFBf-&8BPqn*ExA8f`0^1=L`L$`Et(5wXD)lrOw;(TpUx!QDX+;-Sh{Ki(ReP4qM#x$$DzU0&`<+!xdR@LssuqRp7--%4+ zUn*AZH(S~h{roY6Ka4jM33cJum-y_$Da{H*f zAMGhm=KCi_DAGFKIS}yN@^7(okuD)G`mHD_32x5o!h!C-5PkwVk906rdFS#CbFq6F`FFp(SPqQx@B^>~sw|Gbp zS-XnVf;<*TErjVI`)~Z3A@;_ax2T?)!W$y^O;|Y!#hAqenl=yH z*y%rSF*&4yK^@4t-DFMWlZVPe2=c0rkRKqiH1F5m(f^4CLkY2i)UOy`ZNc26-ib^# zIO}8v8E=%@j;7FCxO;UaqCfMQGgeJ4Mhtl1ogqW@N~Wl zcv{DQ`b7i(U2R$jTJxpXl5}$y-i%zi^0i+cc3-I>>CNz(IC;@&z>P9f{Hq z=ya`5cpppAB}m?n!}gE3i*Zrznhc1{&DWsl@m4RSi_eHx_10Qek&lglyuHRjzD_w0f5x4{(r z@$$kBY$n<441G?N_&VTq9PG;80eqgQt9zt3g6eG-@wEMj8>Czv>G?gagO~1Rcw)lA zG$+*lF4p~AB`On5bx&liTkHD`GyEhoFYp4>Db3x?;BNoO;i6pyZx$S!E_k<*J9h3k zq-oN$_g6`q-Fx_L4rC87(JAnhaq3x+|WPd8~L095jTK+AC8!0#T99jgG;XwLZV~+ zvn}JJ;|%b(a?a?Hk2=o*XI3Id3fH%rkuJ31Js05%-hu(CsZ8olkPeOMOXKSF35+Z% zEeo1HTrcjsGyzfmyi7+%T1@~$j(_3MIA|cR+&hQOgel2UH-QIR>C)wHPus;TA-l94 zis_*MBw@fo^5Oh-Zw}CMX`JOUFpj^3>qC;Q=`-q0vGa#R!3%8$^M}*$GJJ1tFiudJL+%V)GWw?XJ?sR$?&mVWFue|y4o1cRzoi4(s)?MF`b)&BVx3deF zwvv5W3zA)ADSV?Ng|~?O*fPD)kiv_v3a&cTwgR34W52-?YQAUo;|(7!y?t;7J8v^E z8zT~uxD4U4!rYINps}FpGAcY>(d;F?$im{fdU7e96(E|ia|sSO3eX7tauR?EG8-^! z)T+V5MbT?@DLN)2+^u9zf86nIuIbiA6J=YNPBV!9r zp8^j#_cK@%*UP?82yT-V+k{h-{Bv2u*-L3eL7}!lMsX z!f%B_2*#cr)&iMUe_j;D_>b1}iF0cql4`_eTgqh*NAqK0Q;A(=MZBRFf1uNLBi5>q zX@4%*{h)2c81}o1`qf@n9QQRikHlt>;-BV@Qj&M^XtAeb2keS4zQ@-1)?aVdfi#O0qFKYcP5MG5Pv&>hX?McU(<~>z-mCg6&S7KLr%d!i z4kw_Xc?JrS??)s=p_%fmBQIH2vwLDfM00kJc|;bQA=Lf;9BhtIqPMi&)R@nOXGgfHcQ_#gr86 zl~JgOkLRtbIvzKO#G-P*!4Bq@a|UE-gWZd^cQNtdq$U)|z%Xl-H6#*vnlQ5ok=g3@ zdbcMTYqe13%Ohlu3|?5DCZbu;q)9Ow^#C*ytzSZ`9-PV(~dO zq%yRb{Bu|;{5hPeE17XIeTkrhHsmZgZkbv$9Z@HGGo9)QMA@UZt7_ikVJ&~&E{-bc(CQfvn5f#_?VJ&ccIqWj zZ#f^tj<1k36A827d{p*Il%=lnVFO7y2xXaqe&hzlNm2vbxlG5q60rE5=Zo%in-ka* z84b_v5a%#!COd_WsFmKYd*>=Knpm#u-ELws7fR{|2Cnz?RQ>&#n`KP~@xH18$DkjOARYs3_S$wW@nWv>7!Kd&mcx0lg#8z|}{L z0|scq=qL2MRw2(r>4`9hzF|ylV%Hmph96GDlGVw5U+4u%o7hlJYfa$iiP;jMWn(5! zD3YGEq%*=d^zH4XzV^&+U`8qygFPVk1rIF3hQ(XmyZSt2A}O0Wfqs-Ov?W=58N zAP?xZK>-R?k-i z)f?5G&W6<}*oEHi8jz5Y(oXAbJeK?1C~D6}J!h%bW&PdujI3x^8r|qVYHCc-S7CWs zYM2Ur&{ZKluumnT&zFY07mAfMfY=QM2pq#xfP~{i&SkkbGk-DRV?vWi!|+E7WZ2Za zmN)YOxl6j0Q7~Equua8C(}F|i#~YvBowHjKwonG*YByLhdxtSuCNu<}{fneL@z zSf|Q11}A~66k5r|wQMzHKTLN`0fh}h6Vpu*ohI~oDii)@A|G%(oXfY<8@;jfbTf_J zAXm=SyuIjL+qPPJkrkGrHbX8uGOFFpOVSPq+8Nyx6koer?4g<4`>;37Mk}t)S+DLk z8hPDRYZBU(6xo<%$5SFO5Bnp#M@epD9k4`;q+4dKgGB%S|w^TgVj5mlm=1SQ_#+Ct?~E4X@XulJ;q^5)o|<(P%_vpdUHp z8X7)RktPE1geIIl9;fGEHe47Sp*O6#ls5;OBHQI)DC}AgI0Q{F(CImas~%$xhUShK z3ZVXsAWO3v62fwJ*y~y_97Yx9ML`MxujEogSpi{H_qExGK87MF)*fdhqg{U``9zjf zD{VR)tT^!u(T?L~pBezo?5^po4AR-nYT-qL#iis6dwo7Hs312OlSyZEa$TwU@Xudo z?fiTiMptg*4ns9crHDSw*25e5x70Z7=+qdkhsKA!6CcR}SI5-CBMmhKih z3{){St}9*yJ%Cey+PCPWm3Wu!o>_5e4ar#Z)V*}tLI|UmF=j)lE#3WbySO0=;#If& z#cA&vhGCiUcmwfHnYEe7TYgiU#f~|&{gYH30-7UN7f2S!*^pddcz7EW#cg}wkH@E> z(ROPHCMz*R)ETZByqeM1x)hidIunH#i_w(YvI{cqrbJ6h@TLs1}^H(+*o|{e4 zyL32{x%)L;;N)t}8Qt45(&7~aN3q&j!c}ixryShjz zF{#{awiBSk+LNkNZe~4!+B)IbqpsdGEr!N5lE{PIBY0h!qDA4%jflfpRzl%u%a!U4 zDHW8S)YU|GIWHE2m06wKy59f;7_AhvF#8w3kw@MBl_jN*ISq*1i5s%>9tt&ni1&#O z7b*7j?qTP2W*1C-Ks>u&W}A~aV2Z_!8s@PN&BbGTJ%Wx=9QIB@3fJMXOKFy8>M|&g zoLd__pnf6OKZy|IaMtUj0-q3Du;T@kuL6>rUYCn$U$-d!LR_gHb9DLZAfI}W?_i6u zvxv}Bah6aoz4VJ|UBvk*HwF_f)4C8FvzP?v1+@wg@MUzeEHIG_mCIRFiOrxq^K0LA z!ThDu`yiJcq^ zfOLtXoH|Gf1mILao}`e>c4bIp-4)}A*4Q^|&0Wd|@vvCqwY1r4GJ?dWO1#}uU7E6#No%2A zYS!BjJ&qydTBCz{3a%l#C=L=Wtq-i-yf^Y(hWCIrLkNVAt#y79BkE|P+F(74S~{C{ z9tSoBRlLP&8F*~wP>tzGUa$%FCxyKN$)}MEJ)HaJU{o#gJae(1?Oxk1#+l&rh`E$t z$;==EyjnJgi+3&}-`vpQ>X`BM6!?<5=WX-pXlI4uY2vFUWJegz%<`F-$I0ku*LqJ;OFkz>_PXJJoX)MnIO7jX#@=mql$&>bN=@!eB1Ph*tLT974Tk-V)3_*i&;d z1?N#pqiMa{x9l`N=Y+9Z&IZ!)h7q2`vuJzh=DG`VI$&wQ=$&&M^xhJwo6*tk9kTct zb!8cBRpLBEVJcGfvh&#i|-5ow*N6|HgW*&$%voHX|FCNOFt-$u@1Z=dD>_87EIa==>RSfrLG zyIG{%vq9mLMCGFmJ#M+u>4F8XWGSO$>rll@oPp#|<|gDlZ<>?ntC&aeI#WX+O2nf9 zsOQDEmfX28ROIOCi9e}IvLko$yz%AJb~&C$)^<4rnHoAED-JMnMjjxI6m9t? zE$c5Gd{26#Xo%p1lXpaTW)m6#VV3}TWWE?PXGRy8?KZsOY$H_xXFFE9{f>Lr>QIhz zyVnB_qQe#LLE*i4`&6Ty-Tpk}{=93|YnMv}pQZZcahV-sRw5<~rQeilZ@siEapTj| zwK*Arv7_ApN7nk0UhS>}LeP9g*8Dhi8>+V%OCBu*8kT&X7z3W0^{w@Jb(#~7n5pw( zxsF+3GmzJdfx|_jnq^AU-M{U6O{yJG96~!RbtBw5IwX}H*(W-bxAXn7AMLe5L&Zm8 z8Hx!ehdmK=`8qywQ%E9p+;*wr{EfU#O0tyZ=MZCTRI{!+S61L<`y)TW9Ba&X$)Fo6 zeZBS$wXiErYNTtMae0kM5M%etSzC{0a-a-Gz8;UGS#(OshHBQT&C23&Q|+j|4jf=T zueR4c9!YULG$db%h-<95Rm+cD*^Mq{x-d%|5(Sy1ojE%aiAnnO<$4|qY9gQ&8BG^c z09^KSd98N&-*LVg=;>TAjUi(4VKjyo%XkRV>cm@msRc&-dNIWgg_C7g#Do!di)24O zUi`MtY2p4_g8J58Sk+4^|Z|} zw3t;Fod+?^gZs;as|NY&)ffU}!6mNS3xR?{h$W-LX|${v8@YlW$4H-vpUh2|wWi+{ z@BfUOFy8e@UltbQk~IDzHv#akO&5kiLcQm?m zb3j}Dq*hpCFUTwOJBNK17!7f0m*k++nlymGbHEetW5ce>*@hr#b`7U zv#E2Sw@HgSfy$lnDbe+=hlsEdaQ9B^8E-86%aMPv+bKvYI4yDC>mC+yNNnj41Jg45 z=x1xv?0D`Olbwzkf4o*~a(>-3eLY9QKH~V*#G4(bV1=IZ(YmK9t~IY`sMT9VH=D&R zMV{8`bq8$>i=1ft;bIR#uTvV0y70&cC4TO(-Q>h;*ES;fFz$obA}pHzV8{so;RpoC zDcz7yPb}?sFWSH!?9z-<_sdP#XT$kPu4Y?uzB@vpjFR1NUNq8e;bZayY)s{cd*rM!PQ@I+iqP{*; z4OKyRk(2HtOW-rReo!rqZE{}M`I_F2SO6QEcn+nl5cS<_6hTV2Z>MZP#A%@%ckW-w zVvjRpN5mMbz{KV&RZ*|$4kABU#m)`ydWKj&O*9}uVuO6Po}EM<1jh@rw^KC%YH~GB z7vpqYfg5r$3*eVGmw1gb22M;eNAx_Y^_%`$b{vgRuMoGh)UzO{lGSt3GC6j1KksmE z1Df8i9jLuRw+Q%uY?fCO7f6@>U^^`Li&|nic3vbt476BnJ~A zpG+a5yCo(bTgb;*zzz@l=@AIUb93Y!%*Xn1TE~-gARhbvVY!c%t~FssZNnc@c|qjs zv7X828UUZ>deMd9LOsxZR)C-od!w5r&6>5*&|Q;A^zrmBLd{@|gXixt$w&jUk=v{r zyGQXz47uU-pt-$yX&UB{J;$-pFR*Chfsp!wyfX=6xjK$ktG%yn#Ob&udLAL0y_IMs zmNjgmRh#xF0%L2m8^KEg--&RO>r(=ERI1ZX+mA`~p2lyRtYN}^zOd$A)^1l57n+f)#Rw>v+>Q1A zsOvLu;(R;WkRV1L=7|l>2SGkeH$%vEt(k52Kw(`GEh1m6Qtr42tLwm^*2iUnz5ui0 zyR3!x=e!#t|K5h1ZV@7P-83X2&Y_qo%F||^E#^R8DVK{NLot_FrmcC1baUdnDKi>f zx23gRYuVx0^f9kO6_=&Vd+cvsrx>=<)b`EcCRlbVfdK zbNDT|^$a>v=k4(5E07msmY!j&-THS;=$blraq}#hmCz#0H%hapK&n{5k6?A(E2|{y zxtmfNcVX>rlEoYQs=@+hSdmnB1e0)mx-QVZ9&E_*Y6|`@bP}cekupWD94plu?x%q|96M9OSa-_LA$e#tr*j8k<8M=4O`JUP#Z{q{ux`bNh;uG|1w*V`AclY9YFw6 z9*z%xnXNecB(@@T6A{!8WGh|*hyp0#sj$Y-@6yb2>8z0wbTQz1IYC|A>f<+94y2{7 z_T*Jc;J6#vU#`xY!}VIXe^~HMyCp0N1*Ww=tKHclbPorsMjkpQ;EDx3-EZV%i0+qmQqEUL6=fiQ82qVbt1)j_37kX_ww+jZ+qV~F zRc%+BZA6GZELXC_1$@PBu`PLGxFIlVMI&pE1Glc>(B8gs6JQw$qMYA4LBrWHTD^~ zm{iU#@uDsmyVN6UdxOKNUlyJ*#0DJiC2-i4nKQ(Ior3O#%>=K=a--D_UAs`bS@urgST(cP0P4lTQ>GrJx*-D^Qju)*!rjrQ?i>Yuqs>f#$QD-6mDK>i#G&J#WTlBR z;x=o21F51dp;TQFpu=L|&Qc@k^B z4pAL#Mq+IdQF7d9wuM)K?0X6vESe(C_d<@P=5U5g!6E?q2!9@g;0$9Vp@)p`(Rk$~ znO+0tcIFa5EY7D_mH+^{tk2STruBCEC||)GYH)(8kTPy2bXM@1PHw<{bO49z7f`K8 zQu@;8w~z*659jk?PohE~ZI>%~Odlhb2}jLzr$hUgfGjCyP};=YC@Ln~5^o$OFh1 zl8pvBo7TXV*#$UAxfiwUAwF>ps`j)E@{eV@d`s=Mi;LNW!ggzF$9dN`; zHop{Tah|!%ENePZ&f=1>Joeeh^)i9vZnhCD)~wkIb;y=<(T~ zG8c9eMH8+?j*SliQKq6Kl^{x$`Ux(n=2l9WpWCI8+j8=_#;?5szC8IX&3mKC4~mzygT+qZk?tQBCR&ugdH z9GStX-|G&t8h59&lg_m0RJO!c3O{b>-k#pZu#w5KnY968f6_SPHjzh)YJ-3`wn+H3 zRBVXy5KZl-++wq*Hx(k^{aU&9TL*g#Z5|?&lHBF!dnP$%fT2K}$ zvP0|CRb@8PfPy>EIvYs_!3?g0g>|~oT-BjYwjo8w7$hzd8ynp=n}Cx6rYgOhL1(l- zwuu}L4LUSLxMp|)TsLJ0KDD-?Tu@O{IyZMiFl>a-;7h?5^?WX>^DZ`F(ZkJRebHWd zqyo}7s3}vGX=_}wz4HxC9?-0bXY#CbM6~*6v+n*nyKmO*QWoUXd8=(J{#0Tg=6I_Q z7xVDkQkLRQm;0*_)>kcEpi?10E_AZK9)`3~PB?EA`8IU?61s<4PO1aR=+RhO5-7RH zeZ+BEb|V;IwflZJYp+T}pK|ZQL+ZZgAJ?lXb+YT@>IAh;MEA-rJyCN+&LdO6>snjgC2d13|^f+JWTh$m>fMw#C5pi5A%d z#(7Pj<$FwsoK>Q`BM4L|nI8HJ8u|w46^eGhZmP1-a~!C+N>u1$W2^0wAU5zlK*dJn zphj2fLW$l7AhAa)gB=k)h(VXaYAjvZ-gWC9phXodyG5WNLr4{c1_1)v??f_2pq z_4p2{2q>mccMrZ($P2r+^Eh_0hJCdWss;XjzjR(!hd?N?12SXW0jslsj%Um%5M7wsYD36ZWqrR!j>VtC4#ZmD&&%IU47 z^3Ve;UvMt_BpMA=7oK!B3X05)OT#CtlODR>UUw$lj-gJvwqfglYUS1QBhyx7cqk3{ za_bv%)2xgxs1KR0c-o}CaV(P-$f&&Nf~#S`b|iW_2SYsp=uFvph1aiQ5aq`zlc=h; zGYl8C)#{`h!3yjgZBhv(-Q9No5@HYd7jR`*@f*v(;uovzrhxT{9(N{llEgFk|DbT~ zZFhS{HR>hk=)1|1T@T2JWomR)=2|}Kf>KDzU8WqS=T0$91h@v?kn64-kl;U932ip4 zK3$}Csf{A9--z}?B5Fv>Sp8tM+N=AOJ23)HN%PnQfnb&mX3JCsJyR+-&U%%D(gYn` z&OE>&ZBm&tjt}S!0}Tjamub^PThO~ujL1-kSNmF(F?k0uSH$F4kxM~Z3 zERSxsL_$hIHFTlP4zjX|*NdhRj$T%D_uHwve)nHcBz1BsHKNpkzF1CH4^$+DkJ}V0 zmsB;PNQ=yZR`_|7o<`x6Bpc_n?Atl+xdgH7qduQ24xlH^A%LRWHHhcKUXH(M9;~z+ zYgKh>$dWxfh;rF_7CVI@r0WD4@93MV>h^Lx9l%mFD2!vtTI81%R4BolH`WaUlZP`T z*eo+(cfO#+I1lOC0}0x;6T>s6tyZ(6(|r+5UtY`tpn20}5Rwx?MiHTPw*klL^kEWnj)-zZY=aG+HdEI_ z%}zDd6@rm=oH;mq(TKG@OqsBP`7v{_QGBouCl|T=Hmmxh-da>I_M%(BP*jHwcXG3q zolua?Alwss@xEKFwxIwKgz<0{OnS|P?Sj0D+l~c1(zEiK`&2PANzX1lNFs3q>Uv^a z=&4`3Tv?S*JABUu^Ru{6n;xXx$LcH-LIK+TCO6A{Lu{tWLTL0Mdp!|QEnHO+ITw)Z zSXF5)XH4#-Msl@^=~y2~lf8xJS5_%rH6AEE+hQIQXyec+ydbp@?H53e}*!iGECxz+QYUV2yKvzI1*(cW+Z2KhO{-N4+FAEx=pI%R>0mc$%=R{h zN>DHiN!lKe5x)jWzrbf2vnoKDh4bBH$A})5&R@FvZ;kvN8 zY>yzWLt-m4jtV}@I)Ff+nk5r2&hX^uAf8!fO(xT+ zhY-NdiG65As6BWH!T0I8uPoZrbf3#X&R7T9t^w)I(`DI9`iJEL&G7Dq#rAS($ey0; z1Ca@9DFD9@jXoB_iCxl6g;tLmg6hPd>cs|UF`?_FX-JI$(nShMHWw>;GXuOlT&GU!5pmtypd3sKR=J;SCzU@z71!~*b!ACayTI-*SF43E7%I#_ z994Sb0_NTaVrC-n2E4VngdX3*S<7B=hotk>Iw1mF1w}%a6bJ-MCtDL`cQTin?DR)1 z=x(n@)wYZmtq)Vp!lR~8N1zC*At4tRM3B$e4UtvFauc~03*mvAI$uqBbB@ZKVyd%2 zxd~j~nuKDeqY>Iw!LGnz^JG%n&H85HVVriB+-RS*0?*mdcDYdk!Ere7ttf(IRkvFO z_&8>+Y`9n>k}>Ayt;Yv|p072)g06!}+Qp^%Mk%ZDU}J2%MdsKPxfL9ew67{f1yw%y z_#m`EH8eeAMr7bU#trr+&oAIiTH|{ALuye&^&MN!)p<9)4^LezV)-XV3l}RC2^wXy znFrm}jeqLYYOz{qi%g5_3MIeZq>N|;|`0NZ6}u)4bSZF<4@lPDX?bN%3&wXs+@h3AldI$a(m z?`TQ^@L5HARF+8>)^yR>0{=zahF?kBt-Wm~61&WV$WGvIwT1bW6E~>;3*)J9s5kp+ zJ`cvinomHA7fyf+!>I9`H`yt#clor6W@FDjMqTbV^4Xyb!|3V=107(S@okbs3 zvX|*_veQn&>Ig4$XAcIg<{2yIL}G4noJn6WO)Vp+NL;UMxMoyifj>H($aQ6K;V^Cn zYp0u)s?@1`VREo_y{J4`^}!>0R%e5e+-tgs_B7b}@uXEX5bToKRL}3vdz`Veqea#&7)Q(KV8x5UBaS{bfC zaK&{}((w%O#Yo+pj;F(F&UxhhSZ-Z&qBU3sh&SdTOgJ#h!Cde9{*OO(>*TOY9%skQw zEM#h@ZVqfug|w|E-u%eU3MBp^ysu6*e_U4T1tMuTqxD$duvpC__Tbd$F}rDFPH^@W zj1AG!*X6zD!VY7+#Ubw()6IFON2QE(2zKOCMvN+-M?l=5iMJ}CFn|<5azNWSx@?zs zN*-1(>0~VFE@mPXGXh5iJ&H!l;72x-S+Gynh0v_(G)Du&;`h1`cE|1tv|BsYcx^&g z@qQ=l-IAYAs4}t#olGhAi|x$VMwgXlOP5UWoI{3_lKMq&yNMht9m#BP93}9();%@Z zjLbb^CCMTWN=QD_uHL03R=^&UZmo%%^&y6W{=l7N#}wv_EUKq{HIz$>V>a=UVi;VV zhP;9qre1@dKDb_rK8nB86ga75O8aqZ2s=aVqJ6eGkU+~tVosibB;r;IvGSElO|$W$ z-nm4vMLn!bkp7ObaqpKl$v>eGQ`7u-}T&{xuVEYlqatEM6a8vdsot35EF7^4*~%nyqG#aehHG?oaot@9Mcz~fC~x~u^}R!Y(c0e^)p*kmTm62pchd( zrVx)mkBXgPH9ez?%LSIp zvo$@0T3-17F zzC^gyER!=>8zMRCX60djl6&4t4_8}9whJUJW3XI+zLBf|xMV|OvQ!yQyP*J)$T^2+ z4sD<@HAJyjrjgTOU+yFofvf_tv42oUi5a`a&h3Yumz>5glOEYM)CQuP{U6loyuY1a7 z8yk$v^PJtClY%g>4uo9odarhqsoEpQ+OCJKpfjZIhnKn5RTf345gZ+XnB{Y0&#n$dg$RiH+8%Uxl&LHdBkD9j;LF>-rIf@3Y;; zl&@h56>4ad?vS{KkmS&5J=H-zJc1Y`&-gYs(ba?$=E)p;i^_maJT>(NN;lH_NUA-! zrUNJ*={*KlsE4OrDwzJM3&TT&vqNJt@8z>Yml3Rw*U|K9cZ-!5DMO!zpIPXRSNp1| zB5vHNQi-#HIm@M{^Ocv_+0JXzbK@)CX}y?pNVGgR)4(-P`N^{J7lLdOBA0CTXLcq` zCsu>%=ye+_2y zc{f?@x@E`=;rYd%W~n?HpNFGi5zmv^$U{$4_Ig#9>U3tvu!Fn`D&C2J!?N3CBaxyw z*m%bwVY6{2Ye#Qop5x^R0ZHVJ4zs>hSffK|M-eI$>*QSOl5Q;q2hk)PIN-U_H65nc zI+X`;H$l367-SEJwmie7yB<&ARjWOC)GmfduKSZQJ@qeXuTd{>!^7tL{xZjpI+Hrk zkc`x1RW90Lm*a!h6h@BG-Ccea7!{t~)jsJ{$ow}xj6x3!O7A|@+oA)XNkuiH`AX8Y zYbYWzK_^=p#&8kyd*wN_p>A@v=&h;8Vty7VuZZd#63oLUGcQdG$-+^r=jye;JK5eb zYi*b##;r4Th9WsXDDxbSv3p5wU=3d7KG_Uue1jIRCuDr%1W$)#d|hVrwM%hb2!?Bk zWpoG5u;y4~bCfONP!-a9 z5B^3aKEiFgRnwxxxZD`vdUaDncgst5hh}KmL^F*fyDD@4J zu24T=2PqL3#&)Y7q8p#OLI^^?pG{n*^&*d$lgUv76zG!LO-qC7P(@@Jc}96wafM4u zR(3(CfMn1)QzdYw!neVjD(SqbddmR?1Q3LUuRV}nNZoF@3*>~X1G&(Mtd~UwYBM-y z+-h;OB3B7fC^9N|m(gYcF_z&pF9pXtokEl7AKlt*&wa=q9-4;S@XY>lZl{(uIq7uF zsH>|#;wIr7ZbxjaTgHG{)NYbA61#>K(h^UFI7Z`i1{+%fyd>##(oYquRMHxT&P;HI zb@%K~ReOfQB{&=-Z8nIi1(iwmI4L;@znNi-7GamHoMb_m%nh;Zqh@+s z-B6O*$qqWF7O4LVU$d2JaM;(vVp{s(v87#5EhoEOFiN0_JP=@#*QhBdawq}2=6)cE zGwQfY=g?fphDSUOoub4|7>4PO z3;4_ZdOeQ2U#&;8?P%IXZqUa43yM95KU3^U_Gm%DXyF)iGf@(vJPXPuM_ezSWU64W zkvqJ9v`%YrLJrmG4EP3m?$ z21Gka;V^4MrDRq`yDC5c`Xqf*k)QR34ZGiQP+m0a{d5rz)0cU`R9tcg!U^Bp0_VM*>H~ND)RCHOq~>&~Ip|cR9gAY~yXr z!JsLay1Sjd6Yulfu+O);#b0>ubmIzwO10E5^3l$^)`_I#$bwL8)cWCKw8Gcg6mYjg zNdJQ_c<5PrUl?L1&R#PNT7`0{g47e64d`iFon|5JU%9FcM<747Tm`QX+94Uy7*Pc0 z?DtD!>C4HouS0ihMgvU9%PoDZU;tpl?0r6Dq5_Dc30YTB)y9j#V1kjGdQ*4Lj?^0t z<~a3tJAbDQwCg0hPtm2n-yIWRebPk(grK^bnh-+8W?jo!nCsVLO`nPdxzonYrMoh6 zzS_meMccU}6T&2s9L-w{JdJ{5_$7q=(srEgApl!V@g-KWYwz1xu-F>Hrf^s zq^+42xt{`6%1_)g2-B-Rv^}9)dcU?Oe@%2Hyr7DGq2b1D&7sRw<#XM>%$*|Tdh)EX zPHMU(_r0T+AgLtMo~)8bljn0aVAV6H5yjXQ2 zOxxL-JQyVmtWnLnm5bC^tHqE2xy$Zm$Gve3Drm`_VrB*RVkpe4AtFwvZFF?!SHXm( z6ilcf%W+gY*Xq;61b5CFk#yaOo}z3JPFxlpd^k?UMw?p5l^YVmPJwqB6H{1v3|d1n zto{jz3xc8GMrt5UAn%N7Z`QRw$+oC04=dP}KglYY-SO+U=QqdQ8{gznXAJ7xAaYlAsl&4OUvjvdU>OMXbU5!_NY6V+JClA3c<;W(t* zfz#hHUMn4Ac$ZZ;%C?KgWMKu6u;7j?!IE~felrq08Rc<%)Qm%)-gR1X>Ma;2otE5m z&Z!6Y*lEd4dhK7-k^@gRyP@OVuO(-qs9#QY%_nQg6`}FTYZDsdg2NeYrzLm!ODj_s zWBjR_91S%&_JL}0`$J*ltmHCXh~=(b1#~QN%vs%D*$oeE|9)_)(+Yw)=3&>XiLB*& zdNxdaQr@1pnD+q?Vvn(LyezIK(rYFYGzjGN9?7UwU~WS>cfQJYc2Q<3W^CtReR9PHC!HLBriZbjby7AO7%P?cEo1BoV22Syvj5X{#Y4A=zu$GYR7v+c2C)Cvpv_b zTDV-w`-ug?LIg6#t#nP1hC9w+SRzJUfm2R~C%!&gUEXxDik9Xx2-@K|2LZemsAa{{ z=LGt55h^dltO6CKPsvu^TZKSNQ5@>rdhZyt+aU2ZM#+|1pSTck=fifMN7l3r;g=Bv zqp?T7-=59QQCj8K?KscWk{`%B*zV4^B`svLMKg&Gf-2O?b%#5x8)8+denm>;K7*Pm zc8~#J279is%cAxOM(Qhk1@l;4#slbNR=f7N&R|r~+b0@~q~OzQ1X!wy#!0e^q3zeE zT&b4VZzm|LpRRpD0K(fVPFQzm)@=9Z+Hum2cHH7PmM!TRLfsdnkj9cx)CHmorNXv?=T?4RR-)iEllBL;^A(73Z7|EYJ>vN(6%QT;WX zqxM=?Lrc^(;-5Y26iT?eFj}m^=21FqR?t=Spyh@lMd#@rUM5(98PcX#b5WDi#o-{s z?C>xz;$`5!ML3AKjvOjk14d=SViof_!+FqHL>_T)3q`EO% zOQjY$P*;Ipv<1Y{shhs2!rA$J5odG7FxgE3TN#TSQHw#7z`8maC=HC$r`k0;^?Y4% zkpTCh2I!?bcpOHDxS3=_3B_o*BB^VgF5+r6xn`Wo%_qlA$+bkVvm?SZeIot^$@Ibqk{rEp6(?Mq7vsPqj`uP%ZUY2Jgf-=P#e z>)J1z@@5KOSEAGx2NaiGRiqd|IGxlbLThY(c~DXpdoqAd|{k#$(iCM7t%}S`<9=r z_J!_}MnC`~1wpu(c{t<}L}?rF!rTt|w>Vt8=TzOHCd0SHd<5~TjV3ZPJc^_99_|=I zyUv}_nA;88)i&s+Ev^c@2jmcNeE>M3CMI#uT29Ez1e({_*1BG&`zz(vlO9r>&hLQ= zMPCTvkeF4O4_#WUsnQudTX$iUVFt5@bLs3!$hH(ivKnTiLndBG-Y)U!EI}2T1uuO* zWrA%_-;i9F_V1emJ7+FNXpk;SS#+*U4`FHI>!E!EL%v{gF%UB0dw9t&C?EPXTSSd! zCDkA+mFMw(alUBDwp*x2OR|`smBp#e_%oU~#T-iC>B>f3R<7tY|hs?GcRHLXjri=LzsQjG}QoR|`4a{@j3!$1K zu%p#9^lWl`#%W6XnF8=8q?qg;d>HV?oC$~pbpq>}M$M2PE|h5J^7r9Jj2qm@3b>K3 zLcU~zc;=SH`4Ziv$P&sEKqMyA37oBFi$wG&=bojn_Z&0TX;m^?AXCp;|HM)C6*EJi zjWEg2kM-14U{G%NdeFajLL*ye%(x}e`M7a|SdU3ibw@q|^17&2*-RX`2M_(V+-ozrUau zAQCk_ci>#8UDiE8XzF9=Hw|jd zouar)6B7c_zTR|;?|oSxys09{K-I8PYEB6w?$zp~TZtt`N#>Wl-=#@1t(}Ku zHq1C-b|FuO#hp-v+Bmi9-6(pFkOfOYHG=_wb)SefoSM2RRT8pZJ)c|_daKFEc}PGa z&IXYc&6@F#^E5a_9rks-v#_At{Kjnj8y62*KW51Ueh$5sSA}-pJ@oij!+xVEMgrUw zz{ntpS)%hB9Ys^%$yvw-ohyw4v($>n9aOV>aO&u?)%%=t4{np555>WzWk@u2$iT5fNK! z=X6v)HKlqmi|8aN#bBUx;T~EDndWq_4DNB%@Nra^QTy>`ciyCrqel1rp4MRuz8f{V zEBnX)<6#eb%)`KyU(`?i?RPizbKd&=kAC9+{N{i48DBR1h0ijHfB%>#Dvx~4GY3EZ z?r-=>?~de-&-~~I-}0n8-&X$Ci@y1Xycec-*zW@M?_EFo!QxrTQ-1o1Ke7}aD;SS_ z%dh>LAAHQe`ia**>Q!&&zxMn8NK}695s!H8x4->mKl!rn{E9cd>`l*n;|E^#n_qm< ze*N9YJAYVv-m4$;qKkc(0vKKEL-v0a#>;I&gzhwMSfA#9g^)G$%Lw~}P zC)an_U-_ixJ?^3Y>i2%(y$|ut{nSr;@e@Dgz0Y@k@1=+5Jk($P4b?0E)Ya@87Jr5Bikx|G!ZA#oV)A z_8+Fn@AvOG$8WgzZ@u;vzy6-ry>`0()$dEc=|PL;pZ4pI__TX}`z!Xg?tuOZ1JN^B?@<(s|g^$=zQU4+N^2OKx(xa6J?Fhf|Z+}Bo zzW3jLAiBjNuD|o=zah7uH#&d%=J}6&*E`q#9nYm6DSpNEv0w4s&)@lDPyL4vUV478o?P#qH~i9D zP2B(aXCHojEO#6A^}nm%{q(2*>{rVV)?Ipx@gFg+b3Oc=TU2`9tUT+d{?ik;$M=8d zGv4~f&-~C^-W`7MVY5{;{_5s^Z!nit9s1n!Mb1Xc+#_~fBMSHFWi3TOZNZwlWsXTzW>udGp$B%xaXeZ zAAZeGeACCiJo&F*^S(EK+5QiH>XmoA_-9@$|La>$*|$CAX?Op`6K?zXb3XLfA8OC~ z8vSYC{de#E>aV)zJ74;Y*ZM}-`mJux|JJa$o&NMsyZ6Pvct`lATb$_n+n;;^rRTG|8U4ApdGQ+_ap&%* zWn9BQ`L74%4?f?x`zt^F>)7YKfd7MczvJo8o897jpZu=RL)GRLZ@T+`{I#n#(Nj^} zyXW^_{HORUeRxBKA_6!-QY z_4H5N^^xa4CH%hkJohQzJH5q$KI~Z^M1AR&m(L%Z7d-tlzwF*uz4PvS7EgNN$DaP7Z~39$ z`^ENAkA4_j0HarYWkHjVYu4ljd2macZ^c|nlt?v(e>v!Jb*5CE-e*X7| z```ME553?WZ~X7SEDyir<#&Gm&%OAmkNVtS`nf;;uf$KjGLOs;y!GzcPrbb#e%(EP z?}0PcW1f8bOTPZY-}VR3y5&r8i!c1EAF4Mz_RT-|>e~jd`#;`q*^4*6_Fvrjo^O25 z2fpZMe!sfD=%2j)XP@!<$K@Y;?(})`YoBex{@9~`?+N7}zxq}4k3Rl2)vagu-~KX;yf1v*{%daW{NCy- zZ?T-*e|_%y-Y0+aBkp~H^2Rs+&VPI7zxc-DNq_j3-Tnvp(N1p%4Um3ss zkiOvKFZihbsMoyyG`6lsO?)Z@PtLu9AhP!^6%zop;Z+H^_hyVRgKJp#k zaqqW2`&;VScRlr+-t#9Pc}e}@ANb9W-1)`dmj17Q{(-l>@Y(PB&`TeG_vP-@$L{)1 z`saVqcYWdy-}M{s{=lzEFM8{n|Mv7bulk(9oBrs@|FOC2KY#RN&wkPICD-S@{LTN$ z{Rgjk-|Y3@T7CbewZ8lOl@BWE^Raim@?9@^HS?Pvf5iKK{jHC_ z>z!}=pHKKB>h@QD{O{+_7eD_;-tq1Fr{DX6XFl?_uY1bJf9HXF>F&*MeJ-lwPyMTh zz+XMg|KfGMz4_F&TJNHd-Tnpd{L!zRFLziu`Jec%ul?blyYrQ=e(CX7|Ji@~?6;4< zwoEeKl0d*|I!yd|F-lGzV^4C z`qAgT=RJ>l`%~Gka$fYc)EB+%-k1Ew-RXP6r@Z0`pYiV7g7q^#e%A-y_o|=!t6c64$IRX|7nl>NAwd~>a6}IbMNxDS6a8d#aiC+`wtJ^|G1C6>A!r*yD!EazyDn8 zWiP(xP5;;Xew_LElfLG=e~|yipZeMNKL6z(IQ(vQ%Lw(!w|@?1sBiuG-hX(&u>XIV ze{cP#r+iLCzW4c$eA^R#;c|T0Z~jvAx+lE&|6%UE!l+fg3yz{y2Kq(k&UL2%V1V&qlOElB??t(M4K4SyBL>MUESKw-1OZNqt@ zLCV!@UAVeUngOHJZmVl~BY8fCVGp=x4r&~fKu$ZLCZskr^C3TiAEbP&pj#pMEVpt zH7(65(0)l^Bt6zHl-i;t>$b`?V-A0xEQ)Rf)n9O(+D*AEXu(%ZQj-QADZ8}acqfGy zx>4cM$+b!8Rp3&x?F3N;qh# z_2zhW%r6R)l}pzT=L2xT^NuSiqp4X7;HIZYC715nK^jw{+PkY5f=3}l^@-QS#Meh1 zN9GAt_MLU&kq$fdMo=$Ui^SS4z4pcVIo9t5v%I**Hz>_`?5xkAu-*C!jRcLg5t{CH zf9!(L(0YXOyJQXRvT&IHLdL>MS?){fs~M<(RW};HTP(vDPbftCQT=@iL=bn+)iUvIRG59&M+f?w`j;v&;fl-UI*Hj!0OD zWNjD+Zb%5!J6^Cu*I6yjP;Yo`-}Rqe0QvfI0r(Mi*wYl!OyoSbcn+=LVS+1mP^|z@gEl$ z)9w)=nP4FVeHpj7L{379*T`>>eTQ1WwcH2~Q%_2X`vw++P)E=Z)%R{O%K`4sL#Qa+ zZi*Mhtk*63&2<9)Xvhj5Njzg11yFqgNyDbX^Gm?xVI+9a+wlR7Man6~)^s>_XTMmX zH^}TJg;=l8QCP7CYpvq3pVMsK{=W{!1ABSJS9?ay*`gvtz zCAze3Ka4A!!ko|C3x_x4lb}wLk>JZ4R@N%a*o7e*MBi;+@`5uWT9Y;)A@+)XF>t(* z_!yf(I7l;$<0n?H(90<97#F6>VA%J;*nIrTPD*Fgz+IPx?gIqrt%30p+lyp*h#YF- zL#@g{^QUhd_|KU?EX?BV*fJ%a<@8iSQ@YQTDD}_>zBHJJZPQ)BnR2P;J9^IaD6o<~ zfl%}&KDC`O^<#F-+e-r&-N4y!cdS=;wvF(X5Uyw|#5%Risgpbs?{2f7jD-0!=Q8XV z?i%zS9;=5wgli=lQ6L6P?cJG`KH($5@alyZIJc52AgVis32Vc9Rg`>LUraOVW%8=| z@z%>P(A}#E2;u1FBpxthxTm?BaLq=vrbX;2E$NqY)A+pT;BbDdg%;v^;M7QTy@F&F zsCvCwW@M0zhDCkq=_7+zTks<@9Y!stnPyFojUt%8G$knA3ZHNBlaPzT*?K#(yBzSt_jqIF_ z3Yr=tj1YyQk_1!&S?@GxlkdnQ_qlC^pki^ntc^Srt{`4tj(&qV=VDw+qJkT@e=gv1z=N!3ymlkSlx5t37I+8qNhMDkR(xf5PV-#!a$*6mKxS3 zM*Oov3TuP8cNxwc6lW@;)=A>!r}DfS#oZ$HXhx9<2!B^DO5litg#G9`K{P?xHLU$( zCUzMQmkII7U8Q%i1FQkRlX92lLiy#-CjJ0ohY!1RCM6>;eMfwGo9pq2tS~@tEebj3 zWJCaswj+b7-h1AZuk=2Lm47ed*o@}|I1tVAeQ5sMSJ=69KM=wmxxzkB1I5e&tW0gd zcys)LNzC)43(!YCCP&FAt>xZTig2~S*n{XWiXDyWJKjqsk-^pKZp>!!k?wv<=yWx$ z`C6$PEHJQ>Z2vhqXdy;C5VRc`2)AC57mp4HE1%A#6TB9%U}91YD4bvZ3l+W)L(w7D z_+#s0839~fiO_m!$c(f=Es_V$7a|gPL&WRT{Hvu{EBoas6=5_h8nF@%)R%UQZkqjA zgPGDo@~_WZUK~*?Wi5=qGB=LHJzZgRTe*x3O31g+swqLv%WyagEEa_sfEaD8#=F;U z(xgQL(G|D{Jl$Egl)_!-SiKb4QwDJeq*>X8RvtG9^`y84`^>FlwHIEvTgSk5-At~p z-ZB0?r_EdoqHk8O8z@kE__$e&&ORR|vi8Vr<8e~!?};*id|c?I$AFlARDpP@tE8yvnq&04 zlbUE7rk9!1?xa?2t~>M0_dDKi}|EeS1N4SwQb+;+a_ zte1wlYbC*qF%goUBYG62C5U1zVQQbHyLYayjo@SJ(_>)@N$>oX^L&<`GD!8)7~x}s zm30re4Wjks*6o>rt!-QE*A`za1S8h3vYGS0qLHWx@cY7WI9OEr)l%OGo>V2i2E60F z&j7UFJ)XR`bk}#DI$OAFW@;Gu&_6vHYM>6NJmW94_fo|>7npRrGSc9Sxi@Lde`NCfnvKuH-t;{&dXi^ z!RXcY`xCtm7?GT}n&!EdNY>1InyuA%!Z! z{Eu|xZyfo~R3EtbUY*K4aXc!ofmQ|-=sLu#>KjHkbKWmL6DfCY5Cob!?V1<9HU6k3 z{lcxigz5PA2t3E+92r)g6)Xa;>!9y#P>P%ZtfRubT$}9(vN7#uD|UO{qB;17|+oI8oWxHvH?H5 zxS4!xchSVWq3JN7Z%$13bGYz?-2lcUIAW|-5oiQutd+TSN%~_i{U8~OSrX1@7 z4!UNeYo$#Z7(KkJdu?WQLWX8~RivPekOcPmxU|6DY6%v4vHrEOxvj~P?QVRRZPrA% z!BI{@@Ijef4IcTo`RaR?bP{8?oFPTu$L~);%7D4gB{L$8>h zWjP)3-GfLWmw~%Gb%Y{{-6>GU;yQvR2;)8bpF*<8=eX{yR&353eAbi$kJ&zs`w7FR2v;-UI5iB5le|2!}v$ zHMn8H^|c8B*l|qPALttQ_oaH-a(L@j@B6>aw-}!F-p%Qk9AoLPEZFz(14xJvr{Z!c z;jH&X4j)(f>_d++pF*jJ-t!l+wJ%h-WT%{*;T3X`zqDrTD|c>E-#eI{pZU9|HCzKW zp9|LPvdw(6CH_38gb;KDa8B66(~&=FT40>PUIh;0M3SviLG&Z){g%nafW9aY8%DZn z8gne-fFblJP`?aWox_Fy%Lu+J2ZTu^$1pM8Tr8n(K~Pm}XwWb5gSi0u+31Q>!VVSA z;|VV|fgJdkU(dk|e@h_8P66}Wv?~T-b7`wBB^Q7*{T{)5unbpZFOB2ZzcB)qqXN!x z^6U55|C?8W#`gow5HVd2J(hh=rkjJGwB+&<%AMv#*A7N~FUN&Tj^*<}&C z7kLIQink&}&Gt`<6{o5NKQUBTmzg;>`#K+!F@r7Ucr>SrfGsQkFWVBjIk?9_VL0)n z7(x2H|DBM4zJV=XH#mXaJB-aJH@$f{{qo;2%5OmC5%&EY#bJJULw9Fq)Z|^6YaP#O zKs@zkb-YfF*N|u4`VuD_fA7P;dF5{$60gJF7`eD9QBf$2qSzQ2 zUv{yNy)GiXYHIf?Gqm)c8pqES0-DPU9{&!2oR|=p`vF27+s1L2eSS3AKtBrTkItp- z@CMY0rV2+$&0J+Oa>MLxjgF- z9(0iSR(P-+D1!_@ud+CXNE_fVS^7fHkB{!R#v1@N=%!1+CO`o8(J@g| z&gpxDe5}vGi{@r4YKKt?yxf zPm0)Jg?En6cn1zFE`QSZn6hghZ-}_Z#id!Vi@?yVz&OLDJ2z6gL&l-*otY9oeZJ)_ z_JwY2)k@fyj79yRX=YuV zADj^bKG{2ZF=)$l6@JDsDRdjKp)q=IbieL-9D~X7saOn^v~TY7Qn)*x{Ayt|W}&ZL z#U9s!9C=KrO2u#5aks=4Mt1Fdpcwb3!09=>87dPy>O}_D_O2t&`qAQvruVQMGwWIQ zHaOwO+XOp{_I`B{R2$f0X#~K9e;csYs%;d;OVn#a{^FbYx*TABns_ft(je-+7AysU zswwRmEu`9o>5`kiETk|hyuve+0_6PXO6?LoXNE3_PRP>;v1F8`*3p&OKBbo@t@uyf z+HUusT>w*YWSoN*1w7pZ48E}9u&dRMVT&5e1Jt$nB3uyTlFKI$IVsS@bE5TdUX|}; z`zx*WP}QXmTO1}7bltrWxE7SSeD&fTTj=MfTT+bjZ2g#7;R3p0LrW9gvMch^p=vlS zVcq3v0g8Gcb=9zd8n&Q6oQh5ISI0&hf|+HqE65F0z~q?f#?S+Yn)=9v_D{i(?oi3j zhtJ#+0^LPG$tO{c!_#?WRg-n^S$o39nz7dW=M5iiz+u|oIgF!gehT831UP3zEOhUW z=kOaLKrkwPr~rED67mLFp;Kv%rgtTEJWZS=b-de&Kvadz2X6R?zoE%l?WUmfhhB(m zpX_<9eG*QJoG4nKV`EUN*N6CuaoH@}x=Nj3O*Joa$7{6+`UhUs&;s$Fw0xc_yY53t zB6|bF=n#t&VgVZ6u08v9%#_+CL8l;NYC@&~0}5k7sxyGaKJ154vCwf%VR?B=K?WBH z{SkK6y^oi#P%L~~UKPJ+UnvgHEq29xDZBz+^XMW?llt&=+pWAR`$76LguI3I82Sh) z7%*3$t8qHO8;zcyJtI0$=$HsRweWZ|IED7wxUbTu3AWPfJ02`Ry5C{c=tk4B-X9{; zdWJ6QeNDsIjL%HICEB2?3vxDE4Du=2!Q=6{SsZSa*oXmntn zC*jxl?rhL;@Wws%mYjLEW$|@i$98RQol5W7#bsxWQ&p#Nez=2~?j`37s3l{ugjvry z&+)32o>ano3u>+cw0#L+@#>5#?dLT8N6T8@sSqPDEYe3oNK5b2%^J60>)l(Qx09tW zJtq`AYOz|l~?d7*8)C(5>7Q# zQ={DKYq|~@Nj|5x_^ew@+4r7E5hA*|+pr*(SNzi9IKa)3K-L(WxT4Og4LCmLq;Kg` z9|KLHysk=F*IFMXauX0N)e2VS$jW+VtuccR?5$N|o63!J^0sh2uHjZw&Cocl$wfy% zpFw9A=)&%`bCt7VHZ+0^BJqeqS67!Gu`cSmktQFIPcU8AaEmWwN)HnJ7G7A4JjFU< z*IP6sbYF_aFJDUxSkzNP$E++(xr9p$PQQVtw_%J>B9if}sZSc2ub-wUW4WZlwW|Eb zq$dxiueP>PreaxcPL#Tq{&>5vlY?5fE-k6gf4Ei>sB<^0=LkCHzCo#Bd?LzkmvK4- z|G*TwF*$Qna{N?R3f>Nfk-xTN$y9mD+bL6t5bP^s|^F>e5N33Wz(+X2HIO!-Y$QZZ{+i45{sJ7&d^A z+fPmu6;=2Ayb#ALu=b(Xu`bo9B{Y^_F5+noF4TLrug*1w5((s`#@9$iF$6tMq*l#g zVnJc;b;wsrd77k?Cm*`wgp5S6kNRv$K(;^BD~aeusdD*6g`!DX5|Ju*XxO55cd@TM zT@7tY)M`&mI(CdrHjC=gpH1H5wAA8F1RKq>2WGS;ZG(YDS;Ji!P4MwJRpvyh#~$*s z%FDW>0MDqs!pd$Xg!_2g@N%41EG>5P2~m*eV{I<2_n^K@oU+aHpzL))`s1#kvE&^3B)5L$aIvWT_A zQA0>SRz8|%2X~mZ|H{CO>z#=TMd>;ZTiYTjO66-{@icS(G7dc3>FcnzC_;D(dZxFF znnru`%2?%m4AXEp$ww6KTfNb{MkTeWK}p(VWM!{MT}u{YW14QJweai7Kp2GUZ2GD( zY#2F;Buw+Z?hC+aQaxC&6eP@Z;AapolYAy$SEVlH6O`8VmAS9BJm#G(A1dmwk8VNU zspcU_kLR2U`6rZhcADKl*2Fa!awz&ZsqMe15L&<1bxTu^eBjgqD-sE_{(--68ct|M zd*0kNt9Tqu?S$g6!H%dGlyc{e43U=y0ct=4+VG@Z@##k>PZOpb41SsAqvK2|)s|qZ z&GF3tPlV)$B#y-w8bWkAL} z5(TLOX*jTOxgAak91@2tK5=lEN<9M=Ili_uL6Ci!XZ^wfG-)lY;x=$Mz`2=sOz(9p z*$%FTD8-8fj6S|XgDG=U2GpHBxJuFh-gEHik=*8PGILDF)lgdez_InY%DRQ=%G^`Z z1O@xF zXs)H9^QH_E?3pro^)b}(x)1PlwWC*?-Uz3}`O-AoGA;51uXcm&m`hX~xyRTad?(tT zobED34FI-0Un$PM-XLV0?MU>)jgLi762V+-y`2LjO;I1s)fIdB2obm;6aMA>EeNHC57Ls)Lz*Pp z^_)ag@#}NZ+FTO{A2c>B4qS(#!nTF;kM%T2MB}2KlDVP*!CVKV2k7Txbyh3W-Lmhp zvZxzev;FKc4?sY4{ZuIdSS8yMu=M!jmIjafwAV0=D{2y4bG>ICiKrdQqc-i9KMzzv z4S4?g`l%_|In&kLeFQatQ&ReYOeWa4<;4qjhUtE-9U}6Cor3IFfG1x2U!w?Kv#hA| zM5!l)VXwR_O?QCA z7CtOBh+LlX>A9&C3%;%-AL1&fL7ur*aHmVT283W?ANaXbw3eU8&l`p*rSCMjnfB8a z9OKeR?YReGbX-`);dsoT4`QaHSU>UfuNSMe>Y~kaZABfqL6E}bFl4Fkr0_CbHjuIG z>olI-Oh7K_?mCiA<-6d)plZg9^lZA-Mr+lkVkZggs&b}!-A(O;7t=X&948(?pWL7o zY}tMuWgF5N03SxC*zS(`M`bT?6B+QJDC(QL&;JS4{r!6k*v}>UEw2Z-dUB^|v9?Ar zCac08(rN2sHH?%@dfQ-}V7o(15U8dO=3y2tM5vNy*p*WAC(xOs#F$i>Oi>Fln-v1c z4THEOG5}=>UNwKY#0NxQ_;2yqM@U@^c#u1q*{qn=XyEZsBfvisDVVUJ>ZWMbV&s3NN^~ zFoL$t9Q29)#ae3H)u|}nJ^o|Gb!ts#-XK^PT`~j>!t&Cb^xx~hPAI~$YjL7v@FSC@ z@s9_iFYIE?^cTBaPEeD1nRD%Fo_;|*uZJFVD(VY2II%`kQWR7+p2e$tX(72jg16a^ zno~OR@Z1A?*0Z^_-ZaPOa|uZSho5_n|E!$~%`}8ypYK?No1AxI(Dy2kdmW)qCZ8`J zhJ1jpD+vTeLzV_QG+&|%PGY$CMgkxL%7J>Im~A+hV&?jXKfNjd%T`to zbl7)Qysw6(_W0G&(|4MBEoZ1QR!J%T9T~6G91Y>I9fg;?fgJxBYNVwW;+2%BA`d14 zI`0K>HkH2Ih)uXh(_EtfA8;osfe^&|_{1JyrvhEfc~bJ{KA6)QyW`3_*n9ZLJDl;_ zY{IGovd-&}Bwj2{h<~q+SIP3JT|r5Gcy}2z_17*m`fk|gv@4g_K1ig}&g~deZCo_V zvR~D!S)^qV0?*9Fty}?0o5L*Rrz1d!DPYaTkY^-yP1`SSO7NP-W8aRAsV$ih+39bh zsNW6paWZo(59q$XhGoO2bIKg2imqX9Y8ScS4pQy>j$POT&iq(aEt+z0AIx9$85z*hQI+C}@x($!lcMo~7^8;U z+|7wEQZIa|V56uDxJaLMT+DIuA!BuS|J##;`n1auvr4mS!QSobF)7B*7-{vZK52|h zwP0f!I@15~@jvf+fy@roJr+P$obsO|rV?11ZH9vp37q75t36ws&!iS8{4b=uEVejL z1jMo(r5J*#L-0#;Cy{FuSM#5l*=_b+4|h;lX_6KSuXj%WZ1`7xKM z`e^dy*@f|Vr2_B4iiz%G7l%?{qqe^f8)lZ}kjH8yz|QKK4I+MGwFL-ll0LC>^AYbh zsjzJl-gN~1nUwnbLaDXrm=7VOdp9)AKurz zclQB9qsTj{a{)B?91_V;pw5+g=e#i~U-UqMou4ep`W^jv76UsvtQ?bR5FM}dF7w=p zHLVOolo*vP|G0jy@JWw%OB8{Vz>oZl_3M}>!@Md*THyNgBGhuDyaI`Q-Ff~>@jEk% zT50`Jk!_oF8so!L#7d8-t2s|~D(;kObd#}9Z28mF-@NFuPy$1xh|k>uZ! zSiZ2@@xUQgwA4!aunMk9RZ+dfUD(yK)H>Vf3#P*wHkbp!R&n!d;k}Si3p6+^)gi=N zf9!fM=~^3fERa)CbrSR25OPeV`0Tu4)O9P9`IfMxp_HwZnd-q3 z8T$K1uj>0l$o4s`hgaXn?>|VN@8ScWDA19WUMM*cYY?X{t4#36`@(=)w0QEwF}aFH zE#3N1tcXyR|H`!)OR{wTBGH+kD^WrQYaIKaT6?yElCVsbv%tJ@A1iYN%DS7O)fT)v&j6McGTKSNB_g zwf?%p}fWFkfUZvHPPU3VYe#D?&)@EF~psF*6oz0ve~&EDX>Z< zQjl)aM7;ke3z$K$iQ22Jt1HzY&PKTL8CqXhv(?H8VE55e<5`4WxXJZCfeX*6tQRJf!{Ytn<%1311G|^?d}T7ay|ba8L8bZe9S=?Jn!r zdQK%{=T}X`Sk!(MYZzpy$D^=^^*AO4UR42E+P|Y- zxaLBwK`Bu{B1DT16{xJtrTX5;KPr{GA8c82cYlazfTfTLv=tsu4|YdOoUq8{WtGFu zqBo}URj1ObAEl-*RpYrWpUa3K*vZCR4$Cg<*y}%o~BQ_lA})nP8QxSL1NI1qhD* z6blX7gl1bAq^8Gr+J?0JaSJIV6kI2^LySi|34X{jr(0uha_`+rP0}aKhMJIy+w<)d z^n5MGVYyqDRD4sSeF#ajMI66_&*jTQub3}Q_RH^mtv3D~nxaR#0CC1tzx}8kttqNP zT%ZVUtX38Lk;-bxN$cXF({9xuHfzyo(OL_46a5Ip-O*}QxHia!@s7-!Ak7MQ{ue1G z8sO0`RmOmR`%ka07s936RmWQr1n>l3-M(12{<;0l2}+TJ{;uM}IT$yQfGN9?W&yFt z0=qyyQy^U}ch8Ctja~`DT2#UQovI_usp^(cDci|x}#qmTNM0l=Qv({NC2iqS*M z=W%GNl-I=#az{bz;8mafraIpuOPcx6)ETNgHqaTTh#3)!~VOrPtNO1tor?#8Ybf;g+w&$Ld_9b^HkVfW7xW_7XzS*bl-EQiUPq`I-dtt3CkAF8oZF)nW z)a58{h@(4AAzVotvsp7l42Z2R|UGj&32U^H)`ssUF@-JVxx{oaej;oh1RO9#^ zUrwB!=)N6(kxAtcO!=p`g&jb5yw{=j`HVHnLUN&3N*zpqzDvv&Wr#pK;-r5^3{1{=@Q@*tVWLQU|_;qaZj1~;aP zCfkzXRZBxa0t?NACx+oaD^!n|LyM2qhYe_Hk4c=+m!qhWTr|MZ)yZ%u|{0O*JN zFHc=~(eNVHw=Yc|xSw0Swn^bXy8zPNP8}!SnPpYFB znuN&Q5rK|3MgGi#h%nDmGFsPiUVQG6pE{dfiqC`{zpetGv9w z^*xgT{s@wBNIRi7LV>ZkafVp8W3q$dnogf{mC@^#WA zr+a<&hH{TNP zQWTZL%y$MHl&+fD%E>4}d`G_>?fN;MS;aqo3x_NI>m&W=%~yTdM|*j;+Z~-ZFC*~J zy>RU-%q4xX?>Sx0pIc`C`8v*a`w+GtP^mQv(<+3#SCc;c|5ycGss0^8V=h$(_kPDs z|NLscoiAOIoy%c>E##|?ksAK7aSv5)zRqHzhLAJ4qMAQkEG6G%$Gd{vb<9HbUD-2L z|J=SZx`(ETEFN^BM|TjX+dTf>3xBrO6(*ocM_-Y$iN1Es*o^%ggcRow3>p*oOsBE7 zQckiG@BBCc1|4JDqd@LGoK!W&aoL`;<4?+c3zUL8KODe^^6j?*wz$^>kc-T1I+DXO z{-3S)d+0rv56T7}9WZaWX6b(6ho3)sc9Lz1nTT63YnhXR2_tAg8VjlVd zw+X!C(hB+P$AGy-Oh9blZm%cm7e%isPbFKkqF!sAVB+*JtW^e<0zS6B9*scEQ)HQg zUoe<4x>~ZVnmfAd(nj$r(bpiGq zh3fu#SRhSa&x`)%ySnRMeH`8Km#5`~i5n0$hRk&UESJ9ed7o6}W@W=h_qq4g5Pf)R z$G5wne>MHE?ygMM$=msS2m8E*6J#hHZuwQz1dZ(UWywaxD{tM&t8*51l{b9=ySkS6 z#{(l+&0pz|*Rjj_hbiEvpgJ}!U*lt^2iB83fClJ$a!ALJRE@Ucw zHTUO?4)l66IOTcbH8^Pot&uz-1-%? z3(#GWRwo7?3ZD9Q1;719m)a7Oyc@t)*l+YVS9@%}4RC+8JPUh1IoYPRdF!PsfZ6Yt z8GgHUtl`Z!rpt_wJ2`m#|6c(8J%SEF*^zx&kBtt9<+!cB`XBC!I4k}0o9IhF6K?&1 z|2`)I2ZoxaX#XL$o@L zt~>Whv0))~sXW?Pe2n}%?xmv6c2B2%%exSBBoLbaXQ%$}zq)44E`!aU0*P04d}e2p zRsN}WS95t^yze-s`K9c)$d1K?ax*d0XGE zvGQy;5mETHwi;vb*>jvs{r9zTz}TO|Ci^6tB-7Xh4C}B%s(W;aj0^I4==5D!jqpO( zsNG0RVQJKq*$IJ49Ehl_!9~cwRpe>{;=&-V26p;Przg3PTx=r|GX%AYyk6g@BwI0% zbX0(^XCE`(qP{K@+!7(YGId}ZnKD@Hr=f8taHiai`o=8h(o=drxqy3w=~Hbr)6*r~ z6;^6A`2HeU>dOPV=DPF1JUv!nwa2Z1d1qOF(c~6=z!tM+VArkU_Ql=PlIL2%=2Wnl zK&%adbwo1fdaA$YY|(9zfO)y68wiLP&vC=3==wlDO=Y{hi_MaT@j{s^m50d%KyGpj z#fc8mrc{}&gZ4FYF0zXo8{2k|w3^CA>*KUe^c7hbUxeM;w`ud*&wE4^75!;bNkqL% z`NhOM{yGL^JaM9vHMUCXGawPR;N?pKbK{TWe-;;Izy|2u_=Qg6gp=MES+QEtd6^pr zwyao7u2ewJA6>qZ^{9B4OU7uqPc>4gGN&W^GsB?%Rm3taT0+HDG?p?@@R@wQZHJi3 zJnSy#6E11$9X^BFkTTD)l4!C1r@PRh&4>6&CI%@j2(nd1Z?mzS^%hZ@5@SNnUmc;W7w!J>$QzbgkHJSx{w?tU5&SaTqA? zKe=pOe%~jfDPF(<9)D1{^v*t(?=9=V(~t`TodJVVs)*%xFH&o6Y1=Oyw9=vaD}{9u zM-wqk33k3SSa8#0*qqqGOt<3OiIsyXuea)`5AV`&n!6v|Hr#%A@*p=c&1|XvHDi}X zrRn}v)L8(W3Km=6AH9+f>FZ)dg-><8(>ZA^=zw8r%F>l zQ-Hct+`)C^3ISlv@Rs>mcbKTaUNN3C<4xhw>u#b3Q@rv{vK9t0E}F8kzT~Vgh4w2O z+uGnS)PerI?X+^Vjk=0Vc3cwUSNlZJM9kPOUdGM(#B2HYZr=2n6JB$zh@)-^xhvS8 z+m+V&lA*vIZMemoSpi!wLZA#24^|E+YqSi)b5x7h{hGid$66yI^)2_~9AE z%Y5_J_mat4bYf!mXPoTIs~sX}a<7JYmR(^d{}i8g{RNX$LV+EAqQpP-m8yfGHKU=&cL=$l zu+b@N|H8gID_!}JKuZyjl{-38p$B=ch~K*Vi)YABK?iugl_Jl3Izb%%ZNp!s-#=61 z?>x$PnVleh#sLMQvZ0mdh@8;I^+3(#*iVsP+z0`Kt8RVrkX1(N2r3|(N7N!GL0Od& zXOw#Dfe>V1JPWI&jjgLmt=D~j_&ScufaH$2D{gkeEUYkzu3F-bxzw3ueCz2D(i$uI z5u^#qJ|G{tCY`}NEgv;dL97MBo2i|s{YRBpmSm+aoNre_xK)RJ@ zN9cG=1eS{^_0H_A$d}vq0tObm6H6JE1-(;|FUu}DHpPke6PIpR-0XW0;wES&V-e=4 z6(=ab%{*C+R0ma*y7I=(4k*BwxuuxUg^t@;3)9=_3~TpdI)srlohEz_hr}EjOr$Sx zA3y&fzH1~+);wAh?^-cS>IMLX8W#Vutj#h32Nt&>QS9t$!d8l z)Ro2OJsJ_t9pdOj5WxGV0Wm>?Q&mGAwYzDv=mQ)sJ^12_6tY zLVWw=)bJ7yY$F}G`qWx5i!G~u-7^!dC1y+O)WZvHukuDpSbM_6C5^(PW-YM`A#?GM zrcO>Sd?DLmgYg5xc)NB

EbPW35`(F2W)tI_hY$W0j#RsZ85D5=h)SzyzV#w7*Ec zklYmMs%$6EH92^Ozy6cz((}gDL%!s=!!@MPkM8w}-R&=aexCdr-N|@h)=h|ihkKFb62kdzVxeoLswc^O zVIY&o;X{^PZEA>nA~Pwe^IQN?=?LAeLoO%w8F3iJoCtC1PxBVZXWn^w_Yh;wr{m?d zj>~kvxdYKg_kD8x{q9=2YcSesCkD`N*5xjl6Ge9hFa82~mYfK7FTQdz^@B$EH}&g2IswZ%ulb0DqkW}cjyh4DE-m(xjS0*WHn zb_>)O1L&$GFqOxf?DhrtxIW!Z?98@Qd$#ut-qlz>Y4yf009oFiA*5foZg-h261z-a zjXfuhbs-(sjU5LT=aXhT9Ae2&w^9_dtR&?63bgx9hHjsN4^9_A$+ss1zb+q~o!t2* zx>Gm3aMAk3hX#Qw;tZSyp3J|4Kr@`_P4}AtveVK~$c4ygf%*+Wm1SF%?Jx}mH(y4$ zAeDqGXtp#MK?A$E;dCXyFh{*ib4j?s#ng*;GR1EYA&AZ%OkP!{WVy%^Qjf=6u7N+x zZ@qu^bsB%=WTd>qc;edIu1Tv>n_L_Wo&*A$V^-^ypcVJ&a+6EhvHiM0?hii;;p`fFDZs(-4{~nIU5DS{{Fg^<)3$4u ztP2CkZ>-;%92L%pWHc%p=UwRPS**Z2P?jyzgoF9+&M)`u7uHR+>`I{lSehDC&rC=Z zUHcF)`KT4-W2xj_?dmInzS(%K5R&t02>B}y`73dOn-hf*MCVyg)1q(`>*TW`RK3?b zEQC8cDw^(Ab#dPC<62B)-%5GFuR=|Q*ApJK6|fo==JO-%vq~aKU62@i97wme3|bml z(Uo;Fhce}1VisHn z6Si+;SKUEQtdC+Lof$c#2r*ZQpJgxo;zOTp)q*D#9y0V_VqAn>%($_gUHIMsjo6&L{zTWM14 zaNg2Eu+(;IPm?`?^e#aS&+{8&Xh;~4aeFPOc;v%=udX$eFAjOQTPMwVE!&uy{ z^1}n(Sm1$&ZSv9Jukx8ndNeiQOXDf%x@|+suL`Jti>OpkFSP@9BKnT)-5bB$I8#+<6a3fU<9UV{N@{Z%ec*Oj`d)qJHi8$7!NY-F19Ee7c!oNIy z*s?U&r?E28uCbTC<~*M0yPO72Th{ZKo2E^f2%fR=7 zi5sEjnBa7!@Xk{_!hW6@@&` zdf%8m0L-N@>3BkbQs!;VY$u0zDX!-bkB}FD5fQQm6Ixj6D&Q*A9Y#el6ul?WcdQHa zGe6;8KZ}12q^75B#7wf$)O#p!DdSC6`vf)$v=dLwGZcT4`uU*2=6l^`qjl+U=4n6( z_pD^7bPTnP78Z;?RYkAQ7OnP+gKBZmaclvg>ac_ygBmC3LU>!Q-fY=ja@0c4I#2s= zuiabv<#N8PU}(rP&&=XfSVsk9F;zV6re>&tvA8(nltf7dGtzhZxvYJ2eQ|hS_Ala@ zTJKsr+~>ENzx$1T5msa(kQn~LB`6!O`)z9C!B{+{`n75C7vkz4)$itCH(z>y8UTJQ z_i2;e)L22x$UG||%&3K+4iqs?u(sV02Z~&Q51t<~+bTbT5x@LcAZnW52(h7^$RtLO zFwT^Q;Sd{YfV5IkzT0)EIzb&2<$v$$(oC!3;AcO*kDxE(UFA_g!hPm)EFtq)bgEdO z{GF>gBI`MrK9dJNz>>0}y0!Ar!PW}DDj~MqOEIg7pWN<7)}%TIs?5fgxe{~rmoQ#E z_@{Th8A~YTPyG9SptSG3KzKj_$-8MBx03>zAa7GHKTkJR^>y5>9kYvIIG;$;ICK;FEzg+sp` z9dgXkw+s7-^$uUQ$U?c*msm1{ob#VzNB+}&Id6J72xwfGG~IN*kbIuaY3qzH)A%e= z7J8`Gm;68hGV2;$FUv3r?<>BO79#5306d0X3v?dYrxYuQjuBa>_lwtxa%&}&1gyTb zuy56`A$7|b8WAO`nQr!pu*n?fvqq#x%lyK4$^##^u)B;>liB+@Znfii*{>fk4yj)K znXrrZeUyoBphC4V8qa=9x?`4gp2)KzvxiW=3^vkz;O{jO;Xu=i_M_L8jx3OEqY*aV zclxRs@i`-J7(Nj~O7GHP(?#Y92MU0Pe2&=+C2Pgj!?v_YQ)*5MkSb^Da3lsL6I-<- zSKJ+OM#+D0(L7?6`f~CDPH)~3&%Zo^2INURDHBCbEB??5iAgbg5a0IxT(c*2^4FO0 zqZiM-&3nmxw7;L^=T0I=;n~LLCxVN;L_z|RcZL$z`IRd%^cEBA~ zaf|mZFJm~)Dw86Mq+>+nQTcvC$<%e-1DE_ex1`$Fs&tH`lT^a%i?h;0X6{k*dv52L zwI?G-A(Y4Nq_D#d4xN*O9r5l7-Uho=A`GK6JyXpK+`|0}7??+V{>R931FSJmhnZg= z%-qL5zfK+L2tCsd?#{Zdq@GjZggNlwrk!D^A&W?qKh6*Vo_7lss)&DB<$v-7(Cu~C zdBbj@eeDf&>hihN3A^g;0$P|RK6S_dXOz_*mj9(0&*}NqnHg6UWzzgVy8!lcKz+Ct z7TZygu7|wie9?I^AmG3e|GGwp4*Jobx7h$I$t_5WX1~A#Xex7cG8ol6!0g6D+J+Pq z<_26}3ZY{jzE}xZ=~%r*xL+1Xj(Qp>=1HE<>o|lPh34|?fbT;gJ0#wj=N1W?n`9kT zx@OWp0Q*3HDsmdOf_7S`#RAq5n~hI%eRe2Gy(_D}4&)8&G>d5lrYib6)hDhMj31M) zpN28_324x+XPadDTNke9p{N@HqooTt3(R8`BZB?YY4|*2NpYW)-%+J2RYI)YvId)t zV(2n&XXF1r_TDqB$*tQO-nvz`SlCEY5u&0ZARsmLs0c`xE;R~DFVcHrLzEWj9aN+@ zk=_;QReC2*-`&pzkR`I9SGuH?>L)|zvSF~?kMAh%}fN-1ay z^o>Bz{3)LNaf2RK%e3z6_YpITF82TBIK>^d@6byGQX)^!MdN{c;csJT6oOGe-cq6? zIc?+&<&n4@DPLIY%M@WvX@fOU_IW-nn`S*v;va@DIHZ$LA(0PMM)K^p;m`w;4+8t0 zk6^E@>;mT8YAnfQf6uMso8yAK4X1rUqtjum)DHQDunOnVBO_~zdec)TYykInFtZ}Q z%negHUC2n0>*OZVYrfi2Bj4Pp$jpBW8>TuO_Un5LYK6JWM^K<9c2D{EkXhnZ`9tTT zOLsWMn=15bJ`IVuVSBFGk7JPUV24@zn9hk!I!O1P7^P*Wcw#EW@C9_tvCr$bQwmE$ z;&gcUZD@JMHmP-$*23TFDxU+Gs045k(If}#%cf-FVSx>jfFtI4SEB|27jOpF6>yWk znh8oKMbsTjcAHdP_Yam($Yxe$IXdC#x=U~zl_bFv-P=V6!X%qoCQ0( z-6-+w;?8Ij2sc-VV&b5QclgepLFz_6_ROeV`OODwodPr~=i+lp-S%)e={?gbN=q3P z=lFe*L_*fEF#^s57YFZjI^&8h8eVlL%k|WjJ7eU+<+rClOP2(9h%OhIh9(BzI7|;D zUC}FE5^cnSGaQ z&&WbC|CJ_q5?Kt8v;M_FmCJ)ZXEM&?zf$IrV*b>KUdNR=YNgk~=w}SQIX? zw3Rz5KdTfu1<4OGwspolJ5#>C%|A`FfCXbh7MNL!a^PYbHYOXIW8W&3w-$$lNtJqy zY5^1O!FPVXeymQHZs>mTR=F?s%o(InrIm25mh=ft5{cvJgt$+*t{+l*w-++~nJ}S9 zNw76aw7pLnRRH`q$3D1jt#c+vS6bA(zuNaZ-$jmja0EUKBUIwB5@O9}1~>f#nkL9q2Lnb47) z&ebY;l?^|`;+m!Ry3K8|?6mf&1g~8ZHqCW>v&o+MgbitLc#ta3tq#`GCnxl$T+a|b zO0RU#&wkiqT-bwC92XaClitKxn@lH;C@d0HI(QpNxFds8eD)kc7+Otxus*SCwE(|- zKn@_;d%G?-(O5}K($KrF`NjHQrm0|3KKJZ3f%~ovxz9eiUjaA9d2YWdsCSLZ+cHdZ zJyLiTpXSt3%=ZhfN=~j4(Ep^U^zU&GlOk1S8m8LuNd7^=HPR!xABrpL3p!(jU;|Y% z4FHja6zpWV_L#}wADExLzX|e?LT^RCnSZ>0L(Nk0E9WHgB@-$8!h5B1)w@eV$@Q=D z(;GVoF2MsQY|>a?xf&JQcYaSyWlOgG#dp=srXgubR6QTfdGxK-Jswf3-p5p-v@40l zjzZQnsy4)kt<*vqqmkpH)_U2Wn#+)J;+!Q_})Ij(cT8L;sd`Mzv8(R9rF`(A+#pxDl6wL~@@ZiUG! zHiubaMcfgS+t#E!c;$qQ)QCtXLv-zxLm#59%V*PzsFieG2xu=qlh z(djUX1YgzML>W0uru!peyo{VjX|$NG33gT+gT;y1MaYzTwdovt?tXH}cXS9~vR-Fg z?fRFknCG~Wyz3qqNa^vQ&J{hBEN((Z`pN?CLrZVZw`BgB*k5gO3k-rt);>gBIn=aI z!Hf#B0<(0Ov4a7T?xF{0)$f0Q`>wlE57&OO9jjNXrh4jCVH2A}`pxS)WEhw8cW-pl zC|q6fXE;^mUg7uV${VkC2I_9_?$&Z{Z!!fyp?o#f>1DftfFs3O@}tTo0hM${{9(cI zC{U)M)^Li_b{|d!l_7vQM3tQQJRhi3`7TCTA!(o8t=(HHu{yBeX}f-npYv z2GA@W53&U;_B7_?-iMDXK&fqt(~ZM$we`OvwJ*wXVP=cxqS5z=T=7|@mLylTW9Dl;x5xX|<=E~Ntt&Kzpvf+NCNk)m9`Oo&gv zMorP#2nPs}DTU1+l86r)my5<7AcYv-SBG0*-14`Bvvj5?%H3OCimJn_SpJ5athvlA z*j<~CZX3I%Jvl=&BYI{t$(8BJ-JV2sP&-Fk-=!8zT^ml_g=B(CQ2=x#jIr6vH)7xS z$ko!nF!t}m&wuh0?Mu|Mp}8b*hj6mORjb}w$QfiSpIedzQa73t7NXv@K;KVBLi5Wv zS#JBcnS^Y*-{T1x`rtYyvAiVwigWljz9YQ!PM7m{U+jnYSH>k;O&$)!q9M`_z2own z-PE$(D3iTMiTSjFJ z?z8H@x6~7DI{!L8{T-}@V|aNP5Wzb?+ae%2;kI{6DAgQhyLwvFWLeQyULsk{aoX9M zTwxu(VUCx&Ty1)kRQmO&2q*oC-VzIfhMI@OuVcELD_E9W{_ky!i2P- z8rrL)K{T(kcqpu+5Lz+UFDGiJ93s61!^#(PM^2acR_gA$(%x^NeF#e5Op*o`9B-0u z{aE2p+j#;z4%WrI& zg-ZSEt3JdpQS6{~x@xoTTd2VDIBJ@xMfV!;!+Md@lR==Ol}Zv{49cE*@zpI5047Eb zc_j1T#NM5;F-n(4VI!rEVS`}+SXOd?;)_Uptp%Z6Pa_GIUJnw&SAGDiDW*N}tIg+( zzrz`?$QiH1c_6LjN(&o5Qao=w#d*1>TwiK!)!lK!3{wBq-!39)6I{aJYK>awG-pQw zgW|l(sn6v)qgNKvy3CM!;wKI&S%;+_#$Ng&+~TT`T%e2~pM(jK%&H7I!ea(A%Itr{pXT5DxZ{i;O%2+}~d8x+e zi=?-k6}t^w<`TI?)?duLa6n*YQ_+fdcQh{ds*Q}moyA_5!?w-6Z?7EE8ew3!?~uvW zpAjd*H_v~34{H28NgU< zZ(C$}bpc##V+*Pp5^SS~I_AFotB`qDAn%bXdK6oMhC#{_7 zYXk`kitEp+@9@sAG~zFMR>PUhWQTqb8FSs&jVe7^8kAP-S(aa7Ghze?QX}G=>~}pX zNNToudPD9vN1%2@q-DZ?GQRwK4?{!|m0Z;U5on^(s#a~R1{V}mqH3LfK%&++VPls@ zV6G{7Ix|I7@$18Z>WxUTd<%k@XPZdR*OxwIt#ghP?*$4pQ;T~={4%U z=V%;;1f{WLTvhl!)%v;$Vd7mJdGK%UFx_j>S}$8B4_*mO73un`Dv zAqomeh-WBvC;_u9lb`)1PQi!;<{SMT17Q0%I9PS*hgw37MA z8QvxTZ!kFNB3XK4wC--ovuVgV5g9d~8PaJ&v>o%&qXq+bsr?O`dDPzCpDvQjlik~jgJ@Ni zWWz$$&0YICbr5S%;c1O$SkUK0vh)I}vdoxA7sL9ZAvPV8ubXR~;4_m{@F2Q4O6(1q z274T`t$4hQRIoJ!BpbOwHM`#X$;Z@S!Y4+o?&hlnQ66aS5hZN$=`Gl5;kQ!(!{?JG zWLS=UG%<2nsb25cg&=+54%#rV#?5ET5~UOmpf^o*TbL*litdx24S8d7O-tEC;f|j> zZ$G(VPMuLl9IvomLD#?shl-sG>&o>{(=WkAginq(ClB9MPnpHs^p#|chKW1gSAlyj_{*pS=yiBn-LjrHx~n;b_Q6+3aCFR<39 z*_}-jX@P1XULWwzWAGjqznB|uAd&-s&j*X?Fx!_ zT89+733>F&M$Z*&d$>F2yZJ!X=#&n)f|1MbwTBRSmlkf|wsP z5b#W>BpEnb<3yruayeP8>8LIc(br!~R^mV0RLCA+B9|D&oCH>i&$Phrk%`j%?&QRM zFqZi)M2>MAh|Y7Cy&1Yv9?dTo60W$m@RgoGC7XAuQhc;a2Y?#t=-8i^XiD4~22yhi zMyxypLLzOQ-X1UVz>HcgluJOf<4w}_tIq}=##VJ(1fCZ2@|nbk-|x9}NVpuaf!F(j zF-muV3y~{GQ+Zj>cdSbAc2%a+NKmm@5c*x zBXYXSbVIvBZGA-Td%85zy_i@Nd5?J@Xr0RysLjw7oR&x|KpP5wqLt5)wNPB|_k8Ng#;! zU(4waaQ3wexbl75P?%GJ{`|)cYJ%(_Pa|K*Lp!V z0#-AHjx1%edn&m1pz0cP-OSCa=Ka7WMfG&RPDQeG)$*kmFy#fHd}lh;Vt9M>N^zj* z8#G(w76<;EWq*apd&d>ZVHf_Dhb==jNrF6}P;z=jH?!J^L?L;Ge;oSIX`R<;ut>~A z1r%3_T)gr25b)AmeznwhsP*-q<|T2@3#b3-V&VU(ftbn%x=xxHACejgns9N6k9CLb zeMoI(ODhgN!KswK+zwh;ErYqj+uxi`!wYxMEGYsX5tms)#gAQ211>$jo2)!ta{$A* zaF2$*S?@b2wM|U`je-F}{*_uV>&q&!+%>Gh49XY+7Y=bw_8%9`s?2@TRFZt9;6Oz?^5TQlemKS7s2;%94ktu! zjAYYsw!dPLAqaT6+p%Z&=j)sJTNzRKsVC0$k6!EvWME2~s^ej*dEg33iW8`qQj5AQ zN(M$vpL)~)D^kJ#uXp?Z;g|apo|9ZDU`Q(4nyA2jtXwBQVYghg* z5boEUhZ)z%@g1JsJO^iQQpJ&Ld;bsy_QM7UbaM1}Axv^yI01wV=C5Y+-`&>#@|F}V z?4j+}O7}nK23+_4pmua}50RR;r+3=55_ks@_2hy>R45E1%$b5T-w0ng{y?rh-(% z&E&@Nqv4SXT_1UQxr6|#Ce^`wK2PgM!;K*KkG?*xlw-=Je`&q=&rQYOtp=cB129Id zFi?)OONc5T3_JOk1-tGlfo!Sy%w7L4RX;&sUTqzVm#{tF6SW&Uu0KI4~zCbditqJn>{Fb!G@pFV#HcJCY)lDt|Af8=KE67*T{ za1|o@kM)<%n}PEWQhl}h>s1Bt&Ph1-<*oe|4enQeqL{;;?@s@d)f@hDdH<0MxlBbd zG>+7k!1c&UaBCuBZuBCan@t^-R$eB~lIp>@O|2rZLa4{pR{5h3%=F|$257t5FD+F8 zXaf2m_8(0Hb*d?-vK~AXKphqVnyY62Z5Sl>r>CEI07oXy@h!2I&Ojx8G~M42JzPaG zcQ)AyxZ(LLP*0(O{}2YA;Lrd@$v(NE0T8U5Myqd7a@RkACLH>2YDpo`oniI$E-Fp~ zH}W|cBY0JV-^N%Za)g#NCOx>Hi)4M|QnGqbcI`ho)&BiC&8kt$f?OScmIb3K4_n zW_*KtB4>De>Prx0+*sQ{If>z2u?RP;e#ZRMXz=ghGSEML`O3E<^!FH#n_Z_Q zk)AL|D#yW+8~3+CVbc2D*OM9Z9BK`^{(!eG0+PRX6$bllb~=J-*Pzk+5^R72hDE^p z2*PPlmHo3#^A8`#zq}7~!W5+EW!fPz-rDlEe?n1MsxRJU2WN-xc>fO};XA2R%o_Bt z-u)qqIzguYp|A8Ii3I(7XcmDC`X5gXgBOohyTGf0b>JCWHW5P z#MI(|DE=>Z{ye$yl$yS6s13-_6{y%1lW#(WJowpyE>Q2R)qhVKE!#FPaLn#d+B&Od z;&ymZJKxYvG9RPN1gIn>*>^|3$bNj}+I~~!)7ewIdrSE#zJ#5&{>Cp9&?*A_QdJkD zu7LbgyCmzYjlc{vgMe~3l-iEiQ3Le*2sAA>CAVK;);)y!Vjmm?#xavV6+$js?8lsq zc=$hCvgJQIOMY8c;O=>9%rT%YZlaGCTgi&%2kX{WmE49~DcC#toGV^fzwC8_lRy5T zYu4Z~*7V4Zavr*B<|-|A^X}8&j6ZHr`Ezpl-ql;+%+7LmQBT>^tq>-EbyUvYJHN!5e&V;zyWumK zAzs3)8w^6ZQtm*7EL7`M&Vmp%iChCcc173TeIixuW!QGX^|94^8980wdkFb5+~`dk z7iv1qpb9|dGW&rg98I+TXFG2GHtPOAMjk?+fmrQ|=N|M__8Rn1-gd#>s? z7A9KAQ+|~-{qh&_mTBAt$#OCo_nfIJe0YI>`}e`B?0Yb}`v8CizAv{>)I0R!3IEoK z&ENS=S1CS+f=HDy;A;CU=LGUpE3OBS8!TRgdLOj4sgqDBUnXD;uYs`1kSf)MkRaaf z2Q8epDUQUBp*H*GvkZ^8a+N+^Zga>P=C-1jd(|UYt9gp);?YMZj%J?LxTN?{*^QgK z^4g;u{jV{UY41HW{>+>c)(4v?j_X{KVXtlC$0a$}V5DG}rOu`?Z<)z+#;=K2tESr) zf4%Y3(=F~Bh#9_(5+oT_C%HPM>~IKtdf;F2@|oktBpbi?mmU5Ig`I@#G85OF@s=9) zYrYS<*}9%hRJsQx#Z9b+ElqzHw)vVo$52T6UL;IIyMA;Cu|`Q#jn}U7SpQYeeD9a@ zPkuU>$$Ovh#VT`U61&}u61z8omJUxE35WQ(7qx)%uE{wGPWev$!5M|si|UBmh#7kt zKX-?Xl(i6kyoN_pbpHi#j0K%1#Af3eI|>o}Zmc1CV*kkhikBw}Pr&Z4JI_|&Rdn0W zd+qrQ6rn8TsuQ$WF*(I7M2Z;4Gwq7t5x3AOZTyZ zv*ANm=uHve8lEIlFMi6DfT4ij^3`m~|7)HiIEoZF)%8y-wv;Jf%3(t$Euq$tM5#3b zRM>R*e*WgM{yIsEu0)-*px7K&oXWCMo%rQTm#$tkyB*iHZFH+DR<`kYCX{8APOXqc zR^3!(m}2<;z35lE^Suh-*gbbX?>z4{wcyv>oVT|6;EZn@A#XC_yJyW#w==(sgNWnP z!+RgyJ)$QyqmZ{dzpM!!;pOw=^kC$=?2p{F83ijX4}UZSU+aJ;bvKxoplitrvFw;* zZy*Jb{jsch@dhH|R54|{fV7EZoN#1;dvSo_@UM9AKOJO0K#TPlK30NUpV;9&mq(+l z2SF6RgtF$-`f(euJv&pY(%)nw)0a-yQDvDV8(o#lOF-(O_pn(z8Le?ci_Vd)?yBDg zdVSrXOl9xiCgqYfzvVejelCGrV;gqPoAT;c6hS?%e6vDBaei!2wjwt09Gv-wkS8lN z>BXx(df_kACc2z6Q@&FiXh`%>5jMn&7 zvnA|IRGXbjM>uyY85DXqJc|D0g;NSP79hTNv*U5qt{jy#DM6lReF3(`yAViY+4dAI zlCge=7hSFmWiFQ+7Q*~;Nh_@2YL&A9FPPVM&`~;a`dUckZV&5^pl~jMc_Ne(rZl>1 zvUuMrev^Y1`(dT#Xk<2+L`olGIQOp>2Br_$($B#q6a09IQ}kA6E3dLwlco~=Hp`mOV;WD#~gxs@?g(SdC7cEV8bF5EUIZP$4{%Tv{^ zq0dKWSjb{9pMdCQ-8AX`!a(DqzTg$NvzdOj-}F5TI&IU+-!n zZ@p%=PfTZKf^139VGN$L)TZ)7ee!rd89msO(OqZ>w3X*$eLtO!iY>Ptp;X#)dBvM# z6QV8(%Fn-R=@8CY5HpS}d=-)`ljje!O6%_6v|iuAitIJvw9{_r=Z=t4XffBaOkB~L z!_Syz#P50C_=?+V=H%T{OQGiu%g!*jCmMZ?I@!}A#Hqu(Xz!ZeZ#XRYsM1mf%kQgD zD5^HNu-j{GR1;gF?R%5Qosjw3$nNQC$0@L&28-cM+9N)On zjOcdODBSZCrrOdrz2gZMKKO}ES$nPZmbK)8@@II>F6&|~;e^ur7NPl;DQ)HOW(@oSI@Ht1WGg+^f%g-JH|^;9Tz z1pi9{+IRZ5Ya!9q_X#Xm4)0DKqkM^h)tMrhdtQzFTK&kI4m!L#y^KX?ikGDywZFNU zVxS`EXOS=^nR?79eaBrYb%mzXdIlw{(jD&<%Cyy*fYHH@9PxT*PA5*o29M0nFqrtJ z#Gs4n&*P8`=dJ3sSq&UH zw^DcQOrKPd`(Ku<-M!Q6Z{145=L}ch>Ou@FIzWBk$aBLF=A_D6EZHO+Z}`?MWjc)V z3#nr4$Oa?~ZXkg(Dy~kllW3@Gn>*#1Cn^`RT2+y?cbl%ll2Ws$8iW`vz1h`2Zy&&M z!eD4Hr-|#@OxIVC{fPRlXWz$uzbH^czee%jnl5D$ zPO4RwTV5{C-uj@6HOVaCc?o9>#jUcXq^Ffu&f`lWCE{)gv`9@VV3rSYllptmVoe|6$R@ip2c%7B@EoAfYNg?)1(--n0$r;q3!_Q7o z!#gv6>q6#f`49Q4P9`SkJ1=ylc&iZJmj~EjyQRIn8X4mHcf1qD-Q{tJdxq1Hm@GKi z$bt6zB`9hjJU+`Hv$8*TB+xtorR)3F=2(PIr){|laZ6wmEF&_QjdhnUCSZ-vA}NfU zV_WdF`9fC)k@v$2(Y#fg+tYy^Mo{8*VN%TA+t^+`RtqC-upaa_Ly>)#wQtX!vU9N9 z=p1Vy_vLI(eazxGPoD$XW)XcqWA&)K<_x6NHpa!fuu3qys_6sqr-rNGa~sWh^x7(sVBJ;3SYsY z$Ekr&_~gf*@ET8Up4kWJoYeGr(C)t%qka#Zi#DQXOMlWCC(ToAj-$Pd^HMs74bm4d zf)~YF%su9tOvpM>sVE!gb3dTcR(!4U2=t7dNwy5pyxM5VwZAg&9llV@Wv(GVX+XY_wy<+^5S>rH z-_Ne2&%hp&2I8g0#5w%7SM>qKxhY1HVnwILR$Y5~kuBzw+{2)6p{altK6s+qa4Jh_rm9O zvBJ4<3$+)ICfP=@a2ty0(a_h9j}3Zmtc+Lcfqhn$HXfGP4r^DqSUu`WlC}iqwo1oo z`PsH;b?CT@*4@0q2Vakn@R;O)fB?;wu*=V#P8^Z@u6fX(4idHL9#X1s`}Pa_@>%9z zWpDq591K|RGXr|OhSEmLJY7~6LAsinBg=7AoWtYBt9-3>ap{B=;Y$xl-Zc`ve4ZeR zZE&ofIHalkgm|_1gBC^t48H_B3O)dY!)7$mQl+iM8k!oJCM~Go=fv#h`uq)~d@7|- zdH;vehObtC*}Vqnv5?Lk;Y(o9U-^48t{u9L%$^|X*6_IS!{x-mr92$Z6@3#hRF4~P zl+@VOwg(k^QXIUtdZYGsznn=s-HG>eXD61uUNjlKVP|jSFoGzuE}F9=%dcQPlmrSZ zEFUz9q&2sLWa~<)f0?${SsXg+B1#UjN*2EuDEO%TL+Ko@C#lwbVSXVcOZQV6JBY;P zQKg7&F!TkqH4Wet~6IE4Ty?@WW^MazB3mmjrDnm`6g~B!#yk- zUuKT%4ypt28D>@M*2TR-@-DT8vnD=FM6!>kT%*^I6wzYl9g%?l%(5f0$)Aw@%7n7R z)B%#g0=9m$x0vD7F|1hE_G|WsCIs8@+o}Y-7B!}7-D}!xj^oN+45?9eH!)W2(!u79 z4R2;Nt{L`0_l(DNKD~q+PJq07@dv^|nWTaRDMC>vgjIB-Tt`nV}To4J)+ z{U6@(r~2PL^eA{n0VMJ{1+@!3UMblbT9Fq0A8s8hyw?gY{e6U_a9drrs^I*8R z(ur0Poi9Ac@UF6NAovC*r{EUUEuM$el$WWlp$o)<@ z9a!e#XfbISq3Urz?)^rz+9&dAVWrjj&fzr6yK_OY{jQA+X*aqggZ<;{gmLKIkL=lf zH}icr-$&U}WD3?ooX7R7Ypnu)+K#8hxv@{=q1a#bziIfHkGslwE4jA+k-TK}_?mBI zCo*wF)$<13*jC1B?fSb*&W<_EcJcWaROPuW2FCVEEj4^~_%u}2RebOM%z9EQw)vCS zRyeZ+15yI~hY)2Qr>-;is@-d~O)ZOEM;SoD_=n3{7V5D;NwMC#OLD?lx9*`t9y%ho zb18XQ(OI*DQJC+-wPm;R3V@KH<-xfSwQ(*%H%psK#D46aMQ1{Hh3it$! zweFrmf1MeZQi{0xuB=Qf*Qi{RiCg!NlfmzKQUNRgwEiz)h8zJT3`Fxcg$ zl@XU|Qtfpk@5}jLb~!$}tr%z#TRd&iHO62P2~fu0$aVH2%zF`v}f=dmJNZ!FQWj?uxwazD!dGkX|D0^xAzp+Cr3(Jgrdqrtjq$-B!psBbF9Y+F!#oGuH4 zf3IHnC)+29i*@MNcgXQ9%v~%s)^=&~$e0+y>;Pp87h;uTeEAz3Ue3dx`6b z?>Fi54s94Ako;FD+K3Zq?-g9MWVeqzl0FQgxbHB(GM0j zr|RoNCES)RXkw&uHK+oE`dkZvYO@TVQ;!YW^Cxmc`@m0ORNhBfR3Hn?;=0<}5vx_3YSk1nVX-$IRnBSR zy&~d^pN+2AXyxf(XP0ov`1G*lV*2-p&BwH)D)8Z)NyTDTwsxt#v2!AcfXh~n=5v~x zmP+up|MvD~Kv0m1zs9;l%b()Nrh`W2(8xcCBO7zg`LL7N>+ZdF4XfAv-0N2ETa9AT z+QjT@a?uxIO7wpKwPq;$eR6+6%j=Mxl5Deyc?1Op|zV! zx+`)bSKaf0LQ)Ich`;th;tj-4yX-cY^o$0QqAnK72*!$n&GrPK_#gj!XE176T+gw3 z@m^)Pmeb?7!qb@VImaMeyj4PoeoIx~U7i)GD%r~$5!#K%&s2LQD!&>GksXb^d@(R! zhA-`S>zkpS&#y@mdtK->u?~C};Te{3qI^thhgfYGs&|{)`F7Mpe-~QpOna1am$t74 zjry`)_gF)C5rNQ0zlkv#c7pd&KuQOHGQQQ4Ah7u;-Dj=-fY#O(_Yx_SQ3*ev1levU zi#D5ppAspL1Adw9NP-R*CrEfJIX00s3#2adEqKh&+)R{q*A3NU)P8nqhViNsc{JG{ z%o}_m^N7hcP@NQ;AXV@IAB&g z&uEx&*pU_?=I3=1B9l{Ah73#L5yHgZ8kdl5+&I(&T?iNY7D5~ZLoG3UTaZOCN)$Mm zD98`lqjTD!b#9dW&E5G=R7n@tz6coVupg6XPW* z*1;9wOjs$6Z_L)qAK)z=GbN95h4g+>1QlT%=qd&gC|H~!!$8lQcF`5Jk&>aIZ3RwR! zJtpRKp}KT~M`JK+`dJa{7=m46&>8a)waLV%KhrEm>X~%smQHYEDmts@#|%5oKv&Kj znG0T1#dUv{5jQy*4+?nrxtUxhJUu?Nl8teqdcQ1Lk{<#2X+^sx1QN*iT(@LNw`||HCz2M*R}$B;E5&JSGooy=l_& zz<}TD&b7zRGKwzrWrlOr3Hk1AX^*5DT{(9+3=C_1OzMv7yIX#z0++>BHfCDPXw^lm z`iU9=jUbnMa6M$!(oO|6;Sgv*^NRyq|CV=2?VvU)kD;AK(@cb2AEr)9dzUrcv%Y=8aZHiFbU*0 zmA|(hRqA^TzIcD3zfx~c#5cXH(-=$f&LH4X%1jgMO9zEdN*BGKELw|VC| z1;mU6wKcboTW@bZ@2mE9p9QfU?Xww7MIOBc<{fd0N6MxGc&LPewAHIl1xx0g39&P- z7<^BvGQr5hV(t4EYjjQZnXg(oP2%(IXTEaPOf!yh85CtK4(2=1PS!Ux;I%!{fn?6D z`Qg=>L9Wv-V|tc=!{HWe14vb-mVc{KTD@X8 zH2TE~%90!LJ~Mx~dg?s_coOIzRm|6av6^Hv->;*p^^n{rzgVrWuNzdjWG)Wm+MIP; z8PVad-U_2-;j<+Z$KBDf7o`kKY`Z~1kCb$`*ZO7-CJ*=Bw@4!D~L7hJhlMiYsMN`WEK2cxYGV{n} z0zmW+YlagPMlGUe0!IhsIpCOS~jjX4|XkSD36t#RbhH-a*{8zA9R1hHwCj4_cnGZ%!Sevq{h zcFU6hbKO$JU;rid6b1T3+AqNvjWm@r9f^{9YU?OwUV~{voOkaP=0JHIUyQZgo7PjpZO&6_PvBwcqRsDdE82(K1c+MzCr94(^g1(VlFm%LY7 zTGe^Dkygn&?{b5I`zToD{+t|w@pMz)S-<@eykp>1dlHOb>`y=NY}l(zPZ*VPlxiwmSZQTMyWB;quDe_U%07LbXzp7N-ktI2RcP?W}Pwrl1k z)EwV(nnAjEo*VYnIO*|@NvMyWwCmqesMTmZPH!H>%`#m zVd6KDl73N_E|P?N9GCuF%Lubti4{HU6u`)3n&cLM5IZaZ%JE4GAhe3y|NPTzbExce zV{mW0Lro??-<&lQX{xLzATnb_Pr%$4C#oy)SOK%ftFkYTw=hpQ((XvD*E6IUzdm(Y zb(nfhv=UHNJ$!Sc{}? z5QfcYgN}oxNE}1d9)!PU;LLKGZ5^p_&F0q5c=MsT<;78kz8YU|tqhC3tp%Cbo?o8M z9Fn3*PlVrYvU1P#1-iM-xYtsy;?9R&3+zPo;z)5e00mY752yqZfH#K=w7}T286)NS zDG9N+P;Y9W_LSWsAE3nXs#UJESt9P`3D$NoDZYKPU`Z2-IKM%o><_^7vIqb!b-8G( zjgj@Vj|VVPhggnqI2V}?3QFT1DY#Y?#wVP! z*`>oSrW#Z;`5JTd4sx{$ISt<}uB=WOB-$C{@739oUn>(p*kn1Hj$i>HxH!F03ble2S~zcKn&#()Uuol-vtwD5Pq~uAv z-scc#H?YcS7mQtY0WyZjS@+T-BJA%*1N8*_l_mY$YnDFVqSolyxZQP9Hagme*wUr5 z&c!;%*bIW&=cdFpy%Lq-XN&B#&wVk6u8o^Q3v6fSFL{hgPJqrApAN#l)v9cIbBNY2ZCqJGAam8vrK=|oMDZH7 zlXO2lRFu0-G`#+8Q_N@gs|J{D`9Z1nm1wTuLwSE~z3O>fh)Yb^&zbFc8QRA=UJE)Bq?I z-Z=~grWjBzP@`m5qG8E?w8Q`5R@hUx#yP^5u6i`Xc38Z-13$#nhj~^S{^N()0@axV& z(20WzVBzOW|4F^vX;%Bg9k2^Mv@54l0mTl&wD%C}hyH_hFfm@Yor@xdh2GrxgfX{lGDx!{b`oc{!n1ngjc-i~#FjC`_-Ob5QT{xCN|EnT43ntlO*E{fl!7 zu6iwJqsNgY(DSfQ(zhn1Vf)_dY}xZ;fXVjo)qz;F+=BAKDYu~^;)8s^#5EN{N|nsd zGv7e<;PX`lJrT$m?`ytxiinwP#+7G)%rYL=X}tZ@)4o%H-|{X5=F1{xIw$`2LSqMw zGFtk5SW-4Q>}-^S`RUs6tzs_(LkcLkb3WKEPE7CZMv@1X&^0-iM{5TwRpl3egKYja zSFWp8sNr^>aaSfeW^q4RE(O8G4dBIwF&?C}>7@)uKj!_aqfhH|Ym6$tLx9Q^Z~fL z*O_lWsC4!6?Sa=fc4>ivDgV`~VUXMp$fDKeA z!&T4jX`bcrTb%_@r3=-G+VJypm&yn7GgQ2brpR`kRs|XEks!?83Cm*Bq_QH$=|yr_jx!(^~R9Uww6OkcP0DbC>^ z_eMGwwlJCT()N+7T8-_C_zt8_t*zO(;iQb*EPtX$ zF4Y76+0Vfm*fCyriPPjs>!nn3M`xr}q+t6Jnxf}hbHqEAlJnv-E(8+V#82-PS2)Dp zlCdD)*XQ!U$*fW4L3nXGcqwKVzD{SRLRjw!ZPh?yNKF*8)u2 z%1^;&5Sq44sqgZ~0eRBP{AR0wnDIuqAYO#?fMqpjF1Wt(y+;6Za}~Fxn<&_w7TCM< z{q@g#ZyTCgV+2w_ONN@b;OsYV)h6q2?-5FO?tClkr|_~z8j6$ASKtTvxxv}Y$MjC8FI zve?aWQJ6vpR>SYBwu4AvTZ??o6n!JO?TgjZu!raNJ)`kd&**Py!kj@rtbB;tPZ5g6 zcjKQ{e_ZRNMz@sY67A3gxqeQsH%^b*5{JfG-mO*^6px_08>BEZrX0~H`iY!+RO6S| zH0)<;;8V?uFe1B6wL5l@x0SV;n}k-il1p{oCHVEH?8FbO`ogcsBW40M=SwaCBrqi4 zyniZGkg)1T1O6P*?~l$IT-?&i?b%l0gd_Q9Y$P4Bo^9(O*vo+Umd zTJ6-un&oZxy#DYSfdg%}V57Xj0Z+YE$Of2WW3c{yE2dcirw3sw{K`)%qodY>U$0??Pg&$)mdxJRyn{5A0+|nl+#H?=aWu5QWphp zXK2^hgw?07`{OdcvKjZi9jpF7?0tD4lxzF{NqQ?<6r~cQRFbVlh!`iK5|T>xN|7v+ zkaY&_B`GOnt0sGq?Ay?SkbU138T&2_GxNKiXB2h5zw^HD={V>7@xFib>KJG5rMq3Ihg{k^dws@xc^scr~edL9EvgAQ3a@;5Yt3Rqb=}VLDGHE#c+*6e+iYlk1n^pjGI^?(-nA31}nkI*Bwf91|L*Kbs|be z=C=8_zFk(C{O03)2yaL3mwAYFxF|2qI|K<_tiIjLxM+Ki=$`sEB~7yMgCrkT*1m%i z9UAGWNkP|kQV&6VQZv(BUsU06p_WKl@<_(!D;EITkBcpKAJ8-^2sb93jSh1T2V*J25g3#QXRKYQWPI0IXss= zE_<*k!5{=EV&l|%ed~r(H~wujd2q6$eq&9S%Nh^AD78@u-9au48;TRBK<@Y<2q$#G z4H3G5k0_?Hb9IKSPeckyCksuWU~uoUk<7gUS_y=ww?9xiqrVUlIyeG*DZTNRiY8L*kLiVCqjowTd*mH( zx#nMDPDQux@nF{aKja}=6g?71GgK4uS%`S#15AKuifF^$Ph8zYXw@n}j&aHEtdY`& z^bZ=*kO$fOkw|A_h&oQsg?wMN$Kz;`=JOMMs-gvt>qj>Zg^-#k0pggkhF1N;mc!$9 zZ_Jy*N9%jh>_KO|Gh$8giLl%i^5ee}IPywZsmY+QAuQ5Dg2CODh&L!_raSoCKsB>w=@!sVEJbW9g z;$L`7;g~Cn4*4wwR+IKSA5p}xuOq6htU~=~Z6y`mnFowkqKKZCgAN%e0By~k`Je5* zA0Z%;7u*u{x$q(X$R?h5g3v-(=KVxeP>x`&wSEmszS{uphE@w5$ghU19407^>kR7b z{YreTjN^uiF9HQz7i2O+QkIZ%%Z3Cl1MaqQ?mimC(Q%yOQq(oy1pg`Wa#n#iL=ML? zU%AfW{p4X0M7()VhP)+8Gr&u9Yrtq6gKbIX^7@Q;bO8>aU&dAHQO=bXdmm1cO1X!L znO_Q8zs(;eUfc>_eWpjX4HUTY z_eC}t!7c%JIsvyF#os?HMV^Is<4oC8Zt3A*547+M&J%B5uMbMXH;$&idHvAe094_v z4%+DgDo*3T85<=2IIKX#1NO7aqyDB`I=$p!g1A`6sZ&rBJrudb_D-R8T3XD@vQ-TB z3MnwE3Fja6v?052V>~z*c!b)W^Xav%O0Ol_SWvcWxG0>rLIzawH!W(Hq`Tj$Iep;2 zvwNY>2olC%*(yZjvq#hAa#?-Wotba^|CSEh5{8%HASLg=6WtRDki5(ubcbJRAhKxP z*=-_EccVOlWJ5qPs4b1%w6YN~Er3I~8dLekB3qi{rp`I8QbJrP`Ec zy#usC!RLkEhVgsxPfnhK-EpZv0?Qi&J@9D!C=9s5w=;EP9FcurgaR_z&8;Vh4lAEh zg`zU2pZ=qN!6CbxeEpYHx6uULNia4P8JW=yw`IGOBn9?LvYJk*=y>qT9@>rpWsy}A-6^pu5EUo%hsBIA2V;Y z&quWIE!~?rK6-h*!mVey@%ZG;VSG!;RsJT=j07vj+$G9_dm^p{j>6^bNXcjRG6>5%XJX%3vCoc} zo1(0c@D%Q(>Upq`kLer1jZRN7NT5%wffP6ZurjdeUhuK?<28+bKqFeP4U4YQYXKC+ zdwZPmvwLc%sg024))qf=y9sO4PFb*rjT3Y>4-+s>?C**hZq$W!P91As(sAtw>y6k0 zD~v>zKZTX8TO)}H1*I7uM*+G4dk(lb8(49QAWiYF&gie6&;V>Vi&M;Ltmn_KN#zrs zCporBcqCaIHi|6;1|3*(O3!?_A3f#hQd`S)*b8K(zW$YfdiRahlB61&rl&MCQ{VnG zT&dp-S1Ovu&QLAKK5Rr;xz8J3tsCM)-ATZShF!Pa{w!5UhYB)CW4#6FkIkFHzPb|# z5-S&zcf$H6E}P~^2^j` zJzw^dZth)_Afm1{c7gWm(6hXO@3u?E1-9gqxa~V6h z9NEF{ZiRC^dIc40F4xqcc6w#YO!l=<6vm5#ubq%+v?e_VhT3nB;w;^47WzJt0 zd|GdgtuFm?8Fy*bnro;Y`~12Sjvu)>m6abv0oUuz2ZcRHU`O}PCAY*uVSMH3YIH~U zE$r_z?R^7WkLiqNBjRgbNx>oIO-J*YwAo!0q?szWU2LI_F|>Ua{0l_0>2wq31f2p< zdHUg|N|>+dRzK~dhzR6llkMD%NOa>isME}u@S7e~ljfW>+xZxk)9mQMg3YG3g9Bwb zAl6nlCFi_*ql4&40X2Q=Xv^$uv(|~-$uJ5ToN$Hc(sR{zY6?wdX&nlg#_RCw9lSlc zS?@|stpdPQTx&L6gU=oZa=`?EnyEdEl#>yj!2(@+`f$C+l+Xc6t|t~k!gA7)a2jRs zu|Jo-Zuw{REO^rwLfWbP4h~v+57z@&kjEjrnVqAL+*f#`3IRqi!;Wpb@#N%LH2L8~ zk`R-I;OI?@@wr(nWwx4w#9?lFoW2ayJ4(DsRs2|%C{ZGM9G|(n^w3EF=~@{46VpoN zx}~IreGRYC0anE;fR6=aB5&XOnDM83{Z1gbk#gl7%UqA{8*^h(_q551!wc@FWD(6DXd^-&2o1tjr`ZCq&Wen7c4ab zzgcIJi_XJR{*Gq*VFma^C+15J%9u^-`|pi9Z=9TXL^mRjB8&Z&rUBW;)(HWKn2+%u zw?FtYdvSNCyr~$Z0RyjQ6$T5uT$XfmS-%L(TFgncx~#0Ma9k4!gk}Mtdx)CPV<%UoXRGHq zK$z%}*x><+N)RD9aq8vU<3UeWn`R$-t~~4saOgK{gdDut15D{?;JQSf|M+H&xS0<^ zumxE}n1b6%M73q+UGIRedb*K`#F{qV=2!dju7T9-UT(Sg;4@@CC*E8vJ+>RB6dq#0~eRrCp?_9=- z;G90aTL?UI6%2m(URz;6B(&HCZF}pX`FRM0f-3j?LE35paykmkEQog)Ij699?}jDa zrqZErK@<`J4cE@Aocdc^5Y-FNL-z^2w4w}~9E~`#zpE;`3fLPq6-PBUYPql%0rNRR zH~B(m@srEIfFmOEJ)pUVG~DA;^5R$HF|T>0P^D|-{z%*$AeuX4~hRIez_-d3};PP(@4O=Z_?qX<03;x2e)IIuY7lkuREI*86&*FT((IdYUkV zLCi3F(c-m|hg3_#fixWfqVp}lpqCU9J~PlC?>!y#NFw|dbu~!bQ;)zc6tN5ZWqeuP zI$_vzk!Ur-Fi!UUT-S0oUd;oGKWFN`h5R2*j^!p~uW^i-SnWNkP!>qoMp`xb)!(Rp zPZc@E>J4CzSUsILrSOv zGB3-DEzc09#lCy%5tU_Qn*Gkx@7dx_%CZ+#fvmy2F-;3j+Wv(8LLfID^uyA|-p0{C zxrvs89EuxD(A{;R^^lp_b2wMeu44$t5jm+qCN%Z-$RbQ-Z~~`mHzw)_y*_)xC5S2T zZE5x$?nn?obY0`J*g9bc^s|t&Ua{nWiMRHR$rzB%5JQ6q+ARqsvSgNxe-7-F z*{uhXMZ54k_N~i6eOou#*Zlf&3XRgGk_wzzpHrYjd<3$tX*0ucdYzUCQtE2w{upwv+qL`YH*3ERL)&pO%+ur!r zKzv6M6&@2Lq(d3Whe8sx(OAeu6S|jN;L}MpoQ=idk2KqbYc@S{{z`Q0TL%qAaL*{N4l!i zGCp4i!9UPOj+ zvkbj3Rmu%2Tb9q$g^w?;HQnR1?HeL%@{yyE1C`5GyqoYi?<-lYa*R_&~E z;wIh)TCJ#xyb~`C(<7cQR!Q;k0Vp%r4RDrw2^atn5S+!mN#4NDu9iQ@>FJlAx-Ot% zs}dggOMc}7tnEtaiYk-tOC$bk@tO^?WF#`y#2SylAvLXjc@k0VAR;{2yIfu4x7QXs zP)Dk4`kQ~%xN#a>T7)|B+Il`d_UMWjLnUs>^Kvkz+t=O#CI=y*N+2`Vs9?=Vd&ygl z4VFBGR_<$dZog^=S`8v_y*F(Gf7hgQWr+Ys|JsL@Fl8ckX+ZkwWy_v`lwV_CXIFH_ zLyg<#Mmj4V$>LLz^C14lnO2DmH5EMCSq4;Kn=%y%6VQ7g!hRSx@#gqgS-}^O@jc%t zTniA=RjH~)D*!||bn{4F8{qNVCAb{#VQN;bXydSe?R>(nPQw@IJxBX?U-KiVcb6Gl zAokzji+pJS4wSaa-e?~*uaC`c^V;hC8i{R8jq_l(Yy-@eUF+16O1pYJbk{sHuxuli zkF5oMn*6H%#h|Tzf=EstnMUnjTU~Wg^*Si7x*+8gziQ>bUN$hN#RD6KA&kAAED#X#M@_LV-$lF0ROh<6~2RY&B~lNlO9Vc=Fl z@#_V0J&Ow;FGIrEw^%!c9d_pn9IY)NxLvdLgiCF_X=t()xsx{0Cox(Le}msBTo5Ru zb0+`cQIG*t1S@k6J<^ao2Vb{k5Q)SG8helbgk zzH=k?uVa{Du=s;OVUE8@I4G2c^tur7Y?`f7zh=&B2@)4_*nD>wAG~+hq9LUNvd$`q zOd-HdKL@7*;p92?rH6u4ITD5i%Er6hxN!&=>nb1pAzRs?`uxP}OCc{kK-j`*8I@|= zr>_V3XKjq(br|<=Wz$){KZ8bzv`8CJEb!f-1R5nx5R0r>z1pPh=+nIm@gVFDdF-Ka z0mL~NOWkR!+~8^ZRI6IB^7mU zV@!~=Z6QaC<*gw}4@LJizD!38sm&ajx7^%z6hU6CMWu|CL0pV?rqkY8HWb`#%w0I` zPmRp`&5#LQ?`vN`OInfOCGy6sXFb4UOBVx^>{#4h-e5R;0uPj_1V}3Zo~UD=k*;9U z8xZ8B&BDgDOT~giQdw~iOcsM?QY;ohK4q|sYHwkH<2~O$tEhKrKn5f)#~>Wi(F9LP z*vti2M<)@c6@x>@-Uwh|rx`XWivb2^T&KaF+PqyX(GITx{E&AC)|uu6qVjx@LvL<{ zWb`on#b&T|zeaKPxD_ba)Ou9?C7(cUsw*ZM%63!ZNX}3au=(IN$0Mm1&ADZrj>CBp zS5VS-=-5~`S!)_y0m-4ty+9BibS5?t;Jt%r6S{Hf?b=OoZG)Fc0-*3+5M~xCl8<-_R-{}^LtZVF%2ntdTx5v zC$uxa}S7L3TOzc*v|3^OO+2jR~L7n z;w*91KKfa$(VAQFk6B~(QA-p}Y>bX^Ie(CU+SF@p+gq5}bbo?M>PwDxozEjiZnVXQjI6nW-s7bJTX?x6}iR7Z`q)UF#3!I}Az1`61Wa&mL`?~-x(^m;yKxAZWv zt0@j^%VQ`jW7j%1HuhYNhFlh~bk6*~;U|x*R7TWQR~mNTq=fa9S16g29i>uCx&lp{ zdw0JpS0X=YU&Fj$caR*#*+ALAxy;y`vRURy!bGU-Iq9b1rlO869ZI)PLYwawa-m_` zIpWYE>%~mWy&LY52+yK3I{N6l?8vHWE_9inzO{xqg#CQK>U9mu^-<~hN@uKW>{Glr z`!&Q>b$&_FAt>^~#RtP=3H+8|T)ii0K*fPXfvaF#V_2SH(qdC>kBe7oo@&`opE!W$ zkhW1oE`O-o7U@x2Sk(?UUTi()I!q^p$VT}2b`qtqFS_9R6&pea+H}OW z^+=(|R~%hZsK}pjO~=+{u%H~>ee`~UiD|}KTy$39X|B*L#T~cq1mpb0Z?QS7Jx$4e z*um8_yvkh}xz0%LsvPCD^a^=DWsQ*%U7v>2kKrN%*MCWzNvu%P*fYKjE(Ks)<>f*r z9pm(-gcYiFCBU^0=Hy7&ECV7R-1d7M{%(^Snn5Qw2c$Ut2{)=_?#+BH&26=g`WCJ? zvcIv=nzdT8p!@hU7kAvs@V-Xmd7@k{+jhRvz*LNzG$g<+K|M}oN};j`)b`Qg&EFL` zK@H1Sz#k!p+SI{f5{6VT0VBdP>!|R!av?4wL=o@dQTp064e#hm_-+5^z| zX+#}mbkbo=mTG{y^+CMYEg!@A;j*{f6?5R(*o49C!#p}eJkXaf1=lkK1XbEF!69jA zM{g=~B{7#)c??rFQ>j=ueIu<&BLD7@|Luwmk#nK+sRPp(s&)h(pTGPfy;MR~r?yr! zr_R*;dT~p8sf5M;rm>Kmv$~9%dYDU>HYT4RxB#+%!&wV*8jj$_dM7z$97al>vbT@Y zI*A0lUI6k|SMuGZHC2Jl4DCU;C;;}aP&7RroB+B*zqo3_QHc$cDcgjusQh!}&YxZo z^CqSxjU7I$=!#BGt;o#3@*=5&OD6>9RQ9END{}wEB2BQpw*?lD0wX;_)CYIq;pQxg zXF*aFWi#LWbmYCc4z1Wn?=$V_>KhNSq-knk0y4q{RpF}FM~&xhV0~#5yYP6fQ*9Ku zT+nsff@jf~jxHQ!!g)y}%#AR+{nO_5K^{3Ad9aSYwQC;~RD8DoaL>2*WJ{cHwU`vY z>#%!SX;72ZttW~3&lpY%_d01S^7@V}oc3A~+117^!x_=#O8AhzTHED^Fb(_9R?{6v zN?s0?IU*-MDIM|7aKH+5rwk69SjKQ=Cvo@+<6aRXxE`6e3kvvZW<;tlR3R-H0_ z|o^1wa0sSsm%emo)iR!Q_hNfu-JZR&Pa*e zWxrQ-okW2rldF)GFFKIvY!KVbHJVCyR?t#?q$2*F{i-AM@AQg4rgXdxRu-DzS@{gO}IzrWSoVL9KIZk5H6Gn z=6~i4Z~i^UOG5eQr?L@4Whv%x+93ja6jB}r-bDf|uSOGS!E8E6dicM?UCAq~fGCyX=cy{i{g_c%rGrIM2^C^o;Fjk@yV+T~|*}cdaSUC?) zdrgD15LcIA%y5UZLAb}M(8A49A+l=}KEzx;g2_@O8p2|3tlVdSg`-}kG=0?7x};o( zM_yQ1qc2;|09=`p1Kx{93%YDeak!xC{S&J}eYtDQLh#6Z1H()E%J?JTK(L=nbp|Ia zY~JJD(i5K`-)?E$!Ik(7PQ9U+2zvX_3Z{k|_ihrBxniavxn$=U@fn z`qQ5ZGwhI8S%0{StSI#5)QW6tCCwcjQ`fFt>xHG3Vf7YVftXlbCXsLu+_I_xY8&m! z;4SdEM!K4KuX}HgJ8o0K534i{tDLd1v01klj`T(JbR%Brt^neeORJhb90}@p@SBXR zF7_>&)#$?B@!_c|&h#R1lz8zG|DWD2A!Ko__h=ufg|F3XboB}!sU!|(XPd;7+gaUl~N~lXvUpuUVSvKO;+%aRlat zt90o69%N$`a6&TwS|j`tdXMhGQ6otS38=g`QVsqD^RX zKl{5$9wc1%T49v0lL^82rQlZ!!Svqt+A1#X;oUF6D{-mY-Q#6*C&V{Z3V%2}5u9fS3d zHdqla66@entAnga6!TG#!;87q?l3Q%4gWr6u6F`*B~+ulx%ig^-dG$qa3{dR|BHDv zE#q@@^W%YgYxJLHs!hIA^MYwlVlk$ay@(Te%@j1*%AXG%6VAG~4e4)R#gLZQVMo-> z#H3d8_S0qipS&V{ab|LO%4!G~54&%(G28^1YdVrB9hwovAg%qHG(lQR37e&~779Y{ zwEIXwC@o`0ZcNP>>uwIfP)jhv7;3vxeyHq7c+n!tJtZCi9UUE3`QwZvku{ZGjPoZA zIKwHGFEXG*v@VX2qfjhAe)@c2Bro{bxFZG%3fof>c08{8@p0+tda9;RjOnS+JJ~~p zm@$bSNF>PfBKQ9J=G%`Pz+3BkM(uEOp`2YA|1nM-vzT8{2Dz-+wF8QfGM$EA{#l9S zcnU{?rMAN!^HJO@%i7akMY1kA;Qb1*_!c@OBi8~*jgUAz)|>K;L93B8skXN%wu@+t z$t23Ixk&5C2r8>eJQ*523U+x`lNV3jhm^k8{xOcWs8P~@>aKDvhB&O8jZ*CVjI0MP;vd@ z`YR;Sou3XMh4K+7xojXn{;j!WinyxZBHHjTfo0B-k&(pyv*vd@xagsf57atsNQ>OppP%bGypUGjl2njWR_oa88$!UIKb>>frYB_y8QG{W)$5aM(WYImRX# zwqi!w8B6*u?ev_LoW*v@oxHZr3*84vmKW0NC6iayVYy9p80bo3PfZEE9afMLBaP(q z3UG!DZTnuDJ&%4@8cN3p%iao*BDVWb&T?$af$SgecC`$_ByQ`%5jupaP&qVRpCBMh zX;os%)ZO&>g-G=^qK;i$Qg8XlesF5%>bVnejGq0j0Ootfc;GL2srOQzQ8TuoMZJ z8sv~YH7Ak#I#)%(p}$9>h44oM!U78p2OF25j#^PDmEWr7DSvif6UrFC3J`?I4=sgF z9WSoph8J;7a0Yy5oKzGN_|v7<-Hpg*?4B-Nv|>9RHo_gq;*Zp=g{H7Owa8oP_e zq&i+dV~F1<9*CB}F&f4k+!~m8?pOpzLNEI?TmhD~G5W7Q*E1kH<(wZNJNkE!9bi~Y z(Oxr)#2+2c3?C5v!@7cI<&c~Ex2@Y$EPSFxEY|QIlQ>E18Oag0_(T#@rAJCP#B+jn zqVQuB;w{l?DrINf=*w~(+j&9#I~>m#uRIgS+c+yA&=h!5?d#Oi>X2-CK{&x#sxm>2 zl5NsagsY1s(o1T_LYxh4;b6+SL4}$#Ra*qB&47DLHlMn~eVRg9$r_3j6RVQppg>jV z_ZWTo48T{r3cL``hFFGHft2M?50`ckQOu;Q>V8Yg?|Ga5$XVT1K>(qfp$KrVJ_Xz> zH%Xc^;NN)eJsw2mrlgJd7C#=MCET{HDAFH99}oIGPt@-9`xQnQ!gNYjGhcG>VJGpP zn#;VU2Kczu3iZplb?r7GU_21rXFHAwj$0EbtJh{L!lTnQA;qs4)D-p@io0`ksYgQ z8g4~^`}hXP(gBg76I2bFY6H0ym0TbfO|qJDe@K;4cs@u)242|DmBqYB;m&Oj?U*T0 zIo7C5I6cNv+f0nuew<;ZlnwhC8$0Y2)R2LtwO}F0=nM1n^M6jwm`KgHC6H|!FD@9G zOkV2LJomFZ&dJdJ(@p-oxKZv$sk9|J%^CEGjQoivI&E;!J0s1hgcDzqytr*Vs;fD` zq3v53E?*iKD)3P~jL!StCA{MJd>SxV(=~QKl*Bo!2k3HTFMW{1Ly?oy}QGXO>SBi+@JCyT1K>O~68@qrN}Jy3K?g$UM1)77sG+8m^PKpSalgW@Ta_MCCTNao&I%b_p#gIp%?rO!8JO4kS%T0ak=I9U>&v z@y4!;ccDurRBtXNUX((vtiv|pE0p9P{o2wUS$j4qIW!sr)mw7#G>jW+695tXm_*8W z{4uVAqS}C3=!`6uC<4SYUG~6iAfEV2F$W8r8Kd3)|BKqYMwOjAya^pOCwZdrJ*Gub z)Cut1pX*3Hb%9nd!Ta_OKfZ^UyfDr_UkBW~jO{AU4d>^(>PV#X#0Yi*V282(bS?PAMMNh8jm!Nl13jC(I<;U4d^2V2DEk$T3A}IO|;-B z%+jveVN1c8o4=SW?3*k!UeY-38#L}a_=z(k35esLyPfH;ml9qguG6|-HLLre0aipe zO*cwE`N+06mJ&xX?$UGx4}E-GJ2NFcgA!)Y^ej?M$N9q$xm;=gj=csJ{78^ZL*8?$ zi9?j^BgNdJ;f;@FJC(&-QaXvZ*HU`3cAEz3Ws|uppSWY19y7tc^&G_JJzjNgE6(niwy&@qwY;(?w=7XY^luf zA>S_~`R!Fz;>;EeyL&jD2RQQ)r2c_zS_`-FWkn_XC`U7`Lpn7kT)6WZ7jK z{r*dwUI>o+IlT{$8?VlhJ*GeC>FuTFOer(&K?4G^Q9(e2=)pNVBH(t>l9pXmTi_KA zQk&@1C5_=ed!VmEq3HaI^$zJICT~sM5AQ9kC;T3xLs<$83z`qPDBRV^eCEsT)Syn{$F`tpLdM@x(ItNA^UE0b%g;A}45r%9BY^7B zG}$KMjrgau9&_Bp^OGOC$f^?q&6k%3a-X^Y*6eN4C%wT6Z!X_lSx_e1@4D1Wf;8a% zc`3brY5w5g1^WV1eXB2P19;M`D19rU?G)@M2XgWaCbJB@I7$6iKSyRzBJ-;VJyj1g zCi63#mSn%EqIx@zd&kZVBaGTQC#H3u5fzMfZ0O3P(MXqVw}e23Ifa*PQLM(5p#>+k zt*^5P8ph`wuOR_J6E7C%S+lUnnr_|zO zD|{OX*BlkI6|N_OUr;ZkrLUB}Z!W~mRj^s=A<43K^PYCguQ-{Z7@5#vF#f2}p%M0=vAy;qkp_B!PJ_9F10K$k-M8R+pMBuo z8V>{9toER2BCSYJ)IL`^ugic=DOzi1(`AlJ@9kJ8+cYH8q;b*KwusOwO=^)YT=Fp) z5M^?)2G$`G%4Q4MnE)74x}@7!4@EeJyhtopYVmcH=Lv`4&;iZa@8~5+1}nWfp!V$A zU0k2XA&?g~S?g;a!Bp)3Yltt9FChtj;gY&tG9OwXU!v0`oQEXx;bwrN`XdpV0n`j= zFvNkmllmq|eb1MM())&+h8jjEIdpHz1!NjfD-0euLy{}$sIR)Ta+i!`CwHHn()eZU z*yR*fIkbwqEn1(9hC9bgqoC6C+2d+ zAxqilR4Cy$fGK`g-xu<)gY-u-B`*np`L}sZ(6!c_6<7VAWi?#%h9+u5%}HrnscJqd zxzHec#lm{6W*n^zmj}mwJUoaJegS6mdrM@D7knmzH_l%=nYpw+IVQ+w2Ik#%ZZNrC z2+1-^$etlVGB_X`*t?}PlGooQk?9OoWtsHV2Or%C@!<= z=7nD6aY=53-uWI5%F+?_24)n{(c@=yyHt7c_(-k*ls;MPjWh`KF%0B@6u7nuX~`nZ zk3a0bPM9Hdy?wi>ZzqOto&y#xbW;rg^kR|&ZMz^>n-0}b|)41VD;mo z4#S;91^(hbYK0P&k|KpXl#rqvK5o^heZx__n5fBUGQvOX0XENHz-OHp7BFKhCdj=E zfdbI>F#|{Z#q}c#gDvPV$Y>u!0ES+SGd46D_XoTd3c+5RFzej)Q#*ldq!zLn@A+Ue z0xlC@C8zlB^t0%2pC;ykb^WKE;vYHEXp{6uU}{DWW_EfN-U(mw2fk=`A54g`{tBoP z7d*-NH3T;q1Vr!Y=G}4Zmi_|*zw>~9d6VdC^H*GThxAH;6D@uUov9MA#n%K8*0bu2 z%HcPxXMnlC`F<3Jn&NW$A}t!-SqZ{Fa%8GbO@tbnvp&Q5Kcr12$VgK#0q!`8#Krk= zFyl5ui?F+w`=R^DgX5H$y*c;wZPmYqMB`%4ODRIMy2lC!1?jv;{1&^BdVwTOk3|tG z##hh=FS9EOSVKQ|4l=6|rsO?4fR;PhE1*vV&43J`+dgF2Qr)^?$&_Jtol*%>$F?=z zz}NZ3l?5K6M6M$Y8j=Ldi|_annSk2W7;@262Q5rY|Mz|jMJZPI#)gxBB7bEBo6{%{-2+(LQw*p|vy@;7aaA{W zBMxpr`@yc5 zuUvqCuPXy_nDp*A&+$z**Ce_fayd?y}sVV2sIp}H^K?atQX zq>iR)Zg}&>PoWn3%Fz6+tjw20%6ij*sA3dF7`n1eVdzHulXYd^K&PX(&)2I|D76$i zmk>2E!cwk;zG&Ibs;UYkE|(a(c}%MoypXIe`3yvAA?nvpA~5Pz^PKiT~Zo!=%I14KOu zVo!k0rm`k3wVAL%?H#Qj^3}f_t;v`cjRhNiFB(G&?P<{%sHlEu;{WY1nYii@8+-f8 zmzIxeV)o79Y7#o!lnz_GRTsgFW^y-vn-tA-vmM0$QF{L?-06Rm-v3d0XOF7?KT0nm z&Ho>z_rF5I|65A$0=6(VC^mnecVnQ5kWyWAEdOPH3LRIpizBfkP80+A;RCr#6@8y!xhub6|>zl<@aAgTW$5t^x%_)WL&A0es#mRTzY(MCaneX$b+ z(49o}cWb5J50411R7D1YY`p-evONBX#INYfuR^qSlW|e(`wW^o6+_WU8N(Y9txc5@ zqsbV}pJVgWwZbYtgjl^?jovSde_AUkjLhrt22v2imQ1Cu@F>Aw5k>Yi1^^H7A4=*E zVT7~s`nE?g1#<(R8K%=HHkaYSn$p8Np0pLII zS}1b)QP$4^og?rw;F8=!WHSblAwP_cuW><$j)P%KT=iMSk{HGakpDv=|E;-FZC5&D z-c0$$4}TjrGlj?e2Yj=ya~di=SkeyP`W^7-F9*dEEgxO?kxO)OFZ*yufu(xP=pLmP zhDty?7l={ck;s0N2+ee@zvbY?f zJ$@iXRk*884Wd9KGSME4CZmDcov!6SNp0fYDvr#J*bqqjh^Oq0XUMZfLG{PrE>>P# zAah5AL6e!T`{5yy{<9fHk7c!QPo(um$UgFpo2Z8=ALU=k+F&0|ztbE`7J8XNoZpLsjncnPwh- z-_t!O4MaX zv;bhFPSN3v$Oi;yBy|2>y?W-5qta6{6;jX{D;y~iI#&y+xI54`)&%zkp=(74NiiEO z`x@m6g<@5?ewyg8U^g<6jyrZetbh^Oj8qX>wKa?tOuQ|^HWy)<%wd`X%?}qgYu8^~ zuObEho8Ay~H+Z$b(sGN5@UE3~QFEKaGG-lIE*wn^Aab>e;VAVfBWogqvwGe6fb5 z>=3%>%zeyX<=ZB6dA582*#(1aP4-BAx|V0HoA17eL%S7wM(4A3g(4`nuGJ^_nyKPrEteSzWFg>#!vsT z-4|m#4-9IV5{MM2?3=rOE9dUf`mbDof6XcCTXk4m5p~hU4>PAGJaBt`x9{(lPCdX$ zc&9Jrtgp+-!xz`yh;g7lw{^Me*U785TYuel@TdK~11D_td`lpr_Eg zH=>hq9ya{N1Q$9!__jv#RHo3fHcg$Kw;18~-$4T_V_qs_`q|%Lq>ZOHA>fI)YGgiA zfz61p&og|J+gG@*gx4+|ldX%dqCaVdr$=j6@t19?wHi-CG1~)ws#mM}0{v{QeAO1a(cKGp^?_#ljpd z#_V_FhgzX<(EgYmO#f7%AQG{(rpJzohr$y-hO$5;?)xNWN?GL_AQ(J0u|<3hH{W?B zh#lNF14rk77xj^;)h6;TnPLzjOuSN@6=x<4-)toIgOvGCdobR9U34VuEwE^Yn&Htd zc#WPl_jj}AP zdut-s=4z(v zWsI?^8y0OQ4EVu&G@svA4qf=nWC+A=R zalh8N(>|UXw&)*9%U5Ckb+zc`&6jj(LGhCE7izZdF#Yl&b<}B;+`?~Ulx~{dH)>zi zZC~q%Rr}1c+5ec(#c)mwfdnZcBQt))6zE)b+f8d(*uUnCtH*4H%;Q7RS=9E5SIsHP za97`P?H5yO{U|scsDK^K4$L(*El? ztqa&T6XD5kC#f=C_Ld{OBcpVaG>s=*`EfI{gFjaP?NgO&30S^vKlSn`OXx;hQQw)K zMR)@*_S`x<@0m5pE_inr)@@riV~@4>F`lSw57*l>Km9~uQghsG4_px&S=#6kAStN0 zxJT25tMt->xPQUso8EY}lks{wm#>l_9KO9uc0-K?IOTe)U#N(TO+J~RvEVc_}w(P{DiB1%r zhhLYs>Cg^`J>_>+A4?e0oVckWT7S5QR*5DfFEpCVV@2XT@moAktWh9F@G!lZT75J2RCzJs3!Ly3o11CEE92m7 zap(jvUL>!It7Qv+){T$yrT)5mAuE|-ByqWOTa~gVr<{m8oV=Zi1>m?k`M^{>4tE%S z_uYLm*aNe(1bjjJLUm~)9~mSsImUn2;p4+&oNTNN-?k8Z?eYbe1OV|}4>qy#q^&w? z+IQpNX2_0yAE-{waNNZ_SXLFzaN$>3p3!JnciS!2hQ2?an&d7#o2IdTL%4Il{>Ql1 z-i_?*r;D=3SHU!enfqSN2;Up{d9epXHf)-KS<104WIU1fS;HiupFU_u`H`@2H-z?j zB8J^KE9?n1!a`n~&9#!ZnP$FNJ%IsJ9MWTI$yU3y?Ctdpshgb*Hgc_D=XkD#RNi|M(WH<0!{LWk!bbVlVH=HJIk7NH2eOniEAC=ev^>D9 z7}~+eJ;fB|2<5AG!l@7Q0JkA~AlT#H%%;Fs2+1^F%gU1)&F^B1a z;N$b!G1Wf=U^|-14iO`T$7|qKW<3P}bH?G{O&9j5&!9~3?prM z?Dm@4qQFgZkD2DxXJlP^2xse5(1pYLNXuT;!ftnqV=VnOs8I>CysXx1UIH%w?fgPy%%g^eYXe$xbR$g7qgL@=RTj@ zVE&-m}SYTjLFklp}6TG0PtefsG= zGk#gzU3h<2oAqY4Gg`qma<9I-G9z4_T{rbatmcbmrWn8T&;HK09(OWBSXfqCjw2Hm)AI z(B=Z`1I$Lw<%nmLQvh#$>*{_2$Hx)kDaWu{rF@SFnKvC#Y6LF}vk!>cQGgXbp}>#2 z7Y!kj;!gw0a*xsU&2b&jQH5KPkaG3~lNGtp-F5E#O{tR_MI_0njPA+PWN*s5R;6&I zuoN;sHllYd&m%&bbPO4VQn>}fWbb=ep)@HxT9x4CaOGBk8sW%P!LUz5j&MXsgmJV_ z4eb0AE_bm({Ae{R1S8oI;pgYIJC{ZDjtlr7YZy>T@6X7Vu1KdpN5t5lmmiXRq5I0j zUz6aaxK_JMs8y+g+kRZ#Ca0v7oQP|SF)$E~Sa#)Jj7C~M>F zY|&Ic>)owtarR@}YkUgX)c$<$Q-^4i>Fqi!>i>{J^`qQ5EGay)z!wFv#T0o*-k40q zG%+KSg6*47Lo9f;Sohf#IBY42*Wiy9Qya!s4iZn@^TsI9YVTEf-0C5o;}*t7RKAbW z^8WEdYG_i!;TFwEn11JY{rhi)u_K{E9|;vZiTrzed`{-}wo+C;EMy7dE)Uh&EjH@z zBy2+`ISu+3M5%m?D_9<1I~P`4d2VjmURJgAe)@1n8Br}rRwYh56p6;JmFpoLiYss7 z#U4H`H6l~%IdmTr|BNsmEsWig5hIBBEV&`%NcrY_K;*f2$j0L#=BTaHXV_qaNaPwj zQzU-6IWfw*<4*2B?e|>d0+3|+%N|IUqp!U&(Q5JUjv?F+INW4_J+h{6OySr8xsPIw z#Ep=nn(W6{!9OTBRcjCOT@7~YdEChZo6$QKLXv`VbJ=!eGSM>uB&X&azc(3Hy(FTy zWRTc-2iF;S9}~>8ZU|Y%BXH!WEcegOW*PDq-cpyJ*qD0%tNTzX6gBmaC6ooE%+bVO z$QXO$UM4P0irTOCifvQndZutrRb?v8{NCfEB?>E4Bd6-BN9r?el(_G$$O90*4HBDH2a(5=QWMM%lSL;cw_i^%uiG23oF()Do;9w0-eJ+n z=mAwXi=~{Pf{@EXFrt_zkyYwd~Tpc>SJtjqUUugH_2Of#^OO(Vo9rjo|ht+LzFt2{CBUQR>0nuPM* zg;9GD<0ya=vZXCxc5HnLe)4R{4Q5d`7|Eqp7TTW=>K$mVSMbw&D{s|wBcH#hrdjf8 znYglM!dwnjL9Q3-TT;h#^u-fzZ6tT+>vm*6Ci&AK#UFd+|j_qJ5HS-ZuIOL9e zP@RDe{OM8Xfv#i^tAwg)F}Kgou9|8em7R_D%~ur$$f2Cs54mfz#*9}g(#=!f*o=2!)asL(lO_A2hUc8s5qHcyCikGCJ&_MtbCU$sF^k3Xn`Rws@W+OkK@{* zZ!bu&tNLJPKk#Z5NvUY;Gv4ZxZ7BDu)GBK&i&UG5BEtpsX zH|HAL>z+f$lh4^qj#5S6xqB3Pm?yNe1+6NVR;|exRWv7zWY#B9!@Ui&A}TCTXPCEi z$xmc+DQmu~q7NT&I@^44eAlZSiECdxh?LPADleR=tZCzgx6)$N+q?=|>iH(J)v1Z@ z^Z+Srqi(|bLdnx)r}v9B$T_99n%N!mM{hnqUwaQe;Aot&(#`wD4Yehm+{SBDta_7a zExoIwvv)2na3dQyN-wFi@td5KN$f0tOpBI#`jrbHQ(Sy`g{odHVS{l7NPRQLLzA9poEV4^AV9eN5K;RJuQQsLJcNb=m zc%f5jv<*rLKlVX%JWe$dQL0J%{;JTpxJ@(SW?{(6QlD&TtzTQz6J@yhzI3g?G^83& z7J1p1B&fXGh^do~N~2#*>c$k2RZgfnI|unpP)f%J-f0cQufrRjQ1KpX>nVD7(-DU; z=8iFKJYhsOPq^XHmnyGIfA6+!Buhe~*3P)QVTWOxATDPGf1jV|MpF(kJF-Pgm$=SD zf2GQHGL|kYNCV?itekASgvGMAWLTf?u6{8jwYJQ_UXWy67Pkuj!6L?n{AB>=?65_& zFs*2bo?-QhrLFXd5h5k4bysxGVvn>{_Y0+jdYyV7cvk5(QL2^}x=oPNc*u)7{3J?q z&3L5xZj5E6TYVY{?#uQuw9UUh*&SFjP&xQu;KkC#@AX~<;fLDi4CLwL4;}ZQz7HJu zD08lI7Zr|5&P%~wkLa~2%H4I5rn*ucZl`NOc zZo>X=zAV>ywJh5`h=cFiQh~R5er;aibo@rFC+3WFx`>*LMNy!Aul0ym@<44}U$!va z;ZR)u^2+Tptx9Ju+0;5Lgr-DRD=%V6$JLLq@)1_nr*3|MK@l#6%L6T%| zO|L%VC){$~w%~>+CPmrlbaY^rg-gNhhnNQss&XX&{ClNPvPdV>mwwKXP*RmDz{oPW%owo=2b=<~9z3nI=^rkkQy66e3 zP>;+Ch9v#~u_9e7!y@K-OCWPrVN)m)P#O-IeoLIIlk%p;rmE!z-_(9gubAyDG{Es1 z7FdmQ6-Kwqie6tXTZ41KbBCsXV@IYe`%pgMGSAl%Mza|Am0wm#C~M~Kn#UGdJGLbI z7nk^1^GbTI6_(ZnN0dd^TmZezH2JU?0F1=Z2qhqq(BGkMfd3yQL|a>2mE6V^TNAZ;%dv@)+xC=xXw zRjp0DG&P>31Xtjp9+JHiN7@hfb7BX1LK9}u;h0v7q$9S9?K-T==hmaCWbIxIG#{4Vp+-Ih5A4-6Vp3_)&Hmz@0;?D<&2$w}TM+u8a6q%T6T03{o-~5d|YnWy( z&f!~Kpm^DGo7U0r3AJ@(d_72K=700=ZRg{5@y3#H%=(~50E5^rG}}#g$QlM>F9(?Y zR80v?SkK|GjuM;L>$u5ISdbkN(MPQjZMIrfDH}cQ*FE1{Z^IKf>N|&j-2geq=%X;@ncB^7yr)DXI2IgE>U`sQ3P$C^XLlS`miRy}} jbb={1CNp{JsdSP%qv7 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�TTl1W8L@hhA3F>W={hMl?>*fHShZn_xg2K)(dW5sCOOQ-ZI*gZOy^4U(EUJF@eAPuT6{v_Wj z_l9%K{0ps1-+SA9gVIwTiW<3l{!u-X^V7x<_b?T4g&ldYysGMX)^-o&+Oe{AJPXf% z<8i2&|97kh#ofQ*{xSmL%?9S0s{4S1gd5-NM8YF{gWm5?#N0qrtRaylH0TIkpx%! z|88jw$gJwC!7lh!_V(^+rsCCGK2C+DHX~JRj4lFh*QKtir0t_4mz95>Sg*K!Mzi49 z2;L9(A)zSW_SDt6A1);i7_;4D5`#{Y``r7zzt;lnv5CLg@yB5WqJDLnX6CKBsOk4( zB*!U20;#9qvK~sftG#e=6)2(Ee(SbymQ|D2yuy}O{;UhVaLut-_d1yQ%zk!(d+FRk zd;or){TBQ{h_V5GX1oV%Xei%otS=IrZ6`&mg=YZ5t+hqr z8509H3d`Du)&n$FXsY@jq49RVk=3?PZq`} zA1Fj#NGn}hi=FKJ_{D9?u-g^@;?&BhUZjMp+KOdOYmwvmA$`q_D#%(aTArxv%p6^q_VB8XoLB2! z{4Dc6;OJ$SZbQbkme}bdEGqlh*xWM(BmPT&b#sdzVR*V3>wawoKW7OM32ozUcQAQV z0g3-Lpp`Nln7emb6p6pw&BpKdAp{aNUz@ED+lUhx%wVMUuADvbQoo)pBfue(W*9ImVy{~*m0{^{U7diihmCB4fG8hvlGL16{3;yYYyF;It|#nv zx7-l{i}HH;7mbCn^ys!O3iny=i#vP`H=;z!tc(7>+l8E+8Xo=J?gJ1}1kt$_=Kb%a zC@k+apK}0do&zRP2pE)n3TVYWU{j;2jAa`RC-cAH1~fpt`Q@+}Gp24i59a6|y+gI- z%0A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

=U$&!a>E$P;wtjsK(vgYaf8NQUk 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%0A4|v})tH#%sfL~7- zF>m;&G$Gu#$n<**+lHiIkU6qZ-3sui_t*!stHkc!y<3FdlceIf2131fg$D;A2zt>A zWo2np$qq4+sJtduXNSX1zY?(vN3~!_ptHBUPP|pP*Vl4>Q#)O92nMD(}4KK z4TZu8cx+99*smOwI}wab`i`9N4+qAl%<~Olx%CI`n#B|DM1t)wd{NqRLwCIznNX4J zLJJ1Qg}VBaEMW637rJeJ8>%Y7azv)g50iV=0y%%4uq!`UGYKj2k5l-hl!H5U z(w~epem`Rl!ao;3x{qt-O+GO=A?-yY3wXg!3lfMd*-gSv@dDfmn^b4_@bmKSyjpsy!u(Mpc>;JCC0JdSbeyEbgeDSx}Xjp8IUln`EY)QP#6& z9V~=V1GFT`o+Y1l_z!S9>|{Kxar?|#yRJnbp5>Pie*IR9;;P^wjVoh~Zl4?D_*yeJ zu|X$KZ9M>uFmq|lRzU-K-ZASPvl>iq2LsA6Qn(h=KRP?xv4LHH^mCW;XupH#=eAFtQ%`TY%I)eiZZO{Dmvz4+ z|0#@ePyM;XlxV?rPX&6f0ueBHI48Hz zKac}dMDN|SAMalDf9h&~&2R!;i`nx}N(x9gU9@c0gt$(Zy;l;k;uekTp5w>!4i12h z+;+f6)Q1KL<{>NVd`K{gzE||}LM`V0PV(*L-BS|MD;RgYE!p(il!R2m0rr&u$B@KboUWv5-Mewvuus^r%xMD#4!lv0jjAL?Me?KmT zXCxgV*@yz?;>3V=5ea>!1g*8`II4aUx;m8|Iv#EJ7f0w5jlPCxpB-Nst=AsBU+qIv z7j=&Hm5@aMEsyrZ4f7u*G$3ErJnD1w^mAyLvna=j+k3IX{t6I!7dI9S{g_aCq{})V zpVHj>7Ita6BU2^uGRd_VVM_3Htpqow20)Kn8kWMg9(QcEWTS^Oa5uaeP zEY(_mG@!(!Bjc_0OyPiw3_!F`vfgd~gaM!{u=y-@DbhoS^YvWz3T%_nd*;cLQh^fh zb5@5(ea8?YP@d(&!6&*9=5f~RE%}Vmd*Msi+EmbE8#UusYNTrmPcMQ)hJ&NNBsQfgQyBPR=dsq%y!0u6HA`A=0Y29rok_X?mI3>p?8(bm6RTJl%p zVxD#I$VRt_Qh=f*I4N))ccfiMSgD_O6y9@*s@B}XQq5HeAbC?|6OvyuZ?!V*eC}Y7 z3takS+oZb|OSlt(H?N=9tnmM98oKymp$TRYDX6FtWh}SX%y~kR+^>XH*0m(bSuPIC zUVABUFyEwI8l;MCih;m80@9qV&o9F2m0HAM!3frY&85-rRboWsC%-^%|NWg$1Yvyo zCr_(l9q}w-aaoYCrGf4P*#C*}>Jwqd-3A!&A~5bJ*V>ralM|gLVT%h)@3y5;UI{$u zf9vf{<4;WNVTtZH%6Hdp6CONx%Dm@8R|UF)cCNeFniHA*(Jd^o8vW)0ypu^ZaE7!y zhq8R3USyecl2GxmE3g!TLTo@k0e z0vn4IK}{{|&I2jgf!lFGhU9R*PEP`6OMDDVv#%Uu=ym299yoPSbpq{qbjL2>Y7XOg z5j5RIlv=_=%3Z46DjEzMPW*&4sNUb75R*Laxw7u*e(cKl0S~*FK#JPtraY?xHgYn% zh_<|X1vFLxjgD$b&Hn+3W_D9}6$X;9d^@2^rq#_B#p&w@a8D z7}z&p*l~=WpmkYhYeNfXjXsS(Ds#cDDN{jt=H?WH9gNIMYv}buvO{rMSN*c3qgowT zf2Ek%s}L6V{23)6p7A4z=HIFt8j{B@MmP`p$48!(`Srdv7G~!?CLxzzZl>qu+a^NW6Kfs*S%{Hk3 z)6kZG0IQrP+SBP^7Bh3YKgZCMc8G+O^t<_)p;*K*5>Mz9J;qlEVY>uGyAQ5`RNE>S z7479VH(^Ju={Xwq8bRl+CuOkh@X=K|U>+Yy`A#EC)8AcU!|k%SYlq}kXSS1d%l4pgu zEJUtsw4%Bpt;!u4F`eJHo9q!f)opQMaSrL7zEHwsCWg7M<)ulF_UjS5FRnISkq|7) zbL`%XBc`zZ`FKtF+0To4e3NL5_vPCDZ3Mrn6nGz`QS5SJzq0Fn0D>7moieC= zFOa?>Gs1EvkO#?-Y46_WL<-DE#74rJjko>2#Hw=M4~@bDs0e%E;NOmRt-X- zUBY;EMXTar#gs|TN%o)Cqu*$F%JPi9QX&2dmVC*;YRz+?W`6!*bEP@Sr6I<`geN75 zX;ot5n0PseYOGe=Wr>^~cCc&36C{SO`%b0lXxMN)P{rffR7Sa_@6*azg#jWS2`Xc^a6A@pm31?I}a<*r9xIk_fGZ zBA7|wgquF|+U7SGph7~Sb8k8#j;WPgGAs@sO01{u8M92}WQDd)5Dqi0$A4Tt4@p-l z$+q2Y>$2O(gS;KY6)L4uU+ zzk(Dt+F>Pz(CnJ)dbYnCChIr46i*dZXvR3D&duH77?Azt#fyMom@u;6tMT$W_^SwV zC3J_fNM2KFf{aAd3rvId5J#!>FaL1y6tlAn+rEqVdF8H9rY!Qi`*1pf?c5O|=%?tP z9H^+EJ*r|L1gpDbgIQ#6yuWja=jJkG8ak!6M_~5-3RF!LCr@U1cP#&ERsOga-&jF0p3I;pWI7WAj$1PrL#V&zU~O|f#@dB)}5x#JYhdyrUf zp2C^O%j!r-Z5{wxU`gRTN$w)X$+N9QfCXr!D&t8xz&tC&EvWtb%}o`Q6=g0vHbYI) z<$+|@!DBmh|H!Okn2P@`vxY$O&&>KaH2FVg)?cA+UK(pmKWG9fBqiYbpU-sXvBEY5 zZ7-uQUXNg?@DCF(Fhtv;l88vyUxt}VmAw1c5q}LU0FT3*rx&cS zy#tgSF8y-xn{TeYm3@+UXdY;lR|?+}Du=ySfQ?7_%}p?s<_9~&UQ2r%>MF5Ge?7-1P;G`5&ZSq1xU8Uow~%-g_)nvSlQnU$mIpM+`6rk3mq>x(dvr zo2Fr~Ln^-$(vxJx@NY5_M)^ay+yss04@E9tr7B?&cKmd1M(1$?1eKW&1UnHtULz34 zG6?dAA@lmm)zz`9OM`rNtwy@7#QYU0vX>)JP^j6)tFUK>VlHqWobmzGI^W<{Hz!rlC8Qu1{`Z=z9Ig9r z1^I>(E<^tXYePZ4p8dZTY zX1Y{JH`3B!nSu5Pxy=s`TUdTXn5gPT^66gzjRID8J82o27ML+49(oNXc^Ae`O=tQ!cEvjea8GhG^rjav3) z7ncK1J!Y1Yt-m#f@7XqN7(}7)iCyti}vk;$TNBosw3p`SGwcX z%5kh<^PB;}Xp6UB94=0kK9$dpY9RQ8lvCQbqw&BMEsp-<<@-J==YN6o-DYD-XT(T0 zc<}ox?k9wnFBo-*{po>R`wipW6D4vT;m%1&Fk}O%kp7BuEItK@Yub5u{Z~NBLW+DE zlMkl=etrRe)pWy3=f%Il{qpfl3Ilt!{sUP?jOOx{wMeWS!F%rjeor5a89V%FD7I@s z{W+VW7n^R$li&s@bUeVGPC2g4crl?|k}+X;%FhW&g1A%?yUs&p&C3kg=Z#?zm29xV z5eNa=W#d|%aK;JWxLRl|n8am5{mgk<2bgkZ0}JaRQ1yQ4 z?hhqI4q8$kjkR>56e*vwvw~fT)xbkso%d}|agmdNGzvs)4_~@;>EVx(3jlDg+%m8) z?Ss^*B|(NL<8~n#*k-3B5fM?+CAYuqA?I(T>SXCqZ?K?Q_atXZg#Fcz{6h@F&l}&| zfqJnHV3FjKT7P;^8gBIo@w`@c0C+1<$>RjIm{wLJ^F655eY01_;~~vC?yTJ#3mLD( z@ygW)zkm4F^~lG2YG#4~qmX1);rc?n^5})D8n?!Ht{`?0$5P3s=iqa8n*CRcXOA69 zp1qoYILLdV5Rmt8am)uKm~(Ni>JK^?0oH@_Lyo^v+u88InM_$EwPkr`Bm}-WL$NZl zN=*IURFjK}eQoUdkE)#r{DPF2OUaj$+uC8<-C+=p5+i$0lZrHhEK69=$|FA;EoLoTprVqzwnK@1XBI9-p;x-d>Z1)h?726s};fGP`^5drS;epa3Iq zgWtfOG``CgehQ$-es>TC4U9r74Ntdh{fd(?7w=CId&mOZ$RpX=C-=arl4)dvGTK9`&>macBWiB6tdp|CfqrOZ_m+mTqdw(MLJKE zk=ViUPsX;TYTf$#C-CR992}DYQhEO4qp|||GRHr@1}}jx?>9&8pCQ>eg>*dcHctp3 z#Xg*&ob3l(n+~%y*ZtwY(DrFq6Bdk`dgp$<2V;ANX3QwZYO{2 z4WL<$Ch{!h^F+!!H}W5JB$&1@Zl5JSt3A!US<`|f|8Js@v%A|NHl7Jts4swYBUA0^ z5ZwUarv(`m_7fLOlDThHU`(4|;{!99<}JQOWc^FQRN6k{?%b3$WVanLDXGl6TW|T{ zrnaSPU>xvhd5rUS^xogWe|6qCF)8KC>o(1&utPaY(peO+Xn!cZCi5eoA0#yZjcNqL zNg%X=21fQWvL>~w+_|*r2i(oeJ|$ZpZAQO^pM$+DJUviKG4CRqIGBOLexrh0ZTOaY zN%A-mxclxJ#P_4~$2Pk4S;zH}R*$_eM`!#;*`Cr_&HY1X2!TU~8;)Gw|2TA}f-&Kz zVdMq3%X2H(?vOx7mxuC-?Q>i*0qL{so&H>#wA+~(U6R&L>=(~J@dKJ;<+jk3%V+;| z(3t(e-B%Gv+sRi-BUkzce>cr9>v>P|uO>N%(4kUe9;98SF5ZoF1nMavTFwOB1iNsW zh(#k+Jgf6=Wkr(mJh&G&r2B|-sZhsXiqH5`jUO)iR~4vOfCNF(&6HP|8BFCf=O9s= zGb@vV6sWzOq^r()rM3s8rJo0%GkkjO)%hcUm&s)7{fK>My1AsbvGV;cfEo1f)Y7T$ z3O*jBNWQu*u3h+O?tL7fZg;AE1{S8$M}xUkq*HsW3z|0;Ll-7mWI#RPjCQD~%?X$r z<$y)E@8otEB%Rn?unwnPHtrHUchi5;;T#yAv=C6+?DJL=tgxS;+U@iH^XI8>cwFST zTx+s9q$hf=lLMMJWp=BBj{}kn1DxBa801imexSvNVMXT-ZnIi_fc}Ih!NEg=zuDhKed*%re99w`9DEz@)|FAle`a6>BcJ@E|a zhkXXwvtW+vC(w$rfg2|IJL=mPVDT~`dk6we$3`mNcmR7%g`Z3WH9Z4N-J~TZD2O6P z)aDQ%#?o=(4ma{m)OfxKfk$hHxG5!FACOTu-k0bjn?V@d^$;Zk9WJ(ta4{FA_jEOwie}DJgB~_ zoMS!7NLMjE%CL?KTr-9P@j)+70WjhYyfwDWRiu*~xNV*nN~lGuZV>wHdd- zgLwH$UNAEO-}C<;=O2<-IK}@Y6JW6m^Aq14!C=2aRlq4C+Od9pA1Kcvp6~nUNFL5z z5%9@voAq|Vpgc*0z`a9YAV*>YcF@og26BcU6FLS$#`uR=s(yv&HHQzzBbxT(HU80HalEu=qdXvji?zq)dBB&v zkjO6M+0pWfYbbWCaX_Z=Rkq04@ptjic!qBxTE1(`1}&12jOmj+zKhFqp4uIi+a5U#IpV`x!Nv8kxC8u3h2F(Gi9GGpETqIYCj*DNPVdQJPMb+8cEWR zrk~d3Rz@DMKMrM;BLn@!zm5wSHG807aZ_462uz@=x_U>$nggiRHevC14j@5o8CujZ zk68;t_bNq??`CM{A3Cl2X62CpiCLr1=8w$VLXhJS!NMHbQrp=e2}fJVQ)ItuIJ_oI zf+^1g4?D2S7xY4p?g0F#Z6-$|l`a6%&=It)n=tj2ADc@;M%KZi3P>Z#Iw0FazI^4) z@ledk?d=*hksh|o(9Jb)`Eug*hVk4!5i3>^sFmkZMUHBax^d&g%2YczQQb;Fdl&j0k66ywbCn-EcL+cq zxvwF7N%zv<)P*e$xY>RN)onqWsk6!QlwN~D8513=2XW&Ppc5ky#Ux?&y#%bMC0u;&*5sH%S$L7`&7knAN#%C ztTeUszs4F~dTy`JkHDvFS8~u(2EQ1U>j5V;?_D0Sy_rrm58Y(t< zSLiw$&UBFo+f1GJq2u!jVC=o(MPIy>0cXSA#?mXVUoreiKX`;j4U&GM=bnz#s-=*l z6gz(R*@TXLtnWv`l#D2*rj=gZdr0xebe)`RT=><@CxLjbf9u8T)i*rvi2(tZt{>{FC;4yAzak2XWA8m6PTweO@tdI=Fvc~$#TmQL}9 zLB-wz^N;?@I*#fLOcEk4Ad0{PB3SN_;yhdf0Ouv`cL3)=O7+xmwJ*`CSW^i5)kKYK zJxx%e!>&U_u@PafO3H$!xFHOmQv&hU=V;4nN4kbGS`xwv2&@HHi46<%6_Q!*>}2D? z660dB-3+l{z);7j5rnMFym9q`1@*d_qdF5xwW#$h8h+{fmkl29;NY=p-ng@H4;GjZ zguL#rNpkF6(|{zo%{yaA^2kIrxNAy#ev(xA{>P*XzY@_pHhzn4(Y8!=^*7dd!sS%- zCq%ChoP$@=vnD&L)Cac6+JIQy%wpM_HB_)8LwWoj- z6rtskydne~Rbq-^9I9{pD1Z1;bCT&f=ZgcbsbU}!E9UHeQbLr~VaaHs*N&pZX8Q(d z8F);l14L^rC?>)C1dyO)9JB#lzs$YevFL2WxjtF#$~SU>N@*I|IkaEis%M_L-?&8y zF11pQ$;R7V1E{i5i>zDgd&jOv7gQHn>a$w(_rV!~qm_xi$i%FboBXhAfoG;GhyHsi zrY85^^5XcX&l5ePhE8kUoJBU%1+;jt1jmVZJ_wq1$$v2KmG=GqWVVMCkU!~9&o>w6 zN;4NaZrePmNWJzl!Na5`V3+Fqo5m`;f$J^9=fB;b?wHlKhlYB3l3F^M{djC)^9SRk z)SIku6>p{-O4Hf2F=~n-i50c0oUhn@28lIIy2jq~AC7CX8(N8%Ez9;?A|I@ccjZuT z3989`ezCeZ?5owBrOUa()J&gpHC$?WZ1zUl4gA^fQ|+ej9hNU%ixtIBNZ@QqRxo`s z)uvag;eGV9KCpEBg!&)4me>qPc^n!&|G*~gd(=7oqL^VoT#xIzC#&_j5MwYPk*x~1HZaE-1aFkp9+IG^84--lf+8M_j z@o4kj8@QlET*ydBEOPzER+QnNt$Sgof4F! zc~+6#yriD}sZiPmXlWYWXm(1Z+13#U~5;GOR;K==xt? zoE7ssDWOC<4Xh2?c;|}@#@{6aAr(u!S5LMvnQAN_$;;C=|0XQzT4xs1>l+D?H!Wp_#fq&zv}Cf06}LIzPoEF* z`g@Mk=9z}rQVLSK#!G#|mi&+UE<=N!3KTksX}HT1w|6tR-z_wC>XXY2Rq+kQqvu|m zv2y-%!h@hjBeGS(}sT`>=+vEj%EYtGq#G5bgie&{{?6R zC9qD~t0bZ7s4aIUVj;8vVu1JZINvbTDp3-Synsa|m@CC@(L1+EgT~cCxJmS zmSwG<3GfBr6m!1r))!L+^PQ0_u z21w;;Qz|x-(C*BwG*{?D=#tJOT{7^omjPm4tw3)K5uLZ_X8@VqD$ti$z12pO{urR^ zwj?Y0_Vh0fXVf#i;OZkCsC6HJ&mt|ql!yex7n9A_+dK@2H*~JQA`pb#$s69NVlx0D zZ~`Z$1L8kGYojm!##imL%YM&6^`0Jyf?h?yy<2hnE~#sCR6m<^bz-+I>lSSqs!X2w z3_kA+Kc|zzAY@n4@Bo}A33~TQ)eABNsmk#PrD(U=`Z%w(3!#M2@Q(ZyNyX*o%dEzf zIgzLsrf%;386M=m7Wu;cyw4_r8s4cp>y}~OxEY*3!u`=MhDjliVi!-b5enJ^*hsMY`}8MyB{ahCn!cndH3? zK-K*8_m2EF}^m zwRg9u2hb?m+XZHyy{S{1v-OpCUsCmhT6F-{ZnBk~->9zg!5Y%9EP5DlMyJ4x2Ka(x z7!=ddp;W3m%eMFoEqy;Px7@%pWqEf#8Ngp2f#m7CwDHnLa-CvmT$jkkan&eV$IvV>@S+2#gKl`Va7%4Tt(zrt>>>Um<~g0 zL@rzJBmd(;DL?wujz@8w4<^%42n125%zk(kL{)|thCbH;RWdA12|PfYqCeb<)mR4h zO4YYFQ@mnsj#9HLx&t#x`%KC{Vl`Q_xQyN@BAeYsE=J>={$vO4cc}fH9yVBHp-TB4 zAh_LP-abXX+|lDguGH6o!2ffNv!7r_gG22>T{-Sc_dIZ7ywY+8LcX@S{Y^GnLrah z9Xy3@)akP2jT|Z|ljk7SWBMuKd+J_CU-&{(!^!W@& z%b%T}7-T8Hz>LGZzuRY#0IYIl$jC(9y5Gt?(HAGryvt9ZZTgrObrR*`2eLWR-cejC zRC1Bo-%g{$_-{b3J82bD+^?M)rnbBdu}53eczbPDKA7cT?!EdqdjlVgRe#n;k^@vP zNM@j^c?^i+Z>%LD!KhhP$f$g`Im83hYd+|*FrdUr283PKIzsD&W&wtHDb^Y99O@Zo z(!L)I?)m=yS@)S{A}`Kff~v?2$;yI`1F58yTC{-ZDqVeyw4HJ9q^Xi!rtaIbH_-OM z;Eh}9Y?JvK6fs(vQe}TsKWJDJP`m56-kh$nE{_lgR6J*FXS$964k81yL?g3>(}_?+Atn4&yCZdP z;k@gT=c7FmY*QJ-Lj2LB4nR z;Gv(N0Vk$7@l@J(3d)k@(?hYp(77#&x&gGl?@K{WR+U8e;2k@d`PMBNXWh95lL?=( zK&|+mu+X$RE!13MBL_Jk>9c2;q4ew;gT>(#=pp6MvfN~mW4&^?hLq3fN+d5+lHNz5 zAkph4JWGQ28|bvn9&}_dokL?WC1mU`etsL>$N4m(9D837p+)5SG>D#y^=3^SBqu|Q zMyNL;!KBZbUiEbPnB28p+s^UUfeH{RWRTkv;~Nz2$;ZW$EKdK1ryJOy<8)6)PZFqO z=AkW>1Yr9_+goa+rG?NgfGs8sbi*G9nuqf>B~apwk^%8_lNa=|WDbnfTqo>IU>&f8 zDT6JNpS@d~*vA1CdDE5UXKGi+AJn$IL-L*Rx!x#6tqUlFslrmpNF3+<-!ZnxxOYt_ zW8S!1``jn{vi7~xCbDX8ceW<iv+<-otSsDl)cnxjbm zKWYxwb!5~mm{)vg*XyfE@M16x`BOt4g4TIH7y>Z8cK{(>F?%D9UG}p}Ik3_qofZGCcw zL2eSTb)y|Aih0T){k6`(&XTX}{mdgS?Z}Kq0I-fdi-B^%wOO&()kYy4*PowJ>z9h; zWn4rchOZ%`#{_+3H5{~ zn|u%+w7qI$M3z@DYJI>!Mh1IIcA8=GuYQ8%a0&)xzYyu*r{$&tHVcOSD8f-4LoO(K z?)KG_DDHV(n*JdKPP6k%+T3-Gia*s7KKPp=;o4pcJv@mj_(nPR8&4!(dr5_`w4s*1 z9@PD%&^m zJR9uB(EtULe-BMUu97cElzMuI!A6#*+j3<&EfyF9T?^ZN=O#(TTIcoSiTM8_O6C(+ zv6p-L*FF<0Na1hU%e|@t%+5~Wy}R0SJPc!)w8R>aPvtX=NFv`AjwCE#?Zkugfelu5 z-#XwgMM~g{9RYm%_rn(H#B2Rb!3|U55*8=vJGJhq(LAj^oLTQxkg9y)ApYbuA9!*n zNd=(7M6RnidJ`J;6GF|g#5m155nJi7dLoVO`X z!t?d(^YSo-10)ZArM7t4&Zwzg)L!4E8ox>Ck>B~LVdfXVvOK3-yX&3R1k{fyO@y|* zK0nZj4kHdJ$MTafuf{ zeH@BVp?V1%{&Dxpz;Z{{q4_Tzg3y<{+EBcNl?;f$?&3`n?ZJd6Kqta>kvT_6#G~<+ zqhC@o;{O6!O8+a!g7C{VC7{`E7BTcU{;xnO3UGc~T%W52`UsI&Uz!Kx5dmjPs80rX zA35~8I_6_bf3&!8V^d((#5S+42MjAvPK*^%J?kBxhR$t%!pthT4v_Qvmj084f+JI=;0XRJGs7ENIN~nCb_)OZ z`AO{oEhciT`zFKlmrGOiX#m1=2t_L!|K>9S2he0hL*YPLw( zfzxG%v!0;}^F{9sr2`Azz~wbm3|d1W+0pUizpL4U@)h zitY*_GTR7EV`etCFQ#c1E&Ba_F@J;|me?R;_JMH|RD1bWh%=;F2^~gxFxfO|ZIhh^ zFbS}S)?W?uKlECU6ynM|f*hrn1RP~`+;N^h7~cJVSbGz2s@Jw}I8tb0nTrrIBuUE5 zDr78D5y=oTWF9hGOGV}>A@h`}!H_Y85Gq3=^E_vsSy;<={@MG!_x-)k^Stl(9^Y{| z_TKI6wYApuzs~dgP3Kn0w(B$@27XXX`ukQFqzg@msEi!@VnaA3x;1wU#L|MVX0_$O zNP!l)=Q;s8r$4BVu-XCR2{;0Df0kRb@fo1Pmb}?jITMw@0$j#8JwgYV{!8=-%Ih~P zssCyB9X0v0tz5GEz0--ImkVsQ2s@k?P8j-2tUwp*l9(;#pVR3rwi;3`ZXjbqUjK%F zR-qEh7e*pAOG2Tta>iX~k5mq-c3)29 zhIAG|gDFt!uvTx(KYQV=y(Z%l;@F_0-#4v+$di*O5t00<=H~C+pEi-6{9ath5CM6! z27oRhTserYUH}$l2zMdSUA{K`z2k6K1w-W@0<$4vQ8Qd^(W>JdcSo^MW6c;PpGN7r zl0%51OZs9^Mi-CTJ8`FZeE{z@!Pg4-q!+M0lE3?4Ov%OIU1|94(|`ufZ&lRWNrE&7k^Ofe~5c;llb0KIHe3?G1dE7q{@TXO)_Odx##0giSjAVwrPeAQ;F(Z!&wIu zV3M!6IFnUU@=kYuj+ytrJhLYY15FslCI&~@17Sy;H~_VoMrG$$Vk4gCn%AFX0NlvTYF!TxCb$BdLa(P zXYt7G*8l?h-B5lnonRJpFd~rCb+AKL0zqUGneT9YKPwSBnu_8{tup z@2oum%o143=Tk~-9 zC>dMm1mN$1Snu~USjvGg_uVa-kBg8K?n~w25CLLEb-VRkh{pJ;wY3c*LHI}uPZD9U zJ%Nc1<`rn+u$He~3ui#s$IbVkmtEq*Bh+b}Pa~c0|ZnNjJQ=@g6$<8I`n-)(h?{cQHLsB;cW$+iq3k1m^@)4g(%7Bxo`~D5yq+Js6Z`{^X6@kyVy1KdHW?%wYyPo?Qi9B#lBL*!HRLjXGAuGcWOAz$= zL7UOVKOnx-E%R>HOS+S`b!cm&X#SDWKbW=I?mGeV$4-mUOb3)HF-*X|#Lg5!rLJ_F z;c5h1r>p8Ncgd$qR7+8SJM0?xu$LgCa~RHlrtas`=4cQkq{6ZZM3hgr%A@s>1M-V$ zPuGA{d<3|JThGLTF$5zlt@T;4e7N4gY;+2I*FNjYl3@o_^$15m;9a8TIz*aB=Ztw*rl|uc{5dq7d%Y5&{5n5o? zezr>hzYACJ%*VHbIiJ^0J?_j7DzfaAd#g>po7ty6a#g7+1B2hXZ<8tOM+HN0INh?E zw3z>MgA`dmnn^Bx+5muy~Q`JysG8(U<$>vE%aNtp0ym-5QW zo9nSJ`B_=5;uP?wivMs}4S4v@(-MN(@_y_lEqttDxXn(##fN^T)ZM1vJfFXnZ#6En zsHAWXzYrmR;bD8}t-f;l7oB#JAU zTP17b^zr#UxL?WAMeUl7Q^dxz5&?eJv_IF5KUktyGP#+xZCt3lRUN##ea}@&LC+aV z{_3$5Eb@r4hI<0*GQp?qQ~LY+cQ4X?q!^0DV80ZkuLOSrH0S*8h|J?!t+kzw{+{?t9u>VKv4Y~qyMm}l_8h!Q9tf8FBjjzO>a6-E-L0QMY$ws9eF2(56H-;JuT&6SjgB}k&DE!NhQp7 zLNh=~Nttxr_?1mi>8l|%VD^5d%2V(e*aLZiOwbV7`4!3p(;4V{9ce<~d4K#cf$OU! zXh7b0yWdsu_VP*s0SSYI^F@%OrC>G6otI1q5Byq?y0DFcq3+c=^&>!&w5D7>Ctv39 zC*`Z-tID?nLY6(I=-7 zP%6sX)go_^#QRjuJOk&&p{MitR6Sr&jTSWN@KArk1f>ydsGTXoO0On=Cd=G+o`T1- zbRIB|QK*ZERDWU(?wgq{#8|H?g(CsPwI*KN=_1IZA=+K@=n}UYi#kDZ7wC7eio8-# z1tSXLb?%n~;%;>i-s-XbC5QfB8;Pz_hqZy=f@GZR-EN+q<*jod3TMvSm4K7*-w65l zl9%=c)Kmrx@f4s&WKrL$X!k1zG8Wp#g(8?j9g?O-X zoUhoYHjP)!ES(^GW3SpkXZ*J+n|1*USH?X=`9&m((D0R-OKV?#DRE?sKEpGt8SBP< z#!wv8U}}{X7oPV{p6m12oDP!UrjI-IB;7(D3ev6Ot71E@Q*0eRx?gsTuiMTwA1uG* zNL^Y^6-eVZ`f#l0R^L%KubV8dIv(}3^oAtR%7FTle2d^J+gr>RefGA{2f_@M82=Wv zws=JAh~%hne06&GJ%Iwb3wN0-uOG!{ZV4xp`)i4}==X1@*^GQgX9_;@$re}s&cU$U zt5(1hedx-H1WVR@5LdR`biH_+6Pr_y5jV@rQ{lz|?hHUhw>6rZd$Bfz z1ug9??^g?QM;UDT@;H=~u(yf{5}%s0VD3wW^{OQ{!_Vjn)kXx*@nU{32JV=5kpNtGT!=d{|eu$mEZUD%vxEV9EaEv-h0`y@SHrEm*U|_lhXTW zQL zilD&&k3a=S5vbr0JFjgR+;+C~W#hDhP%;k*GTL6<0}4o0x4x*u^YoHgZ;J~c7i~v( z_qq5zj(qXSj=|$ZQlENR-$b{U2P#?gJ$=Xm?Qwe0N&PD{z4 zlR-!RIJQSfJUnnIO(AfEy>m3S6nBf${9L)Hi}(Ov40)Q~4WPZ#h0pmC|4FPdJaDC= z*ZAzssq)s|SZn!(j-vD%C6!$rZ5^%8lynjA zaOC#hB(lxj#R|9D)Lq~!x8JO`4D7cd4$!8N!C4EPv0V1bOxSjz`M${{rD=1ZvY6kG zo!)vhahu{vzv#12hWA$y~cL2+}u$qtbkr{9SFnWjw7XEYLsZN4PLGa0b? zdXq7H4ir{lfP3#vNz#JTx9v;7Y0OvB_Ew!2vRKr2_0}Yzfo!1}_`6qKo#|BVOtwJg zH^Aqwk*}7+b|;9|K;L~3k<;>iht)#0&I&yw<4hsVyRdJZS`&$?rNCIRAQM&NDe zxd2%*_l-=z9dQ5hIauO%OIi*xJ%Tjg);T8mCvmH?lp-Pt{Jc#_zLh&HaVR~0RyIwf z5!Bu3l-s{2HOeY?j!4BLuCAx1fGULi8CFJ|u^!(z(~Ee@sj1Ik<`n@RTH??-`D8_3 zMjCkN&xaaO(3`ZBlyHRM{Gf93Y4QMJ?y?6R+ z=ZtjX@`4tdn39CN-@Jvm$MKe}KCqAYgs_Z;OZa&C|E&ek3mKX`I9V8aW6u50;=@nx zOX%0RUoJMs^_vuSwM1Z##f$7Cvpx`c2ba${#V75b5ZI6M`*t@`2OVGHC;+N+~znkgwyU>_i;M%SdAH5 zxsQ$EB?%SliKH1gF=9w^gBrT?&qmkZn-`%M;5n@*ciDrLi{PoX-;=KYqcp-;NoIqOP|9y)K<15vo_o5dSw;C}UG;STl>;R4J0S5YhRMJ|o zsgI5iq6vcUeUAsD0nCYCK@D55spvr9A)>X`t?&)W*v^o(*M<6n_i4@-Pc4Vc&Z>A$b^pevBe5>81&I{QJME3W}zTQ>A@uIlaK`_sh}TRLj_$;PJEeA&LWO8bJHYx`QMS3FP-_{Hxijb)U+z12QlCp{ z`2`$2ca(z^e0WBh7Nax}Cdl&mpHUfm@fgMcpWH%{MIp=yAqR{}-e9pk@0|)Jwx5gY zKZ1^xSH82{x{*Tg#s4jSVrfAUsWBov-P8Ml=}4d^W0o5jY2rTH(vuU9Orv8}b$?5X zo&Hhot0el2jl_KZg`{-QLh(rcV^OuUCZTj~SsOJc_{s&{n*<(|QNKXtOm70~}k20pWFH^4* z7C0+h*(sU_%QL*4FqY~}CyA-AX}vmCNEEL}UKfF#H%GlJ; zx7g&ZRePaWKOR>O=(Ia*CXj+PCv=*OmsRThrP>IoT;OOfk+kSOQ z4gaZU@O`aGd)jg;l7xg5+8UC( zHD?XMYiUXmzplT62y~-w1=iE9u=-2j$@Ja1VyZ^9XJW$@+^z8vF3S??PeA?VuzR=i z)vaKv_}c~|k6Wg}{_zt`y^t|M_%I&;_5LyHEnEW0Kwp=*<30|Io?g43XK)Bq#2IjT z_<*iB7;sX3&;rEdQ%$%)V^H{7Em_(-lZ#9W z(Ejrhg9YwUlZU>G9v)^b#Cyie?6@NLHx62FrmlNlXQxKMYxLf7# z0pX%^)`UVF@&w}2DzQLrfp8Q0sf~k;kn*XqK%u+WeBC1k;vmnwGiV!9*aW0C^dBM_ zCL$%fl_vfmYpCyP{ye&s5}(RqKN!s-An2d%_1^%Z@Ib5F!Slq3QueCq$(N9+I7Zp^2!^A9C1C+3r?+r9ZRQBv|@44suu)Z$@9nF?vs%n=2!7+SY zyn*fN$vURN{K@yryWo*tS6mp+MHE!okURGlOo!tIW>ocY!vUM=Rv3<(l%7Pq!K`ZK zB@Fjh^uiLj_|HbBlGs@dVtx6|$FHR^nX$o2kczmIrHHHXk-0ssF))0pRBQ$*auy< zC6v?sa)Cdz;L7OIXgxw+0%YvNBx=X10*&*9clg&zHgbN064BbhnE4xe5g;he=g}<} zx!Vhfn4eL4g_k?%Uq7>mmn!3uWYaJNoC%c0`@7nH8%8Y&ry*6U767o>KylRwH71Z7 z1kmfbtL2wCFQw4^AtxiN3StxnPq{Q+KJT8J(eH%SsSE1!&;$TYArtsW$4=8TY;{{1 zqM@&n{UHX_?>Z7*m-2Ki+zLtXc3(W>?X<)3jc=M0K-+`79)vFt#tP|lNShUPfCBSG z((z?|%UfXcZ6%8eE^}2akiHgpC4~|CS8_H8@J3C_ree z>WKwAbpFiJzAN!T2p^wf)%>2u)S0erMl#bK~TQs zs-0~U--?&BD2bTX4|Xo$BkzdVEeXAyIQFahs`7n0`3V}o-0}7+1zWcK6GA*GqN}>D z4D$Sgb#YEd1DaUw{6+oV#JTn8ZdFrzJF%Hu-TYz(z2kMrj9rbA+nDWs4npv$XCRA` zfuy>?HaU+o>K=J^py6zY73PNr&Kt_cG`}^Vne8PQ^=s`y6D^4$;CD8)O@tu4Pie1y+u&ehG zjEGtwe1$y;)SX)``4w>zKfI4S>zNB2-_=Q(&e^UUth<4A4( zkOTp|bgKW;L#jZZ5K65$=xmU&$(>AJ7^Owt%Y7{dD%cq;Vdu4oZHo1fyxA2nZ$T!5 z)l!B|nPXS6H47L8y#Uyn`P}!IMYytImabeyoefWT32ZY@!+BYRnYw@kvmO0uIwFLO zhW#CC6nABZPg7id6rqB!zipXJbgwEfj|OBq_J}NmriJKvHPAspdQB`Ulz=w_$;}GR zBoezH5no0%v|V$4dB4v*NrW&UOE{Cia<2665&w;uM(8Xst;doy?t!K<`=F`qxV{;! zzh%ZQZNAI0-F0XAZiI5i>XijdQhk+)c|*?j+hx@1HQ`ffbANfpAs4E8imK1K@;&p* zbEGBC+ssEyj**NUsAMWh`f5>;6|34Sad@$E#>sYf*w9EZLX*~Z>j?cocUK;b)qtDt zUBwaf+4uGUwUX2Z9W1QCEBzHpZK?isho60RhfGNW$;2cPNae4iGZbq1|^p5cwY zZWExlQx;XmOnq45G>WZsZa_sCky$&de3l`_%XWRfKL7%UMznYGXo&fVDgX9$e zaYiH<&9XTW9RjfV2;^zi!snv)$?g&U(9N~9_Qle>qH8OVb~OYy@g_L35exP)CJ{#eWbA<-;Nj1tYYM+_LC~I3%@^)a_2xWuOBSzV-*o$IIA343-5-2^P;|o>gvKE3z*>Kpe1wI zn}x`!I6SH1ZbwmV0V@zv5e2xX52SccIbP5y^#YBxG%oeRlNJwvEy<1?`KTppFsSo~ zG~;W9{6OK*4)@mcH1*4S+<%$VS?v`%c(BT^_ejp|^|x-9{0eJjJy%j+lYQHTRyUu) z7HNCGvl0kX-5V$@)@a>5%nEc>t7O;LK_l86vcqeL@6?9B3p)-cAP1*wAe08^p`8H=ev88i&+lu#olrs?zRek-2wo0-&PQN^X z>w{upkI(~WlQz<{u*=sxZ9OPhB<1G5^@gE6>}VPpe7(1u&$!*XeJnGkro;;S!Skp% zXo<`>Zw|*d*g81a^dJH4z&oqbSJ(^_w?~OYn98>+`;EVXT#kTQ?k^N;-i_v+*~4H{ zF3l7(%>5s}3r|jW4N($f8zK8+Js{ z#842*GEC=~;Mm5Ea(seL&A1f|DacV|8kG6>cf+q!jNv7WH)uY@BLTDaaU4;IpG)k& z8PGU}&J!I`6;b{!JkG!}Xk+qjowaKF2eYj)KZp!>LXgBAeq)Vg2(cex%%nc0Sb z&6)bwk{o*w{+=$?=Ta7ak`~$702EmL0x85E?n_ZaLF)!L_E+zJ;&^}C#$c(+o4+>- z>@D=TO6(7k8$!tE70>LHo`FqzD*x4&KVw&Q)99~}q;a2PWy|cMl%-)*RZAFIu_mH9 zggQ#>@6YP%@B57P*L5CZCDgM5*3X9&SjQ{Zm(KD(<>>2+b~71_w(hVyIWPGrad5!I zc)d?>@(0I>g2YXEKEJwL$B1^hr2zD7RqPK(9fLvA&ojdVCmHh}IT=4WT=@O~j{jEu zM5U@BF5A$zEYvG~{AlWij4gb>oDTZIX5##s&$2Equ;yjFeBOpE(_@;J z5UWCeb_pLz%U%QhP{vk*zWnyi@sDK>I!_C$C@=hK6pExvj!VB88&eJBkxZ&wP~#w5 zaNB1$CyAZK8o72kFJu*N0GLH%7q0teKsA<|;^gE!470KDlW{lHC=SpgwK7Lb_ERsz z7F7V|%7th=u-&5oBNn#I*2+H&x3vr`LinRy;WeUw(^;57A)QCFQtIP zd8OhkU&lTQ3v$aYLh7bPf9db`aGBY#%S5-7@b)Z<>9gYWQm;oq*I92Y{IwNozh?II7i*qWrzIX9eiz8Bs1dnyC z%T|31zj7L{M{Cj{iFi>P!^4b3bBq~H36;kr#fq0KFP?by3xoZ-!@ZKE zkD=Klui4>V4D6PfAbHIqfQBRwK`(0fN6~Bj15(i}TBF}~xN{+E;4}ea#3vX3zG(ReckVb$xIXVdO8zb*_A|;VA|#)Tf`+&w|AQyA)0HcPfgJFtzj2`>%idc zbhq@Ypd2KR31&}RMMSyq#i~tmc@X{MYKg6LQQx;}A?lw5jAv8LBYWikwD%=NIfT!r z2-U33hlJNztxo%)zkVx}hiGm_xrE^V1@fYY_`&vRB(1XTH>l@B`c7AaQF*s?!>Fi{ z(l*}JFjDt6*UxJ4?n^Jd)lol@^YvUyy}7Z9VUAzdcC=tso_lM#Ee~7|NlJ>@ubh*h zg~3h4W9u?3Uglya=HNf=e>IqF$=9<5I*Oa0fH)YAG0*-6`v{(rGJE>IZV3C8u;Vcr zBy0fD5YEfrxPn+!{TG|F?qq}tnyC2ZeuG`prn1fzJIl&*Z6WJCoT06CW(HPpoIpr( zH|#XeJ?K!_C7#qA7k=$h1o_i~BfycwFht0BImxywO?;2Ir|Spze00klFBh=Uo7TE= zmxtr^TPtj19KX)aQd3wRpOF7D-ZbzF_M42&`@$)@Pk4l>-Qg}^6LR?Q2t!j^p_~_o zQ=8hp_zrauZC8vf3<<`CIZ|llhrltxe93Ow@x`g-8aBVf3!sm9v%*HhrFTVY;y=4S_gu%N;kSl^xQDsnRK&4X zO=4AUnI!U%6rfo;cQAb3P9h<39F#|@#bYLO!Yd31|2hDa3dB7*l{5q7Z#KHu$H^A) zou#X3V(4bFV8W7}M=G^3wGS<$&T1B{}EdiqOzX?+x4*Hy%r)*M(}s{ zl_udo6;Unk?q-eTFE+}#RhwXU+<@;#JGr%}BiVLVpq@W;T*<^+q>Z1|!I zgnz1)I+(R(lO0xvWUd{)#Fp0r2a&}c5PcB17#xTLkbPo1L~4tStw(0~v|9_ct_5Fe zP#d&+k&hl^kS)9kQj9i)DelsWJ`m`K)5z2c&D!dA;Nn^S{3`sC`a6xAK5hV>%LV$t z(-I290g~|8iu?mjF;ejtrc;nbl~lrbz=Ga@Tm0tfGFp@18HGn*E;m9AQshop z4xqJ1>wET3jTjsitqeWWh!b#liY7?^^Y={k^s3kHiH*L{2)oJ+(q$D+-CvjymO_qG zH9hLL84aT?^z;7 zar?Rh1~*R6ZpA*n@M$vP?lneKZ?ur*sY}!Y-7usgV+?OjITbifYGGu!l*f8StVBLIq;-62|H+1k(E?+=Z@d%zCZE?hVPV8f+6)#3PA z((v`5Hscn(7tHayDE(i~rj?Zj7!of#O#3+giZh|~vFZH*^ck&;$zv<*8IqJ;glLvS zkvaVz1fkd&Io-(n97v9w!3KSdQIk(&h(3mteAW8Q4!+@joS1qnF6_?O^EUpK)FGRq zcl5}yo%KIk6n=$hLOz-0Ok>D<+^y{`^o5%kensyr%?8^iuo>^qhqMJ^F>( z;*H(>Zyre}jx3P)pYJ^`LY|lh0A?a<9cYb)4c+C#V=7 z^+6bIJ<#qzjL%ZP@Y(udI(pgf%lna1@Wqu(zkPQjkXg*3;ir;{iX22^<%08UWg#<@ zo4TIIhin0QB|4xOgjm-8w2{IMR|A4K>z?jBLsA1nLuAZf!6Wr_;`(rPGLHtTk@8oC z?fr{dn{D(f?080HA3F?yE?DEUGb8~AR#NrDiZWRC4v8N}k)0@UW5@-0h20J0nD|1= zlsYR%iA8_EAQ0K91bvf6i;)Gq44fp|ykv8}mDHbWI{xGT!u2b*9wMzYU5_Hk; zT@o05kV~wC%ydwsuV;!_FLtuBO3;{MuMo-+mt!5|pYd5=JNp1T{yVzHHr(?G46NV0lGmv#;ZekFu8>8_vE(x+YZch2f08!6ePQKh zpD>F^kO2DnXpW&mym8atS^$&Qw+3nw-4#?v^unkUcO4J-7>wra8Zl`Qo-h?E(Nyg| z$qr_7u{Wp>6LfyGq{1X|I;8oE|7g5=*%D4C$abXfkbBuUe($f5hJ-X4-=j*{WYGYZbP>wO=yP=y;Z!d%oD)Hi2eXDn5Lzq7#_n3&$-TC6z zcj_XSggb?JI=yn~9W=jZCocm{n-Q~)Xsx~c+1dQ&~y z)*0f{Y!%^leXw?jLDKaygvbXVUWHx4!fq~hm(acdk!vBq;>TbCL#0DT7%UcI-ykdX z2awMsZu6D9m^<0fPRQLdf|rCZHauUKk4$}0`3qS#VzR}=p=|$?DG5?Lw<=zAyW#ys*}2mA?D5Z)0mSrI}Q96 zSrN#fb7X8mK4XMx5sTh2!yJSR!vphX_fPQg(&f<=T!Sfx+c&rS_u0LfXk+ z(9znE8z$E?J5)yprDY`t#C(y7PW@?3UTGzcbyP9}j3a zyk z&2>V$oU_S43Ip~QWA6UubA_v7JE(kLEA0-AB!(W4?AH%Clt(h+F#x8$vG&Ap=L1=tEBh^ zqW}spoaz?XGY=df&pf+7$z>fseM*ja8Ym1dYI-yDl6lDa3@ih!Kr98e-{%m;Wf=y~ z%Ha5#pm6M$k!wu?_BBNByWOgN7aIhux-Znm`PU~!(%RIJRt{x+;kz~FAJ^r|O;?O5 zxDdbCoD)(cm%Yfex$Ph!_IX>dgdsf zr(Rv~z2c^lWb~zvTZ>gVB_!brQ*An+ZRQk`hKcK>;yo;lkDWfQ)8~}f#;lX2c)&ZU zh4Kvvk%yQ|R$_@O<{h$7JMsVpyYmPnSqxI{B1DIMJ)zzg@EVgg@D*M80rUR~+{}OO zzbiorM9A0`Js5uIoTYrc*MKARINZ1#I+MM|n6YvSQzBqk-Jt8XYc6gKi8EvgOo(Rj zw!+-Ka*5@Bp!QS6&ojwppQ;sZDAjF_V~5w}($pld^ZH&P<5I6#wB#~Nq4|E>FEa|) zn{?cP74}J{hMk5}ZvQ_52yiLdre|*e7B7EWn$unfF zZuC$GW`yGLtIgKvHpYz4<))z-N4+YC7{5$rvltl-D4xaIw+GAqcs zO9OZNm1m-M9GqOK!{$G3dpmLB%SrR+w@kwHZeRL2RG79|Vtr6z5yzQ7Y`1;xYoil- zLGLz?W{wiVerJI5B;lV%bHlx!4XTa=Tw|zWX$3ZCiCX z`wwI8YBHVacE{Vh8jQ@=0fpNt3<61WJ}pVIPy0li#S*}R(D=OZtts}R`RJ{Kzh1%l z0IQQ2D}&QbT|`U^mD)tFk#EM1q7up{^CwYU|jJUUu4!#i^N2 z%J*}A_>zD6-Kj6?qVN{bzjXWDmT#p%azQxq1i3KIl4FIvvNewlxhOVe;WATwZNXo= z)8P2#?I|OGv~aDngnP;L?Rt5p`u4KpEotYn-LJgBh2^}F&S!$weU&|p&+HJ40v+x#gc^>V@P{7fopW4 z@^@LtXxer0i*KG>cgVRzMV_*LkQJUoL#*7mEXQOwuJ`sKjFe6>3rkLa;<}x&6shq3Z?K zap@j}!?ip@LT~Y3K-6I2AQX#i_Kv$MElvE@@!8m+e?#cxR6(;&sd$1vAeO|1AKKk?}yuvqe>Bx@)y& zq|PV7Mv!z50Zm{}!f#Nth}QBRoWSRENi=PnsFpmv~PGoiZ`pR5$i^X-aJUXUfLf4NZDQ@SInvwUnyd$4islR z!zRy=kWfr($xB=Fx#@eFx%T&BH@65BXKB%Wnh65tr^R`^ZceGGZnA5jSON=a~?Rm*p&d)L4^QOCbe{V_Wz?rHUA1C(@kKdGIHO5cmoT^z# zyeHXWizHooUO|8d=qNFeE1jP>q}ilGEV0|I7;cGNW}Z%Drj?lenm7>6%fv(8brU&F zl)o_}lXYfMiAM}0Wk*x>Hlm+>B!BJOeVI>>!8)fa=y~3foTl6fRA=|jh8Z%{yO&y_ zb+yXd+y`=RT(dyCA7%mMbt=_F!0wA9l=r8GCj3YN+N^B;ebY_8IZl`jeOu!MYRhFN zQmi4Cw~uVW-CmCx29p}!g`&M{E&8}XhRtLKWA2h)5dtoJmbz~)B$LpSA!QX?#7wGM z&#sqkb2s3-kF9X$wPcAudf!D|*WF)?FHat+f1WRF_hgFg$;6w)4THXqI_W`MX-C=QH5mYL@KdKKE|qXTbQan*VlL+{5-@dj;h4mb zTV0d2DyZ#z6@ZAUi?3O5i7bALDoezl@~j3GYf6=yMmSl<>V8RiGaq$9nx_!b13r5> z4j32>#<)s$HAWZYqbBQ-b7|W4w2fgACl@E0Xo}(8zPJ6or0D+Gcrq`~m%)%%BGswJ z7tza^qoNi_JCmz`OaPw$-~j(>WDbx&xWUR$O%1a8FR=4Y0JF2%J`X0mV5b2W(svpk zqyfrZtff|Ey|nymGQA=)+!kWrOhBS}2C|lez*=w`2BGrW;VAPQ<){mYXEp|wXhJab zN!VV;*i&Q~&}&t89I?PzE&!c@EcHR8p5-nphtKCe8)gv?q@OrQdO{Y`A!beIKU#v5 zu6Ogg`*@)tSOXyBn;G1BOa;jxJwD~<=Lb%CT4Vttbi7FrDjKzlU7yuL;s%!=6a(M} z6b!Bc)&;vSeWkyazioHg`}p^3In8LEZk;l$v~n8F);Y0L~9<(FcU3R!`C% z-4FPI@0nst@)(uTDKHJPM1VC$C80ev7jj=xJ|Hf~p15GWzOD!VV9fom_s@%)Xl|pW zr!&e1ukL$Ts)>{65Q5(NQU9A{r}4T~$61@8TKi{{q<&5uvra7gCjG%MZ=h)~fBlj@ zJtbqJ??>+y*4awhieA?)-_08{y$1xiM%B`9F^?(WsID5=U0P2Jlxb!Xz4cLlzTLHA zmFp2z_)An&sF9adGZ*K>K|c|iM(U>x$7F49_cXqZ^ChDF9!@P<)5d@s^oMIw#C1YK ziFRvn^U}u^qX)!PFJIm|Z+(H|porsoIZmAB1QbxS#LDC$!zLlS(aTK($(G2UIVlR6 zA@-poUpdu4T9THX-D!LAU?{>WOkITD73^Vpn?=ADKaRMpbnMS+F#hpXpHqXVD# z=F$fHE*AYVZtm8sOC^S4rAhoLFYwzo7TwF^FlCfVut)B9+$FEzz?-Wjd*E5wxx&s{K$9X&xne8<1hI?H z;^N|6Y7agxS|lb0SfPgGLV$&({7u=LHyn_3$8=BU$?vnz?m{0|;Z%@=ZX)<t(QDR8lCwfpo`1! zKXw}W3FA{pkpa;AHOr^omBx_1IlZ}5fc5%iI%W-_mty(rvz!7jq9I1F;NQLX{zuRH zbcp(A1%^Ac`U1%Cs@)boo5Q!+0QOc4VPD-T0**z4D(JuJdj zai~of_*`vwHffS-JS1$6QmiDBu?C$KtRv&`vk7ZQ z?FyOwBH7*QtPIR;1&492dFO>1tq~+r@GH0sgBsKnR5*`#{}lcPW?%bwW`*({_G7)utpXtck9?gB%Xl|kU<+_wbd`fF3Kn|gu5v3O$zpjM^Jy8Y_!y_O-6kfTuMR)C- zmN@^q1b(#*u;v<2+gyarS%^pmK&`J>!U_wlt32Dk=~WLaV|?KaJ;nHy^jDofEic^C zq|_O(vRu&qY8_j(2GBlVbe#Ghi9pUS4g8Q&U_deKsoX?X>@9)6j4}4>3m|wHwbLq? z3Qd9ln)vAbcooc~RNF>`0b(OzIg5_0Y2n?7hed zcp3=UE6ZrLB5_Y*$O`^GS1P-why7^Hf$HCFmnU09WqsW8cNixd?2OX0hbKeN_<{RTczBfE79&kne$D^XP zQog>1eXwcSRQdkzy*C_`?(iN|lE^q4z-Fq~u<8i1EXkJp;FE@>DtF(?$TD27y$yjz ziCfV*<8Pjkjl@a1(K+?mlENo!+@o3H?jq)+m;G6Mp`02vO7mHL8mD8&uo3gy`P$q9 zUEY}#L9iYPCi*G(@9;gl2s+Fon)-)o(A@$Rh-YVO`d!d2vJx~7Fi^J3?A79I1hx@T zwjQ=;B53?7HPmx+FBjQk6NK%BdbswCW!)fU&7#&B5jj0@>VF~^$OZH-?5h;8Wi4N* z*tWVeIk-zGM!BQTFrpGr!*LW+n^k1r9-QCXa4LKKI%Jqf%Xv+}ITB70V2f#cuO&!x z;Q>RBLFGyK01oJ;IM+P(dI^`UaFJ_PS2Etexq>y;Ms ze@{6>WKRnhbb2gDiGp8OBZ!Pb75V-X(KT)`91q+Y1Bw!bQWS-oI?!$Y6IXnwKRgIQ zKxK~t%_aAg_)s82Ba+tSXR4?%=U1g{WW-@4Y41mM_V(Ns$Sr;rTkBmm`&CO8{Dw)F zb|CA)r;n&MRBIe`q^DRA(*xOp!_?0&zV92(5NPt^fY)yEo%7Ya0nwcZ+07oz!6pQ< zz8F+@7(-(iJE>LqQk{c=AmqT;f%fmQSBO&Nc{38DH-Ti^kVn_PxxL zcwQb<2Dw!^Sr?;5HV2LeW0~5;7uFXBt-X9@lT0CeEgcWm`Vo+v)KIHRJQVR)XoVcB zAj%NmEd^Y`I^^Q;s5{Hcy2_F$?wt1_yZ;UL-Vf%;f}(IsiYgKT#-7fihE^(Pmki9@ z$W5_QRtu)xF{g<8Hg5&-5}T0;OC96T`Nx5;#EN|N#vgtS)Y`#gn(`|MltFqQqQId+ z)#oPySoTNw2b==5MN-d>%o)I8x7=-Y-K!T*1IbM*u^kl^UNbo&`S677Kzw5SjRu5M zP(Ai3ub}eI%e7h1eT-ffubUbdD%gur=9lNHS3zE$08NQ)%r5&;m7Acc8*bE<4hNF+ z=W1+@h9G)5g679QiswazjOyiI0p><`I10lrfDv3_ciN6h-NT@B5L%~cK#ND=sW6r z%a4D~uj?Dc>_HT_-JJ)uI?OI(DE3A+HbDpj;y{|G-eth{z;qqh)da$!z^(w_kG?Ik z@X!I?~z894p92}t3_!hZ?XfZvwwrYK@KlpXY-&%m)Ht%dQ z9z0kmASCYvDh!p!Br5wxZi^Nb50=yD7BR*EEn)*7X!z?9z71D2H>9?2r2St+4_`DM=9Slx4}q$vKi5BK3@{rD4k% zRUQrdEXADWExhb%nNgH44OgdHk--xdW;`50e_)7kM_Uh+E7wJ_m9ra2{z~vYnzqW{ zx4rtu(EZH1(l(D&sMNjpnonW9YHXe&9=0`}6z7SrW~n11A$JmGhB&qJ-cA>a z{vX=j1DxtU{vVDMQf6g$jLecmA>+v2qh%AaGBPs5L1bhkLRpDYnb{#^Zy8z19+gct z$MgOk)qUUB??0~RdamcXx*Io#@tFYmDA5&Yqxik(8oYI3A?e#l?PJuzb`0}^Bq=U&U|@c+yOsRE z?8zn3r+qNS+LPn4KnDSzAi8x+8X7F(M#jwh-}DiY-MD}iGxRk7xrW(m9en7{dFDTU z`#fMd0Vjn_r%RC$&tLH8KK$e}Br_2gD=hx)j`7NiXNa7gL6Zv>?@`);IFGbA5D6l= zX&j*TX6<422c9^?h;7+nU9Y85vWmJI*V2-z_~CT*_%ifBO(g6|vH zpC!JtD8=f*>U;L;9=IG?CXUPz6(|_&fb}wtq#2guK#<;mNw5Ye{~SN7MGou;83bSM z0=}*KnwpsD*74E_^5Ocry65i89|Ow*&p+Pr1gz31puqX(UIjME51-(^UI_(AOdF|X zx@FgEcIQ*D+muzcE5}IcwpkKUi~P|v29~kYmk_a0M7#1KDPu6qmSO*b9(%crAE$q0 z&9j=!ka2(oe*)%PNaZzU<)dcpiJ_2Yod33gWELA@+p~EVur@cSf7omL7?O~EQ_2XI zk0M$K+;}`u4^p)C02v86pbn5IASLm!K0HoM9ROA2HC`YL$=)`HmymxtGs=Q}2ibfy zWN8IvOZs~0C{_LNU+nWV{Kv4X;U-0Wb1#qp-ows!nhl~4l1-9N!GgX;s z?y**Go68>j^BRdj%D)|De~XbWYf5zqqz5Q;WjhGd{lwy^m8QZKB2#P0*-h#AFg3fu z^4ka6F%^s-e?FB=xO(X7oc70Up(z_-Ka!wE1o*)CPS(sKop&-d{29G*jz_Qk@zyvh z8}1Xe1$#EU%v`j2ULxMFI_i-T@At38NAEmsNaBi7>z$KJj2|t2W!u<&L6@{{EbkZ} zOsh`Kr5~3{yQ9JRL@;gBYtpcOX}7St_;Kv*y3gf`rycxuuU!;pCY~EI@LwPlco1ST zy2dK6tSTErR~s7ofz^TO95XT68tv=+ZZq_v3ka#Dw2xAJa0$h^e8w-C*5HwiwTnSsz&$*&a)a6wK>NhLyB8-9Cb7!juut@iiSGWfkj_%@`(|iAi zaOBG@%4a_&4O@d7iP9RF$5>dfOMnxlvwsZv@hO%>R%2F{u?yyH46H(wCr?gY`v8p6 z7r-%%y=Pn1cD2lPfT+`5iI1VfA3CV&#%s z8mrZSMVTvV*+&+TY53sV?b~^Zi$#Ye=NAZ)#k%o0Nyp3i`2(K>U>JsKZ3mFX zQk}XMPD(ph#=Ctl*Z+~f4f~7TP1kNm5_{L%a&dC4Qw$uH(`jL)-iQCTF^duG(zhR) zfK7cBHVOUW->s#1cCc}P=DxtX?J+RrSV#YDInDg8B(mi=>(wAxLMn+_XnO9txY^yg z(7r#nb{<*+tn^aSO#1l1ogcL> z9z233Qb*ad21BtbP05QVC^ptzzVf|w5fk9i?DG&`Uw=T;{SJREA&`I1Ksc3cd0KVP zI4p1`V`E0Bj_0fY{pZoup9?kVbjgy0!%3+$=`|ACF1}oxoO&Q+KaVdy*Go=stZMW* zLxrZN{DSn3`LA%1C5--^k>}jC%K9+78kzoBoUWiP`m5G#(CvnH_t^*(RrH?!jMd5d z>dZ67o$dFRzV%4CzK}UIE96kuSF}HBJ`pcLtj;^&@3L{L=lOU+g==kSR|YcfLmX{7 z;sajm^Xc9n1gqshD=y^`y;<9IWh_LAKd621ob|%+-f|2eo<48xUVNiIairyAk-{B| z^XSgf_X1EfnV-i6>))lDDvYo1ZPlJjk45!IqS)BkOaL+&qPunJ+=IJC48BB5${#ba zrWisKpnEiAZDwRLPRR|3O5Wdpma|OIlc1CN4-li0+J*7&;2RonG*xgPS4i2_5<{TH zo_cu-Di;As28Y0g;J{3TL=!lrcYm%x$bO9A$O&!&!{(2m_vH#*DEsf9nFE0t;~$t4 z&O3JkLNCw~6i;XYk_HsI3@D{xS?qaV35UR2N}#`IzdWHJzbnD`X;IDD8Ut*Zqnbu% ztIk8DzThh|CbEfvX@IgLd_f%MkwrUnp46fJfizm7|L_Sg?<&JA?^r$c&%)eU+F}aSm<1;h#23LcokD9% zf?I$_DC{0pY(aWtkN46Fq>cm{xRJ;%hl6mGT!piA=2bZer~7SzcW{7fY#wd zn_x~2MO`jH*c(V78ETSDw>&9@RTHcLT6!BT^>wEt_7Ol6PNF+WAq!lGx(dC%DG()_ zy1OaV8y;{~z_j|NIJ)(4#%G?UP5P4Zl*#=No~-2EI}C{C=;#&1-T}JMX83n(OsOsl zd;d&H-#0TrbtQAvNhH4FH>3xylAhvzL)pP{#9r$(IROrrkh?~OAvdnRN=nC!l^Lo@ge|9RPa2L1(WKf2nLxP|h~aq{O;XZ$D+Ii$^4ZEewHe08C$m=*H@ zWQ+2NA?;t&6lH~{>4t}fyQ5-4U)}dAK8cVs46S#*_U)>+mrRw)mG3aJkxbNeF}{n% zROGs*^;^3y1UWtqeVg=QEvX%?i+bR4bFS_Qq3($aL#zS#n?=!I#Q9f9IQ4*t`XFh)8%=tc0&DyEWzWUpn}1>6L5 zRotVEye4k8JLnY0K;tNrzL%y{Y=HHOkVuAENd*rXLAkouvDYb zkHP_AH9Pc?9@6L#q4wCDq}ecy)4R2M?D6LGO!F_e__r7)m0O3EPRDgwHFUfl|a0$k`b zW(!?8Gxs>(5Pf#c+zBb18ls@A;}eQM4G~gEP}T9mW&A$Dq@=^-S&6R;+6|{r?#BMn z`%$t_4uR{GN4RIO4WaK>js8!(&P?_+gK@97+nzPG!k0Pd-oY51QCtAm>yocj!^cet zpC*NwB3jr0Z;WL6{IAYi6&R0_PnYrFk&yCab{#|QZjXDtzm9!3NIPx&R=0EKKA>k8 z*MciT$W;cadmVxr8_f>piPg6oRb#KwEPcCF)|-A#@npjTdX7Ec>Fo4}*%)*naF#x& z7;wROJLc_hVr=OlQ<6!A6GOr<2j>_+_d;<3?F5)dLD{`xs^(L7f>a1q0r^!in%r|D zA}T(lNOShm?xk#X0)@uPi&UCM19Qjh#rcRg9sSdScaQElpMlw5T_6rsP)5f*rvYp8q)np*df-nU#^|B zO5aiD#f0yAkB=sR2Hw}sTHx{JSaz}hn)^x(eTRDJ=>&s51#F})E6iV5lg)4Eotshn zb_&G+@7LQADawS`W@!;rnYaTNo;cd+tw|TzBYKp3RqH>Puwk_5G zA{?Wji(T6V{omM4$Y z2YAwrG(20O5wfD0HnN764V}Jf+yGQ8>$N6&Zf+YTFD!!lf39)%U?t0w2 ze^7u^|Gd-eRn78jz+4S2+#ox&BJKB!ShaLfRlfShtujCy=HK;5sq`1{!TDgOg9t>0 zgk(yte5F(?w!62izSqI0C0w$qXQd6yvbF3d!L@p=+(Z!VR~u+HN=+$TT% za@y9o$c-8*dwVIj308sdp_e@$`WYxd#Wu~aql~MV?c5NqQ9EZk<^Q4R0y5ghWzwSP z6wKgcKa>d6kp4)3SGK0@YmDSrg83)1ZX4Ml&;cwhWP}l2`0+E*bPRP_3m*U&{8;eB z_ef;2Kh&E6yy2VFb5;d8CNmzpTQ=^TujmfGqNh4eOaRk?&~)F!zKr{~`}U&)*{J@i z+A3!RiLHOSKkN`ddbGXk-{B&N5u7FfS5!$8*4tG(&4>5cDpyca`$vti8M0CP{tT^A ze;HnYkjkIc_^eT(1yxr(@eRxDbz;_gN3z&WJ4&xub~X6g1T1}e{xQ~GF6Cb6md-hw zSL5cqBX8YwBOXUtZ;@X~BHQkktG?jRG$x!`pIviC-)iJ%Q|$!?3E3)<+z-DV3&*|R z-Vg>j8g4L1s=Bs zQ_+vTrk8Hl<|s&|bTn(;=TWz?_kRB2+k=hJJO;a_J;l03{Y(^2Tar+2M@ zre|I(h?d?MLAY65e@zZR)__foEW6!atB3L0c<*jJLolpFV)!0dYIkwR$Xb}V zmIhs{m;Bw^K^{E$7L+X>G+Tbc_{jJ9^Ggu9BynYp@Bh|}@E3yT-fP?DIPN0%XNsmZ zh02dO^vm6Xe@RT3?KgbXjfQLK)4mBhvLZxAxvvJ6v-kI=#rhm~5McD`y|pc!tl>0z zW!>EVJO+etwvajW0BBrwnW)U_^@Goo0aQO#N-w@~=-~61`eOx3w;QHlCIn8yey=5z z^BN~v;>`6En~rj+Ju@5g?Zqkfv`8Vh(jTva8E-W$ZFzl1P;K+X-VZ%SC&iPMF!F6> zw<61b<1LcK{a`WY{53BiR^0Q#9D`c%b5!K`v%5viETTtQv(sN!xnSv%^<6>8Vl_r& zW>?Cae)v4)eqFVLv~2nQN1tbHLayDjf|1@t^;s&Ji44@viIHvYdms!T{i%z zpO74jNn^Jqqol*dR$GUs>WHf8SH%=HS+?d%7`-@L#b zdE9MT``I#8EcG04&yoY40*T<|c7Xzt{I&j9xb5EOlF#f26&B+~J}{KF3h&`LF-Sm| zEff!5`bPnxWSD}1`A_E$2h-`3fBPJhf6+s5*D1$me;jZ6AhMr; z*fk2FLD`of3`N&3W%dK+GbSMtt%zd=bfD|b3=dc{K4d_Y!!{xzW19}i8vT-s1@YF{ zvvHTvo+_q((?r!dcMD%wZdTEJF2gTT)_I`%<>mQer$alr(*zG&HW`uBX6Hi{bKJ{< z^s6n_#}gc20?mK7nYpZZQ4R2|;>*&0Y0+o&IR#H`|0IhKRH}@nzrzBsz@3@6%s1E1 z^4eNy4G_(pJ=KjbT;&bUpeD=2e90qiFX+ca16~yOW5Uq-b5aY3U-9M7Jr$yL!4rF( zRokp@^+JErctGl`(l+Eji=Q$I7CWJESPQ+I(P55b{xbPUAQ-8>=ue=yqcFacY$|;@V0=Ub!)g$XiDyb)7`V6_A&4bK<*d4e!~ZV zezwAz;Pt+l8+TEbOV!pJE5^RqUPM})>|jezF^u&9^fBAoj^1&u9R_%6|HYV#g%J2s zG%cw1bEX?e*+9|#AJ{TfJp>{m08ITX1+^bDpikXJe}u68TQ4LVH55o028jraYdx^j z1Ws$uAmdoE&qA4Jz?d1f!X48e?r^|46Q9p)l{0t0Z-qri9+>7e`O|6gRfC#zgx@^; z^et+gT7?QeBQVgi=3>?&6sk0+Ipyq1!bsR|oFz{8Z$;abBoiOkptuCGO zVk*td&;nK&(h4iO-+g7~%%j&OD~%s?)eG%TiFQsYuJoUI5B2s_9Bv zwo#p=l{f9TS#I;{C5^L+X~x!!5YTsOkwUZm?#2l9thm=XeR`Jeyq}{tA0K-=tW`oH z0`#8YVZEZ$i607sEk&Zpjx6})uAjSGx}VKBx>X^2j*$wE07nb&HRSM!P5`+V==r=d zoljpxC`2u=F()$N2c3gyK~hqI;jm=es6Z?(3xrc&mD*m_ydMi4CtmdPLT5B2_g%$$ zUhA}9Kc0N9$@%2Rf-bbqE0KhE?V2Ckc0TS)oc&~Oy8n2*ad*;d_Y3Y49K7Vzcb=Fa z42%@NKAH#8Nk4dF>ljFSC94|o1fF1tne;Brx#636)wZmLOO4}UzeMU-4X?bNpS>HC zA{W#1qxo?1@X=6`RjQI5MKU@=Jv`NvBXri zeIJr64L^`zlvd|WN+O`QBLs?1VMJ^DgiG?Hh-Ap8Tb1JSOxKBV^%m)uhL_*=U#d;< z88`Z+sepfDK^k3^ec~Tb=FaN<${EW6A)dTXr-DP}!g9__Pc=VfBQtE$mshUCM%o89 zB{f^yMz1|ySW6HM7~6C-TuxvN6Y`qJ2k0fQNLa?!fXfBa+U2R56T)3zZ38@2^Y=r9 zQ@2#wNE)-69d@+4}@nDlymOjhWAoNI#jXEnUR z?x(NTg=@Zk!HF&y{Ab+x3;>E147kl;uCOJI)hZDV9q@^zuw4V0*yA9L9k>(?q=Vta z-2OKwM!ERZn@DXDmm6gxs{7%IMJ#$LS1cNWcg0zZ`QS&`Mud|*&-PzkLr@NhyAz4Ql#q;IAI8JmdNS-O!7L8f;0j0MI4*FX17FvX8USol=P(ZkEQ*&@-nfOVA@|4| zbWlJ$;NXAYI0JI^GjYyje<#3@_Ierp`%pBuzq^ViutxsD0(f@)Gf+mdSl<6uHUCkT zq8Q@3@h&R6R{LB7!S^JlgXS)YEVrh52kCd{^G+~RACna5XLRBWjoFlQ{@A>H`b8Mr zrninkX-7M;9)s7~MsEGZvBD4D4S0;DWOr|Y$GFODKW9UrAK-P;Mk6mk8I;iIO()PE{fzPI?DpnIDmN+bVL z=fa(sHAvi<(=+7~?>mP5-`mOeU8N{hFasxXpDI$yF#&a>NQ~0j_Az~0|Iw&c(?`gt zbK}+}UBEQwtcB`Akzf)k{u|p%5De}u05qs1Hfyt-AzH~incI+p?g_eEGK~~qGfO9U z_Ngt431Dl(+Wy8!k^KGQdLIDB#L8PNPXAwkVG#6xGnT)Dzy7ZpMOjd9DcO~p zk&N?xX2|EbOE3Ml4(F~ywMiJX?)R7ERp^JE($`7jrS|jU7dRWWWs0@$?=yy6Miqu* z`S%;Xt-%nyZQbe{BZeWNl-Lei^2b9w_EyC&!~Xn5lF5Q+NMwiIk_`F{vy-nhfR79S z@F+s!JiO#^gmqoQ!i|uuCwuV>JLOR1xsRouHit~!MUHS^W*`}mg18W0I{ge~ ziHH!uU9{c1gGO6blz$MpEOZ%pBSiYR0zJE8|CW7kQ_-0>0+7`}b=x%HJP3f_x{W?k z`aSI0AAkQ@yaL=xw|mlNfKKn{^WfpY7&jGzC-JZBLlaLDW#_3-5|JWNcvH@9%=_d1 zMI!*3z|37c%N#*UA|4=10`ySoOrizPenNQ{Zac=4{*Y3Hx8Qz=UNh5k=tk^x#QAqe z^egM*aaNy73M52Z|5G{QQ`4T*ovO1uiI-c#_S-^vK9Nb9&XFa;<)b2?eowN0f6)m0 zsO7hcp2_V`Z(riL3dnoBtqhm2lojj}(CYtj?f;j^(EFbp7*6j0PThNVNii1l#_8BG zRy;$bX39?0I!BH$uE=U)``L*fOW47auO6{8n{A;8R1nW2=t%a zKev+*4SU9j2d?1HBQT5g%_!FWD+YK=v=vUfDqaR&_D^C{!0~WUYhakG(cKx$+5S1B z$u9@`0mHJ+@}Ok)~QxpRD`bSzd%6$AERPrX!h?ZqI{($KIDZSi~+jM9cbHC zYm3k`TwKJD=LF+|pC0wC5*|vA=;bwpmW3!PPl03LIe%-2j2aqap<9sADoD&{X7o}aNxep?@wjEh+j4s&c7eH7U5k)M8F+De7$sNS#_&p z8N%T<)K8$y;{q@X#41sY*>?xTq_^cObqVmF))Tzbzv~vT8Ige9XNJ!Gy7ZOB2LNMr zy;tyCZE@ipjqgL52si*yL`?>;NP=H-P+l8&pr22SNFBpKWekK`+CEztHY-d5_Ba`Y zW@0Z|MgQQP+}N7Qh7Aix8&+Nc3<@Rq?H(1(VLDf1fF=WNL=zy$1O^9JdE*`YoMTko zeF22$>tLJ45)4Pjp~YDLgiQ%VT4g|Wj1YvERC^6%$5-1~e^?!ALOb`pP7VbOq6xsr zzXPGGu*II6I*H$$6t+7AAgW=_250=)!l!fUe*Sduf>1AZm~Ss9fK`_nj}Wn#2aEj|5r*O;XmE!MqRMWbai30fJUr0YnLE7S<$R9fWzmNQ?D+%_pyu>7Ex3~7t zYlSVYClg)Ah^1v^4LaXsPXc88A;3!x0x(Mw{8rg-B#m(Le9(CM`sD~6$Uzh|3r@+}TPbVsV5>LIk5{o=lmUfo_*p@Me6 z^Rg?tgNjV*!pbiOoYGMqw7^#v4in`G&(8p>3d28j8y&na<$* zG@0Ok$%^^-7NCb>=uVv5`zTr}y!Mz4Ok1kQNgy8VlAd7pWa8tCuVI&o*h4Z@7-o0I z&~@^u=FtX#C?A47xwTScVtxQrF#$bnfC3!>oMr@RK5%qSOG`_LZ+&Myt5;0Up&Bm% znk>q?EQ5{+#)m$bnl}vKsbc`1mIQ2I*D9N_aJ%TrQQ0ttAlQ{mtJ|Axn8Y1?0f0Ue zZ3*JC3$wtk065oR`5Nl64=auNIOgQIz&vI94Dwqb=h zP!syCNe-D>*-@W}zBoxa3{V7#n`NLGgCJ-1oW9)nA)S#b3@gBwy!V_6(C9_XZM>|; z9o92BR z=DX9)iBRau-s}xnWnYngGjZs?8&?HDr|f?E+Z1i-LK=f3l->Fdp^Wnvu*$w;;$;2H z1%>hF2x93i*FQt?<8}OU`Evc$DLJgV31cY{RA-M;+ni%jKYl&RzR4$OY&&6<96d#|4hHLQMtN(lK@bz0%&T^?MBK$&-HIG7XoZXwszOn(ESD4 zX;EN+AyTm?d32)Mw9BI(1qFQqeDaYGdlg!)X`oLo+jkq5V!WOM3`&ZG4?d`ODU_5e zMsfP|8e+a>Jbfx9t#SL<3!>QujgRh52yym%Z#F6sgWbITJ__783$kSZFlZO(v-K$~ zkQ>~rC^E*c9WH!#NOYWv!s5xr=-k6Ah)5*Neu6+3Pjt2WTJausn}V8}aOrJ^+(}Gh zMUvN+B~TXd5WWx4`_1W8`i+{r6zk2vNmA9!; z0E@XGj@>XZ$?HSpmQB*TW29yySF^avepAH&g`?K0WB)@6n*~Ha?QL$W3Ypm^l>nTY zZ1Sa(|LeqRdb_}9vBdXMFE)}RO@xk2{T7W%4zv}s|0YskatNGdSjb-@L;Y zyV8rglK~k8uEYa9#x1MRq=5k#*3Zz192?&$h-ClzJDtZH{+-U3Z$=;m`#D^t{ZbOq zNH`Dq1TIS-6l(p3{j%TM6_A!Q<0Wn;dCX$40=M8<3_Ywe3vOtTUm42{{Fcg49t{`* z!`4R`ES0Rhp-m(SoniUgU25pjAdbZIAO+Jv@^YE-ZJEULwL=|%KYm6X4tm8_O^uJ@ z5d1$veiFQE(2^)dkE8?h#p7YhUn3o!eLSA|36=k8m*A>F2CM=&0_m;^@2M$Rw(n!7(8b(i(bN>b>MEc6{i)pfl26 zFvQHQKHSZOhEo!k?ytW^+Z?VMIVNuIRnz+FVokE9^)1`umlJ!SjSVfsc}u{YiA~sU zBUI3ef%f)+?7OORgpRCJva0njdbiBMicj_Ke=aW2NSUAOyhk(o@IX#+yx7I^ zs#j6vKFop;2^N`4S^x=7F|f&$rOROXkL&!0X*<}CbB|)o>O+(wi|f^F12NWtt8dhq zSL|UidZ>DEU>f)xLYAp6&?CLK8~DN=!A|8U02-^sg{6emN>k>zOiyhv4`y#y5ZR8? z{q9C{{(bC@Tb`LFfu^)5`(&|enX|v=iye~~Pi#JGybJ6_0sA@z&}>0pVMNn1!DWb8 zyZWpzU+&NbP<#YuF4mHbr2}ThXlG+7R@jkUd}HKbUe+c;4W++ZW;75X)bNZyjY9iL zK^qX>1;#GXX6_!xi$el_hNyFv@jdfQQ7I7FjP+(gFIEVaR!3#cfh$S|4=+n3XFLrG(VqZ@)cU*&mMAdfuIbD+ zC5RLS8EU8V?45#TNnB^}W#D3p*_QlZ=P%3;XgNs1Oli+Vu}Cq>^~?l9N$~M?W!Am}ip`Coy7A0jc ze}b9Z&4+(-u|Utk;Q}QURjX1o2=Vgs>Ldai60{I}tw(^r!Ur6kdVw>%S;0bKWo6vF z>8}}six6!>=5$ z;QwiKitIEGHZXckaQ=cyC3RSi>*NLKcb@rAzwsVuzk(RwZ84*J_wp%bxj^>vrwi;)wP|a-tV|(Ksgb%i_m!2I z8LwS@`|Lb$N?E;6ONt=5U+5|zYYq#vP+s)~%-U1zO?Y6-`9DWWMN=q#0bETE;U<3g&2NuYDCOXrIgt=%Q-RD@go6@sW2s6b>Lgu&}K@7sR1C6SdD3pK& zc~caZNsXtpx?GK~bg_c5DuG>U{4eH!5bYiVNK1!9G^c#>*Za+rl3_^!?4`zs1xcoU z&eD2m;8!1i;Ii2P|H}yt&>$s>$^H_jGh<_8Oz+PHWVA;MMe7v_ZCR>^&bBnClF*~YunflV;NS~m@(v9;TEaDl1sQ!V@AcQhDEC@v zo#lOz7PPfo(_sasK0pxh_SN(o>LA9~rF#z2PwJS&xA^_)01rD2*tOon2v(NsQjc+A~0Z#A-S4zbhErT$6R%J`N?-+_q5iJ z8M=LP1%5Dq0NGhsSzDX4mVJLs4uGP{>+dVkJYZ!7HeY*)v>Th7x#YlpJMzrcSmke& zxh{2UscjngrBA%t6&&{HBGP=yqk9tQKG1YJAQ=d4Z}ok}#NRc(sn^7*y$fiEa9&}` zkC24^SZ%G1hF~}adXE3?om5w}gbt3%g-N1;`?uv8Wz3A&+kPE|Q-1ne$Bq~yrG2qh zpscj4^a1G_#R+7UW9xt>MUBG!4V`nZ+G^WnV8@#e2v$fq6a%R>&KQ;+Uy@{pohb@) ziZu!od7!yXH%cjdTP1@iJWxN^b`hAM^%r~fOv-`I@QxzjM{jnLwi>5rsu*Y4_}9TF zPO&T!kvA9R@4WW#f^96v&)jSgV1LP%8?9YqS;LzX-!tT8X4q#)0 z&Lkj^1iK27Se~#>N{6s#@|t@7l0$jk_A^gKQs(`3mt8=z`Tea=z5vqGp5~c@I{0kC9&9^$p_U|$gmZOBio41)4q{;A zq)HM3vwmOZvS7fvmS_9zvPnHG0MP(Ix9Ca>zbw-mNUx_>0FExIG@9^eZX1}0*Zf^l zccDy)bINsS7}VZp>ybD{J3K!iNa`?fa5jnHEEV+0n~D0p4(9cXszN& z$I4Dx4TZq|kR}9ad*g6*Cyepb92F!eaJagoP|(@NzqUBpaoh>IP0ugKxHd7_dW@Hr z@_H#~8E`V5zjXlEEl&pm%j~##?IC}@xXN$X{HowII67d3@szkMgh6KCSSEkAykztt zeGADegl-0DopaFAvhgoqt?)TcDX-`Qy$o?vKbw?tSY<=b^SWcv_s#JaCS$RC>0C0* zw8~1fY1pVqV;oaeRzgNg=1UINE2kV=+KE00yNg=sk4?K8a*>N-kUIoDW z4-yWBi>(3e;8V$)kyBT9$RChDZ0tYR81B$hpnUcK{)8N!MCzju;>RdAIXSP1gcBE; zVNZxdigKgOaM)@!%5j+$vtyC)m7pQq;cG>YdHvQ@tJH+-kdOumz8Ab|lZ{6Ieh(QY zBXeQTWV{Hy*GY#@?tOrO=>Q(laSn3WoERJ&50qC2xPl^7h(2KvnbYz_qm1&h4)2#Y zsba6Wn_h0OUaygE{-S9(x7K&^)C{l?o_t&zP)YhfsfIQ|%;sxb|%Qr4r_o?6VfDY9+ zgAS;&hUX$~Y9w7}c%$Cd5W$+T@gy2VaRTS_%7G9SFM(u&X8}v-aQ}6hTGBaSfCJC` z(ub0g^?We?LscU1Bb6|?e@~W~O21!`T{?f6qb!Bx26qlW2rOvuah*5-dF@jwwyOqo z6-}zm@Pq#t1JcLvJr)@`275mfmz|~h7;bopl>b~;MS5iiQYV5-68IoOE!E~sE3o6@MZ|p zv@5(EMJ`t*F%k~Dn(OVb8lA|o1de6~T>I-DlIR0p$WZ9*S^kc2(QCCPA@ zQ>oIjP>!79INkYLg?@eX=Rq>jks3Kg?yW2NYssj(J00&!@BLsI_E-r;`ZDt-8oT=F z+vn2JelBJ8hc7;Wjj5tGV7~JvlOprQQts>XJh0q45=e5TR`Z}@;D}P5sc+p%c7|r- zpxn?F|L%7kG=497rhaV;4-2Kan!;*2%KF+4RZh8=a#E>8N&ej*n3v3}w{|y!cQ{Yi zcV%*b5hb42;5v=p(_8fTb%I9GgdD9=3I#!vjQ8Lhy<+1`YtLOK^fL9S2|4UW#{dcs zXH#?G-m9yv!?V6tDz?WNl-zwKEWyZfIm9!vQ9UcHGuwN88wp~)EYXs`PyI0?K^0{p z=Q(M9;11GWzW0VoWD~q}!kc=K;z(#{fJng%U4{=-O-nkwGDToouyyqPjGBzR0Q$3- zR|%Q=`qGb6{jiO%)UyakZ%*4<=56s@d1kE+zTD9NPX4<6JSK)CTy!~*ek!M$)RE;3 zx9k&c?o%ndsjy3CXnYrn!${c2)C5&7!*-l)i2`_{(H;9rJi-R3*0>-Sh#U_SQN7MI z-DeW+wEa?va;QVA1Q<-F(Bf+I7F+9+x>_Z|?H6ggkak4km7j}ZuHL#*x=jh{`nsA` zhgg3AFrmtz1p*ZLq=szFt`)6_bH=aP{;vK^&v$l_!6Trr(^0Fp^iooi+c?D@GZ7(I z65H(z?aYuzyeHf`lRiR5KW|Q_Ntai#x4WP(`fgDrO*O%JrU=-Kws+S-IBEjwMJE<9 zZn}HW9Yq$dG8oRmF$f5>VPs^-ln4;Hp>eX=ZzU8TnKpv!W-7DLfnp9;@mBkX=we@0`M z!Y_a-5iOsw-*(>;bbu*Ow0?jFoDBNv7c$K1!Oq;DZwt2U-yB(p2Vc7k4WdV0ez4}A zd;L8kL@|ll+X%Q00(G~Hx0a@%rkxYNFbCPs_KRPsk*W@>tJRvjZ*HKU+$beQ@7N)M zXaZ=gCr_MG=rsgysJBP7KtPZFHI?9fME(b1-`4wFua@Bo45lM|a`l1ji7y?+LxnbR zRO1SsOqsnQ1|r;rDX6I#r`EA z0S;fqms1AUcsDiXH)3`;T|636*HoM%T9z*!)q zI@9hq8l)xjPB-2&o}lS?2?{w&^{XsP`rQK!pRz=WEKhhRCH7IaY19M4m$L`nj**Ey{eiq`Y&!VEhZfDtq` zCSh|KnuGc$zUd;mF4Q$&4pHkdK^>Pvf9;Utb#=LBuC(~EwWy!iA7iG_uAP7WxlbdR zA=L9kHL?yl!?41@ws`a~M{WwHXZPKmiuvY50m~=jOYroXp+2k7t>8ZXs!B%ZUd5X5 zbo}^Qcm;eH)$fY_ZuiJCf(>7}dT#wlli5-rUO1~;*s^5X^*WS_qq7ravif1`!?(m` zJ?@@t-WgchptW)5eZwBlxjP1#uUQJ>o?oP`W;)>W5{fE0=^BN~t!?}A>`?y8k_>SV z$fW$N(yX!!ManBFaRE)96t8EPVfA8ryYSDxmtA99Dt>X#PU9Xn4Qu6=RSWZ#XVoi? z>ZeuKt5>ZC#Apa{5mH!j-vB2qM)-J$q;Y}napv#_Yv3_MDLcF8ydDB;3f>R8Jn#jE z2Dbt~O&3X^pyG+22>q;}&A5Ro5H%rs-oNrzQSDt6tlkv=A@XdF%(U(;hOVsl7UAwkmD(k$R zkp!ArmP2J9w)2A51uQ;GAPPUi(Eo2Xxp_tZ1n1S)CFVDt{VcaV@uOpA4l1K4*aur| zXu726^k%i2+TAAABrYQPA)R(bIfU$odO=)}O(Bx|hGD`2&96;%cZCSkOtmH4F2+0{ z`}m~kr+ZJf;c|EeDQ`#sk|6sC5M*Rm2%VFHAYe{(uJk!1NN!$jS*}$&xC^@|Te-D0 zV{@{5AQTd_Afh8Py}ZbuQtZTVGnwoOG8TA^_HK+xJ|UK3I*$+A%C1=M)}hV3%0cE? zFh_!@gq2j4Z=(*W<)2VG#MlSYhk`ZK(Vknm9jv^R-NK|75NjWz_0#RX~c&J)?I z<-2RT>&dUyVk|qzBxZj3I()TEHi*!Sb1QH#iu zn1g)p?hcnP!xfT;5ddNtxuDRmoqIco>d{MSsmiTYQ_nq10R!XXa9ly)Bx0nqzdp-b zT_1OrH&S>(AhdB_J^rT1_=r?`;-_=_XAZC0!)!dGw>d^Nve}>G=*u5EJw2k;NriJ1 zeUK67F&dt3qD)Mp8ns1!GyD5A&}iLZ!zyfgjs68?8hjrL30IC|iZg;^rDuKb{nFPYjO8&OEuzIy+p~SN$C0nCiQnVTQP#$) zIRk32i63FO^$N6lt^x&IB1EUuIa3tGQkL697UW^kXDV!}av1R;JlrXm;L2LLIvGk; zfV=}~P{qV(P??ui5oA=LG<6O4+R5f)qM=b^SFy1^J9XZ5caFu7=bQsySpWST0rW>6 zZ0mcWB9D3=54$h*&E=27#kXNus@^W^MBm|nQuNA#9n-1ebH!wKC{8cbMwi$%)1Q9H z(J?}aNg(VtvMDUQ`C>ATq`h5~dP%Rwkz$i#-A8%;peTC9OC)T46aL(>9{uoq+PtS# zxkm+?kXVZVD z0fp9?zJ!M)9@(q?@5{B<$=8ran4{pmTAgH=@lO@}o*XW~$=Rbvz*sBK;Q4WsJqb=b zg$)W2Nnw9*pjY$4PRm*513t5WJ*t>p(LSeby}MhSE9=TZ=>JfhhNU%qg8D<00oo{E ze(p8V0#g}PGf(uvMInjj2UCTKBDW8$`z#L^|wFo|xq3C7UB~pNwwZJ)vGd z+$qv2Pju#W-I-Olp{eqS+3mya?L^IT*hG*ooww&BcDpjzUTaWq&ZxY0xnX6I+V=&y zuO!itr@Xv{J3H2%7~<{hGVug;NL4vik=l;W=&x((wU*8vF1yiVGho~!eze&JzlTgU z&ue$e`JNwIgtVMEiH{X7C3%Akq-)_n9H|2*saTKr@=5P0=eQ=7$}6`K@_0jD+prpQub3aKN;|cMq=pP^1blTVIzIvUnD-{ z@O-IQ^hzFrLmf#vP(_8Meb|2xOB{L5lOw@O&&JdDtbj(@70R|L8VxXe12m>ZglyT3(hMA27pHp9ZaEny2Huc8FGyhxqo3K zxa>FY0sQEXuOnVm^zOgC=mFGxoo~Vw<^y;l5Ghc(=R}R_BbT&%F=L}`ZyK3y?QZ3k zgUqmEEE@q6IU(NAUhS0xzH7+Y)L#=;^j=RB{;Bo)(C?oZz6bdcI0dOsifMn!6gV&V zyyW9x6|1a%lBv`I}{pSQ8eDZ zO82F2>Haul#kDeukr9Er@wo-j`)3rH=~Zuw{NNu{ktYr*$0^i=+~hj>rpD&8S5{ZN zpj>_=roEq{Y#oooeP@Z9-=8Y=_fsQKhw9J9x#!~Y6Vu4-_a~4C{Der$5DkGB=PpGQ zH2ED{Y4JUZI0Am5T|DmMX>y5E5+5lj54tx!zMjtA}FyR3e@sge9#{S?AilPcQG{$$E$(r=U+IO#og z{y4i;o*^u~1T}(L(n)~ag z2{}{#ru@U38m}jEQy4cjoHvNiz(paFc;Nx2*HoYGYJ>DB&gQeDkF=A(O?+SQnSV3l z5bv#nxaUlu;tifq1J|RNYp=+E2wkKNJHdT#*T;NoclVLgD6vRlNIm5a6`6~y%3-{j z-k8IS z=WPnhF*fJp9y!=+V4U~rF57HHu!tlL3cp_oaaHAcUgO!q9*G%r{XNzb_Br<&vlLvk zbUxNyJVMuPm2VhP*F3B{jy2GGA@|inU)t5#+-7$~AdlKMn#OFm`pgXB@a>>DJ}Ts{ zj1lE{Z8!=;@b$7UU`Gc(6y4Mih##l(-{CyB9I}7NMwHOok%L4TRZ*-HiIR~@+~K5a zvk#T@I!^6Y=a2j_KIf56X^PeV+B}I|*w1V|L6iT*jT0|aGA8Y6P%&R=)t z;{blA!f#h|(vJNG#2*#{*w{-&z8@j=H60WPU%$0eElqSv&H1jXo!1l>zwmBC#XuwV z4%|EP5iV)J2VG6Ur@c$i3}<;;(ngaWxmho5Rp1lsms2d<_~|w&_>jAI03VASlZnNDj;TuI{Vj7`8fZZL zW|0*3GFZwOlI=;i$5TW~dC!~?E;oA(PYNMe8#-^E>CGs>dWrVB@}vG5+5Xr5_c{IB zDB<}$w)ve(7s?!8ldL4gIjw&gVS+rjS>sl#uIabeJZ5u`nsUYnUz@TNMz-~gv50b) zr@YeVEw!gO;j-W3Oj*8`6;}O8@WW5-Da3s;>HUs_@-b^X)&3(XE;H?#6LKQ)a9XSl zo&s#>WxVj$<-}Ts!yamnN%!68airA&2W88FW`WM(6)fDW{m7V+eHUvcXNCLeh#o=Q ztakUhjXSaG5{K{9_{>N&k}kf7#Hva(q#Zl_NAn1GsMz(lR4?PKaGg*g95qFTN8|^B z2AI+JucmXMCPN#n9esm`dkp`Fqx#oblEW5Nqyv27_4*Hb{0}OK=|L?xUUVv&A)ftZ zm5^JcrjX_#uHtKh>;Cl}NpBCRb=JB-0(^77QI@&d+?V0`yJ@9eZ%l(2iXYNu{T&pa z+wq!;doHssr?jNhW0aOz_m1+cOL*Px7wJ2Ud!Nt1@|vRboby@Xkb?QIHinO-qOg77 zuKqN!0~3+NdFnrzF(eEm&WFJ+XTcCK4JC_yjB0fz#J>}>eNP_|!f6x@w~7IZ25Lg5 zI%SF+tQUjYzq}L9q5lqgNZ#;2{#v8-yKzU43L*8eW=9%#3%JHzNh+Ly)=>^_=6IpH ze7XQr#`sGPu!UeX8^Z&=wqVmEL<<|$w)?JVBSr1s&w6W%KHAXtXUvX83?~;fwKOI_ zqLx_~GIHO)Ufs@HUFD(o3~NQ+%@c_~p_6q&ZbHt!j&oBZ{#P~X>(*}n*4D}L02(~p zyS~7D%X2rU9IJ!0zZR6UjtodJnL#aCGkf46Bu!)N(C2Q4(rXa==)T7eRS_W#I)+#a zSMh#epl>+_E;t6dpktFszdAC(OO;?^(f{^8e;oDyJRsx*|99^D?-XJAiHF^QVYZiM zH1Fo8&DRSX>{qkxLmPHd$A50<+|y%32eEzoWXWBO$kbVOzlk`joqPI{Ku`8?U!^NU z-+nu0De&1H+yf%#!<(6@|Mh_+RcSk#;@XmU{Oa@>xL>Rb9Sj~S%PAwzbbdzQ8Xkvm zXXDEPbyfIj#QPf+PVI$PrOssDU1mixm6Mwqh3=%K`!Q1hNxsIaalMZ172Zr{auRSM z#-G|=K=s9l-!ojHMV**4-spPv6MD(cyc=q1FHh}Z%=7>MZdosbxZ2H7YpY9ZPuz3v z{roI&;-h76o@(k#5&s9tIgAR&hq=7$bQJC6XhGS}>jKO1`=8+rX z4((Rtftdhw1vut$>YG-}DLD<%(8WkuUTP{Fn2_tA;+w^VI6PiR9Lm%c4Fixsm;>(0 zU#8Gw_X+B0xMtxOP+7PXZp4eb7_Q84s{U8EF8?pA@|(ho)QvN|@dGL{mxn6ur!#Gx zYdu-_?FwPrWN2UKObodul(N2u9(#TqMPbB2Ixy~gHVoJ{XE>QNBkLZ`oRK?tf9YVU zT83p3Ljq)_61I?77}{=K)HO5R-E}Rrt5N-b5f;*ahlMQJQ!Bm_8%Ut6?%N7YdeBu> zA=D%yIW*zX7*|#mG^a&gs=zqblcmXM*_&?bmKOPC^%`N?iIam0Dxl^on|r(QNk$^7 z?kjucC&9wq2TsVrW>DVRU7V&xf?<^f1jEB$TDDH3n?mR_eW4mXc~s8b2MJ9ANMIFJ zMFJ}g1lFn-&$W{#y*;Utt1)yLuL4f-2S6TurVZVHuz?itJL{n`?GooXZWxYo^KqB;!~snM(QeBH z4aN^JSecA>Y>R-wY`T0B>|KcXe|UTEc&z*WZ#+&4smKo5BV?A5aZqMfqO6QUGRi6| z*&1Z8lu?8vdqf!#nGp>$WDD7{{a)|mP-o|TUEk}z?#JW4fB#(H^IJ#8`}6+1)^oi6 z+Al5(M(~9_dO+8U1%gfkc*dpz(Xl;UlgX;L#6hkcXdv3zW%H-qfC_02eQX1_1!+u3B3Lh$yu?mxqa&h*I}c@p$tKZk75KQ6o}Jk0jPp3dd!ekqk`VZ ztWOQ?m(DdvYc*mv>V8>hofyW{<+yJ({HPcF$GlSe3l_}jhjnr2J@PRH@M3jH8C{GI za4~#2zvp1kWx6sQigKQ3A!>>LZC|*@qz?K*mxDwn`F6`aU=;K-)i*G$mJn@`+pCGz zjL!pgtP#X3!yt8u1r+9WX*rNW+hK9QM1N)F#&_Myh>Bb5jX(fR1xlm>>sy<0aOzVA zF3Jda@`eLpvL2voKAH>mJ{zm8Ui{;lwVL2p%JpQeA!H~;}YM6 zRwzmgIiRth&qMZa3DtMfmKgixzaP)O_)fYhc7xfge9g!*^mtFTf1zgd>3o-wz#l_Z zu+3yj^b@e~-ZE4)SJ>=N;>uJkK3Z^?>NMovx0wLW%2|KPbz));!Ig)SsE2hYrRC7( zB7MLA!R{<}O#U}Yl*;JCXc|+^+WdinU1E7t)d98lhXev&vLHFK%X^xc^WmXAaE{Z zxV6|e4|}re6=;lxm%q47etExn6Kwr0X3BrYz}5ogSF5{kFzxTg$aPUEV!rQLsw!xe z;RUf-B*2~qU?R=KL+R9Ql`fH&mS$D_{;Vj6O4YDYaWABvydbBRI9)bX!I2} z#+<`~0fTqhhQm(n4+jvU5kgYngALe2-ta)kY#2zu4wp`Mo4_qgNII^lnxAqqfCP=A z#U=Z|KzHK96F+*@p-g+Nsx-{zgr?*8lRGD>(g1CHRa&;SHrWm`ke4PE=Q7W03NTN5w_elQ2PVLmI+zY1h@Nd}RtISSJ^0WpKg zeLu0`MABXwM(wfZ_pLj!r|;>~ZWVt>Uz!ZsOs7*MURAn1aAh~le_XdM>u6L8Yu481 z*uiz^7mwqQYW{!vPgPtFj845$MNMQ*gKdqP&^+e>$HYLPoaNMta<);X z_S~xdz(l~x47ZtF_VV4rU$?mr-R9@$HtSVWRBle&r9wghst(FbAgO4yh&37=bqDmh z^wz|YC-FM2u310Zl$aVS)_+Xas25&<9cUUg!x`y`5g_C{4RA!lnHC@&*mXg0yax7X zqPAf`>XE3Tpt{zUoGb~C`o^UrJR8YS6)knle=)RcaHwM(d#9b3LXMWoQzd$E zVc+)_h8Qp3e1Uq;s7WyLCp=|;^A{Ij7?!xif^DpL6&uwTtIpgfQ$VZD2hwgm_}~o( z@@Cs#66JaRlvM=~=j8iL7-L8%B3w^_+|Kl}Rq57o^Lnz0PZ=NKhig2iU`fq?VVvM5 zFa*5Bh2mjI6tYWB=CjOIBK$%|&ehZ=K@FLf+Mn zx222#gh)X(9EVB-Naz}RC|qldTdvNJ{Ft$R`K25#`N&j3YYYe<``;JYYkfi4o3TeCdGmK~BQB7)He1b6S(Ut^W^@o(3)SOi7kXCMZl+Tj1x5ljHL#gcL zW!ey{V9T;CIC+TF4*-p_?S<2U?iT?U@|3zS@saDl?>M|+2#1^9z81)U;75rV6%X&` z{$eiWbInIo$3^*lJbNcXpX#g1>QIhuB`*YS)AJ16 ze&f2Lt#8pwes*P!X@{{th&;4@YeKhv;7_zySQMUV6ncpIj1)(45#Q&~_%p+hh|HFp z6y}(C=C`rQ#HSujK2BlFyfay=+bNHgclR9F_M+s|v?&Lyb%AIZ+i=Q~p~<*bA$9`3 zwV`T5RCChXFUJg84M}1$FMnS=)NS~4NNHWTJNnpfe*}nIJBS0WfT|^gLhwk1*jJnK zD4TQK*hGEju)DIc*X9igDDf;H;Ld{`wzwQM726RNSEYa(At%0b1Lrf9!n7^fmuqcC zt?pE8qedWsSrr{ zHGriwCU2!PrD&$C;H5*!mB7feoZ@?88|W24w*#0sepg6$=E*Y#&vkS7x#I18B)(i zuM>FoWmriroV1u}KE>3{U@{n1$tmUT!L`E7s1;^Kg{2$}LP-M6b2r172%A0{*bOUv zk@A&BT?jK70iw21{-X^U7?{+{6&csSuVH$!%SV_PcR3Ynm2_z3Yjq0M9*MpuPUJH# zE4dqNJZY5z;tcV*2oSpJq-iAZ)G;{Sr#J+4eFeoJ$aO*>sP!#jU~LF)>1jboqNSm`U+@O7v8k(efE2i<_1IVc2+;9gL4YB4=KqyD_`O4~`)7(u~O zfK2;0^_xHtF%m4+XTN>E(-K_mqwg?zb`rNf>x>}jdlq``1Kd~e$aXlXhO~p>rBCIb zE;2!>!0YDb1}TQ;OUh=JarrCL4Opbu6hLFl9c$zqhbo+FiMqyasT)9NaB$Ma%K+w4 zlh^jTN!#*F#p|J)F`z(g1hFE&Sg*Yw1<~ny`;EXXJe6dI@}(Ud2z=#kFO@Q~z9S+R zGEC!O^+Qa#-&L|%c^LEU5ET1E?;N}O3fHm!cb)L8GH#CLcAfn*v>jiG08r)R2Cz#) z{ODGugq>prOozkIU+3bu9+_VV1Db>BDJ&^!7HM5I@@4HogyEf!Ka_ zA31Ks7)NHhS?}MSEeey96~eZM8{9;J-xeVv?$>o+f@bzTJ(o4EN1N&Jy$^5~v(@D< zG^<*ddy}sycv2Etyi->4-niR|i=Mwn%l`l*#8*xLAn_`4*PIf!>|-^2E#jGHo%O&h zw<~mo@-9NT(DP)|58eaJI^aes16&{#lEt4TyP48Lbh!wg_Q$}FbBCL9^>Rx zAoQNqQycz+oGgK=yv!6c6UPP)jb^3Vn@}1^qJ39VD+7MXgPagto>E*slEGZ2efe3m zz8s-7bUYI^!G*AA=Txz6*Pwi~j_Vj~b#Wd%jW%9gpX$^)_EWnONDW3PtO=CO`jQ@& z(gR95ClHg1CR2dd-xB(L9oWp2ok(Zc;R&oV7glx9yYbDsoRCB;BCeW6$hoTE?K}*X zZv@!1s{+(-4n=0RT@lLCa7_>$nc_BVS;R4;Z|K;L!GabixS5KnLeQ&S0Od?D1(uY& zF&|rb{iM{Pbhy4w)62b1nlyLpDpu2^=4vP$Ysi)EzWWMgi3zMW3M8c}*4)m<*GB34 z(%A{u=!sm0XJlVC1@?R}|4He()pTw9qBMcL|4%P}xp)LLhDPqgRd%=*_%BPA9(TFvwtvdhc&_6<^%NM05r??HWmb&a7D; z4G3^KyG>oZ84%!ky*Y~ZjEd-;tEy3(h8XepYfJR0IgSo|`zsn0a)i0i`|<4$+Ap2V zT~ejoEe{1xdC#beSN62u-{2;@e?5W)s!hY&ACu{@!QEdClD`~m1KCv{3b|S#tP-Ls zj`lk_`KYq14Tt_#v!c0cB9lIL?wXs;uofVD7Z@ogV03T9i8q*|Ca>cy3zcbYz!gr7 zcy4f^h=wjexng5pm1|kZW5u3JX8s+$T|J~Yei~!fC$3rPzC0jVy+={2LPaI5RTW>Y z5TFjr1NnaTI)$!VQnh9ny)G`a?K+Dh8HgfNn&Tz3VOldnV50NE683~d?2vzLoH{Sw zkszm~I-(K{(*C-oNFJ!(D`RwKM^P^XZ$#VbGF+V^SVDE49~Km_!oBfGsXm5cD6AJY z1Xd9j43=WS!=ec>ZlP+v;dwrfj;ayZR&L~Nsns#C`LAo$>y0-ap-@Ci2r9Sn_Am_8 zr06_WQLq=i*2}G(rq*?J9^Sbd$(Td^Ae??mVcU))`mzNZZpH-?PP88L{4@$%iGK3` z(BXcjov!hEDbx29Y+!R(&_Qq0ZV08;vl8|Bi|0Bx%SH_>ib+hvMP%Z@%YTH z>}PKPo9H?h-eBk%K|mI^p5*#-=@6lgNfS70KBp^sR;!CZru4XsD7Ss`WH5~$l>5nEL zO)Ub|u+2;5_9Rr15pElMRLBJw>unJ?Rxqz?RXqZ(sv+vNJEnipRe=;(1wo!Rbj>J; zK&j!o&m`-n}w>ZMze}2VsL(?>Yys?!p-`(4!e3>KVEBAz!S}UC)o|=68 z!R<}Pr30EH&`eDx@f1A^n}D9tro_9dwb-1((1rt}QgdkcVy|-71+NzSOaWKW3!@4rqTw*(&^4O>OR#OJX0^o$Eki(s(mIHpZibj0pR(|)RK{nyS{ z@A;|))ESh!7XeMz2%J#uW0g8naTFEdBkO)6qo~`5h91`-6SjSGd z2yvtv^Nm*yD((X4i;mxq{aOPF^p#rk#QvBck`?!!t!U74BnXmKOJ4lI@0A2N+k7i`H#H8#RWIzj zbPQWm&H`upR|A~dF(GfI+Lj&0j0xPB7xc<4gwEMLG}=xc0ZGZCxCgoiH4mQo zxCA>aP`z1H%wJ{(LqR3X2ISEH2`{LhB714*>4W>~S zWW=+|29Fybi233va%69E^!*#W$X@Jfz#~F!s{vxUPh31jNH z6R0=yL^OdXH0p*xtPrm|PO_?XVVI5v`x8~OqYjS$zxq`6(x#W~#`2hn0e0?&sxzNK zf){%=*}s~p%(o&{2F$!-AOO~VUw%BT_dk$G-!hwzuDBk#e!~?;& zn>;_NgnEV76hM4X*U_t-bFw$^h;{ltPzYg58CWY#$7+H=yw088+|sTy4%e8Hx?j#U zJz~nJ^irC*)gC+(h#T){WDcQkr_6^M;%qKxh+8#n=Gicd$yQBO?QQm#qauiNE6C}v z4>XJz?@OrKvuW#7aHvU1a6LW86+Xrw=Fk0;p7NyFYanZnrZstgOlyAKjx+Hk%$KPDwPcPyx)xz&x^nfU=fZjYmsUaj*VKvy z)IX)M`!%sa2P^?$Uiayvk(n%V=@(grNmMj=Jn!GlciXiSThIc{?AT<7o#j_qV`&MM7{SR~`}Yg~|Md2Ta!Ls^1^9h*&f{&Z zYQI${=7295pvuX5BCfEZw`bYF03)6?I_s8iZEkpTAisunlF3*fv%;y`dp{1H!9gVm zlx=2jxZ_S-yfV0P@RNT7G!4FiscLI1m_~nt39@CqlK6aB)D<-1(_ zFKV}6C$$J^9#KHa%j;}|?|Rv;{$=9`;$__VD;7A?YGGC9H z;~;}!=j(z%b!XdGr0UHRtfc~whnF04Lr@DcDk+G>5M6k!nxQiFzhMc>O!Rd_H@#ViE5v-4c9%nVq$WnTV+hJeP1xhntDU)WfG=<&gbV^X z&BQuPFCxpzq~e6k6d#*$W{CXFmAPzm38`JL=#GamDR|L^Gbyx6HIf6(@9BFzNI;^9 zhozWv4@TGv^tc4Gu>E&H{DTUa9{@z=V%O)-5}+sExcn#4?+?)j+T*)*aQymB?P9F% z!(Td7(T05MUW`}@2kz2teKc3tTL8zI44YCbxvfObJHX&ObSRI3<4x6_|C_|+%AL2j zAiS=s{q=>nkO}T&Kw+HB6=BF`usz$jRwdR&y-%QgF#@X^hkhHRwb96VKsB5WU0 z3j^Qu9phtH;k3UobZ`TeD?HTlLe8pE6ew*5?-434X#m12$*X!Cf~Omh=$SAW6h}V< zr5*i)tlAbzZ_%Rd>YN&^dX4fHgW#oAKpdiz;sU7QNtBuX`6PCT<1X+JhzNgv3y4}^8Uvk3^D9pB+xlLZL?SMBhlsMu)5#riBEN1g+>A81(JfI7 zARyWaMDSIVA-A);C;SE>$?$e~gmPqp1O$%QxekNP=DXG^t2v`^x5xz!yLplc$l9N5 zE~KcPdOS*3J1)?EfVY74T!3^``=MQ!cpj6mhu=bWc7 zOcQpSHwTkG)o{7wY?qJfVk)3b9Tuxx4dz<+Wjm=!#{K9G%mzjxRQVmK>_EuacIp!{ zDsTf0@CXQ*qqQ|86krJ&H@I6xAgBttE@;Umpu+&&(J*MRV?oQ=0F()K0fC%)Z{W>D zfRAOPS*SFUuf+hI{CRWrE?WqUPbB1#Fch#9?jB|9C}p7Sn3y6W$5{ID)o>>Fwn7eI z8H=Pusd+CQEvdhIVn#*Z_)7@@`l}BTeIfo+N_~9hZoWCI^zdJvG##*#n`yaumF6Bc z5g3K`a;xQhbU}cT?_*j`Akc4>82+ts1$6(;YaF_N7jqI1-{RnzZ;bw2jWv{pgc}s} z?z=U1K~+K^9(M$o!}_qH*h_z+I9{k55af8g_ws?jtD@SOdD|JK1c~+hrdv!N|7h%Hl z7v$5A(Kp$6rHR&dwSd$adJgehv3t#&X4UPDr+{)mLF5K7<%M)`aL6hHR>C|y4;ni5 z@9toyQiz>W0z<$(UqGC7k-#UPc)YFvrlpir4Mm{qNkj*}|o00#n@*#)@z z6(CYbZn=5Zw($_CzaAxXyA?+;S>&`rnuEu8DG0b5^(F+XHtG-=n6fs}zgT@_bUmx7 zQLAiJSgwOjERcUU?GX{+BgQ!td}xFQ`IlUVI$PM*TULS?8YKxIE$K7(H71Y6C-}s$ z6;#eYE2#QwGiK88hR4Ah&UcgR=hXrz0e(t{eW07);&~Ec*}e0`Q6)}C4vc&@+he~3 zAgF@ohN^(#yKCAnxr}V*&XA##Ty`6A#SxN~g=P$4%_Dvvf!Zmzm^(grNo>MnaKzMV zTEwm~@c!5CY)eK2Xvy95cj0O1i{`!DEHZU_M^bZJTCHVyhfiRxZHlHz?#ok05R4*% zO=RHg(}dV(=7+StfNC^u+!YO!VEox0d8@02!O+R?4S?q6AjlO4H-(9wmpMoQnq4}2 zNURjx{7%CHaEtGsGBjK*!QpWpX7ndmj!^Ziu8t)cB_KpxLwO90DjlZ_)q%sc7vQ&& zbeN?&jwQIfO2UGfGGHjw>rMNX4<5es?)woL6t~~Wa*Ew%$8?V<&8Li;b+1*mqo?YN z2$Qzfj23;`4^*v&;DKusaCDrXPnlYT<(AHC?=wH6X`Qhd2R|cm+Al7^efMFdi)V^H za;=6ln)q80mufw@J|WowLER$#4ut>J_y3@y%Jc>kO8Wg^m)#&I*-fO^k1;oLt@hq) zb{ucJCNQbWDSn6dJoK$<5t=<(Vqiomu8U!p%Dj#^rL;=u_Juywy{@sr4 z@`2ASIQ^;OAQRgO?JoX;G6eatXx89ix%tjcdN=~4(AFhjy=vBI)PHj9 zTLQ?;3npJYYpJr*|CV+?c&COD-pzFCt>69=_4jWy&-z7Pe+Jyg zVu15-5@1>`Du?qRPa5_cb+zo zm@fQi4ubaiW|7ts%-Go2Yj^keR#rX5Sy6{MIc4SM+?v&q>o+|BHH~`%hD(}hD?8fQgr4)s7o}*{m zp|uY}c*&}ur!s-77u7+%Kf%Cy6{y|Eo*Kqc{*lAsbDsN&`M&;Akc3^|k$6pzH*T6INOg($4*^~Yub~3kvl#h|4)-+0$G4H-D%3+aY<%>0 z$p1G0kI6AEcTe6U3x0DQn+t_jAI$+H%Xgg{Wc_$IzmLu`o&~Iz!U|eWs6rW~-kf(_ zjEGW3TzzN}d~c&YH`huV_R$K;p3gC!_IJ75nt1eOpL?bUU}N)$uLT@j1b<=0!4)2* zwqIX;-ZWCJ4W#awwv|j{sGo znP+EOA*O#{JLhniD#g05Orly+=H%U_w|k_>SY!z&Nt{pVDtKu_r?{|AnK#NMT?)b zH5&ly!Lm!9J=3_epV66K9MC{~J+S+V1dym?Qak8NKX{LfOr$6AS5_6;BlR~SKDM_t zgb^zT?X{cxWA?`il*ekILY?Ry&&=FII}n_X8M>3iLaZ6s8{L}OiCwrq9_%*76 zy~{XF?|cgPCGTEtlE(YtnNXhRe=LBj^3QH~S2fo^igTj>g|q)}8_p|9vF?b9U%9oE zgUDPZ(w+!)x(-)H;#i<=GUjbodBDjK>oz5d)u2bNvC>wKOM4>LQ#9{L8~3 zkJ5Z{0|PRw5f6%`&0D7*+QrRE>ku_z4&M$bY=q-Zg<}S1;PU(L7{Ol%^6Pdu@NmsP z$uue+(nXvYz=4%Srw~955La=Jc6REqKKHEg!*>Di{c*V;<_)}isrkYpHBKB62Ie#O zkq+Glq@X2(#*r2YuXdkXTLdIw-63b;pZE2TcJkl-r>yu+y(#XQu1m!3gUXIF+duT{ z_p^WN*DG(Q?$`L}`3_KyPd@;ce9Dt9Z(l|-^YnW(d*aZ!GnQBuK~sL0KvGH14oTqU zcK$67)NT4MT zk7l9`Fq})i^F+X)P#FXO7({Jaf9)yjQBBN+Se(L>C1-dypLqd|QlkXyPnPGtnzTy- zDz;-iX7xoKmxTiM1B4a}5ZPigodXSGAc(H=JTI%WsZj8YQg8Fif~tWARSj_egb-t4 zSipA!m|OF8p+YiqA?D&~k>C4`jo(fva91 z28desTh=_|O!zgFq_o#{D46hOa8u7VumVw@cWf4_i+X7_ymb8X@je|s$>)f+M<;Io zxaakAbOZM>c+6Iv1)c5J=2Na*JfYLH5GVbvSP32i!2~`}#3x}hxu)}N$X^~H#ml8H4B2Ynt&ST&d-mi|CHtx9;V+D<1?18bKqcL(L+I0l;Kz??3ogWHR^u*>g}9<~9LnkA3MO5UE1! zAn|trOTOvlLK)bP(8BwV9)2#O!yWpm#P}Ou^sj{`QB!?J3O}IXznfm57rG?xb652b z{y!z%BqF>z4821`ncm7wS{3khPcRLQft+q|jHYZnIjDU(_8Q_4d;ktXw%U4}gi{*S zhtg1IUXs|}SWsB~3Y6^mbj+s8e(A+CT=t&;l^yc~NLU3~qgb{kI(Avama%|l;1yV{f)U#A%gW6xwDo|0tGx&V^iz_w#gSN( z(_<|O2}d)eQxMPWN&>Py0*2RxsqcyHpa6Nj`iwbvG#Ob)f~uw-IdA!=2L?$`YzTlj zKrE*FIjhvmug;I8*2YsDfdKx>**g7Qe)k57_dz=zY-RaU^T z9;k5vnG6e0U}W}FNj-y}F?IcKUC|jrr)_j|+B~fSTy`hq4VhK6*SJa1o0YNdNvCrO z(_Gl`JV;ux+7C`0ba(nCKCU~(4HC+xTb=tuSv`+pftl(}E{%SdcR{6aWp^HwZ($FXr-bobK+GUV$ zYg^~JDrTz7XIU7i@XtCN3T7&j3-AUFz~py+qgS7ZC!PkbY1byhm1PI##v8?%k6VBf z%#QeNy>L|XulqMOu9YvHr+K+Er0c8gxAR<}=c;i+Mzb>AUO+3DklLQTgAs4zE*b*8 zp!1+C&v1%YM~Oi5y7yoEJEsrn;PI8GecDZhX|D_gVzGmQYr2%yOingD`u?daXnF;U zZ~+8kbU66o=aBC4N`4IrT2!9k;{8tX{68lI**AhdH;pS##TZpKz6hAA#DS7-*tuu; z(t(+}=GNG%tnUx^<+3xxr_E~LQKX?MP`Nix=^REf_Dormfn$eVOVq|VVuM3m8H5ZH zg|e_dACKm9)A+HCx;%mre+dq*%Qi|xUeDE0wjR=xP3*t@AJJ%vO#Jxm&dTqZ9SlMB3(m2o9#s;sghd zmpEV^=46Zn2Z+uJHpRv@s>YM4hc>r;@xdWbW`zUA|H6LBDM1v4<3F?jnSW{{75^Adns%w91sAu|_l)!y z?y`^BMv`2YQc@wnqqbiHezuRrx^N>)+8jZT)@ZUdPlE(5Xt;|dN;+4pedk3YNQw?E zJcgq@*PLL)&;)%Z8&Dlxij&+%<6zefwOA6OnZQDsiSOs1osrYPY+k23b|vi5&zgG_ zUt7<0j)F|m;l|>_51znr|1NK*b736br0X3afzulxZp{vPX9%XH3`|G6NBt=@?3bTC z3kgXTwc41}<2jw`i)o4#a9PqWD>^_G!(*F7LX+_!WGlN_H_L#7r=i~5P8|NUq;Kgqb#P3+z{ynz)jm-Q{7uko8S@3bKX~FsY?Zy{2 zf~5GPSW_(ps7-#`tax*bm`+sVOM>daXyCx+&$1)Dfh$S@E`vONEv{Yr7}HlYqnuuX zkW@nG{e% zt@suy_WPtr}Bk6J7Z(yr--QjU6uy>KV zP~ux$b+sg{IEe+)WB2MP_;=o@KF(8CfHaVIqXOxuUUmU!beggc+x3=!m1?g9d|wMt zp{e&64=Dv)Eqvx3Ng|tJ1n#z8$cytVL}eP9LzPHgw(wydHitA?r6-Kqo@?VO(wa@L zTuA$;vCTxe6&CM;&tu0X(EGnN0U7>p=h%OH)vUh0D*A1K{s(qm@TE@?hUYZ)pL8+iEm%zmnK}Fs5~Ci5c2+;{Qh?OP?A!Kag}tT7>U=yRCTg|?$l77W zplG!j5arcDH{ehq)kZV zF7Na3nAPCgS@L+8t)Hk$($Q)=Om+CIP&|mZ14-1aUMx>`w4T$NONX#3$q6)KEQIRt zQJnF(b(C#)p$hB^8-AzF!m#<8dgKYd!l9RFrl&CE04Q&Wja9WIf6jZlTfn0laOPHjE{)NdNCM`agQW2KF~wndWN= z`llf1Kso#5%I4d3p@jttd*0WqJd@C=2r^*$G zAT%^IO2`c&De_qPc1J^sLqI_Du-hw`=w@O-9SeD6z26B%1WE!u)>XN4UP7=@;{Z|bfMuw3lpXH&TNc|RjTxC zfvbPlReU5I8Gxrk$Z1MBWxy9)AZb6AUDk1uwDrfA_0Tk{KQU$1{u*C16MDzK*$ zHov>>608txHm$n-!dDW_E09h0wfmsKRR%IZ{NvCqeZm_cfjEoC3&_9ub=NwKb(%mG z%)_gj9R(FcY>PA1lQobjaOG62J-5lkXx}_r2D{qkzaqoq@#9Y~Dey$3UR~-wo5%Yr zZ-dYHC#kp{9|VOKV|E`(|H&#L8n(VZ=(5OGVnoy8Q25E z?OB<%Ge`RGsz{1oU5#LpuXQQZBkxyXe3&MRTncnNxQ>9&s>Y7OP{l!MU||P=!*?NK z#7>80nGv0P2hrRu+`;Y)z*prJzEv%Sg4n|lSUV7%VaK2f`o)d~1*+c0JD2uQ`%q6tuox}4|u{0CF=Uv;fN4+J2^1~-o~R&o#kR`*@^rqS|} zA2VxXs1-pR4FnT)60W z7kWUmqT$U2a{AA@LKIZTwS=g$aw;o8!F+aIb)U}PvV-~M6x(z#%&xhCW_NM01}+{X z1j3+4q~ZOZ0z&NOO(R0fMa#;SS^;E7tHC}2OXuvDkV;`JUFQ~6h1Oge%t*zzjU8&K zG?Z1EV71Y7jV)NDG!{-ze$IT^qjOAVzr-*Z4KKK@m#>$S%)npqf_UQW&!3<6*zmtTQn5VmJnQ?`Oro!>>WS#XZ@GFH+0+vEPf3#zhO_*0dv ztdOGBP<44b9{MAZ8?HLj5zh7x9gEm2v{%kp@t%~`s!arQH7kJJJb#su?x8}g*fz*@ z$IHt7B7-I4dQ@qZO+yeW3k+T85Tus#R3*~KyUqR9T}PFD@t}ZZWs8eOnLhs(A?mV z+GVTOzqVlVshR@b{_B?{fog7_v48%o9dzvZmugA4?iWD1zC3JC#jEv%hDr=7M+3!M z{$1SG+x`-6ep52qXU}4DMaim#&VNlcBV?W0k7RYIbLK%Y0kT)J*FacW`1^2>4B-FP`D zwMJ7e{qMuo-xM+QSxOF6`r-ds`gvkKF?lNdf|^K5*bm32sqrz4+g9DqA7qx}4@NMU z`>Nu-!bVBDV_of!NAu1af2%9NB)}{Lv*n6R$&cCfU4VC*oh#8bS!(gasYH+Am*x8- z4))POm6I;i^n4N7oj_9I0>V-h?y^;Qii^BsSS>ms2zWsUTHrwn*EQ|O< zIdCrQ+wPhpjQQ_gx!+TaE(PyR-L4UV7XjWJ*sirXo@IR6>L?$S6Pv;wD=`# zyDz=Y2MgC08lXCdy9e7m2o4wAR{(w811HUs{X064gO~gzP_C#~#P)THsEmF9W%Pgc zPeatjNDuxK93PM=1TG+Zan}t zf&KI9{`=Es57@Py6ESEtO6)U$jc^%o$e^KK31X=~d~QQg!TxM2&Pe4e^BwN?*pOH; zivWtRT6qc1(v$4xuS54?Z*b*_V%=dPd%5w)_H_WJ zW4+pyEN`j069{^t`y59Oa_jqs%3bMYlKK92-fwaWbI%E4dEu4WH5e43y1USgxL ze%<|#hUT{k&<;&d)-_p8KJWY+Ox~d7ECbh@rRbFXqpJdc=oUns{@^+PH?5v8s-u~$ z+Gz+P6tFIm#arJtv82Frt|$3D1^jk+fg8?|OVbOuPVd@bp1ayG7WN^Tr39a~;5HYJ z$QNUyVD51srWgnz;77R3;1RhZeM_VqA+ zi9S_HMidS?0a4(aVE7w-?=A^Afmb_|4lwm>RlS-5N2j&?t*tjNe!BfHBBTHMF74c~ zz=NjSSQ4iBX>@GEFbYq~c$;EfS7r5DnMUS-~R~jDWpFHdJMW$!z#n4*X zUA@0W2^XS4y^PZ&oSHg@>yg9IFTP_gwJ+}E4`)r37cH+xGS#YJHuB&-wrnQC{;Xuu zpv)hSQ<7jsV!f!j^B#OSv7YFv*2;Z4Xx!oGlaXp6wDdoPNSqBPE=1m=!uXAe;|TR{ zZCz*oC6D;Tv!L-pdV8T}i(2TtKi;+tujLV8#eR%0 z_$gzFv7G5&_3-#GRn%e7Z#mtyE-_usL?A^qu-2um;e2}+J`uAw`X4fTyzHdE4x6Uk z`SnLp{BJgOUyu@z+4IPLOCWOWW-4vo?PgEH4;tu*o05{%{%ug-MvgB(H=AT>0oGYq541 z&Q-v@{uJuf-|dC5!Bo~_PA+19jRASt_0?1BtGer(UcD>R!_OY?!W1T_@6VHrF(7ej{eoTC}6Asl(iK-(TUqJfsT$aGK=Xn&#Cjw6yqbB(s|)g=cNy zIDE(l);I*8n6vI26?`LNvVk=Ty!z`tzYdF;j24|A#c598Uw>4?3g?%1CGRLc(bN%i zf|GLVem}cP-6B4-o}0FYg9$%nMl$=L)psJ3p*H8S-~mDx)&(mshlk5U9c|TWrfky( zjIF%p)bUR#=wiRM5YZKkS960tYaE@GP&0SlBkM{q!)C>to+8&O6a%5x(onw`fIq<# zc<6|k=U|Tbqq!lpcXhSvVI?N8QU?9y=9CYd^Q|?-UGO2WK3xr$)Sv#j?J)H6^-B98 zP?EXYchESW_+{c9Q;Izm89qo3Lb? zC+ebQJCQ3Zx>)z&)f}s}N_^qveDDmOaXj^hH=GTHfL3g)KouToUPwxTzLadxuCW6y z@GCfur5D`@Rwy^`XTop`mnF5varams|8l}SdWaQsmM-Q4Y2bq4m6Pw~$>BR|5{i43 z46$SX5s<>f!huNkF1%cvVqjymIru#MP_muqBZhe0GuuP&>|EwjkN*lPXD+;X%ZkjrFIBUlYqO7!bj`(`}H|efh;s(qxk9_+wND= zhhJY#B8oclc(c~3JG5Wer^_@4zij0;A@)_pMhr8JmV*t=%1**X{)KIxw-h{wZRut& zSy^7IH4JBR8>%;sC%%HIlz2pyg?&zgM;~u)fJ4SL!Z0R@1vZT1C{&2S^f%`eA^N<3 zq@PcNeUv$bC~%D_;SMGzly>=7aJ@+Lc-H?1m8{40x;t$gFR{_*=N{{n)*!$Cr!Q2_ zExLulW7Fg$%PWPIdM7M8Pc|@xAD&{aad3t^)V?+NNc{F~BQ|u_C1f8wx5hhf-fxZ< ztgj_~`!#B#UVp6o-{sdeKKz zW6HyrI8>j~a-qix@A$A%-j(Ry^SI#5Ur~sSL9)AiYW7~K^isb0?Ou(Nnr>)~lCBi3 zdM-gBlSy|gleO;KZVOfnAMc5>6Y2`zMbAl^?}ogkK)A9MN(BM<3B^F|ohN%GD0aRW zTy=YvH%kS3r5^r$bNn^no%#?EPmKND&`YW4y`H1DhlqJV$GcjRd>Eq5%&~!c)^$N2 z4|1quQi*3TO_=8F-3h_Dx_b&v!NrTv-AOMuhZ{oU74FigN;>iO1p-%ADeGq2*1G=s zs=Cbu&?`eAbjBq(lj%t?6xR2z4~;KHcPWN%=YvP}2b;y=4fDx`yXAESvu$0u9ZFyM z)>&S}1Q!V+Ok~%Wk|6jtRO0u(_g*PkNI8ywlj4J?hZV*v_Z3`4A6H^XQ)T8|0{BGF zsNfu|PrZHfcPODGvEO_l$W2Q8JCev|?doX#^FuzaB=+0qq;7PH;a5>Vp4ISQvG$sk zkrdO+H+cHoD4|ST{pyvILW0u*py%GOHettI6Z+{n3lH>2_0Sa`9p(#fzJILf+s>;q z@yDz4?1sRwk^$S>66{aa3q;Yo<(R^iyt#$Bw&6@; zV!$>K^d>)pEGd9jebsyJe^y`=zY{1&FA-;!M!%w&K&cXtcsox*S;yN6hBHScXx;#@=fYyt(XJp zzSr2>2p4hnEn(ta_^lT{c0Z(1y-5Ln$#x7yKLR$CZOm!WMe+8g2X&_ys3CqVE+U#G zCs398#I3*XT@C+IFUXI*3u5r&{ar_^3OVQQd%sc``qZQ)hmR>O_8mtL{1VDNd3}qwWy4U+WqC;1=kT)mNZC+J z=$9V^t_}|+F$S;UjTHN_aRsG9|8w38cV5?PyVfNnefd(R*Zrs-7WHBys=cSvs3I@? zddyS!@tEGMUGr=%o(9l9yWtM0D@X*d#jd6IN&xygM(FEMJ^u4nz#*(vYox!13&BT7 z&`5b#_uD<#jgGk0BQWdZVR(y3dLouBQP3HyfWPHPau7ey4Ue{Kx*t}67aIffa7E~@ zuWnxf8kX?Y?qdxN4dGEyloM?!cc9xYkvFlp`V;wF?i1@}Mhs9yg^GCeh!?!lN-lV= zjyR6SS)ML+93lCs-|f*<6MD(dwU#R|nqusFi!)p<~=a`wEI-)^0rkpys`#(tEMmIj+Tf z*XF1+C702m)@Vc89L#8{AeWU*-PLcBLAuxPUcetp{T1#_dbtH6V>#;S4F6Is}n~ zDA#*=p)Nk2BO)$5O%%U7CkJBNQAT6F>E%-j6E!`r*kbv>0=~$F8y~@1evw zu;po~YMukGU9|GTQ3{#HePtn?Wl(9g+m&9HpTfJ$+i~tb zx<~hX_64guS4kMZIyQlEm+NwTki>cLxjhOdPe+6l#O55oQ?)vysMXI@BO*FQk4I2_ z^bPE^2*DI6F-PHhcu_Uxihw2auk!E?JV{Ca6uRFr3sr-j`z<=-j{W*0)V84{_%ChS zJ)rirj=uL&t_@s24gB0&j1QJ+RMSZfxmVfRZyi59+OiYB8W?WA3xRt^pbzOT#cW1M z*s1z4^Bv}FC#ZGaJ(?1T0mTsi5AeZ}Q0Rv2m%x;TN=1U}BwV8AaO}>;@!VXW^Vy}sglXPk_kVkO*iu>DT$yOs0aHK&=*I6 zoucx!YuO;0%F3N>Ij0MD$&iURT;213;6xO zC~d#YifJjp2&urqa_uTPCNw%$87wR*(nbxO#I(}>6gjzT8jt<6Sy-;_*oGmI1artx?5QnPSEc zbJmqu>0}lM7chk!lVhZ=7H)ff@$^(pR2DglawpV0txVmmmDna@#-6dYS(F*xCi*a0 z&WU4sUMh!5O!pbCFPW5)XkH)xAP4bp@7|es;CZzd+2|rE;Z<+-xk#ip0RSb4K&pFM zjZIr>7>@L1tvoD8FyA|awkkZ=$+)z=0c5n|bx{QuU~AY0-KX?u?EV2fpo1dR3%xZE zDvu(|(WBYZNmfH?;_f*(iJrDZ0F4kivo~x<75JRz>9DuH0Ta#J1 zjC39G;FjvR+-2DW3@7uTt_Rf;2V_{~vR20#5bX{SPBDR%Az$ zc}@~3q|kuK5Sm2hF;j-jV7&hvZ! z|Mz+?*LBWr``Y^+*1hhvK5MPd>J%#XRE&f4JV#B_q0bj|?m~DKIQgmeMVBh3&?+ZN zx?2dbWz|6>i_5X=+C70OgPenXB0lz1lH3WdGv>`t`JPNqyxUR(GQehBctOj?r_-Us z(jnd*GG23N!!$fmj<-OW9Rwf!_|gq#bQTRN4Bdia`F0vrOu`L+@xlSeY3#t<{_`ml zp&IWX4zkyVCT1>b`^kWNe0MN6LvY{|2ZgzpI9P+q#BZ>TI~Zj_SK45D7M_dsW||WY zjCTnoIK_FP!fb1AiGh{BTTaV?fINKP*0aoCl%Z!xVG4ex@=Xe}btb)zvQAu%QO$XI z6)7Fr5axZGb}_{3089V{XlY#h=6bB%HWL$hFmfLJ z5NyBAt>SSJ*+8P59}Uu!6j4j+v^x%?%iC-u9`|*=aW8C9kZ=wJryI zZr1)5WoaCMN~7F9A(?B6I>w5|MLUy$Wu*PO6a=QTJ7P=4jopk{Qqr3e`VNpiR{Y8| zZEd&rE`uP|_?@CJMXkpdmL6-@hPfkg0cU=RWtfDR^f*t+?1ldmP{9f?>9}(?FV}8C zf9R(!&2UdhmKhzh6-gSQ7|5Bo|=KWn~+ln2Jt3S8c z-~Ih-*+u$Aj{(*Ak(t)LPtT7^%UfQ(N%OtCa@n-LRKnPEwrkc4>{we(vG1=FE}Ty0 z*rkvtDcT`r{*m?E@_c>cLuf5x`}y1j$gAvb)t7`td;_*>c&bjK;02v7naF+akTM$0 zjp$WH%o;AjrC?OK+;Do@@>9~{V&aGgq7uC5R2k&inYZw8;oJp9|`81RtF-)!6SD*7W>B zWsE4DQlimnF2EK0he6xLPaeX)(O`}hIxTr=IRkj5RU0z;9@#d?S9VPH59|vAD(w#P z*Oy)$)_hsKyt57D?Avtxg{p`0>fd6m-`q9+pd9w4D2;!w6=wMQ=jGn)i-YfGWXJ&I zLGF1f4^DZ;Bd@dEXYqR)<7sjMsrFw@+teH@W7q?e7i!4g*S-Ew6{0U@2>&TjC{?Ns z3Eyu@vjYJsNs16N684{YO$@;ZI!{l`JS|+!vkNk=P+dDpr;mUUA`)c)#NwVTb;+)Q zT_udf%$>h{;*}k#!I_}~XD0Ls=mbAMzB~FJx*|WMziZs?aTP1)U%N1 z*2It_ln5<#0{DoGsCrSxEy%R4W?6MAOO}VyWJ1B%>l!O_Drv-U?!Xl9)ax%pB^jM) z4HK=pBxG0j{{5c4+YNsU$=s@Ssx^UB<(#NszCt^&yDudI`NiiMy$Zd6p(yO3^2Az) z6pE1fhhSk}C!-#**$;f_5-m+lXA8Q%P>n}9xV|(xAel9z^fexAXW1Y|AIJCF;}$sZ zr$-B%=eUkf0I!Fi)g2WMV+k0jP<#CJRM&(0*E~Zb+YhZeQm~6(;BFmx!Gl9j@J1^| z4w$fSg9UMTAnq-8imTXdr#BFj#!;Pd;<+9-Lq2{3?k@E^DIQBMKA-qN{l(cSQ@3SJ z1oK7Osdsya-@O)b_QmUB8YY)mn(bGU1tuC^c5w;Y_6y9^^C-vd*|B3NC#mj(=oW~s zeUcf3^!(Z^GK|~JLG1%ivzT5WXLsgMou^{PV#W^n7nkGnWq}+whj&2c~H?~ zVboER$aH2z&hmcz$cXDVAk)q)H~T%yve(QEdnZCg^4MX=H?oKA^IGc`!sLwGj~|TS z?h=67hH%IbDr0yoWC%^QlB^;od-oB{@m0Wt*>JDpEzrbx&)WPu0qAU#=bL3*g^}${-Yh7KQ(v z98K5&_Q+*urNei7SE6ZUwyEmN!e7k=Q>_N-MxUP@1Z#22P{*vxE*BRv{?9ZD&x6 zmUMRs6Ozq}civcn5$Bs*{Mo3I^RWtgrU0-t6(A4}Yq%yIYfK0G`<}pv!3<Mxb%9I;A)kBTJ)FHrRGCVyZd&06Mem%bbOupdp7%E|(hmc~eKWh}wlMN*}o5gy(j_+2_HmSFXJ+5wSdD(O6 zm%}b~&L5z&GJG5np_S&<%>*ke%P2ryoIse~_+!^JWuWM}=VW56l#ItVh<=Q9c5(2X^x_20Z= z?L>jhvAF}Tq@5H<9VCD{$g-Q}d@l1WwzcxrsC9=!RmzJFgE51e$QL=hir}Lf0^}fQ zq4Q=@Qux}Bo9sT0&8hRUhb(_9p-XapveGU8S98a=paU`Tv4;lRjvsbk_`+)09>sUPK~yc*Fa*sg;4bJ(gGw@^2m4OL{E)m zWsM}pvWra*9Oaws#)b;WswY<*%i&dde%{h>pwx2Q;s+?)wt{Z5~J&-5RYgk#aSJFDl!9 z$n#rH$5^_Ldy0yumgMqWW9jw7AO^rgohVq0T`o8>-+w^Zq)xE|hW92Sn5jUMWBRH_ zD$A%KLOLscd;lxKX>t=k7>l&D3+b=sE03J5<2ejyW{9}>Y0d*20i@dk`fK`9v-b25%P%vFUw$U7*uE&IYPTB#tx^LQV}Tf&h?xb_rz*~Eoo$M7;p)-4k=*6 z2oDd}pKZ*s)YOcs9;w0n5?W%+7PR~cFktr5!7EKqu3Ewur0~`OuC*6CS1D!T9Sc{! zGbI1^U#H%e?%oCPyhAGZ3fS^-@-Dm1l#<(R_o8p`z=6@+GToh$ z6l`;#+!vavBy2t50X}?xcB66xR7GjSSLsz+1;|F5C~Ld_I@i&>SQ}J{5fQF{>TXM5 z499#CGM7{<1Qj~tGOJGw*sYV0+zhfV?r;PCl}b%;&XK*iifsM;tCDDlVvE})15)k> z`l58t$K{@)neyS8arnHH zEP-C&Z9ltUViIQ)^K-~9z9~&7t*KN3x(dbBCLEPZaMEwr)|lN}Xu#Z_zc^(D&=1di ze?+~-*Ldj7*4`^sE|mhtiJ$rzC*T0vXl+j=h82t_=u%HFE_CO<->{|A95N0U zZi2n1Q0q|Ss$=44LPh?{s!N&K#WcKdX{I34<(H`K@C$5?6rcgdb#a#10_W*QqmkCI zM|H_gU*oSo5I642&bBl9{j(a#3lzSJDS5}8nilW%dq>@;+s-IhI6aajSoL%0K))xp zLzj9o_42|EfkJ}-%(*n}7bk6!T*u=%?FMT3g~nE3x5~oKS8HMH=|a<$Sf{NN6Es)NQ@Zqmp33(wZpYXf-0S*^GL$*!S=ZGsgiQ8Y(mU zPL-pASFdfmN z<7uwlA3S6AdEq|02X=lSI<#L-7IY=t7tnSx0g9QNbY;M8yJ|6fLgfmzm6&ZpSk&Z# z-O?oBY1Cx2r=oY{;cGwcT$CR$NjanV_VJus(IuD3fRGD)f!CzyxfMet-G8?lgSB)I z_3DV+cX^L5v&$1hW8?R|<$kI2W7om34nRN7l~*tO#h>6IGJ|7@%`ngb?}f|<8Dr)r_d z3&G6yEq;%RiszjV0BT1Q2ud2{Bn9!EZG1I+t>ID4wua1a8dDSRLaMh&r2^4UT?A}^ zhOM35$OdbvrJq@aGwqPbvRKSs>&oy&NXqHk-3!nd&Gpf-8)wCb9@0OwiHLaUS$s^` z`iljsC9y1U`L$Cb2YRGDJ_VNvrV6v!W%z|7=is-&e(b)&vpYp4<-zhIf{Ni>plhI? z`rN|*ZKmT^`%-GHCfDTxE&f2pV%{v{qn95{)B&LI9w3U0jwl3lsZ6R&mP$a8N>tNR z^$x4^m!Lz)x#t{pl|L_>A*#;q&YGxuc{znhjd6!?zcH9@03TZ3hU<6KZxZ7@E38kxC(nL#?RC{j$plq zxUkS!&mtZ@^wU3TeYbcv1)u*|*xOs~QGL7cj3EmJCkmOln&2`fb@0i0vz0WiCt1fx zFp~ZfLQEqvUcV}*&afse{TBS>!}A8a?7A>=Y_#38D}-f;e$*kXJ$76c>VeEPY0Enw z|8S5fj#PWl7vbl;3@*hKM3LG{6(MZSKhjWU0g$c!*e)w@zrzw`cD6vh1PX=DBo@8G zB4i5HOhAcmPE}EzYfgwU>{&3MwjIzjD0p~y=;rF(Fh z3lAYqQ(yNXB9-Ck6~>1hjE^@l9X_*DMC?J;BA7L7&o5vvPXHkP1jsHlf6o-XWw3(w zu*LS@uVy@abTAit)cv>NQ_b99f%Gi>@(bbd?y*|eK5w#pO9luybq52(mmOm1iP`4& z_#l2Hfc@Q)FKp9*Y0J6xoQB!WE^IlF@r%$FkzZp+r6UEg7Y|>1t+=mXuX_8df|Cf) zGV5Mu2lvHDzhbV{V2gr+!lvq~yw>GhzRf>pI>z675udQoi=pGVRW+JxQnNUpR`o>n zU4Lp;3}c;1XPzpQ{#VxA;cGPf{OohilRDTeRXCnt#P#3W<>JRD`}4>1v>JL9#d{Fy z0_aY}#}OjZ=fvu6Y<|XIBMHV~z(uFQ79CNA5auET;#YfLJpHNfSQ^g)P9=b13Ka%~hGpDM)%-^w1UjkGDm))SG^{i_1hBJ?tNZYB$O{F|O zFYC7*-H*?yx;--oMP*B<(cMCl5y;#l*V(ZZ1ldUI7L=}~q1rNmSW)YoEze@er5YO= z9=KF}rZEM(n8GPo)C-*)xU>->Yg1ji-Cm%L^bb~6ck|QTAsm>}v1^{Be zE$9FPpp85Mm`(W*yD<=cocXdAP3_S41n#ShDBs6QU!Nl8W6RtwV-^?fT&sA*&V+J4 zc;fBDJAqxNA$`&xfR#K5ea1RM%&@4+MbDRs8vwSfEq7uQC;gSd_FqXV473uK zw0MN#6Mm?Ka7u9FXl2@#;s*U6;fuW7FCTV9E%FW>ukdnbU=^JbCt`+V_5RK33%|A@ zbP;~S3c78>ASKi zVOh0F+^~#-8Z&_?fDgFGKlslvMo67Xl8c&Wqm?428b6u-dAY~^eY#J$6#G#R{&(LJ z;@^l_4mVrErCgxqT0E4Z4J!8;aHG&hOL-;~PLB!iaH%;}V2OBP!1t|=6Q8zt%cLGx zfk}F*T@*6{?i@nQQM?FvZq0-ZDk2bs7Je`HQgfP)HT6hT>HfC1*>-m(yINI;1wfKg ztHc7}*hz^jd~bNVu*mtAQK8^ zeW1y*b^iAU)8EP&2cdDIV|Acl9dwU>awSkIXuFUsgTVW{V2?+DfAyZpvD3KnkU_;> zO{Yo>k8omj1WP0Yg%s)&TB=f%iXp{-38))8PS1|T2L=_VmP&*i5!Qg9wSaKlHx_rb zA8tS&7*Q$~rM$doSCD<>vuuLP522@*7j$=sO`HctyIf0b%Jzm*pFrpi8dD{30!-z|~cO3Dt|TVFALu@T|hxsA%yb3$Pu z&^miBSDy&*6AEp)6a%~$mBDu*=&J*fAriCC&+fWUG`OK4k~laDkkn96^$|3;oLTpq zB7s9wB^$Q1CIV~MWn}Q7SQMa=jmi>AxPp5iC`E*7IZ^en9{s2^8A^`ja;&=c3=IwC zTn*-vMfdDbYesT^fBz|C;}pc;G{g203h@+x$#BF8nD;Il?k4kbgq#Os0DywQ{Oe}e zBjjmJ^;-+tMM70GUrzIT24Qiq>A=F#MmbvoUv+mA)9&BGNCccA{bw}8;N^?V%uG~MI5Q_w znL3|!RLYns)}TU5CE6fgE`&MfYBMN^{%-7Yu9p2L8Awskv$R?EBYu7df&|E|XJZwUWofqsKqp4ngPs_1)6*pD03i$s|AY+y$9#LMU?|{n*ZGayrBL=CU54l2x`k zX?17u`NbBRsu26~cSELt1-xJSto%n1fhuusecIbcZN#Zb>b9XbnZIN}dkD0;JoH33 zx}z21E#DyFC39B(LYj8hmn|L;({swSc2LWKJnS}E`TTOotMu*4mQz#qImSVI9wN`w z=Kay4|Kxb%H9#H;HRoQ>mSZhM$r`k-qg%d^|0*}jU=HwHo_`F0WqnTTX~>W>e0NL7 z@@ouOnsSg^B8W0|AhfitZM5QcEZ|?yTPk<3h>zLg#5ixNCB@ z`$rFh=C=5hm~S_z08A#Aml6V%qhFMA>IU>72QAYA1Rnqn3q2-v5opM%oq6T+9@n_& z5RDGwuGj~ME!(p*S|soCU&!fz-VdLdIUdK^1}tGgS9UUN5UIrVNm7at$yg9cggWge zhRi*@g?4b_+V#~^h$82s9$XwSXqHI)PAcYkau8z%EuxDdVAlB}`4lpD``&B6g9KQC z*J2US9!Dlut#!{%V%ucXJ3p=8&A+=GgbqvRi=-l`<_mbdZ(;LmqQTru4n;TuXJ_{4 z1zv;*n*OJT0s*L6$oS-eWfAo#2o!ulHzDIy%JU|@amb3endLOGw=fy+5moN6v|Y3; zI`czEdPsKb5O3Pw;z_(|ZzEL%qyYQywb$Z`j{eR^``7mhF(A{pz`)n7D1E}od|mp) zi(<Odb&r0K%3oQ8Nd&jmO19Ng=by>h2Tuy9$r6#;DQ5nV(%Clgeq@|GtVqW^&T}| zV!$2&62zR)Cb$IkJVk(&NOSI45{_(z6=*H9d|YcQ3t1bK@=PY-ISnz;xj9NB*JZBLg|o0jM-V znG8~-k12!Gue>4Qi9_MV!>SEG)nmM77#}~vXI)YzoFHDYhfg@v$42WPO6j@HmlqKe zvq!YcJ^}(_&reA~XUUu=zI-3)Sx*CzCjomG=DQYXxSP?`$1!fFFJe_;xz5 zhseAEMmP;P%(RfDqAHuZ*b~HGY+Sn?8ah(@I3CfFAGxaH4M;8o_2VBwA76)f&}OXw zGgO{=yYmp18Lnl3*CQIBc6s4I))%-~`2nrVAWMN1s3RI+Nnb7=FbI%C)%!U;ciXO?k_K#P7FBd5H~pbx7Ni&B+7^bA&yY z-84DN+!VjkHe!JR=1So3$vuVBp4eGW-ZJ;#3EB)u;yq^bDzgMA=9|S4!wc zunkdOTOJDso3Gtd+xgoz%`;WCYzGcRluBG;(3P&#qf(4Mu8tbeY~w%Qsu5Ev0g3q- zb<-a}w&)8HfVT_0p4)Gt@4b*bhO&3mz`+u>5-D`}J$pdfOFg3Q$o9u7{M>8@|Dx88Z495hSVt!vw} zg`l9x^W-aBLFy}9bf(%fNcaXV1e1qpH$n#(4bOnL4&uGj%BU;tl$0aXXM(I*hCi;$qFu*|iJ5Eem{ z1|nmlNE{NzA;+-eH#ia|)Z@_WscLxy@p!?mcl5FX@6YM5QVC@8fXSo+X39ZLi%JY8 z8W^}yzk*EYMyW64#6TrIv=Rt?fgtd|R0cAqcN*g^;bw5Ldw{1f4B} zk|1dtR|Zje0TPJRsH05%sryb@c_WVi-Y^_#%@DrYBQ3E5%1AJw!ZP=3A4BdNA_zyR z-j;8l0B2LHlxSrIqVL4zpe7hJd%iURBD|T4{L3iw!r3(U+HmuX)BZ%zkL|r@Axx!c zo6LRnCv3Z&ZR9Cq*-Ioc`ojvi8^~hP(A*mIOX07k9H^_cq_a8145_AcxG&mmvXGVJ$p zM9tw)imH$ka=U1#Z#O}%MAdD+OX?GCIhL^kb9&4~M^OLZEJc4XMk!>Kx%>7&kfhozkx#sOlb8Cd%@Ll2s@fCyW6XwEJONjO(U4ReNHSQ zia|uhyE?@q*00lOR^K9n`%UQPovVMEG!8stan?S%wIjlpNK=Uf-x#ya@s<3ZR!@Y|F`T9- z@$s4&-=d``@J1{EOhC%Bot*8^?FEIjZwO0jZq1BV6cXi`n2lHZb2W3kq4}6{`CQ z(E)#YRw;$JS1d=NC+mIXL^9mHPmS(#YmqGIFedA736N$RxR;$WilCUGgb`n63JbJ$ zD5*&EJWm~?-HuAqLvMmU*uC-gu$1prkrUD(C3Dpw-*?b(pcA{5E!{$)T02HN)-PcQ zvH(}b$-AE(N?YdjEBw~7hZfDX}dLMgMm zD}3p`TWguYtXv-F6Pme3;l9e?kV$bl=Z-j z><5}?i8r;I;j<~8gCSQ?^@q+u3guoKa^>_;B>1OVwKlHS z#*MBJK#b*&2`px~1Fg*>G_qw)MA0khWTf&%V%mfzds$!I*VA3uK-y!#z!AA|ohS02 z7&!1(2+t@)$DuSnT66L|zVts3?{{A6dyjY%ywlNIA%+MYDJY(1heUze z+rT-IYuL8(VD(m$3RJa!Qwfyg3jGo03uOo02RFPBpYk9cZSkxYtffJpr%QEpnZj5r z8dsf-l);(fFy%~Moc!Sb^Pfm)R#b=(%5f!$Tsr|Y_mxLd;N$~E!1=S%3xSVbu@twr zMBuZsWL@2Mu)xt5` z)_dBV)Q#@qp;v~vO0ILln8Tay6AcUAJk}y=G}>MKo!;OiPUwNJW$E$BHF}H^4q|P$ z?{u66Yc;FTCbFgeKE;6uLWL^r#3{JULRi~2tvf0(sm-{{EDP2`{g%L;MY^FY7eJ|> z-xYI)MsXpq@i^U0F=a>qLNr+Pl^@gb_BpaRis<_OKueS|4PYyK$0qTp7f91z#n`sW8yc(4ZE2t2#1?B30WeRbb3eDuB{lj zwwwQW2>taFdgMBVLa=m|4n^|}*@g!b!R;S!>g$3Ho3b>wshr)vkJEWlX}3)qVm+eO zT`$CrX`CJAaF?>E9t)hTmOC4=?h#X}h2q@7g81Zdp`d zS^j(op7hd3GEET-GQBO0PeMdHE(KYeiEFe0H<)gU)d9l&SE&YAg~>G}ESi2}J~RxD z4QCM#loE_|7&u=D*PLkZrP<>i4-X0lQR8NJ8%(ubN0%&tQ>Pp%DDd}MbtdCfvY|hw z4|nkVWB_AfcoE)#w^LURc+cC*I;}(YxkSSukCLRl7JMlNmEEcdj)-20W%N83n_RS_9Q@38xECN&*F1urqeFvb!`A1 z1%~@e+GBj*T8eZYPO@NKq#c^9dM$j}tYkbeojvB*B}MrEtIArWublwpE>~~CLKs9m zka#}qKyJe&C~q}+YN^sM*Unz*gwxrD(CEbW@e@E9;9#x<%E%qvamZ(?ROEFMVo-g& ztojtV`97k!x~!`~PzY>vP-+}X8e_J`ndHO{JR&?nBcufLffZ5a_;P+!#PNx(*#AI* zCW|OUcCWd|MH*xyG9g4dBj5dHcFUDc4sX6ko(8F~h&=>s1|AYT;uaH;LTUEy*SX_S z^xc&6s=XiFzlZg;FdfE$YWT~y=OiS)`R}yUqJ}fVt$2$Gp>A9MctG0K;~{R&Vj9=7 zkCm7ZPuZ;}`>UT7JltD?t~PH&dLXkqmgy|4y}G|ZhXm{?n>KAg>^QhZq&>q6vo1~A z&$;>x5%DX;p7O^-$nX)tfdXe?!&j<7Tk=x2N9-bg(`u6}*AYrF(R1z;$p;a<0Qy1a z58jQbhV6G2d7~ecUv%FJO=&0-R!qz@d!;=diiug>0va*dkQ_FMUQx*>MXwMO0qq~| zQWGMLP@?+??V4V4ZT-5Nd{{8f#su#htO+o@lFp<^#oIQ?FB;E2v}!brCdQwk6?4r7 zro&SglYpNudNW)HY?=NLwgdPlR@Y)HFJhN#1^*q`#X}! zs#N!h5~8)g|6jiPiQ5$mv@3Y1=GV);vIB}<_2#~9Qjmw@^BR;+}(YK zm^o+@Gy+xTU!iXc$geO6KjO4)%efgmb5qF|vX!*<>y+MQFnq4&j>liXdU}iVhS5a@ zL&F!SeyVr{%GbQquod)50WS{9Bs_rUt^-FZ;+)(rQa1)xsabI%w(*2tj8kMu2EW_W zu=cz{ROq01_ktaBk^UddYgHSb+kSW4Iq~YPmqEmqJh4%U&`$9DH|; zTn8o(CLZnvqlF7oRYV~y7ZItgeG0x{NbV~W&-36z6mxq^mf|JJS^K-PmXE(7vR6zT z*Re12Rz-VX4UXl)4Z~@}!oS;&fAHQTYE-N89>S68)utK z(pxU#4puY+;lrel0Gf*D*;-Y2AbHUsTH$<`g8oP;>4j7Ubn%uQ_&u`ATa5Cmr%9Y{ zo8?)O)Z!;mtrV+*L=i;P;^|GvMZe}?{Br^GF2hw6A`b&!QFulFJY^B5vKKkWR+CW! zmEFaXy^Sk}mqKi76kfaSex-Dh<7L{``-&_SWpZA9qLMF1R)^jTC^AM$b0!ElJgY)V z-;hK};U{0slXO(70m9)vj^WlH4(OBKT#(Qp^#BsC#iqyBE2rWr8%WUxM)g2z^_SS* z;hHZ5W9z>-Hmp~1)|DC@esU`=;rqp;PW#z0{A@Vome6GEzM2aV#{6#;9jx$#78nam ze1BL6c=KVMz5v$=UziI*B1T703Cic0ENLe+K-kT7=e5AgH9m?*gMxVxzz>?D?EvR!k|)4C^nPFQeP1vYm2ME_|Z#d*d*`2-O9 z1C+=j%Qtms(ZgZgNx!;$4QHkLpZ_iQ>xn6xXH$zq1&Np%2NxJoFa-30bS9-KB=o%CSqY^6^V7C^WTmAaBfPdu z1|UtocMJ;4e)jRt160Ksd)`tEI%Lz_CfcIy!`-Ba!To{v6F5i@QuLwdlOhVVbP36O zd;Fy_Aq}#);iLoP1<8YLbmxwq!AHEpDg#IxMIrpn=i0*n$@TfW1FeG<>u zwyKV3F~l#i*SE?K;Ajk1jf;P(^C(d+8>uNAv+3S!QYJS4F=NDi5V>vim9l!+YbE-l zTYs-v!P2Z|NXv1brNK{YWifg|e`8E^9YWTNh&%^g^>Kbs(@&yU6etNy)7bPERe{K; zwXj@Y;+X$eI^roxq+)YhOLUMgn%jN^Q2d^`O=}6O)%^SMlt548iCQk;;^w^SUVXWW z?1xsj-3Veq?@3Cp(o%%N=6GGNK8xQR3V(tjM_KJ%DsqDhW^rR%yjDNlzNKknz_8RhH+~c93)tnA;~_nJ>5guo(z?JTCQcJ8MrCigEor zBX0Lg%9q&PrJrb4w%m;#p=d3Dq8-|OcrBnKE&U2`v1jkz^{kmhcYvEJbuTLoIpOG_ zTl1p;@**Z738iSvsQf#vw5~Ntq1cD6-#u^n|7XiBpm0-hV%NG8|EEUuhdFM=lMd2O zoF}+|P6f~n z9fueIm>qb=7u@AvA_{N0CqFj$L+7*Zz=`ISHPNij8-UcD`;0wv*K%Pm*Bh@Z;wf$x z6M9HXhwEmvGOb@A5txJL|6lDW(4g-23U#diO+8QYQONy`=O;RHStLQ6u{ddUv0Zw6 zNQ7o}M}e0mw6t>n+V{*ikf0Y8+j3hb9dyNnUZ^0NqglG za;2`bmE5@S){i3#|CuGHq0kjitic%pr%YHTFawV&gl-3;y-ci;nuOvz#Aq8B>1A)? zG>KO?`L-X!Qb$@H;A7!|$XO}7 zRm`GDBz$>g5xpM6pq<`V*R6goF)7JD2r89qsQtP2*1z^;K&plGGVYRH^Cx!x{8hb3 z3t_rS>$RX1MRv&wC#yrS7JwSJ3%n;8yT=y8A0Di<4kr!JTS0cD?}Mow_?8hiNvD;* z3o4o)ln!%JMt#ufloAM3&xj1N{AHsfD+~((=L?+OSQT`^Ji}0T^roZ}rjCj;$9n2)>}8;qfEL3QRSsvY@572k}umM(aO4 zLcE37Lb6z%OK{F77ux>C6aEtz`%`7&Ss_>5cf6EMs3T7C#6$R)0IdeNxz7n9Yzlv( zSy7)q@Vydxn8Z;%L!LEJ0I2Y{IH$L9%-UP<%1ad4@jXunmf(*MdvpaMSK$nCu#RKr z5vOnK%)GlyMO5^5~VMohi7d~=;Hnjg6ZI}n(gRN2&jRd*?k-@v>^Am1v zo6H}kH@xbRU$|Skky&y}z}N}siAt^XKPL7@pc3=YpkV-W@dJp~!Q~{=up}cDm8^*$=S++e5fFwBp zlAxOvlF;{Lzyt!0{WogAuWV;xh~~wIOaJf2c5rs0NH75@#VNxcLD1h`FG-hsC6c_2 zF0HM@BnI4+$1g(`fKHJv@#UJphp(8n^38Y4^G-n80`rfF1xe@T#BnxVqP4$Yd*uo^ zG=C&HVb%Y08wk9fUJF7mVY3{^k42vQcE5yCKlGmR4zg&rD^6v-TUh*=R5>pk zzqv2jI8~QO?$r0w<)rq00ZDgNgU*P_-&B?7Q*08xtxtED=X$TAQIqYnMrlRwSMGFPrryYImLL{BM&KBGu z^1evAxOAI~zL!JdxYk~702X%)(n36$;=N0|qMO(th^`$(e_ z&|?&|+b@>Po9YUS1|5h0V*);fe09UP2ap06Hv0A6t$s^xn1yN2o#S)-GOWjl2d7CR zB2}w?2qqg|qQZRu?`hVVM3y^uH$CUVe>FumZ_nTbf!>`l_-|m9W|bgb7#vZ%atyu? zKRyAfR;gS+b5k^el@U|)^CV3whs0GZ_quyLGL<2HPuDuWxT`dhI+)+&Er<3~#xz-) zD`L36_~={mBl?h&I4~1$XDNjGnBLDn(XfgOiJ8xj#eFvoM~|CGP>kzN zYB83B+=_ZcgUQAkwvtx{2CZiw67|e(i+3GA95Bf(m9093EK0v`Z;dZ0z4eGqDb;_UBJn}`iNMB~dFRPz(TN3=Xjw}3 zlf1!hP98|8Ad#H2Qj97ks+0SW*}3^TYjID3N6RQy*&%QF{|^zu){9;aJCEM zhJuz;mR%QnWUK5A!j56!4XslT1R_>98jUHL?xWLVUxN|BFFHTx3*P0+u^frnvtQu| ziAOrU%DziMW!>GzdD?~R_Ml{>(=93?X7@mIMyG-2L56`zm0mXykLk_EKWB4yyq;D) zAmvDphUKkC8W(a))lS8Lcrdx*CLc|YThrdtuqJEEK@e9O^=o5nA2YnBohvDM-CTZM za3U_taFg=%=h*T9K1=!vNd|V2^`Oe+>IOd8HZlF&RGnl0s!V!Qkc}jACJ2T3?qw-|j;5XsK*pH&Jl-*Ly~O?owCmE{-J^{=q^kV`22?9}x0VqB9GZ{Y0< z*;t}5q&Ap*CzGo=82)+qx~Kq7Lj2$)^7i(NP=1(y;-BD3O?Opt59g;ydE7>Cr1e{x zm%8n^@l?ub$};vIH|Epa?}JV(-Hmp9&b@9pxSe>&o^Hl=^8>YnT!Ah7&2#imQ>Ek9 z4q62dY@?+40lL+lekiDmjq}+2Vnwv^ZyR7>8_6ADS_|c728)`)bx#YoODJ2~5&p~& zMsY`;ol)@JJa1#!C5BZomyRW=le7HcAeJ+&>N}T07~QFJtTTgHkuUPEGRrP!!^V6X z8h4xgkv5H?_~V<`MoL)tfAgYu8;n!VO5byG`9Xa*Q{Bw%5Jke_V)a;DyBuf-xEWeVQ7Iq@39K)ZRcNAqFk^#7*f9vVt zS=#RxOQz%3>+hvSuHYknXj7h2BEf4qQhxfsK29~%riG2k!f=Q@il?n9H1~-qUmoP{ zDR1o*zH(H6!fEmu+1V3#p(`Vc3)!lYG(kzb{St_l>0wd)`xfwirqT-^I@WY*;z16s zLJ zi)VHYSt@shwm6N&>DoabL^2{j-;(}C-SqO!SftDM2o*7QMHQuHY)A2667yowL^m;( zX`*3e9^r4aa0aa~~XEM8oinyu+h)5bFST^sLl!tDruXz}+ z8P7cQTG9zK%l*UX`5&ciZ6$6oBDuS~PVThibe6_vJ;q-C_Dr0fI8oMKRBzGl45hjOmG#fR%i=C^0XRa@D2>VnJEMn2R;+f4$QFcAa#!@B^&N+w=AasVpz7 zI-UN`E4swLME6JUk`+vMF}~cI=P~-7{17|Z14DtHh04&OSJ+)!>n#7vVcyw;tkc>` zuuh+n#V)-r+8*W}AOQ4CtBd$=UU)l@;V%t`X={6u7Zgr!vbLSSF~gU%{QIV@Ml8ur zp6sXQ7Vts+{Ahsr|9>9ficXuptgFeNg*89jocCWYq#OHTfi`}hp{N{=E-;Nt~SR(9wNI~jJxplw# zyFFMF)z@_Ro9*>e>a4A%oIQ*>qP=!@Ct%#PA4ijEKZ@yL0xFOebtZY^NfT|iP|WtV zQx^a6_Aro!9Yy{GG2Mzu_f+0*cAQ`=e^&(O@qi0G&hJ?n2&)Y7ZATYFff>ee^}UCL z*8l3?j@mnCMg8RPm&}n~qMhiqqJDB~L{oa{m(sV=8B!5t)B5Hrm|2yEu7i~%T$Tfc zWe&~8*p}eus`w##&ybGjv}YX@UR@Ju50aVs`EN}Dgag)2pYCae{~Us{uR4RSXZhPp z3{IbVf9(uBfQcjnLy*I7bao&ZvWv+*1u76JR zg77SUXZCKLc$*u(Zg0Ix&V!+Qm#{w4y1IC&HR-wY+8h5x-!Dq{-CS8y&OJQ~au59J z53MYh?iu2G(M@{>R@NKfjc{1c({*oN5r$2(2VC_hQN!!ji}g&lSB1pU1{RQAnF}w< z3C|+2lU@d(s7|W#{0e-Bd(RU&OXB$ft5f$X*^D=vf^E4%lMn!POeIzRD%){oHR&a; z=$y{$&0=i1L6^WIA+6w8x$i3vL~E?BF6nFf2{(1e8DoOBI;B#wEM64nAGP`&NeJ;9 z8~LEP{?L)4`ynhFmRBXc`R4jx!8iS81Tn^*e@Qq902^AvK`Wd#zeW(`7Zhd7i-{Ey zFCKO!k+yHUDrw!)@;yW_Y56S0%A&4)pL8vWhsQQMhq4u&OUp_d3j%DhgAPp$Hy3zh zre4TI&Vuj-@WM|S=`22f&{^O2$V;#P6`bcgu)xfZQZfi@K|y;;18|#fBmVJ($#bk| zujJDG0^ZyJxB(E}@{fg8dKL!RHJH>-(WFz|mWw~Nq1h|I zoTAI7>pZJs{`~|1pTDJTDG3wtP6QpsPquc_m*C!=wf?5C_6{r}1&o&WI_&+0fPObv-^;{X`IfJ#X~of? z0J>R_;~C-RS{ZVX4ve_M+DdkHJl^(j&Kf_&R}ogc0uZnx9(D(CuI$RhSoP3t%P9!A zE&WUJ`%($$q<9|AxV3{Yb9nKd9>msJJa?VhK^n;j+IPp=EdS8H_JTcbU)(n<1WPa7 zjaxEpera=DWXeh5cq7+Ld`9j9k*#0yVZ++7N6f3c=xl(@Ol_^^r}qjvJy)b@ARuF zCw!?7hych50M^1BK^b*U2{cOvo062j2OmVqFyf-(nv1eYAF-s z!b>^_)LUChb1C6Ow$tNB-dcU`JD&F*4_k}l&ey4926Tx1Uu*Irl9xB_>pheSq3gmAs1JsEQxlI zW!a$dx<`b!UX^^$8y&fVis1Az2h2s*&5E!Byz2OSk1uQAi^Db$X(^+Ra+>=gI7v#C zxcc>kVgKV7?Fh&9wE{JftAVK)J5v&v!PFZ|2+hD2J^`%lfr76tVF4*GyKCg3Kuk&?YSLlp|l0Jw~oL%$FstqGSpB?0t zcFh9|-Ga=ZLL^%2Vm9V6q$>e}l>Gh$?&Ai_}f3>o4Gk|h=da9IQ*79-R6>Cf&%&akYEkZa8$-QZ3ay4nXjS&|>n>8{!3_NOADqvv zAZFp?nNLx?I3dH|id*AU@ajTER|nw(nW%z%PW-Bu|NRXyGv!>2jETV=vXbJbyr>`$ z*gbB}=YNKiG3;JWNSL2W?__Zjil=phld3r3liuU{nu;|!JJFaRj}&M^xQDEm0u z;n|_7j->y8d4MZnJBQfNKx+~ko%(5R*4`icE9Z+#g8T%6tW4R%Ug}1w|L=ms{^2GZ zj};$tuHS;&*$A|;$s8 zKD=Y>cM@%`^nQ|;xRa>AufqE5pLxN5oZqhu@jcNwKHOQ-5x0c7OQzx~I0FgnN?ohU z@`XGR?e@n;h>;G1G7{=u902zm9?#;uw($Lr_jD#%ff-2L3hHoO0S8IA>uc7|f?LE)$`{Nb^k&xaPD$dfgx>Duac+TJSCipY2}c?h7LU?FoVLV?q?gGzF0L&P z8T29LimGK?rhb4b!R$a`UhLit?D6ba8ubq{n0pg{-#+ZG)@$E}7z18&AXqfrh*@_Najs>?KLw(^X?B?0UDeu02MnP&daqbK-4W* zP!b*xUqgX{2|o#2oioN>9uT}>LDI!IB-eRRbO1X|{Qan_(pKD$B_35MiWo=j^Yu;j z5ABJga&w|pKZQ6I6Am>|BS|Ea$T~N003M==)h-&u|@*G|7HkI@HA!3W#c^vn<^_ZXT?FMUR>=b(05MUidiEk0Q znRF!@+y-Q9YfrcSs=7J9hDBOwMQ%MN2i*#wso)d;SnRkY^#*j+h0>`TbJf!AoLTVf zM(DJZak1>t^K&o}H_`N>RbNGbTw#QD z1@f*wOrVHLGil0LgMs;d=<2jRr}n)!3+9yxcIc|Ziojlzdo6ZTCBqmsxA4-fXqKYJ zL|JB{$wG5+7uC`fe?)Bjy`8Z#!IiQ63f_buf-Cif4>xD7M(nEBPQ8K3TaF1X-vH8a zc~Cv2h}x?d9aE-&LGS)Ff>A{H@Lmk^jO-tJJlKB(uxL97w)c@pES9ee%WxXIWOkZy zZP52F!jIR=mm2hfO=1zBgvSzGiUCYd5I0WJp^kvyU9!yc3$8cW4EmR``>UQ7M!(fmTt@-1an>DPezIrJF7fku6e&}V;RwNP*8-1h*AM` z3+iv_^iZ6Ea@^{W)&`l04DQv}a?IND;d7AO10^zUjBYA`|PIl2po{&qM2LeC3tUXg2k(}?v``!S@R`h;+wo>t0wmIi5ILlO}*Y607 zFt2p}6d+lT&wDdq409hTFw*vu=3|r|E?MNif0S^|%fSpM5$I-@9+STu0UN&GXR%Bu-t#)-kMAReH8%%wKmO0 zrvNddKY`m`lYwBfe3YdKL9R^K_}h45a;?o|q^?mjllvHofi&|7koaz(qVg zBUjc$O0)Vblp%~QN$ZRtW^7!5V3h>XC1I%?is3k9a?i!?J3XKnxgGqx)_4?iIWECB zh7P+Iw5v}*zDSe#mRNUm51S8Eq8zOksOA5WXz}@ zb2%`50Zo_Qh4qIWp8!62v2*POt|9zfOV-q|owz}jV((3}4P+fhd(K_M{>`!;?loNY z`8(N|Pbu=8yo=1-@_n3(V4zqOG87vAy`Yk@PF-jG+I;ml-4z{@QIv+-|NId6~QxKO)|$G>DMbiD~{G zjywpOSGURe32rtOjm)vajlmJ?ZPt0CN5D(tHkIDI6Vi{d;31m8XXyYy)2#=f?a=43 zE9ZYWr;w^s5AT0+bg#}K;JLYb8|bYm5SFlhz8lb$=;Tm9g|D{S_p2W;OA`?wQr=t3Iaj}5eHDz5S3kHTKw9-p zYL7fcz()8fw7tincp=J&<6O@@m|r|$zob)-GK%KF_y=XaCLUVqjt- zwA(Vf$VaZ8`3o}Ax&7c=?=fsUtq}z9CT(O|1_gLB9#UsmevX z|EIK8rNJTxFE7n)+-gE(&9$#0``%jF`uA_gxU!HbRNVk}4Y-E_ zRF2>=1Hus7&$8-f-LtzM4xx`rIQ9Rf-kleR;nf{E(3TCX-$zys@t(gQ)vB6*0bq(X z2lt;B2uG1T*-x@;fHPE8d9{cdM!#t;*TF{>Q}JCD$Er^Ji+(!~A#rI7#HE#pOB#=? zvd&z*^Z+YF{a*O4P$1Gl6sS)+k2B{xB<}-~+g6|Vac3f$jjdfClNkTmz)+fCXVhfW z|EcS%&jD?&3I&ZscnT<49EVX0*?txdstEV1FaV-VQ`Hw_vV_KIEIpLiamBfs-9QB*D@APZ!x#%QzjY-eX&$SCg( zCbs+InTAeN;cEZo`T9J#b3-aI)f!NSo^0hpTeBZBEguKnTb>2ryW{qAjr zE6R$j2tNrkLukaTOA)@>XKcP2;(d_i6|1&*8RlpI`2qD9iMdaYo%HAEC~B=?3u~E( z%{5vmS0mO93fqCq3bZ3E(-8T~;?34t@YVdNNj{qloz4NN&Jdso8-o!_zph-X{!Oj(!BX@?6SY2$jDr;;1AukqOGXgRX<_0zZ4)pp;#$sjvPN-7QpOmeJ z)~m~j71?VL03NX2xrYMl@2>%>_;@6q0)qfbrNJ1rdfNwrBn@gt@)e9L0VFPY%;p=VK_r4+8+Zk}iF+M3!6f!I^;mA+8-G-e?eSq#qZi`wbaXm<;IV z@owHF77ga5VBH!Ac;K!rtBQ_jUmCO#!bN~91^%R$cYIYex8e+Nx*j5@ zKmE;vET&k14bJ@%lKcmO(mPt(7cK8)j8-hS{vG1vl3z7!7V*3>@d&NYH!)HS06XHX zHsf3!07?%mZQT_x`l|B~nX%~Z59u9y|hzr}SnJtA!F$-$<^8m)2!_}i&RXXQ=2seeF-#TX_h-xel7z*~|cNWF& zY3i@3Z~V5~D9drm!N_Xc>g^GWd=2fQ9}oCatSzlIn4JaC!{uinA-|C4mc^>YmaAB= z?!Yg8<7^VI1ibRQ=R1MV%?LQ84tC$N?m>7RalBRxS4-+KMA{2YY4WR5gg(g0O6)5@ z+E8&gZjdeG^SWqfpF-Yt%UNGj?93$tpziLFD}z{Mt}t~lLx%{}4V3$4GN5$=k35S) z$}#Jh>DfWV=SvtO81Po69kyM;D+xkf^Jfg{@!>IcBSaSuFb7oNX_pn12SS-_jnAX8n%6fZ#I*^%s1=O_Fwb0EC}sC@s0|3oMrt) zF@m;laBJZIz=imk40@;l@5rQA#C%Pc3EU-t+-KLIjl~RMFaklp8Dl)Ya?j-#L|~w7 zcfpcO(FsauZrg?Aj-*VdS)jUrtih5@12LqxKThNKK{GCMO(8+76!dsf1X!11mBcje zL+=^Vdc7iFfMGZAr4ZXCpkrv8E)uGoJq;0R?MZ3>HX=Rv9)uwi8-# z)n-ztvE=3o!=rAi2*#55S0?C#RuJ027q5Z*Lz3G#<4pXb>4$4IgR8!znFkV5d;P^p zWAw2gIH9&P#P)miZT`Nn+xbP5>a?E1i_;Ca<`&w~3yD_>T(wGaNAiga=;z9NwqsRUbb@COv0xz$LBbG*O5wS#CiL-qmK1AqB7B+%__HL4W+eI z8KA4Z9Ap!Qlqzq^Pfw_$Cvr+`zMF++oxpc9jxim7?4Nz@gCdCgy7fe?IvvN`@tPa6 z{gIqAV`P$oT*wLLo$v7Jx6XY_mTXUYb3+g(iMQ?k!d!{<>ucBv8n&%rDFsG5mOtpK zfG(!Fct~pO0F@$n-o2q#`R~7k8AZ(jL}A$eY3o~n0tW7(z;_c;51Xs(M}zb!NQCD{ zj^C4!Ji%kvMbl0Yx;P#==$O;#N|VpIP{WzJv!g_4GU>3eF#R5zua9oj%PG{G1;KG( z6KKb7Huc>hGi5qPUa|U#W@TI@ceXo>+_oSK<}qlvBgT=jA33YJ^A<4&!LVJgz@RI? zF+3sNOOEI0OhbCku8@UqI(G|51P;OfzNL7^Bvr!MsuU7@op&-%R!rqgy&al@GYx$^ zzj{mVx!Jn?#JJQaIJ~}qO>OkSVF7`bDLy#5*WuG^EwQLY30231iB32ndn=qoXK3%Z zYk2Qn+Sk|OD(!l>J5v+vueY*9E2F<8#Ee#o^R~Vim1tL@8g`)`b_WC+ONG}Q5SAUR z8S2WMV6yxOz-H3w%2cp1@Bg^lj#%L7X_jByZ4#Y(P8})J&46vLU8+TAK_EW62)qHb z{nwWdA|G@SN?M6JWUT8Mx!RD%>>i%Z0{X=p_T^yaWNknE((kkwWwFzmSDoyHvovG7 zg21?GhGjD{;ROcIRmzq=dJ&JjDElOH^9L=SN0 zzaU^e<{T4U3g>uUC60Y!9WoZwTUV}O1c&>L5a*5%w=^!iXY{($YT3e$9A4cyQm{RN zrkNHFb?wYW&*c{Qv8&_{s!e6ynCbSv3+FMuHsVVj8vLF$Oj7B%I97nz(nx9Ue5plp z$iN^tGW7z)JPK14x`K5CyLL}dZ5Nu>_@onR?^j*|=TOs&hUl`%F`?T+Zo{&G0( zdcc)OZgW`7ql(lMb~JK8WSRQHA6cLUZ44492IsvL$3639|lj z-z)1oW$tuQXQ}$4>2!rv4w)sK;GH>BSAD?Y{gD22(i;^}UWd>jj z3cjJ}`AT$LYm9KLX$s7CDM!|!?1drY1ez(?mvZxwp$<1&4}Vym@BVX4x0NW59lBgt z7_gN>oA7sY^efk_ixg%pRXs#C_z*|j+k>4`|LO88+yrp%1e zm*Rl~#T;^7!Dbcw%(CT%ud=>p-hh+QUAZd-lNWov9LB>t^NBg*&Me3zIZ8M8g zx$3;}(K#`$eD#fK=lEN`=Yp-|OM;N^wl5B~lO!i6TZBL5%^CIROb%Tzoc2|p@jcAV zO}5yrzQ{D++P-kPl|^^>?NpoIKvCEAxq_Iaa%Q2?`-rm<{+?) zk2{zaGgF~HU!;DA?bVy-MzZ5sxpP^aa2Dit-ZpDZ>wYoy%))Cht#DeYNpS2zO8V(M z{QS_x{!mvqS`%L`TjI6i$ugI=MLK!KKX`w*(=rqMzy82TP z*x(8PgSY^tIesnfx2aLN>8^0WO&-514iN$llEm#y&c{*Y+WMYcW7X| zR&u*P9OL@fF;}%Kc;Go=zZNo2u8W%qjX&G&5@?*05t2ST1rFNSdbT!n&#TOVn9RV66Ir z{)xt}__>r?iNUPV3PR=}DX;YDSpDL$K(Irx#RXFjE1@5cyzZD)o=8`lOG}`m8O|QI zA12lbGC8*(f}}m4;56HU#@xk;Os*OTyRyAHaQbH{MT zsHlMXLyH6T*1d&fl3Nkm_>}al3@)c65%`vP^WJq2E6aH+d^&Tf)~&rrG=DSw#cgn~ zZ7gK!PIAR@gAds))Wfm{!UIv^1Hz6&2I@3IV^1sZNZJp`ObAaUf5)e{OEi)i&+a{u zVALj4Z`Pd%XFc9k$o{hHilybT?8S+M?Va3HIj5m`nS1G!zq;C3wX=48Fo}0RSi_=|$ z7KX40F0vVUhu@eH6~HRTX<^z!DK2VPj%E7!NWjqY@@Sle7tO)zC>^9K;cu#D>_fhYAh26GYxE@xS z>|u`cC5~H5Xw5Ft6+RN#vwIR21UD8Y8t=lSq#MPJ^@V#iY`Yrc%5HqK7V*LKbDv7` z!M^qgO=VQpPksAj-3zOh_5wGx@#l%{4s$M6ZwD`Tl7&v6dDH(gFkc4K3nV5z7)4Ogk+!ei_>1{ zH%5nKpDK(Bp=T-+O5$>sLOqgV!cr_Xq-)Ayc*;^gTV}zm{-3^W#@44#sMV`R>tB;q z67D3F>`U809DKPe_#&Ax+F$P~yW86I{Yk#%oCZ*z9mtxiQ5C2@%cE{q~I&~fzNJhgX{}v385@JzmC-}-1 zA>;b}J?RdE>G^=|PL9x{N9hqeQa;fJX$vK?N*pTJj&VIE=N%9;BVOO3jm+hdVVFpc zjIWPJU}3})WYM71c>+RU*Q+@<_4%tqGCVjj)Zb(cVgt}%f15i$NIIBWFAS=y^JYZb z#aPI0GZTUY|1P^qT3wQ=!%TOlMS^YX>(t~oqY@UE>fc0uyf9%=bAj1K(s8!d!Yr&- zQuu8}d12w1R`W87PefQ*-3qL~LEI;1 z(B6V^8C1B*YRMQ1| zmn;4Mf(1+c|0(>jYc16O=>@?AZz4O5=!dWHF3Au&=A$!&-cTZ4Vjb1+ZAn49E$B&or_X->ytt@ah> z2r%bjyz261hpSHqrq$V>^3DISE@w4h52QBt!CnRy(Z=6H2@2cz;64$eNVg~T@U;M& zx!j9n21-k6$Dj$d(skS~MoK7+@<(sNOx_WopjYr^_+fStfB`vIBN}p3h7kid|KSw4 zIZ;}feYh4dDCMZv!=4<))Ar%eGr!YLsAE`5@+NDq>w701*ZN5h?}Jg2o}|WXND)Q= z(%M&J_y}V=j5}f2q%W?lN)Gu#$`vMD>ub|N%)4NJ`(JWxA~bBv=0~#dEDNO_phA{Q z;QQsG;YwSA>7tXOx0g-eym;OsQ>y!Y3^5uwnsdF_98IW3E|}@>Cd1V4kRKjLwr!Mg z@ZPAzr9{&mDp#<@sSyjfF{XVI`x~f`gN=lvprD8#))%>l$KL&4n_6+`C}~|It|2z{ z71;n&>_GgAB-SaWvmX+^2^$h&Pbf|-K>Dqw`KDRB!hYz$0+mxfa{2azR?_ z_QB9V$8A|Ra~l7EhgoAHU}p000Vsoe5jG#is0H$H*t>3RKVNL|F<7l~Si&#AEEcGK zc}@eDAmtD;*iX5RDM2BfcUOwx!|F@L@Xiu*=4D@k3KK_);mezrE#1@kGZa6~N~0z) zPe@^f3JmtP7Vr)agFz*mBI<6fdmks%7Ni)?aNiH}Do`9a55>=O`MNuP6&PJ9@4Ynv zmT*CD10eA)5T;D?4B3g5!75 zVF}zgpHvs2b>p^826ofD9k6~eyAZ8B*9mnTX<#cpo^&91`fNZNv~37DK0On93Uo7y z9l7gf;yBd|(+=BWi-MIsZyU{fFr(}fg~~T}j{$7>G}lX>yw)P-{0gL;^_X{^DLn=P z7xRY{LagB-1G>i1aeKIo%+Ll)8^+mTS5sbJ;`JT@WdJnk_-g7m+ku;j|5!|v@>8~r znPSq_v;A9@63~#&rP1mHUvZ<651Y**HR}q#(%6mZYjQ5cou5Crw#DiajbN@h^Bl6v zfn?Mj&RT2c&aZ@T<-kIeyLtF9Gn0#KvXbC4xTbj4!YHh29u@589Ex9Ux~}8&0a6~E z-uVxfS|UT=2b-%{-MK)zLbzG_HW>Eqw8%G-Uazf*-cDl~w^n1(8E^>~_FvkTUuPY; zDMTfi;@;a;0z}zyd zeS16EHkHZ2aV0dhtz)LoogRs)H;exLJj!a1Hgn4Q?J+p(>MdR0Y` znbqFc;b>>D4SN#j74pU&Nv!kjn{ZMoN9@$*sE>{jjjMAjK;k4zB!+{sEKM)!oWf(6J{u@|0XJO)?^zIb$^wr#ekpIvhWQKiO+TDj zW4|*JeRgo)U}HnqO3EGC;in186vMu~6JS(sc^+ZK*^MdG>zZ#uFT_-{89HL7(U*K= zs#g@r`c_6BR*g5mkg@I>xHu}xsU!G-DYZKaA8-C{FbPg((CQB$hk|9TUESe`6jCf{ z9|wAD)NLn@Rc6JC8`!l2#J%av=@bYVWknaJf8Ser^IJw#1XqDC<@LahXQ%jU2})dHg{>#R=)i|0U6-Nuow1=02-p2x}X>4%YqGUSa7P4<-id$fN;1 z-U*HlkE98;lY?0D3zNnP@jA$)x3XdD5}rG78OHopp=uUmL&(j;NO1*2c~9*cwAwzK z;9#KBEl0K^TZU&KKzzS-r1tm$jto@OZL4!BX=lZT)tD4gu-n`IeQ+41K@`FYG!7Y` z@u5w|?$_gPHe*v*hQO%m90U}wlIki&7(8`sh$R;L5M(;R+A)(YG}yor+u-OS{UNhW zB-G)y=Jdl&5v`{g@rSbk>EnW0TygeTg$ft?g7<+U5qf){l5aH;VnxM6WzM+6=8%{S zDk1u=gDPzgKC`|MHDedh87VLTx9(4>xA#A#VpuW}b>p}5Zb<*xAj zl^0GyuP$eus|-2OwcT<0bXJL7rwiqm1{Gruq{x{BRct_Nf=HP|tgn7HuN6;%x_+AN zXMHsm>&PR`PusP?*pC79avNAYVg8GCD6Zl*JqbZf$X0YnPv19wcl@|!wsyC;?hWtgT6TG$*JF>a$ z884EwJ*9m(7Ad^I8Up2!=^FnWa-@6*<#*blfspcJEr-r|Q12o;aPb~`zE_w_BhRU? zI$Q)P77kH!UiQI}vux7xr#h-DwFe4K-|s^$nRj+_h{oVmhIUp-e2>d5tApY8^P?rb zc~Ru@7E`TOB~V|c1E|kp&-^_`&wIWKY8gvns%uoA&ZoQzK%f^X<2myI)$$|OYnD+i zP4Yhw7BwOG7}9J#!ZIlk7tcDf<$oAgm@1A#PO|ERZd!d`u6FF3g|yQQF89obnp5c% z($J4Tyc1wOzn{+gfcOLPV@><-4kHM55+I``9=vT-!T@b*e*2U(W1tgkA#Xn1c0y>n zLBMmN-mu#w4>oTXJS;Tx)%z15ikfdH z8r4k#255 z{0sbD=N5R)Q%>Bqaf>zKFp5Zcpwb`vMSeEsjc?Au|IL5aKgu~M`AzY_2pCH|@V@{z9FGzSs&^4_NVKT>KH|8ysNJ`$9wRmDM+Iimc@wpbUPubn0gcnEZyz7Q1Ka^AFJ$542|$tJDxLJpHO^{ zl<~Pry3r_9$%_`}hC_SRL2pe*DiA%UwKfISDpEFx-xZsXb8}Cxf%=>>N&5v6qR}PZ zjT?KK;-Xozh;sY%Xjd0-2|{0c>dUE*NN7dqkY{_(a!p(%`f4-Tq%)RKhYw6B6+0-| zJbCR@$IN<7$8@@;?(LKO5QL^fOW`|f9UGtapnc)H)l73Pl-&964aCI0`uNtS@7djA zsGcY_dGK`V-m;=j>EoWAQ?HJ<{CfMIU5Cu2Zi0%U_;EQCHha0xqUyWSYZU%h|EXWE zP^8dz5i6$x(y9i8-EFF>DzT>_?8X;7R|4AS#*p-Hf2pcMyvwsvmD%$C+#3J)I0l?j zejUM3{N#0jI%|(p5X2jG#J1l+8SrXy`7yWK+lBbP=gfUa*fL{-1$Vc5(JzkN<*&O| z_JoW7)3uiC>E*qi)I10Ae_RYW%9?38bo}y&1X1Pg!x|`({tG7$6u53Hbr#4)FAQsf zyYQ40n%Qp&MFd*AA5cawp+`@bd;-GM)vB)uyQ84cHLwTNY$*1?DOPIVwLUld~Mts&kIxj zZz}<)SFG}c+~}_3n4SNZ0xh*7q?ns28yg0YATtPSp;glTf8;BOavV)@3LQ4=`{Czc zHk&Zcr0|yma;SKD^`W zR|L<_=n_J{tu13=vc-(A;w{)b7p$D z^y{LY9KMq8vm?lS0kLK(LF*exN!A1irA03TjgE_tRA_pgpg8Ti^}|V4*7<9Q<$_@y z)83Um)^U4#F+#|i?&fsdZ9g~Z!KGPT2!o-Eje9LB_~ShzzqJnHI2sxnJ`aG;?D9k# zj#NNdcPIR3N+LHqk>E&IDL8iV8(3Q{yiZ$pm#4rH)W9Hlju`~`63FLvv!?XIb1L4Z zf6p8|m+P>wlqN5ZmC!o|4ZB77DId(V7(8Gdna>WZnfx%V-4Y`bqoUJJ5Q$S~o!G`G z5*|7HUkT2s1F>J&%-0HhptGRLK6zV=<2VKt|ek*Xu=9z}v}Y zHiUmjb>gNu$H#Fv7Ut+ z%D7urH9VA;%`XL6wb}4@^a3ZK!cD?pek>GCD=?HI^DVJ}#01z_#XxqW#VkJF?{h0e z=vB1$9yi#6`|qCd0&W*==0594uHli&EyM0Kyc~B=MF?HAw}I@m`=6*yo3|7KwIHOx z^d*w^SEW>)A}#`|7d1qaN-0)d9k6LHk7La&Q?f)4MBj#d_kuM3Kw6V_;QURZ0MUg2 zoENl9X<5a+hBV#&)>zFzbDn<-asR&Rd*b5MLC1b7>>^GZmC8$fMdH7>1w($N)xa%f zqP|?^fxs*YTs5dq_cf;)$SDYby8n2=dj1;F5k5E{gHUP(3mRtry#086FGd2*kz{!k z2&KHpI;XEa*HI8kw=)ww4T_BTV=w**!o=Rjc32N8UcJxs5_cF`D4M`sfP32NyIHKp zT6l5POS7-)S<>u4Z0|olM-`Myk!j04ngOAf}y?R;xHRWY{sO-I;`zc+|`DY3~; zhJ~U8w@v`>5Tgq|OrjnCG_wnSYFb(k5Br;rTS?qRJqSL2jgYrZ&vX4o*DQs(;F&$tm&G! z-Qr1nI&x&yLDB;7WU^UhL>IEM{XWWZIrB_=j(rzI`_PvOG1&{Q0k2@CzRl%2_wOy^ z7g0<~8FHTpb`KYxh`cwKNIAU%EC*X{&yDvr`OqH2+D2qOQuB)?YE*_(hj)N~STtoA zIv{iYnEFMs^Cix!T!1i&cLGK76#Py)kmDqk9VsmY>_MPh%Q$zz67jLsAZtCf(8E_w z*})2ve_Y$r6M-c@C248`Kh6#4pPHFsK&ohcq7!0c8djm>mEuXMX1y0b?nPC|Yr0!j zF*1)}O~Ov0sNt*#*np5#K6%vAS{Ro6Z)vykTzmhJmdbZAqf%(@Vus~-Up2p={-d)L zPqnk}lQ9H>{>2i1#Cq3-&Wx3M1ayHAZXKbKi-V@7Y2abIe0I_^b2lT z4<%U?dY7-M8X2(l2ziz$wC+ZM(3&eTwo*v6zij(>xi1YTYl!~-)^gx`3M~3P< z0S~3do6Wu*!8c?4QsrgV0O$n$$4@Zu@}CT9>6!p3cqcN7R=>Y~C*ACMvZN3Bm^&gc0PRA0J;UVm5>NPgl0;CS{dCTE6eZ z*UCW4f+pWF@~#H&VbEGDp%Y4mz*xZ($3o%DgoXv0|nVH~)2iRPDmKJ3Gq;HX=GwjX2&T-~{E zowO>+`f81DY^6UEmU8|Ns!RVpvSmF$)e?b2Y_4g+R2Y|EgC)snyB~*%$)VLv4q-6P zF+|ovF$dE>{0hf=q6Ruo2sv-A0p9RY2almdd&F1u_WbFIQ)?sEJp>BtAt#x&0ciEu z+i^RBCfw5!Q~ib#Q3)aOe2i>^I!Vdq;fqoFn(o2+y9aEM z9<{Ny#2~dD%7}x{L_M<3Ei}GKO-+74m_7fabQeu2a%4vouk-iTtj3nd@fcppAB<{6 zb7W<~F3$3A!!80Lg+hmS9JrY|t z<(F8-hqtV7C1p!8<&&dK;=h)wSZ6SNE7KNcf}oB@*M~N=kk%zQZ6}FoZ&t$IV!(ITh@GsG zWH?q;6S0~}@Hoa^0R{(|!q8B^3L+sGU&b4%oJo&Q)x5@+H2?lRP}7{p$n$th_cvk~ zG7Hp-n<%aRHc5evP%puZ?~c7HUe0!S;Bo&{+n7_&L?YH!mAC;4F!wyhLqpglOg%87 zeuDK;P|9{wWp8T2N$vy`n#XKHEO`ndug?kV=SN-Qx?G}6=qM{|1mwcL!R+Tcn(Lcv z#n2?M{T7z`|EwU(bvb=9QWgfN_x*vR#={k)--R+=KCc^N?tRx_q`_Marz>|=Ia0sC zg=)J5*!3AgQN<}Ssjf`UXWPslqnd_2RvbC=P#dtB{!B^N-lZcK{Fyn)ss{MIy|7>8|<@U+UgS5NSP?qr71( z8~pjsUk^~~6qpm!p$AMnNw5?@W-@vmbB+%vp&pDzZ`}<40Uw~fjhKiagoQw^Br+X7Ke7O4iQPSa+AT zN&RW`Rr_I<171|6DKR5UWf(Cw1brN-H%F2MV*L!>46J^`bpTcC!a%oOZmThtK>OyL zDNoE3TG&!DvUjs+Ry|m}55+4_ikHO}vWLXtV-VGR?TC)jPrA6#Q~$S;1|tbZt?ssq zur_F;Y9Tr5|KGn^69!NzDy=N{y?UktC0y+bU++{3W+swyvPk9L6rsj3M+&1vl*JFQy+xn=&SpBk5 z+j0aK8awb_c@6oV;Coi~jtps)LW+1o&{>J}>1YBWKk!O*tW6uz3eETR;pwY&QI{^6 zw<%lcKQ&M_2z*_RhR(Xe_m3C>>6?sdt`P!U!Y*_ON^MDbIK+j}>O>B(pITh@kB_zS zop;D2?jb-Q)J^$Y8!42oJ<>HWaGPjU&(${=?mMHE%e%rQplf}(?u4_|*T#6NUw+8z zg%k{8@yGq(1hryZQ~zcH_s!cyazsTqEyltjlY?i%;@**Y3H~^5m_1r-f>dx6a%b!L zpeO$-04UWpHAg%QQ!53&zv;RPbqEiDNM2RVeyrM}6>qZJ@MD@QEWvqZr-tdFhfKBJ zZxSk?#XH3gk!qHBdO(%^Ye$gtX8el7^E$9Be{}0+gQBKKe*?31q&^cjX500D4fj6@ z1DjO?6}_L?GHYu33ui5>2-+Y|Rd*v9!m74g>4$PwsRk)On10?&^ZuRws-_n>%8-^+ zq^i>eMV!EwQuRW{&^6+Pls-)sXL>1>)puvc#PKW&1|lU|q*X{#c)^^6|2S|iO!>-~ zL+y6n-m>ihY+tYhDbid|x%8?4YOcGG{y$Sxt&&dgELom)H=&3CPou!8z73^|&7Z-*ddu^Fqz8EGIQJ6qWci{uJ zSA6BuNXawJz`&r?2$&}3;{)|B>`mE7!DT}ijU`ksXB zAEJ{t0j^6P7UO*Da{}qxw(~kLc$0r%v@MEKW$^Fm7PCmG5KetvqrC${g?7~I6Ih|* zCoS4wQWN_-V55R)Wsh|Eg+^49|V@hA2}i;-J; z+fBB+GcP&wt(o4%3~ZQ@Q99NDUI;;8z!N zb!?|dOG({+Mb(L{fx*I#rwNyh3D{Z#-DJy6W&n*PjLul`L z=QA?&tpBjM{CF`Dp%Hdegs2iU*4FgRC88N6i+VXtD4KW-JEG@Ipii*ZCQ_*8#1oub z4zQa>(MPs~pi0S31wCZnhld@(mY+jItv;-P*!khqn4OZaV(D*_MO=c5Yz)>SB0E^T zA;0naDYN>mx7cxSWGpx{3esB_xu=8p(9=rrjgVSNu4RHvt^~ASFLqkkJ;5tqEg0TbVgg4m@<(srS@5;lyO z$u?iSLr7-YA+BsdL?kLg^cdI!sXv;LohIg+q4$+$yWj|Fe7Id0N=zBFp4^6?65oLT z|Iz4uq1lnB4cWKFv`UJIS@eoj|M_QUfcWA_QhVVfpPqXEd9yfZRJF;bv;4F-@MYCA z@Nl*zbmx8^o69F`C*46Mj-t8n(4bd9lhR{nt<(PT z`TF(iHtnp|y~Q-fWqSoh=qzMELwH;w6e4sNAhsxg$7;V{M4@sBf8m8@;Ptc*y)UYs znU{lyppls0<|LMk_s`_LXuh7CQ60N#IG zU{Ukvl#R4TMd5uiv-c9S7SOd={Os-?>c}8dQIe9U)q_wiE;l$Y5@S#FgdduxyFd}{ zL0D!^g9c;4bHEokxn$%*Q_+zj9|!HfXY}s#fP`)5T{=s}MP;H2C4|ZtY|-#x(~15k z#yK-z5C^?8+k5iK>=2lDsd_FjyRL@XPs02gXOEsE>28AL)-+K<8K^|9ec3E!nQfGHG_#p86ZV_qQ9u^3uf`ND1rsE!0z7NcS$*C;rVQN~k-b!*zJ6Llq^^p9qDNI}m1b9l=NXz&+u;r>_6%wKvY*-&Mxn!u? zPt<3;4&^m&5>;=Yt}N3t)vW*)-opaR3c<0mZzq5$zz2=FT|lmRf8rsI6_WKJ?7T?g zwhPWYt(g|3jf>Dq`v9CLGZZKzX*<00EtOSNx}f^+0R+}!grYOYhSyQnH$nb=tf>nK ze{S1&KOct7&UcmzZ}=PRy8;f;st`Q*9)!lHrZYSHaPR4>pAxTdT`jx^q=Zu>pD%Rs z#sUxI=;_FgVDeYquPzJ2&S7x<@qVoAp8JHS68slsegmKWsIMJ&g8&3h= zUVa{ux=^FH2t6bLcKWgT%;cxI51-;Pa7S#|>Fvia9ZLIjwMly@wb3)=gl+LAeA)%f)coImls17hH>?dwbR zX)bGgj=JYBBA!+H{RHF9al`T8mcOY2g$3c}Xy7N>qJxk7ky>yRh*G_W zeUKDtm}5Z*?-Al2kF}cD&JQ9vmVU55j$93OO@g^yTQ(hwNTjNA1GQtDwija1kZ6yw zBIH-oq4Z|`*z>j*_HrU@0EZKJjW5($&uD}Fc>>e#puD>MgxzJLw@py1j=bo7>ZwsqC)0Dwd4j74-5^qL^$+M#0+Nu~VTt2VNFa)P>bN-51@rHw z<)?H2A1A^hQh4fntH2)En@O*;m-`RP&qYudhY^7j!gT&Q3E6=@dno7Z7kTYi!o z;f2$}W@p^A+WB_~xo(d4DS{1X!cr&Dv;w>zRi!Z6&f)q%TR_v`*^l10tfY5TAloKs z7Q26VCI}gI?@|AkiJ|0FM`3MHX8s<*KOlzFv)*B@=WzM*LL(S-;P~B6gn@k4kGsH3 z4VVt+Po9FDP6PZW{*1zpizUcnx~LDR2kzh>dTfWsJi)^*Z6ctk7vcMZdT@wk?k(5z zRCeW?Q!%{qgIF88{!sw@$N4zY*Pw%!tooNgObiPYvmbnV86bf|>pj@HibqS`@-3?j zwI*HV0$3AjZ4Y5#SEXZ*d!!$fe;wJ!2w`z#1MSTO1l=7L2uhvRvkQVEx9QjS58{~c zwnU%;0VF5}s?Kg??F@+{+hm*V@B??_hwtB=Yt6FaWIqdlu@6N>{-5}eb=Uk`)7F1w zlDY>sy<--a`(ATh9_{;TfBBoR6)1bR?aLZlqZicZUSafofJGR;=b{%CVM%-y7%lg~ zRMKm%4N(t6MlbTBDi{`!X@j%7FOLHy^9-z!k#&65UV>^}x-57oUHIiS1#fQ*{ z*0=dDXON%_O3?S)R)HThwA^^gN2Q_|@rLV~Yvw(eq$X`O&lsqrjDN3J?Ay?Ff@O05 znlDEns+IXz5owrSHwS;DnkZ5z(@T;!-8K_aM|oHfIPWHY!)yLGy#OnZxd)vW#2cRP z{DbhbrAFbKOSP{IBHbXI9BNmNU$>QbFtpV~aW9=<`qS`Hn+XU)be;B9TqV~3Jk66< zFiH4^_y1Lya``QRklJw{$mwlC2;7PkPS+sCp_ZW74e$TcEBy?)LF3#zfu9@a2xKoo zRZ@Yp1$%pQA7#L_qf0eSelgDhC&MsAk<@SR$Rb=~!(E^hu&Y!$_8_QO__uc980ACW z>nmP=GQdb^6X`#yycwG=fJkGA0!sEnFU|I1-DKMVn9g@Z`U1Zg;eaGkb_PEcmHWWo zxJX|$CqVHMKki2dV}u3EAA_}7{4aj}OUYJU0^!l^GGFb&Ue$Ohn@#kGF59fzEI}$1 z`nm>^jpVUvmGBlGOiR_b+A*i%=I~Y@1r*vh{g`;l>lL?iUpb;SEv7j5oI%HNN~|DY zrd9g)m6g}f)W>S;66dvE4UUFG0pF-NrUi5I8+^|VdltUnT>sXnAw0p*#+)tX**FW0 zW%cP(x1fF&!t!QYq`jvTwZV2AZi*YmeSfnuTN%iXz1AX>8y!qH*~q(--Ldy~VX2H1 zw7H9!Q?TUOJ%#DBQfO}-e;O^m(>C>?VZ1Ht-k~d zq51Td711evEMmjbbEBRkCEa~#(OBYvdPQ$IQm?@J8;}bwApM0Go{43OFDTUHE6P8` zl^(f+w8fVntO9;qjgkGb>y>MsP@-D@QhzYYqpnCdWmNdO-f<}574HPmZJ!cHM2zP;xQuzn>qg&1_sonu(PLb*WkRF0tL@Olptwi$>U&_r89Lfn^2 zLPj0dwOV*CVbj3nY1oXbeP}HVN7$|Z^}me{Yb zj&$Ds^&qQ+z*`QPneB3}Kq|Wxm=k)J1ULcPcSSAgYZqbN*W43~YQuH#^%FYK+2ru< z&!QksQ8(&@O?bGHM>Q@9Co}OF%~Q8rnZx}|gFNW(4ayHOsF=vsx-5`l!+JiNvnU~u zntWcEV5uRr_g0E*Xg>x6JO|y|Z3J?0?I-$jXXAr?6~w_M)khf|dqmv`r_L~8x$=V< z&%3ca9F{WuMqg2Hk^bx{B4tNSq!m}QzSan9P`bGww#gRk69y5NU{|L&kwUZ1{G%j6 zk`qel=afIUP?NY8iS-79|4e!DLS&A zT7#{J|E82(7i}enhI{hP#qNdMH3SdtT+XZcI4VuEmn|vX_Qf1(bJ&=_PwdN~0tCQp&R#tSb2@R&uT$(hO%}Hq1JV$eB z&_FaADy2!YG-)0*Xw>LkPjXJj*&HA5@49~PAMf|N&i8WB-hDpLv!3;=weEY}%f2(# z!P`q2mT)ml`_xUNTNxp70LlS1@}jz#)nX&YhSiUKORy7lI;kKVaOFKsM)*Quq4Ds0 z6^Wm$AyAT}5Nsd6z4S5dT%8{Ela_1b)yx)cs2qY2IjE6Qt!7CUK05{ZGBv8fpeBJw zwJN#^I4@($BXW~*lCbkrDbeg5flyIbLvK>bZnh|_$SGu>C^JPFnFZnH+H{5=8? zO!m;J>1pV)qy?~24|V*!O*)sWeMAMiryUU$;DPnzpwV+OnpXf<;y`j&_alI7h_H=s zFV)H&U^y40VI-7=RqoTn+0twa@JJ} zn4@k@00e2E6g*<83yqZcUk3N@bsXOf_8Ss+ zIl+Yu$N?_rH25&!seNeWYfoj<>Zea9y=B%B3Z|DEXuX4Y^r&MRm;p&`sDT`nl%RQ$ z7WFr$rKNSa2O<4?RNO3L3|)X008u}9xm&bcie~KNJC4250XT#vYzLUU><&^qmCP#x z1OkTu^4S4Hd#8?MC^Jj%3L>#nU@t=5IH-+o4d5H7Ja>2~YjngC{!fMlWGg=bSlT&! z4=H0$KrMAMuc=nn{B%Fh>C0DU2L4niGkp!s83ESuuhi1X?>&VUx^Vk1^H0s=%(9^F z^XSzEsaMc@0L^*Zd9<`z^9USkzH9*sdqh|WbeU*8>#TVZpXvn9}O!e5fcznY4xD$_bX-*%2Ok&7wWN&W|5Irn|;5#bnitKp7+wAN{8@>auxM zf6*x)A(uVxpBQc*1U*(4)TV~S6}GGxLiO&cAkbz$5Q=nB~vkOYkIB9zWuOk zr{U{O>g6e%B<>#sLHJY9%}92@Vz?D)+wLM`8SkxPlo1%O_JhJFR7p{Z6ZPaR^s+-^ zX+C0LsHMib1z&5x+s|dT%o*5OLLHokN(TT8|6X2&iYk0`&<1E6xiHvrlaiN-5~`Tm z1=9TpcHcE_OcD4D&<^9xH67!&(DCk!nx=VLwNV?=od4jRU9N|BB7#HV9$&S4M~$H9 z?Ud0KpnEZ&`c%-BS=>SIBM&OGJ#R!Jw5_Km{*)eo{C(W7~w!$!Bi7=T`;ELbPtYS@B)AN zg&G{(NHx{mb(&;jp&?8J=*iuvL(TsQHTVDF{_Py&l`cck*a;Ld%q43OT}k_faV5>& z5a3A-iBjZE!A6D@T0?W&Mc{onLti0(c_7JgG>s$!nkF@%rs{uT80kg3Lu-5n)UeOL z0fZ^=oK&67kaf!&!k*qbP^sR6imZZc4n7gd!MN?5lZ__bb%L2KI*+d;=&1DyL?s8K z4!sEwpw~lUtFORUY(Zoh$^_J_rw32SqqUceIWjB&m*c7912x#zO7J&9%R`ja&1%%X z;0#8gH@=kJg0W^8Cm%htI=o6M>0-?lD^89`FbHXX!5{0JY58JD2i0B6=2$lba4`D8ZgK~M59^hCu7kx;Q z9;)Vls*uUraNoWH*rySIRr#P7D-fxJfMOWb!br)};f_zD7BK9qMDze2i_76I+;`D5 z21jqiWI+CbPB;ZzI9h#&%!p)9kT~f0P-zC}h6Saz1cM;{;ckavn@>*XO3r-&th5bq z5NEr&>3rzF+c-Bf8Je;NN+`Ko7@><9%HapV*%!cP{f_GHJ>ln`Z4V#p;EiGrfotfn zV@X66u*wG$x$t~I&*dZ4Q zL?xz435u(dbrFArZAsWg*8CArp9N6{Hxr=SAoT?A872pj$X#U11OVE5x`g@A7})z6 zR5U{B0&bX7^7e$$HG5grX51CisPF_{m+jL1OP)Y-KwBMw6^Y#Ypgxs6(D5&I1}{18S5?+7^` zTwDscj6%dK1D9ZF+Fz?*tcD-!fQ`M4CX?j)9693yV)t};o-a-*FcpEKf?uxB+4q2~ znoyMkfh^?ZsVG5c%9s_DOy2>m*pzo93K6rW2PBSAAZP?vP-g%ousiKp9eU8M_6WYW zODH)p;AWquomytik0v>Jss zlpiFZtkY!PEq8w{cRfAyWhXAo!vGzQdatwZAsijyiEXLkO3I#4d6-ryiL^e-4_b7m z_Di+HLz&8!lmJhn(7NHfE|p&hAB4l!K)I?|j!c)H{mdeY+W%Dp%=y1O!#`3@9n`?M zsjn-DCG5)>3E>Qd7m_;gKn%Z?_afLmP*ez;ycvbfi1(^EAwQO7DuUXs!7G4f%(843Vo|X zh{m=9Y1%{c^*dfrNeZ;yO(qB~p_}DNqw%9M-u{lTFw+vamyAFKNp@8;4^TbY!JI=k z44b}Od*~G~9zAE6$%Noaz;$@uFslqOc-9-00bZgVl8n2k9Uqa0`3)!-B$EL3R_fs9-i4)uOn)gzQ3eBOuT@+u%YFye6? zASdI72vMkwdNa4)Y1Gm#2x1|JfeWO8U@?#^d0b(M|8qB4TtsUSgooC5U!qY(bRRjU zj*AFsH+Q%nIic`cl7d^}0u#P|kAqVqAZa{admQeAe_&18=Mamijyx5bpNBsn$&q{@ zVxSx_&+AduEbN-$d}?d(na6D`vd0OB(99$Rvn~idA>txpcNn{u08vs9q#D4H`1apR zlvpN#-`pG6GJ7Ramm)>tzGQ3&Vlm`!lMR(H9q;-^#z@jPK403Mpz+$B;i-^9i?km* z!^%PER6lu2-7H`(vNf2~o|eO5MdbA`pww}!6dr)dM4y0))d7bXQL2#`OUHJ}ximoi zI_+j5A`lFI2Q>0_(CL@lerVhb$u)r3R;lh32nrYvMYG45%A%Bs@^I^Q#4m~7+6@V8 z`Ex#4$ai{{r75@f@ZDGbGQY2w{VNd6Z1rpu)45*wVc|>w5!0MXMgNKvf9(&PU_PEv zOeWfgV!wc3}q zJCL5s3EaKq;3D^%HUj}l16*Zj*&L$hP@eO*vEjDp2Cw>tffY58!|CUe0q*l=_SW2S zDFP!>KBMeKT%|U|eNFm;ST?wfk$3Gt?{I^21_R*2=al2|Iw#vDhuFxPBf|Vp=RK0^ zr;9NGx@~8Oga|KEN^9?nPb#a*0Y<|k#LGf4-fTD0RxkiDh~v+tVwGdB^wwYUlm>5| z=&3vY&Mf+BBsBGeqUXOp9D1}!8Fu5;6KvBAkS9>n@ zP}R706MncGMqZWHMN-9~>vFOKgnB%dKS1tf`AtFV2=_P;isD&H#~AFz9rGGQBJxb2 ze83*R$i(oSvFroLHi9Y+^@u;tW>%jpqf1(Bsk)-?{7+QrSN609;kE+092f%(IYVIB z^d($$Nk2PRcY6F`Rg;&%;rd;B%)W5}?(03@h!{kO5%{Q>8Paix0;=L@1d=xBd%hqb zl4qTgoA^7O#(ph^M$2Qd!EE{)nN>+lbiokf*x>DUZyz{W!H1D7_^a_+!QJIL3qn4l5(sgK(TR&FwH=&>8;Y z-vn7Fxa)NzNw^VDSH!6X64s;H863YiNfeYUMxH8~y^7Dajwvu(ta8$F9sEn9^dGn5 z&yJjF4*1xzL)^X2JeM1axze3c ztdmUI<*+>Rk1%4yiGab>EqmGiB8uqTQmEgB@j*z*t5G}o+E3Eh?Frdjg&jik)9f`9 zgIFq_-~)L`jMpX0LizTQ+Ja)8eSZT+CslXOG4YXN);|QtXR_r`aX;4*75|?9=dc!KaS)Z zY(TPy{ja(4zr2iI!mxa(?woF^ek zDHf!Xj|w}m-A0h;0MHC@(T^M+iC`U0fPtWc}0d{LXU zY}xaTTyn4WCx#WIpVK(>F=UA`HFuUQ8He%xWmTBF)YF*fKt__DvbD14nn8y(ex*Y|_}tPX>5Fg>xWD+N59;0k4yv z8Tt9REy$FbOMUWy6`~&UY1=gpTBXgb`Qm+`TbZSL*R2}X!Qu(AFNo7Sx2hT6>?Ae; zTsxWa*^)tO!hupxjRr4?>=flaoAA(LzGqO)P&dvf`(&f76NT4Y6EyCN%WJm()Kkx+$eDMj9VnY%Z8owKvHk_KXeM|1uLAVP|!Ldw9`KwG7!;1xaL}~@Z44EW(m6&TlA*nXde{UGI=(xax zTN_-#m>hv|>!aH*!RpyknfG722CDBu_Mj3QVXXu)_%SjgsgyKY-v}yKd0s+F?p9Wi z7OJk7<2|$zvlAsWpx!CfantYbydNB|B|a$z4Pr=FD2mG|^y?}*pkEwO*?j*iZ{Wer zSR-ocH}W$|VIPM;*+XbL?KJm4?#X`!mc*PH+@*KXdTmZ{z*3_Ax}L)8pp-H8M|pMg zhD9X;t9Z)sELYk(lkui(4zy^%{Hjp@wbftZcI38#i`5BtHCUn^^j#D|{AFND20H7t z%qrfK_^)KNxRjnfe6WuX>0K3Rm+T`g+sdU;+b?}Rp#kY$1)7=d!yNU*T!VeAIV;jV zv>at-|TN1H-0e^NMXA1safswo}<6~Fw9GzwhWQyQ;+ zc;VnN4?SaHEaH7*beHW9slb$w*hlx7S+?E#rQKTk#&M^>&5YNU6=AmWFdvA2s*TO! zy9_h}7z@+zo|4wh^AsU|eC7tu+xWQAEa=nv=`|_k(5+90vK$mnU2!_vEO!r2&!(c;GIv6Pb=u^Sg?s3$}kkRwNsSRpnZqTp6hDHh;b);0+ zNlGg~)8PGhOs?4UDTdzd>gvirA_|3wEvVxk-QiW^K4vXBI(O&ej0~HH01tNojE4=% z*G&nRTWm&45%|AA4FQ?yhpM_`H4$X1H@!;Jt}@PjoV(Tb(IkR{kZzzJl*-@wPokH=u;9Cd4RnF&pwgM=I zb|PKzk#dFPHxFbfK1+z~Mv9`Sx{ibe>h+&AA&~9$%d@b$n-5+(UJ}5y&(WJjH@max z37cwalCIjF%qEa`H~`S7UQ@6pnDT;VaiQSEBf+()y=7;*%R@u0CxNE)+P2|TIZPj> zLc+C_hx$tRvT5+{I#6>y(+5)SR!>yGh4wVPSNWa;{cUlUxXtfCglW{%XJ7RGH`K^X z!HbI9e_Qe5gVqxt5hEpH*j4nVd5BeCEZtc^>dIdsh3nC-QYSES#)gIl<9g{CMbpLw zlpd=6yU2!7&nJ>Z$dXZ+bmbeo4bauKKQjrQKM>D|Sg@9a{ZGat~j z4m3O6=uaMcj zt*Fg33m7@yKo<8pJp+R>>!^?u&lS)B>o$Nv-}Fa@#Xv{%D9;+5BF&MGqIcau1aVnV zR|fD)&klS`A!--z?M7XS7n_|DJ(?3P1(BpFguR{2naute(EMKMChoYmo$^}Qzlsbo zDSZ zp|gcGE7sC_y*yUny6yd^GjENM3I?V8ndI{#Xyi8!T?g!>8|v|0nN$)gB((Bbgl1oL zERWwFhYZA?;D`^8#=1&7L;%BztZ(I@S;nN*io1KokBB_D_ z0%J1(vg1=yyuFR@u6uDvuG$SKQyrN{K+`}9t~%w;~hmqij82<&3vd_Nlb~c!{(*OZ;Y4h%g1Zt$n1rSjK?q-QWqPdlz~y4EMpgW7rg7Q6Ug%EbcLmbI zVNfA3_A7fJ8!-B`+;+cXUBvmIv|7MvaNzn4Df^xRcc>jiLR8P%1Ido3m9dFuAebad z%z3lNOLK+t^ht}E>E6`pB;;Zqf-C@Pxzx)<_Z33N5Rd1AAl@&!iEkjg$Ft9KB9KE< zhVnQs^e?v~m3_oqQV^Cmrp~Gg9z5iT`6ebGbA#Ap-ss79)v5pXXE5VR4aj|z3>A^W z_6{j5TaK9kNc{?qz5h#zhEN>6@#hbYg@6h*A#)knxE*d-I+c4N2=nF%x}0}?*;I=n zyDSB(Hk_v4#0NTRof956FxSFyrF)SIVmf9|Lt2CP zs~%W)JYDAbLBwA(@spvrCjpR{RIFg?mC|AnepnT2%A6@wZjTPWrV_#10ywHswY+$z zT%aLLda=w#6OQYyo`JCtw|mj7Zr zQxCPvaek=jHLq%a=En{AKXj00ef1MOCsPw6*=z(OSv~3`cSE8#jv|qofP zIwBxKd|2j@-68A@aB?pZxUsgxwXXSL81chM;k*Pm6{{P#s`vsIez&IO zsA8D3wVO>BN9dj2s^~&Q#f1AphqCeAMJHC{B*dcSf%zYu5yL@a&U8EnX=!PV<;no| zeZRh=dcQG1@A>OMuw*t#Gss8`qLsv5@Y9sRKNg0wq-@>-AqxbLERLTd zQX^aAwi;wGfOmXi0iq1;FzQM>VmJ;usD;X<1@ZOWNKp+wb`Ys(%uXS;gv?EIAO03# zY&5RV;?SxH`zQc|8nSAU|NMxzh(4Wb&J`D#&Kf|w^%-qfH}Xjyjfid4A zpBQ)WEfmjHu8EQsaLT%BEACBr?IpNP6^6&)DDJ(G=qeB0fo5+}vY&y_O{1&C|50yL z;59$ow#oQ&bId9#D!fNb6k>|eO(ZTfEx$0=LtwIzLvX~y?G>;oe1y)`dLEIhq#d}= zs67-F73GJ#qpAdft=C@hwA`tFC_~oPB3ZXYDT+rD34M3xv-@=;{|pzXXS_)Dk$7BYqHdft#So{VW=h z7dnRG*#%%1t^_*7m}wq@V59T`2mUY<*$_VvKo$}U_RwlHs7SdanD3BdE1I}a)Q<*I zm9;Bs!R}>w3T*{;=U_Ch0daE>d)@}rph}<1Xt#{zAQ~4i0>{F4xF9W|FGJUtEpPKu zlqnM714)bE{_D#rZzEpOBgoUTu2HPg5z?hdmb@T}<>+Hkjc6 z7u+(3A?_aCj_2R!K#7)VNZo}Q3XrF|Czpzz5{dtbgt#%3D4mvPjK&_gSRvTmkeaR2 z_RaPnG*$@r(c^^}kR7QuGRl-N>;!(v$&wHOK1W|Q5M6i#-l9v+b7#8(M+B8O@(3`i zDKrCkWo-awyV}pT@A!ncC8*&Z7$|%f0afkq3B-Agz~F&CeqFeo^{gKyw#vn(Uu(E>@P`3x3vBx{Vwks=%cik|XE;NHA#Xb5;|EI9M7yz6`zFroH8 z9)r_=?`3plP_+=^4N}rbituVT0Szo;mjz(8Qk+hm zv*kh|sgyZ5%g$EUF+{*MLj5E_TJc#fr64RbJ&8+EFjb>iFG1MX3-czV+aQ&>M#+d~ zEf^2Kz9&mzEb*Kl^}|FAdm>`VLhvF}nF_j2uizf0F46C)q}C~DXtG}kL@GF%23)n+vOX53Iq>vg(hImAA}{a!D$6<*6wc)(0W zDU`pI-skEqcLPKHs}=y`9`-`YnQVc4%PjgX9+tgi{Cnarm*zs=>th*=3-Ua2zZuf6 z#_)mXd`SL~^0CAu3W?kKTBc-J)H2vtE{AkV=#6e+UNc1zhT^pJZG(30pZEJ9c_k=yz=ZM=Rbe`CR;*Kf(FJ>?hs|pLO_F}bYgHyVo zJp&12>Sp%ydB#$Z|294~K@u`=`pqUKJ_zGGA{li}&4-%(PCceIH;YzE!nu0hxU6tm zW@Z$EvM#D9+!fCw##c%MsF6_6pr=wekW0Ff194@ZI_ZeNgC=%>$+@89IbDC=k|MLW zXcoloagAPOUN7_oVdCNb$CFu>qZcE+m5XOgCy*%lR3Y+B?W@myK7!_49MCKas=xYj zZ9CLJddKE^^qQa$)IlG9X_BzchJJDaD4E{noc8mxbcem?E#-&GYNgs0SZ}S{5u&Ro zEt{UbwdPX)L(F%%oB&+qanZ^$lK)-lCXxUo-v+gX217@yxKCVlY8w$tOasJZJYE>? zhq*8r5Y=4-AvC;FPe_>|M4|WjK*oeQO;huxLX0%;3LCnA@@sBDblD#z6Wq|A z&PpXOJ9NtmLv!KcYZE%5-uiYc=0#WtPEO*1(8B!Y@e#*FaK&(b7PzTU?bASw9Y_jw zWev{OiuVi3YMu5!V?q}a5Kvj3=|{JR01+z#YK02{3tTbECD)B9~b{>CPT zE*slp_mpTZ6vcAY5bUWO2*W3k{8bg5{VJAxmolp0q!NqxfZ`$m&{K8s9d?M385Qy` zrfu5$Ew&?KA3yvjr6-FF5c#GWw!+?;3_7!`cSk^QdI9yFV(vSJc^50T!G+Q!&+!`> zuae67g3D4v!2OUM(y=9RS_OnXgyA&Ac9ZBdnjGC%9rZU#A0ut^1UtY~iK?s8c)CBa zZz9XiVWIY&SISQZT^f?wb;~X9*!8?G@Q4;i?py5-D-bx8c0?bLL7mh@1MK+TKG;Rg z{4D3+*hO0;O=TD~pil$4;c?Gw94M0t9Q@YA9R-MD+omRO%$5A}c-X;!->5$?D)qOW z*s~E3ij*5lt%r5gmMKZq{<&n^R_Xd;vCxH>mM{XyG2T!=2>bEqLvvIFU<|zZjw*hh zC~1GCfKe7p_y1Rppa66sS1spPw3hnTC!WAtJ<6-2R$gO1Sw;$YJ7~TfHOG#5>4Txi zy6`^3K5wBKb-=5{Yn^P4UX-CJe&GADLPss-#$g$4^hjSR07td6e&ZPFD8hc|*VZKb zTK=-cRKgknU{bOs3(j4Kv`LVE8E0k(%~z}JXV$-QOR+%deixCHY00l7b{P{6B3slr z85Dbo1aBcee5~kh$tG*a9y#TK1{_W4&+uqR37} zF4F!G*K=M+O*G%QT9A5?Xeh|)Q;PZF!Y)F@#4M(J0rNdhU$Gqgn?N2%`V*M_6@7x6b^2Z9PTR#ties@V zqD&~*v+s7hml%2^DP2`gE^e=aHzL^AiW@<*SJa!Zmm1>`EjayD#ncQ!6*t-n%Z;kW z7?t|glV##tn|k&JX=4r$

4&bmD^cAN|of{&4xY&{cOuvUjI9x?p(Tr#yUEa5?r* z6EEq!O!>!m53j?#Wn+%8W%F`R7%_j6sz(x*=F|~88G0b(*lqm(hwg{lhD_uKhfcAS z>uko=3lTyPrN?w{P%dqXPdE!42GDPj=W^){!)#p@u`m^MEp`V?T8y|^&L?9tInW4> z+#8GtZA4QdP|qjQsBffU%bhh=gGmKOFiYYU0C3x&5atY!u4Zfqo;T694XJ&gb`S() zK#9n@!!6XJ?GBoMyn#cr%vsj9Pw{PIV^+yk-^2nJs7s?>ebkPo2J>4{f8~PGFZ6TX zy?@m4T97TOX!zdG)oI*1DK*D#J3aOpM(t+5c*A-+nLbK7a1#=s5O+&>y;C4Kcc(Qg z@8jHwY-ril0Kn7@a1qWBXpGK)vfPMuYKI97jD6*IK;vRr5RcT{L&ovG#8a|e-g8Y_ z+WJmW1-C-f-eOhUM4K83r)U42HUDvC5<6RS^swAJD)i6jlK@o#X~1_;c_S%^PcUbc z;jCq+H=1I+FrKrz8V7p&azOy6L)DEI6^#+H`KVHIpIP#zZBMkep&SsQkI-df9nl(s zV^673R?W{aYHx#9+S^En1LeW_#dS3qs5o_N&6W)aj=Hc1{lfM)AY!iu+P0sd#@yL@ z4{;M_B9e3)1I_y-+~3SjjcOouVKng*()^qkg?+uV)Xl?R_Kwx%Kym135bQCJ9#SNC z{?uOB-3%o?kR*s`KvnSRGqQa(>PaCb@m=QOjFa5IK`~%~n^aurr)P_3dbc!AffYUg z)P&akw1IzK~t=QIlR1pFG%^jr6akpw7x2;Z4knA%w(Bg(oz`8qI zgAuT?Fkb>y!je&a0kzHMLmg&)e8OXu!n5iwy4u=zw5y`>0T-l$bMcWJZwWBtE(bsU zsSZ!e?8~sK@55kKJu_J$s|S#MD4z|=`;9C6lXC4Mu7~4l$Dk5i0Ow{a0tqMPp0Bf` z0wRINf}}?)VI@lDy`JRs#l;(6RqRXW09ysuf!`PnJdRMquVFjL${5hs)ZuZ9D)&by35*=S(M> zE3iyPTaO~gU(nX7`5?$$3&C_(DVsr)&^OBtAnEX1w{Bf7vj;7Pi%@xWK|tP0p~yMK z@`z@=&`=Vc6_j-KMv-8Mf{y)Zj$Dz+VYuDiBsT?TKddW+8g0;W{Yw9+6aM$C83sJ3 zS;oh}TZJ6Gj!*~Jys(M6jr&IT3L?X4h96xgzzKs?&WDVa16yNrWHycM zJ?RERaNLiwtoDrRSp1!Vamn#wZ=6$l1l!EKKO`J;&Lq4RiMf4fQ!cMrL=C+s^5&@d zA6=p9u!P?yzA;idL@Kyj!0j|IOR~(DS7gnxV|!4Xl7S3{0h^rAVYXAB3~` zYByvlexyF$1q47B=*DD}J^q2amj_OMo5qfr)8D{7@wg#J!Lj>@Hi%T|7lamu!Pj^S z+5?I^$_qf%HJCrN+GE)TSimr{^5o0(05P4$r%434>J?y(Bz?R^S$G(h@o_Ds`ylj zXx|)CJwolLpF!2xzfR`9P|?nDYn6p5NeFjgO7OJ$0*W~$p@>fR)LyeRz`K-2;9NtJ zfQ6>zI4sq}mQ31{(AAY#HiR8&mP0d|1{oDXr(kdFB$ zz$eVA4>)u#4D;C>F^A_hXh_pWT#$SyMkC!Z`_okN#P1hr5Vp zz~c@MfC0!$Q*X9T<}F|MN`f&MhsJ+w5A7O+4rZR)h~@MMD*9}-Za2*DW``i@ZlIp> z18dZqady~I$QdCzpTP0*IJUD)MTCV{A3|zh$W?y=tIG%0A<&gdUW@P#EaS!x1F;Rs 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

uWhvB{~{rUR_(}#}#en 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

fJM%$1*cH$S@iJ0Xne}cZ#gZ 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

#8G}_(i1G!PZRcTzG17`1qIb@iOjfoN|17 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%0Dn|21Pl#Wxhc?=l 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 N$rGIIGHp5Q{|A-s0M7sb diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/images/tax_lza_network_diagram.png b/reference/sample-configurations/lza-sample-config-finance-tax/images/tax_lza_network_diagram.png deleted file mode 100644 index f80b0d41dbd6b364ed01f4f0af5d17462bda83a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146939 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

6;-;dnN` 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%0DxA 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

YG*`%TqXl_2_%6c_w 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�*B++|3kU>B#I)2 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 UQ@(2||QmsfD!TPcg>qWN$^$utgdRqZd5?Nw&-H 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

CJl?JbJ-Nt^CXlm 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 PfS;FA3NMh)4E+8-CWNT; diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/ip-cidr-mapping-tax.csv b/reference/sample-configurations/lza-sample-config-finance-tax/ip-cidr-mapping-tax.csv deleted file mode 100644 index c7d9218..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/ip-cidr-mapping-tax.csv +++ /dev/null @@ -1,96 +0,0 @@ -Account,VPC CIDR,Subnet,Subnet Range,TGW -,,,, -Network,10.0.0.0/22,,, -,,egress-vpc-private-sub-a,10.0.0.0/25, -,,egress-vpc-private-sub-b,10.0.0.128/25, -,,egress-vpc-private-sub-c,10.0.1.0/25, -,,egress-vpc-public-sub-a,10.0.1.128/25, -,,egress-vpc-public-sub-b,10.0.2.0/25, -,,egress-vpc-public-sub-c,10.0.2.128/25, -,,egress-vpc-tgw-endpoints-a,10.0.3.0/28, -,,egress-vpc-tgw-endpoints-b,10.0.3.16/28, -,,egress-vpc-tgw-endpoints-c,10.0.3.32/28, -,,,, -Shared Services,10.0.8.0/21,,, -,,ad-private-sub-a,10.0.8.0/25, -,,ad-private-sub-b,10.0.8.128/25, -,,ad-private-sub-c,10.0.9.0/25, -,,cust-bast-private-sub-a,10.0.9.128/25, -,,cust-bast-private-sub-b,10.0.10.0/25, -,,cust-bast-private-sub-c,10.0.10.128/25, -,,endpoint-sec-private-sub-a,10.0.11.0/25, -,,endpoint-sec-private-sub-b,10.0.11.128/25, -,,endpoint-sec-private-sub-c,10.0.12.0/25, -,,tgw-endpoints-sub-a,10.0.12.128/28, -,,tgw-endpoints-sub-b,10.0.12.144/28, -,,tgw-endpoints-sub-c,10.0.12.160/28, -,,,, -Control,10.0.16.0/21,,, -,,control-vpc-public-sub-a,10.0.16.0/25, -,,control-vpc-public-sub-b,10.0.16.128/25, -,,control-vpc-public-sub-c,10.0.17.0/25, -,,control-vpc-app-sub-a,10.0.17.128/25, -,,control-vpc-app-sub-b,10.0.18.0/25, -,,control-vpc-app-sub-c,10.0.18.128/25, -,,control-vpc-db-sub-a,10.0.19.0/25, -,,control-vpc-db-sub-b,10.0.19.128/25, -,,control-vpc-db-sub-c,10.0.20.0/25, -,,control-vpc-tgw-endpoints-sub-a,10.0.20.128/28, -,,control-vpc-tgw-endpoints-sub-b,10.0.20.144/28, -,,control-vpc-tgw-endpoints-sub-c,10.0.20.160/28, -,,,, -Production,10.0.24.0/21,,, -,,prod-vpc-public-sub-a,10.0.24.0/25, -,,prod-vpc-public-sub-b,10.0.24.128/25, -,,prod-vpc-public-sub-c,10.0.25.0/25, -,,prod-vpc-app-sub-a,10.0.25.128/25, -,,prod-vpc-app-sub-b,10.0.26.0/25, -,,prod-vpc-app-sub-c,10.0.26.128/25, -,,prod-vpc-db-sub-a,10.0.27.0/25, -,,prod-vpc-db-sub-b,10.0.27.128/25, -,,prod-vpc-db-sub-c,10.0.28.0/25, -,,prod-vpc-tgw-endpoints-sub-a,10.0.28.128/28, -,,prod-vpc-tgw-endpoints-sub-b,10.0.28.144/28, -,,prod-vpc-tgw-endpoints-sub-c,10.0.28.160/28, -,,,, -Staging,10.0.32.0/21,,, -,,stage-vpc-public-sub-a,10.0.32.0/25, -,,stage-vpc-public-sub-b,10.0.32.128/25, -,,stage-vpc-public-sub-c,10.0.33.0/25, -,,stage-vpc-app-sub-a,10.0.33.128/25, -,,stage-vpc-app-sub-b,10.0.34.0/25, -,,stage-vpc-app-sub-c,10.0.34.128/25, -,,stage-vpc-db-sub-a,10.0.35.0/25, -,,stage-vpc-db-sub-b,10.0.35.128/25, -,,stage-vpc-db-sub-c,10.0.36.0/25, -,,stage-vpc-tgw-endpoints-sub-a,10.0.36.128/28, -,,stage-vpc-tgw-endpoints-sub-b,10.0.36.144/28, -,,stage-vpc-tgw-endpoints-sub-c,10.0.36.160/28, -,,,, -Dev Test,10.0.40.0/21,,, -,,devtest-vpc-public-sub-a,10.0.40.0/25, -,,devtest-vpc-public-sub-b,10.0.40.128/25, -,,devtest-vpc-public-sub-c,10.0.41.0/25, -,,devtest-vpc-app-sub-a,10.0.41.128/25, -,,devtest-vpc-app-sub-b,10.0.42.0/25, -,,devtest-vpc-app-sub-c,10.0.42.128/25, -,,devtest-vpc-db-sub-a,10.0.43.0/25, -,,devtest-vpc-db-sub-b,10.0.43.128/25, -,,devtest-vpc-db-sub-c,10.0.44.0/25, -,,devtest-vpc-tgw-endpoints-sub-a,10.0.44.128/28, -,,devtest-vpc-tgw-endpoints-sub-b,10.0.44.144/28, -,,devtest-vpc-tgw-endpoints-sub-c,10.0.44.160/28, -,,,, -Disater Recovery,10.0.48.0/21,,, -,,dr-vpc-public-sub-a,10.0.48.0/25, -,,dr-vpc-public-sub-b,10.0.48.128/25, -,,dr-vpc-public-sub-c,10.0.49.0/25, -,,dr-vpc-app-sub-a,10.0.49.128/25, -,,dr-vpc-app-sub-b,10.0.50.0/25, -,,dr-vpc-app-sub-c,10.0.50.128/25, -,,dr-vpc-db-sub-a,10.0.51.0/25, -,,dr-vpc-db-sub-b,10.0.51.128/25, -,,dr-vpc-db-sub-c,10.0.52.0/25, -,,dr-vpc-tgw-endpoints-sub-a,10.0.52.128/28, -,,dr-vpc-tgw-endpoints-sub-b,10.0.52.144/28, -,,dr-vpc-tgw-endpoints-sub-c,10.0.52.160/28, diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/network-config.yaml b/reference/sample-configurations/lza-sample-config-finance-tax/network-config.yaml deleted file mode 100644 index 1146ac4..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/network-config.yaml +++ /dev/null @@ -1,1180 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -disasterRecoveryRegion: &DR_REGION us-west-2 -defaultVpc: - delete: true - excludeAccounts: [] -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - - Tax - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: egress-rtb - routes: - - destinationCidrBlock: 0.0.0.0/0 - attachment: - vpcName: egress-vpc - account: Network - - name: core-rtb - routes: - - destinationCidrBlock: 0.0.0.0/0 - attachment: - vpcName: egress-vpc - account: Network - - name: prod-rtb - routes: - - destinationCidrBlock: 0.0.0.0/0 - attachment: - vpcName: egress-vpc - account: Network - - destinationCidrBlock: 10.0.40.0/21 - blackhole: true - - name: stage-rtb - routes: - - destinationCidrBlock: 0.0.0.0/0 - attachment: - vpcName: egress-vpc - account: Network - - destinationCidrBlock: 10.0.40.0/21 - blackhole: true - - name: devtest-rtb - routes: - - destinationCidrBlock: 0.0.0.0/0 - attachment: - vpcName: egress-vpc - account: Network - - destinationCidrBlock: 10.0.24.0/21 - blackhole: true - - destinationCidrBlock: 10.0.32.0/21 - blackhole: true - -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json - -vpcs: - ############################## - # Network Account Egress VPC # - ############################## - - name: egress-vpc - account: Network - region: *HOME_REGION - cidrs: - - 10.0.0.0/21 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: egress-vpc-private-rtb-a - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: egress-vpc-natgw-a - - name: egress-vpc-private-rtb-b - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: egress-vpc-natgw-b - - name: egress-vpc-private-rtb-c - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: egress-vpc-natgw-c - - name: egress-vpc-public-rtb-a - routes: - - name: default-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: core-route - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: egress-vpc-public-rtb-b - routes: - - name: default-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: core-route - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: egress-vpc-public-rtb-c - routes: - - name: default-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: core-route - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: egress-vpc-tgw-endpoints-rtb-a - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: egress-vpc-natgw-a - - name: tgw-internal - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: egress-vpc-tgw-endpoints-rtb-b - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: egress-vpc-natgw-b - - name: tgw-internal - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: egress-vpc-tgw-endpoints-rtb-c - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: egress-vpc-natgw-c - - name: tgw-internal - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - subnets: - - name: egress-vpc-private-sub-a - availabilityZone: a - routeTable: egress-vpc-private-rtb-a - ipv4CidrBlock: 10.0.0.0/25 - - name: egress-vpc-private-sub-b - availabilityZone: b - routeTable: egress-vpc-private-rtb-b - ipv4CidrBlock: 10.0.0.128/25 - - name: egress-vpc-private-sub-c - availabilityZone: c - routeTable: egress-vpc-private-rtb-c - ipv4CidrBlock: 10.0.1.0/25 - - name: egress-vpc-public-sub-a - availabilityZone: a - routeTable: egress-vpc-public-rtb-a - ipv4CidrBlock: 10.0.1.128/25 - - name: egress-vpc-public-sub-b - availabilityZone: b - routeTable: egress-vpc-public-rtb-b - ipv4CidrBlock: 10.0.2.0/25 - - name: egress-vpc-public-sub-c - availabilityZone: c - routeTable: egress-vpc-public-rtb-c - ipv4CidrBlock: 10.0.2.128/25 - - name: egress-vpc-tgw-endpoints-a - availabilityZone: a - routeTable: egress-vpc-tgw-endpoints-rtb-a - ipv4CidrBlock: 10.0.3.0/28 - - name: egress-vpc-tgw-endpoints-b - availabilityZone: b - routeTable: egress-vpc-tgw-endpoints-rtb-b - ipv4CidrBlock: 10.0.3.16/28 - - name: egress-vpc-tgw-endpoints-c - availabilityZone: c - routeTable: egress-vpc-tgw-endpoints-rtb-c - ipv4CidrBlock: 10.0.3.32/28 - natGateways: - - name: egress-vpc-natgw-a - subnet: egress-vpc-public-sub-a - - name: egress-vpc-natgw-b - subnet: egress-vpc-public-sub-b - - name: egress-vpc-natgw-c - subnet: egress-vpc-public-sub-c - transitGatewayAttachments: - - name: egress-tgw-attachments - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - egress-rtb - routeTablePropagations: [] - subnets: - - egress-vpc-tgw-endpoints-a - - egress-vpc-tgw-endpoints-b - - egress-vpc-tgw-endpoints-c - - - ################################ - ## Shared Services Account VPC # - ################################ - - name: shared-serve-vpc - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.0.8.0/21 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: ad-private-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: ad-private-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: ad-private-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: cust-bast-private-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: cust-bast-private-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: cust-bast-private-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: endpoint-sec-private-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: endpoint-sec-private-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: endpoint-sec-private-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - subnets: - - name: ad-private-sub-a - availabilityZone: a - routeTable: ad-private-sub-rtb-a - ipv4CidrBlock: 10.0.8.0/25 - - name: ad-private-sub-b - availabilityZone: b - routeTable: ad-private-sub-rtb-b - ipv4CidrBlock: 10.0.8.128/25 - - name: ad-private-sub-c - availabilityZone: c - routeTable: ad-private-sub-rtb-c - ipv4CidrBlock: 10.0.9.0/25 - - name: cust-bast-private-sub-a - availabilityZone: a - routeTable: cust-bast-private-sub-rtb-a - ipv4CidrBlock: 10.0.9.128/25 - - name: cust-bast-private-sub-b - availabilityZone: b - routeTable: cust-bast-private-sub-rtb-b - ipv4CidrBlock: 10.0.10.0/25 - - name: cust-bast-private-sub-c - availabilityZone: c - routeTable: cust-bast-private-sub-rtb-c - ipv4CidrBlock: 10.0.10.128/25 - - name: endpoint-sec-private-sub-a - availabilityZone: a - routeTable: endpoint-sec-private-sub-rtb-a - ipv4CidrBlock: 10.0.11.0/25 - - name: endpoint-sec-private-sub-b - availabilityZone: b - routeTable: endpoint-sec-private-sub-rtb-b - ipv4CidrBlock: 10.0.11.128/25 - - name: endpoint-sec-private-sub-c - availabilityZone: c - routeTable: endpoint-sec-private-sub-rtb-c - ipv4CidrBlock: 10.0.12.0/25 - - name: tgw-endponts-sub-a - availabilityZone: a - routeTable: tgw-endpoints-rtb-a - ipv4CidrBlock: 10.0.12.128/28 - - name: tgw-endponts-sub-b - availabilityZone: b - routeTable: tgw-endpoints-rtb-b - ipv4CidrBlock: 10.0.12.144/28 - - name: tgw-endponts-sub-c - availabilityZone: c - routeTable: tgw-endpoints-rtb-c - ipv4CidrBlock: 10.0.12.160/28 - transitGatewayAttachments: - - name: shared-services-tgw-attachment - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - core-rtb - routeTablePropagations: - - egress-rtb - - prod-rtb - - stage-rtb - - devtest-rtb - subnets: - - tgw-endponts-sub-a - - tgw-endponts-sub-b - - tgw-endponts-sub-c - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - interfaceEndpoints: - central: true - defaultPolicy: Default - subnets: - - endpoint-sec-private-sub-a - - endpoint-sec-private-sub-b - - endpoint-sec-private-sub-c - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: logs - - service: kms - - - - ################################ - ## Control Account VPC # # - ################################ - - name: control-vpc - account: Control - region: *HOME_REGION - cidrs: - - 10.0.16.0/21 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: control-vpc-pub-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: control-vpc-pub-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: control-vpc-pub-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: control-vpc-app-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: control-vpc-app-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: control-vpc-app-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: control-vpc-db-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: control-vpc-db-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: control-vpc-db-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - subnets: - - name: control-vpc-public-sub-a - availabilityZone: a - routeTable: control-vpc-pub-sub-rtb-a - ipv4CidrBlock: 10.0.16.0/25 - - name: control-vpc-public-sub-b - availabilityZone: b - routeTable: control-vpc-pub-sub-rtb-b - ipv4CidrBlock: 10.0.16.128/25 - - name: control-vpc-public-sub-c - availabilityZone: c - routeTable: control-vpc-pub-sub-rtb-c - ipv4CidrBlock: 10.0.17.0/25 - - name: control-vpc-app-sub-a - availabilityZone: a - routeTable: control-vpc-app-sub-rtb-a - ipv4CidrBlock: 10.0.17.128/25 - - name: control-vpc-app-sub-b - availabilityZone: b - routeTable: control-vpc-app-sub-rtb-b - ipv4CidrBlock: 10.0.18.0/25 - - name: control-vpc-app-sub-c - availabilityZone: c - routeTable: control-vpc-app-sub-rtb-c - ipv4CidrBlock: 10.0.18.128/25 - - name: control-vpc-db-sub-a - availabilityZone: a - routeTable: control-vpc-db-sub-rtb-a - ipv4CidrBlock: 10.0.19.0/25 - - name: control-vpc-db-sub-b - availabilityZone: b - routeTable: control-vpc-db-sub-rtb-b - ipv4CidrBlock: 10.0.19.128/25 - - name: control-vpc-db-sub-c - availabilityZone: c - routeTable: control-vpc-db-sub-rtb-c - ipv4CidrBlock: 10.0.20.0/25 - - name: control-vpc-tgw-endponts-sub-a - availabilityZone: a - routeTable: tgw-endpoints-rtb-a - ipv4CidrBlock: 10.0.20.128/28 - - name: control-vpc-tgw-endponts-sub-b - availabilityZone: b - routeTable: tgw-endpoints-rtb-b - ipv4CidrBlock: 10.0.20.144/28 - - name: control-vpc-tgw-endponts-sub-c - availabilityZone: c - routeTable: tgw-endpoints-rtb-c - ipv4CidrBlock: 10.0.20.160/28 - transitGatewayAttachments: - - name: control-tgw-attachment - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - core-rtb - routeTablePropagations: - - egress-rtb - - prod-rtb - - stage-rtb - - devtest-rtb - subnets: - - control-vpc-tgw-endponts-sub-a - - control-vpc-tgw-endponts-sub-b - - control-vpc-tgw-endponts-sub-c - - - ################################ - ## Prod Account VPC # # - ################################ - - name: prod-vpc - account: Production - region: *HOME_REGION - cidrs: - - 10.0.24.0/21 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: prod-vpc-pub-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: prod-vpc-pub-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: prod-vpc-pub-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: prod-vpc-app-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: prod-vpc-app-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: prod-vpc-app-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: prod-vpc-db-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: prod-vpc-db-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: prod-vpc-db-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: tgw-endpoints-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - subnets: - - name: prod-vpc-public-sub-a - availabilityZone: a - routeTable: prod-vpc-pub-sub-rtb-a - ipv4CidrBlock: 10.0.24.0/25 - - name: prod-vpc-public-sub-b - availabilityZone: b - routeTable: prod-vpc-pub-sub-rtb-b - ipv4CidrBlock: 10.0.24.128/25 - - name: prod-vpc-public-sub-c - availabilityZone: c - routeTable: prod-vpc-pub-sub-rtb-c - ipv4CidrBlock: 10.0.25.0/25 - - name: prod-vpc-app-sub-a - availabilityZone: a - routeTable: prod-vpc-app-sub-rtb-a - ipv4CidrBlock: 10.0.25.128/25 - - name: prod-vpc-app-sub-b - availabilityZone: b - routeTable: prod-vpc-app-sub-rtb-b - ipv4CidrBlock: 10.0.26.0/25 - - name: prod-vpc-app-sub-c - availabilityZone: c - routeTable: prod-vpc-app-sub-rtb-c - ipv4CidrBlock: 10.0.26.128/25 - - name: prod-vpc-db-sub-a - availabilityZone: a - routeTable: prod-vpc-db-sub-rtb-a - ipv4CidrBlock: 10.0.27.0/25 - - name: prod-vpc-db-sub-b - availabilityZone: b - routeTable: prod-vpc-db-sub-rtb-b - ipv4CidrBlock: 10.0.27.128/25 - - name: prod-vpc-db-sub-c - availabilityZone: c - routeTable: prod-vpc-db-sub-rtb-c - ipv4CidrBlock: 10.0.28.0/25 - - name: prod-vpc-tgw-endponts-sub-a - availabilityZone: a - routeTable: tgw-endpoints-rtb-a - ipv4CidrBlock: 10.0.28.128/28 - - name: prod-vpc-tgw-endponts-sub-b - availabilityZone: b - routeTable: tgw-endpoints-rtb-b - ipv4CidrBlock: 10.0.28.144/28 - - name: prod-vpc-tgw-endponts-sub-c - availabilityZone: c - routeTable: tgw-endpoints-rtb-c - ipv4CidrBlock: 10.0.28.160/28 - transitGatewayAttachments: - - name: prod-tgw-attachment - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - prod-rtb - routeTablePropagations: - - egress-rtb - - core-rtb - - stage-rtb - subnets: - - prod-vpc-tgw-endponts-sub-a - - prod-vpc-tgw-endponts-sub-b - - prod-vpc-tgw-endponts-sub-c - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - useCentralEndpoints: true - - - ################################ - ## Staging Account VPC # # - ################################ - - name: stage-vpc - account: Staging - region: *HOME_REGION - cidrs: - - 10.0.32.0/21 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: stage-vpc-pub-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: stage-vpc-pub-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: stage-vpc-pub-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: stage-vpc-app-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: stage-vpc-app-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: stage-vpc-app-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: stage-vpc-db-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: stage-vpc-db-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: stage-vpc-db-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - subnets: - - name: stage-vpc-public-sub-a - availabilityZone: a - routeTable: stage-vpc-pub-sub-rtb-a - ipv4CidrBlock: 10.0.32.0/25 - - name: stage-vpc-public-sub-b - availabilityZone: b - routeTable: stage-vpc-pub-sub-rtb-b - ipv4CidrBlock: 10.0.32.128/25 - - name: stage-vpc-public-sub-c - availabilityZone: c - routeTable: stage-vpc-pub-sub-rtb-c - ipv4CidrBlock: 10.0.33.0/25 - - name: stage-vpc-app-sub-a - availabilityZone: a - routeTable: stage-vpc-app-sub-rtb-a - ipv4CidrBlock: 10.0.33.128/25 - - name: stage-vpc-app-sub-b - availabilityZone: b - routeTable: stage-vpc-app-sub-rtb-b - ipv4CidrBlock: 10.0.34.0/25 - - name: stage-vpc-app-sub-c - availabilityZone: c - routeTable: stage-vpc-app-sub-rtb-c - ipv4CidrBlock: 10.0.34.128/25 - - name: stage-vpc-db-sub-a - availabilityZone: a - routeTable: stage-vpc-db-sub-rtb-a - ipv4CidrBlock: 10.0.35.0/25 - - name: stage-vpc-db-sub-b - availabilityZone: b - routeTable: stage-vpc-db-sub-rtb-b - ipv4CidrBlock: 10.0.35.128/25 - - name: stage-vpc-db-sub-c - availabilityZone: c - routeTable: stage-vpc-db-sub-rtb-c - ipv4CidrBlock: 10.0.36.0/25 - - name: stage-vpc-tgw-endponts-sub-a - availabilityZone: a - routeTable: tgw-endpoints-rtb-a - ipv4CidrBlock: 10.0.36.128/28 - - name: stage-vpc-tgw-endponts-sub-b - availabilityZone: b - routeTable: tgw-endpoints-rtb-b - ipv4CidrBlock: 10.0.36.144/28 - - name: stage-vpc-tgw-endponts-sub-c - availabilityZone: c - routeTable: tgw-endpoints-rtb-c - ipv4CidrBlock: 10.0.36.160/28 - transitGatewayAttachments: - - name: stage-tgw-attachment - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - stage-rtb - routeTablePropagations: - - egress-rtb - - prod-rtb - - core-rtb - subnets: - - stage-vpc-tgw-endponts-sub-a - - stage-vpc-tgw-endponts-sub-b - - stage-vpc-tgw-endponts-sub-c - - - ################################ - ## DevTest Account VPC # # - ################################ - - name: devtest-vpc - account: DevTest - region: *HOME_REGION - cidrs: - - 10.0.40.0/21 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: devtest-vpc-pub-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: devtest-vpc-pub-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: devtest-vpc-pub-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: devtest-vpc-app-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: devtest-vpc-app-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: devtest-vpc-app-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: devtest-vpc-db-sub-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: devtest-vpc-db-sub-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: devtest-vpc-db-sub-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-a - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-b - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: tgw-endpoints-rtb-c - routes: - - name: tgw-main-route - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - subnets: - - name: devtest-vpc-public-sub-a - availabilityZone: a - routeTable: devtest-vpc-pub-sub-rtb-a - ipv4CidrBlock: 10.0.40.0/25 - - name: devtest-vpc-public-sub-b - availabilityZone: b - routeTable: devtest-vpc-pub-sub-rtb-b - ipv4CidrBlock: 10.0.40.128/25 - - name: devtest-vpc-public-sub-c - availabilityZone: c - routeTable: devtest-vpc-pub-sub-rtb-c - ipv4CidrBlock: 10.0.41.0/25 - - name: devtest-vpc-app-sub-a - availabilityZone: a - routeTable: devtest-vpc-app-sub-rtb-a - ipv4CidrBlock: 10.0.41.128/25 - - name: devtest-vpc-app-sub-b - availabilityZone: b - routeTable: devtest-vpc-app-sub-rtb-b - ipv4CidrBlock: 10.0.42.0/25 - - name: devtest-vpc-app-sub-c - availabilityZone: c - routeTable: devtest-vpc-app-sub-rtb-c - ipv4CidrBlock: 10.0.42.128/25 - - name: devtest-vpc-db-sub-a - availabilityZone: a - routeTable: devtest-vpc-db-sub-rtb-a - ipv4CidrBlock: 10.0.43.0/25 - - name: devtest-vpc-db-sub-b - availabilityZone: b - routeTable: devtest-vpc-db-sub-rtb-b - ipv4CidrBlock: 10.0.43.128/25 - - name: devtest-vpc-db-sub-c - availabilityZone: c - routeTable: devtest-vpc-db-sub-rtb-c - ipv4CidrBlock: 10.0.44.0/25 - - name: devtest-vpc-tgw-endponts-sub-a - availabilityZone: a - routeTable: tgw-endpoints-rtb-a - ipv4CidrBlock: 10.0.44.128/28 - - name: devtest-vpc-tgw-endponts-sub-b - availabilityZone: b - routeTable: tgw-endpoints-rtb-b - ipv4CidrBlock: 10.0.44.144/28 - - name: devtest-vpc-tgw-endponts-sub-c - availabilityZone: c - routeTable: tgw-endpoints-rtb-c - ipv4CidrBlock: 10.0.44.160/28 - transitGatewayAttachments: - - name: devtest-tgw-attachment - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - devtest-rtb - routeTablePropagations: - - egress-rtb - - core-rtb - subnets: - - devtest-vpc-tgw-endponts-sub-a - - devtest-vpc-tgw-endponts-sub-b - - devtest-vpc-tgw-endponts-sub-c - - - ################################## - ## DisasterRecovery Account VPC ## - ################################## - - name: dr-vpc - account: DisasterRecovery - region: *DR_REGION - cidrs: - - 10.0.48.0/21 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: dr-vpc-pub-sub-rtb-a - routes: - - name: default-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: dr-vpc-pub-sub-rtb-b - routes: - - name: default-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: dr-vpc-pub-sub-rtb-c - routes: - - name: default-route - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: dr-vpc-app-sub-rtb-a - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: dr-vpc-natgw-a - - name: dr-vpc-app-sub-rtb-b - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: dr-vpc-natgw-b - - name: dr-vpc-app-sub-rtb-c - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: dr-vpc-natgw-c - - name: dr-vpc-db-sub-rtb-a - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: dr-vpc-natgw-a - - name: dr-vpc-db-sub-rtb-b - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: dr-vpc-natgw-b - - name: dr-vpc-db-sub-rtb-c - routes: - - name: default-route - destination: 0.0.0.0/0 - type: natGateway - target: dr-vpc-natgw-c - subnets: - - name: dr-vpc-public-sub-a - availabilityZone: a - routeTable: dr-vpc-pub-sub-rtb-a - ipv4CidrBlock: 10.0.48.0/25 - - name: dr-vpc-public-sub-b - availabilityZone: b - routeTable: dr-vpc-pub-sub-rtb-b - ipv4CidrBlock: 10.0.48.128/25 - - name: dr-vpc-public-sub-c - availabilityZone: c - routeTable: dr-vpc-pub-sub-rtb-c - ipv4CidrBlock: 10.0.49.0/25 - - name: dr-vpc-app-sub-a - availabilityZone: a - routeTable: dr-vpc-app-sub-rtb-a - ipv4CidrBlock: 10.0.49.128/25 - - name: dr-vpc-app-sub-b - availabilityZone: b - routeTable: dr-vpc-app-sub-rtb-b - ipv4CidrBlock: 10.0.50.0/25 - - name: dr-vpc-app-sub-c - availabilityZone: c - routeTable: dr-vpc-app-sub-rtb-c - ipv4CidrBlock: 10.0.50.128/25 - - name: dr-vpc-db-sub-a - availabilityZone: a - routeTable: dr-vpc-db-sub-rtb-a - ipv4CidrBlock: 10.0.51.0/25 - - name: dr-vpc-db-sub-b - availabilityZone: b - routeTable: dr-vpc-db-sub-rtb-b - ipv4CidrBlock: 10.0.51.128/25 - - name: dr-vpc-db-sub-c - availabilityZone: c - routeTable: dr-vpc-db-sub-rtb-c - ipv4CidrBlock: 10.0.52.0/25 - transitGatewayAttachments: [] - natGateways: - - name: dr-vpc-natgw-a - subnet: dr-vpc-public-sub-a - - name: dr-vpc-natgw-b - subnet: dr-vpc-public-sub-b - - name: dr-vpc-natgw-c - subnet: dr-vpc-public-sub-c - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -# vpcPeering: -# - name: ProdToDR -# vpcs: -# - dr-vpc -# - prod-vpc -# - name: SharedServices≥ToDR -# vpcs: -# - dr-vpc -# - shared-serve-vpc - diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/organization-config.yaml b/reference/sample-configurations/lza-sample-config-finance-tax/organization-config.yaml deleted file mode 100644 index d1e3749..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/organization-config.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure - - name: Tax -quarantineNewAccounts: - enable: false - scpPolicyName: Quarantine -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: SCP-base-root - description: > - baseline SCP for Tax customers - policy: service-control-policies/scp-base-root.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Root - # Example SCP that can be used to block regions you do not want. Modify and implement as needed. - - name: SCP-us-regions-only - description: > - This SCP denies access to any operations outside of the specified US Regions. - policy: service-control-policies/scp-only-us-regions.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - # https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_tag-policies.html -taggingPolicies: - - name: fti-tag-policy - description: Organization Tagging Policy - policy: tagging-policies/tax-org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Tax -# https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_backup.html -# backup policies contain a `delete_after_days` value of 1095 days, or 3 years. Before -# enabling this policy, ensure that `delete_after_days` meets your organization's records retention -# policies. Similarly, ensure that `move_to_cold_storage_after_days` meets business requirements. -backupPolicies: [] diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-base-root.json b/reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-base-root.json deleted file mode 100644 index bc9ae9e..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-base-root.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Version":"2012-10-17", - "Statement":[ - { - "Effect":"Deny", - "Action":[ - "organizations:LeaveOrganization", - "organizations:DeleteOrganization" - ], - "Resource":[ - "*" - ], - "Condition":{ - "ArnNotLike":{ - "aws:PrincipalARN":[ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution" - ] - } - } - }, - { - "Effect":"Deny", - "Action":[ - "iam:CreateAccessKey" - ], - "Resource":[ - "arn:aws:iam::*:root" - ] - }, - { - "Effect":"Deny", - "Action":[ - "s3:PutAccountPublicAccessBlock" - ], - "Resource":[ - "*" - ], - "Condition":{ - "ArnNotLike":{ - "aws:PrincipalARN":[ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-only-us-regions.json b/reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-only-us-regions.json deleted file mode 100644 index 5f8e644..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/service-control-policies/scp-only-us-regions.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllNonUSRegions", - "Effect": "Deny", - "NotAction": [ - "a4b:*", - "acm:*", - "aws-marketplace-management:*", - "aws-marketplace:*", - "aws-portal:*", - "budgets:*", - "ce:*", - "chime:*", - "cloudfront:*", - "config:*", - "cur:*", - "directconnect:*", - "ec2:DescribeRegions", - "ec2:DescribeTransitGateways", - "ec2:DescribeVpnGateways", - "fms:*", - "globalaccelerator:*", - "health:*", - "iam:*", - "importexport:*", - "kms:*", - "mobileanalytics:*", - "networkmanager:*", - "organizations:*", - "pricing:*", - "route53:*", - "route53domains:*", - "s3:GetAccountPublic*", - "s3:ListAllMyBuckets", - "s3:PutAccountPublic*", - "shield:*", - "sts:*", - "support:*", - "trustedadvisor:*", - "waf-regional:*", - "waf:*", - "wafv2:*", - "wellarchitected:*" - ], - "Resource": "*", - "Condition": { - "StringNotEquals": { - "aws:RequestedRegion": [ - "us-west-1", - "us-west-2", - "us-east-1", - "us-east-2" - ] - }, - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-finance-tax/tagging-policies/tax-org-tag-policy.json b/reference/sample-configurations/lza-sample-config-finance-tax/tagging-policies/tax-org-tag-policy.json deleted file mode 100644 index 1606f5a..0000000 --- a/reference/sample-configurations/lza-sample-config-finance-tax/tagging-policies/tax-org-tag-policy.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "tags": { - "EnvironmentType": { - "tag_key": { - "@@assign": "EnvironmentType" - }, - "tag_value": { - "@@assign": [ - "Prod", - "Staging", - "Control", - "Dev" - ] - } - }, - "DataClassification": { - "tag_key": { - "@@assign": "DataClassification" - }, - "tag_value": { - "@@assign": [ - "FTI-Data", - "Non-FTI-Data" - ] - }, - "enforced_for": { - "@@assign": [ - "dms:*", - "ec2:instance", - "ec2:volume", - "elasticfilesystem:*", - "fsx:*", - "s3:bucket" - ] - } - }, - "AppID": {} - } -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/README.md b/reference/sample-configurations/lza-sample-config-govcloud-us/README.md deleted file mode 100644 index b51e163..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Landing Zone Accelerator on AWS for United States (US) Federal and Department of Defense (DoD) - -The documentation for the GovCloud (US) sample configuration has been moved to the [GovCloud (US) Configuration](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/sample-configurations/govcloud-us) section of our [GitHub Pages website](https://awslabs.github.io/landing-zone-accelerator-on-aws). diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/accounts-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/accounts-config.yaml deleted file mode 100644 index 95a3485..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/accounts-config.yaml +++ /dev/null @@ -1,48 +0,0 @@ -mandatoryAccounts: - - name: Management - description: >- - The management (primary) account. Do not change the name field for this - mandatory account. Note, the account name key does not need to match the - AWS account name. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: >- - The log archive account. Do not change the name field for this mandatory - account. Note, the account name key does not need to match the AWS account - name. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: >- - The security audit account (also referred to as the audit account). Do not - change the name field for this mandatory account. Note, the account name - key does not need to match the AWS account name. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - - name: LogArchiveGC # referred to as LogArchive in the GovCloud account-config.yaml - description: The log archive account for GovCloud. - email: <----- UPDATE EMAIL ADDRESS - # this OU has all GovCloud accounts. - # OU was created from Control Tower - # in organization-config.yaml this OU was added. - organizationalUnit: GovCloud - # enableGovCloud is a one-time non-reversible option - # which only works with creation of new accounts - enableGovCloud: true - - name: AuditGC # referred to as Audit in the GovCloud account-config.yaml - description: The security audit account (also referred to as the audit account) for GovCloud. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: GovCloud - enableGovCloud: true - - name: SharedServicesGC # referred to as SharedServices in the GovCloud account-config.yaml - description: Shared services account for GovCloud. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: GovCloud - enableGovCloud: true - - name: NetworkGC # referred to as Network in the GovCloud account-config.yaml - description: Network account for GovCloud. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: GovCloud - enableGovCloud: true diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/global-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/global-config.yaml deleted file mode 100644 index 42effdd..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/global-config.yaml +++ /dev/null @@ -1,50 +0,0 @@ -homeRegion: us-east-1 -enabledRegions: - - us-east-1 -managementAccountAccessRole: AWSControlTowerExecution -cloudwatchLogRetentionInDays: 3653 -terminationProtection: true -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - @example.com <----- UPDATE EMAIL ADDRESS -controlTower: - enable: true - landingZone: - version: '3.3' - logging: - loggingBucketRetentionDays: 365 - accessLoggingBucketRetentionDays: 365 - organizationTrail: true - security: - enableIdentityCenterAccess: true -logging: - account: LogArchive - cloudtrail: - enable: true - organizationTrail: true - organizationTrailSettings: - multiRegionTrail: true - globalServiceEvents: true - managementEvents: true - s3DataEvents: true - lambdaDataEvents: true - sendToCloudWatchLogs: true - apiErrorRateInsight: false - apiCallRateInsight: false - accountTrails: [] - lifecycleRules: [] - sessionManager: - sendToCloudWatchLogs: false - sendToS3: false - excludeRegions: [] - excludeAccounts: [] - lifecycleRules: [] - attachPolicyToIamRoles: [] diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/iam-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/iam-config.yaml deleted file mode 100644 index 9916449..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/iam-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -providers: [] -policySets: [] -roleSets: [] -groupSets: [] -userSets: [] diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/network-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/network-config.yaml deleted file mode 100644 index 3a86541..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/network-config.yaml +++ /dev/null @@ -1,53 +0,0 @@ -defaultVpc: - delete: true - excludeAccounts: [] -transitGateways: [] -transitGatewayPeering: [] -endpointPolicies: [] -vpcs: [] - -############################################################## -# Global configuration for VPC flow logs # -# Where there is no flow log configuration defined with VPC # -# this configuration will be used for flow log configuration # -############################################################## -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/organization-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/organization-config.yaml deleted file mode 100644 index f63f6da..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/organization-config.yaml +++ /dev/null @@ -1,34 +0,0 @@ -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure - - name: GovCloud -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -serviceControlPolicies: - # this policy prevents the use of commercial accounts that are tied to GovCloud - # only actions by organization root management role AWSControlTowerExecution, - # cdk and landing zone accelerator roles are allowed. - # Further, API calls from AWS Service is also allowed via PrincipalIsAWSService boolean - # this allows background services like billingreports.amazonaws.com - # to make the calls to the account but prevents admins to launch any workloads. - # It is highly recommended to leave these commercial accounts locked down - - name: Lockdown paired account - description: Paired commercial accounts tied to GovCloud locked down - policy: service-control-policies/lockdown-govCloud-accounts.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - GovCloud - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] -taggingPolicies: [] -backupPolicies: [] diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/security-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/security-config.yaml deleted file mode 100644 index d05cd99..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/security-config.yaml +++ /dev/null @@ -1,56 +0,0 @@ -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: false - excludeRegions: [] - s3PublicAccessBlock: - enable: true - excludeAccounts: [] - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: false - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: false - excludeRegions: [] - s3Protection: - enable: false - excludeRegions: [] - exportConfiguration: - enable: false - overrideExisting: false - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - securityHub: - enable: false - regionAggregation: false - excludeRegions: [] - standards: [] - ssmAutomation: - excludeRegions: [] - documentSets: [] -accessAnalyzer: - enable: false -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: [] -cloudWatch: - metricSets: [] - alarmSets: [] -keyManagementService: - keySets: [] diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/lockdown-govCloud-accounts.json b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/lockdown-govCloud-accounts.json deleted file mode 100644 index 96a5fdd..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/lockdown-govCloud-accounts.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "RestrictAwsServiceCalls", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws-controltower-CloudWatchLogsRole", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/quarantine.json b/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/commercial-config/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/accounts-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/accounts-config.yaml deleted file mode 100644 index 25f130c..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/accounts-config.yaml +++ /dev/null @@ -1,47 +0,0 @@ -############################################################################################################ -# For additional configurable services, features, and property descriptions, # -# please review class AccountsConfig reference in our TypeDoc: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.AccountsConfig # -############################################################################################################ - -mandatoryAccounts: - # We recommend you do not change mandatory account names. These are used within Landing Zone Accelerator to reference the accounts from other config files. - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - # The "name" will be used to set the AWS Account name - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: SharedServices - description: Shared services account for GovCloud. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Network - description: Network account for GovCloud. - email: <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - -# This section enables LZA to invite the accounts into the Organizations -accountIds: - - email: <----- UPDATE EMAIL ADDRESS - accountId: "000000000000 <----- UPDATE GOVCLOUD ACCOUNT ID from Commercial GovCloud mapping table" - - email: <----- UPDATE EMAIL ADDRESS - accountId: "111111111111 <----- UPDATE GOVCLOUD ACCOUNT ID from Commercial GovCloud mapping table" - - email: <----- UPDATE EMAIL ADDRESS - accountId: "222222222222 <----- UPDATE GOVCLOUD ACCOUNT ID from Commercial GovCloud mapping table" - - email: <----- UPDATE EMAIL ADDRESS - accountId: "333333333333 <----- UPDATE GOVCLOUD ACCOUNT ID from Commercial GovCloud mapping table" - - email: <----- UPDATE EMAIL ADDRESS - accountId: "444444444444 <----- UPDATE GOVCLOUD ACCOUNT ID from Commercial GovCloud mapping table" diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/bucket-policies/central-log-bucket.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/bucket-policies/central-log-bucket.json deleted file mode 100644 index b4e1c27..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/bucket-policies/central-log-bucket.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Allow Organization principals to put session manager logs", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:PutObject", - "Resource": "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "ArnLike": { - "aws:PrincipalARN": "arn:${PARTITION}:iam::*:role/EC2-Default-SSM-AD-Role" - } - } - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/dynamic-partitioning/log-filters.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/dynamic-partitioning/log-filters.json deleted file mode 100644 index 54d93ee..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/dynamic-partitioning/log-filters.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "logGroupPattern": "/AWSAccelerator-SecurityHub", - "s3Prefix": "security-hub" - } -] diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/global-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/global-config.yaml deleted file mode 100644 index 31ee955..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/global-config.yaml +++ /dev/null @@ -1,72 +0,0 @@ -############################################################################################################### -# For additional configurable services, features, and property descriptions, # -# please review class GlobalConfig reference in our TypeDoc: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.GlobalConfig.html # -############################################################################################################### - -homeRegion: &HOME_REGION us-gov-west-1 -enabledRegions: - - *HOME_REGION -managementAccountAccessRole: AWSControlTowerExecution -# Update the retention time based on compliance and auditing requirements -# Defining and storing cloudwatch logs for 365 days -cloudwatchLogRetentionInDays: 365 -terminationProtection: true -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - @example.com <----- UPDATE EMAIL ADDRESS -controlTower: - enable: true -logging: - account: LogArchive - cloudtrail: - enable: false - organizationTrail: false - organizationTrailSettings: - multiRegionTrail: true - globalServiceEvents: true - managementEvents: true - s3DataEvents: true - lambdaDataEvents: true - sendToCloudWatchLogs: true - apiErrorRateInsight: false - apiCallRateInsight: false - sessionManager: - sendToCloudWatchLogs: false - sendToS3: true - attachPolicyToIamRoles: - - EC2-Default-SSM-AD-Role - cloudwatchLogs: - dynamicPartitioning: dynamic-partitioning/log-filters.json - accessLogBucket: - # Update the retention time based on compliance and auditing requirements - # Defining and storing access log Bucket for 365 days - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 365 - noncurrentVersionExpiration: 365 - centralLogBucket: - # Defining and storing storing log Bucket for 365 days - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 365 - noncurrentVersionExpiration: 365 - s3ResourcePolicyAttachments: - - policy: bucket-policies/central-log-bucket.json - elbLogBucket: - # Defining and storing storing log Bucket for 365 days - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 365 - noncurrentVersionExpiration: 365 diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/iam-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/iam-config.yaml deleted file mode 100644 index b26fe21..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/iam-config.yaml +++ /dev/null @@ -1,83 +0,0 @@ -############################################################################################################ -# For additional configurable services, features, and property descriptions, # -# please review class IamConfig reference in our TypeDoc: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.IamConfig.html # -############################################################################################################ - -policySets: [] -roleSets: - - deploymentTargets: - organizationalUnits: - - Root - roles: - - name: EC2-Default-SSM-AD-Role - instanceProfile: true - assumedBy: - - type: service - principal: ec2.amazonaws.com - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonSSMDirectoryServiceAccess - - CloudWatchAgentServerPolicy -groupSets: [] -userSets: [] - -####################################################################################################################### -# AWS IAM Identity Center configuration # -# For AWS Control Tower based installation, AWS Identity Center is enabled automatically # -# Tasks that be performed in the delegated administrator account # -# https://docs.aws.amazon.com/singlesignon/latest/userguide/delegated-admin.html#delegated-admin-tasks-member-account # -####################################################################################################################### -identityCenter: - name: IdentityCenter - # Assigning delegated administration as Audit account - # Preview review AWS Security Reference Architecture for assigning other account as delegated administrator - # https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/shared-services.html - delegatedAdminAccount: Audit - # Predefined permission sets - # https://docs.aws.amazon.com/singlesignon/latest/userguide/permissionsetpredefined.html - identityCenterPermissionSets: - # Creating permission sets - - name: AWSAccelerator-Billing - # Attaching policy - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/job-function/Billing - # Setting session duration - sessionDuration: 60 - - name: AWSAccelerator-DatabaseAdmin - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/job-function/DatabaseAdministrator - sessionDuration: 60 - - name: AWSAccelerator-DataScientist - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/job-function/DataScientist - sessionDuration: 60 - - name: AWSAccelerator-NetworkAdmin - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/job-function/NetworkAdministrator - sessionDuration: 60 - - name: AWSAccelerator-ReadOnlyAccess - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/ReadOnlyAccess - sessionDuration: 60 - - name: AWSAccelerator-SecurityAudit - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/SecurityAudit - sessionDuration: 60 - - name: AWSAccelerator-SupportUser - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/job-function/SupportUser - sessionDuration: 60 - - name: AWSAccelerator-SystemAdmin - policies: - awsManaged: - - arn:aws-us-gov:iam::aws:policy/job-function/SystemAdministrator - sessionDuration: 60 \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/network-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/network-config.yaml deleted file mode 100644 index 2817364..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/network-config.yaml +++ /dev/null @@ -1,283 +0,0 @@ -################################################################################################################ -# For additional configurable services, features, and property descriptions, # -# please review class NetworkConfig reference in our TypeDoc: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.NetworkConfig.html # -################################################################################################################ - -homeRegion: &HOME_REGION us-gov-west-1 -##################################### -# Delete default VPCs-- use this # -# object to delete default VPCs in # -# any non-excluded accounts # -##################################### -defaultVpc: - delete: true - excludeAccounts: [] - -# Transit Gateway Configurations -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - # Adding transit gateway route tables - routeTables: - - name: Network-Main-Internal - routes: [] - - name: Network-Main-Network-Boundary - routes: [] - - name: Network-Main-SharedServices-Main - routes: [] - -##################################### -# Endpoint policies -- use this # -# object to define standard policies # -# for VPC endpoints # -##################################### -endpointPolicies: [] - -##################################### -# VPCs-- use this object to deploy # -# a VPC in a single account and # -# region. # -##################################### -vpcs: - #RFC 1918 Range - # 10.0.0.0 - 10.255.255.255 (10/8 prefix) - # 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) - # 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) - # Replace the vpc and subnet cidr ranges based on the requirements - - # Network Account - # Deploys the Network-Boundary VPC - # Creates 4 subnets in each AZs - # Subnets includes untrust, trust, management and tgw attach - # This vpc host firewall appliances - - name: Network-Boundary - account: Network - region: *HOME_REGION - cidrs: - - 10.0.0.0/19 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: Network-Boundary-Untrust-Rt-A - routes: - - name: TgwRoute - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: Network-Boundary-Untrust-Rt-B - routes: - - name: TgwRoute - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: Network-Boundary-Trust-Rt-A - - name: Network-Boundary-Trust-Rt-B - - name: Network-Boundary-Management-Rt-A - routes: - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: Network-Boundary-Management-Rt-B - routes: - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: Network-Boundary-TgwAttach-Rt-A - routes: - - name: NatRoute - destination: 0.0.0.0/0 - type: natGateway - target: Network-Boundary-NatGw-A - - name: Network-Boundary-TgwAttach-Rt-B - routes: - - name: NatRoute - destination: 0.0.0.0/0 - type: natGateway - target: Network-Boundary-NatGw-B - subnets: - - name: Network-Boundary-Untrust-A - availabilityZone: a - routeTable: Network-Boundary-Untrust-Rt-A - ipv4CidrBlock: 10.0.0.0/22 - - name: Network-Boundary-Untrust-B - availabilityZone: b - routeTable: Network-Boundary-Untrust-Rt-B - ipv4CidrBlock: 10.0.4.0/22 - - name: Network-Boundary-Trust-A - availabilityZone: a - routeTable: Network-Boundary-Trust-Rt-A - ipv4CidrBlock: 10.0.12.0/22 - - name: Network-Boundary-Trust-B - availabilityZone: b - routeTable: Network-Boundary-Trust-Rt-B - ipv4CidrBlock: 10.0.16.0/22 - - name: Network-Boundary-Management-A - availabilityZone: a - routeTable: Network-Boundary-Management-Rt-A - ipv4CidrBlock: 10.0.24.0/27 - - name: Network-Boundary-Management-B - availabilityZone: b - routeTable: Network-Boundary-Management-Rt-B - ipv4CidrBlock: 10.0.24.32/27 - - name: Network-Boundary-TgwAttach-A - availabilityZone: a - routeTable: Network-Boundary-TgwAttach-Rt-A - ipv4CidrBlock: 10.0.31.208/28 - - name: Network-Boundary-TgwAttach-B - availabilityZone: b - routeTable: Network-Boundary-TgwAttach-Rt-B - ipv4CidrBlock: 10.0.31.224/28 - natGateways: - - name: Network-Boundary-NatGw-A - subnet: Network-Boundary-Untrust-A - - name: Network-Boundary-NatGw-B - subnet: Network-Boundary-Untrust-B - transitGatewayAttachments: - - name: Network-Boundary - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Network-Boundary - routeTablePropagations: - - Network-Main-Internal - - Network-Main-SharedServices-Main - subnets: - - Network-Boundary-TgwAttach-A - - Network-Boundary-TgwAttach-B - - # Shared Services Account - # SharedServices-Main VPC - # This vpc creates 3 subnets in each AZs - # Subnets includes app, data and tgw attach. - ### This vpc host sharedsevices applications. - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.1.0.0/20 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: SharedServices-Main-App-Rt-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: SharedServices-Main-App-Rt-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: SharedServices-Main-Database-Rt-A - - name: SharedServices-Main-Database-Rt-B - - name: SharedServices-Main-TgwAttach-Rt-A - - name: SharedServices-Main-TgwAttach-Rt-B - subnets: - - name: SharedServices-Main-App-A - availabilityZone: a - routeTable: SharedServices-Main-App-Rt-A - ipv4CidrBlock: 10.1.0.0/22 - - name: SharedServices-Main-App-B - availabilityZone: b - routeTable: SharedServices-Main-App-Rt-B - ipv4CidrBlock: 10.1.4.0/22 - - name: SharedServices-Main-Database-A - availabilityZone: a - routeTable: SharedServices-Main-Database-Rt-A - ipv4CidrBlock: 10.1.12.0/24 - - name: SharedServices-Main-Database-B - availabilityZone: b - routeTable: SharedServices-Main-Database-Rt-B - ipv4CidrBlock: 10.1.13.0/24 - - name: SharedServices-Main-TgwAttach-A - availabilityZone: a - routeTable: SharedServices-Main-TgwAttach-Rt-A - ipv4CidrBlock: 10.1.15.208/28 - - name: SharedServices-Main-TgwAttach-B - availabilityZone: b - routeTable: SharedServices-Main-TgwAttach-Rt-B - ipv4CidrBlock: 10.1.15.224/28 - transitGatewayAttachments: - - name: SharedServices-Main - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-SharedServices-Main - routeTablePropagations: - - Network-Main-Internal - - Network-Main-Network-Boundary - subnets: - - SharedServices-Main-TgwAttach-A - - SharedServices-Main-TgwAttach-B - -############################################################## -# Global configuration for VPC flow logs # -# Where there is no flow log configuration defined with VPC # -# this configuration will be used for flow log configuration # -############################################################## -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - cloud-watch-logs - destinationsConfig: - cloudWatchLogs: - retentionInDays: 30 - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/organization-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/organization-config.yaml deleted file mode 100644 index 7061819..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/organization-config.yaml +++ /dev/null @@ -1,59 +0,0 @@ -#################################################################################################################### -# For additional configurable services, features, and property descriptions, # -# please review class NetworkConfig reference in our TypeDoc: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.OrganizationConfig.html # -##################################################################################################################### - -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -################################################################### -# AWS Organizations and Organizational Units (OUs) Configurations # -################################################################### -enable: true -# Creating OUs -organizationalUnits: - - name: Security - - name: Infrastructure -# Enabling the quarantine service control policies (SCPs) -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -# Implementing service control policies -serviceControlPolicies: - # Creating an SCP - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - # Path to policy - policy: service-control-policies/guardrails-1.json - type: customerManaged - # Attaching service control policy to accounts through OUs - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - -# https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_tag-policies.html -taggingPolicies: [] -# https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_backup.html -backupPolicies: [] diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/security-config.yaml b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/security-config.yaml deleted file mode 100644 index d729678..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/security-config.yaml +++ /dev/null @@ -1,364 +0,0 @@ -################################################################################################################# -# For additional configurable services, features, and property descriptions, # -# please review class SecurityConfig reference in our TypeDoc: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.SecurityConfig.html # -################################################################################################################# - -homeRegion: &HOME_REGION us-gov-west-1 -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: true - excludeRegions: [] - s3PublicAccessBlock: - enable: true - excludeAccounts: [] - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: false - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: true - excludeRegions: [] - s3Protection: - enable: true - excludeRegions: [] - exportConfiguration: - enable: true - overrideExisting: true - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - ################################################################################################################## - # AWS Security Hub Configurations # - ################################################################################################################## - securityHub: - enable: true - regionAggregation: true - excludeRegions: [] - standards: - ############################################################################################################# - # Enable AWS Security Hub standards based upon a customer specific requirements # - # For the standard configuration, we have enabled the following standards: # - # - AWS Foundational Security Best Practices (FSBP) # - # - Center for Internet Security (CIS) AWS Foundations Benchmark v1.4.0 # - # - National Institute of Standards and Technology (NIST) SP 800-53 Rev. 5 # # - # Controls that you might want to disable based upon Security Hub recommendation # - # https://docs.aws.amazon.com/securityhub/latest/userguide/controls-to-disable.html # - ############################################################################################################# - - name: AWS Foundational Security Best Practices v1.0.0 - # https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html - enable: true - - name: CIS AWS Foundations Benchmark v1.4.0 - # https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html#cis1v4-standard - enable: true - - name: NIST Special Publication 800-53 Revision 5 - # https://docs.aws.amazon.com/securityhub/latest/userguide/nist-standard.html - enable: true - ssmAutomation: - excludeRegions: [] - documentSets: [] -accessAnalyzer: - enable: true -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - ############################################################################################ - # The minimum password length was chosen to be 14 to align with common industry practices. # - # If you need to comply with NIST SP 800-63B you can modify the password length to 8. # - ############################################################################################ - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: [] -cloudWatch: - metricSets: - - regions: - - *HOME_REGION - deploymentTargets: - accounts: - - Management - metrics: - # CIS 1.1 – Avoid the use of the "root" account - - filterName: RootAccountUsageMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccountUsage - metricValue: "1" - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - filterName: UnauthorizedAPICallsMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}' - metricNamespace: LogMetrics - metricName: UnauthorizedAPICalls - metricValue: "1" - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - filterName: ConsoleSigninWithoutMFAMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}' - metricNamespace: LogMetrics - metricName: ConsoleSigninWithoutMFA - metricValue: "1" - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}" - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: "1" - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}" - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: "1" - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: "1" - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}" - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: "1" - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}" - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: "1" - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}" - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: "1" - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}" - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: "1" - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}" - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: "1" - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}" - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: "1" - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}" - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: "1" - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}" - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: "1" - alarmSets: - - regions: - - *HOME_REGION - deploymentTargets: - accounts: - - Management - alarms: - # CIS 1.1 – Avoid the use of the "root" account - - alarmName: CIS-1.1-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - alarmName: CIS-3.1-UnauthorizedAPICalls - alarmDescription: Alarm for unauthorized API calls - snsTopicName: Security - metricName: UnauthorizedAPICalls - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 5 - treatMissingData: notBreaching - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - alarmName: CIS-3.2-ConsoleSigninWithoutMFA - alarmDescription: Alarm for AWS Management Console sign-in without MFA - snsTopicName: Security - metricName: ConsoleSigninWithoutMFA - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-3.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-3.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-3.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-3.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-3.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-3.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-3.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-3.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-3.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-3.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-1.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-1.json deleted file mode 100644 index 14e925d..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-1.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ConfigRulesStatement", - "Effect": "Deny", - "Action": [ - "config:PutConfigRule", - "config:DeleteConfigRule", - "config:DeleteEvaluationResults", - "config:DeleteConfigurationAggregator", - "config:PutConfigurationAggregator" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "LambdaStatement", - "Effect": "Deny", - "Action": [ - "lambda:AddPermission", - "lambda:CreateEventSourceMapping", - "lambda:CreateFunction", - "lambda:DeleteEventSourceMapping", - "lambda:DeleteFunction", - "lambda:DeleteFunctionConcurrency", - "lambda:PutFunctionConcurrency", - "lambda:RemovePermission", - "lambda:UpdateEventSourceMapping", - "lambda:UpdateFunctionCode", - "lambda:UpdateFunctionConfiguration" - ], - "Resource": "arn:${PARTITION}:lambda:*:*:function:${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "SnsStatement", - "Effect": "Deny", - "Action": [ - "sns:AddPermission", - "sns:CreateTopic", - "sns:DeleteTopic", - "sns:RemovePermission", - "sns:SetTopicAttributes", - "sns:Subscribe", - "sns:Unsubscribe" - ], - "Resource": "arn:${PARTITION}:sns:*:*:aws-accelerator-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "EbsEncryptionStatement", - "Effect": "Deny", - "Action": ["ec2:DisableEbsEncryptionByDefault"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventCloudWatchLogsModification", - "Effect": "Deny", - "Action": [ - "logs:DisassociateKmsKey", - "logs:DeleteLogGroup", - "logs:DeleteRetentionPolicy", - "logs:AssociateKmsKey", - "logs:PutRetentionPolicy", - "logs:CreateLogGroup" - ], - "Resource": [ - "arn:${PARTITION}:logs:*:*:log-group:aws-accelerator-*", - "arn:${PARTITION}:logs:*:*:log-group:/aws/lambda/${ACCELERATOR_PREFIX}-*" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventCloudWatchLogStreamModification", - "Effect": "Deny", - "Action": ["logs:DeleteLogStream"], - "Resource": [ - "arn:${PARTITION}:logs:*:*:log-group:aws-accelerator-*:log-stream:*", - "arn:${PARTITION}:logs:*:*:log-group:/aws/lambda/${ACCELERATOR_PREFIX}-*:log-stream:*" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-2.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-2.json deleted file mode 100644 index fdd0bb4..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/guardrails-2.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "IamSettingsStatement", - "Effect": "Deny", - "Action": [ - "iam:DeleteAccountPasswordPolicy", - "iam:UpdateAccountPasswordPolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "IamRolesStatement", - "Effect": "Deny", - "Action": ["iam:*"], - "Resource": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/AWSServiceRoleForConfig" - ] - } - } - }, - { - "Sid": "GDSecHubServicesStatement", - "Effect": "Deny", - "Action": [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:UpdateDetector", - "guardduty:StopMonitoringMembers", - "guardduty:Disassociate*", - "securityhub:BatchDisableStandards", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:Disassociate*" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "CloudFormationStatement", - "Effect": "Deny", - "Action": ["cloudformation:Delete*"], - "Resource": "arn:${PARTITION}:cloudformation:*:*:stack/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventSSMModification", - "Effect": "Deny", - "Action": ["ssm:DeleteParameter*", "ssm:PutParameter"], - "Resource": "arn:${PARTITION}:ssm:*:*:parameter${ACCELERATOR_SSM_PREFIX}*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventCloudTrailModification", - "Effect": "Deny", - "Action": [ - "cloudtrail:PutInsightSelectors", - "cloudtrail:PutEventSelectors", - "cloudtrail:StopLogging", - "cloudtrail:DeleteTrail", - "cloudtrail:UpdateTrail", - "cloudtrail:CreateTrail" - ], - "Resource": "arn:${PARTITION}:cloudtrail:*:*:trail/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/quarantine.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/default.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/default.json deleted file mode 100644 index 611ca94..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/ec2.json b/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 0e4a09a..0000000 --- a/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-healthcare/README.md b/reference/sample-configurations/lza-sample-config-healthcare/README.md deleted file mode 100644 index 6ba2fab..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/README.md +++ /dev/null @@ -1,155 +0,0 @@ -# Landing Zone Accelerator on AWS for Healthcare - -## Overview - -The Landing Zone Accelerator (LZA) for Healthcare is an industry specific deployment of the [Landing Zone Accelerator on AWS](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) solution architected to align with AWS best practices and in conformance with multiple, global compliance frameworks. Built on top of the standard AWS Control Tower accounts, namely `Management`, `Audit`, and `LogArchive`, the LZA for Healthcare deploys additional resources that helps establish platform readiness with security, compliance, and operational capabilities. - -The Healthcare industry best practices folder contains the deviation from the default [lza-sample-config](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations/lza-sample-config) and the differences are noted in this readme. To leverage these configs it is first expected that you use all of the `lza-sample-config` before adding or replacing the configuration files referenced in this folder. The primary deviations from the `lza-sample-config` and the healthcare industry are related to the Organization/Account structure and the Network topology. It is important to note that the Landing Zone Accelerator solution will not, by itself, make you compliant. It provides the foundational infrastructure from which additional complementary solutions can be integrated. You must review, evaluate, assess, and approve the solution in compliance with your organization’s particular security features, tools, and configurations. - -## Deployment Overview - -Use the following steps to deploy the industry guidance. For detailed instructions, follow the links for each step. - -[Step 1. Launch the stack](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-1.-launch-the-stack.html) - -- Launch the AWS CloudFormation template into your AWS account. -- Review the templates parameters and enter or adjust the default values as needed. - -[Step 2. Await initial environment deployment](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-2.-await-initial-environment-deployment.html) - -- Await successful completion of `AWSAccelerator-Pipeline` pipeline. - -Step 3. Copy the configuration files - -- Clone the `aws-accelerator-config` AWS CodeCommit repository. -- Clone the [landing-zone-accelerator-on-aws](https://github.com/awslabs/landing-zone-accelerator-on-aws) repo -- Copy the configs and all the contents from the `lza-sample-config` folder under `reference/sample-configurations` to your local `aws-accelerator-config` repo. -- Copy the contents from the `lza-sample-config-healthcare` folder under `reference/sample-configurations` to your local `aws-accelerator-config` repo. You may be prompted to over-write duplicate configs, such as `accounts-config.yaml`. - -Step 4. Update the configuration files and release a change. - -- Using the IDE of your choice. Update the `homeRegion` variable at the top of each config to match the region you deployed the solution to. -- Update the configuration files to match the desired state of your environment. Look for the `UPDATE` comments for areas requiring updates, such as e-mail addresses in your `accounts-config.yaml` -- Review the contents in the `Security Controls` section below to understand if any changes need to be made to meet organizational requirements, such as applying SCPs to the various OUs. -- Commit and push all your change to the `aws-accelerator-config` AWS CodeCommit repository. -- Release a change manually to the AWSAccelerator-Pipeline pipeline. - -## Security Frameworks - -The healthcare industry is high regulated. The LZA for Healthcare provides additional guardrails to help mitigate against the threats faced by healthcare customers. The LZA for Healthcare is not meant to be feature complete for fully compliant, but rather is intended to help accelerate cloud migrations and cloud refactoring efforts by organizations serving the healthcare industry. While much effort has been made to reduce the effort required to manually build a production-ready infrastructure, you will still need to tailor it to your unique business needs. - -This solution includes controls from frameworks in various geographies, including HIPAA, NCSC, ENS High, C5, and Fascicolo Sanitario Elettronico. If you are deploying the Landing Zone Accelerator on AWS for Healthcare solution, please consult with your AWS team to understand controls to meet your requirements. - -## Security Controls - -These controls are created as detective or preventative guardrails in the AWS environment through AWS Config rules or through Service Control Policies (SCPs). Within the file `organization-config.yaml` are sections for declaring SCPs, Tagging Policies, and Backup Policies. SCPs can be highly specific to the organization and its workload(s) and should be reviewed and modified to meet your specific requirements. Sample policies have been provided for the following: - -- `Service Control Policies`: A service control policy has been provided in `service-control-policies/scp-hlc-base-root.json` that prevents accounts from leaving your organization or disabling block public access. The `service-control-policies/scp-hlc-hipaa-service.json` policy is an example of a policy that can be used to ensure only HIPAA eligible services can be used in a specific OU or account. It is important to note that SCPs are not automatically updated and that changes to the HIPAA eligible service list will be to be updated. However, this is an example of how your organization can ensure that a select list of AWS services are used for specific use cases. -- `Tagging Policies`: A sample tagging policy has been provided in `tagging-policies/healthcare-org-tag-policy.json` showing how you can further extend these policies to define `Environment Type` for `Prod`, `QA`, and `Dev` workloads, `Data Classification` to track sensitive and non-sensitive workloads such as `PHI` and `Company Confidential` and how to enforce them to specific AWS services. The sample policy should be edited to reflect your own organization's cost centers so that resources provisioned by the LZA are automatically tagged in accordance with your business requirements. -- `Backup policies`: A sample backup policy has been provided in `backup-policies/backup-plan.json` as an example for how backups can scheduled along with lifecycle and retention management settings. - -In the `security-config.yaml` file, AWS security services can be configured such as AWS Config, AWS Security Hub, and enabling storage encryption. Additional alarms and metrics have been provided to inform you of actions within your AWS Cloud environment. For a list of all of the services and settings that can be configured, see the [LZA on AWS Implementation Guide](#references) in the references section below. This file also contains the AWS Config rules that make up the list of detective guardrails used to meet many of the controls from the various frameworks. These rules are implemented through a combination from Security Hub AWS Foundational Security Best Practices, CIS AWS Foundations Benchmark, and the rules from the [Operational Best Practices for HIPAA Security sample conformance pack](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html). The default sample configuration has the Security Hub PCI rules enabled, however it is encouraged to not enable the PCI rules if they are not needed in your environment to reduce cost. - -Amazon Macie is enabled by default in the LZA lza-sample-config. This can be leveraged to identify various types of [PHI](https://docs.aws.amazon.com/macie/latest/user/managed-data-identifiers.html#managed-data-identifiers-phi). - -The `global-config.yaml` file contains the settings that enable regions, centralized logging using AWS CloudTrail and Amazon CloudWatch Logs and the retention period for those logs to help you meet your specific auditing and monitoring needs. You can also define cost and usage reporting with budgets in the this file and examples are provided. - -You are encouraged to review these settings to better understand what has already been configured and what needs to be altered for your specific requirements. For example you may remove the reports section if you do no need any cost and usage reporting for budgets in your organization. - -## Organizational Structure - -Healthcare LZA accounts are generated and organized as follows: - - - -![Healthcare LZA Org Structure](./images/LZA_EGA_Healthcare_Org_Structure.png) - -The Health Information System (HIS) Organizational Unit (OU) represents the logical construct where workloads that contain sensitive data, such as critical business or Personal Health Information (PHI) reside. Whereas, the Executive Information System (EIS) OU is intended for business workloads that may not require the same regulatory controls. This OU structure is provided for you. However, you are free to change the organizational structure, Organizational Units (OUs), and accounts to meet your specific needs. For additional information about how to best organize your AWS OU and account structure, please reference the Recommended OUs and accounts in the [For further consideration](#for-further-consideration) section below as you begin to experiment with the LZA for Healthcare. - -## Architecture Diagrams - -AWS LZA for Healthcare Organizational Structure -![Healthcare LZA Architecture](./images/LZAforHealthcare_2022-08-30.png) - -By default, the LZA for Healthcare builds the above organizational structure, with the exception of the `Management` and `Security` OU, which are predefined by you prior to launching the LZA. The below architecture diagram highlights the key deployments: - -- A `HIS` OU - - Contains one `HIS-Prod` and one `HIS-Non-Prod` Account - - Each contains a single VPC in `us-east-1` - - Each VPC uses a /16 CIDR block in the 10.0.0.0/8 RFC-1918 range -- An `Infrastructure` OU - - Contains one `Network` and one `SharedServices` Account under `Infra-Prod` - - The `Network` account also contains a Transit Gateway for infrastructure routing - - Each contains a single VPC in `us-east-1` - - Each VPC uses a /22 CIDR block in the 10.0.0.0/8 RFC-1918 range - -AWS LZA for Healthcare Network Diagram -![Healthcare LZA Network Diagram](./images/LZA_EGA_Healthcare_Network_Diagram_v2.1.png) - -The accounts in the `HIS` OU represent a standard infrastructure for development or production deployment of your workloads. The `Infrastructure` OU provides the following specialized functions: - -- The `Network` account contains an `Network Inspection VPC` for inspecting AWS traffic as well as routing traffic to and from the Internet. If a route table is defined, for example `Network-Main-Core`, traffic will flow from the `HIS-pms-Prod-Main VPC` through the `Network-Main-TGW` Transit Gateway, where it will can be inspected by AWS Firewall before being blackholed or continuing to the internet or its final destination. -- The `SharedServices` VPC is intended to house centrally-shared services that are accessible to all of the accounts in your infrastructure. For example, you might deploy central security services such as Endpoint Detection and Response (EDR) or a central directory service such as LDAP. This central location and corresponding route tables allow you to efficiently design your network and compartmentalize access control accordingly. - -## Cost - -You are responsible for the cost of the AWS services used while running this solution. As of September 2022, the cost for running this solution using the Landing Zone Accelerator with the healthcare configuration files and AWS Control Tower in the US East (N. Virginia) Region within a test environment with no active workloads is between $1000-$1,1250 USD per month. As additional AWS services and workloads are deployed, the cost will increase. It is also noteworthy VPC inspection is approximately 60% of the cost of this configuration. While this is a significant percentage, the ability to inspect and control network traffic in environment is an important capability for improving your overall security posture. - -| AWS Service | Cost per month | -| ------------------------------------------------ | -------------- | -| AWS CloudTrail | $4.30 | -| Amazon CloudWatch | $8.75 | -| Amazon Config | $35.55 | -| Amazon GuardDuty | $5.75 | -| Amazon AWS Key Management Services (AWS KMS) | $15.65 | -| Amazon Amazon Route 53 | $2.00 | -| Amazon Simple Storage Service (Amazon S3) | $1.48 | -| Amazon Virtual Private Cloud (Amazon VPC) | $301.56 | -| AWS Network Firewall | $648.52 | -| Amazon AWS Security Hub | $44.32 | -| Amazon Secrets Manager | $0.48 | -| Amazon Simple Notification Services (Amazon SNS) | $0.42 | -| Total monthly cost | $1,068.78 | - -## For further consideration - -Although the Healthcare LZA aims to be prescriptive in applying best practices for Healthcare customers, it intentionally avoids being _overly prescriptive_ out of deference to the unique realities for each individual organization. Consider the baseline Healthcare LZA as a good starting point, but bear in mind your objectives as you begin to tailor it for your specific business requirements. From this perspective AWS provides resources that you should consult as you begin customizing your deployment of the Healthcare LZA: - -1. This set of configuration files was tested with AWS Control Tower version 3.0. AWS Control Tower version 3.0 supports the use of an AWS CloudTrail Organization Trail. The global-config.yaml file shows organizationTail set to false because it is enabled through the AWS Control Tower setup. -1. Refer to the [Best Practices] for Organizational Units with AWS Organizations blog post for an overview. -1. [Recommended OUs and accounts]. This section of the `Organizing your AWS Environment Using Multiple Accounts` Whitepaper discusses the deployment of specific-purpose OUs in addition to the foundational ones established by the LZA. For example, you may wish to establish a `Sandbox` OU for experimentation, a `Policy Staging` OU to safely test policy changes before deploying them more broadly, or a `Suspended` OU to hold, constrain, and eventually retire accounts that you no longer need. -1. [AWS Security Reference Architecture] (SRA). The SRA "is a holistic set of guidelines for deploying the full complement of AWS security services in a multi-account environment." This document is aimed at helping you to explore the "big picture" of AWS security and security-related services in order to determine the architectures most suited to your organization's unique security requirements. -1. Transit Gateway Flow logs are not enabled by default, work AWS team to determine if enabling TGW Flow logs help you meet your regulatory and organizational requirements. -1. - -## References - -- LZA on AWS [Implementation Guide]. This is the official documentation of the Landing Zone Accelerator Project and serves as your starting point. Use the instructions in the implementation guide to stand up your environment and then return to this project for Healthcare-specific customization. -- AWS Labs [LZA Accelerator] GitHub Repository. The official codebase of the Landing Zone Accelerator Project. -- Introducing the AWS [LZA for Healthcare] blog -- Get started with the [LZA Immersion Day] and follow the section for guidance on using the healthcare specific configuration files. - - [Best Practices]: https://aws.amazon.com/blogs/mt/best-practices-for-organizational-units-with-aws-organizations/ - [Recommended OUs and accounts]: https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/recommended-ous-and-accounts.html - [AWS Security Reference Architecture]: https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/welcome.html - [Implementation Guide]: https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/landing-zone-accelerator-on-aws.pdf - [LZA Accelerator]: https://github.com/awslabs/landing-zone-accelerator-on-aws - [Operational Best Practices for HIPAA Security]: https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html - [VPC Sharing: key considerations and best practices]: https://aws.amazon.com/blogs/networking-and-content-delivery/vpc-sharing-key-considerations-and-best-practices/ - [LZA for Healthcare]: https://aws.amazon.com/blogs/industries/introducing-landing-zone-accelerator-for-healthcare/ - [LZA Immersion Day]: https://catalog.workshops.aws/landing-zone-accelerator diff --git a/reference/sample-configurations/lza-sample-config-healthcare/accounts-config.yaml b/reference/sample-configurations/lza-sample-config-healthcare/accounts-config.yaml deleted file mode 100644 index a376b5c..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/accounts-config.yaml +++ /dev/null @@ -1,36 +0,0 @@ -mandatoryAccounts: - # We recommend you do not change mandatory account names. These are used within Landing Zone Accelerator to reference the accounts from other config files. - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - # The "name" will be used to set the AWS Account name - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: Network-Dev - description: The Network Dev account - email: <> - organizationalUnit: Infrastructure/Infra-Dev - - name: Network-Prod - description: The Network Prod account - email: <> - organizationalUnit: Infrastructure/Infra-Prod - - name: Pacs-Non_prod - description: The Non Prod PACS account - email: <> - organizationalUnit: HIS/HIS-Non-Prod - - name: Pms-Prod - description: The PMS prod account - email: <> - organizationalUnit: HIS/HIS-Prod diff --git a/reference/sample-configurations/lza-sample-config-healthcare/images/.gitkeep b/reference/sample-configurations/lza-sample-config-healthcare/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/sample-configurations/lza-sample-config-healthcare/images/LZA_EGA_Healthcare_Network_Diagram_v2.1.png b/reference/sample-configurations/lza-sample-config-healthcare/images/LZA_EGA_Healthcare_Network_Diagram_v2.1.png deleted file mode 100644 index 137b643c99a8e13617d9475b24424e67c301f0f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 560646 zcmd@5cl_Jb`34NL3ZaY;5@sNTKtkA-C0RCMX;_wQOS0sl0f9=gWZCkRY*{vhQJ|2p z3njFylCm2{*bqhwBoruw8D`i!VFU{S`ia|9bv=K5=aM_~_`2`@Zh$zRvm1 zFYuWye!b(b*IRGBEm($<)?07W1?#Q1@r4sM1|_%uAvqU(tnW)1V!i7g+v&aa)T=wg1&D*BRHFO?_sZM)!+>re<@x zS#S3{zB3ahz<0LYx0>J+6oYr62;LO%ABr6bp+{nSf=j&HZCbKr$RUo)t0l> zF4973r9!rr)!-wo82!^|0?YI3o%T=Fp!j~se#xJH=#RSAzb4NL>Lz$#d?uU*(_5|U zHBSeV7pwnk&b%C^R?QT-Z_yCy^)8BXL*}PO+B)dzk1J3B)3z`4QzKXf>X%bNh7J&D z%#V$XJcz~Iu5PPh+Vw;?tC_K)J)qNuikIWOB*&?ahb283%{L3hK4w_`rdGy`Rx#^n zOaKcq7QQ60b(gsmZ^hb5aEy?_A`S0IiMFX2{rGMefK-fP@T>S>34Le7hhjRE&uu zK`E0W5mm|;$h;lIMhpVi^(>OC7N`-{A2xGiE8+Fbrac6UOUKEQNsK&j#W7k6N)^DB zmTKp*6vKhbd>AmvcvYwe1ufZU0?uq?v7!Nf88#akTS5hJX@x~mPx47K1-=AQ8V3Dj zLd9YgOrR}wlmR^eMFS4Wf(i(@1Rtq!FdhkDf@n*tni)-Pzyuit)v`g*NOKI;Q)~sr zdSi=?TC3FqDk&A>ltXxqZW+UFkWT?mB3LRJb=h~VPL~AluJCiV}k=n30&Xp>IY>Fv?A<@Hr0j4FlfW_-U3W1~)T|x>jYo}dsM!}aOreKnX}~&7q}_qe2CXZB%oF4Vd;b|(9P65Xw%0Uq< z4Xv?WJyk4J3wcx_WS91g5@?mS=weYGFld60IZ!A^i%7Wr5NUu5H(i$%FJRhU!hoqz0}5e=W8lE_*f0wPgs2s(s%_8_%wgca5A%sK zhKD$qQE)Ha=WM9yRb3;k)MW!1b}=`el2tkB$E%uH_8FmAC|e~7sbM&**2|-ohO=FH zJcdHk>sJ&!mFxCwUhT65lh@?3X84_Yi{e4IS}N?wz}q`DxRPXISOY25SqB;x>SaIC z7Gt*Lr%i~=MwSNKRLsJYmLTiR=w2;!qcMOU6XPCACUhz?x288tjsg>aGF>p+@sMF@ z&s5+nQ;HVJXyN&&)`)92Bg1bdN+uV%SgM{ka>YEDBElIZz5IAs$U3c@7qc6{>=9T( z+5p(;ffFZ2LO8M|CT$kv(x}>Ej{eR@k217>bJR zlp^P~-3-p-rU)J*L7CQ&!r3&b<47ak?jiiZ6zk*E0JDXnnoDz>oFS!H8?Ft;Xwh*8 z+Azh{G~QrE2CN-18fkJLM`J~X!zr>?s3)S{V@f<5cAfe#l#B|L^_$&B+AS)ood!Qf z9^bU2umI=+SToEACPq};q+k^&DAl$>VVJCQV$(ZB+NvZ1%!57QHH7Xu++ekO0r!yUBBR@JI#0uG}fuY-H}3v`LfrQ*mhAb6-t=uWHRAM z#N1KEt2YKEIR1wF3Uqw(MIIU5&lrbx&q=KOnOd^zoP6}zU zeou2Pq(W!PS&pYvRBgv#m6o_7?1vG~XAmhZqNHfG+x=QCD^r~jHQ=hDsm6yo-jT}< z3sNc$UaNs-TRhWgYZBQVRFRGe`H4y^RpzRLm~Zzw1>lUdoR17k3Xl>*Cs2mtt!7*B z>+Q|}_1fB4(V(o}V7z!+ZQ@lJC#x;I7!><@GwWrQ7Gx*1R!Pm!T_{x?A?av6trput zB&VH9nkZDUhsk0KE|yrAujoaVuJpTXb5xc4^`Hyh#zw?(3N^tAs}f&u3T(;^u#^D2 zm`L(NuFMK-Iihr(av|onkt4a;^2d3;P(`3g?h|u+NqLgdAS-vS9>&;4BV6)xi4q8>2}hM9GlIxe+0vE z&~n0*)~yEOxY0&rn*xA5A)RZHUV`H6=%TmYb?V+K}LaUf$;OZ5$b8JFuao zWz)||Of(_(5LF7<4i7jY;nnjY)lq8co))o3hi-N1gHB(EN@c;%HdD!zEoF-kkz-m3 z)Tt*?D$Au{hUSN$wiQjf5T_ehN)_y=>4;@e?Gcv8ilWsV<+xFRdwi)5)~jAXCp;%nD z)kKxW(e|)7(4nHoCmKW*P4xY$-X~f`b7Yo~w5$s)o(viZ6VZxZ8YAJ1T9)gYR_=^? zYP{Ol@PMWOGbl^+w;6(o&%muOJ3bE5%&L%7|Tn{edS}TT&b9 z_bYytki-sSR7|#@>YuhQ$_a$oLwHCC_kxn4zRRAL3rHOoXc6Oypk zkkc8Z?c@DyJi=w)j0;{HTo@us{Ros|0|y}n!g~$M@B+^P2^Xyu93Q|ojhn-!S`kRT zLrICCDS{bSEIdp)o*(5MM!u4Sj547sRo!>S=|oIYYGD)#h*7mS>{a2ulN8;4E#GxK zp>2(lqEJO@`I1L!bppdVtppf2VL~a}O%_=*gP?sa!mL&p^;IQ_wAL8%R3ReTf*6a( zBr5DAdMMR!QAACcOex0K3f;7(XduAEr4Y}w+=^V}vPQO_hT4fSumiO=6ss|x=u1Px zN)3t@+DqeTp(heOEFEk24K^mjE>4W>a*gT_(~)78S+eUB3gn7ZbA;J=mBL-G#pg0A zhSZCU&Qv=pu5|^yNDROT7{98q8Ldl=#juOyQK*p<`An1_03xKLK~dxzU|I;ORm)|Z zF=El}I$Rgae7j=MVLGqV9w3LX&sJip3fhT?a4yD}E@lVmkR+v%0Sgq1QoRoNC2uzI zmQs?l`CK1Cvs|p5HK{u4o2fKp<`oO-u-yvP&PDN%_$hj1253K#%&9>*4nKkSqWSDrVk!Xr|U|**-f^SIc5|DNXxAnDLdVFOe53wMqQn#8cEk{ z8yOpeV`yFhI)ZOBIL;lnxKtl5umKXUv=C1UMs?RN0YAY5OP2>2LQ<;R?=jV`Xh3nx zRV56ws5SyRq~ufBuo1-rA_czFsa#PHA}au|e%i{xB-0R^Ng*t-yq0fsRcOQwvSvas z3pQO#Kv{rOgbRV%v|UZGrk~H{z?YzA`0aWg;S!aoZQcp!K@2f^M7B>4dc6kbq5Y%) z7`m#niG(Q`Alt&q}o70Q_z^57**(o<8#HEXVZdmP*I_!#qUyM^$tv%{lExqf)eno6x zYO|WHN*I$8VY@nV93`jua9Fg*bbTB&kdW5Hv|lr|Y8di}7j$^1!*o)GrXn)bC}o+_ zuqhQxo3kYlzDKQ54ZxY)lf912pw)(H7QtK;J2cZVY6jcRWyVBFDf_${M}{#pOPD!= zVMV!br-T3y79t2JFJ^jpbiSWFutgB}uG|bBXZ)PZL@mgRv${XFGCQszF4rg&A!tB{lnlLd6l`j-b$f zj1Y~u=C;GMO2q{-rvVnpt3s}a;{AA#^oNX|AJ`QQClW~+8KK>QjX-&83^yCPmrql2 zr88(W^c;dAtl2O31Bq1=eP9Q)oC%ba@8VTl$txVq<>GLYfU*?VCu>|eB??ZaUmbTW zg)5a5h(xhW&*}|oX&KM)s5>m=^Ce8kh89tPyUn z8b-(yHQm=?J4#o`h`M1FaFwvJ09<=;l<0z22b1_p&K+_dOrTY?(ZsWM)i+v^PXgIB zPG?C)44Pce8!LTWE;?`mA@gCfL@H$Pf zy+H~Zb|TI(?%5F&84F%AAHYM9a|m40ENBHcj`ZdDxD_8uJmdHolWx>qb6hinOaa`` zDn^XAJ`^$(-71buJsuKrM-7T~Iunm#lIXeF%b2qF{> zBhxANi%}L>uf&bEMu|G?xKvQfiV(_KE|`0gpLF&44y z6lA!Q4I_vy^s<1R5_#3GWC-1LyPXt1mbh4{U}mZYLNPe%J1H?XkjJ@3K^TccACt4Z zS)-%pz@!Y1MYmC5$9Qf;v}#6-GW<-@M#cnXBp^Hsm6J);cj}JR>*L8VHi~m}A8!Kl zC0u&kis8eGOJFr1N!ObVVr(l(py=07SZ}b3kLyr6?wU=J=+YeK6g4AbiaG*ul|e)6 zR6*V=^?})h(SU$a?a2_HTVfDh^jLJ4Dm$LmR394kd9w?2`Ez;9tdCRNSfw~+)oL~iEkS2Iu zEoZ4}3{)b_w3vk0azsGvuuKkGsaRjNT6E}*Yk`#YnwiX~g?ZViLRT?>3PDE%j~)}_ zQ4o(a1S4UU2y}xCOXSd0%mLRJz%jbq8}(g~4&~FhGg9rO5e9gSOx8L$;IU;m36PR# zj~EfeKe~uzSijT>%~*HX0d^N*S#gy9aqA-tcyyF11rf`kv)R-rLZbnpHTosU^U#1N zx*($mdr^oQc0l?_U})?|4hjkxF+*UnB*ayV5h7Rmt$y@4&qVVvF@oxbX?U=S03jKb zs&OWV<;|kw(+Lly#56Z7!6U$u3CGPLPG3-Uy`CS3sv$^aGZs&Dl7z-kVrz(2op$6Z zafC`nhS2HSc{|w|RO>7$Wf*+ecF3VL^z?(|itPSGeF=S;Jw^K~L6d8fr$){yF zVGlb+z@2G3FKJnN*h#l++aAM^jV7YpmcaNCX9EMK`)%>ZU=`PCy#gZ|mn0m~Aeo@t zVu#Q;TSdKrAdW+?h%#)g89lGx=0~tUV1{}UcB3|^L@F7K3PGa^zUWLoX%&Z-Mg$?~ zk1%vyQ9=vWSiy_q4l8L?5NGrzhBnDUn4*nFRZ5{@#%Oa*8Wn-i)6zT}v{M!vY#}m^ zqS>YOeqS5(0Hn~JD#+tm(zI#-G{R)BDe+~g>9SE=^qFY(Glp!znD1cmnmi~rl}-ou z3gc!kNfrBYp<77f`E0^2AjPb}#>)tTb&`>tsJ)tq<(tti6p^kDn>f|gWLf~YQ>YL` z9DqI`IbqM~gnVqs)hfkQ76^jrG)l|d7?sJ$1vxqIkB(Wa^%a)YEaPg3DCOFM&xk$B991Jf>i)0lpH+ zV5}9e5!0zg?pcMSJQEHIVpYZr$Z_@hxQ>8dh@{c>2CUZPs>w!}k>hA0R*OmL6xpnB zAbl!Cxkm?(eOYwsSRQ~AJsznJn=evchaTnzKPo8>)R>NyN_5icbm}M>3_Z@(m_AhGzUkFsGyCCfr4+k+rM$;I+zun9S2n35s4n%PbtRqF|f%k+?xm&?Q? zQ|Rg)kYYr!s$3eu{falxN_+qtnL(o0HPBoRcOru`Xjb9+sbq&twQ5~Pb8FRhy-Vtf ztZGctky9`boQAxSt+ja6@nxMBNt$gYc%hSz&<%AnZkH6HiW1`^w2-o7-NB7o!%*3H zC*?H)HBCoe;>biPmt)E$6W91QDoN=c$UX|$V3+}rl9VVt9(T07f{?9PI@PedEpQWT z^KcKZckN0?X(ftIOK&tZPl;5{LCP--N*>UpIy?oUEe+>;1mH+?Q6CJ+LDv?1O(Z&r zR--%4TR(BDPOMsxhWVD9w|GhvA-NkdcPK6-3ux5GezD6d7!!x1_E3=`=v;*_7c(g% zlNu;>2tY5b<+6=*J)Y{v^BLmDB$e)BK}Z#&IH|#|#}#;A&uJYR*I1`920}N{6G2i& zm<1q0S*8&*g&|e1jPqgItoRZ-_MwIhZicNmBN4-508*V)qYJV%It})JM)`1nMx!Z% z(F6qo0fc~vwxQ@UN2*{%)nPvK(}+rl;r6!nk zleCF+J(K8-fZp0=YiXY*bw|MIu#{$rRuV9On(q~rRJ=EkohW<>SQ2y^yc8G7L@Q7JQGMk%aSVT4LR2zWZxWd`YN01^-090NIoABRjEBkL5*$R)BOHT_{RYlJ;c z=!_Ajov!v`I-QTFs)5a+P>cg<0Zj*9SWj3{^e7iNWo-EFNGWFk)g9L{wS)yk6BoNJ z0~+8tzgooVoos&KfV~>CT-W0*PLfiCq}{+#@jl_-%I-d2>D-%oUPmeEBPloNoidm_g+n1M&uV8rUxf^4DCk_y!ZIDG>`b6gK1 z%$}@VkI{@)*w)~D7a1>0vilqU7{6fYAK+n z6K+IcC{}R*kEC#`oWVQ#C=v{cs2$*}V^ibEAafZVLwqq9s*!t!xg;P+HP`k62V>+& z+$of6Bg9T);{={GJ7mnq5c?;S4M6L@pvC|d03eN#l!Ek;7*7-&Yh+dVTD2VUU*0NG zdKr)%Eh`Q{c0V@334}arGB|LSWD-y31QAFmF2Wai;XvhKl z2I^x^OQnlixn1?6e)R_-o2!qkluVIKwm{}`Ld|UOy;?)Z9op;W>2kH~cEpb8S0X(n zhc;sI7NXU;bR*5vC1Z@2j6$C(w~%g)6A-uCqMLfL#__NQC_))3H;RocQR51rIwY#Y zl)yD*lCNa@ejgrY+>YeL^;}mNGe8&OgR1Q`a5e1pGD+0Rrg)L!j6{nybRf!C+e0|3 zGEkPwC$yA?0EZ#sOpxg?Qih^pz-j2#h>zLrE*?uJ1PdetM3-_$a8_z3wQ;pAstu)Y z*PL3Z?3CSZ-WW;-*q14^#GyWPioH>h=&2-Sjdia}Hzi6P56!Sv)nl#(HbTfaf)%)i z;{oRE%S00AXj-lfvo_a2SqU;76YK{mbvu>@Ms68pp;$_dvaG0qBLHC!cPJ6niH=ED zD{hH}MhUxL9uDjAD90cG?-!dTT-M^Pq^1!aIl(*0Vl`c3t4Xhs2vrA57vfkr%pyt* zk6^eR0+EwaN+UH!Ae|H`P<64b1#ntSjcX$=9RTH#im8a4CCdOAX|fpSRHUM#S~92S z8BS|My6Un6AJJI9+?7G1qN-{Yaq20@srWq2wz68X+>0fpyqoD%nJ`=Gh)5v?g_@v; z65i7gAWNl5L-K8+?;w^}_Nv8ptP>g|tn;H9mQs4Oj}48OR8BCAFhJ3~(Sszs74vA{ z&<&`JCerbU)N}l>tK}t~)D2r_4scd0te1wJcEhD#IRH@brN}vq&>=oh=D9nCC`mJV~mAV zmzRB3*8K+AiUUzPD>S0Vv!Ui6m`*i^uI5kk^{j0PIihi)%Ux?sCDnW^MO4tLu;3s^uJAQ$1c z7&#=+D9(0~0^rWzqAk^v5*hYVd_ib*lJ#~O6c*EU-4)V~m*`P;E7d@g0}CF*T`v~I z3;~ zDFyYuQBBm6q=y1s(JrP;E8)ZSf`hOgAXRj_l&T>>Cz8RANIfl#lQ`2Bvy#vR$DP=w zpdzIlmPr~;R;PGvI^Xp3-I$YYbGFdR8bS%f4I zOQCV1&r=+gil&Yja#E6kOsdGa(aqph!EyYg7)*i-mj@(jfk{KfQIz%GSsb)e%gCc?QHJACnG^c4UV)be zv0SrE)(wYedU}czXo>{qSx`6Tut76$I|`^D@1YPc<6|dXOjtJ^(^MZVN|TiM7Cb{#9XWxr1LP@Hsg306Ms~f z5e4=2dd^IW*#K5tTt-W|Y}R7^g4|{4dbxu8HHyc(J-VQCR67Yyo#EghoB$S)NCKUu z9#TCX9}QErYMRW{1UM*CD41lGE#lo8!HZH^s&#`1BJy%;$RT~n6MhQJJzB zx440CRt2b;z<66%hm9;*BU^k4&E_K&W{{!KY)K{u86w#<3Q!0(F6A+su(G+_kWPcW z1~jMCtc+G;A>H?kOg)}(>vcaS#FItJ=~=lRmqk&KgSlOEfRJf3n=^Clm=GblD0TXY zQ4z@a!(22XKy!k6M5>jsT5#!{S^!O;c9#m9WTxSn113ZaBV>h4zMBM{2ix(Czyfxo zR7<*6xc+}S7?0)%%1IBr_l zAm2;L4pjih;z8vCmvf!6V7P@QW@17Y2U~61bErBIaNwA(f$gT!AmtHKCsD2=Aj=thBQICU z4{%50T~nh>kESYe%_;JJd^i9*aN#JWN__x}gdvwy#qEI%xa#LQ&*iG%fUG&9nSblN zqe7aH`3}%|qYQ?oM@6X5x>>4`QmQcmAAs0S3=B2HNOa0naTjwn*SF%yG9l%{RO&1V2m5$#$9?BS_Uu{NYVSrRQJ8Usjk zEDh%+h|w}57a4nypTWse+r_a#oez{!37l9+4(*svW1^{gB*0PvLqNk2>J~Ig@SJWX z(*SNnFwLmg>+%wapnf?|mQsdBS?wC(#3b7SBob6giula|O3E-~~ES zQ+X(%VG-B^`4qJ2k7S+Y6Hq}FWG7DbKvCYtI~YhE({k1Xq_I{ua)R9VoRQJjcxUMP z35q6btwaSJY6NGA$v!E>D30U&oZ8L#AXTC1T-yN$Aa%_l2xAXN`Mn$cC3c*CYKL^&h)!M*9bb;j-EAoFzfV*&S#TIS(eRrk7W_omum&ZA?2qT6YByc;wzAhh)o4Li&+)T6l?7UNi#K?(*7HZXlN;@(m@ z6$)f5nozL<&PaoP5@t%TK`h?p8(pN{I(JCXz^I?;Fhk#y7fML|=|hoA*^_p^oY`~1 zjLT;K3Aw7kT+K|ndbaqxjpNjxrfh-y+NAG;_`_(1t(2f4*Yx*ObE>(c+br*Zz!MauHC-}!9QnG4?f?6a#s z{bfUY&YCXnv+U4kE}U@6asT6W=+_gNDY;!CvRE*8lI<@lz(BHTyi{&kg?n&)m=3X!iw|pV>P2 z)_uf%cBz%kt$)_Y`jeP*kE51Q551TC@7Disz2i@dp459YHIJBYqz;<*{$0Nq`=JXq zePHGS^qM1A@E?6Q{E`%wKXcK)R&mE?p=r(cQ>87|HKvF9`*8XPkQY7*er3MCAqgQIrjVBw|kba zDH&V5JNthQu_YR-(U4}5gxwTIoZ-^J7C>^$q|-8_53ms>9j zl`E*N@B8xkyMA@*8bO?S-t1c&&+K*EE603u$Eo|@^V~sSz4^nLr9|WD*flTjYp-ISTK*@rLf5&uM4q%0nM1pVcA&tLIg z_1U|hx}$yglryiqAdptL2DnF{Ne9&-yinIm52AANMEPj()jSYr62Bf&89~l za_k$YV=v9$<6`Bi{oH4sojbf}&WE?Zar7;du9d#}ls z_|I>@^YVuN;tA$46htFOecl|dS@PfuuTGoynfOid+}X=dFg|+u?&5wkzuIQmF+Y4a zyzTxi=bV4lGgpbb-DW+qEjIe3Is{X@^zz)oJ2%`iIsLVV_R3C~n({7tsCn1*BX)_wY~DSLXK zgV#M>ycIg?%8LT;uk!W5(hqLEVCUb?uH4cnW54;^af|-+@w!iUfbHAQ;SYFd-ZvlI zb{#c;>kom6_}_i=#qj#OK3VV$Sl4qFOxQLz#oXfMsl^A+*m>2T@e__-hfV&&R?nTa zQRcMm&z}F(dprHd{r`3G>IKK`FK+(#y(?q($-;QzKbHjBSCf|Qckj2}i@SdIL}r&e z;?~2>-4;x0t$F#Vj|TUC|Mg$p7jM0G>AIDNt@_}$SK1RV{$|ZhXMQkk$9VOW3u5|j zKUnbo%Xcr`v_#GPX7%;=9)6Sk=u6KnF;@ZKdGCPXhieZ0^3@H`x!pNu)j^wWw_ktw z^61Kkj#zQvr>9L#c7KCYOF*^fgWsU-m!3O#vwdIQ@P|1c-Br9HcS`?kaB z<=Dm;3-Lu#{e*L#M*|~q#qvd4_w)x<- zg%fIPW_hogrN?mWCbed@O9(`GN9)tPZQwEVF4 z9haYV?(%PbwetS=9(?c0OWLd7t|Ie4A0b=%?>vUybHuDOQ~uk}E!}A4QZ-Y{pLY6b z(-%K==Cp%eSbN5L_?g7Ljvk`}dzbaX5JY>E6O4##diHIPjypK3(Ha zyC=Nrpfxw#|I({}KjDKK&OKk* zU%cR6e}{hm@9qGw_rm?jcdnni^wyl7PFmY}f7jN$uiv|}egE&aIO&DUxAeA|bX)l9TlhAUwqOD5Tzu0TTfMr~ zt;ru!?_4-pobtx{pZ{&ci8nLG1nX<;&a9*P9%*?QCW9^AoK+L_i@EVbxyexjphjX94vj?Dm?jQClztp;I zm3_fN^?G~N_B-tP!q*>uy5f+JltU%nOuUnZYzA@ieeE&^}yN>y8JZJy!zJ2?GPX6XaXD+^e zH*U$6yVQ676Y7OeUsHZ$$)y*rvsNzs%Y5Uszj$vweCrNV^)sfN-|YVGS5t3Tk$hwG z!(MrN?)pyP^WqO*xaF_b6YoBC<(c!Fi{H43KJ&8& zcX?U8;lmYkHeI=OcpnPZPPthueoVO zaOBZzPht;0;{Au<6$d^w?Opox9jz;m{dVnx9~?jVj=hm}yU#f(yKuh`uDj#y9fttR z4x9Vr^n1Q~`+{3{d+)x@_U~VL@%Wk-Z?2zn(yA+_AGh{Zj@qAEvd!L==QiBw_mjoj z!$Z?&Ejk?x@A>_9o#yR%;(N0mK(F3|+-%<6AKtk-{~58~X03&re6QYQKYxG9dj3Xh z*1eED?N86Ywd{<2v%tVUxc8h{+zT7e*=)jrvv2y#dv|~FvY**`aPGR+EAze>Y%w)) z^U3=A1x9~p!@XaZZ(cD{-Z}ol4QkUoy#nIVjK7^R=R;?+$*mV|hHlLsq(1$_%&+hG z9|7giy8!)KmfPpKb-T?s{&@HopHoX7S0~KHz6a3Qdi0OzZ#LTdf!%IA;)WfPFMPe< z)YH|?W{tOzc8G5?b)Y^qYwAY7`s&opCoR6@*iRnX<3kWXu(N-?cK#O6F0Ny(7m)W4 zNUeVA6yxlb^X6~8?ReUvGezv?oyPB8dh28V{L{h2)C=yt`{{E}+xy7_KKth%;ns%O zPFvmk>fdgkG2eLEe*3FW-&y+gy45ez-pbD(+UNCFa*tnKaoc$--dVh&xc|4GDmy9L zojG&ClI0&gmPQ{(W6U?yN-9AdC~%7LU{sDJvw8&?b7p4iT|$uyWN+c zFn!_`2TnigSC_7N$zO5H$B&=5=n8JZ7lnsj86EeIKK1I&zdH5g=EHM`tE@?nZ?){X zi=O#2zTr0)p4>VS{q-UR#F{y~Y0p1?Dfz)W2O^(5o_!jA^1vB?e`1$kFFoU|ckW9= z#ORz~|K8N;&mh9PiCnOW`1M!MnP=VsxZZZgwyQslIM79dm1^aZlQw)Z^U32o@3mmk z&ZmDmnzZb>JJta&d>;DjoGBOo@clQ}Q%joP-Fw7$zhur4AwPbnC?hy|kL$O;bJ~6j ztnsQBCofxc?bOMGYmWWl`)v=}Y|qV}pMRBnit)tC-rYB!Ho-s}Pv7w9!_B~MeRKcB zLy(7e5}w{$nDXN7y}z(Zb1!>~g}1ngshPv}IONM$PuTYp1;mk$KfU4L(`PS#;j16M z|Kbq&n$n(APms4-_K*DWho<$z&t7@y^S2-9JU;!!?RovX3vLAqEF8G@#HDL)e5Cp9 zlpFPruRHGht=ZED!|QjxewXHyGozq!*EtimS;kCW(0?aA<*=o}wSU=vin8JYY{km8 zGZ$=g{|{>pxcq0{cY0a_vLP%z5M<| zCf+`I5B}zBcA59t+2+D+^d&;_wT)KK`DBNaHWA-k44SyeW~>R_)6R|uYm`%^-);2Z zmRDD<)wa3${_6w}t(M(2+kKhePnfdXuddo| zeApu8t{)a&bx*1iACO^df%yjD6S^5DgQ z8-A#4Qv2?_(kZ7XM}NNJkRP7AtoY6!YZvdi)v_-)S3kJsuh-o;c-K55wS3Y1^kI8k z`N|V3=c)H^dC>_}a|Fr zpE~vaJrBFBeampukE_>SGUePO=2SP{b&FY_9Cq1?(QR9uzTt0o{%Df}^$)-Q=A+>k ztA8p#a;Z_?w^ZARN&cxgQVSIbRCUbxN+Fv)FbNX@RTbt@p&h8(~ z*kGH9r(CvR_fJ;7weZLfuMjS2Z?HYFObqPnVyK$k?1`PnorR#;@+%apJQlJ~r#3 zJ${%asp6vNB4Q|Sx9-5R_kG!A{&(I#9pt~z+=*+qny}rn_}x+Dh9+I7xYnb8TvvZ{ z-^8q&_z$j~eDd`l-T2zVb@i2x%qNy>*y{VL&rH~tTKUP01#44NuXz5HM>W)adGWo# zack#zh`7&%hpc{l?I{K&oO<(?tM2e?FRsULJaOe8-rME7iQ6r!Tz^FC><8|9_C0m! z$veBR-+cd)%bs5Yc)9h~^zW*dt$4`U`t%Jqm^S12HP7Gl!FL;cyVYBF0sNq#FP{`v zep&vx95Vfa2@gzJc4u(c%?0X)@PKK1o;W;k(z4`s6Q4LSxbE;fZk%s`qq_GQCvW|$ z<2Pp)%vPEFh8u4lJNq=}@Oj^SeoO4hoygl)XaBI*^N;Pa|J*lEnY7zJJ7M?jF!8Aa zPy6)ahxlus;^$mjc%K!fym29*-?tqj?tgQ5^=^OpVDrs73s3ffIlEd<00uSV;$3f# zvYuJ>wOeKJ!_7^UtG0T3-&p?q*rn6cmw$1?#EBavr!7{M-<*A%KRt|cfq6fC_r;_` z_Bdeb((BIp=YjVy)@3h!cG*D(cb>iOqLVh>KRd;|a@$iUQ@d?8@zb4G+;Z73sO!IJ z*Nj&uoi+Okch_kfoJ}psUw71Yx3vGc<6ckPz4H1aJNM6c@&3OZzu~?&ZvT4mvURH> zF7URo^5{K5+}`t)#x@VnI(*6-wTOMsdGjnXKgB%gpcf8EeX{UKan0%2%QviB<4jt% z)xGEKH@D$Fe6F(kaca#BV#kvneQI&}rdy}o*}DM!{=r?2J^BK)_Cn{Cd+y%t*W3Sg z+o_N2c~>R`!J7Mw`1Pru0jQ<+dSl-D7d-mf7Q@7qnaRtZyX&z74!+>3`O@+^6P8~# zyRIi*Yd`v@lNPntuJ`c$Z-We}{PEu|dH10m zcDr%ArB@RH|@H)Z>8_C4){Wm|QgxbLIhHGBNLL;qpBXGba0 z0HjC&Y)$ox?>`b=n{fCsFK`dui2<|!)j9Ur|J;1>{Wl)`$lFHtvcXH-1&KRQcja?; zezN79HLqT|T3kKl!2?E%2k!!Ewf%-GKOmp|;fC6}2S(2Q7+Xr{9 zzV5}vOCEfGvQOPQ`TMJW23MyXdh1rF?786bJI}d#t7X!vJ#P8mot<&k9DvtvJ$AT7 z3IMUM{&3;SyNg`ugZaNu>@GVMWc&TI%S-<=&|hBc8R>9uF`wRv&o@Q~-M!;4mA^wT zjKagF`Dgx%8~wM;xZq6iq}R<$POY6c`@Bwok>Ti($dz%R|4yZ;UwU-#56ji8DG`B6RSp!a{dvj175LxEs>$eq((-}TR{ zXHL_u+4~oca@I$Y$$hiAd-N|Y=wBsgWj5OV`!}Dc+P|57=MnMs)GL3k=dM5LA3H?7 zwXbf5QSD+4%qUj{IZC4ZgS+{>K)Vz)AP(znt-t^S!y{@z-(f{~FCBczbqim*_>gr+Y(z~zc!NJ~ zy{z)t;kz99VCk?Q)5z(6-L(6|pP4C-y#N2O_SRuh_FvntH=+(;P!bX{Lo0|NC5Rx* zz#t+Hq9P@wG}0*`NDM=VMd~;zAR&l!C`u_(B8@2BjfBLzF7*CA&))le-ed3M*nfgB z!(883YkkghEv>Xiu{<~7@jc)F{K)@4$=>3=+tJPxoJ)b^j^j=au(NSPZqMlyL`IO(+l7y1sv z`}vEY;VTURI_WZxu~M~;j5eV z*fb7IG#cJTf?Dq-!+-6M|NGX#r@-qCvF}aaV<>hm!7kzoJT3C0>_5NYk440az=TK! z|6YFh!0w|L6g7FnyiVbFj}kHo-vnk71Y}skZNGP) zp4mEmZ}7-CDP?jnBj?kf-QT4NPr$7y%1e+c9VwW;VZWvPAD(763csPw(OqV%+o54} z@zq}7$Miu;1o9(6V)^#4K?slP{Mj%y7LA8Ozo3dLV&p$xWa!p~b57bz1=+Fd^Yi!5 zt9-8R8Jex!o~c+JpewB9_AY{=PT`it*2bdN3WRRznIpfU3h#qre(V#Zie+)n-&lwu zSAo3L#3K|X2rle=<15ep$MeiXHO|8`FUX~{Sg2_zwkBVAS^f`d635(G_^V(t8Kx(w@Wt8?W>pdm7R!n0m)lyQ$*$%;W=6{gGe& zett4$HTz|tqLcAicX`!MQD<`hswq=^s!^8L=7ndI!ir=|@YZc8vD?MgZ@j$71vpjp zTE(5U_@(8sXHjm>BO8MA1Y^&7{)cQ)TyCA{pUx+bB7E{Uyk())ATVRsYet^WCwmHW zCcnPadf&v5fus*^-ByByR$pJFjjFbhZy&#!=>Yj$YR*}=iMIN3sDfwi9XczdvyEV# zIs-)ANS4o+v}}H7=vaODSI7d8(c(xh`I7m;T2>wTwV}Aa!6?NUz?wP4&7!4NjXuDZ zGmQ!>2ER|Av5cCRvF&{QgDtyW+^nVu@=EqzJePs}uejMce7|&6tb8eUdH31y123MbMY$nGVd-d!_nj|M$Mzsm_Q#iE zRZd$t7Xb>}N6O(iDP@(epn4xk!5K#mb{jpu!@jF+>0;J@r5H*Mu;FML4PKg3N{qD6 z5$xcfhG_j^?M|_ljde%L%THWdJspHMGmM%saV<|X`?;@Lk9Te2RgM7=z5<5o-~${Z z+@3=WOj+%j*9SAM<=B%CyT*jmS0h-Rct_IoBMK?Vdivk+m#~r9OfP~`VgS0vf%?|x z8h!rzxF0zYrT|a4_LaDv3LS^4#u<_**Jr8`P7iPRdR9F&6(NLs7EffnGKe@L)thnU z6>Z}@K=UFNQN7MeUYc7*)qY!RwdISA^4Y$d>pgXbEplgU+C|&}x0M2MElH5F(US@+ zO?Lm@MG+Vh$)=;c1Rhr`s$Wo4A17G)DyO2mh?lJ@$XOADj&^t5_hWE&+yH*gNWXj$ z70zTrs&l>k;(}v1gTLcMTl!4fWv#bnBTcEdTXlib_r|Dkk4JZ>cnr{Vz~4svaJ}5? zZE~gCba#8m+(2PDd7^x4ebWBj-GAC=LEP#pia8E{uEn*d%6sHR$=89%^?i})x7B9v zVCaoJJ;G)dtmFQXL5=SY*Z8{)fW0UE;yLozg42p6 zlvB!M&ban;^gVjt2O`%^Q+e+&Y?A7(04p!7yNWgP`r*G1qq>L@nB(7Y>B| z>FL*UjAlNIRZ7y33W%s*AZ^HFve3-v-#imMDlx~z%jgN=TP=~-%Eqo?L**vYp4oD$ z1n+kgt5_vRFujl5I_mK5pBbcNCfYJ%G~eHw|9W(&6ko+kX2bLY)tUf>aq6JL(k1zn zG5xwbRMRwf{`CUPmaiDScZ^wW4MB9O2rl_{pT#dSEN@AcPS|5ASCuzZifBbS->iK1 zE=^JJE&++?yYI(UM3e{%#F4NgY{j2B4r#Mt*nMOHPZ@%IL`!bIM%`PX+%dpfmt!QTN1r8AR zuT&;+5NK8Qa%?p|A&v`eLbWcD0d>(iF^i@YJK@DDkkvG4>uomImjwjS3p}ORFuq_; zxA%Kkb}ii?yCXhG8nz8gpZmQs10FXIv+9B*Am2#usU)$c=oR;J1W^=m1M`*b1ojZZ9zmjdx z^(9%pkcWz6(Z7xL2q8T89p|}l=JYy???puAnj-oJ`ky-3zd!~p96-N&=Q; z(0PkP_#0oXmm-})@=-##&?WqFBlL5VbnWMGgGYai_Q5|><2#A*B@iEB+eAL{l643f zSF=}>xm6PN^TIU z5|vakG^mgBNtpA|3U3mPhC@v$*RZn6CFe2`eJvTS;3<44^y3wX;FC;*y4A;m8fe$~ z(+ss$P4`4TvZ>H*@*QCgfKZXgw3!S-l)6%)v|U~*!8o#cZz_mIO=JUtEGyv!eZ}e& zU@bx&)f90`w zI=IKJ-X|1BOnBAzqO~dEfbFPiq8T`w|ud=QHoog7+cRE z{I$C4KY`-B#rKEsVXuQ{g16*wYwJ*boM-@}Fq1G`JP%)i_NT6#vI|GyC-b5$V?ND2 z(6@KgQrvX$KdBOb5|emVW_H?N2$bFzeb*9)Fm19^{N)p%w1f(&B z_MNZa2cDT-r@8s067Y(+;qLz*4l;sL=@zN;g0CFWxB02eo{EdD(-tP7kD=cNzNE+6 zpIgA-1|fmY^t<^f-VqTCBuRJ;W-%vLhR@Mt;zD5NJr6A3adRo{>#?n|ZbWOgCt1tIhLq zW4r-&!Ve0(Bb*`Z|3`S^3|_TP4f~Q-EJaAwT#Wn}Er?F=Os8fDCEitK7SonKVNleJ zaUs-%vk!I`*xBSU_$a@A#&fepjB*Mq^pV{C)<&OJgZu=2)RWBCQihp7z*>!u8ULb1 zAhX%h&scxUxJjr-l#9uSf+R;Do`fJ)F*%l)KKi$1RlP9-adjq?vL#w4<_@Y3b;u2A zrC3jpk{zmOBI_KV24JQ=$w&9avBc*`8J(+AcH*t#^f{=RJn>SYJM>Ca1=G1NaU;q( zxy@~Hlo?KWw@h5$GXwAGw=7>GDwu>LCk0|f3J)0j(90sBR%ZB!!#l)Sw7TcX&oHHC zBocRr+w9owqL08&Xn!F5_ni6|QpD}4{*nK5l3&(2D+c(1EUP-R3W_(&+;V;>r@0C9 z{N>OnrxwlqoyF|sc~AeIgt|yHsJliX-2wm6g*jSLBfj+X%P;lvkGImFUFmfeFlCsg z^Q1EgCte4A-5Jtl=W_2Ax6;{)(ojX#8+USPEU_CCAY^|#H>b?4%&hXjYxw@Pg5o4F zXZRAa!^;N!->t_^Mh23)u#3T*!DBdD z%^K`B@l$YHFmr7aFYXtH3KjvKgYJw;h!(Phj8TNXI=U4zfAgU7Wvv=s()$DI4Q8|| z=V9$!#{?H~ZQ_(HkXsYYJ92I)GEtQ2l)1Gz_tW)J<1FE9$4|l980g z8eZ!Kob_^vdiRE9=d!I6gk@W>l9pom_qO#n z5QIqDVR7=!X2){u_3v_t5J~45;j$h30=1B(74M+o=!kB6{pquJ3>6*-1=9zaGap~( z!+eHP!KURMUSSo=n8$&Gha&~KOekr3sFO&!+2i|rS~P&kRV|9!PI*6OzZI)6rBg-P z8xrIT30@E{r)6mgkE1P!TYveb)6iHPyVN78y22P5SW&ls&VU zt3^K1v<*$mC9L>!>hO?aE4E6pj6U}jT$|ZuwUI%-%qK|bAB00j%v+J+s{QM~rfo*) zG*#4-9S9=%DgD35-&6*8Nl6b?+a}L!N1^|5+7#bQ>vSWGMU*pLy4>@qqHh^)kXMg` z@dfKmdk&>_o@OK#daX;X?qY=a_)fhe8pl(IFoiQqUOx*w9bR~}>Yh2&3W?F3pu^nq zhx(>uO$)WZWn7sy5xM&F{ewN>&C#7}7vmhp18Z)nrIJwQr+{g6ospk_h5Q5QkdwPX z(;Y4-V`L~+KmWA_^5S9~PplA2Nn>)IC4EELveWD;7zZ(S4^E{^N%9z(Nn_QF{(US60ztXoH2`-uoKRNMIP;Z`SI}^(|&R1S{H8j$H zP*$euDx6kPIXwZ|$yb>wOoy`!ik*Tk((q!ez_BUJZx~uF!!L+`5_8md^?jCpVPkgC z+Pj@c7<3b8IkQ{UfU*1%;w|81wW9H6qxKLbki2N_IO$M82BlTK$tIWz;G zN7`Q-M_mQ?|5qtA)q4!Qhwfrl3rXn*4ZKivsZdMRZEtiz%*QhX5j{gi5R(%WybI)i zb(&3=J$;&Nrl}2gwILu6&B*gN=Z}*Zwi3BikUZ;{R_Z1FK&P4ZtVETH-@u7DF-$+R zE)yQHnxCl(_kOOGdUgiDSs{HV-ZZc`MbavE(*w+4(t_YHD9U|&+W7@mD7i>L>YQ4#TDPxrz~Ouk{xTKK$;^BThGeIAzCjS%F&fOQpccxdYN=&0sp#-Xl`P8^R1bpQ_u z@w62*BAAZ)c7W6Xh354?i3dheX6KGk=J+HM3R@KLW*xelkR*>p#aV|cRnSAtBX%m? z5f1SxhF*i&d;4>7kg}zBx=?w3+(u8_N8-tLH-n+MGO*E-&*wT1K8re==Mwl6a*7(_ z*=4BZ6D(aOvkDWE+A970!b$>_B+cvS`pUiC!whgb5)=w(hsC*OwR%fpL%i5$ z;HQ1pJ@k*|&#EASF-hyTI=*E8-TPRu%)A#|fB%{`9IT0$e$&i&jHIwJ_aTzLF7Tu; z1i7u%LBx$$5L#A~#ITic zHiQLJ%;nr5J_9T`f6N$f*)W`U^Yc`HicoN%8&DB>atZ;;IToZ=KTMl`z)u%rPXNd> z5WW9erY>RZT=Q*j+&A8du1Df#A_02&i&Y_OKPs8G=rp)LJr0BO@;$fi9yJs^U3ef9 z(v76`|CForI#i5)0dk9>5&9h(roArl5Mu2aQ2{~^5>GudLX<%zH^gw?xA^RPs8R+T zISnZSB6voBqXvUd3O&mSU(a+QUUd`BudmOWaoBj6i^G8nU}|lJ4QykonI@#_BBs57 z)@PnYf4R5Bb+Q+X zBeMrj57Ebk5zTQ!!fp*bW{z8sp_X#oE^CNv3t$ z#Iq>decT>To^uTy@=;;ofVkFk^P%B|B61Vrlk+4=-dp0RC&iPHGK5BZ>J~ZzBhdv8 z=-wBaiM{%_%Wqb(`f$qFC*cmNsICviX^FfY1j;b_qk7&5><0_VEM`-88fPThhe~*2avrN;^W{IZM5i>6|#N)D;Mspx_I5J0AQ5Tfx?BKkGZNA##{0-w+O81X21_ zJ9x`a$OiTQB4vc=5{qh6Ek~b6In1|eNJJUrH0%ztd27>=?RFrYs3Z+B`$5DT^4b5W zjMVpmK|N`_V>$?84x0=Y2pQ3da7hf0l%;nd(!!-)`0DV={;>^A^mRzaBtGj~fSxII ziwaJzHft+u-a}T=@DpBn4_6+uUJ)t#9ix49ouRs7d&@g6k63^^rowlET99m=3`jED zw3;MeZKqQGrhFKnY)H}tM5EzX19HC!i%Iq=v=NsPN^x}ZhFBLWWza<*pk zLIBj)ePl6jS_FJ}M28$}xhzb%&LB>UQChKv0}KtaDa2dY+q?^`)p0}0QC86YfRlPA zIuazLkJVbdY+pRg64t27SKQ19xtBS5Pt}6}*}}=}(h@LDcfDmWB4(>{81P~(?_IN; z?=i~sstb1*(Cc5M*D(5UNaq)wuE0!k?*5PJsK_jft zzwVM;xVl>oGamZKP;9ReRG*Cr5d<1Y+>V#6v zDbY1C_-k`Y%ybbILGdh_dCL2FaYCCxlW=TrwX_HkO-~9kvIVXACtjnU!HcPz=Qm4$ z?NsCW`1776-Tzlmr# zRu|MGCU7cYu0|7#(+3GLX_ij|xEI*0gvu?gsu$>#1rPhP!Sfqm_!~2kXFG)eJ?{|= zs~35apY@iy4>O>l!;s3RW76Fa4-}6s{K_7i3MoB^kuPkJ?_8pW60T}EXRa-tyT~C_ zgqsi^9|UAy0x*R>mRTN9x&Z(bI*cXyd$cNJ@o^ny6*f#P6V*3$!Q>$@LERk3Fr-zi zPaH3V2A{ZlvK8j_u`KVyEOqM6WAD(iRui{yA4RlPVjG?6qR~AD9PsH0ugmHzAZEE3cYNZeisUqa1H_(WY+!v}oN@ z1NS+WPNi%CsJed4i`YT>YW!~yPh4$|Lv5v705CYgR_#2N{M^xq)(j(YBu|&3>>`4B z6m;Hedt)&Qr)O5iYT$70>4#MrhFO*O!UA zf}^3DKSAmPjwwJwUx{&xsXPkC=pcdmD53UJ_uCzr*H4ADU3sQ@fP=%7Ntl-`LUqFFg^B@tDSH~w!ysF`^o7CUnhfYun4`o5_(uv8Q4-vZ2cJ5^97&+|1 zqa)I`k8sb)KGXt%!XHJ8pnMc}7%Fagrl#L^N~~HSm6f0LKHI3=>uOYd3f>E`+~F-K zT>K;hOLkd@C*JT8X~|BWigc{ih=7#Gqs_J0uo`-y)`OvoeviESjIEVKDAnGkW@a+V z3m;-s4($D*79PTd4kQ_Qf?Q}3 z8PbjiUP-#O@VzGg@KC-^P8YP=h5#T_{{n3I0;Emow_)M823YRq^)Wq^NiZSW8PcfI ze|GPEgjeMgAA0swC8S#%TMHUXY`rFSmdv9M{4Qw|I)zwrX^V+?9g5F>O@;ALQ_hYu zPv^;p4=*+k-nCtZp!{eqkoqt;VZT*GMS<1V8gu?u@mR)~rTC?zgelNN+@WfGx=fRv zsTQ^EmKxI@lY2?_p!m${V3bVm1PGO)vcZ5UlQGj6VYF4MqZhz@=2mv?`?ZH?!p4lj zIfz$Ik}<*rx3CbtMo*Vfxs#vc!h}>6_>NITi!Mix{7^miWo`iy!Nej&ZBJCo;ptF- z)WgltV^8}2uE+qkI;a^ug{6yGq7N`kRneIlLOopX+{WAbC>0O@Zw5yPy5*}7(FoS6 z+*0^TXhF>xOo8~zW0>d&B`Z6`f78?+jADljLr!HihDYg^C=0DqkaGLp=^% z^I@n%!OhP8J~Qq=+AZ;}7$nJeOthbG^FEoxJq&<-7>ydk`9*tkLqC}F}Ts5d$5d|{gUB-1JdhtPKusRaN9L}p2=>px8hELOst2>@e$)6gR z)-SLf|MWmK(I;8saugA@0v(+tbSH{$6AbLyVo8`@nz2Q;2Mv>9xzVu%8CtZxy*{oN;0&@iB(|xK>F|Uyb+} zws(DF_7@R&4K@HdFvgrYH<1AEXbfo)%~YG9q=TUtjmg(@uyd>N#*GQ7Q&XD1`uJyz zgfJI+>sG&~FRVnKUu2sKJW69m@EkRGYPqzP7%}p{97Htyuy;>n6bmn}SYG9wIkD=~a@75U z+#kOsPY%s$xgVc%)F+#}VAULAgEu@aZ8DJ!Z|DRcjlWh)ZKuGx1W#a5agnilMu zR=YV`_wD%3Z<)SNa*xoXrqY0YVEoc(_fC#Tfls*@rA2<^cQxbjCUV%fInzVnk-g`G zz4d4@Wh@Pj`T2{raLk!m{momw)_huS7?^$a{1blj;_mkUR+Csso8m0?;zxdqAo6H2 zn-B)eFh|}7)71;t4dia#ApG%w)g6V0x6yPn1D6YqfhsAx?lwq)KJ`KVyGJH3MEzec z0GB7kyFTzX^nxB{=4KB?{tROI0C)C^BI>AA|D8om8HXQA&})>Bu{rA6Mu5($Y-hwh zoo8QiJaC~1c115pU)PDR`WJJ6*>QOe#SmWQ07Sig+3{PVxLNRI^V54fjRc@Pa(Cp0 z>VWwbCB?;y?6h&@Y;A@wj_)rq`b(hw$tYON8#?lT`>8yt#3xO6`jWF4w-V zq!=7BO=_RVvlu_^R<~Uo`H~IH!!eb44QViR>qXo+{0|LGTYFEGPMM}oLx-Vb;rQrX z#2@p%0#d{ZJ_SX)nhaio$n^j@ur?}Dx2iyPn*7huxatsOXM3JK|91% z2}A9&jsJgG?C^K*8Z8O#(hT`?wq^SiH*QeFCyCDd?zio;(*_QI%xzwhW|C|R=sKS^ zp*EjMa&B~lB*hh*S^|cetVN_7(;J*xeRVOseQ@D&n^8{=YD1@ z2hnCji8}*uz!?E8lWpm7r9T4b5cAmp(x3~56onNi42u%&OVWABoluicol)a-4E%OT z49mQT%|+ZGU|g%yOqTnWrJGx|L&6Gr?41$s4AQb&??AL;HS%~m+4?*zQQkcp=~ss; za(Jo|an|ezt%PU6$pWoKRyKf*vfXFD+e5054J(3h^cj$Bg?^mt0U-AZVadrM;@BfA z1Jz-DK(VYxaSiuqTKh~wxUQaTh&sDo_tvtd$_H99+h1IxuQe_J08o7S;^}HcqFWyS zT8jxdEOm%z{Bs!GFnNn-7wUMC{$dw!yA}FAe*-i{(D1D&vIj$%qF3W&OL@4{C;5qQ zvcUpz>4U%2x00jynTBcw>nVSrY5H%>phYF@`BsI#V2THs#$#NBvAR|Ln%&MFJb-v+8&43`CcJ0{GHn_#(LL0A%h!qzJW+eI zEsd(ayiqY0^-}ZOVgCWX*U;?Wb21HjCZdg~d}oi=_daWtr+N!PiyjFof9vxkLC9oJ zD+V3e2|mnp|G=p-(|RbRnx6r?bjPg7a?|G5RM(z#!O%-M$}7+fd*nzTu`b!rAM%)O z_<0I8!PIdcLb4l5KOR!g-f}Qo*u%&J0XuXwI+)9QD)*N&bp}GAl6`)4L4je2J~V#= z;}59$3)vCvZ3p6lfDz)5ahpOq)cvY(xk$fP_wyv^HWcumhGy!>d{UhWoM{RF1MjWB zmw5`1S`_*6VI$Na&GoGc3!W55m|pN%`ryZ7jhYE{XqDj3qNDKERNQseOET?6TJqD9 zOx0&MmoA4{?_Ft3mQS1;zKN(h0Ps5FRtivOBSHi?3G9Qf<-;lp)Y`qkmJ!deY3U;QVGTx1uD} zfBZX*0>>oS?MV(g;F+2GdIWA%ImGZ6AQg|d%ZrI54r1{Z_Y5u3qzxRap?NKnXR<3} zJ8jvo)AkLI#0z4gm-j0MeF@E!3S=dhO$Bi-i4-6M3IcI79g)6+?I0W?`rvO3p}U z=+`^Z#>=udCKgZ(hJZfKzz~kQxpZGL8hWlD(CBZJdoGR$2v5WBwsT7yo~Ex3-=m@x zy+HOP?7L3qPf9bbv&LJ;b1Qf~(m8yBl#3q^wZ`)DO0a|_wt>TO)Q-R;9O3t7?lfq< zVPqVfe?O6FK1P<7CQL+Ps`x>^Q!Gv@8bXJl5mEGJz9Uomf%F6FQB{7|>%d*7dF~B3 znARKbFtA4`cVCLSL0AM`U$Cfowg5&k6#qs>fuxumNJT_5OjY^nK-hzWl7=|4Zb@Cn zPR|4$v62t1?wPg(>vy7K#5HQCR3!hmZaZ+0CV|c8iAjj2g_m=YLzI`iw-LS-|W6Ho4aWj$gy(vx$4JtE@t6 zvK>=us&XRU%4iL$F3~DELd$8j5{(Smm!+xJii-c{WLi!~?n5!-|Bhr@W{{?vYii$^ z;3Z0BLhV6Vf!t$nFdYVmF)93qLOkhTd_nROl8EDl|EXMwGM^y`PdkW9U(IbbvfnCm zzudl?`YD7dIk0AM&zHZ1(n(ydIgDoRNqhpHHZ|u)<##TpI&=_LoMi(VkxuFp z&#aF^anKylc&0DHi=XzvQsFD`_+ z;3%0OO3JljE)jRB(pCe16AnLn8On;&r1cGxmUzpXGQAG>>Mf?i`6t+exH8*t_9{O} zk~3hsEdlMLxmB~(oyGLJ2)`WLxOh3r2HN{gE%F^dMC+t{UxTh`a4;0KCE)Gj5vPMZ z(GB-yx0Qkl>#fi>X%M*)Be;IjGOYoz!VLtR7gJtzxAA1%?&b`Wod-mMMW)Q{nK3pL z^G|d-pi51q`3`bbC6(46g97goP%XV6)=Wafw?*Q(t}~mYWppBlW?4C}xt&_o1+R0p zV?2BPH@}48A zLDs_y36kkb%)A-J9lj6~M2?){Xi1CuF_J}~zYw_fy78UfB}xEuNOJ)U(&+4FFyl+m zgAgGihZyg7CWHJw9n(PH%Jvo1DSI1m-$hA3jjXfE>~DSuVnp{|mG&2y`}m~66cH{S zDjG(p_s%)UU7&V~S2D)s>Zr-pwX9t$TntodHZq=Wu_)2^`>js%C8eKrlSV509s&uW zuz2t{e>Mn_@ooKVUqyM?JH>vv^%&_rjTNdsbdNMP7u@ExEJEss?&?;bxTek6>$kmG zu&b7eJItLK6C!F<*2%IKok!HJCi92gW7gKkP|$Llu;*E(!|HAlHGYyi5w|e&pl&4H z;RfZ=uTerZ0ms6EmmW*TW4TnOOjnKz3$Sx&GY_J(co|BFkt}w-A|4V4`Ht1l@={A` zU-CM0hG09^{FG1|;qs6uRHH09DiC1-I&}mW-3udZ0eJ)+f0V2b5#IoYqFl&aq@{Zo z9=69Qu*@a?iBHqTeJXE-cQWe#Z94)aqStU4G|{awUo44NGlgLsKy5oK*h%!m+N%3K zZwa=_ar`FI*Qm-#Z{*rn1uIfSvB$7tRGuO?Dtf7p!Qv0zpsZ)^i&oA%cW-aIzySLs zZ>Cr#be8fgQ&pU|}6}CX)-DO0^bG`Hzh1G`;L* z^0$P{s`>4I)0{_y{hyS-J3qo-221Vfm2RnvWNOF`ne*Ppk8s~N?jS9n96CuGpgZ}% zqC{F$UstZSIX|%8?Id)qPowQ}o#RmNiKZgTv1c7;dW+rUs3lqz^Ni~oueM$>x-(y0 zU>sslU4{tfg zlle-Wt;D*ELirvLg?<52OH2w8QmqW#wNG9w=q6#2p0*{-lqKc7^K|#y_8E{HZ3%K* z{FeQ1zP`U?w-deP5U16?ZyR=(J2bW$vs4AB*3R=h&NuRENzJ*2n~w-vrfz2=zQ9Sk zwaZ4o_As*lURI1GY?{7E6(L$8&+z4Xn8{bjTmuffMpK9+Zgu9ct2_4ny&OxObs@4f zdV4wgA<(m2hW(KI5QHV8jbx!4SP!*T<)Mk*VrT5@OpDi6hlC@SS!o%Xpr*P)@8|=1 zY=PT{l5Zf@-BRRTFTM7#$)u_zu9ohf2%!neBgBjG)VX%R;Le+zQ~N^y9#o$BHeDP~ z4?-w@v1@N*-|gF3ZAi;uwa>nO^=EjK63F0x3Vr^h7;5pH?R1vv4JVV_h`#hhncHrV zEX&X|9LMfmY?WKLiqb)ejO9ibf$4GeG#|g~nEV2h)gf7#pfMAa+0@--?Gy1r1oDqN zxit})$n1y5LL(t+$7(krT=&?Uxov72fln}C%tF}z{!`U}UkdUnx~@ld?%o9C?q%4U z0TPjag8hN3)C&Q-Z0O>-zgT}%;I0%~AGFuqM;*IslGe7$%#3rAIaB|&v^dzy+&>(U z(RC5S*dWzZJNK$gVjJ-zt^`MRj7f+wGYvIunec|mL2{^D%tMkC@u5@Fb-6PP!erev zp&GX6Dz<$LLn}SE74=mH7Uu@)9qLT$%#BQVeT^cQ&^Kb-Tuu@qK< zT1DS|=i2L7Tijvf{RI^JPY2l%W8ycAwX85RRFc=i(s1W%pp&>LmS3a$%~F>|s@*tS z=&qVz*;Z9ml8`FL&a<=GkFsEHRYE+2_5ds$L1z(trscD~lpA4--9;yc#mYkugC>^O z%hPhazp@f{brDm~l)lxha>x}jx0g7w+8MKFN2|fx@*eM}o}j61q)mLSDk*vU9#87w z>aHe(^hgxnzd383Puo0vymX|65GPU|FB?e9?;3V3#pl;`894SDp-ittzy@j(&J-Yd zKS+%Asj#EzTg{?QkS$M;`%*rgU9&nKzt75Qm67e!H!b)C^b!6JIYZit@aNNIN8kURd*`ANI#K z6KZ|)Llnqen z5Yirb^Tp*;7Y)VIf2Li9my%FJt`t}!3wueq%O{wAD0IMh3j-cC@dt+#H=4N|KQge0 zPDQI6%spU&Ww3bGIrsB}-GL{FCLJ~3xRm}f#c5}DR1{x?na6}WIY#Z3-@AYI=}xGB z??ES{)1iUyCd-#e=uiq3CV3JDpsF;}kP6ieZuro%Z^vq#NvjD0>O7+aK_-7rF$q_;3iUgEs`9A z7uSc9-yIq?sEBM^>E-#cvK7;ya7+fD_7FqAAK;m`WD_`fod;{9+>ud04`Mz>SCbg4 zQDL7R3M%Kn$oqJlWU|3@_H7a5?R?_YS&-$upyK4nFL|VU&j#QH1c5Zxpj}nnd+wL3XNhzf+R;ALrU3VUH1-7?q^&7U-_KWVv{RPzY1etj{qbUPVR0@&S;-FHk?*&vx=% z-<`!TEV!_y^YKC^vxCG;T%s*$aF%!-IuA&Csbl(O7AnmH$sE-hH0tm6iY@aol!ItL zfjImFeyNLvHsdPGB;)GzAc9rm&hkp#w4B#gS525bB4u5Kw4l+6l{J4~lQZl*Gc9hhbxord2w0yJ_fR`n!5fo$Z!kfT#+D?DE1~+ z>HRA`_N8BxYhvX&*bnALh#~$`@ekZKDR+7|3Sq_!i}igmt$6(4i>wF^8NIU($`v5A zcQ`pM-fS2=*Tc3I=si3ej&3aqy*dZ(({9hT*u6}YQgGndCOTqB?}>7-X)A|MybL6n z29{LMh1>>bmIsg8Src zMPW1r4y0gP(LqN4{Y*TJ{6YO7Dgj*O|GTTpdFT4&T0RKP)0%!~2eNOGmC&Wd1G&Q+ zKUD;;L$`g@-H%5NY3Av{pLR&}8%Qg4t^l@DCr>?6+}uVwWWiT2GDhXZ9`nI;kFik3 z*>n7;jn|Ol)$9p%cQbm2`u^A#=^`5zX{nmW^79r2J&b&lR^ZW8>a}d`Jv+JlJk4y$ znW0V+V2p&O!6=je0Af(YZ79Hs(m8Ygq*E7?B(oL#Mz|}PN#Hk(->@8D2w+8D-u@3H z6Q7QGQy27w6=g13Ra5|@E4RY%)qd$wF2N2ptclYFJ&lb~y)KBS4;bxZA=MIG%V|$AXrGxf2y-3IPkh{3wQ;bS(zrjd(0ju&z4`v_*-G4qC__(@zlpnot zHQ$l<7TRxqIMVS_RN0Ttw`7#TZ7AB|4kZug__4M9Pesdk zND46Rv%uy@{N;<{PY>pTwZJSAwMjZzE300w&YX)(5|ovwfTq~*=I{O+6&sRJd#ACp zqUJ_bzkOO#8`rdZ*3^CkbJ)LLDw<+EFA#LH(i_oYQtm9MyF>N=D4gTCk7%n{E%3oTN=FB{YZT`djP!eWjSgz8wYjecjjO>vhf_9yMQo zVs`J}pyl{=xmAN($HLFuIC;}Yjh=3{5QbEws3$Alr$(}z`%`TXkeE-2gDl6T^-E;| zG}84aSz|FU?jTlu@;U3~ny)L^r|Eh0o59N^BD6346z`tLc#0t~{x$4ho^y7-RTsyH?V*h@IYfl%Sy@{g{&HDc zB~T5zs^YUCx6UB5@jgEkld+=n-!G1I%f2m$PC|8ffsm0X6zo#1abQb>?=>_`T$emy zE>`sv^9RF;jI*pUogg7*zxES5Zg84#hYZRU>_MBhv=|y&>^EhyGV?P(yd~RmU>twj z@l>@VGU)5<`v#~+Rdv~5bY?_i?=bfvbE$O>o8G{exL}_BqJ%F$zQbIfu-B?bqo;+i zRJ`Y*S1YE1D5u)lC4BkCqdTL|Q_N@1oCu#^`l?kZe$?ZKKQKqIgt!-i@M-gqmHaP~ zE8hKd|Id5B(&Dli^I3K4Da#AL$%=-~&)pWIc9oLLPJYAUhWt;pRApoMuKQj?ljgl2 zdaEHsW^jdxv5VV#%4CJvmlY0a!4n_Kag1yv7#^>=!=O{^(h6ldA6|3WO79iuKEE-| zrzdsQ~4k z^;Q8u@;`03clh6RQ#}VpD^c+AzZk87;Je?1?@|T8=D8pw6rbf>zCqop{C5-R$z`j; z%P}IpVYF=zH}f$P`XUnYe<^eSHz4<>nf!E5G2LTf9cEs}!wk7Zlxm=aQ9;b!g{;Ew z5+-4lGBA#lk6#Qce8a&qMCcaw)Zi?$SOTk`nz_;wOa@!tk=O(U+odrRZDjgX{wYit zfk?)gi!AtDPE9jd;sO{Kcb2K^ZpCGQb4NAg)$>nKXW|D(EW&r!i=64TMM#{=JEF8b z<}LaydYrI&8-k^X)4^7jmiI!Gcjnn=U|`wnA?cOKWk`6Z>AONuXCSt@!7$)y2uM|Z zz)AE@CL?(6?Uyqjj+{mOj_TG(?=H=)@nevmrDj4Iv!$=|0s&Nr4V z81H8OwF@eWtHe zzYiHa`Ed_5HRc0-a*#6hYp59Pc}eJ?77G%z@tnAns)&t2>#-B8qMJb7JQ3!lipH_K z#`Mr106e?VS4T@pq|Dac1e&~5)S-+)>#|No4vav$LE2x;g{T_n{G&D6n=~4Qiy|eFP81~aq>WpJoGCF6q zd}Zc-Q~kqsox~hVe2Wc-(#v;S^%Y>J*;AMku}+WrS18*!>X8+;m;>sylTi3LP<+fy zv`350SaaG|I{56uqor8h0NrDI z#G?|i;+u-`TGUGl@~O8jn}IHk^cneq%KKH3#ZYYABJ&gyRUZ6B(OPy<-@sRZ_zm7o^xX)%L_zLWsnAd%Og`*npm8+FGS#o$l=MRi7b zyf@LT$Q)EYK3cKXPy@vA<(;l2$%=V6KcHa1%&UEX%vS4UkY>yxw=o9;G_m5;r^xXw z7Fa6s@LX&vN)P+Q#Fls{Jftlam~;hBElk>;+;S4(b>{=Qs3kw{i3(u_1FU3AO@s@p zCNo{CDfuakw?sxo0wG<@%)ztPF`?)wQSvZKp^u^-cY&Mt2tC8c)qAV3_w}M%5+tom z?ReZgpl>?draqQmVCea?IjUD$+wZeyMP(?@)-r8QL8-O2KvtX-;D5q}A+fZS!mNea ziH@Axm50>d6Q|C9y6!8u0j%8~BmQ@r{b1t#g#O&5U8r zN@5BJO4ne(Hs%f?KScnt#{b@=%5b$xJbZ zXTiCw;m?xb)yh$_i7E30xaYi!|U)*zPd1Ij!VhckU*+N@` zQT`~ZYpss0QFHN+jhNreH6h)>F~fM75Xsoc0HI&Ki(=PZ=*@HQO-!Xmel^!j1Ygfg zd%v%?^5#5b1W#*RFa1EBfr;(EN)vTTnYEiSExw>K3aIOV@`$mW_hw}e>u18wPXNrQ z|A@a#yxA5e0NH}Po{z5uLv`kjN?$i$Z4C?&y$ldY(i)o!eRci0SW*34(;!?Cc1{^} zPz;qzwCPP;RTz7&IO5J5!wQ31j0aBc89(sP!O+M3&+|3qG-y|(e7EB&JU|l-y0}|$ z=HXL*77Oy}45E_2zl&ZgtNc}?)mIMi(68Un-ju6Ta!2fny4XJ$ZIZJW`&YT7;?emF zPyP(R`~PT36#3AxtL0S%#LKV~DhVfz9(V`U85`-TDfTH#D_!EXzPFP1y~(k@{poC- z7B=cxv`Q4Sm6LpoKHDq5`( zI#|BH(fzu}A0TD%xi{lzm*y^-_^|-Wb;_Ll?(ul$LQujA$_++VBr8u{JUz_Z@mz!T zH+$Y;v1FKiJ_-e~iHzG+XBwGp;7a~F(Uh5DMoZud{eN~6G zS}LLgVq!yuM(+3fZTFd<#F#MJg$GdAXtH~1$EEB+0n=dV5u&@CB$J?DFT+5DJ=gqQxEZQj1}^IkMU%jRBd8Lm65G9I#c zZ2g`^SwpcuP9>+u$PK(iR(xn--6N&yz8Tl$)dCZ4E|M@hcT&WSF~@8v}`_oM0kE}T|U;2PrDl?=!^mTxfvq}bFZ<5 zfnQ;4sVHL)%n2PQ(o5U-76puKIy6QcS1oUF{c9hHy&1@xw0>$y42{~CXd!Xa-w<0W$AI<9mrSX=jN|C+u%n0MPy6pPL zthJE(m!xjIOk*G^%^*En^3M0aDeymMYu#bDVrv&bLydj5PitoBrM}?1f80IRZ5rq(4^!c)2K+|<8zD&eVkOk`I zmJei*^4io)*Y`gTheUC&{Lerp5p8zNb=Y>6ls9}*CxR@El0r}sL{pTZT~C2jDA>?% zvJ^Mi_NjFO3W2|?I3iRi8lvP3 zJ6IfCK_$#{`4A6Dj;^)&*%i`_#&bm@k~IVT(O*^$QRiws^c^YMW_`&s@+BHFM1xn* z(Ng<(tm^+I`$duf*mZvi82ox^t)?~mG6E{!ja_ml zM^STCo**PdZo6xMgI}ASP)&SsIGy>*HW0b|_z3u??9YC7nF28KUID{YxJumqHBE{e3vZ$<#1Xvw_PFk7& z$)p?P1V||r$p$t4k0Hu;N3}KpUq1s6*m6$(PuiG#RFnMnLwLkV<66jzJ>Tm&1%^^B z9)P`Me~1EBRro2O-I3yzbwW2Xg)p(E(6XV8Vf^1uyO1Vg34N;ZrADxD;MNbPUrrdw z(AriW0zs0~rSe|3bP?kNp$qb#Ah3FaB%Q`b2s2q#{xHFRz+|xAvgDRt@Qu>pCiao&YgR}wl|aZ>elv6WJ;R1 z8MZNiYp?S?2dk>R^8%SaAWWkl-m=KCdV*x;u~QJ@AP_Vp=l0 z$3QP#1lU4(UME3Oz`=!LuRP3hZ18+{c{6$7U%^+L5tsXraq~EH_iy0)M#!bFkj?;L z5=X?T3l}=$<#!UnvfWsexVFu|&5SB~X<4PdVjU$4pmU~x%$_9YZcMI>^jA%;$@ILG z>3`G8)=lfyMfbN1_zJpq*gVotsb-~+?1`X&-87KeI$d;j9ymPOfqreviE zzC4r%)Bs#8%jO3_2?ny^_76ba{185$9rXpYnSHeInIo|NkEmyTQU~zOf8$D! zI71vTzyD_m_mNAO z{=X$;KrUgIOD3%1;hvZL$(09-_E&5?xmN+7{irPL0!DImBI{x={~^W${9tI)>rswW zh8Rra5D`Vf-jlv9pYH$HqNQ?#Jp20pvuw*3XmMe2S74=&^Bui_yBuwWtRbroj4%E(l`OE;ZP)% z=)6=JQdf7|8bKI1wxIn%jxu;i>G**H)XeGu2&n8h4gC0Lkr?F>87KLj%1u%L6=@CfBLT(4fesm8sf~rxHX0A5Of6r*zBT9(!zi+gA6dQoi`1j)kh5_9 z!JeR_s)J>^0sn^8_0}kB3m@!frE}#C(DH~IKuv&=_LpGwhYDiyfBTuJWIO~TFHP)q zb2Q_B1h&*V%UfuPcw*)r_i^({c9|L;0zZ}PDS-ktQ7hIS;yKKzvD2m0<%u*H>5|TJ zzo_od%|A>#-@4(lDV-(Gnvnv7@b~$gShz`#VLEnl(`5|i{ngS)UU$}5#FLULe-&va zd9<%ejhm{{hTxa~OH0pi6>Tl3eP0$DcSwVMjzUx7X^aBZD`bCk}fCduevyOx>(+`3h+yHF#1DHqE z*VxVcL=Hg#XMbQsE{O@+Dpdt&wIAUxe|EvmH49(2NEIOa)ghH&5K|k{sOArFD&kmr zgoo+02aLbm6q8<541(y^{r7hpt#i=!qLV&65WhQ~3G&^KzkxxY)e58Y44BlS+yo%t znOOiPErwu`nH-T~`2%41E7Ef^zGw0MgpLJrPK$J=7Hv)NfZ}MRqXi}H16+uvoGXps;PdEYqqV!Pt2y80M6+ z+%H5JF@Y`B9A2dI4GBM-JcehK5RMfzCnXP$h~>(8alq^z=+0v^>T<9=cFW(xL4n z`5ov*>pRZz@2!R0zl(1C`k`!l^XFAy&1+2QK(u7Ba=7$kLU-0DEprCp50xF=-G48u zVf^`nn^#Bj)#q-S@vi?ot|57LgU!=rVw<)(RH61pOl4__f`IqJHZo!3?7%q6mz80MxT|6(=eDP{WQCJ>M!paTp z7~7XTm&8N7NF_Uspt)Z|-B&ImELHKt&Dlss%!rhpqs&zJ9fvfby3Ox57?UP*Gsic^ zU!AN^CtDZH3_$-*>@J4TAa^wo-D!b;$?^)YGZp8g&k%4^fE{3gixS~{s|0G4)%valpRAz1=YQrTD@+it0oM8V>gwU|mfi_a%Si5>tYJ4lH6q55G(~@89=d2|XA^N1 z8Tb&VXTCLozZJ*#a2XB2s)68M?M8F+POZ8&sD;3s@a;~~OunJvX>Pl{x%a$y@ryg< zgiipwobN@?cR6X|+`wB~IjhElAf_+8Lk`fjZJOW@5@Ix;aYHLHHhuj!J!PeXPvVx8 ztMmP8nBk>7VL%M*sy1B2<~heNDL84OPdFhnEx=_1 z=I7@(!OO*Tb|iuFz)}fk&~>*A{&FH$7Uxj&F%b(Sh?jLGv^30Q7Kc*AhmWrgp-9y5 zq4hDtb)OoSG+}Pf|2f>%L@V}uSpIuoHdh}fi}+A=U;7Ua+Js+#<+}>qS1fd2-5HWU zsjA}6k5QkXUm83^9T)%KmT?4q4hvLEjk$0Eaiw%zMc(ZWptq$;CEmBc_Ml(}&rapV z>b@nG5wsLmT&#h!;;+GJv@MAq(38yO5XUqDG%!kjXQ^!t2nVL^)8lO-&u^xbst{`;hCJ2~({P-x4*VH~VL z2u?gP(Hd~xgW;zaw3pk>@U#T3Sonupd0-loyws0z3Vvel)Z8kp0yuhUyuV;H$a1_o zUc{-(ep@&>R?x8QMt+Qd+qTh2p1cI=W4ngFw$FF|avxFq3!1YA$L zY%BhpHi?#a%1&XDAopze4=Ii;&yj5I1`Ma2305e>irLZ-4;bHgSSrVv>mcLr33}v1 zp9@-4=rN*-U+vjB_whCJnkSFYr8kxVj5 zg%Bmv9=Xe|lECDUE$<_I-W#t=c)_);h-`NJMh~-EUQAS!<|N(}7o zkBPWg>Z)n9j!ws2Vi>%8Sj&#;{oE`{F5SPLJdm7SwJu^LqohTFq0pk2&NutBIgd5u zAjIBiFu-D~Yn|5=rZ(eG#_OC+`8^ib4b`zm2@!<{GH9r`S!VpQ?P1yJXuOLdNIVZU5a zvQfFq2HhoXn;Tr34847sf?D^THAElVVeM9^;E!x}9_#$!0>AE@9Af7*t3=fOf#`uN z=+j8jh&urY*B(m{Q!%}xsL2bauEzTnCM@sCAH1J^UhX&1LQN2;d7bS2Xu|jQ+LP?} zCmgDwB6UW28!1jQwr*~_X^od~+=nK%FQF0MUjeeJ7!!L%Y|<-C7?=b(iw%`sNVmfk zL`R#Vii*Aqd4lDvgoU(je0_G`C#gx8?f2yL>^RlN)csPeL+YbE{sQo@f(4lBQX=#| zJwk&^hey>NF6(f*o+_bY#7tg+c<=|R3WzKmsbf=_3MT4(li|-x-R<(g%@6L=iH2O; zS6np*?o%ZFjPw=VLvkIf)3qPMprqUq^)g~#ECTSniP zEX^aoSLso*gsbdWPDeWP?z&vmFRQOJ_6`g$L#|UT#)P8HJXvyLHxa!AAq9AdlqXq! z{5NuOm)Kz@*Qq3^lRRu9quKeQ%AACE!k$NY8NTr`h^FI=^ro5|E2#?5&xgSrai=;$ zaibhpz3E@+dzMZTsnd~=t(={;vUDLBW+N2Hh+d$k8nW@}2>;QIS;wQz&QVdbn+!qq zD;!f7_v3ZtLkIbT6$FNfgj$AhpU!O=Eo{n^M>Depg9)BuN*q|)B=(X6E&m&p^?2dO zLh`N&icR;vzM?n(&d>)5>Pl*(KUaaP?9F^Wp@yTnQpU~f9Mb!eSXVAy!Rz3Hz z$emnze-TAytMh-Lu$(Vj-oMCT@K%;)v2*E-tSL}R;+Emh9eC zlW+alV5HC^Xi0jyb5FQSZr1`wp#++v8X zVyR>+RiKFwB~6(SViPsKv8FThGF>%+;34;7y&2rTH z^+NGZ{u3KdAk>{1DD6VgX31IsS z<>=wG!%j01cKVGzkLWR1TXJi&UzD{a;UHtSTfTJUfpz=Zn!0myQDw2X;I?%{RN=U$ zyMqotw$dx{ZrsPQnTHB|A~5CvZ=tOe9SpkFJCtG)m#i;IfY1)P#k)b;7@}uO$16!E zP7d9YY3h1*2l!=8JR6jeZ-6gI`N!PF<{4I)NP}9&qO_sTF@LIXU=ydWWR_hO)+1}5 z5R!W${8G*UGP&ESKm3?F zr47@2+PE)&Mxd4B6r53=luBNWpB%CoHoa&UItyrG+xQ5xsNu1=s0T6mPO8Rfu$UMg zg0hO*_5w|!auOWTScIT@0wg?@Vl&zjeNb2^O7`0yDUH73!T3(q@YTZ+UCmEk+B8O3 zDAD`3?$0MFgWfNAMCG0aGg=o|# z-p_x997ecea`S(1`y{11qYq5-@b;YXXdPDFT`k=8)D^YL_%J;l5F^O%!H#?LA8r_J@HApBWbFUZPM zBfjCk+^h!ni1NR}l37UgjbmUWn3fQiVfWQwoXC3!S1w3;Y+rjX3KlWg3@Z0cxY6qh zN%aeq$e_CHNj-52j~jl8X`l$t}LE9@>$u`W4IX%neQS50xjFhqrWJL?g2(`ZMLmfwC_d2&h< zn!<$pQ~Wp^#^QAO3qEH&b7WKEZB-P$=xqDcwUQ}<1+^VGVb9QT#trrSsMcOzHu5NcQPd25`+~dToqQM9A1T6>%f58ZvIs%=cl*B= z5g){I{YjScvov<*mg$*s;+WP;PMeI*lp;vmOCIbyE}cDqC?$0LY}xdf21PF9sSbgE z4Q0JH;CW}iEziKysn15hO1> zI+X9yA?&xZREo!U++v$HqhD4KSeJ$;h8K6X3qykpOG?06_HHHL;w%}J<#I!5U(PW4 zmWrs!Qt2^}8ZT3HF_rf2qp+bLw?4SCn0(?Z57LR*{0rDDf#r5w$SuRnXU=kPe)EJb z$4<_zG!AI9I<9v^uU|*SlZa1Rkh5{eEaqQab$}y$gS`=%{K_LI$QxwpuD}ew>SUG&@;apMI3E66`)DH z297PLvyp!ZjH^;;m~B)+W!*#-K~Li}GJqH;J&Y2@Exv2*;ASji6nMNG8k2S@H{ z&LrLP8U-e}k<7hhws7bB>cX5%@$`zskJ_OD;j5L1a1uq_P|P+g4GwKt1bQ)1{Of}6 z-8`guAPVNDB@GnT@LP0&z+&G?uiazC==fy-%nCk=erY9BUm8NbqpC{ZbIE3ChQnE& z>}GR-x37qmjBs@1jNMXNjXctHoz#;o_}xpE55scpCYO}xhkwa9K6Cs9&0X9VQ>4&{ z7I~Ii=Lqg)RX47Q{sk`5j?X5sw9=3|*=xB7L9dfSCib9bRAG)9iFAHx!@s}$3=SzR zZNg0uwHJvSpoP6M^qemlh|@RUCDX%4z@?+3TKTI?PLrN8=~t(m?2mqlPT?HDiA<{z z!w>AWxZWMQ_0?`R&o8vG)33*To7%|X)k+9u$W=b03jF>&InZiDA(1uO@KZEl>#syb zlHo5lvid(f@aTIJNm{k0UAHQHk7fEYIfym}GsFQuy|g3RQYes8AXM!$d>=KVK7!k7 zG?)gqH(P^7hkgf>#d-GGv0|BYiC3Jkg?KPMu+v{iVsWmXuiTx)d9^PxjBA6Oz95FL z&Ch*z{<%k)E>AI|=%98vYN8@>plbM=6b>t)bv8rRmED=r_!H4UEe0&IS8oWXEcJLw z+E$%R@q~3PX*4OT3CI(+XgtM}{Jxp@@rhHx z+zvBFheUG?+xOM_{UOlsRmQl&zgTRK+2?54(1*@*2W6Yn^62~TfNNJq;qH;k-VtY* zDnxR))~x2Cq-gpL5I}|<@=VAdX*4J)gLLY3T;*o&Xo`q4pc|HDuPIw|7cEoN36x-WP-jFHMRYzEXb%`jv)VRdoJI z26uFXNAEbw8A5z!+e;k4?0v>oJhmq%Izyb|RMqIPAo7R@iU>dvX2mrRzueRO)Oc;{ zeX2G772FO?sIzD&?sT_xy$#bg89)KFU5ZWz__Gc-(Ie7wYrt^hGRj?A1L(bwmXJk* zsr|&-%1;iH?nh_~@JtjZ!|yHRlOaMw?@pP>+`=Z4GG(D)p=00daPi9W+@Gu08fYQ& zVS&Pfn9yH84TseT+3;2mf!pVEitZ3m4CrJU7IfxV?~(J!#@dmBgA@mO zVjF1?qyS4yjWuFb$TWcc@>EHl9i|=9+3)KkuP_IN52s{4U&?9%Qlse zL5ua#OVvLN;42xWGWvK#Y9Q# zC(*lh`1p<_E~4;-=5?n&9QDm%4GJk${3V*|Ntd8Pg z(H){9>}7hFWst&U2g99`t1)@GVm49%v6{i;`?4bvy_G zWzOz`q>e})vZk~0p<|0^9@^s3EJJFa!?#V&{rO!c6R1m>%7n+E1PS$jr|KW#Dcc-u zC_t5XHHtrzVIXeN0_}0@S!g&(wX{(W z;?pMnoJ(Hz9{z*(YK*&E7J^oC#XA29!1TCC0XQLroAF625P(^)o$%qL*CwxfU!x?6 z=dbl9_ww_Nvd6JY!HGv@tOL00ipg9}jJ^VG$=KXb-NERma{=o+owx`&Cc2gg2!vd{ z@xUiJpmJ}fHXKI$KnL>vkPLs6RNu5YZuWSoiRt+?Z+Q;I`Z-~e$ITGZ$K+*L6u z*Fkr-j*sv#ANWP_z`Q-3q>LxIaUlp@$8~-?3m2OMZ%#w~b? zk5Hk+F8bBxcSgoXxPXXam8v)#wa8Tol$oscW+Wdg{J0aH^~f_~J#qGdDT8v$7u9SI z@9B5DkN9Ysvw2+v&`)+*q$+wz1|Iq+n+>D^Q7WypiArhQwErU8I8$ilv0VX(897ON zKfT?bz(rbK~in2TCMX#2b3uwg1w1$q2(%LXwq%}F3?hi&4rxgL-edmeI%H@sf8FqPtn z`{|MMah3qTJ%u65Phi}|>cnm9cqDm3k#JP4Ivq)>LMp5#kM0LCi(Ua|)#du*kLZ!U z`6l+3qI%I(3e6&KO0-){>C8`7wp2%JU)9{PXYfYe>%16NGd^-T%l0O^+|u~_J)eK) zS_co&mmwX=MC`>Sb0J`#=Ghxs=?#3q+CaBt@M$-Nl77%KwNiEz5U;vJ5;?v4Iu+hP z943xsgvko^OJ?sKN5@id#|_HKtlY(4=QEo+1;iQ1*Rb3f)uAvLN7jC|EJ;UYzO6X6 z;H0`(L!^tBgj3kp^v_5veK%#0qJarM|A2U(+ME;@$!-6cUxrimhw)mn$W$Ism5Di@ zf&rqj)J@lw^krn6?_$_wD&@iwi#+K~e>>iW{>bg0H>93cOzANxbF_KRS&#*p>Rs_x z74p7%o%HGKI8nBfR{tf8iI_&J_yZaS-$$m7NG`MQp43vKemw8_2)!^sOw^KBEK4gz z4aYvtfBsvkX#+R{_VcNBjlLjBqb_)5-!I70iXs(vAE+F%aZ3WF>7Zho$z#O$%GN=2 zM!rq0|6h3_KrR|7LeSm>@q69_K~Tn4G*`Jq>+9Xg3VORd;9?sdz*@}6F8qY3y?QJR z=ttSvLY^N9nbk0y!g>rf^4IuTAGlseNO82eDmC==a=p#U*ZXRTxRjq($Q1ZK?zUxc z3u)mf?|i%ps<=3Zq#P#Y5!pH>Kwm_E;HV*=d$^k(0F7JGpOd&+nT`H->n|N!Si;^> zg{J#$d8;prwlTo3>N&d66KG$|Ytslqb1-LTp<=XaHIv>0!=*qK&-sldTpN~9bZ{=} zzf!+%qB!+MHOpwK)svLV2ZOjoJCL9=N})$ecR?J?KvX7II-;Ub`T%#9hj~;4eNKGw zgHZ;XEcDg$Bs79Ay6)Q_+r#3G))T_`Tm?dd%q^9;c&q)XE{N1cINerC(|H%wpEG4@ z4QDHwAUaB0q$->fi{-3;S`l1VIF1WwTwgHid;cXA7OWv??OKc#FB&M^Onm9&bN!zE zo?xvVqU|a`;sEX>1pYuV;cN!uX&NvXcb;mv&di2cag8eywSy1L6@RFbA>E;r8r;xY zo!YL!C1O~li!CpG;X7M*a7w{Ol_}y$nW#WW1VV*B27DAJSegmd8i`3Iyo%Xjo7bui zjb33U`a9fNo-PK17#LVi#?!j+6y5z;i^h$Cb2?|w{FX1#*^rx^Wom_Hoq6 z)j7!}oG1IM0bQt#xJyBE?^$YV>@AtE=4D|4*bUCog_w%|JV1Y@fsOdDowI4$$xG4Z zWQ&fbJj)Pa&Hf!B!s%;QV^w5WI4+%?M)|0?JLK%^=|DXFn$pG8E{?Bp+@{3ys?^aZ zp46c58TF83+=BvdPqfdfZNe=W z87!fDogu~W*>y%cyI<<-vgjjEh)-;TG!N|^69_bz_|?0&-%-r6$6LyTFs3GI^e-o) zgef(cN#T=`i1H>~A1xI``>#UE4i!UdGyY!TP>EpJ#Mg#ue<^z37sROK1WI=xrh<4J zqEmLNuJNqjt_7etWE?UiW@l%|bcWL4+|!Q2dK=dhO5xxRjT;_oI8vN)yxq;$C!W{m zTYlU=-erKh$n0aVuZ*|PbFPX*O{{OG z9|Rom^a)?#Am+T!E4<`~TGDdJ3X)g%v3c{)HZ9(L8=ay!?TX5tzZL8qoS11YR2#ta zd#}-=5Gnaj3gN>?raFB1Gli2$^`*&Oi_ta#Zd;IJbYM)zWZI>JXH@Wu+E zZMTtnQy-cAXJgotkRt5N7d?WY-xQm*@2vYkgqSQnc+Lc)^m{1Dy@dgvJfuS*r*NN7u zAK#z!)s%qpN**3=puwQ^x4gGou(bFZ@-FGI)hy0;iRS$(+fM5!N(ld zn_?GHaJY8#?E`3)im+!HkQM%JWH)noo?U}S{W^=o{Qs`*(O#xc3@kD0;?G|RbN;QT z{0i#YA0jzBsX=81M#Akv`-EomdF^61sC9a=kp@{VbSd0cb$&8O zgS*>UDN^o2sFJjMj9W=qUS3oZJ_({&Y1)0@J4HLL9M%LJZ!lXT1`i7e{Z)xL73Rna zvfhc-;XynjoI^nJfD*Gk!34nPPnrAoI;J0(1s-zF>4={RKP2tfTwR7 zox4I-g7B)y@c^ZYM!e)M(!6f?DmAyBl6fZl1;we{qcZU?nQIoZ?`Qk=nscvhU9R$b z{=q;f?y|q;LPh_GEKEc#%uOi1JLvR$Iq-r+=%Lm+DQf8X?adYas5}6J0!>k)tmcm1 z&>nTZYE3D?eWt0=Q36`lLDjaq1mGa16a}1E;JKD@i(3|5Zyg%;vz%=pf^!M4Wf{b= zIFGL{fF!z$nsf9Q$LVU^cM52Em+HY&zI*8tr5#%~?n5PSS@V=KWi+Y_Q#7k2$f|7@ z@C>}p#3#73HbuuszEg0w$M`kUBiLIsR(t*E{Q|%~ZSvFAe>X0O|ToP%%$tiBiCH;5&A8*;wejfC-?~MH@zh-+3DYe zsP3c7;(^E&=f`D6#eBWHWnittJZika6rG>pYb%Gr*m!L|3xuCM@l>;2o=+We#G?IzpW1;UsF_s&4wta?_5?@B7Xk9x-DOSYG%uxGu~F|IF_|qFfKIWY4`4^~e<LI6Ng-CxV)V678B(K{@~5zWUO!xUvf`if&OIv7&EvHxrtq^k^f zvxLyTX$RWV1`gC+dBVpI$6xI-QlpOAZ+ol_lNg8c=~2T?XT>3Z?6w%>f{R& z^&9QgF-)rG&p*A|XY!7iB6E{Tiu}n{USWm|^z8UUx?U$&@odMUP>jp|OJEn3kZSmf zQc14F3<&fRM4u78DJ2gK`M`f$mxC#UgJ2)v_%dgYdtsKuCY7|&HkU9)GgrlxgACdDp8R=R@Ie6&rLfg`t8B&;wM6b zJnx;R)&9jsHjVz*e^IvLA@|0wQ{b9h#fNMCaVV$0Y0FcijC`&}qV@|7G*n!rtMJ5~ z7>Euh4GaN$N}=@$QPi|HtA^B3$;) zh3{tmz%XBlI}#7!@%W45QY)PJ4X}R$^>gVF#UuA>;7KGoR{YmY<&^yv2`tI`P6Dhj zfz5Cp0xbcBKtAWy&@of6{Uk``4I!MdC+#(n|!v9h`+I)TZHUMZ81bYLE6vw3b)r8X3ZYrt=n}$uCO>WY{Pf{qGx? z7(w&wHVUaBzPZIIvT!NV*=E_$m;VKb&S(*~!w2$ehJt9x9iQ*d z+>gLXr)9ZmIdvKMWM6lDx#u9tEKURC78Tjd=x*=EpvU!JoIIsO^6N5RAl5G)7bses8R-+c>)mJTp9HsP+^om9c;<=Nf*o*0ez5d<HqZ$U-)-%V#kJgQPFiI)xzO0?hB1U=gy@@Z`KK>pB za%8hQ_z0gHMry6BhPdrl;;mBV*zLqS^FZpeV^1plMD{@9qTZYpM{^Q^SKhwx9T5YL zz7pXp_~d+}csp(ZC*><^!YmPA7_i-tgUJ_UKs;^QL-9P0Rv-EJ_zYE;%Er1Cv{R3l zrVPa7aTfTNKy%TZSGxi-1>IYFdlezI-^bz$F!YteQaH^Nu*f)3N!Z>d9D#|m$=u1n z1~bVu9uf<5kj_X7MpUMPpOoxI_WgJJa^0) z=-Z$wAuV8rL6?A2pv{qY(i$Zmqo2L?+ah8MK7aF<%;|*g6C@aho5`tBoEq$N41t(m z@c+XxygC_a5&|a?Q2=ItBvjrcav|(3q#HCD>E=xbo@%>sr<5hHiW85#;BVRt* zpmV9iVVZXPr_wXfUYF*a7}MfPRm91>2ixo)*ds=>sEb76I=>G^`!qV$p2F{2#5zP4 zKTDVS1qw)Q)d?C;z8T~?=LJmv2UqIBnPi55(m5)4NDh4guaa&&Epi?Z{{^DYGoea+ z#0k7?qW*23t(FQM?36<2g;G8_v_xv_b9L2v8tX(Ocq-`knQX?|B8+pc4P3NBS@wG{ zv{&|Zxn4>WauEnCXDWC{SW$ttpztS81*i>ve*SO_3=fB^t<<6TxrfKVMHch05Lf^n zd5g#fv&E$p%1JvY10kOcytP)rfq(K~?3VGmyYHH#o0fSN~Ff1W^O!Bh*Y)}4^ zm+${!0cP_3@Vn(gP3CZ(34Lg8ZXO$4;JBfUT;+_LD%2)U1bNjC=%aiLCS}Gr#n&Ny zYRLpggMsR=j^W0Qe=CQn2cZ8&AWZ3Y8~+DS@$zDAu!RxWtMctU4ob0l$00jEP{~l5 z4DkHFNt=B4!=AN%WvEH= zKxwTgJi$UcRv2*4j|$1?zqcu}7A2ZuDwxL20nGKbYH}NV3Y1PoDuK!rTT~13zjh;w> zMEz72gH&JK#WnoPJFpEGzm69#^`&R@{11-%^&Kg0N8993U3jh7??|H!&@PZ`3iCvM zU>#`0eq^fC$kh_NF_|>LI!J8U2ut_xh9Vh_rc_13$KSAJSzudFcYyQ?iF}udE>32V zAgzLm3>Lfk>0zl7y(44sXEKJR?%s5iHGUm9g{@k3mKE$&FSB?bO2h6Vs<%e;#yI}N zpBnd>^oqbWDRog#%t~+?6HaUN*#CE24k*FU7jAj1gALUJQhdDUZ(cwEBL4SL+>`qc z9^moX>w9>31Qmw$yuEIGYia-y&zFmpcxPW^-?72$qKIH;?aw#%%@+0V0G`Nsdtj); zj$O~Pb6FhzrM2E;BL~UAJugt((SZ(fI!t~iV@B9{=mB3S~p}b23cMg1}bQY%2UBe?5l~D z9fzhTl7utm&JTzO^n-Bx{lwM`S#s@u^Q5ecVsmv2O2xgCAF`2S>&xb$TG@yo`2Lk6 zuyc9v+}|X0!H7ojRna`5>6d7fX33DVXCb{CY>xwq2Z!dnPEB9x|8PX(VjPf&n&mI| z>^FC%7<#1*-JP3p-yF?hb6)EWBYvtE6p#eiPnc%04xWf6MQ)9%-HVKh*VEQhMf84F zxKit>f@Uu#SPi42sYDb~Ck%9#CUZ?Wg5-fnSt^ik(^$H-)H@ch{{ zlI0%FHj(`{xrVFhXfs;lHFynD1~2NzLO*LzS(}o5RnopJabO% z_Hzs_)N#mDG)%~NU**^HaFX@1tU%O91VJs4gT!aR?eCZ|M}1Uf($z6H!tmQvIMamB znrEW{M%r%yek0^3P~jz_?D%4l{p_i5@VDj*20#m~jUZB9x#5d6_(!L??c@Rgh?KX7 zgrFlr3PiD3&p6OW8KXK&5WQ0G{p6bGX%|G^k8r_kZ(NQ3*GTf(A?LSc@s_3{q&UdU4?XVSJ+9l1zf zu1py|X$~}g_Gn*eDC1I7=Uf{3O293UrSwWq^^V}8-jNM&!dJBdnr~^02vPo>zUa_- zXw4fOE*_(`{bJFe!k`bOHwKN90Iz+Ur@qrDonIBM33{WuDc;|g}#tQmN7@1j$ z7#4fhOW1HFm8uEfmc3kgsu^Y?7`-o+TP$KZR*9EZV}p-Oh`;fi&cL%B4D)(Zp4|wZ z%a(>TfA$$avYh*jT|8a}`>DJGH}5taGJTDc@J;v01s5?cd~)g+SDou0eBE)I$Uu@O zpCxi&U|^QS*8x^1k2~WRS&gX8Pv@cpdG~*P(KXGJ&qi0Cc_kh9E-aX-#?#dGcjHv-P{;ZwO*JAvpf<;Fb>S z4~I5}#`(Um*dUMap<+f%f`Z5J@hT+m}t7!2?*0cQr+>%8?`OpSe4jS|RbRMcl z1oUE6@B1YdD_s9@3Ss$tpt+TN{nFnPoPhTi0=i@N0cu1FaQk?#lx<$kX~Syc959g2 z{y3e3Sc65c#;pjq3#f^7m(lHOkCTT>{k4P|?p|ZKY2ej+o)#?fIL%AZRK{MDOc#>Z zy3TV#@xN*o@c6w{r`+?Z9=*{d0=6TvZ2Hot&h*1@yNA_#W3N!55w$Ml6D6=E(+(Qb z^}fxK8hUbW`;g*SHr@L+;|`$=ta`bwvDc)0K9J(E!eFH=ux9V~FZ415UC6;j-ti;- z{53@N{r|T(j(LB;pre%Hcm9nl87JIe!(I-g@VE$`{rPCGaTXetjC~G=DHc%B3;E*- zMcWJi@QT7*uUT|gP;FzPEc8`Wi{Vm<<9n?8#YF!lq-p=6pH~(%M<+d?qKcUzo%OxQ zi=5;BnZWskM#tm>El7k;+=QZ}y2EEtlasb2cF;#<5=U?Tf+Bzxrt>lH(841udih}= zY(>Q!RMluy02*;U8+U5YavVYn`!U*6y{~QeufZguF?YA5!+Wxe=aMASB@DGjG zbD=Uhp(c1Vc_Ou#0bMeLSHNPl0x%<#!d@COH*dhZ2H!+F_8&m<5sd3TD!5C=go3H~=?|SDEy+CV8?iNSWakhx!~pCnSo3MO{Yo zC4NfHvw2!n2G&yuQ4N>{X~S*#mJ0hTYPXH5o)A&z1C_<~af(9iMq1t94uLDY(eeV) z)6rx{rU#Saa0QU;oYBq1u-LKCSYhw)glkx-E$^kfYuEXqnD|i;C~f>bIaK%L(Sf}K zp9r%A0b>1I<|nGhcT7^RCYslV%=Md{pDI=HIrQtxfAieAWQA@EIIZU4A_h>ii1DXE ziVx*a$g&E}zo{`q}JrKwGZ$(UQ8fXZrg>fN3+=g9;7h#SL}Y+oquH?>lhuNS3z3&}DnmpZ`jc zSC4t&iAe){D&j2<$&pvtS!jH$_C@(23#@dmiD1Ex;&H(FpXKhmFS%z*srcQ(>YJ&w znjKFtw5yCK{u8V|(Fo=%NENHv8+r@-im!)(2G=jKNGIi*;Z z&qe98qhpJ~7yz^(NWIO%ICySGRNZbsif5KNDNZ$2mDe`gV48q#*YLqI3@ILXia|@c=~|ft;b;io`!Su$GAUv+IJh+hX*Ks2mRqKg7vjA11uAT)XY@LeL5`6PhQdG zlqltMcBRohp60P-bQ|G*#de!P4ZTkjgNP!~)98>>%XQoOt)r$G!T_^+$CcEb#!WG? zrxPBL%4H(<+mK!ua`m>+{uPMoiM-u++jixvq&353DlN`d`K21a8h*Kz>6|k)a+hzD zhwEDcJfk;}ExFsN%CK?zv~`~(@ZE^~gw5+8G%tmlNP#cc?Vfi6n+D3>&ssz(fkj}L zi4q&X$uBwi0Qt(fc+spNuneWo%s}qvKT06VQ(QmWcY|0 zyXkv(&?Y#>d!xaZ(4gKkNfnyRSjgGZpeToXhsN&zMcG>hW!Z-PgDNfE(jbaAoze|b z(%s!4A>AS=-5t{1-3`*+NJy7-*Pi&i@9fNfcXnpK=m(LR8?Ng-kK>nok8N0f?_!h+ zm7zGkZi!^+6pE*^NcDv9MQ4k=!c*$WXJU0|PLJ8;GKFwD9_5$>DdKUF0!5A5e~JSl z|AQDRZ0~-?a_SW9kuU-Nn#_M%J59`gTDvV8;WX`rNmrNi9dvFUp7#ddneN_>mFe*L zRe6xbE&MnG>ty5y8RDi|W3e}bd#U~Jm3q%#>5Bi51lcv!>;>DW0W&F8!>Sln8>96S@C*|9JMo&Ky2%ul&h5FxOoD@;~xuh zHRf~u{8$YJHwqCXeA0PjXS_I*lxnTTI(^+EV)n0(ueIA;f6x>}uMgF5iAB*QHb}K& zWF{1NCjPF+Bq`szA1z-5k8qRw$twx$fe%{iFxwNGRP5+l)73?9b%Pzst{&D*FUrRf zsJ&)wi)3$_!a5e@9<6r9pG`_zvtKmigcaEK$CH6mWiCBT`x8 zAJXpx1+tV|CQ)5iw(@7b zYFiLAn2WS{Uf}z+1pl<$tb`~QlV*Bh6E)Qxc08uuRU8dzLoXu=H~Amho-d#a2NNU5 zlo|&Os12F~`8oiE^@sX9n`t@?xXq$i(NO`O<0~Dnbr4b*=jjdtAtpq?S4^6^=AOnd z0oXx$i9PeprBsNu&BiLa9QFQE9;SZJBZ{ZcefiZ8G36*$LTr#?ZU`s3Me zH=ogwG8*k) z(R|_OZHmhi5Vh8KoR@#Uv9I)@b0@M+%F5B$PINzCcgN}(uNUl}b_JuzccqxyG9H9lm#J_N`%7&2l(PB9xV zc0Wk9xLn`^3nk9icrv+z=|S9INxceIyVws6?=rnEuxqWCApuO>HjQrEwP@p|OI{&6 z<5`1^CP4#6`)q@as8U$_6S**sa4Et!fBa-?L}-!DiTptBDk3oZ&OVjhSuaWd)jGN= zARkcs-}f3~$c^}_%N6^Jz040<;Z>K)cOgJL0}k3#;46_z)dFwVCdj0^S$-}g2pGUTdo z5;%F2CeOoZWSMksfnm-I8-T9@O(i}6 z+duvG=Y?ogFtHY^{Q&H%-tY|3*Nz&M?aS*1MZB0YO{G0bjcU@pI~KpEUwS(qbs6wk zztE{6>?zXGn;T;nDZ`D@LV5DRA6d>*MptsuK{}ev0*WxS<%M({lJj;hyoVe^MI=v= zy#wN0zs}X`QS=Ra?t**&{Yt$nW(ue4Yg*1){wCrtCkiu}Z&*Dc5-K#IQblYTUfVAd zs?1R;H;1UG^a)Tc`TZsF1o=g#tL0rJvW)U@@@K2Hk4?bmi)`3(k9dI_2}qOP*5inP z0eyTk$^CR%fO%OgL3FM|-%td+16p7_UT8p#b^xaIy^-Ol^dZ{WqO}qEFjTm|RMMaT zT8ZGvTXwhGcbCLG?{_&~Zx6th4K4rsdCg4qpX?t;=5J3z!(AN;B=;iQavY-q^*#-f zTnNDx&&v<(U69=FkR2QY;WMxR(7u|nO!yxsx?}Z7krcWGp-)Sv8ra0475`@w|M-uu zuow~C*o_5#Eo^qXp#{b5W$<|5c{7KNB>FO@(T=%d%b2i^p~Kt`myWG^U+Fw{G>rYI*1jYnE{cjEjWB4Bpp*Ai!ez6T0j;$ zqPH1<%$h@iV8jd{k&mWmO>h+fMxlY3isE}cX@+H?l~ zh-$Q5vh!*6_8kV@0y^=im>iL~ie4Cv}b6$*8o zw!NJ*t2P^B72XpEoba~#j2S|qHSK&gJ5n_|dXE^x=bcDdF-^(gU$x@w$&IG3QI1gC zSBZVlfsDV|{ZW2`Ei8rm)lKjQs?(B1TrqlQia(sV{=06xKep?8QrwMCV(^B!y&fB@ za=(QcYaQ12kQJ%5wF33E?wrsM<|+DOn02Iu?_^j~cssfAf z@xejfUmf#)_zr_$SQSqbU=kGbhQpAQ;x4>n$nWzf@gcBo45e_RtB`Obh<=c4#nAdENkc^TDftT z<^ENG1*v|zHsn>bhYhk;(f^Nku4|lwoHple5T1?>zTCl;!uE+m8evpaB(6}4!uy^z zX57uw4?pz#d3ZkkC=!e{cRsmq&}3+Fek@KI_oE0ZS+0cg3&x3N<}2GE7QAMX{EU|b zxdh%qNF4nKy5AlUO2}vmr*{zF)4NfvMjI3ZkAStYUBq9Jyzir2a``XR9U_2Xn5E2 zUuG>)eZJ73R7itt>3~k!C)kO2b>-hZys@?n3!Ax3+`6Jjl-hy6mFnF?lRzCg!%0t z3H}rFhKvB~pd|}Jk2X0X_EjT{L+d#&WoXqU=5h9I<9+g*+Eo}dmak$fTOpNBVULoQ z507-dydF0I5*7?epB=~@I7!6(Z+ZV-iN&x-vNV0^awMyzU1>x@J>rXri5<^;uqIp! zP4?vs^Yi5tV)hGY=^ehzpAlgqy!+fq;Fa6rai-jBfoca7p!wbZw-Z={O8U>k(U6+T8Yin!XARUKHrBqW7SUuyOb;LmTBk+{3 z-Qo(7&lU@8^Rn9u&e!7OaGA(w)ww6z|06@kGY+XUjUVH4}IJHTZ8hM;-2}1 zOTbV30C|F0Aarm>8T*fm+q8w*ht$xjoCoDm%6}0=`Tq>SnYZo>ZtegXP+T2arl!jG zbohJ7o|1~r&dkb*(MzY*5H`Kf+K*SqLod=~z9+6Cht+hsijc&X?;|m7{-f_n?;NxV)lk%IM89VN4dDZ{WPE`@0m15gDf<- zhUSTHCX)d0?0lAxZZI1&n=Teu)C*U@RGH{6<*zwE9^lF6iBnTjiDT_A<-%Z4oT3?l ztor`If)ArBjr46>IkVmj@xaWuGKRH7Xm7OkS#TNLT ztJYZ&0RfvY@O7+#ScO0Ww$gScms7*@qZJY%f25ASa1uuIDF~=Pg58~jBib^b$&(=& zO6MbaxV`8CY2JQ-Th#?=l@Nto$-u9!`@#8|Kv%}-dTwgDSUYUmG+$*R3X-|xK;#-( zNrm;hI3hmJrM=x+ER!OF$EVj~JAV??tKlWqEvTzhTH-E8=h-zDtS?gj-y&83jd#$J z`F&62e`b`bK#}dC>KOEkVIp~%C`hg61(l_G9X{|j_`K~2$^r`jSQXrytPmp)>U>`Q zN_v2!Nn;b8;Kx_M;&NT1lFyMQFW(~z`l8;1&%;^U1*DRjulAz*!u@n}C?gqZ-z(^d zgg;`to)dhl!mdi1wmE*k2(Z{1%DgQJuHdm+67q0ho!c6f)pM=jtID@28 z_s>v6zI+RX)$e9+dB0OIDDOdgX|g-voW+;L-HxR3Qf^cM(Z|ovi*f7@n{oHc5s=H9 z$q#b*nLUB%m_W7px=shZgiGaiDd~)_%l0{BpP1 zY$f=5?pLMtdMLl>+lZ@qF~IVhK}>* zb2_BqKnwy1ScrLiV-f#apU3sQy+lJGpmKXOJ-!mO#qonwMjB!E9mdbLwS{s%0muki zK<*~E(&}OR^ymSa3SWUIQXmM6a>MxJ^h~ZydYJGJj0`~K3od7YWKRrmoJBH$ywvWw z3WLoEE%YACk3xmS|9PWo{)J;rrAgg|kNiXO=e?vVV8DP(I8y5V_lww@s>`88<-kTn zxr45BlC{z-P{E9rc z09M_t@AJX&a55aRE&0ba+rPxQ-EByIFVu7bwdFCuydpV_Fb=pJ@kw~^DJkg^t(ZN( zL+`9qQOD6G1Zc=;zjnz7N=Vg4D@-P5mkg^K3DcRz)a+H3Pgsa8%WwDMon|a+)iWK- z`ktS?5sqX?t>fm`^TP#%_`f`zASsYm>u!G#*HCKx$D@$Pc>O2pCW8X}KyLViroZAS zS#s|cFt5wE@49BhsJv!p0`bda{%xChkEI%zJ@D5$UJsiIc#d7lJN2e7Y5b}laK4d^ zTw3(hepFB!N(!y|AKZu>B^+{ja46I=E!IGjc2jZLoy{=n{9%+nol*gQ4&$X8dXMz7 zmw__dB925T5N>yIcdeZ9fjL{_t?pm{`SLIS<&M-67BTNv#pPM4}l)WT2$M->w2Hg22&oOV3JG_fE9+T=`4TuhR?S zW2tdDc9>6fK8F-`N-oB+S!+usFbL35UEkel$j45PvNPEzSDVT~!@}Ypo`Ksul3InX zsL!DIJ1`=~i@eRBQhSZK6{bWUa-yS=ol^}oCp=IOBS6HxzMzCP7>_wi%=;;YeI?60 zG5utB@`E_k&F*9$fUrA#e*nrccze`{D!JO+hjP95b;JG%N%%48F|kNky6?F-1}QiR zR-r2m2Z_O5otL%&M=)F3`R=Kr{_DxB!l`Q*uow%pHw=nJvl5{kt^_P74Iqk#UNAVU z)&LV+I3N`wSWRfv{lSm`UDu&%v`m91yOKwV&uSJn@yRoWB`!c(CxS7J-4!~oY+0mi zzcRx{Lij+uoagqsvT4^K>}kYG2|* z%!{piMnoq8G&^0UjR%x<@X_+s8h0}QX=;RQfXB}ien;yn$@NXY9!nRJ?EfZI84Rq{ zU`MUZX5DM`tpP7bSckg0t)p&_w~&Lm!fTVhb`m_>T;{KdyO6sp27-4M6Rx|+=lh2- zfD92$!E!Bvm-`EpvwCThLO_sz!wfL1kfU`^Yq_Ss=VN?cPe6ve^49bF^qiW9zvr-OYlgpVg1LY6sDrf`@Xk2ja3U3xF7JLT<0p5lT!;&?FZ3uhGP$V_b+dZXC=LY*}UNM3uZ1V%}0Ala;UI!c3nL9JBi>kS1rVDio< z^4Ip-8d-M*aA~iDGamGY70&$>@Oc&n|M;K4%9m^c&peV9Q;8!%E`T3CLz#dcLV|^A zGarRK>3+?Km>T__*9Xfj8dzS;#zUr;geR{AMp8I{I*U=Z-1% z9VWGj7w9p8JzTU{b;Wy^91r)J*Glu|8J~?Bk-ziOIDgMHqX$UeHD{oR3l5np;^iXt zKGdkqaL;)SZ~dD<0hM`1;eQS`L9v^rT0y3J*O<0__7ot+WmNr4J%UoqZj&$lMFK!P z*W}bA_-e(G`nJXgB z+dUk?PDT2NRu~=P$SD}k1CAg6urq9!tK9C4?*8nE5AeCKg5qd`-Y^>YLIzotX;gm* z-Jy`_WkSp6D)z`z1e~AVd_LcwBEj4W`zM@UDzH;+vj38B3KN7Q>}S4ta=&X=tlf;? zhe9ICG@5O?SVAkGBZaNmD)msCKU+-9jq#x~2n3g0hkt;`)j|&_(uG1J_`tt>c?Wg8 zn7eYkIj93s%D|F{xdSbY({w6u%yE*UQ5)jtk^(U_2B?_{wBA91khnfB9@k5Pz{XJg z#Y(l{y@gg6^5!a|cnzON@__ZnmZk&-gRprA9?x_52Igem7w+em(en=YZn~yVya|jh zuWCuyLrA`EBG7%^>t+PM*|uLQKS`f+wi2U1$Tql21uvWvFd}(zyTSvS)}F7;;!c^R zQ~}dRG)w^ag6;WCqy;Qcf-D~%8S+*lONwhe#h*C|VbFIMQyn^A1=lhuR-f7V66#$> zs2Rm`YbYoL&nW{#nuKS2(OW$5Xp?#1hBqKi4Nh)v@56t}j>W2xadb1tCd42~k z1;~RKO?P&8DGLP5z`2N3Qy2X~M&dZgU`F2C{0me5Y@3%CjdnBpn>JAI1!T@O(V1!g zLGf0;|00@9%@3Ir0TMq0JMwqAg+QM;|8^jl;8{_72J-Pil{YrF4pjsQ<~-5w1`QCt zzm(cMJ;XV*%d~mqR&tn*D~$#>CZIjKK@g`th-(()CC3YWk~xjf4fzO`M_I~XLxfyb zlK~fu8BkD9@uk8FBO8SNQ}9jFn@~9yW>(PUa+@rIJP*V=A`6@pawrr32g4@CEm{SN z9i)gy_6oyBH>&Uxc`5KisQ^VOWRpCe_o~0ByPfu?3cg!_!4JfO^ZnYS(>DUD-A7|~ zFMnK{;*OCN6LNAnNrH4Bx3(K?D?#YoM?OzxMg{4E!dO*??yhNTSMnz$c4R7P?qB_D z^-ly1N0PADb4RAm>Z#UWKo@g7F(0J84dT$PRK!#!V_0y)0#F9OP`W=@IoE)U&qDP- zQcwws)minXX<$X^vGl@A$|~J;!WCW#d5vuK;6+^1khEd1nw?@k%@%xRLJ8DEDL)pM z#{mnVg1#f)&62BEo3fYRk)j+dgvTPP==E0aUKe!BoaWh_9DJIhRKdMSp6)|+`KoJr zq<5UkP6%A{C?|QH+lwD91A4#UlYe%!mLz8W6sVDU=ZGR_b#ssXEW3La3f)7WF#H@W zOK5W>_N2Kt2>hDP>)Baa(o}ACK>MOs=pSq?6P}W1Y@xa<>+S0e-O7 zw#+mc%sd&je!-iu91{{UC79)=VES}Ea-aB{4V7gS&&TtEknM?{Gk~uZ{+d#tOOFHo zgrdHIu9e~fAFM?8q`K~JdgR~t-+71BVT)eNK#OZYxm~q`d7H+35+hR;xbMoXXQi_x zqhZ}%Nvlp-38Sjn$r|ooi3)p!4y9}C$NN$QF^+Y!d<5~-=+l(=;u4^d);~O420s4{ zDcPFo=0Ge&r?pw`&BY9^c0;}Gx&(v2M`ogV{7dRwRmo)55@@^Yl#(I!l#;^$5iEn% zT@W#=T4fxnSTIa-0sLCx-L*r&GNfD)tb0P6VIvX3lla{(b=3v;1g8qEta9@y7QT9@e+^pyf~)06UB( zBZP7xo7?aFTU^dW+%JK(=k;3f25oMnOdL(3<^cqBw?JN2A*Y!|nu_=>fc}|ZIcT?| z@0dd0Fbawp3I(8GUl3|@lCJ#_hG5L@SfA=6c#UG6rgA!Tf@scc5O1N{w4e#9xOhCh zU%?pOD_T7_Bt#tL?{FCnU|rwbfPjEEpc67(-fAd;QILw<>KP^K{(&hqLOr^Nv^}al zV6-;;2?){6)&T(7uc+g1)+!nL8$c7!X@>X6kW5uH^DC$$z1iB5da(XfK15)C+@GpS zOiFlvmYIg|-f4S6B1k4ge<1W97?kYJFLECX_kirs&P-Ae4-f3M%E zQ^}KsyfEWFqvA(jpKn!As(+a{B8g1w{&8594nLoky4LGYHZry1lK_ncPakWvZJZj} zCgw@@Z5)tRG{RI=W65dR&|zSE@keP$gPBeENj(2{e^BQQgZa06 z&zt*A@U{`P>2SZv)O1TnnJASh5;?t#b5MV4H#4-uTkaP4RfdGGXUE$Bgrp>+fdHJJ zQfJBj0aqjlE{E4^>pNrhH&8_z3VO&uvs(?PgjV4cqf&H^&om4E!4AAaHe~9lpH~qb%XKO zG1Uf6$qM+%uR^!ko$qqA+>!v1>Ki{0^{khj&ey`79*h;tGNxs)8xW4vJeq64-D9(& zdPwG6MjgbZ?*>56SiTve{oi@G0MvuN+mF`vyI*yr3>LEh!V*LM9oPM3N z+no4xzPo^~LZ>a<|8;ZRqMfu@yB7L?WT?;9C)8I^C+GbJX4}0rMLUZnm?P;ND;7``>IjM6Wr?RICDb_M$!AOYkv+JsKioNiw4S7h8FOWbPr(_^TVSG{10ILQ zYBO(Mr~v(`F>iK{!?2LhLeo?YxJYJ#I#WNu!>%p==E_&C;WVb#4g=J3@auV?3ItXI zvvU$ALhZU=0RTxM1)4I z653+6G`3x_)^gtF;nuAy*_jiEcX2 zIoBY;HR`bjC=yOL2mgGS)Ulc^O}&nk+?M{1m%g3@(w3L14&j?(TRP1$&0=pKb)Qp-5p- z63w%!3_F>5xD(bw-nu6(RCa94>jty`ZE@*w(o?F=FZ~) zM0@HK+g5Z{)??2BybHuaN2l)@U8K92IZF_feaDFlwHB;&O8`R#ESogI;juiyV z_J{Y`ekE{Z-KUL4?%>#LGK{42hKne9j8!;^-UCf3kWWsbJrt?dW8l}Cesh)D=djpI zSt$J|%&5YSH4&&kZ9Xk>`wk(3PDteXHncC-!Q$Cmx#V)IuGk>`{D3!k2V>#}q z{;?kMs|yOmVnv_U?6C=hcuYp?E&GBM>r;EIp~TInxjPgHWzf<76(v>R%h>lOF)4CP zQ84`++bMRi{Tvcul^ZDn5u|u8KlRh$349kupqhVj`n#wT&fGO(bzv_vI&|r_ZSyU$ zP(`V(Ft4!4>bK}Fvtj0?mfKkAQQi1KLG*5bL7V(s0j6S>yV-4}Cd>$wJobRj+3%|` zMs#$!18}UwP#R$wZ=T&VKZcpEz=C0pa|;&i8@Zi6BXobvWF6z zkb^5e_H5x~3S>$E+f9{zW}`5sT{$MghA1XjJ)Xc_9L>jy$NX-?=U-&=`dpPOo%yL~ zMxhZ2Z{919*aOmMl0JU?@&WSY7D>`wgTaIq`R`(IuK!3L+|`i+a6Rv={+N6YE5v8h zht%;X#1&7qsjyM(dE4Vg(k5{QYz?LKO6b4m8~v;kL-!fjv>6}p7%SznIlbN_W=UF? zrxW69#iww)LE1PyJ!jd z$%tZl74Kan0?OmzO%QT?00w)q7Mu5#%o~|0rilz%I*vvS=^{cd1sVI1Pl?SJtMeu1 zqkj}MO485ATD>k<(D{Q4>KublVDXSv*4k0XtU*otzy_bHm>4|C>KZBHfFZl3FJGvy zNp+gY*&TsN)nhsdChj`h$Zjslf&?nH|F*pgCI-tcg zIvtJRLi3M(*Qa?5caoG5W-LYql#q#IKwE}zaCy9Hkq`o+dG#@j#Y}OcXr?M~l5rrd zgOXT6z3r3M7oCa#-*Z96m*WuW(;-Fc`FZVk70O|^b=-s098XHq>}UhqNqh?%$7~|4 zOz2ZdfV6Mz9$uc7cIyBl{{^v_Ic2#w`g(SCx<-!cT)X5&l-}zQpTl*g=_($^AHlpN zzw;W|L6DQ|l_!1sB5JObh|5K0Y90jleRe2PVo)^F86okf!Vxf5JMupYA9Q7N=b_%$ zI!a?83vvla-jEmOM^VP|W=f!GTrK`xI1s`6CEJjZJm9zx?x9fYk9FqUir0e_sjq+l zwc3Co-LxYpiyk`XxFA{%kcgx1->pWA&U*}0ML#SP^%~EA%t~;tG3^o@xKyi!*L!JZ z@eqWJb(cCB@MU;TXp!^wR1~lx%HJn6!j8wJrvjfJAs;FLYz8_)4DyY6hn=lG&`j;$Phg{6N0B z3q8nN_&rXgWltVdGFR_+>ZKisAa91@Hmaz;48*R5E9?{B=RixNLFOhO*cAZ0?Y4p9!^)H`cP?L@MaS{d9fjGN>}?-Qu=$ zUY2M2XNaSCFP_fKeWBTn&uBO;dJ*b93naXQ+b^y8X`AO(?}%8Lp~-C<1+!h6m``HA z_HXutAENAuypH_#Q)J_o7jtlI`{m)XxSP}ADoc6B!`%cB#h7)5%ol%$?OM7p`)l*v zqCwv;Uvracs#(g7V$Flu<2BLa1$Q3fWQZkJ!3c@S7KC|mPe!LzJ)Sg#VG4$SWCA55 zq*`G*-JFx$;e?{B%;qwe?w1c#i)c8p99X(_5E{IXg=8IkGJTENS%irR%QI*JC}}*6 z5TOp${|b4Vzf)xIEj;|I@89^)%Yg?pT~#!o@({ds-0VW^C*XRnQdb2WH6@@F3AHiO zJ3!ZH2W~oA6q*Ik{X9Su19(BO0ksbPu`m|sTO%N1xB2$Kj@k1Q4R!)IP?D4@0bouB2Qi~Un+#*h^Gz_)z~`&w?Obe+g;VvftJ%Z#4#O+JR9J7LE4H$eenXc0cx7XG64U3dffk&!(iz8CU5J1U19UYI@AvcY^> z*OAL%4?1GhQ!A+uWnft_pDIM3wp8^=hU+Pvk8aJt)`)MLSSrWVn`%}@M44tc-xHI# zu^kX2YjCBwKV_Mi;JE zrmA&crd{5s91xnTJ{;U9p0U_&yranJAm()zE7`Bf0Q3K3#EDOV><__vmu+LMosmN> zmM?3H%hd-H&4@j&cVCp%d_oWSg8nYI4jxuzePYX;d(6s%071x7?2qMO@x`krF*9bv zY`QP%W2ZfciN#7;RvvpiHVj{WS4UEW5e@Gtl>-53d{nnfGOK%Aau+?w;2bd$BRx_P z^0zY`^k_?QHmV1GDt9z8ycAqyzM00PHnAnDiP((`EN1+D^!Fn=M7;J91Mv{aSrT5` z?9f2SOoJ4nGR8g-Zs&2GcX<}7)V#&)L5uo&5SUSHQ+s#!g?o$HXjr7F*4kh~Jr=*l zbhqidBJJ(Hi-@Us_2G37H5RimO}H?lZ`r-;IlQtk6guh#lW#pV0@6lZg+i`E zgT$JIUy?Ti9Clq<-so01MHpWHp7DC#h+$=tvC7etP&~a>_We9FqW~)X6KPgPN+{Q* zY?zE);;l3OJVZW~4?G#UtTW*wnTw6x;j$v?)vK0Lqwpg$t28;!FAM2U+?OD(?EDY; zt`>UvqBEnbO|3y63M^U%l4!`A4ypv#9p4ma7mK-aJ$q0aQK-|#D`1Q38wwcv zibm`hLjfuub4!3*sEOkSRl*Y-5rUR(Z^WP)_(NV0ga3VjK~PEX3(i0lxoFsiO*Cnw zS;n(rz7HCr$q5Re=$||C6q}uGm7Dz}Z0#w6ysPUh=b0$_z0aQ+TZ+RHwW{6>;FqW2 zUpoY>**(WZX81SE)~l&9*pL~;JlsZRy$Js8x?vD!O=9HJdk5i0b0w!rb~6}_!~co z*GbQ}zxqh0)m6x%pr=DL3dy zq;ul+X9qor%MJE-y<@0E9A)2^JBXF0^mYMJILVVx@=RG4@SB`@>fBH%X)QN;{)%RPw*;&BZy9b2S*_%%neu8<8 z<^1U*RImoNmt2mI;uQ!{fR`Un8~DVY0MI84t2>o^S#O<%aoDV_BcE8%mT>dVJ`_Cn z!F6r6m$l20o0A)LaOb<`g>KV6p3{O4z?d(ltbyVoDjxO0wxE_vuGn^(B8JjJWbajJ#E~3a^`pusxRACy3ZOy?_6xas%%UT}QeQijkw#zuj^IUj0kK#vlq$m4Ee-ep4 zBBpPrjAP1K9{jAZznwBmsHN2! zt~1%{3KCH?2*`xY-bndx1fY-S_r zJVNPSNwFw|s9CjA`0{D5U<&L;m+yc3@Ew7QPOq`7fbe|v`6&C%Wu4*}yR%;8Wp?wW zN(bK{eUM?4dpk|VA;_y=DkAo4oopKCf3*NT7OZHjwrCUb)H<<{nIb#4<)uVMYxG&< zWd2l*I^E2<^sB#LA>wGYy!7%R+TrLvtk$Zci>zLs+frJ^5HQ|nO5_CD295y2^Xp?g zy^d^2kdfKQpH0O=OBZltU7N}0!Ftqf!wn>GPzNMyR_Rk>_N-P(`bkVxD`{LJbgKW! z7B^emlCodF{TRTM4E%t`<4jg<#5Eud*IKNkYYlG8DjhDSWo4nQ~&5Veu$w|NN`px)#P|2y!m+ygMo=vP(JE@eXPd` znhk{#k;oQ!(CYz>AOy;7^`A&Q&e5?c<$ZQlCy>6|D=W#qxy$l~k^F)XJ ziucP>mL_;am@(o4kP{*xwR6+ar=ATk4^~{Aeu9yBzMOAHnE798RGXr&CxPS;-sJXq z?>x_*#{j#hp75uY9{M6rlLH{VJGQ3?F_KK5DJqWvbMf-I9w{W~!Fd&KZ#e_`ko}rj zCuXVDi22Kt6{f(Z=USnIVzsa{OK4Ubq4E2BkzozF5zU5tlfFw9&jrclzMx-dJft#P z_RUT>LJr2J?# zp7h7xS0^GYoq;SCqO_3nTJ~rljRFRoPqk@37w(dQS%h3-iS5<3Dkw(UxU|xGi^A%CmDZYj8}w3v}j`CZ$U;!9sGS zs@E%c$!xK*seIoPAr$*s9^~X`SoTIOMP;Ms*+8F0TphRrq7O2ri&Q4TY-qMv`{f?v z{_*ap3U?`CZ!!;c#jz8W$x2gzf?!Q0r36FXyuw>HsrpjRw&-p{*B(Mm+Rqtd&1k(8;`uIFEbFom@iZptktq}9mnce zQ}0@*w3Ik0-mp|AR2?`fx@6uiANY8JxeU-$BJA~l4Y8TXYi z1LHepS^1^~nJgB6Xcp@1_-KDO`vz@7r0ta5g(!KY1wcSYwg3<8Wb;QmgkZg4jQ5{~ zi3pss@4R?_+E(>~1QQU*p-{+Dkbn2hDg{Zn4micx8y=Ic1~Nst;~0@D8yB3 zm0^~FxbypJIg2Xivus0<(oR-HJ)tW`vZxCpR(8h2?F8r=*@7|(f+ZS-0(eX+ws@1P^O6W zpA&vk#Q8~P$y$qE*FTsH2M*r!hMFs_$2&xfq7k274Yr+>h+BFJcpxS4<9+GBZ}4rt zdb*F^Q>2u!Zatx(E+0-|id3f65Uj$b%kg@AOb*%@5x5Ia*%;+U?@37~%Wv?AJHa$x zEC9Xn%M&Nl2WHV`Q>)$~xHA&QiIJgitF}?A7O(<6GxrHlCgyPklfkT$IQY2ggQ^(B7}Ki*XYx0;w}+*g zfXcERZf!M#3WwQPGCrGhQUDv}0@hQj(LSTvZ1U8|DFK{nPOf*OXysOkhay;$N#W(% zZT_hl7HtRX;pYe8Ma1WBlQQ1?SY1ZF_vyH~y|kSOIde^J=f@cad@|%g-pzg{`sdvR zDJX6(dD4Vgn*^AoIv7>3?j;s)^*gU_DNTIaw?3OAUq*()sKb-T<$KWfT?m@MOG5^k!}c^o zzX7EJhdw&V!u|*vve0BspXp!xyionHZWhPWIVw7$!rzQ9%oz*n++)oZ>m*IcEn{s)ipKaxA@ZJs*4nS^;}XYzfL=t8LZOCB_+H;xfab)5>%EG(T+{pCO_+2Jh+e;8?Q?v8U^Zq?5}K`%qo#8G3X(3r z^IFck-$fj&Jc^UgwM^6;NZ~W!4G8efxJR&~7!$SS+hBEX@*?-*EgrQ|KbNX+jH)V` zuSjBRfxRG<47eovQ}!DBDa94#Z@({Fgu-cd{~&bLIV|pIBdq<4_hFa96(pXS5zZ$C ziW$u`^C%N4w~~pb%auyiKx1Nz|N9aWUT1zO5EkdrB*L76s$oRDzQ&SC{6KBC5qe%s@*xYHhIaV_ zl6Cfrzv|MPUbvRl~N3i=VTmh)3(@+MJ$*HLnYpy?TzCA<9?|Eo- z+7e%5(djQl8yIGZIXp5ji6+j-@^PJB-Hwh9*A+X1!zv%f#KI;{9{MFXvc`hH0kM_q zN#uDc{M?u3>m&%pIyIp;KL@+QVK_1YYx%?*ta)Ny_g|5cXq9ke0#Nh!4mLPq38qvA z`O&ZSBrkrhB(uW4lA|#Jy-F9=YKrAHa`6<|;#h){+qCu&aF8fvNqfNcBLo>3_#=Nk z$l@@0be*!{blO6SEs%}$N4};K1o3sq;zSN+XY zx4c)B|41r_lppV)NY7_aanM8fxmOrsGHGsV zqkC#$+)bweS2+m+Os+H!C|@V%o2=WPndrdOJ6{vEH3M6}z3G!SKHnD{vU`$C2Dzv0 z(KNZV;@twR?Z1jO%3qYRsI^*O8TF?{=eqOwo6lNzXEV*I{B9b%uuwrjfAzgT; zju|ab{}KPFa8$-{mrjd)^xc5LcG+>&>h4VkuhTd;hw1$7Ff@ii*`p7kFr#qU&Rk_8 zxw$Aqs8G?qd2jvI=Di*(F<9#e#seSvU1vE{Y;U^EF*6h51Jl`UzgHUe|B=P=nRxT& zT2D_ehE9v~@^#c3D`kO*c4??%MI|2csB=2f zPU5hm_#8p6R_TlI4V*SvK+Jl2SPiiJv#WYPSp5}%v#GgA1?r24_&qi|;}J91BPkVW zrWLBtiIP3kjURfyKj(V;7OaX&+u#WH8JPW^h-i9JSB8=M*1WHTpe}(BhfP~^+Spa{ z>VDp?(Qy;MCzNDX;F0NIs{f_nXW+?7#`y4gsGHvUVARcP0zgn&3vRlj0{9b9JWQ zozr`xX3cBQMC6`x+v9wPtmR@$<9tiQno<0=Ke|uy1r$X@icAs>2ems0RQzU*-y4Eg zx&gwc)|}6!JP|hH%A}|4tmL@m!>%(9QXbpT5Ut)+=Fh?LK?8xD!8#ySwqjz@4&cWego_ z+XwaL&!*c^iwuF&!4Df5{MW2!#-rIF9%{TDJ*z-%-;uC!LV>HA)hu3-*<^4eo^B-^ zGXRuE2-fUYGu1h5$I1vU%Gc&{cgHx!a}$m`+#>8)R{lEV9|gbKc19RDJzhvu?7 zg$|fVbdssKXn209W@o@B^9p<3Ix6i+8MW^oobVaAHIZIyJ>HYvn}Ldq-AT=c0z+1AdAxP?D zwpE}>BbWgqoV2*``8>I!3q#a@qV@3KcLpB=acjLe=*8Ib`S|Or(0GaEeB~6#dU!-J zWI($iRY3Wp3d)Oi_oj;^v3v?upB^8;rEIV39J!K6Gqa^CNfyMR7Z{q>xVa}c>!}41 zwfZq#%G8`RZ7L$Qz?#7gqt)e5xDiTc1~${+{5F=%xm>Na(}xay_3K|@zum?INIbgt z^dJ1gvO(+-+-J;tZ5eXs^DQZ4JH#u{$5F9ntvV3@hR_>iE zHaSVee&@fvL_tJc-1>gTe}x$#({+|BKk!+QQOgz^A{HQ^3;2! zPki{?E};!_TeAC}?uCw8o_OQy0KZbG(BSSX%(hXa(}E%3XLb;SS1bv8HWentbtQM3 zjgT);g)_BY^wR$7@(Wc0Gh)DDk$e@7M-&5HYKvzwf^NA{Fpg}V@^HRH=mLx3IXgx4 zaOy`NSEpTmz6aN^E-U>q+Xdef!cq*}Pj&81-m#Ob9}@n-dt3ndI@@+uaq)ahknTJ} z6-yrTu0!lJdz}2GMH`?#M1x6rP z|Cc1PI87wfzO8?k+NtTEX~7XF@a-I0zV5#M-Qp2nxjj&-?y#8acDq68(RSlTrA2vf zv{-6vlKCe4Px!riLe8?!aMfD-p42i7lm$Ej&V-8NlbpaE($u86jcQ zz2~gIm#ts5MBV{N68mS$f9&iIz>c~}TCT-h0Zq%Q?F%s;#v}y?;+kdO)#n4+_C{Q3 zY|1fozZ^xLovU0p@GI#`(_Yh@S6icU35ubUviT6CM6VLmMR`fY5;N%y4TFILubCvk zouRrcL-Yhk38Q!X6QkB@Uz$R}%#q?ig-WFu&Ea4?SiSmiHIV9?80?2I;tT*h+sR); zdm|2LlKAw2Bnd7jsKsTaP=i|!8Z%5D*4 zeOXjO_1(BAlbTpU;Y^P2fU)-J$cTIlOkv4|?><_Nk*m#w@oY^1oV~0z z$KY%iQ|)l_41H%w`Or_Sgwj>WIs+`V%1_!!$^syqe~Lu6>iURmkqkEYdV zm)1;s?ja!KN-71l<<;WRV!7_`O^SGFRnK8(BME7D#6o{JTGd9=q;%GNzbbfLZkvmZ z6fQVF5n4I9Z-A9wA;g(9^juQH*!s%N&?rHduXiTi_+9ZmZPQG5`WP4B4tX)uLY5Ir zx4M(?qmhHR=kJ5JQ@37Z+B?$Izc~YzQMAQ*d;5Kr>mu*jrSe!GN`tK_@Ao}7K5Jxr zdGl@XsZ~6)MMo_~PI46geKa^}`;rN-^WAm!Ot5lq0|^to6VnoNcr;b$?j5kYmXdoe ze`Lp37K33@S;{WG7djQ2Mqo5G(pD2-^Ml2s11uTEQkqL0F?6c?oAbFa#q7)nYK2k< z3S@B-U{WnIEHnEm0$!9K zylI&1K|duxXB4siknp%aQ$iJfpVj79Nb+4wGWVM~!t4~gGsu7?VfW^iT37Q!7HoyW&$<2(+9ygx+w)>9SRR{8&?SGc< zKvx#hd_n`>WS)-S5v{M&r||{-zl}b$e9=pVtX3v_`Ro?v%S)<87ir z*LRLYZ>?*Priu8lt{wQ%gm4>ME>U84wDwZ&7R&QF{a_PtN!)&wa>sjzW##6I^G>I? zA!+;D)oJh`x`uaS5L_GtFxqMM%Dh?Q0uFgzBt@HbfQ&S1g%V;h(gCr6-Lf^fEFq#i z3?#8zIpo7Kc53Zeh3=R>kDZ?ZL@NNJ5fN$CwZt?A8B|uex-XEN$p+=gjEOX}4~m%< zDQvP67IOTWd>SfFspO^Q4?COBgNhpII9cERAP6^f<2je`gP#@?^&Xs0Pc0Z38TIPj z?N~QuQc7X?Z&j0dh6=Sixc8)EJeYOSH9KT`xXWskBPlm(X@p~+_wc!SxRpw|YR)-% z264jnOi3?Utx)%80R+-T=W|pb3Hmv#Xd4NmKfZjIo{+4vmRP|oS^ozCmc-KOc2;}a z2dDONhvOeS&o9{eGxE%1BT6x$7^)}pN!J73&_|OUk+kVlG(5$1^WW6S*l#Tz<(FHO zj-B*H2c75^4(JRKa1H_s2liU2K#Qm+X}{8C!{U$HBnus)c>AkwZ~=ObRX z`5M)POLqL;oWL)>hO`0c-Sx>_$UV`CqU3v9tay7(danmKm&LW3TR!uL&=HloFO;sM zg}ff%e>lsK?lu2eGX;V2hGIy6d=YsG?8O`9P;d7ctX<}I>za@cGFr@&PPzqqCc#z0{>Wg=B#(?4X@m25Cd7ive;d+U3w1)G> zxIAw$%y7yvV^c*FaC*pK&Z4kLj(-uM@1waJ#o6+!b{-^;lY>iYNJOHoZpN%G&6T{A*vE5}i=U z4|7Npxy`kcr0%`xJ+)z_JNZSJubh-=Qnj(j;2e@6u6);!yRiJ}<)Yguu6j)>`rf;f z&a&dAj)~YG%e_=1=%rBlG9-c=O&dCv9lnxVmh8;TQI=VBq2-C0T<|cpmWs7liK^dPrg6VR37@{>J@CD)UYqh4fAA=DYeoHT&FZ(`#uZ%% z^u^IuQ%Jdr`g*F$Ms07!Dwr+8c%-@d%5{8$(k1VPnN>f`*rZ7mxlXOB5A)2v@ztb&6Ie^Yb_Ur^+fDp=kGw^7;L_g<$*WsS4*4I_q4Av6K6t zV5Zb<4*IaxNWIX~Cz8$=ceDK7+*%gXqUz21Hej&&-ps@L`@7r@6IHSJ^x{fYoQb(I%$>Y0ulxrb z1zi?I9%(jY@Bm~Ei}hbmy_>hi|HXQTwY{ZAzTXN(8DX)%0* zm+mKE9)XD?&@F+DO7k3x7n{&{A9#&6-3K>lpYiATZrOXZ$%!dccU}aM@8hrGb{zK~ zV^t0$W|PBJ_wdDAkIS%OthTjjLxe&r*gBM_qFQSN!@G@~uoV8_Q+0k%QDUO17Kw&V^^lQgNX)C0_6N7Xw-#sQ42E4(KZ%@RtZN{0pbKA@8nME;iziO|5e5%=%QS70Pej zt>TM8pu1T!b10#lRgU%~>5QgU*=c2Es!;|VdvBtV4eU~_NqI+*58&iO*2B=O&`gaq&1kX0W8%x% z*|$1KWIYO^D8dlpMN&D9doVPQWt(rawN%_+sVVJf_ir!2qB3KIF@LgeQQWGTf1C#W zwFlDRL#rsZL(z|!92-Zu(4?mTx)NG|5+U-jq6fk59AUF^zwtV^%NY_rW0cNMzA&Rg z!Jzo#B)+Z1Z4&x~HZcFgkgX-Liv$(LuW4S$(ZxJVZO`Ziv~?Q$|y zMBoj;Ce2BHDXvMqB7|xaby&1O@X{4rfMqm`&$P!HY#5iuV@vP*#`3BB{DH_FV_Ovt zs`21nt2c8#A+UWrsGx9GK-a_=BVI7EETQ5BSX_>nSIbIm$`p>oCg)y8bx4(G6!e{v z4a+eK;fX*v+ZUce3hDS~gs^wWInNz;%CH9?TluT+jwOT4sv@v(#r0Qgd4d-2AtXN> z{Q8C>yVnCDgpE1`nkUOb_A21lDFnmx)uWvLAjQ~Relm{NIh3u}ZT^d?or`D~Kb0p_CC^{?JtWEN?!={8)oDf*2?RTUnc2!_JE$XxOcVo;Qb+qtD@4UaEMBOX2hU3* z;N&hfFQqC7to*jBiWWuN3Ibj338u*Y-Op60UcHkBTPKK)^e z!5EP;$~@bNjfQ}y-(kq-$90>|2KoGm(sM=nWl1Xw{2gvY96d4`*$%Aa?4Q8lc9g73 zJ43aVHc_hV3(SPQz}`3om|nQ>GU0_z!z0kJ5bpsV-EX1961|WSJ4V(rsfWajMX?tp z@=zM<1@`Tat8XGulBnRk-CclzTm4rzh9_QLWs@uDRXOR;x~lrLGupE#m{GTT;I~d+ zJzkN)G5W{1W)82Ik8(1e+J@=OFIO$X>ZH&F(g#x3R-iH4tK z*TPHQ7$e^qN-pMSh@mItY4}m|2GkLo2<45u3W6~EF{=gdaFyNgBk+f5a5hgC&pAyN&wqS*lZN6tU(rK6O38>yfQGoPD3TZ;sRT*L9mxR?-5p%5M$983Cz-9s0x=>OfOrhzXdexP<`; zJVA)7jzuNmIY_zAl3O#N`YyqG@{iea7{FyvWUB-BaeH|3_;9x_c7_>f*Ddgw=zvmH zcY^qzU@Pp1BE2suW1iN78@0}iNydLEv|)@p9b2=j>Qqk@&)z>7X(>7HQt+sh^6hRx;b~5ci>K}WM+)$euvG96btVk1pc$kE4+6HDUdo*eJSE5faoWz z>MEjKbsj6vge2~@>A-DJB;(K{O!xh|o5F9+RAfvG=IhP5$42`do%-cpeU(lf+{WN9!v7grQSO>_Ap z0O3I}wk0gvdS?KlKIM?Imng^1@5Uf}iOJ~8fgiUDq;G#-FdZG{MVgl;vqVbaZb0n;BzT2aQrk;Ul zSVdqG)~mADXN_$k_<6sE0%nG#tX`U+-;9hQ`I_SxSgXR&LL4JhqrAghYKRY$A?QKk-9(|W3hsx;f z|A-P@_VZ9~XLrga@)V4?cE|~kWr)RcQKP%Nn}EO&Uof>EO2(y+Aw<$A0e_=u*-ymQ zbT;Y)nEgXEwl|2|2lM&Mb9diW7kC8d!f{!dDfeK_+K@T&1=1b7`lma9GMOpitHLfj zRS1wlyQTH|&F3DMBSQMqHQF#9UT^RhSNT+Cp1-htS2ros7eOL#{6?`OvUrK+`;)Tj ztDMw91VJ25PF^M~*iOcg6Rh1gumvd-tvqL+07WUm+9$nsM;d2&fz z0nAJ}3l3j&dv@sTR?8XR*pFL@yY7gj!Uh(+<&meO{lnH>N+g}p1?nrGhIfvoh6|}A z3+7iubeA=U2Vr+S;uL)2{aZL<{*70Xps}mHNe%0<*T%)ZXyhYhF(?9X_`Qp%J z6wb%5!6IENDTWlw^ha;dWofxF1K7~@`QD7sj09DRoUp{6HLZb7UkPw?7YK*3iBFEVvC(q`!C8Xxz)RqCHpwlN4PC=_{i$ad3Jj=2yAvkAvm=4+zbOT<-e;wQ$|2nu4dgA!*7o9nb_o=UH zX!^a|yzOrDDOvA|eM;`%u5~c!gri@Otv$XS6HP!HS4)Py^jh>4zB@J2$S&!m`!mBlH z{)*qqK#@%lRo1cgKipj{fN()qzXW6>_lHCg`jW`)#ldHghTS`D-Ty4Zn}AY{@z<;y z&CaoFpwz~fmjZn$eA|gcmB(ROXO?HPj4vZilMQ=^vnJUd0_4)x-ud)$>{7?}irV_-*!pCn9Po_pb{kVSuilZ~xcJ6l znvwOv=bGR?k%=>!TeS!Ybb9#=Ar1V4qTJB4=RG#(8*rG(dR}cLDUXOGO8}$JFG*vg9G0? zQ5?zY<6e?r3uHVhw86x~wk`uC9!J&<7+vZ9`q!DCPZ%Ns+(YA@mO~uiAec{#GVt=s zs_uT;{2_I`MRM3-%aAA2h>K3ltJf=exGKQfGE2Y(a7zvQ>VuyQS8moPbUTB6*tQvn zdmpq2sk2t==*;DGa{cO_DxmtIN)6nTscmx{G~2u7HJtql_%r$h2aQWIj1i3y)Ozn^ z#Z3h_dy*MCB z*N^9a)^9F~16!h-mDkw_e|~ybeWMeze_7(hI#T6h(d}EsW_9V_1xh+FGDb?-{+Ryk zu_~dEsgP!uh2q)@aaR-(6&YvaR;_(Z1QQpr$LyWdR`>=9+dpE2@QkrQ8MfZ$CVVQ* zgxazQo%K8;C9z@{Pg|=Z|935jkSEKY)qH&!Q^ZXE2;ck~YO<`4J@KMp!+T}T6c`SN*Ckon`9 zHL@htL$JwmAZNrm`2%Eu48t-ku{W1w4Vma=W1z2n;<+)^H^<8sr=G$eva+U89rD`v zLjt>TA}(4bplJbRyyZJn$rsZbfAM(z4I-d!zo*~OdSXhAm}C_`@zgj@=D?vc3{$;W zbN-)CkEc&vid;%ZWMyTGz{p+7O_Q>{I{uW08UhKn6K@xv6XpjKoyJyC-(Y^xUI@tRjizmgP;38~-VwxH7x@SNZkX+yupmsw|%EegM> zuvYTD2RGE|U~nfQRUlqfmvAKaV!;GP|2(EKm;FbC!S2C6ubqbUIgAwG(HF=Vhi%C&R8;|; z4WW|l?#A@xge*`;B;|v#Q!d}Au)0vTm zlxHb5t#f_zaNjjdhw?)xPZvfDZB1scN=nNgK_A9Jj+I~Z)A}Dm5OY;je#u|20y-0ZsboDl$eKhouU5-JN|uRNt!V z9VC&^Gf}hAVI0CmE*6NuRSEdW)8aY2j$q;1_o<}cVdJ$A8YJVkUo~aI@9@okps`ls z@d9S3zBu~NaDlGnqqfP;_^mA9rC!~ggf2(Xu&rr@8jMh<%|)TL=l=W4cxvP&26ePB zb7D|Nqv#E@+IShaw**j!0@xO8G4uBrwrKLV4bN!Z^$N>UYm2u`MWox;i#y9?2@0}gb}q2%`z^ymaCJAcH5GCQ?sQt zw3|Z2AB~u(cIe>_BcRuSEv$2b z(7juZnw2Kae*+q3Gm9SP=K%x0HJAB}>e7?MVU?*6=Qhqz^tdqsp=)fNWJ#Y6k*Oh7 z-B-*%IXb^)h1Ymvc?HYKWy~<*$49Vd;&g`}1G1~xo0w|QmtqjLXthi_w0v??{2fR7 z`3ssbKi`jW%n@!KC!EgmQ5ZUh$<~o{q{*7hRyAS{s+cIl!?c%I*0r&TSIJob?V9Ap|H}R5l}E zFCpI}-tT~CT-YLHyih2FT3cI1uZiMb;$u4u4GpcY_21uQ;XF1dF^Ja4yw;tB3ZV0> zTE5Q4T`BZ96{OR#T3zeP^5Gh>| zJzC4K=9YJN5}s+wwNEc^h!}jofKm?Oj-|s#kou|(J>$Ml5le0}#OP`ya_9p-nu)iJ zInQj__|R*RN1l&KT^as1f1)bXZ2W~N_vr_G2y+Qk#D0PQ`w}O@KUOFpoGVS7-05H< zs49~;qSAytQy0vzY=QAVz*O*fZ?^qDbQ*6UmJ;7>_^p@QhfIk>P8OpKBx}n3M?YN? zOP-Dt3F8dS(Va4$wZUvTy#`MQgG=vJ%MfnwJeDE+Jzoh9G#Sz62l@0~x_Ti8@wyGR zwwlQ8bd>ky`8dMAri%?q9H}%+O3e1AtvV33pYP~xU-XyIR5~%@i>`RjBz&Ho9>0|0 zO0DszxFrb=JJ73{-Spg-NE%+M9*sJ+Lp`9)9-R44WQ$!Fp;w{ZgYL?F&$^ zTdnbFgrmCeV#;9UUt!C5d9z{cXOIP{Z7$g$e!uilpz8#f5kIr&jSd%TYQk{fLfk4q z>3qNAgJ1UpZ0tUFHX?uW-Kq#NbR$62$)6LC!S_#i5ALbiA!J{vY(LTJU?~9Ll*$|GgQEiCP!t z#+`|~HU?669_}B8iQjp|loO7B5FOVJ;CkvXv&%Bo%BX;A2HR@gb91lG5Yt+gTDNmj zR|Q~or&jy12nlKq@ElH9a39nVXhN0_v4RP{&~# z9xk3$t|$#hdY8jRzT*i4kQ_RBn7bI^7X6`wiK3lGe(|bzVM`=_ZX?pbY!c0SFgH9ElGpB|H3sT699y_>m^}vAa z;z9fIw^g_!q_E|=DKAn&jS}C1TJVlE#O>e5O)>Y|50Rs>N9QNTWyH@&?9@XJP{@of zJnOyE`@mOqfse5LP5mQ=XMonk{ZEcKlNz>Td~-!tt_(;tdV-8k?>`9F?$u0PuucE7 zP4YgJ=m+LBp?*q#`SFk$0!Sj9_ucx(k2k@&FfRf!1Txq$F_FS%Q2vbqm#S4A{X@IP zDq|oQ`JL@en|moKolp(jB*33(Qx?u+j?i|02^}*Q|EZ8G@14M+kB6LpbEjr946SP! zgM(U%>;{E$<32m9+3-2BjSgHArFGmodUOpu1x|%B*fg&{F?^o6Kz5t+Z5}?gVEE_+ zL+s?=tZ8c>@7uYcys#v5!dH(ZgmI-w&nx2wzt+iDsmQAqpu&+CS1Q=}QWLxRV?eXv z{)UY#m18bc#Apz3`ewLfFiURKa34bw@D}h~n|+m!NiGjx&U)C&Ghyz#O_mfLjsCSL zs8pou*3a`tlXxVA!phPLhcJ(~XF!qH48g{8`V@;T<0Yu5Tu(tD(x%%A;zFkxEG*F} z31K4_Q}eC&UDG;$UIvyP_CCEI*O8vs5zJd4XnKtM zOf3Xlf`Ug*$%Gwx-VB?9pmyb^O1*g!-K*VkW6_@gs7u=?Qe)~?O>*awE`cA8Mf4Eg zD$Le&Rl4gE-ycLB@DAZ4R$~-j_L6Tj?bfkoyX@ZAF;qrj4xT#=25PCv(!|$!7>p^+qjjFwd*ST-UtAAPU_HqOc(37$%%3`*;rsz&i4xOMABmtTF8T9h+lU@+* zqoGO7DHdt1HsAGwKE?h`M8VvyNPmG~tJP&>&|0tO6@;-h^APSX2)?G=p_8#9Vde+K z5S2yPep~PY2}smFkZ)vj%g)^w>_Sx+`Ti$ZP~MYu!RG1EYDE2oIhLzPV36b5#Aodx z{x%O*10-V^a%>A=;fK*kNHJ(he5txi0e0yWERzR(*Z+3{bbp6rcxF0o$Z@ootPxDS zYGbXDeD@&RX_aDl7R=e%ked1HM-h!M>2uRi3j3lYkM*x9J)x_@vj0VUitfEv-3Gd! z&SSjE_v+0$*ANlaa^(R3HW9scX=*eoytZ(;OMiO-e+obv1L{aSUfU)l&usL-2)o1w>-LcH7z<2vdZ-+qIbsF}6NAhpMnp^iEOhWvEe7 zo@SQWm5RJgY7|o~qV&d4O)+MqndLWqqID)r^&kd>PJWAAI~k;D-^Hr>8AWjM+VkBh zpIF5U+`*(=MseI`Z@W%0P_W)JUFl_1Qh^ed--8M6jk-eJZ3=Di)x+7@D@qm{owgz_eSF6q2r_UEno{;kw=ZQ|{2Y;!h#=#I=W-r)@9uGh-gm34}^ zmY;Oo*WkA%GkZP??%XKO6|F0Otr|zc)W8W6>JfsB)|p*fyguK!BOh3`xYYB&=!{ zEalLvly6ljke)|T)wK<}EsAOWpyEDj#LTG{9g=6%{IdyQPJ)DwXA^rU#aGZpHWdPG z5Hs2S?;pQ$_>~?4xX6RV#!Vc-aI4Qs=f?5T^ZDPuyMFTE*X*xoPFp$u?;#VD?^Hrb zubOpP-XXZuMD#DFvp^U%`pioZ#8qvz?admAgc@j9IQpV%x77>LW?{i1NJ*%dDptHD z2~CiAWRUnm*yOxOB5a0Pq4|NwtS+M#>z!<`p58j0x<~XJ+`Oe$u)MBYo_xMqa z!|x0kGS^oifP>$}qkn@F9vbaLn1siwT_-|X;`LuD&x%%}O10#<=ml$uS{z#Dmk>)I z3w2nH2cLlX^O<`#UzmPdUn))FAvjo5BaOVnqk*INVmrS%+$4v?W(wfyWpLMQM_!&! z4tNyr>-6&B=zd!lt+m1p-#UDOTa!y)74-d!mu@#gLRw`e;~v;_mG4Lb^Xas5?`v&* zVzz~aSl}Ppll{NZo@XV5$2E>SES0iVo@w=a6qNwx>u*T&W?E@GU^AT=ZUylZ`H&O* zT9!gqW;UPF&m|I@=e-u1+@5A1AvNg-vj!#b0P-h;u!~ z_YjV2VyMJFTq?}^EFyWH5pUVL9M+puMvU8L`6`^A9P*q7v`0}rRkj4ieJ;(6p-sQc zEaMpZ83#1#GfFEqwf1SdH1Xlu=Eh(B4y~a_vdM%} z?r6^2bj7vElbYUK+DD=Kj|8YnSgCN=4Xw>qJic$S!Z|$Gs4(ppMT!Cuz4R$)PgYc2 z=5hQydPRhY!KY0R)*&SbJ5BXwv%ojD@0|APcK4GvKD&;rVKM>t;G{Ek+Pz__CESK5 zT=XQs!l2gKlJ$iQUEd%1Dp&U+%tXHID=D*r?5cm!D4TZy(!v~b z{9js_c#j;RTe^Q4SmYkSU*NRtB$AWtDI-|ilWHO!&9?Bc;v-GE{_oYvhS7LEz4jBi zJ}8g^J1{>+_xK4n;lEn+@+Th2#!3q7iq0`p2+jTeBt*~cPtfDfFBdUyU~VESORzrZ z{~swxc}z2x{2v(dCG@vnSvpL-nJLfEB0oR=0dOq;XCQu4Ohd{OQYSt`$USCW<>Br| z<-Ied+OjRQRCb{o>@qFIX7HbFUFmS;EACHug|$6yg;ecsOAgo{9OWIS!Z?SLFz;qhzk ziUN8xTNqG5%lnfIfl6}b_Nt@^EHr@g^R>nsn|s*(ft5T&ynmtXgD>!*ggkR+y?s46 z?W%U~eqf=9sAA$}rV9ddGQP)khE)2EFAt`W;|us|8~s>iF`IvVEpNyq|GW81pw3_a zRu{Urc%{YA;dxY3Zqr(6cAY#6vR8;sS4`RJZQ=l0%i9Yf{2y@`uVmm^a?4Ohl3b4I z`N2?+7!f2-_HMDpdc8(m)m{V+7c{!U2$O%l__Q@!`pN%!KGG#@$VjznIvzMS1YKH) zx}XQ$IeCmymZQ0Vac>4;0a1Hghsx{Z0xm?WYkzNcT{_%qDmFrz>5d(ZruO8$t){9W z&cZW;1(ap7n}gMmhGu@u`hWK+8#f#MM#F?#Cxp4ka~*`aW?QDK5VR3)2vxT~o2{|s zi|$2DOIDVn+oD!TH_QhlQcAAD!+mNLFr4#1fX5des4PU$pR*7%*58_L;68>tbA-$V z?!ha`E<5O7{(@Umw^`t`5yenOOfE#vNDkL(Ubw%Q)yunmvHtkem9k~xoBVcprQy)6 z%k#Z4?6L|NSplQoE1vZH@45Bri#M?j+^~$*=mZeT2yyADG_nL_3?q*0?ND%IZ^DKA zhv~O+KjkdRM%?rC%!K6RtfGgMw`wu^Yu{ffhig@fQp6OQGFj^?(t@&Y#{IBbG`ZCX+ReHZqLD0${eAxK5{-yWf zjZ#0Is7|nuG?MA+0qL)T-{|=b-tVh;)qnIc?J`|U+gfio3hwWNPZ=5M!ESP&PM`b8 zOS0IkuypOQ6*bkp<7Y3*+%Gqc=rszC@?9F1m8~b~W}+rgXjQbgrv5e4M~N4D4Y@Z- zg5CSl6V5}z0fRZx#Yy)u4~3KdehucojNSM#3Abk@&f|)*b2Ylxe;)HPYPK+UgKbCr zqz}X2f!cJzmVUORZp_(n>TTVH{0kaHe-a!41@a{}3d3!vE?n$mb1Re6ljkEN6LeZ8 z;_^Z?o*M0NtCr#KI-39bp0=OC9g^jDm4Z7&Zd>xH;cq)7Z7J9(J=?~WjCe+aPQtzM z-&eq$fwMZ6*9BMDqk|WAS~2O8oR@_6VeNG9hA#PUA#L0lUK#SIgb8IacR=?!^&NA2 zs?~h|mKX#LO^O8O$PNK}a5gX=QGC*}**H1IaO!STaXc^{M z-QRisrB^@v@1^9w`AEh4k%RrB3~j^doTP8uB3EmsU56Vlcnbq6mORK4-fX?Ma75%w7+uSP)XcC8 z|Bp4(6|)Qx?vB^;09;hAO*ROr6m7vZZDLeagme3Q*T112zxe#JxwbG9H49>w#SH>- zX6SjjjP5^f+0s{W|2ojVq_RB&x#F((r;hw|$XMxozj$1y+F7DByux|&N)=pr z^wTpVact3Dnz!t87lu=tNX{%%aepE>Fx8OYG^}1T%uJJU7`+xBO08grGrdXv-PtRK z9NCxQ)t#n@lIPocsnXa;b-pF;jDIG4Jp9?&f?3 zW5gYZQykkWS64L#H4m%5Y2zW@4@hJ?XevL))&jqz@(e$Zv?{!s<{$lDqXvk)^Ltc;8%k83;P`%orwYQrJkv_&OWJNY|UfR^VG;mfDn1>#%q)hl5jH79*18_ASuCS{47r072sd8Z7 zJa47`=?k9iU-`@um*p$?^-9dB^lRPoVJDc}gYI7A;RjnhGndUn6vC~R+q6A`+D8iF z8kUp1yWYY_CP+P-h8hpkqR$#I?ms^DQqLaoUam{byUfU=`|Ic- z-AAb?SXm3L{NuDPmasLLQ3}ktB~$|(%V53@}K$!%eS8@ou65xh6$&2 zdJsYcOju%@|MltpjQ>+lI=MxgAN&fmdLM3yS45OB%LA9VP{UoL*aBvs9qHX>18i`G zyz(9_TONW#g6%;-GFghEZUa3*jUz@sVhCg=Z=jY>14~pTN|J?zp+`BnhbOwT_>mjotikVy5R4xc;7hG6dZV z9kIZugBc=@04+^_`X4XEGP!wa+sVf1N$f&zid>qAQ$buDq5afbG3$ZM<3x@d|EP4- zk`HTjj(b}v{T>Fm-QI3^xX*tK9u2*hCPD&fElt>@Y|_9Z=7V5RzXwhDCF}n5l)*{)UpunnFXthX^X$o+HNk!gl$S`$h|vdts~QaOSbdH2_qogQ2LBqB0a zV~)69AYLH(Bv2|J%GSZ4Z|>#SrW|RqCX}jYx6euKdKEj`6Uxo-#wvBPy8iX1cn?vm zl!I4C8_8ez_AX#+@7FDAJ}WdjG1p+7LX9;Yhmw)`zO*ET7+PgmqN`h+WlY%?%i|nV z>IWM^o`anSlENr@@1Y;cnzJg!JtAgRTbzXhsA1_ezyDzWG()QXi~HdD(d^OlSG8Pz zdNiWhnlsa`PdkdzV69lgjU#OTATQpP@z_eKD2%Hhi48rZQq5R0n+2zNUc8Q^b!)G3 zp?lkj9X_(Q>3b<&(QKhWf??}Yzl!N~X)>0xxV_si~Ut&3iv2Hh*iu~Op~ z3enUb8}2(LE#FA_+kOD~{ufy^(v4pD*;uXNq)Uu$fRh`EI<9~4i%I{5PKd!PQ)MvD z768MmQ)*OE1Y1QAn=4{vOyn`ogjQ4r$uIy#ZV*b|9eD4zww81`Olx_sn7vKbDNtL| z(Sshi$bD&84{ZKBcp=rGmS;pjYMcBuZBnpArwqjGy=DyPHqUUJC#Y(kSTuF~W2eU| zE6zdc!dHN-(a!m@2dI#?qWyy18|6my`wQlot8>5H(6;##ND>2R3leij2kK~#;JLqI z!`9|1)+Re?RDiG8Lc30)hzP(?*anjh%5QfXUVh@V%jWhkW13$4P2d$R2EIKjjw1hG z9v9@l)I5d7n7%TaK7l41*Q?8lh=#xVOzgK7Elf*9H%xc!&~3-&ee1JCGWbSiNpYt5 zEIL!u{x~ZpFWZ#0!Img`NRAN>2?^1L(2?4DhR2P^J6|BkbGfK#bV+LmX29+Mo1(If z5iwVDej0?Z`ubUk3`Zz)3c+|=B#wk|+iLCSoGK3#x;6+?dmw9~{AJ|(XGamAH zI|=|a91^4au}(O_(bwRb$xh*%Ogr#T?*>7v8OB5tS>>4Y8(uwg(FvcYhd zGSpi&PQpSF9!(8SnjUkpUk<{ER`Q4LG-&8E3;WA+AO{mExiACoBIL8RD6>afY>;Z4 zf&al6rf&*>#N-$q;Cd(|W%Hd@|2SPv6W;Iy*yE!FF3`Dqw>o3y%bznuz<5B!)PwJM zVAG`wIEOAb1+xT1vdaA_0%4<_7I);D9`1`mm?&@D5a9iOg_4ou;IaE zu}u)(zkEGy?(YuDZ?wfy5plOl{~XR`GpIDPGpV;A;S3HK3^{GnxOnvkk-a*61MF1B zS&Q1Fx-V+%PchsO*tVT{(fXA%Q^bDQ`HDzOcBUD5v2 zuzaa`R5Zot*FT=zaWdOK`7-%5(f}AIW4g_7Tz zCOviD4%glSmN4djGV_UkZ9AX=yXvyRk)R7!o~0lHNoUgC&3V-W5HnNQkhJ&+lFRt; z;y-T2$mRkwmMkgB^@^gVTVp)+c@*iRJ~}|5jOM~2Ns6>IVkLgpLn<=0 z;!VPP;6z}I>##6+p!$+l;v}c|hWnlwK#@ka2rGNh3#uFdk8K9%TLM!=wi7%aixe#l zFpI8q_NLJWR*DoUUp&(zAI2icqe)TFJL&IfrpmwG^s}WSQy-@tBdp#jG5dJQ4R4b2 z^E0gqUEtHqW{dCCrIT0hCDTT5M<&ca_mu_D{i67<*P2`ss!8TGFIg}rTWxd1#aqBo zS1zo!(*i3X-DovGoh(Oxs!-%gp2;iYJ$t|24zc0`F_LzZ8b@_-8fU|dgIhIt30~Jz zpRx7U#K!Py7s1Ct%B+BEQfY%%5)sgw1nEh#Y^r^PvU<9t4X*Rp$PqpE>Sm8Q4+Ppk z8G#9B#)#`e*XDg1F^!JT=hXNOu{3@woqB3Rzx4(BBPihkssfEFX5Qg9`{$ouyZ?kz za^$%;c~3z!Zk-b_V$bthhN)&B1dUBboCJG@{d zlF*^@vK00?3BMSw@D;mM6P@Y{D0rCOQI((Kg==Efyztwcn@X zR|yu`BFmzsu$i!jNam-PfqmdpiMB8BLLiiTKwbgmdV1E3#{{Su<4L?nc$zeyS6x!#`xeJv8(d z7G3kH zcNhsUa3Llqu1rof(G@YV2RTuIB%(qdbv z&SLJN-WlttrgkoI5!Qk9)JQ*0?Dn}IJ&HG-4^l_(u)8!!!3_D`6k+O2xH&PSO$e$c+es6UmM)44cW{ZCzCJ z8xI{$E<<#Z*0b4>l?PA}4hVY}{7|bg2-mY73&fqnf}cg=^3F+vXCeO3@%Neews6}Ug|OSBR@Gv* z1yKZc8m|rzRC?8ZKW+hDT&q}*9g?<3HV2-vs>{NxrOU>Ygpxurm{D>8|NNAfV)tRs zC(%q74StNKEX80e2cT4_X2JII2)@S>ZlSwldO3VfCpqPXVqZSP zz3Io&%5Q8^&y`QD52XT@~00n3XCYcYj@~ zTyOdn;s>|}l6~Fg($v{HrkMnHW#ZTOE$5B_6 z-Pu3)*C^r9Xs{GHUZq`6^@Lx=gq!S$aKzKlu5o1xZqLiMKU(U3=S)XXwO>NrnP+HB z6yhq#u-Jg>P0N_(LJ@DpZHaoGt;HoRqV$NnC(3uD|99KH)pc)whSX57*mAlU!0;Vy z{o2ZL@9Y|Pf5rap;t(@SKgrN%xcaZEvbleI0rb0-e%%6Xr5HCKbEo$X5fM>1y;Ku% zvR8vwP-SU~ET5C2ZG<}kgib~hEae2Lbc)oIPY2p{_4*}c$q9;#- z(D7senCW>|fI%=9%uV&{hw4&JE(Kb>PSxT5;>$0i*RtN2?oPlpCTtHkE`Ev*)eo`K z(a?EswU0BWWz9k-bc3A75X}Uh`MT_<18uf~&b}1>n3!Oz&2uj-T2SvyitmV&GabEl z;%D_{~ge_8_R?mDg(ytJ&tv>&rEpXV}{B&HL zRU@j)wqW?>{b~C4SjUc(PRBJ?TVFr;y6eeba*&cpGNF!;MJW5*wp-a?w;OqGj?<}U zC$DRYJ?DJudWp2Mk2KA>M^gW7qwNlX&%K>opNo^P)@rcWNd@4gAO8^s!IYZB3@Cs9 z1T3P#rOx2kqBvs-n?21+9Gqj;{yEI+9x1)yw&7|pD5a(g1 z?yX+J-taQg2rmw+^iEtReH&Ez66e)uR( z4);nfgr;vZJMLU>P7JXUpTF#YdgVrNI#FZ_3+b)#?qq*SHqwz(|2FN%or~l-IPG(X zOy|Wt?*!vs5hhG93NKuoKUwQtNOtgk!KlE60Z~_dDDFT7B{4K zF8yboOfwd2Qzs4w;2ToUg;YGYbH23xuwC~d^ne+<%Y=&dxBa|ggQoNB_ujpLI zSgcFWo|p}nBkeIQ>eR$%1DvQ)Q5tsWwEwwE<4$U!lE8MHF}V&Ua{m3ABf1*`s%$nS zZ^W2&DqBw(;pJ5{t~t7`CR?#c5NnmDzfd7DS24hA!-M|xQ*Kh~o0_FAjN?&sPfcd} zWRC#?W?Marksr(dysS#^uA#!mnylEL`mFSgIUx67Xg?W6ML(yu>jfPv{wB;t_qxX?v(bE_47qn_#6B$*4_fB$~I~n6$A;D z+>+AWl7ggkm!$Nj1w^_-1WD;mDM31xN_WG#HyH2tpa0DH=ggU5UU^4w zKllBtyw+OR>ave1f5HvdVvYc)*}DGtHqvj8!AZDvUXsK`@?-W?Z{S6|cH_Qxf++g{ zY&4ovTcLHnm|W+LLr)}2E$P!qnhChbb`a3u$N8S$C~OdSU`_QH(W?J$W)&g-%` zq_&|v?4A6;p8d?&$vbzf2;sxAE9%t%yNJ7(wJd3mitfmBAg>5t}Z8onyqC zE)0-ec~2Jp&|xI`N%Rlk1=snHz|B$aZ~zn>@1QXZI)ED@^PL?#q;A~$K-2c5T^$#J zKU|c3Nt9Seqg*u?WN?eYQ6A+Q z!gc&YK{_t;1YX;K)HL(O9#o)|AO^WQk6=S+_N8Bb(!j!+jU4QyMzNndH)8cD8iCt| zxh=TW4K5$h;q=G&vRC~!z7*zqsvRe8p_~EGj@gdE0gCD3H#SwRT5l?&D)iy+%%kg< zDEkaV$5JXqSsuxBC=9*DagOplQGwYRm|4Ecl=K6qSoJ=AdNU@Hl}0CHsze3(h>R%A zzN=79*7n|-#DCt9A@>X>)$=gl;e*iObX=kzBqb5F<|AW#oI?ev3uAsCnjdSYe#|}j zrN75-LvSMi8vY(eO%O8)<_*3|vb5wg+H6b&lzFjS@;vc;w;o33!Ug2gdkhkCyiHs7 zcJ;62(Xc2wxoBnfu6ORzPKYh{o}9I|%!F^3dn55CJ44P8kOY!1EkQXZ9`kxo(PSER zHW`W>W*Lltm?%Bko*J)mfZ#iCEhEvx!h%r1nVuru7t_WihT&oK05~$S8a9-b;vyq< zH=ApBZ;%)uNpRe1eQ<0!LBe9W|^+V-l#@xKkLM}PX?xBz@J2$#`MB!*Y-6&y26qWe-ZrqUd0rv6#7$9+YA zuL0*V2sz#dFZa?SRpR@?%s$d$;9?WX$^WVhpZwajE9{{I38^E!8NMd~H!fG<7)NN> z|LQ$hz&18K)^J(`oGn@eV6pp}m-B$d;{T}6yx6^Fbl@?F@42-&@S4*Bc+ES-`CeYY z4KoFHL-yRax&ByCk&C08nwZlsCS@EBu zgBk>IwU@zRw(ifW&f7vc2iC;wY0X`59$ee2m&_24Ai<^FB~+CC;jQ3z3UvRB7E9{pr#?!;29p2vZm(r*wO%e$t1l-9X?_-YmBL5P zLDQN2)NKd9>!oCWfyjlKhY$i#?*Zw&z~Vc(dw};d73XBBfWr;T*1wi#NsPkMxfN#;i3IqA7^0glAdL?R88`p?^e%TM+ z$v}HTC9ohzs0bjU)C1loG%eQa9UJzG1*S-nH!14Fu+=gHM~dW84C0ZWWqM46Z~oo^N-%qfB>OiNZ2z5tBLYo9CS$ztZ;;nILxK#!?^cMK)Cq< zC0|S>KN`W67juDKiuBRPTj*d?qh=s965t1#G148p&wGL*e`5TrA0`l8 zkm5gwp@JXgQlhIy03>NwhatN@48v+Jh*m=`U(!N6W(Eaw(v!bYgaoe0%}KuMFL^sl zz=rbx9@N;N!x{<171yuQ;NNMW5yq{p|J@5olx_u?P9@t31LP}aQ4lQ0t7>ays@klV z3oj-&TT1bLf=%0lQ+8&~_X9%j(jg&)b4wPE@_y_qKkA)0*U@M^5A;I|!Ao&AvC zPO9ca_&)e21YYKw9SF= zGdg5^Aj*Q7$EuHXk;qCa8TpY3uDhiBC3RK$s#+BPj)qMe+liKzgwKuXPkfgmQn zmp;CP`Z?Fx=VS60{+ibc!wVGVE)|_i^zUFSrq}JcOEabiE9H|%k(CSt8CLD^tit2D z#pp|5Xh%@5rFcXSL^Sm|&`tAR0_VNFJi_*!rX)doU)1)|K01-i#AA8m3i>Rz6s$h| z8+bp~c2Lpx^Di{ z9A=1ymyaQyupa+)wsn>CYEAGagCpHW3cJpB_3+?F8XXux1!zcG(0?6Lo_6Qx1gQJ| z!H?3_zy2o22T;TKX0~wx|3_SwBG!`Ium5<&Z8R$@WkzBo^l_GpA7y7G~lJL zGN#xAzx1zzztiu)R3{n2)g>0-uL(nmrGPg)B1E{-^4-e2A}&;D;C;R6$2#n=YxF$O zY24bvAZ^mWaagHV)zL;$G<1CmITXK8x_@@*6+Dq<59;0d1oXGjY0$9h4g9*{k>9mp zl0~{HLvO~0)1cy0$;8XsA$2A0E!5~RPp(!+Cdq$kw8qi@8qCUPCYIOQaiK-};-eB= z<(9%gOGa;`4nYthAxVCNd2gtX>~L__*T`Ucj?Z8 zM;dnjLn5Fh!fY91K_*PkU>1A&pKsM_@85Qy+rzc`>z0K-NlH0p&g~GtpCwDD{S_WjGR?J?92HI^Eb^~ zlw0${x>hbk5e9O$8#&&F{eSfj1$Bk;pW!${g6p3rVm%qZ5r~FvlMGmE{Euje8scDn zb~b$Q2?cZOsHM|&Ax`t$39ZK#F1+}PnTZCrH+%VP|7LHo5=2S}{b1B^$yJYQMd;n(HLs^2`y_bheoa z`E2u4AR$2zKh0_E#B(1e;J>%}ioz%xd!pbCr657Dx!&LiDBU zqhCaP4h$2%?ljiFTOogWNe53~Z`#7s91wVi{cpjs)NXpfgQ91m5O>F+S=UeC__lZw zt3*%i(eaiMSE1(Z7XA8-5GH!fEc*JxmB3$BtFsehs6mlTI^n6TV?%_FyQRWWbjDBp zGX!3#Sq?4V8Qj3d<3Nod6I7*Sky`)WpTg_)iy9sO%{jjCdYuLTjko&4==7)F$c`I> zKaz}C57)nBF;v@(h22Twm}wA1(XGb)D#D4x2?fZ^lTV_?&;NtK8kPO;K>#5C-jc1L zvVjgev%dM|r!){F*Rbpub1%UZAgweKR&^AGu*j@7uBm{E6@5J3}poHkOcs)dK zR-aE=Iaogy3Q@Km_b=DF*w=L3TgIL&)gpW)6($NAGSwE#2Fw*ZqqbhC6=;wc;nEs+ zfUPP5&XA!Txxj8tv$?i9hg#=-^!ojkz`4%wEQ1JEUhh5n9`%B-vT*gt?QP35Jx}Ev z%-f5eubC3qW^~1oz=je(MC7TqSnUidw-5;UZN}H0-)?(2uZyI6B0sRuBG_rtRz`b& zur~g_A~CL14H@tlNo*p#vimiU=D7L=4?IPt@=TT%mn{aXK5sP<|kaNRn+v1s=2E9{;;0QxS#|FwWf>tDP<^y6W} zRggcm=^kGBaB>qM1;14b7wxY@qP-RWPC(XIls#hhpvMHwJJnyD|Fo0$@Xf81?FU|# zrAG_>GJg^GNs~`?|FP{Er~pxzQ!3wYj%uzFq&XHkvY?ZS3Lal|??YH_}{G9fq#aPMB#lGa14%p`3jg>`D(ew!K_MdF0#i8(mD%>?iEKa@ zKlM6-F8ovQt|qqk)(xVOf_MH;Yixh2`V}=vg)OSfo69 zlX7`o)MDg)D5EByTOa*Iy%R3eLmSw)ZQ}=KN6JWXOr;&xm(N3SLbz?lh~j!<+fjbH z(;KX%c&*4tU5pgz2%{%_3dAVzi6G#|9&=a_J;WT(v<&^e4Au%0sg?Tw4Iz!J;OUu4V*jz@>mF*2az{J0eAT;6CwCo9-Fjpn3e)cD#*)ei-eDg1&@eF}SG(yNz zm}xWK+n?SJngVN{fL062cK0$9P2NY}c~XtH1mlflHa-w#@9v>VDnP%-P*7oAW&2&+ z^rjVL;=aVLNoSB<%$EnXgwHSb@i(R#0*l5R#2Ew`+8D4yh6|>Y(Xzc8PU!UA7SyS= zl!<~%Hap?rX!gaTG+EB&A%BfAtYh<=Rkmngevt`Dy`x0#5KYZqNj;OZ$h+IyIoqRN%jPr?xdfq`wEc<2&6*9Wd|WnTxAuv+ zRkVkoAyUA}MsogK)v9-nnWY$;65pbot)O(PFdAR0%l}bUUNKVTWkoHUO5IVV#-@2| zc=a3HEypD)ffl@8%b=hyVP>G-e0~Om0{iax7UZ{3DA`R508ev*1gbpdFgTtlB>EeJ z+ED$UGO%<8pjK2GywHotJ_k})TiQQ`kJ!Vm7iRxl^cEW;>^{hS_nC z*W`0Di)5zOAe838>KubCs;S2X&0N-HMGh*AHC-5=Av#&eQUK%J^#<5&ClZLKkbyR` zEb~=4Tk)rKrd9!$<<^GIF}q1S{odyA-E2C=fS?Ku+p%yAmXz7)?J2dz;%}Bi(B=H{ zo=HFw9J#QcGg|9_33u8CN~yl64LQNe@}7|z&Gzbjm+r@$LgM%#u=y6$;hs49kPWYs zO!xKrp3W(yj1Z{Y(S`d;Jx+*>*~4xqZNEPKB0cHwv+t5)E|a21-JDprs-5?DOd&l< z$AlSH-`)o%?czzgo%h-1sZ=Nn(HP1_l2bfC+eX=#I3~8L*=X01m~=jnqUpGR1DnJ- z%6+jfz61rrq0Q^0De%P$B5>PxK|Ek70>ua4PEo>d7XH8Yp(m4wlP3G*OB{OmW_jxbO=gPa0nAA3o?p`qK-L9$#KYo&uXcqE(;R9jQW>i&M>Y4mNW(;rm zAOQ^@l74f7GJhSpK)1gGcpdGEQr{itD6glba*T<$qW;7?| zS0233B6u|47?iXcKC{(NohYzdNx#dF69U#qlN@v8bxvbvf2lS$UmHD0+p+o4EG8Yp zLt<|#o!k65i667ttAS8q^_wq?RebEz*f0yq?*+O|X7i8mSPcSdLK9j+nqs$e#3n1x zZ91`R?hgead0S-JAi;PG+xH^fl8P*uV|g7=j309#qsNAO_aB}B<7Dpb9QWRolb_eu4d6KL8LJX)AAZGS^N5?4%U8U{~+UIV1= zK047B`QIYS4mpk@D8OyqM?_$N{Ah4tgVXI!gGhZ8n;wiabC~vsXO1Su^6n2heLZ-M zojH0Ow41M1#PN(LKRD{iCrYoU)fRm5tXd(Z4_U*92W&@t0#n`W$;IB?8Q9Zla=d%u zvJz3oK&4!0RNS?BxW0!y^iC;s<7PeIhx4r&u_C=@4E1N~l4qK;DIKzR6wp(`E=h$Y zODUS5RURvCG_h=|2&>Me67Q^&n8y^UmSkLrHlo@ThH%1-bHCKR*`r3m9<>Q{i4M+ock)^0A2o9wapUOUt!998qe6@xwUmDNlu<2W9ins!wFd*Jz*I~ zeyE`*;lHOJAe(r)a>w)Z+*_Vd_?DFhvt}+jE{&oe$TPCL_+bwi9Z}yY5QKv4mjU5> zfP;A)x%FTppDY$l@wAS_1Gh!%`K~zr5Mt1x02aLDSNIg`H$zBtxI@>bgiQ#*o}9u9 zy-HE-`$tBlj7^CT2CWzyzIUNL`BI^W!mR&*OpxEDmCc-uLT1S17wNkvDR^2{*1{>n>R^Skjeck-oP4C70NJpA53nxeL0@ zF4yZ~-8Q{G+Rw)XkcTF>pQ3OXns1OM@m9GgVROsALdTgl_M=CYdU zZ+NpgqFJpUN_MpTVr=LVC`?uJd{Q)6m!^DuOQSLU#OaAoJZ*TQKj81k zR+8S-TGtow!M+!wv4@tKC>Q}L&1=m(4~*RG_k0(+>|VVZ@biZ=)lcG;GU)2VclHt8 ziFy9eazRKm3g3IxJQYml@H6MhTy9&fO7yzj1*vy^+7FDrp{5EGZ}-`v)tiidB*#|q zsx)DL_Px-yK2p}ZQrSOtN4ZO-(uW@MqkgT9xALO2IT1yMjjiRQ#U-)N=g}AMHskNE zezT>-AmPSa=XY^3%a`-|ne&@xzOwn=;X~5+H$oF>}UucDhBuVcw4iTkCJRXtxT+DHJ*Y zB=QZlUaLH+xr*yW#08f>8!o4H#r=CI<9WM5heQ z9Vv*9U4$k)lR~584AaPi;YEOzTr*&`B5=oE5RV=A_RSWG$A12@;)})bNU&)D)J2*6 zQb+E=ff|=|vXY`;V{oboIS}z5?&Lb*Y%w6dq=d*3Rv?dv^)|F?z)SPBgrep33cseO zo5i&1Y>@Rs4uQKZAA4eM++R^8Yp8y5KD5l4^YC3nMtD+;8HIYX8fps8>)G5;2|I0n zXoli3L?(t#B799)GiTIb_}iWqvkEa}YA(w4K3OfS*6b)3gESx2^P5I74uyKRT3 zNpF%m+H!QBe2J%P6cCTbUL=gu9FG0fbHwXPeK%~KbGJ9u58QD564ncXS~YQFckJkK)OaA9}aP;;~yzE|i{BKrNl zwo^@}+_?Y^BnE|3`w7jbRJH_ir3*wA&$ub1v*WFqF0S1Y<{aJNtmVl(vVxJ4PzQ6W zZ(kgbz$t-qJ0jZu*ET@Xw)`=Bd;g3^Rym%*%IwZMC*-5eE9{Bt_n(|XyNWfG zZD{Cw6Zxnl?~)dCRD)tt&_;H>JT@3 zF+N;Ku%p7ZHdb}hZRcL=TO*^cL$k+0k*zeC`&6Syv0K zw_WLY{d2j|r!r@lJ+*8!Qd<4K2w1@co#^m6cf0AvnfrvY?Yjbz;pxSexhz6r)8)03 z0Rbfcvk$;8|B2=YK#msJ&_;+j!S?wJ)Bxms2IRjpYA<#AgYil1qlTl)9H<#vvpp*NhPNwi&y&Np>1}n4)TkIoqIG8rd@AK8p;YbLARYznThkI5_^7+ zjkTlapEQ7#>Qgfo?O>!Bt>~825CUp${aO+P08Qd2q^S08*?&vg>Oh=Lw>O}Sn(dvMK+?z%+#(8lMP~q0p zKkfaMYgM*Y8Stzzs3B)>-nQl9HC14EewZF*Pc+_7ABIB#5tPN}Hd8ys5UnW>OLGao-uUHxoO$crniqiDDcWuAbBqwJ)9Zf8 z>GF&*sxVdwlEu)$#@S<2c_z){CTY@W?BW-Tl(O{I&~4ekRsD;Fhrc{{bYi3wKjz*} zc<}6(bd|GZ^kReIpx>`mTJ@AH)i)s)lbL*ro*ekM&Dq*r0-J{wkxLp3)=%A@($hi< zn8z*>NcF$}A7G`qIjLlirm46raEkm*J~~-tdB+%jupU8hFzauv3*vm{IoLS1?VZbL zG5Uc|D2W3*y2-k&lQa7*k+3~4Z8*jg(bD^M_Dq9nz0M?qbJ+;_Ppcs~G>T!L1>KEC z4s4EGXL(b3eeTVmV7|30ekPmmbo!lzrfb||z82>-Jb}6?CPhD}7BfDq^8V>v2-7jE z-z2NQb6S1AeOyduSR3dLEVuuv0W|5B@oP*8<+`0`eFvM>^qE>;-V!?@Odu3Jvj_2$Bh!yWOclJOOjk|@h1KJR@UQv+0iEB21d@Ylk+ut zkAjTkjTdvAN4o>W>>b(m`^n3r99n2=qLSeszq6#2xgV>pVF-r|eiDt9ww^9~Fwx+G z`Sa0-j}=ZSDp_hP=GE^Xr0lNTiIXXUuFU2u=MuT^6EF8Teuk*zbdi zj&8p^2H02a0OKr}!X9?q1pMs5C3+26fX}^rT))nq<~1f+_$w65fiIrlSLXB~Z2GZ?fq&-B_ahEQJo+m}q|0LsRhfNhv0l_jDOf z$O1zovEYw=ce5nbtP0iskf^r&ktZ$T1vG`3hmrb>O!27Qj~-rPg??vNgBBOhOl4}G zPPz*AuO88VD6Kf0zPQ;K_x?e=wSJbk9_1b4({6%&LF|xVUxnn@pwtUk=dab}i%>z2 zOsy$JukzJp#bHfxCxt?kxzV`nTK5%3cc-;tB5N{=pn4+&iZh1hXwW=qDKK z*&Sz&TXZxX$q%dq+?`yjbJB}+{Pnt3sXw(wsRZy*K4pq3q^>5JERZ!H_lE*s?NIxi zLB{R4&D39kFmo}1J5ON@qgj-^XT^F#4(CN@ueyn z($8T57D#U*LuJncQ!6|Du$$c+W^9m{BHifs16N9^R#Xu>`G3ue+bixy@ zgJH@z8ikI~NL5*-3-?*xHlK+IBBx)*?X|%J{=)Uj%(RzzK0<8h33zu`C9!*0Kt4>S zL=^Ee&|5>a+;N4|a42l-S&36xWwy=tUDw1+E^qn=+#RkTHYbqzzBYyDT0LYSjXqaE zvMnBW5|0uXdth^*`^{!lYixDAnH$(2oVll9^Tn%QPQS9emP%y%=$O6F7Cz`8M_+fj z)~=eTIRB?6g$~cQL}UAPs18Hjm@R`rYYrz;*wwI2Lj{fj==Y{A_xH#U@@X10?ds)T*{71T$YRB|`Be z^h183!o+oXtVbWLO&qIn8JQJo6cd4Y_3*=~OM|4s#I;hbazrpO(*`Ev`cfxpbPfc- z_M8B)x`za8>9FsP&wfm&2+(BEqz2}!2ks3eJ*o=^$jNU1P)xLnygzj5bE??Fth!oK zhmOzBwh>}{eGY|u56v3l1;5Z6tgpa za&MLWgA%>{eD}+?O~~t5<=CBQexFHf1`*uHn#p^XYDvN=4}h4>iFVHQz?ep>oDvun zwBLzfO|owDL{rO!eauEF9Ezn2PivhYpnZRwt82DiQKgP1&2X1c4-DgV)_Z=F0C{{O zYDd;7TT&xHM{hRVRm6-#J6VKt?MCU6*Bb+Gp>WIHcLnTHm}IaxIZc%h;!y+vP+|=mN1%#zZtFY4 zUB@;6y=`~CoILN!_;pm;u1UjK^L+eWyK6O%_b3vCL7!UH(|p$FX4E^NgI^g0-BxCi z!!StvOha45!J=^n1AfQ#k_!3$R35@lw}kqgX_DGSr6R-@i^iakkb5i|h?PR=S^mo9 z+}I&{0zRy&)ZAV}uJ=#2Z#)~e^2K0D7UFc=%dyN?$$Oro%&eZw<$73rM!*4uoE-jo zJ&2npDz05vV=WaJuVA-zWXrg#@r%on-SL~2>E^|`N7s(Ks`W%2%8zR6j#4^B5r4fQ zae7)IX(od@#zmi|GeR;jKLgz{@PBrI_-K3k@M~VyC{8nIMJNHLcTAxoQKnGu{rz~& zhL?_*O4FMoQ;%2Ev@?RGs*=ju{bcP>|Lf+~(HoBDS)%hT1Vpqs82dbI zj!x+X*eQ2sY}ycKiwhz9cO$J^GTIUqh%v7eY8?Zh%rfu5B0S^0<-vUYruvs!YVLCyzs2|m2JlB9;2tbwCcR6VQwGXyoQGyNh zjH{Yphpf6}6&Q5m1Gw(Xt!SdmkC!23{KA zpk@YpDM>2w2eCn~g-9Qg9HTEj0eZj@zwu^|u{X}-j+J)az2qv}*Q{Q`YTC8GP)5XF zE#6HZ3onc>3a5m`gh{hJt7-p8hLL5HP;GJi_;zDT9>g7U<;`@pLb>^pKKDog2~!lK z0%P31Y;(MQc=Wv@v6%3DwQL+(&qt)^4SSk|pT+m~yyNVny!&;zZrV2O;?V{ubgabW z#i3e%pI6Gj4(T2+%ZQvTpk918oT5f3g)Ki;6S~Cq3=Ni}n-?@Cv^+oYLh{&z_^o5Xh(6nnPGIb;en_;G3Q+mrM1VY zwH-p=9-XxG;0Hh{&QGSlygW~2w?!B-cNGh+vY-F_>iatXED#3i1Xe)|u+kMY3y6(7 z62`VNz6*oieWml(yDEhDaEaue5B7s%ohrm)e&-NBF`$ceeHzh2v=FPwcWI1l$6r?* zq>rA8a!Yb}wmB6w)$1{^p+Y3{c=Lp{?fn=Qofgv8_1C}f2ntdBa?VwriBA032k~g;V z{M-o_jITa^>1WH3sa5QLcQ8kS2_7VYW`kRLtb+H#z1$fE(eI8D(U6ifIEo!`F_c*@ z>Cdxip;kmJ_5aV>RKfQDNo^|J;XAGsB491RCa%J!JrSd%qZeJY8C~en`9+qvs0EoyI z0JIq%Ck2%PQuP4v^GqrlV3BZ{tcFS&B~G!R^br+})b{KbrDBcOB`P_Doa0qhHCPu|Ht2FsHp0mn0qkQE>%OwI%J z*7O=h1dJL*`JQ+bonWi9a=jy*wnhxC1n|-qFM>)T3!nianXAv0&qEzDN#l^@2<-L* zu@pKl1V1#&EqeifJN7Q;j!};qL!7epz=7cMWOd+EtD$)rm3m!E zPrfBN&)uK)(vpR6uyu9~Enlk%3LzZ?VSqw+vpQ3LHBQJr!KmJwO&iVE<)^;SEk>%+ zDsXkPlNgui7w{zLpKz`IAmDn2MO5?5-?sVb%a4M$j0b`@qj+VY>hYzUC|+=|GcIjy z;fQT62Kg%-UX?a0o>DpwA0!jL_ey%Dn-26v09bS8p!kO@^>CK)ZO07~^;U;}1ti#l zoaQurc4BNO#4e$j**ur%o;n^IgJ7NQp||)MUgl!|6Y>b05}DUrQmc67z0;&(Wjm{J z&%#U|;Z{>NR&9?SeY&N_s6j}4P$KnOp74GNKW*mdKwyNVnO-GwZ#;Vjx^UNBu znr9x4kiiLINSyn@B~1d4&xgkVv-(}m+@}2hznn?LbysrPH$v@wPdBG`@dnf3M}t&%(1_n;o}WVddlnd(l4A!x zaO;7ZNJ2T4X!%KsDHa)Z47_DY&GI{;L-PxIh}F(>cip2R>~|BENAmyQ%9``$#TJum zM`coEAD&II@9Ynld4`pL65H;r>y@2e3SR^@8NdcXV)ae|uHe^SsN$f<0|~13vxas9 z|FAq4-qSNhWtbFtv7V_k^MrcQBPEg)6t$0N5PDO|ya`DjDIhN5f}V5FcTe%)A>N64 zrI`YacyyRXbr@7t$PO$7lWnKbuAZNY7F-3}X&C3?i}&V}xveMz%z(R;Oy;tT6~s=e zviYGAoJsvE5w%jBfK~xXg>r4Y!KD-@NT#}MdoClsKDzJe>?c6(RNQW<`dr0db!KMf z*Xe9%A1=_ZYVkfA8?Y<5)^$58S!^+`3b4iFmp1N%Jnc-eZGNwxP$v;aYXjK7WU1jB zJOY6_qu#{!lZlB=qm;+ZOnTD^=E6bX&8?=-@uwG{?c-p*1ic2Jyq4A(ssZhY^x75F zCHhSTYq#-_sd)Gum){a}EmQ}sQ}-sn*oTjx+s37T`zag^+c=3^l}@23J|R5Y?Z}Xh zrbg2b+ogQ|voQeB$tZZO%Qm25puwE2&+*p%GR!n^v4=eFF&b^;ac-?2 zp0b;s5fo{c>&7w4$MT}l4kW&z+=P4rxoIMg5wlspK@O4@{^vAxzcPv4Mf%6FAN*|Z zeVk9KjH1%qpnu*@4dDk^PtQdaz-k<=hnXvUE+dVWhYCwiT>8;X?WO~p3ihCF;-&A` zo>I}tKL!X@nd?W-y|oF1meeMH&{)YpVVcLoQub^Nhh`{x&22Zq^Oa~oDD>A6yDa@f zn}hqNnzBkEk@jD+K^-x6 zyG68qk1$rb$oX8-VxlqkB;5pZ`}r`Q6EROXH|ZhSgj6<>x_Vs~*UFI5=<;^{(@GwA zn`rWIX+SSN8>0thd`v|Q<+GY9vcq#xK40>)nvtd2b)h7F|yTVK)58d{*L9>-Y z?7@F(h5W#!ZezIkq{W@W45*}9WLoRtb0xD9El!7bHLphZBvF^IhT5qv?3lr9K^?%1 zI1}=!xxwIXg)LZ|t`!{q9k8qPNRP{M%XXA4D{%PB1WTnn_ysng(?9)?-$PBCR06&S zKha~QTA^BD7`!Ftub`T)Iu0;`(jIO}adG>waNTWy+CGK0(Q zYd0S+5;uZP9>1IzP6;6pf3RB8vh#=Lqg&V2XM-O4MW$0qtn)H75F!!j^dW&Fm_z&} z=+A$#&|?a3-oufcJetD<OL%Hg; zkYdG97Sd?2k>`I`N|o!sj-1%av)KMcPSd^c*SxTNlx{o0){KwqE{;fz#gE`tOw2EG;5Ow=SHTH*gitE~QAl-D-&E`9wa4H{czE z_v=}&;_*NScv=9RkHE$Gp@FHeexqxx&t)^tc);t|1#6~5CJGc36pT7LfM%re!zK~zfmC%7ci!OmGy$Lv}@He(BV9%KwyIVBe_A) z7 zv0snL@`f1PW71GV-N`01#_knqmiK+h2HG071mej*1 z6|^#tWlLtrw2uECdpl%m`yux7Emn(t(q}kQ?%RU><%*=`qQn0pT7>-iF5j?1a340G z{9>XJ!g(AU%X1%4Xu_8QFBt92cgIZ^04}du_x*w%tay4AN}AY;xainBI(7C7s<}8c zuSNiM+OQUb(pZr;CuollJ=dv-4i1vF4gtxM5nyFen~Dj8Mz0hCFkIGx)~^5nbeUk| zgNk&qZk;MxgLN)lwmF2~emMojYa#rx5?fV>3HuwMGoVZB(AYLyDRO^sj-_YEBb>)AA46H(K}r>RW8 zSu6B#g>1n5yq==O*a8vtrto{D8R95=VJaH%5>MuZ%$dW-E@M^$U#{1(_V! zD#Qo*q*|&wx%aR(&lFqkg}RB-o6U#qh@kQz|EZ_kKm#0$=st*)VWc`phoPOy4wT^h zWaL=N_~$%Pi7)O&B?iGpuf=del>F^y%o?mL6&zG%_{58bz_}SWDBhbW8t>jm08Y?M zcc-6-`P{6^zzmPm9!)7Tap{2Ae8&d-@UgR}=U4+5?i|zM283+h#graQM=zvFO#Ksa zk&GJw!Y4(exYOPv`pbngnSzhF7lwF&EEkO}c zND3g@8Mo0$C=m{^gFy3Zk~OqE_qKV1EtISRK1L|U`t=_CL$?GGcXZN*{6swm9zJwy z-~FUuXc_+xCDbJcDBBw9n ztl>y!Vep;~ya)Aih?1xv`os00#5-L$l2L!n@r9V}?56RsVPV z7Xjc6l_*zk5NzPDcBC`WA^2Q1j{}`(smPqV_Ocsbb_`CBH8ICm49rojC^)QUzTF+P zBL)O1QFiTc;b8bn0dZBvbNzY+cp+V%_WD;E8^sls{#Dk!#eCbyTF~N@cze!gagE;M zf*&QN1e0l-6fV4XuSD2418e$thVErn3bi2)5P5k-8BXeHOe837NbuzaCElMFDSusn zjnK^92i_jk?Xk*`9It4BTHEH@{s~~K3CNrh?{KyD+qEFM`ql{mou)@}9UQoO&7m`m zjsSYGl;;R=whZ7RZM(M2hsLWg?Iz03zKjWFzaG6+{Iqc6`pOJF0RDm#NqzlUB7SpO z|M{%Dz@FRP{C0z<`y8kBrE;5UHV00BxyQzQ3RJ26Qf3y44m9 zDOI`1@S|T>0$aw^E&uQsUw*Xvg6Br#ou2{}Dwxxk1>qgvm^{p z57xs{@EDBK{C5E*-quVYnb1gNX(%kc;RXr`oK~u8zz+T#hL(t#Yyx`TH$q^p-dgVF z4%>epP)HI;1KI7Mh=xz+vvUYp9^h{Vu&$?2H*rU)s&DT0-dd}q&M=gxgWt1A%(aym zat!zh{$D_Z{H;HTR)s;0*BdKmGv|?jLSO-n2m9NVtJcn;?paNAbJae?;Zh z7AJLSW$sP*<4+?ZTwl1K&V4x711%7qIyC0~M{ql^?B5Gh7BYAWsEcpe4O!7b;McurFW9R)PC2I!)GMzUDUo8`&GuK~b8O zaEbi-&E_GAGt;urQyT_d1!wubFJS@y2GM;}KrMdGtQEm5{gkE^v1bANRl97fE$hj` z*x=Z@NT^L1{k4)}Vr`}DenQb2M8-iyf_n~8$W6n$aPpU+5_DKQzX47t;T5&Up}aWn z?d#uZ`7Ko!ZGPVWssGo&KXV=RXyj~y?cyGryBs3*4WKr#Q16ZFhepu+9Z71Q&BEW_ zAr=TF1$8ZlWxxmXq4mKwB>kZrrsvbnAQHPp^zHpd7Rurs&g&t|`%0F8j0Ok~x$^bD zk}qNebNbJb8{i-RyzQlF#wktNZ1-@a?>*G6-}C@Yx>xfU4%f{JA|klgYJqS!GL#C; z_=C&OKxS6{nG0s9qqrF%{LW`NJ+p8U~6Vs34nj8XEt)fk`IR4iXEMCU>XCogOBzGnN#d6J9vy1_TDqfl3NJ z*se>flpeObG+^B3k5-bnGfJR@4wFq_eFI2JVYSsDTz(6}=jYlkd%O*%(Mts2WVpK* ztax)0Ef1YZm*DhrU@jP|oTLjfpTO(tePlYi3izjdX)d_0V zuTOOS-#t|2qwLP$rgR(-{3rqoQ-%YlwuxYmGJm`yFa5L&H$tH|_|F|oJ1hk{Y*ao3 z_BIB@nGQIK3iNn@_tMm4eY^_P$S?ps^)2}BJ6grmAW$EpP{|T!yqRQjJBG)&;|{ZS zg*aLrK<)fMmWc(}tUJJ;z94j;0ad(qP(q7+!2lRz69ql0aWaAl6fRh1*>pMzm4uFw zq7hcXl3Y0d;{Qe1dq88|zwzUUx>4CHviDvMD|_!fGj3VgGb5v-xRJ^xvqBBPMtEm7?&|Ru93FM(|Ldr88s>hlI?0=ioU`IX z{)UbBn=ifKpY>UgE9tNXutU|IfRug3#!r&Ww3N^wn|v_w`NUL+V-3S3dts#7-jNfiz>d;~69jVme^8!%f&dW{W_c9qf2qWKZE9PpIBz&@Y>K5kdp?JeJ1h zYMz^9#%pzH!7INBpfYO2Q1KOIb=J3?#Y+=F0_i^7f_D9_^FHGz=&ZI zAtLG2u*ll{?_I6GN9+H8J=((*e)B!#dQPdo>p~Tg^j)n5WSbBXUxzl?+fXHsqZ$Z3Lt^ zDZ9k9%$hQ9Sg3jDIy951qyiS@030!L=|mJ{ucf0h*xx2M3YvEydkh|}J=|b{v9kn# zx^xL&DJ`;VN9qugv&#WPD+z2R=ziOj&lMXOs&G+zvjNKuICeH?b2QdCN>T+Z3nI~o z!vksLq;j3M04V=ez#axOKw~@}0fn*S`a2wF*Ev)!-gL0d{iq<~jio<`=Ma!j*v{x3 z2Gn8Ha4gos?T9GBCV|jlPC&yv$NtwHg5lvnfpL`?74{YHcPtY@>{D=v+;~6T;wBRA2lH46_E7sq!F~TkwFegxHJ*V8|mm?;z8yiQB{wZQu~I?l10 znM7XWKv_kDQ8zhDHlwbt=K|o{ow!TzVSH%!VIMAjiJlhiMyy%aV(gx$?(Q|F?oWO} zOAE*OQ$z?c4p(`XFR|f@5wge8yw>uqR)#{aWpR})*UtMOm;RI1J3^XRPv;epJ{v|@ z9^oUwZi|c5)sS*n;;^OudD-}XGSo<^MGeR!;lF`gX_ymcaQvT~;^82Ift7eL13QQ> z#cdCh?o+M|b=+n1{`JjR-18H|#Rk7?sLoo?iKmq&>gft($?6|p!y4;cxoddCjX`2Q zUpsKO=xd3-%#sc=cp(_d zkquAlaZl1G^D2 zP#%tvu-ghCr$^^$Im9z6%(HD)fmXh{@#R*>y=j*h8$fEwHGX2ko0ulPxwkeykepi- z{Oj8{e>yI@CZ+rA=>wrt5Cz?XW7CV`Ql`$F=^VODf-+d@-bWAK9nWEN($}^tjn1q z_BYY&4$8VY7fK_q^efSF=a&Oe$Fm2{>}GWNLx!RU8?PZ9(712-NMriz53x_<;|d}x zy%hh@B{}eB{_%#~1)7-MtMTBro2bdQrWXD@uQIXR@PFs^nGZ=?lhrnEF+r+M#uW?) zW_uFXdM)$ST@xf~v|PrA8c$%-s%#KRRSQlp!)6~PC8f`UrZ&oup0GLvZ7j1Ov?CBi zXLIOG(sxYlvi{lNL1D@2p^`-?j344wSO?nGdtwog4Ts*l}HvO6HXJ`lj&~6mB%{5hqe0@@6bxd)>V& zT}(B(D5*cMX$%@M6i0thBGwLku`+s908OJ`U@ob}G4)Is6u8^;4EwG>Gt}o^w^CY! z(v%2m717S}gG0EPpP7XE?Qw$Z3S&~K#6yP@?Zh75sDI(f42UDLDaW6`jhzYeraOHM zqEleZp~mB!3l+Un2A~9DXYIf9_4s^Ezg>NYt04Um%R%VF?IAxbhq5n{IYAK}jabhZ z(*^mKiAePR?r$d8v3HXxY(w6`I|P1tm(Nr%$CfUW0z?91nBb>*Mr4T$xi#I^(~7k2 z&yQ_oc)=Lz0s2@EUWRoe2SaDExIl&n<-%#j9G&SKXc5pSN)VoA|9AAHxnPily)6wc z=3_!0c$!u4TOMqGrmn?j-J3VKKDJr7iM``LarmMGQ~re2MGWkMx+CWXabs!^#Gm#! zDOU-d1arXVulJ3M=WIQQKLRrJN9popuIzUvQ2 ze}Q65sI*SYEiBqGhl$bYE#W`%EOP`@02FdB*IEqfZKQG14Iyx)X&`Efkk}eB6sUDk z#)$)X06ZVLLo{!*RdW6a;hh#k@HXCDprD0_NgnltO2ol27D3oVxAXUd6e{xR?iGn9 zg2Fp1UZPLZvF-`KdO~2icJAi zX$iPrR{B3b()<7CM<}8qq}`IJDJR8j4-m7Y9{!ZuNc=whib$XUyyf+gul7H@n>Wnl z=1xK=w*s}$LxC$lOd%1*h<8|91E_9p_EKfK}is*c;_=S2q(7}NgN z1B21i8kEYvFYM~Q2)H=T;rh$hgnQ`uzOA^&Cxf@OQc@pR&tExM&s4dlwh&#&e19lqRo%0cB?# zCco12O-krveusqpg|U^mrK_{g$7Z+rCuX1Z%r;p!e-NJ7z8})PZ8bh2{ab9dk?~v_ zaJG-TkAy~eyh-gb+OBMe+=?SAi206!+&Z#5;My5f1kT^D2nJkK_}BAxCPcW4G>iD8 zIJk9WpRW*0Z@vDuKW+UnHmpwMS~D{a9()av-#PHwLn=56@XKJ79CyJj>KO|a90ifL zy#*o+?s>FQ_9sPO!e!B?49=8v?^07CkyeSi1c?B*j)N8J^SCAGxdItTFQ+y zwWd}m(D!U+YcqKFZ7?rpfMOmdNW{xMg+}TSOmpL2-vxOLS@W z?v~+`_0w1}iU|g7Unh?KXTDC4-Cql?wQ3%jyY-cQ%WqFLiA`0}s#*N#jWmtzk zKC=&LssFHBnNkoJu2~|*1=qK@U;oc6v_)u~i_5{ZM?&#*COiQJsc*O`9CIr!r zSE`KwxkhGEI^2&)jY*a66i9?G*FuEXNr)A7>ehmGe9d(#Hw!h})tPr4nNe5I40LTm zwR5(FGMG@CpIUZKnb)v$?6UTk>tfsa6FdqPOy}GSIsIMrQ$2bN>#RfjQP}zh(@e*B z=edpwzYj9M^_Dz(^cL2$iu9{yCb!>N*5sY+k=CqvazUx={4TFLvwGc z2cMZ93QH}Zn_o!(p%6ftfi;o~1^nh!1)6t(20PYBklYboRW9PTmbtcheN+6(%y#wc zu=+@45SzdA$2iBU=W|Z(R3un``*`DmMRnTV@bg;@{%)$JQhxa}x^;OmQ|)>;kJCA~ zj|EZk|6qKv-FL*{N51O9(1VoH(LO29_(xYoL|Q#<7zYZq$k$a9a+H$!QclwThT-EP zhgZRCfj3mHf0_GWL$MIPHFyrUj+MB(SXNKph&*#`J+pfKwQ;rAJjQEu=w1&CWfeYO zoQ1ZnC%Jx~*%MPj=Lx7)c;#*N|GQRFy-1u$SqyuBfpEEv8(zGF91eiiwhv2F7nPd@ zoi;;{Cvd+=?3?D}ym9cfm6Q(Oy7E_AMm${7^2i7ug$jC)`fHO@Q!c>uPQkgM?~39F zG%%>@sQalWU7OzqpTIqb5wbyP?v99~e_*4WqE6~Des<$}O`5oYeq5BQ?0$8ITgpY)n>a*RHl#=?<3N1yFlY3vn^C8vS)c&gP7Q`x+ z=iDEH-j-=wux`4j0{^+o!=xjP-bXmX)(FAxab4DXBDq@~bd0L$f!ap6IA>3UUe~2u zVJ89HFqZVRpA#I#jJlNcPTzxZOWq!IQb%vJUO)7)$oJ0%c%*yv;@_HD6wzdt0A%b-8Q|8cFEo=LN}A+ODf_2unL_I8Q4Oy0%rcd?$K zzEh!xc@pPMtj>63>SpxNy*&jHZMSw`qM2@$x8Y`rS5gHPcY~!hnZkuFqu*Q9x~P`1 zH>zmTgv)y-&?!%pSn#>QRAH1yPv#Bk&_pdE=}`{bvHf#?_0H~Z6eRY)yj7&Y$Siv$ z=Xy^VHXQ#oHgD(Oap|Ic z_mqiUKHd|W|M2~}%To5ZLUgVBK3Qj)$OF9;)Jbz!JPAzjo#gNt(NY{nHz9^M&eg$ht(GDzV!=KBtqLQ@w_ClFoZ#$YcUG_#G;(EsC0y;vaU zu}Z7vZiw3@SKcr?g)hg3yg|x?! z{xUx$CW3-Mi`TL1@%=Lu!;K#Y9+uO)EO3PM2~DHO=BY3h{raZ!vC^qfeZUD*Z0auQS5TYP5PfF>Yx3w< z^Pbkk-bO5`!)}>@Id70I56dO=UDI?{0%d}d!cl+H>!q_Jb$O-X4Af|@eGJ-4CXO#O zba$GykKA{&xW=f#?U|qRE+2+~98Zo_e}7D-%Ssv`?Hm_rMbUGiE4} z!E(BI(0TaDjrmoA)wvI+L-23Oc{j^!USx@;7r#VX<2-(o+&U<|J4Z@&inBTM<}DL_ zx2`rG`6=t&-OgMEk$t&snf?3DuO!@u#@;8?PtwkQ&K#6Ad@SnVP?yEC%1kLnz!s-; zLoUhMpc8R;Mb4kwGXBTmsUQwd#Ipyz3?`D4| z3*5@W4Mw<#tGQm*ER+Mhv71lrQ}p7VSb0Bxv$IKz%A<`Osx3wQ<(lBgqRvsl`;P@m&^`r5IMYV_Oja$bldb-mj&5c&k zFF(re-_NU{DC{+KA02>0F4C@2ak0?7tRNEhbQ>Gz1Ux8pdfPlh0UdFZCniJ|U!1^%k$g#H` zQie0|GKf@YW2kXGLiE^ExX*T*mWqeC=OFa2?#4zVKt}n~}R{7OS+Q2kFCI z&A^Ybd*!!X6+}GFy>aIf`Qy_#nGgC%Nk^kU%e6S*GgJDSw;K6>c!S58oFFQV8&gUFx{g zU|m#i-sr#e-la2q)ZJx=xSh?yI}=YRf((T%VI_VJneg_GUNXfbBt(DHWho?GKIY&* z)Dou8l^TtY->A@{W+?=bktZc2k(oRZFG~F&CWqlw9;}z#O(Mlb6)8f=2plEeFBB^D0!mA`Iq4HdXLf5t=}Wh#oi2ZDC>^y* zXlN$ID9aFd!8ILt#iX})S}y)~5~R_mz$@n8{FF>=-&`;1?uB?o1-$yX1X2dZBgly* zP_%4*Sve80ETf8}P=PWGTO1bo)|+?aU5;!HY;1IS(Js(kpqrxuQ;2zKX|&%)YWSd` z%rj1uDr8dt6}N$b0Xda`MN)RymP@fk&E*Ueui(Wf%PTNr=?9IzQ<8qpz5e05zf6N? zv#!j+G{yTOeKy#M!lisXAl{iU4Z2+X_02F8m2%VKiHX4F!D6bF;68@FNe1;Ue(I^d ze3u)339uU}^~GH3FNq|*#o67$OYfqC#)~QQem*cQ2)WtZv;PWoWeLH9b7~L!9LFWL zWbHV}z+QVrvi*-k@&bo+URwU7FUdC6j_{fk{FWUdt;+({igI(*l2Pl?IKsm}t+R}1BD9MxA)zyk#T zwn`kW{K8P_1JEdbXztJwfh!I@=wJ|V=Z0~wHQ2yA0Bv|}AgHU0=?+=f2f+wv!MRU? zH>icI8N9|}kW3$Vw2xr{yh3$*Q$@KOB=(Z?N*81-@CqOmFo5c14jJ(r3yR?~=Wcg< z4lO5<(FPtkF&CwmfbNV0zp-9>yTzczy)O5qv68dD%My8{vzH>#G~GFcz-%zgZoFGc zf4Ayn;M~`VaQ*K4=M127XZRfd+zOJ~i<{T$>`=>ZyV_3iB#x9Z^PX~*6g{Xd9U2CN zQU44bLmsiL+Sg`5NxdGcY->jihAxV?<8e)Oe|WOt12}kH`?QJ2%g?D|KRup1iAQpX zPYmlE7BL?gDXGEBqcq^vWo1tF<|)1})MeTN4Xgn|EHsgXcmrxY#x2ZBVL4}w+eZUb z>s3|AQn)W%$j&(DDcJOG@VQ6hib^o#Hv?UXH8ht$!Tjy~kLODT&3jv#8x+nLpE9q* z^j<#<3bbuOOMucBHv6grv7t~$;jVT9n{&IdOPUTEXizSfzGf`kiq9lUv~pR-W{($XzwS1gZ#rg>x+yqs zX|ddA1^ea62R+9k89jTsbBSXw>_#G~(=Gt`?>F918!c2cBneoq`}sP2kIMM{b|hym zIqb#!Lh=)J$8kIF8}l=k9(f)wwt7{49sAY=GI@UdI(JDAeVYW_RQc&7wmV63NensU zDFi}iVf|0iR6@j`G#4p0kU%|*vb^@F+=fz(RRMeg*&(`+gLyysn}<>JbSnYkDn9(l znVv=+c`Yv|j|Yw??LD+V^W*Ywp41YNkhnItmw_D?e|IJa>Vo>_*R7Lw-ltzeo{@b~P!>-xFHP^$g!p zHtMk4`$cZCH;B&I{3M>S`$nPId|Tw2-)@<#;fvwo_Z6<^8kSxjbCFxJUYmLNcuna6 zr^}ty)$bYS@|<$QuXP%is6DuOGEYUfHW#;y^IBh+%8aBL0lLEoF z{3ep%`uw87R0tP&eQC{iw}<(INMmX%@8k+HHkF5#QKMQuqpzT8+za|V0=#;gC(T3h zW==3lr$LxdF4WDTwh4lJAsSdF+*5TD zv`WeHc+!k7=qn>p=fc@` z-#!Z@yK_|BM%>fQ4n2PPpqS8`)F$Dw_;!T&jAzf&i5S_5F-`DH83zc_j{0DBn z&s7&2Y4VSCE`3ahr4HYt`xW+sZlrcUnzYrfgA5Am+Z2w^l5k?Nm^N~3dpb3g3S<;@ z{F7px;!bv`zg0U9J|!2O+*8Q>v2AXJ00eXy`MyTc6iw_?PfN zRQlEi2Axf$6J!yxY>?~j^5$h{sj@8k-O*F~WxPB0P$2(A<%|=Zc zy4I(<27}ed*5rn)i-LeR#$p{CI!Wesk@}wZ+cq>76H^#FJuAX|KFKeWe$Crcy``Fw*Y82$F(;MK$SnK7IYJvGMnf z@5HKq&xls9fA8S^gv9jV)9!i0P>ZloNB%+6SmQ_c6e@ml5Dk>~O+^l4ZVjP-_Rg>3 zzLN2s5yz4&>iRl0>E;M&=5I3vpMNKfSdVsyll1Aa=A6k}30gI$oyU17KIK!rP3a%& zY05RV2@{Feb6Y5gUz^)!d}veqU4yFb%}mc*sP3 zhAcg`kq2EK1ayi#Uf}$|Zg;Hc%b{uNSP-Im@b&8@{WF^M(C~i*_=1X>+Jt={B?gHp zZLPu+-avW5#K%7Z;F~DSvw2qqrlQEu z&!Le%P}aQ1n?1Xp1-uUV1@+KTmamHfqXDF*)UKbZS!$d|pVi-$* z;(V9BdfjKHQRLvVI;S=Jd|zzoZ3|w+HY(?YDNuZ3(6%Yla;f+xg)}%yGtTDXMR^p) zEH&v~NokZCXFzW4%wx;gtrI)4#TGKky=2bgFG2zglZhU=EVl6&)vM_TU+KpesrwMS z`?x=pq}L`qJGT<+QTVlHVgBj!rMuc$vjCsWYzpAuNFqVOS>`bXf59Q9eDD(Rp*W1e z$+*SzGHk``Cz&LwJ_)DU@^hbTZT$}vlrH}C?MDtHkOswJ!J~0R$&2~3=s^xp{x0)e zEOjFA(DKKg`4aQpI(Zr}tm3{O<2?IZ*#ye`l>H;rm2*V~k2H*cBIEbOhyGPtn-a_W z$sj8it#-J;yP05p!=SIz?gfWPimkeK8T(%0$V&BylFe;1;3?aLKR zF7snL!6>TL6%w*W-&V>rabH~Eov&1_`=g^Z#fD3%$NH;!nn|m(9+bSWT|Igt=pw46 zc2Pzxz3Nh)?Sh9z^X=e(?@8aL@{Sy9%Xu4jV~owEtn_rV3z zRNYRCCxv2Gc#=5#Am2Ky;>su@mR~PO$=U1-3vC73odw6WhD^OcL6i`dw0sCywy6K9 z{t_pngqT13!@Z<89)a0!jpqHm=cG>4UFiem{^lW-tH-4--Nh{=N;50DGSfxJtDlb# zL=ztEwPPn4NR2;({LH8aT`P2bUoUdM9yz==Q8UDPpYUiEN6&vzG z2I&U#Pc9NvxvVRPS4W{~gsgJ&MS|uY^w1b0L85h?2J z=O_`MbM|5h#pR0=EjN#!lA{r_eh5P87*DS7=l78$pgOzl4i{8t(tKS;kvMgRpULbr zHGmAET*P|(e+pqDq!7M7UK5LMu0QMV#egrZ30YHFF3qm9*!;)c$QA^6m%ABJqSw_y z$1!zm242Q60ttkiw@((vHzF67koZ{aH8c{e%t{TNGT9LYkx!mh6;HyZ9N#}(6?3W0 zY1UhW#+m}d`mfa3w#K8{8GTX%PDCvB4+zH zdW6kQ&LK7Pyj@Im8@h7#n1!xIy4o-xI)CtwYm|7TSZVV0zg&PlTdx1{%Jo^04DLO| z&;J^j|7}I7!rOy{Z12z)n90eRa67*27bb*$A_4NYaV1l#qmJ*9J}J zs>+(FzFUiY1w4VXhe(WywZ#})Z9*)GMUFZWP4t~667O#f{MigX-1)O-Zu=eI#@=Q*+Ro-?hFNTDYgh$}zYF5BnM%Zj!15fErPc3K zk7;)6WYyZ5gY5X25oi6p%xY(=kfYGE2EiwW_eEYa}oV|uww z!qrB?bXYMjaxufoX&;r3OjITae4!$zo|pDbftISSidTRvE1TZ<{4Li7Dh;H*l#N8| zO#E6FqD89kb8>P_komBU(LM4s?)vvy;qOoayl|0N2EW{9t+&+*Y`%^H(32}6_m`Ot zuCc`m@4Rf+G@&c=XFoVg2^mGk6}4m|q%C!dLvc7Xi<^d)1P13_tMZPm%MzWAo!KQ+7 zyOW*OFI`BN#jiz=1YdL&i-`@>^*oJFg1kF2Bm-fUe89R+M1mNzGQ9gI9j#9V@rqGt zegvnZ-hh)H7l+86<#S@Cm8q(oNZpcdp(;626gi^9^M7~ z4TsCm^b%WS%`=^Pp+1pDljI8rUn6d;>y+RV|{7T;rr>}leb=~bN6^wm`q->+`gyUejr5-0d{E$6lEub$>#`zB>u z{x{8t%woZwOB&}A>fW3(jJaCG$Z4TrdL{Z{5Dl>OTAs@x_r9$JG!J;?O?yI_)E3^455fz3*;!|y&8pO3d{H4Uzz9Dnw_*aavR&H@a%SNeu;ioiVVl#bYcMX<3(_PmK&D6P zb>dk5R&?Jm;Jvy~YYW40Q9NkL$Ri4gRr%wqDS{U@Ky{(e?$u3ws2&l)HHCTvkj{|s zJ-F?5AL*oV0CWoLs*^X1nJ9b5pR)x zwy>R*@*)fm<%_14>6HKwlq6!Wk|F6|Y?f!!99A8+8@x4>>Dgc6{JYWmMX>&xdmQtC z&9&x~HEn5Zyuzz!jUpZZA|XJtbh#$Cgwt9c_ywyw z+q~t)Igq{|Grz-b3S zF8c;1;_#Fe{$Sc_xPS68M*~5h*=}v7&^7(b$|4@J9i_*f5wMUvJx@(TqY6_Y+Ew{k zt1#@sT5MHnQG+?jbU_R7g9k7Y6~D2)I0|?{WCZW2S2}saAFG_AP;pNl3z%0%{-~ci ziHV8HgIPrbsAip^E93|1lVs>tHEB=$sWTWDEK&z7iA+I+I%XGoMER_v06z#Q**4wq z?ZlI#zKQhM%eNR!IkC^j9N7CK2> zEi+u;Xc@4*fx({4x*)WjRtSCKna6%?W)!Guy6jX(5^0y~9BF@Brbw_7)_^?Z0sf0$ z((AExKjWR{MpYh)AIc=$Er-M&xl|N+_WE*ddSy%Z5P)aGuVuooGDF~YXlMyBDDbz5;m<(XB+-ltp^rftY(rU+u4KV9!( zd&++_k2WSCk$yto9nsFy`Ugr7RLSlLg`mv z{&4-BBuL1vLF4t{O<#qCswE5u*ApJjJWDMJ|Lt(XsZAHA_&IKxbkLD-1eA-cof5tyA&E%P;N*q;5a9Wuhzn($plN&BM zRkkHQgR0RvC(f{vRVY_iSZ$C-q6M{1S=|;y5`iI-Rf8emqmJWLNd8MWL1ZIR_Q#uK z3TbSZw*vL&BLHW#;fvo*Ym*|(z8*chnz-e=0zk-h$#8N$DoMnkh+31ByV zU@nE21ncl!|B+|DT<_9E*xq3Ry7>$Xj=zBU-?f$_B+)4t0PFIaLbr*+>qUpaqYc!w zixcXai2?lQzw{{!HrdHDh3`}>`vNWt?vab-Ky)c8I*#wv;M0ql5Y?5yn>{X4YabjY zqT*E=>olO3fL?g%!E|(Qga5)@RnG+qrXC2XFa~=h&un;{H0=6a%?&vP5o>*l*ms#S z0*yy5h&A$4u2OkLKuxHe+2! zI_u}oaUCl^s7*0rhqm#x+JTMf?!C|!>C&xy&kD>=g2YqV-3QPrLm7@*rx11Guq~Q4Yz@xQL7N33?ex@8w zP+Z@fdxfTquEV2z{a%KdR>8M)w@=1taCk)KO2-4pNoQ5x2Jr}R2jP>&Lh-s7cg*!4 zKn?N@avM`KiyxPy)?;_ODpfRN>4Hl&l`TEnHqUMPFX>EqlaG&7mh>*vG2eBz(kjp=}Ut8?nL-mkD(oQ@v^M;1pxnz|g4 zXb_2(R6;i*{UUE6yFd6o2UMB>l<+#7t_VyA+oN{M;WMc?ABR=WQ>mWuev5sZDs<~t z4~(NmIMhX8rY>Ch!BF8G$5&d>61$sr#x2A$TW?Gbz^rYE_r}*aPZQJCx3Z;26OQY9ClXAP-N@oHM z!HHi`_Ug66ztl}~>)G)ue^M)O#&YHeG?HndiT5h30=U{pHL1p2Q%d8mW*&t_-$QVi zeujcW3kr;cj?=ZFM8=P#D8nGI0-hZ76?|H4+yMwlN^jqQ6oVl|b|wD`nDjJ+QR#79 z-CL=BUrS!SD>sa<{&f!^5x5Y2?gHZ#qCp=6O*AuEs>%4C4igMR` z)ZaJ%-ADfae18`-oT|L3V^^H}s^v3kYTZWV9W=%Tnr4;r(z4Iq-eVQC`C=;UZI;`1 zc@SG&w!=@{6bKMkbuqfBN2NN3G6eTq86=E6 zuyXWNM+e;_NyWBfe~~;KJVyY4Ui07kRDNT3GG3ZZgH$gDXQzPD>JmHX^ zSSfU>1RoI6+Ytr|74TB~p;w^-!xh9d=Pq0!y$1bq{Zzq=ch1rK^n)To1BlF>;4^<7 z34fU#$W7HVB3zLs6M454)3-cM-HS#qM)s8+iIfpy*Q&EFCB2M9-zBPvBf|EE{oFbV z0M^L9&dY{ie1&W#esay*TtG8tdo1uPLEtGPjuL!~L(Ulv-bq#n4ma+7!80wlvAV-v zkAgih<>z9!D{cmswKPYzFEs}#{S1>|&!1T6Ad@|a$Z{44D0Ekhs0)(e`-*Zrd9?P^ z7=!D#Q_KfQ4j0-a-XS)wS@M7j&Ry$fQMWHPoReB}2$C*$Pe@(0iH~tX{#ERcD zC$m*=$qlb6KH+WuA55|z09EOE9-a}rM zCDqBr!z#T#@z5H4xaa~;;9Z6!SnFM5gCH*|ZBad)_x^V`na%!Omg*}LdwztS>1@Xa zpKNmvKCwyYwyJm2#XDV&8B`ECDUxK?#Sd$x8KX5 z4?MdlGy`KB`iG8a<9~pu*9nB{oXD$s9>t z^#0fWi?VZBPcN|M?|z-R1|FB` zB|6XgALAd1F3ua3*8OT&__j%kc_ta^b%6*U3ucn|9wummSnJ5Z-pCQZEdSvT%AE!nSpuQY>Ih1Ij{)yh$fZx!9Z;Kn zJjswcUi-e}joVE7iPmAygs6Be#^xWN!^)OU(wH7uEkzX=6N)*Iz}*Un0!lc?|kOg{YH~afhv|>GC^YPX0@{&!<4beOo)8 z?KB{~mn=S0f~Q3tW<6%ROIhGV@8bM<0?QnSu3C=a|NF7Y?#Cdovd^3A6|5XT*0hu> zAlKEd=Xk|;Phu}$9L}~ecGPhFI}?Hb)K7s&a~ij48v;W#GP{VQ44W>8C$xog$sWl| zORPFFabK;aYxPyk%1f^Qo*tLQt8|_W|w@Iq2&ntRw$xx@fHSTR#8=lw_x<= zT#cNfH{@Ehk%Kj?rCg`N;|#e++&x2tJ@2}2wbKJzK;>8k_C9@EGR6^@Z-^up2N>q!hCpuCCl_@;oq>b;pCZJ8e2|! zgWt$)+i-PL6b2jao)36_llMMk1ftJb<32>89hb(ra*7oBj}o(!Pu0Ysu$A8}l+Fpb zG4@VBN%!raI3fG+30;-J&@NAF9sedL?}Mp*00dglr5GHan0d#Ynu@_<5AdQ z+wAEcL%76(s?bVn)~x4+kK9I}dLwYiz=>nJ^uJFCLH5X;uL0%xp+`=kS+8?-0l_D7z z8>YF^dXSfJYXaI@`B>SOne+~FCLVDJCj{73+$=2DK)OK{>UU&A3mNhCtGC8ic}<^c zc{Z*bbssL5@06vutCs15iE!!5SAPHeiYI8yrdBWA)rr*OH}FwHkp1qtL;_zY3eM|< z&vw4MP6~OjyqOf5QIv5DeOD)0J`YZ4O|<=}V%S}?4HQr0ZCYTWI2n5kDgP2}(Y zmpq(BV&BmYB7tkW`Ff&cM5y|^5$hLno%!!RRO}o*o>V2hyCPI4dFG0^X0VMNhpq9D2{! z*&k__QO>8QEL{79utg!J?dmKY8D>8ICDF(i3w>(ZOxBK7rd!)bpH`>1{_1`InC7W5 z-;`(MWq;_xQKd&|Clf+ytdOnWgcb>t=n{(gGiPyFiueJ+L-m}fu585av&9yY{gQ>o!n^)Y(ZN=6Si|f7nyva`<4R;9=6#38g~T`GMh)2-H`V-($WB z<(;e*3=%%Qpo(T-S`p&NRCMVt7vPX>n{nPdKPjB^ORsx75iY`in!m*qmJ2d)F+-UM zcmg=B9*9ICq(p?m*{?_p^sK7}m3aGA8FA2aa#+Hp5Y$mYT+ zVcUozhAUfZE_R+k@@Jj>Up&(YmLMH+yIah-17O2Z@b>q*c|f(qy*|q!U1XVG?+qI^ z-(0XIaQax2C8`TQ^pK5EP4*?um%P^rrXCTt)MwDfwutA^_WUl>XwM_utYJwJBpW_f*R<=-?;^H!0PbxFi|FqbTD=9sVI-0o;LZlJ6WM zk?5!6#6SW0QwuV0ARC%Thard$dNWXzz!xq1p^HD> z5d9ilycRoh0m2Be9KkqX9ijn{_oD_um^Xc6+bT+fKA>kx!xUQPHidh3_ed~P&+_z3PfgsSAF*tJad_Y|RoKmg=I$M*Cv zyXNy^Ld!(>(uxt`cKl~`OzRKR=LH|s!GJ3ce$t;5m%=-C+1vkOX%hEx+-MMcvfY4Ix3rr^)2uhBaWZ#-O{$cqyGEJe!yS*lL>=P{j&?hWo#=l6SCcI7(}U28u5 zmTCf3=&*g9EB3`kI8y1A-9i8LJAsPkbBb9~V5V7h)kGkbt3v}JTXF7Q*=28>W`P4R z1u~PcpWlG#KuIz~=gLUVD_}Q~kM46&eba;{Y_18du7qMME*M?5AB)g(ET#m}LLK>a zP*e3-_CG%v(aaEX2$D?Kh+=PZ5#)8m&3o*x-VoC+2o9=Sx^wDSpsT1+Y5Z0W zV=z|oT+ho1c=3D#a*Xct)7^WtbYPv4=zoJ#*?PWpfWUy;NI8Mh$gmUv|NKkKPH?8$ zToEig(tCs~@CApF3Y=zFP0_vZ@P`V$_>UAVbRj>`1nN3o;WNxf0O4yQ=(WcqW8VRV$pSYim zWnm)U6sD|vRrTIgc18tC34fby91ysQtU)LgewOe27?%tj?rktA)msE&9u)S15I)2z zy7g$P_wKOD$#UQvZ`6(iN!bFRDu3xaXn%ywNr6$G%#HNka@;rFW`LC{ zp9prt8Z`q{gpS`b*pD?;V*~Wo{s0#`2Kp7Nz%cWH?4}{=JDo~nH4~qsmpHyI7@9<$ zM_7(%GX;v+n!h7=`vRmK83#mH-j#ELv`YniAUsY6(XFB61F771GSx6-^wAfLDqZ{> z7UpxA{G}m;ndLiziAGobt{N8zcxt56%ROpK>hG+N{zDj!T*9RDa+Y4;Vz7gg$b0v7 z<8kT+mGgS*QyJbgDX-GlO*Je-P1VY(sFhkyDxAcA3&bb+kp4i}VH7&kWRzB`9vJxf zdw7|u;N~-1_qw-%o6pU>4G7sZ0*Ig!>O25Rulm2j+gj zep$iUx1NnvP&-m<>+9Q}fJWLySO`v$Lr&Rk=sUQRYt8_q?jo#vj`VO!4hc7-My7*6 zm1VL}89)9Nn7_jC)j#dtlzsyi*N6Q`jt32>mp~u2FnncaX)qso2_&WvLG;hGdF&Vj z--BcoZtUeH9!)8h426D*5r}R&Yy7C3?QF3Mn0>Shh6>f^d_F)Q&647Tzec303#d&y z@1NxxpHzWNk{CcusnI(9k;n!dpU{P2!`Ms+yY+;R)N^$b-<_iB#B*fMv-wiRYp4>1 zwddNK;AvLrB!P&u5okqCIAFma;LZdg%Yesn3A%=t^cK7C(1wJvu zQDB%rHN6TPB2FTL?lkuPxeBw8Ph&@cuf0pwJv;!@tqVony!sM}|2DYEFKPyOSua6A zF$$}F4BD}unm?|jMKP9%wjiqx`2tgVP-mk_I|S705?{0RY|856^n7ar0R79~8Nud2 zWt)j8x4PZXENV`p24S>sp~UL*H$LCrn~4A2~IvTiWkV@?DmV~c$O zzTB42Uvaw#^P1*I11RKtsvRl+3ww>Puk^O`ce z5*m6e$(eCGK~772K8VD3^_EWpV&pA9FOM&%h)Fn_SDc^$MVCT)_>PL@0lp&AKS?(Y zP9~_$rzt&9P1{%Vg5Q9@Xhzf9-fg|AE1SP}{YTRE8xFIQ=5->(kD!LI1&#ZsU^cNt zzm{pDIDt;bWzZR8#aW#G(YovKYcRTM4APBmZnY{#dZOJdYSo3Mhw@SPm**$7dP26& z$HXK*f4%?n$rMP6e#y#%5tbQ zxq+bEyKQP#;tCPq^cDLk21}fUE=72_RX(rF@GcjIS2rCK>k?rIpU$a<;CZ{qS53^n zI=xpa#KeyGksXe)()cLHQ=Nq=LrU$i8_q{{@5Z$K8ZnmhSKr+>CGcc zQ)aCmh`K!Snt^B2|H56L07g+@pimxt^Q7CLtqKbecJ3fv`>roN0DJn+g9Ufdx&g~V zhLqo`5GwsZx6Lpa-Kiym-&*|`BxG+f-+fyJ!WF6jtj4{#b2ws5384pGGz2QN-JM(& z96hqJdQmGk@uPlQb6|a^Wyksc#vqvgoL^VuqkZ48U_F@oJttHS{^?_jjnmz*~?+v6b%)sn(F&(>m>2pk;<%(o#2$zwQx6{wzLUYCoA# zXZXhD2gNJc8Pl(#RakS;wr(L&ZKFctVZa#Z}XNPk%@_i%#ZBQ?)M;1RJ zTvArUN}&Nvb3Q%kIzf2zDH`*d2?WgcwK6bq!QDsN52m?w;)9`i@KT7D41_G4lD4Wa zF1rQ`>eo-3Ww9U#{fZ2);O<)UYZ}uUW=mk(90QsUL7#vBHL*f>*^KFgd(tRgk0IMbk-=_7_yx$*`(*SzQbi? ztA|WN=T3PH%1QU5&MIse6>aX>V`KZwf*Z+z^ZFFW8SC=QJKR7fYp)oCEKO4aHGtTB zI4W=fbp7K`6kq`UrQ10|qZ?p651DCcPW?q`pmxP^n2oaS{&ppuRSM7WhDg2{woJ8+ zMx&V;kxzAQRJXD8w5J*NBRl)$ZD>r15sA+XVLskATk=%yW>O@Cy$|iUnI`rVB2+a zB30KbLK}!Mw6puUJzo?Zr=H$>^@K{2kh+k?yWD5jQBpyt@$oS zURsJ0)+QM{_u%K;C2~_*yN81Y+2^$K6-8`@6nhe=C0x_*6rqN|57boJOpV~$tH@LH z20&8ctiJC^Byq!ShD<1m%A?rei%)QDP(X9a<^FlGb2$sXBWe^)YLzN}esyk{&EEP* zbJfJlODBNd(q4z|JQSN|4pl8zfBY07A2!ydNFuba611>Q0@CXN?#bUH-^A_l!hdW1 z9UHHu0itGeJXBZjr;&cuW6>g`3Alln0#Rlx(e6(eLA3c1Y!Q5DC~g@K(9E^uI7l9z zo!y1MOSxExFcoi-XT;8a6>Yi;T$$?ZlO1EzA_%A22o6I#)S><^lp-FWO@P6~fuVyN z8El!JJ7fmus~w3AJ9^cY8$?Xn09&Ib6nOGp)sv=n|4Fx7{KkLdLT%cMGMG$v}N+tlE!y{O#` zRp#h_j}%;Us4+jmZ|~?e&Aq%6BnT1Q$mb<&?63OG2ZmI*JeNN$rF7lsV+H5xj0N@8 z5-{$0!VnWAe(GL+b+GigAU{>qQ@8Y*bzJQl);VOh;e;&1YuD6SA`LUB=Meh-@C}C^ zGM|ZATBYewKtt4(x1*5|2cG?JA2J)FA6siV+|8ZlE;0?m1s0lu1JT))MV#@kt~L{N zwIR8)4q6DqSE6hE6#_L!RzP#)FZak9q;!L1p&F{m1k0k)Aoi*cqMoANrI~j6wR4bw zKaM^iK>setvQ4IruDW^F@KS+>WdrtK7Z(9h6twsITsq4LVAI^wkhFs_onNF6w7t z3`*3OFTJ1fCfauYV@|Mxt1Xa{>Y_m}=eczh8G6+ifPzQ|)UdzRvAa+(%}u3xTLuL& z1)t|Kcyrym^BF?i{aLQVza~27ATTD7Ddv$h1kHSqGeId+lk)Zqxe&(ei|^r0OGQo?>Yi^q9{ux; zXiUk$!`RD~cgak1-z~(I_Ve3AI<&U)xz{22P|^J#=uz(f06h-k6_vC=J-BRYXQlv+ zp`R@T=Q9u?zeUNMQWt-eEn-t8Kj-|=pQ`yWt9%&Y>Pwg02upF{RGeW|9sn;mpxCw8 zE_*=5&0q7;WI|ZHmx#Ymvs7(jn9S3J0UM)PIC)Zk!v}5fGTizK@R+Mzedd*G^5%zV zs%(G118Az|ZKfC>9BRR0h{&>7ji|q8a|EZDN?wCRoh3(8Hkuj?@1HWzKGxyFVCL8@ zN9f51$N+sQdXIx({eA-0;ifh75?0$ke(N0UO4EF2K{`|U=w48Z!hFeeG0hGSQ zStfGUcl@hls_j&YL<Yg=gtJxQYcT?=Z=T12~0AJ~^ zJ1qoo{Y^3He}+}W6~Oo^ZviQ|X8#|}-^&;Z8fQ>znnR3oSE_>-x)7>t3t=iyyU59NI*$}E=30-xPBruXwHa8_7I2~T~+Kd9MU8EvyUf|wk z>v^YsNsIjBot&X@mubv@3NEk$DzC&}5iJzNJ)nuBHC_c|`2hJO^SZS!O4o!9y9>gO zYVp%%VUo+RDP`@~sU5CUG4Gsnk$oOphwj~>M z9PX0vMmRPfCHpPL0==+1M>bqWVC;j+d&A|pJ?}j$ASc(nHWUN35yVtd4EI|HAc%pj z%|x%jWE){m-4^2{%}!2lPQ6Kd17VR+&dTFCRW;R~d29>eyGPwM&nflOT?0Ks`=t9IxX^+5SG^w_ z+lApsG^TvGR;c&?$=?atBaE7#$Nys24mfTV5Buf4fJjDl!S=m{w9lC!TyrY2 z@{~HM^%cR}@h#-F?V%^13f)!K4xc_LdDu?t>a{FdqFc-M_V&{z9H%cvt2(p0p%7JXF($M2xtVeE_AxQgPYC?#_1)Fr%{Ow7mA7e~DUd=Ql41Wm z*Q=ii^Ld;nSZKIzU6nk z1-f_Xch1%_o?hIB^mjSQ?{LZ4yYGCJGn$i7bBfB4iWRo87?B)cset9_5iFY9>Rtzh zWdE^};BF(qXJ~*L4&$LRG~>`K9Z(9gw8w|@J$Zs+2aZ1(&oqzZVhgmjyCgkvFRWAA z7q)2KtCT=5U8S+;MGx(%CSQ3na_||P)PMv9@fq$yj#&b}md`uoMF3D#^1yf=jzCIh92= zQB3R1ef85}=aHKYVUMJ5hE<(tSrSJlp#m zSran~sC)u>wG*o6N`^Js#Tw~fGXO5^G<7x8GqJM$QmgrNob zU+=^fF2Et6R@j|iYV#HcVEMM2Y@&MS##QJR007+Z<)KZt+%_eFa>E~JhM9sVATtPa zg#&KHp(hI!2j1X#L$tWf5E3F?!1m2Ksehs#0b_@x@6Je?c)%G&iP9=y>M_KlUecxQ znE>K^l?0UBZeVDfB?HV8^_(vn@Vhjk_`~;%50zlc=c3g0^`;LJ5TH#aH)BRqP)nK6BS+uv_ zWleD}uQS+IuCr9&;yw0i5BZbuEy=l3Vc=*l5O}bW#IF^C^&{@%8()$?YM-*+k9tBV zK$LXU*mw)dd8Bk0CMm1aVYd{(foDodp>|sVeP6S~bS(`|j?f_oFp<=G?wWxO#`QSO z$M%R%7@JQ4g;S;-x&lalNt%Ygu6!*3FKB2=xnQ4%Y# zhSH4ai~T;ZG~CiQGy=|cF;_(=gz8(CTT-F6LG1b@vQJ71$d#7ZtB87d<6X;jd|-xn zTK^eVt9iL&^_L3(z^T-?0Gu*xh`rSk*-EOwVu^5>LA>Ex zrSX6_L&%OxV(hhYiAI@q`}v>oTwU8m=H1sT{FJuc_u9vgc2`~>;k(*?2pIYK0nDEj z_v{sato`z{0fxN#vuDrt-HW9MzuQh$vs}!7p4le+1S+PGik@xqQi)Y?Bmtpzf77^L zt%vJs8kI!1K?!zU_5+FcVI%f1Ae&`%!fbiZt{!)oC{ZhhEcM4je|x9L+Y#_|n-9Ll zri6ofdMMIq{#^^2INK?y8`&i|P|hXxh?)S&QBn6U*S*F`9O67@4?*o`!H;Lwrljxjc~5R3eN>D%Dd)ebXdiv2}jqwda;k{%yT>s*!P^2Z!ca zPwg8}#Pk7oll_er3C7pwKO)2Po)wMcmrR&{EYCRIeC$!A7SZ1LV^C3W{S%=Bd&)&s z$lVln?!dwNC!i2;SX+3Wh{{4sVD83IC^J#E&dI5-+QZ3J5BiQ$^r=-GTuLfV&N1Wd z4>&-z#^`Le%+bulpQxd_%bkial#yfYFcnhMU`PJthnedX#Ikl}jVyT&z^d-pZfU^1 zs53cr85`-mVhAWo^8RN{6L;^MA`CuIc^7m=p#$4Tx+CDN3H(r7 zC*S;Fn5m4w%SBodoNX-s2YJ&IrG~#wJuG=oB^a<#yIXgix`vc!Pk9rHjvJ~>K8^6w z0quY$@c+xF9)iFDs31xKY`cw{INL-ZiEd4D>QyKO8AP>TrD)MVL;iA205i9YE@dM~ zh@M|_FP5ne>}Aod_^iXd!g_)e;m-C2brR{?+aL*>b+RJi*%cBd+WH{lC;#?xAb6vL znDEf3c~$spRsfZcpOgSv=DkdLV!SuAli{P6$K-LB9agK=->#5&`<<4){KD$@QNCoc-A8A zMf)Fk#sEVg5&;3KkS^n3MADkYWtL!VwjkxTO^6v4c-0|uG?lN{#K~6a@j%UE9U2UV z({Cf*VB1m{z`L6|NwGoC@J2o#wj)>2h5(FJraDdi$>8sE3x|0uKz_CEcgK=B{N9== zGDE*X%xrzL4fIZs=zxwsG1qbRtE=;+qrK`~(f!640YOU-Qg(rWE%i&;fDnh1r`jUd zQqLI_kY<2c_D2X%sEg{LeswK+q-ma8uPyhm>-9?-HL=0B{ZnUQ(JEW_dCW*Lx&xLp z@JpptiQZC}Ti(MFxj0vaSEo)8cJpa-+)HEA$yFs}L#L{^#RHtyqtBO%@`UdznHIbES6mi~6Rw}WVEvmTVgj_;peS&NkI z)n~qI>xYVkWC($qDU7Px&$NL!gM{RQD%6y{5@q;r?*Vak9<&-{bOGfT{~+a<$3!1c zVFvQm-paQANO(X~I=R{uAdhtO(;76UY=~k(@R{3n*m~ZP!En}q>gYnWeyxBOe?Dy% z?(?VaHzNBTa|hR_UY#0+4d!!m1ZoU@a#$rb2l9kW6Gcc7XK)}(jEJ~`KkC!4loM%q zuEKXUwvm;i@%%~{z?Au_Uu9`6t_LIx0A~OwWyn%UBLGV8IKl@YZ&LfQDuNvP5Fs4z z6q#`~3|N-g83Oc%l;=KBnAFXeSlgXD@W`SKdU5OMJ?L@ZGV+;$V`=Ci`Y#}TaFMS* z!2h`>c$6gcv8??GVYT7+o}$|wGTi6cLWf({9tVW$^O=95pOzfHd9uY^q{GNRXfkuC zf0|55*j>0A91aNtyi*^QML3bi<{UysOu-wKgXghvwy+2QY6FO~MnN|!u8@yuscUQ< z2PGL`tZ@*e!N8(gn+!N&lRFb-rI*khP)!EXZrN`mwTB?Zq9au^O^2)R$X0?xO9Yx6 zbHvyam$`unZ8{UnM_54B(Eca*yHz~k;*A)a7g6#{nS3qdQ<-j=OHaRaCGWc3Cc|Fw z$jwcG?n`DS9YM?27kt6dl~qGI$wDbP7f$a{KAvIs1kR~jZH?Y(?ti~lK@Z^BbuA+8 z&}x(X2&HW&CKB0UtM zf2nEkt|`0FDjIc&jQEic=9_u#C*`vc8~IgZ?iG^}z8-(@i8AO$1KxJ|od#ji=knE{ z5Nq?7Qnj)(s7|d%`Y^0Sko0DVD9m=>QT<#83CAYup^ov%#+lr0F$vy64bJ=B5ydo* zpV~8@4jcYFJ~)(hyysq`YodbWbnd0cko=ZD2tN5seXmURu)tWoJPP%s`)+d;CQOrQ z;bb=q#>qUK3p7OdSvpZ5c;k62aJ5@Y6u6#{^2OH=?dJKqpa(*dD@_5kg07fyadoD{>y9<@v;7**=ojXhdLcL{&)H19oK3vPRQL-P@aYT6zUtl6X% ze3#muMKKj*$g$d7=lHgYy8pKSDLexwV$meo7+`9vjLVxx)7)P_Nai0PAfA&J*tov* zNfx|>kg2A3Q7?&j9M_(d!&pT z(n0>De-VJpMy>-oxK54V$-KG0#8jD#i^ z8UgmOG2`0D*oUfzIn=*+vzyWX3*HP()D1KK+dBZRg@}YPAb!Nq<~G7eN2W^BU2x># z_e(IgrRv3d3$?Ws^;)~>xcF~IHo(vt4n13me1EZja} zctnB@mh#9Ril6WVNoK2g%lU&K7=(!p&DzELU5Tf&goM7l!el6vu3Ug9k9`voEE^Jy zw!Xhr)KcxAWV>?ia~1EP!IUZ+d@gl(Y0>e zE^CJ@NL`@RBf(Bl5gqa4MeQ>x4K2bM@^%u<_*-<+0q8I$J_VFS!Hp93^D#lhyAL$p z`8BWKzQtBSr-Z_Y%GBj2nvZwuM-);L1w*kMviwobme54oub@T*#Un)D02RB))$ z;~qcuwxh$O_eX{4NR!H$Zu1avKd=-x)1T}AEfJS?{4*~KpF2WIkH=L*E<*3C>cYYI z*-$yS5^{Lm&`BRYQ8DAFvAE};x2U{B7)~H%iR7|p-$s=;diUY0M6ep(7cYxpBk*EAXU(i(KwQs0LWCmr_~mxO;_VjPhL=lWeXo%&;xJB$mXz%oNt*(^d#mo|%To*H}x91o%HXRclsJqNBusYuU9N5a8+ zMzBUT4i&M2I$Xn?6tB+^Y}>Q^HEo%+G(%^)bCPR;VJ#fG?S_5483Q(yx+(MK%!5UL z%Z<2s5p7HxV^wU z>iQuCDYqIG?JydlE%e?dAxV5{MwPMn>cQNPmlBtcPKvZqLG3vCdWc4vU73aitT4pr z5jkl~fgWFM^~&O)k7N$0F-BTLE5hIfj~wJ|V&~DOjU+#pX|`FJ3N4v2NJJiaytXkU zLnWz1sZMtzn;l0TqhXSj(W5+bUp)|b8ml(TTy;~XT61KE8z-q&?`Bw+Om@9bhOp## zf?j8RGrLWF|1>hUwT>CFc=o%mavxnN9pmA^&{^6#k_)z(t7LODdPPm}I(uXo7N}v9 zD`NpNT_?Tpsbo_p#A0i8%wE?SiPdL@yck#e04)R&Th>D}1ldXvYo0K~nSmD@tU?^r zc~T2P*THXEYuuKbXKuYOK=Z9Ib;Jq$_3m8QC{;WDw#V7Fr?2^}wa^MH!C>EF!I&+R z>E4|Nt4Ch3@Nm?@sMK3Vg}EH%H{Mzkh(|DT5r~-`3)IhZ)A8QmH{h@e-q2wPX{Na? zORmbW5Ckkp-G-NY1!DyP&4LH9G<4lBtc%CM5o>Xi>W}qyS?={pKV_OLTVQ-=uLJg= z#?gRnjd)1C@UM8ee*F)#e?OwPM(@_*|2Tb@2`5*B179Ir=r|l)mr#WQnh|9mGM-r( zSlZnV)B$khd}drOEJW-}gyCl%t+E2=n9QzL8>E5?G(=)7bR?wAl7qL%85AcfU+teg zKgrrTX+4S=5OWx@Jug_Fy3dOe6rgKJ3kGm54_@^d^;FFZ{=lA?%c(>AOdN!D@PQg-GdOrq9iG~$P_4VERZ*bF> zWbz5<^@A;Sz_Q;U8)e?xp6r+y-5luEbny$`jetTxoUjM2Mm4xH%HB!DHQJ*};D{E)Iiet$m<{3CY|fgxC2?Kzf$>A(yn zMW~f%^pI-QFZr^zU^b&lffvIA$PZ}z1pGmwKV9=isS=(ILz$u<=M`qFDhz)y%p^~Z zD||TB-=O+4SDyKAHg)BlPe1tW1LtUg=>I&A^E=vL+Ddp}IJ^bDmg7M$TAD#@1ls*d=gZtG|=GTI;wOJM3yAwu}T0~Vpj zQ6W)LDXOdTyLXV_3O|yR+&`I@zyEYH_xY&C?i0zodv+0ylC$cswmi)w<>0`3&a9}Q zH_I;GL#MIU#Lwi`7f9VeM?(toyL4RGeGF#8mrLT!uPv;XsrEJ=4I(l0!IlG%Bf-vD z3sgj5v$xQ1J;qH{~_vi4^`>OE*`_i3}MhCbym!fU?JD-Kpz_lCWGaO(wo!nX@!w=NvfW@(8T z$!Bf$TX3#F)7;dL7IO)kAdZXy;-0&ICz(Cj;k%*aTC{Wx(uc7L%dW7 zE^q?xQKFW?%Qn%Qf}b>E=TN(fMGix301K@dp2A-C^=@r@1kj8B zL|y4^=Zj|>3Q~Jtn;~qlqS%>Agtp`8TM*Ii3n?9j0yl z5_tY#Kh)tW892jV zl}N?Hqxu#a5+D-6?_;A4=D#;3V&dSZ>QhZs$$8E&hnzQETVVeD)3IGx_Qx zKpyrEN^E#~v)(fKM=>ohl@Vhf^Lwn?ibKm+3!|^zIH}=-`xuY#-rL;1+u;78qsxEzK-3Ej6%KOaP!4u(y_!>bxx*Vs;0hDWST4ufH9Q{tLi#0-A| z^z1J_zlqP|rc7L+Mn>zRms-SL3BiYDazh~rt&F-MquC2vY0R0H217ZPe!Q$X zmERvI1-5j;>X%6nC!|@&-V_jQQS1mbwE27tlGW%b>7A|MeLBToV7bCikMDT1Q~3Fv z+J?|(&8;!awf-?o*{|Ije{P;}xr9Y-tx#e9!33Ywi=Tkh40T0C;TC@MEjC1;!goi- zgAe2R=oXrS&gGh9f8Yt}on=2@-OL1W6fp;3;|`hy9pY88#vhO2z;tDkRM+mpeezY6 zmHo~fp>E+Qhmbss0Y?fp>70z?SDu9zFv=mkzmj<0jC9R_&-h0)`M1t9v+(B+`Wj87Nd)p6OeDeOhm8 z_iE1aBG02_C?kGE{W;g%QIy~VtJhhbYmIFt=QC!>cfL?@nT*qUMLIG!`v4yL;lWSo z6xP$<2ynlW1uwKw)7sKBWrUQNIC%I4f30Fgxf}juB%WiQs37R%tMqPrKD_;vi0FAYUO--UKIYnBjI~0~(6jN=I2L>6 zAX9pUU~ymlwNQ=W=clvdxbT{G@r>?A+}Wm^t08+og5uxTm+S3y)zBOJu4-~bKarHp zDB5P0y~(9!^`xy3O+14UxQtYUeWEyz)Rccts`b*5#feP&v9W@l9zx0WJ> z>}i;zD2f_>W$@pd7q)eH%Y;ickUtmgIDbm@B`1qsCXZwIq({B3v7Wv)THb?$|A+=J z@Gv?YDHA7myBCdfdC6;$0(`AL0oZl6NQOTP9v><<46j1pgU6m7vmh3N-_RHWzX8sp z?>S6-b>xuN0t)>~G@Kw76e5d}gTDdJ3t#WJxzDbAckX#rm{NRTjq4=q&U%j)E+PYa z6j?<+XTtt)8u{;&WUR*i%C$xI#E33bDSg*>WnK<4g?qK=BXa5@pHPBqo_{&yk5STg}W_P-GPA1YymG^?FGr!*ccLlh|Adq0=fzrKa|%GHNX zYB4rP;r&H}bK9YBlnpKLHw@Q-IzjY*FgTFrgIdFy!v+m}7&Vws=2#^yMGuJjSsv z4jU|a9g#=C59bf7jf>hIN)+BwoU$e?E*EWa2};FiC!TaHJ5c#b#JUsFfVN*QTI%AD z%lZ8`>UJ4?$^rT~C=6C;_zPZ(7Bq;|xPH2mkA~$+mJa(~Z}4^hlJ<4mOMgpy={uZN z{MJJs25{VirO5;P8zSQd+DRbdEj?!LZ)i71-QWa2Brlnc zW3N=ZJ(WU3h80rm{ngM{#__#^%5ApE%Ww! z@fk6*sEte!{rxQwA^R8?R4e0-Ek)r+IAC>z^p6GF7M=nzWvkz52Aq*B92qGxf)$J4>8JM_Kme3opcDd}9$S?)fH~j(Mc{ zLHRa{OfBz-Tw-IJ)!Omm(2 zcdF1F_~QNWK~5%;*WH@Qoq2;H&I)E%6c%Mi#TSrWlyHYceD&UKxX@)L3FPqC6?L%K z#pGcwXc{a%{h(gMVS$0iy@4E@b?GPYSQ0@VCWA|u{VuF~RlCetX9)aITs)E%Bf*VK zJsJMeRSfqilA(?$pmkG$0jsvbP5<%^xF9ap<=p^K@fvH~Ql|s}5vkq&r%)soJe8x| z?Oe&Fz8MiN&k zA5Ed(0EO-)_Ao$9`4SiM*itYwd{m5XtOzAs6+vE~N)aIjf)m$$!&v8PEM86@!t2c{DRW35y>pazo|H9?(<56 zW0943HGtq8$>WgnS@v!>FCP)~xTa7DSa?@(2`$7afQKJP%~4B`A_KrvGRms`3t)Ma z{*T`zSUfZy3}UPPpPf)}Z_K5UOQD_${7H*@#)Dd`8eq!Nv-PsYjp!6SJjKlPKKEEM zdM7%ouWCVvmaTo?lc^rJtRGYQPtYBuS?;hgtfg3M zYI~Wq)nknHdu97K_$;T@h#}{K$n7UDY625k-QYt*N<{z$DSo(d8?u;xgA&QqnEZ3` zuZmO2)u%0f{5*4kCqH&Tcm?j}@IGK>ox<#>=mt#Anid#0j zqIr_ZrlF2lYL*+E+}HbR6R7fRox`%(t$G66iVjiMuao)%GBc8hN#-cr7|&VR^lr9N zy%pWWV?&*;Z{c_g(!TjW^Z=#;00A}N0jhd)-eOaM%r8KAd4m@XDIMfQ=Tfrpm8%!n zfjnEGvcGH)h(k$@SwruG7|g8d!66jWB2*L0nS@bKxuxIJvq91;|U!>Bo}rp9OotP|L&^jU|wx1xT^sfi_-1f zA^r-N*S`n)_g!Qa0+l-m!9yA@nwx>|`xMu&H@PiA_tEZwprk%X`bIByT^}!aEzBcJ z-aHhIiyHA~i*<|lZZ|Ra{GvQ{oEpEm!$V6S8m#~f;s@v;lAKGUTH+y@E09>8g&z#M zt>X~^y6fx=?$NiT#$^5ZqCk(M2wllFsgtA}YQ|n6$Jg5FZe@E@hHW(VTimtFsB417 zlX(ZD-1Iz=6YXrFvte8&8W+*tIN_QsJ?$*7ATz$pF?}Rz5+HHD;T?fmmPQA28dDnf z7&EWCIX$?fjy_exS!sEA;b!dHwB+^X`)Rg8kGjvRJ$_#skv@Nl!vn(f8$WPVv8Xs) z%K|_G7Y*Wy3cZ&QsO{rCV1G~(JeKX_FHOuJ_-KaMeB4M{aWwHr-pjj+g2w|dNgXIB zi6}NF7ffX1jvCK?H1m#LW}rfEpi;JyUQ*74t*`lwY~E3Nyma?m&iSVG&u3YP~afXNihsSy#>%h5QGpdgf8JheI;Om=qQAZ zBQ6~sIEk!&!%V<1Ur3vtEG*K-!g+&?8STzl81J1c$FQ}T_{_Q1ZRqJ6xOqKPP(N|Y zONTib&E3RIp_6B@az`zt!4dlzJeKbJWq8vZy(WJ!=g+s*Dq-G4O>7Iz{iIE|4D=Rw zz0-iKT*I%Tecny6pGRXbX|nMM1MBX3YFLxrS)fMQ24y3oxsskW5k83ekvE0WjJ|s* z!yW)ARtL7MA4BXboDlU5pu94RbB)pR=nk$Uzg-$ zXZFpWdN}ZPOnzQg{}V2^G=mkA{-9m9<=}IAxxf@?oQ3J5mGsh=2LJQ>EXz+VQggu7 z2dLlm)mQRE;5t**=}?_PRlM|}yKasYx7P(D1*n7kId=qMKL-h> z0Psvia%Z>84)IPjdPWmEKc!S~ ze(!g}TL-e+)CQ*J@ESjq@7{~sR2fiyBj$wdIbq;2K+3zT>unoFF;y7f;NO6*!gOK$ zoTlaXWM@sUSNn&9$h|#12M zgMy_L_-*4tT6XcgM#5VGxgNEKP;=ci_Aj5hO%Hf&`H~%vv=^C$-_gW8RJ zLx-ku_?~nz2w^1>-(W}u7L3!uPllH>^5V8O0NB1s`)Y-TBtaotYVsOht0AHiQ-F~w zK}9=-K)NdMA0miUf0#eSFhXB|rVqMIwtrsz2v^-Ce3S{V7Qlo-Xh4-b2z%(oF>?oP z9HhF65wEp43d2gg-l4E+np?L$a;sqFA}`WGTVeN|+SK}05ocZgmfvbHoqU#3D>Hbl zy6r+O`s0O`J?YY9Ue3*>TmrjUy^^=5nUldD`yW#AKg1rC?*}KDB=0<@UW&DwRa&oV zu;%nN{Mb|TtfvfBY;)3SRz7D;mY{ zI*L$EiAW0|tmqpKUGS!AtZIOBL&-(BpZ2InU&REqZi0yrHM0Uwa1P;`Q_^_7?~C^_ zhxgCLol{Ti%IfU1b&Zi9PI@tao)82Q(zl&c?a4(rPTRCKkQbBd4WCNr2^5^*r;t#%uCH-db&Idm& z!F9$L2^qhQcAnh)Owl^+m@L_opfg0->$|x^&N*Q z*yxKLWlfHphDT$(i6_6Hk`CXZtzVlNC z*KqAR1K7Uv4z8K4hnb(p&HGA-cNXZ@-K^O8vS?OX>P8H&=DZOSw42lBVSBh&ek0^H zbLHwzPl4XvZ*#$|l66`F+3CJG>l(eIa;lzI7HT6C>Gt;-dPl$V&Oz;G1Zux&(4L{` zIO=qlf>Gft@Tc9x+08raeLif`@NC>Nn=wO}dS6BBbqIJJFbf)<(U?ss?Pw88u8QZ_k3;}dNVE0Dk(fS zs?jC}LHjo7x*O0%1z}c7Tp-1JF_=;hd;qAyf(XK!zJ#!XTqg+8ED4GRI0)DG0fDD^ z9ZuER#EbM&EDY**EQlh8bpyVG<+@)U+eXd0a%rv?^V6-OMUrwE-XFr11^wkWLI)-h ztLFk0t8Ma9@mn56Q4?c{y=n4bNi0g(QQrX}^r-BXTrjAj;HeVp3@ee=Ec%)nXc`R3 zn)IOeC0TG=-u&a;1t2)|;P>SsUtn1EUSQ?#+F8l7t*}M%+Gkh{E`1o)!bns=0amhZd zwBO#0bxuD9cPmZ`C%{qd#|xDWrhr&KEl`+h65k!--vNtoi&tuTsXSGo4I`kxmj@PU z=<9FHHQocN^Y5wj|eTBYBPz7gJ=1gMB~zr(IN{xH2o?1wK~kMoCZ7-O<358xhI8INAq<5WisD7;Mde!ooI$xm!^7H(dZjw$ zom7=Hs{yZpJff`@(EjT0?~eyNxE@Xs9$l@8@0qsS zug|PN5He2eSCj1d*2Vd~&3Pb-$ydWfPY@VZCKrx3tB5*o`W>uW$Z)tHarT=PKrMGD zNz&zE%5)$zebG^407Cx{-0RZWeX`c-DlJrHxbD2u5zhkuCk4}2M)+1$kMm0XsMOPe zIka=0D6SLGr*B@2%aR&BIJCyaP1#b!DAh5V(%B!%bY-crczq6E(F=m}e#Ks|&)*Qs zQ*5kKvLE@QU$b`Hzy!CHJNrGLg1d4Sm?A*0Iyi9DrOp@(up;Gm>GesE+blLmYs?wq z>XEK8f=TtrI@gWNCm+T}6TrfRIIx{!0EmWK00BR?pa7}0K>4MF*DPXy6zrJ-ABT&>&0g{8F z6&m(Hh767=VH;$!ErpVSlaB|QODvJ<+Y7-{>l3A~jEAtqHh6}|n_SHIP*MEJ#$Rg| zFmQ9Izmnt21>^&#s^Y5%^LTJ8@V^dsU~JND*}lqRl04lGMDJ7Olc^@+H0T3f zmvcL+!So$$uV){qnDz7aQY19|wtoMP_UhD;LzzG?^c-SyUG#<#fu^n;t~37R;4f*( zJSZ^&fBcfJe5U-jtNi#5jj0Sf>HYLQ^fPYeeYGd0-4C;==rR^HRfA0xc#<-62!FNF z%NQcZ&n}+_($7lRZSo0@R2UI1DE}|Iz5*)BHR@WB7<%YV5s(y+l5Px2DWzlp0cj+L zt|3KIq`@IYxyP!X1|8yWN#?Kk()j28Qj;GJHFb#gQ8=g zwu*Tmh5Kn^Hjhz*Uc1?QPHoOq%q2d=r-t=U1&2a#WqFmajh-i;H1G@6#8$|>i8Aj( z8kyJvMXG-y&WNSHXX_!^YwYKXm&D-|=WEi})ZM3F)bjr0E2kLBguqD%05DG1>Hi-B z<-hp{NWw;glM40(i}p=fk()`{%BiouzU|7PknL8Y`*Sa$6wg&=2D5b{{gM%B7|LQ$l_)PHEd~>r$AIftp*bD);S?UNYcUpN!{ut%^DjDMq zTtU|{xzjc9jcS)xLNqp|uTP`K!VW>z>CMke|Fl%do(^w|(ZFvLf>Q));G5iL^!SPN z^3zzmNhHApJoukR!NxGeiKWVctLE80uxgAaIKxL$bvTR`=#HO6Xlx#h8z-Rb3UeRh zxEEZ3uPcPXE)hiUGNQS-Zi>^n%O>Y7SBr)9m^IqwUy&|9Z#e^9`X(trgr&GvSwVq7 z4gUiHvSyTjssaJ3dq?}6#zss*IOpp`G}Y5D@u$qQyy$pA81y1&ae4*>ShWV39~^ok z5(@{H2?>O(0Blp*-Vg~sj^kCYtv0S_LGlb8eJLr-u7MWiWNZ`97Pq6>1=&bSAA#N* zNVQNsZJx%z^YzzbPZ_Y%k&|`gpv0zvLa+f(%1(rCN{6hHPt7o7a z{X?eYKBhhrz|tHM8aKFA@v{Gky(z7T-N*?lmWo=;DLcHr>FR953&~Er%5QY#Hs`J^ zuikXJn&LF1NSV*GJ97kHWfeO+JAb({;JsvQ*Lb8;wH$gYQiIQgZG;u^3jQi{=3n%0X1uypHDY<(<8~6qi)KKSEAxboX5! zPUIR_c&C@7)?P#fz?x&yvWD*5zM(npzLu7knHx^d6+9ih#*J{i0L_PX1EvXi8>Et3++Ans9>` z6;2?AQ_n=EU+-EJ?4}_472H$3pvtx%*-uqIXbgzwekMTovK*xNJjqnRA+J!(15s{P zwS9UxOLs%Nna-Y9TE@Ke-`!|Dnfo4{S|$@j%#dv5=&1-EOkpBactgfEVXcfy`YbZ^q&)Uv)Hlu#AwCk*S9 zIOaH(ziz#A(q(@8ChaPv_Z%p`!&MIJ36`7<^+jw5X=ZaduDDt~6)VA{x=1PcL1vvM;57^VPGw;^poUcLL zar~)C86cp%NOWbW!6=7h& zdsthCSu;B%5)G<3rT2{x+HKHpYdhTf6+~$Sed=59x;0`bVs{ToD2_uk0P=qN6RJR) zjk*)XwOLcKYx*2E`dB3n02=pvLPcF~v*n6UmbGT{q`(7P}-2w02Zum?{IBC}N-P!GN;Tb!KEtA4mir z1k1mpX$Hg;|1Y-<@PHaC+=>{#&&k0W_`g5_i1)o%X2f}#B<H5m^^nCuB z7kmhurNwYL{+ZG<5EYjLLKsnvdoP&8R={9D^7I<`bbl0sh2@HqdP z4Lp}W7Hs)mpU>|DYtP9~w}u{%8~ZlmwlVl#ILgisX5jJG%A6hkxUQp>Q-6OHf51M!0XED_T&AZrDrxkkECAx*3#*`{WR8~sZbIR zLZvC0k>@3tIH51Ip7gZ6Ocl_Q^tydu9&g1c4Y>w=l-0*xFqBw0s9WC0Vg1Llti&I= zHlDhVGHfvWn~};)tZK0Xt*mP57Qn=_LE98~RY}v+B^VCE&Lo&Xozqr_ouBL^03-Td zU?-am%!~JdhwlT>TUJSfs$26pYkMdw1UKEtYfsUC!y3|dcDz-<%!>NdLDN`A#%ClU z$3ejd(uz5*_r5Upf(hZr@wC0}9_702WOL z`^MWDzE^dh`%rZ>$NgaS)vXS_vf=I7dTm7TzU4WpLX?=|GRedShcObte$e^2?~u;q%_PHW_fqJA&I%e)VVEzJhU$NaZ={oh??jk26;C! zQt+Foykvx|gfOsTKZ+1W$%1F=YksV(JOIAsTKFNp&`?=3?3f8uUMD00$8*p}O-)Uq z0vc{R`&rvPKn!6Aj8J0in@*KB%vk)jpa6%({c0148kucKT1-|)B#196bMN7G3d?dF zk1*kO5-QGBa=i-_DGNUt&Pwc`9Iy!itW0}8keB~~RB83l@9ekG=rs|FZ~ujSLfH7l z|BMYX@Bitb8st=viMp)Mq8L%@2O*jt2tduDmi_yiAz^S-?ajLz zT5@&Le4P)$WvE^#D*$<@$nBXUOyk&8M-ITw0W@f3b+wBuy!9z4!_aUf52P%* zxbgV;kqs7~5hhNjUERw2(80K$^5C(L;!#OtwDLmwU^&3Fy_?=`hS}KrC|<@P7^7x3 z!O-;?q81+}Hc{qv>O}6Vnj)-vAg6*wWW(368{nbYt`2F5219vPdMA@t1skLj`jAxa zUN(r)<@^#x%KoV(G7RI1Y|Hl9;>))W6{RPz{wkT(1VUg5Abg(f7NlD!UBAuU8)z-JF}qB<-*({gRw1ub$i__s+sr(1A3XUhU35 zMPLp+%oO!AN8@1N?%OQNmr$mp({Rvcgp)N`pszYl>-{oq3a5o1JCn9` zQUOX51e2vQKSpsAr2phVo-huso>%A~wqgt#2l4?3L!=c#mEqHJR4NEFo0;0O8GyZU zwk3n=I#Qsk?s51Ni6k`!Q1Q_crU_Y_R@VWCjs|eyWboYNC%_EDP&WY`O#%pC7zb03 z3EQK09LUAH;x6l~Af9(L;HmB%&^+o42)TY^{HJ5` zJ-pXjcz5yFl-jrQ$TD%(=oO6IA7O=QB)B|Ds}8YhDj`8IHN#h!64GsII++@MJzAzl z#K_%{?=3iJ-zWipAOgkk;;1FUaKlb>CLQC7BRr#LGST^q*s%V0u-5~0!I&}UslMHO zedR@nbKiB7gFVeT_5vK<(NNhaiTzL|A~_GxW{nbefclo#Tw^o&gaN*Q*6NU}dc_8Fh zap74i3x^vSZ;Avo^CYfUcFk{`%QQAGw-wZs5 zZzft`EcCB~8#n>rl$wze`zl2zHS<2-X)&Pkv#!&&>q2;KWgrCSD;Ny!5p}3SU(3G0 z+S5~0i*(+ZGi+CG!=r2(l;H?+cn5VLf5&Jnp)Rqfa0>cpbC6u^1fJq2(GB1*&#T2T z_CfY0c@Q%z8wrLrB;LK9K;#E$fNY~L;1ha5+fy_*%oHt%Q*--;GC(}D?dO|bU4zJi zAD=gh<3OJ2gJzj8B;l6McmHU=+V~?3b4#MPz5`aXYjR5L!^7T@)23aSBxFW-tDpK9bdYAbwBe zI7qq7p9PK5M{EmhKE!WlDf5fJj&Q$1>5?o0|LKzK$4KuEs8F+Z4V>|Z4asiuFdg;b zDDpeRg7pUR{5W?MA?Als{-3~}T(JZJ18EJ#;SHI$#`S_=pw8qG04KTtKI;Gt+{{ui(mm-^hm?dy$JNIB9fGMD(SH9um#!M7 z9`qfLUa3sGm>e1rlFBFq2uvwxD6@8a`ukeI*gkjh-lDDT z;m;wbjiQ=Qd6kw(s;0-yV6-o{9AY8wK&23EfZU1#q0MaKxg;!*Or&(VWJ@x2!xvHC zK?%SH6_l1O|3hlF@P0Ugb5&L)_D02XlHJf74WZOAR98kZPEL}aB!oskY839YjNE&# zSR})$gw&%TunhkUh_i4RiwA;W!$lx^GuRWoL1j7JB>dGsY*x|GMDTCb zsRKzrWkvJaJw>3v7i9C@D5FPqm?+Y*a&EawMZA7 zmm&ne4c=cUM7PLrut6)M?=aX!$5N&h6_*5U)~G*Ls>uhsZ|6*XATqaH$)-)rR3ZxY z0^2b;EEdB*dG9dD(a(>-w#caQ`M{mU!<&(Jp^FfUwBKPDAY+F()lrGv!F{`KP3!kd zk}iU^+MjR!&sPk{0#Ye_m`Z)GeJ)uLos7my(6KV-U&D^ewv_J~U%7rf+FR*)ASdRP zq*$wraO$9B3;i4^x&&b#MI3BTl*ua^|AvvP>roi)p$7Bwo#t`AuHWe03g|v4y1(R> zX*)0cPMbf3q!T9W$B&1T9dn}tM$JZ8F+8>V;|5sQOO%3!X>U(T`p0sUm;p)-!~HPr zB<-M6Oz?fT9J-FC50<-4e_KrH=YNdkx@aCE^hbou|DaBhMezXGN}BtwKZ)U?WS zNUqWJnPj*ud;}z8lDov;^3L-=CQ`XYgvl!mmC@+Fmcgb)i=D-QO0mqb7!-+g~BiOyCwI9Yq$uYVlM z{cR&K*)_0zJwx4i@Bc1@4NO_JM%FP6Hv9TsK0!=l|IX|Hdg|zZ_o3qU1J)M#7}hrP zQPvl6H?9a{7KFGd%cv{(G&$Z+BtGGIPgV0FA#<1gr(nyoYcb>0J=iukYs(svn{MQ{ zAz1!lpQw5>!K`_8E%1+4B?8_Or%(<^ARzCftb?0ctTonvULm!oDW?fS}1OWiN$jprJ z*zaMl@>!0-OP}EV{F9|>3n0bAO-vo^BnWerT@PX3E3ZrGE;^VXs{(s~41Swfsx26C zbv9%f4)Ny3K0E))bY~uuH)EObJ5Y~cM0j@!VXzwI+P+qPpMu@LlGHC#h$H{+-lPC>8*GlNe{`Z?@y7=)0D9n_ z*Ft7|6e+{sy5G-+HvO-gn(n%53d4v{xgFR@etXREJ&XT65ES*G~$oO?) zYkEuI^(-OZ;|P7_@1JhxSdBh;U>g{Z9Vk6z-Ycn9}-%z9(JSv#H8 zTWIky6OW9%96hDm;N^wdzlR>=mI476l8a{~{xXF2u!B6+(#< zGfZpWfLyv4TDmJfrL6Lh^@6Z9i8x%=3zNvGloFj2(gvL)M&4ee$75)UMrAD3?RC*= z-!+$evBdraq~45}QUbBYO?42~*!p@r$s#@9olE-@nUpEO3Ec7ZML)@Cv4-wML#0Sz z_e0m1zRG{>@8xSYn3q?1u-jpsYuC{9YLwIe%RRWu&07wBCwD^+<~%ld2wsQzuxPQ_ zCo*P2Q{40ZM=n!Rm!gvT*nCirudb zXE*LN++GDNB&o_cw#vRIMLp)q|w*OX^*7*m!qWQW*{$f3okUEOH1?R2gA}$n*?M0bTMYWc}|%8*~)hx zH7;q|k<0#}z!AI~4KS2!k1ql^CQRTcdyz|w7Cw85siFe4ko7~UAqa9pF_07;>QGH& z-~83F2tkb=z#qgw#GVj73x*k#Cn-72bh8bEb#+`Spn6Xz-uiIA;tl; zrX#4leIk`WX$$%Kp4X6uB1M+T4b=os5W2K8gib|6n@Esc%=>aGkK&j<2d-6s#xMU1 zUmO7WvP@)JX)ilOsn3aZNLj>1zsg9#h2#Yo`a$4iF1j#yhN+oBuooeBGjm_R@dEQVF=$n$9yWe^)7?!VEtytfOocSD|2))QM5BL*fs;qrSIpnF4w=! z>BWP8<;`NiiX3&9)-uVc`|vK5aR3fN79)V|V$sGynd#3--Co6ebpA$W@VJq{`%kZ% zzX+E>wYo)#s7oYK+7M#~_azYOjCYqAilClXrR|^RRrf3F&iQ(W+ikv*1h3t13ge?< zl0e{JcoWkE_{5P6U*6KPgGW$~nmmV#4aFw`T5U?~_F&@nFWc?9p!`5-Kq9C_d^9_Pzs;3>-bN z@WMA3!wO7>j7(PR#RV?`pIiS55%h0*hy<)fA?*`apxbD{LuadGU5DxE@`aWYg6=&9 z^?=I4f+m-skSQ;uMuac!qyp2&^SQrkY2s<@YJj0iyyw|ip%H%w6scN?(iCVl0n~YN z{L7dFJzjar{ND^qaB9;|O27O;Xq=|)Lp$E{>NC3&&E}bq%(5XO5W58*2IHU}vX%&m z-N!)=F#z|GtF?lk1z;BdSr|u#7{>{qB71@L?2+V;AP!uHLV(@o%JGNx(%iFT3ZZvR z^dJS8Us-T8B9E69p`p;-0Rk#}$#o5o%uJeCEIgqJ58p=|c2Ga!z|z!xL&1~nb-K?9 z1)_}<+`)xWJREd^&?4x!p<<0bL}bL8Ccd|SXn(09$Da63!_&@ZrOlQ@nNw_2bDv!b z!bh3tdJAw#%S{(3-CWWM$-n6fv@*dXHzW%$P)X~Rd}p!F+Jjy;M2g23#Jo2&$eF0-se zXNI@T{zzh>DgeDLcb-_>vfLsAdd-T;z)cDq)-XSz>})HRlNYfxrYLp|P1*V!$ADGbkkt(%xjjQb_vj0&-_5U(^D&o4bfz0qr`c74+rYWJATs2d8+sP&nmesZ9XV!qG*Shv$Xl~0r za#V6$x!aCW{`0C=9d6GjZpP4JyckNKOr5}N)dOj0+Jl(6ApT}-3Hni14|~U!svdBw zKLy_~KKb0V17gvKe$u+IX+>x-CL^eFHR$Eio@pclhUH4OD|~UiOQDwOV*dJyozeSH z6FCYLC}N-BL3mBh>|9Z3H?e+V$Hy1Yp6{ah!JE}3kKTQPskVhU=RAzp%Aw_3yh-)d zIV2n z>_A{JHQ&fImolq}GOK;KO35AM`-a_GlPa-z&js1(6E$Z$y2MG*RN1^l8gP#|Fj%|6LL6&o)9Ry{p1IG%_gX z-5N^^O7DZ&)TlS{iF^yBGqZV|7O7{FoHn9IJ^Aq81GS_@QhjP~f4SWCw(4bQ2NMOC z%`iB8rs$O3DD+F2BIuJNC)vhaV|`bMs$2QKp9(%YPsidBOC#u&oCYLMKGKlWQh}#9 z{A$k+@3y@x7D&4QOcUq0l|Vx0DMB8G3#@_Wt7NR5$Wvh$66#G zq-F4LIbP%7AM*Qms{5bi_^RED8Gb}R?YM*)b0@D4!=0&BcJy7gmn7|~XI5sGcsLm0 zs59$NGsEgEYBKMz+p)&oWxd z;17jf%Zs)?cGJrpRtpOwa8FH`0_wvzoeJ2QA4jSF~v*zJzBE0MU7ae}_PuUTmIHKHG6+D=w z*Qb0_{^{_;me5uXj7J-HOC14T|NBqxW$>r>LcMjMVk2x@i@Za-`(&w(Bg>*9E|)El zBnn!XTenT^={%<)o!>JR@lFA%=zuKYE;ic2QyTk-h~H0yg0( zoKQe4gx9Cv`a{>gI`kAgdTX2#$b$jU@o5JUP+Rr6xA9mI`k*@LBi}c8CR(QioWMU&622dR&e1Y+>pQP_j=Ki7|}e3p35W%`KSB zK404TI-b>9#PszdU3Nl%&Y2QJ_frN9T}&!Hl%@h(BQYn7yn*=&r{x*4WjMqQe<($5yiam9(#-kgOws|6hPjwj` z@w*j@2D^2W`JTy5yGhxO_l57k5MXwcSL!kQ$34Y3k-#hCuN+wK--7!K;-hEwMX)l| z(uq5%Q81MM>KjUI z3ndO$0M|YQ4Mm%L35!?!%R|T|i9MhD>Hdln zkYTK+3x8d16XazzofjE166i3Q=vDgo_y@J5N9A^2`}s5H01bSXB`3k-WKD`8W>9k| zYnZ3xnu$3{D61VEPpig_zF)2aXmDMM|JdD8lj^)<;%TJX#8d17z}l(Jw@sq)KFabL zLEEF_HCna+Chk?+dY*I8IOYcSZ!`{SHwa(Q@sM$Sb`MV79cr#Dvgev(s=D7;C@1u~ zhnk@uB(Ik+%}2cXKNjf6EM zag&{&<}t1SzC3~C_M`h-!Uxk#eify+B1PMjzKxhUawYbdA*t3poYoTOS3g(P*sV#f zm;dnzOpO>6>D5sw`H~*K%lh)GC%CD@c))5-4{P+_apB%4fbwCN*1cX^zsbOG4D#S< z2qnX&#dcvqDe@{%=pq)BL8S;u!JHg$Bj0wq+T_FyUYaK5{n-mB5;>>Z0+|sewfsQ{ z>?epPfZV*!Ct8LYnmk!#x8qnep(m<>deXn{r#i7>dD+!V%+UckXOYGWd655{VUT*R6N zAaBW%IH?DTsYUIFD3mUyYQSZE1Sce&Tfe#xw>7$ysr4!C8LmIQL_D*JQ6WI4gUlyB zs^~lf$VK^CT%QLRqu9S=js3G;atHb+v$;gEYATkbSN$9$ukj->1=9V5FbaO*1A|3i zW;e3f$tIY7>Bt1S?^)r{HyS|Qk?V2CF>d#9WT1mY7K)sD#dys5B&N_{!H_Opr|(LU zbIO7?NtVeHXpX4Jub)#_o-TEwFd!kD2@O7m6hxz1C>og5p+?My%)CEgJZ)3K2)r+P z(oDP++lr>w5_3iZ`)#kOSY873_ZZGB*FNXb&}g<9BA+-`VWB>IZUngL)wgr?D9L|T z>HJ%P32_mCLgThFNSygq0%cX*&&)T@8H@=jd+BwF7huQXRWp<58$0atNhspkchq76 z<(INtIi}!7i^2-PtA^E&NA>pW#Vr!xTg}uuu496Hkd1V=K!N(4wr~qG-=N(6&P7Tb ziJWIau!#`mj0iYkLOruu`<%Op1##%-m~WE1AGST~#Sk@~gFA;$#b@Qp`cs4-Tz-Gm z{UhlhTW9OO$-$QhDzN`(g_9qZ*j6YcZEVS~b?#+)MPp!XFK9x#9s-MJJ#Yum^ zc$=JlYj(Ry{kf4x-S=&Fp6eub-I(P_uH0ir!^)4<2bVQmKiYyVdcxo8ul!zq<2ih+ z?Q6|jVeyz~+y$2N&}sIqZT9r+$=9+k3<(ajy$L?Ic#?&qRqKy$8-$Ew&f>ltuD(t&GtLR&V6#;dBvwG3WK)j-QMz%HM?roJ!;zAqyAV{E-;1(<_gX;WGrGd{ zf*x5*v@BgiEJCp>fqKTu1^!z>au*g%tMpqsc5gN4TD}kyjhtbdStn410EcK0z7Rg?V#M?T0No6= zLoV^)3&9Dt6CrrdYW?1ao2{TDsd()cmuN|W?GdOs?L%%TQ0h-(gk)d5)4Lu-k}*F) zx-(vqp_JQVROG@(gT!A3tx*OQI;TQ+%atGrulaJO12|m<7)l)k@Vd6=p_0!-XEIOh z%AyFOV7pksgKuh`-{N&lEC)+Yh%24L8F46+%9P|SgD&kM?JsCa&I0cc=D22d(2@D?;VQ+d0=4q ze!YFps5#}MX69Qz&V;u%@lE<9WJ$pL@~WMaSv_g&lnyWA=#tpD1aH7Qt_EO2sT(-z z$~+LH-sjb`iI@SEjF}L#f%MsO_$bz$r`3h^-$Xgm9wHM{gubdJ;F5+R3Hh3Krpkic zRhY;z{(>q|N+N)#2@!%sgPWg#1Bizrp}eJggW-try+JQj*9!hJa*@rTQezV!KDvucZ918kX4$9-A&bv-9q31T zHv+GS5*|m3SxeQJy0*E$(zRi6;S_pMD}fg)!;QWY9t>;B*el&j<%XAm0(7eyK}?Y0N_gU;R5I zEJ#Cd&qz57Xmm&MjCN_z?I%`m&@gm@#ww&5&Ysg=FkZ99Vc;IK_+gjQY}8-Y>?>^7 z+bJc?q5}Z%Gm}}tOko3-u4MDMkPtyqo(^E*|4nI!*pwhdnQznO%R^|mNeKgGm8S4q zy2x>rBQxFku!>#h(EL?UIeep5(kglw^KU3i)Lh-a5euqqabGLn^p8Y9>UFzZM zJ9Ve;37y))*p(`;SKjsPuWpcz)Q?TuH*gB67x1X#Q)Q<2L}Q(JKAB6rYt-y(?A2qw zKd;O<{<_M1o;wEHV}`l+JvUh5ZG4PrJZhcF&DeBa9l^k_MbV$ZmvFU=>s~nTmpIoN zae~|%h>9H`kV;&Wb&SH2w+lq=zHk{_fHrkQno$Xlt~E3uMh zY&ch|x_*~j#k%9`J$j3F*wbG1UF+rf*ToR};$s;vN{lGj-m0zaYx?tPs1i*~cF#tR zM#O7LCk#-$`RaD*>X7|B!#jJ**=zERKXUb_Law^7h@u-kkGo?P@yxdisfROHZttti zU!5flR(Xr(dv;Op?_uxstjEtk3Yq@=YyM8tfY<}O<3?5Sp6Al3hwY&q&uczEDYkV{ zoXIrunswEcNwM91dcvPnJIO8s2tIBA~nYfH$6>UEPr>Voe?L!J*az?CDyEa!IgvdC-US!R>O~ZJqp~We#;|##0g@Vv$ z$Gd3W9Pj!XaC9+zJmRG>u6KQ%QgRsktC3B@9p4H(zX$h>}*=kiz-x@Sgn+{*m%Zpu~5cZ+y-n&UxEaS85X)E-B{lvEk^fV83^+asR^FUJ257s$?|Yox9W&Lpos9$wFzI)Uw;g0^bk`PT`R8xxA#K1{yd@K6h0 z8^^L4A6=rO8+GtjbSqC=uAZ#}#J*U*Gyff(P53V-3pcB2WZcD8K<<aPwAU|fFH>obXAa7(*9d=&EmEAq zr<}!>H$5Vya%VR^Q-X(i$SSx<0vl0R4n|_q1Ew&P&jHJfp0?*D8VRT(Llg{Zizc{o z{5Jb6UP~xtB%7>Eu;MTn-TgpVzn~<3f08)dpAraT*NiD-IR)26Y{B4U?zeTd=!hb9!^eP1PTma9WEWok~Xi zT}yDK76o}<;!Wi`jkYIP^{Nob@JXf42tr8q6ONY#UCQ&RoZSW-Ph_1i=t5!6}O1Mntr^%(qY6Wjr8p)=8q zfseaPkrq2cS;__7_}5=5?nq?F02KgjdVHDgjS?8t?-@{1sD<`2xc?xwrQ9-YM#FBm zq%`_c>g)G;K@t-ufFC{*?B-1O+>{8-=mnS%pAs6Gmg@SS_2p7@*um3-u_-44eOhQm z8d6(5zoM4zD3=`u3DFD-eOmDD1N)yV>{Zzl!13od>FH|!LpLHd!DC2IUAd zS^J#in7bDdKf?m9_?u8Wj=%JUa3R1RACAd3lOm~((d$z^pZGJgksfhimFVoYnGPq9 z7L07zfpVMPQf)iSS@PCbQ_`PDzg+K)^SVc##Kpw5mO=GO2;*VZL+wu=Ki-rp6PkAY zstHt+DZ+Nq!By%p@%lco1RG;L-{{4&joOv=+jHLz|5~LT@ziU?B3Q|_E*_TnLif6` zP`_TO{e_(6=*MsQ`<5UX@NLj} z{X=}J8bn5HhpZAGyd`ZvHJnds@tpjJQf%hT<1|Ej3)zM@oDNx>%`LkV6%=`&5p<6e zB48C32p04t3~@$yZ4I4nVIpp4I)l`c;`+UpNf*D^olFb3R;E>_wIqVIDqv#nPJ__N z8vEu5I$=GjbC2pKsnw*#{XuU@EskpEz0BRD6)TsGuEP%$2Z6oIl(U=Ht?%w|`Mw() z80fXo2D(k9yJ6@*5pd!Ws{JT9HM9ByrDNk6$TuW1IrfB6ddqkX}NzN7YjbiyWKuM0K{>&Sg zM`#u^F0aNp2jdd&LL;q zfZRXIfgWez1zTZO+S%?4e194#e8)GU+Bnsw2Q;+ZN*9O_3!D4Pt|Y>r95;Gv>w^t3 zk7$WL0RtLMWa4Q2O9_VjbrFO2?SV9Qv8_HAF-zAfyDwfgI;L?R?@g6)s+L8wm{+** z|I={NKdlGDdE-~|64q%~+mTQG*9o@C&n%ykg_DPe0++97TqG3(V$p|TyDFyddaE`I z*f#phn#aiD)VjZ@{sYG1Zru>fPM4Wu7TGy$d*^X%GenOTO7}~#k6d2DjV0oiL=a%L z^@G&FqM9~R4-ETYN!I_pP^Tw7gABC%Yw|)r9_-$&P&p2cWs{j?qL9Yg2n^vO6%)k`;atfXx?LJIK4Ez5k&zO?v0a7< zj``HMOjc!mkhI^dS-wl=xp$iqKbiy&xZGFN_eNIKPk&-aR|jzfpNxj17hFaZZj@=u z(g;2~X*5pzUCeD*%~EjENbu^PQYf(r*xlrjixY=a%c@GmJm!>B*C}?2rTe|?M+cHY zw${gZ^na{4n*QifJ}}$lD(T%!61-=|G8|afxRU*9)J&tf$pmc#?C9Nzf5cFLUPQ{J z6MMZP$z9UC@1!R>dirhqNz(<+eqXfz{z^kMq(2$@s`$j0VK4!@PmHA1^|?-~wDPWM zay2=4^CP}yo(Qj#_wq|TO}ZU`s0Fr1!}d>BDZ5cXW0Sa@?ht_AGe(?te`;rW-8r?K z8sG21-s$Z++R$1ELR|Hw2ZPv}j#ZWg*Pp7w>dI1pkXCq^dmevCZN z%lE@H5oB%>#96uC8_=N%FNcSb>`y#Z-_So;?ThuT1iQ;HNPP`l_9MQ;og|HTwYB8w z3%ld%!D>=uqD$nL=f0z}w=|C$lSj?w zOU~~dZrC?pv+ZJqLaSb`2jIqSAMc3{nsm43bmtMc8SCAG?aJJOrQ;T)Y zzB6ib0$SP)`;M&!<+MMxfLaaB_UtTWV7u$r$z#z$lZU)$z*=$bhk(-7Ld2U}4o2_3 zDj@YZtt=e%R~P5jRk_~OZ`2nn*0VYJBGwYBk9elJ})aanKIR6Y`=cWhd0vO zm)s%k7davga>QM|emAw?r0q)mz)h!Uk&XC19yrqE6KV(-g(Cq7E8HQnZK2b`-wv>M8?gj(=rH&;nRCiVo>nwk40 zs5T6G3&gbPDu?IeyNjG-ZLOtNlt#S{3z6+staQQ4OrM#MR1(QZIt=q&z2GLQRvHDf zQXK5bpN#vh?Kc~2OxwX<3a%W29S^?*;SY;ev>yXd$M2Ox+~DMg{sd<*I1w_Q#BRlg z_gN^w;4Zy6dyV0WjkPkLpixrq}+u z1U_Bf#O=&%*Mvp!dHCKXmn(brz4`r@gDb8#I>k{xIU8JE>ayyCEhQWl+`M-HJs*2A zpU%<9=%KO7tDj#&Jz)5qk|&P;R&Y{++|_J;i@SW8jVT)zt>uJNJi6ymK zDp(fMVnBfX;H@}mqO1H8MsMdKhy{t-T1v{Fb5o2uH@shxH}aw3j>XDXsyU{`%l50+ zS63p`*JGZ*n@RcSq4ztZ{v2KFrc@EJ}GIxK)D7xy8GqWfDFU`MTCDt+$;5IgmDQeQ5fP|UaE;<|5BiVMyX z2B$yXgY4hz&|jGMK1t&S76l^6q$lc3bJ!}-hBsn4TSv0ZhDbe$0Tasfv4^=l8G0{7 zILKb#|Jr9CBEt!KHi?kVY5ejjRb*nCrWr#X3gYu@^bxH}JN>TW2ki9PEWSJ9=&hM= z%LY6|_Xpd=1><)m*4p0mm?s$KGPLBWXA~pzQ2WvDfLI-whR6xEnNk71z(_eU54Eq4 zT684W0#w?ANU>F_qhoUn{oN+?q0y22r|nC3>6Y4ViM44xmke)}r<8a{1RRc5`%~D( z#KeXhJ>3ZEF!?FgcV_A;Jmdp!5CjOo&HP39=^$Kohw%&?7m}}k{hsJHyJC91lHKpIyF3?WMJCk5} zvpNvBX6z}*=n57&?0tl6KZOqfyj#D*DQ66`o+jgU@!_u{<7Z_y`N1{vjb#WJ#F#oQ zHPnL1GGD(c(?dSMtsKy0alE$nu{j$)(e@O4@5X$awbDITT=ETTif4SR^}QK^*CngS zx$n4(- zaLywk4nsyh{@t`;dBZz7Nv>=JA3q3|zer~n#K?39>)UGV!w(;bqHPX&SHXh;;dntf zonPsVpM7O8zLO!sFUZVB;|8x&AdLOP|pTc!KE@Oj?%eB+!y=P!)G*zEhh zuC>;jbInQ8uZubGEQbf6LdEytS}Y%wK3K4S(D>kpO394WzN&MWAVa2trCu?!rtmDM zSNQSHrSgSgmtnxYTd8(!0ZIWT6l;whOBfrLs|cw62e1BYd;+la{x-ScHwq?+U|Loe z*%#uKPeS}|O%|{RMe-vs5RCmtg_5@<^0{8y8M>o9E51}<<*#}Ovr z%weX=ASf&R(A5_f91=&CuT;Mu53}L5)#q;e+S{iaiLqLx*lS&mG}R?hC2dt~dw$?5 z*J9%0_K4TE*67U~#n?mJ{xnC9d(87fBb)7YUG7hJl<3~4z;ABKtm)uJW?eBrSW#)ysv>;`%}Pr)$dIG-d%a8bzK?f_nFvQHKqo}OEJ|umedhfB4IO$) zsi>`o06V!E%FS)JmGLD&K=O8&QBHqf9O=2^Qt`0Kt*x!0x{h0v0W7?<0yus;MaTqK zSt?MrNxqu`26!r89gU0RQsnHYN=bs}O6=6}NM)W8goeFDY=|c0_`A~3dIzxY#a=sP|;D@4SI zXYun!CoW|HFH^t;(+3jX;6CFxUSPu#aF;*yaCD$0=yL(_`n$d1v!AA-(x1%7vErge zda~RWqH%G^p8En`UW(;^v;bt`PP{*1Ls4uNe7Bq+zY+s0ec@65)<+6s^HEC)BFnVF z!pHNweI(FGl!h3-&P+)W{#c+w>ip}mgi?W|2ZA>Fimq72LgRH*b5*7DJD!>!SqBjB z#K7^FR5GW{R9eS^1HIUE+Z#MMW`kO*Y}GnUb=K2Z&8ImG3Ka)3zN7^j~e!? zzphNnamQd_=cV^1TDb)#-x^e+JPeCLcQzk!8PrZ!TGL{wrAeiAig|+wwrV1ztx5x@ zaNejo;U}G98}3K>mr590Dj)vo?z7bYgsj_S7a3Xan>@T+<4pC7)hMp9b%uI21;>|b%3t4y9TRESSZu(XmAu%q z31FuR{QF`QsO-HLooMY!C8Hd((k4e1?9*eNe)_qF{*YU#+#X9VEQI)oOIR@Ldx=Y8BUUYm{l*uvaQq0&M}y-;p{es+}kr9`uWAKidI!gj8{ zr!+wO#Q04vyAXm8s(I^)&zIwTwYT_Tqu%Jd^rhb;VW8voqEZFFlPt*FukG|kg#4qt zXvunhrYY~r!qZ73+RZnTYn08bZfLQftv~vI$y2M=l|m)r#&*9zZ@0^(A%vqVj?@kN z$+#1wvwtoz!ZpBF7NHvoD-HwhHZ0xOMs_eEgu{6eVMy>FV0-^PwO)So;t3X)OxE?D zjM5?baYcF*%a-G8b#A%IdDv>IU%z~l3PAxWx^}!aYGoC#pCD!C* z<$GVhf1z?y0kikn36_kmGlWXHqFI9DYr|o56N?=d3BiyX*4b3CDd$kV=ZJ}|?svjm z5x;H0su1tBv)akoW-nUR&$2PudhYr0ZZAC*&LtnZuCLRtZnBxYUN5$JA#O-VcOMad zQ7QLVI3ph?*PF4^*k9W?35ocPB>6pJ-TQc@ncShh+VC6n(`m5ESId_Or%X)DZ03oG z1IetN24>w6aa!ZOTv$gqO2dhXRtPb=<(b*b6mXrKcYUMQa zTYZrqCX4fIL!q-@-MIw6(bAFj^jEb6RNI$mH>2F35f&%VP`a$sQ-BjNGZXjY;1it= z9R-Aq9v%t3(DetSdA-mhSE+qOFA6vT;xK-U@9bCO+RhcM&oe-kWF1MIN98_wu&h5; zfW8WinfMlK37e;VV(leHEXE~^`}e)1uT4aQ;+sq*DQuK}715C!^65-Wy5eDd3_&bB z3kj}amuZC==NraYT$FfN@RFw_%;u$6QLtN=H(j;@8DLO(dAajMkwPXOFyZTqz@kzH zmV1>nINv3f>damo9v*_~XWd{mi2zj0JC8O=EQa4Aazz53R1E2SE;M8!{-`1%B6iS3 zzi45xCrc1YCMKq3pcDT_Xf~LdvXpy;5+URtsru6aW%|Nj%U%zjjz&w&>6>x~5@hTG zPF$cOG4a)xd}GH>nTnxAUb* z;QpUNM@-J?-expi{-%!gKngJJlBa)n#hW^B)oZseF=~Uc<|*lUr#ZIxs;2)V{L*4e z&zR}fx`rn<9=d~w183DD^|Jp;ixaWX{DtU!@%dQWTSPed7ppgEcF5++(W^~XL2VSD z>of#3UyFi5rEr%1fmqAoT}0nIL9e?HLHy2Gt;F7GZl@T3E#hNVMW!YF{0Yxd-0ePY z?8N1M^?i6P;kpoOZ>Fs%Cx?X%SH3_^$A`z~^h}s_Mj2z@~JQ=a8YJayz=}62AH|MhbUYY6rqp_>yG|Aqih3HtB z+na>fpORlx=h_a|KB_vOHO7Wwc=i^Dc|MX3Ow*1iNBEDY* zTH-MIfr_|nJilc=uV)w=Z*v`~EcSv0V5(8fMjctYD{ZaGQFd zUoa}HX7HaXTy2%R?gA-%-2mUZ9wkWu`feF1KmU1c`8+YOCE=!35}8bGaUa)Gf)1H< zZ|mckfu}R4O#1oZBfZD6n${;nkp|FzU}G0HS^KoN`dr$d9WN2@jOOl)zoN4NFa%XrGArf^qcJiv>bLuorlzL$r!w0&P|%0oUR|vMt78HYe?bWo zxYxK0)JNZQlmBx4EL2ivVqkz)>m@U2RYm_yqAgU+=D#}u2G^^jBBpJ$(?#USTQNfZ zEeu*o&YO!T)~ZT}-^^b?$1ylRc{(v#97mYOya`HUHChKfyzZC&sehf%1UP33qUGh#a;Rl0*9&o>s~5uy1QRZF!Q4#Szdv5uub6(Ws;3=gr*mgNgq5%yrOe4UY`)=r zTqOx>Y3W-yN!XBKc|s%Pf(V_ zSLsK%?-gVkEkK(VEu7=2EaN6NWa9q(UY0-@MAA)4JcEahOH`X!82i4Y+2Vy7d?$}} z`K(l6gD8{r-3c-L@z{Om?yY_`vK9RTc=IVpsN$aJ% z#9-JsPj=@4_u8h243_r>|KUQDOy4hNM*&J~fGO;pRJ!Pz>{syhCvfnDnDnt4SlCzw zPYQ;ii-UnhEY^hB<49_M$_n#h_hpWV-$38?KoTOK%U0@ZNlGP3x~F3FuN`B+YGH!P z%fpkAAp{qW5>s@@bO=S3x=%3cb%e=!*h-CpniM3d(XrT(D~UUaio)}+n^y2qjjQ+z ze=qm_M^-srx-UbDm4`H9Wg0kX3d$9JI82&d#U<#vW{Xm9{Kl)TSf_$aKBguqKYS3a z75E~qQeME^dgrj@LnqDNFqZ^8aYdZ)dfuk0>V>WpA$#8KzBET*QO*2pZAC2TeMPZ+ zB7sgKb`4E2^WbB6js5AxmRJbQFy=S+!n$t&ZTRYW%yf82$~)g#>}E4o7eiIeYW?wn z&o`cDB&Xf$sRs@4`n?+FyU?&TAqE~uk%>gMhyquE2~BhGJ!T% z>Eg$9(~UY=P7Nt)DPV3uF!OTqH zSXWci(G~iFP?(A!=0V|iS{I=WFqEYDTtP14pJ%_+LLlVL`P>F9#I3cI*uoZ3Qc+!> zfFOg;!NC`sP-(OCy~*%{HsB2veBPh2e*@Q}z94%JFsNrhps^W;yiUb@veb&`Oh$$H z^>D7<6nqNQNgY?Yc(+35pqS*ZQ%^z4q3)+QaLHV@%b2g^@MXv?cvzAWyN074MiRMR zJb%A8@!2$uJ$Q-^jYue7;%zTN52S{0i$38yH8<&O1SnqbhF0781gRUB+WZ(%F9@PT ze9(>8Dnh}a%RjVJZPc#JU}5EcLL}5dwd~&-M>=54i<}&lz5cvsK$EGwX=SQv&A$83 zXk(#1q|=-9D9h=7u8|;bHr#`JtVmbwE80;=lm#oj7&{6ppVJUEdXQ|*(&^{XjX)EK zWat5J!Yz^U(?4|;7YMXrK?OfU*oZv67A$8-aJSY zN`yg+0{*W7+6YUf&=wx)ugV8hNKLveU($OR6wGLT+IL;ld_M}*o=U7S1eDl5HU`>k z-C?UIaPWS3;fMBR)0HFIch!n@#l?}m4>2mQ&SzdyRm$QYEKhN%>E5OaKqAizR>tyX zkH#`f4Cwg}r$WbW4-b|0rU&VgnJ~Z8pL*mG*lklkE3awve#iE==f&MkfJ{=I)QflX zhh8N!HSB4*JzcN?y*TU=lk9pej>0tFv1|PZ4Opqac<7H78lRiWeBIbg$@DzE!G-P& zMn-)h>tK*US11pp$(x0~io)Xcp>ZeUM{uU3Qm`_$zaZhOH9tU)Ip98nc^dMM!oUmT zlKm@{nW^rt;GgW(m`295jpb7<-Jp;#G8~nFbw&zM=wIM$+$0dh6Etefh%cccLG75DVy4N!VFyYR(fZGAoX`gDimx72S< z;7S`$C4{fr5)<0d2-exwft&2zHDGku-gP_!YfsUG>RNG&85XE{1TcmgW$J4WNiLby)vVnf z;IH*$@wu?s&2xtJz3Gl47jbR_xjL`arCS3HwN7w63mM_9%W-cCh`6~q}!nE-Bjj`gYr@_0?{0o zc%!aH0+=8VSG=BuUy}9aMu1TpKtGM9=a6=*7Rd)C$tcz7%>kpW zcp3v;`O};*puTnVM+uty7DjrJwLJWiY~$dv?~;6MI-j`egVFZQ&o$eXSijxb@21~k zP0&e6r!HY@C+Qpww*{EXN%bZEvpzi4? zp=6fs)1@-|aQj~0N%3j*!^e>H)amM&e7Fqg7*DvDA%qkuXRDHMoFhZ&vwl>4osrzL z8duJy+=GSqI6jhdB{aJvJp8?=q0zxhF=Pvn1+~ycwcrr z;OvYQxpOtx9!1m&CwYDDiX&fn3|Rim_IA*tbZ6?v2<`uN0kyZR14`8J>c+Jr;emIJ zIHi;-6+h`j?Oib9?hvt5yR8hz?l5J+*!mA#D+uHR;2PG@&|dZhA0vz30?o zcPRk#ka$Msv%L^oK&%KD&AW&NviUt?RQyIzOEDX)Nr`wI^6&0`4HIAaTw#9md>}zT z;$NLl8T_5a3r}_&GQK<)N}Ahbs~y#Ix^2=OEA*||?W&`#M6Lbh$JxeBXQJr}6oBpj z&g3NP?(^(yw(*F4Gd5C%+zLXhtT1%z1^;o@S*7G7&5b#ca`)%>A3#JJ9=8$(3dgZt!CnSQ<_5K~D;ijd3X277mQ19{rM=l$8k zEp00)^JE$_>?mlmu|^;aqqkhEn&5^KjmLJjBX2hG)mz7lLmh-|qyk3eyl22Awo}9H z;1}B7O56N&HGofc7-gbdpG`C)(b=2XGKl4n5A7%-*FUzeK-^Qn}ZuTlS6?D z0O%$HVq~9W0s<*?X6D$0^4t}%h_W*TcUTDNNI9L28Jt{t zQL6gsgF_xG32{?sp$lJ0BnV)kj1u3@HHaFy(;Q5+ zFoKH@F4UjelEHCWP`yGofppN_ZYi`o4c)kEWzq9i$my4)I;-JxiC$E(a(8C-X&Z(% z-v=mw?@e~|k%ajCZ#*0Ci``}6dyv@nZCBuTl(J>w9)wo6mk|R}N+SPo^yZ7zBg{7W z6AL25b{4orDNi)PwMN(p)xn2G;l&mbp7UMj{#@f{^A_9u8_KA?+Wrha3i5tTUT%Vx zPWmW6+{hU(iqu~fKaU_q#IFvcSb=2p#Va_d0;r01ncVMsdMim@Cmv3!eNv3^5*zen z?`?kTVTf8V)!>e%-{Odls3axf4i4sfJvV18s{V4xwyZ0kE^1fDz2L%d^gIs>i$x?K z=&6NbA52fb&|ybpeI7jhyw_!GcC>1*+~#;TtTS0Un5Z}P3teZ_R1%kQl|y?3aXi-a zPyBFjt&0P#->CXil%Z{wGcW$&R8!f+0|@^I*!F)@$qEYPYwD7~=3)HWWiBFrM5_7jWT!ZY?uv(5wHr z_bo@*tJn7IXc<0prn#vH{JS7dftj+X~t9JdDWY*9+u*e$ejYlQJd2P z9d9H%CWZLR8t|mp9V_F*tJaUYiTRkPnDV#eLQBE*Z~{$_r(&lcAzabuS&)red2-)} z(n*y(M`O%YF3%s9@9XQtTk#YK6>U~ZIq(pM)-6eh-4A0pX#PFo(D7yAj`qi3@plut z2U${Y$6D+Vvt#nxP!j7OWbBH(6i=6wW!Yz}Mv6z{v8oFwYXvQ_tf#7;ujsg-O*5%f zC;V%aE{hCBiu4PyasGpdl%qom<3mB0y&WG07u2H&Mj{JQtE=B@vtOO4p6h~D^sRuT z+cZ#N-&Fx{!o}$<01*)pvq+kyWAj5JFfeC{QU$hz)4?VnJZPIO8lTlra(gI)Poz@` zPTp;AA~e7DYCl21&dsl#u~DcjkATmAI7f(BOib*MKw8lb!lw)a#j24pm58LQPS_ye zYgM6)k8Yz4*LB$>HV z{-iz;t{EQ#P1K9cZWjAL3gbgYOv=u8Q6N;8^swKUDwzl&K99#j2=2rmCi@DOhL9*S zn>`W2MY8zDb0zQ;jduGKO<*RsDS6olKHKMR5?6lwWZl_jz=(P+z)y3r|Ehd$I4ZC{ zj$GK}F8YtA;N8Pw*1@kdF!b zG^XNW2;p@BS_yyi~q76*W7AH~uJJZnJ3k$t>c|dt!F#0I*dZJi4^V^)$-8$Fum295&h<@{IWVuz8rY9k8Psb#f zEj;~LxapewZ^_iFb)sf&D_nG6|Ncaj`kI^W;_N5RRGDSe0YSCXn{_4`_~%-b{C5+;FXUkb;1OQKYOxx;Pis`k+2G7(Kp15OpeT_8 zQ=WE=Zes$al$`I~70gVvevsYuU+pG;4zS&B=NKdPJX#3`@TA_<=Qn>^ZIah-_P5qz zvAOpp?gvy<7H*fWL~B`%+VX&r&LQ3};7ktEIT7#Yd~H_Qjvdp69i@% zr(FpT4}k4YNHWuL+s?9O33??Cs=)X3zl_dfh0Q?nE@+|8D&Uu4cP6Z_t%Yf9*RD0u zCnJ?$F$GRh!7bjKmhyf3Pwj(`x){5&my4U5)SEX*acF!kDiLE>fM38cMo<4AEr1@? zYOJ`Q>ff_7IKD*C{>UDEH6KWp4Kx9Q3w|ysVFd`=6tZLzH%;ib$or0O{%S~ro3ryQ z<1G6(M2H6MyE6!ojqu9LHK&#eG&&j$9_M-}Qe4Mn^-4muvao-ideIk$<+^j4(4^!< zBtaj%Kd4@Rw_rlWrnD%n1@BaNU%wy9`;hN$KiBhaVV|`8_vRlQbRAh9TWGR!J90^3 z8>aH-k5hXI+V{mb{gpBAc&%As9L{(?fg|P=fJ3!M3@H@O^1Gu^)M|j~DcTqFoU2NLxar>NcIYDZT`V%!QfJ{G783n<<$>xtqmXIW)QWqV0USCyBE=?C_nF#oF z3Jn-3%b)W(s&SatPn=YKpt5M+lX|1j^m?d@7f`eFZM+<{}G z0vBVO-y=fsC|#l$pgM5idQw@k;`WkgDFCK;{TF`~GX)NccDVE$PltJqP=O1JO*)^p zdeLWyd@^iCGiIN1eF;CdNtA-%73^+*3X}Wp&-%}b1!mp+s9$x=qnaaiCYuXS?w+89 zN^mihhUNI4zeZ_fOzBa73?#@`KCi4S8TG`C7dR=kP^~6MHYhxUiB!>R{Jx?QXo84A zw%J*v9F|0@)EkA*S_6!E(b;8R(jqT^2A3%rGv9Lf4Os@ zviTdNOEEuW{hc64P(1tI;`HhZ@AMyzAJzKgm#u%!-+sv9{9&1?lD!_JBN9>>hZDix z9Q70V;L`!nS-?W~ENJ%I@ql1p#ER0YIFJvBdFUyfGeiEL&Tq&VRgydumZtJqD1o0V z0flljc5@iA_E1r{I;Y_iUupn9gm%Zil<+z>708FiFM2YZqEq)8#4CIbEGo`wj}?Se z&}3xM>58+8p04M(UO1?oVLSQ)ccsj{>z-KE+w7Ke0I$W7En39eUXJ6?sg$9H9~QF3 z|KWpXw-A9%oxk-ERnb~I;+gL6?Zo!N{=Jz>xgtKjt7%=4(Os&lyMq8M?e;q(;Jhhk z2r;qC&KmFPOvoOY=d_8K)@yPhlbdP+P433;j3{fd+EF9nLfwq@-?{As%!j0oCy z)tU)nwSgJ_U%jufQ!5Ndw+vbwW7q0k_a;pN(Z(BaN)}Ls0`4#OHP!t+g>hO=rt`yo$d#akJu4g**leQs|DgIx|)E3IUziib3M#e5uS6U~OHt zco5$9UxF9KgP~_kdA1LvZ{-`c`_pN&@h`eqTGF++9}Os)+%4ElA;G}Fppwna&HdSN zCE@eM`XL#s4i}y+;DHX%CQbe%v(X#_<_@>BU6nI{IS+oRsSyPMI+!FR(ZHU~=^8E( z3scH0JUqO6bTn@8^C%=X9xe@QNIKR`mJmXvJvu&)1#bHH-;owGc^t?tv%5lp{46bq zUk>gL3(^E^YkoC~27w?(X=CB(7#l#nnMUWPq@<7qG>>cS9r*LO_W=dTWj=Q`O3(9p z|6V;KTFhxgbBW`cy0gXbHzx?Xk&Ne3?NeP`@IBZJJnW=BMHhWjf0tVQFtk_=rOMqa zHPiB?fCoA}5lP6xSeue`TVP~x z8;uOPJ^O<*fXCdSAmp`0Q&DH36>PFu)ChEt?)W~ZH(dPu4WdvHHHyuS1m1jF@Ttib z+r#&pUd>LIev%-TKf59BjKZT6tCd|gWnXY$pczd^OFI_ggL}mnNUz@lR!F>rOp48jPY<&YR|115FhyTi9l=u>Mgd zVk74gD{EOexBh7)6y(Cb5ukok$i$Ki=!hr8b#-;c1F%zDw->5;cEJq%FI9Bl#I{DV zGdp>lc?vg>C29KDq|)C`|84WXhd!whmohjkQgu^)T}N;ErxhTowEql^IrDma5Nd^f zlEdZL$L)08M3Fh7xer4pg%(If6=+o(Rs^*;eLCsSP)PbSsXOI~K6QCtRe%gMS&onm zDh0r|#$i!O=YcFY(+S*SZ0jE1+?(;%!#;U_ePnL>~>ZKS|!Rb92O(*eZ(mh z&=StPVDrD$yE@0RCfox@y-1f+h|eABCys3D^Ayyh|3yn1<&hv!zL%2L>{->GRRgo0 z?wVPqw$7T{Cz9AGyQ)DyV0(a$s$7NRR(&vhVluXuFHHFJ#r^59&h(sEc% zH~0oZfyf}&pJK~kFI+avxF1x>G77>KobD#>(t0#A41c((JlPU3&w&qz0iVSjSx zU*FY)1VUNo%HLEegZt58DHEZGfctIKSdvo!nTLI7fu@w+qW51|z`n26L_iRtJ2F^} zgc-%yUqn@qnEWwQhu}|q(sCHeI39GIssD_HgyHi+mcu+hUIXt3Opjug)Stvp$tiv4 zLb67-OJC9J?|hw3=QSEEmk<`X?K%MAAKxx2Mk3&gWT(ez{3d3AaIBozK6H7L+yCJ% z@<6}F7Wd;9>&l9dgSEv^0z^|KKR`I7!Pf!5BFuPv@e@-3$j!D};FHfx zR*DED+-tOwnX{sHjkQ&2w>t9$%;d>(Fd>1oo6m}GK;hyi=I&q)+r=$c550VEG@DvZ zajkF0M7Z^ISE8ap>94Ys6f{& zH$Pgx9MOV;0)qHL^=4l8uxc(cX$)Ie%{-*z1vB^S9ych z+TKLQfUWU_w>G8$kNTr%xYWcmlU`2U|p0>k945U#xV+ z1P}@P)Z#{Xxo#Vo@XiTpAbqTs%}AaZi#7S!khWSLj&V!{=9Rp7RiIYu$)#7N&?uYw z2*IIc4K)>X-^2t6Zjg~k0Oe~Sy_tnb&z588$FWQRK7+vbZ5oRZzKoms5ajzd(I0A( zjMo;@A33eW)~NQfMZ+FFs=e;_UxnAJ-|6Aou}{B#s+YNFXwZzZ5n#PFx*KvWfp+pPSJtV(x<)r3j$37wBh>-m-=PE{Y@yKZr8vz%ptmFyT2dcZ-ICC3SI^9bh9%4mFie2TF?%Vz|ithAc~+xFR7Q^bXZ=9ZQ^J%U%z zj28PPeuU<%tSsJbJHQ5ec1=hcG+N{Ksl4ilH2KV;|22M%BY1pw1ulR-t9E-Ndk{js z#QUpK55G_*&itKSfBZ{*{1k;i_4n_o9~ItxNK3=F^dLx)s&yzm23Hxac3c>CPrrQ$ z4$^ch$gk$KosD0Rz$CZwI$0JPLuYahES&8)n5ni%KL%bozEny#s#(SpjgD(wbZW(@ zdIX98Hv{~73i-p0RX?J+o$EjBV-CD+rG0OI(!xVd33w42iKZmGx5r&_$ijrey}N3Y zsN_=a9q^|BqEO?v;hr;^{KbFL`wzFVQa{-t+FW>UyMIB3pW0M~77o4Ir|5*VeeHTz z=2%jGgs2J~UZs2U^J(`zjVZ=xvelrBC<_XWeM^C{-!g03oEGeqT~x}&zJ#ua#~f%UX zpQkdeCFUSdw6-Z`;iY4bp!U{OK!p{(X;qvcP`2*XEwux>cLCB>Gj2NGre1gDjG+CUiF38EaqE84)k=XIiPuHMRUb0i zK*(>jn0VSzad6P*5Q;^Z7zVpZ=f3%9P{L1+jhVLuOCq^e^ND@ zF!ZZ+EN(|Nv5tU7#Qan~)6D^%TAIrWj2%t#6+SlHiq0;?*7Xg~dNyPA*GZI4)~&S| zqUyF7|K7}f-0Gy$EPyLU98$_ckt)@trvURj10*k9vo0zGwc?3H7zR@;2i$)z!;bFv z&eJw7?KaNYoWTgh&{NF3^stcXrSJxJKZe9qLu0S`lel#tH~cuT>^ z(Fe*fCK=ht3J!rZyOM#vJ{!m@L1;r-ww|dX?fd5D?k-&@9Wz#{MvG2$4O}GIEb)kl zpulIm&IS)F0q%7|3Ypk@;1s=)4M#>shAN1KB(*vZ0%n}H25=wlKr}f4+q-8Vd!^T` ztXxLIYyCYB7|hRuG6K_qjkx3E&*tHonY6XmTGKugV1}B`ZAa8?TC~;|N6xnb&NCqc z)sk+LEpk9WK;wp7tX3s$@;Przs?EY4}_1pYG zh?EV+b&w^zmaAVyN_co;(&j zuE$G+J#XjUT}<&=&X*x0#1auut?a+DVViMaX=!nj+GfgNGap;gu46|^Dbs=`oDA-; z&VDf^Z>n4g!;w*3KRZ%;llGEy$4c`%A2fxo7jw4h{H5pZHgl>SdhcLKT8Tk1dWfyU zQKrf1KC|cjKG(0~*f3e)Z?}O1fb-cov*#>ZKpC}FSMqj@O>B0ZM0So#}X^|f4vmwBMXwywCep8XHxBym^-jR$OjSIjIYD|I-geKK1LC1u^}^ zS}^}XDR2&!Ki+#c=v>m!vP;eW+=+QyVEZIH3}6eKM%Og0mCR34yRu6$*;Q}7NiL9X z-H$s$ZZDPVqM1qN-NrI9)2kM&tG0gXJW>tA(V16q^flZv16jbyr(hGP%~V@T)cG-W zF~KW9wkh}|pRah^9ebRw$W2y{D`$m+b$~2^eX{hBkdRuF9u)Tj2JIS(*5HIef>+L0 zloqwXTfYFbsPzXIheQkl|GOF@nZwxU&=g?wF_+WW>c+eHRE{_cm zhLC(Id6;gp^!pLeh~TEjlBUpMdyvQ&b?T@?jNznIC@C21<{K>M8|Xt!{!AC<3p&*i z@52?`bjatw{Q0LG#Ibpv@8e1s6|I3>1_Fp)-FxJb9HGHLG%;6r6o_aXnb22ONH;e~ z@)FDesvu4+G*SjF4!c-zx~Sdt&d)oTXe-jHjLwYwoe?~8j3&)zsmV<q{R4YJh!)A7<{>|r>8-dRK zbYG#tYBgfgL-D1-jVstrfkR{D>H?x($AezGEAQLaNYZ;3F$*=-3q=2;JQSp|;B4h) zp4wgj`!M3w;U=B?yQBv9t*D|GS#xXbPMqBhTYpnz`b8F$Ya+JsjuR0_BRB?t9C;gw=chYY^+)`jQi|EF&bP2Y$G5-Ld3AS< z!|!$!6x1!fewENY`^}hnp}_$(%Ao&bGutMCFkq8@_Wl2e(V_Uh_J-|Y2_d%|+l@9Q zGhdOz;{N;qBNm&>KJ_L5$2fMFB|WtX3VeW$r26>*&TOTId35z6*dA0sH5ko=V`0=9`WHx zw}*1W(;Sb9LT&fdxXR8KW?X#-3qb`(Q5=9SoYS9jUDCq`@*XfHfwI{$qA)_AOrp1t z{~=~O6V7cZ=V~Ac@IRAH>8iW33b!~X!fY+ z=psnF`%9U%s`TZQqXUJAh~A;b-KD;cB_~pC%c=`EJ6lYoB?EMB6b&o>W$3knd`6E zJN)3#$!Ei*Ty|D}Y!QF;x@VYgw1m>QGhY(~mz6vWw_?z7#b7{u0I2-x#iltvTW}qX zw|5}Pt-Xw7@EL)CF}7}lyX3h3909}!a*m$HY8aUGo%9LB%ul8tDgPeh*>)Sm|B}qWx&~YSVHJyvPOFxZ#w$ijC379MjTuAt z!yi*=dMw#WYz!GfW+XXW1mP+%gQj7KcZY$F|qFAt!*(`~3J(D&{{-16_Q? z6W5Rwq54s3{6DIGnqS|!Zu!6#Smm=GGB>Xh9jRL9C^rY4zk8eSdTu=aJHgSK?bl%{ zfR#~I@MJkv-fnUg{o4Xtf`xEw?nbsW{+s-~fzkjK?LXN2w5E}ZkmBU6fy=b4efz(~ z6tuXR3pnhR=chn7ul(KH&HaVVN}CWApLJyI)gFhOQm*h8eg`Bm{vFcI2^jK7ZpN0D zNT1BlUqFHM7L`^hXVdwKX+omfjS{5^&MC1+`CKxh60P5BYilcd#Hv|AkS5a?Kd24V zCwWz5Xl5}h$Mmi&(@fsOqencjw zgPlUdu7A5{$HYXC+p9GyBqTLnyV52Bz$MAceScDS#Z|Ik8?MJW&?2@+{bAKHDYEGy*%Sn1npW! zctA_RuZS9o;!}iZnB8v&Pw&Ae!s`YF_)RCa4B+q@RDhrFzch+51@wS zQ#!mzz#%3TaUakw?anUx`-)51_WnYs#IPRkx#}-IW~Z}7GxujMM~ex@B@ELxtM0oB z+V2~Glx_gooDab@9=&R@Jfi)>BRttI6W- z{Cl6Z{fUHA236lYg63R1{d%IoUk=;0o4>jI5;4eS6fJ%xJw_gXUmB}3CVde0T%yUk z+o2P8(C@2&7dzb7Vv56cC*_NMZ8qo<(2pFG)kjzQLg*T>Y*|-3bOOHLqoF3x4(C(e z&bC})e*9GxbQ6>LuKx`Ni|O2YD5?jkfHTq~iEzIc-L+?|n4~A~-43e>KBt^3t7g74 zE+(D(_NG(VKAn)SBl#r|eXpn35Ef6Gfm!iCp!KK0HfY@kSo6wfoyHh(&twV=)L)AG ze>``^%*F@Wn8f0zKA3t~Wy+{}=-)zr?J@JO@8>T5Dir}fUSAIu4delZE)oV&2weVY zHk2$Y^4u=0aV$)A-qO14pX_W-Ta?`;`=9|&!Dc(4_>FOy+8D)ADqye1RYlobF0wtZ zP+}|f^1L4`O^GC^LE%Fi*Ho*T1oe*IrdVMCth2{hzm6RTvWjOt=wyQZjcRn=V1s3& zCklVI%}*day(m5j&OsrI7i%iOkc-XJfw6hNP?xKyxJ9&OpjeVxLIyT59(v5RY*@lgel5 zizW=gAQ#R9{-@gY=4jzH@&kDUVm>xARc3Eu^Qu95A4kI%uT%3+rs-WO(Im@2bKjGz zW=KRe5!t52Q?-frncx3)@uy$mMEh`mTM=keThspw!BsgxPXL-kFaA2`y9a{RL~-Q! z;cT9E;XurIqmov`$wchw5%OG!R6X$2T6S}4d29UA$IWp|IYwqA2HdSYTshe0iU8P$<*yb(^b?_>K73XKlBcb8z0Yl)u8&wWLj-ZU zXx2{67O}aV74-Fla)m`KEAcrJSh$3tUX0tXD+C&5l#3LT!VG@VDg4QTf_u0*U^_jL zh|c|P!VN;F9TEQ8B24|sTmg#58kFy6P-{Pp64p4+*ZQvE((`bsne++EU6tglIXjqP z-5g0qc!?P6cqJ8_td2u^d-*|@Wd84LXt`7ITi-nG9){@}UbaAkT1e!xs_jbMdI>Ec zZ*v@4<{|Dpnd}}g-PTaZ!#2*%#V$70 z8MPk|AXT`l3`iwJ4DJ-S{&$nSAg%xvXx*W&2meP4pu?DlL|e)L8=6|Ng*^5Mwk!XE zph47yPauq}`6}uZ7ML-?Bw67DYSDeREw;;jrZ@XHcd-}&sx7JZ-cVRJJek@~Kkov= zLYi;A(t9fbkD)$~!j8xDq`(Y7-93hz&0e>a2&gb+PBkyum!b*kyr|r5m!dm9F|Mb` z{J95bW4ug3Z^~vWeP+_#VI|D3)uWd;j1q0E*>l9zNbqZb(zQWCLC;w0V;=98(RWv@ zxA?<-le9`>B^jib!<06fBcH1br^PKp%<*2lI3r}!zW(z~hvWDD*4yZ-g%>ZjuTE*k z1h4<*Yh=2Ys5cUQiQ}HLpQ}OVsW#a1*5hpuyE!8iC7RqG4pYdq;m|A z9A?92?{C-l9D$ne^{hKCr6;l@--MCuE5auMJzsAE4HMVTCl#`$F;{m_EnTepOi%Cn z6zzG(?>tvG*)*wK0U_SGuH0JbI5nhQ!zwj3B!?%C)4H}Z{bxg*sL?KTFq$CT)#fK1 zYkxv(Y)gn^Gv*B*Rr{ZV4C3XwhA+F7WIB6{O(R`3d^bubij7Y2bVgqyg`oT7XP2Nd zXLRiLrfobkIujd12Mllks$==vuNEh)V_g(bsv}XNrwhVeA+GM*{3Ec}|sPc4O=*H^wL{0$@uON57O z{ll__C|wWD`T`;B&zQjSVj<5B6tP(>lg@>Cr4JHx-BDCQhk`kj*1Pk~K_AKc7J*P> zW1o@1GBWDe5w?MzNGLZi+ov#dvValY!{5SZU)g@#asgr_kiu|6f2?;|H93NR!zLNNonYIe4A7Jg-v(vebX9v0m+g;5VT!OJ}HZZABqW-{@YD#%k+H{fWou>>HwnQ-> zFle>WJzx!|h39GV8VW`@#1|ma`f3)W5-Mwg_5xp7PUUkdL?7R=+rBzJWCON3MI0Y9 z`IV7kJ3$>mfF^X`*7n7r6Hp6AjbJ$K-M2~0XyZWpK3=NZz2z%P#ISrbasfEs_i@!r zX4J*WygxnYuPxTWqhI!RG~Tl>c|j71y2~8Pq32}C@S^FZXrE3|q|-R&(zNegMXbzF zT&?2Ut}W-4lZ9*_PQwSR7Hk?%kW`v70KigNm;S8Z0V1dhpD|oRwHERl+TVO;k$jjw zr!*-dZ7Df6BMNH^gC@gq9p#R@wg7tg=$j+ybJc3Js%&yo`5p#bD@OA(+NK`>;I4zL z-UoJ<*J&gH80EZ|GQ*Bn4%s7c87s}zCQQDIlSbyN`FI_SaM*n0E3An#b(tPNZNR1f zp8>W$81V&{m-CnnB#{6OFRwM51^2~s>+H%66CS{#SGkREprXs*(0k~HuMzlFlXDOB z{2oUeBok@E&c6&~s5kwEYlDvh(l1oGY;%Xf%y3GJsP`Yt=hnjSFC1Ruc^*8MAA_nVB=%NOWZDdP)+ zqP_S-NjH8*GPZC%!Em}n$0EJ#;@WPo03BVq&6Yi&0h?JOdGAEK60LPs^L>A?Df$iW zIz)Q=65Y_088&xYW~eKra|j2YxG4ex>4>jK3-lZk)On|?7pKH}#2N{ClNW1uu2g93 z|N8Ab@Pe0eM$o>ej%4|-miN=s&$f!uV#42Zl{GLv8v$kgVe#MxK8L6Hn*!JlhAmx3 zK|Zu@PP%(bFv}Oj9ZkWnUSrfFoAdk=UWOt5G>!a^3$#)dWIfTp7U#v2zL*LBu;@hr z_>=KOA1Xznp70S1=UuJO2bA93(QUP=Td$VJ(Fu#|?0(+4iJq+uCUBH38ZG2~;7IaT zx4QL5Z4O_fVod9Y;fyKXV)ely$K4-vfC1#kbd+n<>YV`a>9k&85V3@{HJ_IDig}y; zDZOYBIjq)X@O~uy!&sB5#&f*Pc+&@BbzVADmG3B8HyG0>`mv*n4QpArk7A3H5p6cN zVO220$^@rrx1Gh_3DJOI%hRQ0MWWa1ESz$2mryI2AhodL5lLQ-ZA-{`Na#a&J^S)F8PU7?o1yTP@GlBW9Iww*a)fwG4((SHF?q5wj zW>+rZC*9L%ZRHZLCuzKY{v@{p;z~&U^IM86w&e`L>bMHe!Rd`PYP)QyEjN%gG)h;2 z4OPIG4e{8$lCP;T;|HZiO_2aLSca&;aX(X;566)KK~fkcggJKg^3)+|V|}=XTaP%D zO2b=7A4Q%xGpcaxrid!X7R?@4@8$W4xQ?9&L~MM{(#23AF=T0($wTWny1x ze`k_voo0-ur;&{l?@~OdRI_8=Q%qD zmtOR7vCcgqFm;}J%f8&SzdJ}1NhPU{OKq{-zPDT{;-K5@deF`K~N5&06us|wP! zWbOm5L&;V&D3PVf6ndXw5L!SIKE-V2(;f%bW5R-BG`A?ktP z;^rt#S+*g2Kh4@|H0@WX&fZV3ji(!Q)kSv0(IzV!a_o+tcLUZGLW-)ZD?8s2NcSc( z)7SKJ2?ax;M%}12ou(|&WBFL!38Zh2CI`m?}9h`q_PQPS6c z)4tR@ZKRlLW?g1Qa>YTSI zKGxUgRBCr z5sG6P{^Kmw4dZqzc%OCb?a!W6;kw6xSLCxQW)< zx1s26L~YSl$S%QZ&AT3^O56~Ef&&{DRcY(P=*`|jbCxm{%@M;xT00S;iX7da6rAey zwy!Y;QttqX9sYau3m|%v6T`uh$Kt6$nr>WJ<7Xyit<@t+*g344ipWPr!dZ`Ka$Y}K zFlwghZb5f#q0?U|HOQkKx1Yg8gDSp`)&O$z8U%bVz0Z;^UjU0>#1IgBrfC>cCa*nQDhwO&*-ZVK$tyNz!p*=*G))6)mWJk1JKLy zT^#VS!XrBq8XEe3I5RLc(OB|x0Rqkw&RGy+oX`!LjfJ&$Lh-b_CSoYnjCg1h`3R2Zkrdm{vJh~XOxw<;wQ}9)z7Y`~l)TnrMd#9FN zu*YmOGij9fQsL=GB8I!B`2)%umRsGfNs?X^q6l$9uTI7XTI;Cd#(|QA=yXpK4_)H( zm8xlCXZHcSFlHoBu*|4}KoGb-s$!&%WGe&v2PqSZ)m$%k`?qcw+C}BhYsJaAmM3OK_pZtG|IjBQMc<_HZM7l*#??-3Bmc3J*>n)2Gj9n0Q^WFaekX zBg32gLCtzgb{Qds#!J#=p@4$=T8FpH7TilK6~3cj{QA~%Zop>LG5Blkj_3f2c7>QW z7%0W34Q(8DV4W<B{mUB?5NOnfvXo=d5PlLbr zt$E}3xY5-3wuBEr*Dm&?Dgu2> zAqs!KE%(rNYtA`@$Yi)#E!-i$ucdIC)geu222}o0#X9g7PuA3y*J?PHn_l$E`fq?) z)3M;mafdD=Fl)+crPfHHbeehKl31Ksy-bi-h0>K971NuShn9aQ+-geoErYprW|9n# zox9Gr*ErlrQmi!p9e5;#XDXOazS)K+6muYIpHMZV?|5c=wd@xB(HU?|dls#F|MdVS zB*LK)0;~o)dQ_~1$d{P02pEDmC@8kM1ja3Jr>or`7WzOdEB=ouhF=&}Fyn_CTMYdg zv06sA!*TDK3TET&TUY6)hy*#y$GlJeX>q3hnpsT|)v2HY91A5YIo}x2+$8<4$mVHfH!$d7)q<;#ygL~B3CmTbq5CWo2Z~#*Co`F+er+3I)M$Y- z)kuOgO-avTd*Aatzt{R@cn$zrED;l=pPtgn?zc+~7*qxbS^+Qo4BpK;w{y}|{TW7} zp~2Rzh6H_%c$g}mqDanV;;_)@uR6cIy)ssB=jDBKghTbve8q`yXQ7|a_`u)ZCH0Ac zmmBdRUefT8SvbU~sX94+`soPXpOyX?`NK8G6V%-fD#Hiwm>+;K%tI6fD=^cA&VFK} zoffXx((P7BE_Ye-j-(eMws4qTDo?2%5*qfs6H=V3nvI-}I#a(l{YT_G_8r8#brGw5 zQ8-7|LeiUMPpHVIL-f2fd$Vw()tq%C?ISnn5m?tPj#0MHT@O%Qcm&Ky6W=>DA!MlGe)4G)C*QH*GDS-6^B4{v$mOh%hMR?9aj0iPuZ@1YV8tm3kkPH{PIt3 z#T`>}plkTyaWAgA4%a7l=SF|z;jKG5?$LYSTqQHq+Khd6uztZ~>GgkEkxG+R{i_K% zycNNei$I%*&$w5gD)s zUFlR+R~ z9b21x4^?;E5rk8z`(;C)12}Eapk}Q^Gha1*`>q8S-jgT5fB^k{vDW%tL8Qq0{-w=p zM_sdP?jKlRgKDa5fP~<%!(AIik>l(|lcvS3;atKwQ+vnxUd!gFyaAg<2{590xg~gL zarEVM0~mui|5%s3JXTmaT<`OSd|NF0llT6jRF8h3=b7A{fBRqHQh_f#L2i023RjtP z7%-ic3x<|Sdbh{1jb6KHbLf>|&=x-3C4bCWa$nB)D(H;EgzFA2nB^Pg#P8K z5#uUpA~kklm7%uQ5FX$YAjNxq1woW~J(%spV97VQjt6~+49OV5&!a!$SuE?U-p??* zz?eAvDv_$fZRY8fR)anMInw4q;MKc{g>3~02}!ge*Hfs76Mm|Qdsl7LmdShOPo#^_ zAk_{NgdSdzoxnoroO#T<<4)bu?v6o{fLD6uyyQDpQwzQsrgP#tGvS7JoJN&NvWoM7 z2;W1Nm?u}h}v(U)R6kOrjnM~?EniBa1n)m9S{08v-a^GRWQjPO;ZF*FzFPz zY`Vffw3hsCp8RTuN0=(qf1dwzUqN#PPvw~C`%a8LQ669y<*ZRrgBj&>9eg$K%%n{R znK=gzd4Q@p8A}4j^RH}h*ofgJ&$0W6-p~=t@gM0iNXz8mm$>5W?;ar;_eDXa3=^^r z%d1Pbg+ZNXS9j6P6US2)*1^+1yi8>MOpAQ_pZ>q6FHtDQW3$YaU)WynGs2B@K$BV# zq`(A3Ck|%D=`6K>4?1huC!#(OP2rOt8lmW)+bots|8~{Ejo`TxnpjCfW5{J4; zGBf`~dC970fEui3fxGu1;avj3&wm1XK`0Q{SH z1TCpV{cCJ>vECPHhlHF4LSKT?So3fUrh^McU}13?#t(4&g&fB52a?1{7$h3k_i##_ z0FvI~a~)tHNNGD$?JvppB{x%ROC5~Rf_tZHEdzk?jQ3@uV8GgN6kpeAhEV?-xK7z7 zDlUA$tCCyvBT~Lu;$^Lk}2xCcG-Ky{wu9pTBIZfRUawzNEc0>EL zigOGc1miEyk;gRzpxeL46DQdxSZQ=cRxKgg@90?F{y0I{F z8VH_YHA)E4cE`K1Emiv)s-iZK+H1VNrL#_cy^;%vg|5aU;V6k=FOcP{Fqid66WU``$fe}T5V}awughf} zsaQi67ZyLiY7`1cE(WpHv)m`#xJ*n<%#1F%?4i#R{N$vpa6=)tGJYA8JesKN0Y7B1 ztOBlv)6T}fq;MX(8D@yCJCar&=6mnXrM=->aNkRt`LYDO>+aAt`GvHEO{9!1W^F3KLfbkiPNh6osO|2qi*ZL_^V|-g6aKOrt1C z(UIb5gSH`a~=0zu0*o zyDpMuC+S`s$X|<2To)ui9 z4x@4!UXt;rgnGM~N;j%>>jj+Zxvex%9JZuDA5{@$b}pWAuy0-9^FPVP&z zKWoDdg6@y(#FzvJ8n;4vsb5lKZ-!#)s@^?e48Ln$Lr~z$B!v;`*MS2L^HtqfusziF zMn;C$`FZq7)8O*9LROLt_bB?$CAeD^96!f}zw`zAJiRFDeeEJzLi+mPwpPr@}pZ{5nU0SGaIeTMzb9KIdJYgg&Cx_tz{6p?*77)860`D|` zV1(4}hsv&@d{|3aG!G2h7J=s)($`D?aEEJK0R__-*b1aqq=|b`0NOns(5M9mVB)p+ z#xXAe<4(%jSeV5K>S&P`-gLDUIuN{(+x2aE=1dc~$7;Vy)Q2UqYveob$WOc?1uNC= z5L-OeMH}PECX}f8)%|PFTyKRq{WLS<11nCK$76Nz1`@}5_;wH7LR;p%_|nXtyG)U0 z;bIDbQHS8xL>n8!@Y^$TBbmWzSLmM?Em!!*ImJ@z_Tr0$;pD)e`HS&Ih;x?o@`oms zfC+cYgNH7@Re}keM)db$=m>yaaUdhJY11ZWk{}j90r1LN#y-feZ=Na$>ejlUiy3_u zarbg9(n5>eKUUcDDRKDI zI*Mla{cji!Fw0Ah8?ri=1E!LhN;;pLB`?cECt0Yt6)xFO7d{m>-P&SlRFH{vz|;$b~nq+R&aNoUv*lhAs$ieYk^f zSnFXA4(s-6KY2z+#IaETh`vHjCrp>VLMEcvU1le)v!^k=X)_P{(wON+R-{9=PQ#T- z9(ZLF@oVI z&+Bfi62tBCwsPM+`!nxBPwx24%+0-)z4o)hy^5Be!8C%OY*BB;6>pD65#PXBfbai__NjAv@f%2vFO`y((x6p5 zf!F852b_079nWLm=hCW~7UcZ$ZD59*bOou} z#wJlKU%SN|yHl_8-m_FB@!2Zou%LnDBNXXp+s+V@+~d{(a?gII|A}doYlr>s#BxhQ z!H*;70dcu6Cr`BxH5kSjhSfhdL5Aq-W+r{KIR3*02tp`a(AMk_qJjIR)XF8p>#qCO z+=Rm4@tXdpYT|u8G<^X(?8YsuUEh!fh2LGw&rJw6WiC#3@BoSIDI=smO;k`NMSx(s z)~-KTuk^XFdk+SOIE(Zo; zQ1EYqmFyhvE0`c1Fa$7B3eL@CQTo6|N7iD}Spg&yxSzxbKU5jIY2($ySp;1+;Add9 zc$WF#$NihjJz0M=%p|z|_GHEWQV2ukhCSGNz54fL@>oZxsYZ9qQ%01_oki5%RN)9VPSdi9`c@zkF&=7H zYnoOxp;X|JWt@mv*h5kAJ)hciW$z{_XtH7$@JGKqY5O@W(K9XnXNIxh#6|dQtsx^* zGr4kquJ-fy77KUXKdUZQi0Yclm+RO`IYNyXIfzy9g?jSWwCrTm)h zsqC*|NonBW06IKqEAcA$iw>xb-m~?~OiXymtlO8({P#{ZO%Q3^Q%MubKmm(`c!Ei~ zMC@z#yhpPIUM7qwqQ)u>Gz;X!k)VR;Mf(&Px;h45p6|arq|`gX;dCzXZOL&ECM2d? zkHr$|C%Y> zB)0+>inarfz{N1OcMO$^anSs{$cTtTCmb>6BAqh&YReHcP`>p%Y&gU1@|eNXBD(DE zHQwaSyt|nbBk>2jOXB9tRJYcaDuLUGzSd!Z5Euf40D6=xpg9D+nA+Sk3C}EcTJ3|^ zyI5`f{6ti3KSvFAq;}YTv6~UNmrf((co$d_2jZXr&qJb~Xgc)Bo6B}Q?(&H$i{YVd zeyp_@|I>9-pF}=`@V+n`>W>oE$Qqs>A8fXy=xoy2+Dt6^>S2VF+r{W(%QG2QA?Hm< z@v^8yWM}Y%TI!9_ji=~&-iRsAdaxc<{QOqPh;!b_3ppv0h$%gfJUP-1UJ+ic{D|40 zd>Le(iw!~sA-!$uQ892?l234={v`YD6ftd#>!OsOyOV}f*`e!Q>os9XSf8P&CcP%Q z;OAGC;r(2P5@Z6yo~f}Rx=j0&saxWG4wE{7;`Dfgo|hrZTh4cI4(}6}JXehBi>5Lq ze8r&#A0gr6Tm++0*5p!02r6?jBDdW8{7;4TTP&hGSEBhvnv{_uwLaILljSDpWL)QD z;Rm2PG6Z_8OdzcMp8J?=nAkfAk;LT;w5lqN!R#>J9~25Am%k#o1L=Ms?*kk^$OtNI z2M*izDOBq-@I`R^)rn&{-zeO8^y3MS(~8mwb$FZd`}>%Hy{;m3vRyUY4%mOmpd<|M z>gP%i4~u;7yFj!bU+eu3L{U;cgI#KI;XozYJBrin<;=_fUR0)zM#cu{}zTtlYn z&)`@=U(}@I5j2lRkFZSh29G5)GS9h&Q$dbfYi4&Z=$acaTP`=Ar-f4UQF|Wk_Q;@^ zUZ_OM30QW*>YUC=8^>?RlPvT0^FyAkWlT5c#~R%^$H98y zu>WMPuOjM-Tb248`Er8s{b-L2{wa#e8BwPKB?TV&=LEB)wDi`hS$^az`dQ*Wi%&#e z+D3S%5c)*Q%~$)OEw~Ua-xe=5!&yCuE^73nnZgf9QY3xVabG3rALU`UjjDISRf4;8 z;NxGNy*7U2i5vB3_RC6{-4f9-WQ>)asLP*5qTNpr)fGcSNufWMF z3nDT-liy>-lo1aFK+O!i9q7gTo~a@rWuS`g@Y-wYG3+=>07imGqkq#DP}EC-a(W3c zGq*oI16K%Hf`gi<0$X3mFw!a5S;&2tcI85FL#j@Ic$L<^rO!1Sj+8saO8WXFQbYPb z8+_5N{sK08Qdy4{v~By#@VK@?-ovsDsx9L}jBanPAH(tk z@v3A#l081D&iW$cu(4FsWl_gcw^-zeN(@rpGL^E*eyUSGv_G=E_;tOw`~&6SE%tnx z7^)k+P~V*4aB14&qW?9I`_dhA=3DAL6Dtznxux5J~6=KQSsUPn{O4Rw(_VH8yFxFtg4*cpz{Hn;_%?8>F1MNB(rxw@F3z4LIyZsT zfJ#PsZ~mJF!ZM_X-V0yi`bgspR5m6+T>>wFGg?knvOY0-Owx6eqw@b>jj2;&!`GGJ zZ1qr39hdFJ{-;xC!|U3e|B4<*qPJ8vwv}n^qWbr0)P1coX)pM~xb*3TNgl*#*go~a>< zdOtl8C(J0j`H0i6?bD1AeDoSm+ouT!a9|d{<}aECEsnW;Uiy>wgl9coO>g>}V7-eU zf;HVQ=^$QL98&MPb8zPo;PlvYS@n5%I3!&Y1iN1&7X|0%J{`E58J(1QWZ^2u$G4{X zh8S78g!(Zf@t9YE>b7D^Q&;k}Z)7xHsF9m|5K56X)E>I|;d!v@PviPsO@{YYB1XOS z*RNmVBdtde*YohK8Be`%@yeIqKWy$Y+)x#>3#H=AQH?^m*Yxm zC(d#ItX~tJXJ-pUzuOBf8F{&KycGHV9M5t?KV;p(hoS&S^B~Ydm51Dp9fRwl>99Fk z2nR+88`Cw#IvF7Ivl1-->Wat$!bKk7+m}dj2(-cw=ldxA43Y3a1nAjV4-v zkZpKC?Q5O_R!esPs~ScL33}}kT~y!eV@lBY-vR4N1vh5Ca7SDSIIpQl`7;>C6v_a1 z5!FJr+hcsvaGVzT0)Jx+v!*z{nFnUe+uWZi9b9~TEGT_^4&YJohVvCl0THN-m&>3qV>vzcg-)aMo%+;+RTajYU6crv7}iz zPu}z!o?L|bUmM?~5a4Wqm>+ za0Eo$vSUSBOOq8V{7_6K7{{|8wAo69hE1M^KQ-~XytfW&*URYhn+DRwMKiHEjGDa- z-vF^H&DwCr9xFyi#mUx0%vi^x_fRdooYXK1^`Y$R9C!(^ul){oi=L3>yj{BDxtIaY zR=V}W05jri$b{k>b@U=kyCeNU7xnOfAu%)*yTGVLg6xlp*+a9So%8U0xgMI`W9?V> zvUKYxa<=NQbyp{!zjan;{dJYWD=V(o<@J4di~fIFAl}G7{CjUtO$KjS9kax}E*=>N zmOuaM>^w%K{d=#$eJO^lhfyb8ELPb0k80ptUtR>|;(XJk;o)5EssWVWmdAGb1|6){ z>$8^?cqzt2Bkx(pkgt*?!iXk@p{Y*NScG*Us+OV|^3N%S`n4g$JT&s>@nHWNceR>j z^b<0RU$!j%@Q{5NTI-R?%|lycdqD@v>Bg|aE(1PumGK0_XAiteV&eMeJ#FqbBy=L$ zaKb@j(GQYAkL+*iVw{5OX8DGR#&Ik$!E5q~s3r6O!gz3IH44dX?FD-#VDYOlJB@lO z(xH5*aKE<4fP2!92dMRjc)}^}K)&ZdyM$*emaa;TT85rk=Rc0`E=~qcW-?%dz+E6l zPH$QG>Kwuw78z+aGV<=-JERW~M{HnPxK`?87)3s1rbvaxr>7j0*1^6%C6-d4ycv%77%SCtPcKM@&VExf z#Ri}#T;sC+pfn6&HG!by#k2GUSG(W8k!lx|kb;2=_CQ1nwC*fn_Q3S>@E4DAkNOEoG zWT(nw8IRsngU=vpR4g1KYCp2&@zXO)7yn5_K7sg%DYEj5jtfNGlE}7E;C_jWWTNPm z*lbqMRA6OA1rV!kR3KtS>nTg-SrZe>P!c2hXjuxadblD>@G*$h^Zb2E(Um@uJnsry zYQw68J3s#dX*BAj>Gk0~you9qY#FWjsws)7+alg>e(+jpevNMeI6E2)70 zw`Vc7&a zcO z6=wpWn4gui`9M-!yN%3_#RJN~qEFjilhf!^0JZF$Q?sjiZ^V=Dds9nJ<6E3&|DK}5-&5o((~x`=HG=rh@qOz|P(r{+ ztCR~~hVWV*Gw^LifCMW95^R1=0e2HN%qIp7@tm?eQ-WD6zDYnp1+tT5)qxCY@ zu;XC~-Gz@aRKF}Ztg1mc9mcwW;v3LdC8}+2Xs_q9J=ML#qI;*<%-cX2xHK$z8wf&frC2ra6ELQ^1GU@fAd)mw`+B@F z@;2$1i#)3OhTfk zxUN*ETOiyj$dlVCv=YehrZDC$3HkZ;Dv*@HRr;w{QOq96HIPYVL21A!~XU9#Y;Y7v!6E}13-c8w8#Lb}=dOUT-I(w}ve$q4&31$cZ>*-#E`%7k#v#V;r zWvMg}c{+HHSP`oy^QFWgpl8f9h9gGnTD6h+b_;YMidw!>_QP zDv*|*>iA~NE<}7!6nuJ&p`B=usvf4`4R z9Y3S4{do-*A*~-6pSOrXY( z43Ztq2RQ6kR0Rw7R6p`C>LovXse20+rSg(>ylC_wq4zmk;=y4xGJkLS;H|cO5`e<- zVc)m@f1IZ_jK^_NN*DM=kf8vKh#tU>cLeQc{U{AR{F~B&wC3J+iny0RM5BTXFXI>D zW}#GBk5z?p+tRn?cGCh&yf6ZQ{Rk$E#(u*9v%iOqn?n>BasuJt|9E6S?t0_E)Vucg z#1OnCF|4g7l&JUq38zep5VT4h-&6!;L$ro~FUedC`P6B%@2$jWu};iR&hE??A|BV= zGC0SmPwg+otj)sHruR1c(u*+?7ro4ojyJnt1D!OO6Pa9|?7)G8)5h#qzLmk0iIxVZ z@J`ExnMU`90?8D?XK%c41wVh-ll__ZB=NhQaV1_?0?ZKGWu`W&{uQ}ZXeZ+4cNj>S z5iAh01LgYh`)C`dY|-@o-+oH^wb;+A@Z!@~K*3)z%de~^07B{act$mY&g77H9(2V* zpe9n8IiKe-J^BnP*($bto+#!-ur9Z%;CWKf4?F5IlJp zo0JYK2jDI8>eb^yA_+&?7z=vC-6nd#AP*f+827h3PJiI7bmV3J=E~!a%J%?UF zxa!;P_y%fSLTI3RX_TpOoPaHxt8zA^`{{E8qw#Q(bU!Bwxu^6`muk-n9%9dDw?{UH z$n@w_(%UR(KBL>>BW^oc?!Uu%NfsV7J*<7jq$%!uIS+bufM0chwcE)a1R8KeFixb) zNf)J2u8U-iQsgotJ}CKa)Gp_kim2|L9u6peC8R?XZ6gyN8m&RTGJFah>q(r??f{ zd;gVi56BSPixqt$k01n?Wf}VIdqL9=HFy(IHg&)VCbGhHCGu3&oRTy==<)1nFjbHo z$lGRCGzaEnc(|oJeUqJGWEw{SR`(8m=YiZ%@u`Rh@xFe)ma=3XY+1bPyf)ytGtGZO z_9W9sop`L=ME0I-2is@3P@ZB%Kfxi$xp#q9iGv@OXz<0p!qP$V_KFWof8Gx2J;p2= zpwZC$W!?@D$;aRzPFjgIQFpAH_u8URUL@7 zq9?C_?lNYFM&bJP-Z1=O&Vs&El9UsM$)U@=6S zC>$BiivfS)&ps1Y#p^xxxx1%_Z)mRGm9LpkiwM-lWMB3O4#Cb#Mqq|h?zW>(;fjkl zU*ftwNh0F1u{hS>`1%k9Q!(Ss36Jc{s0 zGwk9L9IzNp4+8V!Hjs^{Mj@AslHMP|kDGsQaCbPNR`B!MuIO3(sV4dIw+P6#xIwt& z0boL1dGsEluEM=no=>-$X;j8D3sT`1)4Q*dNO?sO{l{J#O$g#0r4+24Rm;J$GA!N|vCiZ3C6XbI8w#=^Cmfcd29y##1| zq0;z4h(2BT-9(_tZjGdokDyTfT@6k9z^B>(2IuaPOpu&64(jht8Vy-Me3h0|p!~U< zWu+H{a8nWfy;Y-(t1K!nTJJg2#X)p7HTH|dcTwQ=}2i}a#aPC|{- zo(B*zy`1*6j6GLvU!tEj^llCzLMf(<|HRkJ3lLxV0N^060~V|ymWp}xCvUDV^^}zx z*}EJk!5&YOxE^Hf!xYYr$?UVFp|a`OxPw_F2!^x;&%Zu&*_nRtN>*T`@@+Clkw{5N z$ymKMfs~302S7}C@$f${wS{XRTCN74gK9W%W5ZJSi#?rY;WJF&H`-@g<#}wKXioP2 zV?p@!R#{6XaA(+`acGeSwO6u-J-|c#Xy`n|gH;&7Mz_3l$RFH;2>BnL=k!Mr^B~@G zRExjCM1=E{xL#g-{GrUHcSOEr``G)B8V3xTgKe5+w^()ulmW{WoA-~`NXF$~>8=m$ zOup35erqDC`|H;`Uj$>DF6<)9i1P0{4J%R4vXJ5r^S2a)eo7Wqye z1@l{G|0ni-nGQt1gYVbizE^2WeY!ix=dxh%dqM|>EPxWZEI=Lmz3!E15|0VmlZbQB zjNp2uAn6d_o4>%MPYoG1GJQwu*lmCKXGbi(9RsNo8(YCGE2-s;Fe_ai+Ti^x3VHHF zHv?n`5*CQqz4mrF9Du`4T=Xh5BO7ISMIEazyprgkNxXM{+1`1%b@T|lx}VL;Nt<9v zj0~t{bRPf*;$TpRw^x{cJ6~4!3v6d%9b^no0Bs@4>%%qGkq7zW&+8l+$*fJpBOi7D zG|co&IwAe3_iNA*tj0sh;J_W_mXHrZz!rc_fPH9qIPTxG^z~aLa841hLry!k*%Yv3 z)#(*{4S+`go)_>ZecesdWqd}hKtaQ2jt_Qm#f9CL%gM<}ffagF!FLqy0U-o~jT*@k zj`WWuM6vJ|52mWBT&Hb7?&S2xQuX_nq8pT9TnaK3i{?;?{TFJFcp#xxQ~ zN~SE(pEqs*mvb|3D{Uo6uB-C^KOrvvqWKq}_{{i+fNO();w?DG6E-F0>OenIevUPN z);Wz&|2$ABjP1Vxm(Ff6m-pKmclKrA(yrH~`4AkhYxHv2bI+^z&8w5`DWu;XHzRa+ zWZ965V)n9Y!yLM*QXT3ndSQ%p-6!Eu_>T3NT#D7mEaDOx4{^I7 zsM?uLLXj&$^4|zEDZcuFq3V89zJ)ZfHDh^a?s1Us+gLrGW$5T|ic#ehf93vrmmmsV*a-T zHNJ;BmG8%w`5!Joprm=FtEmxqL+Y1-o_msqNI)cM4?#gZyWF)+l1=p}!6)(;?XFks zNmB=_#qgFKz*1?C9$YTw1QQvnck)a_!+Xm)A~Mz~!y{#QUvhJeN~AthBMCp3II>Fk z09K=n{Cp%&ei>5L>D_B^b^cW{9VXFiTgI4>6j-!ts=B62k)^t1X z53xY_dYXTnAl{qy-+3-D#AMYZLwAG~&K2z2Lu0=1sYnTy_-8Jdi8qoZxT{3T$hs{_ z?~TZ{b8sNyLKofyoP9iAzx;`%$Y33>oIzDzOddzG2Hy&iQ8tTmqO|*{I*E~hjZj91 z5qD&3?dMgKMYnQnxBU$I&y5^lM3!~5MY}ul0={u}89W@rU3-0OwJ4e~sG*UB#W=rc zDFg;7zx@j)a%Cq|?Mb(IGF+X7NiLP82*x{z7i$Hl&xw56&;bmxclysbd;eGv8-eUT z*B^@YxWd<$WMF?!$Z(tN6!ww}G*Z0)(s<|<%!u?N}h_nwj+UEk&~sAK?fFdX={ zGZ4Ta8p?8=Im9oH&mqbDX5`@q*kx!qU6GyzkKi94D@!JCcY)9AAr59TZ=Pz{c_Ck1 zD(2n=Be(|gPsaDKJP(%bLWB2e7;IZBY(vDZ-8+XV#*TZ3SPc z#!7n$suGVo3Jx?HxS0GxG~&iOBocnzlSc5`Yih>*^N~~KksrCL#I$9k4+%>`JF#4^ zd{j~gUzhdw>9KsK1esr-m?6>d%MFd%Bi`2mrAU?MTrO3kk;2nGbw^%(R8$<4z{&M|=q}(`c?BOr8eZKpZMD`y9l!K7u+)jFToshN+=@t_=o5BvfR|8V|G?dK*W3Z4MSOQs$hs_ z+m-L9bWu+>_PE)l%3?A%7#hOP%S_q)6DxV;tYGKc>`om64L+eI>fTkE`q8X@zj!!s zZu8%DE|}6oCFxu{F~T{_#T+*M|M?D7{=NZ70oShF-!~9Deo-iH6F7x+m)n=n&^)ca=vcBY0UWp$dGm70~wypmC%~*MZSUdK}l<(RA3ztrshCbQ5X* zeOJVKIcF9!{B(+dWxB_xsDK`tpE!ypO;RY|WBE0FlN9w|ppNq|P{$uAd*@zC%c3;; zPEWqeP<&t(QtyIfOGgpDj^Wjwt#^IZ64xu z9i6dc3k=-53!;tT{Bg(j*YmAmY?Hx(WImJsas&gy$k=2M2qp-p{-_PM`9!g1A*ss8 zM>uYK^Mm{n;yb1o>&DB-Is;6numFr!wEjrfz_K4~>yQFeo2F?nbU+i+tOgK|q8sZ5 z;an*}w&`#(A6Bj1jQIp(J>vPn!9nK~#*_Q0>T?{jB*KAU9-|ly_4JzR8}o@<1N~}| z&Kr8`(bt6U>{CCWdbJAQ&*t!z;i-Y+)7-5{dh$Ax-fp+ZMOsVmmfIWA#{G6Y z$L&d8%kdH_Fgv2vE`5#z{Dj|v?_L%#4!}s($M1CBi-?-v8lyjv^Q}w}t~4MRECFj7 zVC3+;&Uu{>Iax#wA3+mq_~LkbifnVDjEI2xk+Gd=U@dSFmIZs>lZl7>{KU+iv}NN& zvXkE5nK)FnUatj{2T?v=gl*+3ipN`EW)$Prp7QG&%jc~T5iLRuzSNSN8$nER0J{uZ zeJi}2M;XqJC?BJdP+FQl`$#(W1mH986B1&ZWghzO5yc(FDQ7U*K@ZYvWK&omKkb90 zQuw9~e%#{9bKLW?Jcpaf|9f)xN6&t{rjSYT>YBNtL{caO{S%o$1HhAj$kO5h(V3C^fyJyr>qA(5sQGu0ko0L1RblyJ9Gh}k(CI{rS^YPy?8ovaw~o;) z5a;kbt5j>WY@ZmrgTK^XL+ziJmu+A@3?rDQ`NgSZc>F)c-a0PI_1hZ;q&tR?76b;Q z8Adwb*amauye$XlSk@m0|s9aM7Y{({$_m@g)bA`b9m#&ARcprUt*FXIN z$o%MhB;S>{?(|ke>D=ahgk);~L$}cY;**pIp1d`h6Q%kjsVpdhy7tC_8z;Ow1Z@@q!LlTs#qYond$m(YtUvH9o1&morivmzOJ&Ui{3VB$ zwPyW_^S7U2MON4y{wd~g>MZR&U8H@*Pr`+wRLgzN2=4-=UGkq!=>N*lA;(L504}*~ zIaw3hb0%5v7EGdN!FM5hWSGx(fcVQgTh?_`O45A(Y3i+;;@+*iwPgzO(89nG>;wyZ z=>Y+1n{p%JlB;_JpFZGr(g9dO7J%>#mPey1MqbXPOUJ0c^>PAJYe2jOSpV~xGwr@B zn=%eus)|<3s|_IWxInaw0tql}w`s7QMoCd}0@8W*Qwp+l>7V_H`eG2c=|djMg59oN zAh4)$D;>j29n`P7B@4m%8Nc612padN&xY}vQiS)YRJVdJ^IBi!#Xt)px`HArA=!Gr z4-ZV&a$EyCI7(x$dNbSMBQ4MPx#4WTGQkykEFn~BS@{gsPZFjSrIYWUR|LzpV*}3< zuxehl+MN@qFJB-FXCTFK_j{+PDRV^bWnn(6cN3tbzQLJ}SwNE+el7{Lyq8jEpyTiG z7PxNo)O8U3?zQ|$PA;2*cZtYg8t8@)u09-roj0nFiofe*SJ@hon`Z1Ia4b4I^eAT^AU4Eq&3Oqh&yZJ zKK?RHKV|@1vg9_SO#6Bk&OZnO=N^=fU9|**!)Yz_tEbcXGhsj>Yl4W z4>ud;rRr^`5#EChS4e``(KqM2klYAZ_P5tYlS*wH1cYVF&dRl?GwW_wRx^;fkbXO^ph(X+5j?;t(<(w%Gt$okl=N!#R2KGiD5rB z&u!Guu3D)5=bq?SFwU;WqbEGmaLR?^1CYQ>k_BDxLnS&8yrwhT^ATeGqbeG@ZfZo5 zs#X+$W^wuu>%xmh?m{eZ-qj4(n>$E2ZO@NoXhviYP|kMxg4roaeFjfLVDDR3pDrzt zA^+J)rF-=kcsZ@&ZBqs49X%e|^rBkT79W z&NsoWaKu$mMfDbfV}7#(5qm9mWHffY|XU{b>I@|>wWQtvE&%%Ym%yr zCg}v6H&3c$eP}^mj6w5aaKQVqVy3!?uF8kaA+V_=;>YY$dY*QFlDrUzojEx6>pDI{ zp<`=oC*Gq*S{LSwvZOdeh#}Pd0(3vMPczFa69Pta9T$%^*~3D+U`dU{u4*@+`3%Bq z^HnXPT!SQT(Aa#IMP+f5+4oCgMvWpJUzfhq1gk_6o+|~ z6m-zP0m=R{c|<@5@rbEg5h_31=wrX(CQoN(DXDfMc!b7-TK_A*q+Oy>5*TG4gDgBi z|A2d(p+H4@gb97U0pMJ6^pz}N$&6BJ(tHoQ9?y&kpI@<$+pI)$X*bfLz|ExT1ZPr zR~rd1z}udM82Q<}QW$^-^EI?cNDnIO_=AFNF@}&Mqj=wo)xbU4AtpHraXNuiAhg*G zA;rpB;5Dsgc=qR8HaS87m7oayrO~M@ZmZcU@ALO&Nd*rW=*O&!p472Xi@HVQIfIQL z!yt7I>`Ndsk$DH!wmk&*a9cD2uF_%w?K~lXg?oDd0@<8$YSW83)ZP>2+y!}+LP?Bf zyxbcb4v^w@uuDE0wCn7|Q-I6WBLQ;VAiz(zC*IRd$>IQUbBJXM>hF~1{Q$#@w&A?^(<`jExj}X_dm2Luj z7RW$o6ca#P8gVDthZ=Ep!sQSU22fVw1ArNCA@T5&sonu6c+pdgs%^D*0;IF{U$cG% z+PZl&&rsb5X;SCSVdb}WsR`gR1%xa0l3bFgvA+FaD49VisoqP<*b(bIDf>5|9Sed+KVZRn4t^VX>F3?SWQ!}*n)nxELAOt}GAxwl>kic1U z?lm*Ot$)&#`eijs%#~t#z3hHz2l%>09{_dBmyD!H5h1RZ%YYw45 zR6(d(dICDDyv2-OX0PAw7SU@3K&aW$14s;Dzr*sQyByh{)JEiI%d}_E{sdIib{}Ib zcVOyxH};S)um-L@^E}r`k? z4Ow;yZ-xm`r$_IAjd$TBpf)nm>TD~wF#$zs;?wLsIdZyB|3ywmc-diB0J!#mfJp7v z>P*#{99>(a-0wZZ!NUO!cIHtZyBCC%^f#q6gaMRN76^ftm@u$h$;|fgvZdZGspM6WTmK|c{N&mAmH5=Up;`HKO%4H5qg!1Xk{ZVa?3AeuN3Xe(w9c5r5&Cmx<{>GUuM;R%`YXucPvx_s+3NEiK_Y z1EqLK;A82k3neahz*DU?LbTe0&@>I)lWlAS)&uEj4|%HegV)&G<1SE+G5dRxBr|1(%?-QqO#z#r@!rAo3q;f4a7c8ktzq5|2b5%FTr2RU z*(FVqRY+5n8#Pk`brCK|{0+;N4zCvBFm6eY{KLJ)F+(xiwjCmIdCZ7Q!5t7o=lm0P zxg<~L%a8U50!Qc=fHDvV3~|Yj2q@)g zcaj+((jWK6bkF49OYzutX6mUB!O^1b7lixY&&MN%@8~NjQ_5#V!1}}R><^EV-oKaK zP;aRKlL>~ys6z9@5JV}ml6K|z*YzyU*eFGU%=BER0~(Nm@rE`%H--T`K}biK7g*ifFDR^W zqh9<%=Flgqotd+~p32I>RQ9IZx=8`RLsfVPjW)Z?JVA8Un`+gi4SN87j(%z>GVl6N z#Ry6c2*HswFi0;Xula2{4{)PUP+1&f)LE!AENguNzBPN3W0SFCGRnUUvC8yK!rB1& z%Y$7yDLIbdx?+Fw6cJhun9L1ha7mg12G;?^t1<^k$4>$&&+RinX!2%Lz`HawbQ`qL5<6Q^WtAjZ5FA5R4(CDr?NHPQpZnrqcUykR82lB|Apofpu$zI1 zpKo_os$_*a>(NYY7|HLTcIX_f?h$9Qz&)aLN-Se#2-w5U8566uNpio zm9QxfxVcGV6ywp^GsIlgZ@M*^wnRh)dp{DvtfqAHjmzlnuET+@Iz!lI#dhprD?t63 zW^}=q9>_}!^>%N`;WRFRNhr~+W16l1-rj0QwRr*>j3YKH!#kiy=3~lEso7A?RYjmr zQ(=e(PB3Q+Lrz0=n%U7!2vLK~jiZGhH+eXc9bkvb>!B0S4lrnz8GINSjha3{;2VaG zE~Y{EGIrKNI!B|E2neIS>McH#F{elclEI6(U0LAoHK6q%3OjZcTh^RKEj<8qaS7v{ z^Wb$elSG~XWow}8$>Vq0rpt&VZ5wyMY$E8TV*sK|%{$tH4#YD4bB52ZB3W}OSTb6= z1$f}9glwn`75_8Bl2|)4mzvhgQ=H4A)+^7R-9e%GHqddm%a~H&;raL0^Sq;gqiaTo zN!3#c*;l%0n1{6l#&@}GOtoJgwqww7w^P*-&|0whsMhReJp;(cYAq{VzjXTaCbm6;l7-KI(s{l{Y$3tM2!E&z9_hEd{qV^Z9v z8wq^z*n&V{%f{lLS9obp>0N$gK!rl-DO8E07%eNZ*t-@t>R;#)-0ae1??BrD@ZGX`Kx0v~BE2?U0~AgTjtMF=(r zSXVeQN37l}(dnlWJd8xXBOOplS?uoeLdcG?Hr61T9i zx?Onx00Wgif2o-FA@?0ZGRQsC1Re-IYxjT`qg-?Vz{2=;z5?a@hn_+be09Dj_CL6H zC&7M##pa_qv-mb_2(;~y0vnhfO<2E910h%!sxhfCzsJ04G2Re$n@F80v^M#$kW|lgE%;3>;YhLHxI~b0N5@KS+sV&^(eEO zFVOoU23n){!*eO(BMlqhKavB%9A@kJE{-?WO7M$gP(!Nys@w%DcJy+DFs)mUG{YcLHxC;ioWJ73=T8)@ABr0mvX>-W}(~QijokhV&Rr3;z znr;9#i45tH@1Uvs7rdlNuoK3CM9JzDV#1!N)?ClD3I>IV)Z;8Sd>#qnKITC#F8K(C zsk|wcuC3x@-|-HG_D2r&?JU8i>5eauKWD8nyeVt*9oXvMVb%4@)#%T=17%g6kM4pNf|HUrZ1Lp~plGuxNoyXLWP*N0Qg{ih^ z`vBkA<9xWEb%fXRAF`5X!}R(I8YMa$%!KR1*};Xze7{ZCy8*jh#j|r*(tuU#U<4Kg zMlpw3Gw^IKgGXor3Mnmk_BLm$xv^7(x3Ic7jhgyU^*El?niSKI<|*Eq;*=wmj;Cdd z=D8?*#4P|V$Q_7h#E4RuKujQDNLJ>G1P9?QEBpsQmU-`x%UMZ(og_E*X@7IFR+y762^(6pA%-OK}0$xi}Y6Yx=LrHH`Np{Sx&Oeb*>a6iN!0g{v>Xm{{q;(HSyb@#hG+M#h`H}b_cdaMX-T?klDXjz2gEsyIig6sYrzsgk zTI#@YTX_U#W!@b!xm{3SX_jN1WuBt4K{cd5nrc@f>@WbkkT$&yZv~qZFu_Y5ju<8Q z69sH0LICCSCO@fbEYX9efVxbp5dZGoyWEZxa`b5cW~CCso)|+L)q~BbK$1=0selc> zwR|cPw{LFG?=CTO2n*P96f_Klhkd=sIQv{l`JDn&w?%3ZEFg!6UCB#1pN?ZQlroLV zhy@H9q;)dCMu>s#bbF7((J=~1D6u~|cGt^#vRo`co$FMbl!AS~IZ5G}&)sie+e(=J z)1AeD8L%tq`YMT^sk6Rg86p^u+zSi%XrYI{Cfkmr26ggp|fEoPv5msVf zL;TkyarPyzA;A;&_{18(ARw?(<|F{Gs9{(?9^>{ud=zVg7%)`VXRZefKxLG>;_0fZ zjBOF&-+X%(sQRsRtKG2;G)n`@N~IYHf5G*)$HQX(*Jo1MmQqj1sBMa`8*NYrR&p}? z@#fNECv(d(V5e)eg)nKf-e*wwnn2d8)03Q#&^6Ou^faZOgfS|6s$Ulx2CGB>OPby! z82@Mi#$Kl+J&F_*1PW@&uhXCoBAtDC%|hU^J{Bo^2S*zyxX{52Zq7=tX%LPU3Iozx z*qTfgFi^kAFYX$PgSK=+P4p-QZ81S3EfNKpX!ZA>5F%4i|6u-!n3-*l0}7c1O%X}0 zX(SvT1Z>$gW66sUj`U9JdHcx-z-*pn8H3oI*i{@f1EsH&pOm92mA?}luG}N;Y_+!j zVlV!p%LDYP@&YLopBvCYTO3;O~iojDSRudIk zm*?0w4y#JR=sA$)xS7jAPKP9dY#-ak- z*TNsufa2Mup6eHZn9~nT5-W>=6dse`ZWV>(WMy>peLq*f<`Uk%{VB)6u-Pk7mb9}r z!;%~z)^~-YHNuuV5|Y+~tT{FL!BZEkXw4no1e3@1@p3HRj$UOD>qCI?;s9A4EDdLo ztSr5AvzK#H_C~g2iWd%&4H_K5e9$KsxeyvuYJ#Sv4ddtW?QB${Hwb0K|krWaK9lbB=teP$-$5iXlpjDrg$Jpz~AbPvaMy zP-UgamtUJbpAQothl=blA6;uF36awXL2F?~vhk88%=U5#OyW#G^Sa}^5`=rCj&+tp>Um5Xk$tU9kQZ`pv6+{!~Pl z0g$!LOi`E614Nf$WiobZii+?CppGZl5nH)CgXvo2FtMJT#XkTFto0~XM@!~Iix()oqTHjSe@_Oo{5!Ei2heuB_%qNE z_gvudLXf!ZT#6jT%!;sQZ2cik zyAJTn?+g%Scb{87^rojOl8v9AGJT6ICE2Gj!3a#E=`qQTjf8+5Cj+3=4o&1&p)nvj zVa~sA{lowDt#7=KiXbZpI!*dN1I)du&%}*O?H4uv)0_FbY5CP-XbHKZoY%sJV)HSkWK{?AclRI%(*FT&tH6RL7*kR%YjjWRalU{g+b7w z1`v}mKCcssK;h!M{&f`1v9GiqcbQ`4UV6WMM5DAthKjHSA^7V62?T-Khtu`fpbPhu zKh`B#*`Buk@JMIqeM5kSh6@qLdp$$M)ra>{7J9h4Qg>(blbv6oe}m-X_ft+<3jFLV9GM+#`7Bu}9wRQ?3dC3cVdomFNXQ7n zYJfa^vsE;v*`9Fd`}?^5DG*kdf)@4BjNWw(2%f*iRDApQ>!6RPtuP)XS7_v|Vg5*j zNf{J?XP}4PmZze5x2Y^C=%4K?)%2~pzkY3*HZBAb{R16_daV+I?2*rv zMDlU5R(VxB-H^r={`1KYzA{%3D5gU8kuY`03jJsX4ZZRVg6b1&@svr`uyhoehPf+qpKP8$mqH zC|+$n1z3rH#!f-zJ`1GNXl!vc_~0ErRfV6D1_yd)D~7ic&oD*h{6@)B6zuLXQ2vss ze1`}+7X|+1p%y))3Fx$x(TM}&7ohwSqT`a@OkF4o>d}{SOX8yX;XOCO89|7Kfi?R1 z!d;79@`erXWEFq>u%Gj&%j)A{w;G(NR%sRmRL|xX9dTCJrh(|r^oKVd0vvAnMi2Si zWX5hj!y~vb_9D^tmhSrCWJr(Dt$++EPS@G~&iodFnCFzGcN4+$BP7D=zUv4{R@DM6 zML1&2E-E9Qpx_}FZBR@fta|x4Js$$kRwVNH^onV)s=TY2TG$~#IL*ZW2Rl&l-b4`K zN8O|_{e51Vy-yk{2$adoZZ)Cj#w}j$SXJgEgJ9cQYog+vnfXruNGcfgdZ5{j5?PCq zAH6SxV!_9%!KT#29H+sq$6qxt-z`1Y)yonj5$3 zfg;(_T6|Yg`v)yw?zVcBP46H?e?TlLKlS?Jp?)aIyco-f3)_1L#98)JM@AaUsM-Ur zBz_Mvk?-4anky3kjkfau!)!|ft#-`)RrhL*0bTk-dOJ&x)jzIW-;Vfx21k`$68y2^ zpia7dj@46{&1pE}+z@^i2n5JW`bjAHtE}S7w9&!$fUk5B@R7evmX#m407Aq?u8dAD zrJKz(hRC_fIjw7&`nO)U{`2ec(|? z7GLZ^k2a*@)_;k#;$6PJBb4ra5mG-y{p9IC^bj?C{Hjuuz98?l_Hz@sloH1p8(;7f zb}~kFm#6Nx<1M8TAdUl2e1=F2)xPqT<49{2_RA#PVoZqWJeCYnjan9&O(@x_H2=x| zYT`2}%!S}+GgMI}%C)@>8ej0MhsEuz6vzEJ78>~ZN2UeIU+g>GAFKX*=R?~^ZAHXr zi1ZfqGg3C0hp;8usXCk3l^4$)DU5;->M#dGFt%Qy%YcK3jMZ(hg!xKU8XQFOzg3Rx z|5fD#JNtV6%Iqo#K5;^DNFTFn%T$4ov*`O_z>*U+a=DI27cv`SQ^5$N$D`r~zk6N+Qbjzl2 z!cLmeB!XvW;YJm0M}c&S*qlhTLQvf}<`N8a-vY;Jk?Eye8SNy{rd_D9GnBLEeee-p z$lD?-MF;o0nfl`;^#-yV@PCq9Cu9FeDBGms13Hpw2vJ)R0fgga`ad$jyP&@H%G8!` z0PxRoLp=LFTh*;cX`)FropV3I&3@fvO|E0Bk;1Lt7HS><;|jbtAtZbm`KknqUQsC3Zt;p^%qD~|0NhBld`G9#n6amus zl++IXz{Mv{tiVgqSuXRZn$0PtRTD#5qr*^I6)=x+qbXl=E!gw-R2zxAP4$T7Jl$6V z3NKU{WWL|@b%@4>nF!Iv6YEU60c{*2ttWAr7^EkVSZ$^Qkz`%HtyMO~A8U&l9jEWyA&P$uM{I7V!u{NHzk29ONiB#pR1-D z21MGwx!m3XsNT^AD#cMVs=>_sCk(JAZJCxwF;otEe;}p56nJjWargGTG+W~KM?*8a zb=K0}1#7waG+oUjA!Hita!cGKk{|PMRA_z#)IQ|QRvo==P8F_o%-THyCe=3|2>m^- z?(42poYxLa{{X%7_5i(yr!1KZvBd=ZPL#tuqZU~3LAm~HBJ&vL>48P_jC?l)-REmF z=>PqWijVmVbr{hxWa}wC(!s2Ue!pAMd#MW?4Kt}HAEgwfBP#||cv7(%qS>MN(t(s@ z87{BAUZQ0V`|R(Li>c0W=*8HziLjogICVa-v8T43K7DKa;PcYT$%W`m<*=@(oOx83 z6zxfsunr1tQJvOvF{L6M}2-y{qgQ`e|R%#)Y&3Lmzk^g6g5 ze>D7eI2k1S0Rli~D!+%oJPXA-3M5f@%933CeKp&0fvtIq(LOyJe0Gw*zK70HR zHNNLlAOZ4!l!D@f5ROSyQIHy{d{+lU$Z(^tZ(F9e6bXH{-ZQx-fxkU36ei4>dx|As z1&OMvy!5v}&~_aOOFs=e*-NycH=VuGYgBaA%!#afHZy#$I%V#+JanR^+_(d=I7uwPftNKBz2W~1OpG#Df^taaHVii&8-io`1(Lz zc6}0`eD4I`{kL4Fk~{%SP|HkKqDmX-8ga|p^Rj_(Wf_Ke)_9UC$utp1Uo22 z#6|6b(FGe9Qldai9udvokR zE#I?$Qw+X(oWEcFhyV9iZ@vtgr>GFvM3d-P5Dzu79EF*LKtvA&G(v?kY`IX_;w%uF zEY{$-g9p&o<*NforXzt|7K0q^N3F^?Wf_0=-8)OkqE3`W zBVjzoUnyXk46!E+#QM3Y4&naA#G35)na>4Rl^6)418{O=;{yM-DSn90JM{%+h%(;x z6876yDmkKSo5sXuXvixHorc44!d?Y2igP@@1L!AZ2J%mel5!22=5f_7}Bl z5(kEWS6KcRc;$NRtaf}EF^#HNRo5A$ELBxmx#mO!@2+HzRKF~SFpZ?NsvT~M3|y9C zc8*N~w+BxRFv&+77nS&;nd#Bx2AZ-Oi}yQw0*^If2n7K1@=4kg>aa-@Uk<4n4Ku#q z2Th~2riR}Um%!@u9U93j#Cf>zT8Pt?;W1Ww@8|RNnYY5%6cFPaVF$TGA*cr9mL}B7 zrACF(p%jV3ed2DBKE89qi-;qdw&Nm=GD*ZhI{SwN_D<0!0x1fog=!|L=Ce39fcSzb zU58)PBLumrmmmtPh(D+cHe>j3=H7KJdp+SwE#?R&&rei!eiX=Lt#p8u>WwHURwWGt zsRSfeBo9zTBs{j;LNb~@f?i;)@8^1){f~jF22j=U7eUU&OZBjIrnqP9Q?3v(cF1G9 z3eeI~RXEt>URznwOxkC(x=f)AT3r^4hcAA*#&UWSja+^hx50!`ew>2kT66+}_Rvrw zLBVYOlL@?kHQzsk!*YT&`c%<3f01JEg`mq@5`kX^C?3#=HJ0Lk%J=X!ApN#daW?wq z9Gn|@JZL8+Ae?$r6E(MaJ&prOSgXDiPn{&LVX6#_(@Q#y9}!+n&4AUd!V7WDy@!~% z+d;|_!YKZF=oM3xk*cQXLrjGDVI*H!K3atyVfH2gsail9Q^1&9BCY@8$3T7cbwd-c zF<`s(f>g)GtNbJBNPu>nOUz$NxWSM3_GwYq-8ofw`{ z@)0@Q0|xRB=bzUMJqu5M6)|!Hs2(Y^9JV{>t6|gB zw+OBLh7gJE9LyJqsER^Edpd<)GHrs0Py;HhVM!#UQEk` z+)7*-M4c)t}SXbfh#e}m?4m5sVWnjgvw{Q-KM~1m-QYUCqKdE zP06SSDavXmj?LXGu3yNGf8T6|tW?A=IT)~t!i8wm><+OEFCC)ngg>5NAn@fB@Qd1j z3e&wo=xX%)+@i?;Xo%+h)fZodO!45{Lc~Bq0s?q)2e~#EWG`sjZ`b)L*g)P06IF_U z12FwfQjL!Gdta*A!)ampzkquCPsHSv4#Fs3<#@Rof~|-x+1v`V0(ZbG$*B8Tq-720f_*gL=O2nyvG*SJ4On+ zZBV!02O>U_(9}S`2*~WIpJLH&`JKJE^O4qX#aX`!%_^DaK!|3k^n0Hi&A!cbY9S8% zM}GzkB{v5dc6p%?kwS{>{qnNd_Z8Ci!5gk@^VDiW{r8wEZ@I}XA2?Fay}i)Xv)eHU zVR)CA$rai&_;i&sk5XdY?s(v}N1Q?sDvJ%<$ah5+Kj;WOYsQsYZf@;fBO7 zGfRCOGOROrS8NYLB2C7t^D}I~h7nv>O8yW>tpe$!Qhk9s(xPj3r=k>(;ecdJiVDKv z^&X4>%(+>Az?-me7d$dG4Q-AGC@zfTv_fUNpL$Y{2~kN|^B0;`N!6#rQCp`gVALGi}oaPRY-+5aNig;ZBYf+lq!%)7%?c4nXyF*xg`NI+m>S zO2A2!MEE0%T>7gD5Ho#i%iHItN#M(EmCVHL*|ogN^Fp1UR#{)#?jUk|w|gU(1Ek7( zqmnU8I5MKj4O&q1v3~}4Nw-S>Y4Y^sTrB6~v_$>Z>K+TAutX5j+ZNv$zEEtHfj3kB z2ImL~Da$jMJi7;nOX^F@ty+VE5B9N~H1vpYN!u~Knzs;y1Wl! z{s?`psW;F;b>ogjd_q*~@Xyzh3ud!y#8bwU3II17Z5VOff!vTrksoDp3}1c*|7>TG zgb*>fIH=0ldedb_NDzkT+SeOh5u{Ec}G>C?j?5UBmItnt7Eq+ml0 z@`@&15UHj^86tT8)Drku^Y%eSyXqa@&f;P6&cA@0W~jZd{bKQ>9hL?llcOob@cR?) zeF@Q?x0~(HNf|GHN;d-J2DieR@xlpcLgv03hdOE%N{1F^1get{gln-}nP1o&G>~yW znOXxI1y9z$a{Kjvv>i}hmM|q69!eMQp852K1|X>38zQS#gv95;%6>Ih`j~&bMKJ{pELxD2ZvRQhaYU=#ggAc{@wZn zZ@PLVpw757L@GY?_R(p~8%cWaEE7A0)`o?v34gqwnO<=n?3tlK;z<7COVM0cjw&k+}-VhFF&ds$Mv^#Nj=R%@KM3Tp$N>B z(@WcmHMQFk=wK5?Z-`NMCiIhx)>|WQnp1pQbiC)<7OaHchnobm0Hr%BR1FLE&j{{% zE75*W)d(h4W}{od`)~r}^`>y!Et8A;J3G%OMPX!-fd4klGq|F%)GdHsuY?40kk-k9uLFW%u} zDj}Q2Zi`7S(dnz+7Ct<<*beIc+74yy<`mi~-fesis)^7|tfzW!J|(~54~-ATsC0Dq z+jIpU!yIGpF`Fq0qph)*SR1e&^`I9*b^)#>u;>410mknowI6o*ayz=XNgq-``pzdDwrs4`Ydmatpg2qn z^os)-ZrPfl%vpH_DrYM?>K2gboxEbm^$9asJLKNXXw1@JuD&m6UTaLKMic{Ba=zL- z#ppyDM14MvEC&Ajth2nHAfNE%VEynjlk3V5jKKJKC<=e4B@Y^F=$VcCvHMkY`WHsI zJ2xADZD|l;PI8@pdhPRW)xin72lf;^rZ81vE!N6MkLCqXv#B;`wD#l0H7mV@GW*Th z%4a5Jv#tSFN~FP6uM4G1+%^~ce|~m2o8W*xDHSf#V$X5F4uz>csfBeE<*sl(Jl(&o zr__J^KGt-qh{tHO`}AtI5lyG;O5*SctBO%yyZTS_Tu|dUTPML8{=@cO>?*!wr&tMe zl7hj2m6FFtc^T(Gc)C#hO4f98k%aqjGuvTx0hx>mN7g*l$uiJR^r&qmYI*Tv8CK=5 zD`Gu5c|TFq@eEE3i3=}^B(9SLS;fxZTKP(7DPFq`!5H*AD|FeKqh#D}yS0#CAk_GT z>Z&xB+{1Bg>RoEG20ohpmU<@2tpD`q5ZDP0BxH?l$jrAHT!XcnW2A2y>^pPvPHN;E zGMckwE)6PeH_>};<+f%o;B{N5Cwoy_4n+l9mKm9DHjkI#n1k!F+W^=(mS0{y{Pf5| zuX(wZF4#;SpU({{^Z(;kl6;b_2Xa*UxWM$WGDHJ*M!dxm(v#@2_ zuuwq^oE(9u5MXKgNXByUIha0n-8-35t6zJ2aw2u zeedeWC~G57W~pV~$09M4@7T?cx}#MvP-=hFa4LCqr_=Osz}<9z`_9G-3PpCeC3C33 zzGJIYl~qOwuJfFKELL_9DFpuZbdMlb;rYZq>-J~Ff`?xm+1|bNtekI!eVO^6&kQhv z-5Ww&+MdCeFJB8?meIFHct8EQ+eh@;bwFV+=(G!>mA zHM{qr2!}ba8|_!3)R_u`G16+Qd-D^ksrTdC=7-zG65z42)6Bu>aX)GlGW1?7Tise-pN*kf zSoq|u(V};!$e`}N`Iq=$S^CbggtMbRfu2!Gm2TY4mSsxnojZ9Y+l$d_AQzLu(H1mA zIvC(gQfQH1f4OJa-INpW@V`7R#=Z!YD>$l?TG)dD@32JjWM z5q@rOBG8yNZ#>L(WCp{R$f!O4b30TGNuU+sLMbTdQtfwnpz% zRpD6rWp*giNfg-=?PTziMF|P7t#SVJAZ)YC;fiT9^XcvT#y~d0$gY21WH9Ryc#UvC zBN%izm{z%}10`nDgA(_srs8|MGfCB)a&_2Ws6Zy47r55#pEla)6*yJ~lYXZuTKqP{jWD=f@g}-F1mL4cpyleT@PK zWDoBRRF8v$jJV2^tEnl$t8JVggUMG{)1m~c(02_4SK_j87uIa0tt@)_$Cx7z?`Iz| zG)u4EZ4WV$A|cFE*iwySz8j$(h%}NHI{YwSLHCDfYt-wj0$bjXH6N$HHyu$E#0`Ag z{YJHAzQwl{b*?WqHor#-jQ|xr$D*@wILDG_oPjaNniW##$Zptxm6^mE#nZy*xcE(< zNlP_Nm=g{?Jk9jyW;3Do3HLJl+~dl|MmcKrT($yg*>4flN+(!hjB^Nt?&t@9UUn(I zGC2&|fbG3ie@>`E~yx2gsovpr2a_;MRypP6TWOO^V#b(L?`ENklwuYY*ZA>c)D%26hKUz2@zh4anbp2j-usIPS-+^mG5 zaGwy+#j;jBb+#2 z$*go9npo}dXURgTt&zx070dK98Ha`x1cxydZ74Oo=>$h+Rn8?xuW!eU?R4^&tIcqo z@9*v3;iu7Z7053}E5}Q8u;d9q1|0e8e1-BHo0!xGc$@d#+xwZKzP8Gfr#HLd6hHFM zV$gEmnj%Y^DA#$VvvfDI%HzNba?gkuEY;G@`4;Voqus0`cJgc9pJ%Cr#{<+lC9;8F z&gN5-wqc^hiwIT7=;@38{H2x0!^MxaHVE*sS9o5mVT1J%k0W7tO>eS2)7;j*qoEV; zd|5!Td(d;@v9pWzX1pwhMg3B$XCRfw>)YM2@<}8sf3cVeqXWmYU#gFd2k#$f&wK*W z7hEW3v^2A(oo@i9s4fV$Zg%RD6wo(^hv$Fho%tPu&Bp+rHmA%Z#vtKt{4;IXd=?%r z*59s|Cvt}#6-EyZ20H=x(-JANA;tw6A(7?)ik?%k77?N5KyiWeeKUC!pAvzu2fjpe z_MxOpN#z&87)(zfRdC1%TTd!I8O-dn(?yI@ZN#3PzxQNx-naDlLdoi$Qz~IZ18OvN$8WLK28g{%PD(Mo zrFHYnL4{FE*IUts_=8T{g%xc7^Gnf3`D!ul^S8B4x)pJZx_>aoC49D7UKJ`7 zm-BF_c%K}D6m9c9lgIAB)C{j2)*zK-2Yv{m^{hV96B{0Zj{W3>chx`pibqUA%7;^S zrJUc7G(LCQZXNYVYjNN?r~B6IOqI%O6{$hjO6&EL_Z72h!QJZjY;vKu!GVANE6TXT8IRHk2~pJ4BqtxXqM3om_aD!(v-o}|L6Fha z*?lPw@51%0z@faxGrm{fjz7HVoz?lV$?2z3sa_QdNWOzRJ_dQQC>6%7a$wCEZfQnQ13CbgzrA;y zYVq|hJpgMB(LvswG$OUTd18({Wfv0{w+jf686C!FFW-u~Yc$rLjKyVVXM2_KdK=cu zPLCf!qv{Mj1Ud{@RNp{=+&VZjlkQQTg6Vq>XHxJfg=!yFfln#bMu<%0kGmMvY2^eP@59G1V9UQ2{2X?Ym8@oiN8~toN3>9B9b*~7B_ipoa#yyaAN~(BLf>)k9kL}9O;i{~dc`!` zaz2XLp(*wgr?8we;KAdg8>LYFpw|KGc8^-%GbfZoi$;_Mjt}EGHn})_#?5Xts48#Z z5LPWv%9+S}PjUDNMA=Sd?gg@HmTIdnJOR?&(GAlG@P5Cln%aG$WM(yV5mvMQ1xJaD zk0EAS8M2(BTVWIfY+>4XnXf1;s-|SB0s@elJa$k(czYs8Rr@2&(iihB&t__iRnJUR zEeak$BONn8C*Di$eyrJYc5yU6?z=MdRvI`o6Zn(wyFNbN9)6_p?xZD=;Av)3 z8`yETv(1Xk}%1yfqTtXN z9vt_;!fBssPMyZbxW(ai+3z|{UgMRyw|4#WwY>6?Fx-QT&&#*i^;#&=qI@8x53EP? zJJ;d}YwZo_TE92G7}vX(%n{iV90s<%%3*#ad)HC4GL-h^UE}_@uHj9z8jFrd9Noou zp4^P5F2*Dad?*!HC-RCcnd7)&1O$ z^-seE6;gSb!cIw9;ziXH%9>=T+^2Qmz@GvJ2apil49%GWo=_fOD}3O6 zhznAdBth_b74XjNZ_OLW6#JeX9s_d{V#_Ndm_fIXj(WlV5FWP;JtpO<%eB$MlDf6u zzoS4-2tA0CT%9PRdL#7WCD@~gu%v;tolrsBsen7uVIlGm@AKoT=Y<|SOY5h7p+w#M zn$bC0z_VNlw%IX>`U2-Pg~0O!r9>XExQ?k))7Az=<9i)$M1p)Flf7Sq9#xTxs=%*5`bu7woUOmm%R#I~45dzuk`U-v6rQdAM1G z{Wli%j4qHIto;$P*B3HkSOx3u2=@ePT;Q$7=#8b*b$uSXM3Ks*mJWW2F-ZjlOdlT~ z5bRK>w{pj3qJhEZU7TbdE|zxARBXj(vmbr&>%Ubp+aqA5?ri}nXf8N@s5u5~IVnV5 zP8HX;tskn)c@^DL_fmbt`!c@OnqWb!I1r|h&vwV;DT<)P>J``c23@dGAW976&cX(2 zG@|N@oZ%{8paWrY4~OVgpT58HjKfdSUYP^~+#brv{mUgZqE zcr;IL4#kuE`8hOnL>qE;F}Fn_3Zs5WEgWNx-9QZbv{qRq3hl;a>@bIB*KYVjVlVNkK0Gc$xNP~XXoM`5;ZCC;G)h;uXt*X;? ztcBKdJd=R?5esb2F5Z%mC+xOV$pA>C&H^wIJxp8|;2H5ReZ@j?=l85gjl+5u*4S|l zJ_<7i?69^NGo734HlLN;!iML2BC;@V_pPiiS$Blpf9c|`P2#NhU63Z)jD;Lw(*5YW z%>x^Hg|_DiCXJBLFb0n=oIcw7Br0iV93y$EQQ-fCjV&MxZ)vcdJi9?m%WhbXwzg2d!p>GGMJZwa|Q+2EvHkrq*83%_E1zx)1nwgn?M^n0V@ zt)FaOuz@_u@s&j7RxP4o`}xM=b;VRR*Y{Aj!^u&KZ-p!%Co>YBsf%Td&Z17;xvxmF zy;=R3d(z*D_vdcv%8Fi{Emlja0L_<-2M=>CIff^_i=I1A1&H^(WudEo@jY6*SfcHS zYLfBkAvN3^upg%O;?PpOwRtKD`zvJGC~{rc?X#1k65SL+Zp!%=df|}bN7>$)enpee z=kLVeFk0H%3!-jP$Bh-*>m=jMsnaE0Y;YdCyNqF)lu_l|+#yV1V{Blg@{XBRXnq1$ z8dWeWjn(WOAoY%fB{Zcp#&=AD0PWikA!aUIaWU4@0R^t7dNjdCA%LzIb33GH%kM{rJ?XAC}?7pyZK@=2~8d_RpXoe7wQo3O% z=@>vd6$vGj9tMyGNkKqbx}+IexT65Ho>_dR2 z%sUjTQxdg%y0~}j89K!5K5oe=Pys8o9b@CkOziM)r7i&jY_~nsUM~3n&9=dOZFa2^ zV;YRl$%yuz9b~G4I2C#bkk-W*HF1m?NSvsw4rCrW9VKV~r}^@)U0@XYF)l|NnEn3v z;K??T^2cs*paYWXX74W#z{%@gPX}p!*bIRYo`2|{7@(FD%I{@zJ2t+sUQP(QG4+`9;kP6_Sz3cew%*lY`fmaeTcy?L5DT#H$&?g zH>JaQrQn;9bC=So9VYV!5+yEslM009Gm0Fg&|_|Tz~e{%!(=A zgM=u{z>h4 zs7=1nui{lf4+`67C(7ARHiw+9`P`lgOVWI}cy>@PH!=IP^}?}eS*h1vdoGPZM=S`VCyFGF65+m7c27jNmaw)9)|d2D{K zyq-K!)N(=6gqXIbV+IW;nhZwrgSBBIyXtM}?*nYo6u2^_65Df9Z8@ByiIFs7s5m7u zrq!-n0_2Bmb-rdl7v#Ro8JMAl&}E*C0}DwtLPCdK?*GD=&Vg)v$8EB%V96VMvQZtI z%t)C2SgCpEVC@SlQcHFrAtBL&2TWS9;V~eHH0I1o2zxvMWO(B!va{1Z@uRIRfY8j1 zK+bsJhcOS*`XeBB7h56o7KP9ayzMl>c_aUmLQJO5`H3PV>!plmh%YZOupj=e#n07a ztPuSGzBmTljmH{5u+g}bAw!FwZt`h!D**wJSe$rKPgkHsBGqjM#;i54sc#gt1rxBt zQRi6!z%Pswh}i06zh0imqW%k}Ku3iV(_$Q6?4mvj29G36yT%g=ecmnIRp}!8mkcJtlU8sRIgZt_kc4EBOUTYG*$Kig`yY$>Av}Z?cw9H8OBdQ&~r=g~H?!_&G zvu#@I-L`J|9~CMevT<7eEKJgd6x^0!LyR6p9aT){fWIowSos_nRH~Dc?^QZvjI#JbZ}m(-Z7|2J9mB!9v>p|~q&ndxW z{fL#cG)#j`Y59V0CM(`cg9II6EdK6qOk+vFmkX17%~49h3li;gERpH0iECJ0soa99 z_l4iSqa$s{nY+an{auj#XZ{JL3ku$N^Y|XG$hL`8|9txpiQ*G;;hLTI$V8pmpT8DF zDTsP*fep64HzN`Cp!<}Kd1W|o4w1O? zjL3QKB>nFVv$jLJv5C%;a1D5O-Trzw6=~1n?-KI&0JmEIwX`%%huDl=gFgF+@K{&K zxy@-zPcF%2?0?zyvBvo#1sN?9%3&}CIa-@!Ei`U~;2z#E?~QE94_9h3dJ;%Xx3u~_ zOt&79gpBSuJ^s@&8q<_25lqr_|LIJS@$YmY=AL~yGIZv~WuX(f%=~bbO%`6}{lLN` zO%}7u-4R7dZwfPePJdeNl&yLeuYT1=NXIw*&rqA8Z=DHZUJNEb_jUiA-$VNkKL`}; zv1Gpevjbq6L!WJ4Xq)VCi_pvTXu&%q;hSA8>F^9wGdH8vG4>k3??%WGeKTgBGD)l) zjkf3CGuQ>S_GNX18aW)ocbwL6ve9>7JDg}k*FZH6A5IjLfgv?%9>08&DIFTyB_nJH zRC_;b3f3&=7RXk_2kud>f8HmP<@ zB_4rh@6IWw3)_uzeA(eR1AZf_8m)-^^3{%SJ1+NQ`$R=W8J%G(A43t!N;Zzus8QLx z@aPmfS*HJS0ci0r?MFWoNlHnzZG@{r%;$tnP^ga9ijP!HlRrNK1+JsjtjlcnM%NYO zx)jg`Y|~ULQ_Ue-(WTVxGsfT@QE^`X@D4nHcc4t()EkT|y-9YVEFpCE#+JRea6r4? zDbMBdmt>T~0DlJeWm{+#gCd0e2#&SCBT9e!NzUg&c!^gHMVz>l*C^ND8#g1U zGBOeH2OC2p=xF1o$ILuaicOY`_>63rmyOd ztmckZNK|=aa^3}P?PcMB7Ua+Nc6hqaOm1^iU;;{aa zeOLdb_1htTIVP9r2fq(#K5d)D7L!#PAoiveCNSd-(C2aSGK6DiFm%5RH9*;gntP3<<`Dhw*HXt-hkY6r@PN^@SrI# zn>u^ds$|g`uB^7+Z6N4mnlcA1G^)K1CPJS{?F`p*-@HQ^AMJ{*(Ntx7>5ByI<)S;3e1W;TZ*~`jU31jJw@&Yw z?i8mUZJl|lZz4W|o9v5Lksfu}v!bx!;I2&Fm*=_mmTZHik+ENt@sBTFZ8Q?2DZYop z`*cC(_2SR2Ov6pn4_zeW#qaJ^xc)tKfb=FXDW-2cvPTJr#mgRfEsw^W<;4GthNCyN zTYb&OhqG}m6BMKG^1AB&Xn39+@x}M|1v;)ZE1cmqy4p7~H*lDu_rrlfb5pvQ_x&G_ zN~$UE%0}etmNHVnQHw-3nAOzOn2};$`|{uf$?4X;w26Bd9ca=7jNJf8Nen8b{7#_$ z^$|eWP?N^5iY;~Ko%fi{K`_7rN+{BAv2Vo2#sSN4ZSt%)u*c+C`XEaHZo(PGpcW}s zwCa}Ei)FdIICXO4X7YmtA}ru24T{sgOi+$xd5B&9X!31B>O2B=cFgJFlzg%>V&=6# zs?39P~lVu*g%~S~k{Rn8s%h(_N;+l)|f%xRHqSld&nTcYb z6;m;d4Xh=A%)t72#K-72n8m+Up|tU8aIu(d8ul?Kn+=@oNR!46k;`ASZ!96(YV>-o z?nSiAe|#^{8@_Y8-c8d`oJFKxl-q###*74N6YheRog&4Sq-O)Uf`v9HbLL%fP@fK5 zpTjveZ7FQy^RkEe#l3#x6;ze9AHkPEQqHRUL4*i^+#0UWw%^odh9J028ga6GOCKo5 z5*m#A&~YJm?S4G6_urwHSmsP5M=O!yYC?G%mK#S}7O<%Ti&1*K`PUs(P?^w2Lr%oo z(_j7556?bnXzHAGk)+3;L-NGsL!*{t^G9QU4W!7;pXKFy=k(gE=eK=WM3BC6> z1ZooMf-)%rOS{gk9&l^I?LE>L6cx>3q-d_RrVtLg=Phlx1f^D|v?xk(G6SEf!py|; zKYt}C#4Ic7thUPB6lur5Yex;!-yk zj)s^L8?0}YS)8kP>wDruq=UKZ7l9*PSBfxx1g&_0g1Ib{=FdXKFG#LT-4Pv!g##J4 zfb(N%1$szgmE)A(J)?TLqe~j_rd%T$8n&6b$q}jAUfnSXt?xNJ%c?g{&V&KO=PstG z$AP2vr~>oFMBL}k!t0?QSg3n^bcpgKj-JTSo7?vA<*J#@Mw8~#=1deC=HN)~NVIOQ zX#&A$l+aJHRyiXnII1<#CX@pyGjR!yduk6Uyzb0uRbsga=XkY6YP#MXmyYyYIuvn0 zEhrOKwKewl>f8@iKBHUiP9zgKq>+xwM92D-SeNMq82qQ;J~iQMG;~y%|2_zmY5V_5DArSLBV?_t#UqEjKYvcC>>b^q`A4GVnnbF z4dutMp&C%R zD~ENF{ScKl7PM%t0G7{(()qy*&{z2qIf0*A$8(vr^_58M86mb z^V!{U)Z+%DbY$MZ@zIjKAm#_A>3#9CZ_Z$u{rhpY1wH;cY zTiWgKZRce=dsf<_KwACe%q6l$F9iAK3MmeLwm%%92P$ z7@|bZ#d^!$ab6HET{=->V*Y;MNgVNu&Yv`F;Y@+7Bd&i=RE#nyM%*-fHpWZX)5N{= z1Bxr_$4$Qt3mo~(=-A(x%8+J|mFWGziU0Nd>$iyHsKF`0b-&u)U-WWm_gAO)l`1wT zvE%G??)`eosuBi0-0#*Dpc@KLeqpT&2@}I?5(2( z#aS1P8_gJU%8e3DmWZW#0c!OINy-Y_38EsAml`Hk>k3Gdz%r|j-$Pi|!)2srL3efX z*slNTiyUkehTAXh>p@eP@MTXTWpazf2*3W78>?;zj$|^=>FY`#lR6hOdHZg%{xysK zqe6U6TQW^fUd9jcl5pzIVYlSpHc4d^OCrSN{i)i#V-=xGAS8qC3~m;7M~nMs!76*h z+{{dvcaF|J5|Ly0@aWX+&q<{z9fg9u`p!1n;_T*)IW!cRlpOiAO)js?#%4GtaBlL} zWR)c!__vF{*YK^?8+duGz9DaoJYVSFGO(VV$KU@$8>F)(B5SG!-)z9SHE1>X1L}fW zmZkOFTj^{iwc9LR)=pg@LMKXo%;e?>#Ub0eXc4qm2`t@AlXtgF9D5x*b8~whxs~b_ z-S)BK;P_X10iGrVO)KGZCr3@xdESqT^9FB@Zy#_K29{)5nW{j7mdwY;N3l3lxBX@4 zrXA=$&#$bIrZMgUh!j5IYGP&sT8PM1bjWk%HtP?V`q0B@W4uVa2`Ge82iDMnt6UcV zqt2&?4sl-5M2)}qcJT*572l#L`PkM9fF(05Lgc{%>Fn}lArAtk*s(%AKI}-K zW4_Q4PMt6QV(Iw6gDdHgoGmQ88EsV-c4=nh~>!eE?fJ&1Wlp1!3WQ>xr!W+V;X zW534K6V@qAZzQmX^vjzT*TPE|8j2w#>+RgWWYoG_zFcBdS0+VM_o@K~rh^yG{cc>p zc>B?+haoTU_4(E9jBD{kfHFc8Xc}Xr3p6#Js zu{A{|c2gF$|8k>)Q;QRR*Wj;jBtRXT4W(f@VV|b6vsVh?)pf{4^k_`*U%IEOE9zs8 zEs?j`T*8TlL8ygC$sSS%h8H}_e@(H5vQO$!Tq->uB=`)kd*~xnb62n2NX?Ykk@L4VnZT_j4P>;UEi{e^+YYc4woM4Lac6nv(i95?&SKxpP>eG z1AF~?eFgnkM)DHlNCa95aq?+bF*X_I^fo^skLCE!|8oM6f-DvJ#$7NHnkRK;Z9%_Rt*ZRMmI6Go zynI^Ht*~I3)2YlUs2d=bkQZv?U9~!SFvJoj)HBj9uTtka6=qnYDHGrW;6M}bL9z{U z;bFHyH3j9cDlUm%9W2VWfQ<44uLcXt=oI%F)9YAj(`W`ftZD{8Bl)46eMcIvoeq0D z+Dl^xwL5rpk$Tz{D-iQ|`TS3$L<=7M&ezj>pv(VhSaWx09OSoYOR|%90cS|22SSwp zk26&1vePdn`g%|Kme$TgkBJZ4x^4q8O=g!^R>eq_FE< zVRM5I(C1Vty_BbvwQ9;v$KS2e^D2^Jx1N9d5D2p9EU>VTHhlhTXdir6A|rwKJs)8v z1(A^+$6gV1fsz+rp7vNY=nA1x&3%k}A%XMDt_-Fk*k|@{(LeZd(QzFfgHCKA-2=5~ z`<0tl0a}D)kR3_+J!?8C9i@U-Lj|I(p+m=~CQKs6NN6Wbfp6Dzxi7vgQERnetOc}{rRhIQ z_6;!yh!zw_G0tyyii_FS*cc6v_JxpOAf0ctAk}FWN>Z}X0_7pYJR7EG6_*t4a~tai zAz+0)x}Qx3UePcBa96l^;Wz7#N7B-@db-qly=}id{M%q84f~t=<4A~C1(J2YgIIXw zQ^x~UY1pHF0(1ZK*^4|%{r}G23daIW z(yHaD;0cL<^-b{(`s54NSDN8{zZLoRsdgn2B@HRRojko9g!$WT3T`6|%o3|5vhkZu z5>9`8%?{*g7%-b;wE5kc-zMo?j+e_2sLjJvQ(lVb1E8$C^(r_Bv4D`0^1?W!@>!X2 zire!~Cg4{l{jU+6NSyz5ip}SlrX5rISIslb=U{$nBs9+K78W1)&zNimkkR)N@wnn~ zXD0TlRKJ`AdI6Af65YJ<=Qz+18`ne~R!ybGKK+*U&j>CCBxmvdBN=8H7S&(dwO4XelQ$;bqV?? zPj^*DNH)vhffXv8%`yz|f2oPrP7dA*osbmoavMt!90UCliVj->cwhKnkNP6z+m{L> zdIF0qCB_DB>C+EzI2hhLUs^vnv*9-ebl;M4?e>DbERNcl&B|`#MmudWufGH!V6G=V zL?#_Z4+8{9=RkzZ5$G&%eO5}&!O;lBVYHN||1VBcy<^HuWO(CRezkENF?{{>(9htmwk%JlbXICKsyxFyk zXOMD_Na8xM5JJqDwRhG3q={LTFh4LE`?`|*;5KF#1w46xt-sH~g1hwl+~4rs`31R= zvNVjvgkf0Bit+tXKZA$8Q%;?Q7AE8*J=Zz8s=kisFdtX|^MRH4D~?WYnrN6U>`=WC zmO+ANSCwkclsdA0P|75npYhDoDVH+xr8H(;z%rzR{ogMkH!Q_qs)X&xf`q+$nyZ>> z-z~pRjO{2j4@}a4LrCKfROg4cLT4FsA5?q9z%%1vxwAAA!1N5EG?FP>bfV2>E+74= z#Uqu7uw7J|CrExD?|dEM#`}G~BiH@GXO~hR^B9;HC&z3{JMokT!7}5_BnHlC7a5aK z=+8}j-4k-|Te`91N+jruQ_peWcgEe)lkwlF;lKh6UYXaizsbOvvbj~04JD(Z0y4xp zLYyzx5(z`tv896V1&Z8%NvW_wYK435BCs}Ge6?svc+y_I+HT==7SBso_Zeen&)d3a zEqAqSh_11n2$4!Ch}rBa>$_cNGTQx1%lh+{H7nE;JwwI5%P3_N=As}C3ybQ$d-cM> zgoL}D42d&2M0(s&IT|tTh8*aI9~|;^Hm^_(ht(cHU zzS%(r=5M(CieWw;XL==rJ#~O<`fq{l*!?xY`^$H&Rrt3Oh=&<8^1s>0U%xQ=#}4&4 zK9WIBqy1@F-gki0=kc(p?x{4)dy-$TZweB-y?0g^8E?pp`9WtIklNSu8wyLQSvDm0 zy*u2CkVo`?;m;P)aTq~hRwfG|43Wb;RWBe# z&H7(>RB%{)zIxqGB2y~#plO1#!#^xuPk_jRY7^I*l}%6KM}rHM8Q7Es&xaS2!J|?h1O74*|e}ja@kZqWmVV)l+^V0z`)|fXZ zqUUMTh;r7yy*RIdv%gIj?KPa8qy3m2r;ix(c)|aBJj}!w?)k2@UYGjSo~U9nZNW{b zvKxg7tX+YKL9&^*%nJiDb?=iAq3>Tek%o1;yb2!OiulM}bhXp(%YkR=EPWLmZ_!ZG z#?eC&@Z^lKd4Nn8(DbpVtT@l!MXYWDs{Rr=oU-WOZf%!+vU5RX7Ktp`QqfN#DdD!(P!497%n$eR3?$LrVT2kT>#4X*{k7%OSHOpQL#lv#wcJs7N9QK( z1f0j*YIN0Y%X_D|BfW_|h*!!2Aby(1H-#WNgjgD1;BoWDMpP1e`b3aM8De%LCVODK zXutI;!9i<0tXDPlr6FFs0XAFFv-dj&aOGV3oR(n_2%Hvrn*Rj?dVOpVz!k(;@#Qg) zGoYti(*l#%5YXAq1f?_R#8Y}?bY#jwvJ4-2?OSjDs2=#)aJd-9Jy`8D2XzIGtr#jL z@Wz`5@-B~d{If|v?0M|35&`-DcL224(^sjOF!|~Mzh(CH>C>Zme}do3-6YCMyfWWr z0LSS40|nybilxtrnDYWQWP-%1`zRvn)zvrfx}`3D`|c|x;XcUQt00q2UaLJ}lgFyU z)C8+bq#4#iLZ6S_(R&jUf*@nUZ(`7vr`HI=Sp)!}hi-)vinJCSpo5BK3?V$g?*%ak zHp0Kqdap^HCX_^vjlStkr0y%>O$J=^jo?JxDFzhthvzYhS3fP;?10yGvG7Qf^*RxD z9bO1D2R~+uI}>_%cKm{w9(dl|1Co%G0Q76pKR1Ta>VkA)HeW15_8bLm2iX>V@=cPC(#t2g<0W0+vT za8SqXXHOhHFg}+CzFwo}pw&QWk4BOL+P!p+auVYc9#XXK4j|rRTy>aBNLe+cyR{$% zP)(TR6kuL}J~E2ZAD9E=e>S;s8=_7$2Ap(|z%VZZc(ExahB;0H3TX#$t_NmUcR2LQ zq{n6edkbThN96HxV_c_kO;J|@=@!moP4VlfF~6WP|4@<%r= z0a}v<>}l53?0Uc#8s&Qq(4tX3-gIBE1okT0K|3y=pgJEVZycP;y$oz3pIm1S?>dl6 zlV}^fD0o7GJ{p*`fVq46fc!j94ij7EIdB$>j+Qt`<`L&1)f+NlZ1!gSjO5A2;EEx{ zj>7AD+Qf0|YU>bw3G&;d8&{8c9pJYCf+$DC&xlOjo>G zV?i2VP#7ru;wMzW9#}Id{yaJS;WRea1l*{;f+-tCXx5ub;Ez#gP_3$<2YelF5KKdb zk55kvq^i!2{}gO^2t6`wzE%ND3c)E<_kFcJO$nllMkd7#{vLIu1TGoC)XP%mB}M0` zrXqpi+ehHAVyZ9OV};=cZt+_WUdvWWkOgr|I~_3hkbraJ7p&rYwi`^`1aP>*sWZUg zivrAN>&|cdsgPkI)!DW#GkYg61iUU0-U;Y4p!uxf$|vX;aS1RutkaA6mg}S+!{5=I z+Uf&d8?oNra<_K+_ejvSHrt#mb*b(%{v9Xqy7_nr5pV4{al zO~zI4S@_M0?(XxlG;w`)qz!Ll{8WktS^B^c0xSLRQHqVtLb=&cri{zcZ!-@MkI&XM zz_t|A4CgFTG#aoPBIh>#4B3orhw|=bht)tMAZQ=p8phmJ8q21w*s!XRr@?&S)TrDt zC$|Y_97yNgd0F{U-LV&Y8>2Bd*C{k6L4Xg+5hyfRQt$ zd_o|I5c9vd&o-Hdl?(x`u={D#3)RYf#jCBdiMdDt8_LRx{+tARHxy)eF8n_LVd1k0 z_|$E==|1_4`ZfUAX6a8BR8ue%wO?wY6x#r7PPWDP`1dJCng8Pgc)0?5;e|{}CN7J< zMEICoccEsks$viV2Zq#xwCQ@x%a(2cl9VE$dBqonn1^{gurjjKFMxE(oICazE=6;D zHzOj@+e`#>%*JJs!Yo?@Zq<1H$+7j{aLz4RH?b@RHwf~LpZ4_c4gop3t7W=|73qEW-D<&YcM2FPv14rlajtwH*N>AO z)(*-X?-QcC=*j4XF%Dc+SlnxFK;z5rVu}pvsuaD>j=*b>z)&o=EgXkMf z?`@(yftOTKnjEX^t}q~DrL7-t1KQ}i<*lBJHwu35G{GeBC?88Y^Ra!Ouja)tqdAO? zGu{1o4<}vXxKGur2)K;pI~69s8wJj)V{^x%>l+&d#>1vYDA(Sq<7rw=5!s*9QJIN0 z65qE4=ic~(bDGDkxwu3@c_V3H_Kk4hzyT1Df%DQZ5wOU=f3j=2Y~uP2jE;GwJ?A$m z;}tAHT|9s6s~=#(G_AZd#uSFRU5XMF{^jmg420ZhI;*!k@6JQqH_-d;k42;jU$rpP z8Uz02@Lmo{tu||o&YJ13)f~N3h z2e@^&q3CY}l>(M=eiy=(b%KK$<9r zOD%-10N$<68kHwrrxZVJ@h*K>VCE#aY4R^rUBcb=<~On;#c%K2w&bo7M6}~EtL!$1 zBN87b@4k;PN1Vu2je%T-G&C&uY3x^z$quOp;{z3m9AD$3$(afPA-FB*Zd`y47~R3s z^{^&Sg_4cbFy+!9aQF{bfKjXI@1S@2jmuKdi8z`^_T$P6+H`^AJ3UsjpnsU3z@FHV0(SyNW<_anbDqCw zm_MXS_!^6`^VyOyUDLEcoV--Dc=Pkk@m{u9_uE#I^j_BDUCIQT?qjz`-R55=vKN-A zKc`?PO?Rva(NGTnCbjMhxj$_VCMEIg&rl;f4e!6yi(sO10*^yi)5u8Ty(?NQ;6kiga_ zfM;JODH=ez-@k|#d8H#CsLX%3XE2?O&Gbf#Tr^a1%yWj{R< zCh)-5Wc~%rBqw8ECdh80SUYpFH4tB0+T!+$*wv%L0cZ=yn;a`!cUOMF5(~%2zOqS4 z^%A?OQlJ1ccK(kSnu^!XzT2sKXGybi*Rg@;@fXslw@V`*-k08fE5zKK@JAQn@pRx{ zW#-cJgo+&ds&dR_`tJsv$;g(Hq^BjhiVzZ@ZSGH+Be|S>7)Hja5zHE!Kr!Yw^p)bq z+h8@YB5+(i|C@I;d~@%2RPQCx2ID@9O;4nXCib|rk7h^ zB@xF&QBiyg8)C+IHTL_n^021DE5dB1e@eqtaz;9OrDTx^(6EzQAI;mHF!9avFJV@S zp9hYNxCA$h@?tr(Z;}>_20d%5*p5UZ(acgUKFzCHJ}BP-m(MJtodB={i!0!r07J&)PZc}vHo8&WIbw-958l9 zeqm2lOG5)S|JqI|4U;`vEEi9&KYo43cLH!dUeo})N1Oh;_`Y&x?XU~u=GHmbk#{4H zxufy<`8f>$)^PM0k(5Sxu1!~2uQ<+P>XA7eYBxtmk9+rdzGa4FvjE=Y@roFzzKgMx zysq*kLhJ}e`qSVnc_k6aW?@ipkjeca1l>SEVE^_Ocwe%?`$k4cL@CTp?#J`n41bnN zEVJxg1e=-dF;*6fdhD=<0&lAh2#qaWw^8#lQK5lrtVtTdHZOH4;!wfDg)x9u!y~I> zy6}{m$y>YFQ16xzBp;BXgFqf9Ltp8#B+LBDCXa;)ocMJbSu%4G<)OAJqnMT&W)xmu>mOBj>{ug zY)!g7&w9{GcrF8L^k_-AgJH6X*K3?)c1f>=I{Y~v04>SJ!WO)`sdAx>C9yXjGeE(z z9vHMb1$!KbIov(eCOF^AEWS5i)yignw^dIn0&Np!aK9o{NV^hv2!V->=WD*XfZKqNe6J z{A9F{UBZarsboPXEnE5#gyZzrAl!GT*pBN@ixQWJ5|&;;Oq8Zz;UQnV&dE~5+_4(l zaP>6;H4W{S35@~Rp?OE^F$RDp{6dP=Skxnrh;1u-hEw)>D*va4X5_z970sita;U706PO)Wj9%iCFj=4Hdz^L6 zj;O5y@SJ|XT4=p-;AY$_%*f(1H~n>wnQY{^+yhQo^HfQWl?Z+3$rV?g5c|~wfCkYl z)R~Qy8BaTFSjtN_lj8mUYsoI*3j;Q=AljI@$kScj)BSP)ngTQAu8nY|NPuL4eueH? z>ZuS-?L#EY^#i1nDCEsxb~nQK?nZ|C+cyy#AuaKkJ|f6xzJ$5fUmf;coFl9UVR3Yl zj~m;H$1?wYS$%0`uY&BH(Q$e|IhoT%v!O<^y9!~d*)#3rjETixL8jZ4#Fu-*n>7UL zY2yZ==GL8o4+2V|awzrQl0V|IN15&IAzHC*6s&)LFSIR0bthuaq%>DL&52`*^|67^ zHOk$M9=izOc}kG)gS3ET>H_t1K_1UN3vG#}j{Pu>*j{KkEILsHjM*G~{rXjF>NOei zHHxjTl+Os7^+)5g`N!XKtf(Pmw*a^{)-5S=j=>d%dSw6DmzI{6tccZ)Ta}bJB$nZ= zElqAd-DhL_sQ(B`{W1Un{PlsrY6qNr^6QMq@`2=iHM|-QtSw8id(A!8+gCa7pRJwN zcnIdakFrkuhq|4T3|`d;)9INgb-bE;Ah|KLDxS{IRaR=_{~&rldQk{_NJct?qw4wj zb5gy&I189v9tPj7@~Kt3D$1O(&#vw16J{tp7H8z2G{0wYIrwx;nxfvooY~j{TD0`A zm)op;a&S;Rt|KMNwQ_HLj6EPQkioJi7D!(i1%Q@Mn8}2JE;;jOfPs9L|B7<#IHjaX z06UC7A?Kjs%`q{kiZvYocCYWGsbfO^48%wGL}Jp;Z&3T?KbYI>GXfwH5zefZk7WbR zo4O-em;bEv26H!^X&6~K8Xt=({Ti_Sd^z(u6me#iEz>+(uy?7QrM&VlbQImHsXQ{b z)77Axe$~KpLo~gm2m-gT0lA;4Id;xFih@VK%WeLh-*7qZvHzMLVgG$L!~0P$O)yy! z5qjG8u_ccr{d4S}8oyG^Zuj^p{I=C4SD_kru|rjfug-bzy5Fxm94zirv{DKj{=Vru zZ$I%=ennLQ&`7J-{IYAHZ@cK8P@NGD%diR+{-~OjxIextWF3Mal__3D#`kmIt6O=c z@;D5dL%s>F46RQvSqm^V4I|^w&Ct-|W&X@Zrm1(w;jdzatX0Z`wHR;`c0c(2KuYX- z>1+vlR2y>VG!gWaBhSeqM>%lzi8Cq`JYxc*X3IUX)5N`YB3x6!1RltJi?&k$kM#ss|bg^esEN|)M) zl@-XFbMwXO_)f8}C)R&K+1xLFh&%_0%4EGeH-OGQWtMB_Y1{y&*zW6jNcu2^3fbH= z_O%qu%SQScfR}ZmqIraIpQx!E{8AEgm|L$&Bl?jH)B zuKiYM;cG07i)mGNWELzPYimHdXnt-idU!?Ia(U8F$F19d=W|v z2vJ%8M`0G1r*@-%2uG;^W&|ZM1s4iV299=ixPGL>SP1lIV(s|p*=FLbJo{CO%#Z;T zsCVUZ#

$9UFucMj7kXQusk)5$AW^c$)7j)~C8H$TwZ>uYQ&P_Ln2}um}TT6DHwm z`e`6bJ#b6cbx=mn{@cknwz9c9rZS!r+%V2#Ys!q%y9GU`cM51ML2dS&;1X)SK<@(eK))fw0<8t@__v9-5`CVV{~F!@A9hdMK6!3P{+^%Q*RL*{@cJD7I5vcB1MzqT0ZF^Q(LBlaIofzzgHa&g7PFxHR5kuFk89?>`vCuiF&BASBKr za1NKnb`1MIOik%*;{qJMC<)(-BSB}}$Rm)a12;zr4#&7M-4^WQX-Ts3gQrc#@IJ%k zFHXrm0TBhNXdxD`EMF~FZqk4NGJ?g*#kCL|$pJxs5+cPi*r8Ju5TbVPAOn<-aDd&_ zcZQfVqGVE_fM@&F`J0xN4&=p?I?7Iz`oA}-c?cSL=J{~t}c zmnzob$Ek;YI`Hm^6TQ4H>L_Iy2dM&yfK#>T%$Vq z!q@1+m#g;*z9(eCG1NZ`B|+@g!xN0Sr}%K0RR1JN{p*V3VhOKKU@352ZAHAdP!P1< z%P`<9f?hd#apH~0mbDDzMc*g|DSA?=2LUulbfVCUK;6a_5A&cqsP;-I8KANC_!trb z-BHQ;l;edN<3?Pl}%DfkoQ*DqMcbiEw5+MWvDiX9pR2v6q z{N>gRv#h~Fw;#GCm49#Kh(3%w=sd9dNK`{*i{;)MDy-jz9>OoEGBIO zw~`Qqj#>Fxj*ueU)KhLHViNCPo22xhMY~ZA&Vkf5-|6pct0;15{XY9=HQ*q5g#))* zRmcFefB{@Yf|Wq(yEyVdcu3dCwIL*NTbf$7=ina37D+Wz<*@R5qoY5Enhd&;_{6d}G8~1DsuDIw6uCut_c%`6JWNR}~Q>w;z8PHEGQzBzFOwZMokrdDRp0_h%nodsV|}+!G+H+LYz} zg4?1i@fN$FL*=h8nMGi<4VUp3?im1?@7Zg8DEl97I_@1&aIz?Es&9B}aClE^Ka_m! z+ETy^L(Y65dqLU$oDJ2?H|+}?3M1h4+vTviSTEW;LtUd`_0wLkxOPM6OlpjB^-C!{ zUDf7WBT9SaH^`jSdQG;+KxQutt(E!*$4w{wgX32E&n)Z#4GBcZpSFBIbIw6DhmqKU zspE!hOs+}>;dsxj3X;`O%)MdYtDLFZshzv8lU1B$NH!zpB@I*52_`s0H3Ua2Gz8oN zQyT^GoGpUoIZNMTSgc12Z=84$pkrOI{uAd2+5uO9m;+m*UfF%UG9SDqGymP?Zj(fz zb30}wfqd<|k6k2hY56QY%(PhJJvy+!%Tb|(XaRKl`C3Z@HYz-g+EO7z*;X6Qx`RT^JR37)A{9`&LShLncRSk7kU?@$u z)B*;yYl6nNL_Pv_kRxFsJnHe{*3%spbdFnrwgs2$YEK+XNgYd_3xLh9{pvqMGMsxY zOqVoex`u!FO}pSP(3=qS2QNj8K(Mvdd1squtRId4D~slDsJR@>y@P*jy9|y0{*jRv zD(Qa6BD0bVC5ueyak&-aYFFs*o$!5HzxU=%reK_vtccXWkRvmZ6MFG1`0#D!05!D^ zn>I*?Kq&8jByMoXqKflSWfDdclAOC5O446t2<6gvR+w^rp;XDn3N^Bj%}&PLGx%5u zB)-!(zJ^7#Wsl>V_{^7chRGwi>J~r!;F4+9>U)_v$M7$AanJLN?*Fe|nZd9GK-2OS zT>wZc0{k4ov00O4CZCD{HT~hI)22EHR-{cOZy5!?CcI9v6kJAtmJqC(F6U1bHOcgwJSzK;^fr^D&9dP!lYq5 z5*5$B4&iG`ldR7P|Khx&KaE!(^t8dtbS&Nh55EBkM4B@!>+k)xoxNR-&9ap0KHCDu z0ZbjI#ZK-$>6Y|V&z9xncldtOL@oJU#oBP5Px2nnVyMdZ9Y%eJYF2=OOL4HTK8zOy zesEi74T|Y_lRY)}<-8NR^a=W?Wsbb<4BJo8$$7~uG5X+nzez}t$_J6NPC03qe^({c ztfld?Kwi9E5O1v3srukzVLD4-yj~YgWj@yVMv!3qiqF*-o+XNAwR+wEF=mX0-HJ0h zHRiyejtNsuIJ;wWF<;c2TGTWj89FwoLwbL*!t;zw9Tdoo68-Y%JIsW>TRr}l=eaL@ zk14(aqyqeC{tLgaR;#oy7+oBOut%IS{oDYo{P+0W+Nn(jWNFdZM)mw=Ej}o@a072o zK2$MLmIkOm8CZU1fSRkn;yh*O%TDAchHvi2Q6H`Rej3;F8;oP)O*OEK9$5Uy36I4S z@cel{&}}6eoha-^d`Ks8cSI`;^(BtJ<@M@hrO3MW>(j&a#jy>S-?`pGzK4J!U=tL7 zcVn^I;BOR%p6kK+*Q8;y*8QZeMAk53w9h?Z|NFu(SQYG!{2IIBnOG>`_A>+`n4xpi z$DQ@nP6e;|z}Sa!QaODYwCd+64HzoVBp%bfY&22L$1?@9r|s*UKeWBR&Gn~CeW+M- zUj5~*2*Gc-lvl8>cAR|@ie2k+yq&%}RqR%=L@VYKP2B1cKF*?O*!HFg+3Y_|E>oJ= z@l9MEQtxr<{y^o!9Uu-Qo$!d0G^;#UZ2?Gg*%lt-leXqm+Re}8;)<8(8+mVCoWB;L zag~%&ps?u=JW*o`whGtPQ!jP?<%Y+ss~cv8i10>Rl#z^QaG+B_ zy^YK5e#f5im%6ROw8~|7;2>NlJeYS~j8xKim9Ep zsj5zB0(k+@%#j9TCm8k$p!`x`qVS*rKHG-Q)AEc?&*z*8UXBFWEJSyOxn zLYYB9VRQ8$cqk`{pADIyZ(+W< zpJd;n;Ajd<6zc;$s2n^R$$_*fqb%u;WrV%De1mg*o2=E3*654Zu?BOVfWz3$wzJ08 zeup{pljYE(-DbGy6YUEBLwevVxdt|tBMET)`7HehCJ5!rX z8TMBv$$+@wGwpNNBf@o^gZ83MZp>&EP}s1aswD?gPvOyCd#fM)6?;THS?{b3=T6jb z+PkKPAa3&PwYA$l-3zM53Mlp{_?Pw~4Kp{&N5TZA2ApN+63Dt!(#n%LtQR&PlCZXc z;-YyA49+jT(zKnx>h>ySH_kJ}tKq`Ba9j=IJ_mjN4K9VXT1+NP&vmQof+%4>+ks); z15?sl2dprR z;^t?qHHZCA1mZ{6b{hVs83oaLrfm(&bHq()Ld-|3`i-t>!oB_vVQ(2#WgB%13j&e~ zh_rNUx=W-xq`Td;ASor?wdqErL!>06q@+tiq`Q#@={(og=RNOtetl!`jNy0`W#9L8 zt(bGJx&D=9Q*XomjsEZKHG#5f_u-_*coB+Ln@qx)lojY|rpB34RLiX>!DzMD;R8^$ z<|(a5Kn(A=fVYg|*YkgapfY`ih0tGtBKM*>yJ@PRO4InHK?HYI1NCitIeG2M#U&GJ z^LwdqtIhJo@81quB0)3x2`_1Y&sJ4LJx;2!#Zd6|xxcZ>ST*xD&q@0yKgSCInj8&_ z_?GxIog0bM$i`AcK)Sip$t?!T%SRvl=F2I_3@7N_IUS=UmW58NwFI)*U9 zbd$AYhwauC;hDS_EU;)O$Fj$DH2S)t$M^eAM#>t zzJ7fc!O9XhWtJmLyZoQR!|nUt2^35H(h8>s^`mN!feNwn`ysok?6M7J28MYvV=BmC(88WsHD)J`NKxrbu5N6qrfr?rnnNRNc^qoYI?YV zA&}%!`rh5>w0H>s`OoLh0zd_sEHrt@3+KVQ|7s$Pyl+n@O^`}`?=Bw!12_S=%q-|D z&>xo6>hN_H0o6xItBB7HVM|Mki3az?c$q%m_*q{9y{T=FxTKchzO3qCO`z@h2m2PW zuW!bGr7&E_U9JzLeD5`d;iXVYpp@!XOUt>r?a!$~(Kg{~$Ozy4xdyo6gvl{P@y8F5 zyw0>CuFdYzu7zB}9tk**!UAW0=FQ+p9q1)@K9VQ^>54v;O~2KcGQ8QiRqsaj8~qaD zqb42>eR^~I1(YPfLR(I1mH5{;L0o#dfN6{tb=3;JuRCEA&tDW%P98JI#IClQ4Ck22 z_cLmjvLA1-W#@)Dz9r~SMuYW^gXV(P*5&Oix@Cvm>Qr0@aITPkVwzv^T)*y9V(Pai zq*CA8rE;^QNWm&V)!nu`@2P6%M;2@b0bpPT%zl3Uq~412Ghd-ZP1(uTyx%v(Pn8<{ zOvI$E4w{a@vvF*5P+NTNeBR7X}_$_^q$n+#D`>Hq6pPzrfy&jB!#y#pkNrn}6 zw^Ys5lZ7;J6&6FxEXD0tK-GWx-y!5CWC*Q9y1qO3AZ%~juFvS`{U4mOqyAnHv80Pw z1~I$y=`MjPq0P6+f;E+trQKeEuj=|*juStp>Zj7Ho{AT1!TS>v(8b+NA(zZ*ZU5Sl zT63y1bmL^Fr)bNn0D@yLoY*XuFYcZjHH7&f`$aM(lYsLIkBeph6}5I)Dkz|zhJjKR zUnx~U6~;hzzCR!N>H{x>E-Zd3mQvynC?7Ei*+s*R0OTzVq`8|9^4=C3hwtq34o&s(p$L(O@V*lHL22f^HV#kc92nIr= z(F(ydS;SN7rdJvKs&emvNU|NsPdTiA($L1lodPS+ahIobiYjVEmlGx0Pk>bnm*72Y z*W$(SS%d-p7!AKT-q?(#83%r0?z=(&s2SU50XR#?vahr_5mG3H&-Jzk%u(kVaLnh2 z%1DC%>T`nnQIDq!xN(h>B3F4RXj3>RmlE#qp>xqGx6O48XwX#lncgNfy}Ro+bQ!sK z+r^OJ=+POvQ zG9r^B+7kc@@~LC)NJD8wLHz1uiqAZ#chhlu2cxcwvV|I@IL3H+P-qoW{1@xP-zbq9 zO5#OI&K=xS`Q)tlR;7{@QaGIGIj5Z2Hv86Nq-=G+PSP<|8wL7}k#a-9Qp0cfAVIZU zJN^Qkm~2Rj zhB0eZnaTb06I1j60#vDIM@+9li>#)6eAugjC+K-r`ho`I@ndOlH#Fb$12rbYcbNFo zJcFYhnD|7Uy>V(`rkMyNLX`}z2F2}w2v(}RlTYVn08%qxFt+u7&u{mySlESTo;21C zP)NyNQQ45Gsp09@J5WZG34QK-(eDc8Cq;{t?DN=Nwnh*@c-H=7XLl#SSI8h;7Gl06 zEdwM#8{4P)4rg=70Hg^7#%x4e4SRN9LD#{PODN-fa@7F|tdhnXQwVu94>LK1BP@d- z>D&D;J0AH`c|;^KGG=CqAMH;3AOS7Hqj(w*Prrk(_7-E%>?IbfQ3KoeH%?=XxLO zizLpk?EHz_hE0;r!%qZ66BWi4nc76_Ny8RilmX`5Frw|tf#G50=CJQ6w5f=~PVZe> z!oFMbO3#?%=}3>S|7pM%(poLAYdKZFTAH=r%#)|Tx0+=E=jpwi$!zW0(OUg{Ssb(e zM7didQR^u0JRR4yUYrhvHoc(`lDi5-bVJgFvzNQOr+Tv@B?2}KN>cdM_|0?l@NO_H z&TlM2gwtM0u8m<5gZlZAhrg816PJD$>W@yUfObp8xc34=6u7)m%2KD11^zvNMyC1) znN1h({H6G{;3IhARzT%aNcC5Kq6wFuTmo5qqWjw3UX;tu1cO)!&~I)p!tTr&vC{x~ z&;YL?Kd)v12X@3UqlHEn1*!U#*Tg%}-*XMsx~io>QMBy`d-Miqv$ZDG-{hipg<<_~ z$me#vcCgD#C}Dp?0&U1%f}@0vf98B88(4+RwVz^ zP}tJKl{Mx&fj-(`*8Fby-7yL9J&7C3*O8_$ih$mHVfU0uk!P`-F6|#bqxtW1R^LaL z&gxZ$4A%jvjQa2|AUA#TbvQjxgU6^LTcCG-e5_`?;p_rLL0_y! zb5SRMmmRdFgsbrWDXBZRIG5}>xXWth@*gXHzj~pr;mVjGq)#hpttyE!O2sG@{3-En zZ&do%plnXQW^innIHNjRuvoDqWp~SgeyuI-{qO-yaDnJR(0SkUz;p52&Cn*;nz7(K z>@jpMFxGJ3?Q`xiA-CTBe7p&WBa#_4KYt>?G*Pbih!fid2GLqXR->`=1m2E|VaMxQ zdNmnkgGBG&ep0aove}=Y_Bjne-;Z&$H>kDM6${~;dkET>Ycf zpW~Bg0$ySoa4}=;vKJpMY~aHGupg2wP1~Oi=~U7Pq?e+8;v5M|==6Ak@3vwMyidm& z^``)|!T=->>g%*eg4{@PvCAk0C~-oV_k%zXki;#yv=u^P7Y4!e+(@%D10D)*GE{O1 zIfKMbf+;XN6;nyfajDJmB$6w^TnYMa>tQ>_xO+W{Zg=c&ik~m(jwI*jvBWvdKDYx9 zYFZv1%}eaGMHTs9y%2mxJP9pc)ITb?cq|MIa^}sVZA|g^ka`Zkeoko;Ov_qIAd|c zQuX^OYNE>E>N?6vbAM(fO}3|xjzAvNx;4R6A9o_G7YOJIfNUp?s4LbQlTA9msK|1m zN1P@E_N{m1*-Y$n{BvmUMr6u^SVv4mIfU@`ewI4ba`Kh)AQ^(Y`3&{sPzY%; z_z)3mKN7TT3~-&?BA0XwSs9XFy_?b3H%YsTt;XfeXL_dsMo|AR2Z2w7RnjKCXz{oL z+7KSA>CRg=Wx`)46^_Hl!_aak9t7<@f{0FoW^JpCC#XwA*y%m;uV&;j{e1+jotSYc z5O7f+BV>|)KjP7rN2CzVN$vACL4|AhKbK1`Ju0xTeLG8M?0fqaU7oTH;b?-XqKOKq zSd)ODcEB`7hz>jPRhF=!d6{^cIFg#mStY!NBIE_=Lc>x3hQ`Ljyc07T1OoZz$xPZx zNsE_Y=tT1#gqzQtDG=Ylx=TtbE_r3DhYy2ZL0qOmu{O9g2$!tO>!?8{G{UdpmYTGM z5)LJxzhLw`?;X*K zw!0S^#adq@%qvrqD4OiDjS1Y9#48sk4o06#pL%|>^C+IS@0OcfI=={wLfWNS^2{M5 zYbh!STe7q=)glnAQ_b%FDI`*wq3VP1Mzbx^5Q0)EydMWfs6O@c=Tb(p`NZrtE))0H zkmS;!4mv!s<(@yPIuWc=G<%g^n$9c9__5I_zk}QQ$lX`dhZM@5CzXB<7u}cOw>(|C zBh6(j`N3#e@`ogV6}1%zgp)6w2I6TIeqk`e<(rE(4o_eNW+N0Rfw63qlwxQ-vEgua zvpgiE6zYE2t}V{NOuUI51hu6RMFd~9xDH8&JrhZeUv#j+UH6YQ-+Skx+$RnHy=7D# zhMg`^k#jt>*av8_qTERDhMzE5YH+(A5#v($Phn@P4AN**?<6`BRgoCD&AWUKk_Vop zzbbG@8^BnICLIU2moOOopoP3yXll3;DwlYGyh3A^E{?qpoJtdneJ*LaxO9QD95s4< zZ1aNx{gENzYg_B7FFA0lXgZ~gPu(&j=;HLSsHK6Oiq`V(o&yyv9IO@2u<6)Jn0RHO z{I5`oXPhsy#g;<-$OtK8gWJdvJ*}`&#PQDh=(8YqVDb|$?2F1{?)ZD4-1^Wp4Dncd z7g?k-sKdk=nUjNOUh_jd5L7#nsZdZd*~fX}K=}8I=Q&f$5}}ENU#%*) zHlq4<2n=ejD-6N%N=kvflQBb+s5-^NW);kRRF=!&`^SfXtDO0`U!!$?^TkQJ!Fhe7 ztcua3pB1cCXEurzot8FhmkL}$KOb{{P3CN~#ZXEUj^;B&ntyo< z_$RzNs8#bn} z&C1yLD7Z=BaaT-l4#f)4QgegynY5Fa*V6n83cZvDhSKHZUS=fmF z*b$dEIh1@g)kwR-A{k#7rl(8MQ>!q#D1wHvL|Xe7B1_e+uo;$-QBkiYX2Gn?xHnN% zXKs^ceW9R{(yhaARD;P=;=9`czN|!hJ@VdOvT+Kz+n`uXUCSdigns7qhRNX{Hd+Fk zygqhWB;u7U`nA1q6Y~DAc|>;)Tj^e<_r9&0r&E4;6m*42v@|4iZg5(K-0wp5%@MRX z1t5KV_9|fG0uqe)tc1S@KvK%DJ?qmKTZ3s_8pX~ z+j^mTI(QnO@ZUn)$|Tns`m|NHngmXp>r)UK>~cxj7!mDoB7PHRrKf-9jG}ZVLG%OR zSd*4f6~FJt4;FC__5gUaMPsZ^O@MNIdw6Sy9dY~On%?6qT*hpB*m?bzx^mJ1{i{Tv zh0+ox~{zm7QWK{kiLVS=JIm{YK%-`lh2h3TxLtWxBO2<@Sr) zS{{d^^!eSiDZMt$@B4q$)g_m+6`K8a18Z+DUE8%eU4O}@aqKIjO3j zA41v5MF`)`ccXm~bIl_6k+%L9^|g9iB0X8>?7c77Q$QJ%D;4>xnLysZ&T3TL{5*K7 z6*`$NW{DU(dbN^2-` zq4Yi~qrixQgLuDhh<_DRVYZFYQXzqyU}xTC($ML5L5e&Sn(KAFSEp8CB=mct?8EVo zAhf3%ybHkotshLTlvfcOzZ;dRvYqCHm*}{|&3}djIqer-x(M|?nB>#mO z5yZyL&?GKKpOV!<@cq+hDv)lWWpqTNrx9R9jxP}Db0?u8q*2B)th8gWy+aT(e#LxSYq!8GH=h|d?|Yk+r@~z4v16zwAxy|C-W`2M)8kR3lt$xiET0UP z;wi+W81>=RH=pW}>(-iuV-g;#a=SnDjG_;Qx)avZ?R9?03pw|?b724S;g>na_GZ3nHn=(j5n^UhR!=FgHPa-} zD{HNYaGSkoIqD`!ihYvS2Vnl(ZZ1i8t-YG}-irJr(9OqVrRhC8a`2|$3lii8t#l_( z&)wi0W9YUs|I_Y<6nMuyXiFX=havP1hXN|m`VxF2!?#j@4}&OT4wAc%gGj9C7gQAp zKC|yP*RS9lG~k=DhBUl_pg2?FlL!iHe%36@Wx-dFO{bbwPnTRr0SS43={klq$vgCZ ze2*-_Cq=W2*}g?YXPch4pYsm6;OSc6GBp=h#Qa=M&K%(MYktSZnIjYRTs8mvLxV6? zMf+Q=-T{X}vk+?cHlIYo1-zNE!HP6rLlIJnKce^nhrZ)$*fD^3Kq4szbM~g&AJt z=v~-EOj9(xKyMPKC~qgP5Y7FVhO|;^jOmCWpYUgJGnxj$2Fc-xA({P}ihCM~EXtKL zb>}er)%hW{<1Af%_ZCC^P9>V7^W|o5W?T)j0kfHd;Bje14??XVQfd0txIvR3lUf8G zc{Bw>0j&NMwcY2&G)|*bLm+Z`IvC2sl#-Bg-OTXAzWLQDm%O6gj3;E*>3naNsc$|r zX_VZT=4qi~%0NXEqiyS45LDntBxgLyWJSu_LWM>2NAHD!H^vO-a~|IejIQN9c8e{M ziF(C@0dU0{Yg19q;yk8p5i$GjmSD{%6EEWXYcCSP-dWyqn|~L{(J(tIp+{23G|#)7 z2>o89WJDCnzXL)nAcMdXp#bd`SWm>XNyVZ>uQD=+qe%Z%tapd%`KHL$M=wnCn>09 z$?O)6Or;ej>B&>RQ_CnUG!^x&K3SlDJG02dqQA)Sj46x$o9ljTBx79o6Gg;K&3`1%;bDoX+Hhu=AXzs8F!B8^iRO257DuYDBji3iY z$BvA8E+S@*KOo19&%yz}UM_=!v46KcYTpSVndASgv8#Je~f&m2go@?8ctYTGLXfw@HvAbmm$#Kl!X zfT~jysZ_<+0ynY)#haoz!B{1&j`!^h#mAYtxT40hSl;LRNoZLyNni`;K$1&EvL%_X zjux{%UQ6g?9xP6wzQ&nD{K zrbqSh840;4<7qdy;yyHRZe*l!e1{26rNF}wCmKKN4W*~@QwOisCC~kt-&FN=rql?y zy&pxlswtberzjW>*`DYz>=ck}4bOAziv24pX#OiIWM4LD;W`K+7^8K8&r1WQ9^!z5 z!ncotdjrx-q2xZUkR@UGIt)2hGEk`O{| zI{B(@j&4s-I2QT6YtEF&{Y_ry>x33L&D-?F_aB=0PE33)TJBv+z8V`LuSeLMVfcAFv;Jpr#86)#iA8 zz;Zx`lqOI#5S_}}rs^mEyDe-1!7pNMms1?c60Q$;ivHd@btF=PyVR=akMq)g*IjSP za)f9cGDNKeTu-~`b!j-l-I;Mmuuk+dLQ?(}^%t-Q!%wmV5(g;#4NxxtCh(Pt-DTQ5 zW%P)QnjQFKT>U`E@~@smH0)~kUw{-2yIrN}o@Ay`im1nphmxoCEyJq_h%sjjn~ zq3OkTRSL(|yWQKyEAgO|_uP7FV1NEUT7aV=r`uO7yPQ1OtNe6$T=ELXZ?{EvnMBys z{v5mmT z-NE8R1s+oeA`K045*|6)_Qi%p<>kLyw)i$Tr+@BrZLSZ+mpb?QsQ!ZM3OCJc?Qtm{4# z?VU=Xj-pxO2Oox1X8$)@gYnBfVd4WfMCLBCi4IU?bBr9Eix8K`z%LP0LiF$R>*{uj zmIs|Z76N)#Zcew2Eq&Vq*q&_1<1O={{|UC&7CY9*w$Or4*`Q2!dE^$Tf>~Q`^i|E( zc9u@tKvQP&o$<-F%b9Y#N^TIy^V0-7j!rln&ms!Up6)IGP)SaH_p>>J2?dV)BxkN+ zJm8q}A(Ei-i%*VcApFoGP2)%y4CPq$_FAf+6Cr`xpq5U<69}j`aI+6XNjfn7Q}+oX z3*i30+VyVFTm+XLwNnEU@}O=MHv#n{l9yQ9 z{+|v46)NWTopX}R!yoCte|~u_VS(q|rt3I89I}*7>rQ&e4!=K#jQ*+UzD?-Ct}-;( z)whUogq*EfvUoXd9|U8O+Ml*9_NCXrhA&{Uo+&zi8{V%`uVwzmaDP$ke^rD3gFdNv zKN(QnZs5pV&JWfjncO_)8N9k_Bl^FAM>V_)bTaA?{5BDqEavGdl|k&lBg!mgSEQ6U z5_qba+xT<5=~KUWh28jg zzW7xzSzLEPo(%nYu_Oo^h>?9T9Q`w1*(;+H*4y~4>pxVBWmo23{KWh(eiG6&$m1TO zfb-I{Ouwj4l=1*}P@LH$+UgLZciR@TI*zCs@l&x{a6~yLmFQ}BhYnE{tqaqUko>rz zFR7-B68trf6qKt@@+I`us1_!JlkbC7U;!Kotl_h_&Q>Z>-z^n{*Sf$_zN5kBT88b-0@1ydc3MZqe4$=fC)hLW#1)(#5oMr zb~kwu3xl9JX|G_xOO)iqToDaFzShu7!;kJNEFz95lW>CVoWw5lc^ubW*2iS3fUKM- z`vaNvpY{8e)NS1l-f6U~!rPfUI_#&*F|BdP?_FvS0RD~GoK^3Rh1l)QocUbSs!g$n zxOaEAU9|Nhw*Wr;1S5q$7>-%GNN1u&0Ej5;y6qX#3bCIfGJ+-S5My(M;$9#}_%x;o`!G3x*(hXkApJX!dn}8R=k(WwMqQ)&ilFLCj z{#s9?5Db?pg5I1OxFtnyzb|5Y@&4pfIwu$_tyO$}er_v+O|kHciumeZFhM3}Cs0OY zr3Ntm04c79^9q?C8mfD%!;sO04Y+=CJ`GtlZ>Gid>TEOf_`v;b)DM6^b^7yb>kZhT z@ldAA%HqziZ_>9dcDJxB+e%7oEl78$d!_#mRc|ryPni6cdxenDXNXYw6BwYFuXKjT z$Hr0s!C}1K*Eb)zBgOzHw?hxs_r(H+%4XWqX+vNaTt1MXRoDZzY zIDuoA;E$MTohL#kv5CyOYVq;$wrL&sL$x~XV*ielINB7bGK|3XSD&M=o(=rChqMSU z^+i2-E8~Bkf;jl<$soqRKk4<~r@thoAdo>M1D{?e@iAayKg>A~3vt@b@iVAo`w>g= zv7f^6pZmL8bKu^i1`L@RcFMoy8#TWVppdj0d(KwpBmDrlT^ZqQe&n;ygqtE&0YzlP z<;I6~V3njT#$Tk@gvbsb5}pHUgN{^IFrD7b*c)=WGQH9@9nK@c%7a>OTh3*y2o;!nFBJvVR z)`m%y8wKi=x6cKU9`imQupNH>d!hMTetv%M_rOO5%2~q4K*Xj1th%bR-!&S8C!Y`2 z8FK=~3(NKH$v2h^i2*tu*%u7dC@ms4Cu7RTU=Qs8K#|`RU2>2_idUxQthfD5QjCar zclG_x%lSgCyG~hi{f%h-2p;R|anddPYan|%9Ug|7F2y2P=NGi&)ST(0zQr z)P_JL;?o4*ArxBG)lDa+fwFHxdS25G~&I2^Z6)&&0@;HG21~{f$ zDuRO!sR?r0d-|yA$3ZYoW)|z`tTNy%eH(sa45pz!>CD+DLR7_@4@YX> z>hlZ$j)~cA&M~wx>+xHqv)Lw(YGBAN1&%B&C{#zQhLu`Btlx}l8}|V+gB1uZG#3Ra zYMK6c3*u0hOgXON?+Yo&yBbo#4N%^5ptLtVB6AnD07RZ?53nFv9~e+q7NP}JQi50@ zN*{0+rsd#Jm5w5&1uVy)0ad3t@aOvsc42CIZ+;)(1RH|m*4E5io_q1XZb#B>@oIQ^ z)cN!;J&gFssM8;Uy;!|u3X5Yi7CxO{gR{zK#(=>&?k2JC1un+kxN4|fz^97=@YQi5 zcNY>ssmBVoc$8;&Y^13!^9=}mmUGAXo4@_6!FfoP#^1uIH%8p^VHL(lf*qF-eqbZ* z4)_8HBv8r6gfEjO!h?-$TlGHA)zqHaU~};rF!$+)M(w)`oMK>^P!c}1Gcv=L7%>N@`OO{h1N+p56~GHN zc4o?nkGjNYKcJ(}Aw<+h)hRP>H6rGDlaHtf*hNh-Rx@q*a^nJ^g9$Lf>-4dL2y#2E z$&SO>@Z%Yq3OS&E4HB>1I+MyTabnux`F~q~GotQ&QITM1ocHzn1ET<67>c7#N;we~ z8`t!Ac9c%joOTZM?$Y~L#V}~|9}CRR(DxJmPDqRJcF##L&+Cc&E}Kv|0S`JpGvxR0 z-%iWH1dKu0*pww~Mh@)hW4G+*KwoCo=7*qnHofP`1zg>uN#lT(MPDRa3q;$Tx|+$kn~AO_Hqon0qMULt;F|J?+)L#!U;r>ONvMyh*yd&fgp3mXRWPYI zQ2XHp^yw|?d9!rKId&6_6D<8|xY{nEWnp=R)_=A?&y-@|6mGlNVwhANL;=)MuAHD3 z5V$=VLuRXNx;>k90xo(?2-6sBP1zgL0)0JXo{HXX0q&Tqe!gcRo_~O_R}`#|E)KH6 z{`%E+T5bND%-Enqw8hP*axs&~QADD7?LBZ@;YQ4_o!s|gv{+MTh7EgM1~Rsnc$*Q5 zWFYb2JjW(3&F6iHoLA-=@njG&fEL^ej<_TK9vTWefFgEc3DMK}*MkhpEWe9Ez?zuO zV`E`~H-@G7x)F#%b%8p7PAUQq2kET}=c9mp&1wiG26;RYOU$W-93Ng)bQEC zugFI(M!QG{E9kd_ox|gHWC8;UpwHK6r>@z|^i*p8<)_#x?c8V#EOORbuJ_f?VK2_6 zqZTnO=XUaTg^<*-t;8gQe}2Sv6oY_1xdw9-43Qsqj1ZV_^*bE{YV?!OU#|EGMO(Yu z!?3U0<0VDO34o*Nz6C71`PN9zF-W+d7CUMiU3NYXnx`1OFYY&*C{Q5aIR_iSf1Q(~ zj%IqF>5h||QZvmi`rfmE5Mf&QU>78q6u3-Cm9Q#jd69#+Dng7Km4i6Qw*HAsU@xZU z!?aGI0WR|a`SF0^J4ts~VcIh3kBhf}|Dzt3c2NJt#utNcpP4sRU3dRmd9O(_Ac5)x-Y z01qVMMf#Ocslr}VBHW=DJEem$Erw%+r=~xr8Atu^!VenKDBNM+DQ*;GWmVca%m7XOIuJL4vV<_e z9#pK~65a(KM*d7emshR*S0!G zab9M#=3s18tn-pyP85Xr(Tv^6Jeel|e)xh*zBgShGayWYQ<5|2Aicq6*69sWqy`CV3o#;9^Mavj#(w*d>tbsEBiTEw;Hm<2Gabz-b=Qcn27)IJX54dJNVJ zl4&6k(|ImU>xWjbV~7!t?CYH#Em*kVJp$SI0Cb5ykqasbyFO;)!FXh6n_k_ z6}%CXgG3gLg|$uMMSoo*Q*lt|>4aL-S(>}S^MpmM%V6=pJ~;IQ|KH4W5%C{I{fwP8 z1nK49*Zw@!5K>5z%2_B8i$q16s4HfjG?I;ewH2MB-a7ICA5j;)_29N*;8DpK76?N? zJ>j2TdFB^RgC`;P+>A#w?fG9;{a6}OHU^r;u3pIVHxAu1eG9XL2*!xIc8%2&a!^35{ZE>ea5RP>NDT$sKNMjxY)cZLpGy>(f6 z5!NvR8e$H>7b%TwlKRjC;ZUkT-cA4D{&geZMdg6i(K^V1T2x-#VTY91Z-4_DoizP* z&dCa3gp*!1{+Zdc=gMfkSiwkog?U~Dx#kBO-iUbKXcVeoPR|J697>o&X>z8%hY!o$ z85{pF=uYNhO?1Q0q^*Em$$7hdZ|16cPJ(qlbP?^J^4j45mZIXiJC)O~APz~6kI!wv z$blOmp*6&Q#LBIJ8-CpS3}pzsvAua5z-`7RaSiB04g6}R@_KPNEOPopuf^+fEl!3^ z_1-Qqz4M&hOb|o6P_2jFf`12QsZSo!Wh>F=58{549L1aE7`_yR5*FOBH?4%0Ed5Gq zG!~-|n=<|aIRBOA(+X02Blho2jKFDt9|)WOipR+Z@Cu#~4syKHam`6hdw>%lkBtpC zc-)S?nl7QZNzj&V2?kIH#rf;BI z0^O^%+F82<`b>9m?+{xI5Kqe)hXao@vwW_K`$t9^U2LB1=Bf<-p2zbMHp36pDZM_v zHoJ75eajUgUWpk;h@@{dnw-{2SZ(=3W%9$@oawIIkBhq*c6;RMQnvR;P6^BXO6@)X zZzv(KPSHo(LI++ZtqHDOD=m+S3>}Bjzdx0f#3p3XtC3Cek}PB}D+KOK@%=a3k|K#> zlGa^7;lx-Iw;?q#t;55WadiB!Qx6EtwP`nxo+AaYm<@L|e~wnc+__l9(p(>kSD>kE z57(UMY7X$?e(Ta{E}o6jO36fM6e|XRo_5Z#*4TzRP%i$gFc#4)tWwRB<^&smHT7@{ zm>i$WM;n2PFFrZBu=91VDBPjd+zrfKR1ZdRCfBot)?*fMQi^vdutP+MUU78@q#v;u znj(GCJ?p`hHc|xd=M?}4e483H^xi&17`vv`h{cK{>;$?ZY#Kyqx@L;9X>*ⅇK83 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

NM)7Wd{nU**&S@s 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π(R_KzL6N3-9@>eIH?DX^Y<2eq#g` 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 ap|`qrZ1>WJ0Pipg`~?VtSD#-g+Vcm=2)e@n diff --git a/reference/sample-configurations/lza-sample-config-healthcare/images/LZA_EGA_Healthcare_Org_Structure.png b/reference/sample-configurations/lza-sample-config-healthcare/images/LZA_EGA_Healthcare_Org_Structure.png deleted file mode 100644 index 7eb7eed12cd757f85c0bca7aab55097a83b47a8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77133 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%0A@8 z!OQw#w^wj%JqmtRB_0&{s=KP1OIm2xNvlZc_{R%?y_A!9+I7HBRZ4*poRsY zE=Cb=$KMoi|M=E>ffNj5y4J0-DEgL{2O5Xf2+N0L4QbVA!14A9L%$ybUVTnPAe6{@ zUTMp(bxeEM7*_S`?SUEmi_4d;H89%saG>%{Kr<9)O zro6afOe-7jqC?oMY-4LfyzlNer!66(vWc?_sl$_R)Jvbx3m;><_`v4&dStxM9=sm# zWfS{5rzdcPZF9c7p?E|$Sk7vf(V|{jz7tA!o{8h3(0vcR$zJs7s!}ZooS4pYG6IiX z+p(%CdePwdPB{e+xpAa3zm#q`*BeZYFG`7^rmB1lw_#McK8D`+>sljH2?9O&1wvhh_BegK=xSk{$-7*c8@yy*x*EIXPK%Iua-6yMi0I z%e!nZ=3AaRuca>k*fv3IV)8T_uF{}g;O1N$c6AOiEeE>O-6h-NP0079WQ0&+X{j1H zW87y^&6VW@C|oC>R92nm+bnAfdrq(1R*+2>Y10g)C9_{W9bA$UCGh2A>St!1K2+n_ zhLV8r7s_$WAulo03lA49*IklN-fhJ>WzHw#<6g^$!cQp3xD6K98B?_;NdyI+!U97k z$=*Emo-LiS-Fu=EfY9?F<`Dw((l{KEDj`4}@k0hJibp8l;TN6ny^prWjt2Y*PXe;j z#%2>KsuYz4!iMD8wzk~PB?J+Tkba5wSn!`WQ&unDEBikS=I=o*>>4);1(pYS{g!`xy zrf@bDn@rnkj_c-Y_upn6nzs^1z6o?|?TqU{57sk#B|l#;_fcnBs%ma)ctGW`l+p|h zo7Al?3oSA4vnJTbA25#Nu#>Vr)d&mW0qO+(M?9zK?md&E_`gr(MtJm$c1>NUn+FfE z?jA%2j(SDvHD%eR;9oQmP@q$EVfm(Krzt}5XpLLLX&H|?JFyM5<(4Y5y6}paPoI0K zqxXQ~mAu<^ZFMleWUF*DhDe3ei%GR_ zb+TccRqat2Z1_=CReVdXo8lu)cv8~`Zf*M%vW5rYWiykJ;0urH0lCqE>M1>LFI#zX z@n^DkhP|7G3_De&|7~v-DsiS4WJ@m!Nr1i6_S8mDnmFdh>6W?Zuz>^#`na#4$pr)K6`3Yz0UtvMPT77Dgrx@+Q;NyiOjyd9xWQ zX303iV=7SGUEsV;pyogd(SYTb;2T}e+W2_arm|6z&sL`DqwazfwK+THGw-+MAc$>c zi5@6dzLP&B?}H2DL8X*VssV8O^`&HH#Nv;lBUORxO$-i24PlJ0{2VUZT)B-3ts$yn z3EkAa2lw*Wd%IfvI)o$~EMMbL3`lu)le&c|y&wQkM!_sBx3 zbw8Eu2BjtceEn9OV1{Uz5k9}V;tj`#((J#)E1RkG5Q5$7SVxwlEVRk?Qfft^?s#EF zhqopL%Xe?J&0N^@T5E< z+V~af`Vq*&&Xj&6G6ME*Q^2y^Z>M`)(BZG}8@?ewol8j7fBSCyi~7bz^zci%vJth+ zaKB%aDLmilpn{nvclC%lzsWzq1`yW$D&(BULsM3O*>_Iud2b{_0=TR=zW0gppbpIR zDg2k|d!wB{FkYCP8NoZ{p_!7093ap8|WSWQ$1!S zYy)uhI{@KZ4dupu`9M}Z8^;q;OT0wSrT1dk>weH3r0T0e9%WJhnL3X_rp~`0mGlH~ zD*ku~%u|Qb-?K`HP!Q9-vPLh!Yh}lTOj8lghEpFJNdbRJ$1r= znGdBu2A(iYEZ^Y=bOc>f^C#g{+O7KSO*Jr}M6>_Zmy0tUFAvm>l`N`lEfgixT1~1M z!hvK|u2I}MCWn5ky|{num{P3p$PRrecD0e^mphsb4cu`l)H`C^TBu6+J96f)>d9Mu z3(WG#Kga0jbjZ2@YuD7D%Q<~yk4pgv%2ySEFsQR?b4&S zHSbsGg2^?#c6h7`ngCRF?_V4grE35Hi2=&27YX+wBw#=uAG}2x(_f>-@pGY~J&C+z zN^xOM$b4Sz#AhH4iUN?!ng3)IDsQJN2sDf4<)cwI5#vQ#%J%IjHyy0Ocn_swZFV_{ zj}mc!oL~ntaJu{ohG0|&r7)QriW(YoRNyrnxjY(8Y;vB4e zQ{lXm;hGP|I9Dm0RD=u&__UBIS`oPzn%I(Dxj`$1D(E*6V$^q^31b5JzyB8#2vLxU z$Tc8Zj}5#!DYO{{@>cM3-{+ayu=N(W%>9`3+?C~vYIB93e^ldPLn%Dh@m$Yw|EqO1 zlR(-)H7`P*!fr-H4DQ(;Sws5c*~~u#u=T zZe5>IAFt2m07>y+8UqB7TkBS?rQ0%Ld zZZhzx^mz>(aJX(Kg*l+V)M5_@wO8JAfl`3y50zlcOJ9=G+zuOIxx8?XEf|2=X|+Y) zaGa%+cAcvxDdtCB;Aqks*+yfYAqXJgJ%HfbtljOK>=-6*ydUr0^dOmE*}BPhHOqrB z%=f47d5(N_fii#-w4!WRkcIxGtoP?UK1<1+GV)V&+ehiGtJ3TV50`6`-7DbN}2W?m~?<8fhmnU^674yXdEyiercb9RYv{4ud=CTt|~9SounHP`VH&yj#WK)-~2OE``s5WD#9Nk-%r z3?#xt9u>^4HA3rqHnM%ZG)x^ZNXPHS4RFen_U$jh%0^jUVjj0l;BMWB0_?P#@!1qqpphOf(-{`8d z`bl4s00!*U6JTO?KY-b$i(Yg-sg_a6yf#!XP+_uCFL47)8pXh{NCptlIb zp29T)To+U?ig81LNaz1@L;t<)dkp5c2B;AHJa4mQ0W%&?UlBurv5*mYx}zU3yMp^# z`C#mZe+={F|7e&2wDtdOt&&N=l$=Je5}k_v))XF`RKsyYzY`#c0dP^dD&{ekU(u)w z<1=&wz$cHNb$}16dt>s_Dw(1$FXrM@0OY{PSZz?olL7R_A|*(1UCF2hh%BeIKU_nV zItI|IkF#9PX84+3BJ8$(siU*^FQy#i6@G}scD*Cauzclt^M4pgN9{iwEq)Tt+N}lb zznlV7j?za%R_}>pl7Uny5H9&*jd_5dC5lwnz`(JrCL1#~%O}(v4K~yJpKIpPV;_^h ztcWgHsIqD@CZ9*1xvN)zy|;c9hhbQv;zk!mH=bxND}(mvrH>7POzb^_e=3w-G=Bn6 zcsxL@TPl&-tnpH9w%?jZV^fJV-X((Y`qD=cj+bZMY>3J$XSR2zB%Y0qSSg~;0g;)4 z8WzgWI0YF-i(lQg5}E(1JSzViE^%I2Yqg-pE_&9usEc7_`JABM6kRFhWj{R4^1D)I z7n_msafMAyg3y!JEE0@&@M%xEaR-$cg zdGh>}>b1JK0T8K%6q9GcfU2=g3mls^^Resj1Fj^1+y!MP&pbZ+oV9uZP`}!4mt#xc zi%Q!IJnPxFkpf3;_$bF5p)kci=>+tR5;O}^0dU04Go! zLjf*QpMPoW=efhYrf6O#Z@qY|wKdb$G;TGmM-WYPu*mNC6~M{B#+Yd1LU{YGV76*2 z(P=#T%t~o2W(!*^qgtv4pe@R_^UXQERDK^Yrj~6y4^!eMrSTpt-wr;wQ&AVUUeWd< z3(}+^zF4oZJyFC4R062Y6~!e)gX*>$)Z`MM#{gQE>kf-}`N zM2)UZM2q&N`cGIjbE5no{`tS5(sCaMw|i(Ujmn<>z1PY(1k`tz?!EK9V%>g-%|iBrOmU$`}L4z3T{(~d_9!R1KPZ06qp!s97p)#*|ao%gXWx%qU5 zk$TzZPd(o`^f(wc)H; zG+Zgh)wX@xa`r`!oceI&{nh6_S`2E@Rr&yt7d7+raA7Q=`eyY^y+VcH>~_S?^yBt! z$g$eDo4_)rS@*rTW7WiWUAEK7MS*KqDrIQ{4@bpvg13QH9Csmc^M2$O)ez5qUiiBB zHjtDh!|PNhkasFb2&bl;IAiJF?_jTk+jajIUa#s86Zf5rAbHWv;MwCZzHvF55Q+x1ee*QUq zZEv@=;QlpT7zJs@Qppbhc6J=tp!SU>Mmm5Bm#LZW)*kX z6m@Y5^i%ON^(L0_4ZDU#W-;Nq#3=o?_&RESMX1!h_$c1FIHRfz_@|;$^vAg4kO}sL zI11(Mhd5_{CU)3wcMqbUjRO(~g22WeAR+;*!>Co3$)ZZFlA9VX#lA`9T<2K}PG~T7 z-yd_uChI(h-uK`x<&Qu~8-?6lg%ou{w2RDqBl3Kcm5F0qU4T=tw9I&4?e^AXf>FqO zU$t(YP(WMs;xz!)J=4zsqzKfq>=i%^9~9}jDeTpWjT^VhW*cRNT@EVjEt0jy1!jSA zAzs5ocO6R0H~pI#3{}^9m*&#s2YejfV)PTPsm^4Z@fvk+-BxOqlKFKe2u2>OGy#y` zVxMWW|6cGVLA3jH_QH1^Wf!AAeeKW7gj@HE1y;8G(>R{cf1qBx^%Y~_;e--pImMIbeeL512QoE7*!4zdKvn7Vzo@YiIPHcQJl(< zLTM^JK8UOkkJ;sUN6fKQkoGo*?O^HU>FTje3M}xHMNbFV&JSp8bj&JEW0UCL`M$w^C)E*- zUfqwYmG2VUzIs~a%g7Ik3vh5Dr=eT?kVqS+ne3A$zE4xp27u`eb9)uJIgJCs%)yAb zw?Ct(1#(@J1akkCNnuI_bb8dtWDwOB(HrnX^}{YE4tV`+r_Szlt1$*oo&160+(p^d zxJSG3m~Y#=r9mutRa_@(PL|Z|^@lM+Eb)77_e?GlwRkL!U+6Y|Y-)C2&09^rJa((7 zFluVi!g}OljxsTGUe|uX40JYC- zp#qEBZ6V$xaK(>+2mm{F#l%`5vBGD^%hPw63|Dd70~#1e+`-}nsm4}zyLm9 z?PvfSD^6g;WY#*r-*0K8e)saf>JbTq0RjMn1ANruyYoRp`$@h*a{cA4i$8Cya1;AM+mYl1 zyLq^2tyvX`R3{4>`h!ay$g}MiTVwgq@2cX|-p|pWGFxtc_hDoM%yFlsM_o*oVh{yf z?z`HfcBqR3zp-39fdtNhHtz$0qxxAuSx5MARh7rO>(SY>Cz-fE9?Sx=JnYd2qZzp} zq`MC+11Fvs(w4F}hYCHEP%8=kse>DDxgPw@y1ot|!B3oE<%G5rjZJ*dvut-Cw`ENR zpxr9IS9`f12V&pgKVNkdDJ69EUbU+m3(*|`G-sgPaTeeUm9ktw$1Gf=TX46$L8zNBF4;$I112PQeM=kVh=<*13S8e_D)dzhyXY3j09&YpNayxpHkU15e={G z{Nl0*J*?$QJEzs@RZp5jO!$Qosqeu!^IPFMtL-BV&C|r57^qpXdsu1fS(y8q0)t??V0E%jX;={1O2=v3l zT-n94Cy8pP8)k=7c+?ZF)|H_*Jp(13rwgOv+C;@(4?ITh4sK6N*gmq5g*l1bdz$U* zQ71L5GyY%%0Lrgs(4QXo-S*G^VDT7I1r#vEltg${*U+|mq!^;i4vQ)yzJTdm>{*S_ z^!}?xVK$#iJ6GC9_GhDRU3SY(s{LW zm)!< zT#T=Nrc-aksSSUGDFN8<66;80Y9=eU^Jw3Pr}~?b7uHcKi=k(&_sJj?uXi?QtkaWO z1ur%{*|fgZlN$HXaeUXFt_rWT+c2uQ;9pg_@4$#J{#ZW>7_BEi%|8ERg*m?RZ}3J5 z%uces$z~~6)4{t_bD?RQbZ~M0sgC`75EZayr7rRI;8j}Jc{;P~-vYqAUqL{mqM_0D z?^(=DRCXWLtb}1Xvv!qfZX+e+eHa*(UUJ?kEVg^?QR3jP{ zdN!p75Cvb`vN2B;m#g4?E9^Tg$Kuc;65QP;G~A;8YgS=#w0Z)vrWlzW(Ft_=8A-HnGhiN^_*<_uy#B zM9rX1`A^_9W8k;hE$`khvO!?yCNR;_HOm0festV5`8$OhKRCso9DrxBFr6up7DswN zs25_$c5pC6wE?=T`&aG@o&_`Fky1yZt1%>&-BwPEj6CIHRVF$F|;yb}8Vw9=LUvU6TiBRWvcG8MAA35S8? z1}4>V+==Z-Drl~*$rOI(?AU1V3N6t$9O&QShNo%AHWlqmA{V|dPYR;lGAs(*$(EWb zE!o9hatZ8Z*TYSUG5xl5`A^A1Y`bgS2cYOW~ z1hsk26(OSv_Gj!`|{SSW%}m zVF4BTAo^w;>2;*GR#MSYNNaQ!PiGNUIX}V*Ye}FsU9O+CFJryrfK@Wx`HtuMMOqBi zM|I{CX~ghHf6#{yK8;*kV zz;OyrI>>VB39V850FnE5GW7bADZpOF;>X=?9dp3>Zy4`-N%OXuN7WkPEFp2E+)7tx znF0J=*^6%(ABewMzCI(VpY{z%X-qN`F04M^9n;e;LlkBz29kBV$=B3mhOJ%9_Ww~Y z_Yyl(+xM<jMIZK8E~1iNee*G?P;;5$w3VHeC4=_id_%IE=8RS*G(L|FT<)tGj&D#Aovf`MMf&tn)sXuTDA%Fa2I^XJv@!pV_bx&oRmi$RIH; zzg0WDe#pycbzImmt}PlY&|Iex7sML3AXU+D`YmJm@BwdU(ouTK>61PTKylLXks|w( z>ER3!o%NxVocr#2pk*Z1-TQ@pdZ4Ye>P!=!=df3bycR1;XG3_{^_I%CsQSNyaf*P* z-i{uuFPn03MG)tX;`m!=?gjJe^?`e7QmBH?89VF{Tj-f%HW7`aub|jwC+_}5s_`{({-0{0djA2)(vi6A`Z>tm@LGNBcXS( zme7~ABN%@p@<)7rR#7Qu`rvF!ZN`^a0jS6p%Rb?xugqIqxng@GL& zZ#2Z)EuO1>PaUXaKt@Djrakh<4c>kHBT+nkbE-fN_v}NNch;|<7J#Zy>>G#%$O`L~ zr)Cxk*lsMuk(iB+v~Vbyl9MEMdFS6}MgdFxI?I_3k9n6O zk>t#uiM`ILzmLiM@>L>giMsf6S405#xT{!dwl6?iKTI57*6UIZWy$5$m=52M0etGi z_IX*-g@E8N&>XcQx!*VwPUXE_ge80TtbM3x%lyt!wLTy&X}tHXVK$|yKC>2BFi6jIdM3po5yZq9BcGkL>=D=4Eu|3KB_we4;<6fp!PXM`K z5g``t8z3H&J@eab>kzq1;;8;26L%78_AvwOu$=Mb_vO1As&voE@<+5pK&K1})6AiF z*-rx=&bWKe*cT8z3}E%Mxx~?VD(}7NZ#3XwR`<~NDXzm4_X^PDD1>Shf$}8*ujR;p zZsa?wp%8Ljk6-OGQ|$(4iMOUp_!-f#*gcv~i?VZ?o=pSf4D)TG;{X`OKcpz~)hODU zGu{2+!e~pC7fW8xBEs)Jf<0h8Iq*x@V)+B8$9cCT(A8XRiMAw&6!9dZX!MDkR@9G0 z4m7dHP)(FITaA6v%`B?73>W5uE8NOoIiF>0Zy72to!iU>;Cs=vx3Bqs>d_8$-1y5 z#m&uat={>>r9D8Br+naApTbBE8>&DL0GW-m{zg?bc#GdZ`d7*-ZOJGI$wsTIl)=6% z_LFjP&4`&}-!805|U@TCTyLDQzQHc zBbOv9q51vBxqNl8kBDx`hV)HLMZ?}EZiwrbh3f}8&_H`q3t}vsz~6qGag5m}!IclZ zN*JoU6J4I2n>F~8m&q#X6jG+FW~j8`82382RQ3?{wXd4DTWI5=23Y+gDia=$YA>6&?w8o@*DSvWkIib`{RvBg zNoo?~y}kXW8Xlf+24?a@uL;zl=x`mDP|;p;flOig@p#ei+PU<#&myZOFeX!C>*VPo z=76|&FtKXhl1{XY5mCML;xT@3vcI?SGQ0X$f{xv104}5N_qy3KjU*<8O$8LNGCDtm zzwSlgI+V!!vWz?g1G=J{2uFZP5c^IyAr_Nor?R$qoQDIz8Df?JID=V)zrxCv4BVge zZx4w08;%R6?ck@$6A#!iP=FQl6;SNbYi&k+Isn;vSk)kE+2%9!IaleG6#gMl&&2w@ zHU|iU{R&J;k>SRQ-q?Mw>P#&4#8wn@bG2mvCb3lUbB}x<^eL|4AR4q-{bEf zlf@nZLSPmm28E)mLrjO6kklxYtv)}+i)*~f3Yn~7U9%lLgRFL~^5I2m!$6As_?3yz zu$zg`6!|}M<>^mxHglv#MW=<-LHE)eNw;pboU*?zOthcWq) z3N!MI>~zs3ZTF&$wVsCb6=@^CLxe#FF7%^>pR-QfG3-0o^JydwFqW%Aa%rPxz^HWD z{yBMai5Rsks`}(bc+)i}oZJfSxO#ARw-^dSS%ib?wY|w2h24Q>+Itew>*e`t_oTuC zaMG24HdypWoe}@E;k;I*XBMTPfT(RTNP+iMpYqstvI8jL=^zX>0c|y`08kgm0qTM> zhPn^|X&9dQ;q^kc5g)i3!E@`6b~d}^zOuY^QoJD;3%WI>$6~?Y3Hv93oNeCONWu;! zkY<+hqJH3YEoQEC4~(b{Eb$$n8bR#7^SXlhekpr)iKzZ9WA$w$5q=h#&V~9Rc+aUJ zwC%I!h(*%rah~6kKf^!UNb}0ZgeJZiOX|xVHry`hnbBQa-f}mIT1G69p*k1Qs|#Ei z(g{pV0s1(#G1bL>4CbpIBa7&eV@J^qW5qQEK>Xtce0xX(V%DR?15Fbk7#ZwdbQeuj zRPWJ@7l~Mh4LbAp=ONNSS~cOH-IZju#C_a7&zal*jGiK96DQ*Ep_17+XpiE9vd)Y! zw8^e&f5aLLp0=>fu}Qtj(%Tk@Uj1w@p6_?CvC9!KlQe~!B$IAkK27zN z&E7V%W6OdR{BqU#QrOcwUEd7TQAMg-J2ygy51m&(oFC!cpg+=SpngpI!6*m!p8ot+ zk$&)+Bs zJP!b25MQ3kfauf^zZj~UT0DW?^+W+ytjpBYc2H=LS_g)BJ?xKIrx?n9j?P154fN<{ zUB~Mra)n+#&VqvJlX0eX0>MdEa>7`~kUKYW#p~qvvMkEadIXwkCa2yu6TEN#$t+1n z?s=Xk|DZ-$H6cWv$KCNRj_Q}Nms(*A9-JdH$$x`3fMJC`BGWIl8F5W!n7W|l%`l#{ zdki+QryW|5T+&2L#n~Qs{V1-Q?>i?~A_gstMxu5MLQ?>e1qp^^aZYF_i(SwBp`LEj z7rQ+mVR{FkBlIBBl+JK3Ss(;AQHUUIb%BZ(#0r2d{s_#25W*{S5Ku?6NXztSaaT7J zm^w)QJ@~pQ!63Qm-B@j*b|upOKd}I%;uU)$>{A!dl$Ro4l%p@L(sZ;575gOq*n8~` z#0e=gBif%nwGbZ4#IYw4e3##V;7-a$S*DIFa1PC|TBYh}XHA_yJDW!Tt{AKTy2Igg zG+LXrUuEp+y&{HmJ14K#@+R}AlazSneJgkS`wf!BVXDANSNDt;kDX@8VTkr^QALBm zo5W}wQkIS*gTHme;kmD7PaAkzm6GWH|8X(g7&_WaDHSW9setZ4Kc13-C$M~RzT-}P zGB!J9Yc%qUx!pNEqLCp7hUp|mzO09Gl^Q`2f)ih0YXRNXnW;A?S`X)DD;^~=paDcC`OzH zYlJo99i8+6+=&mA*u8Fae&NC1_MCz#;b~L0g}g;VG}uSm$E* zxyI<$B75l9Du=N^(2^#06}s2pwwW*Uk6DL4isR^LP8q199&93-(Z=$iFbj~t^D z1egjxCI%0C8mw}%Nx4Ak+g@i1-TZnIgH^QmHE?KxPqI!OF960!(PZ4j(g^B0-TQ5Sk#^h~ zJMCM6l*J&pTu2IG4GUl~5`xbt=jlYxD&Q;E&<1p@^98HWyjQhBfV@Jk#EKaA zwL1W*C#~cSIQcU*c(s+4+;;mT%V6GFW<(VU3Xlk(lk#tOx?tJT7klnbKAHop7qqkL zjwQ}Y`2Xo4Wk?mdN{wjj7uC`(7M|r9G zIWJMJ_jc~Ck_dhQ3YiOp{YM|UP-zY5Y?jC6yF#YI66fUn+HkT<=WDMkn;DjT6v0gx z1$ZCb$poVhp1aYxo~!~)-=M8u58r#`|HCCkQ|wtc8vx^!V9PrbQDAfGz@o1L(1^|!+p?fE&e;hqwPyYu^24vAqI-vuaa+XT)+PdyAwPD$D)t*Dz>caNePMD6&#nB>p0M>zPWVa-Taf9|_C99{m-d}K@m*4|0)1T> zXOcTA_Zfva3jeB|>R{Bo% z)DnziEA1~nz@J}PN{=D&opOB(k`=yoH<{D=e__{9-f+4l#sad&lKzk$LhTP!=?e5} zS$Ep8je}Ip@Ln6{uI=}w4FPP2ygY}l83FrNtk z@Qo|mXxasQd;Qi$h^g^F=m!r0^Ohf>LpPbln!f!)6%o%Ic(gh9MgN|hWgh!&;$mW4 z(d5>E4)+jx80k3~FA(*#9dITXe;(dp>`RSI7!)DW9lM>@nbzD>xx=^>6IMcYNo9;A zx_Nv}gnbhwF-48!E^lv;Cg`7Q`!Y{b?E{Ks#fsdR>vKY%#*BYCn#&WUBma3xY@Qu+ zTyX^O2>OPrOWIyePdWmK4kSc9fs?*z!#yFVA{weF;u|TVFm`)dS+TVor17GJg^V#k z65mHZJBn#fSgYR$=G|%Yx%TK&Q?Ru1i)n|8!x)XbWwaG60okO2Vn=3IkQHmi8Igbb zMUG-|?%Mz<;)dsD^WR~nb-~1kG)_Nx0ogR6<4Y>vpn$BTLwUUWMo(A)x{L;%EZU@Z zbeBd3SR@z%ubmw5vZui@Vu>7zeW|cK>EY}|p}8yEy3b1KI5NTF?8m8Cn0afKFy@G1 zk~eJj{|jkIduIQ7KoeghX@pBxM4Y97xfB*5A{s$^5$tkJS#|uELG=8Oi-spJDSmL^ z9i##)D{qWE)15^}>l^l%i6AaX!8KW#Cgb=zv(juHYC1H&{@0ePn$nC+4RpjmwqJap zfAYk@4YNXBjE%nL2E&y#k~mqfDBQ>#+TpF%x-K_aPosfn5^n2=vRvz)9=d>Ei2GV% zTe0uok-)lw41ci(ua)bA1P`Gh6 zUsq9E)A~~k;@CH#DVGT02~r>*a8%<~%!CjIEOHa7(iRYH_29I6s}k`Bi1{M+`u{Ly z#(*{Q{n<&xm){feJy~X74%zdVWNlOZ9OL)-M&aF&jHyIxci+Jk z2W*z!GR+HbGm$CuUDU?PIUU&^QRB!HI=_z#xw6YhwRw zNSrnR!|g%0RXE#C7`NP0;=E}^W!ps4+oc3rtJ4=uWCA zs&*j{v#VbpPM5=~_LKE#SP?o$7UnIxEU@I@B6%IwBPV)lIqz5PUY=2SP{a40A{u7} z?-O0fxr2o$<D+l}K#h|iVc zGGlLaZeE)|Q0VLtGq)VE!0O|wZ7cwKC?~}27Q%so^;zm`t1`=A2avw1KLI#FiQ_;M zF+da3rQvQ7Z7ZCxUpkQ}Wv*vePun|IGOdr7r>Yw6LpQU4u+kE{J<1Tegx+6JrPph@ zVo=>%$hyqDnS>ZxKSXoK`p4k|Tf>h#o}428d>bd7Pp0S({$UE?p#EhF-R@=JH){Ps z_AL(mQC(*k5H(kj4|=UYG8~kvnaenQH}5e?>f;Agr0h?jnrIj))gUnpp$gInQEEqz zTAI_qokyEC(!EEUE*>qOiCz?Wk1W_!oax#uqI@rq@EFng00^nxgo}!3C+XZ=-h_`rxQf7tvQi@a%R}*n^itq^wRduc@ZP_ zmef+hm|equ+(!$~eRB79+j+{Nd5)J~X1|!z*&6=%XyL6byfA#UgjzqS@Qd-2(DeJe z+AvAitn+r5aH7JS$q2_BpirNnL1f4N9G9;7YJF#`fbJH*BC04b^wfb-$ly?Kb@#W(>>yTlGRs znak`FrJ-`g+#-P|bXu=SWXPyVOsC=8tP+ zfX7f@5)l{iymkePpd+9)^(0_Rzn9SX#5iU->O5~IIb5T8l?PZdq<*WAP^-$Z$Z0s0b{DO0wkX4Q&nf6p3N0e|w619G&aSTA&0xuDFL zd2>>&b*_PP^mxyM{mQ31Qq*Kw#hUPbvG1e5jSx5cR`w2@vtW4Z2T*iCn z#Xf-vr3U=_8_K?Ya$XCd&o?HmPxXCA^Gj);xN_RyZBixhNmFfql~L!8*;)yz56>+_ z%HE_EG0^x*Y)}I$HAGNH2sZD)$G9q{X;o%jma(vNLuT1MICWL4isGa^rA; z6l^698E+B9`2f8R><5S=*>&(}R@8dP=g0$^E zt!?p(-kjBBWNR&m(WAu0LO44~4pd5mU_7=R$2R-14AQoTOP#vo&s}A4)TJda6s#MI zwMmGvFu$J^*rZ?t53D;SiPc2kqqU3?n=zIv;3=PVBjtmM5*$7Qq}M zqfSAP-waa+?qusJ8IYC4kpX`6tN8LD^BZFHELK?Vi>FGiOnEpUn0mNQLWDowSKuev zOBO3XaLEuaKk%JFQz!hZzl=Ibz@$0mX3YK4nEN+7WUcE(!Md-Lw-pqCf8;%`V5j`2 zr^Eq~EcDR$BWYGu;Q8O0)n$O@!|`f4pJOx9V}4W$K=-6^RWgC63%ch01nwpm`fRB7 zPwSF6JmJ9e-V*4bqqG_g|A!agf}zn!2jq2{tA&33`z>ij^LIoO?i!9k?SG^31*(D$nMKH;-Z4=LI$(l-<{Qnkw5r=s2dl9=<51kAj^4q>ib zx?-Bl+vxau_CjCqNB)S%K(C6&&)!FLAJ29st3wNhD!(;E6VUj1HN}-_gesQ4olpp| zk6@8a!OGF`IXM>R7OqtVD_ zVKp3{sE$*ilhWIzk84J&wcP!p)2bk|Q8@$U?NT2~ z3+d{e=k0&H$n!H=_TrYJ1iOCF!|iTwT^bf_DmnUhP|fYbRog55gIUPx7kI;hAL=a0P{KAIbv#(% zw)4w|+$+h6SCT;GJ{A|Oe1Cef+1)CuU_hi-f|wM1i(FiswCQ*SuQue^`Ln0szS~Nq zT;;9<*=%VtkC9-eQdXy7jMc(vk!N>T&`m=hUW!7zt*)pUpxv!nIq071op7uBz!D|9 z^x)c&Rd01J303iQLX?ceM*IZ}=Y2N3pmD$&tKI-ML@dMcnN z!sz6j-P1oR548Gu*Txc~Ub4anC3e+p#*1@A^3_9*wNKI&wDVR{+;)G^ab(@bO>rj8+}~0riJH zc2gjyeZGttgqDthwd#;Bv*v_VLHegY8t0EQylQu4vnzoc9#j;}AS@9m_l5c>`Q}Qa zm)?iJS!{>qi3FdIBo!xcOm{o|IQv{)8T9R>2Idd+O1QzIX9ZZg%2OBh=7Wo0t)~*$ zAoAKg3P~zpP~M=tj8!Nln69nqXFbP}>dKdq)|_(iOtpDLiPbPm7UFnHXL_|}z4`s* zc@lray!-4#2>7yVxR4PWA=MF)Yg7t;0J8t&gqR=^0|(&})o)Lf%;p>WRte~|Y)EOa zsAgs7&qQxSvVC3`8hXX3-8h}VCn+q8IE<6ut_t@9a-WWWHewV_uk@sGT94N%L z9?xywwY8jd@iA$vyaL1AN6n1Q)LeGg)W$P8VAi%c0Z?g{ZY#5$VIo{a?opkpII;_( za!HfYqA5eKj|XaIwgL*aO;7CzO#dKz8&ZvczS&H<7ZLeLy7P0FlicjGIpHvc=5{Dv zHz3Da3I^=){j|f59M0z;DiE7SzJ%4(;_I=QdTZ0Ei`KW8j(-1|>oEzc`vfqm_qpkd z4I3Osp0A`yVcb`6T4XU9IoMa3^x7jJaaG5@5xGr-E}+QgG2)$xKZsU~YJh4nAxUvpbu<068;$u%X z3%QstJqmHPTg_Llcl@p1P|;1(Kg{7@`Al6Bpy|2D;3HeW1{0|RHX zX7P1eCTH^XKa-tEh{Abr9H|f-1oZBg!tVPp0H?*|<8s*9)6dHs)1aZsnI;Dkbv+pCbepm2vzsA1 z1+~_lQYDReoTaMn-Q*`x(dOyK%-bZnZm(_6S31QhdW)J2z0yBAXNQ}Q&-c+zSW!K>PSvNEsXlijt3g;D2GCv=yr;_iqy zUA?g%YIu6?AQP7k*hEd-DPG7WW*x9pRs(J6#>FpdFzG5~(~lMc@mbX;w{My6WCe7A zsCZ~bqVZMj;r27Xp?Caeolg71uT7jhVCxbM)8*S@KkNRUer6G=lJeZ|m)o0l-PwZ; z!kZQmSTq&HYi&+H=Jy9XNx>j7)b6eB1zOs$m1qG1&rol&-+U}i&Fd4ZRW|!#w9bKM zW---3b97t4#(~b~v}zANy3l+7(_=K3s(Ls}ba70Y_FUk;E1cCw!|T^!$$ zVJ7|0{WGcG#9ot+&*XQ!Qr==F{$^K7EGOI^h_3RIT@O3?3XKIsDvl;Wgh0uN0?5gp z`$$~3Fx?}#R)zS6)Fz&v%}jpHM491m7=z3hUj}E5jc}dwvO8s6%iK$Oi4V6ByE(Bq zDX~kBwFm}i@K21hF55lo6O7SF2rrlgCUaRv{nd-2#q4GN)qc_2)ipJSRegx5MxD>jde)8Lhk@u=Xm@EH#!0{* z(WyQMpDq&o6n#&!#V1>wAjso(o3h?}FXv}KQv0b5tpj>|HXo#MPiyIXZ~EECKLIXb zMJQ*R>^U*pg2?+IN^onlkx7O~t()kA;C;F!SbpR@P%u1-z^(7e-;oZnsZ$9&$4F&9$`AUOjQ07*g4kJ zx3bJQ|0fo}aBkL$3x>D$ru6Xbe#^{+?eTcZ$l&R(OOg@l$I2iA>T%1oNjbf^$BLlV zrei~vV7zZOQ_U%27hUR8^&?3QZv6}cI;|ZcyGIo0vxWtyoxI2PNh@IBbr{uRaeq1W zCDkeW?R~V&aMjNAj>aXXVoTZh7~VKwb*&ijz4addLOyoxOn*Jo%g^CUnrGW^?29RcG{8U9KvQZ}4aAlGB1@VXKIQjFE3|JBKF@8k3hF>os!m=^RANUKD9m{9bT z)%&D12ACC}SBJeM{I0zk`yyRhlrKFIOgx?NV&KqF9^;MPKyxjXg-7n!v1MRvQ9_ns zp{3^UsB#(g-&N6`d07IswXTa0hfvzjm3zR}ojZm|;tn*ppqSqL>;CKhcF853 z*K5x5Jm);0kH_PA&J=QTQ~zw??)Qt!O8sgKyoR z-@Vfnst>r_^|G&vCMvg2R`QJmhc5EiRbg*9E1Hrb%H?<}&8N%kzflhjs_0}eEm$dy zzJ3M!g1(uzBRjxPTzD?5>-+{T5{&?^@#}m{p)NqIw2K)Wxj+F%UTHbA-v$1E_J)EM zm=&hsm07VswN(|G=d=TkNkFBlZ8(P^O5+6-0@ELr`10!V$ZX(;M|CgL|8^ML57DE0 zFLbU5M~o%Q*t_%>TCTt`57mDlm-+)P+ykag0lq0a{EsK0Aj z0-!N^18JsCmLP3ODH9&X=#+#fRK(?De)!%y$$etKKVQrT#6ZnzRP9b0-Kj_^lRL?; zo-80IYxRF3Zp>}HGW*?k35=jv`?F*kZ?POp9>&m=1XiluT7;B|E@Kjp_-}8zSqu2% z<|TQv*~91M9|@=cg zZ?u`>2FN+e1f3-CT*zhFhlZe7FaDL*?_zCApxe1m&x~txe0+$+KY~-fy7?kDc8@Np@p}}N4k6ygh_h9jF%ycMo+y$s9aw6NB`PS;9xatQ`b5>H1FBmb0uauBdTmR zv2STo-~4#!|L(>G5m1R1`|IY@ajUgS?U8eS@t1gfuU}vf!P0?taXmHs56FFpT_1*r zJVq?NT%tcT>6ZCsVR@cKsU@xK70M~PygG2#);EdbpK%3zNw;8W+vSe<{r~zqN%4d_gom*-FL+k zw;%qoJ9%JtXZY0X#a)`$Ge!E&1__9kl3WVb_3#^b{coXX&8ecFg7LKzIAUq_doI6u zbXad@7tO{TJ}4q&2c*sGGhSd&T-t|bLRxG#OI;6;qyqw2UE?4Dql!U^en3M)&VmL znN0MeL4_#lt17O&GKhrRhg0K%`T8F;-|cXa8a+3i!`YltSc<1d$ra}t&PhTg zSq1i=<3LmH^0HRw{b@=VXo^p4&b&@w%wYQwPVuKhN*WV8`A)k5F zB;)S>*MDe89xg1;d#+gj9=ZQ8IT}=Uz&1Y6_EfJ=T>m;8SE-Z@@LZn;KeFq1o zsHo&%2Cw(6nSpL|a<`{nnV<1}^;m7jX7ivrZUjW?YGrZ~oUj+$cFLOtDNL@d{Jtg6 zSMy-kdGC_e5XKgp!|#}`jpStpaff=Y;UjByYCbi`70zTFh@U}zTGtXbrT*O}!ww9vQx(>MzrHM0d6&q(Fu4lVf*?K|G72xR7x^A+ zMpzz?L2j4s{u;_}%Bz;ey|1>b9@wqzE9jwqyXP}9WbkpKM5=y!Skh_yeT2n8SvM7{ ze#(4!%S-y-Zr!s(_Nf`3_&4r9V{g<^s@bS}wWj~mVzrg!0^X_2WjHo(F^`G20N{Ux z-XC?Z?Dst!ExqKZawGnH`i=U7mB`D**V>26T^ydXoVp<~ZgnGGMeXC0lL4O$l~yp$ zUltIrJq^FAoC4kF46R*P1uzUXyR;JM+(~aA^@{poZo+{~Gx^QovGcK)?DSj}n@p=9Ubz2|KmOI%hFO-B` zNIUbzXWUPe*XO>wa{hwnQef)ZAY;ItC9TbaSC6~O$1RnI-D(z7XaQdBe(HY9ep&0% zTmMgaK7pSzf@ zr(*(0v@zbL>3F3NFVqw}f1Dxog+oaCDF>r=HLo^>%qG5nHS__e<%~XzAFiD59}!9N z^{N-{#AqA7b0771eX+u%FXLc>L2BsnSh3fHFy@}3$#RjId1_4Rp!nnYh|aGm_7N=f z>*9kE*CRJ7kHfx{ZGRdbp%uKpFnI5vF@DLSOZ<7+^~a8kAs%>+z~aZszfgxT$$;Qe zrF-3;WS_!84a~eO)5@lnmbq+ozOQ5=aKt6%3Bl!%>H0YGHdZ__BXHxOAmAWt$;zL+ zG$J=3dy^(V>(54F0oEi>kOAHoK$$rqn0YtJ)hv{pHc)u`EB!`&{eh#a^5&f^%(A4l zjoEtjIH#1Qo2YFmCpaqz@gg9f@=N*@7qC9&M?IH1;8iO+DFz*OP`T)9U7$BoH&tz zI3|_&qrd2;?*p#e#dW2<*SxD*YArMMgZ5eVaXJ2TS1JPpQ|yFW#~jCM$}3+hzpZ$@ zT=3?2l8?_`hDYlsH%pjnGG?xDqgTScN*-9vrf%ojFyMrIid{#yVp0_fQ{+mrDE)`! z?z?+PiRd!(+8k1&Kj->s$&NS#~nEWByG?_0Q7*`@Y&z$##PN3h8A zC0Rqx)mUog@z)zP;b+Bi+_@bpUS00D=vs?QX)tB^xw;yRSsj3L#a5Q~{&N}az(VHqtT>|Nt$aHUArA7vi(eKv>oJ)OP zU$2Mv=Z!q}xlwav1DxSGMCaI_h(0mNlvn1Kby|%!Y;vVYW(GeTTy0RhCd~BAFw7;`x4+x^~1k@*9%gO$gdo@eS*Y{Q&lDY$Jn*ZuNIKXvVAG^7slVd zZH&0P6L1i4oEl*kgq<(m75V+`!5{J>0Kp_zwqJ6rCJS=nSZekR%|?`qPnNt}&8*xH zi&WN*bznehDVFVuM-0LX72H3r6sR0bolcqzDaoRN^$op{1B4jKCoNJh@9K!88>t$m zk7GyCyA!)!TYjpb?O6wq)zT-7EsAa6o9AU(;az69?r9p%_mMHV3TAclSSb81;1kQ{ ztjvE55B~buFR73E^$J#1xxJQ@3&nf{s8vpoXZBs?r-&#Rhf!xhXVKzNb2x4)BzhxT z_#!?RG)Ln10Kd}&&bT^$txHWG0wwwr8Qcp-yuVKq$1s;_ecM&jlau$!809H)Z~sb) zvo^`AKq^IX>5B6HV9r8+5Box${w1w1r7tM(P2dKEl^vuXzR7nSy^8o=`Kf*+7+$HA z(+zvmjW-W1$+^N`t(4{U<(TWj-WTTu;w=9tk0{Eld};t`YhJgxf7_Rf(R`qMq^L#_ z{~Or4Od8Po#R8429K#eYmhLRc&*%T@pW99yGwb^f8$NM_H&|QVi+Hp zazo;GU~j#x#1U)@{r`X>2LNxrTSAb$9JE4QwCw zy1T2GB4(zMsdG7Kw_PL`y?&ffI-I}k6?SB^382evvGCGWx^~{>}>iA-noAWx&Le~?@(YslXR6k>jHlc!Q>APtnQ|E zv+e(%OI^Qz>_745pYJ$*@bqy6|3B{PUv`pU06e`rPUT{^Mdx1e z1zwnvblZ>Y{~6;j{na(piQUqRO$5D=O$Grp-7`@MGl{wSzA2t>E8enaIp#A$Gx5|h!^zCTGknMIU~jV!Ot~x1&mFs7 zefPV3>zBgo1G8P3?h-@0fYtr^5&J3hKG><8|@1vYVe42CRbsZSXB8 zegXV7{I#_!hDLy10(MHcRi@LjA%1k{60? z>PS{F`bY2cn1Ixv0pMR0R*wJJU#>m(_{8NqnU=m|-aeg5bLN{%W3J$q+6^#H*D7v* zahYn0>d(@qHFF2|TP_YcW>yG4P$XJ5!kT)XX@-sZV(?b?R&;89evl~CiQ)8F{iGmz zhMHBsbeJ_?i;B*h>hJIyUnk{>lRpU{lfPdT4yE%dB zMKuZ<_dc0cK6?;&5tJiCHI?sec{t7_ow)p=zsS-V;8Ck#-u~{d_^;otNM}vZh(36fReJsj88GS=Hy> zK#`^L&SV%1UyxKHS25_-CUSXC-iUks*xQM~otFb&YtVbIuT{KS9qe}hp-jTETTCbk zxPKB1+9jcWup6n6X2F><_X}=(eDW4tEG()h^=^hOU^0Z3Q3}@DBRZ?|DMfB!EAS1gIUU4U^M6BwL`^}|4xbC6y z?ULE5$Bm%9>R`eyWtU-n)sCm z!J0>aM9Q}_xK*g7im}LmXg+&yUnfoIq;!c)n@s;oEcCQqvY_ovu7bX}tn-8EEDG9> zgw(2NB2Ia;zKUSYycP9@d5B_C=Z_X(8~ZHh!lgVz?+iNPf^-3^;%?yXN~^NeFi>r} zf{yXo|20>$%nB=+w1{;P99HoA=>WTfhLEy#Viv%V_{<4WK(la-&(Or07CCbNMHV|O zR2%a?Q^tQT-8ZdD%d~+nz7I>sp7uW0$8!5ve$aB=P7x_zYhe_u4u-LwV^<2VDQV)2 zwS?d<$x`L1fu=8w8$4|!g?6FhIHB$MTlNJuIK?LCsP_n$p9lNP2WiRsE3Ja*ReWdf zzGUJ)-Sj1lOo}{99^KpDS$30*h?F~R{ZzJmg|OP`b-!gtSJP#M+cpZK@MEqF+@`MV z{%ty5eJOeU$Cr=m;csbL!{(2@sK+Q?{+k6y3Be!^>1cXp9vVd&l@@Ra3m%&lo7H(e zP~|9?1M%$C1$$(H2k!NI{Q`kowsZF8BeaWNbF8b%50VuJhfD!AQUoUF#EgoeK*^<<^p``P|>4(M4{ z?{B5#l6`FY?4`XdQHb#~Dh{}Ff^Ku#oTQK}CUqaRDshK2|X7aYR%%8bvb|R)R*cwWw*GsGrxJ7aUz-+*la;yiR%1 z;*Ep8_9R^Q*FeA`c>#`rp2Z>iUjWMbBY&f>Rhsnv+B>USf5|hf?mb||TpAAQ)}&Rs z*i6v$@RKn#KR#a%b^H}R0_S7BE9=;KcN|Xd^5Xo{sg`>a_cy%-#8JYl+$5b$$N-;$ zTiSJ&$jFDKI*QvX?=P7T?z8EF1gpPo|DA@=Jg8tUjO)gSN2UC}qdjpsFn#sCwTE(U zql4TNNIjvuboP&=m9Y>+xU^KJVu;5HEJcgz6h(2o(dqZ~TuWSQYWG-d8|FCJgp36< zFA9zW*=W{{H0|qKX;68BaSTj~S;PlYWSK2VFvjGIH6&=M>#sV~w%Sxpe-YB80;c~K z!1G+=xaT%2d$ZRd2*x|~&Ns*zwk54>`Z${H*j5M&Cn=_YL=3>T33(p1y#-T;C)0_qrx~n}fhJ=>AVwxa~x;&8?5)pBTOq`7bp?Kpy-g2|mB zn-Y&ra3JJFAqiM)eM2+DSt_o@<4tTwr&T>ya7cnPMu}TW&`z%g8iBh$9Q~{{jE-ei zXxF^4WlLHw>D2Yvy6FQ*p){E1(tTIw!h9jqNsorO zYVNi8lAJ!+U+CCHu{p<+Eo{r#i^7dHzBL>bPVLsL6UK*FKSWi*oh>r_X1PU=K=OoD zSVNv>qP0+-CyBD=P!a4Gcp{V^C&;EU>DoZvM9{Ht60zMfgC*lQ4-G2E^7$#H*naix z=mGkRjAs3rq;)$!RwWFt(*SV67O7b5wYMx)5K*2tPC*e{Db7WnSuy3A6~-ko#}kv; zvEO=L*uaHzj9!E&M3c{=V2e8C2ov%hHbk@@+Y3yxO5QPvD~g`Yo*ud=>eif+y~pTi zZ`hB3KOSm`%U=%HawYqxhclb{h}zRFqBII~Hs`m!_EM1jSn|d=jL}Sm^<<|gN3{0^ z46j&E6P^1So##)Iah)WA9fIhu5o$r72u5q*Y_{I)Q|0hyC7|1Zu{=*BzwRDR>_At)Kg(G!NdwoCS4QasBIu;}hQ34T0Bego9SC*7J}Q-j zWMIspnFXaJFE-Y#gPR*+Q8cPFs$X5j8*(yH9lC*r@Tg-OO5JeXXZqDH z-l8N@m$Dfk)9?1`cYbVHacEi%Kkx5w7G;c$K@uKGJMcj-6|`zG(lA(^nWb873drN4 z9j;&PRIbrxbx70eWkg9)_e5>w#>a~6X?M`JhRK}+6A$5K()v$qs_}xYjR*f|;i!!& zPQi2q^8C$Yvk)Qv5?|fxUD% zvXzF_G~wj^zEA5mTBoUs+OJ6t^S`b`@7eRS-beDW;2X}Kpn70kuRYN|qbJ5=S0zLp z&zAx3-=!)Onl*!kCTw6x>?%*>@jL>|9Z}@&b1E0f4I{9VvB$B; zAIo)I?!V&pXD=ZR&(U_Otn;$wTSgS2=HUf*3U+Y5s``QJrTby)I=MC*0jHX)1FHj# zu6=iq*00f}q&9qy-J?!gOXQ=<~&m`SM9N8WtKe~)4G zDp(QnmJN5gdoo&d)jT`g^wWa-jKaOI_ueGnE#qb=U2cE$)|b@SctgIQ96F#qL#-ft zq5PDCxp1l=?;9Ee%t6i<20g9nwtZck*~KRykisflr%GR zIm9Zs+CE4KR90yYupP-?{d?;G{!VnJMnCQ^fmxhhZBko~2=$pAW;|=GX|EL$Z3Q>WclbsTu8vb0e(iR+fs2H?nHibv%`AjXL(`wI9CR>GN^X+?$h8N3xgHh{FLH0ff#PKQYlYY zEBP+jPIaZ&YEiLF|5U(cNr<>>UM!E?hsDp@wkiUCGptF0X zP-VJP&jxXGXn?*4MM0?&gzG`?L10*n>G=2 z(+e2-cDF47e&wyp)yk_U)E7u$3iJ&jqTwzs_N@=RbptEi#um2ci+w4m+_@rtU3EIG6fgFV<$BkOG8dfQHBt!Pd4#P#W|CMsCd`y2JP2`A*{{@PfmK;xr2jz@yl5S|<^o!HA_(~Y8{iV0syG+cCzu5?k4&Jn&FUbCcBoi;@n6*)!xp&Lm*aGjAx^W*(fEQ)Jh>6{lK!? zK2D2-J*xg!prS$58wO@DQl6=HYbqwL;|6Ge5l%8fP9SDuaYR{oByt2KDA+EYs_)W> z^gOS~AwW%_;i|0bNLN;P)NwQn#$r%%cWHF!32>ugf7moU(`}o)kDJxkB4b!MtZ-jH zCMoyK>ab<-5M(fyL#bKVZ7m?OR1_JI?MFB?{qclnd)VUAw-REBOvEBOCooYKS zAX_${y`X2Cb>>p-3wv{5Uxg z8#%{HjA$_AdL+g50mnw~zQ2N?VsQ3@RT7@a-0%)+6 z&wZ9|ToJ`)B|>!~JAP%BR!U%+k)1e4Kxd!Ax%RuUX6uDWxHboHy9gqp-c9O&Ca|y>G|~6M}%%TNJEBw2)GI32XK2M+us2e3O2{nxQO}Ihhc)MCHjZ zw)fcuB)8-8Btg047`bt)Hkl;F@knE0hEh0JC^ad>;k1M5mzw3e`5z*h)cR7;*C4m4 z;(9UGEV>|)O@BM*4=D>a-lsZ3uR$-tiK!X>Kk~c95>#Uoq)e_xF0*4-;u2QE6Xg24 zg{at0VeUe95JRC-1*ftDGppeDGIKpHh73^KMs9vweu%}#Nt59&3)W3)#t`hQ%de}u z<>=`IzlrLe6>qm+e2C2xPN~ws2vI1~6f-%8LhoZ!8(8a(oiO!?;td^$LMFB~4FJf{ zRmHfVK~wCa`9zqsh`oToYp2gI-g(Gx-cF@j|2=79d$oHSF=|scS2-3gc2u z(5v6YI>h@;NVU_$5GyJKJRPUo9|LE!eIT@HrABzV7GBrFrjPaAW{ipIROXlZ$Jsda z&aR3istELpux>|GBKmbRvCjBvUdrU|Vj>3>HI3}NTb2BHzQeI?)D~y^k$WV6hZy@X zi=yxWI(L&38}dZuP(|A@!_(V3TL|x7A2t!84XD5x>rtFB>7YSNR|y3n%Jf(1P762_ zRvcJO<}QU?VsX9kU{VR8uU!JA__o=n#rW`1Xt(l>*9QF^W~zU?K?vhDsy8OcW2(BA zGN0+78?D17+_E>TV*RO)**@*mT>KrO{e}8UKScMQgdUYmqR2o&v78?ma>e2QwDH{q z;JH?|TG#&j5kTuegoW_`qu_r&`;e%z{Tvd$=U^&4KMXK=nCF{>^|&(=q#Y5bn_or9 z08Or5oBM8xX4LFIyV3MBl`ikn&gqI%hj{*Itu_V6Mv4g#cK=rEhMX4xXmnKU9a79< zm&)q-B(^j`Yv9g=|8ls*5Y6}I5r~1?pJ!}@*SZN{CXY&7e)VVvccu?u-{E+W20@Va^Mj&5t+tOaZBwTZg>ivUvh$$zfTA2@U)NvF^UqyR|r%MHz%T=m~v zauwEla5ukQKRg|+>5=#$t;54%hk=u;#Qm|6t3duZMiJR7&=S`-i*zD~bF8>Tr@CoL z4yiz;a^)Q3Mb#x;N;>XIxWHcvdTsXT308Om-5dX1X z!S&}%`vwRC-`~9|CUZ%$pL`t;w*BHsN^1uyO#c z71e=_zDiJDtk5(+QCZNvePO6d);JGW(7X^2uNmoPJNl*Gh zV2Sgd_vIN{${^Y;d>iF8zzbjU+6aiAQ=ApCZ}he~bp6d*N8fp+dql`05`Q(+U5Hg! zv}K-CkRsEQM8aAVMGW|20{6Dx`I*%CxDIR{zmTFB&k`#buplGa^mL+QRegV}aUH*M zv-s53X@wU?nM+)9?&G5^vD^>vc0GuLm2`u6(Ng<n&6D7A3Aa2XdQy^p~PH5fN4YFCvQ zid=)A`95eL@2$?2$khl54=pg5y2ka0tC>SyXc6#FvJ28Sbh3>x-Yd+= z^36Jbe|ycd<#Y>jj+JvTM~}r(!$ew9hRiE>dxq+$uB!VS>k+V)coOksu~-8lHJu!z zo5?(Pw^8h^SfhSNPn8xGt>flWRxchJsVNyLw?5zpfN6(xH0;zhhr=sY*!j6yAxBX> zS`9JRrOXvv1}9O1e9+*!4B+Z2HH5bjN8V#T8wTo?BdpNVOszIq%!K2R)LY^HRzZ8- zt-pX9;xuvGV7|M9gYQU-g} zi|Jzfx|RQTk-4!ZcECXZPdmp+Aq1Gwbt^?-kWw>xhtyQM3Lr0~m!OuH4j!Zv!AL2O+0F1#}7U8e!nQrOCLt7E6NeTC^C zy?~JGUr>Icjs~F z-Wx{ULmD{s_~g}I!?WLWUF-Cawh&qd8hK)feXY#-Hhvlejq;+7=_FHDZG(q$Yy*G= ziANeY;}pg{;8*&*wn&jR(%MBKJ@JunbAspm0HTLkh0S6j25Bu7;hPJxzyjCI>KEAxUxX8A4>@s-9;YvS4&JojF2cwgQrg zRW!Qtt$HW87Skfdrp)QR!sO&IGT$N<)|`0lJC!;@VkE2EPME%&9ENszTGse{GDE_V z1hd)d`691z{4-3ayOmHZ7X#rMrX3=+X4e}z@=F?!1NTu5!qH+7W-6X`{Ye&vW%1|n zguCy<*saLXq^F3P2P>LfUO}W!Q+3)4jjXz(%A?vCKg`g3f@7f9foGge6~D{7cqdr+ z4vX!l((-t}a1R|1lT6r8oa8o(0XTi(&xVSA1n_EPtUIIZxu>UZXk}gIs^F@`&KXO; zBAcqLcE~QpDO>Rp!!`gF3)PObL22%6ELy&V!qsXP-o)$(G+?_M1{*2E;d2ZEi2CuU zk{34S{H;QXTa3bqp;`PY6ET#;Oo&travXs9q8XTMS-x6Px`qf1A%}#7d@^ywhoIRI zdL(;7)2jSo-IUH$>p;w=u!(XbI~A0NR(pJ#{R)V8D2@s97az@#2y}EI>X=CP96<#8 zOHaw9t$Z_v9LupJA)*o*0h2fcR*TB-qODUG8gfE3n(e0sY-J1!DD47;0(aL*5bN0^ z)Gwr48)03ZxW~^#qnz0cK{ly1;FH^?!-dQN{6BrHi=aV9+%cDE9hB)>W!HzeEHNY| z7(=-sEL3+fQ;aG$I6sx^4WZ9;&u<%_Cxqa9=m~iMH_dc*8?F;TBS%vkHZCe;*abjC zi;Vl)Ram3ZyE|Xg;L#;yYAIS8(H5C3SBMC)`kpO6&D9Y|4{>mfT1KM^8lI3tP9UJ2 z6GJKI;%phuX+1P=4LKSOSF~?o99Z(eH#uTrq}}L5Q3OBy47-_72brhan(;zcNaZfq z_XZ+wAmJ!iDF;C>%>W`y0maYEuft7Ai%3(+}iGc z>j{T8J_MtA;NPW>#B;b>O={X%vN?O+Ql1L+OUy(QWwACZL|V@fTAFz1E^C)stZWK8 zR5Z&IQ+SU=eWYGiur}!djM*Z-LZ`!VNppA;aJ$t=CQQ!mFe0-mB;g8G!RAdFGcN5G zkwG<(;bhTqBPtgqNd31I5ha8c5(Q^#^Xi_-K-Aw7lwz89k=9x{ZDY#zAVQR0_^GsP zrdSAymWzz#UfD>MX5feUSsmE5D-zuViUS39=jvctVRsF2pZ9L##_n=ns|Cd zw2v;E{=o$+j zSs{vCLfVqqhxNqx_tsY>tPXooy0v`)t%_-UnJTBYmL53CJ)_+6u^1$2(@U6dQL)5Q zYe`z+XFk7rcr`IWVM#V~rYYb~at=9$e-|J*{X?z6<@(qJ;fkB9vy@UMGY! z7Y(}@0mDwn?A~uEY56>(JwFw0^pZ2nnq-UF-?ial)s>8ZhbJJZ*jkinCRo$x$fXrmJn9PiR`h! zIHrN)9V{UzTX5SDPE*hT$@)q!*<7U+o_I^v?rCUSuTrFdXrwcUY&C~u9d!Y zx=i|w8?Hp9sCm!eiMEQFn#Hj$LEGelq#U;iHFM}WUVUU~22)myC>{J*M-^;&FMs-9 zAb~sI^1UNR7*q98nx@VulQ$7;M}vDJJz-8P+{F8kGVLQV{8y~!Rm;#Aerrn zq~);4eK9nACN7ByCs*&&H4sK#$tV!=nw5t8Kn^D7Fpk_tfn2gwa$afB?$EczLic-> z{*FQ49SU?6T2-C>FC=r(v6EAT*xIdH1xy>r4F%s_B7Cs90+`2SMplY{6CU3U!9!zu z=*5poN4}5Jkr3MaRx&2KWfSq|wxJqG{PPDTbsk-o+vO&w{uLz#frJSs`M-12{>Y?% zr^PvlA0@c#wgo(VPFdjgJ0U0-k{f#n-DQqRvWcI5j}MVlVWk|JYP;1MKLKu@RAU&` z-@L#nS)YGmqD+u!F5!gI@OoRf?zykzwPA-rVRl|#eB$Ne+h1Odm^LUmhzav@Gb~S^ zD&02frG0}fEvXXau9O)s9=j^lI>K&(qC2L7su(m5!u+kPeI+~4s$#c-yY zw4SIE{AkDl!kB5)&e!E2eKP~EiQlU?{l`KysHQ{@qu}$_IQm$>4I&+<3FAZ7nB}35 z;k6EfbGQq#L#T%rF6#4s;`KGTScX3le%==+aeAm!oh>}p!GT`*i}d%r9LxBV7zcU= zm_Cxz=F%si7Qu<~a$O}1u12o49}*f)b%*som`!!B-n-Stb0vXol<-z)E;5r+&?NTZ z8f7`XBZ{CgbNM7En%WVl}uT1k@>oySL+|%4FU_%0_7>L~E zf4B~z(}ylThLJ3dg}FP>ZeB!i&^0s$YAfF_-5par|KBiwDSDSoKGuL)a_{G$zx}cunGKcm=R}|Y0d)$9iO%C85FrQSP-XIiD z>iiq`2NUOgfken{LWE$kpiaeLqASdy7+ITRgQk&{Khb76hTNLN7Q$VAg~n{Nt;B^rmF5 z=id_wQS$iEdz1HVmY89PIIH_o;SEgE;+wq0S6_mLOJ6i=$G!yDF4rrdAMvu{mp5oO z*FE#=g2PC;(=_7^`4$vXrzQh1@X~``tx1Qc8mL-_X5>oMZ4m+YfBj>UlyB<$#9k7LFSkW7iLnlV_N2?hzy*j`#BKT5^{F%UWdvCYmc$(2wdyYbJI4Y(kP$z!jgI2 zH>Jl(o-VoECxMFeZiXN|jdzLFME$Krkeb=+@Od+eU9`IT5UX-57e+$0UF1Ld^Ab2P zWl(hx%3x_*c84pRU4iJdjQ-dOMN;rlqbw^%Z& za0gVBgV`!ZYdz>+#k8KyP|@P zg9Yh*c!r8MI4w=l%&xTE$UM#H5<3=1F>QwzE(l(cE94lia z#@=$meN%>DA6_ZK<`&ryBN|mA3&7Dci)*&#foQO^-WAOdYba@WhQo=DK2UWAq+;!- z^O|cUCB#W)Jk*K^?%Pu!GK)h588>K{c&a9@)`wRzm4>gxzf=K&+A&|u#+3qd5;hj5 zpB5h()kNPDJ;(35i+-umCSWDp#FZ6-X$J8~k79`%10D-wjpusqHdZZ})dE_}cL}M( zYlISndyvA7)#!=G;;)xXUYOx)37=v&hZb!C&E;9CTONE+0!B$(SU0u=1Rm`V9}P~R z_)q`%qb)Y~3$~rwH+TObm7qsn*Gcu*#>| z1>bG?kA+v9z@uK3l z(xyd-(vFr;YR&d-Ar&rMuFW?KBp!>gsfEICa&&6H&+u&F_S*(aG$g&rD-h!E$Y-4aeT&zn_Mi^ApJnjWh0bw5-ckMaoDFU5K5Vv=r2P+$OoQ`B`RTUvcf z@2nK;oHDkdyDn}6#y?tX64JE3<+i6PTyqDGZvw_+sgIAi}sIAR3>H&;{>z@sSW z*ldN`$ZCJ-Og9l?lXeD@P z373KBYiaJ+l^j%+)R( zMx@I+rpBBx4Zt?WE}nJPC2>4Q7k{q(KfCm#^&waKb3_#9V#C!Ad2S9u2$@&|Yw2-) zWbn!dUVp)8&Ut^X7W{^O^dpM2YBJ+5tz=k+Adr9gMddZ!W+B}vP#|7!A_l1_U^gy8 z)SE7afQ#MKo8k`mLYh)|*x)7dbe=j}eAx^k=mv)HQX^fJpz+T}>WhTqcodt~O#oWL zONj@pau^e+tH;KCDv$vlWXB1HhN+=&o2M6FEO<<z>wFHs+JEixpbt@bUt6MVD z3a@u&&&KKH3x!uWA{{hPh}^L7+IN$@=hcXtIF0^mB->o~9(wP_Bg;-0Ylhw{{}$l9 z15})f{yRm?kALZ84*f)ppNE;@nfbT>J7>iPFd5t`nA_F=pw|CCEx)UNs!8&S$=BPV QN5CI_9Ya)+mQCpY1q;N*&j0`b diff --git a/reference/sample-configurations/lza-sample-config-healthcare/images/LZAforHealthcare_2022-08-30.png b/reference/sample-configurations/lza-sample-config-healthcare/images/LZAforHealthcare_2022-08-30.png deleted file mode 100644 index cd251ff22a8624153019336ac20a145e04c2124b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 637748 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≦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

qQjlX7;B%F{H$)3&~u(>B)-Cw8unpJL_T6s$oL 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

Nc*uf2QS@j|^1 z4gQ4m{$y8|L3Iz5ejKTr(ByI{RyLa_IH*X%i#d?uodLBq=x~BX*%*YyMP@B<-oJ5s z(`d|n_j&zgT}PkN?A1c;tkHNQ_f<196>-izFq1RZi~iy%7M@C-n_^h+<{B-(`FR{H zfFx&;L=c&(7aQJSi?Y3#gCS|_$2Rk^V&V+r# zl$cq|A&4`%!?BilNy*I7BZ%?Z0Y(&KJ=gUlyZ9G6{_5UzeU_kIMPE|8_WG_j*K4pn z*QjW{s^wH(|A#BelVd zfWVQ!x(nUH{EnN#7&;e70Qhecq-t|gwR$&~C_MJFgYGe5AOY%rbivRv-`o39j@{kf z5<<%xceJtxZ=Du9z6+ehLB``Bu4P!9CB~-*O$D9by^?Gi0+>qMuL;Z{gJR33q^NM;~fkD8D=fB(#U4exUb=u$7fDSxhkH{Kc znacjN3m^w6u`VccVn;2%%msHi8Zxf|#3!CBzhN|BRAdnxP%PMDrbYSng~wcH&h>zs zNSmJA`J)WqUGD}CBuhzr{?jgpKm?;vH}m}%ry+wO8~!mU>1>iWd#vu}tJ7f4ohU70 zz^g%YK?cOwyUF45#kECUm0}Y{J{c?&tKK@mRhhxjvG26i*5HmJkxATi<&=w_#DCF6 zxX4@*v-3krMVepNiXHS<)b?-3p5lTEz{(*KW(Gw9AQdXeHTdA~Y^wWa#k*xmfO7Bo z)vC4X@d{J5)33&?540N)D?voi)(2xGgapCrPkhF5~kV`p$hh4C5n;XfI) zpda#lgvT354MR}W;Mrp%6@On6yOSXWeq&hs;9)s;LjBhMN2^Zyr&B_oEb*Bm;K*@593- z0e$Kt(+|6N@-Ww?WmiLUw?5A}uTIw$Zw%oLW;HMRC1l zXMc-!FHf$V4y-B`K7%M?jmKpR49`A`P~o>2Fnaq*XWpM+d_Sm78Siw~gKlJ77($G9 zs$(c4aH{7zh41$woP}HpAoa#`Gh#8>F!(nGOtIvB&>CeUT6Vb2ZWQgDc?T0E3P}o zh>n`TT9**#^#S6ec@JHbK*LtQCV~FJv!d6@C{efr!NxbJGkoPIuqDttT&T4Wtyo4J zdyNdUq_#;ESQjg6x7lLM>WoVWQC%%fe}`}1q||2rde!G>BHC+TOjrLkx|qCIp1Q=A z5;T9iStu0}yPnb-7uRyfE`nGbg%?yLhf~W`9Ar7yz@7||1PQnZk(V|>1M97|5Dl0O zgW`|(uA@JjPA?zL-&DEdiX0q$;e^tL{r>m@Ana0}B~vu?EP|%x>8eMTxoYSvotB2{ zX0#J{f)hW+6?CKNuy$KsEh2-H;-?-`%yV)zuuK=u1KSFY7nwEWVq~G?+Sg zG9{p8&kE79A@68@SzjlM#_BqAQlA?r?)dn7cMVU0vj7^C5J!h2G#>dPtnNlw=AzUR z<;r;zp~T=mTis*bOH3G(af+pr0#XcC+fh)^t{5y~6<+PxuI1RwqUG~az5Ma7YWXK) zDlsG@am%|U8|sL^a9Z_b$IP|bwJ2T{=amHU@RSC&L^$zXBDxd@IjU1RcI(XQ7I?@9_n9$dZ;lvFIk}k$D{b=*}lPqj~ z`1*Ra6CO`7yiZxfhp@+O6JI38>PI@~i>D}`yW}|rI{irZH*nPKM7}%4(_GS+Z!;t} zU{xPNNblM?yxsiVMh;A{2xaOUkM+8u21kL^!hYpfl}b`1I3wR(FXM)7`}3;vEqhst z<$m_Yd^me^QZ!cYr~W;SKKuav^z@%(sJ}d_&STX8pH=?>2a~jQ0dBmIrc@b{1y51( zbvpWpgj&Xg=Z3uITxpHdAbQLPUd8>8+H9TzVUg<)d9)Q9T8q4p+7M(Mqqz|K1V$-c zvQ(;Bx1LE-A(Qm&7&IGQ+yq=TXhsgmACtr8|}lx6z7lFKyt!fJ1=OIAV@&w7zG)piDV6G zJ`%cGNbjPtzen&hXTl5n+O|`hsj8qsCKtC@T*~`g&$?mv<{I^-V9gw>LbvRw(C!EJ z_jOikyY?O8Qv2*&kxCm~A0y~2v43RN9l&mK*cDZC!BLOf1nC@7CcnpL7yBSW7@Zjn zr>M_O6adVm(a<*~!=HDkIVCgV-aBBA#F2`lu?jaABw5`Dit4VebwgU`O;8 z;%$^}#|t{|(O~a~dH>L2cuO)zMHn9?9>N`U1d1LkgY-w}9Ga}NFqyl$B_j!#JJNrT z!`g&lJoF%mm>!r#(xi%R!BGGlultdSplKt_5p+_~=XNbGPP=MPDrpmq$_q|_7%D26 z)t|nM1g2-iKT?bwZr7~T9bf-MJ}fO{vEzl~Zq_s=D_%3T%FG(x0ur*}*}V}#S(VC< z*CDkkl+5mmPjQ0+EGPZP6#%ODya}bpvPLuk$_t!|?SFSWh#}-baksI2^+2P+sktUcZhl8ONqXh{c@|E>b z9=Yj)h%O)QZ%Op+1~!*pB?furIkoY73gv8LG5Bo&stIf&#In~0+m(Y{+GON|_~lB) za7)7yPfSk(CUs2)t;dKh1b-E9P+PD@Eq`u?4bRU~dg78MeVwe7qv8F%e(*k|>>g3` z>zhS~PvDOw%#WJN^thq9#PZgd2MZtFW67 z^|mIZs#Et2}zJ~^P)U*AH168_JmI9lY7vpVV z?YB9VP?z7H2tbqurChyPi|t8%H`Lhw+@BWpk5Ts5$k~&48ab9vBgf@W0-(UcPCis_ zor7sqk83%XiIS zPpghY^&ShfPyO5fK4S|wSZKqr=CeGQGA~)2B;`NWp*8%1vZd`wG|;97W5A{b_fq#A z!imn^<+5hX_jY1BKcVPNun-=n=w^s2Y`wQcF2KM9X(5WF(0K!-sUkG&k&#>fYL1T% zJ`|7ecvO~d)~9i8SN+okJW0&QYR~mJWGq$3Hbd0`mMV z9wKB523q5TaUEz*wTEHSzvFJ0A5_*u94}Oi;@yJ#;JRjC7Z1EjD*BP2ko@9du9mxK zQw7ja))ZE`|c~yyMRO>S45dw`d zf-y4~T)HmBo4RsJQ-oBiK&?OnY3$bwsDe)177B>v9K1{LdOy6*dbSSC(>!>tzb|<*2epExMZ*Dc>#Swt~b?0foQmJU}0HtNj zz%}N96;uSh(SETrB|ObHsX+vTle%Oio$KpMoS$*4Ue?!%mng={*+%Kn)8(VeaIukSza&)pO43vxp;kwYASt)1{W!f6d9*pwv4F`P`3|T927}%IRtL$9 zpImw&xYEChOV9poMx2M#ipmNIW7GC;xMEU51VKt?Fs~@+Fh>d|BFp{+KoOkn3xfA9 zvVHQW->-f?JcKn?3fG!CxbgE!zi$_k+?P1xHMg~{1n{GZG$&K#v_mM?FN?3FV#Rm_ zfUgqqlAWzleVr|8g_c$T$@MCaGKE<`CD!eL8-euG68i6z7~K1h8kKqonY`Y~^nBN~ z4o2JTHigSH@4)|kXo4hIz`)gs1XIecMulpqQIrum&>KmF5o>4W3$E0v4u3_i{JAjH zYW~{bJA73lhgM0Qt^l9!klC9KW7M;~Idp1Fl6u!}sG-w_9wD$~F9^7uuh_7wLp+>KGGtj#E@B!FX3IZQz6# zFBybb|GFzPqAx@W<91DlPkr49=2}P(C)S3&3C`QM=Xq@l#+YKvMF9blbtD&;(R!j^ zbT8sJ@W5CmQi5KVN=knVJM`%9&2L8z%%LdPJ7}7lixSbRY2d-0bHG)_P-6EjUaTkG^oP%$*a@g^Z3GtsKBXTV&DE&P0m3 z;ZwZxt$>usF26mMY7Kg;Ry&+2AfMX4sduj>ChIjALKPMBjh~l%B~Uo@;y%o8jjv?y zUp+A&{1iKOf{6X;-n{^g^#xssYf`AW)r#%~W`SJCe33NHAH;OHL@b)NXjfY&-uYjXL=^5Tj=* zgfifO6$+hEs6X&JRFlLkP-Mwc1Bu5MTO0=?<+6`6UogBeGH|W3;)9+?iSfI+I=JeV zn#nlSHr(GueZF8I$r@7qk0t|@Y#EU)wPb;-Oh z!pr*J=(DFfh_k!^^S!gNLi}uc{2`zsLOz^S--qx2DlVIb9mhzf&Z0V3m1c-~ajth! zYP|jpLnrQzyD*}))*kf`XZE92H_;IS+rmVzVV6#aJ?m55_~SSs#6dl%+eu;gh%)_;fsY_FD937>2wlF z`gxmyRHK5|BNLtcl1av_G{kE8mV2b$F%m(`uuG*1&v?@6%E>qHjj%?ze)*OHN*LcI zxk!|$|B&14zfYQK==%p&k586`VmE*0&Idw(| zpm@@0OkfQ@iQGo?dXFnelU_|0%i?1drWj4W*MN$tg1Th&hFGn{ljU<~VaC~}OM75v z(+=Mw>S%YPO9>oL%RdiR$TsQ{AFA1}03gkZsWa=|+ij2LBC z^5U4EJ7pvSA&KiLMQhtbesCr1>i_lQFyTz#pBy>F-KS!9Aa<9I8CFd5&6D^YQd>Fw z>6EJVXiOwF`YbH|76J;p+%+ogFN#n{#jg(ONOZmM9O!>PVX_nI$8WzBWDrajL|1g) zBdfx0EX|<*lmW3A(Km7?N<kc122cHrU zB=BB^Y0h^@!w;ikcm9|*K7AWX{V z4qf4YxN8L;-rT*wKS)96QBkmxRC0y*TZlT99PWJ(cklicU-~{t_6llcYGnWzF2&$^ zZqA@=wycJ|5S-G{E{MYiqnpW=uR3Dze=i^iOrXS!c~TGvOJ$HO5D3&_u>q2_fmT~I zdG;T-3n|4BTzD64RvV{(+oh}ECBZBK{LU8=P{&)-%nKZ*@|MLQ5JE5y;3d!OcuOv3 zy?L+~X&D9x2w)z6+bw!T@+u=*dR;x5yo^wiLPT`3VAbT!m8k7n^R&s}5DCD{9?zI3 z%o$3;LeGPyYF+oOp8%=>hn=mVY$gxq$Jx)r?%(^@O+T3a?Z^Mk1u@~sKu^}o23tt& zO3tf48AnHplPz7;`i}23kn7-y@n5BRnx&L1@{9zx?smp9?kB~{+pM=QvR|}Zl74R4 z_G-4dElFTkMsH#LIFPo?8r*#PA@t|)zT2`d8emjq@1{NUhE}oqIO+ayO#SR~mKVPl zVSvbC{qsGR`aKl{>y?dB{1hNR%yEQY9q#bbWcdwAe^tRAS?h^}<)%B^{Jz6ONQKtE zSb(_D<}+$zkLyOo) zsy_twC()zYN~+Q*qC?(^SBG4qrgi>+S{QXMSRJ|7HXMxxb{}=~b5l&a%)(~M0TS$U zMY=FlD9X^sQM14^EA`VIZIss?t;{8 zvE6I}+9c)v^ropF&64^2fUiDL(nMsc#B<@@Yy4v&dwI?4*_X@RK-+fK-3-a1zEV4+ zaaZG=Fs%3k?men-v%Ni1W#f`(Y8C?%A&4Hel13r@8IdW?g|;DMAL_b7GwMtBZV&N0 znAZV1rlrw5x1zh9U2U&U4-`^R;nYQ6{vmB=q;|7)=TPnii9&{LjCRj39@xKaSR z37q3&(rvIw8HU(_sRxDqS46RJtgZ~@H$|M^|11WP02IN}5N=T-ta|S=(y07}%Pl(h znUsKHxDe1;BJsf_cipO_(#=RINO($gTx!cidIzB zt|*lqyW}?EX$wJK`^D=P`orrLxx|ZqS2Bn_G3xTc?LUoCqknob6hfFI_6~j|2X@S4 zwEdNck+*wnvKklZOK!KgPwQ%GaHxQ58K2X7ciSj(xq|8lTk~zy zDU6yVeF_kS+>tBnjnR8t=M5y5uYk#_>P&$^ZuN!XKf>m}`y@n2o^D+5Q|h#aI}OPq z5p=Y=YuI$fenxo32~^bFnv!+u_uf(jBz4f1Tt`;`mHfkwQ+)D=o%6TGJy#j_DrTcM zG%+Mcls(Ze)8^+*ZoWw+iH)IA&tmvJ%7rvLoNLAHY%!s-$(!EGHM>W>9X;DZ#E}vPbW9b_KMv!%r${W4%=>^ zdh~6hLw7mU!t3|;18g`4>jvda?Irn@&Eu;kk5Lc@w2xt8&gXD4ILysPGKnTqr$jGR zXzb?RBTowrZ^K~l=9Umj67fLR`2Fvc=D(ooZ?=Vj^Tf>sMP792j4XI@QcdtSIo%W4 z>eg}ODfejnUbybjc6Px8i^beAjHs zZzda0P%4kiNcI}tX|1OhKJ}$n;93{0`02uQ^%dDfiQN9BQx8voYP-m2XI@U#^%c2@ z=d)$@YrKa28C2YD7UfTSD$TygaY6XT}NT|sAF_7W@Ra@hdMCoRh zKp68^BCK^j+qr>q@$QM(GSL!2Oll#6?f>r4`FH3Sz&;HH7pJG-b3Yc)bTDAj1U4lp zhWRI%9ZlzCCuYt!IyJ`79S&MU%() zsttmq>jeDC7iW=zUHKxR+ zmuXkS=-P=HaHoWTXy>@_DKT}r2z%OK6SlNwjFzGi9p~}j8SF1a6Ur8__^*9reswQO zJ%nGmtGhShu>XRO$UQAlwgWozAt0jQ7zqUm$WPlbT@N;8ed8*T6nLQM!^K{$A$l`0#VqyO@U{rmvRG|tlpXQ0oP;bE?ui!!v21EUY z@&A32;fR5!l%fTgte6JM5~p^q7M^KhX9s|_x0?7Nl73q zVti-W50{no^4jOdv|ay|BRZu3wo4xk#4WzM9<%6ub!79zK^+6i^e}J=J*Ma|iF6Qn zq);I3_?}LZ4ZwH_QY^3(Fr0yoV0x~K37V}xJqM+B$LUXo;mcFDAy1^xpvfKI9a8rp69YflL^#{mNU6 zMfWQS7-JVzlPML|a)5KzFy+rGBL#ffTs~eIzxjY8hCP{@S<)Vrf9La`3-((#h~mwY zx>P$-C@tXAWVkgW;0^d(w=bXD4_EFHRCszdN_3fE#Ov>j_Bf1;VqmbDHGpt_8Xy23 zZ;KihTu{)H5)iEVxDvtCm9e=OMJV~%=zj7CVE{V_xULsE>jd+Fl`v1Ff@3`M2U zo{y$RT^~j>1)qgi@Djg@!j11d0l856-+kL)d?;WL&*sYekL>^KX}uW^!C&E=b2ost z!iV@u6aJ}Wn>6y51pD}$n2L?SJs&!K-XrIMjjcKN+)0m+4|XhGNEJAiq*GtK0ICD; zegUjH`!0k5OU&zQ<$RVc=tBm?HxQ;B7D9HX9lik@z{jrBpQ*>v0giqW;8|Evuzo6f zp&2!FWU;l#j4thM(JKa$h>H6?Cb9#eI%eo|oP%)a)z2X>>xhz49a0Yx5q?!MV;BYOq2; z+nox*>-ZF?MSc;4OpPefds02a^`O;bxDJST^#6H~F%|yFPkaP+x}_T`fBB6819-y? zi-_*5Yx0l?P(gG^(PRvp1(o@z)Kint5V%%^uz+<*L0gop=sjCbWM=sP{i6T=*B8{M znO2Aogxp5k`uNht3W0Lfw{$tE(dt@mV1!{i3f9=k#t(SGoM8k(jtW1%yGtOl=(%c4 zAMzkOK35OzOwp-$fx>;YoC)<4To$a!qw2)T=XZEV2rXCf`f8Qm|pVs8dzrAm1AgFm`9!1!J6@j}tbf`+k`ic@Fs=K+X znqBp@2+HLNHD!kf4pW%RM#FNNLu3R|{IN-*P)wDzaTS#P1mQ#p=+XD8!m?bc%Qr)FtV$&%gVH_-n1GvH^5V^8 zcrV}uthXl=kO~0U;IvQSe?zl;S26^?LK$^kl8E>XuVkISGd}+XOCocBz&_|t9P1So zpq-DrJ-mukKY~VSeqNh)0|GM!O@5nkjVd&{Npx~Bp(w4-s#AQCnc8&`xZcTavDIV&@#*y= zfc|;?PpvxooB@=`WV+zVa%lIpi$xGdk}+_n0zu&CQNh->t`rx7#N-;1k-)DkeHgQ% z`;5o(JhY6qN9fJ;4~DwdF#TPx`3BcI9SonM>P)=`E0_-iSs2p|Ihga~h4bLl_7=_> zd-6gu&Cx(ySN*IDA+15O%UZ_Q*z<10^I!T@+gQqOoNO}k#+#N_Zl0?pQUOmtIu_DW zTP&TCK2*=8!@W7JMSr?wj@MP>0LwQ!nd;^Axr5HiYSX7MHMLJ!A@l8mAF#jlz0)Um zo1AiHOTPF%W3f%(%tO&=lDk;Bdx7~zH9&r@tVN+ftYPpz7BiI!sfP^?A$z$Ok>o(M z?`!0W9ZdAsNXNzcFNcO7;FiZyM3|cwyWfD9-_Jm3%XdHNn~GE>0zbaf?BJ2$)p$H) z>e50^#8eVf6Z6&vsdluk3L}utY^X02*euY%tUsrwdZ}NzK{S|Y6kN%uBhLpg&sV_o zuoMIO+*+-!UQLng#$`jdCPD#WCeTm267zcnUM~CI49rCH;j7$KHH09M`HM}ZnYK#@ z0rxvN5>a>Thc@5dYAL1TH_o0L+w>F`aeG(n#Os4uhz6byf)V86?S?m<*io?$1xCj7 z|M)k5+c*&FC!qu$5K2_*o9J2#al3Rpw`2`tp0;8xm1_Ub$DquW3g8$R-ChKDwU0+E zePBeYM6R;3mztl_)E3O!R@bKAe&W|*_*g7Aett5z3(9C1STmm0;`0>YWw1*vG0xKc zXk4#}KfyijR|4TGrTt!3k+rYU7~i=wB~#SH>K88tMexvR(9HH(jPIQ6|2n6MXLugQ z4+O#(|>zj5k*ba4diWQaRS53)Y*_S3w?fvJL zo{L)s+NM5p1%4iaIl1q;hCd9xcgqD+o|nclyd9u$BZRM-L;LiV9v|(~lgyx4G5+jn zZj=iEKILq8L=+DXiBmlR}KIjv5!4~(d7N8sZvrSfLacg1%f6FK0FKW0H7zU=WFdx#bt&qIVnRPp?O z*Szlpuf>Tv^Y7@SL-Sn*p}|LPNKt^8_ecJ3>HMw$u{cFjczo=aM#!Z#nIKdG=j*WM zH;j5{_p%lpLj`p7rBq-0;@_6i5T3>cpwQfWO8jHO`JiZUv;|p#@-*lU;U@C;_ zAZu0AD`fI?Q4VPJ=lBTi+M~5#ua{iJh_u7KvzqadGK{U$0f)oV1wix0; z0!BN9?e$mjD1f-E`7mI*M^yxP1Ta0b!uCAqL;Q-=m3C1WEVk-ZZVIE)uR|i7Ba`)S z(FS#jXY>efHy_SO*)=GLFL6i5_mSs0f)rS`4J%I%h_Wl!4l;# zZRejTsr7H&)@IC>09YznkX8IFOY`O2-%u%y*@UXGU`eR5Ppy9eE0|PW+qY+e3&B)3 z21%HfM!K3xt$MfQMB+%*en4XRd2n_1B6B)K*)4@Ubx@@;=S*JTkYM#VlyNw5Ini%C z-Z|x1ALa=XmdlD6ws8`b<_Jf2sQ_xDEeAT+l`=FbYz_R8&w9OcWP}0zfiKT!Eu!Uc zS&n$FHSo~ulI@hrW9|62z=K}UqmIzcN!0=g@n+|7b1D(Ms;ZQ%hW4|Pb9MQyiCWR< zOvPu7$GaSh*;$JMwQ{3dZSMkRnys9OC=g+HG>`d`V|p$>*{Ag^%&~o|PhkqL&3AP0 zb!7lq3vxsK#FFHC(q8nKX+nY;gOruKYGk1^FMmb&mhk4j`$RvCv=2@i|7vT zu{vPFiE=*WE)G)wjg2Pk2Fzmrj*G+~CG=H1;8i@B-xs6)0*_mduU#V5UgL1I_gX)7 zewoYGU_I_6e~W>=vSfekO#@b@HKWmtlYUj@>Gr7V`%_f$JpGfSko)@W31HiXd@C=& zq@SaMM^!g0-I>Np_Mvvvdscc*{>e#qp`-j^OSZ*Z1)|M&eSw84M7;C1tzi-J7GB)a zyhU_AITTDe!ZKhgW;5%u^TTg+PZs`WeZ*U}Oq=cFs|irPI1j-t)-QX+ZF-6sus=fkkl z$ujq1A7+Ed9O(=97gJJC4&&MUaqfBmZ934iiKmEbIVLrF%w%XvT)c$4g#~L2(UpD@ z;WhaPA@}SW_M`xeB2;?~ebT;hjHuAnvbS7?1xI9V2TXhUqNC71$PZC4wdh$9vKHN4CtR-%mvM%Wv9>U= z^Lkp5pJ@7Wr9BR_F7YUSNHw9E$kv1>Sqkw6;OH5nGiG4%=D2P%{b?e!D&E)RJASzpA`M4@6U0!AO3&n6!B)18f z+`$Fj<3W75aJ5{hR^wXrs1XbQAy-o(oKEO zj2ft;`0)X3uSXAtd`4|?iZlwyy#p@N zSLcN%9rgxOHDaI6pZR^(@J2J0=+FNVF@G6^f0l2q{bK;Id%^A~2Q$bFYQ$_)*q@63 z=o1A@*ULEEa2%LN{vH1FLn`I#)wF+C%FO7an|EU;CU`L09!S#7PY>bxHmSA^MUt(J zo8{+S!?k8dOPdAON4+ULM}~?A3{u?KbrY39;ko68P})-cs)Gafqm&0}-GfE=Y5PmJ zBO4h ztg7{jB@^jzbX#8{f3iKWaRs9mms{O>)kX?Vcg}km@^4zhsk5G=Y(S!?E~9l#FHv5o zKQ!-g)L;9m*|k`NAt&SQHiA!lu6Szeqz1WlRfXaYMX(WRi!$5lAHDiC`r-EN%8qk; zmB+Em+v(~XNRH|79J>&k9@ivBRxKpWVA*7v9aozY)RemPR@xRXaBi{dfOHthE+LC7 zemfwY-x2XP6EY^z-4MGlLCu(1TZ_}Y58@2@$}@@hnp^a*x%FQ9|95T?Lf7*ahIlq3 zzgh16`s0fZB6SPYx&AUL(uIy=ovb`2A~u9^prC&I6DXMx>c6o|PfASs{lidpuD+i8 zjB3SrveVPpkk3-DG}AZLIdJXgCF*Sbh;OPjS&vGSwI`)O>*iLC-=Mm->j(L$JT9Sr zF6+zXTEDa+VReoj5nBw;S&g4OS5?LK3Ci_N9C=m%-IG3l>3Zre!W ztEZMU>t7tcGe)XMS3zJV@V0Tb)(*SW(E7Tz!I0-&(z*61=KyjmaYI$C6-|$n(m{pM-Ob$p|3k zl#?>+{Tk-x$&!X!l{{}RGSD%A!=y+-21vU;MI)(E%dCwL3EQ=iXoUR=+t$q7KE!MN z`{SV5)vGhqoaa^3y!lA|Z%+UOg1mJcFCa<%PdO#>7o7XZSqN8EUa-Gkx@?d zKr_AoBwdg0nErYfwSy4)d2MOA!o7{*{N40isc=*OpMb}5g?h`?hG=HSS?kFy58hKw z52kBb>8MB8nextKwdFbIW<_a`s zB)0s2$u#~3uebB!h5t3-9t-(9;bwSMhQv@`xto8c&VMD8u;OF{(;N!JpLfyv8VB{t zhzTD4CT*}(!od`l+RirY$M@Cp96&60w;7`)w*DNJnNIDPrg90qa#C#~#wOsSo3UK< z-iBe!Vv#vX8L3JC)Ne=il37GD?*g>b3-)Lm^(>(uU2w!iC>x)l-f4?>P(mB7EDdD1 zdtY9gEsHi}0o|TevToiA0Rj1?9xK)*WO5nOT&IdG>8AqJrYpJ{x@+GC+Ctkba@9%} zK=S-EHrK=w778VqX`}3D--ZjCy@h{%;ru0Rx8Z}aRD$Mw1}TC;A#cSgTqwnYI2j&9 za!)Civloxyo0uD2Mm&=&ddnYv57m~2x8d6dTBxe#Dg2--)Dk)Z-WRhg4Pnqn2}5QA7KRIcl15|Y+P_$5A0L(lJ=3@r6GVsO^@PZ;D4rBfLOvS-TvJH zF%gF+vtaPrmP9&Vwxy8m^ozmtLHxwifuS7L{n-$P_U41j-}(RdoY%Q?B~GcY-SDw0Avm#O9&fu{)M3oAMm7yvSwVItc5JhJX~>_;CH&NXoK9%gS4;ivseJ?_g2dT z;Q#hb7(QwU#H1`t3vLRsBarh8E^o9TLOqTmDedlPD<`j~k5hS}IY9ZsZ}M zq)iKNupmfwVh{92%M-&8=^7NRb%S7dzRVk_!Jz7J+Kf>!S`#vEMy;ek2^uC zEveY6R@Q-N2E0`QHZ94q%%wBP&3d^9gMno}v!JMr#>yoK&;I7_4XXWl$ZPEwbF`BZ zDm-OIYBdKc?!^XNE(d0cST`p`0x0r;==F`&q^k{9OPil$iko(~3dXCGNHiyI8f*@V zY~1#4uq+@#)~i%@240o!i?aN2GjgP_`>SCmVul+>uCGaF#aZZjfwov{>n!x~`c$Og zOJ$FSVvHnDt@|)^`i%36+0gmGu9x!)_nWYW=uV=Ml>$p0&?r#|`T--=a-O^;vB#8$ zkArW`n*_JozDKKlxh{7wpgw)U1EhVwjqI|Dr0-K*xRg?bnThzG*Z3!HQyAka50x)~8V6iDn z-$0WU^60>M7bEIYnQbecN(t5yPyeLz^~Ff_?xHd)ti3*WDhQ%k@RR{2PFDN7E6$ua zebR0sw+hHmaFP&fY!)ObAq=|gFN0oxX^jf01nQEFw#YO9WiJ<1jY6XBZ2^|E++(kp z0}wwfvPM(p;vJ&|sf^2#BTa>E`PuoplFfaFSf$-U=rhKOBY@9G^D|$~D z(l?;C{Is}g9XD8gc^|kn_1@hQL(~ezaAr^hgfirN))i&P7$q7i4P5(|Ri* zfr!)^6zvzD`#s2b(Bkp^JZ+sqmUSp7QRDH14%| zHxG*F{pdekUfW&ecQk$G+#yR6u>CN`?M7e9{e!4IG#lZ$%w3l+aJuQEUQi z^~Lc&QI$58G?Pa8M;eIv&Se{ytuwWGar>yc+IzHEJ@^_`ks5>Vy0-QQmru!1jJFLSPl@ma4V$I$NA&UN;Df{Kgah(%;3 zFi<#SNodB{xn7=aRxt)MeeJd0dpi{7<`l|^m2TC7CzwypZSk>HVK>mS$M~C@m9&!x z8TnFf_*vR~o!=-RM+Y#-2JIRq0JX&LPJZ(~-}D`F7DGU+*Vhv(j_Og!ANR#rY!75N zhO~oMA=`p{%~5#frMekhER*$vbLxmUx_EIwk;`Trp>g#-w~$X<=>YvPbEH!lk6@?ygkkubTVO-_`-_6E*}5MwECN z=f7gv%=SqnKbwmpDVRF5C3B|&GRjkAe3o5&>PMY+ljw4mf-j?p?rM=%Pcg~@HHGkZKF{M zUlEKMGxCRY9?vXe+VSgR)Mur?p%TFK`LP?k{tz?ej0J*a)$Dfl%ozEGk8xJ3IbXvC ziIYaB=26Nt28o*8tiURE`O+4~cl-@xvoPMQl;m~z#MPE}-kGP!C<%Ys`@2tlHo^}*U6yH>I_VF9>h6L- z$cw;4Mt$A{AB6b1uZ^A37~C$k`l4^`xy<^74cn0YNDXzMT{evFj93iVQ_D9A!tpog z3Px?CSp*4S%#jt`|Jem_5)Q?}mxtAS|J^J|X_loB@Y^8z;u3p73MV2E=Y2(gL8v`J z+j#$EY7JI)4iphF@npYyoG%Zk+TqPU!6AUdY_PNE_sKm)b!6a8L!-`z$Oj}2vOs3@ zJzXaS6PknGuyjLxs|+IOM26}@9wpy|a=U_rMRrMoi8XFIQbs;Ga76C%qOElC!(cSORoP&ln#QA_%IKeKhN4mZ>bsi94o%#X5=4DbCLYbB#v|_Mn21xqdaoH6c)1L0rkJj;m?XY>?VCP!K8@TsH$%Iw`f ziTo1KWcz8hW(mfA$53gK^u>^nL?ck)4fGfWaio3{>@kqHLR}tCl9}_C zL|b}28dC6I(wFE`nqByT%?>L&!^{cS`zk0E013q1u#~cS^q4s8N+izHZsfgG3KiNM zQ9+y%#6|qRjh}4#i3@}NVR7r!>k*9+u@m@4p&aRrq@-3pR4!JW@Qe5`ZR?RCq+_Id zN$7KtR=HtJBD~c(8vQrO*^b2B0w^Nu=qMxm`YxL9=?K`7!)0PZ0>lF;3d^WwlT9R7 z7P*x;&Cts-xsXcQQV_3)hbRR@!^h9an>ZtJehj_Esh`(j3Hg;U7WAAVoaUwjx@55* zp-W8>>ABcYLVV}=#)4dRg&CT6kYL~q{FN)(r=6wckFw5mpf1glkvfd_n>$bCM>iAe zgnv@!HG_ops@D%Uqb_>T$$Q;g_bbGej~dw@z{&jc zj5oI{{fj?IyZvtYA9?oH;(WIrMLJG>D6$nNl>`EiMU~3Kqyie!$gfQ<|_!vOrv7Ucv9z{e$$2ddBqqyUD~Efl*nV1#f7r*?U7)8;z~%w{}jgFBiN*w=1-lO?Ah9-IJokBn>O&PWBmI9oK);w9>QB2e!QmRR^GR0-Wfh@Agq(&u>9!q<7@~en!6?~PB^*R3{qf)5@};3Eo7Nq5BZ@e)#T!8MsE;C)&aE}NHUY^CpY zIb}&Enbrb32lIC3Hfx2C`os3Dh~#xRbTjst@>f&fa5(ihjOZ>bD6!SDUJ33QXk4t_ zW^d-lys@wZ%;&TlGv5~xD z3&upTO#KM9$5MlykwO*z-sl?KkvP`dB#((|aEV;Kz&>=djtw?=*n2iz>IH?U@%*A()?T zw}dpDf()e8)KJ_EliedDO~U`_QjouVmhfZ$?Jt`ZPYIJ53yrp4B)dMGj`2!K++HI# z4!jQWK`{YZotiztvVYUF*5qGM!bU*Eu9hWNvmO_|mM*Zbn>V|QSb227gH!I*Ik>+3 z1DXz#sxXNYO^TptcPU*G;%;P7S2Cmf=Yfwb+dp<0~3=@PzXO>y}T(XuR z^@%kbuDf1}<{zeR9eVTK3FH58o1l6_vvCE2q$Jw-Zz#Of-fceaK}IOW2%q{-kn^>pu1j-f zl9IQ=&1^d_3dur4cI4QRr4!o=IKJ`G52B_+q2;jr=&)?1w9P{As4 z04ES+H%9TaN;^MKTC(!6!a4>mMzDgv?E!ZY6`j8K`8pU&p2pHuE1ucUHQeGu8D?OR z7X`;4^(7KDKN;EXpV#Q-JRQ^ZivnXXTznGz`V1GiTkw{0C8Bsycj!b< zU8Eh((0|IH72M@y4jtk;(AR#98MW;RvF;OlH1b0kGavAYo=gpJ>Vg|*%qi2&VR3TQ z*e<`s7_RMRuD9eRa9T)Is@}oN?f6rzuIC@@+lX_Rlr-EDa!GHE8bJZ&#RcY`mek9F zn)q|KWglFtJO9(gvhZ^-f3iMwWr+l~nAtf#WwbOcRug_JCip!CnAI*VpP13a4-fqd z`YM|&li9}cVB7~rJ}NTnVQP4e4cDls^OV%6d+oTkwjpahGM}VcW*SIpGSMuK4E{&J zp#baJ9NAerNV|TL;~$Ymk;kAHt%7U(P;sJOse^g42iV4?uQy1MpSA%lZl2q zy@Y10-YqNXSzf)u{=l}d*SI&y>m5RrRN6AogzZVOP~0a* zA;Yv+MqhjoFVG$i`5w!A3``!H$f^*V$dJ3_Voto0eR+pSr`#n{7#G;?-Y?I~HyVm# zP-!Pb64opsxJNYp@thN)nDAJY&bSrGIyDhmINz#3{1NKPXN>vz&OEbR= z#-TMzmP_IECy0P!0{TBt*8f68t|x%@j$R{2M)*)V{&kc-6wXx?Au--mygTg^YD6g? z$5%G=uXIUs4i+rrTtn~&F1dB^zEJ)9oXe0Q@ z1lj8sdYL<7VxKuiVxyHKE3->l@v+iO_|y)r20hU3dWmy9C>qeuF=M zNRX8m1~+Jv8#$`~m> zj79(b7G-3w7Mx4tcZI}lF<;;)hxUZRZ&!!y?wi9YRNy$wNpuzNSc>G52|7&bS#w_> z9UoPnpz8^GX7km&`8H!cq&*AlynbAF|C5FIvBwI$f?Z6=$#5oXEyT@mXZZY=HfGCn z8o-&Ad%FKV_s?_28f_|P%a$VK`gzuuHRORJwE3$d54`?bo;Wm&9Y8z=;gx zus_p_uvQD+LOXV|8k0Tw(C@i;zS|$`#|mW8;`9!;*9jc89SVjNqMNRcox6|nyxaq^ z&TaNGYKO~M`yY19IDQUj;4k(VdqtYc5~8*oyDEWJKzH{QP(xXs=tJc01QvT+TSGW% zTzL(fz_^xoL-=Sq7V`^;3nseRN1g3KENd?!BGomp7Q^`60irL245D75bOMfKZiunE z&sb@SD<}zy)2#Q6JY*jnJPXmSz1oi~(d8sbRGQDjb?FqBBK@7&W078a{6dpn{>_pt zoY~23U40z!fAsiD`qv&$t-ZnI23Nk$MA7}>FP2_pgMoB_CVXB}ay~8m$+9J682pj) z{&e+8($cfOgrUV!Ycj#r;!o3#+F*m6xU0NOPU9GAhj(QBd^Pj(yfdad#*#0W7{S1` zr8M2SF^xBzFs_j5Tm`IOXWj^CfjYFOb*bi(Exrv!H|hpqPqiI z@DSx>zK6dmb7*Sa%dOBJHu7CDQH=dUYJ<-=O>y|&hJpYJ6L{=B9_T6l$O@U3>8HBF zxl#hDhl$Xr-tNbo{B-D#Mq=y{Ow(fAX_6@z6@8r9yjM@xs}uI=8?kHo2|wUGgwDDU z^g_6^#{D*{B(vu>)&g4;Xmlo&nyqll9^3?&+>g17?oBC;ca&f=zY*w4%4l)L3FWpd z$i$=cGa^m>tY@<&EJ3)u>k5wO2xTKD`f;`JR8Y1!NnkFDsv|ivItg1>i-F;>vU}k>M*jiUUrALh zKK#_V@F4P7Kus^C5paC|%Zszo{#6O=|F;s@b!K?DvNE+0;tp^rwNUMFj}DxtYpyQd z(=5X}^0v(n4UX)27&r=iPg|=%Ky5iwq9a@&w|zI;HI*zH0@skHxv;7&nU!}F*fN%{ zQC0=NyL$1q>>iWc@oUUhk31lRutjWHxL|dDGLDxr`3+|(+V#Xyk4g^MFx0;0sd3Nn*}7f= zFryVM7atcWDNemw%7k_kb)YGUiPX4t*k~dufcfy#nO6HNtHzre1%9mTg0#r%isF%3 z*nZ`P9VynH$H9Rm6s~WZZ8>PS9^C`U!tcreM^?d{GGU|~LH?ib{^GrD<4Ugo)dK%5 z3HtrbdPY7J1zN~wfNk;>7x?px{AFjo^8K&ujOz&0)p4(3M|%s}bMc1+fGXCnjnr2%{BC%cRs0wQ<|tC771aAS^CY= zfWRQyD>Fu4Ff5DSNkA82F7|^NB$YU^cY>(K%$6}!79Kg>=dVmXHoCViUngT%P7(O6 zI`fsCO)g8%Rku$NC|1bL0+PYKD4eNfH``@%EW-h`B4g}bsQ#^aHKPH`Kd|YD_%S{H zc(CsCnXx%Iu# zYATxXO3V~xnmcf;dv#!w@^r0!;?zA@l$6cD8ym)HCwIpLoFb41WQhuPg!8WYN!2gI zD#=TkHRQOvD6<=H3|VS^6^jh+j7qdTw;yz_(y%I-*hoo_LsB1E(eqX=WocWx4t^@J zf-`$!SYQeKoAr?99;kpQ*ZW_%@AVK+kiOD$CcZex=l`SX?~Qlj+8gl}K9MrE7|wiE zlb@Gy1z}%rmS{zHB+yfPHk>M{QGruo)2+CSLd9DRI~ox#Kbd)imcx^C^X?VAHyA~T zH%4Wl1Hii(uFCR@Aq>LgNg=^;+`Kn%w88!!+SN=^kRmwUp>Xc3)6Y8=@g&ayIf!bU z6I09529tFW__AVBj*mP|h_o=^W7!>vKVu#)uEasZ!sIjsx}m-&E%p;IZ}r z2w0h+)-u8utw(~)8P03s&ayA%jm1>3Z3XmIBwGXML+bd0gC~yeaB^<_5jI=WV18G$ zAgi@qyu))3Q;%js8Su_e(3?c@GO2kv*Ck!ld$x-$As`d{WJM*H+C)(|=hXuSJRA?>ds?T7kn%ku|r23+Cm$2i6OwK0eh zn|2_NY29uXY{AGJy-Rb}HWZ8CFcpULdot#Ca>xsmYJBwd}U*YHODvVOP_At`^A#y9Agh__X`7J`ehRqc7 zA8~XiPsNjYYA9`|u~hU1`uDJXRnB1Sunog3NTs*^Tj|$ZY;$J-R~~cxlI+(_3D4_8 zs1>(L(lX$@>|ZKk30GRX8uK|a9@M6K6*d!(17OL$6B!Agxi8RxCHKPyo!u2(CAaNC zP5F$HzdpKDVOI@ORA|4aTQz)pC1mWnKhcaL>QRFiVZ$i$Oq3*ag*?rwHDu3+lT`!= zxTH#;v;CpDW%b8RlE&Sfqs$D}Ig!04>gcL^e&I>F1)`kSvcj`uKE`G7<{os9N_`3j zBkodXMwr`NaPY&n6|oWsq4G4JOAml2VNl#e0}0!O5h5Bb2sI6Q`9ZF30``u-cP4sd zuG1KMF`h0Tt^!5yb-rgw@dh4BMHnpjW(7wJp27xqhf260=!9`BpRt4MhLgI6ATMa{ zyxa*^{MbbZb)f_{_3r9`>^uLmfA0HWk&&b$lxsD+0@NjTm4B*$${74`_y4O3eD`F$)>1#J}jMhhLt}_BwW4C#eYE=sjHCu$B4Rhwl@Qoq%M_y5uoN z73ez%+mA8!NO-tt);>EgD(&^7fSE(QLEz{YcY!-8UKQE9=v|Xn>aj8PKhy&vFHEFd z(W=`KvmT%^iazE@a%kF`BK@pC!us^P zF7#{a_`semJj$Xp0l)H@C@S7QsJHB$mATvHuz!)bdiGexM16VvaOTKmT>?ytksJ`5?uyjh^AvMO48nJ!mTkVLJUp6ju`IRT% z7!RK($o9SlO*=g7jUNPVg#LvaZ*Vgv3ob*_)jm==V%}Cm$yvNdJGgRWNabh!sdjeW zt?T20iOPSg!}Ii{9x8Ft)hL2TPeZ0H^K<@+C{c5xRv{_L&n-1wytE2R;PeG&-}A6I zHZfg}@{e@Pcv%KN3ehak|LOS;WjhKULbLVB_8=P=dW2YXHUx0<bR4iMWd0w#(^skyOC-plY&cZFNRl1J9veM= zL44V);CN+*MqZP?JYzX#M0HTUUgn;=_Tm6{>|8BWuzoE0*fH}y#%k3K=Jx6ST+?o5 z&9qBd^b~0>i-&=EQs6Y~MY0`gJq4zz+j`IXfIa=UXW-kQ-HVrDm74WsZBw?*evIQc zRPaf^{Mrn4S=t*jBOlF*zZ_rCBtEBj?;jXR&q`- z_@1laMvq0@$`(?=1?sVI9KJP`qOo$eo+jk?Sn@VW!wj!4sy(|Q!~^6kVabS14#eqXfR0~&2AlY{6K97!O9soJn;iO2ACmqboaTSDXK>E`*bF(DiLA_rs$#nfJ< z=4f+TR~b;Vn=(k@UGO$zW`?vygo1NKoBw_RF*H<1Md-6<1V$QZby_i@PIb z%R}c6TC-h*c=e9=DhCLk{aQhcAsc_vfMcLF1>yD3EuPgEpyiKkc%@&>>&M~EgG+oH>sxawiwKZW!VM3pb?m>v*c?1`R6(h5?W3Bk*K63#>L|Zi zX+l0#@3qySS++y5-Jc}oxK?RRDdmxHVZ$DDu5mREw_8wZSa?Hl{)j_aUyC_Ari1u< ztR3YrN!?A&`@@w=YGutCyL79fAZ8P|o*5XahH?q^hEfyag(9;2w%qUyICbXEiG1`< z=+*`kogX4OLI&}A^)f>KI%PeY^z ztg@{~!|F<$&f#QrgaDC*M&G^OX^5Ba4Z{MmGug7?%(#~A(8CI_MI8~TfRcNpYC~@W zp6T2OUjX9vNLJTZ?Uc>gZ%m(_X*F)L-uojrJE5?{s$~E?ZPpbMe1J5Ov?6KRZ~az? z>=SFIgm3(zw)!P?rQn#FZo!p@zT;uIl7e^{g@gUc|C}qt+Gh_=3Qg)t_7Q z;DLm{{b|q~LJ|&}}KCUr?n`2SN4(HS)seFnDm|3z}@hV+T zvFUtIVKi<=mX!20Wsy9+dhtHv<6@wv?qwc#N$fx6W#$~+)(3a_tgvbDOqUmTy*J++ zvIr&>Lmsp7@8h<&lCbYiw8yl7=5eK7rOa}6ef2*LDb z0Xv5ONB<#!8T$8G0!$;1T}@kfe9m7lGjb+%Vv6f8=aRB2DK575TWpf1V~KpOH_ug- zgVPQJE$V5jVg%`GC7|$+zIEVM=bO&ZtLol*=#iy)VG9`wQc+!bLnAfnm=6k**=#_! z6~lEgZ?%1w1ZubL)D(^my_4I)`jI$u9h;TDa%?HraPPiQ&a$apSAeb_J@jqT)_WX- zfmR1p4Ih5CY!h}n3$(kjcKbaCY$bzW1Lq2_rq@YhiDo?<{8(=QXWL7VU?T?ZYOF(j zZ-b)8w~-BbmPYp-+pOM){jnsE;+RE7SBo6p&W4kWk7M-o0ncX7&`sbPZoKc@UA(@Y z=;}Z>l*97^qjx-N_DT*#$z)N9!NK@afRN6POALR=f|6{D4Tlu_-4msD2kyeoSr({7 z+;rQ)`w+=KP*El6Ros_!pB9Ofo(+XMG2FdRGN*lkh(Ye}m@xBCAQKe|8u%Zh*09ub zR&LA)bJVxZM-zmPdaZgm*Etx(2+yZ!W4-R5QO1T|Zn2oWu2cy)p68T=m>w`F5>b*m zBN@s3hKe~Gm6Ez93LKG!1vF#HvPgB<91u~P?%0_65@no!j4l$Q7@m@o1=@(Np*4Bg zTC(sxKK#wr5H}=g?FzwTmRnLvjk6gmu(f2k2as2S=xJTa0vJ)>wROgo~5_g9gjya~MRzE>q z{B)5t9AN&Cj!nI14VlglG4l8t=&J%#5eS3=D))-vwGynhkw+66w9LqN1b1X+SqPc8 zOBkZ@z##r)UFbT_f(Q)FJE%t`|JZr(8EI|pvdA$qJA3FBv2`$~VgnLk=Rc#%O|DAn z|HX~}xZ4c=?KFt9fHUh{NTdLLwY$lydEb#XB+#(GKbozcjBfW^^7lq-7^9($JB$;< zU_ezmZr2@EXt#~FtGpOo3b!xw({ty+*~R0qfu6hY1hl?)lS%i0c(C0x6wLab=k<*U z{u(>s>Vp-b7hhwVGfl8&<^8(X{#+g=S&IX)t-9*(H}#?dFnWL7=)Jg`v3FU&t+xd{ zG0nReLuI_A8&-~^ue1syjpCwn7rxJ$?bB3$R%`{mdA=Gz9q1!JAZs+$-uKrDS-J?< zYPeagSkK2u>Kbu(R?^0pXqQ2cB-BIQBV|Vgk>d)lhCKarGl5!uo=2r)91JY9U50u- zTjz4yXzjh*A7)@Wh9OuL#lKmpBG$3r{75(8DykL&rA=A*#ZLN3*L7{~>@ZYreVFi0 z)X9=4NB`Dp-r@sNq^JewyhTlP6{?d^rrhQF`B8o7(d2FK?XbPjj7Co)TyrF|Kb7w? zxlfZPYqmrgF?kC7cxw%T$RFeg_%oRa!#@}6KLDYf<2R9G^vKx_UlDJ%(UxFZ6)Jpc zQQLm1g!Y=gp+ea0EDl8tSUX-5O~T;T5)c(htNwAHCFF=^G;?t`U}jK3ayQAcvOWcn zxeFMnXxD`4*MFoR(fx{ok~DNLV5%YF)ZuvZ1HcoJSy8?dWwbb(@ao1Il9fw)_509= zT-V9Nk7WkOX!pscfTcDRY%2!;&^1cZI84iY?y(7otAApbHO%Tq_mZ&c!)Enijdg*Q zhU{h!xego!3Zvt_-0EsPO+-x0Q(W48mnT zrg1l}ZqSQ@LlGe|-Oy?1N-Y`Di8S@?oWfL3@(pso&rslET=n3*?h~Tgnc`4& z04MFFOP^RcDi?1(Rz^F|jzb#^p=(T_ZS8ToM5r*5x9nMsk=Abu8R5#&!tAWy1x5U1XL9}HVE8k_z^fnaiRtJ{s80?O8 zAppH^pw;J0Spgc~Ij9l4@=9eFt8LV_EA($Uc*X2_=)0&kpS1UK%|UjkjnQ{c@4Pkd zQti%6)Fp1gV1&{_AGqJFjRY3>8)kI)0cJULI#wdM8u;lON=c$t!5rL~q~uRlgB}O= z_a6c=KUi~S)Szx>khL1@uiTyunP0x#$!J_&pnRuq{}XhNl_DVpeUQ7KLqaUGA3=I3 z@w1<|&(|}D4FAc-DH?Q`gQ9iu3=trf%WJb*sjADk{%lQ zg89?CS)%#L-h55x?rCGCQP}&Gh;Kau!|+C;tk1%*`69^Q?sP-4Rmw3~a`ZqYtOoe> zaxu-scbi0*@%!II^X^IS(Hx1yXW@fJTM2!=@rS3Mo*McMPj^_Wd*d_n>C^_XOJOxspoFb^h$7VFq|QSe<( zP`2@sj5o0Np0y)YJ4Et|K^XdDf4N_S*F)X*miO7vC_u_AT#irBO8E`gR})vl<324O zzEiV3`XVQKcPwW3G!2?plq>7>h`U+YaOd&%1Ugf2x6@2ty`urJ<7@I>ASi}TKDhpa zkR{6BNgyYs?&~CQrfdlsMkm1^=A5_qj+P*bsfiMIsFN8q@cwLWNslZh=N;<|Z)2FW zYac-<#@o!UA?cW`qiL)|^>sjqT^&c0N`Wr*-u8|-Sj7jq)rSLJ&F6~S%kdJRRBzts zXzG|SiQru$;Pj@b`$Ihq#K<%CNw-_3A5@ep5{<*}9<7mY|Eu$y1}SF$ zq@02(x1Sn7feY_slZT?tD%%K~pI_a~bbuTaeC~*Qpo>oq-GZ5((HcX*K^AuPag_Zw zW>3b#qLUD?xtz3CO6>H!>N0WZmw8v!x&1Zja&Br)BsBlUo!DZRwa=ZExOxDjsJI_~ zsWT_sFXn@BAH$^Dz_Iwl?7dj8WtLG~@0ZWe*6il(C^!)6QI-$ut#H1Kv&&X3p;D(7 z{Sjvnwh+&lm7^KH8sZ-i!W`#m$C87bhh&`{4x%OzSJq(LW)SSz(tWVgcCJ=}S zi!AyZt#ucE|E9IoU?w@HxVsuj~;Gjd&OAXF3;d=n#QUuQX_a2|7HOCy+*ijjJC&xAn39#16M2 zxGk;0f-PHKfGFnrgP>$#9&f&{-%W5b1|5)F!)!Pg4E9mNiBS z-~a6N<}zsr$PH(9MP%!{oqm?c$df10yM;l5SnIHW5Ufy21f&(7%kQQtqJJ;&E9KUH;A21Y)0ed}Onw*XA?RCWi+0LMtA{o0>=lalD16NlB z&g$ZjZACP3j9%S5k8Vk)WdrvL z%b!T*Tth~1xw@zl8ikhVoL$nZdjt-}ggs&DTO&>H#FqM6pdJDkec|g&q;0yK-U}+; z@-^yo;%_qog`PDp$J@Eh`Gv0&QPCO`l#R{W&Qq9~u-CV@8Nq?c)VS%9AG#GL-HHz) zx7W+g)`c^y;0U`iAr0p%#M1#fOGB*?PQ3qgm7IseR+&{wht%qld0egp-kE^N1d?!O zfV%Z#h*zUe^>&aOOj2`sL0MsDwtoX*Znxsi8y04b6B_Cmsu5}(Q}`l&Cam7Dx;G2a z>XJtjH8{Ya_`uSvna1H`&W}S#TLB^L1cO{&)Ig*OzcWl@jbEa$Yi#mQg3UTe_TU zM7QFuPT+P(uQ9PiI{P^t{dZqXXqwdJW?Y80mtJ^fw@}aYiFbooDgT9mgfOG|VglMp zOC%9Fabg?7jT1Qa8eGiz!X`<^B_`Bi% z9V)Q9NT9=+yu(#_pIW8RweChWfJrcFflxs2H2;$VD9eRWd1Zuxe6924Yk>ffi{8Zw3b`G43U9G=As3c)*D9IinvNcDj~`4K_T-e$n$3_!UZ2VQ@=!|G1hCJZodo%fTFvYhoZYO_Qt&a<>YkK(F9D(vnsBG(IC!Tv3z!>N%W_^a%gErH_RAt1YeI4P=n7L?os%=-{-y zt#B{=s_uANFO3XP=iOCun0L2V1}&Z@jD?{(<_#xq+Ld!!vX}MEcCCftx7e;7PV|WdU+rM^gzdkO@`E^zD<`Mf3>2MaC zn^lA`*zWzcZ%uA-Szsax=W&>rK|Aq0rP!KzzUcvGK8RL%B1|&cH5abAr7TMZP{=N#>l@3yv99 zy>HUJ(dL+;3>XP=!R5KBr}d&ccpgE!@7~OHuCn&TJ7Z!dVtBQJ_7sT9I2Q}bJ+xM( zs;T(q9~*v{qy6mdc^Y*HJFZst?2K^Ls}OISv`|FZ4_9>z`Bu*H1+?hj(n|!0No^0| z9`N`f)Xcl}Cq{-E0o>yn09{P|G!h+ePU!f+urX^i7B1KCbM{f{5xMc!-wp|y%mp0K-sIi!_VWpG0Lo>x^=Uhs7W1{VD zKFoTS4sdr7zm56;d7Fz9&3T58uWi8E?&)jMc;|(W;{&#@s9@W5luTe@&9A#CZ!hxj zg%C-MlT|dn+cvg5X5>syhH^49x{aiGKC&zxLIV)ip8`dx@m$QVgs;(*mSVV(+o~J^ zP}FZ_7jp_0L11Ppd5Qn`jQ9*9!ap8;xhGG|i{v*9J&mWg1bGK0qrQGSmIGrB-fkGO zVjZn9l8|=?l+%R^RtL1eJ>9c}1$V}LVAn25PD5!bOWdy|*ayWv+C5I?2s<$Ln@ck3 zwD4lacUv-*=eByI0bYh|HHT3~h8Qy5miufN-}g|)BVJEy1+TrY>5o7R7} zg|Ay>0w_PXZ4n-V>#|Q?ytY!|FNVM4OZ2DB=g@^G+moI& z3GBH42v6`=W@)njwZWpf7E(ip@b7Y$81RYu=K_!yjQ)({`~V>~8N4a>H{p$lZ(^iw zis_!D3XC)sgt?*uIVpGYF-gRdk6KAk6Rv$c!dc(k5Zn$75q8-F(73CgBP+aQ$=O8j z=!jz2o^mcx{m@p&9}XxVafTs64%gk|Zl|`!-ukByEc|ji~&(jDmru_{jkq*d*p3Qp4oQ*6rD1{~Z zS21Q09)8lIn=Btu%(C7Z$Zj|lt0kf*?27$eJm&pyyB{;Z;P=uP0UPYVMwCcy&Bh5c zi-4W|VN#~#S@yTc8!5%>tk`uE0j@HOYm%h@;d)yRvhIt#F(S#w>xBk!s71_%exB@E zPSufXgIGyL(4NRWN!^yov_HzVBj{6BxUBh=WpWdwbzF#-dEKM4Ec~V|wO-pwV|dX& zcB@^$$@bNufqPVkW4jtVQ-xZjEf&xwxOy2?Dm6>ek>35va%M1Ao@P;-oDEL;qMis( zV3B=|+O&YvM&XXO<8hASog=i(l>YGGxpaXT!<$mtb5)ufbj02rB-=Nqrq(PaBNa69 z$_5}QFFlyK|9BwX`F~;l*3I8^uu(dVHhoilQKvoCyw_3I!433pYjX7QUGsIk(u_Z@ zr%Ga|mHFIErp6b6d<=TnitU{={0lwHikzRe8Vkc9?RkNckH;ye4|Ur)cF_jY$2rv> z!|w>~PjRp^KHUh-7qvK#MB)VdO*=!P=%3 z#I@Px^|`(+sOrtN7<}?5IjX*^?U;%ft=NdwQE*U#BvayU4N8l#Eg_aM$y(F$O%`O- zcqgv-3|jQa@(h|kUn)i(s&`_)N&xviWU5`Htoqa{x8UG0TJx6sd!CHL+caG1uvOWn zwlr$gq*^PTM$fvx5RTipYJmsUwR*45*@A6ccby~NpI*~Q6Hb!xF&clCb^-O)1z30U zAcrgfFyU}nelIYCjCj{0CmN3d_7U{+uM)Pv%Dyb8td;s*>Rj^&c>mI$*=2;-C<*c-JTnOy_0Y1qD zYTy!z$LepT0~iw%v${7uid<-dRlyklE69LQvJ`V~2%15C_4heCV13<_*PTxHe2gSUbo#DXAHR%C)f*Q0ZpQCGA- zylkP>=8+|>a9qTj)eG8Hdz#d1rtJ^U3D=sdz;oCQMQQuuRz+#I*f642$%iRpJ&X0y zkdODIjC{ZP`$9P(%&0)`i80#Gp&mhQXGhm`e1nVt>EfndZ-r{2)(}*>P_~}-=G_n4mj-& z3dZpU6QbI6rr!&}`NxKJy+n|I;k~@FVwf~89j_O)Un-eclMMcyU|judb_)?y*_tGn zak1_%5D!H2FJ*Czrq4qnuD7|j-SuXYnXS+8B+Tv#E>!0wrB#Z^f{9QmH?MrSkrC8_RP;Bd06|m*ObK)cx zx;c$`S13f>Ni&_&6|1U=X;sArLL76|dM|8^ZACjf@pn>N%WZV#7ZHVb5Gb5hN1X4A zVwG-UT)3sTsIR19o|r0!y})<-c2>Nb&Ps_a78Xxjn*|Sm+^wUrv z4`xZvj0r=GW1>U$l9Z*4SgLDW?l%v%fhIxUnW%F2;anc_SS);CnsOoDAle&8B(`*8 zYsZxsJd0hxvv8ks9m{IPm3xx5-!G~`_QJ`+iK@}vKly2tjj#7)kihT@yFGVnUd9?P zZkAHO$mTM4tjJn%>)R&64>pLK@-HaaO(v}S&1n}he zTT`?=?If)gVpb(L;%E;G0TZ~q4t_gy8p415buzf`H$E0aPpk~|N6od6LykBvk=` zSaMx!NPRK;=^+J}h)xiqpb}mmBUiK?^#udRO{8@lt2XR+e}Da;V%=EJSZ|vzYfCCE zpElpTak6|dM^nc5Be+Qw*u)w5@zWq8hVbnYJ*>v}%`OT`=qr4Qj0+=;hmqiCpAF?g z_AgHsAuk=DKh!G)C0VD>H*&{zyulr`JVKQj>7lz)1d?f!_(Btx+aVPXr>FI9zM3qY z$A>^HUgO*OYvVb0OzG~FK$x7Sbu=ciJm~AUJ5mT4`1zo21fZ&23$R>!Agsu|fHyx3 z%-hfxKpkM~*BpP!^Y~E3q=x3sJl|7M{*IiATbdNi5wafTg;s&IL(`g6@U`XKG!|zV zTQ2rcXlJ8v1BAjAWe)*iW{3o=Eb$HOry5D*3&g~%)I@Ha(+lk$X=c*Yibfh0vVVy4 z9VBJytp;L?e@W36XumRy2S1P_!l#l!iE23|D=#|CrTKnHF05CQ1s#fjSqZ4)cH|744;rqmfmOU7e5%rha2D~*Q`&YUe^ndV1z5z2!j}-?ehCX~C>ZLfn zN)C^6QB5B5isSd-fWc66KgbwV1m|S^_KN1cf1eq4PgIMKcpLLrmi)3f8Z4}<`6LEd zDl5O6Qlc2NMDIlZ;?fZ_yaM!AcbXv@E~9Q-{XqX_uS5?^QbIV8Gs8ATH~&*O?^Uri ziXI3@<=*%)*WT>WtDo>-z5^^ku-dVA%t??DNhV;kh)|l;hjbTWn}+!KeVi*|-+Ese zJYB}iE+#H5eo4X4R(>!QJn{cSpY2pPDDQ%JmR3;~94zh~lO^|7MzZ1z`k@m%XN7r6 z$h@N79cKXK^WL*vIFo-_A8biAQ8Srfn|jZk&BjR>B%P#CdEeb|kEbVtw<`` zR&J`nz6a1~sZB#?Tim_S&1K z{sqk+V;K{Hv=(!;>pigMAW7Sc7jd}nQ5fnE9qKxgV$(9|6L}ZrXzR1C&g-HEq4Twk z4v-IriEcbBLt9*)!Wf9(Kp`;}vRiW^?j-3?3&`8plcnY|D?~`3T}}w;Dy2YbjM47t z5EqT(N4!i*bIdmdconV{Q%6;8HZA~Cja@mSNd*fk0C1b;Vp0Fp(ais-qlvBPow!xn z2?PhbII%{NpxqxFP6EO(kWFd$FSQT588^YcfDtev0W~*Zwlg3eSml-|a4_VGOiog> zVXtEfA_52JPjH-vNt1ePCgd@y{Y)vElGLI7*d{ zgxHR?J^sz%yMIiJ@yiKvfMb#Kb?nw&x-r7lb(o9vwSt+nXzbf@rz)R^Qo!g=?D88L z>iZ+6F8uriC&;h3r7L0Uu?!|Ingj6+{ZwuIcn$X{&lCE)!~WYe9iWNVbW>Z}g0>=; z3;ws|c*s}%Sr%q~#H)7qNGW~qO6!XcLj%3WK;|2qv)({4v=MS|B#OpgF>$^m++DDjW@IZPjw>fHJtu zAzbPQ+<^H7LU^k_&;6>^0@rkMlDMZ&8y-dIcfzFBV7vk>%Bkm|uJ>85YWYt@sB@YDB-oY)qhO4QnPl8G8~$mpe1Q2$L|MD>z)sQNhZ z5~y2p{+xi&_v7k#WKMwY4LCFSbVu+B7Aez#Xk98{T7As`ifp%ySHjEuC zjS2aUVy|w*Yzo?oSX<8Ih}ZC&7x<5L^uKt~oVf-tji}X`3hFwt9sc+nLY75i9HWvA zNkSpl|0f9*;7ASEG1q%M8~U4E*bLxu`6NicrUVQb5W~>Xhr#O~FxCL_0kj2}4;GOD z5It0dS+Q^M%O@eenL*U?5=e>AqN4@bSg^CcSje}G4I@*K?rUZv*Fcz`KY3;~TG)q` zh>VZu36#HU*uP81W6?q7cQ^z<1aIsAbvo$i`TNWIu2aY9`yxIOO#PH*j=g=^-Q>7A z;l4~dcE1>d#q51)Kihcs+gNmU{UpGy_@NiIHbh=!j7WygG$EmfrGK{nDHjL>NJQV8 z2upR{+b9wi565@vz(`H$hAXT3*l8|t7S}Q&)x9!qP+*>hPv&ETc&^of}oM#x; zL_uw2mqpm?(^?s!X~MH*P1erdnmG;M*=_<<@_>0IU9U->N-R;13<#N1D&tdfg7zSt zGmLQQ9Z%W z`JUt@zGVf`w|c`7@LgSsMosaY!?n{agq-7Gar9jpQxzHN8^DRx;Cp*He%up1rJ;zx z5r=WEs>&0Vk@I~7+pR{M{K)64X?HX67}-b6p+D>Rxp~xDD)(8kw{>Jc>DYejvw-~v zEv9nC_`h;kN7YYdk-lVFKe7&P{dRME6fULglXCJr<>>cIwNv(p7h`IB2Wvyc_$H5) zAZ?btjF~?6CvR~EW*-d|^&sUCknKSNV(7q|(?lJz3Fy`QBqs0I94iT^Ck|+JqF8WU zcC3xED?c(hsb1gFEVL%hc+E2?uq*X5#ICfj)?VbRg|d4&P1*dMz`D^DSw$S4@Nj|; z{yFfAUnT7MO>g|k9Umh}4RXIb;g&W+{MVa^v*%8f)Y-V2+bBP;V@Jl0ZD_c5aZxVV zfpyLp-sU`hw0JRN30Xk4S_pN(8}TTsQxLSMtML|fyR>6`hZ^w8Q7%04b7vdW(>+5N zg{d=vD2AIEUbd*q7y#JgC<@ll>4NZ9o0-1*HKc%I+FT0!^5gvTgth!9SVC?VinR{k zbh>TS7$mT%B24$z*)QNW?6tsn@4m-h=ND)!97ml4Bjan|Ue%Hv3(KQ%j5N@1 z@{u6Mn5>ChWf)Y*|DT>H3kU~64b|E8(#T8qMAm0+YkB6wXkvED?pQgA&c5m=W=MBkBlqpEA*0ojRb80Hc!A){7o-$`ZMua2e#ai`x~h z1>1Ma(x-omYc}?>3Z7hbZPuT!1q9&pP#b&S`UvSET}RZT7#Y3)8=uQ?;cJ)>*^*)C z3-)y~5xOvt%KZEHo-AWUcmLnaJ}GO^r>_?lTlbP=5_Ia!U2a8epNH>1h!(2yj~EQQ@mb3g8?Xuryh`W>K<=<8wf!&yJx)M(H3iGqhP><43sZIhpVo;Wn^mEv+ zqG%WVlb#e@XOD5U*V$#WqDp))mZyVRkzH)lLhI2u;+&F`*Prb}`u*G=m${WQ^0!Od z6J)hGf!|*ROJEL@jhV??%PSTt?8c2M3`j3krysnic@8}t#17myU5ZGvb4{mZ9XeHh zp1v65oh$d4b#iM4_P;6LehR5$h5z3SIn37oOcS3xL5n|$%8j3k#5NerB1cdxxfnkN z-wRwiJOEvICv7cU^H#}Fgm<9ujZ@DqQQoa4AbcP?lsP z9O(U&$UZO709st&np&roSQ7ZWKU=g>+ODMb)pRqljdnUP>z^(QNk9MDCLjoan&^~H z;DM6Yl9!WF(IbBXu;?yAwR}KTmDHH1BAe6Am+@*No(F_pZ@fNxQfn$Mi@v`hjCUXh ze&|148#?z6|& zeXqZ@y^DMeTMGv55Bv^_|3nSK+mU8t_+0+ZHgoVz;OXJDvWR$B-?a~-r{Q_6GWN?l ztFX5o&cUttW5mQ}@Vj2`)5kS%Z_}^Ov7rH?C>?>nJG`O(4`|~O0&Pf+<|}gn@6ec} zdid!d!9{-5NcTd(_u_De9C!+;jBc$mR7kihF4!qd2G*Guex^D{l7D4JJAQ}ou%%xo zn(DP;i(ORr(K}K73mmvggjmQ?kZ{gVl)`fTjz>c|FD5~x*HmZ)uln03!CJ0Ai^brD znVk7b+?~4v7|oPjRBYe;B+~xH0%O?Qssf|3p+pNYmj)fN*p2Lk*AP_Q&m1I`|>9{*AyHGC#jD-Ir`ey0(iSr5@UP_lZS@zmx z2WOW~&@AEQ5{b$_a+j1B<5_?ja`Zsz@*r8Go^T!Dz;wts`V?FU4wH0eX$f|l|N9`v zRrE*!w03un_n?vSWlF+U`W=E5_V`fQ6|NeGQFx0X-F=Vt?Sq*_sed3+=(s4NOLTO; zhXwrT8<@iQ7V-RYo1>q3L0CL)WNcw26MZpi`c^wqvf7Be_fc~q53_#!opmOa{qCBB z+V%`-i`z_hn^)?sX3Y4aviULrpp^GUz%n=(ef7J?8Yj&JhUsD4lk*z)+ahEX|!6rqiWp++5wbLnO;dtn)mVA$Qcz^!&3_fNpv{mkv zQRM?7@X{{?V=OdyaUS;J{?86$lZ?J?>IM0xVi7<1mevpqwoiWrt~TJS(k+|DmAl4sw=#-+T@UPcKtUAwV(Yh1h8{zHJ$(Fu?8}SK=85M)B`OUUt;)bka3jMP4||2~lB$jtiEb zBXH2j8cMMDUbBvLcWm`g{+mzk`yaLR$5{w>$ogFrzsaAIukmg#!J-@5&v(1MRFHQ# z#_27e@Z0Yw=CL6bv2Y%CF#`Dtgn}{QQ_YPgVfymlEo~L#bG3`I(>~6% zJKuH7H=716dtN6LYl&{}k8{!0^Hx$mt~vU9A?Um9v!o?F+h9Jtjf)ze)WlB`piX?* zRZeus8w$eDrsewUBK)uPCEmExAr;!_JLNi$*N;sC=%+u}j&Djz7czy1=}8AMD33lA z9g;dC_E>1$8>&N$Y+*M1Vrb>`0c0TRL|OGb!lver!aOXimaYsW&G2}b=dfgQXhA2v zejSv$sHyb?;3ZvDlU+o0li7A7(*pP|D+~T>8FlI_F9E$*!>|NNrqJBMcS6NxVi;5k zFCwk33-Yxc*I2mLn7R*Bz>`jm&Hbu0_6 zWFAZxOh^YbL~IsbWJUfZ!nqOHvcvNV%w_kyiv$-ZSo<&o97$a^izoN5 zH^8umPOJD?vj-Mla`7%MJe%+CN;ZQNB61PmvtuHd#a^)X-p;;2>GyN3_yxq$65@b| zhveS%-qB>hk>I83Qcy^FRZzt46$L!_1!)>{7WJpAfW5?_RWN>bcy-WlM$dmI9Uxu9 zI_VcCDQ72nk&(eYpNxCW>T?*=LRN3_4+FEje%+Jj0?{Ck;MybVhKFb6(Y&iZ*4R9r z5++>x{v2#Zn-{z2(uXDbQwHIjm$PeK@t4+lXek;JJZ-8qs0%XGzW=(N*n2HKK}P&5m$opa zww@pl(1A-!QqC(VyYQFRpM{yJJUbyplUD8!^|k2D@(D@rjRE=9Opq@ke`;l+e>Kzu z*FtJbnxTH~_DlE8x5|24+$(^vRgr46zxfzTUBmw~?W|zITTy)a3Yk#0am#*#+RGi8 zPZHsI8{<^Z-_&jHRTeM$(DgtNYx)bW5lwNl-%G;7E<5BHuSv8SELw*MmsB0(IrS!t zRWHR}%UdW~6w1FW=Wzq)a2>^bQPXfyBk_bY0@k*Ef8WNc0(je?jlJgvPDHpmWq^(n zC>DZac4s1@D+k`aBOoBv&z1ue$a^c@InMG zJx&npfeyRhneLWwF*P(wN$|}&{q|J?QmftL=ndelh`Xg>x`(yQlsp3T{r2%%b~hhU zz%n7>WIcK5kOYjx{%-8!tv*nW`S0yP2trHe(cr(o@$wO*oZc)r-9pZzzOXbSfE+PZ(b0l{+>_REp-%W#amMHw^B_^=I-brlL zuJj96Y10L6Iz}u|BO%p}J}>~$N0*Zr6hUFnY66j2DwWds$@9KEiS!&f$$(N*HoEI! zc&XLjie(Qj0G5a2dYv#nOy5k?6z`uh8Q>wpE5y49P&x%N{A-((v?luK``}2;%i+AY z1u`e9NN5MqRPixu4J#8UjYm4ev>aZ{RHRz}(|@AWjGU7%D-l5C`EpFGgIyQ5c^Zn9 zS<*k_oE;vkG*t8L;1NcqzL0vQ{CsVzGD9usYl=YFw9Xe!jR_+--5YVbmlKH~wNE}j z_b!fA_T+5Y{LQF7wLh@y03#@ZyrqR7QvkHF<<~p+r_ed~3&cL84X8VwjNwNz4ZVxX zs@+=qPu6Rj*a`x9?7_8)NEdb7+lKSB&IUMtDNJw`N7;JnALD*O1ft0*!o(16Vy+1h z$3kaTam)g&p+)#`j;n}u>GT|ruxNHVPbz23JcP(u_P!5Kl>$jx+b45>Qy+Qp%`jeT20)6TE`*gv` z{oTN$7c0xdvS`g!>pRTG``(S1_3v}~kYgVKgyA^VwsXw<30*S z4<-RWfsAnfz!eCx+PBD7^xuJ}NadSH`lo9dhL7P{=NYMC6H8YElz=z%Abl8$@5T6m z;#lSS2`gsWSggk z6p7N*JHe2H>5REEQE3n%RI40{1)>}hh_D6JraFN#aA~Z31@LvA6urYs2Jn<$kbN%&HI znrU(D-zj0jmbg~Ob5J^g3vAjiTVdtc`juD^s2~4wBXuEG2yC^b`<}HOQY6#rL&IZi z2RTYtumT~))Nm!hf30nh>ZYjX|LN_J#>-ZV@)S2h(8%ZBRPts?jVLb6HfUxFUJhud zy#b|`^g8TyGy*5#DV{Vt%g(O@&yb{_@xg;FTl0&7FCi5DL{B&8NwDeU{CNM!`Gw0e z{>>Po#tx~ahSGcUdh>AUnW=2CS%I)c{kQ7C4m_22Fb`L7UkLZ#DO*dcH6CoS=id!^`BE!tg6T!azeU2}p0)5bxo|i9?A7-ZQh{9rT^UP*gqfKI zb*t(K?l%tXwdX<{U@(05?k*H*SD+_h;11AmQAv1?Hy0xLihAwt0Z%)pjsK38C0E<& zLh$_qC;{eRqd&}xk80ZMNb{@ryem&bB4rivh73%`&zmh=EtfZ}8%4jEHzs4~FDD(v zod`+&d!WeZ^t~svPs+o8bgwj{R()?uJ$GVtTAKXG(es zRG(X=Z?N<1hBGq)$%=q~#aJ7ck&^uSi?RF_I!2B1HuyI2x*=JEwfkrHGHHT<<%1zayB=5(9q zS^SprdzXR0g{J;vQ*?ART!t3uvF9i>$41S$B;PA0Ms_n($$0Qpx>#^A`==lwKYNnJ zNkD$S0i*#|26AdK@AmOUhZ~8MVKrX|EK3vU&sMnA2S~iz6+*w44uuRFhW5XE1v?O~ z#|;+`sj}{pUtb1O8@8@Hg-5e5}ea+H| z^S_X^s$*CkXOYS9@_EmU>x8)x)!2mI_?+1ndIurQK|0}i{22NsGS4;q2tyyn02S7V zD+0qH!N3-5VmrG9Ni#KSzGj8$`pr;c)v%V>v5vJ@ddiV zAxW9I9M5<1dNs{Pgj~M^Cz9_O;|A^0R@wV}b4|ZmS9P{uss?RZk-}x^U0cq!om|Uy z#RYxXZbXw-!Jec(zF0Jp0Kq%M!QTh|dYQqW(Xx8^r|G}78bTx=^3RD$jgcXtiJ8^decR?J0szNUvA8C;iTE@r2neuWeXi;Y0mwVw8WTCu&k zv$qWEr;J7vt*T$4COQIqKb<&5bAI?T(3qqsss+ zzqST-Fpkvw5>Qule$nX(#%YaY33S3}QyoJBsHq#aF2@2yDKyXTCkBYd_%hddHwcUp zK@w6QZZH5nA<>X@62VF5hccw2(n5ylPUgHkQv$e;CkB!~8KXm}I_a?F4}K=!<<+$} z-(Q8$$wrwZ)bO_pUuP|PxBH&2jh+78Z=foNnlo$vn>7fOFn=(5$(Hv}_Mqg6LLclI zVnDikJag!Vx4G*6Jp`YvdA|KSj-7ON`k>`F29bsV+zy>oj$d*WIfj;B8IhI6V+jgt zac;~#C{g|(QcJnR4$_o7+(Tj?LVorPAHO$5!M=2^IeR5ae7utD*uoeAQeBx$sU`aT zrBn@(G}G(OisWsGOPW#3mnoODl8Tweq(oj23#6^^y|!Dp1jDSr*kIAzo}vi@D#GyZ zyNC4vjU6vu^1VGgC@##Sqv6HP@xjU2m9{dJb-Z|{?Tz6JK6 zHIP8??j;+C<-M2KSdo<%A)W1+ZXhmf)54=lU?cDtw)rZ_Q9a0LmX6Z;3EDb)vP>Q= z%mLXUA{+W!=Tfb`w=?63ep>kTW@(M<8WkI^w}MT0JqD&ObGAv3uDn-E25tswoK1}p zy|wq^WlJYe3E3Kql8MSF(PZt;hoJoZhj%1#$yaIhA3~F+GS>t8K z8gGeHU`xh6kLxU#c40xx6KV4NAa0=>eXuJmfjpIKBnL;Ff_Xh)k_dKuOV~3cySf|l zBVqOnPCl>72Z~pODI~fbw2qp6XoT$2Umn`E2pd|@q4kyP#J@W(qjVbyU@3a4eyFKM zDX=H*&e>A3zcGca?9~bef$;nLvHq|NZJgr_{aGm<;9J z#c>UFWhNdfQkZ%393FE#`lspo-shb!;2s_wdYjO?!DiG-fdqP`n1*FfBVV<0> z^WM!!|KLV61gRv%$A>OX&x%}N2BP^cH@^d~kI-LoF{CI5Gwd*?j-oB9$TUpZr2W)z zFbu4RkC_wV2BVrpuFbZlsnLl4rrl5HW^?sA=|STNDq>ihcT`{d8J+HMLVrZ_&g*KVJp1SxB}wHb@|y-+QE|kNsId{7 zEEWZLNR}$gXyA8EZ65~S7ne4f-YT98h4}}ES_gG|q+*c0HxvOHn8Kn{{_H1uRZU9S z=Owx2*OB>zmR$z3u(D+n**8M4fE*`hdGX)?wV^{Wo3`m!bz1v4`Z~CLY@I%FOZ(+0 zfQKS$<_8+~TaZog7k96}UmI7cMt(_I;Y-#M$5oC{_vnuK@6+TUzEzSV0f2W-4l(u5)xP;+aM@D{ zQ8%zuWbZc#QH_`3+tq;2xPnti z&m|b-rgJ%pw;G^StRuxP6|kmfxqk#mFjnVo>S#U>U(k;_UVN*+3X zAxV6t>fb9VT5t`U?kdq=VsmYBJAE#7SW@|rUyUXt$_tnLoBNF#-8TFx-XDYP6*3hj+& zrr78u@phWLnyZAZ-vsryL^ecJxiQg7W=Z18V$J?s325N^p%7N*MiJH8W=CV8J9B5E z$5WNb5`OfAQoc5p_adapTcP-B;KK5eesCnMN2e=zuaxs#dNE!kw+KnCX{LiblXQ)2 z|L?l-B3VOkMn@kmF#yBl5Po^K*1VQx|QIaUHuD`@#Mru&J=)Wv{lmnkmtN0T`g5dOY3D z3MH$dM-L_zA~C#X{5wN)b579-CbZCBt9EpPPVp(<&;>OO%w1d$1Z{A-qiUWqtW2i+ zE;|%|I$-LJyfiF1;_$q6PZwO)4sM{dx12sz;SA}uV7Uy$g%NG$alb` z$2;-g@kTjJBQ#CCB6FP+Mg-fngmo}nm~&L07D>ecLZGc6J)J@Iyq@du{nr=19#Qx^ zmXw6v1E%P3zmY(Y4}O3wzKoiDVY%Dx1qTpmFekGRL3UAHj?Zx0YVLT^iB@#N}A?ODSyUJfJ1dRD8LFcvi^^YImV!>o7} zArtYkvhd|rQNnS3o>h?j^CRt}8p?lmnx*s6*e_YBluG$O(Xrj?%B{^JH`_GNPh-mWkWKZ|ZZ@f6(&i zU}}Eq5S8pScyZXrRUgw-?;z`OTB9|kj?lDLn4@E#l?5Z(9=E^Du?p6>yE>w?l#yW* z0JVez+!)4TAy?K#5UN0V@bpozyg!maQG&^5hZI#kBY`8yd1vLa5=4aRAvxr8KFRf- zp-LFYHEyNe`_e>A9f~>HCh8~3Bl?pBQS7~UiE;J?CyQkPGpRpkt< zkUwe`4J3giKypi0W!@C7jF#XO#jKEx8RuMFZ=mPVQ-@tGy-B6-jh+zo6HdPU(caok zUVButr^E);__hArqzCa44s+Gx3bT@2Qg3lnT4oFVb9CffQB}}_2jfZBR-7K?@sxt< z!!n8=P`>NR?@vR^1ha)b`HLu$Q&k%Jp65c6_5eCWeEx->o)-0)O*Z8mjMEBr@89ICE;X9ZipJ%%TA%EzT zrNj|t#->0d*l~<$Xe%?04L%eq^-PH^6H&_B5Nb*hF+k~06bAifqNdjxB$u_Ec)f;A zOhf@Irr81VS}?bK^VbRC6^!akhdKvs<(`x@ji{zr+j>0s2l>m^$GDfh|M}LzHcHB~ z=1$`UFuZxd;=K5to+_0D5fp_^Ur<`BIJgkRzu^}-@`sp4=1@?ZdGZIE6N7aJ$dN} zhl7y$z-I`0M{4qK@TTdDd5IGi!}R160J^Xd zy5WE5#aRVs?3gK0{d|Vl`oHadG&l+|89Ke)`Zr-c;A-VndK=Te#*iV1U$M-8;WY$^5!o4*LB{8jGHroH+Uk!9ufXh_e=@3o`nV@^_G~_Z5cYm>U@M}2&tmrr zY(>E%+eU>?e0k-+QT(XoI21C9Q$Hxjw8iBsx+_tKF*cK80f>;F%_MZBA~nkKf?ycl zdRW<-M|)ELTukyyemc$~@q0+~c(h4$9#a@quVcjotX%NmRTUxz6jn;6drTe|>j~7D zKH{EBoyo0l+lsZYV3Fei=X4RXAxPGUb9nvVI6|Oo;ll^cE+hT};JQV}t=gR4dQSwu znEELwzkc9bqVsw2WR0DW*>1o!YDJ(A*LMOu=Ug04XjW}6Y-JM3{duSDd?9eyN9Q|$ zj_$nAePL#wbam}>pK3b5vX@$v{iez5j932wnLqp1#%mTYXGG8=DS zmugqjmb}6i<^13wRzb)KNS+{tQce)Z3MKpEg~7zU0^e%%ik2n*3XPIp)eqEJ*;8(w z|66=oz+}eQESKsfHK#8#`UPYIIzlx4w_tP$hW>CZxGb_@QB6)_EjqOZI2orJ;MM|=_hwjdRX&Sto9kAh;u8R?BDnHzg1YUB&i(|O?u2QMA^(n8|5zWd9v-We}%*T z_ASwC2!yp-2p@Q^e}80Gb67c#wQlH*2L1i4tm(AiRsHj}NA^^W&yM_1mSE0ttk4cO zPs}gU+^r{4xXLrKVUTTkBzkzVCUXm!ER1)U-tfXzF7g0?02ra-ESGcj-|zOX%W3}tzZ-`*_TTsjA7zAI|x+-N!^_ZKjQjTx~Ca!osu=*ID0 z4LTT$v8z>H*{3q8EJBrB5Jjg;e$=ewRWL+>J(g$t!JeT<>(*A`###_aumnScpudG&)E=Lu6gL2$+Gym}7JAuds%=lYK3QOA3Q?H4n zkPF47#6^B=P!`;CiOi~6*)nQZ#y8vVaHme96kp;ODgG$^`QSNsu3AHf_%+9!(8)qK zDp*H}eBhMRY68+Wo+8bL0giM~{w#>cRV9Q>1uV|}`(mo|yq8|%zUS0IMZA7&Y^-}V zj3`|mEGIQQE~O*7Cp}b@+P9E~RmQ*=9xKcq7Uoz<>9i=M^Sm{vn%s_lo1gkX03bjo zqv*=iR$1n(i9iE^$ zFF2g&a{7TLgvi#gILn{B(V%hG;;$bITD`CI6_Xf$ms#)l^Isw*f$iux~wU&O+DHW->MIE1HY z$6=on5d}8Tmdp16t8Tors&SdxWwH~h_>-m^7L$%cAO1h4&M7*wfLYtIZQHh;iJeSr zcC3kQb7ChGO>En?Z9ADbnfyK9ch36H+BdoE?yRa^_105w(X7>>r^fl=G%J1vU1Ut4 zmsIvWMR6i&q6bqU;D+gbzkVJA@YG0(EoV6lgsm71-=uWFC|r`lV!*>924k%t1|>I4 zhuqJrYCVrv)V>ZpJ5i3C_!W?{RUsDb`0+zmW10g7#*GIGDDZpW>w%8zn1pjhw2efc zD1(jmh*`j$x%xaG1lInL6gBx-4ZEs{z2~hcXK7qFXbM7C42&9G=nVI5(Bd}yA=P>n z^3_HWmGP!z4|^C;Y)6aD{RqDOz%HN@C$I)2Cp0GJ8mJl-BNj3M@0!4xHyBoeXLiT# zexenM#!{huo@4m7@{Lkl2Xl&&E-?vSOy%0SROgRK6~q8U9Ohg12(V~9etT&f`06O)EqgrA%Xe3=={xoNeJU9>z@lC?LKct3#$K zA6OXw#W|O7hK9<1ePi^WY;T#Bx}N*n-52x1K#KI!5fFGA0<|f8_8PKc7!nTe5|es9 zAd8rDIQyx!zf(JDvM8VzbyOauB*Zo5AraM|qPlNyH>5!Oy>)8kXhEJNEhj)w1*ht88D(_LyAELM}{3bg%Fm|dG zU}q775SISY5Luy;A)?chJH~6|2L&|z(3v@l#C+Q>Sl=iHoofewN>Lk;tO;}7ja%&$+l zWno|rXRCzP<5VEV^cX=8+;Ex4`hi(&t`(1r#`pnOWQDKv^91~%&G0Y61#4}UK#jgm_RHk1n& zN;yy5sfqb=t#1MX!zILdj`eavz90@MxLX{sClq>w)Vuk-l{5hSN`C;I?OzbhA<~M| z8Lj{`25-CdV|FYS2wjQF^V{`7iSm9ax#5>kgu6ptsmSu@^UaSE-^*!WY{o&m^y0Rx zX69Y;icjHL&*$TogPF{;8GXSX@UplcU>o+0hf9fT|5ZHT@m4Gp%XxN$9pU>9c{iuo z7coe&N*vsMs`YrNmhk?!IzC38neh-L;F`h(pa88^`KUIUAe|E-FO=-x({N4oN{wAH!u)GKSPTm{f z-tX>+*pAID-T@1S+(F8xKWeLSqn1~Y+9OVNd=d9Mjccn%5RUHTG~|-7ijHPZF>vLJSx!%gXW2IU6hBd3Ugfrgn!7ghIL^+!UFzDQW>P%*H{ehd(78G9k45FE#-f}%{r z%Uu2N8Jr4NS3F3*?ag~2NR{u!2w0xs>kIJyi%4H^74p&b@! z*sM9;OYLVjUh&%0h%-kK4Lohzgt3&TtF_FUX6ssflzk+k z>YW^OKfv<#dm824^|EZLswp7OTkK?;b%;*MMr9~oNp2!DD)Pb_O4IbJPZI@A8Tth6 zP<93AePxG%c~zSrfT}u{DB8qW=Yn-n_lz*pReGEnuYSCav`1Mml+eb?DvU6$-h{Ry zT^TR5EEqjTY~1aMLzJtIzT|L~s9~SJEUE$jLa@}GnnK@hJ{E>hu|u*$L1wgXhr}av z;=wRC@0*u{x_>H&DY9#+%7;VHva6V-m<&yZ zQ=kPDg~%nkfC3I--4`kUBM1XEO#O4j_Yy`aF12e|m#M^4pqLEDbFvn}HW7AH@S6f35JZ)?9}3o1cYujU%VmOVj9 z1i^|FWA=iS8{IeCngvjB5DMSPU3-lhiIg_^i<9bU`=i+;NAc-n%u;NqnCkzmuEr3v z*bCU2Y^NwzC&r58u6@%Ls0hX?wo|oPUN?CUi@!&xDAxjIM2cqhyN2p zyE`Yqca}!iOl5Po<9DTsgKA`=gnR_+$W540#2|O@KF)GwCzgs$DWfQEV<)?Xf)#Xb z5j|_R8kXk$lo^uei@e=@cgb5sNMi=ibHP!c2XPq0`0w1L-{F9^c?6C4Tqvq>!T}FM zS?(gsMOMn4#`w?$i9|Y0m9)#;yuay_ZJ7feZ)O7en=oue$V?N0Wl@u|E%qQQ#GH8#*9ND^hhCw>Kx2AkVhW)bgJ+@jqU59I2yHDl^V=9F1f%M@9QCT`;Pu6&z|1KIOrGSC1Jkd& z@{eWjwiC?ctj=X!KxeU8YGWMBa%-Fb zJET9?Rff@vM2jWnkNx)`LJl0l%J`o$|H4!{7Ic#Rr+Rfp>*zdrJyBtEuhDvEk;>m& z2DWUdSyghyhc){NbOk#UkmwRN_8S${Tk^?YE1!&*lZA@>Dsmi-8TQzywFYtb zqzQ^4K{Q38!`)hPk*DI<^7JzwKEbpe7b$3_rzwY8(R6qz2o~|Z1e_R6nLRzgY+tRB zcK+0fSi!pZ;Sp~>UO{Y})P$HncOezG>DJ0(47y2&+s|x8Y_Nt7v`6mry`>3>X}8@W zL`R1nr{#h$PG8QL^J4@0bT;1_EbX|&FYw96N2hckDTo%Z=c|Pw`|*cGJdIgHNSGj- zQf#M!A5DeBbJYBgNq7@UMH7oUqte;^`z{J;QSc_#+RY(&h z+{7+PfmLz%;cM+3Bd|9V7zP6mri6>P;pAeM3sV~^vKlH`^y1yJpuD6{s0TGQZ(p{g55q4Rku^~;!!U(TwJr6A7&bdOelu~w>5MtX$v@L_RpO1UKv^vD zLG}X2u-|KHQ?jv;!=^U5h-Gw}Wm2Tuf!QMUpIGZpk0XhzTGnIIRwMrixE|Uph$TUH zBQ6EgO??m;e*7(l5nLdNam*QZ%MO!RvKir+lD5Dl16y)G@-vr+p_xs6j?4yxr9$~% z$$Bzk?wvw}jUc}Ln>d`vbP;dMy-4nqs2{Vu1!6W`;S@NJc_9b_pN{LqWezlN2b8hv zW1p>o{kqS~KHKJ`Krob_XQn2FCR`5_$u!U)Bbm;LJ<*K@plw5*REqrO6ECH>i znIO|}U+e{Ma>{d=HI}M7{`Jcz{o`pBPwVbkLTvr{RB4Co`E3tu`sqz-7r~L}-?K_i z^cxuz;QLMt%Ub7zf=&o6_xI(B_)*u+XYWM{(Ph_l8Jv$M7@^W9$!Ld6xyv<)#2woi zUi8Q#cs}1$_mpqnMEW$_e!auw5Z z^7O*r8cF~~MPlk<%RsGus`Pd;<{;3H=Peg#sZ^69VtXiwx5I7<-eikl912uA`*O;owK(Ez2+|CI%Z@-AQ z(?boFlLMDHR!^tn=>%&Q`-;=-|43df6vACl;zXmQVPY_yU3Xij*S6$=|G*8L(>Nys zT5|Cbz zqz^f;wRc-Gt@A4u+2mG7QvEB^(sF@xC+JRE?1aaTx|5&6A27kaXr%95w6)ivfP%_a zX|o8mzPTTdA)r#?!?$}V+aySu1N!jV%-s+Y6Ii0s@&pulzFgJA-U2f#8z+Au=x=bC zkdL*=`(Uy9BslDlaN~ub=E}+vg83Tq{4+Ie7P^h?eMC_4(BbEDI*ds+lq00AfgP-@ z(Lyq9?b*^7YpxZ&xQ%$q*f_H4I6^sXd597z({4=mRUF^ZKWPX`Fr)yQ#pNRj**I)7 z4*6;EZtn0d^FwOpA97)NaE{vQCsKXl(QQ4(!BjQ)s&C;usnd8&O||nFQmi}-Gv_J> z@|dA5Ale78{UD?g0=Cyf;@`b~SkAJ8o9J+lEO8}~REjb{>!)E}1*z&4=W1j!zt~}7 z0PkSVXICjVgO8vJM_{|y5oh(C3kJIoW*ONgWdk%s@GGHS51YU!?OPJ97@l9H?rdx> z`Q{ionP5UY_u|(71aL3Is=sBpNNuT=@$g*TJhVT1(JbGJz{JJ^;#2BkE#%^l{|$X1 z!Gd|=#UGp#=fuVOizWmUL0$vyu8j*rJ|Yo8J@iJc1sIgL_LFSf*77l4` z$iv-f`k{(tCzR$n@we}W$7~YRgiFx-Ud?@JS9f7*#1ZQC=G3+WggUZ6oS<<+3FULk z#cqo$Lk`|VTrNtI(J@@>X5T2~);(#`L24o4I&7a%#;tsrQ8%|XS?J^}PmzQG%sSZv&^P}?!JREn0~Q&6+V z!|bGwjlr+iW_!L4{UtLN`GhZ$$lD&p*}fc&Jx2^kr{e_AjFqo9Wh{G@=&q;g0B3lD!`9?yehwDmluE0ATE$!3%VjBQq^IcXD37M*CBYJ9aEkoe%^qr;Zs6+;9zCDCPXp%j}&n1cgO$!ogS(JO(( zu0xa)J%~)HQ`v*a)PLFtFM@Mofg)vOH3tG~p;T1SsqO>ljMG+9{% z1T1Eol?Os;qo+~4V@P)ijElS^gF~7Tsks|_h zri$p_dGlJYKfx~n#%(&E@@E$I1*Uwip-)PJkrlrr`lI--13ubXt(lvMcLb1?q=O<3 zEl}1<5GnrL#5*h#(|9%81uNwF!DShG<2s%31%zL*hj8W2H~vO8+qE}!4*rcyh%iPw7ta`Q6E}`uk&~z9^{e@>_fo6cpqTbA) zS%xR5VOeX9v*V$TpocAD7p~iR{5bheL_QuWbtlON{`)Qiiiwxh=_e;G4X8%XaG%Q#{;dtc7=e>hyUS7vP zfnz9G$NGPfx~!qh>=;6m-nqk0!d!Q3wlmlm@tFwfVA{>U^-k7>i?x~`7_eI-)42hA zxF2r7%I$ZqYfiC-* z9k9qP9W~g>FGsFQFIxh88*LE%ye>>)EVnQ_3`e&c8|9x}sCa~1$m?GK5{E09kB1r$ z1kGPs;<1HTxA)7jaaZtDGdvinn;!)GyHQx>eU(Wevx5K2K20RsxCXmRfE-Rt`N)=s)G>eGVI~Pz^Lq2VbJ@h?LM&@8T(Gk=u z8^j9_6J45UO*JluqL1D%%|!kEeeXA)jqMN06YS47KC(<%(g%Q; zE^us+3?0Taauee9vp*HyYAWv#(MC~Kn{RLLE_C;~ztt^=* zsZ3@vS8brs#Id-cF#i?uL>y?J+l+`#GoYKYq^M^F(fzxmZ z2|BvC<4c35+_(q1mH1-ud1b1XAdi@m4um39^*r*>2>y`3Z|;A}fH$5VN02`1Jr zj=;{5LZduHrKeF+-VC3-3tKOy?x9ywhpUbjdotX9lS|BS%INDO(@()&FMz8hb1^RRe!xstlHE zM#sbZ;=keoGhb`>8U}nSc8hZaFq90GZXb*7Rf0|SO-OYG>;-00m+Xv^)Apv1uTuyaRaDMZ2F`D$&ogiPVv*sjHkkh4mXL+*z%YfRS8-BJUbUZ-7rL+&_*??$N+~! z0kF>0xH&ZxRX2n6r zzz0R9sw4J2n#1SL2>Mc|1F8jigUH85DHYvVr>SLP(495ElGXP0Sa+1bmAP9luxCr9 z7uUXN&GUo51a=7jhta`Hi}A^#gOjTP!moi{9&<3>`NF#47ff7v#03cIkkL0QY)8qz zPEz{!|5D*U#;};E?q==2MZ>Iav$zJ{nUGTx92eOj6nVA-x9H~; zPznszu7&kCrQHfaUOY3zsTNV{bzv04)nViasaBb*OYryun!t7|9U*IF#uDYo`MTsD z7y1)1>$O<8DzD09OPwyg$Hk3v8vaM%>q_?S*NE?-$z98LibBX`tUR_}Or z^%}IzDU#fJ44e-q?7cX&>^<8>p#qD);w%fR>J_$MieBCymuxie`lFu`T=cf0*TTm< z&;)r_6F2Ls3M}i61I6Ociy5wYQ9+pjn_o_Q!UZNcIDIFG&MGEd9zz}-0{8Nm)#Cw9 zZX~ElIkQUirIKzu{25fa5=+v4zc)LO&3sQUXPbEIf}zd(bFa1 z2hcS;9;>E-8ax*-bKrXEp+1fMxPm=%;Z+mV)R^-EMNR&`0;xH5LJ&If3FK*PDO*Z)m;!EC1n-|VsFqaYRnkH)ourJ~r*-Lr`N14TYD z6Lu`oh$x_bKBJuzmE}jF*e@YLj|KFdLct<^H!zN2Mxt-VOL>yp0D(ATrV!tr=g%dqqP>3BehoZ+@RbBV26<={6#Pl3A2q%MNbJ} zlM@;2nXxvJOOR#>hW~}DM>i$!tt(NBhYPY7cMtD(eN?S+K1|c-oP^-wV~4oE$Pu_E zXawceg2I=poH-zs%giG0rA=9@9GCaDC-PaVw_81@iCeu6E^r*fMC%zsul9EWY(@3S z1G;%5%xc#?f~^<|bvcXZ9dXa=gTz7=CPh7DbiPYh{dz5;1Gdi$@`>&b_#G?%_^xE= z&H#4nW}xm-&Y>h-aD01Akf?SUBZ}N|7Gb@OhK4+RMeY4KXg8mAkCj${F*pP*YssRn z+R6o=y2XkCK7HuU^(HF#xQnp1d%`v(R~{p< zke=UG*Yy-0rz9k^lpttKcG&&K=76Zg6(%Vm{&pH(V7WxM$_nl;_kO^=_J)3Cs?|6W z^t^|jI~Oh=rnVqEYlzg^uWf_o3asvO{!`ukOKBPZ@XKGp%9R<}pZO;qxzkIJG7HvG zrSO4mik5z8SZ;gsWkD#mozsJjHjr1624Jj=cn9K-&ZCAIh;1*o-WQrb3J7jcaW>HI z_vousV*>#mzIGmt?$Sb6MiCbg6}b`;yVmy|Bo7(S#wQHz%~xzI3|8XB%XC@lylhq- z^i5K92)Th(ocSY2qZq5vS%iKq1$FL z!*CsV57(luYs@i%^zWB)a=gyPy54Ji^3d!uSQ4vEEgTH*C`m)e(LrV-2HvaUHI%pP zP81Ll<4=MD@OwURHMSqnLPn@X))UzCUn70hd)EYypSccHC~MyJ_6y4V2izG0YPVp-{f$|XItoTSg@vhrN0Q*y*J^dM~c`_b9g}7@urw1Fy! zk48&;0>3E%rjD~t43Ax}wP`izDHRFzKsz3Xf!O#@s2acIiFNCQRjJub$+jX~V3!ika zeV86ZtNOjTkJ#sG1s*Dhmp4THxrKk=f=Zv10`8D6drmNJ%oYBD>BJ)cr`aW}1}m}$ zZI$_!g-^32owM?c_k;+s!*;6x$qH|Ld+9t}9&iZin}0C-1K8H0+IxQonJ|0-so%*9 z<2J6xqcepUxy8rSKnGI+GUNS$`uVuqqm(XocFkVO*7+bizqVe6^j5iig)jj2A|A0b z1nK!yy747tD9m7}5qYfpCJ{H8ib#Qv=sg2hG2KH;^k$EmSLj?)p30p5&#SV672Um^{8jjsJe9xH&4(lG%6?{)YNLl$?c~d9Sjgn^pAgVGLPwI!`cw%fD~`sW*Xt1 zcuwCZT20&g6tSdZT9F4OXYE-3<>i$D!o(H8hBYs+WI{gw1ab>2gXz*UNUN(eQxT(s zPBSU$@HQI%vo+-FjMUwd2kIC&!Dm;om^wit(@+sZ?toPdFyVIzLnAPewK*>y#`Hjchqc~$p@TVXt&3*qP6zR9VkLnMICp=*aw!f1 zq_pu5y+SJc6>c-(@;P&>hbNppRK=wQs*yPaKb7^?8c4f0RT1!q6+)|sXZln4qY+cF z>gr?)HHvEhKe4aJUGfDNTgN+1O2h`wjaDzQv6h51RQyv z)r*PWoBr5N>H^0VOvF51!JE6Y;ad!)Nq zXt<>R0Bz7f0XikWAl%74Hi;dvu!xbX!m_QvrjPS}#Bb)wO8v<$f1^K(v=0RZ#2X;0 z5IAySf=>$tCXCXYzUBf^z_TqY>ri%wN=JVQ5IZN1YR6@RD#vCV@ab{qW)Zy}`vkr} z4BPuZu7VFpZ{8t&rcSslWgzkyIr)LhbMjsN8O?Q3b>{J;fpE2mDK8%1b1&UYV}t@T z3IC^v+PQ%T1w5rjMpF?%RAr865#1&=VA}>3LVu2jDs3)er2>xo;FGwW{m#=r^OaV< z^#qLPcq|aS*c*UCTbcX&@eF=DYY`6Ji#x&UV@GcwEf9r?X>VqP3S~Trx-r7Zfxm7|V zw3V{$*tkzRmy@O6?Um~eHz!y_xMf}E6Is*A31HWmtNPtafH(N&E6MjavJeLx;*JRM z7dhqp%r&vPU75BnkbSbF7hGUwqODl00sB#bRQ1~u-vN01L@uD@F%?6$-c%S-ZM~F&g9~w+?lw6=EE0A7_$(LtiYmQ8r)9+BB@CCsB2=u+k8B`E1eK4%ji* z*$_by($S6bz#BNBuaasCJc9g2(PkhA24!Yjr~@2h@*pqHg0C>HF_|upPLLEWa=Trz z;<6xlazv+ddm9-+$BcIhU~1}pLOpS22(w{9NklD|FJ7R+Ltmt{%~6iHAH`f&O)ooQPE(Tl7=>}^;SEpHO#T1T5xXz zgAix05DMJB`OW^nIf-w9@JU7-47g?nYr#alu17s-#`J?P)ijcT%Wi@t*zt@5xI%2{ z{Tzz2Lz&Y{`Vd=lE79SQpWWj8bQ>}MvJSXD48x_FGj^ zR^Xk3YJe`xsL`-0?P2Ww4@~^>nN2(sh8hhe`)ko1g^lSdM_s!S&XcyFjAhInV49fS zuf1H(G2@yzeeQE~M~RN44tNAuQB?0m&v+SCoX)Py3Oc(ACy+jy5&|MSM-a?hCh!lV z93?F!I|&uZt}QrJcBl?0AcNT#DvL@f7)5U{CXi<7iWH<@^8Htb*$ddTEbVTkgQj%H zqb)3tHwgH&qnv^d&A|OHeN|wa2|ORo=12uqf;nwq>C;cgFk)pW)5a!}^YK>=@8z(x z#!c7y=ZLk-;_$z{wg$Z(W{B*^9xtj%UA>q<{XR|NhE32Fp+=DH4;OUoPoZvN+o9dG zznWi@ukdj;2+Uh+3l`je3LTijy$y9HUT5;IVS%C(m+QAn|GDQ{4+Yq4H!Uy$wpMx< z0UNB99am4hP1IV*1MQZX&>4%X!2lY)&x3^lQdvmJ9RHJ<+r8(PeCYU^+zUwPXUjpC z+QVkRSHSCp+b*3o>h1Ve7osGKfxbme<#K~B#iY-xvY?Vps_coTkRJR%u6AIc2dm?j zcGceE->Hm~8L;W*Ynni4Lwz{InJt70M;pbM;-JP7iK%vWb%BI^75x>iZ>;!4|f1u@IS z38vX?16@k2;`zvTKQI&B!v6P{`YVpImPxadf&&p0HQ_wOb@wmwLGE5b+B>agF5+sh zTg}mREimA4r3qgLl?CqoMnBCxpzxgnyo#ooyaz&2s0ep6D$il}ZYZ0;0}h-KyP6?r zYJ=8@0iwMzGQ{+a>YzO2ibr}nwSYv_+FvXfCjDNT*yFP5NCc;cfwn)NJ>bb|k7ffx z(qLf>0&D#qBR$RS-er6Lsr<_ZPHAg?s=B(R5xCs4Ev1WZ$WvZA=d^;2?5GU2UEuMI zd@c!sf7+9n;B3vUMzfpZ(J6@+na=^b`&jon7A~G7-qH69^(6p!nb~=ElcC5N%;;RJ zh3)$)26#2Z+|`R=0R6$ zNPL4Yu$^H>Wh^hdrYmC|r`7Jo#^GuT(-tW@+#%q2p8dZav0gMNfTQv81)GAiMeFFP zQ$&ETrUa6X^`S8Nj-qds^~`G4|K4{HY{Kv_M%1#yzB3aGNUp%%h4>8OWCi@)2+cZG z!!I#2Oa83I2Plz&Cr0Cwc?K=+J${(JG*%eAQWaAmh1%Kc9A=ReE2>&luhP11b}k|H zW9=qQ2uz}ttmqiwoPU_iO{r9&p^}XXN5S0Q&(ytR5D1UogFt4_8PCwCoWLBj{768% zJ$lp|d5Eh;FTaJ!FW|M2(Ja%&A?4tM;uGeEB5rp-ZW(PD?gGCs#52_x!}R9CjFnZi z;`Rz9&#%he6EKu&wnePE>B8P$x44Bz!!PYb=B?=r*}%W~bqs?AdiI*^YNeoBz1YCl zVMk^WBVJi9|RqiVotPrtWmLI%xZ|l97Y$HFSj;x1I zF2W1!aE&QsVET3h*seR^GKtpk^SCudmS4(huZL(qev**Cf_@8wWE2cBH9Sa$G(f%I zw|+t`n<_Y94<=y{f~xTbUN|Yi&iC*vFeKWi}m7<9ZH>DEQAC1mWMXjckNQ86>-o_U}W>+Qa zXaOjNBdL@IjHNn8!{y}#NuyA^k(I<*6ShK>m34?%Xi>4>H5N~&LpTVoVL$4&>L$+z`*N!i}m2fGd-IZ@%#8{b)TFfCPHfdfw1eQj7 zmsB>P>nYn5r z_w|D6C~&l!@|kV^ARS=pu|i0;-$tr?cMZVO%!Ao;nybBjjVEz9c2I6c*IXJtxsfEQ zs_3tY`%+Y4IesFV$D9gbuR`>eM#r-|ziVc!w@#w1LMn@bqB zK?yyB^sP?w;N16B%H0lOH^J^eK+4P^z0ul@CetLaJp{ado#e%_F@y? zaShS>GEfl!Yb=Y8JFwiI3{K*m;}absx+o~@$42H zkFFOpWVT%@v20>m^TOiQ?5Zqf&h4#B9pXBc8B(8J1|1DwdTNH5q7beC6S{Ks?O~hb zxK^@FYfg$;a;M3_ISS6X29N1Z8!$bT5aqYUB7LQEP^tpr^@*=e9%fJ>=pICEz38B_ zx0IGx-Zv9l5jXJ4&1@xajb(XD(=9vFnIjN@w=$Mr6Y&eqPz6b!J6>xoUw*WzXK!x2 z9;OiW9WP2^WV%Li(aq}3U8xqlPqe5)euRMnhHM(@RHrnxN=_8ZpJg*7WFaET5t*^o z&CydkUSqe8(1129U+^cCAL3(l(b^t^o#(a)7e=i!^s0Psm||k=uQ%h z{k}C_g&jHo#3SdB{}nkZkgBoV;S6CF&%dkt%pb_XX0e#Y7VaB|HW=7ZcGR_QSG~>4 zW-OMEYAuGAYZ4sX9g|a>caFGPL)F=n1*lA+6?hzQY4Ki4&}_A4gv1J%vO`fC#7U2p zB`c}^oGLRXPlHArj9uYT(SxhdtjrX z>sS5u0C6{KoR(CU8C;H#MEm^hJ7F|Eu8?OVe3-GrQuLvQp0X5hhy7J`z@>++(YmUJ z*f@jGkt<1qr{Q|eLvD&13Rokw_YE`qhaHjsh2Fb^O|XZ|{v4;z9{b@_rIwc>3(UTZ zoRqr^7guGqdrR@840LEMiv++HA5{btP+wdkz<<}yef<)IyqKl$3HNVHT1XFhq_>_X zjB$#7AHeuWfSiHq=Lm#dntt32mov3uIPlamUe}p*?JdzN5)Xa)X9pfXs2@AnrN%I2 zs47F}p+mVkwSr0kZu|<+*2Oe@k)-Uu@Qt$%FyDH+_0zR}zN&BTLms&GxNcwMne{&+ zGn;OYgqiia0-7z}#+>N)s=ul&-U{&7dt*z&{Lu%SL4w_pRQvb?+v~Y8#d{_!2B`EvqmVMqyj){K z3X$c9cN?i%$F=4Sb;e?VEiU%vV*~65?w1I5gN`=JvM^&g-LCDyu?Yf5He%aG5#kBa z0t^&UUKG80=zzT5n9X8^4bN^o#;FlyRb>e%z>WAc3RK!%e~n0oYNR*DkZZu#bR~VM zLt4oWD*H31`8I9wb~Do1+8#8P|x(8 zlKl=7?!Big8oh9OiE(kLnvFplOjgkR4|2v-_7eQp4-Dy*U_IM|BF$Qx8qI)r2so7(c=2yG*K3pwP>n?>_QPD@_;1l&U7IOxQSthsf+CTN9a6)R=EKhMrZ% ziU6(!VbHf}VLxDaR_&(p2!y-b-@=l=b-z&~b18f0)tOL_RaRs09ZwTMz>=FD3a`MQ z7g!&aKZE|CEN!3+)db@J_fjn~SDyG*5-sowBj;Sm+1W(Q^~4<9b}Z43@T;Mh!xtqPSJ;F@K0 z@y8HHw?%wIfr^F0fV)3wzrdE?-h9y9HhIw#Nz{V%TZXB9s@Cv6`;{>cWnoCG;(`#a zY9&RTpQY_m-&E1ef2f>}=YZUeh-k@F&du+UqtIE{T46vJk0c6XP-#rNvNOo+DZXc+ zQNy}S9vM9(!GA>M$|_d{CBOry$y?FwGW2_AjNWR$n3RCLs}@J=n$3xVPmjKp+pf5g zg$+OJ9lT~-d=HHujse(>{E=1T7ZyIa=C5Wr4S`!Mpr?;qny%2F zM}@p%47H(r(%`Lll$!2im=4GAeNN837r%rk`s$D&$-dOG2ji{6n^C!l)_%OZFiJ|1 zZwDTFOS}ZKEtetnj4vJ1wDz3n=Yj+)t4~c+r=l_W&2=iOrXEAerWLW&mqa~4@4Wa{ zAj(5xnuF5iXCaA%mEl*Nd|o%axf&@EH5j~*na3$z3ZP&-sYsh<=Hm>8c{8U8e+aLO z^RTkT^E4qz5YN&}TvDXfU84aKu_A zel;C7pe1hkLwJsJKI@w%`vD3FX5B@ok#I?Hlz{~jJ&ix2u^qAEK>+~K2lRI_r)#d4 z$4l?qP|7cBlM-aQl)2Voi|`Sb-nAAS$EQd*o{pVL$5ehRJOkN4ek!X-JK>tkj7mbcXvJg{d?~*&Mh~6!Mkcz zJ#*Gv(zha;Cu1vn{re5>(+;||yO}Ty#`J07aqWHtV~<_PG$0sFg+r_upwKr#UgLQ6 zaDc_tk*=Ly80f_@ysYm;c1q81a4595T^VuG9Zu9m0O9rJYTiWuZWpT!_{7-;<9C{) z)Y(L<(Rp!Zy;`6O8@bo*mgk_sSZ>r*{XwbYmJ#np((H+<Wb_WBUrBXnB@%hsMs5mv9Fgd4o# zAq9T#ovCr4M8(*yHSw6G+q5-M!f_F`An-pDLmg8FjP_Rs}fpp^aELhr%%M71|eisjWGx0!0lhSX2K4dcl*TGOiaUy zXvO7VZEN`FC$Ba7pWg8)_4-}SQ=51G?oPjMF(&n=$8EW>{Xi+ahY;XI&EWjLh$yQs z6xY!ARIpc}Z1LaDt#SkJe>=B@+tnNtt>%}!&WTi7nB9OpZzAE&+3ti3d*Mz}L`ZzjT~9?xmKEw+Gf?uD7=?*zJ6UF-@#8tP&3fjU zQ`GMBO2ku76e`1bH9C+T1%BhEDUxZ3ure%Sa6Tgo1wsS3DCKK zp~d+K z#p2s`oJyk)Q_}uEWS+S=`eRUEFXS>3d!-GH?&cT`qvO8!qke|?Wf(^7QQ&u##vjCf zJ+omRDIuHZ!!9JVSt!v9kEZyV!P8TY@Hnx892cy}AR3DQQR z!_;?8y(ML+?=2L5)Q$YNRC&oNnxX>4%|5Te!nGwV2wacX&e_z& zYzmFIS5Xq2ldL4F;n_%E!(BqBvk~GS7wLvG8?d*YT*6BBF$4ZI{?wlmhz{{Q9yiEr z2BpOL2axAZ407xHjyK2LCSa(3a^;QT(f#6F)(LGyM|ANQ+yM1k?pVYBNYDN#V!b3A z6LoO>wqMI_gR1%U$j@arKIYtwdxSblI$vUN0Xao7AE1kq*7nN;eBIRDpAw=uH0UHf zf{CL2nT9?FxJyiBV5l@(k#_TU%KOQi0b|VKJ=4B@EVW*IwE7omo_jjL=C57$^C4vZ znT=#(H=d1MUsx8fEkeo+SIJKN`X=$7qPq)`HciN9&b@s zI3rREGC$h0F998;*l_DLmnj*_t8_3HMla~2GMy3kLAbyaCsi?YerSGemOxPJ<;7Fk znYXSHR8-qT`>$g!EX-T*HtbzuKi$GA?wBw`upESl^)&*uB-Xnpj|n1C!tONM3KyRjDJ&x37d#^ovo}67jMbMNRQ7kA;J=m~qpJFk zthYs0wGDZ&_WTtBQ0k4gKuC}(K8n{9{6jzVssJyu*Wb}wLGXsz6hENBX9P!|AkB8| zJM4slcH|1OeaNm({jxe07J}@WpCbu+S8evWyaZKl_Bjuf#f;*2#^h3BVgRM9GAN)I zK$DO!$xkSDhXU3NGMZf9P@`9eKA#F9a3%ijf1UgCpetx!b`v${=&0~!8Ac1saDgN& z>l~pSXA~pu1+m7L6>oK>wSOTQhQ(*?a+Suzyx_s(vM89UN(Y^Grn#ucI1e7dJBDIb z$Lcz_eJf2j#-K#5 z<+CX|E}L=|>I$$zT@PGc`enMgbYiCf0IZnctEv*U>wc122oC@}3E$1~qBPxx7K+|d zFQoMB1?Gtp&yUBu>jli_uBtCK^z0>bE9}O7S>zNd4vGlF$lC}>z+U@|;|7T%=y|nZ zwasi%S)=vjvlt*<`%Emj;g3K!ttX6}#N#RW`5+!Ka&4vePHg_m+sJLalMm&3eFK#< z9;@$#-SE4CW+=U2lEMF>l8uMousk)uFkJBSgO77IFl15>TUgjE8du)7 zLY?1s1WsRc`@}mxpm}z{X^)dgJ#Vm6wZu6?djB$&C28)%aC9KL0R^coESLbpUk2Kx zf4wOB0XcR<=GBwXMM%9AV4>F@hni^GVaU;-7E})mc83~tQ>*@YPgiqug~pHV+SlK^ z?867+xyzAZ_<9ofpe(%B!7~6FUHgxp>7p}FEuAhJ;By^Oj_3C`$T=c=;W{qE^)vq! zRqBsQDbi1*C`%vHV$vu5Ke%*Sz>89)Zcu6C*B~|0fBhGGs$!@;EN$ZGh`r8fC_;C? zO;9x&wENzM7p1j*?jRFBBq^Q1rVF{txEHrY^lM>b3C1}0{~&@)8P9`G_YgV)A-!;zZ(AER_?mR`R89qZ{M&{9hbEvonNmE zo+JKk)gK%*;val|Vvev`LvMboM^9P1MNK*#Q%hBJ72!%nrF&2Y|0p)bt6Jao`g_*iMsY7O^Ogd67@ zz_}Ll_-kBq+FYbko5GKnpP>^YmPU1*^cKsXdXsDa8ziQou|F_)=)tclw}vu&zG+VS zj7_>tXCVEkv65y~-D^OW2mul2gEo%(iye(H87}52%J>B72MnPSE6FCNJ^l6uWL|#& zmJ@>yTCsqh0_QK~md`RL8g2)?wRTzBr+q%jSLJiCv=ozyGu*t*Ur3cb{WPR?Sxlf| zUjJFM!m8J$vxiA&Z>C1W?Vr$g;htcca-wjbZ$B9Q=N^?|(HUw|-cRPAsS!>X`rC}a zVzQQLB&i*&ZRuuhBzLq%@vVihw_cW^xB5g;rO`SYzGBHMY5CwLr8}U(7M&k$I{>hx z>U{ccDL-CTkNf9I+l92Blqck-gMI5G*lkP?vBLg2w{vsA3!jh&U(3|S@mYHE;lL2e z8CapezG6`USJMJr*_`FqfkeMDkZrMKGPICb^?mou)Y3S>%aPt&h61w^1EaP7m%s4% z)ZMRhRi`GPUF@)6`QR2!NQSp~c}CViltaSdy{6PpRf?w(pSb7kpv$ZOIY^Tn>!aiU z%L2Ii@X!Oo+dO@Kg|Sla7RGC#C5v#V*}R-(vxAhs%imWue%_rr1KMu3yv8@`ahSw%Qn9fuHeTGHJ${eObs|LZ={~ z*06B*7JUlS;e6)Pf!NM3!U2T_9@ehTtGTs%iI#t`24}#x8>d1i4it)1}j`pHYysdn^P_g_S;mwLf=CG|9uO-%+BwvBbFRzFh+7N3^ zKy^4l5uAI9^DIxbwP9C*$&a>rrE1d%SKaMVLP)k;kx;$W(4}fv`&2qq6Y?IdGv0}| zak#HXK8{#W^`ilp+*)jNZG3qooW|u3@VNzHVkQaK%Hu zl8n{h;Dgxw8jzp$7+3&N9J`2%4;fQ9a0VU53uKYSULMmtO(XY&6bm)T`h_l$nq2yN zz)!~w!zELzb|IHc&aJ5!B3}1?s-t}UP(k5~HPW&x*j`cXq~#^8l9NB4!b_H*VXd;W zOutF|^_-l$vRGu3O@#w(_HzpkElI(l#jbOvkws>@cM9iP4b6^^2(EVX&UPSz(AU2eH7@%1Q8 zf92rY07p_soX~HGS>k%&^sOFIi5oT|xLT!=ij)~>SS0TWu=SW!Y(=;xosrL$KUka=_;aIN6s<$;sqv#RQ@Tvu+oaF|A*l$aismq7T!2|Q%?M?$7Rai66HkC$_ z+y%%+k&C+dXBn~4S2Kd28~5%mM+QHpBu@vYL(W9q9Baz+y1<50x_uRFS);5v=L;NmO?CJ zk#(&bLssP0#n)X>$iDdO5fu??P%TEz&AAoj{jp{h`LQ8Y@}KppP`;_VeurlKY72nw z{wTx-s^wL#!P-#x5Jp+Br*z@sR$xeFu?&TNo%`ZJ#DJkdzMe$+kx8Lw9p&$9vG6K3 z0kQyy|24+LhH}on!uR{`ve)6Cq>)f~p1vq$IqI~s_(9ySzWOWZmI6Pp8^zolgiUCW zf2!)}Vtx#z=k0fJ5teyS{5r7T)*o=h>(2|v>;E8!zX2CtA-OqkJkv3 zACPkpk~SsbisQj^5o_B~i|eN^i{RmDLBuPWDCZIVUA|UqxJJJAP*!yeL7dY0;zB^X z0upR;QRLXUt`Y)x+KkAWeA?tnK{+`2c$9S$5X;VjY{JUI!5Y>^C>lUnIxX~`ms?La zP<)T;`{pOwy=@6oVfaKgFG`OC4(ld&MUd-ru&mHV_GcagNSP$OS&Gw#qYB2Tz1WZSvm&A;{AZh+)NVW-{zT} zTez4SL`(#CrqOI_HAVS%U!nj1v>qm*tnc`B^ZptAY$61-L?TA=@U&49^T%mSwI&rl zTRluXna^_RyadlZ==3j4ldw_aW%0MCC_ zP{Szi>%UHkda%;sX3A&#Sw`tR_N~`a6bhU=Eo13Fk74*iCXhBI>jd?8z5rb330m(G zJU#S{QPie47+BWPVv*J;I@IjbFdh7v640!FAvTM6wNx{NH*okZhbXxLh^w0asH?Y8 z&??_@CX|8&3owK|+H6yfwt*!$+rw{qqAL>`Q~hcs4q=iUZ=u&s0$Fo&)Q2t28suP1 z%5=nn{rbRyuS4gG2DNEFI^3rW%qQtE5BQ0zjzBX&)fMc z%1+>BUv0E`lf7K%yM_b$r!4J!I|lnc3%fOz8LdMq?R$WduYKS7$|yRRv^Q5D^@7FT z>crav^KaKr5PxoO7Da(RNwE#@*|*@9=w9($fh6?6TLl*f1qqWjFH`Z0g5*wwRY8Z) zTy08Ad<5=@pa|f0%3?K-3#C6<UaPz4}^33Y;1CxX#_g@_g;A^ zZ&p{@81>V!f{rw(_#5-fi;UY(rI_hRlCzs`x8;5Efow@^wdNO z%i(e98o8I|(b2wX6C@Ka1)>h4F9a{~!TRleClS7Vhn>2PTab7`Pb!e{kx+7} z+3w*t)Ck9B2>Ae&lg|1=9nD#wJ)?->7@LDwVn?3z~G zX(X)QauyC<7^FG=B}zj!PI&14+)|U6L-vbwPi&o_be<$6l1K!%8)uAj;Jlom1VEE^ zCq%-H^O`5PsmgB9u-%P2quMNNG9FfX=Y$u+e*jzr`gj_`h{d#FjyPYB=~8Rw&te}v zjW1pF_fKQ!dMLVHq)~7+eae?dI=CO8-})1Dyb*R^+MjY?;^b0PuIkdrpsz1v3Yej` z%cY*YJPG#bV$<(L)OpJnGjf?|L3MTe0_-A46rO@!U(jPYi z5-W&81@UahmLytuV`k-SlI*o{ZUj?=F#kH7aDqU z5&4CaUlc(vMxXexA$hE_nB--|Zv5VqakB~``3|EA8Dx-?7>*!Xo@63{zn2LarCZ%~ zNJpA|L+VH|Qt(~Cm>6+`f^E|!h%LQfmNZ26$>q|c_n!9k{Ud+YM}@z%JmSa1qnk4Q zVL3L%uFL#Q71*fce58pzGF1TlH%%?H&$FWcgtCZ7`OLx_f(lSAy(jVQ%e-yPewI4N z8AMlBu=%g)WJ8VsUK$mm@73uS{j!;Pde1~@*H5B(-&5{Zco5cm96XTy!PCNvuhEok z(FLEY^tIr(-@DWHYj<&`TI=i1GL4!qaDs;lX?2HqAF!)aevwTKt^x3JWJM0Vo}NSx zJ}i_Goa8;kS`hS|PQ+Vq_7*d`hvfM1T&c^4?rH+o00$H!~&E=partqKrrdnEYF8hM|uUFH+gC(7} zhuP-21&HnL6V2jz%?2n#am%OdWf;X-dqZjwEAF@NL5xN%PYtSH1!xI}2y%X%i%KQH zh05#KSrb+6mg})$>+wP!We-z(k?Sp9+8jw8-jK1Z+}bk;v2B@ohjUI%7C&dNf1?i64<{#KV096a)8booHy^yZLjb_xOF6YfK& zgW7ZKoL`@;b#~}!1q)`SP_*wK(bK#^i7J6$WS=%coak%4!HE3Y>zGngI-Zw<0$j9D z?MrU#<*@}Et{3n1c0^0}&8bxTE4gKXFJdJ=Rzg260_T(#auKU!|A~ecoV}EfHkypK zIGY^u#MObM=p1$n-FRobLaM%UybV1)J}Or2ov*`=55XP*W!UB)`@v|!*H<^aO+k`* zV188HZxWFH50Z|p68st877G~h0FYJ=z!PF@90{pbrh+xK3i7wzO=p9FfXCb)!{SBCkDpMlr- zjgA%AN0IhU2C8{}d^Rv@o!z_`zmV^ZyL(!57A?9vLDswEA8p${2l*m#ZYF>Xxrt9?6lFn%>XUlLJ?SV#UU26R90 z`B#^&SmR2gyX!lvlgI3Qg9Kf5XOfm`U zR*9#460oee*8PWbA-(&cJnvQK2#$hXj8M~u_BT90=vyHtjl(fW_6vbvCVk^q3)u!c zF^(|T8Xpwt(fKcC;yCIlDcqv;nhb)~16xzq_z9fb@sP>83kXpHCqee~2$jW?gtqTF z=>c-E?AYC3#1YW$e!u<%euN%rSF?B;ioYaLf#V^0+OP&C%^jF?a8q&9?a&F; zY5RueWz9~>-@TtR)LtyFAO(aZz6o@r7P4w_B!fnIUi+&AP>vwIvTjnwU>f>X3kIeq zf^^m=uF-Rl)zV#x%#HA%r~FqgsJ(Ow!b(jM*6l3hw}_9m6z7kexd>tP2rIq%0bF%$ zJEF5JmQ{U^+OVB@e}z9j^Cr)wmqBXoMT!=J)_|&-FMYG=l`VFPJbI3S#ag~{!Y$y> zO7_!+YJDv|GTz}0Mwe=H5z{Kqbmu_xv6Uop@SjNNqrWD!)0?+K6|zw@Sz<=7cs>t` z?18|Q$2I;STnidpr;zred^7!JlsN6Ia7;5UF7p7=)df)<42!k)0OuWK0DRnH(E{n& zR6Js`gHX&P?z~-DNQLr5(8mMyyQ_D*FzT*tNG79fyCs>UHLPIalbs79$Q*w@0F}YxFf7zLX_Jy2C7qaa^PKPt z2}O%a1lSzS-VgK2oL&7VjfFNpKj-wcOl(2+R}X8MA}PzyJR$9mvqH4o^;8jL1x>#z z$wN!~8s-8Gl1nclV9(=Cs*YhC0=;xLISzmf+g9GDze^B>sJ<(4df5pGWQhb|S`>!& zHhZ#chC81H)x*AgOKt3@ld1}R=rx}WzZZ8JQZNp0IL-|~6*eC?`~=9W2pYoy4L~Z* zn8&)V&9v|DEcSj$X3!!hx>-KNP=knEG4?C(Ol7v=D(h6DFfGvR-O0dm5un_IL$rT6 z&{1qjG&q2!V4VNV>qRg90oR`ZY!$nz-8R7r2RW2ukd7!jV&%<~)rHg_2kMoL#3OlN!?_4;ixKt&b5iq85Cfj_OqhQx+&hq1)= zT_;=op3fmrYz$WDsV&T%{~6?G&GCl-ZFUd(&Y8alcs~o@qzNXz2K=bXv5Q)Ia{>#; zsNB1qpJ?1i&H*aH>Jf(xm%-K%%BRC_%Z#pe@UoDS!TGPk7JZXX$4kWSvGmc&nXH0K z$^*!2I7T+Yj1uc1(C}UsY!?d|c$54*5h7wnjWU@!y#GQk(_d3Ux}Auez+|hh{eXjL zP|ZrXpnfnelpoRNvm8M1XO0+A$Sh`Cr`_>S?CexsiRpCS_s|<7@%q}Wj>el->gzA) zxQ@G#WC)R>DteJa&sE-clGl~$Iit83M%>yVr7TEsukvRraBl6}tP!QONS6isSigo8 z`{kmlXVRx)R-)u^ULs;wAXkI9!RE^}1JmoAl^$cIi0k$OeFG||`{dw78bz8mcyh4q z`y@e|?>B$W<+?iazD3Iwm$rwYyFK`j{*2LgPvKbmm}sTK$w-Q7 z!4_CQM?{@8$8+jR2G(4C;ug#@2>}S%M=Q)57^|5%MoaYL0Y+9LCUIxpK3!m1*w%T` zV>BsWVOk8nD6TM5zj0Z-=I-K~f@~OlokhmY=qXCST{E`YqCZ0&l*$F1D z7tR)mF)5eWXA{=8souFu*q}THFz0r%(dPaF%kk1rhKu%hkU1j;RcK1>xOF`XRRM^2@gCPGLqQIe{41`JTCD)=~F43KjkqE#E zv0sf3|C*>8)KGM#d=iF=?ZQj8l@M$%0Mr9fx;irviR2w}7^J z;zDoVpJ19*izr!5twA3szi%(wcqX#ih5w=9$+%jhVo~mD$mtId4>o`N znw(NUziP?E#k3JEimWy-^s`pLcZ*HM0qwUIf#zm`zX*Lo@g!USxq*9~%D9;}vT(DD;`P7C zGofb-M54mF1T$8uAi_5F^`lr}z+O6DwrL-+hW$G3)kI8&_^j zroRp59X4sx6*KH+>M`z?N5_5?pUF|Kv7| zf*o86QC*3l3vSb@AYqC5Cq%A~X^%p+f3~ts7Hbvh+I|xI-7p!1_&wxlJanv(Eyyc% z1kY+huUO+_Ag|fEV=Gccz&z49g70M!z7v0@^LB%WIz!F}S;`S1LJ^x^it>T^%0}Q< zyLstEYD(IyOQhQf3p3iJSvKf*bXRtN5j+(_LJpP-xidqX!HqhX`~7?y(2Zpv^_TN` zU?tyYo4~Se2t4^wOf^Ot>AIM&=fmUsV|rZqJXfz*JR7j5{krGe+(2tJ2-9N2B&1>3 zHJwPSK_g^sHXvx^B~1YyXIPj$f*==}>B7o@b@KmotCj1sP!^efLGHs-h|N7yNd5de zHTSgqdT`@Z<>=LRHwFfdr+1Y@S`p&9o5AZ8IHO52%5=K7$wH^5VJ$cX3$Ars;{Vx3ewU)DF0Md@4A_+RkqI-$ zS7V)#hD;6V${NpQy(t<=z4t&%P1jZwkOk1*~Ox#gXQUoaLe(|C&< zd~{dHJ4;FCns`)~8H7-&hR74fhlQ9|*}!CsjT7`LSu0-A4523QVCtH*PIvkd{iVd@ zWS$!#MNJ2vB6ae0wUEkT{oCTroU*nIpnfV48@Lsx`1P;pan!}GAy7Tx0u-KjswX*= zh#AsU;}1h$!Q}%ObzYH_mJ!^5_EvJ%X?5x$x8UTYPhCtZeyU~>C>ZaF+)Cv9n}_7w zi>#uT1b-{jv(yIh5`MB^4J+pt}$g6QCUx)uqM&FMl zS}6Jw9s}eJr_K6!JZxc0Ee^YrEAX2PtOTOv(d*TZ)@;7Zo)E~;w#4L`RS5rlI&^~O zs~FPo*wTe8pf)u{Ki|{*TD2S9u4`xGewn#tpmP*$x`!sn}>zv;(XD+zeLM0WW*;+Hd!Xgyj%6BN87IcFBBSh z+z;$uPs@1T&e$eDvDOym+IWOF$V_2mG~xCz(V*#qvv*IQ=+T(Yg>++xVByY1C`|&& zkc==tJ|eV!hGz-xpPR>f(}?d7$Cy`PMIHv(;2jpiC)84-{)^mHq%nAEO|hE1tly@# zTUb}=bfRSyz>0&Tr|YJ1Ddg5vMv^6l> zoTe(h7c-xxPHROD1Ez|H>v7!7|poZgMBx%SSGRJ9TP6ULk~RefL9 z$9l_vrK>J-IqERx4|WX8MquQs5GZ~wW6<><_shY|OZPN}f<@k*N!vJeo`85M$qVPK zBmXlhIhenHNt`yXCK5QPS)hgI!qhM`C zchzKNZChNzs@J)>lJtH)MKtBNW=ZL@H9|L;(Nl{T8NRpzS$3fW2Xf1|BX|RllN3Vg zcbn_6$;0b0wEaMsdB_QUERexXvVtgo?zFH31K4WtEr6JT$FmKk0qsX8^xiHP)sj3( ze7d1Tmi~*NqOSd6<9+}7>Ca`RRe=}AvR?RzgZZ5v(PyZ-vT%90PZ%y)!_GwQj3Khi zqMgQu(`DXMkK}TmL{=U*W;@OnoE`gyl{ASIuPso4n88Asg_gL{OIWifcsekl<$Q zTVEmmXkhI6%aHu2@uMI*TDaB^AN-f+!2DXh_mr&Cfu?7uinM6@jj)C_U`^>gr|=uj zTK2YA?;@O#Nr$O1i6B+B`@X`nck7T9?nHzAE3x5o2o1{zGJfHY)q-7wE}%k+(y>K! zKH>I=?5S2HV}(s37`gblYddB1rqU*}-=LrC>^=+FqehjZh&vX>c?{`IgE zIM-QSkKb~lnX}b`#6@DLA%hHz{GIj%r*k2X&SP^raI!^@+4dh(X!u(j*;(JVN?A*y z>?5xl2_8joe4lQk?;~p~v&dmV?R8z_b0_kX`p2eqS!4ebOnam7s z+{cxLJp>mr-7{`9FS7x`t8C_QCe2t6NWr!+boAfW#w7-h+!38GAhH8ob7;>9@ufC>g z@reW~iViW>Y) zxJwc-^S>&1+x&&I1qThF>KMJmC12e4VHSs&A~8ZO6l~fd0z`0G)QX)gLbljNKp2?a zY$pq4oH|hp1`*F7)0bvQ31(G!K08O0#Kc(tbq=OQdTgO=FdBx~lFGQv-W?c7N$Z3+ z@b1Zt9}XFYUzhV|;l!rLWHrOdV8$a`+{uksP<~|OF$H{Qnq;C&^sKM5*(68Mf)d9! zE1xa4$t+e*lRo?`te?$Onavw)3y%>;Kij!JXStgUA~#3`eULmP7q0%yKWChQ250TA zgr{DHmV!aJ!fdl|@_XduK~6Ces2Ms`yECEJ>)%ygG!n(F4Sk6{iPZrA(@A6S1#bI& zh!P${-prD5tV{FT>%B4b1=lYcCe)+Yuj+aATu)Apt_B+2?)xs64@5GXd>BR};jS zZKjy3!hUSq`L%#XL5EZZW?q8jEHCUvArt)TT2oQ2DxxYa(&LA9fv zz{BfHG=)ygIJ$P%cZLDo$<~LRU;rea1##}6vstA5DvCMQ z@a|}6@9(8s{6FzCXt~l7@@n7>2mFwpDy#OhWU9jQnd&5p>l+!4RIa)7Kl|WrxTxIV z!x-KO$DTF_w$4XQBo^RD>3%~ftliud%$i!}x@*tf46bp;U=THS*(qj_)mCE28ESt;0w?hpI@Zm-h5b@yS z_69-FoW)J~9K;rsg3E8ag~hobwN3l&cbQUhl0$fGGNo86sgQ-OSbJ5QpCn(k zPmk<~h!nb|7ir=LRFl^&^SHYt!xq7%(5lzeE8xsW%M{Bkv@^!P&t-uI+D;4&wfZFN zI}C}Y$%3RBdmW}>@c+P&Xmjn%KSCM= z@FD2r-~(|LIrm_2l`$0D0Nrqt>_@{NTekkwaK{>PG=Te?mcptH3%8|`i;!rda!gbN zyHaEeUq%DuBWU&%Irs*G&t%R-zL@;=?mLuP%p0YrD9^oJ5nK>h$`DY61KM2-kr{Bf zg7nz0e|>#q;GBV)o;sm+0+vvF(Jy?s3iSKd!P3Q(!bc<3gEO0?p>mNQ1|HkTFHQ4@ z{^H?REO%MnT5J2<&Z)HjDC1?O%bblqUnKTgbtR;89kd8J7-&8l=bJh_aSY%VW?ug^ zF#cVeo0UbqdKxEgqzjZzGO$7V3xNdW93!@smM(UslknCY)7M=7t7pL;##MqEJ*Q_4 zVPBWpG$*d;)s_KCq!3ZV9g_bzXD39QZ*Qesx6h*@24M9L>#0bL*W`UUDAQnc7 zT&1HrDPtBN(N(p&eW^|!VWbd|pG55B)ML_BkaeZ{~bo;Oqep;eWx*}xTOodP3@vpiR2ahUB11 zS`N%O*Y8KL>xnVI#AwqRp>UEkU;{egvvJb z&{0VY5RV|k{u!NBM34@>5z~T0qOYal!Bj0-5?)aK@UW~YjaI0%4E)wl@*Hs!irySx{7M$3%8rm#(g#~N_1MUYUB+(}~5#gsj zr-!XlSiEz(%`Zygr4-AE)q|f*y>`r0(108A@|#aXn!xXO-n13uuV2BYcyFR;emdIL zg&jCf;bWFW?mmQR9;#TW*`bF-GEqT$f}fhKaS%$$mnjb0CC|6J&GZ)_(Wt1uSS~)z z3S1lwWmbYn1QbpItO)F4O^jsR5W*50OKAPp&24_;3vU?OFoeN-uM2U_d0x)py&@zu zzCwPpPgU#>ukiT4R;^?cMTVYk^y;k6=muxQHMn+H1@p8ljB_7190e~|9{l7I z;B{*6Vvq>oDf$e7ElAGWx$Vc*t_Wm|HptO=G!ZRblYCI=K@Cxm4zp|y5}l^aM-uzBxv;$lEvpuhjBc?-K&3{!~gR4}XSgI?)Y9bpMM z%}_HzPqczmcmn`>5^Z*2e$&*m!-&=`L6xkEvqnOcw2w{02gcez1m=gu%5SeTNd`B% z5HivcW}%0{&H_D~Kbmb73S`RwHONHRM_G7D@gGJb_sKw_@wV~Azf6MTYSoG1&3AhT zh7WN6;13rZ<%#C~a@fGB2a8btn3*A#P9$%oE=Y^*2)~4@Z|-1I=RAncu3GbhwYR2 zhk%HQJ2lKnEkma+-v3l^E#AdhAHUa=Eue%hL&mnIS>4%(z8QqNfGvCyC)ot4DY#|N zVNbEhF{{FE!w&eDOx3qiZ#LrEC~{+Vj;8;VUUw2n)&^V@pM7xd z;HER*jr!!93=O`yM)`%@xP5kBkAN!RSvm@MMaCTYtRZ5Jwq0HbT~a;K%^sL4of%4yZfLh`tlaa-aV4Ggb$ z__$JXX92I2?c18K!6vgR=u6A!?Eg8ZDBGz2b52Rct*Nt8DG|?-=uG^K;m$<0n8`bB z%A{q-hL^PBpQMoZmS%&zhx==Po&~GC5=Z8zpl-vqPMFehMFcID*VeZKmr9_Us; zD#GAIuEb18*GV)a>3gx>e~eTw-~7AEX{* z5n%k2#5|0FnL(5D50Mf200X1s&^1SZn=D13{{%sQP$XVH@GWck-B2pyp^`n`b!j(V z^d~3fO#RuKy9o%OymgsDiqjnyb@c-uULXOW` zZ2!&S4JQX)1fi5Ww;hjU!>fgq)dG=?!%nlwt`Dqc*QGO5?t2oCGwTLolNV?0rNE}! z0bA6udYa>wkp+n-91PoX>z#2+oEJGAv%*vA&VvV!2)k66%WPfA<(yY)@2v%F(+|PLkE|tzb+jKVz7E~zD z`GHruvfoOB&jnBl^WaBv} zY=d>t;O_43?l2JCU4lz+2<{NvU4y$5+}#;mgF~#X{Gl0?;Ht15Fj7zF+8Pj?syV=f0GRwuR&r6XEA~UnA1xWpdf24l=aW()& zNfDpAZfMuD^dUNYtYb28`J)^UBXq>_n*|6OboyUJQ#HNVEju_4FpO;a5C6mD956q6 zXf*WKxlR-;pOlGRC`WBNL9fR~Dus0ouoEQ)Dg@e6*VBtN(P;T2`yVII7jp>*>d+~Z zMPg>BAip^X5Us`Ie-OCQ1_!U_MS%CKWThgbR00=;4X+=kmmX5 zG)cqt5@c9cf8-AlsNM~5Nmmzl{X)<5wGwTB!XGd+to?ujHqz$)pRT-NBlsw@#*djC zb!N*+g}d|d1|an&+z2j84c{Pv{Rnvg3prbhU!;Dz-QSBeH2yv9RYEJ6(*poAE+u7| zTC(!8p1`h3cFToob>rYi+UggBk?7t#Cs+}pt=LFfK>$E-wI{4cGaHiJ6%4Z~Bs0C4 zNHMkkp=8Pp`;d#Gvu>(_=8o8~{u>%UQCtdC4Ge^fFxm*SpFE=a}91Y;H45>qurNEri9fp5^3Gr|7j@7_#g ztlgDjP;H?)jXN@vHY+DLtVgsYDoas&LbUYZc^bY_*`{+bIWJ38F@O3Y-ryevvaoqg zB8+sSd!kKfbZ>KO>cv&9kZ9T9{m+TXQm;#ixad~+>ln623G)v+W$`L8tnTw4r&squ z5wHmE)y@@v3S8VaHqL9-$tj9qs$a}O%pJ!F%(1PH)EZy86cLOj5=`cGEILiYRM&RL zVh5~aa@mNGGG9=`C+0cnH^0GM*DTmCO5y&dPA%O}6!9UbZog+BJzaxMimL!FNyHjd zXm2umw11TTrRsy+Lw0K3ROYOt1JJ$C`s?rV_ElRV&BG4)1%O^bF|-MYZYChLt#~b{ zW_IF#;REFuKCsI)T{vGO%r;~VW;(TXJD*Po8G9ZHJ0K5H7h$Nv}XqvxAGDB?32N%I*b3yWhIe0x^7tjqAnH(?`lT-8t4F(GE-W9Y3wu$bx^sC2i z@SZJRK8~$>zVo8Lb9XE4YPJUP+@M;yb6Nl2O{ahske_6q!AzwM{1me8+ME{mqAFaE zHI|@w4bGL0JbA_=*CEz&`ReL#1Yu?$|HM~e0wBM*6;)c?(= zWBlSu#KF63Fj}2dx@*XO^s|h(;-_<8VZHqpVun1%%%5ZW&(zgXurXCvr8B*MJM%wZ zRS&~kHP!hLlVN63@mPFId~!;sj=rtXcfx zndjZOY9|XBfX<(`iJ1A-TYY+yRR9^aKcD~*gf+mZ9pgtkN%IV8T;}X@%A0Bc^DJ2{ zU`vF~?#qOVsq;=Lu0_C|htK@YgjS1upt8>*JF+9|+2YBsk^I=zCfd{x$e#n_8qd2i zgMX0|oa)f=DKo_pmcS7=!CEo5!UE$$$h?e^YE8A18>9XTp}nI!GU@=Tx0W}yzY$x7 zGe@r{`QF1S)GA;_&6=8gqa^z(*?!q&= zt;2*r^fukK3XO^x?!k)o%dicgS}gTpT?Hyhb=t@b*!J#D^7t(%Y~~?9G%4+<0W1E7 z_RA1*aem$mgzhe|@4WZ}+^*h`c!YdJ3D=4X7+ zpP%VmyP6#R5D>qcVexj!hSpsfl=#s7Zxgx zB8HV~3r08QPp5}Tcl2`anqt|5GGf1Mm3NLT`waLI{#db~jzn>ad+$&0WRjg2k6@)| z#-2>mtDDqchw~dG?21^ZvK{&T=)mt7mwc-!58b)bH+i$wASWATG-QDC7JbpU4;ex| zVvhO2-0H_irnU8lSOm&>pMJ`-4B0IiNLL_R4&Od+84%2A;K-KbG?6PjA6$A4UH2@F zSmRab8%2{40RQwd>gvV6*4}X4)}xAL2kT~jRClc*t;o!qk+JarnW_k@-;8psu&YQ= zvUBt|%--!_QJg-iX&nSWO|;_lcLUOZ@#rO?GG^gF#nyJ?f)5|u`LhM?dFg)vidP@h zVA7S?VKh}NV}#m>4vn(HcbDL-=D}Dc(TJvmON+Egbhf{zpNAZVvgMU9n+Hj3%dj0M zl)RF&AYhs}o{)M53D!G>=NlTb`m>?^br~Os-uk}lHUG< zRatTQ??#TVmhskqNA@gi1A@FStkVY`07=mKwt;dDC$mZIeO6`~6z($dyC!T?;fR-N z)p)EIOqrpi=PuO9WR@8h2}UjD@iU0QoxRJUDp-$tX{3@Ih(<|VZKV-zkBzWzN+0Yd z$_wA&ImH)A+&hx|hP-RzqS`y*#wX_>SVDS@o#Z}>{5QEvvvv6%`Wc;7K}sDE2D2Rg zCkzfe;O*SFW{Tb5upQ}$*A;_b_&r##N!AXzx@|Wg|M=eR!`?|L+-_=+JP}tSC*V)P zf6~(@<`RKS9?_(Gx_Cyp^hg0q8bBhv*3iTg)Q?oJMn+2^q5R zLqC7R6hZnMR2Y=6N?~?Z=|96;*-7SmA#qb78E{`JWO+={xMn6}%`V%a=>&FYF|k?~gjKU45qlGGf}k(&EY_KOGf= z&get}8*_*a?^bI&%dRSoab?0RO3`naNcXW(YXZPZ>JnpBRY<#A^dJTdy|ZO9^@ua* zC@*45NNL_^Ik`QmLF+G-jCFz0!KU=Y0O76aFx~yQg3>81ZZ1`MXGP<-EFww8i}+ zpM!y=!cpK{KBx3qY~f0=$?!)<(TndQuxjZx+e(N4)E-u?=a7q}u^{Cv|E6>JxloQ8 zP&TE%yx2t4_q8vG*cCU3sJbbxY@glqyhkf1H2&CGb8+EMrs7#i9LyxG5V-z26Awu*J~os61v8y4JfHvnkdJFsrk!|y!LyvZvRP&9gk#2KD}@- z^T{hEN%2?QAQs}c+2hzo7%p5>>;d&i+f>vjL3i=d3mtl+#JJNhwUyvTWQM_tu7|?} zbp_no;6L^=qc3(y(int_`-mgxKSS8M8JN43d=z;Q=w=0TqB>5Jd58X);RMngSy8PP z_bRyB`h)P^kXh7=jAyE-3N`g|%n;DLk$_*gJJKt^CsC|$0LZ*VYM}Y>A-I(QL8)iF z@cwH=Q=-7TZ5aSXJl2T|i6=MrJGxGgCug8Q>(i|Q3kGs%u%RL}^KIvYaTYlh5zdrr z(stnzN0gW3h6SCN)vHS+si@tvzc6#&D{Zrg7uu2U89lNB37ozaYnYU0A0$jnIu%w_ zw&X5X>5fju0)EOAGR*Dq>Xh!mw8*DPa^XVaf0uGah^%(l6M;o$ZLj zADcx2mWwIlsR;=*!YWwPOpx$+hb=!~Pel7F-*zyV#a3q4%=x;rWn)=T5J*PuF5m(! zji3s4Y21Hz(L5gShV;JYe`F#|i7l=WEpH|;ov+;Prkfafl9=ZJ}$#oIkp zOTwo?+10U-iYK5|mNDWJ^{P97&N2tN|9uuS#|rM)Xw^{rBu?B+DiA2~q!G&H!e1N- z9ZQD-KQJ|tyNk|i-MT4_ZLpwD`ub%HKv@A4q8A3>n8Dbnu$AD#Axw~uOCx7PgC6`-LuEVW-evW5 zUt6_PEgql32_M8P?fLQhVzg5BvF) zU;$#n?l&HaZr+nDaA~Jy;+=B?yY4#+wo0XqkqSOWg?TQ8Gb~#EQZu-C5S05)Cp$K@ zdqdxt^wMYmKgiHqP{&-=pS}eg>FlYff6A@vKNW~h)TA=PL{%H%DtF($}iHJm&3xYra zE_Xl9Ob{^kS_iBJsWzLl*ggH$_h?qD2uDFuJjsMHtP_C2Y~rNwT%#Ntu_6^pPZF+< zgjd&bVdNd-aGa8l0$uKK%4@k&&ydBds8X%q>a_c^pW8ZW#IB=+$VDju>F zc&j}pT;htT3+w$-_N)J})DTgU8B-1*qCJXEC!&cA*jVnr9z?q^dorJTjLV%lrSS#O zOl3ckr`)JzZaYgoyYZi^9(gYhJkHs22!+S?;j46j z=XdmW8jzM|y^UDelKa?W5P$51x*e1EVBQvU1m5j3hak4^_Q3#qWqetfS^qM_lI`tU z)$&6yN0V#Z1Bq}4E&d$wDphG2(rS9E#0Sfv*a1{BVCmD}W*lNdZi`h~(VIDo`STNf z(2j-?FR4kqo?``u)ArG-&XLc#Zle3q#hG2}T{;cGMt?8xI)^PNx9VXfLn1+2iyO9o z{sO_=y(>kp+ZJ?FTarlJ1Z64!A0wnT3(n!mf-l-0=c{aKHAjss_8eA)-md_u_8|F0 zAlq`Fc_A%M{`*4sgc=u*ja<>Fk^~h7XR-dIwQfQO4iBGUbpya=KBvw#6cJo};7q}l zj7(ayx^GN<%qwU&Pw@~NG zN7Q<}q|bE~ug(#O+W#p}6?D@w5bs}7MWsI$s;g?(!W%d1-BC3o_t9stz$$-M>!2vVTXHsf3Vn{y@FoBeezVk1?#GB*Mv`?yPX|KsH3M#sD zyDoqw&;B52#3YF1&7jyl+e7tJ4w^oneW>QoNq_C)<_SBf8qT%%T26xl!L`;}R?^x{ z$md{#6@tu~%_kv9EiLX*?G%a2D&>gUBPS`g?|p*r()63%VH{qXn3euBnimk&kjG7AZHp77s zsPC)+eEN^^lctK;&&oTUI4xB|g{jgh`bE+Z8EEKdygfV0e~=~RD5wE7L4z^zozApH z1GKM$4K~JR<|ua{xh~jgrtmX4D#j`-D#ykJB5drQAKE?*yF)^M0*N^FY?J(kbty3l zXILa^Z)yymrjXZzi zLp$)XI)Lk@8%7J((p*s#1B4|m$0$KxDr#J>S)yElnSys}&ZXhb$2Uj*0p z-*FC9ccnRm+Ft||>f?xk$MZ(lkvmglf;3bB*o>5NtIwX3V=hCi(u zM%u;VYd@WOg#Lj|u!bMqV~NFxSR>;_gx&PUbuFi74I`U*C&FgdL6p-yH~F1=D5lrP z`YLr=>Dd=F{MQRbqB)(NwTbK8boGz)g>>}N3^tL1 zYOffcur%2}&*A=mtA*$RFQMrLe9YADW7Co)v#BsQ<28)z`bW>8?PS*ABmP&)K2&W2 zzpzL&=N11)3t6-!IY~3<#yxXZ0L0T7~P~f|~upe?;=I}RTAafsyxqVw;?8Sta;70?VMGK4Z)wJ7SzL zf`|bQ%2VaCNK>dw-?B)@h8uZq#4E$V)W6PjkzZyrNW)d91SqP2*=JxS{v*baq|*LUs-T7|z*ky9H^N(5JUL68rUEePK!x!JtA_+p|uvlIwv-=Fs$p zgOyhS+KsfY7aFxGb;DVJa*&kg#|Skg)3Y4MG32&D)2;k0ObAJeDBmk$z1L^Qcrd-NqB8Y`(o6N7}<9EiBw5w%xiHdi2pQOIV7`})< zUv15r8hZ+OVC6&9_+MbHM$24j?j0oa&igOKCOYV3uXZ8bKE6D)2gC$vqIDy6kQHj6 z)Ky8NGE`Slosvhx3l5lIgTd0?bl5H7eS9e}BfHK4WiRS%T)T_6b{YeGX928vsdf80 z40nJ&zHl4FUBZ`#BtF8JmCT@-<|j_+?89(nrxlN&=HJU@3Ux(r(oWKY;s8e&!v z5oiYFVz8xH$3h{w$m93Ru;8VIPD}u_TkU;CGM39J!A|*zz*2_l*jh62ov|n-;9HYB zB!1d&KyR3(v`Yzyo;EN_-oURXg~HqSXx`}Q9KP&le-im~{95Ux`Ky@Qx*0jZKYzcl zwEzuacAGg5;9@Z)frN;uCG@bY`})^bf-KgIgf8?vUS(-Ba-UZ>qq^u>XINH>VG+bS zKq)c;?vpZfLAh>wC2LvvY1H+fv^$UA5$zPM96%QX0+V{+fk6PMGihUp|ES5r|4U8I zswxw#Z$_@#OAm$9th#4RM>)uhbzX#rHCh&f!)5UKtk2QNQ2bbh(~2RTy^gz#!m5iO zf@4j3u5CNy$*r1blDF97@x$Bdow*U+b{JF4RCO$soXTs&OZ38S%ft`D`2nM9E&7Z& z=~#-T^b^%#b)gUtRwja!BZo1A{Kad^hCwsS**a!QXULbG5hac5GeT*|_LK^R?nM3@ z6tVBOv)M@KYi2V|9bu{@`a^M2Y3==yuX2@37=;ri83Hguvb82Mk&sn(6HAa0ZtTbz zYyFgEuv6LIOBoqv&rZ>1oshqM73oQ)W{uG*4wUM!`dN8V&f4PwnhxP7V8G zj~=~)20Ct`Mjn{R6LBdvF1omMwWR8dG>WD$;RlvN4$QO^<8w5KMI4+KT?;<~QO)9a z@b-pnR|rz5;?DREu-t;LOOVCcBxi3ErB3sS0RY9_$Znw95#3dISI4-@} zXjSZ#{wZ%>@awhQmZd2)zwFW4W_O5l*;H`3i}h7`{b~U1$O(i)#bx3~Eu?Iea9|;f z!I{d(p^j4$OF;BfEH~zGwDTSF@5@6BF2Lp;lehM%Rc#25epKs((R3=Nfw2Zy#xBBt zLP=@0=@Lh23WIP|0A(tRa^k(riXdlYA@S(El@w6N8pRXH8BfY$3>?>$?kbiRd$@_; zlg;ChyJpU&OwdeAOA1HN&KmPl*NRuG;~NZSA`AI5y(*KJ`Gqd4KEP5+W#lbRB1`K&McO^QXK4Ph| z(<{DN$P&|+^1l5xho2wx`7}O6lehvaK8cwp+me1i@G1DISHQrZGpAiuqt_NxGPL;X zt!gR!Ii&cfN4w>Q(+Wd~2g?8~L|hR;tBUHV*QWzJQvTE5H))VJjVCgP3Zg?eFF^0#WD)-X< zR;q@Q!5d6wk)*>feN?8HnTL_vx)NU{TV&M#vZm$lPsQ{+sb3(V`y2c<-^|q_9HA6+ zJo{>G0_G<89(%T*rT_qcfPD4fqXyn0Ce>urDyUKED>cf&f06--Mq*1bAA>W|3yDQ`>#fN?viysBFNZ58MY`HWwA-aq z8SVAM|4i)v_`rDo*_@VgM@d|ly%YcU>whZZ)g<$gu;c>cpA*LRV#VRhG)?5~Oep~R zZwDU?hRqEi94?Lpg9Qoo$ao>Wjl78^hewQ|XE1e~?ywU7-iR9ojBQulHN1#_7rBH2 zNVg1abMd5DUVkB!<&RIopK#1lyv>PM3Q=x-9!(>aR4i}Z9RrYT>3Ud`e#pY@IADMB zJCgFTb*nyhI)OOKFoeHJ=z=KP*~L~WPmxy0jKh{*o9H7?crEpPZ__KVw8B4|F8l2A zqt!2^)6TjXb|=?m{VzvYlZYFtK+9l#NvW%m>XeKWM|zw~|LUfWY)Nr5MgM>=@%Ky&NOvDD&|9G)Kqnnb zq!NIg&&nt7$7nyI#4VM?A=jzXJ#ftDq6JONmnWjB3SG@yW+Havj^3{gA~9A*#8dd` zL!^RIu+p!YAfKjqHaH+#0?X{#pzo$#XF0bAoti)2h{cM!}7w7qc3ix+r)4C zpbI27zJ4186IJt8WS#14IPqlf5Zj~5IrcmZbrjknbeX|qd4f!JB$?4+i5CfJ(Z0#} z(c&G|d5#B#lwcJ2PAU|W_hI!iZdD1zumy5BWn?@h z69cBGL@^w`!M=7>sKZb;F!k1aVo~*YAEF#X|ubzryV*L1#3K@o~XXoXtpwT8#<; z`wMbU$RK^ZVv=DT)v0-fF0C4(@njgm_sS44lZ^sHo>PY}@Y$IlZ{wh`B>AvGZoP>t zsWG?nU@wSD#AHgU(I1Z1!U12AxVeyGvtK%L;mVH(xPxvIrrX)KGZ6L3@Z(PF4Js8U z_=0q$``e$p>EIcI2;h#S8%;_ZGeYS%T*`(hS-Eg@%5JQ9@?b@*xm~n5)9ZqcFE?)0 zUc_U>9>oi#2zx)thGgJYcsT|=;i88m&ARx%VrXsop!KT$!km}T$ymJ7Fv*b`06gab z=mCz*|0nX(>=E}W%^-?@@W%+TQzu&|Mt zs(&jSt+Cq1$lhePQNUsDI&>dwl1+3u0L` zvP+(~K4q_CNd*mDE7Mzp0(i<{z<~MkR8~^5ui1~=K*sBS{(AgP`--|IxTHFs{r60t zsLRUzqo-L8jqck;8{oMN5Qq85XJIY_w~wb*Yc`3!tK>HOX*79pU44R)ajPX~0y2}; zQA|e2Ed|h!9?#m;y{E;WSYM3gw!$xqTtlQdPMB;>wcu_|{ZNZ9r3@;LQe#5QHDM3V z>dOj+VKqoe=&O24-48!Ud?p@r5OFdAsdMc)d5}u;RMo#SGEEpql2*w5QAY>i2i^3ycp;f!u)1S(kJp)lLRHE#WI(>{os~_u*o~ zSk>MNmQo5msW?RAP-H@}B91NWG7#{Dh?s9){%=UgJ&?zBkvpsL@9p$LdS%n=|Cb!3 z{r^c0dL>mR=nSHKIVu~n<7y6_lx^UAAQ1N+olwNoHCFVi4id zRE>Mb%1E3p<@Qh>aM@R)M_i$nIjP~E&?8YS%j!uXTfyojClpYx1RWcEZ;u}09y=5^)ITV^-2JMyt)ZEm`%Or;@b0EfO37I2^1S80C!!_~o5&QbzBZpTs z_u`R*RIh9KF63@PmegeXQlq8^QwE3v3%C;F6DX=l+L5*fI5GzEV>U6U7Mm1)I&5y0 z%BC_6(|}*CUNLQWBd(2Fftks&?Jb><&^rq(jND(5x^X$bsmZy z`R_8E&GPMmPdUtIy1P=2m_MzIYVYaqP^g*NXNj*a;%k?`V2 zK$uUq{G~Lqxx8Tiz05^Xs_!F=QRFqKrE@dzP=BzbQYa*XCRbF$oN6Y}=|aq3LX>t& zgYJS5U3sl#AZ5C!=Y_?38me5{aLhvP-sYd^d{vt`--IRjef~PIlz)Y}>wtjXu*Ha2 zJ9EsMa!R5V@mQMYj|w$Hre`<>5pR+L@k}|#pWTy7GD5MkSz~@bNEp}O9;BCMk-jtP?%qzv}Ru`8)6PWtf3yLvjFEl8$+G7^#S9Xc< zC!Pn!A1vU6%x@c%I$lq^s*-bZhTpk(zqMem0cY(}6i^xlT&DOlMrzgh2W0q>es;2E zhOhm@-_6{CtvdbU(D@%b)D83Q$@2SOJiJ~8O7K>yOZP`DBvv$36xzRY4EP;!?eAdCRy?)OBr`~ zTseM_eM=^Z*XkM=JuYQgbgJ7DHiXdMKPtrSKLhrMOqQJBS{334ob)1p6i4Ke4-`p^KS3lj3v zIvMep-rTu{YRuLmsLek_`P=YpUwlx4S+U}S9){|9UNpX<4u zmnk_WC4aR&D%(4s-G<;E<>YNNQBk+rBcNkV+cQmVeL%>>S0gG;NplzrN=^Q?FKg9| ziUegkoKx~Kh4E4r9|3zw)`}EbMfO(4rV-*a>3>6IcY(M=?^|(|9Y8($o8?Nx`hT#n zn!^7#;1u0sq5$`2O<=zz+qMH^$>zYpz&yD_q@E|ha&y}+Rz0N$zI2?NHaHsFeydGKAT|}PfwYF!!2k_{ogK3pi7;A7-KbVPDt{hH$<2hQd_4@oZSO;FjgrFqJH;m4dOTGY&DM+Y% zLwyAniSv7!$S$FLM}sml$X(L>3;w~ke(Z8lQPO@B!Wa|xC6A6*z$a`=h_V8d4`=c} zwR|cJYsO7x?nC+Z#VdJJsN8u9#Z4jM_>6R`kReToM#sBp0|Fa!(f}UH z|C1Yv;T~>TDep(ss5%6I8OoveaLD^qJXS_e^w@PAGB!ml=Xyc5fwk0)zWlam+uH%& z3@GWDro9vY-y;nuWQ6&0^-D&s{mjsG2Fn+O`5u9N3$@5F(0_r0CWQpN7b?LiX4Bgcs z;V+^{&@=31eQIqMvm~GA+2pG4rGk{AXj%f<2ds)i-i1lm;z+^D^hk6)FsCA~((ymI zNj6i#8bpH~tByvXl1>O@6O=e7ah_eA$U@Az^onIw~G@8>*jL$$#U zcqDPBHrRGUvbG%}Ga;CdNod4#Et%hujJGlMT^MqiQD_0l#Hr5JvD*9UJ zF4k(s`yj`)KLXHX&o(N<%A~N~XJqPOGhM*3KB?gEz;4 zZx|vHv^8bt*M-0LgA)qR6U)+kNL2a-zJ8-WG~+Uj!;oF^`9_l?FvhZN7x$8fKnNKg zNKvmwWsG1|;pcn&3{J|@POe3R&xw>Hn6r)Ta9Lr5APP`C-IGdZ;>GR z+S9N1khgt@brvpc+B&Xt78Zs>qJ2`I^@^2&VbB4Q)zy z3=>;rB9(#7$<5?N&Se-r1`3HUoY;NwLnQf1OOkb&mo~gDIJK0qYL2~Ub9`#>c+oa= z&C>`9$wy-@4aHpS&9kkGx8#Isbm*(IeYQGa-P|MCY&Uc@OvHiT7aJFOEZUF>!fZLv zGhejD)3IRt(+){tnPTLBk8(MLSYVZn)PEx3tWpv3W2Ny~c7lHP{f5*!Dh%cSV)hVH zD{+6=8I`fh`8QRCyv@({=w(dRAgG)o<&_s@#PAP!3LZ`|viG+F8rZnu>(xl;v2{0( z-ruqY(j+7&_1UvSXK*y4@qr>HcOSz>mOJW25N*jC`#?- z)F02Un4F1D>>6V!tWU9yhE{d7>Km!CAqV9_7_z*}VY56RASr>Vn!VNpiiGdb zbgn1-CK@wFvQ74$SN^lbyxKka3$vIr@NH`;Zas9m;7o~Mm@_sDC`zN~?9j+TacB?< z{0iq>3M+W=i``jnexiZjIFI5a`W{bXM28BsVA|GcAp-^=OQmL zWFN?#6Q2)5d{D2jhGKp%49yK_&~9f^LRN}1q1f}D@mu$Jex_&9Ll!}M#YV(vz ze&qh+t7_nS5DSSQRGGmWe#$i4{SSTzsM?G9^Mi!~xnHF9_}Yfqpfmrmv2n{uVnM_& zMCEgN(v^g;I$xL?+tG%Vzep6^n~?~XoYwowAN4H;we>xSTX*Zq=F#9BIsKaO~ga(nN0z{Cfi!T_8cBfhR zOMrF26xq*_l5LfN#vcI(l3lY#vN15*(A(-I#DQ_{;4=S|g)d=TcJeL$C2!O2uzZ9D z#n;hYkhM;&lOFX89~;3xU%GY>t=jd<%FTzcX12hgO$rR#irW0^C|Wc6(4SG;BaB&}wxKRQ*R&}+aqgTvmXW=BWAG&XPjqT(0)%JQ<_u|D{x z-hkBv@X#5<+i}AK-Pwjr6V&TeaxhnNiUi3+2P6;_PoKN z8e#U@Bp&P31Yksshk>f|3U(DaR~DA+T_fF8^XZv(F2?ArvQIu+%wEg=;3aufv!R*Tj>GLK#18;ba79e!6N5>;}5*O@6THM=O$%}uZR zyGI6@fSbpLQnXvnWh9ki59%i>v_jN*o)?Qtx8IaDrUH$pExC!ylLG1`I#hW!9HR}H zcu_<9P+}MZRPQKFpTimCok@$Jmt%I}>_1p!L*}#E!F^n_RqzfFGI2 zLN~O+W)BBq3dE|)>VMzNu^)HRdF^AjO2FZ%OeJvDwd%%FJ2!SOe3cQ;hVl&4YAJW+EDixH zc5ns+=#!?zY??r%;x2aL_tu=#P|&=K^Q`WBB*D}6$ym`E7RH=K7OMG2Q>k9Fa5=@$ zAvc%x89j8Y2@`#v4S!nhlO+s!Y2{#s!_Db#NuO$_k?|%bga(tiIQL=8T@hO_K@0Vk zOy(fUVlTlRe`-xIZWMymemj`JgY-lM$HJn1Gq<_)pp`utbH*vG305gMw5cP*$)lb+ zry|}9#hdIKN|#5dy%~a4b1AL8APE}@97-Un%zZ@82nt6>7xakHJ3KQHPhK#JdDzU< zkC1NG^8&G#w8VvO1oJ{v<8F(Xbcv@8Qyl>_;S@DYM*lC7yOc)R=S@g%0;lL>HW^5z z8T@>^eRYH1*x0{pB&emhLLe%zK9*mPg+-f~6*YJ`Na&p}%G;yMj{3|$d#~skzA$gw zxgY7|0wsqJ87{hpQI3XSWrfzYy!+=lM#ab55dHWO+`ziSne9#%v;)d2auL1i? zkF_k)=44n7B}sRDa}uhXEO$4p93zE@uk$ttH;Os@c?t*1RPM7&w$GYJ$_XbjHj9?#tC*b7MuLQpEmDCO_MhCCCo^2BRF%bQlHGhs zE{9TswJ_bU^mH!p5xo3|=IK@GOhMU|mme!M*iPTblTz*pWefb$OmyjLkSi>Y>5D=$ zWcA;ND%Xmu0^{2xUhCm)!@QCNS{SUl`Z#1g*r9nDpp}W{q(rJ73YL zf&zkkLZQ*3`$;uVxF{^wqayr$kulLp3gz;R>X%UHB$S&V6`gz_qqm+Qvo3qMZaZ&v zl<%|L9&Fh!htVv;a4w@z>iyO8C#Vr}&>g$Y99MS^0z@rz1r!6W>~*fdqupUT`9KWf zZ#si(eCB}~UO$CK&Vu#3@1hGN-r@qv^$DX(TN7TV9N?r_o0uqMJ^Xq4Vw1#BnnH93 zt@)_Hc=yH%vntRu@roG1Fld z^HVN(@{^gd;paGhL}sB1=klQ5Nqw+xU{8$`ZlFoYe+7Am|KAJ>#AX9Cs1PuNiji|L zgluu%x{H1$r9^E3c@MyU_K*D0;({q)No_47Ed^EAgZR^usT^x!*qWo#rNOIR-sUmG zcs-MLH;AkOuR{7fIjcW8CZr|l+!oTudEE%ylQP~ynk#_H)EL$BXoWz490N>kdQ?tA zbxyP={zErc-!No(IAQ%*oQZ!NhlMQ0J!ZrsYcFG=G&0=3afpRKGHNKvq|hVQP=1M* z4Z+qCPK?XKTyDU&4d;k*Ko47^8V`&=mP8%mBZb19--)R<+L~`&L>6H! zMhQ}afRN`e&X#bp+P0jm5xYbi`}2TU>ESDI?S9s~b8;t1_^BK1o{m_!k;HN8S30<} zJWU*-&eu8#PJ8;@rf8f$?W9$b!ezTy3r0Z0mOHjYc-0#^Xu^xeRQGxu42*BwGXZg_@=O4St; z@wok^ZR_-H^P~3+w%cW_eqb)z*Y-}GxrE*Abk~c1goDxxfj#V8pTBnEt-@iiuEAd^ z%iW2Ce0tf@ED$|Uk@SD@htl;2@ZW=UIKIL*i}(i|fy@KSbvh77026UqTuDve?N?fk zu_Tt~!<;qbH}u@i5)QkE6P_Fqu2Voo@=&<)RexHzZZpyy#e}ieSnKu=UB+TK)~Rmh ztlcF^;rc9X>QTF$5x}V@Yxjf{_Fn zx1BF%vGZhxWcwf7nALLeoc#D%4|h?X+t-RgBuGxD!{Jxa{uhk|~I^U9vnznd*d3|771# zPuAuagaw&^eiEzv-~6!; zE4g%7Cx3h}sY*4g({Qi4(i}54n>J83E5&6sCBuYp= zk9LJpDAwGxZf4*R>@{X2TUeU^I896+Q-xHLHxWvzZkrJP36HQ~KS-8Y!qoDkQ8`$J zg5+V(9nvq!GW-VXd-8EXXoB3Jab8z@(LQ5mvnenNWTQjo07=fn+45cyF&7xyJ>zq+ z3CSLO#aDS$2hG7@Zv!v=HY#Q{kFNJmd$%1k*Z&V?-xywr)}>i7E4EXyZQHDhZ5tKa zM#Zk!wr$(C?PN~fd%JtSp84jP{&n)?=Q(Td^{y9dx1%bA5dX*m?P-de4PEG2YMx-> z?IXC`_lvwZ+)0dL$ZU|aw67ON#|VFh1Ep&SU;-NZ15sP9S_fXl*@?PfH0u0%4$2p? zADn6~Q3M%?`kR@_(n)Ik(%K?~ChUGVnotrGpx=65!W)r{ZYRat z1kEhp#y9x>N=GkC3SkG&$Bl28KYM+-=T_rIM;e`owbtp%>Z=LELrWC_KD=MI0s z^MXd2W+Wi1hkWFGFELg0(?pfSlUKU=!&{!yE(oI#t;EW?VuJ!<(ao3wiMo$7F<^(F z?)wTtOf_C4*M(KbO1%V=_#e9tYKZ`Wq_RJUXI@(ENOKTO&JdW`JV&-Aw`3k3+Dy z6=U?XCO69-=Y@>np>Wd@y8B(8U@J(|1b8c^@q?)_HiY_K0IIe43)OBt>CkVFeWk_K z=S&QwSu=ezvtouD{z*<%9bm938aTSMox?G~xG35QV7l=Zd&| z^WT6y<`A5UmYn{v>q9CCmeO+m#Rt(uiehl+U(-*%k>MFgu{*dd7f%rU>7jsck#iQ8 zb(nv_S_I9H8U#!CMq)EFKz+L~*n+=OR(eiOCe&o2PU1fgF_+)g|sW-m{W~urf!gHU8@8E}z{_Qz0 z^oF-m8=j-dZF;S-(_igpJ1DADVz;uK7Ti~^dOytQPWW3q*LdWKA@z>~E#2_X4`foU zPuO1`Z#?z%8{jr+C4THN&PrpC%=T6yXo3p>sx)MPQ!Vl(GYa{@{PQk@Ar9vCUi)$# zE+yHvdBu;_lv>91L@4=YYm{296vv9!lAvz=rbPpv>-3zQ$E$_*G9c92oZ`1&dS8H!vY`osLinHX{j9k(_ z8$TYvBSHm`R%OVHfJVvc%)m-~W+RXV{WQbk$N9}pYz6K8cR=k=hHY5eHe0#@H@8gd zpAgo?%boBj(V4KbnI9POY}kUWofoiyPiOh46CD`*0EpXz33%4yKuk4evp!d6+mew~ zPR@PHC{}D6$0Lz#H3!|=DFw{+T!X)n(0kvC$D4OwS(Vp1tTv^4*whoR4FRi;B4B^^ zp!*lVBp+Alr5DVp%?xg?sw}`;#|2=g+~xUXS&f96z8#gB@cK5(wZSCh=TF^KwKKSS ze6gA20c)He8=B}7zC^7$8?2TOlqe;vuw1PVmV+`FSCZf^GJ`USa4Cm7c9E+IsQuz5 z#SYU&Kl2ya>K5TclAiytU9bxyW{xM*(m@cTt{ButdNk_S(S(3TNVC+ZbF!E3uOq?j zm=iT8R>_}ZC_T~MH1(R=7n@Ba{peQ^YwIJM=^`5j(tut&g<-VZPbnn!loCEE`4QsR`Fjz>$D-7#N_EJdqT$Y3NX&?_R& z>bVmmQXDt^oYBm$lPr!{S+Hm)3t%vpHW$*WhGm>L7Jq(?sT4D#J1TR?FuCCw8tj#U z{>^VhhdG7S7~&2^hW!HCRD=3)YD)D-x{)9R{60T^w}Q+}kBgr)DG~?3nik@g1iXAq z5K~gi1jpd3BXh;tj-l1I7c4dG|uEH{{KfLbW4iqgk*KpF&Z<3eGKk#1t zOWCz**DBmHrZYUvX8(6hJfS=rLJ`bglE(TGOqw~0MdP<{p|4~kY1Zu=mfokBZCH{@ zMkLanAK#NbJ}sJRrWw<#Xn*=mGx_4ldIUmvLJvDg80N`Z2xnx{1-e_Z6(A)4$1VVf z)g5K$m=vhZcTe^TN>&(7SZ>IcuPg8)NdQXza1%yqP#QARX(AKDP{b>Gv#80Qw{Vpo(BxULs9o*FdWv_8(F{6pvX-zxDM^ zpgM#zY@dA_lNJ&>TYXj<93p1y)JV2{pe0S;hBo;TyeicAzKWkShzTL3X~@NfjTW5( zZ7|URLaAJ4_+BysePj<#>bk3(wx5i^x?NzB^AwNY3^YL$NCIYH)}g~^+9D*^Vcqhb zPFmik=dL7BO7}))vOCU3%z=yCuJ!P%|1+jW=&Hf2^E_au5&d>tuwFmL+DN>We!6*@ zzH{A=VG+g`4~^|HPlDpCnd?kLK$pA#w99`#ed=nF86CkubkZZ5J~a?lD*0u1Of^$_ zRmUzOZIk21hewFgap=QEm%0u^ov@z&wG z+|rr!)ds){c83wlyMrEWh?3ahKu0%g789|xl#>ril$a7{EBiB?I?5~et;fnS$U5QH zpe>;>&XJ|rk0WxZ*~}{yNAkz@YQnp!>`No?2Ui+?xq;dE{G~3X!5znFrzyAr9kUXQ9+Swd8zZW=5P_PYD<)_HcPd z0Ne9VS8SO=W?jcvFcwL?;t(n=VLn;@>ZaqyU3b)q3DV*?qM7_Wpi*Uc=BP<%$x+$X zgL;v(jYraRGx;^0Iy$pTQza1jbhL16&oH6DvoWBYVVk4HkNr zb0zGd$t{4u4F*Wh4CM_J^3f10-(Od#D{j5SRY?MzmGqI-5(E4QR3)f@Q(=)Gma`XM z7>KlHAH^4yJTDJA)*pO6n%poecn@IaXP5G6ahx1~Cq<;eNewFYKuYQ|nr}a}+gYrz zl1orARD%Z+2HG0|NTj{jO}+heT?7C3_iC*RaiQ8}c|u%6gO4jc z;5v0)#pmRv8{ANTA}I+x%2uuO912+10w>?}z0&Vr94bJtHTCz^Px!+JRe(H(rewZd zanYYnvn}&QMoLSAu0F&{4&N4A&PbXxfp;Bo;DtJwM+x zBdkiAL+0)B3?oedkI`&Xf9Vw$>H>7f69(W!+r*XNv?w~9Vprzl7r(AlVxu80!8Z}V z4Wu=9&<-|okxK-`aT_GaAARbvsN0R4liv>^;C^G51Xvg}^hF;t?nHw-TZ5rFmq_4ST$+gz90r$t(q+Ieg|YS`X; zLOBWW0G7L;g3j%f3QewgE_*CYX6XnCPzpjJMzc|{1bgfzS58cPO=s#?2Q){ha|o~H z08O#8lMXEeTGlp2{8&ZDPA9Jr!~voT;c27^6k@JLRM3;2ORPj&WfiyH0?c62oU5;y zmzx3pYqk3!D%(7p(}+|bXG51ucrEEo_>P>WY(wTT{_%4O^2#+hl#+J+NW)?hm`4c? zVOuKc$+UE8J*&P}uI~t@YwfsWZoAkR-s8P7ixn()o{)`?<{KCXfQ7ZvE!Px9s!>qZ z7>4@T#Mb+wH1iMgGQqV`K5)U8P<6ngdDvOqsH$FpJ$#p-Xy!-w)*#+<{$ zJrSn#%_8^`>*T~sYZ5AFp5TA8ON+BfSZ)`;i81agY!`gLBl;GiYM*6Zsn-ZM1)^dw zVzrE7%==7e0~o(n5k$|UJc1kIr&$*;uifmP1~0=svTk`Gq2vsx5-Ju{=*`j^=GN{v zZrM3rGcv%X;?T{Ds7Y8@yXNm19qd8r%Dc+hCZ0Ru;Q zZ#GCym5$AtBolhg4K_ci7hSf}vN7)~#aMTFqglP;G|Mb&QK%kXqV{++t+j;-e&0hV-GK1l5|A#dWwHwr|bU{cFMoz!Mgg#5Owq&t%3pP z(jg&=PQzf%OSTuPQddMH-5g3EQDN2y@kzQbi$|5$5*-fCqUdT)3>Lq2;mwDp?E%O# zjKka&+pr0%F_RhA82lMRkC>_eSnyuBApUYh_6L(S#aVcM+(AQHtWU{fj6?Ox-1@xJ z6oynwl6e8=Jee12l9~&Qln7#ro7@)#6a{HoU^tOA7dSU)mT$QFr7HE~h+)3T%G9I^ zclUa@EQ;-cZy4t*UP>0Lit#yB0D%$e574|o^D9qm4cuS~xU5{`iSM$r_poO+IJjVw zC25-sNmQ5N!v?Tl>`P^vA3hePQj?C#d`AZIExN0`1`1 zp)68uL22kX$4M`{{Av`Q*jnWo9Q=aiR5)BRVQ$za*mI={RbRut z=;<%s?5NbkyUqF@1Tl@tXF|@5MM~~(hPQ=I=e?>X|>P>7b zw|%QYp84T#-mg^(R*YVNS9)yQwh>c%oX4X|w z>`eNI`HKID)rHSzg!1V<=(GFnXGee^UWQ&P1nm~;kFbvCucVrfpk!H9xL@PiIMijY zABkbXywA567sB_WHQDG3}7Tk6Fc z!4%2}ODNTy@we&ene^T|)yiyxu3f09QXIJ{cF@~7r|TMNzRj7}<`Qi5-ct5G5`)%o zxWmenH@dN+>yFDZWg)i5Tt-l=NWd;7V+NdvjojL9PVrmJMyNAZ4|?WJ2W;{T4OoMy z&dkfN6QHgOd<7y&YA#z;td$=$m;<@()5y#NMMoy3Uw>yJd)`g6!x$Bt0M4BaKt-$@ zERty+$Su{im?m*mXoq6uTL;eY$bT{6HQ(+&Q}H?z}B}HkHzBRSWKSA_3v45!9VF@H=zz@zf2o4l zIDe^vMB@LY3eE$%mRJlMq^E0kBa&y~R1$3uS)PQbi^p&EZ33>DsM9KNPKDJRLsKr< z+N35~!>++!V-2FBHX|!^#TaxsX(Ki zUD2Wh_@K>>?6%`ur_||!VdZk`Li*V^;4onZtD*pc;A3&-H{9(iA#|fXGHbRe3H|7| zxXCHJK~bDZbU57RY~ayXx9V+5f6hdEN+21W`f4mr%itZs9Tklp%x>L&grz(Lb};0L zJ=&cp4`>cxF_%pOS(nR@X==O`eKWGjgd@3=d*ZchxYAz8l4W#oD$|VGBbV)j0ZFB4 zo`u<-M6AMtU82Y?^zfF`5M0Sx;7~n*jv&O9`?`2NH8c~+cYwPO*x>MQFPed;Oiwz` znL7OQDIM?mv6F4>Ph^0A2EtKHXqPc2W{iS6^6IhQTsX{y6|jw?j%h54vg`0 z$ue9PzF~;rb)UVkJ&*RZA?vj2rfDs0)j6)oHd?BT?gx6V?ix$95J$zYnUCc6?8B5` z+nS}eXm4MNpY4#P#*;!s$=g9AmSI-V`3)3yG*q30b$kF~En36O_`tB&PtMDCc#$`1 z=1azhD|;R8RCMD~y2os-{LT1La=T%J1l^7JPL7<)A@EN8LajcyDiSo19!Ulyu~g7> z$6?F2mT?^UONR5RGt7oSrfBCm(x%mOk<@?m>??}1B-R`%3|s~;#60gqE*q=Nl5e!=>pAp9}Kw4ODyF^|#t1i6CIJC7Bi zsAsj9{xPRr%rl>W0M|>5OeH~Db!xXhLb~crOLD)WQyz%W;eX77qVCdBn1?=D@=ugZ z9yYB{iRp7&uBRCCWg72Z;+j8_*uO}0nz-qSZCeX2p z4%EeYsS?PM>s9>7 zcB;aZ5=7D~3w##O3meTdvNL)HlnqNUOV9GTy+t622`-3K0vD){I*4y2SWz}4EGx@H z_GQ677041f+-5hM%cXL}eo|peBxl@g)kig8@vEV@U25qwIP+5;CTAzlR&e ztA6l@1BI6B_fr;a(U@MR0!(01P1DAW z-L2qdoG$`b&z1R4-<|ob!6SN!XGk{R!&n*Y)=>LZR!(DV$M-qq;zMBpcuj=%-l1h9 zK8#_ztjW~t1Sn4e;KrQ+%}ZhUyP8y)Ku?4K#x_S!tN54-Ivk6KvnoVSV}_4S7%34H`ydenLl zNKp|z_o4i{e$mEbNv2?NgDv5}v+r?A=28iyqGd4+)=>O(g)nu0!gnzuYdMr|y;V}> z-Z7Er+AZ44)}EI$T%FI>YdEL0EQ8px+S{Y()ayxcq~zuRUPZyjR^2TPm>Nxd*X+SO z8S5?2$)z!FUnYhuX61-8Uzt`ntK90x@inlXXx;2>V-u72VMnXYp3U~p9*lW!hemYZ zeL@`2xh;y&!GW;?KLg{TV+W3}HF96;&n@JCj@!SnY}*mw!^?}@pqOH6-8~NGcbBB@ zFJ}{jdtoa}XV36NR&K^!?J{13^Yg2)(*GFLy8A-?ykS+02^5UAqH1N1qk>9O41-M< zx?C5|B1WKfDUoP}zuYt^`~N z4w)Oh>*Qj3z$1{l{dFlZO7dTDqgT<@Kh4}tmvRDi1OnsKl~L*Xp{@b4x^h=(O7Z9n z7>WE{ziIOQQ>%PXs0D{i>Xve9ut1upvyY^vl~H#vAOGw*YcRM~s=)q;68-%KTlUlY zWw!n^G^{+4um#ZhD!-*juDZKsh7WXkTU3|4e3&NBk#wL2P&PcXC`Wl%32lk!1 z<~tm$& z%OXGw7iJyAxDDA5ZBfD*dd1&4`UNT!c)4Hz6I)Oa4<~Rt2HEU$-pZtWPpnVMS6qH$tg-UNx@g?N?y} z+tpXAnfy3CNxz>ecBM8p4+xN$)~q&6owf+DZ`e7_=#UDC`r~NQlw%mLy+i*(r3tG- z%H0`sV8OvW)s!g6I1@>LT-I@co>0CRH z>~E7=2=A`*kwlp+`2MCi>FW@MD$6lqO`vj=8+YeG+Cq4ro{Yw=yyWFdZ?IWwSMb!k z)x_uL42{}QwMlPqhb%Yz5%k>?3XJ4v?(EX~H0EX=L2>sT=H+TsHArNDk88_N(=+sy z{E@F<$)hb=Q6=i6s8(6&S2v!V7Cg<%!_{_MeJ_FZHwyoT*9#!e3T{Ldw6R+!7R1YQ zW>DlhNxf4Gx!@>>dnrm$sGLn4PnI(ErjS!?8{gH(buY9^%ETXfJWNvPw7zzSALz=m z8RMZ0+fC8CqC*T*ejHk^s_X57C01pXhm*EiEx7?qlrPKfsV;BNQe;yG`dLF}YoKoF zfZ6MO-nTra17+GaLn5WRHa_GwITS+jA`c02dc@OV^Bw4*g8}%prqqx_-3lQ)TS{!{ z_?XItq;}KB4g8?vjlppLP^fa-KL?dp#jIYgCM`5n@7FJ$hnLUp-<6^QfOwZ=ieZy6 zR&?wcDcICQGM~^O(MvqCDn}uP7srMA7wv2Ls*;i-A8VC#&~k?EG^8ajjypzeu*dygP5zg4FKx>aYmdj`)ENR zYL{AsQ7EMgOR74RS!>`9tcI`^wg;U4QMpz4^yi?$(%`9u471^Jl_jGbR1?l~wMA5J z1>OZAjszm#$kD~asBcU)G!h>Z$b%h?{kfM;2EOMQb#6Rq()eCR<4si8ub7}~p4XC< z3F1YD(~_OTd`%sF(&RM&qVBdTHtU}nTcb8zsz$9yT}V(N%-^faM>g)>Th$3L>Wt`h z`JI4x%UK_21u#Qo&2u!p-$T%!`l=8hHxABs{hs>G+P6|pBP0-;z-rqXo z`aQYgkr-f&Spql%Pco*M|H9}?7=QbW0I&++Gb)w9{%?kREH^aU5jk~2Scp^A7w%g1ABFAn61F^~=}OA{kGt@`9&4G2FwP5` z)?UEq3?*F&$*h1I>AXkd$_(Gwim`uJCPMxb-+eSbJ#XQ{=VQvT)S6hi{0B8%e?WX= zjz_f<+ z&OzVl7v9Ep1#=4<^CpxEXn9V|8nq^c` zRH(5L{cW?H-h+XSfuLH-n6Ck&q;f9s5$hQS`B@{X4M&6Hn{=}M?QNC*+mA&-hQnRb zN`)>&tTnv2NYX(l3MoOmr)3H&$N+=&=s{iX*jyKUBk<_Kh%y{m=GfTaXVs<*(oq9N z=*pNrR6YCy^;kP6C8J+4p^VFFF)1k`#H-4LXuTel(6Vqw6~v8Gl48`ONWXiDBT-ar zEjiFK0sN$W*h!J#KDP`h&TMh0f@E^;e8E*;lWu+pn4+~bm5xO~xyl5xHnbQd;CYr4 zsSBaCC%Jj#i6WV$!@XY8Fzl2v@1lrP8a!Cs_#9bAwFC#e`r2oPbHOba;9Vp{4Q#H)ii7%(NJIo;ydUi<{<`9I0xK^7U1FgttwZm=XWj#dY)h|dYbm}wl#B7Ma9Dq zwaVA?y3P^(s%iq5dVV{T{0+me0L8Q7Bs}|f@fiGFJbnN3;*t0C!y&V`^GlRu&X>Av zjVgY#6->(CM~HXej5QzAUyFW}fZMU(XuDZ%B=57_712ILlW%%$nEHrW&j}@sr9!LH>!V2bmNShW z>?WGYk`=l#nf`p^ojT~!QsBi-lu7?Od+CXr`0>O=8}ow%`T306Tn;u%yhqR1o)#yK zU{S7r;#iR2SoQFuxurS^nDc@?2J5B9Srac!pBYO{q0`t(rMnPx^~`TR1HS=X6^R6s z1Ip<)Y0WpZek=>2EhbjH`md(qW%9EL_88mwD{uxzn@IeMTE*xZ=89IUs*mE4^=vo zYFAF~g5}9n6=Eod;4+Fq>sE}Ni3Pq9L-wYwK$`>zTS)?Rn1#jG ziIbJ2X@^xFv-gp&+JHXd?+uFf_so6@=KB(3>%3I$k6GH*2K;nV3%^KV{U_2T3&Onn zA9u&U%=dr&bI#{aYByKq(n_~~#HdRDneoZEPmD#l`QfZKSnD*h?PF&)Q#+|IXuw&k zp`3TQA~Qg+JYK>=%+A4SCy`FLN9G_%QL!|>Gbd`wPA%b9lP$i?gRdG#n&4H!xNNTY z(A>*5juVEg4clfOWaYyO-n9BQF-Bua+)s)^Jwu492;(8KUujgto#}bG%qZ!=jV2|H@Ev&Xz!M$4Ab=V%`=Q2^1_-e5vc^M%0$P3k;@>71g{ADa%Ha?Kyg0K+JvK>FfXo z^!_Gv4$N{Z^=c{ATVr@jFLE;2gPi<2yCCKxT6OCqe2xZzcxI(tqHVoM6+Mx zu^Oa)Trt^kp@77Z=MvJi`Bj-)pu=U(Fxj38fMCp8Y>1jIJnwj?u9x@(oOgrd)qenY zX1Wmlpw^J(H;vb?(--6gfg12*2gtsJ1^~8vj(Ncoe(!n@9G8>zL)qjc#HnO;Z(~f0UnqKvIn(ux*wIE6my8F7Vkp} z9FtBT$gc$twsE;#d3eK=nlyD<7E5n&yV>&<8aoR$3G}z)m3li?lXilti{Ih9xvMFY zZ`TH!tPZ#p4?c%{98i~Gtccc&9*w8qkn5!AeciiDvjnVLm8+HkY*;*vk;Z8XU4)Ms zyso{5nc3V%@;1Q&=&D|8^T@H*nj@x`dNCOA@e?gz)B(Es_in$s{|oi^&wX($@OMc7 zb|;kgpp|D=xt`vAX#`h30dBBBI3T417y%Ga*v-yN!i}KSOro7;xRwK~^Kun|OGhNz zF=RMLiL@=61ry>R^s2&PY*f>@I**zR5ITcjMAofJh2+m7> z^*RyvdT@iz*sW@k)&CYQ05Pi<%nl2Ioi8m z84uIrbwp-lGYn)^dTa?eRKdSS^Zhp9;YI4I2<*8DW)-ApgWku%trk?v1Hm?omRxJF zZSs>SlyhqWRq$Mc#%93)35^ zQyXch1yoBJA+Xc-Pk=W>qZfX(ZvhJMoy!j?SORmUU)?)#woBc1i;v) z7q3NU!^IhPtq76yq)!=(T7tp*HQ6DwN+^Rhfl&&|e7!G6UBc4>ZE7xL?5}C+@PwZ; zn*B}y*z%!FqY3=|!VMM8>#4j^R>SroJ3+nS1UTF~^Gj!z6Sm>bLZ&9LduW45S7@`3 zrAQ(IYVNFW7gnT`eV>8g0F<+a-V{{0=78w0%g^C~meYwc3N$lQ3Xz8x=Ohp4!BFKY zgTwg#3}7{lhT#tLDjDDs6N8NH5_E_x z+X4KlORF0*h@_Y0+T_#2nP4Kq1VlI;*N8g&AWp$xG~^emXS;)TFT?5YAJ4)y$&SiC zq52w{Za63pvE}(nF-QXdo70t%k}DYXn;rL<#|M+@+r4;ENs+EfimU6CTUX|kb7w~O z^#QI+n%RnAEsj$y%*`AMS?##jEq1cqrG>n2s0BAC^8)>akMX{)$8(be7cdA5BVc^^ zR`fmpAA9e~|Kz8hBLT%i-4pT$=a5%d2bVHCEg~kNqC!q(9-t|^5<3-nB4V`ajxA#z zv-tA->{{g^LQ$q#&6omtJ7hvD`#`JK0y{D?Vqkv<;&7@wo+DF=ChyDVJekUbABLXL zXSlHUty2%LZm?WA5q^^eXQj~! zY}R-z==%g_hwOVf6zLtuS*cBu<8Ps(X;PBFx?C!!AB!(@XJGEVvp89ety*4N4O z0F7rFnt@zJo14%&QExOhX{dSj>rOxCC0RDbMMQ+Osi&#;k@LzIY5q|F4YO`ElTq2_ z2UuJntDW>v)jwV+=+6p3^~dDNw#%LhJ!%*hz~#x}MLX-HL+^utm&p-ScdKb}5nt=@ zp}f{46JS}(%M~UfnSKh+C+Br`(l27}ey6{9lkvgj^0b?Lz~QPHcbdRZ(x1Lo8h1V- z%U0qA;19UDpsu!&AS(n0M9I728)~8jYE04YB8WqQTr;WhBEsv*)q5x%{CkqcS~Z5b zX|H|e{Ed|8VUEJs%OQQ~sDZj${6-m3PhDoyy)f0Mjm*Uc7E8Njs408&HEm?6;iY5x z_hz|lkLN%b=CM>N$!DbB z{dtu-=sy+O_y1L)mXwfpUEUJSO@O2o0wz|n}Y=Cv$#5k&rML;ZFXObqCZ*^(j7c+2pcX_RfEQlenP|Zcq5Sxi8o}IjB)>{*6{JD zq}Bop#`C7VD603m1X1W&#*t=k&u1cnht(X(Biw9qq;g9y)2-Ls9ViB z9FrKtfpw&{TZ?YJ?svt^!6jm@+30*orBJ*pDXsKH3KwZY)`h8BJ8br`6ASA zP#U?@8N*ttZQ@GV<1iQEJjNac3WjU|IG5d{jZ@;7QFNE)+MY^NB^4-)PB1TS!7oQF zPtF2hhK$B>>meZ02K+UAPWTmcdl+nx@*7476=OA4YrwB*$fmhoMn(2JvU!VFmDe0N z%t4-a4zRN(n3qBkV2USTqU8Bwva{HJia9kkd{+3;>!R;!LOc^TkaRif%j<0R(u*|A za1KkqI7^I{WT2${^Q|k>AU^JCi_>I`!d!anD`0I-Y1>kiB?W3JStX$L9>%S9z32<; z3vtfnQv$Y4p`3_eGQ07z5x+$=PXeG(WS`fFbW4QkqI>+2RObz4UT)$yp$;99GVzL3 zd=nqJqU;Cp3G{uU@PmT(5L;j%s7+ADg4I zks7DVg0SmZ(Bd?0`+%|n-i-K5d9V#WzRRQl)@}fnx`9>wD;EEk2=PA{>mCtE{rMn* zP)x|#8I3I(2}IlTbT?0C-6A?e_m%gi@`O|_1BkBkT0xOR>ph6l?8Y1M9j2AoD7d3N z=ktQ=CH3>|F?4B!>z8!(?1P5TO&gGQ{p5=bitUU7#5e>% zG&2t2kKF0rI&mvxXKO^5hWfl&21d`>CsdQnY4^yK0{ey%<8 zxxD&Mo*GKMTCT%Xw4PC4v^pHn_;j0SnMs&9-Ne^qFQ5VoP2z!iuQ`y}WGdHTdCj(e zsE2w!ZhW2Kd3Wh}duffeo$tJurt~gwKW9lw->$TIniRS^M&Eq3Ac5mLnzX$M4Zhx= zrR(?_&GU{mYXZvw4YyL+8K%TFIgl_R%Xu4S{uU$fxYKIgf&qPl9dw4_Ig1=43z$aa8Ksv71yi~BIKv|`?I#SrowVh^Irx7_^-y900yUhkEJM|N)iGnN zN)AvS1Ml)QE6xJK+COBhcGW)WdIn`iWN{_(<9)Dc}W4OA7^Cr`wb=pMBg*R>uxxH?3`@7 zWQ2aYgi9`gcc4`aN*N@ys7{rgt!CTRj>EcVGDg*N{gY}q`}e5^Ga=nsejnuv)9E(M z$`dA|l`@&!>4gy)EMG0>CGd)lC&y?YnH78%LvriMhLPJ}7+%+yRkv^&w2{vs*1R!MXFu)p{yFqfpbNak#urg?z$IOlJ>o#y4CRH-)|{Ho0PFh_@+C6X z1g{0Eadzx}JU3xDYT%X4$_XGHb?Zh$_s#}6TdL66cGH_UB_b{&%lqCl+a1DcviY!L6@-Lpl1?2GM6Q#2 zTJ!8>=2k9<=hbSmjbhfgCw{UWD*Rf<)Fy~Z7q`jA`^ZPba>L`$;HtY&$Gv?wiW`B` z$rQw3K(>TIZ3U(hME|b?A_1>Scv0p52~1^|R-L5%`NcTz$wLzYL5ZpPQ|H0Q`(syi zvXI!OWpFk{nRC@V9IpdEW~wRl`*xO)&E1XY9Nm9pEjnN31{PA?TED?~Bi!H>#xHpU z2Ae`>9u$cL_2u&xeI+e78_*z8gCHBlcD8^o2S0=7uubry`gU(vH#YK#p=4Hc+R1CSm5Y&!H{2X}Ea6*fZ?K1gKX_{DeX08TtzgCl z^+{nB5PisZv&;S?Y=&BN8f&}cAXP{OdIp&_#IY~Pt9~r`e>ajim&(Z+(i;2rs5X5+ zFjY1cJ05c5xe%UPSBbW|%HsjW+&j;sqEf&ONGG;ll8n*C?=dEd2$@T+5;P=8$)Krw z-qe>K#4E>icWI03sFP3b&i9)*4I}>38`W7@C zDnArff4HPRW3e_fo#n_wk*C8UYIjM^i3DNs%3AV%ZfNgv6U)pMR+``{;3Wi6Iz1NX z7U)#af_c9(3}C0PzJROFp!N7cY>Q~Eg}NH2=@NfyEI#_-%sZV6r$9FAyfQRyaN51n_!muVAR%FLHs^;lRLw^WDh~IJwR%=b?&5W=~r9O;?u67tu&q6?*lo z$cvR`kTv&G%T=*ql3#{#svmRS=uKB;zGK>fP?5jB6l?&J`lLiwH0Z%QIT1DI^Lo4hrDCwWXvOdJHvakov< zX*B?U@E!qcD^P0+oI9;m+*+?SFj1ec)PcTqJfB`73pumAgt}J-E7QO%A6yq|1H*)) zyJNPP%$5Wp+xC|lYIwSJ-%AT`VlR#-reYJ3mcBwBVZL5zsqB{g4y1nQXXERRY~;~A z&oIg2d+@K7_kyum6e|SUvPp&Edq$MB zgA1i8;z5EvGPKe~72WxRe~O#g86s?^nEy0F$j{gc`l zP|gEG+np5O*#pDu^tR2?)lIdqs>~G4U7_>a_`PHPnof+-@mWC@@UWo1?}1NoK$4VN zJ=fsuEl(Sv(MImSlQK-{ZUCK_D;k1fZJCsjBlUWgfLdaAye^dIIB!f{Kcsiz;(!zYf~&avA1ZDIt#`y?hQ|ai=fVJc&f^SC|6< zdRs?%nVNcxB~SCr-?fReu5YG1-)0D1RyY0*O57Q~L7G_iy*lJi!@Nd^s#qKDU&*?jz9hq^<{ktQV=lX?}Nw?tkOnJ4)&4vNk;Km=!gOxQU%k zmSn#5%X4}x=TdrxG{~r$^}NP({D$0#rml#Uxxeu@WS?Ocu!C+pUB&gfJwc#KZ5AZ? zHu^+r2PgYJNl^n7pCyNTL2trd$H27T<>T}hV*~coC9{QH%Gvu2rj$LeOHX8W5ns^A z?;OJruQY9*7SbrDeazX^DBRe*@8P2RWhB0Fs>JK{K~Y%ka$gkQOIyX3&^AG~Y{2(z zY~mrC4FP4z-~ONuvO5IpJ#WlJ_tOJ|{0HyjS9B}CsByUM;iQ{1uC&{P5>}FZNC^X*6j>xavuJmX6p-912t3IfDp3hoENq66_2zbIFs~ zua3jr=wU5mzV5o@;FrjQCkYDQ=K-1rg%3`GmbJYTg5EZ~A^KU+d;1 zkQBbs&x`18B=qz1VfRJn&n-*rEF;E(Qj`g`%lyQN8>F(>m7{Ocj_Dsj2=oKQTA2<8 z$xo9QHDA&bDu%QR%IE)xQ<)VRW>ulQLi{qyLcB4Jt-A(co8Yi;T;ZRmBQ_x%;6m_Qf`~iz517pMx?iF8A_>$Euv}|~?U|8(=Rtf8Vp^lAwyWl$mzd;-*7n^NdaMvPt zj?Ric(L{9R^VH7x6SIzO4Ze{BZQJX)#Do(@_tOtUSIkfXxQgk^E;yBy<>I2ZeC|^l zfDHiV?tLV?xnhp(Bt0I17C*_4Gk3^9d#T7`^QV>JRHJ3RFF}&Y>(e47$_Lp#`Fzc8sR;0;Y8f^#K%Ky7x)qZ=Sa%43`hXSmB1vA^ZAm=LZc|+VEj8jcZoU zc+vN^9p8$qtz~4%>>}Zv>my84fBy~EMivWq8gp!8d-B1-Vk%Xqz^iCnLcqMWa2@Tl zz1P_0XHx&wM=GpiEz2uMMSHZ6_!1W)W91CFQCv4U zH(^a4U1+j0TsfXIhY!U?KKavAb(P{8w$mVnfP)0A?@H*;GKEd4#J}y7D&#IE@0(HK z)zeia9OD@-WBKZK1pN&~DCzh>YqBMOb^F}=#?#|5>OgAff>diY=$&@U_hkTG&4N%~ zn<)XIB2@tETSGx#shZq_kLP_a%il)toG1SQtJ-s0@ADQ8U6(tVlkCQq0?C#>@o@9~ zZTB@+Wv(hQ=;qh{yf^%mlKh3u`&W}U8M_7Ird{j<+B6m|&$Fcazne4$iW=D4g%L7~ zcgOa=Xu{7|aE(pew)uVSfiE!}SEI)gRSu`fJ%p`g zp^_;qAjmuHA3p`dJqOnSaWE$KfL|ja+Y@UZyfH_@jjS5)L$SRMqVw2(vmID>p8VcNgTfrDRKD=R#TH%JO_fP`yq&$4aax1+gw42`~c3- zARZigi%xvo33}UqPU*ps0XHTkzxBt(6*0j3>3-4?=TB5c79sKRH3{a_3_XTA&D~Tv z-x;)E?G)GE#)coUuV01{s(`uP z@m;9Hv)%n7gNa>V?hgcKI?xgc|7FR(K9e%Ivck&V1ZafTHQo+N9G=VEitn@_=)TQ)CG(e3MI&2n}4yxvZD z_i2Ru>UESCI*a%$36;J-Ov&=r$2Dr_;ArV2;geXqIJSZA?m)DHJPIMm@U;9#HZeOjUP z;#QPc;_zG8yJuX$270Z{4aSSxG7t1Ct#1D^B9YYZDXR9d9YXYb*+S1JI4eYX++MJ9 zC4W~mo^maDp7a|CAp!|g)d>E4!gOKVcC&({QLxM!%{&lo0`q7sWD7Prv>|M)qzKS0KwRC z@onF!geT$yoEO~rLq__|G-Z#xR ze!s?a0&PcZWjXoI2B9D7G|GpVsX}9Zo1bk@w4?0q?)KFh0cH^bFGy)EwoVR%-%+67 zT5(tMh3~p>*CUpSOcwS@I?|e`ir&0={S4-q)|G?~0s1m2SQcM4%D`Fut@Q{CyWL0S zO}gG6tNf(zbBq;9_L^K-Y?eVyGP84(be^diAG|Lc?h`5x2qCw@rQ7$X={r${F0ZV* zM5vzEaj5lvc|MH`pnT(ALC;|$FgId9{;vD!4A6u?v-+n_sJR+p?VW?T5vcI)sQB+J@(LNi^Aw|y_J8hoB&(#7W`A(AWKwjmy7bI=4WQ3 z!N`OkDk!8vo_hY)Rqo75tJ}Hmx4rv@`!Dd`B}OJtH-3 z>sBbU85{Vx&u>bJgE!4GAKfiQREWBWkNUZ5^c<8!EOne{cDT<%>viFTdW;zzV1xORG9vPR} zY-En~(K=uJAc{eA@={AMHtZ28A6lpEK+ZP>{qj+SduW*gp; z@EA#|gBOX2D@9n%F46~51EYNIr`n!0i^Jv|m0ag4&5eC&Colq|W#NC&6GnEIN!8Lz zX^*PsjHi4H{|bx1?^~`WH}qmbyyA8zE@CC{qkE;uqX*CEX*{A|wuUNCM)p*z2R~oA zOyWNQ=5Bk2%(+t1^!R_>dqU!W$1tRl{~g0vb1AUIu0|Z^uh9;6Rbi>wTGwF-uFNuZ z5@sV0G&yfQG+I?GuKT6~R2waW^Mu7%T3Wj3F>boB;=ieSc!v;QMP0;io-&OQA8eVV z80@C3u*(=YTU*0(xuS(Jz8T*OTl|C9Zu)}QKj7G-69 z7?tr7uIBnK&>;tVBe%k=wH`kPL$;e>Eta&rphsp=JZ#NFPkj`H{2rY;~ z49ds&ej!_X++@h75mO7qOz-s?gGX2dsmc{)Rcpzp z6fN>5E`6FhNH&w>(l!&Q)V@w6$}6mdfXiN1z1Bh|+fqJLtUGR~J}mj5)c>4Ty{}pt zj;{ZS;%n9rp7RuQhZx0n`NBxuE%cAcGpu_X?3RZ>!|VU@oj#D3J59y2?sFsjh*Y7dUAz`8H@9vaW6iccx zQIw#f0n({ewPFQ4L!pwWS6>(Zl)RgSvP^RasSKAWrV{_L5g(Y^=X(NK-jNsYav@16 zQEV+d()IG_cD`t*GEVVjn`r3M>iRa!@v*)>RncWSq}zPL9TfeiG+`JDk>gH2YwEHE zt_eB!aW5s=S@W+5$Z((z(4OmJ-6lWw!dWO$!3HpAdYQ?gG~}hb$^2x3j;BR2Z6-?3 ztfBj$r{D0A`)x8XiTgI|B$lO#2a#cgCk0#@A!0c}`cw`b-fnpB6PTmwz;tF)J#$9& zHC<%WmWcFh*2F$<2y>p~aa?43t<3UhX&ZhH*^~2jm1K;SNMyeK9-+YK;@ad%)??L8 zM1$YPrl0QqJh$5z(A{InvY_U`y7|&s>ah1cuFt&HPf?{O4bjhzDs%Jli6tC9F-^f~ zE0z7)Yp#hR7qG(&1DOX{e3e~5lrrid5p0MON98UeN#zgaZy~&YLz9G!qb?6~NGS_r zb)Q1ybf3ad{fxk}n9ISkQS?C)1&#j)`RYV!ch)XesXp*NXs%=1%gX1zB3}RDX5?n@ zW&d;5fmBo(OJQ^cfiwr&GC||XE}c%_F=9rd`h-w|BE5e(hky-zX{|4l0JRv8MyPq$ zPjzLi^%KNVVjvn06X8TtZ8h%yvQgD#TR^Hgwte`g$~%G&2sm2L3%9xOYjavRD}F_N~oZ>Is22r~vb_G#baGPLXVA3JvX zL~lill-DZoa%>0EkMfkez<#0h&ic}lgJx)dLD+*o&v;F2>XS|h(4xMfy;?Wfty0D` z_XgET_KP@5qAFiV`cq>OpYZ`qS$$X}l8__frU2&Lhft@YL?5RJPA`2UTkJ-&GX@b` zt3=*w6+7$1X6xa1F5Gp3QN+tXqQgxnDeF5MW;EI5Po&L;9ZOm8qRlw!$?{@)+)&EE zD1L9~wA_A@J;p8ckjXh~V$Gq=oZrxcyTJ)VsJwy}F( z@<}|5-EbG#5r^I&*sBr8-)+&oEx3*$>#HrFO`EbOr>W*Snj*=>cSh*$QohiX(D-s? zC=OfE@+@A+S$f{h+N2kKd2djRRjor<|{8hUu_WtZS|;8SCb9Z2zb_Z_IEtYHm+n zjY^QU`B&wE>PFj|j`)OO)Z#+!i`Ep5CvgO?R^`FUSC6whi>4MQ6Dil#cg?tYV~*Ps zW5|5TNCP?Xzz<+;e)Q@b*9X@ki~dLJC!~!1`In>?MwIr%{Sj-1UGj~*^n(RKwzvB2 zS$fyk)`~T#{okD(5&BRlS^IHyJzgpF-KOI}Vp2t}vck*#w(JXQhkjdsa6eA#*KNYv zzExRSFk8UR8zsP;b$_1Ud6=(hFC}rsR0W!}^77?6B6#0s@ul~N`&-CzhT0(dvr8tp zw%jwi_|K`TQwOKO7Suf!O{Fyu;CTNtRc6ay$cQ~Yp4rR7_KVKR=ORTM?(U;omKe5rVH3msUHp4b7-pWu=R%w4 z8U(;_d%aKbr;o51O2Reg zyC|LJWYzf3yG_WmKaIa)Gpbe&WQKppPLoQ5F`aD@h4Ba-0$0aD^Q$D)DaPGg%S)#Y zgJ8N06?&FopwVm3tLfr9xA)3^d2VaRf92&t8~1XxqTc6@o2T}#cyuLhIN_8$N<{_D z5lwce!+xpueZHG{mgn@{9lJL!O~5zUTYSmZ2028rf;8Ip--L^3kJzRge#R-t_bkIv zRl5soiXHRVpkvUgh1wXA&W54gp~Gk!lhxfGEGFh^z}LZ>12q{`i$G{DQ}c~-%q0|g zq;a8KoAYfe-k*`Yh5qov*RlhO!R4W@*EN>C?dD7R2AeEl4&WEE;HoM4WY};tuxke~ zrXE@)$I@kQ3~|IV=^e^$wKRwW#2Dg58rHxXE5#~>Y>9kq)n&(2GL4U2NVHJ_kc*-m z?W}ru^DPE}NKlriF|G+NF#WN)gOK(QKj5J{$jMLHX5Yx2gf=zxxJ4))bz9Ay*G{Ny}7%eDR+AV3qrPDZ% z)zeuow4Q3**rzX8rodWhpkb*OI^6B);Vs@&+V|niZsEnQEj@)q#Fm+S_39dtI>O4L z9l;z-1dw`I=e{0@XAkJA!ROj~Su1!mJ{@({-7S#m<# zLDNOqs%xd>LqBd=k%nB$_~*oLfk$a`#fMVMgd5qYWymjW#yqbcg~=j<8PprIO}=sj zq<9s-4l;+(qU?WB%-v|+R}Vsme>Jdi*`qUvbbim0K}$|Pz8ORHu6@{x7D^dCsoN)6 zCEsu}Jd#P%LF0zCw*y-(3sWsr7%zJ&G_y=J1IHhFJ=kxvI(KL6y0?)UAiv}5&skaA z32lJ4K7Vp&An@0(vV*980n$#%l{pEC4trZUIskKz@$!zY9Wy1Irr{{#aig$$fiI%y zgW~^SFuX-b*^w?^2+qID;;PDf*3|lFCT_8u=SZ?0D@>-c2#%`$Qf>sB#1IRG<_93O zX0-tt<|BYFzCe4`$8)L`JZYC=8Hc*$aVV9MWlWM`2kOG~f?vyrps5nk$MZ~XIT>CB z|Bx@pmyqrZuw%-?-D*2ar%Os(j}fekHQ04%wEl*js>LqTY%p&taW)+7?-$`s5fuzx zBGfM;%8&#^x(M4#lXGH@qQQ&yK_8;P;Gwa8H}JntD0XPfeC8zI9;HrD+d>7MLFauS zPtE;(ySy?&o5t3gb7^a@o9_3Mcw3zp+VL`uE|_YE?4@F*I!)N%mADL=xW75`kZwC* zHZ1BdG#mI}8_@wW74XwQ334%w5eK%$3%vCq= zo3K)$I_$SD^R%MSO9cRaequb(mzAbkS9>LWulW>4VAX>7(Z(%?U{M(sc{hn)p>-;| zW85HXiTtA&NspIFD4`@}#w4f##hPSZSU^{2LJSiClB5;xt}Y_KA+)i`X#_=CrozY2 z(#*ppcA=%CSqY1QZw-$|LvDmZ-6980Vl6y6MMnKJoX6`A=is(wb~)HRq@o zf$UKmXwze~c#rDwCv1cv9RA+UD7CBD<+Xrb!Ncy)wsmf){9*$Z+fuJ;0Q2G;Cycbo zpo_VN4PNC{iDk{l?P#1N+Ut?JkCdc|7jCpo=hp9=s+Ny5W%3++%bON)Q+fNe$~*P& zJ%gm2Rs>=~Km~MZT&UEluf&MLm(1`XG{!Ww5hQ!uNHCXe=f}*}b#w!l-8H@keOCk0 zIMV8_=Ox{F=4B(#=4(Z31z8ntkJ|(q&qwT|*7y4cZ94+JFM)xlD~iD$>eo+4Ues&egP+NDL|{*w5lrZLz(qfY;>d*$yHmo6?-+U}I*pahz5Q74CaGldXHOiTTbo9AZ)h_AEJ8mcM=RYLQ zrV6Z+vI44Y&nfInNU9>-f%O$CLJTy?c6}#GF7C}JK6}}BK9;%ItFQl4?*7)2f?kS$ zlL^X>0t&S$4Y@w21mCX8NWn5DoZp^uI=!@Y7LE>~aycIl{jFfb^Y47gwT`9Vie1q; zQ_y=R04n6Teq>Pc32=brxL-_HIMc0mZqDWsJE>0_$=jOL#n@ohcFVXB?ogH_ZfF`T2tb4Al#qr?mMNi5wzHSah5&xD|gW(+{WsS z*U0m8usTY&o-~&il|7==6QQBo91RiqhBi*dzA4-+GnRg0lbA7(oz-l)5fMNU>-KM{ zD)|3WHOtm<_!;FqEDAZ6G8brUuODnNlc+BDVur~i$_O0?XNFah>jVXb-h(4Xj*uZw zW*Hy~dxhF#X9WEQM-5kh83$cyR0yzXT*?cX^t)s@pZsaAA0Fv(H*37zj^+U{G)~lg zS=bbjD&a(1&&7jg|5%B<17nPQ)|y7Jx=UMUyLJc1WwoTk9eje!GV6QQvLHzgO&HR6 zg#%!6=Q^}nMXziiEc5UFF%8btBsEKb*IIlySw8=$oWiCaPs{=77inL3>xLqKJ6@z7 z>X#I<7s^f2YNYg_n*n0nG}Q%6%1DiE_$k#0_bTg2=Mw0%U3?a)YuCCAhLCnJV}ajC zcwuM&c&z>+ES-&N{Y3~uP)e0A2cw2TVzNOU&)qaXOU#)IEaG}Nb9g*zNVe=6Pc2Ycyu9X0@bkfa&a;b7=fF*U7dwpR1&(Ve*M7Mt*b?T}C2+ zwmJ@5ty~m$XW9*PQ=Q6#ft3gG>(uukVb{5~ zYuMJ}b*4RbL8G#iO==8ck@P}w-nzLo(P-FT)PqGo4yhzf6BYb#WKhgsz-p2Eo#NfJ zB4useNSjE9m%VN1J!pgy53^KN%!RuzihpVB()qpAV79*G4r>+HBAAAHltuphDYDa2 z5exf8uOv(?e6))>{oc@Lq3K945#GGR6xlChj#a9f-mD$gR3>)(d(Or6r1wRiM~2t! zMK)4MWqkQ0?D3*?{fjcxwAi`rowpnXs4iPR_e<)Pv{_eh8vondsfg(BCk}InQo+^h zc-pF9codFLFF6zj`X7>kr*I`dkB}?#A|xyle&fAIMrlAw8Zz`@)sPM$)?8{7w`_U;EvezwvP zw>|M|=hoz9$(D+#V$pjsHWTx3W^BP+FJKsx6#2{MXbTocRpv8kI*eIoDv`6@2Y^V^ ze#~(kA;E@)@d9qnonV6%c#b1M#U~usUO3X--C;T=C3(E(D6cT}aONu1$N6+id|209 zk-`2%vBg!rKj*lw9-$rfhVres<5!sAJ&gWyvddeuR_URX`2Sc|^*%xmJ_*SSEESKo z9f`MHpUyxzgjk`YL-3!Z1r*}+Z4dgv3)wg*e!Si>`Y6gD+V5_*TdlDk^Qv4Z6!G~k zl*>?huK7vjXt6YKCyn(hS#scaFzzW;RVTw|VjcCOf#2{%IA?^df7|OUfD`l6Zr!RZ zQ~8M5sMk>m5YA3r`Vri0NH59-_zboHA$ngkOkD^qu+bG_Ca8Y@8Z~+oMOlfQalN=2 zD0YgFHw&p5`95UWtEiY1aD-`G#|@ZNELc>syof3}GH#;>BFnHi!<1=JYyj8}3dn^` zZd=+UW-VRm?+VLz#mIzBpqBQ{^pO(Zk*9d`3$XT(1nNzKV8+FC%b1YMDK>6M!})Sf zl8kyg_ra zylxvRe2}l+z$Wzo$|0jES*N%jb+QTy{dP-Bc210rT?q*e3Qk^xjtddvL?Uk#Bpm+- zs0=cE+gK`51*LB;Q9ilrw9%Il_+GU^x9Fo@t1~W9+?mMoMiWkqbS$*>1j4z?(3|Hohc}K5SBA_oNa3B*+U# ze`6W6SvBz3)k_yN4pfVDHBdfm@x%m7V2n~{2BN`0wThUq%@*ZGM>qt?&SNj$P{PBU z++uBBcKepY~vwEIL@&06!Yrofv`y}1)R-W+E^zkdkx?T$8?S56(oKVpbF83*} zm>Dd{$`>Li{)L}#IB-hep2NDKALXE%CFZf+Z#hWQwCVp;E|9t(G66QQVRhGJ9vGG7 z*ocJ$owWz+_LXRv>i!7dia-ndZ;>VpKhms^;T4QRpaW9&J8-P}Qggh3p64!IlTJ5P zl{r7TljH7?C4oTYn|2-aMAPLy@8wl*|DJBx%KD|Lsb}*Y1OBpm?C=vh4`hs$?{I6R zD*5g1Wa*32SmZl|50aP=VWa-iY|)PX4%|iP(IN`4!iF#+APiRE0bu7-&r---Inx7}TN^ABe_d?%B)PHSMGS@8GJ$w%9@ugV-{ zZkH{yZP&gkVN<68WYj5bK64|Mq6e=BbNuxSKNT%TT&5(9K|QGUeuDLgLDTX2EtM;^kEM{z)04N zR116kuPPStU!UR_(5iE4JYavc1XSC+p>;O4l84pVcQo}-uBCed-pRfM5y4AnexuFA z-};7zFHZmUkyd*LKrr1FCFLeD2WIk~O0HPRucv6umgy$eRbi!HhN+N7zAPX2v`8&3 zz234uM;DhTi2MSPU7lClCpkh{hXH~6JQ)ur+23ZxQgzC!AnUb+AX|^<3e4z1b(p+1 zq~aO`(^~@!-|FJ0XOSdJO$K6)6+#@`So{qZ9J{K%`QCPbW|4X(HK-egkZR5te7;a* z<{??TdUx8BUTpBJ736@s6Q9jS z=c`(1FHSn^`CxBr{XraCHb2GHcW5cad{hyXkbqB9I3S%B(PYkZGB>{tZJx;u|8sOM z5fD_i1!$&Y3|A|V&gmbF`>Tx-`8xGFWgF9M;fMy*Co>HU9nUY-o=_TYE1^rmXF!2 z(f^?z-!WkcS*u4^L6#fFVQ?o~#Y91EDRDC4C;i+B%nTDP>R)i`x=(GK1v2Kk3HO^H z_tut|G@H2BTMQ?^w*0bhO5zl54w=cx)a+D``y(bPG7~Q9Gr{kLG-m4kCL1nf*t=J? z0wxUt9#39xd4wESt5hDRt|@seTUs84yWdL+dRjJRf18p6J?${kEW9R4Tr~g5@UBqg zvR=L8(mc#{6v*$P0dL+gSpJZFJFH{=yAu}!&f(Y`O~+jC*aJB~XYrvWyHyF<_-Q)- zq1$`hSI7Y_rzfHBr5tC{WLT2SDy7=bz}T*SonDr6{>04sWM%FIuw0dV8;^_~4*Nxz4k9h#6)Wr-pS`6ywgFi0)N)xz+@XqeQ5qAMmnTvSeKX z3DYKtK_JGLr{~)oiN}-qr&B6{`Hq{Wx$DfR0OE1_^*F_}M_#dCliVd4V!KGND!p*> zYRx^!U*_!}u#~uvhhu?ZAc!8LC2l2V2*>R;ZR53_wHvRvvt1iGo+qrA?ybOok54H{ z!a_-A7z6zWa4nDk|7i*-Zw?JSn(D6*5t_<5glvmQs+nJ-@yrAfR9)X3QEasnlh_S^ zqnJchrNzF8=-=nH2Rr0Ymm6!KkBdHJaMp(6KHaBma94|u(LQB_Pc#;?`#s2nIG8bf zS+Szz77IR6L~u-v5TyX< z`VDzrtwnPKiF)}7hl+meIn-1Rl@v!Ut=j7OIyZ5FmQd{J34mxIsbq(?gRQWM>zA4}7x^Zgs2N(;A9= zy``3g+iOI)Id;XZVrNFBq4ngkR1>Z8Ta(v2^5*-VjuDw~{V9|S7TK6>QO~>%o(D_p zw>JKY1Q5w}vY#V?Wi9k7ie)BdP1~pSQ#^~UUQBd^7fL*0t3c6v<;a3Rx8@kR^R z;i^vxnmThx?{+WBS<6hHZz)ga`(D5h+deGP^Qz*=^J0O&`6QResi%n@hl%pL z%JXgsX*lGPq`N0ordD%VPueFiHTK7UG}hjK%$=HyjA-2GKCjW|hME>OC(-73iTbie zcj zfSCLT<#6borQCj&FKj&L_EfP+f}Em}&!S1K^yXjfb)5{mwnO?$5HKDH1}tCHwjn^? zz9&Dxd?*_K_5T5aM*Cu%I5#Lpx>Nwr3r5yYCAKP=P9uV{&c%eXJ`82Byj>6hmQlOQ zbad{+ki0uJ;>bWe`8lq*z8_?1;(`MC70oR%k`SV;RQvcmCL>H zXZ5e0sG+FjurQ4(9(Oue!$QI9bkRK#Z%V(5|*n+dFGc6FIV8YO9Y$z}b6@LNsOHU0|s0LG4 z8qWbXu(hAU)WMSN_96_Q93Ljuyzjhjx5=r=C7Gq6S#63WlBSgCNfxjDS})pVRR(A? z#lF}qHw(vyqKYgbLqLnhgM-v14FkaZ5k)8wd~dG=W^GQ8nLXw0^d7G_%pQ)8vKPGPBI!vx ztU_Z@akX7ZMFV5ugge+A9V?>)#B=@clU*Fu%~AeP9E;;4OrRl^)&X-G#!#SRUZiEi z0QYv{mOt0qX_6;1Ls-c0&A4m0%YXA4ofZ>)rNJDo(Rg#Xvh^qrgBWkfhicLo^!6+AO~31$Rye? zeI#zXBd&}zVJxq-1z?$9`t*XPAJW;FZ#lXRQ%TqDrzJ4(j ze0_bM$Qy+U!NE%;dvcbplQz2#kMfL-7tIYbB^wKrhVwRAudBaE`^L_h#`;qr%j*h0 z9<_^%RC4)(J<7ISftNCI2~;OdSPE(Ne<_#P@@*M6N-#~h(-1)dXy-3r(tc5u054c> zh2uKeb$tzRZN(9v`Sf=bT!8m$mMMtuW3cvQk5<0qAEb;dARm9uvE6s5kq-Ivme!{b zaSPEko)o{GU(;eWaKl3VIAa7%5i)=wcqeeqgVE8_fad*lW!Yh7lxWt9HGmI2zfUme z%BTQn=;vBRW-199mK>B{`QenGwU94?*}7F*>W1^EZgtXw8O^qb3QXDE4IhFW3} z5!M-SxZwO3hJ3;+xEABr4^q36>Qu6n)s@z&-8Gr76q$z6qBU5OEQ@fYzKXs5n?GbEA&q?9e( zNlNU0G9}0?#f8^-5_|Us%%in;HW1Pvfy8Kx6W7q7FeS}y5V`6cA-56kxBa?HC@_<% zw$EFQhm*vdl_faOHN;-?QC?QvW|CeMwIybwE;~cqg+Uasj-HOznl=fI`Fdsod>!*lNE8U!L$KQ`LTfAqA z0>!uaZz3+!yG(yu;-5qu&D(flg_V8q%2M;54VJd4V&_h)sUotP^Kl5mnp>XF^BM(X z6!!tmjy|>)4Cbpe4OFTLdEg7i6kw8lV zAjC19zmnt(NP2`lZE$aOv|j1tJJk<_>%tFJ9iw+0IGW(O{Uo-~6kK#|srOU(m}Iy` z#1LS<-qE-ZXP%yBJR$V!xXN4sjiX8Y05oB%I``1{Dnr1r{z$`2G0$Nfi_(!i{q^hF zo}4$!*q_yLYa@u~!R#^&@jRMs3r2@hNXGvBu1G+?vbZ-c?9JgC!CiK?ZdME7_(=IH z#^oim0K|d#6ddKy=)Tca?{;mc-DQeu`|K=|#GnuIb#S}$ziGe0#O4^RJqSHzP5#g_G&AZMTj zUL;5I62&NIvZ1_W=$U`xPZGtdMy6-HO?Rb13$*ElU59YhJcw#OCR$S6%SbW1tVYcy z$!^mr$ZlGVOK`ZIRA#vYl494T;Vxy~Mea2v^%t}6y04+Fy*Zv&Sv8uebzLwtw9QS2 zz2#iI6LDid%qxyfHHu55Kp(e>mn(>MuaPzc#>tqCHiI=gm1zdI$TRb@l$#`$N%beH zNOn+-0U<;-g3FCNbMEluTfocqC}GhJ$uY4ITR||f3S#__R(ff6^$Mf2L+OGeZn1Q?xE*N; z2|+Du#eZGoq*c0%m)@weT0#|hr?y4}n3~F_kd>p~5kyXc0Lpk(q zKR-NH3S#nC^Tn7i;{h;%`8NqJ5MZ&Hb~0go@I{@0&|NtO>;OJ5G*8R5%C@96^}CqD zy%7j47kJO)5Grw7jN)Fraghe0EJ>m`; zicM|G@qZ-V6u92-f8VaoqM5#3&>b#K2p#dz_pzm$SbAJvK3}S1>0J(ayBPndLOn0~tT!)$a}(Zqf3ijxmus{9y@P%v&*U=mY@f?pDB;)3 zrklc=g_Iye;bV*-0yd-?uNN_ONqsIev>{`7?eMl=^k(<@yGF2M)n%ATJCGiYw#8b~ zA9MnHjp-9-UKX|F?}G8mzn%@;8G%E`$h)-t`l=<8@*^ z@G6>W;q$&7qv5yF*vpI}KhPtjXN-8F%P|uns`&7at~u=`H>VW@NJYLv%4jwTzIU`pn{VRf-*D%$;ch=zg+NaCDyh;o}~ zeFpXB^}tRK>2s}8h4W10>545m<~%3IZNaFkSrhyt>KqawYGt=8HJsgdAMa1m&NK$4 zI$S+Zi45Yl6Y>UI>dUQPAhF!#S*eQZdQJflY$D3Lg!e)Q$(AS?@xKNu)YEse%+uvp z)G>ckrjCnsYVD^(gL?Wfives$@YJYz%V^wFXne6T4h{gs{U*;D zsX^FL4Ql-M?u=)V*ILEWT-4a!AGgH(T0Jgl*SW^M{5-0!%RHW6fDXTJDHzeXdqih@ zz$)WJpHDg&=h59zp5-S{x9pm4?Iqx0_WTyZ*rFrCn#kr5D2x&qElGbWSf zh)mf_0=aCK1Z;#2RS63?1)ck0W3m;`S=_VL6X0$!O5zshi&h+-Hj52T8D&VvOg_aL zZ?n~(uez5@;e{0=1MoEcZPaTlPSLFFeuw=EB>a1cmhhL*1Q08PF1ut8Y^_PsgE*qM zt@RzDmA^)mTGB9KJrLyG4D8$MGIIm) zWj2vquSwnTqDWThcv(e31`jvBN5%!*6w5L7@Ujk%=kY9 zP&2cgi`5XSP4_XPPB?wp)kxYqM>$SBa|!;bq?&V~jq( ziinwyp=~?n-EPtD-|N#gbq0}kQnBHyO}^x!qRJnsk?krYqy!m0ss`<{MXEs)9}7{# zlwPHY0{;sI?%U;gpZ9a#Ew*}cEKk}EES_^NUtKBhoX)D$lb^VSk`QqDXT@a`AW;ZN zcHZtk*?GAENOoRm?{s;Qtp6(;qY9o>uxN!&WuaNrS?r8b*8|uKCH;HEj%F}V8UIyL z5?d_xGJ8#rJtS>>2celZ>zH!9YLkd>R-Be)>0CiOQ zyQ^HcDeFneryy+Y@4&{xg@zC*#%p@*a3$eqSpXv$TlasOeXjJyVz9*Lll~$h4 zmYqKm2y$klmrsoYrqdAj>q26&B#>ChD9zHIR7_Otp(%!MHQtu2P*iLJ?d8SfYb?AN zJtvOL7dZMaziHB!HXDrAMlAg58TZM63B4K(b{gMcL9vy9VthHO0S%XKAM4ptcd&yQ zfYK%`-a)@uW&w#job4R97Ro?bV=MSm9C=}YNN#g5+V2#7k4DfR_QyN)ZU3#MZwFc$ zQ|w2aZk~dcpw{-JA!wSmJiaN|qi_Gm+FJnCwJlr22@)JOnh+qk1Pku&4#79>uE9Mx z1P>P6o!}BYxI00EySv-B;GA>Mx$nLE>it!J)vgUBTh^MZyJvTg(PP1CBS3DS3MSmzTtC2?kJDya^B48yIuVx%M&v?g&#K!@ZaQ}0P ztDQIU z&ka67IOBqOLCci45QRMucgbO<{9_h6cFHDHoK?2NoifY~NL)Xldo9BQZ=B={dsfnm z)3$~9(j3yS;N+_R(gMH+G`1?>jXQ6PIV0*^Byx0nvcR0#Ns*$JENn9w4`HvBv>_N= z+7BKqnyhKrr${enZ^P+a4iIhNxnB&p+e!#7(&YNQzij;WCD)h-Ww80^LPkY5XW_2; zJ|J&2x-@JqZ_O0|5!}LAQWK1Q{~&0WSU(Z8IT?}j5}9AE%GM*UC*4@F;NyBh#@ZMW zNCUSbd}2l#wWh|y89TUn353LV#eJz=I{G@L=?ulqcmr{$f&Pc5J=2b+@udFpwDGQU zL&MTfXcu`esRV=(9u+dLKAvq&5LUQ^*?=#9FOnlBuXKiIi;|(7D1t3k!ZnXM$T;oI z=EW-bj?9L4jCenZv9{>?BIq(_SSbNSXqzHTsQAAbqfV`V0h-KhQu;yq=ZA6kgNsMdVNnwWBRYL<0n^@Db! zuskU0?x^iI{Ot+NmT%!;>JcF-!{6X9+aeV);3cEM_a5$xI6o9*LxfOwXbs?hj*jL5{O^<2d&ZB8i* zCMaro@UAbbk1mHVPKELzx?9ycGZ7KGy#qPVNvpuaL1rlPSMV7h*NY~KDkjhA_&^H4q$kO#G7fjD zeco>5UbJk(q1fAcZOuIXJ9yhgc?rErXKm$h28 z8E3G*=2VvkiY=gX>Fl&KaJa6b>FHf-x{v>U!UG=|OF9z8s_Q*Dsb0^$P1i>fdFp=y z?$LTFVpsXxW+%C!0|kKg1)godr0R3|^v{ zJ$s06?xn#B;aTc<`sT+p2j7HuK(Ba7N9fNiEs@;}cu9-X_`5EIZm|GqCs3Nt&XTKKv-RTx z-wJGuX*jLI7i8YX^iTvLbp4-*Ytb!E-A*lN*mjxCgWqiYd~n;&mt9v$3vqkD$X>hf zWYCBiv6v2DSdRT1jD#AEXyLj0Ojl{W_%cByS2LUV;ALCEO8q7MwgquC#XZj`%WPBV zn))fQSOhE(K>I~Vz&$Y$);9#yX7=o^6cFHu7cuY|uI~0TU+-kPy%2vMZaK3GTwP=0 z`rsa3)ctz8fRk>St7Lru_?he~Li&LBlXmw0l2)zxY*4C+v{ND`Q?FCf*bKksbgygj zT}Uo5arzf(DYoM)axEeuq2*_o(pOSGih+EQz(R~lrW%ycNmes$s(`l2fHfPp2+vj-f4TUM!QHN~j|<7ea$T)BCSGKs zZ;-!-fw9dRCdYRKX*X%P)`I}Tlb+ZU8d?6Xy5`w9o>zv%_opA~{Pr!rS4J#u0QQcW zTr}5CPo^R;B$9t&IR&?j;Ti8$@#O|3Em7+AtjJT-;R(HX;zMWz@;N?g5f`!O(iS^n zVsbRnJ&r2s-dq$Zfj^~H;LK6iiR}yh;|S4`}*@y5K@qc8FhI+e1a6% z#vdkM{OZFt(BXXtIT8CDmKm}%qo1yUIQ z7kh(0cF?rU#fMacjb8tQ3V?vD^4lmWm#Gnvxy4^Tp~mg&#!BD(?3-+|ZThPU{yLm= z*beWX9Z}p{G4SielXv02hRL|t;ypDgS@iH|{=O)H1C;(X-=LNB(Utq}6M8-`z60Qv zd(RbL3W5uGnR;~FQ}EXC>>$r8ka>4(>CM~eyNNO`ZgJ$NxDl8HOkKB2s~ZXd_k_o$ z4~~m2c^wW&7bpCEP}wlvz_3x`5brG_CAi`Z<_AX-$;88)#YwAd?MbWj*fcNc$A?>M zR}T3P|2&d4w77V1guFd~R6=#2Q8tBpLu9QNfSXj=--_;@v+IqM1f z*t)us96xVczAW1OrzJcu%D}H+bJZxtECPcrq9ViN!B2fsK}O9F zxWVUz)qlr==TP>K^$^4RlP)LU61c&^siO}053rNi$t&L%qwg2S+F&82P}AB*wdrIX-d)Ln)?UDc(8Ls;mbMGIQ_lWRMC_eq& zfM2C%!()coWs~gUgX>QLzpZeph)j0KVI;u8uAZ*aooYg&1RJB+7CqzBpL$#Wef#`( z^99V4|IhqyyDol5_}`9uX-SYkyV>2|KH=+G%Ojq^?xb}tG`?7K(~H~m+ufET?zphE!ffIP+O=)?dkh2pA^V-ac03vB>51mLWr#~bhmg;#M z>?L^~=_q@~G(^9D|1Z14-%HNt3hIG-9_4dA$Z`D$qr##yDjv0a9RC(tDEljwS<-5CRApt7eo0?0Lqycef@YBh$k}Vw3oeDd(U6ga^ubO zV9)F|>>&&6s-;t8QBbfNO2h0S;hZ(pvAh>19Jv~%7w7o*^^TDDCv&b3UXb5!cHD0C zyVHVy3|7?x4W3+tAxO$GCUDcAt1b=gd1r_@M7ZAKr|7t4DG&4#`R%&Z`9rNe_5_orPaD>(T>t)*{jC6Ff>Que)nj*MrX5u^Imx2{dmZ)^>b18-VGqq~W^D z>rP4YXrT0!+t{*FtyW7C7#1WQQNl- z(Kg&toMWuz%m|7khQ~iZRp+A!7P?_O++)nY$=P({0n{bBiDO#@U4Z!A15)!W`?_C-z*?%HjHB632SNru~!q0;=G4zu2^? z;+zjH*B^gtyRCOl)Hx7~dp(Yqrc!>a@Gv%P*xZm#hk(Y_lM77E+Dba~%EbyZS6FWI zM|=0`$kV>{6s}gXil5}!s)F@EviIUyuJ8r@6CQiVolK9tuLld9IvAO34NwlC!8X;M5wn*%Z6ZzNTS-HR zGbJ)xvVLxtu==7*TDI3`rUa%bM#iL(XDj<{Ad_1DSqq@U+a+3d1AuQvAUX2U&0?b= zjz`UnI)}~D3rwCykhj6ZT9$s(3DDyE;);^6mSRrC@NG}1VNXWLi!xtzx`jMJ0Kl!_PgKSwGLqFFqLiI zr}ob`T*}QG&6^%Bcxa`PRlM^yU}?9b0>2NkiHq$BFgT>0Sd&>fGNg;7b-Zul1gmH@ z{B}h_2a8(7zKiVHVcFl%Y)N?owDPIUQYgT%I8Mgx;og+u+?*`uu*SQrRvBcLH{wZI z{c)N6jWymAXoCx$hx%{nJ(a5{y$w6VDJ6hrifFvn8~FhELKAI$D>4pf)FG?^T=AcT zAANlkPmz7mEaT)nB9VwuZ9biPh{5`?QQ{Ai^lq=F7<>St8@SF0j-gma+%fm4XEGM9rA&=52~HMZYuLK$V@emL!lo>%ZvdlwbYs--Zc1P_ zCD5JsrE%WLp(b=ld^l~R{{#6+go8m}wm2S>o=9Jew_n0Xcp<}5Urxe3o%P8%H?{LA z^`|H7lLN9*Y=Y5H>ZiYhy;Gi1pNZN3l&qw~0);{H(NiXuv#FIkE_n!uB2|YBBiWf2 zh5BJ`iRThW_8B=_kx~IGSMg;!vJYv0u9`hMYzXyr*x{ih4y(0vXG+!J_e;7chqaBj z@dzLLKmW>HC4kd39gv(U^0_iB-=+^;EfouY;<@+s zRE!ban5l_qzuBL!mBl!d@?`L;{Q5HhIz9_o+eOx1eUT{sJWDWhy-;NwQg6-!3-Gw}(Umx|GN-VH z=kdZgg5So+_rif%Xd8OKf-MgR%S7XdznND2#*ro6#(h$Fp}*|DU`Q z7^Ls%lm4bhD)F&=C1)H`16KBPTBX<|?eX3#)I)J|I0V6P!|FZIH7gBq#EKwqE&bwO`H0+G`&saG#d)}{3zZrM3UDv&-uy(8BI&aT5&GFXcuAP@%{zv5GecLg;_Bmy z<6!A7+%U#o=TzKSN8pC!?86KG`Q{b^9`6^1)Ij2Qc9v`#^H|m{6^F}D23auh=0=On zyz2GX7}w&%NK{mib(buWsGnIXvrD=t8??Cd);Dawqnh3~B^ipndiE4Wxt720++M9{ za`ABEOO?zwu3rYNu_poKW*~8oI{txD3`t}SCfmQio}%?JOHTCV<5*a)JTXoYyWEfb z5?geg#UWCdRB#OgxomO0vu|&gH@tPoy&O69BOjLLttU_AMyy5L0Et2ycqJc*)3yUu z!Z*)NBh^_TiLCzLe0>cU%52g+e%-gXQ2N^iw}4PUrz}m+*WD^F;~2bYGPfvJE%mV3 z>L2Qfg0`>keLP@pYSR|X?p{D_pXZyrowk?MJ6k_5Yqm~=wTw^d96{zZg*g^74=<*ejM!;cd+a@edwM?UnDz1%yb=wV7k$>X8R&(RN`r)`b zROR*fa%VIHP7_EpdSzL?Yg#o~Kl(NZ=(_=PzkY~;o`9f}P@a9UWO)~cVmMuba5!5s z3w^NBhtj4pf;O<6x>t~hpOk-}dOh{%CR6^R(4k;tL}R3rRV+Xz)!y^~EzU*f4qpyE z&Q>SwFz=ELM|*ttNBJNW@(#N_llw7xFVKj$)u5lXiPAXP6)g;B_7^8>aDrVb85dO- zWf{W)hH?+%Wc@DP&GF{qR%7LGdWOerqQ%Z{V`_AP@hrNhL8XENyeYS4m{9k~IFCDj zr+3yXKBq&O4Tp_UeT?iVi&=DjgpdAs+n68eh{#DcwtRTzdt;SLyjmd^lMF{oKYzfm;Ymt*bGw*q-B!7SXdNCIu z7aFi~Q;Y$PwqZ)9u@~=5pod>Y9^@a*JKjDrHT5O;&%;WxUZT($m z{85qDL3r;G2m`LMDIEqA;E*#8UWZ{Z8q2a*2s2x_F0LB}CdIV}=}cx!zB^>C=_`D? z&!wsGc6)V8zr4$*b)$1_%;Y(%@e!@{)#3GKviN#m92zrHBzC$LV-#y`2Cc!|Osq^! z5jZ6um`s*?kBkD{=1&ij;L)l}G~&9zh_yX*2?u~c>-8Z*uFJtYWQ%UY$9c=!*3qc;vO$Z;s|_JOZ8OM`WhBK|Q!)6j9c6@o7 ziilF=_j!rkRRiRCBPo~m$iyiZhYMe6OVor5z1bij;4JRStcrCUGqZUC#SRDjqtDq2 z_dk>UC=EQxhTA-K46|$IMg79K)$oteQoe%YyO6huNC+sf!rt(Pe2%w-g`CaTlJ1=? zv!x14rt7DTt9e4Ttm>8}WF6qwR;zbeN73&zM{-@bBeKUbHuLG)LiQ<8+Y;_QAjZ%H zUQceg*lui-k4NDmJDjU5j^_tgqI=2=IuoJVtTdr?$!c*sntOfFRN9BAYxI|rYr7=g zU@Z$o!eNbGxL-mSpTp03<(S=PoJs`lS3a_1ZF4z@w0#}8Y0;+3p)1G3NT_muevt3N z%-oU}Hvzd_z#o!&g6n<7qc7mq+ua@5$fJjQ4B}`KK}GX*tIa69qlLKLXh&|7Pk&1GWM9P764FBk4kq)U5PuJSdO~Ez3A_`gZ{rg45XW>3h>641K z8K;&z;i(!hzq->M&V`(Yy)wTV*MR^>qYlM8XH{tKU<4qg=!8~KB?UUQtr_ItP(Ce! z3qipXLT%0uh3s*Tlx9hN0GICA8G%gJ#XwpR@vXd zrLT9rz&ai)l~P4D!wsThCg>Pt+_*@dB&medt7J~tMWvOe$ zv8$iB-?$3%UUv;p!HP}$F=7~#mA`yIPqBW|HmYu{Wl0pNPCTD=g@Vi1_3qKf@V-N8 zxjNz8nOr0knz?|M!PcvJEZP$$ZrpPVRF?WxBg~XkM#Nki{bNlbVPWx1LW`( z7Fm2^o#7PZ42py#!Fns`0Y&$PE>OJW>_{Q{c{Ba08-DZgC@K_#CmqD3E95jRct{w% z6+PU1GNezscaE-mH+$kR7uQ#R*4EaG+tfd^%3}7h#+ss;{~9Tvx(-l_aisraEcJS) ztWk`7n<#n4`R=Zt#^Do(X*-?7OiKhrp!X;O(4fHj~2&`%og6SkPhq?Zjy* zLMunAV=i)!3&A$FV7_YcqAH+Y^p&LNtSguM1d6+1P1;U^-<)%UYoWR<+$Y8Y@xjV` zgnuFGvjTusqV|+@@lFa0){7=BPFj+HVu%Nw{F+k+bwdX${oSC~yMfUvgT);GY@_Jg zSR0X;zF~2o<81s1fTt`M>(XegPF~*Euy;^&lBU_2dAc5ZhvaXeUTZ*|MN&fm^7Odu z|JrVVzwqOB#Q&`o`0ou>0tVnMA)(TaF-(PS2=kh|ueN9Xs@jbwX(k68 zT!a{iUZup7?h2Xn9hj&Q(0}E`qUn#9LEr;Q>ypFY*O@7|rN$Gz7IR?yy)bftK2#R` zxHR1ystdM;l5++(=tfhdJk>byEwO?YES7WHEDcc!@XLt04e6hu4e9g&X(rg^ISCtB z<@_~VM^zK+Az8M5DLu%p=q>k#fM1t83ocDu!~}3}+RyJ+3uiuq*qzS=1q6Pq`B3`F znRW4z{QMp8Rt*?3roYap@(T$OVN*Zd<+xd|-O7eqWnEg}9EK@j3C^s*8TS*|^a zts-zuzvMHn(y6QH%V9Fx>2tX{-F~eq!(B(yP3=6svd=pscEx>H0MR~Mh5`SI~nF{Dm~~UU-tFo;v;~lb-VfTDx_ljF|N+>G~YkK zzdioh4#%4Z+};;LrubH8c5|IrfWxt|B}0QqaW9en8Yke;XVe#$fnov!hv`uK>Y!$@ zRBx&TriJS6D5N+CHwjU+x*j&{Ym`);9UeNseY?zK~KY2F3&!aCGRq#AD$lsBa7(`gxbkY!-nESdVS@m5I9u!KP6}1%L2pX{YWY! z_tLx0_XGpeyNA@LL-a75HD+R)iagRMcYxd_84C&LB0Lra-|!I5p(aca^f&by-IC!Y z8xxTNn2v$v;&(8aY4jBGc%IJu5aqB&i|qzta9!5QxC&rVy5@zH!z{DcX;gy3`5KX| zI&w2Uvsh26oq+!ls_RPfYP>HL7n^E6=Rihr_Z%N z1~`KzLv61cZ@Hi9;jeJzWq`<5b{<%Bz}h+Rvn%NUs2la>HHp@@k9p=(D^~T#vu0n7 zo1H)hSi07_1i8O9Hhb_Purdhes`EeS2IXWPuI?UsW)?8MMoZ0dMqFf8)ehrFMO@}s zW%k2|ahX+}@?3@WQ=RL>cVg1^(vGN#0JX?L`_Gs+JlMM`74_k{PsIB+ElhCFqOvTc6=>?!= zw3ei&NX}>(#q)$m@KFDX{?En17d`{vMOfHL#kAgq7y_7W7YFm*%S;V`yj!@$aM+X< zSyCefr1Rw(0v6GV z{T_27-^THr-6@&#^6PtpHw`6eBx<#$tOEH91eM31PqeAXkY*QI`7Q5UMpG4%Oc;+d zTyQ^~bDLUS?zW(KKDr#^3YE_&ymITv-@tqI$)r1MA%~?Uu)S!~zOCq?%ILe_+!_45 z5RkK+RIVS9q{sOmRj@La*M&)1uw)-m6)EK$?5D_;SkvAIiAXz>>~XGFDlN)Ic%$DP ztm7JVOVY9>S=E;$Mt-%m!dcMfQ}lnUO%0iW)*+z}=%UAgQZ~3IQFr1rXBUWnfnGK8 zhiG|A1G-hhKvj&voyXf}(A0?x{xCU!6DSk?8npTGr6z;hZAXVvy*~Tw>IGFFzsr#IYnAT;_YJi% z7NE~o)?1Ni3WUkP{yDCYgTuN`?|S|n<$hkCx(y7p4$)Gqk3mBv^>+e0lO^REA7T>C z=FxF)e!U`@BEJ5cwu&le})@f?TP_nUz4!}IO03a-lEIwSA>V}1M@JnszlJ2-jIZI~3 ze~MG^?A1#oAtt}{WX3^4qr_|GOx_6?JSEAbn00m(vCnT@?wR9{PgPO`9p4@Nx^mcm zWj1PSqcMD_&}j6Ht~r9cSrr3=Ta+FRY4^9(x%nbVb@R;y`Obu!y$(XskMlOF!!ZM8 zZ~xB98d0|{KyJ<3qQSY9#JU)E5m#uWvimzBb1)Myj$-&#_KBX>k8hP5aNm;Ma(Nfe z#b(vdS;F;Y1JOQPOTjTN5Enq~kCO@j&I$cE*0vx36KuE3bN`c3_3wO!bXcJ$7D7~c zZ{Jr4=o55Uya3R}!-*`MAHp2R!r}-gP-z%50SV)R3sak4K@9wIvCuJbfo+re_FLq^ zZ52lP2is5~wgw=XERLLLGe>YhwJ@2Bo5$(CP~EeqOjb;(gFL9eO{0cO0&*o7!yVMb^;U!N@umtYf?8y@W~ zqPaW(CPpy@h3v9eWRwfyLg4qfJI&821u@DKw!ySbDsyjA*8`VLnSD2=OpRdP#~r8N zs^yw8;tXCuW3b+Gm8kwr%jo2=;#7_SoG}aklpjCSYIVf%P=f*H5TKj))k6lLEHAdo zxlXR}E0$Ok$ccn1j$Bf-Df80173cLpj~Ne#OXnK|>zz<9^g^kllZ`7ESj^V&anp3WZ^Mz{i@~$?j17O1mK!$HXB3usAqT_2|N#H{fL4pZ3&dghTkv> zi~y?v7ydB0oqoa;o;W!YY^TJTo8TulP6e9fzT|%_i@bfGP>J$q~ zoHyM}Yshv5?BO_`dKs~g7+u6ZPrpBwzJ%zVFRU>+t`{OVU2&0U?yd8>*$yjL)~An- z*0`ee`HC#<%Zy_IEEqEBUkqX0=h+*#Hj7#Y=@6G#Ej92zw0NGTp54~)wzZ|v++HET zA#E#tLPuc~-+FV|jD*do86-%M$j$2IGi4p&y}Dhggnb((%+^n>CrEip1B~>4U%Q+~ zX_0GqM|@Xi$QA*Z$(Lg^uRWG+nI|(jp3dg-n{?Z4!kj2qf_(1@3>$1O*A0u^sF9^R zTCMt3`l=CP4DpTlH2qn503oIQB9P?>jmfTLP~Sfjg&RUeOufL`5C%Mo~EZ+gJI``Z|i3in|vMO5N! zXCm~8{_$RfZK$hQF33Ejn{MNRW>@mpIhPd$%`5B$e$czu7qHPLsA?E-JF}_4h4Gj> z#2PoY`zDJ_y2lDYfPpq~O1!&QhiM+3Vx4`zv0@iX@7Pwuo}wvkhBOmMmsZ2== z&g83&U)w0~fk}lNV5Xj&IlU1Ex>?onuH4WR(9zan2C?!j1x;msPb(;q1Oj78v2tvE ztu;|RDC`BsVWT7~a0`GDp&x`SX^?hb$ml8@1kL31FsiRQw2sZ#K~ZG>!P5Y99xA%{fcN zmU~^K>O*>lV(Vm~#6yBU?63GepX6dsqhU~$U;C+j3z(4N$<9R^GJt2tXHT(lghgQ# zr8}h_K$G6^H;1hUFahWcG;V-%HhMfdHXuy!0W?&x8XuJ4rZUm+pL2Z|bnksBwz-Gf zIH{%|`VqWQUt*~{DOE&ACTWrwsz0o9)q;jVJ7&Or6jE0vd@Hd=YZpuD<53+g6l-%u zr2y&DOjNQ^Um&VGQWOHM$u*>T@2(u&eH%v>ptK_nytF<Jh%e3}RrZ3C?U5jKj&1%WYGHX~z}#;h;cioWp(>Nk#0T)SR^^fJ4Tj4Liu4 zh6DL4$Au^WXZBg5rSNhUnc|}?Q!@r3^$xLy&dIX*N}%*`)TAg}Gre6XZHoU@GiM_( z{nG?%|6QUZ5Wb}D?eWLU!@tq8&7HH{VEkx|0p(Fz2;rEaSNkQ zuU$AZZ3yd%MK`!5ff9psg>>lKNLn~&ZgC9p8nwXitPwi@{v7}{j0Q@*V}5lm=`MQp z9VO670~NG@Dl{TptY;$dX?8!H!u^N)q6*ixbMLDHTtWvFTs9q*kZ2GGlh#1zVz7?@OA95*^+s)>M}yMwa=s_ugh!KVq! z*)YCuuoXM;sj}Rh98u76$S1GGbb#B;^e_^PUeUO;ff0ZfaAhZ6n?9jbTsffcDd)B@ z(B#t%4a}$4xWcfwxFIL=6*b>1Fb&!a9E7bRLRK(*okt8ra1)#>y|I^VQqNyVVHJ~o zygM!R8@#<~NNY?jZ+-k(W~Id@pWhE~%2H#d>etoGCby7F59Tk=LH_TfZPhx_O+kUm zRBFHgZbD=tTlpq#5C6TW)VX4j9I|0OP^gXxZ>n?^M56Es9H|?DaBdu>t(tW$U=`}0 zQqymqjUR0cMu~;?$-ExgvlmnrAA}%9xZcF#8ZNd!;37K$+Tx8oQbbxS5d~=nSst>{ znxcF3goV- zymkUKmt)cvue++brkn#Z>S2I|BpBsp+Hgd9myMkmd=>k$b@y5E)mE*OHwl;0MS+xH zh7)=(Y2T-j6YIna{{u7xKzqWnq2rh^C^> zeYN^m8%q(=axuW76#pT7E1>1(mf*3IAB6dc*n(n$`PuvW_&~}-53;cUE_;q7Rvy6MbG=__Zm!)^P!79JN|$ol7Tz3L zF404D=i4U(=B63k9#*)N^aL4Bk?!9n@#;eX;}hA*2X}U_0qV+BRhl?z;A={u)m$#J zspL@}#8S$_+P0n^Azi`Nh>8 z$(tDN%N2DfXpy-+P!8E+dH3t_J92HNyoK`J#~DtPtNg;Mu$FCZLThi*XR4$nl8Dqlc zT72DRs^`Pz0_QrddornOjGcez{oADS$iH) z*TH5C7nVu6jz0}mMNm44SDNmDDDc z>c-qDRxtzx>fh`1$%Ms8!U!+3(L$k(lWVrdPPf|Zj45Y2u;!*u&=Ce);mob>$K48C zaBcIJifJ|rm|GK?9Ox1(U}otZr3Yv!W-xQUk5t=zwGSzd(M4rIXbgo!zy`+@`{{$( z+g!CO&Fx52=ItslWl4|9haoghe|GQAJu<2;G7d8?@=^;O*3MrdmOBq7&>6_Ro&R)i zI6+D}YQ^l?vU16UY(Z#sOk6A9(#*ZyT>ToWe!+Ek{DhnD{rIH0n>v&+*DFsiV}7^8 z)Plaq2^#H&m`g7iza>N;#`7f;$qp(g<2?lhlc7ci=hNx^7lBIy$OH&8pk(l8ACg+s z&r1WA`J_Gw@Ce_LwR3E}_)&6vbW| zj`Q61xn)B3b4c<1Oy8l6ElAfpJ0GK19W28_PBB`sLnKr+R>X8(PHLiXe+a4B4AXbh zx7;7J!3w)SJ@leCO%>*(UReGar~6GwN9VIR_9|Y@#L3tE)A^HXGX`}nz4Ao8R;j@< zGRcUN;>qkP3$W*&N=mJ}gZp?oEJ=fEMKx;xQ zG&KZvfwS7JE*L{cbz!*4n-NOQqFvY!=-8j#uS3)^cfrXIJu6ct?Om1$(_NE z7+6?fd%brUtqCsmIrrUZZqbug(|Ygw&EA&oF487^m|C2c|5^>d*ZfsjOC!$zOIKW~ zs(bH&AbZt%y@5qo-z-~vwXu%ci!T7H9O}Mg0=ak{T*)Ij|*2q^D0E{ z-`C7@(H;(~BXYLDGb%OI@P;o*)H#`XWMkYHDw6t#TeSGKLoQj2JB=hb3ENKAND($A z`VBNW$R%Df)#j+)tbU?KTfda8d6N&-EOT5}bMzZbvoJpGx>d2>0vi&jU0e2hW1u>v zG-5A&Tor0JXlY>DEU!*o;T>;CBMiJ#W>x@73<`P~)oQzF#Q0l6f|kpRqTuhbpUP@j z7%CxKOmx3HFZhHg2}q{?Pzh^bT#09~|8&Y(%<0Cm+Yi{G-oVQ2mV?MpqwRoEVBnx8 z?2yGu`8T-M1Yv41dvw6oenY*T{~s+8_(u?w@xBvi09`K|M}wm3uv6wRj0P_2>y35eTeDOjVj4EA z^2kE5MIP%n^i0{m(4dsR@V#AVbU^v8R{PQ6bmnafAK~IZ!oLZFj5-LG zeU2pFE9=zexUVKcs)y@QDQGs36wq>7H(X-RRz8-Aqg#;f?@$eqJBAfNHM6%UsY0*f z+(e{uqv<{#P0Tv@^%yNGFWCYSYEa==4^96 ze`eAu<&n0jK2EDf`D&t^MO_^8g>U8*;1~U!u8evfue%EbgrKzx5fKf7f*^ZW!J?c(4 zEx0Yk2rQgab_6y%|FO<;#Z6ajUQuPRb$BQW7D$eZjW+)!U6@b)#{zOCJVD{i|yXt;8A)y zpI$%dUSU)xVH`w=zN$SIi4Y}{0ACkYgz8UysY-K94`7h*?RZ5t1;S7eN3d4p0%Zi(ryC-4^w=;J{J}hL%g4%gsTKkGd^4+ zd>C?Lutb)9hzQfs#bw3dW%(SK1~P=J0q!L{f-Ae0vKD`xi+@Mk2T5;#usiLxAH|@& z$-YA{P3v(on67=31!30A|7v3NXB=Wf&}mF*6L_Ys0#9_ez5=*^l7hK@Wi9gL4j~>C z2fR0UoNSEd@rP6jGJ?zX&9XUv$nZ&5%jnOxnQ#ZP?ntY9`)Ci@d0m?L^@yQ;Ry|qV zXev`an++}?_9Yt!yq!he3i7GMn`0RZrx}#pPZ)p8;>v}DLqEhdv-03pn#8lNuR);& zJG7B(ZkD;gw#e4(x?UTTlAxPxfYx%ilO%QVeIGE9s>-2^39F`JC6)15eCJS7>L;UnOFYrgce}D{MVmp4yVem=S(`0QyFpo-uo3f<<@*Edde&BKqQ9NO+3FvD zh^*K_EmG8A;c|!XX^OHfj-D0Ax=dqi8{Cq^)t`DzeF`IiPH8wBU4gQnc;cQ+~dC_1sP-l0mb*?ugA|_ zIBJ&_84i0A1y#)X58unnw;G~BO){x;KD1m;xjTLCt3YGcU#2w8hKlS(E0`*Mrf@|G zYsf+x%EgNOMQg%o&%Ydu7=Gwsx-5~UMB5T`>__jo$sI{Ib@YsUdYc<2KQ@Wd#Hv=3_!)tcbQG=la&WOq*=`?(ffhmbUIWCXTi9bN;-P;8?m z`j69TKrdeY{m_Bul|2nlKRN8MYG>!QVpdUw zsb5Dd-ThdA%mr*jBq+J=Qv#>s94MK+r5Sh{Mr5uc1}>Z<_2_>(9KKt0x|-iL76lF} zOKRbYAxVmA;V_oo|IAH43#7Z58NcOrCm<#1kk_z4{53H#cD$$2H^3vV?zETH)g_XX zFKbnOL#<5&WefzW{VtGCrK4a(cs0?J)sXyUH7|S^_f<1V|31xsp7IOt3&6&}x1}#d zUO*81}Qj1wc_FQANP%Y`KW`SO4MzAyX2r}9D@ zGW1_Q@cc`oKk&Y{r5L~yrpy&lD@b4h?+PnbHN`%N{C01KXPL1tQ9k0I(WPKGFDp`=o!^5ABl& z^p9lf{+r!x**tyKpt?Wy7K44@1)Xz5x_wNDVGu%tSa|LaT{4lVFLUjmsFo5hkxs4RzFI)ax3qKT^rs&`o zKtt+ccBxghR2qV78W0D>bVFl9Rhr$cAAg11>J;dB>ddF@XM+kGh@}>ZhU@sM-Li?f z7`1iPok3I_PkF_298?=b+O=k*w)oyc&hW|pem{kHXkZYF_$f!}uE}}|s_cD1-B#vS zLv$5R51jvxv$p`Ns@=MV6=_6zBi+3L=~O~Gr5mKXq>+#kB&DUhySux)82Gc0 zIp=xabKdX!|LgMFZsS_4b;lfIjydMNTnSFtMSg|fVju>qdXfTf3&Ga{I8GwhgQb>e zBA4hs!2%GJFE%%+$u!AMxW<1$+R^oj+zQ#t@ZVs^VprjSzlPff4Zl)_{M@2>z9E$J zn$Yy9X?o1g7tVZd*6}NTnbHd7Q#cm=4GbD~hQ79OOUCRj>HO+2DEhxG+(+=7lKcS1 zqbB0W0jV@RY`P32ho;difAJ^e{s>&%Z<>iw^@~}gh&CG1dds%Lso&)!NpmDrZ5tte z``z4%d9J3q!hMgZ(r!S+e2&%b5GHCot6<8Fg6vZ0S<&qwl3`7kD;+F&>_TT7?wuwt z#bgb>ZeLj+ajy?9S`diViZDMW)d9HWAp{YhbNw}pJyt)5(Q7rBhV&mJR>=V@E>>;G z$Nz|#A4=edW9aW@X)Ap@$pI2vKjpR&43OaTc5k;0^2pjU*Cp}}i>=k>C3Y?DV`pd0 z3#c%^9s~Nb-z+-DGQuhd4cq=FY(v&rWjx;Wip}#`fT#dBe!ajWni!WrF9PtCi%?6^ z5r7QaMWF%IXE9ON;ON%rjCuaqnSIQ?lWAY1Xks2EhMUuZ zqQF2k0~5FG&^dM~_Wr2q4}l=tf`>MiLVNu8sF~UX1k3CbMBw9p#xQR0)xl|$#QAW+ z|B5h{ByYi`K7c$$E{Fa1EA!IVyi0jzLtys!V9RussLZa({WP|)zM$V_fC^SSETopjdDPIhd3o_R}N7|iGl5?8q&a=-X%?|5{hE!^VcN( z{Gv*kI%Rwnp{F}k*LyylNI&8Y@6BRv@cEDpKv_GFJmBMJ*89KHtH%q#|E?HpteQkJ#st)^K z_|~W|@YwNhg~J>_<22Atrs)V;3k7 z>91R|U#Y6^@1-XvzZ(CrCO5-;@K{IrE99oSBbbnI?)`25EHPks`&Br$h00WsLy`s7 z|2HE3cn`9ghhe#Z2htYe59kpj#ijl+>1pDd4Ap^4LtW|R-N{Z|`w0pPkLHC~zz|d+ z;Oa(KLw4meo4iqY-Nd(JekZ1b!GAt=uc$)6UlqNsgU#;{?M14UJB|8RRMr&aM{0{} zXx^KJ0`85TyZ00kLJnJ`{vWrv1Jq$X!USQ=NmZ@5VG{m3;KK7k{QNdgggf_Wc#A{Z z^>=A1Ba_=n&jrOcm(h0iZu7EvKj#lXiL0Os)vv!TL(a?=Q5Ca0l1SzF$zHue#UNDZ z@VZz(h!5C&+r%SuzyjHvW&UP7oey&=Zz%r4q!ds&qjT`5CzeCn<__^l@tRi^Dkk5f$~U0sW`m zdCYviwBLQQswy1`LG^II*IWr%YL~H#lgCUM?DIeI$xY2BJD^vUznkPiHL2dpCC-&? zIGo9Jit;979G94rg~Xe{2}0(yGRIK<9&7(>s~_x-otAyQRtIas*aiuq6G>3xR7I2O zC*R~u;v$ByH^pv9@lYE2HAuNP*~HGRG8TURgkQ#36kaC4ycRK`;6l_(>2gs_b)cmp*=XCfophOYk9HS~uF`$Fh(_m#PT18|pvD9fDY3{}DJ@ zGO?f@vqO={xk_v+N!z}3kkMe8n7NjdHYA=>hL*E5{DA6s71^w8QP5RQ`C<0XFk=}( zB{s*`*sI;$_^k;Bj6d6(&p%K?O;Sgxmk~LV_4cq=YhM(JV;0dOX18aoDJJ4X1&8R7~M8|+5%2J-hm#S+$Qo@1_FQ)djH}o3! zGi*YQse2hv_EBh;rcvRgU(d9HkRX9@=PnKLm!X`wXUOg2xJV=4p9vw_CaxvN9N_lC z{9Z#Vka6tZL;gi@>O#pmSwL}RFf7&DuwHQ}1cR@bm4JX_kj5na*OdM3i-|F?KdVlm zQs1K<>q4QOI7D$bCZOgZlerpcRC*;iI?w-!!2nx|o@udvL3{f#^3eljKF^ z`l`NKcOc?6EP}c51-RamW>K)qCSiBO{GQRnpKGJ@Hwew3K^ucv^;P1!Nm*21oTmBA z_GBZr#f8yxW+VOQC2$~=y3$ILSx}rAZ$@CL*)pG%5fCDqIa^B}d!f5n3*yKNUU-cU z&Cibqb?+rTY3YnZk-byMu1Z(vQMS+*v5}TZ_pDldc!Fj2n~{_ReV1cN_LpKd%;yGk zFgeY`VdXU<3c4b`IkE^FPks+(4eIE6MI#SUaT#nEkV6{`9NkZw)lw69vtWCjdl@w; z*}#U!q2MQeNf!o2O|8>g=x<~BZ}~blDm6`iPQ*GCcBREUIka&$Zz&v9vlubCgzSz& zkS|^TN6>^dx;Vw0t|BqmL+R`LlHS6uP8o{QAK+pQHi~Q&IRm$*F1;=_-KdlIRAy%O ztu?t_pwIBXM~c7)*d4efWWUk3QQW4Rw1W2}#j`N99!^EfDX!FD^EDh@HZ6j4!78}w zJ!4?-q0rcRODQo?0m7frP9=Hl`S9pFMhgcAJ0cu`bo^QLm;h zq0ac|+Vt^dYF?;V>&i+>db&O}Gj06Y`FZAqlAMyEAE(FxQHth_EdHppwDhy+%hOXT zPfv!CE&c&K1%hzK%|H6cA^GaFHk=15Ok6?%+xB)fU4dkc>>Pl+)jY*a`tTtP6+Wy0 zTdzV8@hMZ)=~(I|25E&&{{oRP05-1#$Vk&d>z@{9ei>$?-_(#pm4ch;R8V*#WK6$G z{0k{tjp0pB)Cd8U0C;83Z>hi$@ zAGOXRxq*U=lCx8>8Cd`L9X-eWbx@WbrD?^8me}LRj}_@P-S2KYI4E6aAOGPbCOiW9yXBbUpsn0{?T3SA7GEL)WNRa3hL|f8XAK)(N(cnw zRkIO-#VXIBV8N-%^*0$@&ZU!$RyI-x9kvvOhBiO7Vn;$6?hBR3AH zy({2#hb+EVs$pVcW)j9zl~?4}=6CQKFfDHJp}*o)FE9S!L_^$XS9K^eb{mM(c)qc7z_MZ}}V@0hx>*&AlT zoVYS^c*rYrkKunKl3U2nFv0mdOsLw#ERubWaK-ibLBvcke^BAyJ9yALmWB< z=CoMH@Q_lDvIZ7^L$kE544E?kmzi1WXc=bU0W2gp@G0 znr}8(makN)jPLqU!r$?HJ+rBkK134~U)LFEh58i^Wh~BFDU0F(+~&`?Y3D7ENgPw| z0cf*YkEY5X;_qQnmuyvQQ{(4RRv%y zxd~N%#jJu%AST8e5h5U?WQ=~cF*6yJltT{+v4HW^m8RFMP5NqSXb8iG$PpalUEy7^ zItm;Xftc1G_)c=DiiVy%cO9L7-jQ=;_tf)qs6cabPI^tY}Xt zp}brY*ZXEn7e8O$eAB8b*cyU{8tbfpUa{RNj-<0|*!hYLSzPXSg{UZgh$L!0m1b3T z3r6{4I|fUWhfTRT)Oc7}6rJWnhaeZQFw9k@#v})}M;8^jT#LYKYMI)%XQ!u;2dr#t zXklzrd(R0LYZ9DOo**UZZD91e6*>rtT@J|d^|}myVbO_MYOupA;|zSL)2i-_w*8uF z^8`og<;_{NA$#FnTs|$W!Qzi^XE+tYL?rD=CJdGfw+*&222(XF{JYBqD1Lr8g!5}q z;MM-&{>&#gj^c#8DPtHik)+KZKJd@MvP|slnXVKQ*z=xJXQ4bF&o9gT-B8G0u6g8rF0U> z+=+U*`53NNRUvF!+)LNqKid^}(eVpI?w!3o@s&5Cl6-CxhDj@cN@BL~2Qzjdm=OSA zh6?;s9>y?l!2}}a@o3nwv?^Q05})nhFt6=lw#my%^zSGZ9=S9RHk^i*A0?%AxI|yb z{+d|@D0TN3Mw;QAh;*ol?xotTpRSpP20R}2BSdc&JJMMvn8*5(CK5OQwp!kHN$IsB z!B@rui=;>du<-p76-G%AX3>8Z5)$nks$|ro{S!8%fzD{eQT5Js9q-42l z`a66kUtXKHTQT~F#ib$+fkVF4qMloFU`(me_L#cy4slthVP2FuRD4@uh;A1#{}wWl z1D~coNHY`P;7827ar`(A;OtBkoxkHEP=lgQp>iPyTuC#APXHO!&}8fdyfci~JqscY zbKBcWejFS3B0fXwFd%Kw{YrK3#sm$`CO?yg6&0mS(flC}y>}V(y6$F;tv3qs;uq4p z{i@orLc+hW9c1)Z)1VubY^DkM@Ljjj^;CaPNGi=^QD|cce=56Bh2aw^8tT3ICMpUF zrx-QYreu%n6Z7$zDFvw-#q+@gqr)>HJR>8S;&HrRDn5@!nk5X+~mEZAf z^sz1p_%&iXdIUETlD26+MTK-rQAT4V7bZhtQCD~OL~j{}p?fudcRNLOs`BR)b2%Gj zZEeaPd^|k5MZSCfm#!C<(Y--D`0Qr*4o7nu7g{EjWkyX4)IO-?$2p`qa} zbyVf1H`5`Q*a^fF3fxZ7I}0@yncO6ybS2c$R@7CfiMAav)4pA{6GlT=dMrMEyN!z& zQ6$f2rlt_g-yyW;FTT+|7<%hx)svDyy*G=-C8EI?E;nZ1J;+rKZ>L4)%#Hgoj8JyT z^(>+*jDSn1q1rm!7Je9?BH-O0^dbb3hB|*1*T;`_s7B4*5V|gzRbR3j%PA0w)x9Un zrKU@a_id2Yffraqj+(5IfFH8QL|rto?f0MQk~a-);|cGA5`+H54ZWU80YVE7U$3T? zf;fdA%y*~li&hvU;{yQ#cW*!m_<#bzbbo9jg+D<82^t?OkpOKyy@{tLpT1OM=##=o zCa11aun+0FJYwR@2f>~DvNr*}fOkh-oKnGD-BrB-I57|5G7A6p?k;OKNK31HPvV*9 z?RAiiu#0?mG`7B+oT)fdkxrAK!PVHl9b*(}O@BY4dc`_VR_!8S`4)w5?Cy82E^W`M zt$qqBW-taQQnoPxj|9*CV(#?D$#V_P0bbfp%Y~=w?4G(^MRKaD=0&}9Kn60L?Bul{ zL|?@Or|*;q{>Y=>jUU}7%jZVlX;{VT{KI5-ib{G~UnOEn3HCB0adf_nvz)T~&d3NK zi?qc^W|1SPhf+3yVx}=W`=fG+rU>%6>u@=4grAMoekTnxp9=gV4kVJ)j5L)L`!>BO zu!lVS_v+hb?e37U5I5UJ3(TIdrqo4vV@VIbhvSf@^}pgQs*U%D@2>`w-mVQI*ia(^ z+X>T)!G|t);4@-^!Q>!hoTISwoH%CFMf-L5SUXmJ252m~E|act+OkDZo$O;HAZ z0A-Z#U;Ntn`CA7NIxi>XN)WQ1jR@*&$Qn|;?)0Qq!h}|6VrX5fPnIwJ6i=P5@KTs! z2Ej{>UYKesA_Ze&_S(hRY_g$eJs~B92zh@dq2695J=2$S{jTv=kULyM4DOlXXKgqC z%kxn#F0b?qQi_j-iJJMv#o;HJot%LopJGlDBd-)q&qm{@n1aHB(tK-G^Rbop0i&3s zi3!tKcvM6b@c`jKf10{38_~~fWKsApg%RPW!U()04IsPxcEENw4aG!;>_^C!RKc(j zvQbu(lL3HdMK2lr6pzWMQOfu-w1*`u-w>EXu3c9qFsU~wSvs|#>R2H9gY75r{DwY6 zrfMCT5X;Ul2vM|}?zCym&B=&<=7YD3kx{Wu-ev_F^{TdtHbz6&O6D-eP1hG0Z5<;0 zY2{c9)!pC3UR1MgA|e^bOq0K${yh9@OOT5fbV{(;u(19nlG`cGndI|EsokDY{~)JD zSz?3emy}&KTs;y8768k;{*A*>!>1JL##nx%T}#!RA{=p zG|^F>KFvhh8cD4f;SEvcG^F{8jXBXH>6wXD;Hg0Lv_f$>$rSLePQU=?%vm>HQYh`H z--oUSmfhZT)0xgk*_Q9{5IV;=9&nLw%rMpmY^JLzmij&<#R`V8l?GFEm)T?$M* zzzEJBC~4?PTWk8h-!xhAUPbL9K7lB^(4xl!+cwu0^QT3F2fFeH_^9}ZyE+@{|GIIp z;K!zk_GT)-7;hCXnVbO_>S_*cY!Mw;q@adqk(V!f{b)y>g}S#s0Y$$}?Xyi--y!G5 z268&phU1)|nyL6FRLKKYG$$@Vh;%~EuUt}Zn>!xc$kPbZE|1gkxb~v$ABdu~w9=yI ztf-`9N?JGRKT~7xF-&&(y6Q3CV4mPo-q&0fT|PdJuHIr+Ck|u;9#8z`uRjQsi*<*; zu6%z3!U+oqPB*RC?5{2R#$s*>;|D#VW{2IoPpf-nb^hHDcy}$q?{&bRiuF?kS9XI9 z4v{qLqSjCm288gUJ)A0F)ej;-`>~Ce+SyGRfYc7|8N>sIaB0$NLeVGeF>$k0P6<9u z&OpDBc6l_V9AIgPI;C`_;O;;?wR2VCflSZ1;yS(kD! z4{N;_P?#XVsRnm^Y#y+CoxA!5I{K?Gd|G@mY?4;$bO~mA{T-c#hqW@2397hEV9tB1 zhWPQDGn?G1Dd1qcb?jxPa{0qIY(q#xFZb;05Brh)y3QZfpA1uNuZBwv-IPwI)*uag z+}=`{)(zvd&H0zS6tKVvc>--56N;D#l&@+dK7dlbEnR8#;3B~f7lsRP|Ju}&kaQNk z(Bx0Sv@zxbnRf+19V$&b=0SEA1*;YZBYg5MYNra*5^!a<2&}sOIGCuYx#WHH8Z)$X z^wG)#m}TK*tHDTDH#bNmu{KjiIhKNwaF!Sb-!L5X=>8RUe~vL79i7Qg;1PHo9<`Vl10#Y)4jzcf(pc#wZ`6v_ zYo~9o{7VcqCEMaC%0N!cTa7`P$3v>4tZ$EPb{DKa;6Kp_)2Dm4NkYx38n|iJ=}cG9 z-zEL@B@iX-jKCMr#w2NJzzbJh-Z@VfF2#i{jk$x$%Thx+((c}yJ`mm<3d~w=bxt5d zae`3q&o(&n8x;9~0~uXTW4g@wJWl;;=k%A*9|cXTHg*f_)%A&-{3+DDk^xjHBECm2 z+)-`FyaXyvX~5ZYfpf#wk-kdI6aHVK)_g_wXtM2)g(;@w4D{$>J|@b4q=9KP&``h; z!vfB0At{WD{syb?y2y|tweOBLZ=rY7KT&2sG##TMU6Xxa!KyaVD3M9P@?q$*C7rSP zF3<10{LM-0=myH7*pu=11LerbA@<*pFXaVvD>!8=JEhTNF%nOlG(0_hY|6{Z@?c(b z=$0)YLmNAyr9p7lAE=i}ZNPXzYEjkr>j`~6MXYx+U8#0Tt!lwV zt=u&a>`})9lRknhHYD|>_ZMlp8QuM5r4*%r%dM~WM6#s$&gA$-N8}VwjaBwwa0fe# zaS=h#OP~#Br_hdp*wIp5M@P0G##8d#!J+$gw5=oByqc<|zSp9-?%H5Pz;I&(lYe(N z?6t5ec|m^e3&!lj)3Ae_B&JC2uI|3d+s4SslNp^!w@@iij|i1*u+8krRyL@|dyw;- z*=QY8VrNn5klLZBrX`fNLhv*xP)rZk$ zWS$vGPW9Xeq_scoc&)8I!bC$7y6k=>cZG^tb2o;~H~v1w&AcjMJ6FtgD@&AGQ&o7Q z^lTwz=ln889Xw)r35ppbC2Oal+afnKN=lutzXln02)^!gdLWL|)|R6XR^P8L^HjG! zX=48zN;m+Dnf@;2VZLfl*g-#|BeK!+G}__l{l4&yd_{J3s99HwOT(v=GFG5v?OWnt z&J+L8DpVg~Uw=8XScBbpwOS|SV=6+VhHWs7CFp_1r)E=LVMn3FI+a$d3Oszeb%Bqd zq%@UT^H!W!=y+$YiOyj!o|MpLVk}(0hXDQBA@EcBtEPH6H;bo9gLF}+#TYFZm9eYf zH@u%`v+UBph#DgzBWG{4M`&-_4)pjjG2UBxZH-Y-gx7|2;HcU2QppD1d?~S)q$6@= zEf!u+>sH;Jrtn+e;E_{vfiWZ)$!hdY2bo)|BfSSQfn+gY=Y^=iKs>YQTo7UA_zl1@ zUAEib{z>IWWuSSbZ9plX$qQ9*z*ZXN+8|;NczmaMoKEFpPDPbH->P!!OrUZ9Kwny# zt?1e|?#D^(oHXLYm)@9(_VyqC9hB-PcPj1YpPVDKZhKuX8j%b%Aer;&(Vk(V&MX0y z#=!d9p)8}L%6nFmZFn_G4fv&M%X_WvE5E%k9!pvZa>@Z#eI#S>=LGHzixaX&zA1#0 z>D}m+z7y)`ySpd8xmKXEw;@k7P5?DeNQW%XNu4jQ`eN%_JB8_Kc&w<=_u%z?<>Q)B!%~hk zeGF(R${S9ZLTtdKzd2^XZ)-TSfPqc5mw~<@mF~foXe!{yrMi78mNE6pZaB3=w8G@P zUi$1HtmO+P5t~~os<)N|g5atl{*RmGOS+i}Ji$~WFz?OQ6!yfTnu5U?gymW_AIj5Q zGZ$+pYl{ax91+ZyTZT!!avwLTzljm<7M}b8m*Or<|X>hHwQj~qVDWfoe z85=d$5u}rK2ghQvpWAkJIRJWdIf)7QL%;74Eo^&QyXlegR#0*_c#H|NKJ4eovpSwG z$%>1N@ZDRJB<~q6W={R+=5rc*64sKrSBxOY zcOUa`OWxw~WzcrvPLAB$)V23~#-*r-8H^sO0}SzW!XK~vy6mqUE-Q1bY1TSsl8Hf3 z?`k(U{b}eH%2rTodBco1D?gppv$+Hl}rF(?<_hiJcd%KI^t+s!H24fNB#=FoEb zq?$5J=_1@^c-9xo7wB>?oXvOn9j;Pu3KmBepot3)E-=VAUyS7ao$S+59AO%673V-O zpCM9N_|IB{8c=X}P96UycYDkZKJwxPISDrs zGO|L!aiU}(V%C@7i44E4a)!L6PLu@!Gn}s7gm@cTbg<@!WG4Mkv!PH=3kG@`v;(Zq zGAa~d;hZEE!9igv1?uHd*+!&)I0W%1B@~75m7=`nXf`{3-qW@?vZyz((A*4Oot3n^ zO!ZPpt9?!3Q59tBOHRU_BQ2kxyzyv=b3ffa!4v1@Am0$u)4@&EQ%Uj|SC?0a)Llr$ z);L+AzQ^0W^`%x(d{-tq(q?x`!j?4k*5tmuZfoIx(&kRrq*J=JDumt{LrV( zXQA6KTN_eXVojJKzcub>w-bS6JaBX<5r4%lsajQkp4>GqkG+M1H2lMh<$l?k#%8&N z$SoAeABBbyhX)rnsu^1D3FBFRJQwf3#9PVQbi@3~EZ>O$#d!vh zo$cGPNd=T;(i-NrcBGMwEDpXb!-1LjulENLc01{(@t!v?`dH^}V^{Y>J+)|r?L@R{ z0ye*4j~!g{>Fd?e(=df$elj4g zIm7)d=urJC=s=W+*4GdlfW_ih)Aer4+85>oreoit{MWlnf62uBts4kH?K9X5nSd+C z^=wx^TFb`KHHr13G$k! zRabumiZXNA8d8!DuYnTEpf626os^_u6szN2o&EsP_-`?u{G7q-0V1mkB_BlKa(P~M%zubp5`n2%JnMU_5NOTkyWx5Ve^m4rTn)Rm(3k# zJ1TUYK0;Hh&ip#{25HWt+mlYOsspYrR(%oQp zH8?THq|y|+I9QSn$R8%H{?N5H<@74{kl9WK^d{7%txtIx6UjJn1geet+XOjkK~fH%gC3SiHa-ET+&-aLFgxa6uT+Fbr6AgYl)9+OhIn zOwjwg)Y^ArNq=nI#;#lkH(%+7fL&JzW)KAyZE%dS(=6gFa!?n#I)%nzwgx{-aYdfq zdrbJ`zcK&r8*V>hsWRYt+aA*hw3(CoseBvEshY_UOx&z%>UXk1oVH#X0+>6$_JyeP{?-V{RvU_K*1 zn|;h5Lu^lD_z*aEriS=CnTBU;g#xi+r2l0}tR!2tYEx5x5Sa8FeZ%FYL>CiYTwdN~ zFq>q+CztKC!g@lcd#W%4$T0l1ri)E>1(lBMZfBfoPLRUgk5z3PV{?r=IF!ig=3u-{ zqFaeTB-5?xA^K~B0Zn~MnY4g_O(F|Vt%`_#2}ykP#FOW00bLBZSg{dcX8i(v3%?+( zw~pcm%YruD8~P_8q~>}guOy(}xK|N0?A}mQgDME1-*&Ba^Q5Huh%!&{>bK!sIst@ZoMa6)s$~Ecfg=)N{0d9&%BQOE4xJ1Arr0;Q z=v}_O244Q5V|WAXfXT;k!n4s^K2F+Kg~yz&#_1ytze3 zU6s9D_CS_KwOf5_k#snXe#;4@nO%)77OK_R_ZeMvp(lL-^^3Ep)((<|e4ejD@`w zv_ezRd6bTGz^n#wJ0nMYcid^N1Dd)Ifl8FN{qy0Kr8Is%zrN zg1)$3WM=mQeD1~itI7;~{+mmA9_{UA4B1>nY)YE6dlH|ePJFr+oc6ptpeda5RfrL| ztI1Fks2ij1<$l5yfk<~#q;oAq&N8Z_+_U-9KHg9}3t`%yEl-}P#cIE=sT4C@o?WT| zM;yN3y2Byiz21rClL99ep-$H(zrT7CX*Sl*J!@>u7-P!T9zRnTy9lsX3Pyk;70g(t z=fpG?O>c-<>z)L{XwdW_RPKyuRo-6~QC>CL-67GThjLo6v<^8^_`(l$=SS~mro1aUeN|l_q96L3 zXVDj;ITnpcaTU7HR<&j(evEzeCs?^Lh^yRm9KymLy*kYvAO$4xtkPGPk@A{L<*UJd z#`u_RbB5Su8Z%tXMX9B}+V3xsfEPK2{W!=~&MwPh0WAX=ZR$FDk7rFpG(`OwwSqJN zo~%mxt`=tIrY>zhZc}SW69oh?fGsBgZ25G^p{MXKko_ydK)$5D1a#7A!pSiz_#c#y ztG{-EnxaY%f-uc;t*ZwMGjGP((^Rm|@pZCgxQ_Ta30CICmu9%X?`Qp8FGgau$hvKN_?E>Wn)5|w~zt}oEzbw#Dyi-@z)3*gR zbR{~Sv`o5*!WaXH6M{*ehc!g1n?^EOAgUs95CPV-q8zY%sMN+J!p?}9l(6ZZZjRyg z$gx?#pEu!Ph>v__f_;BCAKV6 zgA*LWt)%Pl8;Tom)+oA_40r+GkZNrkN^_mMjpKFZSUwh)`No<6PD7h!g$K*ay07%i z)eKHarB;sMJ)xkw!6Y_eLcYW__+i+KJ$fx$Tcn1+!3fzp0l!@51U1!=YVzJzr#@xEia& zF@Z7CVo1vQ9w0iE0O84y|Cmb}Y+kgw-!jrHu;+18A>4p)Qf5Eb&Lv;lHQi`=DL zx3CpwdBkN+dcXo5Q3g}jlkBqOM*VJO)^GdX2gm-@GdX=^#U(zSl+55BMrCojT^`T4 zREf1Zv|9iz(f0PXZenMb4wSntBE#}*U)|vZ>F7(X72B{VU@>kz1(t92N&L^IH`brl zQ9=noj6u{QCf)RFe8N(>ZPl_#Yae_xsD9 zW2J377;q+H9j=&SeNHgaHLD%5j9@x z=#?cEz!*=+5{Ww9?n%rYFyd8@VvIBa`JDleo{6W6l&y-vRfc`mr&ok zSnV4kS}-%#H(S^S1w%R~d`Ln&5;JThlOUGiH!$Q|8>0+WU~Xo}C(0aZXA2Fib`;14 z=tcrg4fr?m2S3bfuloZ07m)nz%l2F7Yh--E`dx|qyy>y-b9$|KQ??}t>9I7M8xNbR zr2{g}%y-|OkFmr5!1Wq_LYKQ&is&#uw~#^Ol(JXol1G3e3_D1j8Q{yE5w)3HP!z;= zIf;+=qTmDR`tFqPOFnXW2er>Y0itvE8wpZs@%79TMMX`siUhopCSRiwPU)CFM(S+3 zdt1iy{bk|6OwRBnEba4=8_R0l#5rxC@NnnwO3VIU?Hx5Z;%3vI?W`vz$^J|=l=hez z^tI_?82jv-Q-Q&i=ZbUI?(p|exa2IY_s{yy_OP{tPDheyH&75B&mJW>^>Vh99tXmx z;@7r_rg$u+vvCZ!$Oc?ehTsj$xtjGYbgnardY73i2-g~FkF2cd)K1>=A;KR!)va30 zu6mq(_2C#G*`Ia)eCT$}b-LDjr#3+~28gP(8!h)`(xooa^<4&I&*4C+CvJlsey7Ic z=>x-&eVYJvFL!s%9^Vf4G4(F(>?xLyKAxMQ+bKe6mUk=z>L3wk5bT+(5~7T4;dQFv?u{N&iF37qL@73f(wcna&2=$iG&v%^~&wQ77;_^+5W}nCE0S~e);Ee6rW=b+E48Xc4T9UP?y*8AzjAp-T4m@8sy*ryj+WObxcrNjtq12RNm|jlp9K zq&QC1a+`~NeJ3$G)x?KGs+I8`o!efy$m9NsOK~Ows^*jB*t4!0zCJf&7~|LnL&Cdr z?>Vyt>*fb6_xm9UhZWv;Q(0SMz$tJ2Uy<=@Cdh(x*ceH>r=sue_DB&?p~^K$Zr^Jb zYD5>uH@iJ>AtE7@G&Kvr!yi$BU~v7J1f9=&J*_^A7gyVKD^JeGAdS z31>7|XnoaV$SSEyy-{j(XXN^>PlLI*vGna>)fQ1|aZ;o~1S#i+!&bGPzW(;S24QyN zEooNZA}$g3dldXEjUzRS2%y;q$i*OSXx)3joc!B%1adL@T7&Q(<2X`Y0s?0@+#L#h zgn`dH{@eKmr~p27=t7u;4A6yg&idAW5WP6a`qm+BYi z?FB@LbUI3AxS}G+MASw0G*-{{cK3Dy_l~k6#^0$O);%l>JQ+s@cd1!iFFl}WKxw>DRP?DaPgQMyF>PP3mxM1A4S6OLq#OgkfG~^I2 zaSyDgc+@2>gVwn=Y=)u%@@1D(Ab#Wl7BZDYPG>a+{IMfn_-mGJ>gI>T_#PyHQ>or8 z)nMXqSvffn_ZfktQO085||LzChfRD`{AjZUdcKfAxt{|1DA+8+HGNdA&W|G`1DAy)PhRm!J8q4=rShgDeIGGf^z zSxcoy2iNRsOHK~H^q-Jl`mnKI|QfjBl7e>R)I2^+7wz;7abSmA8eQ~YEccX~SMCNoGx12hc02^_6VQtnx_(o}oarrAz4 zlbq@|Tn^U&8&fX9O&UfW&z5*?mm9mfz1&qWM+;y(+T|N}&l-QGajdtANJWuUgB8tv zEfg?+hK&4^wma)0hdw#IKiNXAlpdtxqDiKdeJ*}?eG%ny`+YniWxR&(p7c`ftVem< z+mGqdGoDL)Ob5@b)>i)dHvvcuIk-ZOi8JbLYMUpKiWMTpp7Nj&GkAT ze-_iZz$!&8EwWSRJ510f=z_3O;RUMIi|A7GBa(hP>h!Z$FQqoyt^)lkf*3gyihm4n zl_T0M&~}%K<14s@C=WFfmuISwNajs(OqNy^u0R5xwVosEc{5t*PB3^MW^E92AC zn24HiAZ_p1_Tni~Si+q*OHE4)*Y>ogDSI@)tNCsXzutZgUy0u!dE^GndVTq1WGpR1 zjk$r|yTZjdNrvP0j*)Wdw%G;l<=Iy{M;M@%rC$>t`2J0#2zcMXFeTI6%0qRNla^C| z?aO-exL_nG(yHYMhsI4(Tq5;h*^|scyzF4w;rv>vjSV4jgP+uhz~U`h((c4m1I3w_?#FXZorhW} z9%qgcJcf5e@@g{rOp89U%~DlW)qZx|e-EvkJKVplz=%jXnR=cVIf06UtJe+Fz| z0!t!_x$eocSmIQUq6Ig_p`|c|dF|^nT!PO#^}wgC>v=1F)Y}AW>dd05LFJ%Xbljj; zSH~lu%%m?cX`N6#@jeAiR{c2(8kDP5Z&SS3bW%(eK>s!l&KxuhSX2EP`Ugzrzm|2{ zKEsLQvcIzo8;|x(lJ(?z0LNW~0q>p6=qWc`{9!m4AYi|wgp_B#MZRG`%UD8QEp!o5 zihI$ACoBJ@bg=tW$NAeqCWbec<5ulzB8y=XFwFtw<;cZv7`2HD&9y8_i@VZm+SAdm zTXGKBO&69!0d#yvLeh4|!4wuZL76|j7Ikewaad8JGV0W>{-sPzvd?(WDprRzY<)v7 zik#-+WF-d*e^^5j46$sm=Z%8oT=K(DXOu;;WEl*9W>j zRhkluIrmMNcG|g7ft@Hya6*~JC?Eryo>sr?1bx4E?5iu^Ha~I`(DA5|=$<>+`{o%Z z3ZP4_I*Y>m2Af*lrjzT##F3PctHi5nAR;G=|K(I9{$HGm{~}$#SB@(2>Ttq~vXf7k zc*d?iEA5|ICC7n2G$S8@*T7Y2y_o;Wz^s}0tZFvc)iM%Nmj-EGd zYw!*$N#VT#HWH}C6c`&keO9txL8uM>mE;!X>voxoPm zJbI_9x%pmJZ%~(RC@d(Sf}&Gy3wTYke6>Nh9)m0rZ}((z-z2qLV|l~4cRM;x4Vz5$ za?CuNUj({-UT$s_bGgiHk$%BT`tQy~!1|+wwkJ_k)uinVx>8hG0dp%|zuhz1|IWH) z@QQ%besI0aZs0sNVx9eb?xBgw&p^U`xjdje;cd(pRJz5^UBBl{3H`obPyvT%>kXtk zaXD$lp%4wAytp`-4SuZ?M?~4!d>qurEuJ2j$h!oF6)QEdhGVZwh@uU(>&=OWlDC@~ zk2jy(w8*k-$Dtt0QdqbeLs4|a#p4arg>iw{*Nh@AEy{`7J73a!sDfZ#HbGs8DJkIwwVt$}4^^DXNmSma z*;0?)ANGSF@u~py=Ey6bV(jTH&f9Wbq?ad)36vRobecvC0G-i8GQlrYD`Uy?g;2+m zpGRp(I@$DaM}i#oauW9SbM(<`_4^22P4n8M4nA^-OdQ)UWZUapuRmWp@fNVTxbJwf zeVdoGfIJv^M40WrG8l13bY+H~)yaW0e1Cbxt(Dizi$P~}WsWC+mtU_BGJS}k2XaeqCVGPU2b7sbobi9%I{72l? zG}&6J6!oI-p1>IKF&`hri7KX(zf)gXLs81`)a`BPS%U3IlqEr*>&r4O zCC7oEa)J2T1s1UIQ#T-oWo%Rd2P^8QYL5el{*?kZ>reYJj|4&ud_QIXO(XoHoB)4W z2ssyq7S{l%>(mDD+hitx93D{7n2ClSU;ZY(+e{@2Ii;exCN$!Dhgxy4L-}oiv7diO zE+<_gE}pvjOC+G*KW*#z|6%PdgX&zjt-*wl;1Jy1Jp^|`kl;>m4HDel-B)l4?(XjH z5b_lFl`6s?YOP1+9CM5@=fi_HXOfpmlz>;2wSHu|tE{~IOCS*R4X}tV=Om+Ej-?>JhKzpon((E0JrpqI_X@OR za{{=Urtln%0Eg&av+lm!6fUy-n|P0bJsJUqfG%+y|GJuO!JHbL(MGIGNJRCSP|#eu z#NZw^7s;=Jn;jFJ{L2!x0~fcB)i`e^PBmCBk6NDexZFPX%xzSn8L z*ZNtMC|M9;W!PB-dQBPL?PrWM30hs=jq15vW@S~M$&wpjvd9~k69R!w(nv-!lbc~r z!dto9$n%gf3zMH){O)7n_OBC{!UE=G9A0?{l2U*&VXp?d7 zHBnCA5(p8t(IpGSbk94wNDb8D=S=g9D}ymou_YX}QbmxOYtz_{O0t_*MZ_b{@=koS zjK}W@SllIjdX&@~H_2K~>?zFW73ll0$XIlEn;<8Y&g6II&s^odloyb+=9Wfk-JesH z%&7@MF&FI!Wq^HoY_X^LV;4kZ8@0za(%z3v>Xkjk}uXDwrNLB`DGn+I`K znS}tM83jRYeDce}+;&-ojD#DNy}~#79LzD|m7P?@(Wx#G^h%-YhoJ0z^YLzs6&D%0 zjD9BtU8%W&_gv=^&1wY5Ks7BqhulBh*5WOkVbGvOM=UmRP5h> zI^NnLWTz0yMDB#F&~bHw&R6dON^=d$Iywb@8-b8Au-B6Ob<8*qlAaFJWjswhNV=P| zDtraM-$b1hZts6_Kp$zchcnn@^Q>Sh>$*eIp7gb7ss<%pB){KZyc=ZFeLDiu%etqg zEo<<|`@%nXy=eyvJo^Dr1>3m)Xl_R|RQcTiql?#0N~UpVb;)EQ2$<~%Oa$1_TkDQ4 zJUMigM;HC45#?`V*uN5{zyfsrXW(528XIVYaH|F1kY{9!t2zbD4JsEQa#tf3rkpL9 zyHlo+SBeBLMI~(;AoD*_gd8YK!MNL@#VQ(2?qbW7$Qe(D@xCKiz$4YL66sgHgQ2zJ z?TrbkEtH}vHyxS4o4j+Rc>AxuZ+yRJulXH7SWdGlDV7vvhVOl?gTq$gFvZ;8Rs`m! zakxIt@J&%pAL}X%urD*H$4ivCr!Yn@dpf;J;jfv4XHwr#JKuht82^4v=IW`1Jsf{3 z_*7zd`@lf;e3~(Ul9~buDQ|-@?CVcMxta1dix|BsG+kYQnABj5Q)#c=vuY?`viFNv z$AVP4V%(K>)EfUfQ$xnVouDcfDC7!kE9ubiPrT1xM-N1hJ4?3Kkpn21fl>4yy*Q&Y z{in;(b~aD5(C5zk4sjBD^vd>+@(MBLQazqN@E*Iw0gdKouE)DeEScKy6$Z8JZ3CGp zfe6}K7WG8_sg2g@bdJ-c3rgNN>Wb=i(nz|(^nS47roy){E_(}Z)T4Kvm!lATAz9&F zPxaC7Nr_<&OZ``6)9K&B64Ikls~KD830Pl2^n^4up^IBPIzkgeejyV4Gr-O~WWk2n zvK}Sil*fN)Xk=*Qn5wzGv@vj}#vHtV62T=tD5=U3Jr*fyQlz>RB^(YXJUM@vz?}~t zy|F4=Gx?q8mgr|qv61f~{VDUOksx6+*FKUuUSWZ9vPt};uUe{YJd`b-t&m?hCJ*O} z2sx@3qD4?&lL$Zty+S5}5b}b4|6jil?Lo7cE->yP=JrHZ3Uci6yG7ZE+&P%Z?PM5e?J*i4{P^)}E&oLbQ|OYRHWiu!-EJaRl)CsH z>;=0Zkwg*pu4ULU(PxYksWf3OS18lCMg@jenUNK1>bhpJqklev4c%)^-XW;#wohKm z3ubFVovO7Nd2ehXOu&-rBqm@wIueYH`P5$1wyTxXshnx;N|v}>m|VQp6@th4UHbVM zX4R4nC{^?0KTUM**&t*7ifoj^xJt3|v^+$>ka8gc;GOHla7Ja&ShAN!&V*KPqkFoh+G8VK37nGqVS_P=_{$7uF#KJJ;Gt zvouxD$TbxA&MrfR=4!Wus;mDf^FXNbUcVK|=teR^9`T80Ej0|*WN!Onh1WXn+G}3C z#Q5;W&P+~Nk(QLVzsR4NV0?dY9>=Pd9r;eQue+mP7L-Hi9!Mk5~Tc5oAm=8*;?%Q>{<(=il_`SEc{fOr$ z`B4wV{?opCL6Zru=*Omh;gHxhk;4V{JyEQ^;M)fcT1eW!<3@wGM@`Cu4$_4xTtXde z+V$7bYJ($FGY410@M8==tm1GecPU>^inmOf*ZrAxYgCw91POS*t3`mCmH(j)ZAaC+ z)URrhvSr-koYxvw!4+MgEe$2l3LXI@{J5F-o&Y;=lkW6)?m%qM^+BK3+O4l6N%PSZ zA50Flvs8|Hnje&eglbC|bp%5paS}64aw5(icm(VcOh#v)U28r$m@bAO%JVI9lIFop z4A1eK(bxO&nglnK!MkRuLk_nSgpiG@?*36>WaJA1bx^b4UC{?Sv(2J3re^K_PD>@vW`T6J+d!Z1i5hm2HJe}37TA4Si zg@sfK97Z1L6B_L1d6Hg!RWUclmxD-nWXa#7#w~sV%kMO9{2y=8JW78*l6W<59NU^0 z$7BFTI5~un04AiP_hE zoCATK&hw{f?-LMsykX>z|9gj~$-w*i^(9r0cvGEo%cLP0`Raqztg{bmJrnxCiyc$Q zqlRUkAxsi!p>N?2R(vXjZOI!F9;*m~SU@?jmDM0q6H3kiQPeXR)DC&!fE901;E{>* zuE@|&LEk#83*$a^1e3G&cwnQCidWgz$8>WhHy#FB-Yui`sqof9dfd}6);?arYEtmL zH@BIpB9q~Z?M?rI6?u9#5c*z2zkmZN<*Emjky>;6FwtN#2PnO)Xp zcm#6+J)*xRJX5Wx9&tE`cVwn=|1<0q0;)uYuqLH5tjkjv%nz&F#h!RV4j{Zm{M_8O z^w=(#6I%Euru_F9C-4rYe_8$b`^Qt?DEL&7kfvy>I1++gG6*3eROo~Sk*ulNiLuJ- z^A5|flVjrvptT8)Zy}H6LxgaTGM`#X-$LG|lLxi#JCYKL>*05diOjHXPaCr9cU9sV zcXp~Ue>a~ICO45i{9)?B|5!0OJOzmxF9YT2xqAZ95-2&)oh7EQx3`xy$8L{|LEuf= zH)eEuvi<7iqnbl+@7kil7!l@2Tjh=%#M|~3`d3vA6{QPSWD7Gn_k`BE>TJ6?Ruo{2 zx=1(q*Fd>%=oYk|ZR@p)?tTV6^uA^E?ig2T?L+06pL+wM5)wm!qvw z6h=VOb&kAk?sNQVEjdCsZ*QU84rkx2WRk@QFO;Oh>;Q59k0XoJ8ngb7*qpbuC8^}O zqp)DQ_*nAXh=ah+w|}g5cJad!8$U3|3EQGP=fnvqfv;nyWXbOJkYvd@cUt$^j#IH# zGrGg9`&=i|Ys z8LeK+!8ez&m{*iF++7DD9fa~Q?z zWmBxA4+SCUsjQpx%0}HSPw(G?C$+UgX!}z@IcO!=Y4S0d@i{@XnkgpW{q8whXc!pu zv*Ua%V03(WUCpP0RexbkzV=^CwpMpb`p(P}%T;O|LtDFHIktU4tWj2RIq^w|B9K7> zI52-7dN1G}_Hr`@B230wvAGPq8J?pOe$~{FffJG)m81|}p?hS@;mIw?GEHL{K=4HC zCEimUxjd(VJe%Cg8L?8%p~#@?qaa})Ew6wJHX9Ad!+NNT`N&cbq<_Conzgic5-MBI zmDJfg2;En9ow<{)uBWMyiaXJv)Nh*utJX2tDO>$ivwzTa)PC%l^v~or&TUo(Kx1Sl~zs$K36hyRR z#4N6lSi%tx8z6C`V_>hvZqsqsno}ti8-^+iXTEd`-*x!f|IcEyi&dW8t}&QUv7b8Yu1bEtY{n zZH{6pE_QnfyQqo^>lM8uZ*KzFCbb}@PsB`CH-~5{>5MV7dSR18CY?VniiV2nkg4Xy z#R*k25|~CA%E%k@%A9>*XnBMmc1n}1Dw&ZlupIFAZu^8=EKMmT_Y}mZHsYJ5B1k`w zF{sMX7C;*{$+%=aY`f}(tk3jKdOP4aYKMV$L>QNy18>xGpoagB>NjXHS4+$m`WKIN zdaeuAAC2Wn%{#G|Gn|T0k6rEvZcedQE;qS?>ra^SO7C>HPG+raS-R@X4(0ueotR_C zlH2ouhmQL!3s2RB|Jo5x>o5QBwghD~s!<_79NNtT5_bAH(Ac|zW%T8zG5k_O)m4Po_3#eCoq z5JV--%rb2&M#+PHfSJTW5fNfjW4KNxV?H;Ux~9=^9mKj$2WJ>#+RcWM4^F>f3$iJa zv2n3Gh9z$~P0_feIk-~|nRFu;VApNEe}&lH?}>BRUcr9ONn-?FREFbR*jc;1RTD>Jbat+q{nmmArtaLkv^&` z9f1}@NAeExs8(yRK!d{;GBSE#CqqDeZL0%GN6}n*D2D@iOwF6Is~HhShb33(w;}q3 zsxBD^$OT7nvkLHkq7TEM<8Gj*7 z4;bhWk=O_$@Bw2E4ewvzKeB1(ftf;}GtmK4`}s6Ta$6*20icXb@INDjkJ18KD_rpMH48HAXuxtLa>O75ONuav!my^>NfP(O#v-7?ntjV#TK=*a; zAe+)hX`+(OxmoS$?)Fst?$^#-QmM)c3|+VOBEDSH-J8EE=oU8@yLZ^0=4zo`TwLmH zpI`#QQt6LqmQwHz#*5T(6-yM=!sHzJTpv+9%a$T=>j;B3WBiyIibt^cp&3TyW#P4- z9JeQ2t4ZpunMyIEH(LwU!wqsLz7hrun71Ze%;NlUKNk9~&1PBrV?23}UwvKp7f$E{Fo0bux;gi`Pl~hT`F_0KTBBcIwUsppLErprhIBdN~A%J>w>DH&YE+O z-Z?iPvW0=+6Kn(?!L7`M!TqLgKQW~?H72+l>$x=W`9ZSdC~sY)f`L7y9vAyV$!`VJ zt_k66^yi=Can)Gj{$oy_hkh;z#?z{-dS}xXMqf9W=WNrtaaRrXjq+8^%ABEkgHpVd zQlZf-SIUw%o8l>)YzC$0Mhnn+H)EA7Mm#oiDN8<3h!e6qe3`Si*-fg_*E9i}Eyg_@ z>kDEr^57&gN$`F*GW8X?DD;KXY=&%x>-36bUO5vP)aYKApiMr}w<;(y&Twm zP3y|a8}r}3Se&it$VxjoJ&i?byuhXGt~M_+3TET1$qTQE4Kj-xQ)=XQN*azt-3)c^ z7P$QAvR#(1TH^CNEG(?E_W;T|R9LU7wzeB-1ef|F{?T2|6Gx=pWCtI6p%OzV-(pTb zKPZ)_@7DaT2QR*KDKanD@ws#qs73kJQ^f;gR4DPKGZD7eNO4-3Lmj9o9*iV(e9kmBv#zVt~V~#laRg{k4Gv%sV|E@GPv1zy@djICB*_ZZe zjhuC9Ytz_kybVp{&Qe3JuQ4Jk|n97d$H z!1U$fg?g*NEl1E1i(UaQH;Z+9khCyZuu_!=tI!)L@hu>3ynTV6_1_~Xz>?9>=Kqm6 z>*siV;|j-z3S&f;Y;Yfui5(;`O59HBcJf8rCK&o-cgh=8%ACQzkzGgBBCHtmTMucDO%4YCZ|aB)Z`Pt_hIvJEAz{ci9Lu&Dw*oovyn-bB5~) zpXx77EyuF_eDFx4kk>BsXJ&{yVM)4K%uYLd_tszM+RxeQ>c>a+lYzllhTdLyw6osI zFF3xB35hEl@Ad<;vI5TA!`(_pRk$3(jJKlZ5(?mipTi+g!4bl} z)_kUSB<)$nI21b43#Fm2%VR)D3GfEr@&h48Hzkuk8X&O`$lw!ThvsSNI&I*3-%A?` zA~Lgl(G6VBlXua&_9NvXQN~*s^zrAM)IB*zG!j&Y#V3_)=%hM^U)BFe~V@t(dJCn7BD#zIc+$)e_Zx5@M(>ZIZ%NfARoF1;WC@gFm zFYKzz@jI=T~qykC8^Ft3gmf;|H0k9R2e_4Ax^I_+$c~+f%JZjFU&<_m^G; zlg-LX0icwV%P_Ov5c2i;JYI670vYHRQhN?-#LM;M^~CxhKFqXN#<~*OBTuUwhw;Oo zd8C5-3cqK9b%FIKlg%&2e1)t^V^2)1up;(sNyS}no?l-8BqW3is-0=ps$ED{dnU6>>~34L+SJRPIUOgA_`DIVt%#3|0g}~1^q4A>%Qe} z+KxWj=srFkGTJ#J@jfVit166P2C*MFpc6R=O zr=W=Pay}a+jZ}^nrH&q2oy(H_&=%vs{DBJcQwOol*UF={Tt;FrohsMVbB)>uaHG#P zs)D8*aPB|ki@#Ja9WQdl>R)_opOh^Nj8T%1@qezWdtXSsL_QI4SlU;wWn9SXwvA~? zdLS0lXeA{>yG22sq9J*!=MZiD(O#_RE?VH^ZuwSVx!nGTLPYhfq$MH04=l7nG+hTfvu?#~_ zuIUm_zE%yMk1EcXE7D?zf*wGz`X=zqxc)|~Z{W}!-1ayHms{N!?sMD8&k^vRf1N5; z{$w_mPzT#5tu22mU$ky(-#gKu-(4>8^+;z}Q7l=)oc}M%{Tuo}!MFxkqmFc%XNrvo z3iVnM*f=qph00~c8r$KCBGMPI!?X{4>oCHD{~Q~spJPMwkuAn(;k3moW?Q&~M70%g z%Ysw+2F2SG)^mg$Dy5_4H?y^y zZ&!VlH`a5EcIGE&l7Zo_`iE1xRLUG}9BnCgAibU4gUqF)SqI~Z{M!7ngf^T$X-&!U z037X@z69O2U^d{YZF(-JOEet`-zCD$GJT(!fV?{iPhFf}xwG60zO+}r)IZ{^ z^ue7Id=x%MIGltD-&MlA2syJVTEY%4h$LZTuLVBfy6lhBt-q z&Malg@`1^I)W***fc#4V@T{n2SZcFC3-0vGm70?lz#Kc(p1FZVMwTa=#>Z9uHk5Q8 z5?AyfcW;^MS7p}lXeaxEhvHD5E5`NzMcmCDQ+*N5AZ(W-q{MldY11I?hBf#{q#*g; zLgdbjQ%#cbb?ODBF%jIGK2ayTG>(q{V!=RHyQR7l@xxjjZ7qIyYq7Dy*&Kxd9tQ9l zSKR}F_H;?^uN51t_-?12iPpFC)%R*si@eRhuC5A#!VThxXx*0i^Wc43O31zdoUwcl zobKp)h8~`bH|Mu=GoT(+G&{qj6m_LbeSjJsmK0V`(^UYU^)iZq!}8`e*evOsSXi|z zmR;dfm!iki3)}`Ek$+K)X$4Gfl?7Yh`{&&!vtKx(QOC$sx14%}TDbRxxNEBT)u5mC z1z%Dgrmk!W_&?+7V{Gprsm`}x#)23u#x}xC8lNU&l#`%)MUa$WzC`z(q zF&=m>l}Z%fp^9EA-!|;q z-^}Cx{=F`k!bsb(#85YphcW83mQTzpTjU}bCkT=^N~Ar zukMf(l!b3nWlN`(?X|te7^-ZB969B*I68xq9I3&e(fYP*mQSV5nNXj+MEfx7FI?{M z{@3(#ZsgP81!zP3Iwt(+=OzDf@f;*08vYk~BiN5Qm=eYayRFRl9m$Vq=om{d0x2`K z37Yn7P06SeAui+VjqI9vbm-3=jLXy#E@!_XQooX&eEFUoA`K0M!fHvZV+QCqBBSpk zd)H4iMAUg)(O}p0zsp2ZF;!HooZ>Jhbz)<-=7!_H+Ts}y-G@VjF{qqlc}|RS9BXHw zhX-~y)f%2MlBk@Sd-G$mW<%s5r|N2I{afO)Xy0v#!;RlN5(xtnJ~q_hn60%^LwU53 zlj{#INQq&2bV<1AFW1rm^`GSsLm92Si_2YmLFJVJ&$Ybu|3;FCw4yixj_KMJ`iKCe z4T?8PxV!FmOhWhF(N9o9kT@IJJ-qk?PfTy#b8yb7U zcY9a@cgCu7IP9Wyc9No`cyUU&&ee%>BhwH6l8qX=P6yI|`L}7dYC~%Fp*WhPFC1(7 z!;(_Jvd)lKwe-P?>b zy=YACS-;9vX@-re){clkuADhceD$I(X(I9j*jU79I?w)WG(cL1<@uNnO3- zGGEp2u)tFDVY~=&})Ms63>B+0c!PT*F@Q*$UDA^E1&5%I$u9%sP;xr zHk^z~pajK;6uQMg0*t9#z89R0s_H6?gzpW*2ab`gfvn&Sdk4SJsy9A9g5aGCF0lss z5!Bodzs8qVXVzCEnc>U9VT^V256G-l8%74!r z%oBSN^nd;@?(#DROsX*{qO)15mJ`>UjeYE*q*egFV0t<%020CkmMZCXzb0PiYcrN^ z_>6rX!W+nS`W(kKPRy6Iu=p7f8tK}}12K^_V>0tEm2@SV?4R>;PnNuMc+_#qn~KHu z1VfUgkUBXbiL{Ux`}rWmff{MpXX!_|isF&YyD=@#4e{I&2q(hidG# z)14lZZ-^eJXwe=Fmpg)sGL9efqC{y@9m506>2g z5~jCb75ty@LQPRdXD?1%ZEE^y@t>Iw?4?xedJOZ2u6{ZLh%!9x*rc@$b`Clg!iv=2 zv-YF$W6$dD$IC%6%uQ}``tW97p-bsfa;sXXBeX6JNM=y2)i_l-6o%B2I_F?1kUe1G zr%a8N7jQ-$3M5EUk~Tm4>os&=kb~*CA1@6anD~`||9L3HYUAH{lah9-%F2FlIDCK# zjKJ#)U${c`PnemV6+OVw*3n7RmMs)w+Q?O@H7Q}v`}NN(fH#)}JP-s9x1J+y!dr+X zv3&LhD@SW83Fb)0&Rn(T{>t>N3XZfFk!!i&%S9-?*5Itb3^!G3JzXa!$heZFppk{E z&e<_->@nJafOq+dt?*p;Q?Sb{h4JY!KM{aBH$zzeKUO~3;m?&1p%g&Phxt`iFLJYl zK)W$O((Px-Rz{Xp{%QY8;pF!5?#(AE%IvI=)K7Rcbsi>aF`U>V$|0@-~ zMlug$BH;*m85CUHz*1C8ox6%x;~KT)Obo?^IRsG~7bQ}Yq`SQT>;72mD@2vM7vKpW*lf49qum#Y1`hUT}4z5R8|Kv^|kw0c5aCRzw^efyEo zpX>dw(>J0bepMnP^MJu)Dhmk*8%J-)2RmQ2C4^(32DeUkBCJhlk*yA4lyHIc8`A}B zz46StCw^?Mo-nNL)8+JFt;Wf@i+_NWXB4~jYMOcfUNY1Xgzv9{zE^)dse*e3y~t>c zBKCc$)OZ|X)FNT3c3c=1K>Ux?OxNgYsQSI zC115@(v{6szjY`Kot6-hD>PPOPo^6j;l>Jb$GiF}eo$qK0*xVnnHSefDwAck z_5bkem6<8Lpv{+^45CaA9g`bTt5KpzW;c(K9~*=Pq)##KXpmCtA~nxR2(qZ^qo{TD z!#w#MsYqXUhDqL})4d>0o2NAAR9V!$uQoiYHwnpd*FAl2E5L#+WTbS-9N4=^fudiU@iiw9{DnUvil+7aU=zgS{)YreQl8~4^Ob$5Hm&C&E`nGlBx7bgK-Sr__%Q)w=0pTQ@UAm z&S;f6_zh)nis7-j?U_m!8_Uf0D&uu_a@A@V<6%RV;ph&YU0xIQuDD`}#P zS}ZvS$wgxt?owl8Vd;!y2A7Ybr=8y2mOx=K+sow=#8b#5c>$gct~%3*^cC6SxoY;E zPg5^M!M`0YuhADKA4ZHLx*W)YRSTgMBoLed0zHJ=)3d^DSP_s*<&1A3??%>_F7K)EXF`LokTR+yxNOa!r|N8HQuj zmiG-O)JwO0?Op1FdslpskcWB^ zppsTZvCrH4D36zCd9B}*`V|`19n=_0Kb~dqnxz0t)hi;@ z(&s~e$l)=91WFBDi)Cbz0N-(ubOp2DpNThy#yCu58t<5!JFX&FG?QFPhT&U^L6dimHrWP@36 z$Do9%*46s=0@hGqo+JKC<)8jn1fJYt$gmsGTag=#q5-A3tlY6_zDFf};BIyUOA_av zRM5}cb+rUWz30)hvb4Ktkn*8+;u?7q2nwExKYa|{lJ59=VM2~IYuRs%cYBm=j5|>V z_2btE#ga38J^h?D0^ahs76Z@*k#chT%RT2$EyH%;Vr3=x!v|X*R_%Hlxah>+ zv1-)HelSL;%wIXGKCpMar|4EHP*F=BT^X-B*$zH`>YN%qQrIKpOQ&_}X3_%8yLXi) z&Hx`m|2mfgBk6qB8Qm_EeYfYY^Xe*2{RI=m(o$RbzKvIo2Ek93oSEG@utr8<{|Ga&r;V|=IzL8Ou z$I}fm5^xpBwrwd136b+fFu~<-{!k}mOx%xK9(Z+CJA-a3seJ~Cs_YbUJE_SE{uVE* zvxbk(o!m(}hBZ}S9O1dCgsFcMs@b)+s$u|(4UTZFo*1Q#_N4WCeE0;3_mlc#aKc6bqb3K`6;gM z;x#Ol64hge2bc{B{%#0A&d|kJV-bfN)HeBBVxCUv7c}KlDKSX^Ij2AtQ&wl9`sCU% zQpo3`TQ(K$)~JuJfbT+vbr8hq+9lz@NZW;~NQ1@x2r&X^a=a!zx*Rs+^ z?6=Obl|V#~Ma9{&^i9?}yzmI|FZqU!UUYxn_Wfvb{3AWiuO?g<`y(<3^?EzjO?oxz z-3ybieG_elWC^9pS#eHhGe{+^mOh$yh+y8kG>Le`5+=Pbga&w)MapG~D8dKJytuMN zZMc7Uc}}E45dlhS%Xx1V86)L`ZL9GBA~=lE?9!)(!jPU zZFm6fMN!CBNAMSGlEjP}ZP|1kIp2@b#YD)|M;V7>nyp+7-=k_Fr*QoZP`rmppWy^> zBukNC04;nczZTbCDKdghiXoOtEs;&(_F6boE&j~uPCBG^IyaJj-g9j*Qx|)cE_9dm z`?Rf}@D~RVozTD;@NQc3Vxz7_b@5Ty7mO3r1bO=IyUp_ zbW31Tho8LZ?_oK9R-pw3X``BY9Jh)AssN)q+DtTOoMvR+MacrH<5wD2CjDuQ8jIygDsYR zcs-;Qyi0O{1K8FaLnL-~Bbl+^~-@3|!}*kyOF^)rT`E%ujVk zX9J%V+{OUy`>c&|+|`o z6@j3%%sG&zP*X!9c4;zSduE~-brdg8%ddK8?(sUSQL_6M8(xb=XRnWRKAT^O3(^)2CCM4ulP=Xrk7{dHiT$LTw}eUQ=c(4Xz#m69 z@54|Ms`=O;tVwU`{G=eNJIEcaw{nOcaC*Q9?HmrLZxrtt%u{G=p6m!JV6WKes)->j zKe+)uEa3+#D#KwHSyOYm@^ zBWHNiSY}TH|FSwRoASB$m)F@@Kv6O89Ye?g*rfaeMceO2)P*Y0VjFB!J4V^q@2-ZR zCk+|92kq6n2OjPxpFX%;zuFMOWqW{0)sjaWkt)K-wCSnLw7fffgT-mln6J&%Kq&Fm zy!~|gr=Oec#`&knnFvQ4Wb@1W96gQYWv;9vOHJXFHuxKTl1l=POtW|fIkYGJ@d+HZ ze1t8YMv2dnnw{9%zk6uwn7%tSd*#txe)fO1`(iIVA6ok4hhukAyW4_94qbu)%Yj03 zx*UDC`8m$e8SwSa5VrdJgA_xET~nD()uf7Xf&X!EiC?s1lo##z*N6}|Dj-d^w{PVF&i3(FL1XrN_bJ+l6vCQ?}rlrl&wq^%!%c{kFwf; z-G>)W)mSXkl;17S{K$T#%2A~Y@vDm09`&HfxCts?)KR87O$|c4$cmyYF;9}GsX1eycVn1lK!Phksp9zf+tc#N zai=MvSf}zC6BAp2gqA3p)ljH+3oy*u7PG96Sw@Tm9u6+Bp&@bm%y}(PwzfnyEIfQA zi5)bNJY$`zRo25v!(Az^Wh4q{pn`n0h_tkD%`OjdCE!>UQI000Xj*CKeO(K~kl(+r zoE}%vPs^VLl}gE^YF$%L+HSrl$4{~nqJYuRjesw$(JEj*j@(wH!@ndj9#CSL3 zgjaOZa1G7ETRmQ{w${5oeUF+|vh6(Az*Ruix0brm;4uOJtdo!VI?`}c`$iqLZL_)g z(gW_|!oj8*^@iVHQG`t2~9E~zFun0#Ad z3BriDwv|mJ+iI}8!r#4WrnsG|1koicQGfS|_MDZhKJu>OGv-|_1^n}mQ_37`B$1Qs zl)`pp#P}K?T18KU`c@IbZY1NtUxr_~uCRh&LbMnS8CPeh2% zZ9!#NOlDWZmBxxApQffOmDT*Jv{lHsI9=w>T56(Vvswvlf^+B)ymJx>+z@9m`+)n| z^qZ6A(F$?bwrul^&2uZS)-SzQ_b(ewmv#eDH*;{iK)^6&^Yq4+YU6P zQVhpU^R{N)3vkW@fU_1jYzWsgEX^C=6*2dw9pGZ8_v*%LB`hmEV?#E|t*0U6%9gWP zwOUUDc(hVT&SpywnCh3y1iux@H^F3PW<~O+0kC{AVVkFZzt(2a&}^yptC032p3ZP~ z!RwYLaJ;lRYtuw?%5>*kMwZdZ$k&r6!gVtjghZcL6hhzQs$q>{X=4PRv=@l$q$Zv~ zo~wt!b8xdAd$Ay!pr{^L-QMeKV|($yLKIZop-e9E%v*q5DL0taRVq^~(Qcx7cX6xw zeYyOAi0z0c+iWN{z`iI0u)l0b+3j+|YCx>{56QOK-4nLv$DCKa#T>YKE(VUN80n{j5Vum)2pY8>zqTs$}Q}leGdDnF;<2K-y_64&vi3%ifn=~ zwU)Md&1gb@BwgaY%L&2kJhwhEn>C*Ul{7u|0M6-O>7$r~e;xv4`l_}VV-MD8O4M0I z&MfyjxZ(|>J_BTt-!$$O8Fi|)cbRA&p=_z$IHcryFkp_aFnyBS|Lqt9<62=;b($J! zdiKIFwPYsjO*IrH+|1$_XxaAw#`z*lX)hSmDW}8_YnB?V47{i|&Q=3yS>5ja{(XSL ztvmJOP%u?tjCdB5*6Qa!R96{uAeZ^wcv51e zMZL7}dw)quLGtj4B8pd*2H%=)Hm^X5j_x2+2i#t1upW%IKz)F5+=)hjY5pEps!~-v zIUn?Qy8Q;GJGxJxD%S|YZ2@o9qQJ@?XI%h zFGxQYVy*d;bEPap-i1|_!&Gh-2bY54u_MjpF+5rve8(lNhX~l>USxl$NB>_{ zO0VT+-FCQ6-h~=(l-Yudzn5tI{+JW8363#Ojj|cPG^)4a##(xW@iZ=l*NY8Zd&}?PV+aPZ`H)G{IOkj;7{D8gcXx8C{wq$}#PvV72&!{Mfim zj8Vj~GA}iBv&qVzA4PD%#gq~SjhCOrl?;5)Xn1Jwx4IYoc=MXa?K|C(AEaC0W-(~W zzgY2m?gbFgpHbc{aPFQc$`C%g57sF7t+Kyzs5gD*rjSZwL@X>T11<4#g#_s`IHw3@ zSGA|>U`X}Dg~pelBM)8+s1ikw#YmX4wwiR1K(_c2!^!kd8no)8$hBB{qJ72~P7UhS zo<_eQB_gq#aS{3=48$)QQ zfiAmHBRp}YA^*TAK0!*>9_bor!Oanhy8S0V{w3@P^uDmas{iH!lUN3w9t_g}(l>@`QatoJ9fZq1 zEqz}mu@wzI1ZniTr$@Gyy`3Ll=NklMpjx#AMNFi!g6Nk;F3YlHJ0pNLFyM)txmAc#>u&QUm; zUZhTUs)Aml{%(`FO&BA?VL%)3&#UBd*^O$ZUSc&UtWdyQn=iQ=Ot8eA5h}AVk1^M3 za9JZD;JJKn`+u?a7C=?D+xxI0C?V1yA}uK)-Q6wSpwyE5KYba!|B zpRMnE&a3Cl{LVM?%{UH>$bRl;)wQm**1h7W72kMHbrHv&;jzbL<-4|LQa!1BC=2kX zAr<+$e3RDV8U&}*+6YgRchsZSXcP$G)vGiFrrYj#2uuBNiO$UsYLFDu&}Q+ z3mOvs$MIE|zG_n9ECIzO3c8!H_y|*RDViiHopM!_7}KtdwvdE+SYX;LKS8b}u{=6q zCsxucFCd!z7C8!1z6F@y0siDD}ZL&3Q>a*a@ER55=coR`=|$F>%|?@K`c{u>Ty}FcBosT z`c9EHwXS!mFCA9SpCmWfGPE{V0-d7&EYACXGXH7r)7+q?U=IsDWAn%A>LTY&5n<+w z+4TK6-?(E)-UD;Qg>akL9j(p0FL#4Jx3)1~`Gbr+JI0i|IyMPI@0PaMR_0H5kPn=% zt%gC5M=u*v@d_9M28R9bmdbG+sBdjC-Gg~8&>EdgL%%2=E!ZliQ=L}s~eaRWSq&~57aP5jFL~E>;df~r5@5ujm|ip=|$iG zx0`DFwn}a>k=fe&cgR)s+88V26x0>stS(Dvp{P8mSl#%;%(SEzl4>8qv+{1GpVrnW z1~xOe4Wdb^EK!`hmF`K@uF;{{ok_GF$Sg{Ib14E6!1vw6#)w;seWOe>6}F*DrRCx7 z;rElVpqI^u39NHqZ2dzfiM5^}c6JptIpg!6oQyZ zPlqW>Cv%74xp`ojX?mEqL#H5{&e}Z^LcsdwiE9#5mIK!6pl$LC++&j1(3#nVseL$v zLo;}7bv$gZuc7ufEVVXc3n?XLNm1{!#{0mC&$Xq=?%k!CTtOb@lKW+E>G;3kb zs*eYRQ8A2BjRNx7K&GG(smAW`+*(yk$5K!)Q&iOvnElq5)-w9O`_|TYRs$u7M!O*h zj^RP6Yt_L9lY(N+w>1N_G7`0+6P5EZnKQp+4%oj$(+xwK+3O>csP;~`wlS~KhA=qj zqjlc+R5-13+u)+ z&5Sf2Bu@8G(w6O7%LiFqC7s=uZK@uQIBri{N@9$@N5+lzP&%dHmN`y{5|SOCmzKf~ zXqJ1zc^+A#V_kJU-)l{O8mnL*QxajYS{I~ju`(pIgI1WQv85CLED=Jh~CYbt>kN=%wtBc>NUXq^n{)*;-cHg(g2FrCMb_{b!frKh2Xq;AfZmJy1Nl zMoP;>p0Cl3k{cT61MHGvQpN_&Gg0S#4kGTF}slg$z2ClFFd61b+qr{t|k zGjFdKTqn%KH^WsEdo@v!8DUnOvPJH73peUo(|8ql>TPif0ikw3tSa(s%*09z@crCo zsH_BMN8z^9csG1luuw0)%*tNFg=O_vca-LtpaWog6L_wksbhE&JJ?F-DM?*9B2m~K zU0t3*Mo*tQlkXZ6cJ1=4w9BPDC3wU6)aDrprp36ab9JA$>YYUY*hoVtk$5Pp3_gk|9c7oXhXdW-8U*75$>*}XiLp3+B0Yfdcc!u_+)Ytv5lP8t zj#h4GuAVX8)xRhSq~8ovbsQjaJcFo>cPSBcu$`gq z7Yj7COjqA%7ycj;YFaVlVz1(_u-FdVXR){&DOzAN;rw1~2`*7h=gknhv^R<4Z3Y%^0%)t5WC84`8 z>=Bc2ZVMqoU~DH@WL1B5#xEKOm>5Tes=t0M)HdzcFVv>mbujw`bSY_{JOTKh~{}hbbAmxk&@h~!h6x@ebrn~7QS74;K}HU zN@0rV1`F;cDFTIpsnV;9Qp<)K*8Q|62pJ;%x9MQ*+VBbQBCD~FJR{kd;0fH*HjA-L ze<}}KtM(omp_n_zEMPoOrA)b;w+aJ16H*_@l>^o3DuSuN8)97zx8B@IG~zb)KnW!f zKi%EeC6;yQhASp6pCjbfBd1+oJ|1VST0Zu~d!s&R&}65X_vkjZ7MK>abJoDnfixqq zHWg-DBd*;U6uEen^}m_^B7z`td&O5OC!yFY^eif)J%+~cvxJFpzkK#(aqRl9EEKhv zr!z%0=O2aF{!!IF66GdcDh!I@s)Q%^uhf(n8(e(8jt*fV<024_X=-ar1Q3xOu>3_f znKI`a^vcD%d!A>M=v;_xy)sLX1G*MS&>0?@E-|V7<}q)AH&+8?e+2gaVawP&a(ARf zN5?Uz7W#wRilv{$K%`w=ePSB5uDI>@@g1WFowEl(Bar3kw^%`)$xk13tqvDf`kEW* zExH_CXKUTPs_{8)`-rrEU>PxG=w?O-;V4t{#stb;>pLA%GaGcLdS5ngO;TZK83{2) z-Z*`(hV<}#ThdJrNPEa9u~{`Qny&!JVA;a^QPCo{Frl*RgH@rjr&0;x)X7KwA33dG z(z@S!Zq9Lhg(YX_Zah5o@81YhOdOZ3kwXPvzVLQ&5NuLY>}Z~sJl>htMs0Fzzg*&q z*;M1y%pnY_?L?5dVs#uhmEE9~cz9iu=|1PM`6jk_+VfYNQD|R0g^ki1|M@Ii*WBkH zb$80$R*!Wz2>tzo#eoDeI$Wb!B0o2a*gcd}Dp&1tMRuALtkE~hDDI^%XOlU;4z#NY zQBfvnNGI2M0nAumQr}Hw*_~{?;75Iz&3HB<#Qf23lw;k9kdZe*G_0=L%p7U?e7t`7 zj2Q;c0}_dt>rYvSjPHiAWM^0DdV7r<7hByJR(!BNX{e|!gbPjJq4*lG$a#4Zwu#XCVjp7eS9@nyxl{L5GJ1 zu`k|o?yY>i!jP?s4zY%k(fXWjoD<})6Z+^NZKeTvhS?ryR(o7b-@7%LS991Sj|w}! z#drm+N|D@j6eehd*-AylA6Wz3C-RL&o4tI~w2bO|CYiKWP*~tsV4C8(<9Lq-Uba6T zrZ<73HdoZ7cg(i&Gh3l0oiNwwuIeDkKCHQjxDHOpHH*^JzB{6#00uC2>DEY@Uq@!vzgio3 z=V7TDP5uGy;Tt*WiqnTKHQF+18jSmz(pSeENdZ?jUY1>0^gruNL_*PTRlmU^_{H$W zL^3<>VFkG5G#zylIS2{|k?1ZYTl0ULv$|zs4{f4-g|No-tu)kU`pn@|MiCsIKegL@9T6_QAUpUoBSNl zZ+%$5rnwu24$iK?gt5tYf7z-*L`&Yi_qcEWf^tNJdcf&ninMELL-vy8S5_-)R8dpW znLx}DQ|W2lElrw)h|eKqI-|0|pNA^6v}uT>OHAB&b_UuyHYa3d6+FdZ5HsJI5i*s$ zelxs_4!(qHeu&MQ(A#>n);7j^+#`Aa*fT(nvdXer=bn|7eJb!rEo+rgH_tJ;ShIuS zUCL;)&4O&)_k$_Ngh+)1n3EN5^#Y3=KCq z<}E=zS{jCGoHt1N4-se&s1&p=b|&6H%8b5ewVPC^g!@b&7CELhU;Ia63?0BkW$AzG zNpQU06(SOTJ8{sq|D-#{YCBMkU9P6z&dKb!y81wK#v=?eW;$&)FfLTqE$XQNUtVW-;Qh-y76`jA%VxW)`L7iRBEm)$c(6<@!*5~sNn8F(b~F>xZj01{^t z)MCdqWa8|Dc_aw?gGwGaY`3_CvC|F}`WOg)<$(n*>M=$I)1Sp=^a4G} zqM@I(lEcBzpFegJIJaS_E*iRT3>$kArqs1oR(o;nIONXRqt9^Ep0T=~ZvU^+DuMjp zw;a&`1LE~{+PW(6e0WVjm&ARZw4r&vUrdGsNrH0}f*3JoeNe8PD-qsJ@I#?$tb?O^ z%LF)nCg)8FCCOW0+HZJl@^09D!0fWj&?J$hCI&BRcNw`IFBKqPme;%3`@+-iYx_RG zq3DYlz5E*4&mSh_#?axtF*2;g>T*R2^}Rk>1h^Bj^@sx3W+&)m1*P6GPowQ~fJ;RB zy-7c3JVR+^E`H1MPja~amOj4#N7$Aou@Imj8xEdowKak83n{CKj<80pb0T+v%Zt-5xf6BI&aQ_4l{;)$hi1jhI*J z>@Fu>c!ZIcr21~N6G1L!Ay|7B3QQDhz`x=)ptL{WNnZ7=qhhx2;B_6##q%u}1-ZPq z?T04gpo(Wm5SpNj`G8O3@!fC=DuQYmowhR&z3qOlKuGh>`n(Op_R1`LG&8|oe*X|? zdk|Ls!)GFtyR3OJ!@pf=_8i-eDiXs)uv&!ak?U!2;sJ#m1mIj7~ zpHhGe_ycddE~F&bJgzXa2UKZ%vq_T^k`i><%k#uf%Ym}{@?r!7xpMtW(y(pf^+lB0 z>*q;TxqCjVSTZBh||j65=) z+2*iEKgBtO-%_*@xy9v910^qb$Yyb2Or_jdJu%00?AS!;eublIhudoqH*x*W&JGfc zaJcjd&F9?XSJi3{dnUmMa3C?Fbm4#L=C6_Qte^cVd_dpqFmMl7Rt*7e@L&1XUpbwt z_1_Z=aX<+BafiFeKiF!+ty13pq`TxZ=`{%q4RGmQ$75o43`Mz*q;|G=gM|;fv$a>` zfp<>@Z(%_kZm0Kj_)n{&M+2oc6AkY%kB#+Tlr zted&#!Rng5nx>~Hg_6_xCZ8#-cIBUPVgl{crA^+P>Z`4ys=s)xad1Dr;Yx_;s8oepfOHpwZ^zdOax;=hYf|$%9Oi*z z3@`o4vML|uS)s?Q*X7!cE8O{4#2+Y-+0s~`!_?V_;mQ!G8|dUwgte^*2r`g~JR83) zr$igvZts&rl_Mr1vRWMWFuYaNR8b|lbr4A1*bd>a^jIhSoB>aTXb`~&9`??8|*X(qRJadqg(|wN41|D39Q(c<6b_2X))_U6#yxq(TZ%rCUi_O;N1sra< z&5Ng~f&N;g#CPdf5@u@r!7Yw4LbHig&r@cZMGDptg_4rlt@oq9l54yV1lMgW!W}Av z0(sVEUQm34qxTf({Xv>+i;|)&68t*R46Lloz7T_E*6S^CdDbZ6G!iBmW8nTM(EX?s ztM#p`Ttm*3QjxW4@T*a-PC5#^;<-{Bpc@cPz|nYi&%}VDo#lWt7;*Lm`D~sdBXN<@ zjW@2QCeB&ahBZMPbQI;eJ+n#`0XP5hXKas^V>=2A*RQuU@!g}~KK;YZX~)ymy2gaX zl+P8=tcKpdE4>~Eqyg5QXvUqUH!zA;E1#;ZO#m4M&o?M1Ye+Z2Y7R1PHTJJ@tG^qy ze$td;e-CVa1Lkf58YG`deO)R$bVdt}vmh3XE^ni%$}Y&jmN1Du@2yL=IZ%a#@=9Nf z?Uu~kV0^b|EPalNAIm&4)8{XAD(+54;?5d?WuYV65(slXloz}CxvubZ(Fu0`sbIRC z70zn8-cp(E``XisKst|e@jxB9{4W@$MXLU_jOgGb3ES9pUrAAhj}bDX;F&Rw|q474phb~^)GQj)!el7kw7dwgz+t;}vG)|IQCc~L@N{&(_b3<$D&GQO z{k;@WpfTPm3Ykuo-kZTv3O&vnbva;ay4@Ju-`&&P7dKm!{~}MIaj%)Gi{O z#g%U1(5q;9zOn7G<)?ig?NyI_=K()(w}cY4Ilg7Hui;3gTZSD3Zj=(~WX?kJ-xF87 z)|;2e?1<*ovfk*)f73y6?$c z99?ON z!GWh@iIc4|8KfKSNGv{XN%e&;7V24{$nKq4`MO|+hB&*H5;`_qL~LxWZdK?Me4ja8 z&h^W}6{&CmEulIe?yIjkSzE&+)_S}-ijZGWH~-RbeG%?-zv(bE)}g~Jv*r7O+Gy%Y zXx!u1Uqgti>V#SQix*9Yngu#Wh8>fU794QwwpRV^>&<~>CMx41lUy(VEcw@gRyZd zu#ud}ym-P%%u=b^7Ot+!z!Pde<9#9w2u+wr5^?8y>{#9FG~Np&e3{o21Dcd-naEA# zyTrsqp9!{}A63UM+*|lC+F>Mfoy|R;X*SU&Wnmx-`?9S_*>mje7b@EcudjFS?>}(% z1ux+(J-ruqB0~%-6i!ZMr7dxNLcwCL-HAzZdQB7C0owI`4v!v(1HJ(rUQoxr`z9M< zY5ii=qG|j39;3ms>9nG)!eeLMYePyD<$+B#vuPuA4Z^Vo7qVqDDn+%!CFM%JE!yqz zl``!i9HT|EA0gK<7gMM{n?0m-+d6` z3;}}aqN_jS`B+%XWd!%-s`|=Yavo)Mq3icb{WbLyo|uv0frzulCT1#bJrtrGU~&BD zn65URgW0O))s=>&f$V$3=jhnK+1aDV642u|2f{u5ZZPElG`ZkrcVB)(5}2z(M>nESCkK;HbgwD}2)0Z2U}TNm z`9rv_iPVYffL8^_ja|m9vmJB(!^Nz)^Il44R~=isY*)R5IS4%gMi!$lCY!})N<3=> zO)Dcr-9BG_#qp+yW#kh#3-$)(O8t+O4!STo?|LT1Dy!tP38L$(i|<`dTalexd&;Q{ z&_*C1$6e3vE_off@{bwxQm-byVhO)c%#lOxZex|67iMKZ08hzObe(P1<>1P;s;r(J zjz#IOG8ebV>9_;emjx}UytcoVoKUy!>cqr|80iWSEmYI1JUz9YJ|CDq881*5slNcH z3fBb1xysQAAxadf!U|pfulp>LN--=RB1?TPL6|fWJ zG{ZYrvw*aGhkDk2tj9`Kn$sV$-uWqOIAHe*k{83y1&Z3If0{GzJCxED7j$S) zHpf#Gu;jb&L1s^V6PXc)k~@*4i%dOZ`6|jmQ-${GkzT$U=!FDRvm0S%f z_~pIVir4oWJVxM~G@Mx_U-(YD1H8#Gg0YDhG5%q@i+0_u(KW~E0lNlCOdo2E+bZqr7o=?|7tmLH>4O(U_LqVfiV zIm99|FZHQVb!5?plu2w(=^CIxyR%f{%O__rSYpMy>KF0{B5hs*et$|D zB5Tp_UhVmdrk&a*xkf9#==!LKDm!crg;=hKa7_#k0?R!rLP3}jBT1v3wAbzYjOBI> zm=SL|IfJY3Pm;18uM4$eBLrw+$rfnTOfVvV9V1g_m@>M3=eHOaPIM%MFkf^AH!ImC%Lzl_5V>+r+F#1i@9rCMpG^zzE z#&_)sTk*_VH@<;ItCdL#9r&c<21VUYv z^7vI1seSx`-sL#cf`C0)MFnQ&5BtQa9zx|CHXOxL$;bj=7q>=~&d@vSWiu6ofZWeQ z&;Ve=@~UX~ycyXUUK~K&)^vUDSKP_^><{64LWExo2*2wjj9M#FXGPp#0eHGiG}CIfc9Ju;^+xJ0eeRc_LlQu{1jWKn*`D__c$EJ1TGeY+*~dQ zDn4}1z#fO5jVsT8sYBpNA0?6-4SaAjH~QctYuSaiNd3;~t)z`LMzWRW!G4M!dY-?( zWkufXt?~kO6w0v1TWv=JSf;vl<4K)F);mqLvmw$9*UhH8+#N)-V4Kjyv#DI8E;bM_ z5WLy$kn{el*NE^{@&$KJNwc;=auUOLq18$8~7kuSqi6v4vIj+B(i- z5LS1q=KsD6pz}PA%3eMo;}d|Ay*c+LPlR6)R5KtVA;^C>4{Nx! zz(IpZU-m$@&G9~hY;vCIR77}D;8ig`uAt*N(I%88du6S^+01vBcQL3*p(dErSF*L| zgg<<$yzn)2vMRgAK&qNCq>++!MXZ!JTPx*Nm+KrHxHdB56Jk>ht)^lHWgS|o*+N~I zV?mc4&I5yM0z;$bnkb5Xf$ZIk+`H^;4IiBlobj(#LoXD+pI(h>8{YeA1JSh|rGeEt zPW%E9N2FVZz1mt~ZTlq(p7axo9qE)}lYMke$?p~9n+<9AU+_KdBrvc=8el-XQ;MIu zQI=TglTGq9GvJ$Um(hfej$0;1D8#xGXDDyaw+ugB_2#`!qzz;8R#df5FZQs;U{9P zHE#W=XqoMNwVi}-G!QgKPC=mxD+@njRx>Z@aJnP3))%Wp>U8b6IE=zheqUv7Ma{*$ z>~y{F(K1pyA~XD{nTtAPb`k(&-o0PxM6lkHr3)T02U2lzxGz`UI_$S~nmW;Fko$@U zP)j%!NQQ3O9gVJ;R#l9VE19;dWXZ8nDpkHKBiM?MZ>K8@d4q~#f!#k@t#dU>6sSgz z%W0h)8n{Ps<*ZM3gc+wu855_jipSv|3B(v_DwTfozO5<>xmtpw*a8@Yu3_o2Dl!YC zM4Y_v%`_np7H#)f-WZ62iHVxU*OU8qu($}9yMf6m3kwEvwo4p5H0a-a6%GVS!h11{ z`TV!nwN$hi>@wT%K)}Uek6JWa+PT>%G$uR_BwO`I*XKIw9WOIDY4ubdot>2Q8}%wL zj3idg#CrEPKSVp9<5N|mN~W*z(4g>V5ykqx5(QoynjhVNSwnak4sn;7t3s`+{q`y3 zOe8*XW06L^>2=(1_|Vl~doiRz`P%#*E0Lk>U4cw=z?K2D)eJi+-u;F3;wLiPK!Cj;8?J9Q+*dH?Q%_gAhnS zdK|L1u7J48H#CY>(fH!#;4(yD77IBTDoyZJIOfJSh(#{75|Lk=M{86G_TDeN@|U&Va9fHuwD$R4H}Q@&z$*W`Fk z7vfH@?wUA;e~o1&$B|M34zu~!GaQz&e5M`4$<#Y(&CkBRavo)O8&U0enQcDsqw*Qr znN>S1BP?muU%cohh|7To{ES8RZ~OU|Eqy?Ed^YEm!e{9V3#y(|u>3u2J(m9a45&ZP zaK`4DxoUgu(Cri$44Wlf6?EO$GPl3jk?>~5mlXx{ETc06evzdMG7=Jesc*`bp8q@LnnQ4t;N5nG@a*h?!X5-9*9Y?t6;tc?Zxo6 z@&m$e8v08eris6#s+TfBgI+Pfg3BEazQO+%F1=ztX)a`d;ajJt$H;6DiJ?JtzIeDB zn4VR$V0k}66Cv9QBX3n4Rxx9}J6> zqeNYtMKh@_!V-{cKsPeNy@LRFimyJpze(k1=q4ZqPVx>47F>z**sFrz-t*h|?>-U- zdiNR z_?%3Qtprl=&^Q2?2MvSLDV2$MI4i zQUG=sdh%QPrcrw!t6c8!IU8_&P@1OWwLwXh| zbM#v=$!1XmV)??97s-Xqx@-B=E1KYRgwLvmc?nvL$>Kf;f$#LpQE!?pDSQH*k^A&X zSZ{QuW$SHrJU)*u{3>wg?5#I~(d+XeGANQ3LE zX!wQ@i=~9%*@AlNxtbGc$TIut`T3KXZ^oK*o?~sU*4ftQTSIG3vWBK&sk@ig2Hy>7 zG$zHCH1OttbL35zkhe!EL$f(G{KV0N7`q~37O4m+LMZLErQyV|Z9RtM7*7J6h1KVemO{0DGq9gA5-JJeODhKc;+nBOOhnTO&+ki`BY<~P zk#0TzNqankz4YUiZWiIQ%+@A`1>f3KJhh>HB3zPHqMV$gp_BBL>At(tf%ikko`n+X;Y5siw+UYAa=CgC?4 z^qN3d?uiZREni=ZLV+yAOn1q+xtwU4iH zQ8{XI+4mIP$q}r6u&ks4EbiTWf_I(8D&IF{=;MOLLZ{hKbPQbLbS%vDJ$05Vcp1`` z?N(}NP{ObZC4jF`X&^cKZ~P7!f`zj(Mh{bBvKIlpTMBFI3tKv_PZeqLrE#!oZ1Ojv0#59Q zGhzJnuC}kF^0dy{DsP7MjU8`{DVX1)VTtQLRn9Q5(+#L;o~zt}lF}+ovd5Q>G3$C! zEI9N$pYY`ND>Nwo%R`9Oc%2Z%wD}USxxCU}FIz4iIT~?G)0dr@n(rozbU50_WvfHA z=-&ItE?4=ce3X&Hi=m=+e`B)bYb@fj0t>6xCvDhO<1P?JDTeOH=?KFU6)^X)3_+|$_Miq~@iP!;d z2bOA19BQW~Er?aB8uOiG+UCBxg8jkZo@8~u9mQmLNF5V=vT)BP^doJGd}#2bH*|6LplBoaF5IrX zD96xTSfuCECnQ(^yCUX0TO$0T(E)gX+RFAH>hTD6H5pdxA zUQt4SpiP4Q@RlX@>kj^va&-c8;VUj%6O4q1-N}qf_gS`OSYzWLDdldvn>*>7&@9;? zXQ0S~Qxt4W!upo7`jT_8ymKQWDnOxPNP9GvIA`y+v8l|A6iX{mCSz37J?`vDL+qCt zVdn6wU085vigfyR(|z=L69;<6MK@Edlk2BXEs$3bQu#l>0)SDOXfzWE45bhqk%ck3 znUG?8hJf^ksSsGg0O?-BB0*aM+|%-EwD>AVq!4GDw9}ij)2HKNrczrpO!YQwd#vH& zBtbr-#k9p4ho#6y+39IQpHcSi(-ARfF}I4@vaTwoXmJa$OvH>&Fw z8;gTDyR;UCF!ma9Au+&NRBasCYWcS~S5k}POPMZ@pWk+(C!TzNyle=XJtvX3&X=#U zS{AdkmuN^oU4jOs&E@jfENt#}KHnxz4{zEag;$+Ua?e1BD*{OQn!tK{#Wh&98+UaY zeyGPSD1g6vWehQN29r6BB0^Rh60hHrxL*;qhYgZvaPe6_ZB+#ykURbnXM0{iP8JOS zlwT(^co3=J=BB(ezo{fqq{7{+4$H`i%snsikW2K3nmD*tN#vu%wxbzb;@uV@#p{zdtnzp9iT#DNFd(UC zMPG_sEY%+PI3|m&UO;h^K*ZJfKZ^@OAyp(V$R7SHo%;1fI4(pp$pLrflHB@!Ibp zJthe?tArUbW6N`1G1sanU7^nOIS`sOqqmN6G=qn}kSb$*Sv%01mt$ao27Py-$P)A0Nb9#_$V-1q9#F%TSOodFKbfegM(vy-%op zj|~@WrX1K~h$tB`QoUOzI=FwYUSC&i_1ul6!Y8W#IiVm9yB%@U!w(8otr}w>lbf0- z?WJy=zR!TOJu`c|#)^mYK9uxDQ{PK8;1o!ZQ)CqWucydqBxhZ*O5t+z0;jU|s-|U6 zEdzlL{ax}O;qxT~&A&(fTgM_J$MX0u7AUysyszy8vtWi;>UHpC250y!XxPlB#!K+T z3f4K&qpXCi#o1%(t+2B)jSNmw3#7PaV!v)eeXKNFAaUM{;;KAUmon|b#1Qu#Jtx^? z(mhI0_hPWAtSQkdmYHOEYZMHJjePjb^Ollz$EN!OO6R*sV26d_+OhVnr}+6-$Qff9 zR{yVX4{-ts4KC37e>lQ(2+P`m{*xegY|6<~4e`D^ zH}P6j(K%CFaYaL4xTP+{fQCC=nu3yE5Ek5_J*bJkG$=@kj?15=BVaTX!3d8)GIQo+ zLWx24T(A$2QfVB^$Xtm`Um5OoHJ8w54vQz0&-{>(4+$Zs(tY!jusD+A&WxI_f&?s8 z17;<00Q>SIRR8g~kZ;4eAbwl@S;a3LoKc?4`P|VT zjqH)2RC4)Uf|kt$6Tmd;QrJ-pJZ$EOK!S+U#)k<#f3r`3m{KmUR%afzRlHT@Q1s>D zJj>r-SP;mn1xNyg4Cp1K`oHud*pC*5y^!aRToK9Rg&vVA3LKRPo5-q~9UA#GqOLzX znO4kqahm()q!~GRP^B^{)y1Xd)MIgnSz%6pU~5az?wTe;fNU&J`9|lo^|C8r{WBEu zArs(>3sIDifQI0TuEFoGycm>;{;YjK{I_Q^q)A~){v86?H!S(XH;sC|vIvoE!n)aoEq7=h6~DhCto`(PVy zJGXcAz0!0~KLc$Z*=FQ(!B(ujesKA1GWO*y`}J*&&GZZrBhL_;?$FIo_9bN8TOCkR zE1N3c#eo=D@PDSEyaXGNiu*axUD1UGiPFmd5v3t|ZYzwOtnXIwt&JP1hO^4oUCSmQ z6<5Kt>v=H=E>c2y#Qej4^hobiYs_W>A5-wjZSs}N4x2HL9&RTF+}EmuDxjAR=J>?+ z-MQ<}5-rwyu38`9wXrChz=Aab7XK(c{Y;Rpr6Gk48K8_ekumg0H?kr-nC{pf1Tmr7 z&x-@Rp3i?z4n83PI+NWF{8~Q;Yz+{R<*FVL_5D#Px)$QD8sEeL2dYd<2@mxNZV5FX zhJ*;nI8GZOwuno}#kCSn1Q>szrZG!gKf6`L0k& zYoI|WC@9knDy4mb1SBLR^4Rddy~J;6(2Z^nJ3|NTsgAss>jBT>cG}933HSRDUNOiB z#LO@-@pAJ)l)#fy67pWL$Vzx>tC(?A{(v>VTn98NL^0aZXg>nb)Wd-p8gzA#__5>P zCq_1exPhg>{i)KqUWwa4Y3U7jo?buMc7DN^)xpo{Ed9Z^?hRiAfX~|nPzBXZ;S~M< z{ee(k6}V7i^=y<<*6#@iEG3t230as>>?=ypp-m!@jH+tWi^7u;@pdZfx z@<3gKp)h_`cpv|(3O<6GCJvuWQ@=bRc}|q9e~{WMp#(sstOC!ne>+UxLu8;4D4M5b z$FZIsFgN0MzP95_=EQy9LH(}tM1SjbIFvr%xvUc&L4zh}KH(y~FyU%@^=HieC7W^) zi0(klC{U(WFaYkT(z&x_Iw?91_*^GA8muHf%h;g9k$>nmG~Htk2pwlo?|D2Io0wfj zxWfTeGvKOfLxb)vN8ByWBfU*tk^3uT_9UJ#nlXNB64GfC@AJ_kSbkfwW9Qil9v#uO1~6 z4BD%AasxXoX3aiqLTn0z$NJK9QA{wHPC)ikI)g5~AMA_7;kL$m&q@{nn?@olY^R#XNCmc%OV% z`*vl2O8hdsYggcnoz5k*qIH5*pVOV-Ydz`Iq<#e-+rr>g*-(vya&4Q1?8XV0Np{yf zdOJNErgs^VHsl3*@TbtU2^sG`;MBds><$ zfr;a!ZGi3q5U4rNTN5sJhf)8b{@+}njH!q48iB@`*$R*#63(L})(w^o1fS~okxQT{h!G1IW?e;O3n!pz)>U8?X?CYG z#p+NqkqcCk{4jCLWyh;Y4l&KSlDF$ZJ`Fz32wg8TA>EZ^lT&3fZ<;&oESjEoU1eTB zPgdTWmVVQ8pMv9JxN!;n7#|serxY46Ur#t!E!6jZB0lHv5&6s2_IQ1p>0B!><^6{( ze&UseUG>`ldVtqf;h)(9cn9j{TF3t1PB2^nKQdz}eYR;WdFQBR$>q0E z76O@BeU+XG^&9rrQ%)+@rq?gOzYn3ut2Zx*W81A0Pfaj=eXho^&s(Ley1y`&reqPF zu&tLM@1kFT>mzc74-FbX>s5w$Lg9&Gz!Pfi)d2X+J^K*$XS@rCAg*!)^j%Dq4Cou& zPgg&K(LOS4CX=|kqxs`j)&04RtFOwi;Ny11wwM1f-NV0%DR=X*jIGmKHocXLN#3VF zw7a(siUOizQ?LBKh_)ivXDcx_S$dcmYtb@S@F7Qw0`@KM{{@gXDt8@)4TiW8;&X91 zTLVTc?526c^`6aoN`2ynjTdV?ehIdVrE94QS*H0j4e-si0U4o5Z0lr0wmgX*TXI)1 z+54)S=2QF@C0BAfEQ3Dl@*!j7QJr(44`ZQfCia~%#xIgF)QySS656~o{vv!e0ngkt zV=+i;b5(;FX$N)X6lbwhA%p;tcxfCjLDN80-19~XD3uIy)QK>a3>AOWAdb(tn z=NK-;wL_1{9BS%i1t?n z@rD;A`3%1)Hny16;w}`G8YqqNr%5(X6jWbTp&)=+x8xWoB1dgyfy?5Q>fuL#72bQu z@@%^$jZLHu*``c9FFw9>==!cvBKV>FQ&82LZ9S8AKk{|@HM#0BnU2z+RUUU$NC>W0R)#@#fenf{pq7t7#dK0 z)Fglf(|4p@VD257WbZL#5D9GNQ=%v|xjs*6isw&qAR7*-Fh8F{r<%vrOfo;vnBT#n zY+)LB!@2?o+Zq+kozU?Ura3 z!S;}*4AOyFnSfq!())2tvkA9ha4F)cj9V2C#apNT z{X#<4ej19Yf7V>5#2_s_B;v8zpF1RM&&S2sfl78tdw+FFcp~Bq``gs=ALCl>yvWIP z5%50%Uz_WaKkD5?DX$VQ0J|z~dY*G**kR(59N6yj|44hQpt^!?T{K97yITkl+=9CV zcXti$?j9hx1$PMUZo%DQ;qLBk!S5u$*4g*r+G%JuMABG4vx$uD*or?`>i9;J`N@FTVWsOqG1ARjS`vOLBgieMQqUo!%N#BbVwp5zaJTqx!bd{=JpFlJU}1r|xR{aC<%afbuei z_*Pi_V)=iS%l~5-^t;-lZLSGir)kdp_K%V5AJgJD^0zklADkn?8_DJ>@A9C0#9CyrerHGys?#!2CW5vpL- z_)w!%+JOe8AhS_*`{iv)Hea{O6-n#Siz{=A?9`vQ)>=E9FvqNWd4K!x5XrQoT17zi zMGQ)>K3vYD?-rBxeKxI+qyoANt%^BBOM2QH61KVHPwWPCVr|54ghjP84SVU$KHlFl zL#?yx{+|bJ8$ke-96YMO?ID9w`4*F)1fW)Qihq)uqi}m}3~1Ns+7>bN{M$#e`xIIx z0c-&)|Bt_i)IU2=uwlq)jSRe7>@Oqs9t-7T?{Zd3DGkC~{-OJ*Pi=ePzG8c>yv$Y{ z#hN;tiWrsTjDLR^f}VD<4NnwD<~o_DyjUxoPBp_csWT;E#!}q6OXaW--!O-M>yDNj zT@cAAm_{0Yt%ti6523WB7(bafs8T=0WqWy2KGNyeuYX-8rTsWbRvOqDk=~cTSdue1 z-rF$iTZ=}BoF=aD=N#p2hW4#9=+r9{T~m8IWVHT2)siPt_N`{le{gO)4R~5rLF*$P z0EX@MkGJ^mDYu3E*X-pnm><&_2QDpyk)*VNz{1EfvnK|vF5#JBtMOOGV$HHjMfG19 z*nUhIzw({!gPYWB^T_B94Rsdx1Imr^IWdZcx5GU#52O&a35#(3+2cuOKb#=DT#RcU zZd-;o4lwx#Fq;q#Rg5&5=*T-q0wB)L;+=^9T{UI? zJ?A=zzRkH$av9NI0V>DF@g-&;X|gD@JZB$V-Dc3tJjSMGC0`oVk3t)=x8}k2EgdS~?pEse;*g0aR7{pHni+fvXZ-jvG3j_FiAB)Lb4VxtKy*|o1G|f^ z)t49RKz)`2mf2%R0YL{OcvA-On8upXY`8_GxDB}eC9RGWkC{t#iI<@y|MEhd{I9pY zPuq{LKWd)tYJg{dX*S0GFRdyh_}_hh)f`E_3#!(V#a45aTKBJ- z1=ak#2e*@-DQ&B1IGI9<)YM4x6h+jbjck3#FU*8#Y88m7g%z=Mi|j}Jjcw@AMn~G> z`RJUI4%z}NMEQ1Kr0T2Fb3hxK+u{16tJ5KrB{&ijIH|Z0(GWk;nET9%6@iX|aoVfI zThP>+4I1>B@c;jp{o)A&0X*yD0#NB*wVA@P|MPW&`+g@_%X!0$&>Wya+|d_y_k6Fw z=Zh*Y=rP>eH$IE15Nusaysu)7uH>iux&zbx{t}Kt3l`z2Zf%b4aIX{hY0SzM@ zCg=Ai3NUT33kwHH62(u;rNh;GVhm~IIQEkc)2+%7AFtA#wT`I<#k<(pm z3oQ6LZ_6HlwqvD*7#|$qV-ysa_Mdv;Ib9gi>2yG!B2x;uZ2vGUP_G>payjEWE;X*W z4>JOig8v>IANp|V9a{H76C3~Q90c0REAt2M*|)ln4YUypS)>5)WecRwut@0mSZ|Ca zegOun_Dm~a*jQuf4*suU1NpD-E_2O^n*pc=XgGxNgq+c00QjF9u$GZq>$oSgsDmcB zHfEl6EdYuKbDurQ3(+@DS(lhnPyV6O^k)!R!>pP<;z!{WO0u923i?R7j zW|aILkpDN&IZ+K<7a%R3HC=0{QEgf-Yr_i-@(i6L2LS*fj0*dkDEVJM1AcEy0bmus zu2$(a9)weP3zHXIJvmo9_S$J&cN3x5NrZ7a9Xq0)aK=gP(Q}A0D_%7fDeFCa^F#{` zg;y5-YIn@43=K;AAxjf%ADrX(J`RY)Pq8D=WSlr!>YwiMxG^F`bITgT~x;G*cs) z<9M&_jp$R|C={J83!g%BqUW>N^>hVfF)>AyxLMOU8zS;Qp48DF9b|hRra9C)J5=-1 zj3&?WG|Yh(@~9L=7b0}{Dxe@uVxV|E|I!L64JQ=!hY?vx`vSFIM`W3mc6)EQ7FYWV zUH7ooS;*VD{1>SC#0~>_IJ~^xlG2vR0(7oSZ{69y8-J&W7{Uxq06@MsIo%p-%f7>; z&q{B;b7L-+D~Ff%)cag#IXhozHeJl59XYq^GveXES!cE|U8tBG5XPGoGNqGP5FH(T zebhjxSQOLy*L>QimGTS{a9iC=C}(zC7E9)Hc>#||W%h4-zEJI+Wtl1!7j(CJfDENo znXaSK+ym$!IJEG8pZdTrmSq2Q1YwbmLz)l-y$F{yd`>am=g@31rMPJ+XP3rS2nt1q zy|cvr&Z1o)Ws53e7D}ZJZ-gpJnH7iXPU)uVD20vC*v#$J+LoKkbk^>MQ>nlkCu@yed{`&)@TDi@Hk0>Ly^>L}fO3cfX8MJ1Z1luswHk0(_7}~nSdh1# zL$SpPYa|j^WO901JcsbT%h6CK*$?s1FH+LNCg6>hkd@*Uq~VPnEaJP(!M9Txwt#BY zN&^_An(j{WH~C_?UQb_q?)+w+XIF2mA{QophUa4qK2SjmBL1%{D6qFSkUAI6qo0Ci zG&BI-cj)1|aNdEL(Pk0|3@K7z1B&7B9Kh)ON#N;{O zXR5XAl}JAGIT~g59#I^cW?yC6hIw$9;p@AXL=#aQJ57&ph(DO!qZ2%5m7ZTFwnRlm zEjsHUsx_GVXhcSm+Y6(IBl?ZY z5ku~94*D9~nPy5<;?6`P)7fd~brMz*X;374g+6o00Docb38?M}jBuxc&zYP!G^<2E0NaI2H^2TsUBy0}$(eL<@vE#8k@}=Bd$V(nm#lh!s7#u1ti6p-c z^~dCR8d^ewwv*K1dvTl`p|fkv%b7!(gxVDDDN-&yrKz+TV^k`5M8{+osC$ivbR5X} zRvuY;REahvY(FWv&nrFV# zZB~*^ZGxKS6Qv>ZA8V4BWn{#&;!EZxuh0snqH|K%bEJnY%vMvG44(^hJq2_6TR&gL zQ!D?&c6sbQ41GKGYAd_5$GQv7 zinS$m09X6|tx7-l?%-puO4-f9b@G+tUH6z#iCcm5=zd$XyUC)xGK+E5G8Y_QfsxNs zSt$7F0C5iBt`wWlr(RkSDEu>DvpDuE#k8?HuTZrCk=bWL6`kH6M%*SB{o^I3VS z<*KDxqV%mz9VPV~W<8sOjTPQpV>5Fv&le6` zM;sk|K%4aP`P!Dp!q~9emoSTS(H+MzaqW zn5iXuY&n2leRnJqJ}l>y^In+J+kQV&4Zc@qI=aNjcUOI^q}t*TD?71YoAgGpTf{2o zwZ50DZU=TW-GmUdNgV_|!tg;cnE}(vv%D?dc0_M?xgxgJIoqXTp~%f_3d^V|BCUm6 z*@#K7bMBdhY?!-R<-}ERrfKqDL7{VwjJ69dJTHgCIYC&ipG+ z)c7an{?;X${NHzpe-Y{AJ3faub3GRn9~HrmfPjfbzP6mCeN!TIPSG;X75$c1wcBnJ zrSfnC)$cxRR(amU0yqVec@pCnXaxi5UrvT?ymL_u$RwJb%TlKRBa-1ueRJUCLxiT` z%ZX}%T#$2_;p%DBGIClghZnOlh3aC`(CmrFelnKv-z7C$!nCqbk$TJs2(2TLZ#MUX zirnL2ukV^@nY|a65`u>BCN?<~b4?HTj7m&rmq7Z$szFbn?TaiIxuE({t&-&opZgUX z<}-i#2#={Ud0yIW=pQ4IvA`kF>raCYkA#%dFCv|@;(_#Fn2?j%5~ZQF)Iq)02nc@_ zn;=`c97^$P)NP$fGjycKqarSWmBJ^pbX)#(<6!XEv4ISLw4eS3%sWF10YJ&=wc=jP zLEfG7#PJwoP>Yp3eWHufiRM@EO;#54pcuk9oXNt95VfT-U+x8aDt}+KxMz~ zZZ`smk*QTLxk;g+76lJyCH72-`giAJ*{p*6jC^pEWxATy+4qSKtqDpvgnKK?su)|) z;Pr1RGe9o%4<6=!yC!h-Eh75Lnz1#hyss7nij{MU9bNldqo5}yM3OU~x|pU53dpoJ z{ap6wv(0K&^ZghmuGiPO%=#?OB%1krUi>5yY>&^o^qDZz?NYwnnjOwH2aT*PpM6}c zH)t4<^|C|1Ih+vQJ$X5ASTB_+E54rm-OnVw6^#;W_WIR=YuC_zQ`nchGr4gYeDD2!z@yWggjsQp6g>=;l|~vdL`9~$POoq z>V8+~BcwK9d5Ww({a$8AiW`ZCR2YMA4_d4>{akP;3G}6rWrZ5?m&re?ZADv`7901z z&y%_Mkfj0 z(fh+6oGg)iBj(uXnihbpETd756X$_kROB$P);x$k6oH9RppXR)Bx2!LL-#zZXfDyT zD|*)tl|0X85~u4FC)}i~s7|pLajLX>^(WVboXv9+q=VB6tt2evPdgi}Dw zkx`rI+>zPCXIejGEbjAtXpMS(j5@2C@IXH1BNUJij4Ks!t zz4EW4O0jwngf(+JTAWa zVHnZ(XWklLZ9tFHpdzE;M0GXeNZY;$LBTo`qlUa7 zBO4mfSnKbeZ(xTR&`mb(Y8dXVP0H?Gfc;D9?@S9IvF7b{?Yg@k7vhDWSU*o}5SYc0 zW$QLcK59o5>Iecmb>VY_^S8?Ky(^a%D#CMzzd}b?iNA?^f=&aM{VJse?u5 zmHo-Q;p^>6C?MScqL4BZPH*Dm*?kf&C*!A;^YNnOiVeZ%E5O;9eYcuUdi4av45Bm% z3vo2#XMT%|r&Nxy-k(>29ZO*`qo_xOXFF^e{iA4$sr)Hs)x#otSt2I6o&AIWmrDR` zv$vmo@>fuw91sJvRD@2cC6~Y?So2N_M^r=v`B7F<(nUZ2@Lny)T$oBTKbK~)-hn=y z+bMw?h!4Yhl#-T4v@XEcd#e zc3`Tuyo{WOjm}B3b9a6!sl9L4wq8Pyq85QOj!Ja$L|VP*por41kLfiiGT*uELFq;e zeoV?4&O5*5P|sy2LM~M(YC#sk%nAb{IOFORtx2ZbJX(Jfzzx!cW~P6+_*Lz_lG3x3 z^wMmWdK?v0;Hle|#&4#d(Gp-R<%o{pvF>c{ZRN$`kCopdj&BrxM&6c?=IRX-BF=dB zkmYIH7;j;Ay9Jo=_teWR#V;Q7;e~~TLo=TZHg#v`x+R*aVL)1L#T-TUz5c=xE%BB> zWY+c=qK&%w%X98xa@5SdiOh2IM)t|67gX=r#FoN(-(7kW$oTPi+u_oq=a{eg&R%Be z{8>}{3y^Zdp5E>na!bH2P#}}Z)XZ`zFaKT$8R6uvsi{3ef+wdO?p0@VO_r+Y@>qx7 zY0TrHQCvnwzTEC_o4x%$Tl1}Zt3HLLRBJ1SiTDpBx$myYm-zxq> zM-dIOY*XdBl$2CR_BOtgUV+?Z|se>{cgF?>*g=)*&w5WX`fiTAHDoCB?imxhu+v3a+lbLD*8*U*Z zl{uBwa#ip5@8W!D>rXvmG;s{%nBJAp`HIcE)${lp$G%^^3Plr-C|GEvL%YJrC%o>x~QyQsq9bRr9@MxvV{=VU8sed%ReTwlm+yyG>g6 zR5ssCuoJQm&7U-^euh*3=6aSh#)Y?U;&OBL)yt;wYsSTM?_KiDcEtKIkQR7QzSN{x zl^u}Yv~_=p4SW(((k7Habt{uLvfX``U0MkY*^goq!~y&M4djCiqbMWy-UsdmctnBr zn0J>f8=0D_Srj&I_XE$*dHhq{RZ}LQDRSKxG5orysS-aH8zc)-w?_}58@(MJka(&B z>6YnvI!}>ujYS;ZmaCR!(BoorSh}~Y-3}Vkia5a{YpgR#|5!HdtJxA29)j@h5t7MZ zX6HZR#Ztr;QjdE1bX6Z3H`Zju-l+d|i+?3HDgY4%skbk5?i(u*GIz%-E0UAPD2uuz z?8=by;)x-f^fHd=u|(yY9IzO16bVj&zKn4*EqP>sK%z^Xwmz56YYw(EnF8IpnidP! zJ3MB@sOemSX%1@ZoF7mLO`Q)KCdTDXekFv(fXiAe6W*?Fl1G>Cxb_%hHn}+ViwqSrU3wAA zoeE;{)!2F1aJ0JXLeGC@h#%ysj8l30}|F&>P`4lUIc=$N_0WBQ)4l@-5h-Y0Lt!rX#nf zm#2^>mTVh4E@n52(#!^%ib^#aq%P7m1o1gjPNN4^L@r{4_$%Q#X-4rMkdB3Unw@_d zdp@_{+clyG5jdd48ts_yXQrC+XVPtA1V8TmSbq5xTz+$XRlzP?zA0vk*3V5$65)2} zPeL1!agJ-X(iYUxs$&e;dAz-!Zs>uK#koL0wnVQEW8!|dPm!Hw=JV}{Qee4dt`BFk z9+)*rFtIhHRo%Fc4xfjI3k7t4;18ETVjGvMjUUpi1X8cx&11bg5|ki9J^Ae1qg4QK~*gLx~-Lo-T^c zq<-b$!RRzFlYZhhp7PQ~z4bO)soCf&EW+^Z`F_W)kj9-CI4LpfB|iD zYreq|;&M5Q|G?&GDo8JjD>B;4J2HiR`EvDhKwS+|P}d zHV+R=nLl1JHjxnPT)^tgm2&bjL5X8+HJ2M2XY`%w0rq(ZqX#)gcP%G7D@Z(|dvsc# zR~PM0E$Fcgd@dUg0ASd=K+hd@I4J^1*&^;q$f1w$(X!4Ptfv`Q`|V1Zj@b{y=*R)c zUAT86FLftQ*jx&0u_O{gTf}H@tHXC*t;_V0*Bb8XOmvoBS;H3v7Du9LoVn_|zn3FH&fkhhEemIZbuo0Ti|s_GB+ z_=dHexTH8CLhpyx@WX*S^Y#q3p~vJS_L!Be9Nl~#0n6b*nND?;xg)>%)hh`;Hql7q z$WltEkiU+Q%ILoY^_zEM3bAP6S6Mw(drUmxp&yVjoRlUbeX8{p@T_ylzZ~v7IC|B@ z$Qb#$>H~4Z_M5G|?Tc)Xy453}Clx*%Qk-fv=IvtBcB$|2zRHDm6#mdS9{maHimWl{ zp5x2=?g~4cvx*jPj{xzeMTIO3LDR*Eo35U5w7)Wuz`R_0IM+Z_Q=}`fTJ5eNK0r=9 z(w21qW#c$R9DhUVOxEI8>YUUj7c_H&6k>y#LK$(N=Z4GrVl& z6Ea}j{&}#_u*`;9%&1t{oh4*=Z_2bc2LxPPQ=_C- z3h0Q*>ShzvW03qy^gV2zysuvoy3Qwmg;)W~0|jhSZN#gg%>GC@6x+YGphLTmn~?}W z%OZ2}T9KJ>@5af6^ z7#jMVpi|8|Y>vU}v_F_?cX|B~&Q&zjt6~k2iGMy%`FxyQ_FSPIh2C`h}zlBGA zgj7_wD0E9L+6TN!PLGKA*?2Z>+Ub+kXMB0~Mv{1YyHdlE4bah(>gwA-ie48Y0G$Ba zB)Au^B+*N-tm2HahYxH2Li98z>u!Z~DKDf9C$;{FEI`C%Z6OhuK_gFptD-e>U-ii0 zdL|kiDK2~pi^A|J%j!hpF;eqt-sY*&Ho@59ZpQI}0iAxdWx8p+cjqW&Ibxj(n_6WE zWY@dyp03ofa(lTtE6F<_E=0$X)yJuc;ObOA+MOF8=s%4&PNX9r7nGGomp`Jz9vzwJ z`~3x|EKBDJDJ^wm1?_WISr=2Q3HQg|H0~zV%a@l?%1360rS}U~B0z!5-^=tKH<+ft zBs@ixZ}iq~3v7EmK$6(9LXa}MoYzyTh{Rmio8iMV+q~w;*T`OEDIS;H`iM5hDiHGC zcO>+^W=lM|&B0*EOt--nubc@n!)0yy zIcO8(EakXh^xh>_cT};eGn2qIK`RaBn3bfQxQiCImo!&P7hd*IcH&ftZsDHBqk!+B zKr0)tgSGZjTuehy1`XP6djP*k2uu5u(t5cICF6B5n>QAPtsnPBDCj4w)?ZySZHE+C z^pvi>{7E$)@m|A5(nX5=Q&69zytGjdTyDp6AAHMo))gRmeI7h0BQQWFuz%k*kQGP> z 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

Ds@bNjH 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 diff --git a/reference/sample-configurations/lza-sample-config-healthcare/network-config.yaml b/reference/sample-configurations/lza-sample-config-healthcare/network-config.yaml deleted file mode 100644 index 0eb2e0f..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/network-config.yaml +++ /dev/null @@ -1,516 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -defaultVpc: - delete: true - excludeAccounts: [] -transitGateways: - - name: Network-Main - account: Network-Prod - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - - HIS - - EIS - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Core - routes: [] - - name: Network-Main-Spoke - routes: - - destinationCidrBlock: 0.0.0.0/0 - attachment: - vpcName: Network-Inspection - account: Network-Prod -centralNetworkServices: - delegatedAdminAccount: Network-Prod - networkFirewall: - firewalls: - - name: accelerator-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - policies: - - name: accelerator-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ["aws:forward_to_sfe"] - statelessFragmentDefaultActions: ["aws:forward_to_sfe"] - statefulRuleGroups: - - name: accelerator-rule-group - - name: domain-list-group - shareTargets: - organizationalUnits: - - Infrastructure - rules: - - name: accelerator-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - statefulRules: - # Block traffic between production VPCs - - action: DROP - header: - destination: 10.3.0.0/16 - destinationPort: ANY - direction: ANY - protocol: IP - source: 10.4.0.0/16 - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ["100"] - - name: domain-list-group - regions: - - *HOME_REGION - capacity: 10 - type: STATEFUL - ruleGroup: - rulesSource: - rulesSourceList: - generatedRulesType: DENYLIST - # Add/Modify the domain list per business needs. - targets: [".google.com"] - targetTypes: ["TLS_SNI", "HTTP_HOST"] - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.1.0.0/16 - - 10.2.0.0/16 - - 10.3.0.0/16 - - 10.4.0.0/16 - portSets: - name: HOME_NET - definition: - - "80" - - "443" - route53Resolver: - endpoints: [] - queryLogs: - name: accelerator-query-logs - destinations: - - s3 - - cloud-watch-logs - shareTargets: - organizationalUnits: - - Infrastructure - firewallRuleGroups: - - name: accelerator-block-group - regions: - - *HOME_REGION - rules: - - name: managed-rule - action: BLOCK - managedDomainList: AWSManagedDomainsBotnetCommandandControl - priority: 300 - blockResponse: NODATA - shareTargets: - organizationalUnits: - - Infrastructure -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json -vpcs: - - name: Network-Endpoints - account: Network-Prod - region: *HOME_REGION - cidrs: - - 10.1.0.0/22 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - queryLogs: - - accelerator-query-logs - routeTables: - - name: Network-Endpoints-Tgw-A - routes: [] - - name: Network-Endpoints-Tgw-B - routes: [] - - name: Network-Endpoints-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Endpoints-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - subnets: - - name: Network-Endpoints-A - availabilityZone: a - routeTable: Network-Endpoints-A - ipv4CidrBlock: 10.1.0.0/24 - - name: Network-Endpoints-B - availabilityZone: b - routeTable: Network-Endpoints-B - ipv4CidrBlock: 10.1.1.0/24 - - name: Network-EndpointsTgwAttach-A - availabilityZone: a - routeTable: Network-Endpoints-Tgw-A - ipv4CidrBlock: 10.1.3.208/28 - - name: Network-EndpointsTgwAttach-B - availabilityZone: b - routeTable: Network-Endpoints-Tgw-B - ipv4CidrBlock: 10.1.3.224/28 - transitGatewayAttachments: - - name: Network-Endpoints - transitGateway: - name: Network-Main - account: Network-Prod - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Spoke - - Network-Main-Core - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - interfaceEndpoints: - central: true - defaultPolicy: Default - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: kms - - service: logs - - name: Network-Inspection - account: Network-Prod - region: *HOME_REGION - cidrs: - - 10.2.0.0/22 - internetGateway: true - routeTables: - - name: Network-Inspection-Tgw-A - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: Network-Inspection-Tgw-B - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: Network-Inspection-A - routes: - - name: NatRoute - destination: 0.0.0.0/0 - type: natGateway - target: Nat-Network-Inspection-A - - name: TgwRoute - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-B - routes: - - name: NatRoute - destination: 0.0.0.0/0 - type: natGateway - target: Nat-Network-Inspection-B - - name: TgwRoute - destination: 10.0.0.0/8 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-Nat-A - routes: - - name: NfwNatRoute - destination: 10.0.0.0/8 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: Network-Inspection-Nat-B - routes: - - name: NfwNatRoute - destination: 10.0.0.0/8 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - subnets: - - name: Network-Inspection-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.0.0/24 - - name: Network-Inspection-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.1.0/24 - - name: Network-InspectionTgwAttach-A - availabilityZone: a - routeTable: Network-Inspection-Tgw-A - ipv4CidrBlock: 10.2.3.208/28 - - name: Network-InspectionTgwAttach-B - availabilityZone: b - routeTable: Network-Inspection-Tgw-B - ipv4CidrBlock: 10.2.3.224/28 - - name: Network-InspectionNat-A - availabilityZone: a - routeTable: Network-Inspection-Nat-A - ipv4CidrBlock: 10.2.3.176/28 - - name: Network-InspectionNat-B - availabilityZone: b - routeTable: Network-Inspection-Nat-B - ipv4CidrBlock: 10.2.3.192/28 - natGateways: - - name: Nat-Network-Inspection-A - subnet: Network-InspectionNat-A - - name: Nat-Network-Inspection-B - subnet: Network-InspectionNat-B - transitGatewayAttachments: - - name: Network-Inspection - transitGateway: - name: Network-Main - account: Network-Prod - options: - applianceModeSupport: enable - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: [] - subnets: - - Network-InspectionTgwAttach-A - - Network-InspectionTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: HIS-pacs-Non-Prod-Main - account: Pacs-Non_prod - region: *HOME_REGION - cidrs: - - 10.4.0.0/16 - routeTables: - - name: HIS-pacs-Non-Prod-Tgw-A - routes: [] - - name: HIS-pacs-Non-Prod-Tgw-B - routes: [] - - name: HIS-pacs-Non-Prod-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: HIS-pacs-Non-Prod-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - subnets: - - name: HIS-pacs-Non-Prod-App-A - availabilityZone: a - routeTable: HIS-pacs-Non-Prod-App-A - ipv4CidrBlock: 10.4.0.0/24 - - name: HIS-pacs-Non-Prod-App-B - availabilityZone: b - routeTable: HIS-pacs-Non-Prod-App-B - ipv4CidrBlock: 10.4.1.0/24 - - name: HIS-pacs-Non-Prod-MainTgwAttach-A - availabilityZone: a - routeTable: HIS-pacs-Non-Prod-Tgw-A - ipv4CidrBlock: 10.4.255.208/28 - - name: HIS-pacs-Non-Prod-MainTgwAttach-B - availabilityZone: b - routeTable: HIS-pacs-Non-Prod-Tgw-B - ipv4CidrBlock: 10.4.255.224/28 - transitGatewayAttachments: - - name: HIS-pacs-Non-Prod-Main - transitGateway: - name: Network-Main - account: Network-Prod - routeTableAssociations: - - Network-Main-Spoke - routeTablePropagations: - - Network-Main-Core - subnets: - - HIS-pacs-Non-Prod-MainTgwAttach-A - - HIS-pacs-Non-Prod-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: HIS-pms-Prod-Main - account: Pms-Prod - region: *HOME_REGION - cidrs: - - 10.3.0.0/16 - routeTables: - - name: HIS-pms-Prod-Tgw-A - routes: [] - - name: HIS-pms-Prod-Tgw-B - routes: [] - - name: HIS-pms-Prod-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: HIS-pms-Prod-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - subnets: - - name: HIS-pms-Prod-App-A - availabilityZone: a - routeTable: HIS-pms-Prod-App-A - ipv4CidrBlock: 10.3.0.0/24 - - name: HIS-pms-Prod-App-B - availabilityZone: b - routeTable: HIS-pms-Prod-App-B - ipv4CidrBlock: 10.3.1.0/24 - - name: HIS-pms-Prod-MainTgwAttach-A - availabilityZone: a - routeTable: HIS-pms-Prod-Tgw-A - ipv4CidrBlock: 10.3.255.208/28 - - name: HIS-pms-Prod-MainTgwAttach-B - availabilityZone: b - routeTable: HIS-pms-Prod-Tgw-B - ipv4CidrBlock: 10.3.255.224/28 - transitGatewayAttachments: - - name: HIS-pms-Prod-Main - transitGateway: - name: Network-Main - account: Network-Prod - routeTableAssociations: - - Network-Main-Spoke - routeTablePropagations: - - Network-Main-Core - subnets: - - HIS-pms-Prod-MainTgwAttach-A - - HIS-pms-Prod-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: [] - useCentralEndpoints: true -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - cloud-watch-logs - destinationsConfig: - cloudWatchLogs: - retentionInDays: 30 - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path diff --git a/reference/sample-configurations/lza-sample-config-healthcare/organization-config.yaml b/reference/sample-configurations/lza-sample-config-healthcare/organization-config.yaml deleted file mode 100644 index 9fee7b3..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/organization-config.yaml +++ /dev/null @@ -1,72 +0,0 @@ -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure - - name: Infrastructure/Infra-Dev - - name: Infrastructure/Infra-Prod - - name: HIS - - name: EIS - - name: HIS/HIS-Non-Prod - - name: HIS/HIS-Prod -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - - name: HLC-SCP-base-root - description: > - This SCP is used to prevent healthcare accounts from leaving the organization - policy: service-control-policies/scp-hlc-base-root.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - - name: HLC-SCP-hipaa-service - description: > - This SCP is used to ensure only HIPAA eligible services can be used in a specific OU or account - policy: service-control-policies/scp-hlc-hipaa-service.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] -taggingPolicies: - - name: general-tag-policy - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root - - name: phi-tag-policy - description: Healthcare Organization Tagging Policy - policy: tagging-policies/healthcare-org-tag-policy.json - deploymentTargets: - organizationalUnits: - - HIS -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/backup-plan.json - deploymentTargets: - organizationalUnits: - - Root diff --git a/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/Readme.md b/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/Readme.md deleted file mode 100644 index d20eff0..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/Readme.md +++ /dev/null @@ -1,147 +0,0 @@ -# HIPAA based Service Contol Policies (SCPs) - -scp-hlc-*.json file contains the Service Control Policies (SCPs) that can be deployed while building HIPAA eligible environments. -HIPAA Eligible Services are last updated on April 18, 2022. - -The reference guide can be accessed via the following link: https://aws.amazon.com/compliance/hipaa-eligible-services-reference/ - -Please also check the notes below for specific services: - -Alexa for Business (for healthcare skills only – requires Alexa Skills BAA. See https://d1.awsstatic.com/whitepapers/compliance/AWS_HIPAA_Compliance_Whitepaper.pdf for details)\ -AWS Amplify Console\ -Amazon API Gateway\ -AWS Application Migration Service\ -Amazon AppStream 2.0\ -Amazon AppFlow\ -AWS AppSync\ -AWS App Mesh\ -Amazon Athena\ -Amazon Augmented AI [excludes Public Workforce and Vendor Workforce for all features]\ -Amazon Aurora\ -AWS Backup\ -AWS Batch\ -AWS Certificate Manager\ -Amazon Chime\ -AWS Cloud 9\ -Amazon Cloud Directory\ -AWS Cloud Map\ -AWS CloudEndure [including CloudEndure Disaster Recovery and CloudEndure Migration]\ -AWS CloudFormation\ -Amazon CloudFront [including Lambda@Edge]\ -AWS CloudHSM\ -AWS CloudTrail\ -Amazon CloudWatch\ -Amazon CloudWatch Events [including Amazon EventBridge]\ -Amazon CloudWatch Logs\ -Amazon CloudWatch SDK Metrics\ -AWS CodeBuild\ -AWS CodeCommit\ -AWS CodeDeploy\ -AWS CodePipeline\ -Amazon Cognito\ -Amazon Comprehend\ -Amazon Comprehend Medical\ -AWS Config\ -Amazon Connect [excludes Wisdom, VoiceID and High-Volume Outbound Communications]\ -AWS Control Tower\ -AWS Data Exchange\ -AWS Database Migration Service (DMS)\ -AWS DataSync\ -Amazon Detective\ -AWS Direct Connect\ -AWS Directory Service [excludes Simple AD]\ -Amazon DocumentDB (with MongoDB compatibility)\ -Amazon DynamoDB\ -Amazon EC2 Auto Scaling\ -Amazon ElastiCache for Redis\ -AWS Elastic Beanstalk\ -Amazon Elastic Block Store (Amazon EBS)\ -Amazon Elastic Compute Cloud (Amazon EC2)\ -Amazon Elastic Container Registry (ECR)\ -Amazon Elastic Container Service (ECS) [both Fargate and EC2 launch types]\ -Amazon Elastic File System (EFS)\ -Amazon Elastic Kubernetes Service (EKS)\ -Elastic Load Balancing\ -Amazon Elastic MapReduce (EMR)\ -AWS Elemental MediaConnect\ -AWS Elemental MediaConvert\ -AWS Elemental MediaLive\ -AWS Firewall Manager\ -Amazon Forecast\ -Amazon FreeRTOS\ -Amazon FSx\ -AWS Global Accelerator\ -AWS Glue (including AWS Lake Formation)\ -AWS Glue DataBrew\ -Amazon GuardDuty\ -Amazon HealthLake\ -Amazon Inspector\ -AWS IoT Core\ -AWS IoT Device Management\ -AWS IoT Events\ -AWS IoT Greengrass\ -Amazon Kendra\ -AWS Key Management Service (KMS)\ -Amazon Keyspaces (For Apache Cassandra)\ -Amazon Kinesis Data Analytics\ -Amazon Kinesis Data Streams\ -Amazon Kinesis Data Firehose\ -Amazon Kinesis Video Streams\ -AWS Lambda\ -Amazon Lex\ -Amazon Location Service\ -Amazon Macie\ -AWS Managed Services [excluding Operations on Demand Services, except for the RFC Expedite feature]\ -Amazon Managed Streaming for Apache Kafka\ -Amazon MQ\ -Amazon Neptune\ -AWS Network Firewall\ -Amazon OpenSearch Service [successor to Amazon Elasticsearch service]\ -AWS OpsWorks for Chef Automate\ -AWS OpsWorks for Puppet Enterprise\ -AWS OpsWorks Stacks\ -AWS Organizations\ -AWS Outposts\ -Amazon Personalize\ -Amazon Pinpoint [excluding Voice Message capabilities]\ -Amazon Polly\ -Amazon Quantum Ledger Database (QLDB)\ -Amazon QuickSight\ -Amazon Rekognition\ -Amazon Redshift\ -Amazon Relational Database Service (Amazon RDS) [SQL Server, MySQL, Oracle, PostgreSQL, and MariaDB engines only]\ -AWS RoboMaker\ -Amazon Route 53\ -Amazon S3 Glacier\ -Amazon SageMaker [excludes Studio Lab, Ground Truth Plus, Public Workforce and Vendor Workforce for all features]\ -AWS Secrets Manager\ -AWS Security Hub\ -AWS Service Catalog\ -AWS Serverless Application Repository\ -AWS Server Migration Service (SMS)\ -AWS Shield [Standard and Advanced]\ -Amazon Simple Email Service (Amazon SES)\ -Amazon Simple Notification Service (SNS)\ -Amazon Simple Queue Service (SQS)\ -Amazon Simple Storage Service (Amazon S3) [including S3 Transfer Acceleration]\ -Amazon Simple Workflow Service (SWF)\ -AWS Single Sign-On\ -AWS Snowball\ -AWS Snowball Edge\ -AWS Snowmobile\ -AWS Step Functions\ -AWS Storage Gateway\ -AWS Systems Manager\ -Amazon Textract\ -Amazon Timestream\ -Amazon Transcribe\ -AWS Transfer for SFTP\ -Amazon Translate\ -Amazon Virtual Private Cloud (VPC)\ -AWS Web Application Firewall (WAF)\ -Amazon WorkDocs\ -Amazon WorkLink\ -Amazon WorkSpaces\ -AWS X-Ray\ -VM Import/Export\ -**NOTE:** If you are a Covered Entity or Business Associate as defined by the Health Insurance Portability and Accountability Act of 1996 (as amended, “HIPAA”), you agree not to use these HIPAA Eligible Services for any purpose or in any manner involving Protected Health Information (as defined by HIPAA) without first entering into an AWS business associate agreement. diff --git a/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-base-root.json b/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-base-root.json deleted file mode 100644 index bc9ae9e..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-base-root.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Version":"2012-10-17", - "Statement":[ - { - "Effect":"Deny", - "Action":[ - "organizations:LeaveOrganization", - "organizations:DeleteOrganization" - ], - "Resource":[ - "*" - ], - "Condition":{ - "ArnNotLike":{ - "aws:PrincipalARN":[ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution" - ] - } - } - }, - { - "Effect":"Deny", - "Action":[ - "iam:CreateAccessKey" - ], - "Resource":[ - "arn:aws:iam::*:root" - ] - }, - { - "Effect":"Deny", - "Action":[ - "s3:PutAccountPublicAccessBlock" - ], - "Resource":[ - "*" - ], - "Condition":{ - "ArnNotLike":{ - "aws:PrincipalARN":[ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-hipaa-service.json b/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-hipaa-service.json deleted file mode 100644 index a730a74..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/service-control-policies/scp-hlc-hipaa-service.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "HIPAAEligibleServices", - "Effect": "Deny", - "NotAction": [ - "acm:*", - "a4b:*", - "access-analyzer:*", - "amplify:*", - "apigateway:*", - "appflow:*", - "application-autoscaling:*", - "appmesh:*", - "appstream:*", - "appsync:*", - "artifact:*", - "athena:*", - "autoscaling:*", - "automation:*", - "aws-portal:*", - "backup:*", - "backup-storage:*", - "batch:*", - "budgets:*", - "cassandra:*", - "ce:*", - "chime:*", - "cloud9:*", - "clouddirectory:*", - "cloudformation:*", - "cloudfront:*", - "cloudhsm:*", - "cloudtrail:*", - "cloudwatch:*", - "codebuild:*", - "codecommit:*", - "codedeploy:*", - "codepipeline:*", - "cognito-identity:*", - "cognito-idp:*", - "cognito-sync:*", - "comprehend:*", - "comprehendmedical:*", - "config:*", - "connect:*", - "controltower:*", - "cur:*", - "databrew:*", - "dataexchange:*", - "datasync:*", - "detective:*", - "directconnect:*", - "dms:*", - "ds:*", - "dynamodb:*", - "ebs:*", - "ec2:*", - "ec2messages:*", - "ecr:*", - "ecs:*", - "eks:*", - "elasticache:*", - "elasticbeanstalk:*", - "elasticfilesystem:*", - "elasticloadbalancing:*", - "elasticmapreduce:*", - "es:*", - "events:*", - "execute-api:*", - "firehose:*", - "fms:*", - "forecast:*", - "freertos:*", - "fsx:*", - "geo:*", - "glacier:*", - "globalaccelerator:*", - "glue:*", - "greengrass:*", - "guardduty:*", - "healthlake:*", - "iam:*", - "importexport:*", - "inspector2:*", - "inspector:*", - "iot:*", - "iotfleethub:*", - "iotevents:*", - "kafka:*", - "kendra:*", - "kinesis:*", - "kinesisanalytics:*", - "kinesisvideo:*", - "kms:*", - "lambda:*", - "lex:*", - "license-manager:*", - "logs:*", - "macie2:*", - "mediaconnect:*", - "mediaconvert:*", - "medialive:*", - "mgh:*", - "mgn:*", - "mobiletargeting:*", - "mq:*", - "neptune-db:*", - "network-firewall:*", - "opsworks-cm:*", - "opsworks:*", - "organizations:*", - "outposts:*", - "personalize:*", - "polly:*", - "qldb:*", - "quicksight:*", - "ram:*", - "rds:*", - "rds-data:*", - "rds-db:*", - "redshift:*", - "redshift-data:*", - "rekognition:*", - "resource-groups:*", - "robomaker:*", - "route53:*", - "route53domains:*", - "route53resolver:*", - "s3:*", - "sagemaker:*", - "secretsmanager:*", - "securityhub:*", - "serverlessrepo:*", - "servicecatalog:*", - "servicediscovery:*", - "ses:*", - "shield:*", - "sms:*", - "snowball:*", - "sns:*", - "sqs:*", - "ssm:*", - "sso:*", - "sso-directory:*", - "states:*", - "storagegateway:*", - "sts:*", - "support:*", - "swf:*", - "textract:*", - "timestream:*", - "transcribe:*", - "transfer:*", - "translate:*", - "waf-regional:*", - "waf:*", - "wafv2:*", - "workdocs:*", - "worklink:*", - "workspaces:*", - "xray:*" - ], - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-healthcare/tagging-policies/healthcare-org-tag-policy.json b/reference/sample-configurations/lza-sample-config-healthcare/tagging-policies/healthcare-org-tag-policy.json deleted file mode 100644 index 4bbeda4..0000000 --- a/reference/sample-configurations/lza-sample-config-healthcare/tagging-policies/healthcare-org-tag-policy.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "tags": { - "CostCenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - }, - "EnvironmentType": { - "tag_key": { - "@@assign": "EnvironmentType" - }, - "tag_value": { - "@@assign": [ - "Prod", - "QA", - "Dev" - ] - } - }, - "DataClassification": { - "tag_key": { - "@@assign": "DataClassification" - }, - "tag_value": { - "@@assign": [ - "NonConfidential", - "CompanyConfidential", - "PII", - "PHI" - ] - }, - "enforced_for": { - "@@assign": [ - "dms:*", - "dynamodb:table", - "ec2:instance", - "elasticfilesystem:*", - "eks:cluster", - "ecs:cluster", - "elasticmapreduce:cluster", - "fsx:*", - "rds:cluster-endpoint", - "redshift:*", - "s3:bucket" - ] - } - }, - "AppID": {} - } -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-tse-se/README.md b/reference/sample-configurations/lza-sample-config-tse-se/README.md deleted file mode 100644 index 4efa6e5..0000000 --- a/reference/sample-configurations/lza-sample-config-tse-se/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Trusted Secure Enclaves Sensitive Edition (TSE-SE) - -This sample configuration has been migrated to the GitHub repository below:
- -[Landing Zone Accelerator on AWS for Trusted Secure Enclaves Sensitive Edition (TSE-SE)](https://github.com/aws-samples/landing-zone-accelerator-on-aws-for-tse-se) diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/README.md b/reference/sample-configurations/lza-sample-config-us-slg-central-it/README.md deleted file mode 100644 index fc9b28f..0000000 --- a/reference/sample-configurations/lza-sample-config-us-slg-central-it/README.md +++ /dev/null @@ -1,242 +0,0 @@ -# Landing Zone Accelerator on AWS for State and Local Government Central IT - -## Overview - -The Landing Zone Accelerator (LZA) for State and Local Government Central IT is an industry specific deployment of the [Landing Zone Accelerator on AWS](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) solution architected to align with AWS best practices and in conformance with multiple, global compliance frameworks. Built on top of the standard AWS Control Tower accounts, namely `Management`, `Audit`, and `LogArchive`, the LZA for State and Local Government Central IT deploys additional resources that helps establish platform readiness with security, compliance, and operational capabilities. It is important to note that the Landing Zone Accelerator solution will not, by itself, make you compliant. It provides the foundational infrastructure from which additional complementary solutions can be integrated. You must review, evaluate, assess, and approve the solution in compliance with your organization’s particular security features, tools, and configurations. - -State and Local Government Central IT organizations often provide varying level of service, guidance, and oversight to the range of state agencies within the state. These agencies may differ in level of IT sophistication, services and solutions within their agency depending on their mission. As a result, some agencies work with a higher level of autonomy while others leverage more support from State and Local Government Central IT. The following bullets highlight typical components observed as part of State and Local Government Central IT. - -State and Local Government Central IT often operates as a shared services organization that provides services in 3 primary areas. - -- Centralized networking to facilitate some or all agency and department interaction as well as centralized Internet onramp. This function often includes firewall and inspection disciplines. -- Centralized shared services including Directory Services, Identity Federation, Email, and other services ranging from DNS, vulnerability scanning, and OS patching to name a few. -- Security and Governance function to facilitate and help align the overall security posture of State and Local Government Central IT and to the state agencies they serve. - -The Landing Zone Accelerator on AWS enables State and Local Government Central IT to offer agencies a secure and agile foundation for their AWS Cloud workloads. - -## Deployment Overview - -Use the following steps to deploy the industry guidance. For detailed instructions, follow the links for each step. - -[Step 1. Launch the stack](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-1.-launch-the-stack.html) - -- Launch the AWS CloudFormation template into your AWS account. -- Review the templates parameters and enter or adjust the default values as needed. - -[Step 2. Await initial environment deployment](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/step-2.-await-initial-environment-deployment.html) - -- Await successful completion of `AWSAccelerator-Pipeline` pipeline. - -Step 3. Copy the configuration files - -- Clone the `aws-accelerator-config` AWS CodeCommit repository. -- Clone the [landing-zone-accelerator-on-aws](https://github.com/awslabs/landing-zone-accelerator-on-aws) repo -- Copy the configs and all the contents from the `lza-sample-config` folder under `reference/sample-configurations` to your local `aws-accelerator-config` repo. -- Copy the contents from the `lza-sample-config-us-slg-central-it` folder under `reference/sample-configurations` to your local `aws-accelerator-config` repo. You may be prompted to over-write duplicate configs, such as `accounts-config.yaml`. - -Step 4. Update the configuration files and release a change. - -- Using the IDE of your choice. Update the `homeRegion` variable at the top of each config to match the region you deployed the solution to. -- Update the configuration files to match the desired state of your environment. Look for the `UPDATE` comments for areas requiring updates, such as e-mail addresses in your `accounts-config.yaml` -- Review the contents in the `Security Controls` section below to understand if any changes need to be made to meet organizational requirements, such as applying SCPs to the various OUs. -- Commit and push all your change to the `aws-accelerator-config` AWS CodeCommit repository. -- Release a change manually to the AWSAccelerator-Pipeline pipeline. - -## Components of this Industry Best Practice Configuration - -This section describes components of the LZA configuration that are included and items that are not included. - -### What is included in this industry configuration - -- An AWS multi-account organization structure with standard OUs and Accounts aligned toward separation of duties while allowing agility of business unit and/or mission workloads. -- A bias toward security governance via AWS security tooling and an emphasis on security controls aligned with the NIST 800-53 rev 5 security framework. Many regulations and certifications such as HIPAA, FedRAMP, and IRS Publication 1075 leverage NIST 800-53 rev5 security framework as foundational. -- There are optional configurations intended to help organizations with workloads aligned with HIPAA regulations. -- Future configuration options that include additional security controls that align with IRS Publication 1075 and FedRAMP. -- Network configurations for purposes related to central inspection and egress, AWS PrivateLink Endpoints, and a SharedService network to centralize shared workloads. The network patterns also align with common role segmentation. -- AWS Security Service configurations like: - AWS Security Hub provides a comprehensive view of your security state in AWS, including multiple best practice standards. This configuration enables the AWS Foundational Best Practice and AWS CIS Benchmark Best Practice security standards. Other security related tooling and configurations are implemented including Amazon Macie, AWS GuardDuty, and encryption of block storage by default. - -### What is NOT included in this industry Configuration - -This section offers examples of some LZA capabilities that are not reflected in this best practice configuration as they are more unique to customer circumstances. - -- LZA offers support for Hybrid DNS configurations using the Route 53 Resolver Endpoints are not configured here, but can and should be added as needed for your implementation. -- LZA offers integration with Amazon VPC IP Address Manager (IPAM) but is not implemented within this State and Local Government Central IT configuration. -- Please also reference the [for further consideration](#for-further-consideration) section below as you begin to experiment with the LZA for State and Local Government Central IT. - -### What differs from default LZA Sample Configuration - -This section offers examples of recommended configuration variances from standard LZA configurations. In all cases, the user is encouraged to review these configurations and maintains flexibility to enable or disable configurations as needed to meet their own business and compliance goals. - -- Security - - Enables Security Hub AWS Foundational Best Practices and Security Hub CIS AWS Foundation Benchmark standards. The Security Hub PCI DSS can be optionally enabled but the security controls are covered by the 2 standards implemented. - - LZA allows for enabling a granular set of security controls. Because NIST 800-53 rev5 framework is foundational for State Governments adhering to healthcare and IRS regulations as well as other certifications such as FedRAMP, a set of 15 incremental config rules were added to the security-config.yaml that align with the [Operational Best Practices for NIST 800-53 rev 5 Conformance Pack]. The remainder of the conformance pack is covered by Security Hub Best Practice Standards and other LZA Configurations. Being foundational, these rules are applied at the Root OU and inherited throughout the environment. - - 6 incremental Config Rules were added for backup validation that align with the [Operational Best Practices for HIPAA Security Conformance Pack]. These rules are optional and recommended to be applied at the `Tenant-HIS` OU as they are aligned with HIPAA. - - Enabled automated configuration to update EC2-Default-SSM-AD IAM Role with LZA created IAM policies to leverage AWS SSM Session Manager encrypted sessions and centralized logging. -- Organization - - Recommend to enable the standard configuration `Guardrails1` and `Guardrails2` Service Control Policies at the Root OU and not the Infrastructure OU. - - Added `Guardrails3` to prevent the removal of S3 Block Public Access and prevent an account from leaving the organization. Recommend to enable at the Root OU. - - Using `HIPAA` Service Control Policy to allow HIPAA eligible services within the `Tenant-HIS` OU. -- Network - - Transit Gateway configured with only 2 route tables - core and spoke. - - Utilizes core and spoke design with central inspection. - - The central inspection VPC has public egress subnets and an Internet Gateway. - - The central inspection VPC samples STATEFUL inspection rules and domain list denial. - - A centralized DNS Firewall is implemented with example block and override rules. The DNS Firewall is shared throughout and all 4 sample VPCs opt-in. - - Route53 DNS Query Logs are implemented in all sample VPCs. - -## Security Frameworks and AWS Business Associate Addendum (BAA) - -State and Local Governments are highly regulated. The LZA for State and Local Government Central IT provides additional guardrails to help mitigate against the threats faced by these organizations. The LZA for State and Local Government Central IT is not meant to be feature complete or fully compliant, but rather is intended to help accelerate cloud migrations and cloud refactoring efforts by entities serving US State IT Organizations. While much effort has been made to reduce the effort required to manually build a production-ready infrastructure, you will still need to tailor it to your unique business needs. - -Because of the variation in IT workloads and missions across state agencies, State and Local Government Central IT must be aware of numerous frameworks including IRS Publication 1075, FedRAMP, and HIPAA. The bulk of these frameworks lean on NIST 800-53 rev 5 as a foundation for their compliance standards. As a result, the State and Local Government Central IT best practice configurations uses AWS controls from NIST Cybersecurity Framework as starting point and offers additional or optional control configurations aligned with HIPAA. At this time, additional configurations are not implemented for IRS Publication 1075 but are planned. - -Lastly, if the organization does intend to leverage PHI or HIPAA based workloads, the user should log into the Management account, navigate to AWS Artifact, select Agreements. Within the "Organizational agreements" pane, select the "AWS Organizations Business Associate Addendum" and select "Accept agreement" to activate the BAA relationship between yourselves and AWS. This should be completed before moving PII/HIPAA workloads into the Landing Zone. - -## Security Controls - -These controls are created as detective or preventative guardrails in the AWS environment through AWS Config rules or through Service Control Policies (SCPs). Within the file `organization-config.yaml` are sections for declaring SCPs, Tagging Policies, and Backup Policies. SCPs can be highly specific to the organization and its workload(s) and should be reviewed and modified to meet your specific requirements. Sample policies have been provided for the following: - -- `Service Control Policies`: A number of Service Control Policies are included with this configuration. - - `service-control-policies/guardrails-1.json`: Assigned to Root OU to protect LZA constructs including Config Rules, Lambdas, SNS, and EBS Encryption. - - `service-control-policies/guardrails-2.json`: Assigned to Root OU to protect LZA constructs including IAM Policies, IAM Roles, Security Hub, GuardDuty, Macie, CloudFormation Stacks and SSM. - - `service-control-policies/guardrails-3.json`: Assigned at Root OU and prevents accounts from leaving your organization or disabling block public access for Amazon S3. - - `service-control-policies/scp-hlc-hipaa-service.json`: Recommended to be assigned at the `Tenant-HIS` OU as an example of a policy that can be used to ensure only HIPAA eligible services can be used in a specific OU or account. It is important to note that SCPs are not automatically updated and that changes to the HIPAA eligible service list will be to be updated. However, this is an example of how your organization can ensure that a select list of AWS services are used for specific use cases. -- `Tagging Policies`: A sample tagging policy has been provided. - - `tagging-policies/healthcare-org-tag-policy.json:` Includes polices for sample tagging strategies with a starter taxonomy. - - `CostCenter` with sample values of `100` and `200`. - - `EnvironmentType` for `Prod`, `QA`, and `Dev` workloads. - - `DataClassification` to track sensitive and non-sensitive workloads such as `PHI`, `PII`, and `CompanyConfidential`. - - The included policy includes sample enforcement for specific AWS services. The sample policy should be edited to reflect your own organization's respective values so that resources provisioned by the LZA are tagged in accordance with your business requirements. -- `Backup policies`: A sample backup policy has been provided. - - `backup-policies/backup-plan.json` as an example for how backups can be scheduled and accompanied by lifecycle and retention management settings. - -In the `security-config.yaml` file, AWS security services can be configured such as AWS Config, AWS Security Hub, and enabling storage encryption. Additional alarms and metrics have been provided to inform you of actions within your AWS Cloud environment. For a list of all of the services and settings that can be configured, see the [LZA on AWS Implementation Guide](#references) in the references section below. This file also contains the AWS Config rules that make up the list of detective guardrails used to meet many of the controls from various frameworks. These rules are implemented through a combination from Security Hub AWS Foundational Security Best Practices, CIS AWS Foundations Benchmark, and the rules from the following: - -- [Operational Best Practices for NIST 800-53 rev 5 Conformance Pack](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-nist-800-53_rev_5.html) -- [Operational Best Practices for HIPAA Security Conformance Pack](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html). - -The `global-config.yaml` file contains the settings that enable regions, centralized logging using AWS CloudTrail and Amazon CloudWatch Logs and the retention period for those logs to help you meet your specific auditing and monitoring needs. - -You are encouraged to review these settings to better understand what has already been configured and what needs to be altered for your specific requirements. - -## Organizational Structure - -State and Local Government Central IT LZA accounts are generated and organized as follows: - - - -![State and Local Government Central IT LZA Org Structure](./images/EGA-StateIT-LZA-OU-EGA.png) - -The State and Local Government Central IT OU structure is comprised of centralized management OUs - i.e. `Root`, `Security`, and `Infrastructure` and application or workload OUs - i.e. `Tenant` and `Tenant-HIS`. - -The Security and Infrastructure OUs are intended for overall landing zone governance and shared services management. These OUs have security controls aligned with the AWS Security Hub AWS Foundational Best Practices, AWS Security Hub CIS AWS Foundations Benchmark, and controls found within the Conformance Pack [Operational Best Practices for NIST 800-53 rev 5](https://docs.aws.amazon.com/config/latest/developerguide/). - -The Tenant Organizational Unit (OU) represents the logical construct for general workloads and would have security controls aligned within the Conformance Pack [Operational Best Practices for NIST 800-53 rev 5](https://docs.aws.amazon.com/config/latest/developerguide/). - -The Health Information System Tenant Organizational Unit (OU) represents the logical construct where workloads that contain sensitive data, such as critical business or Personal Health Information (PHI). It would inherit all of the security controls previously mentioned and incremental controls found within [Operational Best Practices for HIPAA Security](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html). This OU also has the Service Control Policy `scp-hlc-hipaa-service` which limits usable services to those that are allowable under the BAA. - -This OU structure is provided for you. However, you are free to change the organizational structure, Organizational Units (OUs), and accounts to meet your specific needs. For additional information about how to best organize your AWS OU and account structure, please reference the Recommended OUs and accounts in the [For further consideration](#for-further-consideration) section below as you begin to experiment with the LZA for State and Local Government Central IT. - -## Architecture Diagrams - -AWS LZA for State and Local Government Central IT Organizational Structure -![State and Local Government Central IT LZA Architecture](./images/LZAforStateCentralIT_2022-10-18.png) - -By default, the LZA for State and Local Government Central IT builds the above organizational structure, with the exception of the `Management` and `Security` OUs, which are predefined by you prior to launching the LZA. The below architecture diagram highlights the key deployments: - -- An `Infrastructure` OU - - Contains one `Network` and one `SharedServices` Account. - - The `Network` account contains a Transit Gateway for infrastructure routing - - The `Network` account contains 2 VPCs - Network-Inspection and Network-Endpoints, both are in `us-east-1`. - - The `SharedServices` account contains 1 VPC for workloads in `us-east-1`. - - Each VPC uses a /22 CIDR block in the 10.0.0.0/8 RFC-1918 range -- A `Tenant` OU - - Contains one `Workload-1` and one `Workload-2` Account - - A sample VPC for `Workload-1` account in `us-east-1` is included with this configuration. - - Each VPC uses a /22 CIDR block in the 10.0.0.0/8 RFC-1918 range -- A `Tenant-HIS` OU - - Contains one `HIS-1` and one `HIS-2` Account - - A sample VPC for `HIS-1` account in `us-east-1` is included with this configuration. - - Each VPC uses a /22 CIDR block in the 10.0.0.0/8 RFC-1918 range - -AWS LZA for State and Local Government Central IT Network Diagram -![State and Local Government Central IT LZA Network Diagram](./images/EGA-StateIT-LZA-EGA-TGW.png) - -- The accounts in the `Tenant` OU represent a standard infrastructure for development or production deployment of your standard or non-HIPAA related workloads. -- The accounts in the `Tenant-HIS` OU represent a standard infrastructure for development or production deployment of your PHI or HIPAA related workloads. -- The `Infrastructure` OU provides the following specialized functions: - - The `Network` account contains a `Network Inspection VPC` for inspecting AWS traffic as well as routing traffic to and from the Internet. If a route table is defined, for example `Network-Main-Core`, traffic will flow from the `HIS-1 VPC` through the `Network-Main-TGW` Transit Gateway, where it will can be inspected by AWS Firewall before being dropped, passed to another VPC (i.e. `Workload-1 VPC`) or continuing to the internet. - - The `SharedServices` VPC is intended to house centrally-shared services that are accessible to all of the accounts in your infrastructure. For example, you might deploy central security services such as Endpoint Detection and Response (EDR) or a central directory service such as LDAP. This central location and corresponding route tables allow you to efficiently design your network and compartmentalize access control accordingly. - -## Cost - -You are responsible for the cost of the AWS services used while running this solution. As of November 2022, the cost for running this solution using the Landing Zone Accelerator with the State and Local Government Central IT configuration files and AWS Control Tower in the US East (N. Virginia) Region within a test environment with no active workloads is between $1000-$1,1250 USD per month. As additional AWS services and workloads are deployed, the cost will increase. It is also noteworthy VPC inspection is approximately 60% of the cost of this configuration. While this is a significant percentage, the ability to inspect and control network traffic in environment is an important capability for improving your overall security posture. - -| AWS Service | Cost per month | -| ----------------------------------------------- | -------------- | -| AWS CloudTrail | $9.33 | -| Amazon CloudWatch | $0.93 | -| Amazon Config | $22.20 | -| Amazon GuardDuty | $8.10 | -| Amazon Macie (30 buckets, access controls only) | $3.00 | -| AWS Key Management Services (AWS KMS) | $45.90 | -| Amazon Route 53 | $4.16 | -| Amazon Simple Storage Service (Amazon S3) | $3.09 | -| Amazon Virtual Private Cloud (Amazon VPC) | $276.54 | -| AWS Network Firewall | $573.60 | -| AWS Security Hub | $2.80 | -| AWS Secrets Manager | $0.00 | -| Amazon Simple Notification Service (Amazon SNS) | $0.42 | -| Total monthly cost | $962.73 | - -## For further consideration - -Although the State and Local Government Central IT configuration aims to be prescriptive in applying best practices for State and Local Government Centralized IT customers, it intentionally avoids being _overly prescriptive_ out of deference to the unique realities for each individual organization. Consider the baseline State and Local Government Central IT configuration as a good starting point, but bear in mind your objectives as you begin to tailor it for your specific business requirements. From this perspective AWS provides resources that you should consult as you begin customizing your deployment of the State and Local Government Central IT LZA: - -1. This set of configuration files was tested with AWS Control Tower version 3.0. AWS Control Tower 3.0 supports the use of an AWS CloudTrail Organization Trail. The global-config.yaml file shows organizationTrail set to false because it is assumed enabled through the AWS Control Tower setup. -1. Refer to the [Best Practices] for Organizational Units with AWS Organizations blog post for an overview. -1. [Recommended OUs and accounts]. This section of the `Organizing your AWS Environment Using Multiple Accounts` Whitepaper discusses the deployment of specific-purpose OUs in addition to the foundational ones established by the LZA. For example, you may wish to establish a `Sandbox` OU for experimentation, a `Policy Staging` OU to safely test policy changes before deploying them more broadly, or a `Suspended` OU to hold, constrain, and eventually retire accounts that you no longer need. -1. [AWS Security Reference Architecture] (SRA). The SRA "is a holistic set of guidelines for deploying the full complement of AWS security services in a multi-account environment." This document is aimed at helping you to explore the "big picture" of AWS security and security-related services in order to determine the architectures most suited to your organization's unique security requirements. -1. Transit Gateway Flow logs are not enabled by default, work with your AWS team to determine if enabling TGW Flow logs help you meet your regulatory and organizational requirements. - -## References - -- LZA on AWS [Implementation Guide]. This is the official documentation of the Landing Zone Accelerator Project and serves as your starting point. -- AWS Labs [LZA Accelerator] GitHub Repository. The official codebase of the Landing Zone Accelerator Project. -- AWS compliance details focused on [NIST Compliance]. -- AWS compliance details focused on [HIPAA Compliance]. -- AWS HIPAA eligible services site: [HIPAA Eligible Services Reference] - - - -[Best Practices]: https://aws.amazon.com/blogs/mt/best-practices-for-organizational-units-with-aws-organizations/ -[Recommended OUs and accounts]: https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/recommended-ous-and-accounts.html -[AWS Security Reference Architecture]: https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/welcome.html -[Implementation Guide]: https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/landing-zone-accelerator-on-aws.pdf -[Operational Best Practices for NIST 800-53 rev 5 Conformance Pack]: https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-nist-800-53_rev_5.html -[Operational Best Practices for HIPAA Security Conformance Pack]: https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html -[Operational Best Practices for HIPAA Security]: https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html -[LZA Accelerator]: https://github.com/awslabs/landing-zone-accelerator-on-aws -[VPC Sharing: key considerations and best practices]: https://aws.amazon.com/blogs/networking-and-content-delivery/vpc-sharing-key-considerations-and-best-practices/ -[NIST Compliance]: https://aws.amazon.com/compliance/nist/ -[HIPAA Compliance]: https://aws.amazon.com/compliance/hipaa-compliance/ -[HIPAA Eligible Services Reference]: https://aws.amazon.com/compliance/hipaa-eligible-services-reference/ -[AWS FedRAMP Compliance]: https://aws.amazon.com/compliance/fedramp/ diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/accounts-config.yaml b/reference/sample-configurations/lza-sample-config-us-slg-central-it/accounts-config.yaml deleted file mode 100644 index 1fef055..0000000 --- a/reference/sample-configurations/lza-sample-config-us-slg-central-it/accounts-config.yaml +++ /dev/null @@ -1,37 +0,0 @@ -mandatoryAccounts: - # We recommend you do not change mandatory account names. These are used within Landing Zone Accelerator to reference the accounts from other config files. - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - # The "name" will be used to set the AWS Account name - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: SharedServices - description: The SharedServices account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Workload-1 - description: A sample dev workload account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tenant - - name: HIS-1 - description: A sample dev workload account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Tenant-HIS - \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/images/EGA-StateIT-LZA-EGA-TGW.png b/reference/sample-configurations/lza-sample-config-us-slg-central-it/images/EGA-StateIT-LZA-EGA-TGW.png deleted file mode 100644 index 2051f6321b03222cd9a579deb1f2d821882353fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191005 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%0D08Wg8wQ@!sYdf9w;n3o+YG! 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%0DZDFH#c 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

+CS-#sk97} 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 Sn)_(rPxpe6X0gWAu>S*2TXMhv diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/images/EGA-StateIT-LZA-OU-EGA.png b/reference/sample-configurations/lza-sample-config-us-slg-central-it/images/EGA-StateIT-LZA-OU-EGA.png deleted file mode 100644 index a351d9ae20f171542d9114a8265009b0f3a8548b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57388 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�*eC`#Iu>58_9@i$wX6xiq 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π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%0A-vrVyPb^EyG=HhQ*kObUnH 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â<^!w)s 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≪8s{p()a!f`j%5kcY5(uq*4GYPnU=C- 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

H3U! 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%0DIL#Rbq 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%0DVaD?Z$PqLwW0J^QhWC@YD>d>Ui@PSgtBJARyes z)h9M|ez`M?-e^D%vN&Pg^bmLCv@eX2AgqnLvzi1I_Zx3r2tf>@Mp0$7JWQep1`HMB zhqv632aW(Xs(?Me(gK(wc;#OF7hwhYTb5pOA^c37eMZ3_N#6>WpU!$3d0I~Fbg%YY zb;|A>Iva|Mc8mAH z!_ZH$W<-_lZR5|nI{yrqU}1hA$?JhnC(IrG5Z(;42dK)m$a?Spv|9Tp@>ht+cpOTi zcQzdPNfLsNPNKv;s#|H)vw&q>*Br)1)0OKS)0Uh2`T*~k5Z{ZQm@_(k?zLviu}Mq9 zgma$@5l=Tuv|!dB&gaF-fH!5o&45*%ZIgCy4g@UcIWy(>}bkU~{lq znkwj@dD8rNA^VTX`d8S;ML_}lrw&k2QLjTOx@a%_F6?t<)U_8(*8FVs4^CXM_6u!J z$;0e7ydm94C!py{$-devY{duixg-bc18JW3xw$UC6TNrJY5z?|6k4#}(+Ps;$>wWk zIo`}quZTIE(z(bv=qP~81l6bdfwhoCXtx5CvKp97c9JO6yI z9igWH13Q>`MINQ&CcSV2-F%NOO>bz&uB#v9S1sM1!~`iHshVMLw&&;TGe6bd9paUl z+u12lbC3o?px^ij`=>imrIP~ZDT%KYC^1qIrT7WQH!fgV;PM10(PTmBN~Xn@4l=h0 zY2#7E-qId)9wEJurCI%Bw%y;$4>QC%q1QtC0kv1VE6ue+bb=f3bf}AX@-4nKcK*&O z$`v8Zq2Kj@xT4Y(DYdJBW6+&pu-V`-&W!ou`{M_n&B=|2MK28o8VIY@Hih)Z%lyr! zs%h!bWUu2OXZnLXm}PPJ)lT4kQX;TLjRk1Drn{Z)Q1ken-^~ZzbizON`DE>ND&F7d zD;AtYB3>WWjx+y^K^ux1jUM|0KRPK;b$+U;*iECb07el^<3<{lG z(U&M3#mLS!M;O^eI3!a#Bx5Kv%g*QSgV(^jivFJy?cATCK2M=_1PS>oT^N*iR(5uD z_)rIaINwdNxGBJk?GAw5hnFsd1Re%JhZ7cEwf zo@Qk!jjrFW%9KXs=6ZH95arcx`R6aV_XN&SpgcOe8stS>(7pXhH^MnOK5>Yw$hkf| zqy0*X6_l%u)2BIBM@xNc;DA{AtTaos!*-_9}*EV(DJSqcUTd^Zd&P{uh8S;qK@TGrrFx(9;Sy z*UHEZ3r8#eF5ff$E5RI+^X>AR38^kHRH}?v3AUk{mL+zP`f0XrgkWN!!-Qa};|ed3 z%$x8v^=yn)IVJ$tDcLO5=3gV)eIp8qia9P6}$+?28y` z-cet!8|y;C$qy^4l!dq{2?5k4F?B99dGX!ivx?-fPrYzGM$mNyrl|bwT^dGpZ_hu9 zy`{R5$hi?oO6!H8-e&U+ew`VCNizos6P1*j<;W=+ho%p7n~aKZfD(J& zh`m0frgj!Y4hdio8g=VO?EUyOF0QY@VB0tA1T+xSLI_cR%up1nBL7U3)20-0EyyCg zYqD7W1B`s4?vP#O`YRx3VIRRcF*pQ!9Jrq$3%z$*?fJD4lRpf8K(|=^Ix`4CV2gRS zkF*4)7Dbh^8vdIqn_Dt%ZdgqTS^$T##t%PwaiI_mdL{E>;i;rKe8z7Pg(`8(Q^H^p&C&q=hsgxN(0(-ZbNN}jT*ShV?lGNJ3__)1`fOSKT_%{QUYLVk1i3O`a(p`w09Q9LU)>Z)Z+ zekOTnwd%m27NzLUz4QbZ@~~}M*N|f-BzpnO`pk^8Obfib;=5!9^Dd-6fTpkZhhEQ! zoLm$}wjzHTQoiJeGNg@}aqsok&1$#S5;xAJ>PwmvN3$|ch5oS&iZt1YN{(Cs) zIs4j0ZA9>$#!$V0$o*h}0HEi!`yQrKTCbW;1OZOvIZDo_+GUe0oLU4GQH-$C0ArlqJ%J;MYk$gxM90E(O;3`W^`0an|}m!S;l%}Qw%jB zCO{_ydt$%}-^z5DfYQu#F{zEKU2@c@MhAxoRdfkwn;u<-OTpPBn4l)e?}P2#WE%!4 ztG$vuvC>JyP1BT`zL3(N4yjSbcR%?(e}U-8ees zf6Q3WBCzqs9p2H2J3yZOQJEAM6l9pO&U!gfc>aX!hGlGdtXG5%!VbcJKHcsF_&LNo z5>Eo((req_fZnviUG zc#N5~Qj+NL@|sYU@W*OMN+zEa4_qfzj7xz-@%LBUo+(2>w7;e2rx?dNa)Yo-;b%`p#g zP6y2-V;M*y4Q~-w<8dhBG?!!Bv&>N(#4K#`j%CW^@Y;B1O%T15hnMmVA~~0M!JPA3 z21%6O3|0I)MgN{)Pb}E_JQ<{r#*T^(-&Suqbd={X4mTMUVgmLZ5UL!2rz_I;3>jHp zQ_>ploXuCanXX1TJU#<~$BL?++leIKM)w*z!Fpz#;?WylSPA06 zIBQ#0n|!B(3OzjCw1MHQZM;(T`>5Y2yH|}CV^We`qap&$#L$tnj<*+acGd1Wk268& zu|_1c7;zsY()L2U4Vvo{?QK0@P~4YF`S#gtv~dSSYv3|N%?q~%eNs)fBjospFqheK zC9vILl|SF$W-h=t55TT1aq_uzVVo5)I%z*z(Q5m@8*VezW#h)@KSRSu$yRDNjl(%LVl7Bqv0A=`}P4#wa zWH?;LCK8zUPy0B-(^?=Q?z=Nm$c9F!u>G!YEqE=EK~bw)DE*U^ZZ<4&Br8sXwAb7Z zgEhhWnJVp^RG&dX&u@3uOLeY*CSF^UDW~@KoZLYIcm|hkQ(^ zbLWJIe`2xvfWXi^r%n%xkJLTGt9yvBEZDHJxL^oS?*Eh4Ubqr4;L4M0 zc^>^rn6ZQeb;o%G#c}VU%q@QNgtkaC%ktfe9NCaN#IX;T@&yALE)wMhV-1t6^NB6~ z{e+VP$k}%Vz-{MgG2TN!eRXq*MdqrTfOJ{n1sU@h!PDT$wH7hXca8Peku@H&_=6+xF^{V*Ca5$w?4y7?)# zA7>z8S!L`@m|z05$Vx^&G4tl40yX%^d0y7a86+}jch{iD+Irir+(Ky!t!KXrkt0Nk z?R1jci{l*J=Z?r93<^U&+P}y0Bf==V>@(vCefL4rwc4+oA#X-+{=qSIEqWENEJ3=`lqLH--uoPll?yMY33dRoRv10vm8_$_6{)yA-yi$JUE`;-;4&5cpwgeo7JN$@K1;?GlNNhtnmRvJ}aK2%4PS-xlM;F?q6I9g zXd*(Ys$$Bl``l;m%s^YAxi)54Mg`javKk8L6N-YqU!{GbshKh_LQg3SmaqYoVJjE+ z;&Y&iHSG<3T%G0|Zq4xnjRh z)dVIsm~w)$+9OoT4&plSe(sUwd!rd9s*zpP$iw8!JTigt1DNq0|omb-KeYB=Ch@#~)~8LDbkshv^7nmn-l zksych(#whnPkeb(z{NnOLX9$~$*_Ue|AbXTL~V~_W1Rc-0B4jv2*y@flu>GdDoN>U zEoaG1I@Dv)9JVDVli4rlsZm=%P>*2s>F!{qxoSWQ!N363KL42a-#7CCpf^{hW1<)@ zPQ5u&X#v`S%+^onodc(JB3_i$MCy$3i)FQ@Ed@{sjYS-rhL16W>R7K21~G=$JB<(};&xud@@{uFWo7TWi? zAuW1Fmw;nF0z+wlNHF`&^1Yrk4ck?Wi}c3pf5=p!FK9NT;rP1=?>(#1n$eL&Y;Alr zDr!y%>PnKos7$E5O>3U@myajL1$s6bzxN;Q^oDCb{Ah9rO-c_?VgQQgStCAwbye}e zas57*N84zjNh&bn0|idd>-{BxJN%O&XGNRTwYZw*9T(T;$Gl6{|AdJ+Xbku6bCOm9 zxJ=kG@p8A9P`0a)kVv~8WaOx?g`2zVj?o;!5#?;Sl%t3V3D793-)E`AuQ?22jX|sh z)L3DPxG=@B57``{+F=dXRIRNPzP>l?>3$2vr44r-?fMBT-0KZ}b8{k^kq5(8OJe5$ z&+2UkdOB>`Z51=RxzdoXS;+m49f1nJ5>ApUfXbD$vxuGTM0swVO(;mb|sxpFxwdM7tTT9 zQ?RX#^Dai7q#+*>X2H0Ldnc4O{<+3)Mi~4le0nkljI-lj*1MQ%5-|G^gsfSD!S%83#V5?=7)Ciz0%*AjfC4yy38 zB6cOp@cR=m5<}$BwK|}U0r3pTF-iY%!z+qQm~qA5YrN9jX6uUgwn~ZFc$9N4osJ?zjf2W%%-@?6^of}X2}m=$#y!nr0LL$S0?b)o^myJ^~)wW^2hnf-k zbvltUi}G4!e>t{}r|((m;TSfmd4Z>#D^I&AYN1k;eopN#>3ZfH5uzo+VO_!LAz6i* zV1hbLFXljbKAi`*J?@ojKjDA4T^-P84zRECs(Gog#0}T$N^xeru;k0j2p@h`0=QQB zWxt+vuZWPjUwZ#zT@;kjoPa=z125-x_}4O=tkZf03supJmB?T`g*a1tYT-*jEwSa= zSV&MK&1uTjxuB%Tc9?9vcx56+j>ouA3(v(D?b@kYY5Ip{rSoPv`!jp|4W?IfWtedO zqp!k5V>xmJ7Z2=7J#2yu2EE4LS(7$Uv$o`&Zx*R3P%=Vsa2G!FQpLT3q|bO>)EGp4O{ zNsaZYd;BbgB!y;SJNCBW5Txs|MN}@)Q&8(Y`n)aWxZ11Z(}a0x9?t)|Qx^ta|L<}1 z(&IEvXZvnhNME$NI2r=|liS+@fx!L{AqfY?T_^RxRHV|i_`4duLiEkf zvHGU$J8u0FZV3Zl-x{NJieU8_-4>U>bUoAvX^}(>f4w$0T8y}?_CoMDM>ROLl;i}S zDaLZ#fm|zmZxyckoX!^DR(zW&F7aU*VcD2dX2Ov_d=u-8>Dzd+ZA3T)OV)f^z|c?* z?KYl2QxwLqXJQGvE=h+s39CI8e&*OwnH}qPE$x1qZZu}Jl*&bcp@bfm&>|JjBw`Fd zOPXAJiz4(_l*>u?F5rhhqA=g5#OfbGyq*c%gng&}gO`|3%tA zgp~F>b%yTgG41=sjRq)^f|`dhyTxYZd4DooO_|WkT;eg96oc~9q4`QkXFkiL4J|vz z4mpTBOvNqIG9QQ( zcfIzx%aTx>pT1&}JoEl$U3)R8#0{)EY8;=OYaWH&AT-UJ_O?X%w$G&)Bp^6pSEq_h z!6l)fabz^gucJp!Alia98i|B#_lvU&_BGNn4+FkR!O1N>_IP*&|>g957S7%lYzZkxb4;1(C6j6F`#VDCnS5a2^>J}29R zr60_yOQm>D6Y2hNm4yzbCSHMB&^bq}QOI_Pdy|i@Z)WR%S#}9h_)>}Z>?X&TOJ1UW6QiE2p}|H?5wodSJarbN%~V z@Xf(Q7LV=1T!QdjYOSw=gYnDW$@_C)45?s=Tv;}+J@GbQ3M0r3AV78|y6%1r_J#!E z98ol<*!)`ODFMd7$ppWc8@ZnBuKc$WS2$6Ux+X6)noZF3j$&Eq)zhkLlaQ7uLAgM6 zTJNyXqSFMR^Pe`+1PfbYQR4mk_r70(+vH1mWm>?VsAi^!`gPo=w@@D?J^d*`dC`^8 zUh?&ppOu0p89Gn{bKgLrnh6ON$RPmbmwlBcH#4eoyQ`1i%lDSoa?D*HzYbkdC`GaY z5|+PyS*FxH-8G;!ZZ^+kVAK{Vimgh@cIJ|fIfJNHHsR1p=K?0R8V%5~>Mle`^)>_i zxgM{%xc(^nh=_=3CYF>M^dMRq9_T;K-xKR5iNc}TjN4~Fhr{2`>k=(X6;NEPT~!L- z_Jz9a-7SBApQ5Ucl{F4hXYD{5Il99V>q)@G4Nu7TDT_cD+5EVYICK6MKSK~!UP)>l zyU4`Reu4eP^H!JXLJ%#dFK^J6pj=A;Bgf*4w*HpEXaQL7?TEZ32hRZdV)o!a+82_I zdCj1j@L3zmJe<5Bqzqo)Pvg7krD(xkn+#Ar6RsR%H`dK{^nyun8MGj|IAcU2Tbujo zRe{Xkzkg|Q$vp?yD`XMWDydoa7n<5oVs3o2;VZ|fwX1`JSlqSw=I3QcDR5LU!ngC? zI@DP=k3T}OVB$x}CtqaIM<61UbWSiLSg0tYEThCZAgkNg2L-)cZ6M1_K!?FamXh2z z4-K{qIcuXFpPq`7U}3s;5C?S326@4?oWd@%+|b&<5E+wT*ZUS+Am`3YIJame^G>3! zj?2&mtB*bt+j$b)i0p=U4R#12lf`Q*&F!k8Q%{JhzVRHnftS`pHsE}CbWfodoTvri zQNJ#N0Ge0jTBOoKf))i?0|@e+8ma_TL^?1C2zLfW-fHM&5i3e|zyhGguYtcPwvABf z5yvS!@~1Ahfg%W&WC(U$5J&efJ%oA$i3p?uE?+#(*O1}6zB)ip7&pH}^}EaQ%UZao zl@(#GF&)~XyM>KR^ zfRw{?)@a~cNC{hqIFpS>Uq`ddt%&B)-0$XoVysm+mJ^mmor{=dp||R^zWg?$mz4ok zqM?4h<{%+#`fD+4)tepWe{l^09*GFIyzF@_?YjU5zrV9Q**FvC4Lnhz`=plb35^R& z;NWaU$)?@Va&~&aS*Wl^T~R{q6|1~Kv!YSrg#>@f!D(KJp3p)cV$HPcMDY& zfg!$crrfpI59Dt0l{9N`=i*)=!}zTEcHi*=H=;8W2-fwv@PWjENl`Rflcd<^)aFT9 z0lwvRpJ5N=fcgOGpCj5c^(3$(C^=#I*#=6~MF~6qdzK-m*~2sl*92vEj8wpT-$pY} zpYCNP|MlmZGk|PvdHEpB0Piae2FRy0zx9S0+^B03_9G~`+3;~kg~{4)qT9yTP7N_1YA0Duli{r_S6C<=PY{cE{E z>-I~8TMo+1ZEHJ*0j`%iW2p+m;Y;onQG%Gfe1uSA2I2!7WK0td+hnX$kYz8HnKST+ma>r zszVU$UhmU9?#uP2<@h_-LjBih$^uLNo+3hKyt@f;habIm_Q#Zn1Q4gwN7EqC@Q7Hx z!2Z{iLr=jVL^=ME!1IFurHzN4hoh_{P>RLO=HMeCWo>L8Pp5)SK)U>k$Qx+_$wU63 zcahWmhwCqy?xq&I5PO5ao(za)fjhx`Oyf+fVm^2s66bQ}Pn?d=ikh!GV8BGZmF=e2 z72)!KaOHL=D9)rRx4V^io?z+`SHu-#On@D<;+NbEJS4fH%x$6o#op0~?IpN+bi|5j zt?bLRg4@CC$Bu9JN*NsKfos#lE`dOZ7($p2db>K<_j+6Z2OjQ?gSg=5xaXK*4(xx6 z(z9kWWp?D0SB{8o9)&{-?cgw8Ai8@N!ONUC=b?LQko@hMMdo)-52ih0c?;T$+ss1_ zaUODbxg{W$@=XcPkGjrhc;3GSUMTLZoR{^k+lS?m*Q#Rj#An%46>pqWdcqP4`+s9U%45r|=ziqZooj60gAzoU zIz2DaeO4UpCYQ*!SNHxQ8g7n6JgbHRrm2u1I9S~)O1E?y!GUDE9=E2+lpu89? z=-SuAS|V@fKd@)A2HocML{s}U*nO63muM~XkC9DEjb7_`(y1CN zq6d##-ja^{SsksuZ-al~QK43`18n*IoV5{MSR)ud#=0PaLZS(F0j_GJ#dA#y3L5ICM&v20*Si1}#wEHqWea0(L%6f4uMYm z`RZ?kY?;O2jAQ&#Pmx!Pv=eqQ!*HI7Ff{Y^o^Kd_36pH!_Fjl?EOlWAgvEwl@>P_B zw}UshFiut@dHbw6g$*f2;(GHZo3?-0fZ7@kX1C)55VT`{iB%Dnni=RyqCWlQ>cRXk z|9B8S&gO1r*suPU_!vAW#bFfqa)_<@hONy}B{`BC^pdUOX};6ExXJvM!>jQW`+wx6 z53OqZHQZ?!v1o z6{l^V!f0k#e!k+xTxMRjM1e-}36KZ0y^9L^57O_pb)%^4T?N8l;cwcsthj7Z<6_*u z79V|APDiE@M!5p#85oSUpa#|`_;;q_zFUw89PB!RM#z7nn*Wgh>mG1)%o{*n9m@0q zg<5?>O4_L1>X6GsDDwVt!zCQxLUGLLG-4KHmH>)0Q)RL!&kQ|5QATu9z=w7EHdJ=tIu<(88SZFypP8y9D%2vb=_X^>nQP7v^1ATC18iA>+ug<6bP>7fda7 zS9)CXgetJ~gn<7Nn!c|}F1GMo_BQ>_r;Odr|Fn8s1nCi`k=A&FUv=@1g++c2Wr6!v zXZ4=0N6K$fD*;S<`x&kWQ)xUO3}{`p;i+%-qr|C@Le#8pjbTZ-R96DOI9Q3MwQ{m{vsLgCsU%o3o$iUz|T;Sh1PylBFF< zz8d|zcq@>)PizjUm^0OOen(kvN{2JAa-|a@?#Fc>+1ueN1F>OpjA>{|?2ogCDINx( zKGfwo(I0i>G7=)Blx3d0~Gh!nYYN1BJa3S z_r6m2U!kSt;sS7(2cQsl)e6c{2!jarvO^M3Y5lRTL79wHzG1}8ukD)S-9 zWW73VdKG`XQ;;K|VLSWVWxG!5eK}iBmjKol;X`z7$l6NAd$AoIsHf11N*}m}VY#%s zNPAs@_i_;@dMPxc2ACJ7Qf+P_eD?K+Vzy>j2ol5o4lQrtzwXQBMsiJc5TMZ|TjI9y zEjXOAR(U`WFJ48Pg!m5`nk;M5>?MbsOiww<<60LKE%k8WcMT=q+q_spO97{J&jD*u zE-v;k&3uF<8TZupwHT?DdM5Vgdwe>I61PxVg#wcx>BD0q^dMvLKTf)`&`P@Wvm1UK z4EP$4IeE=~yIftxx#0$Y-HO6e;!TJ|0pT&Y5!4tBD-LRniWG`MJ}abJm2j4={V+=r zzXa;Up5W5XXDOr8hkY(ZPW9)PqUkADW_@;^$WsjdT|wfxB3imw(C>!M65pL|`?e3U z>kaer>B)ZQctR{B&pmbMn4bGiociI5aLV?7I+i6HLM(<;?CKxBjGE{OfS%2p<79SS^rNGG zw~|8-*z&0DO7KGs#cdewD>V}7{&X^o2n~7%)-?3a%S6#+=-#xM(F?cs7(dfsL;?+e zDqW_dS(7RkM@?^27N^oLIOD ztX&qv`4u%&3e(-~fj2{30114vgS`x+bgMicUB37+)Y!M_Ti)lVe{TSJ2Wd1Xwl~N^ zyz%EH$C>>a{q`?Md^W92k{?bgXWC7NQADDz#{rR!Z&Lk;QP;d>w=UkW#CP(JN!SC?pmN< zDxdQtk-S);jD@;>#}uZ6L9Q*7^R@1c=onKI+S6nDKY>2nqU(Yz zpJq&L5TZrxvU%Y!Zn%tvL)!NP8`)Mf`ZNtN#a`-#AL`XntZ6fpm;%c*^`+)jc0zpV zU#`rH{+IPqA%^xz>1&}Om1&-ne43A}?vtA%TeHm_e55MDp5${-Ob5H>9RHnV9!ZE1 z->F+1aTG2bw%r>6U9apYE^Y3_rNzJO1V@l3>t#reU6do8nt(Rk-&j#`LS`{^>np3G$C?2>a4W-z@4ru-6HI7bhMSw4>jJK6jefa# z2r6s2*)294)BEH6<^E!m{pX-mF3C3`?y2MVkgjprRf6e^yuFFigNHz)sen_?qs{|p zhUSZu?@^V{e}pLtPaN1p$SZ4jI2Ykk4#dLgrbNlb&+Y-*)eOgBRQ@8b} zZf6AN{Qa^%Xq=$3W$9c3MsVDKq{`3$ zb^!d^c6>ZFGEy~V&JiH@R3p)z*@8-cfRWYH7?rq*A9=(}z%!&EXD^DIwtrU8fJ9{@ z%&nzz^{Y*ZwGWD+2CrEdF%0sVrgnw>R|FVJLz?_w#V3@etU!!tC(;)Xxkd7QTf^ZO zi#4#ITRgk~!ZQ zw{Q8VjA9is=ls~t2h?TpcG8U1=p&H7z|y1Z_~Vw-#bBGdvXMor|FYRj5^;w6x@b>z z{t;`@r?o@?YmlZRk?k{T0=O*ZSKDv(l>WKM7>X~OipC-ORE=#k!m1Hrdx@UDG~F?5 zn!g^M`>fzLaR-9bGH}cGo*uD{@r!DT^4U=x*I{cXhxsaGZ8m}k@<*bC!@tx1jA41) zU6)X(*#43z>Bhya#*i-IS+6W;(3O)Zp=c%&9&@wr4X; z5iIAq?>tSVqjhTa{&^&N5pEKW{|8%7^v!rE)!Gi7dl}3NN`xXzQwlXit}2iyOUt7z zzs)h+Ijz8pLtAGaaRXL531|V&YubFxT@KGj75l3)kZT4af#Ml&g?;>eQdibyj3U5z z`#G=8Qfx9P@0(N0H?ro6BqhepuKTBl(UxVyst`RUI6A7`Dvki0Z1pXR?Jq*|@v({$ z6B~IfDf~%F45p#+iO6K{@=`D-k~n_wiSbGUzwsIB}AK%ESQ-VB9VW1 zUXjFR`06VU7fd>~=P>E}x1fGTDwjFIBNgU2p|Q}h8J2B78dA+xY*$2L5(lbIw_cXV zr#!$lVHrQxvs4>s8QJlCSE=&Q>G*lB+ls-7!L`C`*HyN+!^}5P2K+V(iz!U0_uH;N z6UA+Ve}47&JB6bwj*z_=^^5}i{jN5cU^lAWe@VI7UBpii$u>ERVEwJB( zBlWSr!-l9$|0MbKsLRmc`f*G2NR;wAYEgKOM9#N}i8&pZ5eM1Mm)>2fyz8rsF_HA5 zo9n7s&w&=S>uA3ceOO2;pIftMX$;+OpJ=5qL(q4OVMV=rZKuwP3U6Oc-z@z z*@Lu~LgL~wC2%n6r_D+{g157US|_Lc8kvf7W5I7dxM+^p5|F==nu5xv&8#fat%sjb z+hxtjLf7Acu}k^)fg-tX;a+qTWguBoQUHQAVK>&~`ovTf@QlWkA7 zt-E^q{{F{v?3erPzOJ?QxjyH)NM3den{qPKxSNj-=5u?bREjeh1oZ%`G+On>_*E1k zZB7??K1Zt(w`6(KVFTrO?*Epbb2^#$yF z7$iMDfn~2ofk>=!AULs9L$LVCN6EhZ3a@xP)W-yXXL4Dbb(|8o64a`5C>QkD!lC;p zCjaA~g>eY7p6#H?d8`(6K40ylE#Yh*Hci(0_k-`ZxbC`3v77@(=@&M|f>&G0Zh%b| z=C$!!H><26wZM1KHFxKh-a?rzkVCWtx~=Q_E>&$j1#h_Sut=S+CR9SWu9Y%QTa-Rh z@iQ;>!46~0h_FSNy!*`{Wn=rO=x*~0ryb-|)F0=Qr0(My7rgb^bN_xU;6#09K%nxf zkD`pC?_37Eo&@i;$Xue=&P!2xRsLQ3Ox@nyg&(!Od^i7_!KTlg77{}}(>`X2EmBH zB`j!)(cz76)xyM_Y|wW+*iya4Vj$=Rv5L z8PXO~dyD88mmHJ7-3a?W-nIcUDB#9mhPzlgAqsKe`z&*4{kvn@3xVEVW58hj(~Yia zrJ?7Y)Lj!8F6a>WZHrCryM64A@BffNFc=wJLZ$niHhWmxLhd&oonJ>Ls_MS$EV&gR z-=5O&03sXLZ@tfnhW_OEY*0pOMWc=dF{6z|Cjr)yb@m}2kTDu`sZA1is?G>3vPFYG zR26?ZE6ey2n+D0ts7)+Ys4)b5M-|W~j%I_rTS4B>pi25(a?Ofk_$}3vf;Av7JSrCE z)&z_0lq0i9i`J@a`&cg=(`Z9@-UX|yPVh%e8;z)L$MIfOP7#M;6*k*1TSaJFhlho( zitzA>Ae-+Y`d*39qtN2%Fu}T-a(tp6UgF5M3P%6>__Hg%-l__&!i*|5BM5fB%Z7O~ z9g}57Bap6>E4ZKp+y4ryV~lv5kGn&@e5q$1I}TI+f&%Jj7HAmfC=REM)c|3V!QZ(EVY$8Ve(&ml7(2-ikhJZ|h4Y6Lio*bgS8>8}V4N~n$4kh+HH z&{z!Ll{iyr^A z=CL3>Jh2pL9{rHB7NzvD``$FCF=w)UZ{qKgb)Rh#bf4=mh^m zSqO19gY^5h?iV9O*J}7f%H#D!pNNWKS2RJVtWpWt)zHtUg%6vD%vK`k#yTMX*XXl@zoc)T*Uw2hWN*5VYy-ML#I;}E(CmT|? z?trK88ut;@T7Qc3!zi3_iY0+HNelb&5iLf}hF4C~ z{$Al=;uB=F(T1WDP-A3eHmrfS>?^P=f){7$@lN%k5Dv-SaT$P=;*#?t6Pm3@0QkVn zceMJu{^Z@U{rZ$qz2swi^GRmfb}O;4?elfY=dA+N7w`qp}guH64z712YYSv0p9pF;(#NR;u9 z%HI@UkWVyVb=CI+9vU8ikJc1&smFdQJEbBi>vo;;EFP9|f1(DQ;tr!CxaXHdf6Go= zX=xuZjj(8PkK3%%tSUZ@7T5XX7%IG-Y&|0JtCPqhSIX7GN&T~o3WVffE`*?A zHd!2Z#X=It&v606Q{n}pz(3XGD7UiPWmu0~`GyOj0y{}gg1i)bS$2qL&c%Tq_~~`K zRN$HXbyQHr*!l|5FfK~rP|jQ7+bEMoH%KsY1A=N4HPnWSkjjnGu++J+AgABP1gTMm z7-ASo9=qi46l?N)DkKDTx+6tD*d~#8^x&fSg2ls^4P4x?CFS|w>*`9Oqb2QYIZ8rc+DE@+6R@90UCU9^s4 z;(MHQdt=N4#^6V^$=B)#>L+;zK@=3*PIfHYx9VIraJFV|VpOr)`+5~Z)It*BoKRAA zL?U!#r=oRl`~M1o^o_ixXWEC5$3&4N6yuWMOhV{on)x#h4+9C(?^DfCOlNILYpbnd6GhR-x}wWsYhx^qi%TiawOu<>_7X53(2?dr8bf{5#Qg8 zd(g9$8XgVJXNk;yb*(Q`2p!?&*M%=rIaIkA!QNI>)~Iv-1Qx^eT2fX^lIUBqCJh;_LUC zuG=NPkN0b+wi`xHbfAtXxL6N9%nQMASGRgO3f5H11W2MV)(_F|&C;dvs!TEsiHt&= zki&;-*nLjWanY;H^w2>L+v|mM8-Rs!zEE#*tayJ6s+fReOxGzM<*BbxLiE07gsp1| zXX@zl5Oy1&j@L?dy;LvjL|(1^Xj0sgt9uUPWSi5M6smUsz#~4<<`N|q4hG%+d&;6b zW1ns_p2T=rgQPyt!krePBh{4ZVRiy}Xnjm`#zz@CKQwAgNeIQGuEm$5NXV81AjgoO z$W*Hx5M~9bu=k1xyXh{klXe*gFzL%58K}UrQp}XfRpbB1R z5`~%_FIH-dR}(s8t&cFLO(oc0og_(6Yl=e)8`Wtb@Y(z|?4G<*k#$9w(nh?%ZIBka z4&AnD5o)m0YPAt0jTE|GZ!)j%@?aw0>awoY?chzeuGOUG`U~3gCP^rTD4m%}AWD3w zJQ`tJ8+BFz+;$764A^KlDy6d6rq!|0Xwt0o#!exrlqsqkrLg!}bq> zO^N7Ci4BKebx-~L61?E(|B_lW58p_K#H)f6P*7<8XpexRE6Gd%@$!W4g;sy4*!PT$p8 zqIf>_@_hXIb6o0_VZG&*5KDNuI`Y?3in$~#5f&&$F``2mDl4y{S^PTsLTGk( zI7zV#lv7bUwxM#WGvuMmz;zonY5Q>mlDI zVuxV#9%?9-%trAPlsn7hT9^c7{vu7u^IrdaNDTP0_d=!O!`{&{({Mps>+RmWH~!Cr zI3S9S5Sw8`@=7GZZy;xQnbvnwhTy_YP*ktUi8*n5XTjif`yAeKpePKP>lDe}%ldbe zzm}>Wf)ac%0)}a?PQ;;4v^Xz5lKLxLFH=uc3J*@-P)OSY5$374d|W&Phef=YG&-K2 zxVa2bOiRL0HY_@vkZFw0)zO~|Zr2q-vprCPf)XnojFaWgs`}@j;P;@q{vfW|?*jH) z6{3Xh(xW-{yOI~8V+;mLXTm`FV+veYSsn#*7SI;8Cw3rW_85DvkAJYZCZ&YI6RKiIds z!sAAz2;`0T2!)@w6>R(>e9S3#ThPH(5xb(Y9t?$EGTQJ!2L}IGYf0MlSp3pPmOqFv z>3oL2qc3T$%{90$>u<2faG!WmoGq)SkD(s*nQZT~iDsGxauZxq}a z7M^&Y+xGtV(kaze%V*onG97$TD2pm^3%)6{*zbQ1!qbn zo*yr1E-X@VT*2R1BHtpUhR2lOk32OWV8@DPJUgx{Fd z>mkN_8+6Ivp%zV)kaigjr-nkFQLlu88QUJ^*zsmOH$0bXX5nXkk?u9~ULZ)H{DuY6zAB`iD1Hcc4u`bzA)z{xT2$!=-rP4 z#WCF}h<$4?U=f@^u>wV)0a6PQI7j~kGtEz#AjJgXI~OM4Z;U@XKEHWiQhZH1jzz8f zCYhEjeGwrEcwieHGvjOxd73BJq}N|qFYR_h8czDS0Q%N~mY`90YP_JfW4#%` zQV1%|uKso3ru?p1jCGSb>SmLF*UuvIfLd~MxWH`&kRf|Bx2X$1l&=@#W@C6?-YzV| zEEY`f?3&|~5TpWLw!$3K-cb0wdf{j0W*o{zpIc6=$XAcBpU%%AK1aItw15EAT!A(O3h)GJQ#rt?Pman zLd4J3N7Ew|5bAnb2bU6c@BVK@mq9wP`<5cOTB4~ZDHtd5)NiM=;OgIX7&0835j~e>;ssGGV0)fp0ELwPxBe7vKM4vB&37(N@ zG7XlIB*Fl1z~dLVMaf3*H3au5EEw3GU6Q_sDM=W$AfO&b+6n=lhD^VC59G@;YO z8h2nyVOQwZe3;P^>5_$uNdm0FwzOTRm28~Lo^;(J1oJ%Td6lOQ#b!GUOtS^K&aKL1 zlkj$1>F;!K8G%Y9Ny?keg3o^mm7-J7S_>XHaq=cFFu`_tyMkeB4WA4 z>JbpMX1h=@rkwhOS>Oj>!)g4;0izK-MW+&fphTO3uZ#+t?N7()#y3fQKCH1m7upH` z)=>we{I2LR+o6t5Oq^VSacNWP)QKwRtDQ{2d0d(GEbe$k7prbkZ_CQ8yP9VH_4CV# zAItf|`8Ot!j2tn3*Qz9C2-IP&!qkvh@IvpmB%VT=MNavWo~UbK601s}lVI*0l@1-C zT!)+JUEcfV2Uv>i4jarq&%h{)=Rr(?E|3Wov6sT2&9nV{qg|TB>(N!{Yr^RoP*S`2 zLkZ0)&GP{qujKny#6HG|U8w(qXvd?VM8##L=Nh}=S(1g;Qwc~l4_|lMQE#~2nwXZ* zGX~RAF{o*t8Fnk#u7P9%IQ2kzE8i!{&kRPbO#d1KeQ14_Q@@)f>(|oLGO2Cty#ah# z-JKH|MYgE=g_t+L&)9v^+=k#35d3+lVI*|q)r=4}D5y}iD=n3IH#T<{24oI8V^wK? zG-DMo%lzpn8btz8eYgftz#mK9){E&ShS<$cwA2hBWk96|5JK1&oJYB}eN>28Jq2I7IY-JMWtFKOEk_WTOb?9VFVMBq?_M4!5$fuC!&S;Q+TPQ1OE#M`l6#%k!o{0&+&iP3podNq~V9oGi4SJ+AO?_6?7VsR_d2nN5w;3-+-QJrw zhgumeVXwrr1~W+7g=g59B+i9>ErrB0_-e?R`%1T;YkCTJU)DL820m4l5ZIk3AVGvG zpFmvp2_OlT4M@9^3HN$_I_d)LI!?Z_POn)#2M^jJcJ(03lkm^Jla-A?E zXte{w!`-%BhzHbUa335ILXlUtd_}(5cs%_a^ta7aIvIWjI|?OY6qG&u&nETXW9Vhv z8@%k|{-tRIz2&f4a--KW%A5m!)?t~b(E$$sO^M?T)1s^9jslm_Z z_s2$2$zaKaUXC{?L z;IY)PgglRIQ!YsS?8S3%!MO+y6St@9BJcAe>f;upKYyKwvMiUFmDk^>(r+WlTDwed z)Mwfn9de+Yke*Oll3UVS?szWiQaW8UXAUZ83Q~&j9X%8h^xWs_QI4~ADeobqVL5Tx#Sz;VjEI;QvwsDX)|~8L7sqrh8}N=X4ev zf^qBrK{>y4nNJc}xaU+7JDqL1%N;(}S_xaib*h%V*`)_|-jw{w^A#`2zE3->=zvKtTOaeabkSO0x1E>D6f*+iOxP zI`0*=Er`wnE}UjkZp1k>s-uAwO_m6lC5$ zd|M$6dw@A2XX(ICSu6Bbg|rC(>fZl*`tcY_SoGcp+idezXlp6vk7s_rtScY(oQc9( z2r{|4r`05R{JA{W6gg1DLTs{r7Xiwu{mKpb>TJGDpDsaMfi8($s8-2qy5ZO*PzvW< z^0JS@^AT+E-~;4b6AiLOx(QVss0Q*6CXHx6xa%zXQIspIRm}b{uV)vhR3U^*_S~9i zKD|81nb}^GCCkiso{qr5Z*{kmf0B~Gj%&S~;MgRCzSsU(ppam6(yU4KHdvACMm`v> z-Nsszvu8@Goc7YjZ%8&mhdtPOI_BV0@%iN-RoZo=oz5bk=J3iE4|vz0Q0!r80Gn(x zO&S5V<>xO85{o?;=1J?XvGBe?gX1;K4(PdBpymt9CZ~mrzn!SV!{5-KoJkO_-`YBD z+9UT#a{UZ~M9!l1`PhUAP@y}@v#4_1r(Q^UF=#-;`7y9ujEcZ#T95%oOL!e$Yq~KL z0i)1+)(@xDKz`p28Qcbm9|2}7!FDDgbI0@tGkEsmFMSlH6319D+R~0eJA3$i&@h{F z2CWgk28z^|t6_EcI1thsr2tx(JCp!AM--|IB3?Dpa(l|GOxP@p70kY>tT!m<*kiXY`4%@hlIXZpVc!z^QQwyT@MmfYQxl;OYM`Ixv z=N9sB@hMt~GoEZ}2T{Pbj*&7hDI8Zv8TQOp73xRGk! zhn97jG>3krQI-@JxYTNEV)kvX+z7{-VjR}U1#1x`&2%X#W6|PZf`ciMWk&@ze)_Ex z@<0PHfw9p1{1E;LUE&!>xT>px@6*J1s-@3n?3Yk-cG=vMZr0hyEvNl#Qv zCuq@%_<$;-SyJ(IgZ`7|T?Z*Qk|AsrxwQkmDDouUPU*w=BxCyLzu#7RcGQy=D*n*~*hcAD3fdi(m) zOp9sSy+m^Adxmx@z5Hr|Q+UWo0-A|6QCLt&MExxA$U;=d%ru+$UUX)72t`qBY`OsqsfH zfoQ%%0q|XuLKUCICgChTZhR3QsSSO{h55(hx6Dh#E^bOY^d9yJ_w^ zm_Aw?|Ij<{OZ_f2md|t6zP)rLp}VYL;ES{qwy+PF7}fIIo8K+5)2dMtW7{A9PS(cQIg zu}OD3spfxdWkwV@;ZavSRH458sl(p-?3mHHD>_CNk3^_*9cT5M=Ai3%-a?SpRVzu_ z6OcFjoqp^Ty*B)X`q%`rsg^P+jTw>ObhbdM;X3tcG!mPUiuW6tqTo&Ny^W{J%aLcB zNSD`1T5z4OmCD{`G-fTP}TIK%o%bbEtEq&wIsqFl7s<;xIWDT^2>5 zW@*1$%;S3yOfWr~cEI#0srI8*gJ}el0gIEN2H6|xWtz#%TZRDcj_&j|tj7Vi8^_a6 z_b*M?>4KotveLa}AnwTlW2A`3{$KRuQ>{t8awv8__=242J71tGEr9Cx_!h&RPH0ZE zrKr~_FZHqUZ^yd}X3@aw>DHs!RC1=!&?bc_a=zF1tPJS z(FO2DH=vq}>ExM=W+4M`lWwJnbX^x=Y^7%$C0RO|;@tt)<}V*lKvv*;N@3=kZL3(D zJ2*)`Xl9oA_G{l%ea?0Ie2>mL9QwYgmE)YFOjwgh{^s1)%$0;7ChB?AMr$#K76b&c z{;Jz3b>+csl(NQ(%u(?47j{@~=x-7u5^F7VfYo>{0iDNSgReuB%~r_vDEBBtTw1Eq zrg+BgK=7re$b>-apQ-O}qJ!le?*&hPBUou&H7JMr`!TM{g7n07&*cM_I~Y9(HVpA) z=!6|c8ipGljKHknY_aUw{XeXvkpMDl*g+!s77>DnFO8)g3PGR?{(wVIy5B#B#C*1P=rjYih8ByrXaM$4s6lz|q zDDJctWWSRZj^V|W;14*Hx7&!WiD7b@PNYs6x_4lJxlt+qI5gw&5#Th0dX~)71 zkmy-+8+r+%>rGEKtO)x_FAeYB38{xdTP_ig=|xKeecv8O^L^LN7)=#{PxUbbxl1q4 z)pT1%`ioYh)3(@eAJaBNV8em|CP6NNCetwjbP9$<$K3&?{$LC^^m=0Y6C#k~utdM1 zn1^A`=#$z1B;9(W*-x@*0fs2Mt3~OPw~`th(ZtX;2W}T@{nYKg4;96cAJYRdfs=Mw z(G$L)y#|jPW34vJq%H)hOf-#Uye)EDH$fKv=q){GBGJA0m-#v97xHm6(SJ?P$v;V4 zLGWvpnW$JiC$X?q-aMW-V*@mSOG%c|xi=ZE^ygb~jIM^*alZJ=To^04*wV`2)z{e2 zl_obQUjOr(@ZoKzH|H*{#q8BvmMRFMCwytdarai_cBJI*bcVv(9w0~QX@^CoqiCo( z;yLWORvr6pb$ue>&_0e>vr)e%4KI%!@d%TR+Q87xI^YT|!3Z^9+|E`S^$IBqIWP+DZ{P5imCK&FApG{ zl$RAOrQiRp=(yo{ll5qKU<4LSB*}o06CFMXPJZVh)#^w8u{c)e6f^*>n`WF18JZiM zraCbyx))R0`9%D6AKd^zsQKUo<~Jfhu;PM;R$0Z0@VBEZwnCm3Ef_=>4E%508*0W|C4sLJhF)vPJQJ8T#H8r&?zD?`0{FA@Y@ z58Te%^5Ns*#XI9Z^^4E)ziiOHQRPmK7v#Td->67Kl)lBY^eg_pF8J1zSa_>KH@;?{ z7N;*l2YhPP)#WR$`eD|HWcDalr;!YNV~0-Qdr6VAs({~I*H_Lz;UQfsN~$#Y7-3j# zvHi2*iK>u${x4>I-pg?GLHA___G#=t7q09@Za7WWe&IJ&WH&DytM@A*hwP=#1l&9A z&xbn#?ayfd2=CD9qQL|286rnc49Ht3mJYT^-ac^;pWNa213DEDe}lMW|178 zb)fLO_OUhBI(x6*$S84$U)Idc<}1@1^BeB~{5=nwh<9#^9Yo98j9-o&6^qAQ7E8b$ zZ!it#&2}_G5HT3wud=3d;GA}ovSuH=XGx)dvUsR=@%9|KhmU$Od-Y?ZO2D3r7Kv zQ$b5Li(>n`y1?O%5;*E3TpUwa{}z^>49Vo4;DXEz8_1nelnkzYh-{o?xpYlSo+5yJTu@GA{ljyAfdx)*at-vsI3{=;@*|2AV5G>!PfcaL45u+&N$yR zNIlZO7-*)x+ZV5!|2$ffzwI4R{9$ijtKGNV@d=8ltgopa1H2y!3)!dzKxeD@Teqq8 z1U^9SqiWhA{%UGK=}(b*NK+pz1LiEpM0^<5-=~CfiL@{URkZA|DbWNsARXc%FH|m! z1hCe7l}RGsVlsu3dKc0-FSkE_>~dT0$HL|$^9eccAJw#r;+UYO# zB^bJ;U4?vBhmfu0EpXA=AR%(Rke8I%4|dKQQhQ!j=t-1C|JA}ka6pSBEGK-*y4_wPKrlR;yaA(^=0wM~YnScIdN@%yIpW z?+z&_6X{%_0L0?+44)Eb7mQ7})ZD#OV}D*D_79O4NlrZZXz5Ji$M9>f#i2iHN+R;L zj&4W#=pGSKTl(`nf)B(~~$)%(NB-4H}%6dXoP$ z*U`WX6(k&{Rkiz$G3?tB9bT~QjNIr))~J-3XZ7!r*_ZHs5^bm=j_0yor9wj1?uaLf zdKvmQL|6KZTWV4b0Mk-g6Hx)L^Ax1sxl?xs2YQ0o{>Z`;6Xm@3)2s>G4SXEWa)7Eg zNyHz#N)g#(!pQ@x7pHqGfQ?tY-A$@RPvW_Dw~)&qW+o0I=Qi!Ixp`xlWpESXgou({ z8^x>^Yf}(@zFxussU4-IU@~@`{j_76CtePdNC(dFOS>2OVVOn^;{R#sc1U=eMvN~L z?IcI)*vIw`q(N7z;IUSUQd7dy^MT+bV-ym_(E-GJo*SKSLxi!k`tF;qPJ?E0>@nw*`-w;RUm9K^zqdf^*=SJgxDpT=)+chkV%Fc|mjV}FT0=I!|mh!%RS2a(aRuzI~4a`Y~b0moI z*p7R_#O;Xx?zJRM#%s2n9~8D9Wgti<;-gTBD3%`ud}=4RX}Czv-DJGI`q)&-kQ*gH zpvSSy7|M=(TQt~Y78#)DuiA1bUe?zXUt?7K7sfZd5k|Hl@mPj=CN(tar4%`IBydHP zxb=3c(JYE^2=``y(f;}Idw&!b>LG(Q>RCuhF{eUxyN9x(Y70)iTumHQhw~T8ax@Vo zYZot3*fdnarz5YZm`dOFkq59x;PzsVsPND}`o%H^-=@?G7Os-`-8il`NST7UN~n`B z1w-5~NgwpRNeMr-FCVxI#~N$ZFpO-wz70nqG>=`z{dNd;Cdy^s^uKbH`DR{pjr{)ospGFI0mzk~kR^<=d0`!4_z6%X*8r z%CHZKH`b5FNk;#bg;}qg?sGR+EJIeFlbka>1F3!vC7u2H(CE-uG{w8%RJATKxLhID zT{y{ahb*OoKLXaMd`FwVm=SM*=+FO-R|zW&?=NBzR|v>hlWfG^+`T`4w4(=d6%t45 z*=q2I=Mr$(h2(TM!Vo|C9$mjRnDhu=f~CGX|9i2wzbVIeg!(Js!5N>2uMaaFsNktFE~n* zhk5g=2azA#j7-8XQdv_cYSWAn|5`PR79h-h1;dLxhKb!*wznCD8WdusR-E;{0@G5E zn1V=@4TV_OYSd7XBL^8}?9{TsL%2Jy`|prC$pGKy_$fk#x(3h37_XYCbY7ExL{4E! zLeD+>=q_N|oD{AFQN4oplN8sE>7#7mUH~_JL65OP20IT^a0fP{y9}j%1S~VZ@xa+JtAV_W=E3rAYI>m#K+bU(03Zg znb!>y-uwL-zKLes?)d7JGSz}sH^a@_s^}+y}I@V;$ElMku_}(hz?K zyEpGh1nHMq_OZCwF?TIYui~TP&(tmi@h6IY`^eiuI;nL(r<~Mqx$(>iR}p)MVn+#Q>TIInDG5aKvQvL4Yl)< zkUL9gwPoCbvM7BuUnxH^pz!Y9Oa)4gcn?d0EVTC@c!0s7w=L^g8%);mGqxb)t;4Vj zTgKgs`qEaBy9ustsRqYf>%79eypHGHq}yBZ=LN_Z5kv)!0)_gj^Oi>}(hyb32yZ zW^?v=)?{2CZgy72`Ft`l;D)86@Ai;#?ZNrR@*N+T!rL1wvuSIYEYT{Y#9d&pj!&y` z(T2Nep7v2UK%yPhH!4FskP|JxWWMJ5RNZQYbAfVF{Cht8?sm4G76=|d0J>Wl)siMWc6|a<;@vdaEL4RuNroo?Y)rvP{_9OX?z1BwK6_5 zB2sT8_oyg3w2<(jQcEfWh0nr|RB6ak+lKw5R8fa9ni7afT}1U|s@ZT3y~}ckK55Qi z%wf&2`U@a%^(-#2fBL+#HP2Aj#&V|T3qdisFmD_`HTc67F zbj%h*h0zWHJOkfU?j**aGszn~6-IYCh)I859AiRa@G{SU29570!}z}|p3KKJIt70d z;;)PNaTu}kmB8tOO;C{+%%JJJEmvOb;R2St()D2{INv6hq2GarZ(^TU1)rbyC!UXk zs8!?(jOO}D9yha}SUDs@+18u?7~+iLz&AMn4W zk~DvI!tNnocKayTg=8F(fwDInI|h(PSUQ`mXmed+U0r>`2DScaoy48nl76`B3=vtg z<+yhbDY0d4$^-8=_UJf2Pv@hb?(O3I7gP<#1Wad>%QPUcJ{VA7J%{L^0VfQRd%PDf zXcipPMZIIY=<$7+>bXY6+PgZ-?voH6zh3$RyYT?%3d9fd&gdpRM{!)2J3UYVZ1GX7 zw{Y{49?}ebcz5R>^UMW=&$|c-c36{@vQVt(;Zz8Y^fH)VQIuGU#uk}>C0a(GSYOjp zON9@ZM{$TEKOl)y?u>@6XqkqeSckN7R%2$We~nF% za?BnI+OWBn25W@>Ox_NU?8Ao1fKmQ5)IG%$9fuo!>QVy~`NxN_$v(Z~eP>rO^@m|J z>X)UrNLIm~c27xyt#kMlIs)&C&(7(#(~9&gAC=6tH@)cj4Ufk+KQfQH_SEx%_9HY$ zqgH^#1}VT71ET-^Rm#U2q9|57v&&!M{3NJ`8e%$M5MYA|N zeHwFqe=3Bu81Sqx1^{CZm=?5Mw#~H(G78L?2YFe!MwBy8%`SdYP9tNIU5lO8 zDXWhRt0s)}mB}MJC@B>y+Qx)>pz6iP*!jcttkr-oTz;!z%*MlGB37hiJ(c$NKy4Lm znx_)+Z=d&;`sj||RYrHe`wKrHM-OSQ$53e za#tT3Fi+*6BUP-^aRer3EQTrWm0mWzyrI>QQR>R;qeQO-Z>a8dxTfwdzf!3v`Xs5( zI^Ey1g4q>Js^8gvxw2usI+Y>W>px9@uig0FccZk@+Y}=gm-@y`ML3*vGLOuvH!i|g zHF=0vXp0+!>a$q6;x@c-6PQuH37i`0 zbXw$0c3Sg=W^8w_{o3cfwsRa&d4FGWCX3qE>v~(9Ngawe3rlv&=(X{ zHE0`KvRN^FbB*RBwj-}p$Sd^Sj~{gV0zKBNGSEV>5ccP%8*LeBZ9ttG9X?vpJWQKw z-NU42VY=5|=F4pdEj7)|jHD&NnXkZm>^8*>Ay_E^U63FInzUYP{`_{wg`W?>e>FA- z75p={)KxX~`bW*woI8pB(M)axYfZgt*siOhi;^9jAoOWw*BScR5(S-Nt5EKbk%E4_c|&}zI^qw8-fbNfH9v^}=4 z%i2y1JhNra!e5(h8#FqSM}OJLZKY z9kcA9wfqi$Q5?s~uu(jT<7GV3*m@EW&F(C{;=WD7&XOX9%B!}l~-pAa!QOiy4eIplm zBa81igl~>(5C;Z+q1|9QH^o=#EWnN-HhWc8_;~csDGsNIxmugkQy-x|SMU;>*q!Kh z%qWapSMYTC30?DhXlujyZ)qAX4?N$zzdBuQyI++E&NfoL<%^CLY5Afz=D&SgYnmMR zbFmw>jG1z%+_|0deIF;qUe=-55m}0@uF_S%VOq_2aFdD-7UPddHB#X&vrCVmnW4XJXs7ZB0C} zZ9TD_Ol&6;+t$hZRo#2ePw1-d-b;IJhxJNGJa>+;-aB;A*RX?=Z9&fqmr%uk*R5i3 zRpG9SZTC=Lij-Uv`zvZO?+~ba>ImdtuEm`B6RT6gts-n<5+CG)GD3vA=(@ai`G5bW zLs&pX8&}DUU7Nr0=Q#7BvuymVwSLe%LO_4k>-hz;?~S)m>0De!IO~^98&WvWecYad z6&M{CWx~Xk8Oow|liHs~4byVc4UAIB_F9^F+k94l55xxj-#287j7;y4(UcTNOJr1^ z#Otk)`s@@t&;k)?cJdksMOZgMsCmR@1A$@$4z}t3iaGa!fBTNb;5%x@!TG|Pdg_AH zgFa_CtNSrNBQ}=Bon=*%;=)}} z=>&YaFRr8?D=2$Sw2bliQr38nShAZSIQ~cuoQvh$vPRluI(&K9&r4JH=d@ij(SNV? z=JOXp7{ESm9wr>U-*yeJitEmj=#ipq+LRvfb5ZXFEZ}Vl2*gTR4um;&9A;QoNrr?o z2-5=EO#AskCN@Vry8gNT>H$jk9A;3sX&bG)YARk1AscvjtZ}?hG%>7{h_SBx{pXU9u>Z#ce=9|f>4pydwoXZCw-EW%xdCQQgA@;@$ zPTf*T(A*~`-pJ@R6y_pNgC%m2Ou;r;C z@VKVEb&I)v|J>ufpH1DLzBMbvHH^B%UzQVr?xpXgC*^v!N%QKLP>p7KUwHgZwzHva z*ER$zqt3&>?yp+6_~4Lp%s#c29|%YB{SA z-coztM{Z>qo#xf2$)f5c)x!XdEPmrCO?MU+(CsXL-YNE&pEfN;v*kp+S)NMD_eTR% zOXkT#mFs^A)@;R%mwo<>*46W)+Kae#&B`gLZzAe~7fZn^E<@B+0I2KTzsEUeUyR!K z5N$+$4I@X8)YS?La!Gk8a!ru2_N&k^N`?6EHXcP8Mj`mSJ;g{H^$8`aR#CN?CTP7V zlJpaHHJGF~C0!g+w5(H$Bb5K@m*+|ws>;c%s@wE6Q@#x4gsE zZ$fVXhitigeN0U zwMq1~;E*W)C@~3qp!dK~`8>YGmU}}NBbxA~X$}M;`{nI>{wEJKa=?q7Nyhn5OfH1j z4tuS2oA2+=Gk>a+-MZS&VoSJqJkmjfsY@;3Y{@g~^XIvUe}L@L3(i@V!SC~y&f-01 zxAU>4O6e2GYMRji_Q+l`)7?1)6P2$ky@iI)OWtZ@r>KkeGCKE^E?BYxqK#m8k}r;0 zoDq^jBr&}t-?Vb75t-0(?klv$4-xwYstWYv_^R@ik_Q|-+2~p3x1{L5W{0&c^cF1E zbl_$p(Wl(!V;iddT09du#i!`Ya+o6eF8ykF$8RMM9BQBnda92?Z-vXH7p(f0Yiq7% z%lsCPBW)_xWN%{L|02Uae%%tkw!rxjiJiopMG}S-vwi5N=~|h0{XPE)?~g8KNH#r{ zBWLkIDj9W)_P07x81N>5r0p36F@s5498;MifM45RtOjoG`g1%Eo9uR?9L4%aY8rg~ z$7zDXO3YyFj|NK$Y+$zlPQ)(E>-;1z_Go<7b4j}|I4siXU^DnpbkC93Cx5k)LC~@x zP_gwT`Ju$VWnS;MaJp7qeI$)`SJsIVjfcgJ4i&+0qVgRKe+d=NkY@p74z4U|F*(Ap zC8uzJ`vHv?v%*CbbOAbEjZY{xaK&He*uckVu=_K%cI6!m(J|CXkWQ(|c^l#frT_hg zigirwH{kMvrVf(HB1WPkw_LC479NIp`DsPL5>QZFH> zD57>gEcr7^oczEi|M#6SVel`0s^W6tdi!GiwISNR?h4%~WhjxO+e8LcWf|JU|9Z{#OJN0*S2wS7m1@&zP|k>89^GFr8Ta!Ssa#If zG=k=I;PZ&d(+O9!R4S6rC#Emm%nXMc{DT9Yy7)f;r@rmp^~2_ z!fsZ6sWMLGj68F>6T&e2{xsH!Gs1^xie!*nj5CKzSMM1k;y{l^yeh1we~&H`p4T=8 zDU4i1xFFIFZoW=htZMtY5-E8zu)ZvTf9CW!%1M;Z$V6>OQPO7+T?vj|odZRaS~ZZf zTwaTnb95!B8X+p=tK3+JHRs~?MmFV;)d;>C-7%akl7n0dd+6pDV7&>Ard|^j9LI09 z!e-)8v?Bi9uYjz+3r<{;-h=o>y0%fXMjNNrB6>0XxDs@)2CC!`B^!paBIZusy4$r7 zxEi)A;^xEtj^G=}XqDG^I?+cP+c=tKs`PETXOJaQ-6-5%IA(CM(~PO0O@`%;_$3`S`PLH!lHQn7+TO)8@mUW4q*gNS*T@w3PS$;! zaRZZf?`hg~&*4k$(9<6^T?}je*tD837;F`k{E}NL-&RqZ^5LiKAj2xnw`ELnzI1|~ zjziV>E=<+l&UHF)2QvFcQuDvEO~ZSxrvE-}6G1TDqwp-W?%-?tOwj<(pFS7%e``-5 zJ*RRE_4r@~QNM1ENCQs*0Q*?$ZmW^}d1*9m5nu04>oB4>kFQ9peoT7b4Xn6R&TT{O z9*@um-}_p6&+~e-THeL5=grr3dv{OIt?ds&Z+XHn(AZCKLE-@-}=?F z?}Q9WoX&)3F}{}r%?9I3)#US~dt@iS!Je^baWExt!M`l!wJ_HJodAL}TS4K%^WtcJ zVrcn)c+d~}7|-@gj9*zV01XUhD9!8nbJZgVJO>uwn_9f%%sfnD1iU5~96!Y_Z-{~N zGOQd#Tmr!mOGs&^l%7SrIe$e!w{74Z;n0;j(ncE#l8gOSO(7yy9GN&t@+=r+z7Q|X zVXv7xYpTX*+%%eo_f zd$$r?R$g%}aGYbXJO(NV`J=0b587uMSKB~xYp-R`2D>$S35 zF1=bl@dmK>t1C`in*sg|3XJjl!HmQ_#s?Zr*9_Wv++9_L4#}cybe8hU?TfybCbp{a zvsrdF+vJ(7?yQSS1VkMrFL@vTS^w*y#|_=Jylawe!0V@y-BHJG{9zd#C4T^)+Gc+m zO($G1h!@69ZWl7>>+sNziAL!wPAz<;oNJw*+ZT6I+dYiW8tuwheKUiS`aB>|AUNL8X*>ioOeTzJT|4p@Twko<8 zG9vUaReQQdHt7lpYdhQ7#cgRLZ=PRUaDxF)$tHdfDWJw&gc4hYT%tNcnc&G61i>ah zE$$+(gfuv9-J!rwbn}`R;k%wC4}=RoN{h)e&Vv!m4@;==2nG)mt6-GCtLTC94K#tZ zMMLfaYt}D8&|N;s95>uTy9XPdKKR=53k4I*o?+dC2mUwxUf0bZ*aB$7)JR z99X_n0C802{z=?-0w0yZ2_C4tN`m@g?r53=I2ExS{26Q%9>f64(Rt3-jv+jl{AVoa zNZhiTH_Hi=iCwiQRf|-MWUTa8=3JE7XD_4@DJk{9|R(s*$Mcn($NNT5-H2bHbA1vSdCPi%*~$3R-IciT znG^Gv71=pQb&j*_>O+jtD`78sB(>H7Dump5p~v|mlCU%v4#aojwdPwugerJH#@vI7F3Y;2Tb8<^R1JRIoBMpzLCKNr&R){l4W*pg-lE01s?G zzxRKaAiM9Z-V}uIZ0W|>^qgO+(lW*SJT+Q&&IX@a=)@r)sQ-A{-d28N1Zp1N7Z>@) zKzq>uYtZ#-4Dl=XiqrJ0sOqunu5FKa5%?a0iO`iA)<6k(Q@Qp?Hk!@`ielm&=8!ots zEcYpMq-RMu*wLcq{*Mu13HJ8f)a8EahHp1Eymvr_iy!g8VnuGFT?YNgFoYK9T?+Ym z_D&(eh!m!%S^ad*c!-7X0{YC_b`Dc&PJg`d{&Hs{EJo;z8fOrRv+qs>G|e zKHY?~KkWZMpAL)U`oi9SJr!lB3T))ZvI<+!sF?~H*L6THDmJqK)=69WL)-C*fR;HN z5+j9YG6WSenKB0#^0?NzVe!S(4cNtac-jt#ya^`s{M)Bz30j5?>&4)|K6=sdU0c+# zHHI#)75NMV!y%0#N(C;ye^DZ57rn-RQ~3kx$AUv@2mx!Ct>(oTmY;)UhZ+K1Fi34t z1kU$f+D_4{0~2+7fA|*co@Zh4#5mc}g0s=$Odnt%GU_Q*+YxV4lwrUSZ1>m~-fJfD z3Tl>T`#4L^8;GtustliOiWNqAb9=9k=5qxm+O4)77`uM5wWst&BksX_?s%J!67qXd z8*dlBV;>>a(66FTMhv^{HxG)3^q>$XtA>*w>P%9CNT$Y{t$XzQwddH_VL>Ue#4X|^ z(H$xp9YCYy5blbQg3=E2pDit4^c_SAH|DZOzBioDdS8Q*_s`707aUg&qegjos9hkk z@!B}q^z(2@09m<@*^M7y1`51^F~iH2RYKdl`jkhzzODTkq5{9K zn<=!LXQxo~B*w+d0Ncd4NK3(2IG3W7i+<_i1eF{KK-1_2!OPf4zm2mF&SPT1u<}49ul-H%bDV_N= zVEL2(1O=Y*#IHI6Wa`&2e`3^Pr`!S+y~O6~;g5vi>%{zOD;!1<>}DO`V0-n+;>G4_ ze8*sup$BT{Ix!+bk7QegoIHh2kq4E|bie9Dt8S>BN#3*JG}LfKT!SG_O-Dnt@Im4i z$|gRAZ0TxDQ^~T}5A3hid17Q@K~K0*LAt&-88~&OWh*zz^(-IKM)l{>ZSG2 zD1KWL$8S(Ohzw2q`-YwlWkcI^ltDB&*@RhfLov~|nI#WoDTLdl|SEN*TBlUQj0(B zO~3Pqh7%#^ug53^Z__aN4=XAhG;A82q5xoN!5dwuaffd zrM8|NaD=)ufSEdni8#v#DlntH@ansI(!bTtq>soD~g1O+|+ z9TAV*Yu~peWKM011urzX(o6#6VL zDrM&L;hMeKYj$K?x2io*1?fE=9@?rc=!t51<@kaPwW~>}qgVIZdJ!EZGGeq>;zLks zch92O%XbzG4&Pt^ztHxfFmH6u#T<6ps+i3%)AzksLCMRBiIQS?md>qB=$A00TIZSk zpA2*d94cioxw{tNBtjutk~Klj9?qX!_+d3{yOaP|knZxB?)<1rir^>r1`4_9OPR6; zzKij~IZwL2hi7jqW1qq`AdNgTcn%^ceSt6gjQS}ay5DR|C3gyiQ`+9ACCYqG zO-=UZL;#Y>dm#iUH^i@JAm{+V#o7sVWmUv+OP(a}Dc_!tN1hS=Ktf`ZvEI}eLbGbM zc@@F4z05f3*en+C2{(Wnt$2C_rI!U`fzGvV*S%fh5OYC19#>Ghr~sH^sr7)+Yo^N7 z^+=s0owJs?zc_uXdViT-?Pbhh>YrF}jbAzd@a0l{ZNAscdJo4G~>UtmY(o1b#nxdEPM@ib6xV>MBwW_o>a*L|RNx9*C&$t~`ysJkiI0hgh%4 z$hS1y^TvMUt!kdc(>}XNquT9Cg)%n9#`}THdpv31#6Ch~Mypm;C#D-s2e;)sAmuT& znER>i#*QO|1X&9=UCEW6D0DhTc!rZ@KRLy4N^vw+&EXuw4vM+5u;g*%AsC&>+QmOa zr!vR?;B9_1Ze0r3@$(F+Gkrfygfvd9L2 z=`SWQX2kg2iee!?R}01`M%>epXn6ovNK)Y*ugCWF9GIS#b=?Wj`fN#T&aNYTFRX>i z4ylU>sq36p6f* z)*StbINnPxC&H)Diej0xwT~yjCEIZT7^sYH=wZ16i4J8skq~f<)Q2V8C^|YI;1Euu zduD4w-!G29_gfI*}5c{#xANI(E<{qksm=d$Tc>!5|wo!qkOjXz)(XPQ$>b9wwX+Q5%$Y zwGX1P{HA>_HkWXs^|xA|P_Vg9QU9^Ft#IL4FZE8MpWC1oK^+Yb-S>pEsvg{v_I)c| z5N)Lbr#Mhti${NG*X+Eu_!^hHA2Cv`Y;HJ;L}&jr#b&-bF7@;3lqX^tl<_S}b$|+H zJz7B@Z(0dh=vl@|PR(>IZy+6NF|GcR@8Q`NDJt5mgd%ho_$E&9ps$HT@!;D|f>h5% z{FeIH2zVR@&;6-r$f2v*mhnaNy60m$8oe|$={n_67YmVR@h$UTmH#E(PN;{@?-YKI>c?ct$c-I`?Z zSFR%j>|@Zp{t9TDK^7fYs?rQEeJk2)#E*{qM>0I#*RKy}y-t;@V}ADrCyI9Z0J+~q zLvdXqr)~40QRk2*+aL2ggPM6&;ULHCvhF5Ar zy?eF2g=P=WqrH4@c>oQSZ-DSqrEE5JvtM!_{gd*z$P9YtJ(}F2()96*4>?jvM`t#r zH|q@Hf9XBw2lO4Hd5nc_P9EY78a;lQXst?UUG74q69VRCN`X5W6~%*u)P=;A z+_w!Fc@~i!O%eD!=}*e|RBXdT%suB7sa|_^QidUd7Zr5ReN&k0Uwd zdIEIqAq5n(UvutlD-e9WGg{bg|I+#s@|D|bTxaBb^QYFzx1gT=R@;=DAJcb;?>L?G zS7ZfKn@ArEg)2wq-;>S$D+3{p5i4k9@iNW$&+wOBdLk$mb=dU>R4+0a$!o>^T9XG5ZP%ssT_F-~K){4v9!@7tVm=|A`8NDC)8u8x}<0#74qd{{?+@EWIeQa|;b!FalETCO)_O++E zMU5iUkIp=#%;GF@+9N(&Jj<63G#$949l3=)<&N=24hwv&=&8HZ@-qs=f9qm9F9;wv zzy1~XO2?)BUdo=nZMGe|n}4p<$7s@^h@ZC$6)b{*<((ae2*bAIPD`Rx`fbs6(olIO znr=A3a4tE;12>Se1N5$$-~w$6X7V&kGycmurM0XpbLO^U2!%{1S+IE ztp+vc(TAXVskO^MKs(ydpvg)n$HN9Bq(zq&5O&n1sbCami??cK&3T9KhZudds8mxk z?A)^+$2)p1v0qC_^4F`<0;VdCr?^}sl0&#C-!=y84mUX#=DV9>DKLm>UdL2oYCsZV zh59RI@2Xx!fq&&tO-)KB;!HpP9Y3jK4S8)(S2mb&_t|6ov*8 z&0}7vX)Gv@m8AlVa)iv+F~c^-b0J?1^ML9~im2uhPNB|$1{qZDv!M$QE1iuINi5}T zVgH3o323SJork_}PmxZ5Ey@z$lQ+*{6pCK4k6@Jh4o<;4yD7WLmA?yb11EgELS|k z2u)b8U9g~4$8Ne=#i3uGViHx3g|i5nexxLBA9E|B^9%4k^n&N3^MybRGhI&veN5;* zc&;Ydr(-<+G&(1~=EHc~u}|1~@5kdKU8tRcQe+s3m>zW`^a=aFacPM-)I{Y37nd~L9mz>snF zPaRFcqP(4sP}5sFN5;8(v1bZ(qcjd_-7fIooXElhp(lq55~j~0)k${e@_wUFFmn=qasQxHq0k4NAgw9x9Q1GO z%2&lueHH25mEyc!B`1wDTqr`DMmO)dR7nBAHKMxJ^%g$VL{iq1y7fNan#$5Je@`WF z6=9%Me5tSP_VV97U$`I}RDm&goWY#T$L}9hB{MXz?iQ6ON9&=rYzOyU0@36I5tuNf z{Nc-!_^B~@?{r6wYwY0Yo}1fXV(%SDGe11Ec@D0PmgC9DN~%KeQ-}K0gbg9_YrN*r ztaYC}PBD-1y&m5SL9!Wy%#zLIAzY7oc@apj-goA#&m%&yKLuyPR)5;eXD_elxMU&W zJ12MD`d+bWw-O~md0p=W-*VK~=jauD;#&d@7jV~3&kVptf&G@Bht{^&4?U)CH^puP zDe7yQXX>jNJ#T*|xjhn!F&UafwjA>}-y#(>9O1{%$SB0xDT?@C^-Q!VEvQ1+LHK2x zK*JZoy(}q|@JHcXl3^yus^Jlb@E=;Yk?3{NQ1{28?}^$=+h}m~t``)pErmP&m7nBA ze~;T0TV%y1{9@{Y!TUJTS6AlesNT&V`mppsMW0g*B#3n*)e!ispefmHcfB09BG|uX z84&qxHUt);=#4EoX0fXnczn0)kMp^o3z$uW?zpWO18;G{r35U){Zu^<_RsuyUSl$U z<#}-q(eSg0AeZawUJf$P72zqIn*~N_%lAVj6wpM6lLPnXjHVF%Ptx@yoHsGoJ8xiI z99HljrW-^Mxd$(G-+4xa5FBz}(<_lY0Q6n6@!Z?iAq3(-K|a?vVDu;+455jNe(cIM z6ox5tw#(SUWKp|$`D%Lj?QG~bzev@EY|9W zJRYBL4gH5Yo`i{Y@fvlKpaG*t^nXfR#{~43&nr-Ia5l0WKeC%}`*a+whPTvEALk}X3hp-S z)4mRHElpzRJ(=GFuce5>Nqyq_fZ7-|BDPv%Ymza=7QnF1q~x4o?#Tbma=4Nu!W%PPCcbI=%QuxMK?lj z%qJ1EH%t^>NLD_6u-ZtJz{B9aVMsZ>9P;1tH2Zplps!gx-~P1C^xXkNPbGekD9Y1Q2|?PLyC&OL!@#EaX5{ z>@zT=A|iK`Zcm$y?l0d)^&VWafKifvHcmgv`aegW@7hRZgF1Jzr%Rg<%Bi{vt!pgn zsA#4`=48>#h#~%3Q*RW9sEi{=6m7g)wBD|TsK=SAS^nyT8s1aL2Wl}A=jjg zT8Y;vK3QD+C&x^10t&f}Qw3r-9NpKVK8`eRwM!6Ia{PBhYeg4%h7)yWMUfDim-JJ1 z+eSG-D5YuUK!TE;=6PZ)ZhGym5U`G+Z^V%+_Ud`R?RlVI@p-|Il;ah)8U4DrAR!-B z(-O~Hw)wJgy?``-MGG2votAhV+^V~q0I5lysWtuz1y=};XJWY=Q%A}7brCGKrCBSln2;;49Za5l@EBi72UJttP$CHYeJ zUW85f{YNYy^E6j|`t&XCx$ZF#pzG(|X70t%^*m9uLWSBU**?o#C=>~MTniLgD483n#+|EJQh*+8{+a_>@RgL0iW3l z!K>VU6on@F_4TOlOH|(y2uf^oC|q;IAnp!=Tip_Aesol)uE%`l`FFF>4>X1XgfCUF z0Z*Am$wQhAFAlbM?jo{Iz~uFa@BmSz0rrUtrdZfg1F3H>#}YYX&xRH)(d|u0%9k+I zNXn`HH=g{S52Yk|_PS01&#oUVLor05V7W3mGz;UG^L3krP-%*PuqcETXiZ6kg%;pqBvIQ8YTwNMN%8KsCL*}96aO|-fB#L* z@Bi$?+j)hGnM+)TJ&c}5Vb5jIt$X)uMGCANg80&V?uXsFLYhUsV7WmpKRa(?{o5Vq zSR)y~@ZcMoO3*E7uCY*->B5~`Q-7@1vh3p%!J}tkPQ-i;r(G`FQ?1{#)$RNS38d;6 zs8A98x03)T-@8&G&U^cQ@NzR&#n>C$IMQLE@I=*#Z@o|1VfYgFRpt_xEyqX(L17<&j zG3%3eUadP{$mqYK4h(Zi38P;XD@$r29XTJOU-ZvgyA9A|yuHT4SGxPZ?cE`{g3#&G zuavN7o+BoUDl}~0hHqigAs?PWsIZkvLxu&0Vl9$KRCyu8za5~P4k(!F+Ugk$8}nBxD?!202TYq9qETFb}*v90t$ zx24PmJYM6Hd6SZd!$zA)ep%5m{^ zq%znv0nc-rPf>1+@OV0_ox2j#Ub9-6!3k{f?rx;42cy(Ntot38GZ+C z>sDmnQL6f4b$z<3QdjbMGGIefS2&WnEo z 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 - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Root - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Root - - name: AcceleratorGuardrails3 - description: > - Prevent Accounts from leaving Org. Enforce S3 Block Public Access. - policy: service-control-policies/guardrails-3.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Root - - name: Accelerator-HIPAA - description: > - Enforce only HIPAA eligible services. - policy: service-control-policies/scp-hlc-hipaa-service.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] -taggingPolicies: - - name: TagPolicy - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root - - name: phi-tag-policy - description: Healthcare Organization Tagging Policy - policy: tagging-policies/healthcare-org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Tenant-HIS -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/backup-plan.json - deploymentTargets: - organizationalUnits: - - Root diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/READEME.md b/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/READEME.md deleted file mode 100644 index e593e54..0000000 --- a/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/READEME.md +++ /dev/null @@ -1,147 +0,0 @@ -# HIPAA based Service Contol Policies (SCPs) - -scp-hcl-hipaa-service.json file contains the Service Control Policies (SCPs) that can be deployed while building HIPAA eligible environments. -HIPAA Eligible Services are last updated on April 18, 2022. - -The reference guide can be accessed via the following link: https://aws.amazon.com/compliance/hipaa-eligible-services-reference/ - -Please also check the notes below for specific services: - -Alexa for Business (for healthcare skills only – requires Alexa Skills BAA. See https://d1.awsstatic.com/whitepapers/compliance/AWS_HIPAA_Compliance_Whitepaper.pdf for details)\ -AWS Amplify Console\ -Amazon API Gateway\ -AWS Application Migration Service\ -Amazon AppStream 2.0\ -Amazon AppFlow\ -AWS AppSync\ -AWS App Mesh\ -Amazon Athena\ -Amazon Augmented AI [excludes Public Workforce and Vendor Workforce for all features]\ -Amazon Aurora\ -AWS Backup\ -AWS Batch\ -AWS Certificate Manager\ -Amazon Chime\ -AWS Cloud 9\ -Amazon Cloud Directory\ -AWS Cloud Map\ -AWS CloudEndure [including CloudEndure Disaster Recovery and CloudEndure Migration]\ -AWS CloudFormation\ -Amazon CloudFront [including Lambda@Edge]\ -AWS CloudHSM\ -AWS CloudTrail\ -Amazon CloudWatch\ -Amazon CloudWatch Events [including Amazon EventBridge]\ -Amazon CloudWatch Logs\ -Amazon CloudWatch SDK Metrics\ -AWS CodeBuild\ -AWS CodeCommit\ -AWS CodeDeploy\ -AWS CodePipeline\ -Amazon Cognito\ -Amazon Comprehend\ -Amazon Comprehend Medical\ -AWS Config\ -Amazon Connect [excludes Wisdom, VoiceID and High-Volume Outbound Communications]\ -AWS Control Tower\ -AWS Data Exchange\ -AWS Database Migration Service (DMS)\ -AWS DataSync\ -Amazon Detective\ -AWS Direct Connect\ -AWS Directory Service [excludes Simple AD]\ -Amazon DocumentDB (with MongoDB compatibility)\ -Amazon DynamoDB\ -Amazon EC2 Auto Scaling\ -Amazon ElastiCache for Redis\ -AWS Elastic Beanstalk\ -Amazon Elastic Block Store (Amazon EBS)\ -Amazon Elastic Compute Cloud (Amazon EC2)\ -Amazon Elastic Container Registry (ECR)\ -Amazon Elastic Container Service (ECS) [both Fargate and EC2 launch types]\ -Amazon Elastic File System (EFS)\ -Amazon Elastic Kubernetes Service (EKS)\ -Elastic Load Balancing\ -Amazon Elastic MapReduce (EMR)\ -AWS Elemental MediaConnect\ -AWS Elemental MediaConvert\ -AWS Elemental MediaLive\ -AWS Firewall Manager\ -Amazon Forecast\ -Amazon FreeRTOS\ -Amazon FSx\ -AWS Global Accelerator\ -AWS Glue (including AWS Lake Formation)\ -AWS Glue DataBrew\ -Amazon GuardDuty\ -Amazon HealthLake\ -Amazon Inspector\ -AWS IoT Core\ -AWS IoT Device Management\ -AWS IoT Events\ -AWS IoT Greengrass\ -Amazon Kendra\ -AWS Key Management Service (KMS)\ -Amazon Keyspaces (For Apache Cassandra)\ -Amazon Kinesis Data Analytics\ -Amazon Kinesis Data Streams\ -Amazon Kinesis Data Firehose\ -Amazon Kinesis Video Streams\ -AWS Lambda\ -Amazon Lex\ -Amazon Location Service\ -Amazon Macie\ -AWS Managed Services [excluding Operations on Demand Services, except for the RFC Expedite feature]\ -Amazon Managed Streaming for Apache Kafka\ -Amazon MQ\ -Amazon Neptune\ -AWS Network Firewall\ -Amazon OpenSearch Service [successor to Amazon Elasticsearch service]\ -AWS OpsWorks for Chef Automate\ -AWS OpsWorks for Puppet Enterprise\ -AWS OpsWorks Stacks\ -AWS Organizations\ -AWS Outposts\ -Amazon Personalize\ -Amazon Pinpoint [excluding Voice Message capabilities]\ -Amazon Polly\ -Amazon Quantum Ledger Database (QLDB)\ -Amazon QuickSight\ -Amazon Rekognition\ -Amazon Redshift\ -Amazon Relational Database Service (Amazon RDS) [SQL Server, MySQL, Oracle, PostgreSQL, and MariaDB engines only]\ -AWS RoboMaker\ -Amazon Route 53\ -Amazon S3 Glacier\ -Amazon SageMaker [excludes Studio Lab, Ground Truth Plus, Public Workforce and Vendor Workforce for all features]\ -AWS Secrets Manager\ -AWS Security Hub\ -AWS Service Catalog\ -AWS Serverless Application Repository\ -AWS Server Migration Service (SMS)\ -AWS Shield [Standard and Advanced]\ -Amazon Simple Email Service (Amazon SES)\ -Amazon Simple Notification Service (SNS)\ -Amazon Simple Queue Service (SQS)\ -Amazon Simple Storage Service (Amazon S3) [including S3 Transfer Acceleration]\ -Amazon Simple Workflow Service (SWF)\ -AWS Single Sign-On\ -AWS Snowball\ -AWS Snowball Edge\ -AWS Snowmobile\ -AWS Step Functions\ -AWS Storage Gateway\ -AWS Systems Manager\ -Amazon Textract\ -Amazon Timestream\ -Amazon Transcribe\ -AWS Transfer for SFTP\ -Amazon Translate\ -Amazon Virtual Private Cloud (VPC)\ -AWS Web Application Firewall (WAF)\ -Amazon WorkDocs\ -Amazon WorkLink\ -Amazon WorkSpaces\ -AWS X-Ray\ -VM Import/Export\ -**NOTE:** If you are a Covered Entity or Business Associate as defined by the Health Insurance Portability and Accountability Act of 1996 (as amended, “HIPAA”), you agree not to use these HIPAA Eligible Services for any purpose or in any manner involving Protected Health Information (as defined by HIPAA) without first entering into an AWS business associate agreement. diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/guardrails-3.json b/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/guardrails-3.json deleted file mode 100644 index 374e8a1..0000000 --- a/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/guardrails-3.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Version":"2012-10-17", - "Statement":[ - { - "Effect":"Deny", - "Action":[ - "organizations:LeaveOrganization", - "organizations:DeleteOrganization" - ], - "Resource":[ - "*" - ], - "Condition":{ - "ArnNotLike":{ - "aws:PrincipalARN":[ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution" - ] - } - } - }, - { - "Effect":"Deny", - "Action":[ - "iam:CreateAccessKey" - ], - "Resource":[ - "arn:aws:iam::*:root" - ] - }, - { - "Effect":"Deny", - "Action":[ - "s3:PutAccountPublicAccessBlock" - ], - "Resource":[ - "*" - ], - "Condition":{ - "ArnNotLike":{ - "aws:PrincipalARN":[ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/scp-hlc-hipaa-service.json b/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/scp-hlc-hipaa-service.json deleted file mode 100644 index f5e9b3e..0000000 --- a/reference/sample-configurations/lza-sample-config-us-slg-central-it/service-control-policies/scp-hlc-hipaa-service.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "HIPAAEligibleServices", - "Effect": "Deny", - "NotAction": [ - "acm:*", - "a4b:*", - "access-analyzer:*", - "amplify:*", - "apigateway:*", - "appflow:*", - "application-autoscaling:*", - "appmesh:*", - "appstream:*", - "appsync:*", - "artifact:*", - "athena:*", - "autoscaling:*", - "automation:*", - "aws-portal:*", - "backup:*", - "backup-storage:*", - "batch:*", - "budgets:*", - "cassandra:*", - "ce:*", - "chime:*", - "cloud9:*", - "clouddirectory:*", - "cloudformation:*", - "cloudfront:*", - "cloudhsm:*", - "cloudtrail:*", - "cloudwatch:*", - "codebuild:*", - "codecommit:*", - "codedeploy:*", - "codepipeline:*", - "cognito-identity:*", - "cognito-idp:*", - "cognito-sync:*", - "comprehend:*", - "comprehendmedical:*", - "config:*", - "connect:*", - "controltower:*", - "cur:*", - "databrew:*", - "dataexchange:*", - "datasync:*", - "detective:*", - "directconnect:*", - "dms:*", - "ds:*", - "dynamodb:*", - "ebs:*", - "ec2:*", - "ec2messages:*", - "ecr:*", - "ecs:*", - "eks:*", - "elasticache:*", - "elasticbeanstalk:*", - "elasticfilesystem:*", - "elasticloadbalancing:*", - "elasticmapreduce:*", - "es:*", - "events:*", - "execute-api:*", - "firehose:*", - "fms:*", - "forecast:*", - "freertos:*", - "fsx:*", - "geo:*", - "glacier:*", - "globalaccelerator:*", - "glue:*", - "greengrass:*", - "guardduty:*", - "healthlake:*", - "iam:*", - "importexport:*", - "inspector2:*", - "inspector:*", - "iot:*", - "iotfleethub:*", - "iotevents:*", - "kafka:*", - "kendra:*", - "kinesis:*", - "kinesisanalytics:*", - "kinesisvideo:*", - "kms:*", - "lambda:*", - "lex:*", - "license-manager:*", - "logs:*", - "macie2:*", - "mediaconnect:*", - "mediaconvert:*", - "medialive:*", - "mgh:*", - "mgn:*", - "mobiletargeting:*", - "mq:*", - "neptune-db:*", - "network-firewall:*", - "opsworks-cm:*", - "opsworks:*", - "organizations:*", - "outposts:*", - "personalize:*", - "polly:*", - "qldb:*", - "quicksight:*", - "ram:*", - "rds:*", - "rds-data:*", - "rds-db:*", - "redshift:*", - "redshift-data:*", - "rekognition:*", - "resource-groups:*", - "robomaker:*", - "route53:*", - "route53domains:*", - "route53resolver:*", - "s3:*", - "sagemaker:*", - "secretsmanager:*", - "securityhub:*", - "serverlessrepo:*", - "servicecatalog:*", - "servicediscovery:*", - "ses:*", - "shield:*", - "sms:*", - "snowball:*", - "sns:*", - "sqs:*", - "ssm:*", - "ssmmessages:*", - "sso:*", - "sso-directory:*", - "states:*", - "storagegateway:*", - "sts:*", - "support:*", - "swf:*", - "textract:*", - "timestream:*", - "transcribe:*", - "transfer:*", - "translate:*", - "waf-regional:*", - "waf:*", - "wafv2:*", - "workdocs:*", - "worklink:*", - "workspaces:*", - "xray:*" - ], - "Resource": "*" - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config-us-slg-central-it/tagging-policies/healthcare-org-tag-policy.json b/reference/sample-configurations/lza-sample-config-us-slg-central-it/tagging-policies/healthcare-org-tag-policy.json deleted file mode 100644 index 4bbeda4..0000000 --- a/reference/sample-configurations/lza-sample-config-us-slg-central-it/tagging-policies/healthcare-org-tag-policy.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "tags": { - "CostCenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - }, - "EnvironmentType": { - "tag_key": { - "@@assign": "EnvironmentType" - }, - "tag_value": { - "@@assign": [ - "Prod", - "QA", - "Dev" - ] - } - }, - "DataClassification": { - "tag_key": { - "@@assign": "DataClassification" - }, - "tag_value": { - "@@assign": [ - "NonConfidential", - "CompanyConfidential", - "PII", - "PHI" - ] - }, - "enforced_for": { - "@@assign": [ - "dms:*", - "dynamodb:table", - "ec2:instance", - "elasticfilesystem:*", - "eks:cluster", - "ecs:cluster", - "elasticmapreduce:cluster", - "fsx:*", - "rds:cluster-endpoint", - "redshift:*", - "s3:bucket" - ] - } - }, - "AppID": {} - } -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/README.md b/reference/sample-configurations/lza-sample-config/README.md deleted file mode 100644 index d374c74..0000000 --- a/reference/sample-configurations/lza-sample-config/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Standard Configuration - -The documentation for the standard sample configuration has been moved to the [Standard Configuration](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/sample-configurations/standard) section of our [GitHub Pages website](https://awslabs.github.io/landing-zone-accelerator-on-aws). \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/accounts-config.yaml b/reference/sample-configurations/lza-sample-config/accounts-config.yaml deleted file mode 100644 index 538f985..0000000 --- a/reference/sample-configurations/lza-sample-config/accounts-config.yaml +++ /dev/null @@ -1,28 +0,0 @@ -mandatoryAccounts: - # We recommend you do not change mandatory account names. These are used within Landing Zone Accelerator to reference the accounts from other config files. - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Security -workloadAccounts: - # The "name" will be used to set the AWS Account name - # The "name" value does not currently support spaces - # The "name" value DOES NOT need to match the account name - - name: SharedServices - description: The SharedServices account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: @example.com <----- UPDATE EMAIL ADDRESS - organizationalUnit: Infrastructure diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-connector-permissions-setup.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-connector-permissions-setup.ps1 deleted file mode 100644 index ee25a83..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-connector-permissions-setup.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -Start-Process powershell.exe -Credential $credential -ArgumentList "-file c:\cfn\scripts\AD-group-grant-permissions-setup.ps1", "$GroupName" \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-grant-permissions-setup.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-grant-permissions-setup.ps1 deleted file mode 100644 index efab9d6..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-grant-permissions-setup.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -#Delegate Control -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;computer" -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;user" \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-setup.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-setup.ps1 deleted file mode 100644 index f27268f..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-group-setup.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - $groupName = $groupsArray[$i] - $groupExists = Get-ADGroup -Filter {Name -eq $groupName} -Credential $credential - if($null -eq $groupExists) { - #Create Group - New-ADGroup -Name $groupName -GroupCategory Security -GroupScope Global -Credential $credential - } -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-group-setup.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-group-setup.ps1 deleted file mode 100644 index 848d3c0..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-group-setup.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $UserName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - #Add User to Group - Add-ADGroupMember -Identity $groupsArray[$i] -Members $UserName -Credential $credential -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-setup.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-setup.ps1 deleted file mode 100644 index dd154bf..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AD-user-setup.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -[CmdletBinding()] -param( - [string] - $UserName, - - [string] - $Password, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [string] - $PasswordNeverExpires, - - [Parameter(Mandatory=$false)] - [AllowEmptyString()] - [string]$UserEmailAddress = '' -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$pass = ConvertTo-SecureString $Password -AsPlainText -Force -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword -$userExists = Get-ADUser -Credential $credential -Filter "Name -eq '$UserName'" - -If ($null -eq $userExists -and $UserEmailAddress) { - #Create User - New-ADUser -Name $UserName -EmailAddress $UserEmailAddress -AccountPassword $pass -Enabled 1 -Credential $credential -SamAccountName $UserName -} - -#Set the admin & connector user's password never expires flag -If (-NOT ($PasswordNeverExpires -eq 'No')) { - Set-ADUser -Identity $UserName -PasswordNeverExpires $true -Credential $credential -} Else { - Set-ADUser -Identity $UserName -PasswordNeverExpires $false -Credential $credential -} diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AWSQuickStart.psm1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/AWSQuickStart.psm1 deleted file mode 100644 index fc1b30f..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/AWSQuickStart.psm1 +++ /dev/null @@ -1,345 +0,0 @@ -function New-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [string] - $Handle, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\', - - [Parameter(Mandatory=$false)] - [switch] - $Base64Handle - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - if ($Base64Handle) { - Write-Verbose "Trying to decode handle Base64 string as UTF8 string" - $decodedHandle = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Handle)) - if ($decodedHandle -notlike "http*") { - Write-Verbose "Now trying to decode handle Base64 string as Unicode string" - $decodedHandle = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($Handle)) - } - Write-Verbose "Decoded handle string: $decodedHandle" - $Handle = $decodedHandle - } - - Write-Verbose "Creating Handle Registry Key" - New-ItemProperty -Path $Path -Name Handle -Value $Handle -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function New-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [string] - $Stack, - - [Parameter(Mandatory=$true)] - [string] - $Resource, - - [Parameter(Mandatory=$true)] - [string] - $Region, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - Write-Verbose "Creating Stack Registry Key" - New-ItemProperty -Path $Path -Name Stack -Value $Stack -Force - - Write-Verbose "Creating Resource Registry Key" - New-ItemProperty -Path $Path -Name Resource -Value $Resource -Force - - Write-Verbose "Creating Region Registry Key" - New-ItemProperty -Path $Path -Name Region -Value $Region -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - - -function Get-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - Write-Verbose "Getting ErrorCount Registry Key" - Get-ItemProperty -Path $Path -Name ErrorCount -ErrorAction Stop | Select-Object -ExpandProperty ErrorCount - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Set-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [int32] - $Count, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $currentCount = Get-AWSQuickStartErrorCount - $currentCount += $Count - - Write-Verbose "Creating ErrorCount Registry Key" - Set-ItemProperty -Path $Path -Name ErrorCount -Value $currentCount -ErrorAction Stop - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty $Path - - return $key.Handle - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty $Path - $resourceSignal = @{ - Stack = $key.Stack - Resource = $key.Resource - Region = $key.Region - } - $toReturn = New-Object -TypeName PSObject -Property $resourceSignal - - if ($toReturn.Stack -and $toReturn.Resource -and $toReturn.Region) { - return $toReturn - } else { - return $null - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Remove-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty -Path $Path -Name Handle -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing Handle key value from $Path" - Remove-ItemProperty -Path $Path -Name Handle - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Remove-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - foreach ($keyName in @('Stack','Resource','Region')) { - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty -Path $Path -Name $keyName -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing $keyName key value from $Path" - Remove-ItemProperty -Path $Path -Name $keyName - } - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Write-AWSQuickStartEvent { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipelineByPropertyName=$true)] - [string] - $Message, - - [Parameter(Mandatory=$false)] - [string] - $EntryType = 'Error' - ) - - process { - Write-Verbose "Checking for AWSQuickStart Eventlog Source" - if(![System.Diagnostics.EventLog]::SourceExists('AWSQuickStart')) { - New-EventLog -LogName Application -Source AWSQuickStart -ErrorAction SilentlyContinue - } - else { - Write-Verbose "AWSQuickStart Eventlog Source exists" - } - - Write-Verbose "Writing message to application log" - - try { - Write-EventLog -LogName Application -Source AWSQuickStart -EntryType $EntryType -EventId 1001 -Message $Message - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Write-AWSQuickStartException { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - process { - try { - Write-Verbose "Incrementing error count" - Set-AWSQuickStartErrorCount -Count 1 - - Write-Verbose "Getting total error count" - $errorTotal = Get-AWSQuickStartErrorCount - - $errorMessage = "Command failure in {0} {1} on line {2} `nException: {3}" -f $ErrorRecord.InvocationInfo.MyCommand.name, - $ErrorRecord.InvocationInfo.ScriptName, $ErrorRecord.InvocationInfo.ScriptLineNumber, $ErrorRecord.Exception.ToString() - - $CmdSafeErrorMessage = $errorMessage -replace '[^a-zA-Z0-9\s\.\[\]\-,:_\\\/\(\)]', '' - if ($CmdSafeErrorMessage.length -gt 255) { - $CmdSafeErrorMessage = $CmdSafeErrorMessage.substring(0,252) + '...' - } - - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 1 --reason='$CmdSafeErrorMessage' '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 1 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - finally { - Write-AWSQuickStartEvent -Message $errorMessage - # throwing an exception to force cfn-init execution to stop - throw $CmdSafeErrorMessage - } - } -} - -function Write-AWSQuickStartStatus { - [CmdletBinding()] - Param() - - process { - try { - Write-Verbose "Checking error count" - if((Get-AWSQuickStartErrorCount) -eq 0) { - Write-Verbose "Getting Handle" - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 0 '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 0 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/Configure-password-policy.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/Configure-password-policy.ps1 deleted file mode 100644 index fde4721..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/Configure-password-policy.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [Boolean] - $ComplexityEnabled, - - [string] - $LockoutDuration, - - [string] - $LockoutObservationWindow, - - [string] - $LockoutThreshold, - - [string] - $MaxPasswordAge, - - [string] - $MinPasswordAge, - - [string] - $MinPasswordLength, - - [string] - $PasswordHistoryCount, - - [Boolean] - $ReversibleEncryptionEnabled -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -#Configure passsord policy for all users -Set-ADFineGrainedPasswordPolicy -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -ComplexityEnabled:$ComplexityEnabled -MaxPasswordAge:$MaxPasswordAge -LockoutDuration:$LockoutDuration -LockoutObservationWindow:$LockoutObservationWindow -LockoutThreshold:$LockoutThreshold -MinPasswordAge:$MinPasswordAge -MinPasswordLength:$MinPasswordLength -PasswordHistoryCount:$PasswordHistoryCount -ReversibleEncryptionEnabled:$ReversibleEncryptionEnabled -Server:$fdn -Credential $credential - -#Create password policy subject -Add-ADFineGrainedPasswordPolicySubject -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -Server:$fdn -Subjects:"CN=Domain Users,CN=Users,DC=$dom,DC=$ext" -Credential $credential diff --git a/reference/sample-configurations/lza-sample-config/ad-config-scripts/Join-Domain.ps1 b/reference/sample-configurations/lza-sample-config/ad-config-scripts/Join-Domain.ps1 deleted file mode 100644 index acdf6d8..0000000 --- a/reference/sample-configurations/lza-sample-config/ad-config-scripts/Join-Domain.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainName, - - [string] - $UserName, - - [string] - $Password -) - -try { - $ErrorActionPreference = "Stop" - - $pass = ConvertTo-SecureString $Password -AsPlainText -Force - $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$pass - - Add-Computer -DomainName $DomainName -Credential $cred -ErrorAction Stop - - # Execute restart after script exit and allow time for external services - $shutdown = Start-Process -FilePath "shutdown.exe" -ArgumentList @("/r", "/t 10") -Wait -NoNewWindow -PassThru - if ($shutdown.ExitCode -ne 0) { - throw "[ERROR] shutdown.exe exit code was not 0. It was actually $($shutdown.ExitCode)." - } -} -catch { - $_ | Write-AWSQuickStartException -} diff --git a/reference/sample-configurations/lza-sample-config/backup-policies/backup-plan.json b/reference/sample-configurations/lza-sample-config/backup-policies/backup-plan.json deleted file mode 100644 index 6182bfd..0000000 --- a/reference/sample-configurations/lza-sample-config/backup-policies/backup-plan.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "plans": { - "Hourly_Plan": { - "regions": { - "@@append": ["us-east-1"] - }, - "rules": { - "Backup_Rule": { - "schedule_expression": { - "@@assign": "cron(0 5/1 ? * * *)" - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1095" - } - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - } - } - }, - "selections": { - "tags": { - "Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "BackupPlan" - }, - "tag_value": { - "@@assign": ["Hourly"] - } - } - } - } - }, - "Daily_Plan": { - "regions": { - "@@append": ["us-east-1"] - }, - "rules": { - "Backup_Rule": { - "schedule_expression": { - "@@assign": "cron(0 5 ? * * *)" - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1095" - } - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - } - } - }, - "selections": { - "tags": { - "Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "BackupPlan" - }, - "tag_value": { - "@@assign": ["Daily"] - } - } - } - } - }, - "Weekly_Plan": { - "regions": { - "@@append": ["us-east-1"] - }, - "rules": { - "Backup_Rule": { - "schedule_expression": { - "@@assign": "cron(0 5 ? * 1 *)" - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1095" - } - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - } - } - }, - "selections": { - "tags": { - "Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "BackupPlan" - }, - "tag_value": { - "@@assign": ["Weekly"] - } - } - } - } - }, - "Monthly_Plan": { - "regions": { - "@@append": ["us-east-1"] - }, - "rules": { - "Backup_Rule": { - "schedule_expression": { - "@@assign": "cron(0 5 1 * ? *)" - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1095" - } - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - } - } - }, - "selections": { - "tags": { - "Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "BackupPlan" - }, - "tag_value": { - "@@assign": ["Monthly"] - } - } - } - } - } - } -} diff --git a/reference/sample-configurations/lza-sample-config/bucket-policies/central-log-bucket.json b/reference/sample-configurations/lza-sample-config/bucket-policies/central-log-bucket.json deleted file mode 100644 index b4e1c27..0000000 --- a/reference/sample-configurations/lza-sample-config/bucket-policies/central-log-bucket.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Allow Organization principals to put session manager logs", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:PutObject", - "Resource": "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "ArnLike": { - "aws:PrincipalARN": "arn:${PARTITION}:iam::*:role/EC2-Default-SSM-AD-Role" - } - } - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-1.txt b/reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-1.txt deleted file mode 100644 index 57296a9..0000000 --- a/reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-1.txt +++ /dev/null @@ -1 +0,0 @@ -badactor.com \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-2.txt b/reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-2.txt deleted file mode 100644 index 19c3151..0000000 --- a/reference/sample-configurations/lza-sample-config/dns-firewall-domain-lists/domain-list-2.txt +++ /dev/null @@ -1 +0,0 @@ -virus.net \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/dynamic-partitioning/log-filters.json b/reference/sample-configurations/lza-sample-config/dynamic-partitioning/log-filters.json deleted file mode 100644 index d8c241a..0000000 --- a/reference/sample-configurations/lza-sample-config/dynamic-partitioning/log-filters.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "/AWSAccelerator-SecurityHub", "s3Prefix": "security-hub" } -] diff --git a/reference/sample-configurations/lza-sample-config/firewall-rules/rules.txt b/reference/sample-configurations/lza-sample-config/firewall-rules/rules.txt deleted file mode 100644 index f1788ff..0000000 --- a/reference/sample-configurations/lza-sample-config/firewall-rules/rules.txt +++ /dev/null @@ -1,16 +0,0 @@ -##################################################################################################################### -# List of Suricata compatible Intrusion prevention system (IPS) rules. # -# Suricata is an open source network IPS that includes a standard rule-based language for traffic inspection. # -# Please refer for https://suricata.readthedocs.io/en/suricata-6.0.2/rules/intro.html Suricata rule syntax. # -# Invalid rule syntax will cause LZA pipeline failure, please review rule syntax of each line before using. # -# This file can have one rule definition per line. -# A line starts with suricata supported action types (alert, pass, drop, reject, rejectsrc, rejectdst, rejectboth) # -# are considered to be rule definition line. # -##################################################################################################################### - - -pass ip 10.1.0.0/16 any -> 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; priority:1; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; priority:1; flow:to_server, established; sid:104; rev:1;) \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/global-config.yaml b/reference/sample-configurations/lza-sample-config/global-config.yaml deleted file mode 100644 index b5e2268..0000000 --- a/reference/sample-configurations/lza-sample-config/global-config.yaml +++ /dev/null @@ -1,183 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -enabledRegions: - - *HOME_REGION -managementAccountAccessRole: AWSControlTowerExecution -cloudwatchLogRetentionInDays: 3653 -terminationProtection: true -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - @example.com <----- UPDATE EMAIL ADDRESS -controlTower: - enable: true - landingZone: - version: '3.3' - logging: - loggingBucketRetentionDays: 365 - accessLoggingBucketRetentionDays: 3650 - organizationTrail: true - security: - enableIdentityCenterAccess: true -logging: - account: LogArchive - cloudtrail: - enable: false - organizationTrail: false - organizationTrailSettings: - multiRegionTrail: true - globalServiceEvents: true - managementEvents: true - s3DataEvents: true - lambdaDataEvents: true - sendToCloudWatchLogs: true - apiErrorRateInsight: false - apiCallRateInsight: false - accountTrails: - - name: AccountTrail - regions: - - *HOME_REGION - deploymentTargets: - accounts: [] - organizationalUnits: - - Root - settings: - multiRegionTrail: true - globalServiceEvents: true - managementEvents: true - s3DataEvents: true - lambdaDataEvents: true - sendToCloudWatchLogs: true - apiErrorRateInsight: false - apiCallRateInsight: false - sessionManager: - sendToCloudWatchLogs: false - sendToS3: true - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 730 - noncurrentVersionExpiration: 730 - attachPolicyToIamRoles: - - EC2-Default-SSM-AD-Role - cloudwatchLogs: - dynamicPartitioning: dynamic-partitioning/log-filters.json - dataProtection: - managedDataIdentifiers: - categories: - - Credentials - accessLogBucket: - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 1000 - noncurrentVersionExpiration: 1000 - transitions: - - storageClass: GLACIER_IR - transitionAfter: 365 - noncurrentVersionTransitions: - - storageClass: GLACIER_IR - transitionAfter: 365 - centralLogBucket: - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 1000 - noncurrentVersionExpiration: 1000 - transitions: - - storageClass: GLACIER_IR - transitionAfter: 365 - noncurrentVersionTransitions: - - storageClass: GLACIER_IR - transitionAfter: 365 - s3ResourcePolicyAttachments: - - policy: bucket-policies/central-log-bucket.json - elbLogBucket: - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 1000 - noncurrentVersionExpiration: 1000 - transitions: - - storageClass: GLACIER_IR - transitionAfter: 365 - noncurrentVersionTransitions: - - storageClass: GLACIER_IR - transitionAfter: 365 -reports: - costAndUsageReport: - compression: Parquet - format: Parquet - reportName: accelerator-cur - s3Prefix: cur - timeUnit: DAILY - refreshClosedReports: true - reportVersioning: CREATE_NEW_REPORT - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 730 - noncurrentVersionExpiration: 730 - budgets: - - deploymentTargets: - accounts: - - Management - name: accel-budget - timeUnit: MONTHLY - type: COST - amount: 2000 - includeUpfront: true - includeTax: true - includeSupport: true - includeSubscription: true - includeRecurring: true - includeOtherSubscription: true - includeDiscount: true - includeCredit: false - includeRefund: false - useBlended: false - useAmortized: false - unit: USD - notifications: - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 100 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: @example.com <----- UPDATE EMAIL ADDRESS - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 90 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: @example.com <----- UPDATE EMAIL ADDRESS - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 80 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: @example.com <----- UPDATE EMAIL ADDRESS - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 75 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: @example.com <----- UPDATE EMAIL ADDRESS - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 50 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: @example.com <----- UPDATE EMAIL ADDRESS -backup: - vaults: - - name: BackupVault - deploymentTargets: - organizationalUnits: - - Root diff --git a/reference/sample-configurations/lza-sample-config/iam-config.yaml b/reference/sample-configurations/lza-sample-config/iam-config.yaml deleted file mode 100644 index 57d14f3..0000000 --- a/reference/sample-configurations/lza-sample-config/iam-config.yaml +++ /dev/null @@ -1,39 +0,0 @@ -policySets: [] -roleSets: - - deploymentTargets: - organizationalUnits: - - Root - roles: - - name: EC2-Default-SSM-AD-Role - instanceProfile: true - assumedBy: - - type: service - principal: ec2.amazonaws.com - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonSSMDirectoryServiceAccess - - CloudWatchAgentServerPolicy - # This role is utilized by the Backup Plans defined in global-config.yaml - # We create this role in every account where we plan to have Backup Plans - # and Backup Vaults - - name: Backup-Role - assumedBy: - - type: service - principal: backup.amazonaws.com - policies: - awsManaged: - - service-role/AWSBackupServiceRolePolicyForBackup - - service-role/AWSBackupServiceRolePolicyForRestores - # This role is utilized by the Budgets Config defined in global-config.yaml - # We create this role in every account where we plan to have AWS Budgets - # A service linked role is not available for AWS Budgets - - name: Budgets-Role - assumedBy: - - type: service - principal: budgets.amazonaws.com - policies: - awsManaged: - - AWSBudgetsActionsWithAWSResourceControlAccess -groupSets: [] -userSets: [] diff --git a/reference/sample-configurations/lza-sample-config/network-config.yaml b/reference/sample-configurations/lza-sample-config/network-config.yaml deleted file mode 100644 index ab643dc..0000000 --- a/reference/sample-configurations/lza-sample-config/network-config.yaml +++ /dev/null @@ -1,341 +0,0 @@ -################################################################################### -# For additional configurable services, features, and property descriptions, # -# please review our network configuration reference in our README.md: # -# https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/README.md # -################################################################################### -homeRegion: &HOME_REGION us-east-1 -##################################### -# Delete default VPCs-- use this # -# object to delete default VPCs in # -# any non-excluded accounts # -##################################### -defaultVpc: - delete: true - excludeAccounts: [] - -##################################### -# Transit Gateways-- use this object # -# to deploy transit gateways # -##################################### -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Core - routes: [] - - name: Network-Main-Segregated - routes: [] - - name: Network-Main-Shared - routes: [] - - name: Network-Main-Standalone - routes: [] - -##################################### -# Endpoint policies -- use this # -# object to define standard policies # -# for VPC endpoints # -##################################### -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json - -##################################### -# VPCs-- use this object to deploy # -# a VPC in a single account and # -# region. # -##################################### -vpcs: - - name: Network-Endpoints - account: Network - region: *HOME_REGION - cidrs: - - 10.1.0.0/22 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: Network-Endpoints-Tgw-A - routes: [] - - name: Network-Endpoints-Tgw-B - routes: [] - - name: Network-Endpoints-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Endpoints-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - subnets: - - name: Network-Endpoints-A - availabilityZone: a - routeTable: Network-Endpoints-A - ipv4CidrBlock: 10.1.0.0/24 - - name: Network-Endpoints-B - availabilityZone: b - routeTable: Network-Endpoints-B - ipv4CidrBlock: 10.1.1.0/24 - - name: Network-EndpointsTgwAttach-A - availabilityZone: a - routeTable: Network-Endpoints-Tgw-A - ipv4CidrBlock: 10.1.3.208/28 - - name: Network-EndpointsTgwAttach-B - availabilityZone: b - routeTable: Network-Endpoints-Tgw-B - ipv4CidrBlock: 10.1.3.224/28 - transitGatewayAttachments: - - name: Network-Endpoints - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - interfaceEndpoints: - central: true - defaultPolicy: Default - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: kms - - service: logs - - name: Network-Inspection - account: Network - region: *HOME_REGION - cidrs: - - 10.2.0.0/22 - routeTables: - - name: Network-Inspection-Tgw-A - routes: [] - - name: Network-Inspection-Tgw-B - routes: [] - - name: Network-Inspection-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - subnets: - - name: Network-Inspection-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.0.0/24 - - name: Network-Inspection-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.1.0/24 - - name: Network-InspectionTgwAttach-A - availabilityZone: a - routeTable: Network-Inspection-Tgw-A - ipv4CidrBlock: 10.2.3.208/28 - - name: Network-InspectionTgwAttach-B - availabilityZone: b - routeTable: Network-Inspection-Tgw-B - ipv4CidrBlock: 10.2.3.224/28 - - name: Network-Inspection-Firewall-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.3.0/28 - - name: Network-Inspection-Firewall-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.3.16/28 - transitGatewayAttachments: - - name: Network-Inspection - transitGateway: - name: Network-Main - account: Network - options: - applianceModeSupport: enable - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-InspectionTgwAttach-A - - Network-InspectionTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.4.0.0/16 - routeTables: - - name: SharedServices-Tgw-A - routes: [] - - name: SharedServices-Tgw-B - routes: [] - - name: SharedServices-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: SharedServices-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - subnets: - - name: SharedServices-App-A - availabilityZone: a - routeTable: SharedServices-App-A - ipv4CidrBlock: 10.4.0.0/24 - - name: SharedServices-App-B - availabilityZone: b - routeTable: SharedServices-App-B - ipv4CidrBlock: 10.4.1.0/24 - - name: SharedServices-MainTgwAttach-A - availabilityZone: a - routeTable: SharedServices-Tgw-A - ipv4CidrBlock: 10.4.255.208/28 - - name: SharedServices-MainTgwAttach-B - availabilityZone: b - routeTable: SharedServices-Tgw-B - ipv4CidrBlock: 10.4.255.224/28 - transitGatewayAttachments: - - name: SharedServices-Main - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - SharedServices-MainTgwAttach-A - - SharedServices-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - -############################################################## -# Global configuration for VPC flow logs # -# Where there is no flow log configuration defined with VPC # -# this configuration will be used for flow log configuration # -############################################################## -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - cloud-watch-logs - destinationsConfig: - cloudWatchLogs: - retentionInDays: 30 - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path diff --git a/reference/sample-configurations/lza-sample-config/organization-config.yaml b/reference/sample-configurations/lza-sample-config/organization-config.yaml deleted file mode 100644 index f801ad7..0000000 --- a/reference/sample-configurations/lza-sample-config/organization-config.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] -# https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_tag-policies.html -taggingPolicies: - - name: TagPolicy - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root -# https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_backup.html -# backup policies contain a `delete_after_days` value of 1095 days, or 3 years. Before -# enabling this policy, ensure that `delete_after_days` meets your organization's records retention -# policies. Similarly, ensure that `move_to_cold_storage_after_days` meets business requirements. -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/backup-plan.json - deploymentTargets: - organizationalUnits: - - Root diff --git a/reference/sample-configurations/lza-sample-config/security-config.yaml b/reference/sample-configurations/lza-sample-config/security-config.yaml deleted file mode 100644 index 6e91f98..0000000 --- a/reference/sample-configurations/lza-sample-config/security-config.yaml +++ /dev/null @@ -1,492 +0,0 @@ -################################################################################################################## -# For additional configurable services, features, and property descriptions, # -# please review our network configuration reference in our README.md: # -# https://awslabs.github.io/landing-zone-accelerator-on-aws/classes/_aws_accelerator_config.SecurityConfig.html # -################################################################################################################## - -homeRegion: &HOME_REGION us-east-1 -centralSecurityServices: - ################################################################################################################## - # Based upon AWS Security Reference Architecture (AWS SRA), # - # Assigning delegated administrator to security tooling (Audit) account # - # https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/security-tooling.html # - ################################################################################################################## - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: true - excludeRegions: [] - s3PublicAccessBlock: - enable: true - excludeAccounts: [] - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: true - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: true - excludeRegions: [] - s3Protection: - enable: true - excludeRegions: [] - exportConfiguration: - enable: true - overrideExisting: true - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - auditManager: - enable: true - excludeRegions: [] - defaultReportsConfiguration: - enable: true - destinationType: S3 - lifecycleRules: - - enabled: true - abortIncompleteMultipartUpload: 7 - expiration: 1000 - noncurrentVersionExpiration: 1000 - detective: - enable: false - excludeRegions: [] - ################################################################################################################## - # AWS Security Hub Configurations # - ################################################################################################################## - securityHub: - enable: true - regionAggregation: true - excludeRegions: [] - standards: - ############################################################################################################# - # Enable AWS Security Hub standards based upon a customer specific requirements # - # For the standard configuration, we have enabled the following standards: # - # - AWS Foundational Security Best Practices (FSBP) # - # - Center for Internet Security (CIS) AWS Foundations Benchmark v1.4.0 # - # - National Institute of Standards and Technology (NIST) SP 800-53 Rev. 5 # # - # Controls that you might want to disable based upon Security Hub recommendation # - # https://docs.aws.amazon.com/securityhub/latest/userguide/controls-to-disable.html # - ############################################################################################################# - - name: AWS Foundational Security Best Practices v1.0.0 - # https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html - enable: true - - name: CIS AWS Foundations Benchmark v1.4.0 - # https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html - enable: true - - name: NIST Special Publication 800-53 Revision 5 - # https://docs.aws.amazon.com/securityhub/latest/userguide/nist-standard.html - enable: true - ssmAutomation: - excludeRegions: [] - documentSets: [] -accessAnalyzer: - enable: true -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: - - deploymentTargets: - organizationalUnits: - - Root - rules: - ############################################################################# - # Enabling additional config rules not implemented through AWS Security Hub # - ############################################################################# - - name: accelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: accelerator-securityhub-enabled - identifier: SECURITYHUB_ENABLED - - name: accelerator-cloudtrail-enabled - identifier: CLOUD_TRAIL_ENABLED - - name: accelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: accelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: accelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: accelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - # Note, included in Security Hub AFBP slightly different with ports 80 and 443 - - name: accelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: accelerator-iam-no-inline-policy-check - complianceResourceTypes: - - AWS::IAM::User - - AWS::IAM::Role - - AWS::IAM::Group - identifier: IAM_NO_INLINE_POLICY_CHECK - - name: accelerator-cloudwatch-log-group-encrypted - identifier: CLOUDWATCH_LOG_GROUP_ENCRYPTED - - name: accelerator-ec2-instance-detailed-monitoring-enabled - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: accelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: "TRUE" - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: accelerator-cloudtrail-security-trail-enabled - identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: accelerator-guardduty-non-archived-findings - inputParameters: - daysHighSev: "1" - daysLowSev: "30" - daysMediumSev: "7" - identifier: GUARDDUTY_NON_ARCHIVED_FINDINGS - - name: accelerator-sagemaker-endpoint-configuration-kms-key-configured - identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED - - name: accelerator-sagemaker-notebook-instance-kms-key-configured - identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED - - name: accelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - # NIST 800-53-rev5 Conformance Pack - Additional 15 - - name: accelerator-account-part-of-organizations - identifier: ACCOUNT_PART_OF_ORGANIZATIONS - - name: accelerator-codebuild-project-artifact-encryption - complianceResourceTypes: - - AWS::CodeBuild::Project - identifier: CODEBUILD_PROJECT_ARTIFACT_ENCRYPTION - - name: accelerator-dynamodb-throughput-limit-check - identifier: DYNAMODB_THROUGHPUT_LIMIT_CHECK - - name: accelerator-ebs-optimized-instance - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EBS_OPTIMIZED_INSTANCE - - name: accelerator-lambda-dlq-check - complianceResourceTypes: - - AWS::Lambda::Function - identifier: LAMBDA_DLQ_CHECK - - name: accelerator-no-unrestricted-route-to-igw - complianceResourceTypes: - - AWS::EC2::RouteTable - identifier: NO_UNRESTRICTED_ROUTE_TO_IGW - - name: accelerator-secretsmanager-using-cmk - complianceResourceTypes: - - AWS::SecretsManager::Secret - identifier: SECRETSMANAGER_USING_CMK - - # Optional Config rules to check for resources protected by backups. - - deploymentTargets: - organizationalUnits: [] - rules: - - name: accelerator-aurora-resources-protected-by-backup-plan - complianceResourceTypes: - - AWS::RDS::DBCluster - identifier: AURORA_RESOURCES_PROTECTED_BY_BACKUP_PLAN - - name: accelerator-backup-plan-min-frequency-and-min-retention-check - complianceResourceTypes: - - AWS::Backup::BackupPlan - identifier: BACKUP_PLAN_MIN_FREQUENCY_AND_MIN_RETENTION_CHECK - - name: accelerator-backup-recovery-point-encrypted - complianceResourceTypes: - - AWS::Backup::RecoveryPoint - identifier: BACKUP_RECOVERY_POINT_ENCRYPTED - - name: accelerator-backup-recovery-point-manual-deletion-disabled - complianceResourceTypes: - - AWS::Backup::BackupVault - identifier: BACKUP_RECOVERY_POINT_MANUAL_DELETION_DISABLED - - name: accelerator-ec2-resources-protected-by-backup-plan - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_RESOURCES_PROTECTED_BY_BACKUP_PLAN - -cloudWatch: - metricSets: - - regions: - - *HOME_REGION - ##################################### - # With landing zone version 3.0, AWS Control Tower now supports organization-level AWS CloudTrail trails. # - # Going forward from landing zone 3.0, AWS Control Tower no longer will support account-level trails that AWS manages. # - # If your environment runs on prior version (3.0) of landing zone, you can change deployment targets for the metrics to Root organizational units # - # Metrics deployment target should be management account for environment with landing zone version 3.0 # - # Please refer https://docs.aws.amazon.com/controltower/latest/userguide/2022-all.html#version-3.0 for more information # - ##################################### - deploymentTargets: - accounts: - - Management - metrics: - # CIS 1.7 – Avoid the use of the "root" account - - filterName: RootAccountUsageMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccountUsage - metricValue: "1" - # CIS 4.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}" - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: "1" - # CIS 4.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}" - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: "1" - # CIS 4.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: "1" - # CIS 4.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}" - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: "1" - # CIS 4.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}" - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: "1" - # CIS 4.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}" - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: "1" - # CIS 4.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}" - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: "1" - # CIS 4.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}" - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: "1" - # CIS 4.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}" - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: "1" - # CIS 4.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}" - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: "1" - # CIS 4.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}" - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: "1" - # Monitor for the use of breakglass accounts - - filterName: BreakglassAccountUsageMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{ ($.userIdentity.userName = \"breakGlassUser0*\") && ($.userIdentity.invokedBy NOT EXISTS) && ($.eventType != \"AwsServiceEvent\") }" - metricNamespace: LogMetrics - metricName: BreakglassAccountUsage - metricValue: "1" - alarmSets: - - regions: - - *HOME_REGION - ##################################### - # With landing zone version 3.0, AWS Control Tower now supports organization-level AWS CloudTrail trails. # - # Going forward from landing zone 3.0, AWS Control Tower no longer will support account-level trails that AWS manages. # - # If your environment runs on prior version (3.0) of landing zone, you can change deployment targets for the metrics to Root organizational units # - # Metrics deployment target should be management account for environment with landing zone version 3.0 # - # Please refer https://docs.aws.amazon.com/controltower/latest/userguide/2022-all.html#version-3.0 for more information # - ##################################### - deploymentTargets: - accounts: - - Management - alarms: - # CIS 1.7 – Avoid the use of the "root" account - - alarmName: CIS-1.7-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-4.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 4.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-4.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-4.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-4.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-4.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 4.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-4.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-4.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-4.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-4.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 4.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-4.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 4.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # Ensure a log metric filter and alarm exist for breakglass account usage - - alarmName: BreakglassAccountUsage - alarmDescription: Alarm for usage of "breakglass" accounts - snsTopicName: Security - metricName: BreakglassAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching diff --git a/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-1.json b/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-1.json deleted file mode 100644 index 14e925d..0000000 --- a/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-1.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ConfigRulesStatement", - "Effect": "Deny", - "Action": [ - "config:PutConfigRule", - "config:DeleteConfigRule", - "config:DeleteEvaluationResults", - "config:DeleteConfigurationAggregator", - "config:PutConfigurationAggregator" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "LambdaStatement", - "Effect": "Deny", - "Action": [ - "lambda:AddPermission", - "lambda:CreateEventSourceMapping", - "lambda:CreateFunction", - "lambda:DeleteEventSourceMapping", - "lambda:DeleteFunction", - "lambda:DeleteFunctionConcurrency", - "lambda:PutFunctionConcurrency", - "lambda:RemovePermission", - "lambda:UpdateEventSourceMapping", - "lambda:UpdateFunctionCode", - "lambda:UpdateFunctionConfiguration" - ], - "Resource": "arn:${PARTITION}:lambda:*:*:function:${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "SnsStatement", - "Effect": "Deny", - "Action": [ - "sns:AddPermission", - "sns:CreateTopic", - "sns:DeleteTopic", - "sns:RemovePermission", - "sns:SetTopicAttributes", - "sns:Subscribe", - "sns:Unsubscribe" - ], - "Resource": "arn:${PARTITION}:sns:*:*:aws-accelerator-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "EbsEncryptionStatement", - "Effect": "Deny", - "Action": ["ec2:DisableEbsEncryptionByDefault"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventCloudWatchLogsModification", - "Effect": "Deny", - "Action": [ - "logs:DisassociateKmsKey", - "logs:DeleteLogGroup", - "logs:DeleteRetentionPolicy", - "logs:AssociateKmsKey", - "logs:PutRetentionPolicy", - "logs:CreateLogGroup" - ], - "Resource": [ - "arn:${PARTITION}:logs:*:*:log-group:aws-accelerator-*", - "arn:${PARTITION}:logs:*:*:log-group:/aws/lambda/${ACCELERATOR_PREFIX}-*" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventCloudWatchLogStreamModification", - "Effect": "Deny", - "Action": ["logs:DeleteLogStream"], - "Resource": [ - "arn:${PARTITION}:logs:*:*:log-group:aws-accelerator-*:log-stream:*", - "arn:${PARTITION}:logs:*:*:log-group:/aws/lambda/${ACCELERATOR_PREFIX}-*:log-stream:*" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-2.json b/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-2.json deleted file mode 100644 index 375ac1a..0000000 --- a/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-2.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "IamSettingsStatement", - "Effect": "Deny", - "Action": [ - "iam:DeleteAccountPasswordPolicy", - "iam:UpdateAccountPasswordPolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "IamRolesStatement", - "Effect": "Deny", - "Action": ["iam:*"], - "Resource": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/AWSServiceRoleForConfig" - ] - } - } - }, - { - "Sid": "GDSecHubServicesStatement", - "Effect": "Deny", - "Action": [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:UpdateDetector", - "guardduty:StopMonitoringMembers", - "guardduty:Disassociate*", - "securityhub:BatchDisableStandards", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:Disassociate*" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "MacieServiceStatement", - "Effect": "Deny", - "Action": [ - "macie2:AcceptInvitation", - "macie2:CreateInvitations", - "macie2:CreateMember", - "macie2:DeclineInvitations", - "macie2:DeleteInvitations", - "macie2:DeleteMember", - "macie2:DisableMacie", - "macie2:DisableOrganizationAdminAccount", - "macie2:Disassociate*", - "macie2:Enable*", - "macie2:UpdateMacieSession", - "macie2:UpdateMemberSession", - "macie2:UpdateOrganizationConfiguration" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "CloudFormationStatement", - "Effect": "Deny", - "Action": ["cloudformation:Delete*"], - "Resource": "arn:${PARTITION}:cloudformation:*:*:stack/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventSSMModification", - "Effect": "Deny", - "Action": ["ssm:DeleteParameter*", "ssm:PutParameter"], - "Resource": "arn:${PARTITION}:ssm:*:*:parameter${ACCELERATOR_SSM_PREFIX}*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventCloudTrailModification", - "Effect": "Deny", - "Action": [ - "cloudtrail:PutInsightSelectors", - "cloudtrail:PutEventSelectors", - "cloudtrail:StopLogging", - "cloudtrail:DeleteTrail", - "cloudtrail:UpdateTrail", - "cloudtrail:CreateTrail" - ], - "Resource": "arn:${PARTITION}:cloudtrail:*:*:trail/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config/service-control-policies/quarantine.json b/reference/sample-configurations/lza-sample-config/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/reference/sample-configurations/lza-sample-config/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/reference/sample-configurations/lza-sample-config/tagging-policies/org-tag-policy.json b/reference/sample-configurations/lza-sample-config/tagging-policies/org-tag-policy.json deleted file mode 100644 index e49fbb0..0000000 --- a/reference/sample-configurations/lza-sample-config/tagging-policies/org-tag-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tags": { - "costcenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - } - } -} diff --git a/reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/default.json b/reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/ec2.json b/reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/reference/sample-configurations/lza-sample-config/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/solution-manifest.yaml b/solution-manifest.yaml deleted file mode 100644 index 1496363..0000000 --- a/solution-manifest.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: SO0199 -name: landing-zone-accelerator-on-aws -version: v1.8.1 -cloudformation_templates: - - template: AWSAccelerator-InstallerStack.template - main_template: true -build_environment: - build_image: "aws/codebuild/standard:7.0" diff --git a/source/.eslintrc.json b/source/.eslintrc.json deleted file mode 100644 index bd7f37c..0000000 --- a/source/.eslintrc.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "env": { - "es2021": true, - "node": true - }, - "root": true, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], - "rules": { - "dot-notation": "off", - "no-case-declarations": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/camelcase": "off", - "@typescript-eslint/no-var-requires": 0, - "@typescript-eslint/ban-ts-comment": "off" - }, - "globals": { - "require": true - } -} diff --git a/source/.husky/pre-commit b/source/.husky/pre-commit deleted file mode 100755 index 0056091..0000000 --- a/source/.husky/pre-commit +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -# Fail if any command fails. -set -e - -. "$(dirname "$0")/_/husky.sh" - -cd source -# lint staged files only, command defined in package.json -npx lint-staged -# ensure package.json and yarn.lock are in sync -yarn check --integrity - -# Display Console logs from changed files -yarn run scan-logging diff --git a/source/.prettierrc.json b/source/.prettierrc.json deleted file mode 100644 index c2f87d5..0000000 --- a/source/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tabWidth": 2, - "printWidth": 120, - "singleQuote": true, - "trailingComma": "all", - "arrowParens": "avoid" -} diff --git a/source/lerna.json b/source/lerna.json deleted file mode 100644 index e2ef739..0000000 --- a/source/lerna.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "2.0.0", - "packages": [ - "packages/@aws-accelerator/accelerator", - "packages/@aws-accelerator/accelerator/lib/lambdas/*", - "packages/@aws-accelerator/config", - "packages/@aws-accelerator/constructs", - "packages/@aws-accelerator/constructs/lib/aws-*/*", - "packages/@aws-accelerator/constructs/lib/data-perimeter/*", - "packages/@aws-accelerator/installer", - "packages/@aws-accelerator/govcloud-account-vending", - "packages/@aws-accelerator/govcloud-account-vending/lib/lambdas/*", - "packages/@aws-accelerator/modules", - "packages/@aws-accelerator/tester", - "packages/@aws-accelerator/tester/lambdas", - "packages/@aws-accelerator/tools", - "packages/@aws-accelerator/utils", - "packages/@aws-cdk-extensions/cdk-extensions", - "packages/@aws-cdk-extensions/cdk-plugin-assume-role" - ], - "npmClient": "yarn", - "useNx": true -} diff --git a/source/log-scanner.sh b/source/log-scanner.sh deleted file mode 100755 index 54762ec..0000000 --- a/source/log-scanner.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -e -BRed='\033[1;31m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' -cd ../ -files=$(git diff --staged --name-only) -for file in $files; do - lines=$(awk '/console.log\(/{ print NR; }' $file) - for line in $lines; do - echo -e "${BRed}Warning!!! - ${NC}${RED}${file}${NC} has ${BLUE}console logging${NC} in line ${RED}${line}${NC}, review the logging statement." - done -done diff --git a/source/mkdocs/docs/developer-guide/dependencies.md b/source/mkdocs/docs/developer-guide/dependencies.md deleted file mode 100644 index 55dcd4c..0000000 --- a/source/mkdocs/docs/developer-guide/dependencies.md +++ /dev/null @@ -1,44 +0,0 @@ -# Development Dependencies - -The section outlines the development toolchain for Landing Zone Accelerator. - -## System dependencies - - - **NodeJS 16.x** or above - [NodeJS](https://nodejs.org/en/) must be installed on your system - - **AWS CDK CLI** - [AWS CDK tookit CLI](https://www.npmjs.com/package/aws-cdk) must be installed via NPM - - **Yarn** - [Yarn dependency manager](https://www.npmjs.com/package/yarn) must be installed via NPM - -You may install the remaining development dependencies using the following commands: -``` -cd /source -yarn install -``` -!!! note - `` is the local directory where you have cloned the solution source code. - -## Core dependencies - - - **aws-cdk-lib** - AWS CDK library - - **constructs** - AWS constructs library - - **esbuild** - used to package and minify JavaScript code - - **eslint** - used to provide rules for code quality - - **jest** - unit testing framework - - **jsii** - allows code in any language to naturally interact with JavaScript classes - - **lerna** - used to manage the multiple packages in the project - - **ts-node** - execution environment for TypeScript - - **typedoc** - used to document libraries built for the accelerator - - **typescript** - project is written in TypeScript - -## Additional dependencies/plugins - - - **@types/jest** - TypeScript type definitions for jest unit testing framework - - **@types/node** - TypeScript type definitions for NodeJS - - **@typescript-eslint/eslint-plugin** - TypeScript plugin for eslint - - **@typescript-eslint/parser** - allows eslint to parse TypeScript code - - **eslint-config-prettier** - turns off all rules that are unnecessary or might conflict with Prettier - - **eslint-plugin-jest** - jest plugin for eslint - - **eslint-plugin-prettier** - runs Prettier as an ESLint rule and reports differences as individual ESLint issues - - **fs-extra** - adds file system methods that aren't included in the native fs module and adds promise support to the fs methods - - **jest-junit** - A Jest reporter that creates compatible junit xml files - - **jsii-pacmak** - Generates ready-to-publish language-specific packages for jsii modules - - **ts-jest** - A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript \ No newline at end of file diff --git a/source/mkdocs/docs/developer-guide/design.md b/source/mkdocs/docs/developer-guide/design.md deleted file mode 100644 index e8cb2f7..0000000 --- a/source/mkdocs/docs/developer-guide/design.md +++ /dev/null @@ -1,51 +0,0 @@ -# Architecture and Design Philosophy - -This section outlines the overall design and patterns used in the Landing Zone Accelerator to automate the deployment of AWS resources. For additional information, please see the [Architecture overview](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-overview.html) and [Architecture details](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-details.html) sections of the Implementation Guide. - -## Overall Deployment Strategy - -The LZA takes a configuration-based approach to deploying AWS resources across a set of AWS accounts we will refer to as the "deployment environment." The LZA pipeline ingests a set of configuration files provided by the user and transforms the desired configuration to a set of AWS CloudFormation stacks synthesized using the AWS CDK. These stacks are deployed via AWS CodePipeline in a logically ordered set of [stages](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/awsaccelerator-pipeline.html) to account for dependencies between various resources. - -Most stages of the [core pipeline](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/awsaccelerator-pipeline.html) initiate an AWS CodeBuild job that runs the Core Command Line Interface (above) with appropriate arguments. The two most commonly run toolkit commands are **synth** and **deploy** to synthesize and deploy the CloudFormation templates. For example, during the Logging stage the following commands are run: -``` -// Synthesize CloudFormation templates -yarn run ts-node --transpile-only cdk.ts synth --stage operations --config-dir /path/to/aws-accelerator-config/ --partition aws - -// Deploy CloudFormation templates -yarn run ts-node --transpile-only cdk.ts deploy --stage network-vpc --require-approval any-change --config-dir /path/to/aws-accelerator-config/ --partition aws --app cdk.out -``` - -These commands execute the core logic of the CDK, contained in `source/packages/@aws-accelerator/accelerator`. Specifically, this command uses `cdk.ts` as an entrypoint to invoke `lib/accelerator.ts`, which executes parallel instances of `lib/toolkit.ts` to each synthesize or deploy a single CloudFormation stack for each unique pair of account and region in the deployment environment. The above command would synthesize a set of CloudFormation stacks with names following the pattern: -``` -AWSAccelerator-LoggingStack-${ACCOUNT_ID}-${REGION} -``` - -## Accelerator - -The accelerator module includes the core LZA engine logic and defines each stack deployed by the LZA pipeline. -### Stacks - -AWS resources deployed by LZA are grouped by function as described in the [core pipeline](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/awsaccelerator-pipeline.html) page. For each stage, there is at least one corresponding class located in `source/packages/@aws-accelerator/accelerator/lib/stacks`. Using our example above, the logic defining the contents of the Logging stack is defined in `accelerator/lib/stacks/logging-stack.ts`. The logic contained in the stack evaluates the parsed configuration and accordingly adds CloudFormation resources and CDK [constructs](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html) to define the AWS resources in scope. - -### When would I add or modify a stack? - -Changes are made at the stack level when creating new resources via LZA or modifying the deployment method for existing deployments. Most feature requests will require changes to at least one Stack class. - -## Config - -The accepted format of the YAML files defined in the `aws-accelerator-config` configuration repository is defined within `source/packages/@aws-accelerator/config`. For example, valid types within the `global-config.yaml` are defined in `config/lib/global-config.ts`. Within these classes we determine the accepted properties and options for the deployment of AWS services. This module also includes a `/validation` subdirectory that contains code to verify the customer-provided set of YAML files is valid during the Build phase of the pipeline. - -### When would I add or modify a config? - -Changes are made to the config files when enabling the creation of new resource types via LZA or adding additional configuration options. This is a good place to start development of a new feature as it forces you to consider the options to provide customers when using a new feature. - -## Constructs - -As defined by CDK: -> Constructs are the basic building blocks of AWS CDK apps. A construct represents a "cloud component" and encapsulates everything AWS CloudFormation needs to create the component. - -LZA uses constructs to abstract more complex logic from the Stack classes to modular, well-defined components. Constructs may be used to deploy a single CloudFormation resource, a set of related resources, or any custom resources required by the Stacks. The LZA includes its constructs in the `source/packages/@aws-accelerator/constructs` directory. - -### When would I add or modify a construct? - -Constructs are created or modified when there is a significant code addition to facilitate the deployment of new resources. Constructs should be used to minimize the amount of business logic with the Stack classes. diff --git a/source/mkdocs/docs/developer-guide/doc-guidelines.md b/source/mkdocs/docs/developer-guide/doc-guidelines.md deleted file mode 100644 index da8c83d..0000000 --- a/source/mkdocs/docs/developer-guide/doc-guidelines.md +++ /dev/null @@ -1,225 +0,0 @@ -# Documentation Guidelines - -This section outlines the recommended documentation style guidelines to use when developing features for the Landing Zone Accelerator on AWS. - -## TypeDocs - -This solution uses the [TSDoc](https://tsdoc.org/) standard to annotate configuration classes with helpful metadata. TSDoc uses TypeScript's multi-line comment style in order to find and generate documentation for classes and methods. When developing new features for the LZA, please keep the following in mind. - -### Helpful Tips - -* If you are developing new configuration APIs in the `@aws-accelerator/config` module, it is highly recommended to annotate your class and its properties with descriptive metadata. This added context helps users gain an understanding of how to customize the configuration for their unique organizational needs. Keep reading to following sections for style guidelines and examples to follow when developing class annotations. -* [TSDoc playground](https://tsdoc.org/play/) is a useful tool for testing your documentation code and TSDoc's built-in tags. -* You may run `yarn docs` from the source directory in your local development environment to generate docs with changes you've made. The default output folder is `./source/docs`. - -### Recommended Structure and Examples - -The following style should be used for documenting classes, methods, and class properties. - -* There should be a single space before and after the asterisk on each line of the multi-line comment. -* There should be a break between the description and any parameters/return values (i.e. a line with just an asterisk). -* Content Ordering: - * Breadcrumb trail `@link` -- this should follow the full configuration object path so it is easier to navigate between nested configurations. - * Description of class/property: - * Is the class/property optional? Include `(OPTIONAL)` in the description. - * Include links to public AWS documentation when relevant to do so. For example, link to the [What is Amazon VPC?](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html) documentation for the [VPCConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html) class. - * Provide a high-level overview of the service/feature. Put yourself in the shoes of someone that doesn't have a fundamental understanding of what the service does. Use the service FAQ or "Getting Started" documentation as a guide for this description. - * Do not use acronyms before first introducing the full service/feature name. - * `@example` to be provided showing the proper use or multiple example uses in YAML format. - * Use `@remarks` when adding notes/comments about the class or property value. Some things to keep top of mind when writing notes: - * What happens if a user changes this property after initial deployment? - * Are there any constraints for this property value (i.e. min/max numbers, unsupported characters, etc). - * Put yourself in the shoes of a new LZA customer. Is there anything you would want to know as someone unfamiliar with the solution? For example, I need to reference the `name` property of a different config item. Where do a find that config? - * Warn users of destructive actions appropriately in the notes. The following boilerplate serves as an example: - * **CAUTION**: changing this value after initial deployment will cause `` to be recreated. Please be aware that any downstream dependencies may cause this property update to fail. - -The following example contains all of the necessary content as an example for the top-level class description for [IpamPoolConfig](../typedocs/latest/classes/_aws_accelerator_config.IpamPoolConfig.html) within [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html): - -```ts -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link IpamConfig} / {@link IpamPoolConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/ipam/how-it-works-ipam.html | VPC IPAM pool} configuration. - * Use this configuration to define custom IPAM pools for your VPCs. A pool is a collection of contiguous - * IP address ranges. IPAM pools enable you to organize your IP addresses according to your routing and security needs. - * - * @example - * Base pool: - * ``` - * - name: accelerator-base-pool - * description: Base IPAM pool - * provisionedCidrs: - * - 10.0.0.0/16 - * tags: [] - * ``` - * Regional pool: - * ``` - * - name: accelerator-regional-pool - * description: Regional pool for us-east-1 - * locale: us-east-1 - * provisionedCidrs: - * - 10.0.0.0/24 - * sourceIpamPool: accelerator-base-pool - * ``` - */ -``` - -The following example shows the property definitions under the IpamPoolConfig class: - -```ts -export class IpamPoolConfig implements t.TypeOf { - /** - * The address family for the IPAM pool. - * - * @remarks - * The default value is `ipv4`. - * - * @see {@link NetworkConfigTypes.ipVersionEnum} - */ - readonly addressFamily: t.TypeOf | undefined = 'ipv4'; - /** - * A friendly name for the IPAM pool. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the pool to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - */ - readonly name: string = ''; - /** - * (OPTIONAL) The friendly name of the IPAM scope to assign the IPAM pool to. - * - * @remarks - * Note: This is the logical `name` property of the scope as defined in network-config.yaml. - * Leave this property undefined to create the pool in the default private scope. - * - * @see {@link IpamScopeConfig} - */ - readonly scope: string | undefined = undefined; - /** - * (OPTIONAL) The default netmask length of IPAM allocations for this pool. - * - * @remarks - * Setting this property will enforce a default netmask length for all IPAM allocations in this pool. - */ - readonly allocationDefaultNetmaskLength: number | undefined = undefined; - /** - * (OPTIONAL) The maximum netmask length of IPAM allocations for this pool. - * - * @remarks - * Setting this property will enforce a maximum netmask length for all IPAM allocations in this pool. - * This value must be larger than the `allocationMinNetmaskLength` value. - */ - readonly allocationMaxNetmaskLength: number | undefined = undefined; - /** - * (OPTIONAL) The minimum netmask length of IPAM allocations for this pool. - * - * @remarks - * Setting this property will enforce a minimum netmask length for all IPAM allocations in this pool. - * This value must be less than the `allocationMaxNetmaskLength` value. - */ - readonly allocationMinNetmaskLength: number | undefined = undefined; - /** - * (OPTIONAL) An array of tags that are required for resources that use CIDRs from this IPAM pool. - * - * @remarks - * Resources that do not have these tags will not be allowed to allocate space from the pool. - */ - readonly allocationResourceTags: t.Tag[] | undefined = undefined; - /** - * (OPTIONAL) If set to `true`, IPAM will continuously look for resources within the CIDR range of this pool - * and automatically import them as allocations into your IPAM. - */ - readonly autoImport: boolean | undefined = undefined; - /** - * (OPTIONAL) A description for the IPAM pool. - */ - readonly description: string | undefined = undefined; - /** - * (OPTIONAL) The AWS Region where you want to make an IPAM pool available for allocations. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the pool to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - * - * Only resources in the same Region as the locale of the pool can get IP address allocations from the pool. - * A base (top-level) pool does not require a locale. - * A regional pool requires a locale. - */ - readonly locale: t.Region | undefined = undefined; - /** - * An array of CIDR ranges to provision for the IPAM pool. - * - * @remarks - * **CAUTION**: Changing or removing an existing provisioned CIDR range after initial deployment may impact downstream VPC allocations. - * Appending additional provisioned CIDR ranges does not impact downstream resources. - * - * Use CIDR notation, i.e. 10.0.0.0/16. - * If defining a regional pool, the provisioned CIDRs must be a subset of the source IPAM pool's CIDR ranges. - */ - readonly provisionedCidrs: string[] | undefined = undefined; - /** - * (OPTIONAL) Determines if a pool is publicly advertisable. - * - * @remarks - * This option is not available for pools with AddressFamily set to ipv4. - */ - readonly publiclyAdvertisable: boolean | undefined = undefined; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Pools must be shared to any accounts/OUs that require IPAM allocations. - * The pool does not need to be shared with the delegated administrator account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets: t.ShareTargets = new t.ShareTargets(); - /** - * (OPTIONAL) The friendly name of the source IPAM pool to create this IPAM pool from. - * - * @remarks - * Only define this value when creating regional IPAM pools. Leave undefined for top-level pools. - */ - readonly sourceIpamPool: string | undefined = undefined; - /** - * (OPTIONAL) An array of tag objects for the IPAM pool. - */ - readonly tags: t.Tag[] | undefined = undefined; -} -``` - -## GitHub Pages - -LZA uses the [MkDocs](https://www.mkdocs.org/) static site generator to build this GitHub Pages website. MkDocs takes markdown files and a YAML manifest in order to generate the necessary HTML for GitHub Pages. - -### Helpful Tips - -* The YAML manifest and raw markdown files are located in the `./source/mkdocs` directory of the solution source code repository. -* It is recommended to read through [Getting Started with MkDocs](https://www.mkdocs.org/getting-started/) to understand how to install and interact with the mkdocs CLI and YAML manifest. -* If you install the development dependencies on your workstation, you can run `mkdocs serve` to easily test your changes on a local web server. - -### Development Dependencies - -The following additional development dependencies are required for testing documentation updates locally: - -* [Python](https://www.python.org/) >= 3.11 -* [mkdocs](https://pypi.org/project/mkdocs/) >= 1.5.3 -* [mkdocs-material](https://pypi.org/project/mkdocs-material/) >= 9.5.3 - -!!! info - The package versions the solution is using to build the site are pinned in `./.github/workflows/docs.yml`. It is recommended to use the same versions to ensure consistency between your local site and GitHub Pages. - -### Style Recommendations - -* If creating a new top-level section for the website navbar, include an `index.md` page with a high-level description, subpages included in the section, and any other public reference material that may be relevant. For an example, see the [User Guide](../user-guide/index.md). -* If creating a new subpage for a top-level section, make sure to update the MkDocs YAML manifest with the new page and add it to the list of subpages in that section's `index.md`. -* MkDocs uses standard markdown formatting. The [Markdown Cheat Sheet](https://www.markdownguide.org/cheat-sheet/) is a good reference for formatting your markdown files. -* There are some non-native markdown extensions enabled by the Material theme, namely [Admonitions](https://squidfunk.github.io/mkdocs-material/reference/admonitions/) and [Footnotes](https://squidfunk.github.io/mkdocs-material/reference/footnotes/). These features can be very useful for adding additional context and important notes to your documentation. See the [Material for MkDocs Reference](https://squidfunk.github.io/mkdocs-material/reference/) for all supported extensions. - -!!! info - Not all markdown extensions are configured at this time, so you may need to add them to the configuration as they are needed. \ No newline at end of file diff --git a/source/mkdocs/docs/developer-guide/features.md b/source/mkdocs/docs/developer-guide/features.md deleted file mode 100644 index 3f3b1c4..0000000 --- a/source/mkdocs/docs/developer-guide/features.md +++ /dev/null @@ -1,118 +0,0 @@ -# Feature Development - -This section outlines guidance for developing features for Landing Zone Accelerator. - -## Deploying resource dependencies via Landing Zone Accelerator - -When developing features for the accelerator, you may encounter situations where resources in one stack may need to reference resources created in prior stages of the pipeline. This is especially true if you need to ensure a certain resource is available in all accounts and regions managed by the solution before that resource is consumed by subsequent stacks (e.g. cross-account IAM roles, S3 buckets). `DependenciesStack` has been introduced to the pipeline for this use case. Deployed during the `Key` stage of the pipeline, any resources added to this stack may be utilized globally by the accelerator in subsequent stacks. This stack may be considered a means to "bootstrap" the environment with accelerator-specific (non-CDK) dependencies. - -The `DependenciesStack` may be found in `./source/packages/@aws-accelerator/accelerator/stacks/dependencies-stack.ts`. - -## Resource availability by region or partition - -The LZA is built to manage the deployment of AWS resources across all available partitions and regions. As new services and features are released, there is often a lack of feature parity across these regions or partitions. The LZA team has made the decision not to create guardrails blocking customers from deploying elective resources to regions or partitions where they are unavailable. This practice requires additional research during the initial implementation, as well as follow-up work to remove the guardrail when the feature becomes available. - -When designing a feature not available in all regions/partitions, do not create checks or validations to block deployment or throw an error based on the environment. Customers are ultimately responsible for determining the services and features available in the AWS regions and partitions they deploy to. - -## Adding Validation - -The LZA runs a set of validation functions against the provided configuration repository in order to alert customers of syntactical errors. These validations save customers significant time as this can reduce the time for the pipeline to fail in the event of a misconfiguration. This occurs during the `Build` stage of the pipeline, where the CodeBuild project runs the same `config-validator.ts` script referenced in [Configuration Validation](./scripts.md/#configuration-validator). This script orchestrates a collection of configuration-file-specific validation classes defined in the `./source/packages/@aws-accelerator/accelerator/config/validator` directory. - -When developing a feature that modifies or creates new possible configurations, please ensure you create new validation functions in the appropriate validator class. Examples of common validations include: -- Validate account and organizational unit names referenced in `deploymentTargets` are valid -```ts - private validateSsmDocumentDeploymentTargetOUs( - values: t.TypeOf, - ouIdNames: string[], - errors: string[], - ) { - for (const documentSet of values.centralSecurityServices.ssmAutomation.documentSets ?? []) { - for (const ou of documentSet.shareTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for SSM documents does not exists in organization-config.yaml file.`); - } - } - } - } -``` -- Validate referenced files exist -```ts - private validateTaggingPolicyFile(configDir: string, values: OrganizationConfig, errors: string[]) { - for (const taggingPolicy of values.taggingPolicies ?? []) { - if (!fs.existsSync(path.join(configDir, taggingPolicy.policy))) { - errors.push(`Invalid policy file ${taggingPolicy.policy} for tagging policy ${taggingPolicy.name} !!!`); - } - } - } -``` -- Validate AWS service-specific properties -```ts - private validateCloudTrailSettings(values: GlobalConfig, errors: string[]) { - if ( - values.logging.cloudtrail.organizationTrail && - values.logging.cloudtrail.organizationTrailSettings?.multiRegionTrail && - !values.logging.cloudtrail.organizationTrailSettings.globalServiceEvents - ) { - errors.push( - `The organization CloudTrail setting multiRegionTrail is enabled, the globalServiceEvents must be enabled as well`, - ); - } - for (const accountTrail of values.logging.cloudtrail.accountTrails ?? []) { - if (accountTrail.settings.multiRegionTrail && !accountTrail.settings.globalServiceEvents) { - errors.push( - `The account CloudTrail with the name ${accountTrail.name} setting multiRegionTrail is enabled, the globalServiceEvents must be enabled as well`, - ); - } - } - } -``` -- Validate resource names for uniqueness -```ts - private validateStackNameForUniqueness( - values: t.TypeOf, - errors: string[], - ) { - const stackNames = [...(values.customizations?.cloudFormationStacks ?? [])].map(item => item.name); - const stackSetNames = [...(values.customizations?.cloudFormationStackSets ?? [])].map(item => item.name); - - if (new Set(stackNames).size !== stackNames.length) { - errors.push(`Duplicate custom stack names defined [${stackNames}].`); - } - - if (new Set(stackSetNames).size !== stackSetNames.length) { - errors.push(`Duplicate custom stackset names defined [${stackSetNames}].`); - } - } -``` - -## Snapshot Testing - -The LZA currently utilizes [snapshot testing](https://jestjs.io/docs/snapshot-testing) to identify and mitigate unintended consequences of new features. Snapshot testing occurs at the stack level, the config level, and the construct level. - -To update snapshot tests, run the following command from the `source` directory: -``` -yarn update-snapshots -``` -To execute all tests, change directory to `source` and run -``` -yarn test -``` - -### Accelerator Test Updates - -When developing a new feature, please enable or create the new resource by updating the [all-enabled](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/source/packages/%40aws-accelerator/accelerator/test/configs/all-enabled) and [snapshot-only](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/source/packages/%40aws-accelerator/accelerator/test/configs/snapshot-only) configuration directory. After updating the configuration, run `yarn test -u` from the `accelerator` directory. -Currently snapshot-only directory files are used for snapshot testing and all-enabled directory files are used for functional test. Gradually snapshot-only directory configuration files will be migrated to all-enabled directory. - -[all-enabled](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/source/packages/%40aws-accelerator/accelerator/test/configs/all-enabled) directory configuration files are validated through pre-commit hook. -To manually validate all-enabled directory config files, change directory to `source` and run -``` -yarn validate-config packages/@aws-accelerator/accelerator/test/configs/all-enabled/ -``` - -### Config Test Updates - -If you create a new config type, please update the appropriate configuration test within `config/test`. After making this update, run `yarn test -u` from the `config` directory. - -### Construct Test Updates - -If you create a new construct, please create a new unit test for the construct within `constructs/test`. Similar to the folder structure within `constructs/lib`, these test files are separated by AWS service. After creating a new unit test, run `yarn test -u` from the `constructs` directory. \ No newline at end of file diff --git a/source/mkdocs/docs/developer-guide/index.md b/source/mkdocs/docs/developer-guide/index.md deleted file mode 100644 index 46dc137..0000000 --- a/source/mkdocs/docs/developer-guide/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Developer Guide - -This section contains guidance about installing Landing Zone Accelerator package dependencies and best practices to follow when contributing code to the solution. - -!!! info "Subpages" - - [Development Dependencies](./dependencies.md) - - [Command Line Interface and Package Scripts](./scripts.md) - - [Architecture and Design Philosophy](./design.md) - - [Feature Development](./features.md) - - [Documentation Guidelines](./doc-guidelines.md) - -!!! note "See also" - - [Implementation Guide - Developer Guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/developer-guide.html) \ No newline at end of file diff --git a/source/mkdocs/docs/developer-guide/scripts.md b/source/mkdocs/docs/developer-guide/scripts.md deleted file mode 100644 index c6cb0e2..0000000 --- a/source/mkdocs/docs/developer-guide/scripts.md +++ /dev/null @@ -1,74 +0,0 @@ -# Core Command Line Interface (CLI) and Package Scripts - -## Core CLI - -The Landing Zone Accelerator CDK application is invoked using a custom-built implementation of the CDK toolkit. The implementation has additional context option flags that can be used to target pipeline stages, specific accounts, and other attributes. Users can invoke the accelerator pipeline stages locally using this CLI, or programmatically in sequence by running AWSAccelerator-Pipeline. Invoking stages locally can speed up development cycles by enabling developers to focus on deployment of a single stage, or help more advanced users of the solution to quickly deploy targeted updates to an environment. - -???+ note "Before using the CLI" - 1. Ensure you have credentials for your accelerator management account set as [environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) or as an [AWS CLI profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) - 2. Change your local working directory (starting from the root directory of the project): `cd source/packages/\@aws-accelerator/accelerator` - -???+ warning - Local use of the CLI should be used with caution. Configuration changes deployed via this method do not have an approval/diff gate by default. You can add an approval gate to deploy operations by appending the following option: `--require-approval any-change` - -**Example usage of the CLI:** -``` -yarn run ts-node --transpile-only cdk.ts -``` - -Native toolkit commands and options can be found in the [AWS CDK Toolkit reference](https://docs.aws.amazon.com/cdk/v2/guide/cli.html). - -**Accelerator-specific context options:** - -`--account` The AWS account ID to deploy the pipeline stage - -`--config-dir` The local directory where the accelerator configuration files are stored - -`--partition` The AWS partition to deploy the pipeline stage - -`--region` The AWS region to deploy the pipeline stage - -`--stage` The pipeline stage to deploy. Stage names can be found in the [accelerator-stage.ts](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/source/packages/%40aws-accelerator/accelerator/lib/accelerator-stage.ts) file. - -??? info "Example synth command" - ``` - yarn run ts-node --transpile-only cdk.ts synth --stage network-vpc --require-approval any-change --config-dir /path/to/aws-accelerator-config/ --partition aws --region --account - ``` - -??? info "Example deploy command" - ``` - yarn run ts-node --transpile-only cdk.ts deploy --stage network-vpc --require-approval any-change --config-dir /path/to/aws-accelerator-config/ --partition aws --region --account --app cdk.out - ``` - -## Configuration Validator - -The accelerator has a helper script that runs config validation on a provided configuration directory. This script is run during the Build stage of the pipeline, but may also be run locally if the development toolchain is installed. - -**Example usage of the CLI:** -``` -yarn run ts-node --transpile-only config-validator.ts /path/to/aws-accelerator-config/ -``` - -??? info "Alternative syntax" - ``` - yarn validate-config /path/to/aws-accelerator-config/ - ``` - -## Helper Scripts - -Several helper scripts are built into the project that support performing common actions across the monorepo. These scripts are contained within ./source/package.json. -!!! note - When scripts are run from the **./source** directory, the scope is the entire monorepo. They can also be run for each package under their respective directories. - - - `yarn build` - compiles TypeScript code into JavaScript - - `yarn cleanup` - removes compiled TypeScript code, Node modules, and other build artifacts from the local repo - - `yarn cleanup:tsc` - removes only compiled TypeScript code - - `yarn docs` - generate TypeDocs - - `yarn generate-all-docs` - generate TypeDocs for all versions of the solution (only available in **./source** directory) - - `yarn install` - install package dependencies - - `yarn lint` - run ESLint - - `yarn prettier` - run Prettier - - `yarn test` - run unit tests - - `yarn test:clean` - remove test reports - - `yarn validate-config /path/to/aws-accelerator-config` - shorthand for the configuration validator script documented in the previous section - - `yarn update-snapshots` - verify current release version and automatically update snapshots \ No newline at end of file diff --git a/source/mkdocs/docs/faq/architecture.md b/source/mkdocs/docs/faq/architecture.md deleted file mode 100644 index e385b49..0000000 --- a/source/mkdocs/docs/faq/architecture.md +++ /dev/null @@ -1,19 +0,0 @@ -# Architecture FAQ - -## What does the solution deploy? - -The Landing Zone Accelerator is ultimately an orchestration engine that will deploy and configure the resources you specify in your configuration files. The Landing Zone Accelerator orchestration engine is deployed using AWS CloudFormation and utilizes AWS CodeCommit, AWS CodePipeline, and AWS CodeBuild to execute a Cloud Development Kit (CDK) application. This application is responsible for ingesting your configuration and deploying your resources through additional AWS CloudFormation stacks across your environment. - -For further details on the Landing Zone Accelerator orchestration engine, see [Architecture overview](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-overview.html) and [Architecture details](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-details.html) in the implementation guide. - -## What does the AWS sample configuration deploy? - -The Landing Zone Accelerator provides opinionated configurations that are based on our years of building environments for customers with highly regulated workloads. By using the [standard sample configuration](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations/lza-sample-config), you can expect the architecture in the solution’s [Architecture overview](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-overview.html) to be deployed. - -## Is there a sample configuration for my industry? - -You may find the current list of supported industry sample configurations in the [sample configurations](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations) directory of our GitHub repository. Supporting documentation for these sample configurations can be found in the README.md of each configuration directory. - -## How do I customize what the solution deploys? - -The solution's [configuration files](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/configuration-files.html) are the primary interface for what the accelerator deploys. The supported services, features, and API references for these config files can be found in the [User Guide](../user-guide/index.md) of our [GitHub Pages website](../index.md). You may use the configuration reference to update a sample configuration to meet your organization's needs, or to craft your own configuration from scratch. \ No newline at end of file diff --git a/source/mkdocs/docs/faq/ct-cfct.md b/source/mkdocs/docs/faq/ct-cfct.md deleted file mode 100644 index 10799d3..0000000 --- a/source/mkdocs/docs/faq/ct-cfct.md +++ /dev/null @@ -1,183 +0,0 @@ -# AWS Control Tower and Customizations for Control Tower (CfCT) FAQ - -## How does this solution relate to AWS Control Tower? - -When used in coordination with AWS Control Tower (CT), Landing Zone Accelerator will utilize the functionality provided by CT directly, such as using the CT Account Factory to generate and enroll new accounts. Landing Zone Accelerator fully intends to utilize AWS Control Tower APIs, when made available, to orchestrate additional features that CT provides, specifically 1/ OU creation and management, 2/ SCP creation and management, and 3/ CT control management. In the interim, Landing Zone Accelerator will not automate any actions that can potentially cause significant drift with CT, such as OU creation. The Landing Zone Accelerator team will work closely with the AWS Control Tower team to look around corners and avoid any one-way doors in design, implementation or deployment. - -## Is Landing Zone Accelerator compatible with AWS Control Tower? - -Yes, Landing Zone Accelerator is designed to coordinate directly with AWS Control Tower. AWS strongly recommends that you deploy AWS Control Tower as the foundation for the Landing Zone Accelerator. Landing Zone Accelerator extends the functionality of AWS Control Tower by adding additional orchestration of networking and security services within AWS. The Landing Zone Accelerator can be used to enable and orchestrate additional AWS services and features beyond the current functionality of AWS Control Tower through a simplified set of configuration files. - -AWS Control Tower provides the easiest way to set up and govern a secure, multi-account AWS environment, also known as a landing zone. AWS Control Tower creates customers’ landing zone using AWS Organizations, bringing ongoing account management and governance as well as implementation best practices based on AWS’s experience working with thousands of customers as they move to the cloud. - -By using the default [Landing Zone Accelerator on AWS sample configurations](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations), you are able to quickly implement technical security controls and infrastructure foundations on AWS, in alignment with AWS best practices and in conformance with multiple, global compliance frameworks. If necessary, Landing Zone Accelerator can be deployed independently of AWS Control Tower to support regions and partitions that are currently not yet supported by AWS Control Tower. Learn more about AWS Control Tower Commercial Region availability [here](https://docs.aws.amazon.com/controltower/latest/userguide/region-how.html). Learn more about AWS Control Tower GovCloud (US) support [here](https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-controltower.html). - -## AWS Control Tower just added new features that now overlap with Landing Zone Accelerator, what should I do? - -A key design principle of Landing Zone Accelerator is to evolve over time as new AWS services and features become available. Where possible, Landing Zone Accelerator will defer to native AWS services to deliver functionality and over time will deprecate code/functionality in Landing Zone Accelerator if it can be replaced by a native AWS service such as AWS Control Tower. - -## Can I create AWS GovCloud (US) accounts using Landing Zone Accelerator? What happens to the commercial account if I’m using AWS Control Tower? - -Yes. You can specify the creation of an AWS GovCloud (US) account through the Landing Zone Accelerator configuration files. This requires that your Management Root account meets the requirements for creating an AWS GovCloud (US) account. After adding the new account information to the Landing Zone Accelerator configuration and releasing the pipeline, Landing Zone Accelerator will automate the creation of a new GovCloud account through the Organizations service. Since the creation of a GovCloud account also creates a commercial pair, the Landing Zone Accelerator will then automate the enrollment of the commercial account using the AWS Control Tower Account Factory Service Catalog product. - -## If I deploy Landing Zone Accelerator now, can I enroll my environment into AWS Control Tower when the service becomes available in my region, such as AWS GovCloud (US) ADCs? - -Yes. Landing Zone Accelerator is designed to align directly with the landing zone structure that AWS Control Tower provides. Landing Zone Accelerator requires the 3 mandatory accounts that are configured when you enable AWS Control Tower, 1/Management Root, 2/Logging, 3/Audit. When AWS Control Tower becomes available in your region, you will be able to configure your AWS Control Tower landing zone to reuse these same accounts for their specified functions. Additionally, per guidance from the AWS Control Tower service team, where possible, Landing Zone Accelerator will also deploy the same mandatory controls defined by the AWS Control Tower into your environment. - -## How does Landing Zone Accelerator relate to CfCT? - -CfCT allows customers to easily add customizations to their AWS Control Tower landing zone using AWS CloudFormation templates and service control policies (SCPs). Customers are able to configure their environment by updating and adding additional functionality to their CloudFormation templates. Customers that want to dive deeper into the foundational AWS resources and building blocks that are provided with CloudFormation, and/or have developmental experience with Infrastructure as Code (IaC), can utilize CfCT to add their customizations. CfCT handles the deployment of CloudFormation templates using StackSets which allows the deployment of up to 2000 stack instances at a time. Customers have the flexibility to define the dependencies and order that their CloudFormation templates should be deployed though the CfCT configuration. - -Landing Zone Accelerator provides customers with a no-code solution for configuring an enterprise-ready and accreditation-ready environment on AWS. Customers with limited experience with IaC are able to interact with Landing Zone Accelerator through a simplified set of configuration files. Leveraging the AWS Cloud Development Kit (CDK) allows the Landing Zone Accelerator to deploy parallel stacks that go beyond the current instance limits of StackSets. Landing Zone Accelerator handles the dependencies and ordering of the CloudFormation templates and resource deployments; customers simply define what features they want enabled by Landing Zone Accelerator through their configuration files, and Landing Zone Accelerator handles where in the orchestration pipeline to enable the related resources and their dependencies. - -## How do I choose between using Landing Zone Accelerator or CfCT? - -Customers should use CfCT if they want to develop and maintain their own CloudFormation templates and also want the ability to define the dependencies and order that they should be deployed through the CfCT configuration across their multi-account environment. - -Customers should use Landing Zone Accelerator if they want a no-code solution with a simplified set of configuration files that handles the deployment of resources across 35 services and their dependencies across their multi-account environment. Customers should also use Landing Zone Accelerator if they need a solution that can work in all regions and partitions, such as AWS GovCloud (US) and the US Secret and Top Secret regions. - -## Can I use both Landing Zone Accelerator and CfCT? Are there any one-way doors? - -You can use both Landing Zone Accelerator and CfCT to deploy additional customizations to your CT landing zone. Both Landing Zone Accelerator and CfCT support event driven architectures and post an SNS topic at the completion of their respective pipelines. Subscriptions can be set up against these SNS topics to initiate additional pipelines or custom IaC deployments. This includes having CfCT called after the completion of a Landing Zone Accelerator pipeline and vice versa. For customers that want a hybrid approach of a no-code solution to handle the orchestration and deployment of AWS security and networking services through Landing Zone Accelerator, can then use CfCT to add additional customizations directly with custom-developed CloudFormation templates - -## Can I deploy or manage existing AWS Control Tower in Landing Zone Accelerator solution? - -Using the Landing Zone Accelerator on AWS solution, you can create, update, or reset an AWS Control Tower Landing Zone. -It is possible to maintain the AWS Control Tower Landing Zone using the Landing Zone Accelerator solution. When the installer stack of the solution is deployed with the `ControlTowerEnabled` parameter set to `Yes`, then the Landing Zone Accelerator can deploy the AWS Control Tower Landing Zone for you. The solution will deploy the AWS Control Tower Landing Zone with the most recent version available. - -The Landing Zone Accelerator solution can deploy AWS Control Tower Landing Zone when following pre-requisites are met. - -- AWS Organizations with all feature enabled - -When AWS Organizations are not configured in your environment, the Landing Zone Accelerator solution will return an error. In the event that AWS Organizations has been configured, but not all features have been enabled, the solution will enable all features for your organization. After you create an organization and before you can deploy Landing Zone Accelerator solution, you must verify that you own the email address provided for the management account in the organization. In order to learn more about setting up an AWS organization, you may refer to this [Creating an organization](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_create.html). - -- No AWS services enabled for AWS Organizations - -The Landing Zone Accelerator solution cannot deploy AWS Control Tower Landing Zone if AWS Organizations have any AWS service access enabled. - - -- No organization units in AWS Organizations - -The Landing Zone Accelerator solution cannot deploy AWS Control Tower Landing Zone if there are any organizational units in AWS Organizations. AWS Control Tower and the Landing Zone Accelerator solution will create the necessary organization units for the deployment of the AWS Control Tower Landing Zone. - -- No additional accounts in AWS Organizations - -The Landing Zone Accelerator cannot deploy AWS Control Tower Landing Zone when there are other accounts in AWS Organizations than the management account. During the deployment of the AWS Control Tower Landing Zone, the solution will create shared accounts (LogArchive and Audit). - - -!!! warning "GovCloud (US)" - Since shared accounts (LogArchive and Audit) will be existing in GovCloud (US), AWS Control Tower can be deployed when shared accounts (LogArchive and Audit) are successfully invited into AWS Organizations. The Landing Zone Accelerator requires that only three (3) AWS accounts (Management, LogArchive, and Audit) be part of the AWS Organization. - - -- No AWS IAM Identity Center configured - -The Landing Zone Accelerator cannot deploy AWS Control Tower Landing Zone when an existing AWS IAM Identity Center is configured. AWS IAM Identity Center will be deployed during the deployment of the AWS Control Tower Landing Zone. - -- None of the AWS Control Tower service roles are preset - - [AWSControlTowerAdmin](https://docs.aws.amazon.com/controltower/latest/userguide/access-control-managing-permissions.html#AWSControlTowerAdmin) - - [AWSControlTowerCloudTrailRole](https://docs.aws.amazon.com/controltower/latest/userguide/access-control-managing-permissions.html#AWSControlTowerCloudTrailRole) - - [AWSControlTowerStackSetRole](https://docs.aws.amazon.com/controltower/latest/userguide/access-control-managing-permissions.html#AWSControlTowerStackSetRole) - - [AWSControlTowerConfigAggregatorRoleForOrganizations](https://docs.aws.amazon.com/controltower/latest/userguide/roles-how.html#config-role-for-organizations) - -If there are any AWS Control Tower service roles in the management account, Landing Zone Accelerator cannot deploy the AWS Control Tower Landing Zone. - - -Landing Zone Accelerator performs the following pre-requisites before deploying AWS Control Tower Landing Zone. This [document](https://docs.aws.amazon.com/controltower/latest/userguide/lz-api-prereques.html) provides more information about AWS Control Tower pre-requisites. The solution will not perform any of the pre-requisites if there is an existing AWS Control Tower Landing Zone. - -- Deploy AWS Control Tower service roles - - [AWSControlTowerAdmin](https://docs.aws.amazon.com/controltower/latest/userguide/access-control-managing-permissions.html#AWSControlTowerAdmin) - - [AWSControlTowerCloudTrailRole](https://docs.aws.amazon.com/controltower/latest/userguide/access-control-managing-permissions.html#AWSControlTowerCloudTrailRole) - - [AWSControlTowerStackSetRole](https://docs.aws.amazon.com/controltower/latest/userguide/access-control-managing-permissions.html#AWSControlTowerStackSetRole) - - [AWSControlTowerConfigAggregatorRoleForOrganizations](https://docs.aws.amazon.com/controltower/latest/userguide/roles-how.html#config-role-for-organizations) - -The Landing Zone Accelerator will deploy above AWS Control Tower service roles. - - - -- Deploy AWS KMS CMK - -The Landing Zone Accelerator will deploy AWS KMS CMK to encrypt AWS Control Tower resources. - -The Landing Zone Accelerator solution will add the following `landingZone` configuration. - -[GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [ControlTowerConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerConfig.html) / [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) - -``` -landingZone: - version: '3.3' - logging: - loggingBucketRetentionDays: 365 - accessLoggingBucketRetentionDays: 3650 - organizationTrail: true - security: - enableIdentityCenterAccess: true -``` - -#### AWS Control Tower Landing Zone Deployment -Landing Zone Accelerator will create two organizational units (`Security` and `Infrastructure`) when it deploys AWS Control Tower Landing Zone. In addition, AWS Organization level AWS CloudTrail trails will be configured with AWS KMS CMK encryption. - -In the event that there is already an existing AWS Control Tower Landing Zone, Landing Zone Accelerator will not make any changes to it during initial deployment. In order to manage existing AWS Control Tower Landing Zone through the Landing Zone Accelerator solution, you will need to add `landingZone` configuration [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) for `controlTower` configuration [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [ControlTowerConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerConfig.html). - -If any changes are made to the AWS Control Tower Landing Zone configuration, the Landing Zone Accelerator solution will attempt to update the AWS Control Tower Landing Zone. In the event that the current AWS Control Tower Landing Zone has drifted, the solution will attempt to reset it. - -The Landing Zone Accelerator solution will update AWS Control Tower Landing Zone when the [GlobalConfig.enabledRegions](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html#enabledRegions) property is modified. In this solution, the AWS Control Tower Landing Zone govern regions will be updated to match those included in [GlobalConfig.enabledRegions](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html#enabledRegions). - -!!! note - Due to the fact that the Landing Zone Accelerator may deploy certain global AWS services, such as AWS Identity and Access Management (IAM) and AWS Organizations, the solution will add the global region to the list of governed regions in the AWS Control Tower if the home region of the Landing Zone Accelerator is not the same as the global region. ---- - - -!!! warning "Important" - - In the event that the Landing Zone Accelerator solution determines that an existing AWS Control Tower Landing Zone needs to be reset or updated due to a change in `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration, it will validate that the version property of `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration is similar to the latest version ([AWS Control Tower release notes](https://docs.aws.amazon.com/controltower/latest/userguide/release-notes.html)) of AWS Control Tower Landing Zone available. This is due to the fact that changes to AWS Control Tower Landing Zone can only be made when the version matches that of the most recent available version of AWS Control Tower Landing Zone. A version mismatch error will be thrown when the Landing Zone Accelerator solution finds the latest version is not provided in global configuration. - - - -!!! note - The AWS Console should be used to enable or disable the region deny property for your AWS Control Tower Landing Zone. Currently, the Landing Zone Accelerator solution does not support the modification of the region deny feature. ---- - -!!! warning "Important" - - When the AWS Control Tower home region is an opt-in region, deploying the AWS Control Tower Landing Zone using the Landing Zone Accelerator on AWS may fail with the error message `AccessDenied`. The issue can be resolved by ensuring that the LogArchive and Audit accounts have opt-in regions enabled and then retrying the Control Tower. After Control Tower has been successfully deployed, you can retry the Landing Zone Accelerator pipeline. - -#### Register organizational unit with AWS Control Tower -The Landing Zone Accelerator supports the registration of AWS Organizations organizational units with the AWS Control Tower. - -If a new organizational unit is found in the organization configuration file, the following activities will be performed by the solution: - -- Create the AWS Organizations organizational unit. -- Register the organizational unit with AWS Control Tower. -- Invite any existing Amazon Web Services accounts to join the AWS Organization and accept the invitation from the invited account. -- Move the invited accounts into the organizational unit specified in the account configuration file. -- Enrollment in the AWS Control Tower for the invited accounts - - -Creating new organizational units and registering with AWS Control Tower is accomplished by adding them to the [OrganizationalUnitConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationalUnitConfig.html) configuration. - -!!! note - For existing AWS accounts to be invited into AWS Organizations and registered with AWS Control Tower, the `managementAccountAccessRole` role in [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) must be created. It is necessary for this role to include a trust policy that allows the management account to assume the role. The AWS managed policy [AdministratorAccess](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AdministratorAccess.html) must be assigned to this role. This role allows AWS Control Tower to manage your individual accounts and report information about them to your Audit and Log Archive accounts. The following is an example of a role trust policy. -``` -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "controltower.amazonaws.com", - "AWS": "arn::iam:::root" - }, - "Action": "sts:AssumeRole" - } - ] -} -``` - ---- - - - -The Landing Zone Accelerator will check the status of already registered organizational units with the AWS Control Tower. In the event that the registration status has been `FAILED`, the solution will re-register the organizational unit. - - - diff --git a/source/mkdocs/docs/faq/customizations.md b/source/mkdocs/docs/faq/customizations.md deleted file mode 100644 index 2a0e777..0000000 --- a/source/mkdocs/docs/faq/customizations.md +++ /dev/null @@ -1,9 +0,0 @@ -# LZA Customizations FAQ - -## What is Customizations stage? -The customizations stage is used to manage configuration of custom applications, third-party firewall appliances, and CloudFormation stacks. If there are AWS services which are not currently supported natively by the LZA, Customizations offers a solution to create these resources via custom CloudFormation stacks or stacksets. For more information on Customizations and the associated Typedocs, please see: https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/v1.6.0/classes/_aws_accelerator_config.CustomizationsConfig.html - - -## How can I protect CloudFormation resources deployed via Customizations? -The Landing Zone Accelerator is intended to give customers the freedom to customize their environment to their compliance requirements and does not enforce deletion protections on CloudFormation resources deployed in the Customizations stage. The recommended approach is to utilize resource-level deletion policies in the CloudFormation stack as shown in the link below: -https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html diff --git a/source/mkdocs/docs/faq/general.md b/source/mkdocs/docs/faq/general.md deleted file mode 100644 index 13f470c..0000000 --- a/source/mkdocs/docs/faq/general.md +++ /dev/null @@ -1,33 +0,0 @@ -# General FAQ - -## What is Landing Zone Accelerator on AWS? - -The Landing Zone Accelerator on AWS is an open-source solution that will help customers quickly deploy a secure, scalable, and fully-automated cloud foundation. The Landing Zone Accelerator is architected to align with AWS best practices and in conformance with multiple, global compliance frameworks. When used in coordination with services such as AWS Control Tower, it provides a simplified no-code solution to manage and govern a multi-account environment built to support customers with complex compliance requirements. Additionally, the Landing Zone Accelerator on AWS supports non-standard AWS partitions, including AWS GovCloud (US), and the US Secret and Top Secret regions. - -The Landing Zone Accelerator is built using the AWS Cloud Development Kit (CDK), and installs directly into a customers environment, where they have full access to the infrastructure as code (IaC) solution. Through a simplified set of configuration files, customers are able to enable additional functionality, guardrails (eg. AWS Managed Config Rules), and manage their foundational networking topology (eg. Transit Gateways and Network Firewall). - -## Why should I use this solution? - -Landing Zone Accelerator is ideal for customers that don’t have the expertise or don’t want to design an enterprise platform and governance tool chain. Any customer who is looking to build on AWS and wants to do so in a compliant way can use this solution to quickly improve their cloud security posture. - -## How does it work? - -Landing Zone Accelerator is installed into your AWS Organizations Management account through AWS CloudFormation. You can utilize a provided default configuration to initialize your environment with technical security controls and foundational infrastructure on AWS that aligns with best practices and conforms with several compliance frameworks. Customers are able to make additional modifications to configuration files, such as adding additional AWS accounts or VPCs. - -## Is this solution only applicable to government customers? - -No, Landing Zone Accelerator is applicable for all customers that need to implement an architecture based on best practice security. Deployment is supported in any of the regions where Control Tower is available, as well as AWS GovCloud (US). - -Landing Zone Accelerator is delivered with [sample configuration files](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations) which deploy opinionated and prescriptive architectures designed to meet the security and operational requirements of many customers around the world. While installation of the provided prescriptive architectures are reasonably simple, deploying a customized architecture does require extensive understanding of the AWS platform. - -## Will AWS have access to customer’s data if they use this solution? - -No, Landing Zone Accelerator resides within your Management account and is controlled by you. The Landing Zone Accelerator on AWS does not change any of the responsibilities in the [Shared Responsibility Model](https://aws.amazon.com/compliance/shared-responsibility-model/). Another benefit to having the code available as open source is the transparency it brings so customers can be certain of what is being done in their accounts. - -## Where can I get additional technical assistance for Landing Zone Accelerator? - -Customers are able use the [AWS Support console](https://support.console.aws.amazon.com/support/home) to file issues directly against Landing Zone Accelerator. Please use **_Service: Control Tower → Category: Landing Zone Accelerator_** when filing support tickets. - -### Where can I find a software bill of materials (SBOM) for the Landing Zone Accelerator? - -A software bill of materials can be generated from the Landing Zone Accelerator repository hosted on [GitHub](https://github.com/awslabs/landing-zone-accelerator-on-aws). For instructions on how to generate the SBOM, please see [Exporting a software bill of materials for your repository](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/exporting-a-software-bill-of-materials-for-your-repository). diff --git a/source/mkdocs/docs/faq/index.md b/source/mkdocs/docs/faq/index.md deleted file mode 100644 index 6d52576..0000000 --- a/source/mkdocs/docs/faq/index.md +++ /dev/null @@ -1,19 +0,0 @@ -# Frequently Asked Questions (FAQ) - -This section contains several categories of FAQs about the Landing Zone Accelerator solution. - -!!! info "Subpages" - - [General](./general.md) - - [Architecture](./architecture.md) - - [AWS Control Tower and Customizations for Control Tower](./ct-cfct.md) - - [Customizations](./customizations.md) - - [Operations](./operations.md) - - Networking: - - [General](./networking/general.md) - - [Deep Packet Inspection](./networking/dpi.md) - - [AWS Direct Connect](./networking/direct-connect.md) - - [AWS Network Firewall](./networking/network-firewall.md) - - [AWS Gateway Load Balancer](./networking/gwlb.md) - - [Security](./security.md) - - Logging - - [Amazon CloudWatch](./logging/cwl.md) diff --git a/source/mkdocs/docs/faq/logging/cwl.md b/source/mkdocs/docs/faq/logging/cwl.md deleted file mode 100644 index 9a9aef2..0000000 --- a/source/mkdocs/docs/faq/logging/cwl.md +++ /dev/null @@ -1,24 +0,0 @@ -# AWS CloudWatch Log FAQ - -## Can I configure CloudWatch Log group data protection policy? -Yes. The Landing Zone Accelerator solution supports CloudWatch Log group data protection policies to safeguard sensitive data that is ingested by CloudWatch Logs. -Currently, the Landing Zone Accelerator supports only `Credentials` CloudWatch Logs managed data identifiers for configuring log group data protection policies. -The CloudWatch Logs managed data identifiers for Credentials category can be found [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types-credentials.html). - -The Landing Zone Accelerator solution will need the following `dataProtection` configuration to configure CloudWatch Log group data protection policy. It is possible to restrict the functionality to specific target environments (AWS Accounts and Regions) using the `deploymentTargets` property. - -[GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [LoggingConfig](../typedocs/latest/classes/_aws_accelerator_config.LoggingConfig.html) | [CloudWatchLogsConfig](../typedocs/latest/classes/_aws_accelerator_config.CloudWatchLogsConfig.html) - -``` -dataProtection: - managedDataIdentifiers: - category: - - Credentials -``` - -In existing Landing Zone Accelerator environments, if you wish to configure CloudWatch Log group data protection policies, you can add the above `dataProtection` configuration and deploy the Landing Zone Accelerator pipeline. - -The Landing Zone Accelerator solution configures CloudWatch Logs data protection audit policies to write audit reports to `centralLogBucket` Amazon S3 bucket defined in [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [LoggingConfig](../typedocs/latest/classes/_aws_accelerator_config.LoggingConfig.html) / [CentralLogBucketConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralLogBucketConfig.html) - -!!! note - Please note that this feature is only available for AWS Commercial Regions. \ No newline at end of file diff --git a/source/mkdocs/docs/faq/networking/direct-connect.md b/source/mkdocs/docs/faq/networking/direct-connect.md deleted file mode 100644 index 4a01b4c..0000000 --- a/source/mkdocs/docs/faq/networking/direct-connect.md +++ /dev/null @@ -1,51 +0,0 @@ -# AWS Direct Connect FAQ - -## Can I create a Direct Connect dedicated or hosted connection? - -No. Direct Connect dedicated connections must first be requested through the AWS console, approved by AWS, and then ordered through an APN partner or network provider. Hosted connections must be ordered through an APN partner and then accepted in the AWS console. After this prerequisite has been completed, Landing Zone Accelerator can take in the physical connection ID (dxcon-xxxxxx) as a configuration property to create and manage private and transit virtual interfaces. - -More information: [https://docs.aws.amazon.com/directconnect/latest/UserGuide/resiliency_toolkit.html](https://docs.aws.amazon.com/directconnect/latest/UserGuide/resiliency_toolkit.html) - -## Can I create a Direct Connect Gateway? - -Yes. A Direct Connect Gateway must be configured in order to configure other features such as virtual interfaces and associations with transit gateways. The gateway as well as other features can be configured in the `network-config.yaml` accelerator configuration file. It is recommended that the Direct Connect Gateway is configured in the same account that the transit gateway(s) reside in. This enables the accelerator to manage the full lifecycle of transit gateway associations to the Direct Connect Gateway, as well as manage transit gateway static routes, route table associations, and route table propagations that reference the Direct Connect Gateway. - -!!!note "See also" - [Direct Connect Gateway configuration reference](../../typedocs/latest/classes/_aws_accelerator_config.DxGatewayConfig.html) - -## How do I create a Direct Connect virtual interface? - -You must first complete the [prerequisites](https://docs.aws.amazon.com/directconnect/latest/UserGuide/resiliency_toolkit.html#prerequisites) to set up a physical Direct Connect connection. A Direct Connect Gateway must also be created and managed by the accelerator to create virtual interfaces. Once the physical connection is no longer in a pending state, you can reference the physical connection ID (dxcon-xxxxxx) in the `network-config.yaml` accelerator configuration file to begin creating virtual interfaces. - -**Note:** The accelerator can manage the full lifecycle of a virtual interface if the Direct Connect Gateway and physical connection reside in the same account. Due to billing requirements for Direct Connect owners, this is not always possible. For these use cases, the accelerator can also allocate hosted virtual interfaces, but there is a manual billing acceptance step that must be completed by a human after the initial creation. - -!!!note "See also" - [Direct Connect virtual interface configuration reference](../../typedocs/latest/classes/_aws_accelerator_config.DxVirtualInterfaceConfig.html) - -## Can I create a hosted virtual interface? - -Yes. If the `ownerAccount` property of the virtual interface configuration specifies a different account than the `account` property of the Direct Connect Gateway, the accelerator CDK application will create a hosted virtual interface allocation from the account that owns the physical connection to the Direct Connect Gateway owner account. Virtual interface allocations must be manually accepted after creation and attached to a Direct Connect Gateway in order to be used. The accelerator will not manage this acceptance process as it is billing-related and should be explicitly reviewed by a human or automation outside of the accelerator. - -**Notes:** - -- The physical connection must be owned by an account managed by the accelerator. -- After the initial creation of the hosted virtual interface, the `interfaceName` and `tags` properties can no longer be managed by the accelerator. However, `jumboFrames` and `enableSiteLink` may still be updated. - -## How do I associate a Direct Connect Gateway with a Transit Gateway? - -It is required that both the Direct Connect Gateway and Transit Gateway are managed by the accelerator. An association to a transit gateway can be configured in the `network-config.yaml` accelerator configuration file. It is recommended that both gateways reside in the same account, however due to billing requirements for some organizations, this is not always possible. For these use cases, the accelerator can also create an **association proposal** from a Transit Gateway owner account to a Direct Connect Gateway owner account. This is determined dynamically by the CDK application based on the `account` property of each resource. - -**Notes:** - -- There are limitations with association proposals. After the initial proposal is created, a manual acceptance process must be completed. The accelerator will not manage this acceptance process as it is billing-related and should be explicitly reviewed by a human. Updates to the proposal (i.e. allowed route prefixes) can be made via the accelerator, but must be reviewed and approved by a human or automation outside of the accelerator. -- Gateway associations configured in the same account can additionally manage transit gateway static routes, route table associations, and route table propagations via the accelerator. Association proposals cannot manage these additional features. -- The association process between a Direct Connect Gateway and Transit Gateway can take anywhere from five to twenty minutes on average. The length of time depends on current load of the Direct Connect control plane in the region the association is occurring. Your pipeline progression will be paused until it validates the association has completed. - -!!!note "See also" - [Direct Connect Gateway Transit Gateway association reference](../../typedocs/latest/classes/_aws_accelerator_config.DxTransitGatewayAssociationConfig.html) - -## Why is my NetworkAssociations stack in UPDATE_ROLLBACK_COMPLETE status after adding a Transit Gateway Association? - -The association process between a Direct Connect Gateway and Transit Gateway can take anywhere from five to twenty minutes on average. The length of time depends on current load of the Direct Connect control plane in the region the association is occurring. Prior to v1.3.0, the accelerator was utilizing an AWS Lambda-backed custom resource to process this association and validate its completion. If the association took longer than 15 minutes, the Lambda would time out and cause this error. If running a version prior to v1.3.0, you can safely retry the Deploy stage of the pipeline after the association has completed to get past this error, and it will not occur on subsequent runs. - -As of v1.3.0, this issue has been rectified and the custom resource should no longer fail after 15 minutes. Note that the association process will pause pipeline progression until it has completed. \ No newline at end of file diff --git a/source/mkdocs/docs/faq/networking/dpi.md b/source/mkdocs/docs/faq/networking/dpi.md deleted file mode 100644 index a65cf8e..0000000 --- a/source/mkdocs/docs/faq/networking/dpi.md +++ /dev/null @@ -1,19 +0,0 @@ -# Deep Packet Inspection FAQ - -## What architectural design patterns can I leverage with Landing Zone Accelerator? - -The accelerator network configuration offers much flexibility in terms of core network design. The accelerator supports all common strategies for deep packet inspection architectures, including north-south and east-west patterns using a hub-spoke design with centralized inspection VPC. One caveat to this flexibility is our prescriptive approach to network centralization, meaning that a member account must be explicitly defined as a `delegatedAdminAccount` to own central network resources under the `centralNetworkServices` configuration block. This design strategy is derived from our years of experience as well as best practices defined in AWS Whitepapers and Prescriptive Guidance patterns. - -Using the accelerator, you can define any number of core and workload VPCs for your environment. For network security purposes, a centralized inspection or firewall VPC should be established in the delegated administrator account. This VPC is used for deploying either AWS Network Firewall or Gateway Load Balancer. You define your network boundaries and filtering rules via policies applied to the Network Firewall or third-party security appliances behind Gateway Load Balancer. You can then define a routing strategy via Transit Gateway and VPC subnet route tables to ensure your north-south and east-west traffic is inspected and filtered appropriately. - -!!!note "See also" - * [AWS Whitepaper: Building a Scalable and Secure Multi-VPC AWS Network Infrastructure](https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/welcome.html) - * [AWS Prescriptive Guidance: The AWS Security Reference Architecture](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/welcome.html) - -## How do I enable inspection at the edge of my VPC for public-facing workloads? - -This can be accomplished by configuring a **gateway route table** for your workload VPCs and targeting a Gateway Load Balancer or AWS Network Firewall endpoint deployed to a subnet in that VPC. Using the accelerator, you can do this by configuring the `gatewayAssociation` property for a VPC route table. Traffic traverses these VPC endpoints transparently, meaning source IP addresses are preserved. This allows fine-grained inspection of network traffic based on external/untrusted security zones defined in the configuration of the Network Firewall or backend security appliance policy. Gateway route tables can be associated with a VPC’s internet gateway or virtual private gateway. - -!!!note "See also" - * [Route table configuration reference](../../typedocs/latest/classes/_aws_accelerator_config.RouteTableConfig.html) - * [AWS PrivateLink Developer Guide: More information on traffic patterns for edge inspection](https://docs.aws.amazon.com/vpc/latest/privatelink/create-gateway-load-balancer-endpoint-service.html) \ No newline at end of file diff --git a/source/mkdocs/docs/faq/networking/general.md b/source/mkdocs/docs/faq/networking/general.md deleted file mode 100644 index 59c6856..0000000 --- a/source/mkdocs/docs/faq/networking/general.md +++ /dev/null @@ -1,57 +0,0 @@ -# General Networking FAQ - -## What is the purpose of the `centralNetworkServices` configuration block? - -This configuration block in `network-config.yaml` is a collection of several advanced networking services such as Route 53 Resolver, AWS Network Firewall, VPC IPAM, and Gateway Load Balancer. This collection of services and features support the concept of network centralization in AWS, meaning that a single member account (designated as `delegatedAdminAccount` in the configuration file) owns the resources. This strategy reduces the complexity, cost, and maintenance overhead of cloud network architectures, as core networking components are all centralized in a single member account of the organization. - -Each resource housed in this account can be shared with the rest of the organization via AWS Resource Access Manager (RAM) or other means such as network routing strategies or VPC endpoint distribution. For example, in the case of centralized packet inspection via AWS Network Firewall or Gateway Load Balancer, VPC endpoints can be distributed to other member accounts that enable consumption of the services by your workloads. You may also design network architectures using Transit Gateway that “force” packets through a centralized inspection VPC prior to reaching their destination. - -In addition, many newer AWS networking services are beginning to adopt the concept of “delegated administration,” meaning that a member account is delegated administrative authority for a service or set of services. This is identical to the functionality of security service delegated administration that has become a staple of centralized security operations in the cloud. The delegatedAdminAccount will be used for this purpose, along with the uses listed above. VPC IPAM is the first feature to use this paradigm, and forthcoming features in the accelerator will enable it if available. - -## What are the differences between the `vpcs` and `vpcTemplates` configuration blocks? - -The `vpcs` block serves as a way to define VPCs that are only meant to be deployed to a single account and region. This block is useful for defining core VPCs, such as a VPC for centralized interface endpoints and/or centralized deep packet inspection. This can also be leveraged to deploy one-off workload VPCs, but if a “t-shirt sizing” strategy has been established for your cloud infrastructure, `vpcTemplates` is likely a better configuration strategy for your workload VPCs. - -`vpcTemplates` is useful for deploying a standard VPC size across multiple workload accounts or organizational units (OUs) in a single region. An example of this would be deploying a standard workload VPC to all accounts under a development OU. This feature utilizes VPC IPAM to ensure VPC CIDR ranges do not conflict but are provisioned with the same CIDR prefix length across all deployment target accounts. So long as the IPAM pool is not depleted, new VPCs will automatically be vended when accounts are registered to an OU and the accelerator pipeline is released, unless the account is explicitly excluded in the `deploymentTargets` configuration property. - -## How do I define a centralized interface endpoint VPC? - -Landing Zone Accelerator automates the heavy lifting associated with the configuration and management of a centralized interface endpoint VPC. This is facilitated through the `central` property under the `interfaceEndpoints` configuration in a [VpcConfig](../../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html). Setting `central: true` will automate the provisioning of Route 53 private hosted zones for each endpoint service defined under this `interfaceEndpoints` configuration. - -Additionally, to utilize these central endpoints from other VPCs and VPC templates, you may define `useCentralEndpoints: true` in their respective configuration blocks in order to automate the necessary private hosted zone associations to those VPCs. - -**Notes:** - -1. Additional network routing, such as routes via Transit Gateway or VPC peering, must be in place so API calls from spoke VPCs can reach the central interface endpoints VPC. -2. Only one central interface endpoint VPC may be defined per AWS region. -3. A VPC template cannot be used as a target for central endpoints. - -!!!note "See also" - For additional information on this design pattern, refer to [Centralized access to VPC private endpoints](https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/centralized-access-to-vpc-private-endpoints.html) from the AWS Whitepaper _Building a Scalable and Secure Multi-VPC AWS Network Infrastructure_ - -## Why do I see default VPCs in some regions when the `delete` parameter in `defaultVpc` is enabled? - -Landing Zone Accelerator provisions AWS CloudFormation stacks in regions that are specified in the `global-config.yaml` configuration file. In these regions, networking resources are deployed via the CloudFormation stacks. The deletion of the [Default Amazon Virtual Private Cloud (VPC)](https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html) are handled through a custom resource provisioned in the Network-Vpc stack. This custom resource invokes an AWS Lambda function to delete common VPC resources that are attached to the default VPC, then delete the VPC itself. At the time of this writing, the Landing Zone Accelerator only deletes the default VPCs from regions designated as `enabledRegions` in `global-config.yaml`. - -For example: A user has the following regions enabled via the Landing Zone Accelerator: - -**global-config.yaml** - -```yaml -homeRegion: us-east-1 -enabledRegions: - - us-east-1 - - eu-west-2 -``` - -They have enabled the deletion of the default VPCs in their operating regions. - -**network-config.yaml** - -```yaml -defaultVpc: - delete: true - excludeAccounts: [] -``` - -The user navigates to us-east-2 in their console and verifies that the Default VPC still remains. This is because the Network-Vpc CloudFormation stack has not been deployed in this region to execute the process of deleting the default VPC for this region. In this event, a user will have to seek alternative methods to deleting these VPCs from their accounts. One such method is through the CLI, please refer to the [documentation](https://docs.aws.amazon.com/vpc/latest/userguide/delete-vpc.html) for more information. \ No newline at end of file diff --git a/source/mkdocs/docs/faq/networking/gwlb.md b/source/mkdocs/docs/faq/networking/gwlb.md deleted file mode 100644 index b080943..0000000 --- a/source/mkdocs/docs/faq/networking/gwlb.md +++ /dev/null @@ -1,25 +0,0 @@ -# AWS Gateway Load Balancer FAQ - -## Can I create a Gateway Load Balancer? - -Yes. A Gateway Load Balancer (GWLB) must be configured to take advantage of other features such as GWLB endpoints and subnet and gateway route tables targeting GWLB endpoints. The GWLB as well as other features can be configured in the `network-config.yaml` accelerator configuration file. Gateway Load Balancers must be deployed to a VPC that is owned by the `delegatedAdminAccount`, however endpoints for the service can be distributed to any member account. - -**Note:** Availability Zone (AZ) mappings differ between accounts. This means the actual AZ that zone A maps to in one account will likely differ in another member account of your organization. Before deploying a GWLB, ensure that these mappings are documented and your remaining network infrastructure is planned around these mappings. - -GWLB endpoints are dependent on **endpoint services**, which are strictly zonal. This means an error will occur if you try to create an endpoint in a zone that that the GWLB was not deployed to. A workaround for this is to deploy your GWLB to all AZs in a region, however this may increase costs associated with data transfer between AZs. - -!!!note "See also" - * [Gateway Load Balancer configuration reference](../../typedocs/latest/classes/_aws_accelerator_config.GwlbConfig.html) - * [AWS PrivateLink Developer Guide: More information on zonal dependencies for GWLB endpoints](https://docs.aws.amazon.com/vpc/latest/privatelink/create-gateway-load-balancer-endpoint-service.html) - -## Can I create a target group for my Gateway Load Balancer? - -Yes. As of v1.3.0 of the accelerator, EC2-based next-generation firewalls and target groups may be defined in the `customizations-config.yaml` accelerator configuration file. You may reference the target group name as the `targetGroup` property of a Gateway Load Balancer configuration in `network-config.yaml`, which tells the accelerator to place the configured instances/autoscaling groups into a target group for that Gateway Load Balancer. - -**Note:** Gateway Load Balancers only support target groups using the GENEVE protocol and port 6081. If the target group uses any other configuration, an error will be thrown during the validation. - -## How do I deploy Gateway Load Balancer endpoints? - -GWLB endpoints are configured under the `endpoints` property of the `gatewayLoadBalancers` configuration object in the `network-config.yaml` accelerator configuration file. Endpoints can be deployed to any account that the accelerator manages, enabling the concept of separate security trust zones for north-south and east-west packet flows. These endpoints are consumers of an endpoint service that is created alongside the Gateway Load Balancer. - -Successful creation of cross-account endpoints is dependent on the Availability Zones the GWLB is deployed to. Please refer to the guidance under **_Can I create a Gateway Load Balancer?_** for more information. \ No newline at end of file diff --git a/source/mkdocs/docs/faq/networking/network-firewall.md b/source/mkdocs/docs/faq/networking/network-firewall.md deleted file mode 100644 index 0f635fc..0000000 --- a/source/mkdocs/docs/faq/networking/network-firewall.md +++ /dev/null @@ -1,24 +0,0 @@ -# AWS Network Firewall FAQ - -## Can I create a Network Firewall? - -Yes. AWS Network Firewalls (ANFWs) can be created and managed by the accelerator. ANFWs must be configured to take advantage of other accelerator features such as subnet and gateway route tables targeting ANFW endpoints. The ANFW as well as other associated features can be configured in the `network-config.yaml` accelerator configuration file. ANFW rule groups and policies are centrally managed in the `delegatedAdminAccount`, however they can be shared via AWS Resource Access Manager (RAM) to other member accounts for consumption. ANFW endpoints can be created in any member account, so long as the associated policy has been shared to that account or the organizational unit (OU) in which it resides. - -!!!note "See also" - [AWS Network Firewall configuration reference](../../typedocs/latest/classes/_aws_accelerator_config.NfwConfig.html) - -## What is the relationship between firewalls, policies, and rule groups? - -Firewalls have a one-to-one relationship with policies. Policies have a one-to-many relationship with rule groups. Rules defined within the rule groups are the explicit stateful and/or stateless inspection criteria for traffic passing through a firewall endpoint. When defining your ANFW configuration in the accelerator, it helps to work backwards from where the firewall endpoints will be deployed and what workloads the endpoints will be inspecting. From there you can define a policy and associated rule groups for those firewall endpoints to protect security trust zones that you’ve defined for your environment. - -!!!note "See also" - [AWS Network Firewall Developer Guide: More information on Network Firewall components](https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-components.html) - -## How do I deploy firewall endpoints? - -Firewalls and their associated configuration properties are defined under the `firewalls` property of the `networkFirewall` object in the `network-config.yaml` accelerator configuration file. A firewall endpoint will be deployed in each VPC subnet specified in the `subnets` configuration property, so long as those subnets are contained within the VPC configured as the `vpc` property. - -**Note:** Firewall endpoints are zonal resources, and as such a best practice is to deploy an endpoint per Availability Zone that will be enabled in your environment. This ensures your inspection infrastructure remains highly available and routing blackholes do not occur in the case of zone failure. - -!!!note "See also" - [Firewall configuration reference](../../typedocs/latest/classes/_aws_accelerator_config.NfwFirewallConfig.html) \ No newline at end of file diff --git a/source/mkdocs/docs/faq/operations.md b/source/mkdocs/docs/faq/operations.md deleted file mode 100644 index 7b2837f..0000000 --- a/source/mkdocs/docs/faq/operations.md +++ /dev/null @@ -1,71 +0,0 @@ -# Operations FAQ - -## How do I manage my organizational units (OUs) when using CT and Landing Zone Accelerator? - -All OUs and accounts that you create in CT are governed automatically by CT. OUs that are generated outside of CT require you to manually enroll the OU with CT before it can be managed and governed by CT. When using CT and Landing Zone Accelerator together, Landing Zone Accelerator will not automate the creation of additional OUs, as there is currently not an automated mechanism to enroll the newly created OU with CT. This design decision minimizes opportunities for environment drift with CT. - -When using Landing Zone Accelerator without CT, this additional step is not required. - -For more information on enrolling OUs in Landing Zone Accelerator, please see [Adding an Organizational Unit (OU)](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/performing-administrator-tasks.html#adding-an-organizational-unit-ou) in the solution implementation guide. - -## How do I create additional accounts when using CT and Landing Zone Accelerator? - -When new account entries are added to the Landing Zone Accelerator `accounts-config.yaml` configuration file and the Core pipeline is released, Landing Zone Accelerator will utilize the CT Account Factory Service Catalog product to generate the new accounts. Similar to OUs, accounts that are generated outside of CT require you to enroll the account with CT before it can be managed and governed by CT. If you create an account outside of Control Tower (likely directly through the Organizations console or API), you can add the account information to the Landing Zone Accelerator configuration and the solution will automatically enroll the new account into CT using the CT Account Factory Service Catalog product. - -For more information on enrolling new accounts in Landing Zone Accelerator, please see [Adding a new account](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/performing-administrator-tasks.html#adding-a-new-account) in the solution implementation guide. - -## How do I add existing accounts when using CT and Landing Zone Accelerator? - -Please refer to [Adding an existing account](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/performing-administrator-tasks.html#adding-an-existing-account) in the solution implementation guide for guidance on adding an existing account to your Landing Zone Accelerator environment. - -## How do I manage my SCPs when using CT and Landing Zone Accelerator? - -You can use Landing Zone Accelerator to deploy custom SCPs into your environment in addition to the SCPs that are deployed and managed by CT. Landing Zone Accelerator will only manage SCPs that are part of the accelerator configuration, and will not manage any SCPs that are deployed by CT. Note, Organizations sets a limit of 5 SCPs per OU and CT will consume up to 3 SCPs which will leave 2 additional SCPs that you can add. For finer grained SCPs, Landing Zone Accelerator also allows you to deploy custom SCPs to specific accounts. - -For more information on managing SCPs in Landing Zone Accelerator, please see [Adding a Service Control Policy (SCP)](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/performing-administrator-tasks.html#adding-a-service-control-policy-scp) in the solution implementation guide. - -## How do I troubleshoot deployment and validation errors? - -Common troubleshooting scenarios are documented in the [Troubleshooting](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/troubleshooting.html) section of the solution implementation guide. This section will continue to grow with additional scenarios as common deployment and environment validation error cases are reported. - -## How do I troubleshoot AWS Control Tower Landing Zone deployment and validation errors? - -It is recommended that you refer to the AWS Control Tower [pre-requisites](./ct-cfct.md#can-i-deploy-or-manage-existing-aws-control-tower-in-landing-zone-accelerator-solution) before troubleshooting any issues related to AWS Control Tower Landing Zone deployment. - -##### Common Errors - -`AWSOrganizationsNotInUseException: Your account is not a member of an organization. in accounts-config.yaml config file` - -Landing Zone Accelerator may return an error during configuration validation if AWS Organizations is not configured. Please configure AWS Organizations before deploying the solution. In order to learn more about setting up an AWS organization, you may refer to this [Creating an organization](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_create.html) - -`AWS Control Tower Landing Zone cannot deploy because AWS Organizations have not been configured for the environment.` - -AWS Control Tower Landing Zone can be deployed using the Landing Zone Accelerator solution when AWS Organizations configured in the environment. AWS Organizations should be configured with all features enabled before Landing Zone Accelerator can be deployed. In order to learn more about setting up an AWS organization, you may refer to this [Creating an organization](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_create.html). After you create an organization and before you can deploy Landing Zone Accelerator solution, you must verify that you own the email address provided for the management account in the organization. - -`AWS Control Tower Landing Zone cannot deploy because there are multiple organizational units in AWS Organizations.` - -The Landing Zone Accelerator solution cannot deploy AWS Control Tower Landing Zone when there are organizational units in AWS Organizations. Prior to deploying the solution, it is necessary to clean up existing organizational units. When there are existing organizational units within the AWS Organizations, it is recommended that AWS Control Tower Landing Zone is manually deployed prior to the deployment of the solution. By adding `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration, you can manage existing AWS Control Tower Landing Zone. - -`AWS Control Tower Landing Zone cannot deploy because there are multiple accounts in AWS Organizations.` - -The Landing Zone Accelerator solution cannot deploy AWS Control Tower Landing Zone when there are additional accounts in AWS Organizations. AWS Organizations can have only management account. When there are existing AWS accounts within the AWS Organizations, it is recommended that AWS Control Tower Landing Zone is manually deployed prior to the deployment of the solution. By adding `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration, you can manage existing AWS Control Tower Landing Zone. - -`AWS Control Tower Landing Zone cannot deploy because IAM Identity Center is configured.` - -The Landing Zone Accelerator solution cannot deploy AWS Control Tower Landing Zone when there is existing IAM Identity Center configured. When there is existing IAM Identity Center configured, it is recommended that AWS Control Tower Landing Zone is manually deployed prior to the deployment of the solution. By adding `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration, you can manage existing AWS Control Tower Landing Zone. - -`AWS Control Tower Landing Zone cannot deploy because AWS Organizations have services enabled.` - -The Landing Zone Accelerator solution cannot deploy AWS Control Tower Landing Zone when there are trusted access with any AWS service is enabled for AWS Organizations. When when there are trusted access with any AWS service is enabled for AWS Organizations, it is recommended that AWS Control Tower Landing Zone is manually deployed prior to the deployment of the solution. By adding `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration, you can manage existing AWS Control Tower Landing Zone. - -`The landing zone update operation failed with error - ConflictException - AWS Control Tower cannot begin landing zone setup while another execution is in progress.`, - -The Landing Zone Accelerator solution cannot update or reset AWS Control Tower Landing Zone when there is already an execution in progress. The current AWS Control Tower change operation must be completed before you can proceed. - - `AWS Control Tower Landing Zone's most recent version is , which is different from the version specified in global-config.yaml file.` - -Landing Zone Accelerator cannot update or reset AWS Control Tower Landing Zone if the Landing Zone version does not match the latest version of the AWS Control Tower Landing Zone. In order to resolve this issue, it is recommended that you review the [AWS Control Tower release notes](https://docs.aws.amazon.com/controltower/latest/userguide/release-notes.html), and update the version property of `landingZone` [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration. Alternatively, you may rollback [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) configuration changes so that the solution does not attempt to update the AWS Control Tower Landing Zone. In the event the current AWS Control Tower Landing Zone drifts, the solution will attempt to reset the landing zone, which will require the latest version to be specified in the configuration. - -`AWS Control Tower operation with identifier in FAILED state !!!!. Please investigate CT operation before executing pipeline` - -Landing Zone Accelerator returns this error when creating a AWS Control Tower Landing Zone fails with any errors. Resolve the root cause of the AWS Control Tower setup failure before retrying the failed stage of the Landing Zone Accelerator pipeline. It may be possible to identify the root cause of the issue by reviewing AWS CloudTrail trails or AWS CloudFormation stacks. It should be noted that if the home region for the environment is different from the [global region](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/prerequisites.html#ensure-your-global-region-is-accessible), you may need to review trails in the global region as well to identify the root cause. \ No newline at end of file diff --git a/source/mkdocs/docs/faq/security.md b/source/mkdocs/docs/faq/security.md deleted file mode 100644 index f12d8ea..0000000 --- a/source/mkdocs/docs/faq/security.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security FAQ - -## What purpose do the breakGlassUsers in `reference/sample-configurations/lza-sample-config/iam-config.yaml` serve, and what do I do with them? - -Break glass access is a [recommended best practice](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/break-glass-access.html) for gaining access to the organization management account or sub-accounts when there is a security incident or failure of the Identity Provider (IdP) infrastructure. [MFA](https://aws.amazon.com/iam/features/mfa/) and [password reset on next sign-in](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateLoginProfile.html) policies are enforced for break glass users through the `iam-policies/boundary-policy.json` and `iam-config.yaml` settings. It is imperative for the organization management admin to register [MFA devices](https://docs.aws.amazon.com/singlesignon/latest/userguide/how-to-register-device.html) and reset the Landing Zone Accelerator generated passwords before they expire, per the `maxPasswordAge` ([https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAccountPasswordPolicy.html](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAccountPasswordPolicy.html)) setting in `security-config.yaml`. Of equal importance is the protection of the hardware MFA devices and passwords against unauthorized disclosure. This often involves enforcing [dual authorization](https://csrc.nist.gov/glossary/term/dual_authorization), that is, one trusted individual having access to the password and a different trusted individual having access to the MFA token. \ No newline at end of file diff --git a/source/mkdocs/docs/index.md b/source/mkdocs/docs/index.md deleted file mode 100644 index 34cddd6..0000000 --- a/source/mkdocs/docs/index.md +++ /dev/null @@ -1,45 +0,0 @@ -# Landing Zone Accelerator on AWS - -The Landing Zone Accelerator on AWS (LZA) is architected to align with AWS best practices -and in conformance with multiple, global compliance frameworks. We recommend customers -deploy AWS Control Tower as the foundational landing zone and enhance their landing zone -capabilities with Landing Zone Accelerator. These complementary capabilities provides a -comprehensive low-code solution across 35+ AWS services to manage and govern a multi-account -environment built to support customers with highly-regulated workloads and complex compliance -requirements. AWS Control Tower and Landing Zone Accelerator help you establish platform -readiness with security, compliance, and operational capabilities. - -Landing Zone Accelerator is provided as an open-source project that is built using the AWS -Cloud Development Kit (CDK). You install directly into your environment to -get full access to the infrastructure as code (IaC) solution. Through a -simplified set of configuration files, you are able to configure additional -functionality, controls and security services (eg. AWS Managed Config Rules, -and AWS Security Hub), manage your foundational networking topology (eg. VPCs, -Transit Gateways, and Network Firewall), and generate additional workload -accounts using the AWS Control Tower Account Factory. - -There are no additional charges or upfront commitments required to use Landing -Zone Accelerator on AWS. You pay only for AWS services enabled in order to set -up your platform and operate your controls. This solution can also support -non-standard AWS partitions, including AWS GovCloud (US), and the US Secret and -Top Secret regions. - -For an overview and solution deployment guide, please visit -[Landing Zone Accelerator on AWS](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) - -!!! warning "Important" - This solution will not, by itself, make you compliant. It provides - the foundational infrastructure from which additional complementary solutions - can be integrated. The information contained in this solution implementation - guide is not exhaustive. You must be review, evaluate, assess, and approve the - solution in compliance with your organization’s particular security features, - tools, and configurations. It is the sole responsibility of you and your - organization to determine which regulatory requirements are applicable and to - ensure that you comply with all requirements. Although this solution discusses - both the technical and administrative requirements, this solution does not help - you comply with the non-technical administrative requirements. - -!!! info "Anonymized data collection" - This solution collects anonymized operational metrics to help AWS improve the - quality of features of the solution. For more information, including how to - disable this capability, please see the [implementation guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/collection-of-operational-metrics.html). \ No newline at end of file diff --git a/source/mkdocs/docs/installation.md b/source/mkdocs/docs/installation.md deleted file mode 100644 index e31442d..0000000 --- a/source/mkdocs/docs/installation.md +++ /dev/null @@ -1,87 +0,0 @@ -# Installation - -For a full overview on installation of the solution, you can follow the step-by-step instructions in the [Deploy the solution](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/deploy-the-solution.html) section of the solution [Implementation Guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/solution-overview.html). Alternatively, you may follow the steps below to locally synthesize and deploy the solution installer template from source code. - -## Creating an Installer Stack - -The Installer Stack CDK application can be deployed using a CloudFormation template produced by completing a CDK synthesis on a local copy of the solution source code. After synthesis, the template can either be deployed using the AWS CLI or the AWS Management Console. Below are the steps for completing the deployment of the Installer stack. - -### 1. Build the Installer stack for deployment - -1. Install dependencies for the Installer stack - * [NodeJS](https://nodejs.org/en/) - * [AWS CDK](https://aws.amazon.com/cdk/) - * [Yarn](https://yarnpkg.com/) - * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) - -2. Install project dependencies -``` -cd /source -yarn install -``` - -3. To run the CDK synthesis -``` -cd /source/packages/@aws-accelerator/installer -yarn build && yarn cdk synth -``` - -After running these commands, the Installer stack template will be saved to `/source/packages/@aws-accelerator/installer/cdk.out/AWSAccelerator-InstallerStack.template.json` - -!!! note - `` is the local directory where you have cloned the solution source code. - For more information on using the development toolchain, please see [Development Dependencies](./developer-guide/dependencies.md). - -### 2. Create a GitHub personal access token - -Follow the instructions on [GitHub Docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-personal-access-token-classic) to create a personal access token (Classic). - -When creating the token select `public_repo` for the selected scope. - -### 3. Store Token in Secrets Manager - -You must store the personal access token in Secrets Manager in the account and region the solution will be deployed to. - -1. In the AWS Management Console, navigate to Secrets Manager -2. Click Store a new secret -3. On the Choose secret type step select Other type of secret -4. Select the Plaintext tab -5. Completely remove the example text and paste your secret with no formatting no leading or trailing spaces -6. Select the `aws/secretsmanager` AWS-managed KMS key or a customer-managed key that you own -7. Click Next -8. On the Configure secret step, set the Secret name to accelerator/github-token -9. On the Configure rotation step, click Next -10. On the Review step, click Store - -### 4. Deploy the Installer stack - -1. Configure the AWS CLI CloudFormation command for the Installer stack -2. Create an S3 bucket and copy the generated template file. -``` -cd /source/packages/@aws-accelerator/installer -export BUCKET_NAME= -aws s3 mb s3://$BUCKET_NAME -export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) -aws s3api head-bucket --bucket $BUCKET_NAME --expected-bucket-owner $ACCOUNT_ID -aws s3 cp ./cdk.out/AWSAccelerator-InstallerStack.template.json s3://$BUCKET_NAME -``` -3. Create the Installer stack with AWS CLI command: -``` -aws cloudformation create-stack --stack-name AWSAccelerator-InstallerStack --template-url https://$BUCKET_NAME.s3..amazonaws.com/AWSAccelerator-InstallerStack.template.json \ ---parameters ParameterKey=RepositoryName,ParameterValue= \ -ParameterKey=RepositoryBranchName,ParameterValue= \ -ParameterKey=ManagementAccountEmail,ParameterValue= \ -ParameterKey=LogArchiveAccountEmail,ParameterValue= \ -ParameterKey=AuditAccountEmail,ParameterValue= \ -ParameterKey=EnableApprovalStage,ParameterValue=Yes \ -ParameterKey=ApprovalStageNotifyEmailList,ParameterValue= \ -ParameterKey=ControlTowerEnabled,ParameterValue=Yes \ ---capabilities CAPABILITY_IAM -``` -4. _**(Optional)**_ Alternate deployment of CloudFormation via AWS console: - 1. From your Management account, navigate to CloudFormation page in the AWS console - 2. Select ‘Create Stack’ and from the dropdown pick ‘with new resources (standard)’ - 3. For the prerequisite template, select ‘Template is ready’ - 4. When specifying the template, select ‘Upload a template file’ - 5. Ensure that you select the correct file ‘AWSLandingZoneAccelerator-InstallerStack.template.json’ - 6. Fill out the required parameters in the UI, and create the stack once the parameters are inputted. \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/considerations.md b/source/mkdocs/docs/sample-configurations/govcloud-us/considerations.md deleted file mode 100644 index 7629bdf..0000000 --- a/source/mkdocs/docs/sample-configurations/govcloud-us/considerations.md +++ /dev/null @@ -1,15 +0,0 @@ -# Additional Considerations - -AWS provides resources that you should consult as you begin customizing your deployment: - -1. Refer to the [Best Practices](https://aws.amazon.com/blogs/mt/best-practices-for-organizational-units-with-aws-organizations/) for Organizational Units with AWS Organizations blog post for an overview. - -2. [Recommended OUs and accounts](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/recommended-ous-and-accounts.html). This section of the Organizing your AWS Environment Using Multiple Accounts paper discusses the deployment of specific-purpose OUs in addition to the foundational ones established by the LZA. For example, you may wish to establish a Sandbox OU for Experimentation, a Policy Staging OU to safely test policy changes before deploying them more broadly, or a Suspended OU to hold, constrain, and eventually retire accounts that you no longer need. - -3. [AWS Security Reference Architecture](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/welcome.html) (SRA). The SRA \"is a holistic set of guidelines for deploying the full complement of AWS security services in a multi-account environment.\" This document is aimed at helping you to explore the \"big picture\" of AWS security and security-related services in order to determine the architectures most suited to your organization\'s unique security requirements. - -## References - -- LZA on AWS [Implementation Guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/landing-zone-accelerator-on-aws.pdf). This is the official documentation of the Landing Zone Accelerator Project and serves as your starting point. Use the instructions in the implementation guide to stand up your environment. - -- AWS Labs [LZA Accelerator](https://github.com/awslabs/landing-zone-accelerator-on-aws) GitHub Repository. The official codebase of the Landing Zone Accelerator Project. \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/images/image1.png b/source/mkdocs/docs/sample-configurations/govcloud-us/images/image1.png deleted file mode 100644 index 388a848af277b25d7e9a13dab616ef44be43169c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 467537 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

e~j0I0i>Hxo{E4*>=q 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�r8b7=c`YAy z`zUFf#~5cii#1`Tw1I+Jxhka>=OxT+r`#7x`xN6C#s{)A;FG`^uD#^W=KGLXYoJ2{1cJ=%whOy}jc}x%AODpZsp>NXV+@|Ohffn@dQRIX_ zkUTo{Q-Z3U>{-t4YP?Us%5I0|&KfMOIU__0?k#)$C}`}lP2|_Xj+3-ae@o8yfJFvoxr5M?}HQ}PX@4_$5`^0I6C#&X03>$h%Pr;Di*mbX^FW}5`ZYXZ|rmtD+DWi z7bH8c&YmAj&2Z#pTh57M0HK61Ah88l|4o*xFodh#rnA$%^Nngrwa(Yo7u9B0MBK^8 zx0`EdC6_B!)}cMPNE*&uBqn@=gblwP>uWNi54Iz9NzM<5_xsJ|dZT*ZAW$g+pC_{Q z3BUYAq_mduF)9J+q@cwRN{8H@W|n04MhY>RwfFK=a|SUm$+p8=2R8oR)sveG15!H) zz^%RK0K&;fPtX}gFZc<3AvRPr|6Tmhf7`F8fa;Tyi6G^E3XcqX>Im`d%*(n%0A9C| z6l&+-WM7i3Js;XUKYeQ(qAz_9Wp>&oiJxAqU-2Fk-Md+h0RR_-Az>`zdb5?jf0@RI zA~U+CX(qCHh&@^?4vQi75sHGg;{}UVf!}@ER!8|nO#I}(Y9^*U>w%E$w((UNo{Ur(xBE@6b9`!F$tc1k>J7H7i)Yd zNMK^M@4C2myGCL94>^^3$)cmlql&Q@iNrw?|6S4`V3PLo?*{Naw!g>=Qu4Ifr}M(e z?uQ^np(B{GA#xOa6IPS?XTx1QE}LXBQTqm&g=m{VM{S_ElL)(zQRC-4Z{&T6XWm3m zGVO)ahqd!|utaI7{au9I1YcU4o6QIAb1129`5c}cefJl#^lp2aKcsSAT_*1m`wQdS zi9Dl)0UnocRVGYc907S7ZbKZvNefVd)cHi<4QaG5?5B-3#D}PD?v!>SAN~Dnqcj3O zR?`k_C+9wf0*g(E|jh-D?a#CP6v`gV1!XGn4UO%(k-Qy6tx3F{3VsM6Uo*z|< z<#B$()n^VyXUN;tAEbfUCCUkscZZ-}n8X~Se@T&KH%n*CkVQ%m5I!{vMHM1zRYU2P zV_hnVTs}G;yq}bH@HsvAm3DiC-^m$i*>IgyX|NUBZ7B%H7`?Ge&-mpeUvyyb5-Xe! zViy08oYS8-KeEGo{`3a+#zh-6$fS^u;PzE~ntEMIJN{lt2-=iulSMoZ8PziJbLL`- zH0MAqBoIdPm=yc0@@H*%NGRRprzY|CCX6KxXVmem=A`)whlq0{9;&c=y~AzVhSG_3 z5n7E%e%LmzJtY0yiFjViN!I+8dt^RcRJdP4EA>w1NR_$-TF=#*fcAkp_!9rDI%LL(x8mxpFD0D{b(Z5J5eUg$<^XglGZPVP1mB%}tfxf$N|9iF4M(ut2ver-@x+#m`_$_E zG2M++Xs3E)ClIj)3#^GWMni$J(c;%wE7kNFm%i-Tl9N1-U^bLHr=@Dn1n-@N=jMd9 z1VSMZK#qC8>$QbQwPy@Iy5O6+OMj1knew*SgSLdgvc+)B8u4Z>L3Rhh;svn{twT<> zUO(qtpuNd|4B9Zf|N0AZ!R$mkd$koE7X%Uvgna|?e%XII-v6btMGJBMYyWMD54nfB z>?|q=otS)1SoEUvUcYFmCg3bD-&muDe??`k#P>@|NdP1r9Id93NSQNC=RK-2v41Q8 z#y@t~cOtE`zCJ@?aVkFcV)Xp`YU|$W#_4w5&6!((g@f}ndLYbuZeZpXok&-}dFn!KMh*&GMWxpeUe7sJI%-g$8=lc2>Um6nnX_aravTq|yr5>@>G!9|YbU&CoWGJ4Ep6o1iqaH?F{G6Uiofa;eSbKQS2o3wqHVDMsSd9>HC$7|vt_>j3!%fISF&2G*Ea@hHXTG@3>#~m zrTMMH@f>d??hp99?+G;M#Bb3KHK?D`QA3&qXNNTXH*p|@-~D+pT7Yl=R4ULeta3OH zpbp>g_2aZ#kxub5o_Wos$9#~?{c8H0`Zm*`1%DJ`@LC)wGg7>ywILgx zUQP8S>>MAAew9-2?8 zY^?*V^9I3!3OW3T$J;}36QQ>_P-(QlDGcwnHxBgCr<`Sy+#n_onk!C?pK>D8R^SX$M;A?WwE{4Tn()&A(*(No# zv z`q?R_375|VVZS1FogZBt@%85FQC&=xhSgFel|wl9EFMqE(|K=*i;AzA&P)-aOLUh| zotJv-+gj`-COmNR`zmx(WZCu!@-wQ!ckxi?s}aq33v8!(?zj)qzb}7RhuV$2$aEZZ zBq+s2kxx4nN2PyhJ=8+}L{EK10#~cOg98D_1ooh&EG>=Akj+(o2^BW{@6m)40NX(# zwMC3S77q{Q76*`WP4YR0Cs2$nET1g4nZy+P$DvbiKVFRy)yp_$<4Hv&S#wQWVXE97 z&O{3#YjgeFlk7qf56$h@`bmXfIGy>pBNVe|JGIb5TW52$$C7p;P{=Q)Og)}3&n=2idc~)LKUtV)YRZNZ?{}!vm4>+FbE(d zML-dehAAOs*;}l2cF9|;;cQ`VowXX`_q zB3D&^{Y+SBP_xE#4J9^pnA64phEj{$3X&6Y?{3kPMM*@Gm3kD$Tqe~~TQ%D5jcl|W z(CbdTDIS#{Mf68W^wVGS+MdX|kyn2)3IGQue)s1k5ha{fGc#A_L58^E#pqI%2kEPi z%Zd#tFQD-3 zM{l#&efFR&pG>fL2QNVAThkRTAi1iWRn^5GAlY3P$Z4Zc@F}YAu&Ct3Nf!GgLf7Hi z{XU_S-NY6yi3Uwttfdn3Wx`;vY5tQ91(FZ<{3mhZbsbCUQtQG*MvSjl+5|;|0#z+# zyZh**3`)Ye%xGV=%Jq$qkZb+_wZG88z`u!z%q{Fi*Kk`!F(9NiTLiuKGBY#BiZgGc z1BVZ)Fv_{?y|Fq)m`oZ#{mLmBuOl?2UOokIqP=v<3mGq4nXVGbz0zUYje^iP4sqBl z*B))x@mf$xmIOR=Gk1{>s163m=Ua7 z_5>1nDeV5lxarKIaJ$HlBNF<#2xbg1!!>o*$DkR<3A3D7ocHI+kFc%>AKJUq<#kN9 z@$}|7LNva=C;sC{2oU!@FF@1aM((l8NzL_ixxCl@0bwC?_7%tBzB^c7()Z@zYoQp; zft6Jx{NIZ$DE%G2DBRs14b->NCVxnL#w{QUx2BGMx6WSPHegygliLWELN=)Zh)S6i zNrB6Pi`q=x?>HA*`^%^wyMxRay>iEj#*v=J;M0VFxDwsMbxLK@(eW6m+B3_~@#4=^ zK9NqYI_Kme%B+VAme3Z*x8$;dnIi_0C+~i`|6HVZX2}Q8QABH%Hyk)I=0&jCM0&+B z_$R|88_LY?9!8Ig1~&|Hl)8F_i#DKT|Hs)1Z}w75<=Hs7fW=`n?IbcX3Gt2USA;?q zI-kvH54^WL{cI#VM+@S7Z-*5}ohVr=-jPaVk`>aMi-gi#OVpvLg=(6vL|ZtGn$X#z z3cWsZ!Bgy8$l@kA?gm>ks+U{E44S(WGZJ)#!ysjUDnZp+I7)6X;hAlf9?jBNe@M*s z*;U+QD0SE+0Z)JXa$};P;?#EIaUT)3R$}>y_J)Z^;?}5Yr8B0>l&iDQ7uoYySH_u1 zy%Xm=lgUW6v_flW5LTCLF&$!D2O$z=k-co%^<3KQ;O7kt1+!jJwOx{PX0dAAuND1f zgD|4WmAVHcg;!j*4!Uv9TrSoc3k@u!Fh?u1vRGuLF}sv7aDO@^bO?V6cykqZ?wM(H zh^Joel9s)Z9k1jdIgM#KJMXfbZ>IVWRd*DMY-Blm19>)^5^K`RV)=jjGhqkvcOudb z9mhddosX_G;Z4Qk5-b*R;{4>+DW8CfZ z1r-$IFi}m6cu(>`%IB7tuAQN7srh2LM~Ow_*zN$}}radA>`Bn*G+l|fO2~7-!Iw& zDc@5V5l5kJiWP(Oe95>yH(jptMG>-PSdapVc>hMD`kde^$^m^!-7*D?jI_LRTwo!S zWQpPvDO{}73Z#fyk_f%`$Jq!s5C8=A zlczJWbkJYngZ1KuCYE%4kEhwQj>ILQO5Q*A98{2gk0q@xD59HplpWEhm1RYutIJ|> ztA!KYP1hCt$_1)0Ydz6xH%T^MCvLP1Q0!K1iGAhm?gDA)wH5Nkvz`#O1X9EB|7jy( zz-lL}Hoi*ue~*$85r!!wl&K#ecBIdl_IYxOK0_tV$@=*BhRJO~^&>89(SPWzvz=#l zhojKcGlIwel_|$;)QgA!mm8IVtkTYj@vx9-MxDxmmX=Am;+1E&oMF3uSCLl}Zr1gU zV0^f)5$RNr@!P=3#l^9-$}}E*M4fNyYqFl$CB)*&uAi5hbk;oc45D5vP46{g+l55fXT z=0rRmZ70>6V&V;UzN9gWky(#*77N*$UEl24Cm*otd&D3}NS{&U9BJqlJ}W~f0pH7@ zkIWxZ-bIGWt5>a?#-wbtm`@r=lG(OO&hGKw{}SMh?r=T$9#c2ekMtM1dpMZ=a%K2&9{=o;_!4vQZBA}^m+<+`uDaFW+63vpvEeIG${>=@N?JI zSIOMkK<5S7Uo=)4EO$ww01Y2&4J0)fXkh8 z)>T~8DBw9KtK!6dR#@K15j=6kQ7w>Jt}jk9!7=LZGKwp<(!QL|0St!tn3YblY zNA_{Yl~_t|o)_7mYqL^Pcri!1Csd><@|3J*ZtISh858HB@r6Z8(|(i!XoIJYaG?_+ z$2r+3vH%2`Z^~V|I0|>9JK=cY=rWx*{bJ24SK-57ZOSQ~4;U7de@Ks=i7kiKM~0eZ zn*CTF_r%_#AdP>#-O0955c1s?O!P`kuCCKxDBgHfSdjrz{RY=QV#9#2Vn5Pk7txV^ zPTGW7$75E7HTX@|NoyvuN#K8C4PhgScOkL8o@U{4y7`udQnBw`8=22>_7~#??bFdy zOhQ|%E*Wi>;zteKi&dhm^4P!nk}IU5kCps_@I7K6I)9NPB0DEs9V%#vmQdTmms@df zc5&IYLi3E?I`skDrSp#D_Dtm%QU5_y<&nWjFX3XHoZlzKYI&l#fHhdvqKe*oRv3W^ z8wEP=jg+%CwROVy{oe=ziySP0Q8JUeV}Rh(x9#+)Qg68zuf=95s>iE^?s1y)`fS@) zqZ;pyPcQ0U3|!}J7e`5V6IAnocOQz!PpP-jBc(G=Pw6owP0KI0qX2cg_In{Kh#|! zyhE{HNpdiOl#!TpCYBV5DxPr=-MkIEbj)sbMoM|w`Fu**weh=n8+IVL4&WWwzb=1x zzn)Tvp;yM~Be*)DtK9CjDWu;L2;AWIFs+-6mMA*0U$Jjv8eItNlb$rwHSd%&#*`J97b&{1>fOq5u->spSl(5hHGVhk2A-m>e@vKkQf+xph3c99TuM7DI z9Y$bu%OxHygi2$ZbU&-L9i1#&F=^mg;wjVmop(3FCDGytXDkw!+O5J`)8q6{D@y%f zai45#R2*BK!G`?p-E_elt<`4-M#jIil3bJzkGr9eNtdduSlKPxte~82l@WuZnDh8N zLsaUXD4x|Cj>sNZuB9ZBR(IUs*sS2#`0Ar=`6X7a+)vHEaRtxU*!OHWUKchZkvU$T zr=(ei5h=DcP27-LxIdhIxLZh7W)j80k;+t&f5Z)2NL(F^=iI+(uw**=U@D+24nvy5 zA#uuTa*q0F@qDj+5joPzWQ#qs5A37Z`xS<^Ow~-)ntkE*izCYC;k|aVLq<49p-p&1 zrEUfVy|~AI(Oc;+(TP2`mu=d5qZ_27G6^6_8lyv)Du3UCqVi7Rjjr-&ZD44qi1=wO zxfR2iAy{<%@W~W93YH8XO*wZ~oCo4)TL&cjLzK>7Vq=Ci*$ut@cZLI{KV2;`7q|{r zWeEt09k}A&5r3qHVYmG_M27TmmGfXVzXROKh2XpQNEw6r*-)N&WO6F|4+~Bbdy}Bd zqQ#x8l?oG@>;J;*=m4jr1GuTp$p{DRhS1c`wVH6d9I1w7`*hFpqOYR94ir!i<$Mmzctl=3cJ z5b@Kn!IjXiW`rdbBS!Q3J?I1|U_vM*IT8wj z^l=igD?Cdtgk{c(CMYCy9v_eS`0l`p{mf(s&0 zy+WLu)oshMY_N)G`)i$D_g;n3_JmD)=IrCJSa{*ih;glKsWOGyu!-_K_tU=l3u*ta z`@?4U)$7dt5(J78{C%l1fy|1nzDdf`REm6M2@D|qo?v|XKq$-UV8;z|wcXLR%dy3_ zm%^w^%X!n#oSrz4xvO99AxvjKalz@6dhrB>6QM%=%m(+)H^Dyvz%SAovmM^$)^RqQ z>}uDq@BMESdSHCb)-e_Eri$Ks&Jyr4JC-DJyQPx`B*%s!84N z7)v+DQs_hq&MT!f416L7KP--Q*U9ZOjknf@GLnm*(JPn%N@InRr3yB%PEV zO*mGF+_m}vmLAR_T%=>6@XA4)Fdu-pQXmN~1?Dd*CG3-gPDrSAxGgID@6cEx&<}XJgrdpO zSbrjZL6fxbN(~h4lX&c^cYyK$u8xTLCO>(~uN!@tX_l&gzE*7@P##(D#_7aei9?Xa zs3fhngqe&kwb`Nuu3iG-ViL8au?p}nA^znoKh{75BjA189%9z!w^RQH6|fXR@4wgr zp)gHV^*O%oD(_3k-WP`WuF1knC)aH5JtpL>>Kpvu0kSFtBZLwM1W8HTD7UX7Z0Llj zIH4pq$x272Xx&2xN>G14egRdEmK+m5sJc1NBv+90S<>5+AeW}Qf9Cwq3*vIoD{vWTM76nA^st~1@F$AYA5cvPGgvzJsm0B; zh*0Z&(SiW!yT%DVaYkb@D469z(+T0{$M*Dm&SnEb-Bt;w=%QCy8pp?fm^n!JsWNBv zS_jgoO_}lt7bU0kSwscIyhp_Q0J5a8^jSg~$6QuQ)Fxsdf6IJm!s_zoh6Ga{g+&I+ z8bx6^{23o1gL7E_pd*AIpnW0Tbk@5$x2Jxn(M#I4H%9}zX*Z#o+9|MAk>Z_|UKQ|p z^v2|{Q;u0~vXTvnUyHU4QHd#Y()sw2ScbP}o&i^7ZTW|ShOB^NqF~M?{0oPZzg`Hz zUv3=?KphQ13_}yG(aBtyHh{F7G4F}w}JHjRx#k}2JghriL z>rbS7K-HpeDXKP6&hF4is})nI-#L-RN^s=A{| zt*}g!=XV2xS%zS+(`|@SrQR#7&O-`6TD)mMnljqXKL(E2j}{=(n}5}`C1RJvLfe*0 zD&Pejd#YFPi5keu=4O5QrID7;T8qwBf*C|D6+$g8)raJ5@8Ca3a7K9~=zUcrwO3Ex#U}}` zG9k)`*LZ}N*kzy7n9H;};YuJrP{>$@{+1U7wXur8=tBkt`h}E3673$LZ^NWz5OUiG zto!)lDP+{<*tWMK{ixOmM%B7R!IoBSRvHsai_zDEy836Kb54sNB9LdYn&*lo5l5#aI_XsSFn#dlw=V|dV%DuBF~(9h zGmn-U9L7)-VbSEQ=Lvq?&~9&b_DLzutU`{^Q}c*6uWKfGx>O2B9j6peBQNAh2&f!p z5TROpICU81PE)u^F22NSUAjGKAlC*^(7K@vUk)-~X`pW(_O|~P6Z|U+j798^LYCA0 zP9uoo->dmwah_#*C&x*1CMTQ&!=vz!XMM9*gyZ6hs5$P)qdJ-e5jWYHK}^S8-P0v@?+L+2 zBU${jRqx0z5!<3r05l{ziij2m>6hci(%Z%Qj8c>KYxeDA?R3RraCqTD_ODpQD{Ir1 zw>%~OTD6o(Gco3lOgJC1D_a^+gus5sP_)<=wn}8ch3H2G2AD{>C{(%1;9kk^S zQhTTpuuc%%yR%HDunGSJsEQ(nA67}yyDZp1>pvhHkLMP>(gF8x^!j%eWK)!)hY*gM z_#+HVO}GdUffP6;Fr+>}Bp9=HR+Pg&V!9>e<;9x>gOqzxRat5bz;W2md|}@&&}%T! zNaqOQh^s6B_y=Jwa0&<{%#M)HM%aBW@P^Io8c_O4k(vAU#hQv?>xLA{0gFs!3wOcZ zQPHYk$=md~`N?9igWN{H>oc<+OAV@88K6Ru>}ZH*GV+!M-r+pG_B6E;4y}IaxTlJ0 zc0en{`|nx(Gj#YffY_GH)wy_OH(d4O)wr{ztZ2KcwequU%Y2=+~A)6?9{;V&ZXpl*BfIB%Ay9l|IY$D7b{et^A_AZb zYv|)nfX9x4;DTk$6u7B{#yhQ!q+1R!NBU?oMyUBv<-HP>P|E`rG$i5NU#KnH#YzKi zSDHL9ndaac2(2>a{ZHrofkV!J+Vo8c;yNprWC`iBk5!~{c8Yd%>`D)!=gZFs~AR?K#5gBKGI&C^6-KF9oI&9vUPE!t0VJ+$10 zgg=yAEc&$kQjvw=is0e)U7a`4D_{US@3uz|(P9RRpj0h_75zISX%o!Ak80;5;Xe#W zwUDjc`$x%-%n>~56*9#h@W{uPH<%RDvVzHvuimt?SVRqlI(Al!=>5 z4TOM#Uz%YB+@%;mCxoyUIl`sY8Vt0}^hslB5G~A)`=9J3@-Wq}^y%;wSKiMT#N3L3 z0E{dMz>vR@N&Ia?f1l$2`t08;DJ=fu4d2FB^R(wE)1Z?7QmvOGn&1;&K%qo)dpO*tosym|!qm|f(&kG0flF5qrUAf60zC19M12htU6q61t%8LeIy$-d zIrE`=aCYp?Mv`C!g?pRDFe5~dR@qbj<7CFfXWlOx_H3#}UvH1rq?vbe&e|L-; zGBGnXoyjj6VfR&HEeQzAg}$gNr}-p#E=5rup`kjm zx}@&ta5{ng_{wFjRSXi>Q4R5Ux|0>L<7;^K2gMOX6ls2itfrjpdx8>SrFdx+mQZVr zgN4!0aNCJ&DkW5ovT1$JOSv#@+wsz6N({g%0{nai^-6`joL70%E9vkmO5ve|Uk?p* z{A=!&8@xxTK{edsF#~r%36gB<6cybAv5#c=0QCREw1EM*7a)=+aun|H8o3u{4ZZ&P zoc}EGJCR{aVX>5YsUFw*OH@v5CE#bCJOpbJ@n1bRkLZ%vdBCEy8X%fJOWR<|6}Xmg zt(O@qgHE>m3saf=q`;3%RHVY$uS4@nqT8^jTfy8O(zQSg{cSi5olB2(*%j|o!RrU0 z=3@|n#QK+3ng~(Z+0+lNvy;B$%3+2IuiN2k*7)Y3(Z8Id^0BwDKbHVfu*J6*zTIlN zR&slViDveh5dANQ^PlAQ?;mpAQDErx#|Et6$-q{|MT0OUfm`e0m!hVCdCT>L_Z~As z9{BB;8D?Bt1T4Nm*>M*o1B?ct1ny9tywi%8aC)FHiOMc_Lz3NHUC8y zF6?jhz2ArHe|-)o11pUD198_>Y5@@{asep7Vyi1b^}L{@UI{U*mq7gE?rC?*282AbjP@d;|@th38LQ*Lwjp ztfb;PYGKq87^CgZs;i1a@wUa~P4OlwMmFP`{{BmIH9nSbi9HQzqX}x!(|3pl> zFrSxNqCrL{YiucB2{9x#-yw@}m0DuNFhhHhufiN4g*<6uGdv34o?Y6a$JXP6AJu5F`e z0J4#&b&qg23Nvf7L=lKqSb!mpMzNeA@VW>){3d+DW#zEmn`mI7kiq}6V_ZX(vd#3+ zTY^HhUkd_8mt~hS>iV7rEkd{f2IWt~#Hb4%r&g|#Lg;N_;6*RNknXm{h)(z3pHw)P zyG4DzLaiD`7ZwQfkC`7xcn3i;aMOlWr4}Irb%LdeN6HJe5Uujj2o}F0*tJtDsJuIz zPd6Wcq8-`t1y;b`*TiarGn28ih4=0VLxw1F4Rk6W5y%Z9WJ|qDo0ot68t69AQDD0^ zl0eV+54 zqvt#4{e9yb!#_4-4`lCkueIi!*SxN4_Jd*&oko6YV_{><{Tk9|XH4t;U`tQPo!T#3 zokujB*+d@g`~o`!H~7}#2KSXup9q(QX>V?FnWNDDzq%>^Y^(g27g%&KXn|NI19}KJ zC99}W0~9`=Ye-5hKD#(TNFu03>QvNjI}~$Zj|FC0*b_26JdkVc^!y~2mLa>-bh3{= z4X=?~FQ~ow4RMCjKUxfxkZCEJe)ZpuT5&G&U8Yr|X~_#_ww2%m`ZJ=p7g^b~>N2C} z(;9_zOeG4fo@xbkWdq}5MWScNNuE2@;k>$r$M0r$iCBK-$k5Y66>0%xKz-bHvhmrm zrl%HKoP^|Tw^%;Of^>!g!75g0&%wo~&f^<;Dhy{S@KaS~2>k*@U~Cyu#!J873+aE$ zn*QxaCAnWolpK?np;p~4mkl$#WWF|A@ ztR3@Ob{BWzCCTAnrVT^QUB-yVFUMhBCv!K4zKwVI#O^;)38lQp5+x<|E0(o<%NcLi zP(WL2t1jHD$miz&VD|J|SN|!K}8wa30$da{wz)cL_H?)GZGaw z+7$}K{>?RG%M`YYkA!E_XKqcKyQxkG>a|eGdW7ADN{nal@G6nJpU!^v^%vRl9N@?Gj70&qQn+14MRa3iPeiIjtHVdK*O$@G`7uGv=hL9uXjFU?*$=_POz4r z#soH88>0S~OyolmP&`hQJ8Gt$9Ju}Xqp}1rEXrR|o)V`?iD`UHv_TAq0J_J;i4H** zp4n=}*C1>j1!_6C*|J)ACV8 z3n8aF_yEf2$s~=__;)L(^B=ovl_JuFOlu|_wlnE6N73`*^R7gyq>w@5 zn@!ySUJE)EYmd`X^8?D3kCM8GN^0c)4vqXP|NQ|{;s#;R|Jx%x5&j{9rY?#W7mXBE zu=U}C>`u%O%yYU$q!Ci>=Vh+E4(n)2Zrr9^LD+VZ$ge}-9l3*xr~{*6VXH0ggVj9V zO~~xdwqh(gi4rP2`zW!jxFnlaPvHDQj2uP%{W?S24=x}H%VBDd{Pfxp1%Tqopu(+h zZq|S@9K(@P&OJSE{hFZp%!9p_o>;dRcds+ zKWf-Ng4RE;C4iFio)l6{3Sf&B3nDdUuN)e%NLC|A#j`g0$0XMei(^ZuyT<&ad}U2m zi3TAec>(|~VKywCEm&}r`y*ROp3vA*sG;=^JY)|84pR`LU*$`5VgMia?o?Og(9ne! ziG>R3g-;GQQd%p?qpV@2zyG}Y&S*2P3|jfTNyY`R~xx60E99uASh zFST~})wc#TtpAJ8{{%r70oYZIXyr!e1_2DE?_c1nA+C&Zy;aX#MY3lO=B_ir(U3g+ zq^7237*7aFe)lO5JqULzMuh^laSFqjD?=ut5yD@dgwQoeXM{%96LbI0M|0Nt1kF-P zYC&Ul<%=Ukq@nDrKA*(PCRC)fq0DD&(g8j8+Em}NlXq^qE0*Fk=RdD3u*4nQl%Gvj zY!Fc;c~TuCUU&WxA37}2-_PQvYZy4e__0!we0@;@krKHWoSK_-Eaz~U*NxJH<>Ilj zcdcblwnk))u6?By#NS?$=4^xFmNwABy;gPOHq9#$_vP$+b0qhaTPb!yeseQnF}s8v zxRLkWe>&k?k?cXvHsK--xl?^lrlRZ) zcnrW=$YmryU;YvB|8=_lEhqp#D((NuW%n7~Np(JpzWd|>AF}=BGP(Ub*8~q zO3<2qX9GjsB8b2}oPMEq>gU)rY2SBJwXjd;?!BMP`RAk+38(RNqe=xgXKL9io6V*a z%C%}+OFo`WIaXCZPmE+Qm|A)9=a>FpF752Mzc@{A^)E!~ESS&(DO_T38D0e5&ghxK zVG@B54p!sjukgmX^Y-zXgRS@WX4qe5Idreq@;ejo#8QlXry1g!IxDYk>bIrO0_x=4 z7u=Fw7jFv)>;AVj_;Xdd!-U>fYBON#Y$HJ4lai2hV6gq1!eD`WPl!ZS)tFg7+fp0F z?NzRl0VyeSH8AKM=GGN$>Z$BWa<6jFeBwBE`^kIz}2FxJ9r3_MvVE#ZhnH0 z?hB+e>AsuEqv0PITt3GW(WawS1^`UXZDNx>t76;8J`fW)SO*KV`l6)W(F-~#j|DnT zaT7~leJ!~pf+?$P*UAvTcg|zZp#oj2WW4|V zu6~Ek6_YH)JlgyL_Rr_rq4-zByr%PoPaR|WPz+1Kd;O$U%miwR^=Ru75Q2Uyfpr3O zT4GEMUlz$SFTJK>KVx+!m$1Gzb%1Mb7Eh)3+u-7t0GA4&-<*7+kKHlT-{23e~zaPzVB-OPu#{X-@V26+;?7eHgF_!aFQxr|* zs$w*V_>(Nj3Knl)j_pcd7(vZf3ZEEn%P-xv?0871u|b*jc*&K68n-+*I%|+*k%zZB z(|%i(I~K^+Q4LUa!|1HN%E|8LjHx+ek4TE+_srhu>AL1;E$(AHdx6le*ubp|F!AH= zcwYS0%l%i>%ihKS*waMnQ4snjB%$>`cJF&cm=S|ac_xEQ>GRI;CHc1qA_`zI_=W)U zjSR}{H{va3MTTCD1b0X7sPjOVxI}?ltFvlr(R~+YKqvM)jG}znY~V|%*4LmC->vO4Y5;hOOMFhq!6B1hcWs@)$RCjZI9C5Pe*6j{ zI~ZIJpbrl-Qf$7GZx-ih!iiIy+{2%EY-I_hHd4eKO<QkF1hYCV&Kj%uD`jh0n z*UL1*MGhyb(t5fQP2Gj_-f zE{nB_Vb8VpCh0pfs$#M%frc+;v?T9q$pq4=9|Gc}AZ+x}FnWEiZ<#&z{vFtGLO*QK zxng65fpKTQ>=(u#x?tl|QltS|V`1V@n$pN&2Gk@J<3cSI9%algAf(7iil$hnFj3Fm zWu3p>dy9z2{e)b-LL&&6DQvAUJD+Q5S;I-02?GmT1e9or)Pv40-Mi;CHrFyTKe%Lm zwy;~P<+_yVSkcoGRZD*Pjl%fPwbONj^U31c{HlYc@pmu$?`u?P`vrl3!r2#pq0~B> z4-)6YIhqxH1p{g3W0FYz7QIh%Jx0iM?bn+a6O?RHLsVs%1<}J+^AMpsKeV9uKXkg1La=RP>T6 z;*r?-@ZDtEaJyWFf}+4}7*SaEdf|}(d*S{8nc_pz=CDCdukz`G(rDk(jGIUN-gN1* zNxkDC8z9M}^tnb)c!A`sfivSX%kpU=Tc4rWppX*D0JN>-Kzk4sm9IAn#=^<^!CD@5 zkg962Ge%o9+hAHa!XXZ=BCTlmsFeyc1CQA0yZXogP2~i=Rvz{ulYR`Io^t-dXyV?n zYgKbnTJ!{3m1@d!_MA8h|7|e|Gtw}|$4|-ApU!lfya)*e!Jx;0Z1_L6DVE4zt(07k z$J)`LH$1?sz2ENv@qmtt0D6RLZpd?v;hbhqlktW<#j*Iwr)Q@(LUX=cKU$DPuj;$8 z^tHuEW}O@hZDdAXW^0$jesSg8iVMV&39+%rjNFupUK20d-la9@x2kkwWuTf&|i_NuUyDJZ&rnJjy3Ub<+0Y=){tR>gvQuX*j;g00jlM8b2Y z#)3!?c9si~klPwK4c63}HkQo!rc&pH-GUs>X8Kls>1?v(*K2>?+#3AcvkxCReWpB{ zr>y6ZZk%{BK3MBSa%~|T2-eud0uMMwF9ZEgud7+|R1WJ=t=G3AxOtTAf=7+E)VA}L zA#D#nyFs7HOP~IB4Q3;t|7v)NQ)>qz{`O4XlcH@;H4E1M-L?|wbuhC*&dc|N*x4kB zi_V!S`(i`xKGS|MvqjAlTW)6_X6{MRPK#S)z7Rh1LRNDa=ef%Y0n#my_Z4 z3KS6)Ws8hT-<~Yv?~mM|dc4*Dt3ouB}u%qTI||15*$Rxc0T6b3LqiRdOu3=*Mt7Cj|K$brPqz*nqJah@+d3-ygwPZsn^$-r5rOlNm7@d{|tCv*8@A5p8E>f}0 zX2T|l{Lu;n`m%izNld0QTU`+JceL+_-)7JIwDRoTX=!Pl?Mw@fI=N`} zak;DP0Yed_qL+M;c%f?K(m`YSqI9Xv@7?`+bdo8LzSlJ}JO^k3IHA1VnXSioMsF=F z@mv;7ow_N!XI@jr1naYZ{*EAWu^*XRL3yvBm7?WTrM5&Ig@kEuQ2FwmFa)4I*3j@~ z+bq@1Yw6is2KZkYy#DVA-d{I55nLT1LiV&d{3vl+)y|E}?3q}*4c-J>8YMka-YaHq zaXg}oQ}^%j&q(T>h6~D_t1Z1>j&2JESadHv;mMTcaP=xlQ|z!n`Nu&C76K+=L3L+Y zJdz!*K0^WJ1${3n`0y>BX})jlM`ikl7!dTGhf94RCg58$%5z%d7D^FgFK&ZTj0RJ$tk!2vuzW#gVLt(CWyCl##Qh0tnh&XAZY)35QS8QPOtXbJVkGCr%Q z>+6uoVzsGC@fK2V#d^TkB)M2jW9)F;ym`C}U_ccbh;Eukvb?)*Vn4?V(xlJYx;kl! zvmP%6^S4)+XTokeumq~|^;;JRIKDI*Ro3s`$%)hCO^5xh@k31nSK!5&xhQCk6Wwi; z@OuOyi9zy?s5B1Eehwta71ZE^NNu2`F+-hRaK^`NA4P>c=UBwe^b z6XQob4N4D3jVJt^3^CXT!+I}4o#_wtn*#nqul^CabRlqE2SyvI=QZvFE^W+N1(v+U zViHQ@J;?0d{-s(A=yHukim1-u7#}39AK>E;Z5~!oS+uc6W1Zcx%8V)7b!G|%$by^# zPkd=P$l)uX3A!&~BoGnK43u-uJGKU__ee-=2xf$qA0it9&iU< zG?%J=OU;sPuZf6)n8Sw6eMOs~1vx(1j`jN3{)s^eO}Hz|ccieQJ!^NAqD6#TUK#M6 z!AGRo=qIJL6ZGz2#K-42_&J^+9B22FRG;xZ=MOj$Hl3ae$E*8=Q|nHF~jw|^;Hz?h+>nc{%CM2YJba# zQrWM2;NZLKFe{adnJ|JwRL#SK1$c*Tv+`xU-yL z*XHxN*E!C*(QeH}@905Hme06Ha8jeZnWp`YIV4cIb&j2_I~D>~iN3-%+Axm0YYAHtySKZC zAqFN?Ij}BCRi+|4&58!&Ya>*5B7%>G-pY>?ovupJjJ7+dJ_e!NGF_Uh0%YQ2SobGe z1UH+WE0XLZ6_j((gQI4x}zsjd>oF^LpbL0HI z{U_55Ez8>dTg|Mil5SZe0JqXU`UZS zU_{O%o_?;vvTOwQ?acJ_MG$c?4`BVr2@(oVD$6rB@CwK=UcDSlErt}8hpmg%-e-2iUUqf-#6Jo`om)17ee(O9lxtB_9pQ63pR z$@O);~_x2%Uqz<@F$cDJeNn&HyDhebY7%ZY54sMA#^@Zw|u^}*XFZ&+>JmrGv9${B#dy&P>i94D(O%g*x$ z_p3#zv?hA}7?1){L7)5~JvN3^W14-3tTy%$s2tk?R(CAqZmXO5VrO>}w6h~Ax&Gb` z4Ib5fy|7fjJ?Q3QzetaWm^=L;x$=VnrZt}!4B!j^umBlX7`k*AHe8|M@`Ae!bJW9crjQ|xjjVX07zFKpS0nmSS|1Z0BNSfkV7ii?u$ z$*r!j(|6&D%ZiUg_Z;0lR7IWPEp$621;wo!t*l3@vTOpONe0_|sR@q2s7}rV5kylkJ0l|fKr3YD= zetW_q1Rb6{LFz^7tH17nGL^QgQ1o8 zpq2aT_l-R7q;Ar+P7`Szp-1!8n9X7hdRl%LiX z6YM~WC1pkf6A^yvTuA)ZCdTb2+6L!CxQBDq8k%7sBuWfIZk@m_<~5q@sFOOdcT#wE zxby|P7WYeJ9)HtD@Ru9yHWQ&}0{@u1^5gyxJ3#)qiI-p|oYXLIwlk`n0_dfPz56Pf zdnFgm!iCpsuMgG*C-XODTyz^VgOovD=!LNf)*O~Rq`p(hnrYrI;Ai~IpO!5)MDoeB#m>UmqTLDq7Fr`DdYd3`#u&vjZ9p)r*AKjLTqYF?DXUF1LiemV}j z?aJ{CCn*}WVkF11CN>}VXnOTjz2ZW0y#!~5wXNzH-QFS9mF{`usqXh$qRa+c0c(ugh2SMvv; zCjD_uOhz#(<@dws91?J(16o`l)6ub4O|5Q13L%Kxq@eI6{-$kFID`Sfz%II1@f}q_ zK6#EG=h8hyfcUm9N?A#f^=i{uW*6OK`_LCUAJ|38Sm zd1Gq(=lp(WLE&al{B|u=cthecW+JkL*Ks0M{0& zR~NhB6b2trKIY%fyXIToWwkwewdvxMGYqRTwJ1g}__8{iGY8NB%5mwyHz#PZ-GEJu zIz5>se;y=ToPaeC@_rq#<{b5QzX2RloM{^YLAvQQpqIWuy*-rPL z%kaeK&{*7jR{BbVfKD2WCCz}@y&AtD)2tzHn|KtJku5@E*$gORHz;Y`O^Cqwvwal- zcRdC`o}9sRe+8cV0)0<@5XaZ!^8gEYku%UI$_ytdi3GB$kRuNZB9&emVubsPS%3z+ z4#Wc~OSoI3Xs)%^1ot^WmPUC}9=KcV7)kMUgR&}EwHp)sCj!ieETAj5tnvF~mX#1e zH7-!am#}n3C{=h4V2op;kTs>TB+Ar!{<__FwWj?i*2+h&GDx39Se{&5NQ?b?Q@-O| z)_S}ee+bo|RI=Z4fL|?8KUK`PM3_?b$J4FTwL>{8gjGe>i}W{{gklA@FR~l3WcSgP zgNuAS>2EY|>1owLA9|QxW=qq-((oTG&unsY!;!kLev$s`Px!6My*rXOYcR!MhBNrY3EpUd2+8e?L0b>Y=NnQu?3M}?Kq|~d z?e@Zu9V(qhnt7-7n}kAZq(Vn0b0 zo;h<7%0*hCr*GyJ4_n^c$e4r5u@smgH3?X(1y?fHAUpci1w$1dDOzmR7W;4EB*FxO z>%rbWVG9<%=Zhmy+HiTEDj-Kj0B#Yg7ph_lq&a?D281OdB+^@kVUS=TRa_+4otmxZ zIXz}n)`vlNXBf#O&PSn8pE=0WTDzfNnuS8mcPHzn=!fxB7@4Ck-5hk=739mXaj?Nb z;6#Im^jsr+aDI%MNCt0Bjp8kSJ0vuftyGpYUuIO&o`2}5I-|zF8&jE%^Skj#n{o8k zNL1cC%f51Ym9B2ewRP#+d)jhb2jsk8g;59zIS|jMld`jg2)CkNpE_G-Mjh|Tj8XSr zH(ZI~d4+DMkZt)s7}r^v+56sjCmxurc^S+ipEP3PC%p?7mYYs)@TNtGZ8wL3<<1}5 zA+Kc%F#OFygG9*!fj*NneAUPWC)EuN;ZIyn5=O8bCGQz8j5@9ZY`7?+)nHEaE5GWDcGjRee&iNzXDni;g z)UCVPyAMA6>S$FavogFHl7%RCTOkt2yTwQW4a{Cw{YgAd8FTEX2^Zorcpc3juS=4J)N}T#&R(nx*^ReibHaOxRUlv}Ut4(hB^c zM=l?lR~kE3;Y#%?@N92#)^1a_&p2y6!^63FZ{?wj+`RU1z*HTLoYmv*1=Rivg8%m~enXIEKI77HXL6;zJS}>if=t0pO+8DdD|dRTndY;YyMRvBpJrLV zi}61Q#e&|GF#7=|!z*YqI{lx|nEY4gt-9!D-#0jFOGn0d6Mn3;>?vEkI0@?cut_gC zC*KqBB=INF9(ul;aa3W%b-g!Mpw8;z{x+YH z#J){1N@CG7ghyo$MACIEiUJE3k7V@H*}mRGr-{GS26gpqCn~BFflhW4{LTq zs`jI?P&tCpmuM{eErPN$51x!G@egDQBuU8o(t12~AoM;+S`e1BBZyvfc4$KrJ8E5> ze7HrQYPjv>rlG;WHI=Jd201>9{kfxoszUcVId6P=G2HXFYu9)K@81{$qH~UBf%@KA zTlha)Uc-SMF@|K+P0e+;j)L1rqOl@OrSHun=rpQ^Q-OqT(NA(_Wwv49YmH;jmPjN+~+ z9~Q2|0Tl6J%4`)v!Z}J&8$=_IH(4ktsV(wU)wQi2`1SmPNP6LjydN{eU!NX1+;zQB zz^PIP;7rCABW~-_44Rb2aeDp0y~8)2mHaY<+`#|i{=OK zCO6;;%t!)z;GjS4lYe}VJs~yQYpEr7y%{2C0oX@47R_T&_)m(Hv$BSh2Zgtb>xzh> z0W-C5n;Uh@4{^sX)ecF<%zRgcor9nlDIbMXJ}Y_rWE}7_T^XA!eC5lobLW^!^zXPfJU6fotn^$F%MkQs_JLy(o*@_=ZAAU$53WJ9PyP%?VHZY9b|i!jJac zbn!9NPyg(M{d&>SNUl_!cz)F3$PM4~{P%ps4=Le;$-cq}LUz0p&z=#Q;pRi*YLE=t z-an8Fzj1QF_DJ?-_v7W2gtwz79C|?CadQQKMfALVqhC3Z7xSWH@0%vqE(RCgYR5;t z`V-_`jdaF*_QjiuPX%*JC`M1EE*S}n_(v$#YV2kQ1@ku z{oqxFO2>0kZ`;C`{QZjc4tNQuOIN6o-nXxtR_4Ox&uQvUu`5jKaR$mZ=Dyt5wnB0r zv>NW>y^&?n1#v%x?~s=heU%{k%HDpAbgS6Qr&z(q8%IR!oQ6Ud#pK9c{@5lkjBTSH z$>+@T3UHEv`i`h-Vu2;V0-FrlhRri-C>25(+|Cj4ccsieS-kRt6tEr^zNc6!#pIC< z8``g`&fY0Q8s?K7BEy@x6A#z}Zf8S!pc^yH4@v$dYuh}|=vqN@9?Egyoq#Qo^N~#`7g3WrZcuRG`>;gk)F}x9 z77@`hFgBx}cw8!F==cwt#Y)NX1gCfCz22_6lFRZB;e=G*zIR?CtD-y%TF*grBmhI; ziSzZ$`Cx~e42_!LMKvR>pUQ2zxV4tEVtV9qo}u#JGF!nlc556~lP&1s!B(T&y@xr+ z%Xe~d9);%9bxxD{-nAKA;a6mv-+S)jho^0xR@C|)F&2a+(i_M^CCB#H7F-@uE59FJ z_IY!T->$V8OD43Eufm!0$>vPOK@3kY+dtzlO8|gok@EeV)9vSynOCNbJ@bIq6pK;N zd0O2TcIH$#Zob}LK@(FslY8h#v(GwVx|lRc$lLwUyeSo*DQY{=$%DmmK}u73gvpnQ zdclW&|Ku>*pVXLU`OfIdlfgs07PrF|R(;miBggz*JN9iEcT^caCKxq!$26BkF(kFM zn-0jya;Q~RiC^-$X2#e;Xgv0zjEqY4u545M>ksccri}}A@3My(cL)11oAEXC&p&>^~oLFEcW)w;@7;sh! zhjlFXS>wTSbM-;N2`ZAz2`wnE(RgNZ_a)hM9$ukg{}G;`aqRHC{BDVog;M(=IjVW= z^b`Oij~9at`!AQMUg)lxB`G*A1FGoR0XSr2Ss}8NsU83jHl6%GlnKuW;P1 z=r5*$bh>Uj1vkYOfrHOr@A3}vVSJ}#`jE510{+7(geHaKf4l%zaa}C9EwV6^VsMtP zk^%R*3KW#hF26jJ8^<0m$L(p2DN>PWzzH@xQpaU1Hn+DX%P`63uAxa%N;@6^)_RQx z0nlT|k4iruuxJE?5{bRs=y;GHtFlQm9quY~1qGXjSEQiV;GZ#|D9lv{z&hb7nsDT^ z;^WcB(6fF`)qUq=%&Kz5_RJiV*9DC2$Kkl!dYeu^vgcom_Qc`3i1&_-du0TQ>*rgp z-CXQ&Bjx<++^vRz4yjdOX6h?UtAm@eve*wJ7bSBuz2p~_S`4ZI1bk{bu_ZGP(y{ib zaOwNrfel^7)5hl`)wp7FTm#j*^}*u^-1Fg{z2?hz@wsKySrwNW4q>lCsx$VZ?^h+; zG7qQ21uflMdOMhZOZBsTth%k{YR#DlH_~cWQAV@%33sRK;e&#LZ(e<)5Ow6?yqX*xkpN&Is77S5 z6Uk0YK?7H+St!BX{-I{|=kH^?GfFvPFM!;9ghqZ?0WtiS%jJ17x>LHUGbDy0a{PQ< zT_zr3Q;oZJ2k1c@7%P99rH;d(PVT0UD**gh9tjBcWS+&R@9~+~-F| z@HRZY-HMc#<+X#u=v!bpY$<%#%NHx-`%#$PUrEoBgQ#D#wnx2jkF*{RB`J_Gq4>s^ zA`Op9vE|MnPy9bL2SLBS;z_7%v_K7$Kw=JdW4kE#W9&N#0E*damp+xvbx0_lDBBW` z|16l9$e5hAe8@eQHnMlr&cV~$$O?S}!aVYYPw!9O{=Jlc0u$sXRKIl3t#_>qu;i~C z@rD~nQ=G22Mln_SeHT19D8;`eR|}&X|DDjSr#)$V^No|GIRHUO-+RxO&GiAJdAQMW z+*xxJY27G+3C23QIZo&THPVfqG@_n z79{+lQP)^u78m({Bi>~rU=|UeYX#=ki-eHm^%;oJ|DACBu))NAJQs5gY;f%wQfpMX znk9OO=nW5FR@BtxiLq1C_mq zfvoG(-Q_%YI;(}!e8)b5+h>cudj;n;rfW?EVCf*i$QbQQ$DtAd*M_wGjkUwp_{;AZQ5>C*h6<>vNuiDCP}z18(T^ilWX za;r6M8u-(``bXZrn=K$cw7B! zeOt5Waf5TL>=93-Gel3bD3FxD$z||MHdr(VVAlcWtYbaD*;D>A>ULa%-fT1A**6%~>4y-TVG z$Jutht@6V4q~AaDBMr_7GGddj-4UU$Q4+^i@#FPXGg-yeeG0HF4=KNYM?K$7*9+7f z8;@F33odf3*1r;hy^5I6Tk=t&zCYNs0D;^k%O~(74LTl0$7y{2H4K{ynwK5$H z*R)3{Psns6GU%{{-u~D-q-UwL*IOI*P2*_yjSa>3`vv89T$7{C4czZV^w@f2jX>{H z_q_5Wz?;^Y9PXtCmC|I=;cqUgD|YUq1qw0tJHXfZ_d9il)2h{!r7hHA8|%^vs#T`E zwH8y!nLaDdH#^hq7Q5?AP;lpskGOmP_uD(G5DAhWs84f0jL4cEj&NE0)1@wk)9=R* zjEOp9oH-nvz0>DbCX>4p`I5wQIK;Bb;S0;3l9feeLR3mP2rWkr58^*pyw6W%Wi~>B z!9o1%Hfb*Sre3U4pNXUV^}$WM>7AWV^Ol9ZKt=oG+&00M(qHt$U(H@6z87vtB4K3D z3Q0_CWuU8&4-q8mq2~#RCifr&cklO;n|)7+ge;F|a7h&QEX-Rl)O&bEO|> z4pXs4LTG)`?}ksvQ)}J5=e$ji)w2Q_p4d@1; zy4NR85x$ei!csmvx%|Jy!vERyScJc%a~am^CS-c`_XzhKg34=0`p8=Ws(+69Bs z*+b3IDkMnlnJu-9&%Q27($q2H!GT$IAYrEaW!&0uyTQr$W zR7-8#C+T+?X>+pD=8Tx*V3a+$8ZA(MO^cuSSl=0y%%)wp^7FC!lIUk-hWgDTxSry2 zsmGMhSn;TpJVlyiC!SnLWc&K5tn53(r0*_%dL**_FtXQ7&M)$WVFKY)%voA5kKOpz z^m|(8 z7=zKHOrJ~{#w2L(n&MrA27`eJr`@s`r|q&xI3m)OqxIR=mjVG zlyEV$xj0opMlPG!`w}0JKgtX-*q#kl_!w3-iT!4_`2>bFUdUb%CqchOC_Is|5U`6G zTe-JaaV@;}H8c=KK}0NYPOGD1mQr-(Ou){%R43^DkAcIC+pj~j!~8ms`EG}bLmkHjPC%1Y*U8lxo#RgkfWU_xJbu)<2vV6Vq|fS~~T#xEPvS6-G$imdGwS&#i%UAaG6(kg_iGKdPRA69@rDpI<}(2}0LPHWr9lsG{RkD7Mu z@tF=fU`AH?QBBwIF-N|OYW2BFO#YfcKvCQlm16{9$QpzLhYv_z2jyVgxo6KrUDmFA zi9ML^H!nv^4RTP6zg(QZ-X5Yoxc zr^|(s)-KlHmZ-y7=*Qg-Cf4ix+=-f(a}5WSJbi#T$5~8lKO);=2l3l*|JGwZ{Pxv~ zH$5UV7jX{<5LJNR>3dk5P%`i)9K=$E`yEkxiuN54E#<^^oW`SFu@?I6aL!Y+2H^;4 zWh+m9S5$1DlUmJOAxcZ^hQ`w_dc)Qn$x(Sf_4?kfRXqC3W#!=)Lok zFps`JESTq{;=Xvif0s6gVsy|qpjcW!ae5wQyf%AvTwRO8+}FyhvU3}a9fu%~21JBu zR6kSMu+<0B@a4LersO(HOjw(IF@AcdBoH$xgm9TQ+>Z-;QSs7>nS16LuD`Uicz~* zthX?VFfX&ba(p1d3XT^z+-CfKbyL{I6=kXCryYMKHou`wcT`V=A&;d>4g_$SM#mvt z-t_5cI8jFeTJj?;Q(^}9eO?%T!H$62?f1!?y_6*$^- z;(?!8Syxdf78eivu7X{@(8!*WrHA7q{w^21_(g&G(2g#SrLcB9=VAc7g;wzOc#`a( zI4kJXsTg&OW#+lx4(R9C@%Dnj{g$0wJDD44dFh;yyK%{hH5qq`){9Mj9*;Y=jP|!O zX4PA~toMWvh`>~tzOW8Q`6nIMC8?CxmJb@@+`q5v9;74N;ciA&$8|wMx}T=T@yah&kk=ilSyU$obc)f3O6s^dGp(-U$AAN-OK->n6z ztdS@!&M1QBVjAqt@peL5{l%4?(Z`XlIhY@@(Qz`)u5o_w+1L2s4|ruSaG`ZxKFQ`8NsX6Oi)P_%=4=%cZ4 zSTo7`uV{ry!GCtBGuEet#?qIQ>UuZ8?XhPPlFNV|d^bY8N6jrTSc!^H@Yf>2W66X&xG6UNxJFkkN?J~~J$P?dZy0VNVhWG4DJncT>DZ&*- zkM89V(3ozTnyZX{g`r*V^}lYsdu^=f&At&J3I7jv++Ryro%$j4v5n8?J(F$Yj>fld z!xXIo5<^rxJi$|T3k475Eh-+dRwZ=i=X}g=zdg8L4g7wp>>84N0Z4<-3pjY>$fnrG zn|$U=GZf*z1>QndGl^9=P`jYRI2oY=QTXZkzMNrpp}sY_qJNAICuK1z0kl4vP>@X+ zLP@XH%#~=j6jKC88p0hE5sz7j4sJuM4DPa3*fCnKMIgKQj3DxD=}eJoC-(^G;%vmn~?n&yN}9lWO;d`Z2~|j<(<1KV$ed zKCZWq%@TXn!$e02v`=grjb*ELx6X49&Q9KIXN1;7Hv!8+2KV)=h~>sf_bzi4j?VAv zMDoTN&cm~5XFV3S20Sm1mG#awJR+*HQ0o=*#YXs6VYy??zX>|u(7kx6f>w(h_?>Wv zaS`*pCIt}< zF;_avt-5;Ep*{~Ht#kMKL|cx_`oc%;tN{WK!!{m!TYFMt3sPR!<{P{2ErNlQsF}se zlih|J{r2>^@b2Z0)qgh*Z`$^5e9p5#q##(4 zo{qn=#aKJb%ZeakcGQ?QN+#&=DUG&#t{e06NX|Lp{J~Hd0^w|FRI%`nGVWe9sjGS! zE9!_aJ8Wr&Vbp2C%xfkEk%e@%86Hc4h;>F7LBr?-1Y*mcc`^Er{di5z#&I&IAUjg{ zVVBX6cK6sR-=7VlZ&!6k#_JI1$j_8X0cUOnHuBnVQ@5xrPm<~U#v;hOn=Wq`Qd#$m z*i>G6#$H%=W!=#&>W4nhP42qdNL+q3Up%B=0sr@;=0k@KC5atLJ`Ph6&6!iRb>GVU zezN7`vIoZqlJOs|DNoUo@n8-%-`zMnzw|C-OT2j=u#<&&vz>e0TEo>N^9j=cF)dX% zcM^>L<8~1mGqTs|HZR8bmHz!%0tp&gbX!Rb9+LWBaXYTU!Yg^aSQ*kGJh@9-$|dEOJiUZ@th;*pl=AjKUD{vM0zF2Tv4mjI3e%!GK$N!T~-!P1zdx)G%oY+0~QGGNXi&-j5dqx&@ z1DBf4>IV-QGiPDi(AIJ5>CJYmKaor{LEiH8_D(1rSZxkpfRidhiyNT>yTcC#|D(82 zAi>Cyo$^p`o*ETd@O!s@v9byWx-KUfbO5! z?MqClp}rr>nxqqMlbMI~N^eqTPc7cP|DcV>A1(rGi@Nd8F%K=6)C>Y!_assOzQ`Zo zVKj8m2-nY;yZH@knEU1G3sNoQbApzvPztAYoJ6m3yqKNjp^F{0_$U|(hz@AD{#dw8T^Mi;n{>b6=_r|iUWh79+0T?lIyt+S{OBjjso~Nm59TPHGy~ zd07w8s3l)Xo>yF9NFsT6ziN{uLMWrU!In1$D6W5mfTrYCvEAqDQGHKO zeRZz5I!QQQ)P0#t4XoRL?2isQzX6IZ%U|+RFMup_#Hpea?z`HzKEb9giI@0)tdwKO zhjvQ~pR+5?R)x7Nj)Gzz7X^XE%cYRkD0$u=Wsf^W1_qkqOnm3ow$zLx4`)Xu4?PFG zmH-~hXxr4@Fm)bHG$-1N;^y+{!u(EE>#;3_s9@i{%|7+#5x&{Rau4NV9JhZr)Ml9WAMcuS5-TwOD+W<(>S`U~@IGuH@XzJ~| z)ZBK1n&-pYEOl3@ulWBF_SI2sZ%wXOU1a~b`+={!? z;_j}+-Cb|`%6Z>&zVF_Kxn0ARFE-U(Hch}26TWLoG_)` zqKPZ^c%=0xGb8}w{yr~2E51O(QYHK_-eJuK*ZXQ`OPjSRLW0IvLem1ndnXQM_qlf@ zuBLL2f_k^|&2~|n#5YWAfLQJDJ3iMu4bH1NLglGyXjK^lH@c3Ao~m~-h<;I{TjkYk z9Iw|#QbH(ncF>$tRY1mL@UrrYV10hvJI#@gak_zWB;E$U9y|J!# zgNxU-WhX}qXI%O>PbzA@d_w#tOe;Mn&@;92fK_@j>N%_NGO6^BbMstK1wBQ222WA- z&tYB)t$jDF_9US>CO6z+i`E8Ein;ugxBZD=RKQh*Q}z|bc4@A#Q?oA# zI1(dKA-=O53i1woZ27%j@79~Ay;Y6hhcRPcBT?@yLp7H_lR?{e2Fzoh*_W@JaSe`k z?Z)4GUUT!;RXQ9YJ>E8I-%ltWp$M*{1I1t!$AA8efx96A0yw?a+idQibAob0m(OgeB<`zHDyU6kkE6wT%@;@Z?#_{GsLB3hHK?Hs2V*cG&ey;4!OVu$27S# zjmDY>G<60=b*!0ty>K*QPoCSfk<`;roDkT$qo^%QlZ$}G0>Ga zc!InbVOZ`Cl8zJvwCgIyioFvu>#~qqz~#np5NLF$_4ZNL@3}5|*eR;wn#l?bj%c|$ zUP_fOe|D>tMKZ3M+tt-#)+=VCVeLa(`hsYf)=f6C^5&%xVsePt`sEcBddLUXeymLT z+)?59lI!4#&QK>CeG;kmy9G?`W|~BQtp*?w2#;dVJ>#*5Z6|x~-;}9L*pW0I*`rcM z+1*OZ1BJJkeCN&YOK2W8lOdwH->3eUFzzogM~4mh`x+Bonbg90qMY{D%;D-EbLG5# zPwkpJH+cFUMhT-;ncghHo4*(-CBh6}=Ilir$YP|lN@EBXhA1sFgtN!@*4X&e zgw28I=|^Law}wc$Cfr>}_V$TCoJB-JQ%Y4UV&3QBDBQluLbn}-BFd<1JNjv^6lvvM z3*$!n-Z=&-P6+Wo1BO{DKXP?$5@E;Bb<585-91jS$qI|Rw^`okIHIDWR+&TxM|(=9 zdFX%3a{dbe`e&kKhT`|L`}*sC6!9Fp&KCizG>pJ-F@IvGgpQv?#Qnx}Vnm9Ytm=^4 zuB1OC(nOY)MOtc$!cemm2G?7NuN#GFnZh^y{ypX93=^+Hdv`9YwI)L4XKudfSZFOp z(Xxrob&C8uHOT?s|4hyP_Dz1$AF$+;gM_XBk-F(fBOkNp7(45Hu(&|uRskP-^|qKS z@^#P--zLZZeWP_fVVGM_Q5B?~FRX=C!vFTLA`}?kZU1|6FZ92|P>n6mixxx?XJ=)_ z#IsZ}K5XLs|9&!&wFQjNWSa{grOJQr#m8kVH)y;t8S~U!?uq`#A23dSB62FP^>{o7 zSh435y%GP4TKpBG{6`~#wbUQ9#%08wm;XrMXF2`u7wC|q#`I=US==#_bEoFc;{@k6Z00f=qbl zf0OI~xtKryI*)q4p+gq-cr5w5hU*%FAH{>Kis`(BuDC$%&no!rFp!(%!%h zZ(7~a3y*PRvP4Q&?6z=&{ZsGn(nyvmWiWJA3eBZ%zPqxYhKRo6=l@xC{lBL*&ZcbsiF%4s1IO>TpT zy$&69e51W2`{Ze*jpb!dad3}$Rd0_cGNMsJWCB4!eTpMx*VceXPOtSf{UOWU7?eB~sT1>!r+g*6Kz<7w;04jCCj1AC%1 z9PHiL-jsXdlq(L##h6G9+~=1i3ML$HCStt=ZM6U=j&9$t^^hOj##e0fc2+F%!xFrw z#?11A=J~X>@DxtksM1Z^MjV=IlE4PXS=mIywrd=8zbgd=vYxwEVJV42QThD!lo134 zQ@HAwZ>lyzvwWkU8=6y~_@|OoRKm>L?y=wacK9aZ)&1QvqjOev(thf2d?vn3W{W)u zCpcA5LJ=_X%wp_aCPJP5)~cM#!R_FdyBu1cHmSJsb9 zzh*Wf)2Dfhqna_nOn&i*A#O=hS>LA>I=V}if&zWpT%T%%#=!47L?sgg@#=(o9a^4r z_e`$zG{$fv5TVHM5RPgQZK0tSrF4uui=Yw-*xCQ3aokCl-oJ|kxjz8wDu zll3Lzja|Q?);vqJ0G~p>_44LM8F;y~3OYCI?VcSor)&5FO2ZMwYPgKF#sCm9P|y7} zbDGL}T;J&jk7s^*3(l|)0^J%u`l3LRyQG_)7(}tnM}b?lS$>JO2b0>IeP3@ga_@UR zj6j~xAO0rv_BS{D=f|~FK=c%ZcF)rJup`5o>r03Ws%RewX~ge--7F@ZKxR7PNp*hg zge}MZxk))hG#lu@_xrMlILxg)aW24&=BU-#tya{{7Zf%q(Ir#I?irh>Z>225JNA#p zos$q3TX6rNk(%?ychOb#w2ItPdgY-OWuTIRl`EXYn=IIIf?{tfj|7voFJ-Xx@`t@{ z?(23Qd>RQtEf4ov}%;?`>=}K$D`65gD;AVyTj1!@G1dxAPn@Qn4@ z7dDxZ%L6w|uFH}#9*(2^rH5^y=i-JNyr_rHIhv@4 zyf+*jo#C6Pf{=+5$qQW{$;rM2ZbW;JWbM4=p&GSy373m(J=o^ZeqIA4Y=d+ep8LRf z$7cy|b|#bVvbsmELR8&W$*;~^>kB5C+gm{Vpqte+SuI!M_eTnWl{um-80397Bzn^1 zKU{(a1M2fs6mn#cT#7X}v%Aenhm^hsGdmYK)tV_Wgb+%7gF+ZdIRh^~maNN-obzW3 znGvRN|;@h&Mo>H@m4_h4l1RdoLe8%FIVmF0<9S&@K0<^E z^?)uA(Hf&S72=W}$_*awV=`0Uh;acS@07={W!z;RfaEZ^ih!N%vX}%+(e*lD3?A0D zGL-;BvU(MZ#80DI-bjT&(H{cfPwWiv;{ zczIfDo82Sh_=3cp7=XQ3+KgYjn%Ow`SuoR_SOO%WWRdJ@Hz_!eK@yf$Fvc?b$zP<0 zd{$JRnGoE zCxsB;Y=*1Z3G~*NK?+38ylWc1c5zB5UwCaVU-V4A-T(VJ`|l&zkqc5z0vjRX@lgCN4GEJ-Xtl!!DUoF{@h!EEvCf!++EA zTs3T43CfaQ!F(HA5o)neU6BqP>*;>TkD8 zw3&?GS8LKSAxFo&Vxg^$W zrLZF}P0#TawYI}cIShv~b3Q-*N|2;0+szr|gr^&rp1f#2@^$mxEki9$_8?JwU_wJ} z-!-rN;y(9@B6YIe4SD!gUbVRiIAehKBrLR|p319hx|38C;^ggHM;+!T?5$8EuWG^~ zcz0!5bHH9OefKn>IH>s+Q})gBv4=kO!kc{q;osHxJCr1SVXSN8d~4Fnmnb*kRB^yR zf>u9TvXA>IfZk?W3lfFjl*|8m$-jC9)jO1P7j+UHH{xr!L1VkVQV?a{uvaD}&gmk9 z7hlZ{fzEY z9r-2)3Q&i94KBcN#RzFZePJ9+T^KF~3lw~DP^8U4m;V{nZ^VKmepV9Fu>1ag@s0X8 zVmmf-{t$!|9h3 zeEr@iTkz`qdj=xBFg2p8E^XdG=)|u^*t467tlU{AWdD{s2FzqB03fC&RSyRJXnEMw zY@{BY*791y$wBKBduN{a*Lu(-lf?NBE-k?C&}$>1y@@CeciU&O&|9Tayf>{E>v?3k z(RTJ?hz%A;F7V5oO3ILdX6};FjLn>UdFv%^8JE*Y^###V{lzx(x1g}Z!et-~trR|6fG^j>iY)BIu@oHa!SG>jnn=d`Hflr=_d?EgH~{@Z)0w9uj$Uh%tUUQ zp_q6lbo`1y^B3o)a8dsDg^~E0AtnFlT*`+N49n`;tA$Xv8i$dDofKcahSQF!mYf-TA-qx)f(hgSajcPdud*659Abtje>a9VsgfzFc$y$gGbnYwGr zpe{1pxk^y>=gFB{!g8Xzmsl-45n|uPnE{1&uSY`g*HCJ?`2ZNnY)^j|AgV3BL6t<%5*Ativ{DSCHDstxcYj^aP7~mK}CLT z&QI{;AL&Lt7Aw9mX4N6`lFfv5>Lum%QhDk$x-DrZd2kQ(%Y)o4|eW2YNV7J!mP*nQhT1kqOllIIa`6Cwu3vyT~D zZv8>*R^WB0`ZbAYrGvxNY9%a2a=?<h(bPk_Uc3Rv+rI z^33iv-~YskQ~l$|tK!n)_b^5NKf9+KHakE{--Hl#Z+;8KM>6?|5{`efg$$(`<+YS$ zg7x>a>>rE8c&!n?K*!K4vfjtY89fxc#X7S<%T@QAz6legZH?h+Dg%(QY-p;>UHS_AL@I8>i$IA8y_DuSyf>^@jZB&=n?xmiBPx^C>ahvy)6a1bJ`mOHQ z5zbXzp{R%8eSmVpxrDClif=DOFoi2aXoJkDFxS_IMQV7pC;WMXTTS(+t&G^B5g0hHykDqcKE7oFfVA|?o>P$QbgqLM#SYtRC z(XuP2s;`~hMN?dnL>en8uIwQFP}lCD zy}`kmhGvgICZQkD;RWmN)CKvK;xJTHjmz4+XiDYbPX2uH5&rq z7_JGem^1i~fbio3ebwDI`5VF48}XEX(A&b!aJG=lXqGmguN6k ztJ&!Vg5oj;Cm{hV{%BU2EkqxC1_o_ehas9TlN*6SL6aRFe>hodoV3=>jGsKHx}XiD zC6!FP?r%)IDe%gG#bn=WiX9F4+0D?m;Y5s&-#r_sruQ0XQOq!@n=WZa4Fd!m=b4%m z#fyDeS&0dlgSLAkCU^Ah`*x@)8P)-!IY=|aKsW|3nynDj$1$QFed5 zFl0HIw$@y~?zNC<%gFVDD?qWt2airXZ!0im`{y>WGzF`4x16e@FkfPsRu*n1qJ?Nz zNA8@}_zr%d^w<-je>%MLxL+QOxQrq#&Hof;%uB4m_jGTsu?LcQ;~m#c+r?!TjQU+-9jO77nG&UhceTi@N$AGlUdw6Tdhnt~~Z@YwuFto^q%I zuuFCEa>|OzA6wE$%&*_M#JXSl5#7TZ1phy?+GJ#ytLcQd^^R!F*9V%X0I~IAa&p)2 z<*O}vj6!@jgroj2?zv}1X8@PZ%4B&HmTOw*j2vfk(;cPm1i2A48g_cAMxbF3s(0!OT6YLp>sF1xMnT^1>oDi*>N5{aV2_l{?eTE%qj zhdgx_d$LlMMD-q{`$qZ;)DF04Zs3pHR8`-i-~}qehxvz2i}Sym9cofoyw#1$9SSvz zQMmN4v`?K{7|YneJmg+>ad8f(%?9*-y{=Q2-EbnyPK>S?qiwY_2?vs3{s;@K=7%i& z5T>++LOUFW!F9qxy)@%96W;@QW09p`$sM08{4oHuA-9s*wey^M^51q$zB_&Ab9kuy z+|3h~RIchNk@mh5hTH(Kn+y`Dy2{tlTXCIeYuS-Nd>6G5eB zmJB52QvV7YHk3+{yyzQ5OxJc^?RdWxrcu<^olD{ON z%lAE!*XA1y1Hiz5J(8lWy(WRwsE;S_@=f=}faP|^Xl7nc`@G1yPM*73;NCD2+ zo6OR@daw)4Swt@V0JxUEFMZIE@Gr6AHs-4mcL+gQa#7KeYijUC3M)8O*lq6HWOg6@jsA~*U)kj0V8Y0=- zRmuNFuOi0a{Vqnp@0hcSp2}o{|CYVl$#TB9_U&L;Qc6n}ONuQFjZ#~%fx?YOs7x=mNnBR|#4tWPD~Agv)yRe+X0Bs0 zs0AaTB%WqBG+5)^HZbswLL<-n;*z55^FgX9dp2cRM}pFo3JFHD9Y&hmkqukfj???q z7cuS?M+6BFXd(|~r?9LpR*!ATanTL$&SX@N_Zves^KlrN6#we6o*V(b0~y4S6_T4j zR&CvRK7A062f9t$xmcPXFH_ECFsK#{A@5oifkmk@Fxnfawer(X(__jI7XQS+6*_e` z9Id;eMY+CGUod;Wt6AGzUfw(TOXj7G$3bymysLI?)EAT zWnnD;kOl1pIq9D!n5F!fAVx9MQ z$@Mkuw`TdY`%a2%Ttxf(hOT#EfG6DEmttUA%2`raav?=7bTI8J0Hs=@UfP6Eev<>D zSbpj$r=0=lk1tRB)EuEuZuEGbu$@woaoI-#JQ?=b7$Pt0&*RLVv#jT8v`AR34n z)kYm$%!94)ra1tD6Huskr zq#v(X$AmB_2_3)iF;ERj#6)}iOf)_oTkOZ9L1Q-uNU)S^-=b!GD@R>c`k)|>A4u!; zYtB_|1OQ5g|4#xfkMfE=jJGEGEj$nr3ED^XlWK74xM{(PuJNNJz1SztgtTMwP+U8W zF#&M8V_r=%ZZjGsm;#6h2u9ku#05%6^RH0lh6|G)6Jf?$nG}oRq;;o=;^Zk-5c)O9 zOG^{L%ZOGT9D7b@U-@TK$zB34qJykLdP+V;o&y78f>^b7xzB*LfrvoREgK6EkZn+GPm#QY6OQLwaz0rAL=K=8k{0E!rzhb%1)Y0U$7 zDgz#N6ZuzrXg@qY=;#v(bp?Tb_C+E-2j<>Exq|u}^=P)MCc<)bct{(kysq}`Ijs@P zF7UUhNTz4<$aXI&Q85;lmhVSX$v`U<%If=+d2?{&v9VHegOou|jwmn!)Ws-Q zos+yIyjH8JqkGYb-z&=Jr!F3zheC3^KJ>LF4<3m0Qf4ytr*TPT$Cme&eBz9}?uwA& zF*)6lrflbi4)Xw{CMs`t{qm+$h$bp2qAOr=nZ^Kw2(!kO(cgVf*ge|I!=XYa$QH7G zgPkc@6bLj^5iaiyiu++}B$?wK)i4Ty*h?#8l0if59tmjK+`+I}h~Ct3AJ<)84R)f9 zn>;jJA&|TT`E|W1Rt~C+xg#S520RwNIrx6);|64UgTey1<4`y2eOdXUs^Yd6WN!AL z%Hs2%V&bXnx6&0^sZ8$Zm}Lq2})Hz^~g_~mpY$nQEV|) zk1aH3({gy(kk`+?TKg{;=kw=)&rW;*+cPcPQVo;}<-#!By4r5JQe}I^q=k66{<%3f zDjbZPsOM*&+Gu84N@%HH&6Kj%FP{CY!XePe0Csla?2;i8vyy?1^~}GJn;S*(3T7F% z*V3NyKH_PA-aAq+{b+y+)B_~{2_M$|9fW4hI~?9X4f&8)n->Fzk++|P3|{L6Z8JqO zo}a#T)GwhQ+?(ij%0RD1ES!`;1qNZp9r*|Mlw6^+Le#ZDD4a|BP=OW>q47hSY$P+Q z;=$VNeXW66H4P=vU%;fN6@3eo<#UENzDAHB`bm5sUknQx^H}o@;}BIuMD9#Xya3Qk zTA{iiY|r#8W*cu}VWAM`Dj-0-LAS~=X?$X+fqxU$1Dw{oxktoLiYpp(twr{f!Ryd< zS2248Jupn1_Rkv<$O$zb~SmiOG)j8GJTN%?lQ=9zCmUrgq>iF zh7qI>oY#Eqf3W<|!R>xU?J%x5r&V?Ie$^8DqRX2c$I9mP_EU+cy_SEH z#A(4RjO%7>OXET%(dD7^js}sU7zzY!7Xp7GYAr=YRHNAB8aRYIypd_%u!m+^vXtZ~ zx9g7Uin~O!F;bVC*1XrlD%`;_iHOp?T}A z@J-HRVh@@7Szm=QrN|ARmsxq#A9(&PJsu}R=BfW|!WCR6@sY}!ZM@gIj)3W^^MM96C)K90LLE9@`mNs-{y601e$qx)T zgN1CdMwDrj4sPtbAN7}T;8Sw~wtudztaWricz!%37t8{AVk8`g`@2=}cf8M#LiY1? zowT*l<_!%ULUIX_&l=%=d__eqb}fZFhvVNRM{O(ic%>UVHv zg1XOW2$nQcn9&4#b$nPj#AuUmXqfrEr4uD~91k{c@P%%z_1GOx9h746XD4JNpuzAH z%$1JK+megY#B}4y9%raO`0rPK^5(3@O5?nF1~WUPcaPYzvk zcP^~8X3NL(*tB#=*8?4yB)iXzUZNW{cKR)-XM}~I#_icaL0A1J@W`M_C){bnI--K6 z?rGb}j?*Kfy4;Bvk>B7*zPB>^R1_JBI9t@Z!b4sv6YIzr;?ys-dsuh)9Ldk&7=7|c zw9c;}ZehU!2{PmYKXKeMoskReaka(UzV<4}8*Uq0A9bTJf~OHkB?WLjiwOk|36h(z z4n^Gf3tvd#|8`4w%zhP+kF{j9SDv0t2|9i@glH}W81q7&=gF%@IwqC1Ijw4pc4m%z zu!PJba?X{7xdn?EW|Bvz_Y{y=cTw#wc*f4KJ^e87>E|J(F1mDq6)Y+A+vU`ce7^S$ zbfleojuUKb*y4a6COex&H4OQ`YS4YSi;=;rOHcSoMXR}if&f4UF`qMEx|O7IPTzW6 zZqpbrHmZ>w5?YlsI}m=wKmJez(P{g%R`HDmES z4JrKE8UjpNM`o=0rwveL|BF{@oPnk6~UJ@G<^I17wohn+3Yt15zYVA2d0(fhld(b*!srJ~Nu~vIN$!&i-APsP95*i0RL(*YWK|VVC`8;DU`NGCNLqmE}F2{qIgs zM*5j1S=l-ZQ_dqonL^fqaBa8D&CObk{qZMq-;UvD?>tJlNnD_X*RrRJdS@k@FDcq( zyG6(GH$kC$1X03JJ-V=(%}8}*B+~4a#E(r?{PX6u(JX|eZaVC6m30Mo${f>~5qVSD zLIc{YP)2akD<^xFTDAS6TKSW!RPX@1^1jjQJxzfv5^vjIQy+y=J-+VDm<9Ni=)gB)G5+=x*6yvY1}RKdu2;#i!m^*eAc>%6onZq$`d9sR7|Iy-ey3~pR) zVM&>?KStw$-e8-OfnE{u^<7#AI#LzJ!u%p-{{Hr=TEsv6~x#@DTf3OF1#>$D7? z(`Ck4|+IV8X%wlY1p)t9tS~nmvoSFFIlWo1euU$|I>-e!u+`HYkJN^_Bw&jHaru zS9UIz2oW{0!x2YB(>yd#3*58e@YSI)y zDoCdFnq%zJ=|$pwl0^mfE?Ph0nuEILXsk_{v2AwvDFSp=up#u|=WX!7;?fpI0BS{C zWXdYDHi%lM@S_w0)zHDL%kR1}6vdZ=iN%ymL)at_TVJoM`1TQL`QsolCCV?%szT}PlSpMN{%d_JZG}Q1gphCX z$aR#vl)FAhsYE!1|)nWbx4Bf#WyQ z?Np$~>VVs5xN{Ug8g`&`cufu%u>;_1@j7n|yS`tqfP72sCOZbBX&hJ)q1=?r+M5~n z0KwvMg(EpF+|a9fzeeJ9*)ti5QQJHbW z^g!ev27m_o4`ycG-Q>^704to>ieCx4Kniz$?qL<1fIyTo`*^=GOn3yT@36`fl4St* z)&9G2=(LBgp*X`4X!}l}72lE>o7@jRqcVNws` zPHCW&Bn>rI<8%E=ja=hoU&p?+q>#Z@?fg`^kaZUvNGPcm^6N%T{`)dqb!PyN%mlK` z<+ZNx%Nr}3Q-Ny4KJ%S~)33Ba!wGG!Cm7y)W`udY4bP)o)slyNsi0?9mra$PD?I)G zkl+6+_k)%GcCRbE$|zmq<_YWTj{Cusxtk*AF6@5zm$O4=(8{eT?x*^Nb~lI&>N@lL z5Q0JYqFW9ro6oa3uH3XenOhd7ld^YF^W z!5Q|dz2U@JQ>(ll89N5@PDLfk&t2$aEw#&U@dc$>EuoRaYrxdwva~T{Pio3{5C|EurrvHe9hGvKx0B|tqzpIYqVAs}jh>dPu;;fWW>6}(F zt*JU5r9Bh?{<0ijYCQxQge`)!ERnDowepo!m%J7QDP7N`D#pY?K+JUyC4KemIw~qz zEEJLntw(aF!IA#0bB1seb0mlE=xFD+>eSqD4#+Vz92bt~Q{u4b327uhRpfV;1g}oo z6t{J{kKrzG-xdcQ>zsHsuVeKT+WJDTOh#gv`c)cG__cWi!wW2I?Ud2qIm#L@!3lGwK zu{Q}2pG_}lS?;`%w@4`1L?Bjuz^b+oD(%Kcg0O42W1_1uaR}G8Vx*-@=>(XibaxAl zjCA?U``LQW>wBGhzEcwJd!SJN(TW_aUBjOk%n2-Z!i|xB9U|jmE(GW&6Z!BC<96tR z+<6w0(Az64!le>0Q633DSzQod@&3GhqyqzM#xoIhyti(_BGF5M<29|(zq$ziEU^E6 zvnKhiZf?N!E#|NeTb0|Ix`{3`?)R{D=CL`k@`sq{Eq@Q(Imu6oM|XEZCEvN|8? zselZ&u@p}j|Dk_wm+r}|5oqX-0^zCxC(9DNBMi!Q>JrwjEyF#Oyo@i>bl#jnU%sT* z=S)iCNMvJbl2!FRx7A%EDBZPD2IGNx%vb}9-wI$OU~T1L23l@Sqd)%fiuxbDE>F8> z<$Om!-XRVV!#EbS7eoj3=5@QQTM`ju-n9N@sieZc{m2kWSVhBt(i$sf%=VM;b?Fg(64O|J4nn_$ zi4xO`A+6I}DA;aXxWRuPMdUSTYWRE^(kbgkw*E@1B_ZeobVf&Gl3Zv?zhSB6m9FD2 zRA!&WpqMHOz8)u!hL9n>uQ|hmcP{wRNw_Qfp&jdo{l*Nidm6gg^D&}0`^{P)=b~Gp zgpb82JJG5ny-|lMKk*tzpg96&K;MB^|T--hf|7J9=IhC^K1*LPrB${ks;eXCL61W%Rko2X{Vql=wjM3GHzvKX#IjE$m< zm_a<$Yez>$NMD~yO-(qWgct8`IH{(sCEfVwmds#sgnYIv%@xVvy#x4Hww{yrok$O5 zkrJ3>zPyaQh`GozHnyAGSeo}+%h`Yd@eb>-%m;E{B#Jp$nLEsMlOWy$6S%*>9?$02 z1GSYW_}9d97sUki5VuXIPTs)P1UcNjzBX>HE{^qZpy?+2aaT;hezm6lpn1uVoRALR zT*7Wg#;jXe*+uM>dTK!Eb`QlT0NF=Iq|4LHH=}7t`3M=PS%O=lm!sTo#^X+^#}~f; z;@2tLT3fnj+KfugTosmnaVdyks>(;MpKW;cQ|>=t-kVopmeNRE6DVRd99~2v zBSL%;V{ntuQPxw7W-3f%;If}-8Set?+PNdIAM?KsfGetdQeTD2rOdw^58f=0XI(P` zI&%b=*DkpvlwG|kgCxF za@wd91~9vm_FIxTD(UpEl0w|xc9B58aDxRf}lhaO>CdXGN ztrH_mobyP60wb?DXP&_ioMcDVG4b{WuyF^n+j!Eb+5vYSh#Iu<&Sx%4Ayc^Ts%Xej zc-5q^?sVPN+4?^%EE0iZD%F`trAKN0pXD`vKYjWJGfJpYaShs*5mU! zbmd__` zpXqHw)H`edw!@WUzDKSSP^Oe(I=K8zMl&4i;9uJfNIHRSACyqZ7?V|HPkH=2lz`5 zBuB5kuf1Ivoz3Q%xWxk6#0Cd4rA{h-8CZWHyZRdb$UW1yivL`bmr&H;7^41Nt6Z1K zWKrkP{ff#ynCL-5ckRUUU9=P146l%jMW!+82w^PdRz&;Dv^y^G!bAR`^$xCIxC|`O zgO&|WzdyNNyncu?B~=`b@5`XmLjGViglCid3aBMAzC4{*768qXbO-JpjM&QP0VCLN z_$MJe^00*2oNOr(`%XV*%~NvIVuBF`FPkj3ye1bXw=lXIMk#_-T>H%(Jaqs=^|wCumFzRxrs-$s{$)fC{OSkhcK2Yb$0w+ znmP!U^%^m|f`+hLB88S~7<6U6KIdSxc@Ge}r^F#}AZE;V%TPKqY@zw#%6HD{&||?6 zVhRXX7d!Se;r=QCk35T}e1Prgfn`XyC2_k(6vI$RyENY;MrSMUg`fi4+*g4?l?Vsi zU*eb>Rrt0sY9uPhhvFv*g!Ec(1c$6AcPG@xN``!p9Eh~3Rg+Mf8L<1*DzlJWl3wO2 zIOr93euN^D{z<6?vaQv?Ibb;Um9HGC8bkSm;8g?_R>0bZDqHXequ|#YL0*=`>SY* z-#(BTV;atDuCpnM#Dv8hDLLZ0drA7K&!|i|ZtLq+piZdpOz2( zC-BOYLoX|ggCV3|LC`Mmbg+vtqVX?L_~szUE*WdDyv8Vcp)-N9u&qNTrU>d;Dc^K2 zW?}I1TYvFj8lg#$R*6UaAJnyDyU-4$8~iR_NhpYA@sTW9PvOfPCuMxsofuXCN7DZM z7L=^*4eQh{nOa}=6^^9M|-=zz{@_c0O9fc~_?@?qe5Kd<;izVpWgT!#w_ z#@V8axVFARsJ$?_?UmJsahbFLH!)ZtAf2r~pW4nnDT~|)oGLP@E;2K5L^aX~XoHpm za{m@<(1A^SbJo|9r-WsrWM8t4H^?|jq27SpbU0!KuhPCdtY31^iA2Zph` zE!`rAMdc7bRnk!TNr=k0KdJgA28n|2%t0;L!}YaG&2EF^-!9+{a4jVc=YAejV8Vk_ za)V~_W0viEkj+I$G)*0dVBf`cjEj5Hu(Ez)`wIG36A61{2S&o_`XiL&qx>s&i|%MB zoDc}lL5;Y%xmCi!TRWXVXFHOX{PrEIITD1h&&fPsPh<oT zC7aC@cXGn<4q3AIM%!kDFgti=jz-u4a;*+KsY1;=ZP9GqZv4W{J01fii~ft$-Vshd zw#X~}sr#K(332W*4&kEyd24u!3pO?usIursd?i_U6|PkD+yup+kSPoW*)fS9{OTnr z_<2q|Vk@mghzY``g$T4X+-E)`*3j_WO$;`sWm{-p9W*kEi6#+?zz$F~u*whj{|J^_ zzo!!Uw()h~To5hB+1-^bOpJ${3k9JoNZe^oTF2&tADj|Dir~ItH!bUzg321*87E|1 z&*E4gu)7f{kjvCqcB$j_l%*->gW>j(x8xs0q@#M%T_mr7K zRb2@<7%)Tgu^84;C4E>gtPv1-a)e{<}4 zLpaxuhY=Eg+J z6p9k$t^bKQmtu#*W9dWr1cO+3G~Gvy9o`}Wc~VzIj?3Vxt?)9q$nROaQ$K7SJ9<6C zA*u)qwx*&;2KkxxA6nk4P~r5%QY_w#wNp`ImJ106btzKVA9j)Q`j00k1vvZxfi4^@ zD5ue42;;2zsix~m`_c&+1OO`11{ih-hy!HcY@~QsFL;yqNbBHXpRg>}FPPU>uq7)~ z5GEhXLU+$!p-tH;1n#L6g`>YgRFWf1b6JqMsCX$+_c8n1EcWB3Jv&MH<-;Q;fZ{1j z?ssFa03F58WohRf9hUip^7s1b#!O@x#ENHo-SVKRHS4^A8ZoOTPFZsUbE30Zqq+-P zp%9tzWvP4}Z+>>?zbx5%kaKGh%Hskda1KRqyF$<{9>oqwK$>Lpo z+S~M{DsOk9_8HVZ8Z3Zk5*ykOIqUIjO89l_cDHHULcnyz?YJM0;-~Q1QjgjPN_x}q z2FvA%vHDPMzn!tV5T9t@@z(0T@z zdJSUjhw&WxC!7p&n1|xy`#n`AU1sYs%ab$1op*0-`hFuH!v)VtV(Hi-K$q zadHjT#`fNfGK#l@t6i)fnT6j z_C~;0!w!h{iO+zuD4St^@#`@y?fy?|>1FeDg}hmbgOTi=tO1}aK9bfZnj8`I-d^&O zVgrxC+A%pL&Mz<}ZHW-U&bm!)%mWMSG3$R+jW&G;$VYzrW&HR;O48HKzxBI`)Lu~FgK*tSt@&exOo1>}bhY#=Sq-1dESeWOXafxoM6k=J{ z2$c9n5VSLmb4t0uEsmq|vpYwf(_DD=lnZJ25@IuhmI^-{E{Tgqk??j9P)wPRGguR4!B88C%#XC<1vLq0cSKWJO)c+PPYM98DJ4P_AH2j435_)a zDbwt{>fKleL+e^)Z7>+&Uy_T(-A>20&7A_>Xrv!AP7NA`0+?wfdp zF^W1Gzi{Ciqj=v-(p(V;QlS9}3MIBcMl7B9tpKVV+Ih#NHxfxgo|p7O)LKD;CWV@i53Q8KuBP`}RFD{?x- zsd;9AEB7PXN#A+=7vXPU)1$&fI?eU17Y!n`r;Y2PUQp&qB)d;HztwaGxir?4!Q&-3 zvp)qVN2#E^j>{e62M1kDFU%=A@9I$5c4X6gO^$*>!m(wYOVbeUg5;(ecn3ez1oKNV|m5eE-JdIzHRo+R%!-wo?-r+@74`G6PtogI04Az31#(G_8| zVmM)){hlxB*TA?c4e(U!4N-PD4Rzp(B;!?kChhxpcnWUa;2aV+-z;G3gsK-ACtWQb zOsJE=_|h?Poe8DIUTIu@@Y^IuXUqVpDeqROTVs-s)oNiX1g&&9cXsVP)l|NUF@K<( z0Hr4UfQ9QfPk%j}_IVwePRr9^9>y%V3%B+pwtF0Tx+|AHbo7=(4RLnVqOPZ$jS1B=890#aX?bs280ZkHN=O`P>w zpe(O~Km?y*x!hIcf5HtK*ld>;J z=5w0m;LzciC@u;F!cdR{g8e}tSaHKD0Q%9ILca|1Lq%bNA8kjyh(8Y*T4C9a-rnN0 zr^RgD2hWFE^1IV2yKkHqL9Ab;2nmmi_3r+m*;c($dUE}>JAt2krI(wv2E7hUInC+2=e?0cr#K@kHg=9VZ8 zrqiftM(vyDz%-U4j&vPyV6-``%OJny+90!4ILhagSA92-#Dbs2Qb=qY^#1OT3ADBY zy=#6V6GN5;;+9v)Z_HsZr1Z5mva&>8Wo}MO>?ZB#S)Tjx(_a-DM#hSqLHZ`Yn#pHO z4qU^Mj`hSMt&kp9-;7!b5>@~x?A`pi!m69?9Lh$%3b3rN=0%A`D^VPZV--v#?Os=B zPNzew6UdCwib`j`)W&b_{^aH4;U{LksA1N3xXnCT=gh&RMQ&I@ymY+do>|Yp2W%tu zj?aL_jV8^&J;CZ5=16xNh(hHov8sXbL)0wWDvFGU5V+!wVm~H_(`d!RA19?*ov8)N zTh~ZimgPJXqDyw02k#navWA;p%Z3GANPg8=e7&FACrCxIMshSQg!kG0IVed+u_Yv) zr?8zOcumC2rKA2af3W-M2|*n!2Eyv`-sFs#(VToWom_H+lQJ*0aR|_O4LUl>$#44u zUH=6&Q6MTu06!_)+2FUJ-6J^3Mu=$vBHK}+l9Xgz1x7xV%WC#Fv%$*=w^Zk8J}xf{ zH;)(k&)>)w-)#Gksf?u-HDwhK@&5%}d{@}Ul}msA0iwzZk}DG^&}ZH_Pp!Hv%i)rV zn%_|%IGEu=iPT6ZsnS7~Z4OJN+e9f*GaSQ4pBp(}qXRp%ovQ%7wQtn7*Er>I*LN>g zt)(7FSWF#>+{giFZRnMV_{}j%A7{&yGfD&LOXsn-<^ni$dEIi%%GXAzBEo8@hATs( zfAz>^j+0wg@I*eIwg6^fewKeFsMT%KsE5cDV;?=Kq|2wU-)}N1hfG|@1(6|jkm zbyttPM-c9)}iO6o<3;n6B0O4O%ix2G5@*P}CS9&$o7ks;mx>>@2FV+@(u=}IqQ~9&? zx#s)jFSRxO*P7VoikuzDc|IhcaXZ*%crqC@A#gs>c6 zqTIa;xxjsBP#m2buCp)75MI<@Tb{!yLD{TT;x?5ck#ZU=@B*Ax0-X7`SuAU!#99<# z-kYf@*4CinJ^WntzOhPnZNBa{P-dewuw5&M8MAy z4u!GaONOh6^lWfpsM%JF?iw@vDF7-0oxxBPT1_N;^&|zCe!K38j zRIQp;;L%-c}vI*0s=o0`Xr@E7& z%R_X|iEH}t>PmkqxtB$LVt`+!X?2;;2B-JqpuBcFio>tzbBYT^mbrR$Wp|&g2@5;+ zKVFI(76ae<9--wIm-!DMv&Q<{Um`OXZ*KYPL*G;5j|td6=S&hfNxFT4)%bq(z zExTK?IVyv}ho==%HT2^CAb?X#IU!V3H%;9YjZK_#dF}`B$6)(Xe1i~r&-PE~ z$t254@$!HQX0^=F_uugR}>) zII<1bGpZ1RJ*`DYW`EK<_V6oBbnp05F7n%uT}6qXA%C(*QD7zj5ZRD%yTRw@z{}-0 zG;a3umn`GTw4%W3COVD=hTiA%a4p{fvT^YB*zAg?!H2>`60q*otVLwF!@dqd4H(r< zs*0Bkw;OWV5uIP(h`WY(e?Wgx5Zx}GwZZfG+@V;eiFRIEg=nF)BSgSVoexd!SD$O? zBA#hukJE?HK@Y#18x1hxdDyi5cUyt8mL4DtFkl^)>QlzBklQltwl&qx=nD(uipGtLo*>ie)O z@wH~bHm~7qD>U{k>9S!{*#E+|FbM-mzTH24&cAvY6Tvo zO>F1lt=TaI6j=!TS+5OPx{HK?XJtqnVbG1i|mogV#5R-D)#apJ9-`oN!IWrXggMAF|e^#mBjW@ z(2peoG`oAC6_ivc`{#|1{X|&2N`^^T!GIca+)FC*hm!4kTlgrAvzJ|K{x2l)g1O&> zdMfcvo0!b4gmu-fnVds!t2+tSzYb#%GnWh;GmU}h+jkD!*HtrTPTxv7MfPkwzuj+a zSN{#jzk&6qk8($&s*NX#;U{{brUlaHED95E&e%j#MyoC1_GkX^jpwd>Q~~BTWfwly z2J5%q@g4X{pamLEe^u}kv6{X2Uc$?7oJEj7zQzoovA4=f`c=N}BGTR!e$uf=Pr%FK zd56S#-3i6#%9yGANh@>2LeDRj3p`A#@}ah9w`*vvA)Vc;b|WyM*LB6-$bUB{hdI!( z`5Y7F!YlTy|2E1uBet(Lh6&n@0< z8LYVUmr%HKv)Z4CTE!2T7#*%-mP!A}T^5BCO#H>I=bHbrWGA5s6#Q9&mNkmRHr4|y zu9O0}{!`-^>o>j#0E=$9ev<(H2+KKr#7{CdHUC!j$D~-l3zQyICt50A?COkO{d`h*Ys@!{F>MFV(&$2Ooj zcjG;k`_@+2;%KR;!5belLoroV`&WM7kQz{E^-tD)cq-=*fbZJcF(7WD$f-H9Td+o` z?{3gv41)6S_I^Ionf?4B$GyFb0NsfH=_~9x6^`|OUruMgiXNe|ZOoDLa5H9d{09CR z0S=aE?HkEbwF1Of&;&~ZdV0-}i{xPyOw|2&OojN#0BS+x#}@#8$T=W4Z7 z62+w-sZ@iBQBHx$zmPCj8^5PAp8e_LAZF5sT%^noa4wcPF}g9(KDvsfatm7$yE!tN zH6hRd)h%z#9iy-`U1!|^X3A(@l*fJFG?AvVSGy{^-n>kauEqYaCx08OTFmNf<2I|6 z=GE3+%~gF8iJ|)s=fyhoNF$iOfNOQ)3`BOsgvbv$7#!i%6v?CI0XX0lCAp-_#N( z+o{H8l{oLje16WAxse(W?*dYn9Bja|p9w?D0u1a%Cao!VJsHV$R&bQWf^<{h-ngN} zRd~n>QRcMStvcIOkPdfJ5d~j+Kuu6_LHTzF4E)3a)EX?H*oCIgWA#PRBgjjf+=~;? zfvugP--(xrxz~Lf))a(-|`7` z9WV2dV|86E_Q)#lVM|wA(hloPD*lRpLCveLhoN@9XGH?L*OITv_zciOK9~+wExNb!@?}Ad)-v#?S>WV!Khb;zM5a)J8dm1xA7RG5iEl7HpK($?gus)~Y%rp|;D01P z{c5n7uMWR{L-XO7jW^b723Lv^+L=i8MgI=|0@6T2&kNHH(LjHNm^9nFN2G1VUIs#t zEav*MSg4bPvps*n6KoGI5(PUck|fP%xb`UB{VyhruXSajuCjTU)%g<#x9+vZb^cKZ z{}qM7#}5!Fs-M_-czaV|lAt*l#5ACO@to|&42h|G{r4kXymbD_96&z-&-Bt(i=EaZ zO})Y+GF&`Su5X;G$2KhG5p3EUIEfrDKgAv&=U$mHNe#q8*rXPiKc0eFQZfm98D64( zX)&3XqM#Ve55|@?2~_r#97_O#-&j=O;zvZ{u&*%MwWYLpaMOLnlw)r4-;#{04?`AF z`jODTILC<+C1O}4?52bpB^3@G{4}LDPH;jDq9xYJwrJ#cTMfNER?ek*TO{1l(C{)6 zBGJM-i&iAFF+=Xzcg|;6<&F_I6iO~-r-u@yYFHpb)vkq$W)dJO)83Klr!n6#SVazB zL3qgwJDC?iU{*biTv_U}76g_Vi z6l-4~9|-NRYr}iFB~jLNE&HoDZk4z$A<%za7~0p+O01O3%*<+1oj5r76}vh3rt31K z49(OW*XM`N7DtdzAMYym;I$RAY7tw&R{dOv>U*rBF{6#xT5@V;c_%tCA<)wWE~qxR zR;Ys%CX*1+IDH5Zg#|PcdTViEjX{Ui{`m8Ik_k?|?=jR@myjdpzU-0|pNAYxipw{qurKEqjFT7E1iL{qao^R0_QOJzC6MI+9 z0ozFR?rMIK+YXq{?D@9X)AH;{xgT}FahwK6?C zlHF~A`6=QO*?6o0p@}TzhPD%?#d#lbyr5mv_3;4UdqfnWQVa)SFEAVcUSKK#5TctR zA^jdpz~VE#2{d@c1s4MVYXJZV^mxemqLXk(b|uIftkm``#q-#C>Lyj za9$x;8Be0BuMt1d%h`QQPuC499vNjY<*grsTD*n+{ys}v)cv<*7@ThWE$XIDJ zeQaD&N_AI;en3f({!dXW#uL3J)9~tFe4gWSFWll5sS+aFOQWBiYs2L*+SsU}YW-A^ zobOdKM2vrTm-Oi^Z*4{o(31GQFptJ6@oOs#@b6v#LElL9IT+G+*VhqHNER(}MlHp-t>IfOsbh~TiNjV^ z&hB-^!c}V+vI;Ygs>bP8L4!SrGGQ%^@)FH?q(9JvImroX6!{Ge<0DAhcX_#(HxE{2&_>Tqc!qNMYz51Ezf~517rc_1nP)jvhViqpN zAnn3z^BtbTp6~1`D`@^}8ZJ>qd_TS{vk^7Vi3 z0U;xuAHCbYmivnSzxm`}DBzVts&j>(5`kXyp~4_0glF5@c{!*6oN-Kuadi>U;oXU)Ft0YYMAt_5a@cP7w6AuUPn#5mAIBw4{J81j zK0mafFWcH!(M&fwb$KQsjZfiE?_9fcH$sRptwf!~L@tV`;B1m$|7#EyPOeKlzkrte zBF4c31Cd1;-n|Pqf)}oavy&BR@GO>W*moK=Xh^}8ibG8@F$AI|Tku6L%R=)Y);m*{ zgeL)69l5)kI$~td^wVoTpmA;@%Rc;0YMwz;A&ffWuhXK8>09n(0(whW6op^>$>h|8 zd(ONs(Q+&1@1M&GO)xcmxW=ho4nG5lK% zpGq4}hq@%kuXhfu>Ory^+N-ZPRLV>t|Eb@**fE=K_D?W0#!OllH?KgRx%9!x5nR)Z zzFPO?Nab~T9MQt1FDKN36fo%(2mG#tbbj1Do!%V&I4#7&=U8zkq7VG>Z) z>w-hQl=Tw%%VSz}uqSnGRRanAhWFXyKEzU)xg8hm?Z!h!SAQEDcTtyB-f5ujMf z93LF5pk+qb(B!n|l`~6CnHr02QOp!BwowpFIUAR~$IkeW`-9;KXP6-r>()E&B$gJM zZ#=;!ZmpmfM-uNb;e%kz!M_W?YKech=R6ZC_GDSgPA?`4tc@GtR@VrjnvL_Cg+@Uc z7!&k0=8pjwNwE0_-Z-F*XHY?$L{-MFqS@(w)Er}3FyGBfG;gy`; ze>ej6HJDYuxGrs(VfiKq<%{}scWb`5rVm!k`I$HF9cAj8afjHcWu zdW-qpr)U9xllRC zFeN!bXN{tDm)3q+*FbSggx6?cVToycl)i89_a_||X;&*a^Vb_pa6#hsFm1;|Fnr9p zP85tJgUyTg|b3+yzPIxoT89$VPoALJ!SxZIHIuPW@NIjc)Epy+0s!SDNR}nZiE*m`0y+;& zW7g$m4KLu&JXM)kO4RxcR|m5A;z7^62W@zWo)(RG#0Yl@zg1MIjNz;*q4Hj>?yR}| zQ=&Bg4NMWl^7wIG$PzY6Z2l4uF!NH23}QKh{!0d!Nezhi+&(-ou^4Q%)vPIt680{P zF;0Y~r(ydPq8>2ZK60x`L3swgvH3;#Xxm`>s?kkjY?5^w7NOf**B1nFwM=$=ajx5G zg9H2fln1C`2-*c`*)q6wf0O@W8uD?Q4g*4Lk+oQqYIWu!GnH6izML9_5_m?Eo&4{! zca(;vBr-5j+~RymBby_+kKmxss~=lFAa4p^X@Zm8qYC#w>|5+nG(`m8GXTZfjUH$$ z_EPiq9_jR;PKC65E8nP{f{)!1QKW5xoLL+!`c4Z4sA@h_WXilzox02i({#zWIZq^c zxN%T@HIcTSbSds+5&)(Tnh_2kl0<Ud|e$|_KFCq$x5_pKY$wUW_THMd3UO~PC zV##G!U;`v6TWyn1b_+j%!Q9DJ^Z^^rFcvQ2hbFKJ3k^$|CWCS9eY&e*17S4@)3sdf#GKOu=_#uen*F&+QOwQJ zZGGHr=!*I#C12atr?2-f6H0a)Eqa)b+d{Ku#+rmFFTmk_W)|Y;-|tn+vghh!`^iAv z`3qAwT`mO?8edUmwCBHrEX}6O>($6RA8A?aKJN!5%=B0W$15Yi7S5E|CXXJIhqF4p zu)Ahs8MCj`+sA>Wi+s_SSfo#^l!WIxnG+*j%6ypHV@v0o%&Pv?R1qmRqKJ^*Hn6vQ zSJEMI3rahnY_ThXE|=2e=p_WwBx_h*aJ`)$DKRu?igJ&# zwQEr4fKIKa&D{#J=y?E{mKgNgs$}mjuo$xs;*WMrYt3D^dO;m2EL&0L1mQfTl(2It zsv;e+nc5+8;khBmlecdhi8#DIuc4*6TbPBbo1qMH+prF`st>(LbXXuqRF!JJbQgKg=1--7a(SvJA%ek8l$%MZ>=A5DP5~GY z2xyuyR9*us2|Y|stQym35Cyv~?lf}|UE|n}FJuS*SKF6a@hnHHg|jy3WA(_D z7^&o4S2r)?=1v}$vAci#5*OipES8+8VXYQ_$WhKU%2mNhZ&4DR$BXiho&-I8hOe_8-z4Epc%NR{#q#)%*D58~Y|l zQLMCQCi8}I?T0~O-LZg#C8G(%&^w%?Hkt3WfKtn}a=AH#?rSS?{*#-3*4@%Q-c3M4I zhHxFTPR3*%nIwnyj?PYPj^h(c>TMJOH9uc5;3bMag?u)+X4N1U=sk5=AzHz}Y^NQ> zxo^-6R(spvo);`Bb_TUj}OwE8?6HDxy~@+SWo*^3=?HF@~j zz`XEaC~HKAmBj$#;^n`Kt>^sB{Qq}ihFuFu6o~cJ(nzcD)84}c`u>lnz|N17ljfO> zkTCXA^EugY{4rv62eTLNGNNNB>UJ)@>+F zit!Z#qYPpfGaTial=;SPU*5e{!sy68tU0uV*=h8#4J}e4ozvkX`4*chlp4m4z!t99 zliPwL5Y8hQq4>an5*BN$EFtkgLt<$_zPWb@q;?A1Y0;ZMEATh)6j(<#wm~;C3Z}(z z6y~sDTEbEpobT=fjkug+z;i{>@;85wJQ2#gKKAN>`hy(<6>mH{F??3$nu#S}xl&&a!j-HKW#7;5Nf91V7x?^shjhGub6%MEZB$s6urUBbXk7x{@!0{`xL`~SVO)U8{zN9= z>7?#2np6Ir0_QAmQ4BBSrf+_C#pV43BEfrjP};6|`qO*hQ*uag_Y9V?9Yn-s$!*^R z!5A|C-wXQdBhUf8 zdbACy=tZ-u)5P*BDfq?tJ~l$;HQ!hP+6EIyg#4->WAvwjzRn z;(}@kF29#6%@+_nYRKPG6vc=#mMp^N-BlhUYqCan^zPbDP?%eb+GUV=pVz_C7;_W1 z=|4!?@6G(Z)_+%^%o{*6qLpa5VeEMsVy^72hp_kpGZ-~F^YFLmt^=@!vM{AvSGm+S zz$n{eXmt7UXRpN3B=sSZ_af85gsWE=#%MD3yD!i;H7&m?Hy~~dWrBa!X_@`rd+4{W z%ahZIWPhyW)hq97`vaIynDk5xIe9V)#mPmVQvY4gKPwFgz?79&CYX=SvtYvvj?Rsu zP{RBh&g)Fj9#Nvu>JuQz>Xckq$L?oK1uE~A*>@lIUUST>zAwniBE4_vFzcZVq^Gu7 z4~*Z(LP;U3Q@46Y-cLI+7uUf%;`tk4Tad0a^K7LN8{_!1aq)2jAZqf9U`=_&`ap}v z!GqmemjeXdlz;Z(Kh>B?rvJrI^cf2M*k5_KfHMWdpE?Q75B6L~`sRhu;*VkTB9I>} zY+TdK#xtX;*Ow%ojwxj^?_v$s@Qb1db*+Em>-aJNNmR~Q)`raeaLfsKElOkpjvsk< zqIPX9A0y*%qW5{wUAFiI7Qjx#eMHt|M2O}LMfCql2zNk$_uwD?d7LxXw)-Opcxc{g z^RHo*W`F_mk3U#(6KJE>T>8;2{#w)8x2TRy|E{YjRCm4QdUtWlv;WiFUZFDj^QXQI zywA~uFQ!1hcSyX;9M+y|LZHHDRSp?S?AEbi9@Y{s6ohjU#ed`S-&0G9^}iU-bWYV& zmi^=BvGV{0@79I5jed<*PtL0?5C731$HfHI_D+R~WMx{az)O#wK_DKBGYERwEgp6y zKO5%py${lEqrV&ee0y1K*#1asap}bHx#VO7Or3=iHBEk)`KK<>R%y9esys`cBv>!GC^|Av5L#MC`%ge&U)y zVTbSCB4c3unPb7W;9rnk&!+*AlE^YuL={B#nlUa~6)vhX&3<&rK0Q|&M$Uh@%}Evz zXiH_^4t}Lf_#di@5!SMCOsmLSma@I9oE`VVpV^BH^wSm=it zrW}cXud7#)+({mP!_+h^)>WCr#Ok1s^p(D>O591z?naM2YWIwFlPx+j`sev%L*yv? zTDmjci2o4s0WKIhbB?Uaj?8;mQ2etAm6*o^XPQSnAuTx`>L<%Q= zk7Gb=nQjg%@;oBuxqm5NV^0`((I@Kpd?uBgU~~Fb`}?0eJwOrgIsQeY9O}%ke97OrW;sSl5*mvq* zEiu!X+W{0G$3NWNXAD|wWg$S@PvyL1nz9_z+3O%R9!)tobR^0;86*H8!dJohlL^SG z{Xf?bz#&7&6Fo35?0*@-M{HhiL$a6LGZzN-IH@49A^Hy9wKhMx$NN0#S@DZG zztbB*T~0b9oJRKxZrhax{+7pUsVfAZw)Qku{g284bVgT!WKSLbPrr2QAKyMgGBmi6CZ|b|=$738X zgC{mb4uigPtYn*s-48zXv#>gt!ZTgO2(Z1+cq`A{aG~l`8`^oR@UGsEP^(E-|lS>lKcw zhZIfPs99um^k=!-=DNe^{^U;}JG+<#W3Ml-iMb&JkuSIVKc6ERBUun${8Z5zZ`3iw%<$t zqx=5E4Jteg)abxVm6*(6e%koyib+6fKUKIK!9C=+rFM;wjBqQt{>%D<>h`L;OS#H+ zLHz%V+8~G<_QSuy1Q2FXQuBW5xL!HfDBAvE-9@jNWt?7`J%59|y!40FSl5}$WVOeo_Tv20ixg8@nrQzLRwy-HI4V@)rx(V)UO?BPLGDT@! z&L~sIb^Xt`9-Qa9yBB@0>{@z4kY$Mpx4mk>|5=9HS6 zU933E`N|T}lj}639qIL(8H2YK{F_yoZSwD~PX~~Oy_guG7@X&WCn`)}_ zx46f2eerOzJZ30T*YzYM=;?)z0`XcReTtSYE&Yqc%F=n4f}}c&$R_D zyLrS-I{H%s^8}N?pTZtbb>6S%-_BpFdJpZz(wmQIZ5YW%|mAY4g#U z1E}Tu)c^5aU_2Cs(A<4Pw#!c}MT_b05a$t$6N;vkU?Nmz9GxLsIV#@7&pvt$N4eK? zt$>x#blIE0-tCiOFhPCN|$}fDWA(9HSg)O19EgRpT2Tv%x%Tithe2Lb) z8^3%rk&vtI`Bd=gF*Nb3G35TFL(0du?PhqX?rPTUvBD(ZH9R7s?%A(Ow{=hE`C5;W z@$BLGcvwMeI?#iwxS=8;RH5pn`VPzBUz#A`32FiI{iM~6QL(}!kPH1*U-_XnEI-qJ z`vg*Qzr~_ecqQ~yQzq|y{6m%BO|6ElRe|6St=rW^0UE&Asc8GDJ3RJ{?9~o49e)Rs z=xOpR3WIj?Ku^;nl=nPo1FvxSy#7?u(01bChAF~b7S)L*Nv~f>;2Ti{B!kJMzgLhl z+Tk~UTU+mX5eam1cEq}jPg^oP-g;2h6bJ)del80HQS;LK3phv67Re-}2&|9;kv3c) zT6_LP+=eC4Si;aS>0@U`mdEC+y4^YrdT#2z5DxXmW%rS|*%tGx`&6u8ac*f>H{osp06hE@Mu!*6r*6S%c0EnNytT?%Fk1JM8T=VzeN+`A1>yM#YqweAV+Q9+_xUwDjDHJfq}m0 zjv7QpAYSWaVyUGZ?RlwFm5l~M>ytsy$d3XB(bLdIJ7Q7M)A5P(sa$A`#oLZ6M0{@c zs8(h>mQBY|Q7ELINRF1JakLknkFcU!oWK*&L=y~tj6lYU~B>uCON>1J}cTr2dG-gIcNp7D8K@2ARHF$o}xbhG% z+NJinpkANEV}P3lH^PbQ=Qqf4n=EKzG>vb;RNq;Xdi#ArnTxERQ3xclFCLrMyaq-5()Z4l&@t$!n^zY#MK3D2_@3lZH1sSRfL`I6U(jTcTZF;)k&`c|d!D{5t5 zO{2u%SQBH|oV|n22-(_yjBQZ(lZ1HF_;Yh?qHZ#=Fu2uV`O^cQ@&4e;f8d15A6oSi z9EPA)*S+vsVr}Y~n}I(Xo~C+Or>EIDtU!TJ>0*0ymcnvcj8JFWsCMA!6(P z353?4Z?QMu_A$|wLM+}J9M*B+H+LeqI(3IXFH?h0PH`$22+nk^#3Vimnh`ZKQOIDy zO30AY2~58um~tf_f7=p1y>5~H*s>J%&SJ!M4D&}in}Ku$(wWtz))GJSp@~ewPjGqG zxt8@YxpsC^K|z6p=ck>yj$HL#!$*r~A^5P@wNu({SY$hQhagL6B!k6P9zX0B-iZXA zQ9FyzC-*M8Pf&gM0@pIr6~P!Qf4j@v$`6JPYX)s6bR#_69!@Fklfrb-kNZR9gk=1p z#bDP0YPyRhW@5Ms#;*~JUPWspZ8+NP;tDb|i{F)r8ZAynJZg(tc$?f8AR%%(-WAyN z6vE&2IO@?TvlqB%H9ok8ItHIL1a;2MLTAx5bW^||Wyfd8i9P1){MvpjAUh~(ToV;( z+AG7sCCI5Htes}!eL33qaLBRbYuWV2Q5bTAppj7;3aL$PbU6a9kCj;%wqYTAGJ4i~ z3(qN&gG+kF1uL~cBi#=`A?E{b-TJDN8HyMd2=`vix3BLf_ucP~OPnv8)8vWmza=5= z)E zcWN~6#%#GRjU_lk@OOWPU0130sOkH9f>xY*aR!kwoSu!;&{qY#;_&q=kRo#*n=Qqh z2N)bK$}$QyjNcZA5!Su#9n%##)u>FJaHigs5 zKake>GYH6wDcXXH1XW_;r+!9j6)S;%#%kD)kqiwj#gh+CAUNRgIxQD>)1XIQpH>yM zZ=FW3NUcswzBG#8ohXhQZ@v*lb*#!&Itp^_EurRG70u}mkZ-Fwu&7=9ZXn?BK&~t? zJu3rOg~p(DhvGNyQHoMxe`gis-xEZMuNpLFxgN8sj#FS}nqjh% z)w`fAQ;^D^{~Y}crEGbPYtNscY)%X>t5JQnzWb}%kIdKR3kRKNch(lQEbXIk7(&v1 z_k$zOq^;3k1P@RHlZGOcaZT*#za1`mg|7<-h%SCY%XnEWCd=L$P(}aZYynTeVn1In zU63OTo}!|HqP9>;-Sl)&9~5{&V$fm;ukW$Phl9CcuO&X5)+ln5ne{wJG_$a6&)X5E z;mW+pO@EJ$GxYNw!Ie5%B+zl~(vM?m*LnX6u#?mQ8yIM( z@79cE#hc2f>zes=anZTEo{%?Q#tbBTtVSUeR+;_fPLV3Q_ z#+GDSySp>>u1L~3{ftDYb}Fkz@OW$YT-YmBzTyp ze;RKruW|J`I)q_w|KalamHxx}{bi#_p70eEw^Ry|KaN`!{Y3AaN*)w zpt!pfheC08r?^XLad&rz;#OdQqEp=6p}1Rd26ruk+c)hi`|N$b>-^vc4D(3VN>*~` zP6F93`LQCZPQ%}=lR95xibiz)=>OmuL0R#5z~xpnS%%jB{>j*q&cJKH5LcgiD(5U? ztNQ!YsAr>Vm}w<)9= zAQm=U$&9+}38uT@(Jm1TaW)eEx+DD58sbLk)}@ONl(QHbEswLX^CpIZ(pwYElyIirGwVr;dCG zuvz#Oj^b4KKr9%B$5)2BPcj5kpKDTfdeSa8uf%N!HRvQl#tmQ83in6CuqKVn*KC|} zVm`N2&eB<`X8Mh5Q;o@Mb2B$i$#4YcEE4sR6H#i3&<`xpJe0TGhU9SqU1z%`W*j7? zM=zcM<+=~Bl&OhULfk^l$3GlgnPx$gKV_@mbZ@UH9dmMoFMmQTA`~LkmvmT@5_)dP zqt&4ML4d_mDChI}JLXprjA^T5bp<{EKoViNqT1kLHAY>4cPL?a3UMu8 zOCf8iHjKyag>!esZ2D%34+ovZ9@w_+kfMY$U*F%4L-#F*!^1{sT1IV}YR+3E3ow%- z+a^R!9WZS5WPYk1Gm0Gek>-Pzo;Yn%D)MJ-bWUW(m*Fe!nOK8|N2^6%#x#0S#2-j{ zjb}wW=1fJ*4KD1Ld*cOcxS>o!wjG!uHmeQGhBslX4JCJKyQ80h@B_8)j$a00+-IK9 z1&8T%mcDO#;0Ru>bj~tT2*v)?cOqSN>79Q|Yv!@Sdi)yv(X4Td0#-5$!b$56kVh~X zIE9)0X{R0(53c*XBd4NME+`M^+rfBW|5{x|Cx7=!*xl!hZ?3qK^+;{@p%6u@3Z;+w zeoC-&XK!rD+?|wPm3+M<7a;^`HJh}|2?`P3&n21`Ui3a2BzS>dX%a7coP9p%D@=_@ zxB^2N5$^BEFZgRV{X~Yrn z&hGO?gttht^G;!Da|a^sXj=gn#DF-bmv<%%P-6TD-2*IIYe9zE3n|cIwS$))l2Z~) z@YcW#D-*~O;e;8w`=FPnfJ35;{sQvPC#7d^RkYm9GMrqSucIyxJpGhq{(^|QZmGY? zstoKk=YY+hhFj#~OfdXd#2i(iNG~y8wodp`aDU+O2z%#+oO<`y;{WjUkC5rO>JsHM zJn}3+RV@bGJdu$0k&j;)KQD4yS*|v0+->lw?*)SxxSyT0SlMNKTwW8om{*XE; z6zAh{L*Ub_*Xf{G$(yzh@X+u!!Bhx6qf|^)#C>AX+PMDvfqd(lLALj-S+fgl8R;bX z!-vQ?Fu?kuPk}OiC8g1!hH4o=OU73%I|@({=TLzAfUV1PB! z4+`swjs(KYjH^vL8{NL1*uxAjSIAL*-A&Uc=;yS2<;IQ( z;3d~>K9KJGEVZnz0@yOe%PQZcc)sImEh3L6-46r_NE$V7(1F*5SIT3^S?@op47KGU5d_m8F-M|H6WUsk?-mX_Pdf zrQAFxsi^4VPg_``8w4BU359e;aqKIW<5*L0YW~=#qy=O>JGp8R6A&RLq)H`9Nd4lJ2{{{4y5zn zysci>H8GZNd^Tex7>f;8m8}j0uQPMpmmSah3s1|B)_AF416R^T!wq%h2G)3zo-fat zTJCZSZ_-XjT*nStll7 ziU`<;$`V8vSD?raImsH{fIdnB z#3m8vN8*z!eYfW*1*LdGDC7AZV`4_cb{__&vc%sljKQLjz#t)#t%Cy*(VczmCbfJA zh!pGEn<7_*m(<@73j_M-@%!2v>(aR#bOFAm%yRlr8}MH#;Cs=4X%ncEyg$dsi}X;4 z4xG*WZ#^j-y9b8W5?^H#8T31#hNJ9B}V;&q+>6_l+(^nXR&C=QQAYJEjVM$6vLP8=b zk84H$o!e%bZ?w^FUKPkBpn(;#ySlM=K2 zSw@d96+BBlJT9kJO0f{&kz3v#jJz1SxfcFBO5gF|pIS0stgO7F&WLoO8|4sA0ZeYZc`pEQ1v+;)P#e8TbIFy3(}+lZXDJjdx}ioV+-5Jb!Vc{G7yP4=N2I zH@-*aHz#*E%i_0=%6k3t&fB0mt`q7S{c+dQ>!cjgtpbjc2r(I>MH_)W0+RihbB8J$ zct9QT0L7+>+~>@%=>wJUid`Y@f2rHWjK@dvJ^H__0wEO0ZwjTHU3Ps`4b$@O5z%Y= z0qgsEp()s(TOs%0P6gYf_PbU7>^^^-g#t}I^9OL7R^YS7c6B)hN}oaFHzr*o5s2>G~LLcFarprtEVb@;2vUO zzUn*}!ui%|JiMzsdmgk^drR^2l09mKf8XAIc3*N1oXR-8FO1GyAfHyKq8TN65a|4;o`J87HW!7p8`@)jj zob+kG7N`W&!6M%CE$f$4ercTTs2u$w5GYO777j`OoD7m_$Nnez{TIMR9p6*@adI&b zOr^*)EwbP9ipO=|4qH2JmJ$(>!%DAy93D|79(q?BLL3gQ*0s%Z$u`zBQs?J;+7Mzb zshvx}E{_zc;*DGmwpjlpViAYU-cmYPy@WPLdK?XAM*QN#6~gyqp=Ry-lAn`f>rgyT zLS$R)$fun=HJe-bIkLX_djQD~LW)Okd}xAXQHiDLuV`Tg*g((}0k+dUNkRBW*yE*- z4IE#P+#hj&I8GIN1W59atthoIF)ai}!1v(j+v~f->KnT>rV(Zu!}?Gl2fYOrv=|i5 zi|Pn6zuu+!?O0{O{?zyB7zoptpIw^Yy6om1=Y3G9_h_p6s^)b3asCURSj(5@T9V*$ zm&W)pfYL69Udz`-4q!D_FfFu0#e^@ZrTMs?FYeQ2y6_Uel`A*aO$}9hn>XmAs|+F& zBwJCqUEtAL1+|F;3&;V3-^~DbOI;y=L5=MZt-EN%DHk-d+0eiLmEmkT7HYmc5*}u8lMP9yJ+$2Xw5^-?1izW2c?SAclkKVuy8S#{5g$^Ys8jJt{z5=+TAa^=`|d>pe#~kKxRSK_NhmjlzL!;D!t- zrc3zc373R%eqsA_;Y_`j;aiKpKvH93O#EB5Ku1LDvaDZi^z7hkGTfOlt!wl2{? zXKhZ#Y+QvIQa59M|2W{g@$@74F5b<#aHU%Jj-!Y38?}K14CIw5G^@rq(j7UJcaBLoN4wsCwQ3#WvO56phTJ-1g2~^xRVH3o=DH#lDo! zccAiDCVBqx+aU)M1iAnFP*xIghho=lNm-|gl_bmJ3de|`0rU}WQ0UUBM=PS{&a+Qi zmMaFls)mWRUI$%GHGtV$GDExF6o_HywQ-eYKd=_WPr)20!4rA4VB25K)@ys&{!AAU z$ZveD_9OsqIM{*j8OO*&a-8hP)en8J@9Y%}DtEz9N%pBAslMfAr3cZQ0 z>!M8g7^+CPS*?jQyfwanl1j4Vj75-+dyup7| zgM#u@#rXOaXTynfpd^N6BpcUhCTPF!0dGNiq$zDk5glT&NvP7R+H1zIM2BL7D}-x0 z+yaXP+$!ab{J`P07Hin^d{?%?S!jKCUvDWW0CeENk^Kh*ea)y+({g^I_Sirr|3_`8 z_jNoW1S%U)!=8P5zDEr$&ukr6BN;WIWg$=#&$6j=W`Z;Qe3PMh8gv^QcA3gv!&3!i z#&XBI?xzndBMw$b&X0z`L}c4pfIP)NIqLsoE3-j0*-H>|RzpokjE=`>R3cf%j^`(9 z51gqzSf~$7REav;xXCFejaq(kamOg&n_88FW+&{$buD-J0pa6c<*gnw_)L!6a7!*> zM|{BWI{f;4?Fc#*5Seec)pm1xr}BUnkR=q*JyUUQy}4E;ln$r|Zi}w8^s@!ruOpCvQoZX{iRbGWMR1S&ORlwj(_Ho@9MRg#zFOKY z9qqYDi)Zaz%NPI+zIrf~MT*&Ykrt7jiKTEe-_+`=xm0UEbFQw}q9D4mEt~YYH)+_i zppRBSv?LBD9Qh#J{V8Y^Fbxeg5I=>nf6Q9V>!gqgkiO&zMa;{sUhE(p;lJUT?iu}< z`{RetiB9e*Wzf5biVBw2E(yJv2i6r5@V;X$P|Uk6bjYTGBjj?Rk92yx=`4=x3NH|9 zsr>`qfXJ7N=vF(`P4t2d5f?YN;#nFQ&@?}>`}bE*MLX()pk#K&ae=YZkWKZNfyG)S zBaxP_w+pi5B{|W*3w>MzRsBs7ybS~+xmQ*{t}I*by0Bq(ULjFu=|Z`jn|<=%POH)e}Hu>ev&kkdn;M>zO+$B8984tbSaJ?xfGHYE3i4)ARxvc zu_iWZVXr#a6k6c~e}b)Uw~p_X2#F)GCZLFOg3#DOQ z)kw}~z+cb#o!1*7Yhft#_xmJd3T%n`04TM$J{B92W3%#61cSc#Re1wlm6A2SxltSrTpp)NrnhS z>rs!%i0>IlNko}RV2(W49UoA0MFAas65(+-s|norZhXPUnmHkt_D)UB`)}S9xz378 zY{_#rI3gR2_rXXf6sJ#x@FhsWkN)vQ9$6-GtcM2x9(^Z1H);A+N>K4i~gmdh44sZjfH3YEg1>YAGm& z1E^fE1I#Hw%T1%_)3p=+OgO)H92E3=NEYe(q+_(Kp)k7p>Fzfgz{lr>e%y9FjAC(G zR*XG9&Vdoy_An)dhh|AHTleyzdSpYB*+{~iNl3D}QQ#_We%3W~S;k84?7I8)t~cmD zYRSKM*8rxqVxM>&@%727WJ<#6AzD82aE|C3k3h8nCV+5~aOSkeA<#ySNZq%l_OG+~ z3o~^4S;ME&r)ga>r1kD8NV?GSvO|hy7?K~pm=zLr$e#;gFXQOK89-Y~ z4Jn>X&knfH$;4;m^Ntv6*Vk?3!aq$@q)Z0*y=OVy)Ot#kk4HL2$ptY1I!@E~koh92 zamjzUDPJ_869n#Y+-|(S^}^g(X&1%07-Ku>`h1n+*Bl3_DSZx*VFSTj_*gl4Ve9Q) ze^8W%aYus#U~thTh7#-j)-JYeq=JR%(Any@TQY^EU!i(})#&;eyCvkx0*8-ch9hB$ zKP2Y&ey8nGPy|#^2aPChddqU4FvwZQ$mzc-S|Gc@L0oNLb+zUtL4u{gc)P#&5wOa@ z$D}z21s6Ejz|FmzkryGK+cwy83#pQ0e-k5D=zSi%|SkW@RS*0SnqC4 zF&P8L#9hPkm&#<2HPn3KXo!QG^b~v-XAS^{d$1* zN0<9Aa1a{b?*hX?C~wb0ss69p0zk;)L!1r)^KWh88Rx#_!8PRgrvOXys(( z3Yu$yHw>5MN0mj~YtJ86@hV!cqYRg+dU_$F;-6($+a3vht3CPMTliHIzMX@MllC)8 zJ0!F!ng?09_BDo(w5zSpD0F-2X-#+(BS}xi(95KG5yfh1>y&n5(XdDu1WL|Kax@*} z@Das|l!t)DNx$@mMW}0RhM+ZYH>1Ofa3B0SMrR%PQcZA;+OB`{wQMG8$lYEBXLiU5 zH{YQ9o*MeKEp^zANixoPE@+*jjvJt_GMlRU#p;HFS<_{OaQ^89;D7hPcyIRJphF!i z*qCB)cTh!J-Hg?F=x)SvARFd-XaBnlpYN2wgOfi8#_l(9vg$Jt3R}?5J*qZ{ObiE6 z3`2$}AC^J`?c=CtsS?mN$95_@iGnHF?smx(lCZCseiXPE9g*ASuyqm=6?kWVt&Ycq zS0>cEW~aXwsIQf;;_a86lQaZEfj%j=npVX$m_A-0;vFQ|<5UhVvr;&%0LBIy%i%G` z3}Gcm{o&v!y$b9_xKHQdJ(j9$Y&qwd; zQ<|q${H)#D*r)|3R3Q?c6M})S2z%aK?Q46MAdz1jGo6|g{E9DkSNU9k0pYQmF932ttNM6h3Icz zZXFoPtboJ)Sd7{iPYMMEf7vqBWD$J{HelwgpTt~^NSENc7!*5kq08PH&L5 z_{5Pah%w^7JvsEBOJYEG!yBGLvHzGNobQG?HEm-~nW?Pko--iavDv``rMx`Kx9@u9if^J=4P-n#kqcrxuZKXMOP`J0 z$26?9C#(BAjxEi)>1&Z#B|fRudTo#*d*7Vsw|I4OUg>F9$J2KaneJnKqfV;xb$j($HR%l8FmCYTd4DR(GiSz?6MHiHWtay z3j*@NBA(&ARw?}tnUT~{txL#I=|nl%ZB^q}D4~4=`*Sb8GFmkwF@JN+ZM+#(QrwaI$j!cy~MHPv>#q45o;i^$fGkVck1?fc%RPcELJzdr;H-ic+}k~?TfbB zq78x~G4CbGX7c}(?_uk2sQK-Z8mIi-TPeh`Rcj7tmUrZ#L#QeC&o396g* zO@zdCNKV>fhOXeh;dfDl{-Wz)P!7y1<9Fog})`^uN%1`>pk zukLZGxHjvg8GRMF7={vXmbT&KaGUV*mPUIH!M!eD{K~Ld1YP|UtW-F zFB&YB5jW=X?l2L{V|)ju=+xoYcf$sM48M=!rC|&yBXA^yL1hcDmQnMjYi+Gtl&r{J zihIg}fyVM73;YJ(X-l*ufw4I%y&Kx4P5UZ5K;4ZW|MeR(ypg=1g{evGN?SHP$e6yj zxjHj+#ZLPl>};J$)WcqTYrmQ{t>-I@c5E`{Av4B`ehs1Th({}D5-1xdyKJfu#7l)MyPm6WI_s@@wTKihUohj$f+IPNtI6*)eQrbm*@z+ zc4?byx9O%>9DGcKHV~CyVO1hWx5cCCo0P9MSnYN^_H;L=+KT_9`3k3gH6+a&z|IXg zeL3a4ne$`a5}g}EKdoPe$q$m%=?2dny`S^-$7um7-n|@WA^7NBH9wQdH`sWdk;EO# zBF1+kwwEwX-Q!a5+ndgZ9D+iyO7Z8x_Rm%L4ziBOHHnIY0H<>v`RhgL!^Kf@N$TYsSzEdcQRP8Yr=-u_!5q zj_DmQE-CEWdG9|#Y=10{gM_~+Q`fcWW&3iyMFQ2y?a6c;&<)ZHth-rpC*d_dj}FDC zrj{U}W|tAJ7u1|pB_#3-C1lzKlkGickoF}c&eXvo6BW9s3{S#x&g1NE3a4FEZKY>h z)ibiX!GBEiLqKwQM1m#keEFJQdmSLrI!AE+^>#kN^*3VEWkXwaw$2_$zAfnV6%iXv zuGblSD_f>rVi=CPO_?{>`l98pLQHS>+{l=MO|#^t1gbfD5|1Jm#X7fqxXmJA4T4KE z-$P?YBP&<6d-OlAxx{ggupVsRQjQY>o=7RjH8P=)op$VXp3^vu8cV-9*+2@PiKH4( z+8pw4+e}&tvtOy@+4>dz(`(qNm${rIDn4{oc7Zp3RlRiUIS{eivTFC+gSw%kTB z9P}wD^LrL_Xf45>wi;R{HX<|intto@$YiaZloUsD>6|u zz)AcUqrC1Tg`cEvLKJWhWKY9cWdxtXD!aDmav1x#?K1HksLg7uvyR`6Ub~uE9DYO5 zbWs`Pv>D-F+=`U)cWarvO6r{T$S@IwCqr?AyNF8^pz?b>Lvfff4b6^Ld^YXTf!H?< z9{qA5gTAX<%AaQ)wE~~BpP{2$QlWM>_q2=6nkv;4JAU3>M$sB#s2c7wB3fExX2o)h zYW>Ce)PcR_kG4I~!RXmU@%>EQm;1G&Rc*{Ms*m63`$wXO!&Jk#83d33J+aw%DY+$7l1Z0|QC1Xr@j53jQ@7cY)-*jsn(S&K2#>TBw|G}u4k}5)mM{gyn5o2O z|JNFZo_2gln+xa?yMjOG@O-S*!46>QJKXx0pF1i5-3^O&pAO5|JB|K-rt<>%+_5 zlg-7{8JQ}+ntf@9_lv;+wca#-!nru!nXwn&C3C-`&8oIGw|h zd8hoTh>pV)ud1|Gqkn?RypHi3GVpXK0XvWjR262)avy-p5!NL<)$F0QQ5V7naKpD!VVHEVzv`=yM^;njgPT? ztCw&8Hkgx7MOUs(DCPxFp`+&igZBg3p>Xv-C!PoQzJ$Hz54WfwV;VN@Mnqx2#RHn4 z3nGni+wXqPRDtj^iP7%GKB~yPi{w<53HVq*S6B}W^6YfWH=QhxQf;=?km&KAGyaGa z%>h9M8Wb9WHykhd^bRk_c<^*~ zE}NX_6=P|^>nBVe8+4tEemV9`u2)x7amP=*G?2LTpUf1SH`4hX0z$NUa3M-Ydu>`e zq{yBv9d>Q40jtyF9Nkz{=G*OaV@HUx@8hmWtJzpXLoCa=bqj2?;H{bG+iq?Uqc~Hk zeH6PJB;gB=WN_NWs#U`mcnb2%qnW|MK@lstns?)2{j@6& zm!P+H6v6pCii*p!LtXzYAVh^72r<|`1WiXf6E{_dOn0k}hYeUIotx=Vim~+pF!?qy z-q9@3b*Q=JP7VYj*JuEhKL$l&9vlWWu8LAd$1MAXqP9?CwRk*)%gbOm^~?-}Y4OVh zOh#m~vVv{4;D}ZO?vI}y&L5WzQ9N^?XFi*LEWY1TBpOG5{vclLP3SnKlRhZJ9%#Uv zxF3ey`A${wt8%f19Lm1b%y|rbxgpP4T)Tc;`Gb#r*wdkPbV4d-2lUBN<9J`z4cU#$ z%O)4fsrN0cpTmII1lLoKy)5dlZR?Wy+=iz$f1@&HrQ3%zVLrU&YqD$b<#7>#c|9NPaBJ>Fxltj}`zwdBiF%^&gdtRhxLhx=D81m#4THloJnUv-$wkjgFhuuw*B zV`5I=s^JG%Yio2LnDQ{A0Np;m3B$FSIxTva{9V$MY|JKDk}xA$#x!Y;>W9UFV^H~u z^WaqQ%=|4vw2D&0cTn`ziQDLqDjbPpwGY}h<%m(`ul%pqbE*WmE&IX1?oF)J81*E! zuz@iO6O5GLN5;Pw+?We1o|C=yZhv@1i4M{N#3?Wl(oWl1!Aw?d#PyeT{=0jK0!yItyiu5j_0Nsw0WvATN5tqv1V?d4R@|bd zYDmyvewZ}50d9X0YNjG?ZA{R58K|J5DT?R2Ij0GWjvJ_%=DKR*1bw4~a!(rYX3?80}K zZlEesb#>)pLzOAlvyi&@Wz1C%K}KLYGOe+3dSN+5T~hhQhM+=7*uEx=@#&9jijk4;1;ODTmvoz;DAlV<L#HNJM3P>rv*&kn2* zr^f8AA}EZyqr)H~iaztZl*h;a;2*7j zm7u&H>*BJxxUV>tHioK!0ZP(PNUc`Zo2S@<-$e)i z+K51iU$(c+urq>*;Ak31V*PGkw`MFNheWZPBAULep{S$^0&7S4z&<@YqqKhdauVNt ziUoQCL$CNq;x0Ifqqxm`cs_8A4n<4)mX6T%MQEDUfVPIJxAhuRHoZG5FI-LvK$c8m z>ssI5j&cRSvCOb91n$Iu%KJ7m-9iKevd=(V4M{Un{D!yw=9@tr9O4zr%F3+s4Yxvb zqsr{H9I`wK8Q8%^Y0d`&Z09qqEd5s?p?y!njAc(Z=d@ph<%&Wq=su1^7Lv4|K`=(T zQCB(3!J;H#1BR!sI_qTE`d}a1ZVs|_=ZF9DOxIngyOhX|{XXX3*y*v3Qhzg^kH>a8 zSU0{Hkds62E!N^WeqsU7__xUxSj|pXo?p9R`c*X*giO~}c_TDd*YV)sD7IUix;Z7p z_p&kZd{hbZw|IDLM9{sdoH%+=*0K0vwhg3tyJfB-!`GbA=3kQukcY0P{7oB&oaV-P zNVA$+`33sfA=wt=)6WO;0Wba3AtK&-jKpZ5*?1sIifgIX$K}}7VMbwYgn6HRCw=!T zger^fHhOV39-avIo8w+#BOjc~whLU3r#rfrsNMkv2j&=7UiwFbfg+oC)7Afa4dk$9 zyn*&CHQ(IX;QjeizL1byB$JwL!z9h%bb}Mcl(pp4I9WOfuw*T`tw|r3oJLu%=N_wW zzt#9n?(KT$I-&Y`-mJ*9M9F2f+NVe7_!sL}o064_`hB?VuAJzt>=e4o3!dsP1P$O` zg6>YVulkmc5~M{2h+L79(oQ*gL$`(r+VMJ@+AI>~Gea`)(WiAn!?|6rBx1f6TLX&?&93}xk*{?9bT$koKo%e3Y(5wxaQT{?*ys0Jq5Xz%lnPt$>c!Trq*b<^~Z)QZlMI`F`ufUrRiGv zzB;i&(`yY_ti@(6>QC`}|6vGWd{uyc|9&oMF$?@&5$SlLMz*>oyQnvF@}uBA5aA-} z%pazhe#pYkw%6#{9j2oFfTQU#BW`02f%M1ElUC!}WjYts6%T21JVP&2td6JN2glRG z`3(+tUy_L=@`ADgd?M0261P*oIEf=EpN-m_jPq(4g#Y9A7|^*`9_{a;I(f3g8kaH6 zlJP^#@$X4Mlb$j|Z{IYXi1>SHcOlFWIJ76PcxpIvNJ4vhhwuxIa4y`CWO?_&vB# z9V^w7u=L6^!RmA};JjSz6v}WV#ccZLP?x$CGN>N($N7KVidhHKGBIq(C>N!$Q;NI8 zL_8>xD1kPNPUN}Ms8|_E{U4=YUz57uBWgAsx9lJ_-Bk4PoPAM{%iK~_>*2mi8hXlp@JKz@ax&->Bm$>p^vx2m8LM$QrVbqNf4sntsfL-038u>a zYNnOIu}imJG!pHw=Sq`*&Gx43iX^+44QKhb_E{Sz+oS%?d&OX(Sc>}^ro{V6Pu|<@ zIp?YZeOL-FuB3K-BdwT_kb)izXyVjZRiC`pT4_tg)u5wimPPG3^xiY5;7&ax?J=0@5csfo)Q z8BR6QU7iyZ4@`JkY^mVr?=N6=mGYglKl4pk7J<2rOXeibXciBX-0vOXOBnyL6Pt*T zM5dWf-eYX!k%%rXG>|t?Z4<*O!yo$dJ;3bY6SqM)x`8MCGk9?8b~$Wskhw2%>0avb zX~_RAD-{wuF6-B54Re-(Uo_Ed?sf#bDQT26yfxN%g1C$frB)7kHOLR>Bz=Lw(jPp3 zB~Nq@j>`0Qg8Ybt97kTSb8-u}yrkX!)H>@~shO&*zvHB2{IC6orL=DJI&!N8kADM( zxDW=e9et3B~*~D(IPg47KTUfXi@y5prYIU|TfnSDfS|&Lmi!`p!bm$vM zEc?6v0i}!10}i{4?yyX?w3HPwA(vrDY&KM%p&++-JHeJEc~?)Ia3i8oTlE(SYCB1h zL`(^c9u51JvJalGQPC3K79h}Bd0PT#nJDqXe^FLSQUcX2Jr2aQ4`?x>RiWz%hPB1% zw}cC;cQ2QLZNi7QYl}%YIq$_#du-piyEUw*tseAGyl9>s9JW)fQlN-6P4^1(BpGHO zs_4lSa6)yqc@sEr%Y57Fug)+e1_HaW4t7BVmuf1B_SbihUw&RlR9&CN^|6EQW_@=0 za&j5&%gFfWqJ>)Mhs3gvIk9+ys3`=y%$GZvb#jQICj_6_c3HkneEl8JsVS`kJP(HP zZz+p$VG2^eTWjUSAGxBMMEoFVdpT}Cpl%_eO!%%O%cEZmiBJj`t>g%Bg=x`HS!IqRXbZ%6X?-XsvX^k>a2)l-~0acWL>VFuJlk}X1<{E;Cs zH?DRI-bmHv9|M^f_V1=(G}-a-$+^ui(_HkJ_D>tW6&AjJtvCjWRjb|(j&$3SwUUwn zwel9ruxpn|nl7lcwzkOHg|y!<{wElM7FSoP&l`a>VUY83frz$AhM65Y7^6ufP8F9yo`&oyochAet90Rzq`$s?e`U6N9ZgH4yi=MT)uf_iuq5%vW z$RKqkh%0JOU9M->4~;ZBlyWJci||q1aIA+XQ;6!#U%01np6Ee^*X!wb0q8 zdWwg?LdvXw=rTU-h3+6eMZ})GIH;wctMhScwr)IOt0FerKUG$(#4VL5zqoWK88KaN z&oVOIuX_pII9h4s=m)ZciVLWu9$`dk{T(zEs~NCMjU zzjh`-u`$U#J1>SFaII_0%JeDi^VfA@%lv%ZtX*1f^G?vORwbYlQ6}_ah+a;t^nEs( zjioVtzm6eM!cW2cw_6$rDfShWQD>8*5(~%+uYfsxJ}4O%dgIOa%JMM!wWCua!;@m>F%~kiyK{}<=qxd=_WIZM_N#$6 zH)LXZ-usy0&u2(_cjXY z!p1O$3SDI_kLr9C3`d@!O}&6|m#K;sv_Ep&SO~(C%M3hpHVSNrMwC$2O8v8gXQE$$ zx_0M(9=vBFi22J;9Cd;wIIxWJM&S2w;N;}`^wLfBy|`T5z!$5XaB^|(HBq742ABmv z+y6N#n3n>Io_=T$$jRo}y!chmFiH|-_v29Bom^DT{QLHS5lBq>HTuPjLGcl98e@jP&5@ILU?~Ka2b2=I^tkY<|ELsK8!@C=% zMf=88_B9Ig`j7sCO#2(9h3=!-85|$VQdCQ9{___9?xp{Fj{o{4&{AB4SgdF)H$HF_ z7n%wA4gyuAXc0UIHqSI)<(>U_>L=Dq#JH~4 zjcd8;9vjWCH#Cx`#f*cHc#hquzm8$8M02vj770T=>&5?rPW~UkwprLfR9i zBbH1T{iP1SkDO0P#7m82z>-q)<6F~pF>*p!D<#d~7hLN9=aqg(ce|MJ$?Sh#_}_gB zdAIXXthlWrJ>l4CCs`5>(TKuDON9V9l?gi!AHui~VYI)~fOPRmnQ?PRKXZMa1qup^ zdMr4{ujxk~Lqui~oAg<O{ z&gaAtAMW7{$b!7VGX9uy%}tXRAHhCe5(Uv82cCPgIl$+4|7!i;5ZD)`-(46l6Wagx zgTK_J3l?jgll>T#^0$2lBb|eOR*Os1_$q*>#+z{6Z5Bh4aKg=3noPU?_v;eBubWVt zl=0UGY-rF|u5&UnGKA77s{hOZ`zx|)Y{6;FtMZcQmhEJhnt zQpdZ6g!&rF4Tu6Sj*6*}mzi;apicWgU-?%&fir-vyh6tKq?~Z~MmwP~*+DRn1FC~% zd5SIIdI<_#*ED&C-xE*72J9zio{tEE`~Ud*%BVWFW$WPX?yfsgzA^g8?oIFRW>u|PwW?;#x{@40H?KSLhBvkOGwx<+ z0R-CY`E{LU!lf>5SIBn!*3gbFGJuQ@W{)OzNIPz-NcC`HaxL=WY)z58A#VR-qV>>o zy*tLU{V|ryVZX|+Y&S^}|0SvaI?3_{$7Il~mem3$Dc!8gg<)|+0L5st{kF0m*GYIm zfDE>dZI}Sv7?8p=G&KgQkjsmr7lY+;u7)7d;R>62 z!DnCeZv`N=)fUhCcb!EDiD}EKwqpEt?k3yCHEyRZaTMi?&?dz9%tuD*-O!)$8RD>m zo93Ii8+3msG6d0KDmyNfP0aRp9utC;Y}CH~>5%h3#@p;;KtKS9j@Q^{;hYUPJVNDB z-^;G5RRJY1eY`tx-jQ0vLRee~e`mtm8QkHv72_Zt&?o+Y%eFBjZ-+UwpWoC6ZNDPEvOAy>^ZT-Vzn`;b-MP=j4hMsn11r zKo$g~95ygR)o_0+9_K9a5ZZEr3vm???|N2(X+N@ zIq+Eeu%aV=5l42FaBu_!Kyx(QJaVkmZLGU6=IyVxhhM!Hw5As0UidMH^LJ zCy`Yc{*zO`pi?UfmNy+nMRj!8#HZkiIE`W5V04OqdI#wx;IMO{mva@8YgbC0PXOUoDyEY$5k?xu^~XK zaxB%}Ob??|+X&r^mke4m3FGvtqAoc%;Ay6HMU0Pa3ImGmcl>h7GDf#0H~-A*{}>5N zIe!Q=94SX8C}jEmL?|0^Wx823uuUDANDf+8xw=kw@0O}l^d!>ZVd)5wtQmJd;j9&^ zE-2W>^(Bs#9YF-_6Col_`^N{kMT8a!jugC_~3C9wC0?IcNj9*PpHT;yMzuc zec#yb#0uHo%W@;4|7G`(xdZrkespg;kaE;~IM8>2Kplt4|4b+$Rq6xHlY63~)x2da zHB8SEvc3|?7NOysjU(S``Tg}suNkt>PMG$eCZHF(c1)A9xbJz7%Z~Tn5lcM{aXa91 zfC8^w|Jz`lpLwoDTn|eu6YpxkF5&S{Do8}me}s3 z8V+!Ro1_}Hts9PHh;>FDGl76qc`E$Zau(>~%HA?c02>8x?fvHr`PT^uO!7Wuq6ZnF z`ZMwU{Ow~h&9JPXCZcX}{*v~3j9%D9(%6`cvSB1!Uh8})0I^NkPRS6}NqHBg?{iFO zpWOW`74Gx2nIo+>FefuCCnG2mC$e0fGPi++PEIy}ABAhxWY@gTs->@*9Oxl{B1IH) zZ?jKa`Unofl7>PF!F&T|Lmnd60D2bSAO{LXcP3Th{| zD4xPmB)A5T4(InqA}Ef#o$-%Jjk+xN>)0ouGewcK&u1=hRNW6X%(0`>QO47jMaeuH z&QP{H%%LRx6wTBa$Puh(WFdK4Em!SB@%(HILVWfPE77A|eHLc}l+A`r(@8U6kiXcp zmPc01!wE>uaTn8#aSOmua=+zFg>b)V?AYh+G6@b@gvTZX#M+)-*3D1ZhYz>F6nahj zlROL?moZl8$130i(`4vf+1;uT$w#5Q`pmSP4e7{A1vzLL_u(e_dM7p7NUI72853Zg zT@0SF8%(2U=EZ_{F6O#kQP~o$do8K}^p= zQeE>M9Y?i3TQCxx%XZy^(WQC_NcQosV5QZlNshj6y2F!4q6U2xazbJC2j9?ufRA(m zDjEx%<^M}pfOGI0xaS!IYH++OUyE&RTYsKBbG|#|2yrP;QyysLNLzwxv4aa2`SaSb z5-K3!_&5Ps3_yx+t5_9L{1LNO3lk-`kXNKkFFFQ!C4lqXBlj~PrLKfxkR)Qzw#cwH z0V*zR?0X+{7w6JZP|(2u1IanEa@0N|oK~}Zm>NbMb&TwdUjf^@CBGa_&Tr3ym>49p z3Tsso{X3xj4%k6iL-mIio=?X zF1V5S!~*zMy;)%pp2bTn$7q}KU1DfYm*4%K(44^5vXR%^C z-gr}f6CdJ`{?v?d(!X3kK}Aq4tNu;i%b+~dTb{?#w_`4mX3G>jb4SJtQ9ZB^{VGr&PFHs?vBQ2HakiXM`oUPoae8mo?kExj5#68&{*VoY1HI zvoYn67p2>+cb3hMHk*vs2z#i%w*mjl6am#MS}w2tvun43L>#L$5)je&QCV_4KV(0B zTa$_zGAv3Ijc<>kHzB9eu6`QuOx5OCnUB@>vCScCgC}%dC^^vZtq?00zpQD?!iN!Y zLF;_L_2^K53e&)V;lxu~UUQL+aHw9(m{2uFccjb&$a@W9 zlS-#j*EN_~v(#4e#XP^wx#YB^1A`F(hZNo}mQ6b01O~Vb_V07P>_}t>Cu5|gn*Z%qA zE&a%KC3!cE;jQD`uaMeXgq^AfTHefD&ng&!cH?|=zAT83jjoSH7>)F9CW<+`-vdSI zsIM@xf@k$uS8$5p)P}4)9g9{XU2w;Mk^Y3-V!dQoQ}~2b9EGk)-zFzsPCpmi7)_ux zpwbvOKEe~!Gvi%g7!!;<7cNwf-PC2hC}h1Dbc0%HdmgcfRjp}((a_2XJiSejcJf)b zV7zHYQrDWcJfd36FrlN75jXP3pYH+0K*kx^j=BBqu>RTB2(m)ghUeufSJt+HxB0th z%$KrqnZof{S>#t&$m6%u+iJYb*q%JBrO%F9bNDyo>rYw|gkcA-PQww0UeI5ep?WsY zOdlzxJ`rKYR?^Ac&`hmP^jR8jR2&nkpeI^b z*!Ja2osd$RUH{p*JTv{P8;DsOZI)-fFQ{L920tyoW*y-ArO|=0&aS3dUm1L?yxFLl zCPp*T$3VsR0|dVmaH^x6Wn1!-6Y^^q-UKVZd_|!c>wZlm#f7jzE2@RPbx0pdfW)0k zc6{{5WAIQ(rK`8LRl}xLCchq3U^IbYJYwVw4X@|oN9Qrw zJx<|GqM`X@!l#?)J$}@tWYBR1@uvnChJ^97+(nO4 zZb&FRCQ_KRjzB9G-!=yx?p2%0A%`bB+WP3(mv|=H}ye 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

N%OV{rR?3;7 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�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 tc=4h!=_)#=mk;UXsRAp$vk}C=RaRG+tC03 diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/index.md b/source/mkdocs/docs/sample-configurations/govcloud-us/index.md deleted file mode 100644 index 9cf5424..0000000 --- a/source/mkdocs/docs/sample-configurations/govcloud-us/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# GovCloud (US) Configuration - -This section outlines the sample configuration intended for United States Federal and Department of Defense (DoD) customers operating in GovCloud (US) AWS regions. Please continue reading the subpages for important design and architectural considerations when using this sample. - -!!! info "Subpages" - - [Overview](./overview.md) - - [Organization and Account Structure](./org-structure.md) - - [Security Controls](./security-controls.md) - - [Networking](./networking.md) - - [Additional Considerations](./considerations.md) - -!!! note "See also" - - [GitHub - LZA GovCloud (US) Sample Configuration](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations/lza-sample-config-govcloud-us) \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/networking.md b/source/mkdocs/docs/sample-configurations/govcloud-us/networking.md deleted file mode 100644 index 3fff7b7..0000000 --- a/source/mkdocs/docs/sample-configurations/govcloud-us/networking.md +++ /dev/null @@ -1,10 +0,0 @@ -# Networking - -The default [network-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/network-config.yaml) configuration will deploy an AWS Virtual Private Cloud (VPC) with a primary Classless Inter-Domain Routing (CIDR) block of 10.0.0.0/16. - -The LZA solution provides the flexibility to easily deploy additional services to suit your cloud computing needs. The default deployment does not include enablement of select services, such as a NAT gateway, AWS Network Firewall, or AWS Transit Gateway. You should evaluate the configuration options to configure the network architecture in accordance with your infrastructure needs. - -The following network diagram is an example foundational network topology. The diagram identifies the use of an inspection VPC for where traffic can be inspected and filtered, such as through the use of a web application firewall and intrusion detection/intrusion prevention system. Network communications among VPCs are facilitated through the use of Transit Gateways. - -![Landing Zone Accelerator on AWS architecture -- networking -resources.](./images/image2.png) \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/org-structure.md b/source/mkdocs/docs/sample-configurations/govcloud-us/org-structure.md deleted file mode 100644 index 0b7082f..0000000 --- a/source/mkdocs/docs/sample-configurations/govcloud-us/org-structure.md +++ /dev/null @@ -1,15 +0,0 @@ -# Organization and Account Structure - -An overview of the LZA organizational structure is shown in the following image. However, you are free to change the organizational structure, Organizational Units (OUs), and accounts to meet your specific needs. - -For additional information about how to best organize your AWS OU and account structure, please reference the Recommended OUs and accounts in the For Further Consideration section below as you begin to experiment. - -![](./images/image1.png) - -By default, the config builds the above organizational structure, with the exception of the Infrastructure and Security OU, which are predefined by you prior to launching the LZA. The following provides an overview of the network infrastructure. - -The Infrastructure OU provides the following specialized functions: - -- The GovCloudNetwork account contains a network inspection VPC for inspecting AWS traffic as well as routing traffic to and from the Internet. Traffic will flow through the Network-Main Transit Gateway, where it can be inspected by AWS Network Firewall before being blocked or continuing to the internet or its final destination. - -- The GovCloudSharedServices VPC is intended to house centrally shared services that are accessible to all of the accounts in the infrastructure. For example, you might deploy central security services such as Endpoint Detection and Response (EDR) or a central directory service such as LDAP. This central location and corresponding route tables allow you to efficiently design your network and compartmentalize access control accordingly \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/overview.md b/source/mkdocs/docs/sample-configurations/govcloud-us/overview.md deleted file mode 100644 index ce4f678..0000000 --- a/source/mkdocs/docs/sample-configurations/govcloud-us/overview.md +++ /dev/null @@ -1,10 +0,0 @@ -# GovCloud (US) Configuration Overview - -This config is an industry specific deployment of the [Landing Zone Accelerator on AWS](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) solution. This solution helps automate the setup of a cloud environment and establishes platform readiness with security, compliance, and operational capabilities in AWS GovCloud (US). - -The solution is architected to follow the Federal Risk and Authorization Management Program (FedRAMP), National Institute of Standards and Technology (NIST) 800-53(5), NIST 800-171 Rev.2, and Cybersecurity Maturity Model Certification (CMMC) Level 2 compliance framework control requirements. Through the use of LZA, preventative and detective guardrails are applied to vended accounts that helps customers to align their cloud-based workloads with their compliance requirements. - -The LZA is not meant to be feature complete for full compliance, but rather is intended to help accelerate new cloud deployments, cloud migrations, and cloud refactoring efforts. The LZA reduces the effort required to manually build a production-ready infrastructure. It is important to note that the LZA solution will not, by itself, make you compliant. It provides the foundational infrastructure from which additional complementary solutions can be integrated, but you will still need to tailor it to your unique business needs. - -!!! warning "Important" - AWS Control Tower has been enabled in the latest sample configuration. New deployments will automatically leverage AWS Control Tower to streamline your multi-account environment. If you are an existing customer using AWS Organizations, you can continue using your current configuration. \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/govcloud-us/security-controls.md b/source/mkdocs/docs/sample-configurations/govcloud-us/security-controls.md deleted file mode 100644 index 56370a9..0000000 --- a/source/mkdocs/docs/sample-configurations/govcloud-us/security-controls.md +++ /dev/null @@ -1,37 +0,0 @@ -# Security Controls - -The LZA aims to be prescriptive in applying best practices for space customers, it intentionally avoids being overly prescriptive out of deference to the unique realities for each individual organization. Consider the baseline as a foundational starting point. - -The config deploys security controls that provide detective or preventative guardrails in the AWS environment. Collectively, the applied security controls help to accelerate your path to compliance. Configuration of the LZA is done through multiple configuration files. Through the LZA, security controls are applied to accounts to drive conformance with compliance framework requirements. - -All of the LZA configuration files align with LZA sample configuration. The following is an overview of the LZA configuration files. You are encouraged to review these settings to better understand what has already been configured and what needs to be altered for your specific requirements. - -## global-config.yaml - -The [global-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/global-config.yaml) file contains the settings that enable centralized logging using AWS CloudTrail and Amazon CloudWatch Logs. The configuration establishes the retention period for those logs to help you meet your specific auditing and monitoring needs. - -You are encouraged to review these settings to better understand what has already been configured and what needs to be altered for your specific requirements. - -## organization-config.yaml - -Within the [organization-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/organization-config.yaml) file there are sections for declaring Service Control Policies (SCPs). SCPs are a type of organization policy that you can use to manage permissions in your organization. SCPs offer central control over the maximum available permissions for all accounts in your organization. - -SCPs can be highly specific to an organization and its workloads. The SCPs should be reviewed and modified to meet your specific requirements. The following sample policies have been provided within the configuration. - -- guardrails-1.json: This SCP restricts permissions to manipulate or delete AWS Config rules and Lambda functions. It also prevents disabling of Amazon EBS encryption. - -- guardrails-2.json: This SCP restricts the ability to delete or manipulate the password policy, manipulate AWS IAM roles, or make changes to security services such as Amazon GuardDuty and AWS Security Hub. - -- Quarantine: This SCP is used to prevent changes to new accounts until the LZA has been executed successfully and control applied. - -It is important to note that SCPs are not automatically updated and that changes to the eligible service list may require the SCP to be updated. However, these sample SCPs are an example of how your organization can ensure that a select list of AWS services are used for specific use cases. - -## security-config.yaml - -The [security-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config-govcloud-us/govcloud-us-config/security-config.yaml) is designed to deploy a number of preventative and detective measures that align with the Federal Risk and Authorization Management Program (FedRAMP), National Institute of Standards and Technology (NIST) 800-53(5), NIST 800-171 Rev.2, and Cybersecurity Maturity Model Certification (CMMC) Level 2 compliance framework control requirements. The security-config.yaml establishes the use of multiple security services and configurations such as AWS Config, Amazon GuardDuty, and AWS Security Hub. - -It establishes minimum AWS Identity and Access Management password requirements that are aligned with AWS best practices to set a password length of 14. This exceeds NIST’s 800-63B latest password guidance that establishes a minimum password length of 8. If you need to comply with this please change the IAM Password Policy appropriately as noted in the security-config.yaml file. Consider reviewing the configuration details to determine conformance with your organization’s compliance requirements if they extend beyond the control frameworks’ prescribed guidance - -The LZA provides the capability to easily enable additional security services. Detective guardrails are established through the use of Security Hub and Config, which deploy managed Config rules. These rules evaluate whether the configuration settings of your AWS resources comply with common best practices. By default the config rules are aligned to be deployed to both GovCloud East/West. The LZA enables the security standards in the Security Hub which includes [AWS Foundational Security Best Practices (FSBP)](https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html), [Center for Internet Security (CIS) AWS Foundations Benchmark v1.4.0](https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html#cis1v4-standard), and [NIST SP 800-53 Rev. 5](https://docs.aws.amazon.com/securityhub/latest/userguide/nist-standard.html). - -A sample mapping between FedRAMP control requirements and LZA implementation is provided within The AWS Landing Zone Accelerator Verified Reference Architecture Whitepaper for FedRAMP package. The package is available for customer download in AWS Artifact in both the AWS Standard and the AWS GovCloud (US) regions. Many compliance frameworks are similar and have overlapping requirements. Often times, the same managed Config rules can be categorically applied to other compliance frameworks, such as CMMC. \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/index.md b/source/mkdocs/docs/sample-configurations/index.md deleted file mode 100644 index c5d989a..0000000 --- a/source/mkdocs/docs/sample-configurations/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Sample Configurations - -This section contains details about the sample configurations provided for the Landing Zone Accelerator on AWS solution. These configurations are frequently updated as AWS services and features evolve. If you are adopting one of these samples, we highly recommend that you continue to review the updates to the respective sample configuration and apply the enhancements that are relevant to your environment. - -!!! info "Subpages" - - [Standard](./standard/index.md) - - [GovCloud (US)](./govcloud-us/index.md) - -!!! note "See also" - - [Support for specific regions and industries](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations) \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/standard/authn-authz.md b/source/mkdocs/docs/sample-configurations/standard/authn-authz.md deleted file mode 100644 index 4a0a16c..0000000 --- a/source/mkdocs/docs/sample-configurations/standard/authn-authz.md +++ /dev/null @@ -1,74 +0,0 @@ -# Authentication and Authorization - -## Overview - -The Landing Zone Accelerator makes extensive use of AWS authorization and authentication primitives from the Identity and Access Management (IAM) service as a means to enforce the guardrail objectives of the Landing Zone Accelerator and govern access to the set of accounts that makes up the organization. - -## Relationship to the Management (root) AWS Account - -By default, AWS accounts are entirely self-contained with respect to IAM principals - their Users, Roles, Groups are independent and scoped only to themselves. Accounts created by AWS Organizations deploy a default role with a trust policy back to the Organization Management account. While it can be customized, by default this role is named the `AWSControlTowerExecution` (or `OrganizationAccountAccessRole` when AWS Organizations is used without Control Tower). - -### AWS IAM Identity Center (successor to AWS Single Sign-On) - -The vast majority of end-users of the AWS cloud within the organization will never use or interact with the Management account or the root users of any child account in the organization. - -[AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/) (AWS IIC) resides in the Organization Management account. Once deployed from the Organization Management account, it is recommended that AWS IIC administration is [delegated to the Shared Services account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/dedicated-accounts.html). AWS IIC lets you create user, group, and role-based identities directly using a default local identity provider (IdP). Alternatively, if your organization has an existing IdP such as Microsoft Active Directory or Okta Universal Directory, it is recommended to [set up federation](https://docs.aws.amazon.com/whitepapers/latest/establishing-your-cloud-foundation-on-aws/federated-access.html) with that identity provider. This allows you take advantage of your existing identity and access management processes for identities accessing your AWS environment. - -## Break Glass Accounts - -The Management account is used to provide [break glass access](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/break-glass-access.html) to AWS accounts within the organization. The details of the break glass usernames can be found within [iam-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/iam-config.yaml). The password details can be found in the Management account AWS Secrets Manager in the region LZA was deployed to. After the deployment of the sample configuration files is complete, multi-factor authentication (MFA) should be enabled on these accounts (please see the next section for more details). - -## Multi-Factor Authentication (MFA) - -MFA should be used by all users regardless of privilege level with some [general guidelines](https://docs.aws.amazon.com/prescriptive-guidance/latest/aws-startup-security-baseline/acct-05.html). A number of commonly popular MFA mechanisms [are supported by AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html) to help customers enable MFA on their accounts. - -## Root Authorization - -Every AWS account has a set of root user credentials. These root credentials are generated on account creation with a random 64-character password. It is important that the root credentials for each account are recovered and MFA enabled using the AWS root credential password reset process for the account’s unique email address. - -Root credentials authorize all actions for all AWS services and for all resources in the account (except anything denied by service control policies (SCPs)). There are some actions which only root has the capability to perform which are documented within the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/aws_tasks-that-require-root.html). These are typically rare operations (e.g. creation of X.509 keys), and should not be required in the normal course of business. Root credentials should be handled with extreme diligence and have MFA enabled per the guidance in the previous section. - -## Service Control Policies (SCPs) - -Service Control Policies are a key preventative control used by the LZA. It is crucial to note that SCPs, by themselves, never _grant_ permissions. They are most often used to `Deny` certain actions at an OU or account level within an AWS Organization. Since `Deny` always overrides `Allow` in the IAM policy evaluation logic, SCPs can have a powerful effect on all principals in any account, and can deny entire categories of actions irrespective of the permission policy attached to the principal itself - even the root user of the account. - -SCPs follow an inheritance pattern from all levels of the hierarchy down to the account of the organization: - -![SCP Inheritance](./images/scp_inheritance.jpg "SCP Inheritance") - -In order for any principal to be able to perform an action A, it is necessary (but not sufficient) that there is an `Allow` on action A from all levels of the hierarchy down to the account, and no explicit `Deny` anywhere. This is discussed in further detail in [How SCPs Work](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps-about.html). - -The LZA leverages the following SCPs in the organization: - -### Guardrails 1 and 2 - -These guardrails apply across the organization and protect the resources deployed by the automation tooling. Note that this policy is split into two parts due to a current quota of SCP document sizing, but logically it should be considered a single policy: [part 1](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-1.json) and [part 2](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/service-control-policies/guardrails-2.json). - -| Policy Statement ID (SID) | Description | -| -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| CloudFormationStatement | Prevents deletion of any CloudFormation stacks deployed by the automation tooling | -| --- | --- | -| IamRolesStatement | Prevents any IAM operation on protected IAM resources | -| PreventSSMModification | Prevents deletion of any SSM Parameter deployed by the automation tooling | -| PreventCloudWatchLogsModification | Prevents the deletion and modification of any CloudWatch Log groups | -| PreventCloudWatchLogStreamModification | Prevents deletion of CloudWatch Log Streams | -| LambdaStatement | Prevents the creation, deletion and modification of any Lambda functions deployed by the automation tooling | -| PreventCloudTrailModification | Prevents deletion and modification of protected Cloud Trails | -| ConfigRulesStatement | Protects AWS Config configuration from modification or deletion | -| IamSettingsStatement | Protects creation, deletion, and modification of protected IAM policies | -| GDSecHubServicesStatement | Prevents the deletion and modification to AWS security services GuardDuty, Security Hub | -| SnsStatement | Prevents creation, deletion and modification of a protected SNS topics | -| EbsEncryptionStatement | Prevents disabling of EBS Encryption | -| MacieServiceStatement | Prevents the deletion and modification to AWS security services Macie | - -### Quarantine - -[The quarantine policy](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/service-control-policies/quarantine.json) is attached to an account to ‘quarantine’ it - to prevent any AWS operation from taking place. This is useful in the case of an account with credentials which are believed to have been compromised. This policy is also applied to new accounts upon creation. After the installation of guardrails by LZA, it is removed. In the meantime, it prevents all AWS control plane operations except by principals required to deploy guardrails. - -| Policy Statement ID (SID) | Description | -| --------------------------------------- | ------------------------------------------------------------------------------- | -| DenyAllAWSServicesExceptBreakglassRoles | Blanket denial on all AWS control plane operations for all non-breakglass roles | - -### SCP Protection - -SCPs are protected from changes by enabling the **scpRevertChangesConfig** key in the [security-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/security-config.yaml) configuration file. [This configuration property](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/latest/classes/_aws_accelerator_config.ScpRevertChangesConfig.html) will monitor for manual changes to SCPs and revert them. This is enabled by default in the sample configuration. \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/standard/images/cloudwatch_logs.jpg b/source/mkdocs/docs/sample-configurations/standard/images/cloudwatch_logs.jpg deleted file mode 100644 index 9ffa75805a63f591b3570f550f5037b08908247a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 321695 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

IJ0kW4mlL??>oDA;*C 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 diff --git a/source/mkdocs/docs/sample-configurations/standard/images/default_ou_structure.jpg b/source/mkdocs/docs/sample-configurations/standard/images/default_ou_structure.jpg deleted file mode 100644 index 2c9a2a3b425a26c6b908ad32383c9f7be36e63b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9774 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 N?}p&t)bl$T|1aTBr_lfa diff --git a/source/mkdocs/docs/sample-configurations/standard/images/lza-centralized-logging.png b/source/mkdocs/docs/sample-configurations/standard/images/lza-centralized-logging.png deleted file mode 100644 index e14885b5ce31e4061e82efa5467d769acc384785..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294390 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%0AX^XC|CBX2)Ozf*9lUUb~$ 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%0Dn5yZ|3Bn>Pyq>O6XDr_h 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 z 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 Ho;?3Q%6YFJ diff --git a/source/mkdocs/docs/sample-configurations/standard/images/mandatory_accounts.jpg b/source/mkdocs/docs/sample-configurations/standard/images/mandatory_accounts.jpg deleted file mode 100644 index 107f31e5dd10f830f07877bd2e3830df9e9f4d18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154288 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%0AmOX^Q6Uud{bldH`pVvW_m%j#`0Y|u)@_b; zX~LCv{lG)(TnVmn!vVgilhK%+(rT$=B6tS=49@05^vA^&GK-dLE0_`zFaP zu%~^a#KpMrnM)99u}o+Je%{KjyfZt^leQT{pb7*`aIXTG+H}@Vk{PgpqvdKJ#@TJM zjKJaxhvZcW@8N78uANBxX1p{$tdF$B>EtPWJInh2{VD}(HzGFD(~nWp z^YZhG0zQW1f?yQ zms`xx1$PdrS+}A&F7lyxEgbq+);5jcD;aEk^#Pr`7qn?>{X{bEo$0Z@Vig?&`$kDk zBrSWLq$V_kE8_aJgEYel1-NUPK1gY??9C9UfPe|^6(Fg7htf_Gd69wmX*b6q0!G1d zIHpjQ@a}_WPhTu^aQAb!7cQ68gT9wFZ+?%H<+&eClW=?lfx}ZY>EXB;GeJ1dtn|}8 z%7kG;W@?JSrs%y6SNyFXK1u1|Xhx>*Fb@SgW)8Piue%!p_+B;)2U$T0(Qo80TQ4h~ z_%uk9^~llLrRM_%;b}2)SSKp+j|gh7)gw?fy+v|YVwDP5>p$9#Fy&|&K`SK<0wJK^ z^X_tIL=F(VW$oMHVoyWB5ZF=#{3O5Y2UM~Nl7J6+5Bdh)v!2>4UN)}J#~tqJ@Q;#? zJyM_&j4gX>^JKv^EEZ6{Ay4uq_*OQ>cN#K1ecv46odLNz&&lhEE=m)!has?O1hlH* zK><5b(3WCUC{|3k=it8eblkNbulKmyA3yT|06+jqL_t)mi0!JVCLe@jLRej4-_3fi z<8p>V#VqafH9<`?CW@-^EpYW)wye!u?ZSkW*oR{VtlKUP72YFqrmq=8pgaQLh-T^< z0k(fO^ze@b&>oL&fgc*L^LKvSALfEZgkc)=#$P^GU1y#5FIn-Q?{If}ei&EN2-0G} z$UMXMFl|?i(4A>BkHDY#o7r*%ltuxLiYJp$Oe1I+(8xKXg9$Kq_?Obb)0^?jhN+mA z(R<5jwJl;8y|)F5mWCl<2-FniUo$G&Ec1V); z-5bcL!7C*{A4>y4ShcYr5&6w@_~+ahmY-Zs;6J?c&->GjeqorvPs6%2>Ixgn=Z}qm z)+wz|TEVoQ@fSIF+9ETTO_jxK7sw$!kJN9Dtz2D%zt-a*yw*ppsTz@MZay7G`We7qdTIrijb2}8b>RC(l z=B!)UI>AyJy|;>1+aj{jdt0DrX&3^AKpi2V0!Ku*@A`eWrl`G)?&m9(8ROQ=tl!gg zg^Q(iZOQKaKd*#T5NvP_+d(9*Q8^Q^64XNGsVq;=--@A^@RnMNa z&sSumh5G1M$$3~bzcHu9OP=JQ?nlWD$J{S341ZXKdBzKBZ$v#Wt;p`Qd1$wU4W{bf zWIj7L0w%a`2l{h1k3v^v!aEy2`9j$P>s;ezvv+*PtL6j@1ZjC;oi1g=@COFrLd!7_ z{VdZ_lBqH821w8?2pGL>3;J7@Q5a$p@>J(&#C)vhl1 zc0X*rWmsI@vNakkxFkSu5AN@_h#KD0Z0&{IljbO z(CImJr+3uC$%bEZdqS`>)>r-*Xqi;kv3q!S^6qG0)2rIU&7hV~@s2?;@LW*iO_1X! z2>1T^0d`i#zvalTFVs||`~88dVCO;G*C?#|4J`5@BP;Twlv7C`J$lZhl*4074sL>` zrla}2D(|gGSt5Y_imN{VYd5=y4?ee3GgKarPRc>bf<~!DA}jFeInN-r@4-Z?+Kgj+ zKTp5a=M>Xyu;*!PK?vQVvI*4-7c!9)ocArLo~z8$iFFEea_MPx)$HJcSW~7ZU%w6yEVhD6`Ne;P|IA>}shS1a7tUg4B99|V-=V(i~C4yD7F zs?%Q;d++(ob`FYQzfXBG&lXBfxcE+m?OwZMy~mf7@&c=R4;EPD5P3$74bk%g{+yHh zu6AAN?jIgp&qdRTfF_i69;q-%xomK%f%JYb()nikFD2?aps|2g9V;$XiyW3&LFF)IAW}2LKw$|N}2aly0*t+tlT5?*(zFbxPP=! zy|0q)J!b7318b7m{5}F7QkB!Sh#Q3ZT5=I%d?H>_S=ac|^wOLBD*OQwY5>7zhP83p z8bVovOf<>SC|q~2>IL$m^!lOg09r9L1o|2+L_tGloly|CV$w#!MPNA9Ts7XoS z@iFWVT0q^~eURE0!Rj3xOo zlwMYvV=7%!*|t7kj=xX|9og_Am5a#rjkID#9u8K&TMVI`I$N(xf<(%4w*bY;&52m! zj*XYXRT)4bt8&81C1KOi7mbQ)D*w`n9)&#EBN&{A#Cv;@oUw>Y;j|ZIT>5PW%B{vCJ_hd(Kb86lw3cz+8NmeAvMi*C6(hjuAin1Jg>@Tg|YyjPl4 zoKOc42$PzgGofZDH?pgL`$UCpshkK8j5HZQTVjLM4pD^A`c6~sDWNJpGB*u3rgoVl z-rD!;C_sZESfg;~xSWPC;jG>>Ju0i~E!t5NcC60IHx;izuH9B6?7_S+dW z0zRJ^0?qI-AQ*1)=P;<-Sc&^dSVOxCKTWj|%&BQq1+J)7f=aC~axTtMl0}i7m0PT_ zgk6yao)qdm797}-1`9kZ{!XBHE9#UaZ&N_O;Db{;y{)@?GDex4U{@pRJ^KItT>u<_ z8Nz`*gZInn6kOZmi39Hq@Gnv0H7V0xO#v@3rqQ57l(cJ@=si~ZpRfM=7yn!o6+TsC z-pipC=Hv+ZJ{?I|F4q>Ec6bpX^E+%q508$a=p z1qUM^)s$6K3fzF}Y-l?%xVI`HaN}w0@%(p7e*>SRkD0vge!R=T&Tx{Gi+y3xrMf-zCdO5^49Udr`1D6jJ%LRvN4+Ve#z^3+l$k$78|T z$$Ci)TBLfdF7ZBhM;g|O6Itvs3*~xwrkioH-g>2$FZ9D@4802+Hp5s;`?IsNdy{?w z(vKhf3?3WQ<2N%B6AO+`n-BDBUC*>D@?wvifgNSehf^domg$`;Hr2(&l3+==I)dux z_(gDy!he>?`#vky1Mj;{=#EX_=co+7jpms@Vu-06K1qH3{ljH%K4Jn-4`fn`vwN29UN--?v~IUk4&LPb{}5vd-vjgG0p`D z@UJx3P`(SIqCAa4ul2!FVpVpYYvXYGZc~UPc^Wv4tI_=Kyc#JxmNeR_D$3HuC3b(l zR+1Q%Ao5Y}_K|RDI_$YNeY&x{8X}7s-qX;9H?pkL1>Hkj-EwZ0F!^>r`o+T_ei{13 zh@1efBa*Of19)PZOmJP(H3K(V6E{LaLS|~dQ{r5&YD&q{VgA^k%w{Zly)}sfUhV`> zwgP=}&^b43be0)Wy`?%HpR7}vo$kyiakP@HKKuP;T|t4cj&G1*`sDOWQ3y!ia2^~}O$gmDNSdFHtw)<|Mq^Az zlMpVw9%2Vg;lU2Umd-9GcrC6cEo*B3_aaXuR{UGAF8z5$DRaZ~L{1N^pmm~Q;8P-a z?GJ?-*S4ms=YY%YX!0=Itj+H+^A%vVC-8ilC-O|0?aLsO#DIlhZr8~_MBx0XZ)@aa zUPG@W9+FHPM786Woev2jLlw*?Y3kwFZ3UX_4t+9e18c`4P@=1sYcdddUt~qTo^cv+ z$y3njw`umur?DxmcRuc0KiZ68%YlhHsaF74;3!y}xA!W^FIT-gu7LI?hIZ8NdDY`d zz+!4Ou~3*KVK2|fLz5wioQzC#Q=>w=K-2qiZ_D20cV{Y~P(G8C$mc)kWA zjd*S+tRawQl<#o~B~2Z7rKS@M2wZB9-T<4HNUK2={jy)maRNaZW+AC_mDLj{@_L7A zwf}G@?bYQ!I$y>YHPXnv007s&iTpC@mhTgnh7=bH{8%Iy7Iw|b7fV=Su(`p*xcwxe) zTyUPMxd&S*fx6l&o^r8)Jo5|9JYGHUl*3|J2BdHIeuDsFX}^hlA)$;)NFaUV28F6$ z!K3L7w&hw=O{p>13fI(r2s&9>;SVag;Xlu zz?_<7{dTX>WV9D5RR_$Jz&)GBby8i3vHoBvjSs-49ft9$mms6I2+_yad$1^G>F z@n`Njt6n6cxt}H-XQU`c@5y`<^jw$8Uo7WZu=zdfO1H9I7PyancjP!ny8;-8FN}Kg zRf;A`*@fqK!1~7)uel8p@r82aw5%#DmxB>G8X#6h-^V@kV9Uy$^`h)og$3c9s&akB z-}^tkvITs0XJi;=YMqQj%;qNZ_HJfDrM8cc9#6c!xa=*mddHn_Al&RUn8f)2u*99} z(eN3DoYDlh)BcdlqF$BFHo{!M>)i=sh9u7ymrZL|i~UuAQhMFqWbPeOqXNEP+iaJ| zPE@0+D5c{w0m&*5Nw+hOV@P(1DO z^757?y6UfdZvwA_vqmet6-3HhXRo1197pgzISPha?a2l?7kl45VmbESzD-3? zEFj9Cp4JQ!vN$h=4`mwy!E3oX>eCh}RHM^b%Sq)-hfkPIhUa+v_swdPVPo#?a5DB! z1`~51OJ<6d@^EdAO3T><^i~H{aCmq-@UV)adC#%W7JYGVWt#fy1zxULne_>yrJSq) zwVV8V2`YQ_i<=k>bGqKsFvsE*s|o_IvYvORYD7POm==e@E&cRj|KcIc}k%50WI`QPev_{REX^uT8)J5S00V$M$d$F*c}6V_n6kKT$(k z#Q4w4%V^c{fbi=L&fnc($~W@+O}{F|DcvTE5?znI`KzAm$r7|;2+kd0h}K%Rrh{~^ zu9Ajl(HsvUq)?Pc`MZIiYv$QLfr%SPm>i66&wE#2)Wh4k-vSZq`7(Lk?&Me33#YD_ z8-x@O1@70bogD#DoPla?Qx2z1H4m9&`F5N9y9p|9$80nLAkEQnOgH(^m)9#=6LNK; zh(!q4)1tB?P}n6Ml>Eib;qojmJPhZ#lR^8+JdCH}Z!&^r>E)X560#R#alF76b8Pbu z)!w)Bi+gF-RkKgr9#{EORo?9vgc-VjD7wDK2$skv>w4nciMsXOqYnWE9Y0g#b1_%@ zzVc8TBxC26@@&l(O+BnGZJI6;@|&>SJxpeDYVcAn1cCLtzu7kLaS%DTF4fEF$cHk; zNY*a8G#;&ak5-uW)I@FCzq&lR zff*aNapE20%%28)o-^G-MEDwq_%6DFt_NszR!x{O z<|Y~TJr0f&+~!g!Pv2~ZTW;chHBvk>ol`{$H|^OyUhUX+k2?hHgZA(CJNUtg}NV8VPzv2Z>boyys$G1Ms{PFqvW5Qf$;$eFHG&lbw{ z_lz-mPE5A~JJ7AhETQ>|m85cCL-Fi-upKH^=EQgM?-&K&%D>+NxhhrjJ{Z5(`ysTk$BGVwe&7GjG&NqSyjV4FVB-1 zPXILod>W#<2>BflG*XM^(9oFoSaJ~;0iZv?5Ty+gd8vMm|Exx}J3mXw|hCWGcR+}Ey<6AF^10h&fhv1Vx276XOB_&I zdIP}+YfM~ZyKhUeK$bSrjH}ERWoX0Gkb{|AEm*J8has)TE2IZ%^35b zm3yh_bvcy0y5e(SDD7_kk%HmEMzv@vml33D%>A8w%8mV!Y_F9-i1q#~xv}FxKbFDo zX-$WFTC7-_x%qiS+vbPCdW})$LBg^FdPV-{4KEP95j_@dD?i)YP+bosprDke+I`v> zdBEFJTS&$K_7bf0o{)<6Ve^|;uCxH9N5{)C9`av)LhynnypkOM)L?4Y&`JmlJT54?fx?Y1l4RWO(aTed(j$hZsq^@aW$(S@Jq7ENO< zel(_igMw}PpDp3sw6I-A<;*~7qu^r(4TvLo_Dv~t%FwWL<<=NniPp1**8P6W{_nA{ z9;zO_HGMc-h`}qtKtr8R?9NmsN;(mt@oyD5E`9J9iL~k#TFs;iz3{?!xga5pyJUel>SiUJgjz8{&H;RCCNY~LX^e&%5MfgrCPImml)b+${G_7yly6u$1%V`op}yFLJ{R(sKk zk=}DjqA<3C;3_64<~`7rSSU|Yr9A6sCEKe7ug??e4vNC79KI-^uHmK#_W}%&vz|uHzQ@b zd1l_O&TM00p#-K7BVFh~%k620+3Hz}UPHn)x3?$js^eTsj4jSZ)#Q&FS0#VO=lj!b zJH$ZF;j5^=VApM1u>m@>%Y8ed$mFIfB_czq1uQAEk!a^Ms)z;ni0rAdA`J5|(Ykib z0m~J`{}kR9^D3Ek0O3QQBvmz|2A|@?@ z_C`V#)6FtXGrMHRt8MWMpH+flH6tBOxngpPxSC7OcS_F3-RoN@ zc7)SDt1-JB|3QB3H!iSh-D>X~hs|SpalvH|;^eR#=Xwq zWBZ2<`;M#tAsblR?i_%63g6Z~vO=NXL8dT|f1zGx$xrb$D`Az6@mK>7P`4Y3d|OF* zbnM`iy_Y1-N5PHX8V;Z1;}dnc4b$c%J?_^V$vL)ERQJH?4Q$BzUtJnOuw@^|StkXm z;_HGoJY}BuC<*;!**|iFwvee#leuCCDty*kT$1fN2uNXxoc*JfUK&bDX3dPfR_8VK z#^x}^uVCekXA?F(HRh8_yw2M@dr$>XU(q!9h=fRfwVFSy_?o%O zLUTp1x}EC+zI?Og+-;`K-m0?O?05>|j+i*B^l=4N#LM$BwaA@(=?VGL(g_;S;fXGy zz|+_ie7Qhx{?WDR@MRv8=k9HBb~1RSTJVKSdT_z#`39xlh&%S%t@jA3s!w(pxF2GIQAh$FmwoS}CM7f{ZGE zC%9CC7agP&egL9I;8zVO0?;v=`*T}2Eb7~4^UN1EUyWP@bv}qpa6ijpwh@JWz@u(b z93sivDt&@NT`LkHH7@}MD7vD|T{E^F8apSTjz%rXjMT~Z8#b|sh6*K`r0sx!*aTe9 z5ZvnlOd@4OA$xxOucRlDao4Vi>@RVgUeMUchYrgIU~h8y5SNMY6# z=u2<=<8ZC{FW+0wFP=}^M?z;vD1;Ip;#nuTz)GsANqx!xvnEmf`=h+H8K3<%M$o zxcX{$XEf^P<@Og`=47}^+|}#6d^a(;fN}N3_ZglW0s6Zy9%E6aYs)!1(POWIBl|>V z?oURI!cn{Qw6HVzwy0nc{%4{)#5G$d!KbV%9iaEos9oySc`QdGmg5+hnoFeafu>O; zZ`!z|SiRkyq5u#YJIL}=LR~L7zWTX0T3}kHKH`?$Zvw+UM~WQMG&dKzLe&0p1wJ5NwUhTHw zp)+Wyd$Sctc)~b%mH~IYQXSdLjPh38Eux~RnZrT!!(2DKIUDx_V)9UDwuk&=@e?k( z`dX#9hrdnK0RsQa6qFI47c)3Rj|LN}r@RrniEp9?P}925c9Gfq-8?>%-FbgIvv*(A zsO6b19ggkTMbJE>uZ4e}3i$a&;~N>W2K_IYpCl&a;yfBp^4*b<#oo9AOlnEo7aO=L z@od;(v{x8BZXZQ?`uYdeWLQxOH5vJWs*td(evAc6Z$~vLpSF^0j;z16$-wHXKJHj? zTQ<`NlepsUp~96{GT2BFEG6xgBjvTYuHvsalK%=5QugVf5$)*W08*NGH;q^b3-0Ua zc?xtiHU^6e$a}Q1tT>1T@Riio+AD{jdF#v#UzskeZ@Toq;c?Q;OD?IKzL6FErGLH{ z@6N3}Y~wa9DPsbw)g`pwAD5jma&v3dzqw=YOEit#vRkZxI|<4*NxQ>R1eVCRxeq&f zz^4hit@)~qQ zmWn}G4*dr32*1sBf}*TR8Dl%~fpfc^*t(u&2-gLQH{cVtSRD7d9SDLAMxWzcv)6OC zIqG=5n{hJH7vo~kwSOk2gtBYDHPge`!VcR%E~}bmKK?r;y-UxIFW4Sttu{={*s-Li zmYSMM^44}DuV+(ahFcF=#*@-md1&%DhXQ$e?Rj@t7{67N?NDgeqjshs`1T=koaWmJl zHfFz%uU@Jcd34xSo+7H3qnQK^R3w*O5(Z1;w^pN>^Qhiv&qRv#`gFI9ytitAwcQaC zic5*>jyP^z1=47&;D1`$aU7r5ILgOEtzcDQg>FcCTWkciw(;50p0k%>r4#(}x0ALo zH^#qEr?CGUzk;8eX=%@-r-gfyx2&DpUTDd*nJH#LDW2b3Xfd!ne;n*u=yXBi_n9zs zvC~iRRIF%!uo>_OH+Qk54yo*syZ#nhdOVXfmuI6TpJeToKAub8h^$JnB%YL6N6T0@ z+sUFm<)d=MEStal8CPuJmv>d9YdN}FVo0aC&iwF@<5DX)?f(N8f>3`HUbyrP>@*%< zMMOw#qaI%QFx^zs+zgV5M^4#*4_=O;-X#qPPex2 ziJqEHAq_2i<22ZUZQc^kN)Y;Lf*JT4B$|)(9QwVn<^dNu3wLNKPj~C08-rhJY3=us z)OH`ALs5zJDJqinJwK)SdL8Qq`od{EnNR=Z8Je*0u4=cq%A%x+;>dXn9= z9&|u^+P+9wswZU=Wz?khGjz3=OSr!?FPf*+(MI#;Xiv_>7ayoZ8*dY?-9)strDeOC z<4W2%TVuz+w~Ouq^mz=j7%j5i1eY8%4Eh`tcD_D&Ms{^@g&96YE_+&_!7z8uD8~Hy zA;$WztbOr!=;H2v9s9eJO#_DZA3`a-A=c;D{7)8*Aq+QE_ac6sHa}lcU{GwCx1~A8 zUln7Da&x-RxKye47r;TK>$)ep$E;Pj*A#}vi%_8fzhvUy`^=Ac}Y%r>#LE_72>vy>?iQhKJs-EQs(gJ5$b>_!TQ{Z%i z>BC=sv8|;gC1vHx^H~q$0SAC$zmh%7+cCq@fiKr)TU{D2))s|<&;*HF5G49jMqjhpBn z^}x3TpFdkM5nT&+*S8gK`zB_)3obeiZ{HVWz{MH=2WeVG3Mg$jR!!<|Sc+EBYMF0V zYk-L=OPYs2lLc3wYq&h7XaDo{RXm8*osM>D!$&YT>i-X`nkNRMu`_cSDC?jgDSzO(`5@Yg|!Qgc(eh=3R zxL^t!^7bP*#ydf#E+_u)G3CXDY?+bk#(AUCEh8pG049_}Dey7Va<~gAEHTjwwlGyj(PmmD(a8+B`!t1dN59qc-+I998gWD*G z4&X)i{~gtuL*-r8bmnqKYuN9M_R*4Q5&?|rLO8qMQ5X%|FAxNbMy_;=~cOnP1Tf{(TRbt7q2ZG88-h8Uxv&Hs=aO=+0a071nKZ{=Z**RA z3;os$lAyUG$vgMGq@TFNp!c=K;d^X~W`yOe z__pV5LY#4MH&KB#!lSQrR{>srKarB9zB5XXh4+B#W=Eu~3c4BYKQE^Lj`!UD#8=1u zFM|?501tsMdS1;%=U8TP>Puj1(s&*n0&Me^S}Uu{&vI}3dUHm`E%@^F@p?aEcVEgm z2qWTTO$*n+GZteai%iR|lQu*q`RDGg$mxEq$8XOd}SN<|~! z@P(|0)fXd%Y@UiCYe0=+{KDDEvc>J-?ig7s(yA~N?)N^SLcb1tSf3=Pan)u4P@sHe zEFOS8Fq`r1XAZl!91b7J6d9aMdTy$HDd@*OahhEy251JfXDb?G7tB9Bylig4>67-IhmI$mo_V|DRBD} zTfvEkL`g2J3O6vCfeqckVG2~i@Ag94MS*C#| zbwJsWsgW4@$xVo9A^)Vr5=I0{_>3FspzH3UCWs|2HRNATCd`-A&Nu#8;ezz3jc7J9 zve(_rDqP6FeUNpgPy?r&%)V-Afb0-^x*_pgIDP&`Bx`H6sf<&=V4M-jK-wHw<%;ZA z`X6TtT%EJ~_Z-#ewY7ui{^5L`{5Aod{B^vs0K4>Z57>X=|B<~Q}AG?^b#e>O?P+&iUG4^5<$?uPEmf1B&o2Df1_Qv+T zSq;PG$M~;*`@p?nz3*)&%mSp>rC)LSk;lkF94PuPVnuk3d4&~c1KbLryb2FSay@=a zg_#<~3FD-+rKXW>IUrTvr6kvkDM%{G%_;Ir$R7h@*%PlW zMOu_oZl@8VfI%~))7!7gavgDM+~d88!^yv>(@VG^2_@`kYe?TW^l`P@6lKY2zm zgq@nK!IhBvrO?NrA7?3Fpwhe=bMQNma8qo}8c-kd-C)|0DrWf{li$72;IM!y zr*|Z$4w?4U29>qFrWu@rgxgE_Z#!HcDAZ-$?T|>xn{>JfMTks(w>?Hj9h&AXo30)= z=O;+5*{149DHr>P-I~0TRzAgUPI1}8LA~J{Aph}jp(4dNg+hP1zRB(tZi8La^zq4% zXlrDvx5eOWP0zDFFo@g=Y<6@){LdUVX&xGwSa8VyH1W#;Y6L@Sy1e_k z-!}+`wd;xA0kptcoWk!ko7X6Qgy91L?dKi`TKU4B-3pg`wDjv=K;nq{5D>1xS^KeB z8&MpWW@m5>?Jx{E1y571lmj!Ov)6UzaQB=2rtTv)vI%}%0phcUsArZFO+&>$Kmv>! zxP5FYqR^yAXVa><#O;#l(8{H~u@ah7Tz)Ovd_NHmw13Fsx$yqBC;#z94Ohv8HsqzaOR@2>Ub0?;flx~xP71v1x?Ju6E3T`{Kjn1P0mpnW zo&>Gk>x1(S5Ts1h1iTi6=YT=iml@H+IV}}bNLZjzRo@3;&7@h+kFeqV$N`?A{~Qs& zub@09Ik@fjy_7`~ZjQ6QrY`rh_(=(TnUc|}BGP5eKvWP$S=&Ukw<6=?FT>dQlw5Wd zP;Iam;4%hvWYHPu#;Ow_^x;pDj)`<17c+@=T%57V*^>xNrVl&(sVR`7!nj0|#Cmos zFal=*{fhABdegsYgSibWpz_t$pk_RM8S=J8K zYJFwlTcbd^7!wmFws9sO&Zya;%+g6fT0)6mb$k^vnk+MsQllLHdL}Ez>(~Z1yA_vf zCT%BP-gbY;B%uqYbuSckc=6k&T^Vo(1lc0rm{#BKkNNITlXE9XYy&{FttNNQ;MDJ* zHBU7>%LLqCy<&O9O$WnyjFoY9$O&EetQ4#T(4>=>9$P$uu&dc{ev6Yii^~-@~pQ<2;ke^|*Y6NfXN%Y#O_jSXe!V0*e&QV53fb z-Q5u(>1i$?)$M^B$twW!;VGE~)TAWto2pBNZjMFbE#x#XJ%I|8H=nELybX)v znJtFjtS<$tfO)^HtzO>d{vlj8Ugrzf13>Vrni-ac%g&zDhsj-ojxNYa_NpYkG!^5t+YI{ zzdOO{WLcq$nAiR}6Usd;7xq)tU-34v{cWvyJ+Z!sNr5{NXs))%O?{&MSLx*sBIvd) z`HhM^$Xc7kwP`iOjNhy_Ehb1f3~~&73hJ`y0n6^i69@appu4tV73UK$k$O^sT{=-b zS@)~frZ?>)+J3WR7WV_~)AoAQ-q7_j`Bcor8jo0>5fey8;GPaY5SGLMS$bCk^f=C! z5%y1b^6%3jcx;(@#;_jW!zpsJ%k!Nz1A>y(9r24pj zz^9csAdva#aWG;02RgLBr75~|-(x4RGHKoO>u|uUasi(DRs>HYKbsYHUK+>aBY9lb zr(%t+MU~LHh~7S#_)z>vvEk4$eY96UY|KX8qA+g^-GM(1}R9_I(?O{@(*H?7sh7= zDNQraoZh9^9Pq1fbX*6N?kKr4rgREDdoJ#^;;)%7Y`9=|Nlbm;HKyGUb&goIRv5-( zLGn6%*=;{jSc9v-tFBIXj}G_YQnvJ+P8;|NQ4SLuNg24QG`T@V1z4rhtReUF)|s9v zgAESKii&g|k6ZhZMa)C0udSmStr{2`P%aPj>gfkR=0TT0Pd^-dUkJ(ZA?T16OE zUMZ2y3%eaYIT6xu;vIdSY5$7I&{KCGswIkF6$j9G`7Z^WDvz+lpKcQ~Olf`n8l+dF zAinz*8uBUXLH5`(1`86w=;1)2Y*K|+x2VX}l)O3zG6vh!@FX2rw)mhY^5HhIkeNSP zk!v7E*_#RpFPA(PxsyXE^u07EWz*dl569PLYQ@3Hgc7G3$6jF~buF&uyggCVY7Tw$ zV{QC@%z6Vp1gDff2cbvf6~M4Q{dsY*S6)xTqjn&_0>y24B<+J>#%CT6X! zPoFk;Q*FR5!!6`{-XRXDdn?mB{<}>lO{1Yw^oszuN@Y_{V%r_a%5s%PpMkfLe9n?p zHQ4v6(p58&jLhY4ag}eAeBs2f3dQ)gwI>hS0dZEKE^%GinVHV;dYOPt2U{E7l8U=| zn6X*eCpZedl;|1!9GV7ZqQ2|qxs`g`dwGT(yubFDNP6_5aXYa1GR;m z+E^J5>=6)UGQKnvo~@@sS)kN=l&+^QOG8UK#cAYzCHc@sX)p9`XmX}fE+fNF8}Vp^ zT=?AkN3(2NELj-gZyHYh1rc#j8Z{7ggIJ5Zf99|CA6-Xr^mPH|t%*cUODyiQB~_2@ zeY2~L#Z;Cb&e68bTt`DD+;s+j_-?YJSd3f)=rq;8y6LV7Z9Fqbbi8sKAP8+L12S!N z!@SCA{qpsmm$T%k@QUWJHR7+H+qfVygvPo|N(^4Y`7+LqT#P%MpaxyMI?_L@AE3Gga?MexhezWBo3Dx2L<>+n9*5l#ZYpJZ z&}puyp%`N8XshO#Y-_o(MXRZVm&@(Kzs9xWLEb{WPup)4+k#QkVkj@9jp6b~P*rl$ zFu9*Ky~4)26o-h~U281f?s&|Cv0`?yS1v2dFOx$B93FRtO*pj`A>tnqy_Lqy*3!}C zKT;#p2%xwbHs_%f_A%S62LZ2J(dptQqF-7tT9k4kH50$nv9e3#u%xMk=52}R5#(VV zXT-avIh!ApfV%(I@wz~?cQzg!TMQwgslW^n6L2|fn?1ciHUru|Ud<5~@3X>+7ebur zY3W5zOoN#%#5t6?Fy-Ja_Jd$y!WL=%!OQLkBOb?>k+T@OkOjHe8QWb(92OU|=rsp9 z>*o8Gq^ogM9LLKexxHtDzq{?-c;u9epMFdo;5lENmpb9e02wYlow|H|(04nqW=*>t zufnJvjdUyNboHs+^wVgNWcVp4LlOu%@6 zJq~{($&_+Izf!Hl;miLG_w9B!yaVUgE*^*EKBs8G{C8Ao<4u86-LGel$)>$IZeMK| z&p*#rR|k0vzwSB@h1 z-2s(CW)dAl(JRz4+5*5RP4+1I^FPK$5Jg#{h?T?Z1vB+v1U3* zwHf>yL(?2VkR*Zc%)|Ya>tUF`E$KkN17PV$gk=%ajWXki8#XfdLVs1{+6gCb(i}gJ zn`9+vU^d?JiQ_zCv_YQX;y{pHB%Lvi``T%@96dcSC+E*|jHXhpK!xP|{{3O%*HQH^ zlK6Ms2KLfq8nQN{3>ThjU}cxwVV;R6Q;|x17a`?u*!L|Q?V1z(3{s6dZPzXgF0`b< zZVUVi#Qz~;)Q5cV5Nz&5IL=T-<+9ri=6?RzLV0&g5|#VKpvfw#&*hj3MlnL+a9svL zqbl~}AOTqoecEU(B13FG=vfMoRv+iNe-u|YsUOF`4Xfq!EJ&WKp1LR4HXDiiuZ+=+_2pZ{vQ!fB|hZ2W$WT)pFwTH~4~*A)hC5>qp? zdiq-#O~_>LsT}DCd>PA+xb~o5GSv3``xgUC+2<51%6NT}2=dPcZAi2Lh*SPna;dj= zmEDS%#nBsX?(kj?*-@A&j%bC=ERCIvKv`YdMeMKzxj{^){;yn?z}_zXAfJoxg}b98 zE;+20V^@hf9_9l7WAtDl_S)0fo;~lL)^fyMkvC*I@Y`5*rQdvKwx)tij&2*b(VC;8 z?eeM?1n?Hy9qRcYXwNU#TO_Af>XkDR>BVqTKQHD6eIqyGTP#FYMtmZv^?tcNqJ2AP zd)6+lEn(QaNy&Dn7-;D@B+k9j!7R_ocCOaNeGtA*&>g8Hv zwT?CWO=IroT{F*mjgKhJ#)_r$t<3zI6{QknUtrIj=QLc=nDC3s2p%pG0nK6Ra#Ie6 zm?ikoU%BOO>3%1)}coUX8Iq0;ZZ zo@VlV9l2HLn z7EP8Hr0#N}oX`du&TU%4;@QjD;Ozb#NawhIvJYfVWgS!>$)`0Ls?lLS*Nw4U!FXQK zk=b~1J}Z;4Pr-kFK_>EJD3N&63V8?=`$hgm#B)XFO{tS=V>vqhKn8&+_xTYE?Sk2V ztDZa!3zmTCbmtl|p9|~zPxmz$o!uUL=&rW*i*v};e=>4-|7<8RE*YNbJTYne4JxP7nk3?jMPsrwltOD7%; zeCuso%0Lvk-v}GEW(&r3eM+BP&mDF2qx(<3%5-oQPZBwUK5G5!%W@6$K(Jd(Ft}9r z^}RDyOFrV@LTnGOJLhr%kvH#0-0lwQiyH5(Mx;J_P#9O(q!7NBHQ|U7 z18+8;=gN%Yk0Y^U1iuQpBFxnoxl^TOk<0UZuh(NQyObA>`HQIJ%tVhUqV`>-Fv?oT zvCbrLB`YYw!~B^tXi@qL#X88t$E*1WBvt^S$sKr+4;O{7XYu!j#ziHDSkL`;A#oF- zKwq~ulkh=^t6j3?PC4=a*gj;4OggvBzjr4BVDX$IO(b3Zt>;IXB&qW{oX_zqLXHPx z^YELeC9~v(9;}!>=^0Ka>bvV-eOT_AtNe^|^0S-+``pd~zc!gNNIO!>$0f|lcS~Kj zTB+-@tvp=$6}Z28w&Mxh#vz)X?P*wd_IR&r#tN^uii0~`fSW(?zH?Y*;_TJpjpMO4 z617@tT8P5`a?x0+gy2LwAAsBP`FK%CEl$8+#k6AWd^hON5`W>|v#msU(N!kDL+# z%K0z^#6axBzrb3wf4pz1-FU;lLp2!}C;~?>q==#eM=8AAR322FX&e!;&4}0b$IiJ3 zF3rtiEOttjFOR>)C4*=SG3KOuS;eq4ilFMgQxPnYWw6-o?-?f&EwOfh6Yh%a<9WO3 zM%H`NUM-`K<_&Sfv+E#}MoNOZ1#rhi3q{zA3Onb6)~7sOklwnM#369)me$_9sV|s{ z`#w1I3Qv6#>x9r_4*nw}<=FhoqPDDRV zKHbMexAC$6O>6r{kKpSp-f-U`JbN&iWA*@g;h#T(g?ImeX{zg9L{iQ>;>sDZBvS2O z+i8urEp|TZv{f=amFsN28>ifcx6^T~zJa^jj5J>|p|E_bJNcoW*LI5Joovxo^1$ra z4@FZlzF8)1Z?S!6(;ALpD1(}D=&+|>H4Af|0Y&f7Lb2O>ZlUOFZVrC!0YaH0bB?$- zpZX-RVMz+|#>r#b_Y%@FdwN~ewX+REjV}6LkZY;P{aGqqe4#@@G*U8i;KP%+Msl=F zP-Lo1oxF{}Snk?SFRp60=`*eG;DA#ZIxs;8_aJP>_wkC)W7hz2k8zMAS)g4dU-bDK zwLe;;Co)PFSk@Xs zXJeuQB`nmXlK-?cJ3)gx((8xLw9Xq6wG`XRb8hVu@XGfW&kwvu)HibAi7vaU(C!1I zHGOnc6byyoA2c1EtpcbTwGp}4mu7e=i*n<|v*ryP9NvYiaY+eqqPn1=3(c2|w`QEa zqrqV8K@krwIFLt609505XPb5Av^L1VwUf*|v$X|PFn-24oR+K~>kW6VnB6Pvh}{U- zYFUU`V|1WTZ#A|uxpG6nHMF?KJg<|cg!SGOsWPV4u2H_4ovA}YI*AmY(}*S=2R}sU z^}R27OFq2BB6dJn4=*%#mGbHzkBl`RYpf$UKm|Ie#aTKIHe36v*0RY?!I*PTf~=mA z-(BuR3$WRm(k*dS1;BLW#yIUZ5kl@yMa3?_Hgy2qacL@Y{ z8h5u4Ah=6_hDICxI(zT4&wK9A`*Sr<(`(hNSv6|ZT%$za9egRB9FAAe;3?9H!x!3I zB%Cf8PMGS{XKD9%KjZa_Xq(H^lzlclF-I^ts{&FF+i59ciftY$KVsAI1Pv{++ff*2 zY2q9gRiv8DvEOT5K}*-WJNX~%=H6f-lf0&P|M5))`%ui#MK0Y?Y;klQG$-e|s6AaJ zWu)f)GMG-^(8Xz$Tg4IdEx3P6`Lk-LA>+_vYdM*6?PMT>SR364j$pLw4W+_ttMun_ zy?DmL!H*-o#n2YQI$hz$U|YK1DArxL`-bkni8+wLU+GH8=U+5qnu-IR&$M zF1eX9I<8Np3%E^9q#5}fEDj5lO?ngf%I$b-(bZ+S`iriqG1#x&C_|8V)tvF&Rjg5>J{!nXS&ufH(D@3MO z>qaTt%87$d(AGZKlm+e+@h7oLr1VxyEfja$n_F+)hcUMlafo?2X?Y*zA6+u6o6kEd zEe(kXM+_>9zBK|am))OM%6C1G$ab-!?ju#*hQtzy$Qf5PV26a#()^}%R?VHAr4=xj zO3Pw1@MWc=C4|$a&XaGs8P$vR9%qM35c<-)4r^6yXI)?&sMXvY$CbpMD9+HVz3WaT z)5jv`_wjO*e2QxwzwJCVAE1Oy;tYC3r3?z9Fg|1ILD*}N4n>X|jOe0egfCNF_&}$z zy7Y9Z2iU+Gq?C=}ymdB+N_WA(``&cBvlBi@dO_AKr=RvxxSV(?`rM8!r1gYjKA$Ha z^>Ql>;m4ylt6=}2_Nfx#P5uzLdKeQJ^x9mvP`$)I`$f2fk$JxtkV2yE$Tc9+0x4kR zD9znxZ7xQhA}J784PKFYAMv(0`YsUEGJ&39K5(;A{-aNNYe@~lJTOOvw>47z5%bjV ze0zQiDvr7i#*bX9QRt26jr$h(phr~m)d^v;spHn%;P;v$o&ZEulvdjcU~)K2w_dIe zub5}@N^L+)V)w;v8q4Ku-)`BWKpSh?H#d&^#b)g3TD(f<+QjRgeL0&SN}tN@ZH&=O zcgM@krGdtc?1|p%y0L^D=&53B=eXDANM||zyJgZ9xR+at<^>>~t(*rk5u~{t@Z0hU ztpSTN-Xt_o4HuIVU}BFNx=m?n?No_k8|T9=N`5#@o{_DTZZ2_~$8a8wf`ejbmDakE z)C!Fjp6a3ZEyEC){ql}M!vcSASTEEZ?~t^%myiH~){W=aTWzW^2J41T$*u1>tr%@8 zrKBUFG0T|l?7(G$fB_>U9RRGN3E?}eZ=7{)1FxWd#01U91jh+ z;F|A>WJ&~-J5#1s!1yG}BjZpq|1*plk^OqJCa3T_)OrIn*IDuuODD>bCWcO}JF-xIX_g?y{VG)S z%YZp=4LSHO5H-OYiD)eM9FB}nQ4$rO4*pxqej)D;(n%%jGQs^KF@X1r@ye5^CgICI zE3RCE$gJXV7kMn?cPiR=c&c8H`T<@`I@QOjH>+9+-?Yw4nb&jj-At|1uL}4lJ~O@M zj%Yd#&(9BLndbW*Iaj)#Rp_jKDJV9B^{UGkZC3kO%nv!fDT5X7RXUGcJqql9OK~@S z99o%3b)Cu0C+84d-f&1a-z4!07E|$3Jeo(f&_h0(_6z_lvWI%SJ0HrT(2!{R$=8%& zP$xV}@UyN_&OkC^dw-I&cZxG66^^#BoRDw$ql^&jZEp(I`AR zBM{<17rkDHg7XKv0j~AaqOl<; zo1shQ!!7-m0&q$%=tiK*T@k%uS`+=IEt8DAQoV&q_0rM8$ z?r4s(r0@LnoQ07OYnw3L z@9X{HQ6$jVG%yx>ZJr5^UhNF!Ocn6H6Gt+Hl0$8XgyVE!R7VXJdfDCL<_i~p%VSg`*L317%?8zW3pZY+wDBPWparGdf6`22^V z9_N>~oz0fEi}$Ye`q>#l?pC>IqTqDPk7J1Y8Cs9!Xj3ub-6Ck3+aYKM7iilKtqFs- z31y2^M{p6M^KUI|!kSmkHgpFG2K_suJ96sfB>gj0fIQB5slPJ9DFkHf7Q*I^TN%AZ z^VKUiQHaJ?6TkK|Xv?bbeY>5R3uaMAS~G?BuvCIhU!5p9(^9E#zXUbGalEnLap>Tw zWTMBm@!$5)-wL$*Xz4_S#11h^Zw)TB1tzx+>TNa5g&3y>EMxT)dBb<%YMM#1+QK3slO&?1 z%ao3(u9GZ~P58~VZqae0i%(IA+b=7f*#pxn>-$M2@8`KbLlS{eim(CndGGp2<4UQ- z;SPuwjcU%PM33W$d?|!CYUzoFZdUCqEx5*_!$ePgl8EIhZ6x~mbjbwlW5E;gRq%HUYAI&r)cS(B0 zsz&NssMXc-1PHdf{KZCRaRBL0$E}DpKurme)qhZ7pM6{Z z2S3ROoV09z7PYXdc^vUpGmYL5e|Vm>8e)zlg7NRgyc}M#m;DII*1MrhO-e<=d=J)_ z^~vR~NXFh*4j2=SKG3tqz=Tx3C^-7XEFDqsDuH`f`ns!MYQ{Y419Z_Qr zgOr(#ZRc6j^!c#&wDt?Fm%ee5{0ybMgjo6={_>!=IXc$BCrpuCwAtt%5!5?4?pv@` z1h=z(7@9(w6%ec0GmqUL5LK<>nYHxUdG}rgHISAkl$XNlVK{J6_!Zj&lvj+uh2iQ5 zAVtX3U}0K{83(bt-~atmRLty$8=p1HR7AjY#Ve@M_RUI#osE7QLt^YjN`UdWuyh-6eIK={Z_hph0zc1Lu3rD9;|2q`>p(q&53 zG_Njt9z+qXwb=*N+b7#EHF#n^V;F|FH7Z=IxeK_9OAlMf}1X0kLk}pFlQ05 z^s@{O8+`N5V&+BB>+(^~Hvwi^eVzK_=6?_u7$w{+1}xYW^&MRGDGC3)bC8jE9qWk^ ze2iXa=2yzr{G*2bxu&;*+=`2x$T!|_9bqh(&ccszks~oo;VOl+YqvlSw$f;5a9S%D z2Dl}4#|l6JC)KQcdAD8R_~t$PA2oEQYd*``#Y`z-8rAw_DtKO*En*-)azJkZ)8eiq z1$orUCr9H%LJrJQX}QYBF4e5))bD-MFQ{c3;1$B1pwZ6|sUA66EnsuuyoL&j1)ZC( zb*(&RKqHNuZ_-RSWC5bKMp@OsJFA=y{M_u)w^k94bN>2EvhHS7L5ptA4^~q_-hfMy zMh<#Ait+nha`1f4&K;A%=GjcM_@MlRKiVt9W39csBqatOAo`~&bC=*>ij)dC1j#bn zLx$|VF}ocTuaS-McYz=Bt}+z3=`aGRpBU9Cj)6TK<|5#{*SaDTEl z`V629FuZmb!1P98J>MKY*PCzsu`Wo8_b18z*%6eu-S=6~LJj6B<4nW8O@K-5m&Z4q zE5sa4#=rojvC2n+ednLetC8zKhqdc)Wc6P(x4&XFRkXu@&`l(tv$akK|Jf2>0^giM znNal9GgwA)v_GZfc-|YmsW$faY_ii!3>8(2dZUAu_ld06W@Y)_Qt%jRt-W~mZIDtG zLbFl$J)9PeRhHkUhvYQ1+g_|+JBo>B#9_)O+R zZB@w>%Z8B2i&k*W7JT?UE^h891{5ipgY8(v3>c4sh4Z?d=!T`VAq=auA6B2Yc`!v% zzQW=kHNvh@Nf$K$Izc~|;*fB8mwA5>5oSB$jg&~YVq$1z_*Psd>PcK~Oj}NETXtZV zM7Nn)F((+<^<@^uzSzWdIFOrh_Rv^Vn8r@o$9>D-|M{YZ^e@A&;nl2ld4VBhX~sJc ziWDN=&UyPNMYz*eea3ZDP(3vFs%p?+nRwAfT4F1xN3O?zhBm8Wajuf z3Z-5u55+?H&zh;DcqPMpl*7OP=y}!aYG)Tg zZR~CBOtF^RE||=*uPP-aemVbx_Hq^UO1uC3^**6Cmen4p5cjk%vJ=#{OK-3BV+iv)CB&a zLvQ^0%roT^>(m!Pf6)}}DpYezvCvVMDet*Kdh3ZhlwNO?67{`17 z9D!?__iv{_+aywgUI|vnYtX6DU?BJ6+xL?wi(M|NnC>aKSf|u+>)AxrdK+@sD+bw| zX~*1Qz;g&u2WJaUe}Tv`g3K( z6$aPun!*3FSv2ZkzqYoXoGBH8Lf+&Of5_gN)}?((AQ7hUEGwGK zvaK|>aPE$(G8WyyDHTuPb`q}6k^Fb2BtkVgAywj%EyCKR_=NC8xFN7MJ0o9rti-~5FxQT{@g%p31m z!Rr201qSKhK2P0Nb#j8G(b9>(b_!Ae4RHT}jdy^7UR&Vic>0=j|-<+mE zV(rK5lCc>8we^=i>S*tP&5ui9Cx!Jd$y0FRSmEJQEjxxI-&=YLGstl`>u{P5m@Rh| z+IsX-m=EX)uA_4$rdQjwH?(%+BB+>dUxm|Sd+a!ustnB)&|BF|by@@csc8|zYP%kP z@==*$-#7z?E7vEAZ6L>UL@j!z8WM9`QrNAM-A`;wF?`&7p)3v{0mQM0@@><=O$}zs zcW}jz_Do=uo@J@E#qR~XCISZ`a&*%y=GPx0OpJByyNo5Dd>fnZe|EPf@rgENC8j$HEdK`nFWx z&J(M)F{*3oV{vheOYXRJw%%bcF~-M^=Z_1OD!%Aw{$9Dw+|&DMmJA#ZL}YPUZb1e- z)bjL7HgO(~tl)C{;4eWU9))v}QSFzB1;$k3mgqOubj?W*sOj{2R#V<({~95}g_|hsmAdb*rETM8^>O z8y@`SYI$mZaa995nmH|XY>`AdmCX+5+XQ`hWja%D8ca4(-{^hbZ@*B38nx3N9;XYI zo0;#rC?(YUtevzdzPwknq5Qyc0cd3{NsL8nf5@-nAn@(n<#{r#cD(H~ju(5@B7GsK zQo%iCdJ^g9ztu`Nhx>kH?UW81CEmGN{3|qw$+) zfg5RIivrV?fS;hQmsDFB$tLLJe`64|@$^-vPQmPFlY?=$s9K zJ{WudF1|a_Mi6M0SHds|cQrYMk`G7v3^QX4SnVc0zZ!&alneVFS$d$z2csavrj``( zT8xY~wnHfKvgM_{y!*R-Y^Om;jEiyEuZ=;Fw|?qBy6&I83|lQuw{hP9H#tNRb5^;Y zN!oEh56WynUcv|Olr1mSGfBYNsYNBd@y$a!gbu5`kZ0xb@bMjPO+Y4u%xbvh)2IQf z;OM-a^x*{u_J^f9f8QZWXn+f#=tJ}a%FM}W35ZsFtaC0Q(Lc80|#QiZ3)$OT? zN!RURL4JkE>VJx`zkCWtzZw3a-%9QC$lbil*!zb0-fa2S`sg5vaayzSZRY$O9;W-% z4h9+Jt7!eo9`!kB^&-9bDgI_`xmPdQzkhVJDeD;jn9_qqs-?C=uoxgMCgH}ezW0N+ zh9{t0=$+%dMbwM7cWei^*8>h|yqguv9u&;x-Ku>t)u{nUG&)Ilz~| zVc-xq6@bil(8oxY`FeS~+Iamg0UBf?77Ch`Y1jh;W(JZhManTQ21&T$??EWEV&Ix2 zf-f3m>+EMkfGm%21pc4Z0LM!h2>)XdpuEY=>8ktX0gmll2^#?iAPb99RQ~=3T!=*> z{6SLkg~L)^x=OD2`|tXVB^w(izgpdpL%w`bgNY;UdXIesbhqg9*)CLxEgrcd^iP zz%bE<#Gk+>UXVH}qSHF3*(T&yu3S&AjbYryo|MdaZ#izIHy-&LDr+NPrTOT28jD*T zgCI}GkX4*#5Sis=5;D0+!{z2Mc}Bayz!A%1{0-L4E$lC5bmNI8ty}Kmh{QUhJW=bf z%_FvW9oc3RxLIF3)g%6W{9R920Hte8nA8t}PIsuez*pof(~UVBtJ48>SVZi8%Wa4g zVaQ_$eXJ)}3GJWli*wvj%!o^VxqKeLpQ0?Sa(+o%4=(W@N zzcL0WPz7AlWTQtVadvpGvSK2v`-_Ba10%0bSCu^1=5xiNJ3RVz7O1PM&nl?xz5{Rc zVS=e8=k2(&=r9EN!~@?HdYfBp+<+@PLBaP?^LUr^=9*7Lx5M7v@@9BM&GuG~x@vek z_X5L5hStc@way!&ys~$!jG!*>%U~oYZupetcvc0glhT?K5GE(>h@4`Lj^A~P?q~OH zghj}bB?$FKkX39~rq&*XzIZJwirvzr`RRJMC6%iAJPenbvQ=PnF2~66tb$<~F>o=v z(G(rJ;r-ZFzq0HycxN!M02#Wen3@fG+{X!e`00{zd!`cDa_kwLP#>W)TW^UiVh$D+ zsUDDc&F^1tq>uSe*3_e^G41nK|20!DZh2&ZI$LFgEL3gO4wKmxrc)GQP2n(LK)aK8 zwlBJ-5T5?QY^**MyD&XtPQ&YSO}_SqKg<8=kP-E_e|T(ciHIY3Y(4Rj*ZfYo?fTbm zL%`FWU4&s~y-Q2l?R#P|78v>Y{0GUX?s?FYTv_UbEa?G5Mc%AC@1eCBZn6^jN)}T! z#q!)YtgNzJYrj4od^(X}aV&yk8C!eA=(0Jc6Yw79hugG|=%z>RqpU-8GT3}eYTpM5 zIc5`zsN7tqaXY6a`@Fp*NseMYhPm0#eq}%F+&t6)4W>E|tIx>7d6hyQG9kcbK+EVc z=tRg+(dF{syp-mA3UMzELw?(T5MR!=&I@$eQ#Jefr)Y-NA3Fp=By*+e5xDdR^Ek9N z3zOLm-dAr(ngETqe2(Kra%v@9il-?C-gA)MF`1FLh=_cw6A`u4s@47~pJsMY-nsUj z+sKEbWiWr79!xq9uEGD&n^efQ;R{a=DZhO`ZRcC-g{tgJnDQjfX-w3tJ4ZZ2}YD#b<*#37lhy6N?^-Jg!?`?!qGeDw~af+-lZo@^kf9JY0;FI?j#VtYFW zmw*ob+rK1J{l-Z@{2{8C08;+RtX#|cZoqdi#IkC02x2u*ICPBjDwO>?M^Cy;7U;?UlR6D$Wq8>o zh10n?7xlt%`lT?Z2CSTcvFj~}dlUCLFfE1ynBHSCLkt9sO(T$pPx~ zVJTI)MyU#S1BDnkH{gkR{<}eH@)`BrK_PaT&kY$cs7MglrRMkM^zvlQ8Ps6m71nBHv3}e&jL2YlP9t;JO6JuC;)xt% z$2k;b9YXj{=XbIK+sCB{XFVXYZ*(=3wKJP7u`GY-TSxmXLlS1rSkDX1y;|Ovv8_a36;r6? z9obOd#uHiUdRH>3qS>sxUyHrS_D3S3r~h!fYdC4*es5r!*x3Mo%N9DP)b4tr>kB ziENx0VU2X$)1tsy9!x#A^}z@xL43JS6@}UX4qr%%idg?LQ~$@8RAIj4Yw>h*R0U1T zV91KNV6jIRb{oXV z812|2hBq!zCW+jG9y=^IepU87UD3n$#P}LUBH}38aeimV>va~4OW3iiDt?vJl8(^H zJ!oTJF)Va)Z4CJSa0V0A_|pKJ*=(B29&{?iV6XhsCJBp%lGAxZ4GXFd4651S1z!2Z6UH;q1Iot%3svt zA5_!&j#^Mqu(<$2tP1-QmwF?tF+x3t=%roshXj-S19>j@a|5QK7mIS42OB-Xu5s@y zOq%SMSZ}NoLYG?I!tB@0s=Dqy_`mDc8iv+JAY&u55?(y62)RRc6tJrf4IEb6LTZrh zy+590@Fh0WD?QN}<_fvVf^x+D^p9(c#|P3@+dUNYUVWU%n6G1-82F;o^@sTR_YVI= zVM@3YmoAhPwa8a6hOyh^un$?C?`wxnK0x;?c)H zuq+ExyE3!nRJ@+=j}IerJa}#J$xHFBI|!j6BDq~w$1vez+#7u9Ie3t)g_nv{`p(rY1+{b`13C|nj~BvA_5%Uy&o3iC3ws=UdBenS zbxn=lVPldgOFTm|D?9H35WqCU`-O25$w1pF@e5v<5^(Hk%LO|rA4ID+i<=ciqy8hC ze!T|;(n~_OU4(0tXPAI_1ckPH2Xv2;*YfBRW7qOEG0#W*kQ_0kC_)Y{ZJS9|acB!K z9r4(QV#S$Cr_SPdJjR2k`)9e|0`>*oS4SlK%2T<%cU+HU_leMj$ybGdSxxe8#& zG6xT4DdqoEKd_5oKlnpCuhv}c{J8V)&1*&ee)rQ+9HJbS5z*1{KK1lNUj&9m#I(^u zphp!PhfV&?qwutB8OHCQ@4I|#HaZZ>{5yWty$|zpC1-f=ovGhw&k(8I-t_&j%}xY1 z20)HNe737!n|9d1_bo_HRgD~Z#gE8n9V+a-5p-X6PLQeusF73zkTUr(5zUftTfFjn zI1dA+8#ZmrI$d9%ELV1Dm8zt8{c5Zy4AE=%z@!j%mrwE#WHoM5#&_raY_geFuF;to z0*IwV&tIzFSCf$ue0=y?oR^8=2OZciXpn%3?KL<()94kwH0Wa0zijtfYP4glCceka z7XCDfn83&Iv@vgdbF#c&$3rxh%F&*OOj$8NDeA77bicT$n;{BivHb}`>fm$1)hbg9 znzoAzO`6DjE)GEti04kb{R0)d9qc}kQ_mgYV*6z@&1>>TiqcAE$dEod3 zLNir>p}e)^hrja2-$dr$Wd14cH02E|BswOFVBifkwIn|IWl7;ehiptngU70V#4b$5 zIFTpmP1@D(pEH)J#_XHZ=$%$zIBoweUErxqy%+i``yo>dZ;0_WV4lng6tR|dM(D8I z7US~?oHr%@m?RrR*(Y5gZ9M?loyA)(n;~FGV=_O0dJ3rZ!)&W$)aIW2D4ZR+9Z!Qj z$C&FYgSVbR9Z7rr@(N!fY4BI$Z+TUAS+wqTswg52^3;5T=2QIM`&hFtQmbuOGBoz< zod~77@5gj&9cBuOqm=_^b(#!?ezmw1I=56juNJHX8A);Tl&BWs(ko6Y^#Mv!!#ZRt zbgB!QKI7d0hOc)HrA243?!n4(uISIHK( ziM$!gjVe~(I4sRg$hjp-8#9j+qgk%?y&D<2+=`k~x>IY(_21JYg@KJ&@Jq)HXxL~n zhbj&$Ov3rQI{IoGKvl%!_*E-lt%inWQ$k697}@)=0~jU9vG#^FC+zmQ-UR{E@7ktU zJ{nu%A&{`HH^vGl(xX^0*=C~ciuW{TVc@$j6b*O(W_?Qa>y69ykYb!qp=?aqvyrEo zPzqyHMUP^us@N55@W^xUk47b{%~yj^--mO?gWc4y(uhLN^-6>Gl(T!eJ`{`+wd3_J z)uzLq`xjkC(StBUG)A-c;G;e8catv|@H>@!oBuXahN%jBX_o*YMtX6IsB6J&;Dem4 z#UN%37fRfECSx#2PF7Dtn&%txSRgyVYEm^p--0SrT+ho zn$%W*p!VyoTtfDw$g6g+AGLvMHw5g<84nlmoLWXH{ta|sub1TNYo)NGi@^Q#zW0={8W7VC`FIzWAH4xUk%*iGC*jjJ=c_hGY zz$7U;wx?~?UeSPPNvS>rQe3q59+dD*r&IQ+>$+W*a)qxfZlBPQN)}f9eT;uKx|Qbj zf_KubLKU~)YcE<_q7B4SEYHbY^+s{HNYB*pdhC6K))iNk6HFfB4pLZw)&UexXxV~5 z;VEr?kCM`b|4FMd!kXeysyz)h8s#1*zj&QDp3k$cQoi^w{W?hR72U7aT?*pnkM<$W zhY8E?5^*C_iq*kt=C{=yPobBCerG*C?&ddDv&$#ScbDaJt_IbBBT);c6VC0{p?Zsv z4-IxV;&*$Q-~({iGp?oU&jp^tH6P?r(H$%bkw{~5P8rh3#Rnt_XUbj~93@qkFVB}- zVPspdkuAYYCvZVQm*BncKS~@7ehs71Vc_4B@N|`MRd=vsl9>;JWQ~1=^t=maSnS7d z&+&F#58OELnTjdWKflH$Ab7X#cf-bKv!dqBng68_86Oe?i84L0c9twe^?on${GdT; zqn7F;7Yx&y6_1}m5%D}V-Tj0Lg}9lNsHQ;*6A&)8X9`h+H^DGf=9yt`%xnbXHnqXuXCwo80#g!X&${wYiV7KN5r8loAHEf=P!sP)i z_MEZkQITTuH}^CjI}RisyPwL??842Dqe&~dZ@G?(iL50g(0M*iCWf{(J%HY43HI<= zZkd&+6$u!@(IVwpfF+!D{o5Bsh&wK=&xHIR(!x<=Jb&xOPL>m#LZrdsgvOfxlal_f z$Kl;DVvGjG)YW8dHLG9urOvcJF=Ep@O%XT{W4v?vSxLg7tYb`Q7HozbOt}QhGBWtE z!8QwuPW$sZ0)?2bBPs9)`@*nCUI8x$8*NwhSGX{!SiVZv z2Dd)cg}R2~ZYca&^M20t%PqMtS4a9{9oB+nI?Xw}ib*U6u>?&r3aSq^b;zD+cbKHZ zYPN=j7$H5%x#C)~f}o4jd(ro%uk+=&W5X%~`sPS}xy6ZKBo+MjI^ZGI$fF+Pgkn(? zFZj-YyXG`h#icGfb6KQw^22p1(>yl2;BHn}&M`sG5i$SVP3NEVS8?u8q;JBXW{VhU3hzS{)Fr}=dCovA>K_|}Ht#*$OP=1cr8DCPJT}S*uJoRsYB0gXI~Y9D-qt!SYp1^r!Vy#n zHp1ZS@05$n(ab58QBLQ6kAECVz=B(%*5EJ~nX_AtrTcX&MgZ_NFYT?%LUyxT8kx<~ zY_VCG=Uno8(vgPn6t>K{-_>z0^ln@IE*ZkWI#GlB>(kuXnVRND-yVt11KM-8vB>PBSlyt36}#WFOB54co><(d#q`WK>w zdZs*R4*QeYmOWCuqutRIuwKPADdxu}lqkR*W3IR2$~c@IkxAoSt+_Fya+GB7Sdr&i zLITEI$&^XRf%uW1lIkH&_T^;z7p+ZWe@nsl-aZAxCy+5RG;>~3lca$&9RMNFgxC*Z zZMXYO;IPiSB^nl|;D za)lb|e^{K#5kd2#Li>9XGv^eRUu^ObTCWwm)2=7BK<~~VKAJsCxGd#9Q|8t{6#8{k z_S=LtcO)5)FY6LiXP4-#B@v!V=UepA*7fd#Wu!c9`yDqDMeV0;+5Y!e3#=hps)`$3 zuWP!XbI)3{8C8GNVXy;pG%)s=K{p0BjB*4S8PfwQt4eUaJxH!Q9?}&0VL4KS#e>TK zw$rW*vaKpgbI~gYNikid1@U9I2H0&aSG70?0gsHQGT1KmbGN>C)M$|ra!6yZHm=-z zN3*QoU>tCC_-zk%FuO=fd2fc~yW8a&Sd6ubkz;cx$>!j5T@IF%3U)r->BgN)GmpvJ zC}{+`J1ToIovNX6g}e|;Q5DzYe4~Zw^`*O&)9##I1M!m1oU(RyLO5fdt{5 z=VqKly&1lO|C9gWoA0_^FEHVVKgSKeFt4dul*nuvmPZ};5qKH>65C(id#$!6jY}Pzz~@Fg+&Wr=r7XUVUm{%#EIxgSZ;gj`2va@q0>*da~uV_Qe52Eun~KOznyO7 z_9|YUzqK6n`q00TlCzZtcnHHXxG=`K1{aUH;PPDjb{eN=VT&8Ng3S`hDj6?%(2ZCl z9)Av<%~<1f+^(*H1#Zia)FP#`r5Ga%&a54 z^pXm=mDitJ#Ah_%qbb@qkrI5?jZav@7rnvF>n^1hZd^{QH)5(`-G<=H619hWFoRwQ z1uJLB*78!l&Cakoes7_&s7n6Ez^j>I0rvx*A0Qz5nx0w1!?m0t7A!(J#8g$(40<8@=gIR%*YFyCY%v3>{=4=9~syahcS3(O@+3_B+D0dx8r* z9&*ktrWb={1#V`* zQ;mUIqx{J}6J-4MbeNpFH8VmX@RV5^Oq^|q8ca3!pG%qX)3AcdvFBPnpCg8-k`s8T zd~Xx%e}^&S&&=!>mx>EBWRz+bspJQ~7C-2@A5qTlb`K#0Su>QdfW*$();g=pqaV7# z6b3|CAkV-Z^P$+2%cF30Zc(a2U`@0&ktMuA*l02v`=vR#D9S|(QE7+)4?0?bzFuZHc>kUpEM z_^@(>AK3hO>HWMa$CIZH7lkNXszyzD+mUvNmUa=_lC3Dbb#9U96@yhN3og%}{`&mc zI@K8bJX_4}ND=gT_>I6tQs(;E%6RHv@!#IA*sYNFH#bJ{2Aq@vfHf%c5M9#)87)`D zP?aG2Md9+~>co>`pFWQd(561q_ zg{v}*gCwW@f%5;QV*c(iQ2aRx8T);(@}JxKANGrS=uaKT^)}{zKK;*y?{y$%q6vw-5hpa-g%mzp1%k-Jev5&s}eOIlrd8dO4t%N#KQSt;gpBZo_;{Nqy z=aE+}Nh}Ge(eVw`uxB>JMZ6XWxJzV`^A4HLha4uT`}`-s{Kq1H83HL#GU3>z>xY3k zTDM!V-v8lA{^>w71sT3p+rjsws7p~&jg|c$==vWodMg<=x{vD{nt8q@~_Hx4rJSoq041v3w!ShK>G53 z|B*=-<^~+Pkm;5G-WO8z3-9IMXXMZ;1jgxIj&-eW*U}}}t4pw_4=Rv^xm*FYC0pUY zs}%15-$(^Yco&_7Ik@@a}+0rPS1p6fqa0RJ=vm+14{R_c{h`k4O(eJ~?j zHZAxyFtYuA_?+~KamvMs`w33C(b_yGMa+L4RS_+wAh6`q@Un!}eB(|TapbZ~5I}W_iJ+9&F zR}IAf^@D)*el9XOC1769{<|z$x<~4$u*$f|;o@V!E&4whfjeFzyIeTv_3s zXQ!&pf++6npY7DxXu4dA?dWu@yxVnbw@~`Xfi7FuAc~+E>=X40OPgDw&#VAvtLn?l5Z5+x(VP_N#T+$VSAf(EoM> z&?tjKt1n9(`bxaU7cDtfq{2#{rPO|``YgW>1f710(`(d7@&Ip`;h(RLlCzvLtnV4_ z%~0+b76c3^D*;P9~r zf3byO04y$RwXP>$3P@K7`p!YS4xx9Wu_7W0(Ezx#$7!sT1Sch?xw|C5DyWXTEFz=L zx0KIGzKd18;ZN)$)n@)xzq1UJ4t^y)*y+&#%n?M@Ar6xF{=^L z*#0~zk$zi-%YV06H;#N{Y@xLA;~tmsfvG@=?6b-a5xI;`z-?KrYqpY+QQW9U%jZ`* zj+w*Iijh)Rk~e-+pWLs%L%*0FJzafkNkrYxd5^KlzLuY`m`}v-+M|zlx|-oDM6P@} zN`c?uiws1*O>{aLHM7DZM#~{TVC*X~Z>A_o3bz)%IdsxI3RtLrMki<3s4mu z5G*q@i-tye+S4gkk!9=5S(UA5~iixE{%I|2W=^ zKh?MICjy}H#{-f|{{U&>2cYyFj&a`e@8}d)wrs|G31>|^zv}u;jAUpO_PNU8ZKtKu zEAPwg8%nDs4-Y72CX@)eP%a~NLWUE1!brJ4Lm}!1*LM}%nz}yB!YrCm;9p;2E#ups z_1Veh{fttxMlBX*aYBy_@#HyFn@w&&#Mq{C=XiHt>FEoF(_Igv;e$>ozDezJd}AA<{9^?#$EIGlD`v=G0iP>2Hr z1iqi-GS=9+?!#YcNjcA9v10nNuSdf2X@O4@WWXaVa)_+XKGrooMG&x<8l}f7ji(V8sUd}J$dOgstlu^tRX_D3fu!O?U%g*=AwW#>cT5OSbpJ+o=4c=;r z`)K0NjE|D=?_z7y^$Aq~@SWdbMsA2*w>4 zt_D~}8n(pxda(uO*=4A8C-Yf4ewv^PBcuBgnrMClnn1IOzWgEg_=Fu2(2?}b?+OvR zQ2jQ->h;0x^BO(Idt`G?lzUekt=xOufyzbZzL7bmQ;k%$h$@|^Ey{XZ0iN4A#i#}T zz(mp>OujUhQ-caz`eRI-=GzX?OL**yxU>#U3ms@SGTZ$ljl0Jd{5MYSMxxc^b7gk! zuW}zxCl3l2j#Kg>mMvUN2?SSexessm#qX1SZj}<8T1_JLTT;k4Rn(xVU~1kx@~#^P z#aPv%z?WJrIJPk|TA#^WqPHj)OM-^cxG$(?SJRUKEJ3gT9HxIwYs*&l?*cn>A>y`{ zaz0Q^McxneyK(s!`U8emGZbbsBl9N)ehCx}=l;}KV)Rb%;8)ts$Myg-j>&`<+aE-|}Bkb$+U3%9-!0h^#C6&6H@wM+9?oflj?W*aiI*EDPLFc)!}R3t#oH$ z@B2*ryB<|UBvR=%Jk_kiqjhnPNKx;nIBegr$>-}h1%^4d0fA>#@54=;NRjA(35aIs zg$tydqwJw(+0vy4SD+F9=RwR3Gl7h{L=_%VvsY>f?w%P|`Uox0&G_t_wGI+3a-KjC zsN=9-nra4WDN-hiEPS(uUC^-ba85uuX&gEI%Jpc#hWn_yKVpW=1jdBn*(OP<)5qVH zy!nRMu<~yLh4mE!169=mnaCnUlZ30^?>sc_Oek8fQ09z{2Z*mkW%o)i7y2(r(%(0Y z@j|}@GXhs!UK4ax)hf{p#M`Jw`jjNphK)nMdO^r<9&$<(n?3Q1DO}7{Jkp1}&qN>3 z+*k8J1CGBq#7;i4^U9(lHabeq*xj7TpZ`d}CfUUkn^F5^T4O7&tZ4tUgroli-PDrv zgcn|d(hTtU+lu+ATcLNw|B_tJtxygF+oc*ZLOa_>*&ZUthzPxS^{kahiA4;dXx2kH%&=8LIs$dq)>{kgLI+|1NYf8V$*n}ewd z8k^sln6h8Iq=&GkT`EdU&l`G+S~0|puZYPW)yRN_cCfamFvVP!*#Nc6af;h0Ngip` zw-?j$yF*iQSz=p~qDNAw_*}5lmPA}Zb}s*cp-e%QI7|bIo5^@D)v2dCR1}m`=69Mq zZV9sWVER}%hD99qKFQr;kpO>jckdqp<@tIEeTMd>Tc+Kd?ZX+wl4&!wt z7aH7gWoNGAhjLS1ms{^Oz12L^-C}|J#iyMrdl^*Qlx`T{!Pg!moBNhrW|IpCnO~}}-_g5SZT#nd-iFOKN1no8Y;Bkke^k&X} zUm%}i9&GOqbd)Iq#=if9sPn>cyKT>t9c_1OFa15h4kHYW<@4i2*4ufWOi4sW!OTEQ z2V0?S;ID_ea-WfPInXF!v%6H<(Q`|-# z_49V@tUMm7T4zx#S&|b^@oCEEG{5zh-AxLwca<--M>z`6<5LuL{Y8LISc<+4-4mZ5 z)nEqXO%ey>+?-ChZEDyfEvFJqDG9nqzKOk;(5V&-JtRXSM@Jrf@{w%9m!XwKtEO>2 z4gjl10N7fU0ZH!SlUg5{3Pwc7#VSIV{~uv*6;$Wev}@xU90I}J9fDi%iMzYIy9Rf6 zcSwS}OK^7!Ox)ew_FV5;-}>IYtN!|HoN&OT<}+$Ap6+=me`sR%A-r0yM)kUT4g3ch+SvgU9?*hCqqnEX83W zKN#~aGR}Rqe2;3tZs!#^gw*^)5Cz?9FvDJv>F2deubDwq-a;mW{?H%ATMZR=gk-Mw zq6RcI2h#%xgsT@z6oHkpX74<`u>hAHGasV6ae|<44VY2*wnTsEV0YI-Hw!;1-Rf(% zI_9f}b{?Y>=ZO5Z(fm0UY>kW~h%A&hfqqnkA2Oexct@;RJy_vk;Kpr1jb~eR&#(SG>lKzKpgr(Y1F~2<`1>KTxZ+DYZ~>JesNX0%e3C36Uqt zOC_$h&Gz(9B}_fvxstPzru-=Sj)S}#DQ#lJffy*tmY{~+v=`>B*s1Mqo?_Q+m}8M> zUNtc+OW$nq6#IdyF2}7jDz=ODb;J0tIx>E=(JjjN++l$TL7_o)^0&z+(ZCufzuBK~ z2zi~+r2d;?3myfniI7!AtSB4ghHP|Y9!{wd-pz&`9`g_~UZxll0#NJ~j0jw6`47e+ z1f8!7rTh$Yn1z0N@(K5a4u{{WxwZr}?+6T8((vg9!=@giYDT$n;qu4R{PMFzV{D($ zTd4$X1~=-2+w^0cRAL{9B&JyR9VMK}A~au-J9jij)=hn6(%Qzv8KXNkv11SoJACz} zgwa!W%)z5(u@1z++4rFy8pQdFk#Z&pZdm7VH_6yMP4?wD{QFI>qfW+UMzmL*iX_;@ zuI-=-@a|a;LhH`5u{p(Yl6vOQnOT2V7#p14RX|0egRejJu`CWMI(@1I42^6_|Ksub zkIoP;5KwdZpDkyp65 z|0>nNF(TvH8S2U-1M`ar4KOSEtP3bBZif??>%ppXFT#1#FZ)=LJok9#GWo-1D-Ob_~B2P)rV1Zl9Dro*5wfJi1uiP@yy?d|QR zZYY!*V_*pFfQQE*B2>(SUwHzFnwK(bY>BWC=KHyp5O##jq);)G3Y&&CA1_;k#y@U* zn3O#kfD##R$&R+OEyDi$IcLRo5ox7Kz zFg#;mWH==^ThG#q;%1QhAyLLt-7I#)AN{_n;xFi@+((6!zcbN5{4_3Q{Apxq_+R2) zyf4IRn0yhPI7;!aX4hRk>+&|0T5D&k_b26sz1FFEqS{b;1t_V@jyoe+Ywb~^fKkmw z)+7hwV57yeEyHJBxYbH5apnDltRLg?c%I+7-!M5mS>i}(0IC8wGT4#4)0M3=@MV7uBh8wh=4 zxiFg2V17Z!gKcG7uTpBGX{FE&{uhkm^>8i{gU8ug%&l`Af@r5fwiK0)?YQqn`BnHI z`Rn#C`RkT+9^!BLOUMnrLZdc&F#3y_wOaK(&cH}KYVQUJ!nWwm+W9HF+Qjbj2zNXk zaKK+qqXH%OQ5*5gu*-`Or@vDCADvK04-`G{GaXs|6T&JG!-M#fS!t$DA$_0sKVo|* z2!2z9oHIc|FmubbJ0nZ#f<@TSmx8w)A4Np){{OfoK`t3_h_+uohRK=x|CC7UWQpnN zqo3DTUJ6s(%mWo3(O|d#8w3mJpahlB^e`45{|@x>dr*bsUwXV@r|V3mowKR>16oka z{^LOYM+M!b_)8y7tQ!0$%z=&s3mrNLowY*<`TzUPP_WO1?aSO}nj?JMqQHN@GiVP% zZAd_}GF=_+KN&J6HG~GgK(&nCV2--yqR0QnpuHkOX?+ZQb+g%j|Gx@xz#HV<-!}O; zi+e7ps?YSH2+APgvgbp>B2V{6U|51GrgBV#)o7fv)4uEc+au~@xTwk_D=+J1?82FWX{?Kk_tkh{ujKpR-zh14@7yPAI1OxKKted8-L(hUf z!_Q+?{jW^E8~hzPWeT1j$$JDH4hab~8R(-_@LfY@W@ZLEnT?e-vB_>txVE+yyM=(y zBmOc?dw;q>B)GdGb9dKdrQwK3>#+}l$LrQYE$F8pg#!RXV*tU^Y#%h%8Lh;3Dwlu0 z*}~`lMy9B<#UkJY z)tF!8j(1E;5c!INhF=#2K*CC)wCCmJ1z*j39MIHN6mQXFul%o&D@Du($~4TyCD)=b z10)Cug_X->N)w6(r@P+5EH1i&Znq@zrUzs24)0eh30ne@cbhl-O3v5pi!n&bj$(Pw z$L~(NUM9&S!^6k#JkYjM8MF_FLIf28&!cjve4?C+4?Hks&GyGL*cvQmNPj65K)l_r z+CvTazCGVH82k?W|Jp{E*mgYBPKGw9nw+`UryGm;!==LrDkbl6`|da7UmCTfcPFc1 z0*{;gUhhxXA8e9}ii+$fCmAkt6a0tG`uuV+u>_a|7_t-Gv3_rMWK2v<5g_Q# zzi6RcEg>!rrO{?V3KwFN*?#?j)yD%wD9?k2vV(xQGw3VzUn&`ReD|}u?ssV|4(a?a zc)yg(7#r=@v`%~b`Yf-u#Tra+%xX`3-jEVORM0yB*!{F`J;ICm!=C%JlFz0U1Q>2N zIrhN_PwnS`Y)dWTo(UJSSsaRjgtNdJL$Z+_(8h~+dk#BKWHuhU+4c0qn}3xerh_6S zB|QZFy#f$0ePv(7eRr{uCy^mDA*FElUj>rC3-!4M3zCRr0TduhLgX=s`P7YnJE=Mm zRo9tGU#!w}4$$_~5(4xcqyIhky7)v0p?{~%B2^#A> zU%T^FsR58gIjbzsPj*%0NJc>+hPnM0PJ6siu8-;uVi%6uFU-mmfkQFAx|ojC=Nd%$ z=5bjbCFcY#6JM9maq)hsSAPQ$PuuGgu8I4jSad&pUywUOg`)d6>P+dQ+5L;)q zycK~gZD27pj*gC)sHj2n02@@vFdTv(d{8;=T11vD_?iFu=|shCtp6V>vkaR=gqXY z6VppV5tYRv=f*7MIRMz{%_*C~lhc*)Mo_8Isl% zG#9J6dEHC<^98QMROjt!{Zy+0*N(epsWZ#bSwx-f;_=s$qZd!a!=XICg0t1;nSFoG zneyt`WQB{)4-(hIX_g$ESuNxDtEGCgx!)oZ@K2e{hJ2hl_@MDxoYNgog43y$s}wzW z)o}nkg^Nhc0GK;G6Rma76mAJ|>+x)E<=4XBh=Z-89U8^X!&10MP^#Ad1{t7cW0FGN zK(_v(Sg{Bp;)i836J~tg8V!~!qtIk33JP6RDE`&lPhKHuO#1sHfYV4vph9}Rk=O0% z5$@u8!_mSmhz>NvIW;5HisOTYhE{YYWQ==y`LF>}cdli5wmetq@{S~z=)T;u-^VP0 z3^vwqTVA(^8AqShCm-3y;dqS(Udl5$Np?VNc)j?m*VT?2=Dv?6EP3P_P&E&Cd zthY~0j8MSt)3v*G-g*Zr(5$y>L0x|>S^`&Pr(iE^V9a@{v!`!@ZY29L!d@1YqDwwc z+wwZuk}?u;a`JI2eCt*&i2H63pVuuK4+1w!n>%ZveNKbNF8gX=Xoy=!GTCK(fODUo zi5X_cX57L7Q-Lo^<>YMmucnr!?8b~#sk2Gw-cJrQ2BmE6Rx2S>} z5OVJN#P7)}8fv+5q>(`(gLB`N`|P2|Gum5X5R>mH0kIs#fLRzPxkBhh-8a z^G#%Pi+pzJV|J#W{pxiqneiYH_)rS6B{n@e3Uci{+}%;o7n*+($IW)IxlpS&lU*-m zbd|TIzH?u9%%Ud_6T8bEq`pm1{8_$C#AEZ7nd^Zywwg7Ch%|8 zy8DvTT~K4FEr$KRH2lOnwC$8DbH?Kew^g&bcD~krI6_6}jMNwAUvU`=6T37RUE1mW zFxygVKa6TNZpkT<I9d8!zxY;qLZt|(9+J_(c7?;wlwRNZW>BopO1y}2;|f2D0bejyz9_6D)*0eW;)6Zprs(2F_;f`wFIoBJJ4Tgrd2g`*e<%a}v&g1=_Bx`?{lp zSlMqHvL4oj6_2qDtMPdSB6$Thm&}@A_PfI~>{jz|R >s{4db?V2q5BQc1~>jFQo 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

i$Yf@^ie(C< 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 diff --git a/source/mkdocs/docs/sample-configurations/standard/images/organization_structure.png b/source/mkdocs/docs/sample-configurations/standard/images/organization_structure.png deleted file mode 100644 index 55bb23066501c6833a13f8100c00747385e80d45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70556 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

%?Fo>X~ivSjY zTc7pqZ>v&$DSZR;r9cBySNU1T`c&22>6Vp=SKD#1`{C%jgX#}hcgs6iC1MW*47t0X zE4W%ePO0kOQ72^rbW`P#}%?9hnVB9^v{c86SK$5dIfqbvm{B5DLfl&+0-Q~ zzT_~i@5EyC&>}V?6ssG1iRI&TT2FrDgm9{s_6b@Y8 zcJ-~-uvS$*U(98!6MDuElgwOPVBNX>+_4qi8g9#Z(bNg4q=EzNDwLl%x7*u_^tz+6 z*A)dio`3X=C8n}CRlbn}4$NACn@Kq}+_e_15{A?Wo)w&s zq1j=j1jYilE-8-|ZZg|@cTC%ry8Hr|a{{C64?zOQNWdM}#*-C{jwi;{=ZFecr5S;` zE2nSzBP|=KT`X7CqcYZ@dYxx+6=YX6GGlR=W75)IJxyy@bts9mh|AJ{&5?v6@eFPYa)?n zoyiL`ra?Jx%f%j)9=x?(YZt!II;gocj#FEFZCE`D=v7U=apk~!vP!wD3kN^I7VM<@ z>Q@G3F8gU@+&*e{9<00(n?c>}LuPpR*0TIx4J9cnCs>-(Ny&QP-mufvINr5T>~woqxJ`O>z!qXj^xUG;%7hDDb+M8F~4 z8m`x}xfyww!{obEb8OV$iwDvwkiU7re9ANNcKSaAGJ&!Sc#jrj+Za`S=Y1i1ebX?3 zG}WhphwAJ*8x>1VWnIrogCQzYIoFHRg!sTnM1}LBM6m^jq1N9tj=1RB4@NmtvUR=J zQl~2B%&SJrQ}Lq&`|jyw1^~T`>z-aF@!#~aPu9_tDWLHB|AStJFXyqfJ5Gv?EQ*%^ zZ+%u)mjTxQZ09Tg1;e_bw(0f^YEd((D@9mX%f3r9g;+ftN7t$yFc2O83cNvc`J1sA zo^5W4Oaa#BG}I1jyn!@_pcM!zTLzj@~$H7~4mPYUanyq7dqOpQ#dCM>6e-^8qe z^=u-3%n>?90lLIijb&`*b98<-ztlg-+RjxxHS0Z^v}!Bku-z!okl=$m90|u8X`Zpx z5+5ZQWfK71ykdCl*1K0v0Q6%X*UtO;{%(88EHAHh(3@wmdkG=JKBKgJB>xQ+S;l;E zu{uZ7dG}HOIX;2nPrhQYD|R1IjWMyH)ks~$C;h$G`rfQn)lo~U&}0uEW&So%jt}SU zK{`87I`&9n(SaevYi(q;DgPg*=AD{J^>;EJh&Psn#E7TUrcCNWa$vc{qG}Gn3Lp}t z*hC0`+2}jY?h3+)DBAT8hLD9a5iQ;s^0S=jz&=UH>Fs&BM3lXy6=~j8J?I@r@)Rsf zqrA4*(@h^= zlX@q>@G9Cj`eEqq->I|qlh()*8*wPavUGvTGSd=Ql?rwxqrZ#J`3>^9E@dwC4auOp zK>mdYY_2)Y%pjr;88&H}UrUR-lHwQr#=MxzQCMgkBgpM!F1n>3o60fcBRc|)4i5?? z4en2piII8km<3VC=yf#juLax}#N*yJm~4)DYa7kk)Z6)bBzuKr#T2Sl*&qg94C&T7 z7T3gpme_dN8qb@Cbn|21n2Kxaiw!g##HA50T`z5MYU?-{h@fc~MVQz{*;RLLEKj?p zH6TP9-C{;tULgC53wOMiOu)COh^p^ra11n~48(ed>E0M+uIQ^_Z2e=__IE=10Qb1; zZH7C20T(B52kXwy(ggJ@`K>#pY+0s|D^5&w*D{-=L=kqL;`#Mu7#F@@vTD(unxR?Q z?T56|B9vu=QLf7U&~LzvDvkA3U|-UGFrX#u0{W$(_zQ^BrdlYk^*7Ph+9a28slzrpO`)6L0ln8} zS;7R4V@rMLE$XTIrP7&o(OIZHI+{MPY!s@h>zjD^4iqZuFNS~7TuI;Ay!D2c|0*iF zn5QWSv-NAq1t8K($D-v@QUatj8g5b@Gw#<~U&{r@dmfq*%AxruiVUZS|mrBOP8`-1%%r zO`NTrV=Bi)E0q;lc-ABXM!Y+@Q3uH7{UJae|ADp0-Ge0|frp{?Q;7n6NJ|M1#JgSl zFw|LmH*}A=s1vv@OoXoiFHP}?0dX2k!KG`gU*pYW5_S>AZIE3q>D0hzWmANz84=_qZRq~I@U)Bg{L%?E<`|-*JVbe*l4NwT=n2M zox|C2mPzOzc89NuGcgxKvl3A}9nNQkx&4KFR_0<>R=YHJDKH`pfOG%30>IAyfsP+w zII+>Qg5@q5vkT`-CG3xdY_*y{Y5~{=a%`cjc?a4E=M$heZ*H&5ScU$>L0L;>y&E%R zy&!zfvuz4_$A%k4Mu#%&~H@mtM| zC6YpW<9M;M<+J|Q7bdNmJ%9`Ot)sy7^?Az_%wdNbsX8O3kuSbd)Fdl?xdV8g#^*$e zy-D}GbNDIJ<(Nz1VUJDuIRJtafE!upFOKwOI?VG=4+YvF9R8n5?U1e}I>e)z3_0(S zW*}@gPESV(6+fL&-h+)NNKrwZMt_nZlo*U&7N#Hbm=q~1WAWn)+u6X8c#m62kO&aJ z#DPwgI^kZH!U#BE3MV9PGxjwLVs3f+X{}PY>}#={1psq0RM=EJzD6KD^jDn~u#VN}bd=?HVR6V$ou1a?osgeda0DVpGyt&2urP7c=3?79F?zOl2WK zb&OXR*>@?U-vFZmn9?vVX@Idw4=^^_|6y!S{9laCL~U}^t>Ham6NHL+j8L`=2V7Zl zb|`Qbcc-xJ|B0mMa(YMA2`^Z=XF2TBnF=+Qm-@lItF*DA2X845MMe!V7EjK%imiRC zo19=7i;Z01xV1x?#46vED9`tt-uqU@jQ?^v&mt)e8pqd6Rs?;<=2^^c%wcYMe9*@L zf|3q=-TWGG)=bLbjzAC(BJjYd+DUJT!I!O`wXNlM!^iDpOgRE$iJhbdOz*11pJJihT$P$h6xN!sQEJX&IsUs61qLKFMX;_ zqpc|Ievi(5b$4Sz0r>3SFgfjTf3jKg2L!RIr%C_0it|nWd}X5=WNz7LSk2s28h`wO z8E_P%{V#6OK&K(%!_)HeS0OC(D9q+RQRA98_o3Ca`5iLwS|C75!r5vMhs1;zpQr&# zNTwgr&rb6W+CXdg|(R5w08$a#!C7%H=N#xXD>%aAIx4@wL|Q)-xwu} zpBG=8>G4+6;gl)2r|sS$2o3I}+{zV@Ip2EUY!qLDC$lTpTcLBb=P%CIfi)-Gip@Hx z?u-=hD-b_*i!BYkm^b~p2A3DPs*EfR#W#>m8gz}c`^jN1KB>AKe6}OI2K=0VI=i6?K?T}v9tF|Z#~bfJ1_~zrbgGm1C57M z(W86^QsCk|hFB47&tYiBJ1Nm(vG)`OUKr#hjxv5AkbDuy4SnlnEJa*u*G%93`-BM|t580g!Di9D+=CCPE!mZ1+5oK-(=&KgPK=;3zQ&;WLe6ya&Plj%r)Y@4~>J z5*K^cK1nXV4w~BNj;fu~h^UO20dSVx!@G1sn4DpO3_!&-O0K8aIx4nqO>2qcpR2oy z+S={E*ipwoGO8_%&zst*AL(sOnf^H&7_0#@Gz0xy(nss`92u3=T(*|ti)>d4BaSB( zzb**v$JLIudjescHRMeH3M{8~dE0D^xVFpmr zsD2VER1Lb8Jmg}D-FbILnwDI}2;|Rm>&4e5)h|Ykc4H_H?XE|^i2=DCpzcF_1^M9K z0|Guu?v1`xR*N|rx%}5>{@U70@3gAf^Ru5*dYat)TV?|_%&0kFB#{s|UDLl9y_=!Q z*Q#zUT8zLW!@ahj@|nr1Gi0J@f9P8-XGufg2%be+t>m(vR zNYoxZz&8d0jDD8IN&nQPy{ZtNFd>8gWaE{l&I|-PBa8F_EcaV37GAHFiZeXQC;J0R z?OwsasGb63r!PZlAlv`Y`-PwDtHbCSL7jS}nLwq$;KUeWX!TL~?LowfhIZApFryt% zKl8u)0w*iPQ`GwSQD?G0g55TiU8Y+WXr@m?+&GvOcsW;24GS^J3QZ6NXMab7Vg)8m zs3EQaz?bZq? z9%8s+$?vkM(*l{$s?)q>{T$+3Mpd4Is=d6RFs*`B%Y0E6*IUzPe$A-QR=!f7)uhET zq$!v~Y0@ftk04;HQFq06$!NKnD`v2kk=U+%5J}If;_pI(E9y+nFqimBa_9C^sL9KeZAW#SL(GdKQS6Y z;{aT;BZf<;T}@F5!8t$_7c(`R?Z0^^?p;QS@+m(QDyR4ethshdp1$O3-IgiC>F3Cr zk_7^@-Dlx|8&-o%va11Z*a^MT!C!ajGk-Y;|A@d>O5Ms)Qjn(nu-LCRf7~to^NP5q+Mut=fVR^V5C3nTo`)I`ags~< z=G$VAuj^lMeq;v%?1pMDQIU`?rRbV&E4r%5K~j!-^nVz31Hk z{aP2Saux!at|mD(x<6EV*LnZ+(?$n0XB! zrF##>_eI8zhdUi+ikpsO$AEVyX%G>euxU9UM9US2Ft<}_XMO{e%$cUmv_twp21e0e zAu-^0lcwY@fMEifFe4e`h0H}pq2&o?fH^PABIqf~3CL#t0*jFw8GUT$>66ZWbU_j; zQ;6E9)hbmZHt3CefKU!v`Ea4ZVo>5txywrgOyid57my%mT6?8~m0X>2piQzEksej2 zIY8L&{H8dn#A*bpb|}gqV$BiPb7zN_vF3B6nixXk@Yx0xwtB6-UZ%X!z16^;Mc>5> zBwrya0O`qJ7?_E2B>V**CE8Mkw-OfcT^J2_5z~F=7l)Z5LTGDiQ3n?O)T)bWUg6;XFLpA;5)Eq)?-9_dR+WM5xK#VUQBpoEJWx z^$_9Wa4@*+1?b9nIT@Kq~!5`{hCZy{E=mg>eeTJHkIFcpJ5CoJi1+rp0Yr%wi-H|ENfHQ#|M*FRtp&j6yE* z?n#JyRg|j^vwD_nN=HvDo4mUUwdmv~8SovNcekfoG#evXGn*ZS4-0N0K56(|&`4E( zDR8{&GMy6_fS3~cwfI3~(r%Fvxb2Q7z1iGj!0;o^Y-VyzV=lgpCS^Sk0IhWWV5v)_ zNrd~i)R!97@hrXpZMb{F1oi^F8_ILl&XpYd@7`aZ3b$O4h2LcfSyPo?ozzI*&{>9C zz?#T$jPh*zrazr~Nq^3ov7RWfDwq>&^AROrR9V09Nd7aDvHA~r zvu!TaZC{l-%5Gwn>#-MLGo9f4EgtXgCqC_liaXyDEqc(bPu|(KJPugnM12`{%LqP| zOm@1Y;a~Nh7zEkO6n6hGLmoS2H~AYJLR?5W%?{6M%sA6nJg|L5y)cH8*~x!vWQ&8p zFoW-+%*;#7&em5$37H$`XzQr)xhc^)3D1oY&-(huL|z7dbX=>_ljv7Lkk^rNO83<= za{JwC^n8K$;g-*R{&G>7UWP?Rf_z-DR8Ra+UafoRp}5@QRr$i26g9cEI#pDuW&x}- zRy9JC(0YAyB*pA%PpL%i(pn_B9m`)b;zD$)X({E?nGH^FDA_}J?u@7>29LE$45&D= zyUI^IPj^6!=)CHMmif|p-ruCq7r$D0KlVp{$@0N{Wi)zYOBqf&R9!>5U6v*4fhH*# z7RUYy`wWpIa(P(rK8i_+^89cbb0{A3_u_fR3+|6}g=@wXDYWkU21-zUhz5CO29g=3 zo+TP%7-jf3#dt4PP*Z9fb3!kU?gMt@0m`h|oAB~xQ(Uvdig0!dj3vOfbnASELGCw2 z<|9xoQARYxM{BDNr4hRjuiyW*ly^7l8mA!B-eze(*+#!HHn&6DuKQADwPifL2?PGJ z&U=X6_soo+5jK(C8G6mzA53`8%Zwx6tkp+HIlOh?!;Q=c7Q9?yBx1{?A(X$^4l2piVZ#!$j9KxRwI!% zLOxb%QU5?d6aMUk&AiGKry=N#i9n~>mJG{(^pXV|-Xtxr_R=Rga%Z-PLu@daY(%ee zfJPtH8`MetXS-)Wv)Pwr1Gyuuk}4)|tel_XjKi{4eTRQaTHOn%yJnq|kn1O8bGlI{ zt+}qLxWiIVM5n2q-`UYDzP9yvNaz^RP{PO2$&OcAFLQoxJU+vfj_|ITJrKD(-agOx zbvI+SarH7H{7rESPpD7YBKbbLYWG5q@-!M}h&Og%oyKqX%`h=|$k6cI*`_sFx z8v@rxJE%Xs(Uwifbt1Qr=X#4+oQRK+gTw7bF3nE%6+@{-g-tsB zqxv~obLC6st{9Hw=U%I&3E{ELqXMY*Qp!T`m9K%mLcUtHEiEbJP>7u75gB=KtCGSX zn{RAqh4&1{7rNg=7b`bT*D$_khcmL3Zghd2H~vB14}5MnK+mXnWh-moxwDP3=Fn2gujLw-NMI`UavxHf^+RkADOI26WH*gnvGLSJoiDejP!o zBpr%JiloI8)?yd#QD)k(40~{^1uapEd$v}+7Rh3cnYYgDu^JT@~2OBJdha5)+gyF{5Pbs_4~&$rc9xFRUxij z>)xN#3eUNyh0iDpzZzUm_r08&?2%VztwHuQ8XL4avB}}?hpB(omCRzYUklIQiaMUj zE4cDrC7@ht!UmN0XZ^dUvwt$^ym&D1s7hp(15Bu#*0<@et_QI*x$ zjh|YK$KnRbsp@qrQZ@qt zYrs^YE-CRBoUHJd=h`JIU-G(Ey2p&F=<%WJ*;KD!Ss#O6N2P-o2-A2A_0dWije|5_ znsUBxb$U9LFRP7Gn%>o7QvXJaY?0D3+r&}IsXEYc%=^5SXTa_97H|>}bnDuFrQDZZ zwS`L1>i2>V#}>~emVeZ|2AbJ7I2g$w;jJ{R$Zrs$>Z*gz@2$z5 z-bg$powAS#idjub!rzUBSc~D_UO}C%o8Ap`i^*v&q~%z6*eZeq=WzEjw{8DaJ}(A_ z?=Rlkwf*sp-hcP~a-9p7{o`za_`H){=BLrU4Nob}dpidFt|7Kd*x()V3=&SF0Nh2r z@wp&cO!~MA`318S^*UZ(n#B;} zBof*69%n7`OGC|x$poLURRMqw?(J+d?3+_*s}Pd*FB zv|qqAUUn;*q{Wae%FCwIZg3P)lYG`;sorA8ufIC^WA@=lO)p_+8BSbX23KL!B5fHg zuG`N^)b;C)p^3>~m6l0qiej60=uW2cNy6kIrleps#DGTLC#liiFWWxEZ7%hUa_ z^oysXGc8irWvP}yM&l^QaoXDw*OR~X=9w;1EXu!8-cr*NXyTcGQ37vX)>G4+PMhtMygAL(m8RXvlU9S0r zq>}u`M2fQIQwFt6jiUUT=V+#qKS8qXq4r+G;w{z>mGo?KwKSrs?{ajZ%B+!vy$abG zg{7eicpNYeT={3T6lVMtsS#gK{+f56EwDZTg>n0xyz_&W8JdJ43VZ|%Jw_uW%Jga? z!*55T9E=#q-7Z)C0K-dR9&!$7lCPGR%geZ>Q&NBVB=wAP0SvlvMpb6lxIN`UwzK{> z*RAaSmP$l=nik>S^A7Yzn&7`%RE(ZQp>-*3PGM3@;fRP7P9tqii>`=JUiiBE@ua5f zfryH1%!b+bg13|;Y(#3%ki!YR#LlShkkXJYCAV~-^>cg#Wy@E8h41sLbTCVoj5fMX zmrLp=DdmK?5{gIZ!tUSCJ-iggGD`}*rDr!sNX%f$PrkK98Ss0QDYeGlff18)+$Cv) zesohy?xt;HW0p=hXkISx%Qf{%e}(A>jYq-(sGb3~)~)Lu=~mRI(T(b)mvt*a1xdU- z&OZ4jtLTGEQ$b7m- zGH0+-yg3Rm#RKWTH+RBA|8olSol_I-^?->Qv^$Y>3Ky(0j^XsT7n>f&_?@W>ZNy6FBnytIlYe+I2iM*6 z*8vSOerN9EabHqEHjU_&zsV!S^4FNhF}4s*sxM%4VJa8(a^`v$=%}S0$o%d!so&$Y zgkHT0Z^~oxMaAm(ce@Y0z9(VncuAPiqIU!atc4>Ss0`^qCuO$yvCHH1t@F|*MT&*~ z!@s(0&Elds=wvqfCyeWPIWB`1tT0|jguer~)m+64Ex7R-Fa#RNDI*X59O>)$ggsEmZe&c7T08^d4N+|M zrJ&`qI;EMN<2#|+7@E6E>c`H$vAP`jQDT)2QN>zN3Xi>C;fTq`X;v5`6)WAifOgf^ejjdL>)?@(i}DmnB@|uwdt#DjE@9 zuT1R*TxeczFCBHu!02s3_~-DKtRSzA-fx+~tfi_|!B5&mWno(!3(U`TW#^?*tn6F= zt_F2>XPM9neUG1D)#N0LVZUADLK$N7D97~~UN8*?u}ADZHsI${V*SFnDd(NkF7UCS z7{_+$T;6$FA-I-tvO?3C?|Il+#9e%w1z?HzFMulpOVZ#$=oeAl^xr_I+n0J3-#^M< z(Sd$SJ(b+mo_<Lqx~V9Gg|hvj@Un$5rXB(>0bFq>X6;e!(mdUDewT<)eF3aRLN z8dfOp$6O{XrMvoaq^Gd$1h<`O*qh~Za$(M|aPNf_2Di_A4wd|p%B<=;3hnz{r8-q# zpXe#pY@AM{iS-{EavlJ8liWEs7U%SLP5wPSvM`QYgca;lA}7inFN0`-IjKOx>)c(! zwRgN-JkPB}Oc3l^a&DmFMQD_#r|l*+5s^Y0iZ3esu{X2|oj*uk_1n(#DJ;MP>OVg= z(&WMiEnGEuUK{!v*3~LSr6SIK8FQH;cVcdfK9`p21oQOOSp6kY;N0ncM;IqCA2LiP z%K&3m%P9ET@%CC-%)cNxYpU&}gteO$51TQZNGJb2Kb|5u-e0@Q^>xFLGW-c+6Xevzv=23eNGU=sJ4`5=d5(S=NI_Xylw8IJ2koY0CEW_9rJ~Gvx3o=6~Ip zFL>B^xE!HqI@Xs>lU)9WQW+fA(9~vJYxg8&u%%FM5K}g6d87v-vk|PF7g6Y`VC1E7 z-KJ@rM9AhNwdjtm7m+@y8~(O__@vDFZJvxahmVroqf&q6n&3xh_{Ors^STs&LoSYr z>0;;tjND)mhPrI)b(jtN2>OKwoFNZ<`OIDb7x*f=%B6WO&BpoxA*1oHBNK|VUopX#N{7o}=OHIx~z@W^U&6xAs4bJ$J^4^ucz z1iO8GYkLeZdP|0^e7!!icpYe2*k>Wm(Hr`4cFy@l)zNXo2$?i|d>Nt|qz;M=czkLX zbIjRb9P)<4i!A=q7#*1s=8&cjyQ~#!^{PcbhBMs4rc=1IDWhO>0HRXLCD^Rj(~i^5BiX`zx1Tnbc$}sefMCKj^~f>gk7Q z2lR#vz1XY_x;pc8`kU)CG7hM2;^N4F6o&7k`)Eac6Y~_K*MX&%EHge8zSo zBcpsNc)AKn91w$$F!>!`8m<71NQl$#sr{)CcY7Nz`?Ty+YvGoHSXQPW{kR%kk+Vs4 zXqw?HYbj1dmd~9Bzkf`0<>W;K6U?o!vh--Bq0+nWQy4S{Y1tb7LV;*3o`wxVKEwP> zH;|1-lS`8;tz@$v-IO+|Gg6vP#5ps&P-tOMiSE2)JX8CImjSu8Rk;L4?*QHZC^#FU{sgoP}b-*IjiIlIzvL!ib?i$^33g4IdN> zjZL@&?3au=a^X!VQIRtG_tKa1W$zxg9?0f~t;c76Vu?>@iYR-p94<4nDJjk0w?HD4 z@ScBqxPe2Kx?=N}q!XRi#xr;_mA8@h(8em^7cRUx_I}!zHtMx;#}l6gDr^FYM-nwM z5RBoUCTJAN4I(bub$&|O(c}AsY0359E0`Kq{kgbvy=e&bL;7W~SsF4}84LiCE~QeV za7Qh=+dTRk_)k{Q2M$?yrM1cn5>5n&vTT1SFyL+E$fbxBRtLi)Ihl$=dc)NgMU3}X z7AM%}wz^&4(L^0@H^`EtEQMHqT_`AfotE%#c{rF?GBXeI-PMM`6}2pI65Wh_OWGT5 zuR2s=#}9sICDs;&f4r$e;)i=PPKHF+%_Sjfqt$5y7)B|osao-JjgB4nGB@l~F_w4= z)yr3{X6>L#*UHUc3-ikgfYP)pZ}A-e_z}jf8gvg$l}?(jKPa9-t3+xUe5`@I8w}r= zSA_Dol@Tdb3_2xZ6ZD2G?_WD_OSJuP!sl2Nd8OU{nkj}nmw#3%&J{BZV{o1->sl)- zQk@?)c)q5d($AVmIi|n$$_wdSNE+es{QCe|P1rAa5;UO+(KAv5Mq^sJvlmKV7f2K> z<7Cw^I%T2P4v+-*jZ_04 z)5Hc}w~O{SK+C>gBFU#~<>4zEI`Ofw&nxGaA_x3nin+jK^ej_q6eP5VszLfq4+6Kli76-k}srVM+D-pS`;_e`%^$ zxtrd&SiMhVety6}H-u7z&HltE)OBt5-gLH=uA4Pqzq+%e3+mz4->EFhU>TbBmzU!PeAs zNcx^`d%P$T5YfvN2i}qSY5Z6N+WPd}g)#<;2CaE$BZ>1V{xK%{Mvj2SHXq01Snviq z{xKyw(Rp@Hx;_sJ`g^E~ttU+y|MfQgH||~TLGc9g?3s%vz}JJ$oe@F4vkk6c-7sGV zQ5q3Lf9m9$j2}~r zSSAZs!GL%ov?i%T!~IYw)a-9Dq|WgjIvyIQ?H6lpa610=w))(Ywwib_@Hjr`fzO>( zni5k4E!Wk}vgX?6H4>|(ALtoM8-x?L%3u@bI<=F-h_G5aJG~0V$CGBLP1?Pq$dg9$ zj0TcnL7MHDky@ssY?}1}wcar(dW~{JS>H^N62yaVzOQT#^NBsl2m;+X$P0iw_f>_e zfZJAmrV!_LejNr0Fd6ibY!p8Qd}58A#NaRE9q^bL1)~2zt_iwcu9Ml>X1~RiSI_K+ zKblE$lgW(9exvR8oBl@WKU@Ho!;`v zb`cmncGP^z?t@se8x#lE8GMLJxdk4b`8%QlY-ErdWT%c7w~vyPxlAp9_%oIJ-S_u? z*+9Dt{#Uwf-w|)?6N}j9XumyGI~5F9+7C}O zxlGI0-Z%1_wE05dJEra4MIG>klFuUbw{2et<_oYcaho*xYS)+}AImMo%M$;>!lRO% z;a`DnL+t6#i9ds*#}Cqt=+8y?*=1YmA-b(9&B`Bo!kd41o#M*2b1owEEjevbmempz z9M`0uNY1OTW5Fm(uIRZ=eD2^D0%Bjcy0#Nb~5(`)fx2YV?W=2wI8Q5`;a69%0& z6vdHbUxAQs*(<+mp1ylbxAV&-cJYIVw*rmbLI;*#NTk~<*sM{Iq`9Nx(&a6AfwJqx zSpR#d?+m9E8S%9>iSo_X+ad9_`ls^5sgE(*O2McHzv>XXqeUPh-*@Oy@ck)^4E@5eFC;jFMc+!aY9gH2yO~1b&4p4Fha9C z^^mCwW4v>p!x*uj1@h{XmD0+safm3kEn261j8eT$#yK zRI+YQ(dbh>A8+J@MNL3_D8B@do&(=|4E2id#l{!)KI0yvLl^_`OykZlpthOT5^nMQ zB?lwJMv^!0^F)TYtr2C{yS|7NSY9CG_vh>RBnB5}O$)$=Th;s|=ei~}xY)4sr9cb! zUTegYCw*{#CLYiuPKXA@#raJhJj=QT9-W`~2LXn2Ej9e%xjx2^((Cd~bo1p6(+|I< zOsyqRz@wl8SSBEUm8P=)^$js_@^6RVqX`(mxz<%+`h;|Mz$0Zv4!wlt(KhDBu^$a! zt2#gh64bQUq=R$k$mTe32Z%nJ+wpmBDITSfGm}{O15!R3zDUJyvZ%D)Niktn_=G%8 zkJ%#zz{;Z8=&22-08`mgp5tCK7!*HUSWdB0L0!l52_{R(2jq)Qa*SSkQ2|0s1inD9 zBv5IKwA{%v+v1=1rHw0$AS%WopnM`6Zlg2w=2E3 zF6bPc4pN#&^ILz{k!WZIva`*QS#hJ4##ZtxcBnZ-jNlg0m$SPWT4J z2VU4fB=1Td6&<^c4vTM3!q@bO&QYI5qL)mZr z?D#kk{kLN~1d7ex8d+;uP^tSE?~~6dSs6o@hdWh3zR@B1tG@i(k)n|SMzX{KsfS2!TfhQ-Zs{fU!&ha?>}9V2%8tPGwiW7isU*q^9+BO`x!ET8p(tmySN+@7@fAwt~GGaigM z{&?^m@ahTpGC#)vm7l>quB7qr$`kI<4bo|dSWjSVZxARhjd{-21n2rG1H3$au;qn% zWKfuW%bN|h+KbCywIkW~6^#a_r5nIu>x}Bil(uJiq<1bK&U&5INJf7i3EiW`yDq`v zuy@4Qv(p? zg5Zy+r$y%QzaJ8}7p&3SxPO=Szu)Z|e(2~iKjfHnTD=pJIQztQueX@wUhro|-0Koz z7QgcyK(H*zrYEf76>JENZe15Ryh7jZ7U80Ti*{09i4&40BBb^(MGn8ldCLJoacl{c z5O(G_t21!4B;6gQc`y0m^ACk46lxW+7@2awaWLPxNZAd#!J05+>$rSJ_TIR@2XS;e zf}pb;lZqDkSSUs*=dfuSP`DNgZUJ$O;Q-q4U_o03Wm8PZCM*$)MSAsNoaC+ zpViJh&oleoyY~5TPSsmQO${^6x>v7ottjn*(EMKrddq~FAe7dPTV13Oo&|%WV z7^-0)Mn>#F2bNU@ zBc?g#aj5~1r=!@jT#cBe;@sefv7X(Ju0ONWA3$^UHRR(u4q$)MW^D-2=~N9IMGX-neZ?2SlxN6l8Ygt&8|5o7cglqBGVn^E4twq z=@WU{+4%rt)0B|#kg?^cd!zmy!4RLG*#56G z->U8Ti{0O-`vtS6%mug@Xk>z3)O1ieoRgeYho29-j)xR0fW~q&MC}*&Kx2KZ9Uh8u zY{2xgUba=)7Ti5Lu^W^9C@D@@Hq1_9I4Ne50ri&sQuK9e#s_rC$G3+2BzN$2ol!I< z7SpdpTvkU)z=d=VY%fr3xdB#5h|1PbCFDJ7hfe#^xTZ20Zvj8@Z*Qexb|hQ>wQlE9rKEQ&>P1dkQKIx#vI&^C?| z@&9ELIb_N+sl4%?`h;AmCf4;O9qwzG#^38`E*t86gE|$ZMNE(09Iu96;udJPi ziY<6xM84x!vRk@=@XEsN1H!zrX+HV2s`>7c>L9KMHS9zutHz01xS28j@#)N#8uhDM z)_MmI3<8>PuRWsBg~RLhS1vgGrT^lCNr?R_c(9X^Z1T z#bm#Of^CKCL^OAi6#?jk^5;&OaJkz=UUrZ({~yXTk39!2Oe;o|#(GT-vl`bmCc94a z-#Smqx$r_5T?M)vZ5H5ED)MpE-gXl713b+NO$~Cn`_7iu@Ya1dFgO55*~9o2`l*xe1y>m zsoq+cudj}8o6sSvRWGO@ljS;AEbb`jn9Tzkx=>y z{AN<~P2E5J{Y}VKZFyG-ev70b0cxV?|AC8$=kV$L?|v~89gLE#Y0X~0_Ni7euW*=AycuZh2n$;4ClzJ^VI zhgyAvrNhi6v8)Jk$Jji|Yt__y{g|yyckHSaq$CY``j{1+A=CI%HwA{^`}j)=9{P8? z2vz@=Pq(z$s3-5>%eT9Q))6R4K!$VWSA(Gkz=uJ0dmn`$&zCy$E z`O(=JF!R$azb^fZ!Sz8f>a?c<+V-hwfA@5qqz1xb75(7Y`MFzL*Sj}yKJ*_zv$Eu% z57Um4TDQO~1u%p5wC+A-55hS}M5-PN1e+{>b;#V=`xTO`@pL19TJ_n^E2f+|A#~`u z+q6S&zGcaotY9uUFOpw=SHFz&y~y8fh7!@SBRMsb0>_F?*~}ZgC_M7n(JhCm&itIg zFRMNyQhkci^;wKc@Bm0j6f}T5-;=PU@=y&^_X+pQc=vVB5jO11>jI}EHOS8N;ldjJ z1w43Mfl=ABh29kLwXtIQQ`82n_ROOZJI+hAqG*^pEf(ilu;Dxl$jE!>RN}=)BVY2w zLq18UIkA2=9MSo7=aT>F0pz>+RAlYWbWMr=j2#u~+rZgtu3|c2-ID9vb9t)wtJB!1d0l7R!dEM%O{cOwC&=#PE^V6e+%sQbMW>>l=jYX|&Q$ zQ)L}U$t*e4Q5V@rOW5URtgg4;mtuMG!x4 z-!JrZ!Oj~uHtq{OcpenFDi_DkCJMt;-&9ISQA%J^L`E-JXahs@=6pCLFy&;G5SD&E ztVU2f7B!A8^F~UE$q1(Ic zb{Bt4TS_a?O6Mz5qa11UWdy*uc3`?@MZvf>CIu*9>Qc-$=V(B)u5XBHZ{W=Ih1=a* zepj%&eEb%L@Uqhjq~eTJzsk#W30XQO1m6{SP=vL{g`>M-M4a8w{ZEk`CGW^Cjgz`g zq{p4skkz%UWzui3Tz$r;IjIB$t*_@+nixcW>#$wI{DOf;`@EVbqB}8&^fwxnQWLsdjyg6*B zBPXokXQqzI{MZkLvAIWjU9rIRk(5qt7DTkfq&l=#3c5J6QvT|qw#lGIAt+GGbEYQo zP{&^xX{ypEDi{g)#Id)6G7>!#$k4RIHh&Gmv^RexrY8BVDY&?cUT!Bx^X)5so?yv@ zU&!xU_YfWhfL>f|jM_`2VfJ}bTDR{*=wUN;KXqGld`eTJG94EPb!(qS=N@x1yhGo} zBss}F>T`AS$N6nXcizIol!1Hh#|+%_7W$~g?qB1V))l{ zZesCM;=#Z?7|(ZH$F5;hSxM<7Lcme=sI-iNVjw2)6OE|E^;Nl$C%pG;kk?h3LZmB$ zRzCllP2+Sa?Y>~guLcj_%+dpX9VKev*sz&kSt||_ay}e;8y6A;jFpB|fD=?QieZ`p zFPbRH2GY=E%M{}$))%Q6|dzfDOz=l4HYK`EdGhn zX)h|_%=ybY^t?ynEBhS~vpDgIkAXq(<3opH`lA(}jVFT>ah!mPmjZ8GQCXYya zi3@Y7G>a4oz(wJjQBzKqhKu9!QAJPmn^0*zxrhnY=G>Q_orc!gw|U96l8kQyL+QL3 zr8YLV&%gHiE-LB~I30OG8nM+;wDmp=n}rp5tGU_90*p8*%d<qQ#GdV~ONpJb3fuR_ zB{M;rZfQ!J^1Pr;lelNntn;_=Uzm<)AEifWJQ_D1R6lT`p`H^*s2yPJUYsrDw_PgF zoY*dlJM3{o_PT4s-@QI}J24Qc1bK*4S%oX@eXL+}q!ZwR;uf$q=9Z>CnSI)w(8Z+tMnG+ zIzGu0%)AYpq!E3+zt}cIunE`;tB{^**To%I0Xqzw^lyl3RjyuE!-9wSk&HgoQI+Kj!BgLM;`oRH>1s^PQF zgXZeWm9Xll2 z-Xpo3k(1J{m9PeqRN|1RS!EEYNZb7Rs-I{@Uwin*h{`xJCJ_o*Z#L*hbfIwteT` z(`l{%`{V~U4&d!SXx@OQc+346Q1Fx^=Oy9VveUp})t-xT=_QgZE}5J4r??z}cres> zn7RLF49FBT>^NAR%3eHYI$0&y|2YW)pHz?4BI)o8v{f}vEO4H~FGo_h&{{ST#b^xa zomY7=SGNkmiy>~u6oj2gD#5{_OqL#C8GE~LSMVnI-^!TvrhSZ^9CkGl_>+%6r*o(1 zj-(82rdD#|Vr-f%ySc3&Tl^i))*FyuDKE-Q?)Z=AMDp3LEXF5|s!5Vr!@ppli#6;{Zs-GNiRg-JQ|z(CupQyJ zycXCFr=466FgH93C%67=Q#9Ywbw037-;?~6k#ZAhiF>7k%ehD&{s~%F7f*H&%b}cK zwJ_fJR&({2mEca3L0MPiK&An1ttVg$9E5-mk9Mxn;Ep{eCI2s#9N0#eao?k4a3Hp4 z7>eV8ZH5rJLn|sC#REFPLs%n@v)M=FLURe+$X;u=zKK?%T3^Wi?^oxCu6y5jPMi}a ziBDGPX*jWO%Ub^q{VeZ`Km=c`WJG8d<*5HVw+riDdZ*$gUm;)%g6dT*U^bj!*tx1+ zB-Zbue}D8F7{bkHU!6m#W$jOncfzndQEl*5z*M_OtkNR>Wa>_fj%hqp=1DC`fBKCZ z-R5~WN_cr`k_9AO+a;}6)X-a)-qrcHlEnI!HW{}Y zkv4Hv5M^p4F-L-(;R+NlxG=g93EH$PM#y0y~eLL zNVdDC@EagXomqjB=@pCEX=l4^ln!>uK3;}-R zSkYUtUit-~H}5nq#rJSJB?U6I10ounrWWYmSbRTf8a4V|qY<=-!r=(x0;UCAJ|4C~ zZ+DulJB|K<_Jkf-=V-rO%rO6XO3Ikkw%;lLBLMBSnjZ9WwodSkZ>7pU2kL;7FN@THwQb}5Lps&!>?$(+yKNI zqn)P004nq6y{UK#K@B!yR$D(D1~RooJ_wkrrs+6IaGKyY?Y{4k=ln0>7Z>UlnQfu*5^Z*B>%MkDstEnVBapeTgWrfkMa@!uzm3CvxPK z{eGQ$r-nh1(qYl35h!K;&PcUHz(hTXpu&TJO!r52&(AjMqRKpm#X9X~blZtDJ!?F? zIBoT=GKyZgF_a5XfU}1UCQ5q6MmOJE|7<0j_FxNWtOc;vMZMxgyA)W4w*PgXovED> z-`6er*(^_w$Y&}p?ydAiKoO1Keo91@Iu46H|M`xT$v;U;Np`yWWsv~(_n@WUW7y3X z+Bai7%+4(Lks+NQSm38A{E6*9f|Rv!=v98ea@{a_aPuB94W!xf2=8sm=`}}0Q_zEx{b$Y@ z0;+LR`8U(0o6nEC=IHMDMBX$+JeK&-U#$0)0NgTMXY)7->)uZZ-E%Fnt|xw!yX@u6Vq zA-w6UVIeUMpK(YQr#3f4u%x8Nz^Q2Hb%CKkE)3-@b2^Z0*ldku@f*xc^l4>&P<+$^ z$+KukraaFqv1ss7LM8j1yjfSDNB_;Dagv>f(`@s0@ zEkfwo6P*#8p~I~p)qUmT^#O>Vw9}QCt?6KZf-JUVX00JIvm<|Nr^=`-Jp0%P%{tBv z*~r|OphB^*lsv3AQAj`zY)AE%?#v64yHvWh>ZVGec=BwSJ$UkSz1*$0-YeTmqOSyXU-&$~>!Pm@hfcLt#ZKwA)&bIp!- zA;9a%=la&iH9yp4ejp7ZIs-zxJO=%UydKfTwuoX@-KqlphbR54jr?_^x;uX%9E(EZ zi5sGJ)3=yj^uI|@o6cKXLpL)P?5e)fSM(pc9g&b%zWhD%<=OVxwLGJ00$VZL=vi4l zm1I5cqLPWQgN?`xGN#sDY+5@&N8d?U`Y=el+yqV8CsULBSr>r+LR@VAtaO%+e5&Qe zs&r_5b!YBkSZtaeSq#67Zwl{RfhAL06nzH|azo4^jAun7KfF$AfdBES^zC{cx2wG? z%^5OE+pGzrrWtQCmLZE~iU9xuVLs^!Az4;h_23^7U0(RSn=AUT*wwf&Z?4gwEMTUZ ze&b`Wfm7KjSxO*K-e;MOKWTV878PrZ^o@dM?n>`ASCDUaDg=x-DL~3!%A+pty^kN= z9X=GLyZ?eX{q$JPpga}DuI_CvA@?!-&AD=()MmibankBUs~3wi-x3V}I|0ty^Fcs5oLOcA|zZ^48!U zOcM(51E3`Rp3ej;tm(L@Nq?kgk;8wc&X+8tXY`7AZug2trctI2^Pm95qV9vE3;`|M z1OYZO8ePSC4ci!^a|Ow9nEZYW(m{TWb%`9p8I`eey636p@b2YU3`*l+p1s4{TT`^` zku;@|r@)FnMZm6)xfq>N<8joDRV#_X69mlZC?W6HiW(#^d>ip zc)*mnYWiJO>6af12ipV^)4p|Hb!cpF9+-%n>%^!T`-xb6AiiU7B4GjNri2J^hXNUJ zVU0pUcY+>S5~V7aX?Y%UaXvGP2NnQ`ZD)-7RK^KPTwkzOg%Bll(_~}^dqSjpTO%?+ zdaCgL#~E~$Ml(bzrc*FVmd~Oh!e>JD<_Dvy_i~{#RA778T0Tx;Q-a$amx)q1hX+zb zP3)~Tgd#AY|`RP6{lPa7go+-xC zyii!KqOW;a?&{yUDC%|aoVqiS{uTx**s|0CUD}49b?(X=*eUjuXKJzc~7Z`Y*s<7il-(u`S8=nb@Q9vlx$GCq|2k|+q|(d0M_E0^bBH%=Nhbv#>cWOYsrd>R! zc|!yc-b0)`74>>dbt0Bc6<;lVH2c0>B4}Kuy{mnEBiVAOIs7zPzlTk`$Y@Rc6~ot5 z>0m<^!6MW)0vc%1Z_#$Jt1#-!cXA$&;pF_Jw4B&{T$JupTIy^#b-nnZ;TA7&9b0cL zO)+pI&47Y4B2a``Eno98?m5KV z|K<8HqmQRxB+tq^VQCV8MGG|C6#VfqY$rchla#o6a>nkD@-8mLj}B&8^18qMbzn>) zY=_ZhN9trq#5O7XC>VCSeS|@6Ku6|sex~Nab}1JU^UyQvmI?D8^OEfEr%3rcsofw+ zvhdi5EtWjW*Nb=!m*n>2Ul$kUNz`9j8?EEFH7p5)P`${LT5Xzb)+-rO`t6U%Bfb5A zBkCC74lTj!ZtwWrloK*ekA|&nv$B=GNZd8Q@Mn%&m|^CMdNqgqt#|uBu-w{9$jh;3 zKNC5`z9LBTA^TAVryqn)c(MEC#M~+pYZr4Iy<`|B=h@B|S;*nA21aK6`nlKI~wnhuTm^gcTwvf!Y%*^LSr< z?_d~jrrvMNM2C^9eJ*n6z4XJS+<>4t0dyvUE*x6Eni$fs7pgDzt_Q(7N!^#~BQnGP zwI7Im+^U5~5>D;+#Xo5L$`m;ju%TE%24(no_hh5eS+kmK?)19zN@v7pS0{2hMGWGj6uuZs@_OkNqVAIN3q@QKAfW#k*wb>qU-cAWMysjK>HHRx#u0Yn=OqwIPIb*_A)zWxEe84;1I)U8YaGk7L=_`QN z-P`kFek!w9hWrzU#AAIeqm>UtFh@N|UQ41K$ zT)2J4xJ;1s%4RrkIKt-|T?pSNgdwMh#G~MznjM5f_j1?cU#ShXn?D=R^P@}l1n*pB z_lJlrsKhdAiFmS}!HSL%z_~H(unol!dp-{^_M3@L@zkT&3E-Z|sBJF`1&YV?qCa&T zCXgso_`WS29w@u3!8X;HUXESaiUQ^JCx!^`cKO+m|ei7miRAdUVrz9 z8H+zu6d=M~@f+~9PFyzzE`mbD%Y9eZrocc@pgUqZ55dp2gW*K-d7?lhUxpLO>oWyE zbzuZjVTsL7*;VHXhZU0ZY4CFA(=|Qk*h8dU+csBUxR1o3rnbQQ;kM1|67GCs4Z@?e zRoR_6P>IV1=N=$=GP3&v0Asj>Zrz`Rt{|UnoEAi;Tg0QA&)siIuwg9UQqVuM^MRE8 z%w5zsewtPAg;HB%&fQ4GEvQj@-{9b=hA?Hmw+xttq*9==IQm+85(R~8oyMIN;+-Nl zC67sd=%f_K=p|(7XLr_6F6S9ZpbKq7?$8OBhDqkM%Al!18)2WRBP4ADFX?2)+BZ`|D))wa_A6zJQ( zkVr074;ezT@$32=FuewV(c;pK#9*nC%H1Ny(w{};Sl|$$(v=d z?q#OVCPsksPFKzS>aD4gH%xoNmEdlI2QfnqGvy*;MUMl)i8)Fey)sSbmJ|C80MjmK zebVkDUsJU*mV)@`#EY1gAD@AUwZO%1a!+KVhMccD-YJ3s+;PGHauV)#dgYNm>xJ@B zF@OgNJ(Z&okN15)+vMp0w8<4$RH&a2dHF)3uJ(bHhF7 zFMET`GTu{CIaw_BD|Ty7*1A`3&Gxa_Bk;l7*?_nF}c%7%{gPi651FV%TL% zch-084+H~LwDpi9)t{0d=b8tNdLQ^V)O4g@3>zWb#HHiD`iCkX`}WUMyo)vJ8h@7| z4SoI9KkV7JpSzt2`xkL>oFNo_iA6A@aLH(&rVu9ckt5*MR8a_$O&?rN(E1tjepKbl za*%Cnhqgjzq2b!}mImrtba-T=m6xCvPhnnS81?RSnPtZV7{QhA{u*s`g=Mr*T*=jf zzr*G18a~2&g!Q8B-dpc`zL+=N*@@?U;Qo!LM`S&59Qwo~jx(3DBWaa8#4-v6mU%P4 z6T=$sR3G_*bVWjVV)-+avSwPS1u%NfBIWoaK$b6_tgQ;D@f6tIl2~CFeoB{ z%G#a_4wwj?_a&+t0_e?J%aqlp@C_H-HF&>1D3)YKkc%G#s}ch&@c=C;=|CiQ_Y%k* z=Lm^#u4@rA!t*9m8vI1doF}KecK5P<63&sMyr>YJhzDJ?Qc90w8D}JClt()QGK5&^ ztTbr!6Q#V?xRR^aL!&qkgQ@;2C{d+caA!r}^)=pKi@$m$sZp276VKVOS3k!Mn|Zy5 zCYG2hOd_M>Zo9CuH?3$)QQkWa7}P}V;K1>hKILEJ$4<;@4^+N1Gfh_DU7>0-MCJZW z4HH=vc=A0Q^HdS;=Y0l3$Kp7jlRKM9nf`hy#`}qj%lQ>a!Y=oe;KN1F6nt@o6nb1C z#mfy6Wp5i_$nE`^(!1QR-mf)w+GP_UY@R%I+t<54%2g=4>x;+JC-YdjzGaY={yqm7 znh5n3^*qBK-P`}bFSO{#4A$?JXY#qF$0vz7h67OFL??NKTVFEL$MT9ACJy@Jj=F^t zVmadrJe(|EE+ku3{poAJxQ83d#kn(V)?GnX&ia}q;HwBeakV*X)ehM>;cdEDB`9=ht zgJZ36%Od-|Hc0hq16&jQ#T@oDf{I

    KeKNbmW?^UByiWHsAc=6W7(*XjeP(iDa{fA~v7D`W%jAh7>FE4n8J#=g zglq$Tv*LN`BM0-LHmF>QKe!4qk~DKi57rN!)VUIa9BwK7*9;`>q|9VrxAYBC@zX^Pzd>{9zDMa*%z@ z2K?92@NIv*`FVn|{mgejb%cmuj5vA?I_Tn-Zz1LfLGErkA{wIw?K46H zY)}=4;-5XoA=I3Pp{xF;9nv@J2^T+G$B5C~bINsq8kbw;8)UUcxVSBWh7~ME-7v(S^eP=OUnL*O zFb4iu%{8C@f^jkr?+}^Re?3Rgk69#1}JW?S)~~=W=i2L zw($+NsHzM5XELVl520Ao`C^e*kC^El{T-0eC$Es#WCVs7m)B)J;Ztnt@<|%e<1p^= z8HUTP2QJ`L!*zJuu^=!wU8aaE+-~L#As}e5iUNZN)BQcnwu{A)D7*pE`v16k>#(Y#Zhc!3 zDW#E;lJ1i3+Qg>2Ly(s4MoCHOlM%5G*P7kj{zbsc)FiEaqZA&A<8Te}RkoLt^8WlDadP|5nEH|G|Q zRq1@(?~lwME7${)}CMxV2PdHcE6rnwhs~qU~wOTE$(}DwZbLmtvDYvi|5i7qJfOt6Kli1W5Xjv zkhmnz$lOPQhpnZc*LQK&k-p6yj2RCPpNQoLI9e;2y~H9LDTb)ndiM=!d{teP?KCSB zcT`HFJ4SHc^S)4>Q&_&B%Fu$oH?$$B9G?$1DL7q?xzVpaZBnM-;P)AKDzLdwC{eJm zDHFO0Daj->+r=sw$Fzp1Ia`}R%ASVZR>p08nD*i%dzX5~e!Q|!&PUbJu(T$aSg=%W zr-LFky1$Xx7L z6TtQJp$y1)J@Mye7ctUzOc>xyph)q>I~A{erPN9H{;cj@YkZG$s74NIcsn9b3XJXv zxhmf6=M93$2G zhNiQr2cD%YwS+_XlF_-YkxZ4_GrvVV(^RwLO#o=Ci+bg7?fXvhf_eJbl?WFe<~!`- zy$rM3)E`qs;FIV9!}Ve(Z6DQDMC{mc@-CH$3R zP#L)TV5=cjC%bW4=QGq)C7z%5mn&E1h8cKp9We|aKT0BhJE+>-Rh?l!HN0Jfr|E;_WcO^M>fzI@qXHnlJZm0b{hb`N0s+Pm>1&Rn(a3SclcUDDra39Y z&df4t(ukzduwr4AW1_Nf&hnerqYu+EAxvj(UUj%TNm`v4tVgxstBojTU0AHLxeXoe z`{icIA3u>kGjw#7Hhs@vQ>jcyimoCEMY@3tcOCSM+ zak`5Ecb)=^FkAZwakg7|WkMwEK8UDJXBG8^N zfBjJvPP?c6k_60{kCdFEetyzz!#S6?v!+>s!W=}1j#tPN1M@Fd9`aD{sYhC0hAURU z`z}7UNYqwkPG~qky&rYrRw$?YsC8DTG*DRL>r;DR;xjDk)?O$93HE%7vU1B-6;Wgq zY{0lvtJ~QO4KESI9Id;l>%BskN_e&Cvgez zR7F1>iA#QI5NTK6Q=4PEEG z7`77wiBrOlo@Ps%JGEC2nAmWjpCTb0oDLra5FiSCIiSJOjHPdd2}(L6qq!o9zX|i? zs)vwNbO2!Qn-I`Cdy(6pnQa-qHQQvczEv+ahE7LDM)qPW3cR#E>pq29`(9I;JiBGQ z;2JPkbn7fWIqd)X`brDpD>#_Mf-YH{svMVy<}s69dBYS-udnhwy#UDyDG8jdRs+YR z-Oq7bM6F83+1O%8Ek-A$l3Ii-f6?*bha=kFNu-ZB(&i7#o+;o4cx@nCIY;aPbO~}~ zLTn^Y@D-Afc|?=L#9{(ZC-Q5gxyi$CA-cjTIp{}2K!=rYA$yc%KH)p#YX!XFH+AVI z=dZ?GIit`{4AD<*h3_7dvUjCwwe{mohN}6QoCn4;yzP6H@L=mboWHVf;E+p8$3mi&JHS4^_lJf zOifbrKzwDg8#jidR*2wxp0AL)TI%xB_1y+Le2AgQ=P&S=dJADM!2%Z!vS|r>^_S}y zN(~j4z2YFK27|sT@wDoN#G}C3kOd-Ddr>1`&TOmi5~I`=M@QDz+oIH zqWnx2E8q3>EPK-c*xHVFrZ_cVUjq3*Vc_$%yk2P6s{+7lCany9l3+}1ooq*u$A`_N zdMSvX7v47o{1}O!q$s~A>^R1rSm_vX*())ssJ>R~OQr0*#^xpPKUDVQ;ZFGk9TWlSov@Txf;m=op# zMgHw33}&TGZ2d~;>Hch{-Wspr%dD~G=q{SiaQVqmjLGka&buQB`VGISE#06Cz_uf8 z)IV0m#bKv%*;qL=_}pFIUd4rnhd=r>T20bF0O3~ovWY^?ivy*Vv#FZJZ1H&IK`N@y zz*=lQ*mOwloR#WRK~`OHkcx>EX{p(wSvvSI4w^s{_qZ(}X}Es6(b1xR@v(u3`G(L* zkCKw?zhi?X#q4Gbl~e<> z3jFa$-ZpkSdky`A+qS||?n7%HR$ZYMUYaDFPm%J3*1kHrVmY2UV%nTkb&|aV=zE}i zn9~|enTOx(e)4)0v0#twmW%N7cM(%@=Px+E3lHzMN~G1SW8!0ePT`iSyq+kzR%_%i zFO1HwogDd9&|ST?k$L^F<6Wfym>JbtzZ|VB>rcPm^!3yjsr`0Zj1Z-_6tbtRsXUeI z9cJ5CNhFq;!nAq-3lL-p{5(k88{`N)C&0)q2cG>gE-RE-df`8GH-HBphX+e7e?fty z8fbBxRN*rB33iYOB20@OWxK3weQFgZ1SX}QZX&R?IPc)s)Y~O!@TA_l7h|@d$=z{S zY#9t`e=vH2r&UO--2b|JbRY=;Q3;Q(h^el}-Q6XJ6?U8~Y5zvD_xCrVL^JqheHV0l zC40H-50yZ%Aq5A(VT1M3I=R6y8GrPWDl1v21FE6O z3C)k`+isvR2?&uIEW^Z+wiFT)+F6D`9)ChxT&&Z1og^E#FPSPk%*+iadds8M)w)a^ zCFoq_8-r1m9PTZ6D9cCL`TI8_Uc3-_AtNEGmZ8Dp@p)ehjfuF-b(}Y4A-=7!Qre}T zEBE^m_(f!Rce$dl)Y{29LBZ1GdVd;5)hoDZa`9tW+XsA?r$YCtO)}yW0zwx-P@M;% zws6AVQ7Qbg$g9%AG`u+|EO0!-^kiz3!v1lNV2_!~3}PE3=~*sUK3`(RKh)wOXT3iy zR>;5g=%_w%7G-tNT)G#JAO&os(bQX>3teYD4qtu&#mRR};n7}-uT%+j=SSY8IjoE; ze0gvagsHQ#e=ZlSe|zM7gJUMBm>XOaaQT6&nAy_Ir>sx0eUt<8PT-+xe}-HyyyY9; zCZUeX9bg&zx&A00Y&4#D;=a`w!n(bxj{6~g?KIHEj$9NZx;$@`l`qI|0w(>>fkI`} zYS$G`|MKLYtg9r3tW@;YLIqd21A^FDqOp6r+gAhqSW`7r#Su`>{;~0t2uLxIfH7TZ~ zq~xpcskVBz^qIx)%@i@%L)V?A7!K6{6wglQRRBj-l+>@8%+NHf{rLS+Hweq%EkSHd zda%-9U@IE<+~uV5`;W$DalOB`sMJ%X zs#$+OUGlZ(@SRa@>d)M+OKgaAQ?4s<;PX1e+2?KLJfNZ~%6Dsj1SUBz3l8q#Xw*Mj z^!by37w%;y$rqyzT}b>h-Asw;W=jTtmW(DfVO+ALUe~KBXQ_V7uRFaq4qR%9>Pr>= zDM_{&6kXJ>5SCYF&39?LzS=%99lhS6?Y7b>865z(%S@FgG`8mP6YB~nfIU0@+t7(M7^ZTq*Aq)zD?y$rW{S} zaD&%oOdShNM3K4tl}1M`;WH3AEvYC+9ZffybMG89&tm61pbsT*Cm+@Z$nphuF|JZy z$BtuUr7~{^72SOUCsB|!X%`?1%{1J%`Kp)eqLe2X&1Jc~R;x5bP*AXhoL3o$wqc2l z61RTMov$=%#gYtwi;SP#?=G7Bk40zT!lA4(;UOLS#~tJLpBgVv00L?iz>@5{Dvm|( zx}Xz-AtajJ!KkY{$@k`O?#d0?`FbyS1+yxfIj1P{Tn-ns?KYqN=XX@kC(+p0*i5?@ zHxQ@fLG%jY?JsDt>gtvm)OT;9FuJ1Hd-WR@o|C1=1*h%=eJ2~6OqRon zkf{Qd0rgI5wiUd~;Z%%CsGy6^n>0x&a2zlL3f-}v7#W9(J?;?;*+Dh2FN@~!%6&=derXaD=J&aA~1Sw^at zQiwD>#hR|e1N0!~-fn_diL1l!x~mnCefJbtr05#i-HQg|lK-CR3R32ZK=xYkSsre9 zG+sXzkc0ikx~#gxhA|1SGY;z@#3_aIcbp2sktYw4B)v2Bl~!y}9GM4o;=g26UA|R^ zF7Cx@^>QJ1zKtxZ5t%=Q=$Nbc+?#e;nPOXbTC6&uu=u56Mi+ZnM-*CAvh%WHa+60{ zSg?Kwshdx}ZzXln@?ca`;bXUZ>sepXbAijw`|onXk$v|54MnTtQa!6?q7xC{PEKFD zbo#mCFdscA`Z&W#v&e-NjdR1O+X*n=Oz~}mOUBg2LK?A51y-|RDJ`BnAE@a`lw+#F zFIv9Knv;vfK<5P7_5Wr=AYpRls0DEE7`y>9;^jbh{hQ?!td*C&LuSMbmJacBg&vto z8+vltPBmctLKWSQU8%*DfUB)Ur}MYrP!7d91_5p^c^kzf=W6KpC`HlmEAk1G#$A2X z5&EF|WPrthKWsZw@P2x@YIeLxGW30ExJ!^*nmiSI2_WO^L3jqzmAX-LJO6uVSy9D} z0HDT2_?@>Fh3Mo!-f%iE_A5j*iKC_V3$|rKPOHGS&^(Xggf9-{;l#v4#l6atFYDjxaQz0{**Pq{sODQ&;Mqs!zOKHbw#way_tC&b5~D&1_P@Q!S&geeXd zMK}KTC(_-9w8;Dg@cMPBWVz}`@c9U#{)uQb9govPU`0?WABd%LSb@|j3OdiX`F>?T zjo``Gq{OhJ#yWp-Xr;X(bMG*FY#I}bAOe>EYGvz-z((AnLtf@TrJ)Egh13=KeOrSJ zBLjvng=jj=7hF;dujY7PqNF@z*oEFnLc2RqJ7<7#NYp_~m<3g!$_sg>Zv~A+Zd6%Q zcIqVMUcgHkqkQchh8`!fb|YzQ#IlO8vqC+lA2`qV&a?MsTF%=Py5Bo8W0lR`9gV%e zO_=0k(6|yaNf>NA5S*kmbH2_i4;jSgVJRMrqn_yo=x$gBFTe(7&Db z#j5W=srrfRpNLG(-m@?B*nPjxivY^ms6x5#e=5La7v5WzJc-IFTxwR!Y0C5si?&k%{O<^YS19?<%mPw=N=|HCZ~Tj(;rUk0F_w>$ z=1N99%*8GF(+OIHJ|8ut8rUK$S_|I{vJa|(y4B0~;>ypndvVejWsyG1`0thvr?yRh z%eB8eMkS^rbbKs`S~7zN7~A}ao(ipDRgnGNFNgzyOQy$X-VSFBxJ$V#KuflmC_rtn znKx7A4hRTfipg6TEgh4()mhm89}i1S$MxD9wIZd?njQc&HyTQ0N~O4xPXhq=$2->O zyNkV$4xc-iDnPa4^E&1&`2n62=ki_v+%y``lbLC>BZYhQO1VOhx6W!3g{A#mC0L-m zZ!Ey{n9Frv3mzW+vwee6TA_Se!4r_f@h4X@vPO>N-Mb)c8ig!?F-zo{t@eZSC{MTt zINtTIh$MfDo%Qwgl`9Pd1A~KML5&t;miipJbatHY5A|nD-a?{%W%?JKRC`J1E z2jJ>c35a?B@jM`H)6jE;0JF!{8_SUAU z@bkes>reP7xz!$*#Gf1FO_~Vt^NPh#oqC-&#d3?Xxf_E0k7AkpKM8zWtvfae&ISN+H5pgMu&3-_I);eU9fW%xnQ_`X4! ztK2fFQ*n#p?LP{c(CPl_=aUP_J$U;y9(nGKnL-NadDs2x%>LsQ{)#1ObiC48q*a}> zSdrQ>*4KvrG3NM!o;TBGB(Et_|@>cR4UL*BXMKWuu1!(+GDlAkL?z}8|mOehXNCg@*P#ai`y4KNQ>sGilN z>`DNL?K!7_nHmcP-3`^sdbDs2K?)MsMaOW+RkHXu32*SuyV8}51?i?sSkTU_lgDp&s+%0N zw|9iW0~LeDx$#6Mw}6Bp;)yLhz3Q;lOq7fj74DS1LiZyF)|qpSc9lVzVQM&hkf%p4B7WCD<4K%aH?>;84Bh+wq|XxGTXK;-;Skt? z6_uZ?^dIlpOQOXX_gW3wJ=HUFr3dkHkpa69S;HQ&E);AQcKC32wO-!1G~U(HY$QZ< zyjKZXYu?Tf2Mm(4>7_QJlVGk~mI-Vys%boysR~CCIIUDorfI6;b>>SS%x1H4aAj-X zesI$pJdE4L=;NQ)$lk$dk~{5Exl)D-eiYK#$$=<>o)j%H8O|tJD{B4vEcI+ z!B&Zsuq$kIDGslZdmd{+7iRDbAiY-Dl zfd7A%2Ng-_NAB^)j)m~k+iVs<_kYKo%L)ck%3gy$u0E*b zjiocQj1V$?P%c=@Pa&tXW=*AYAVB5=E@CEfcJlLwD|L8nv2TZV;oK;BK)&J>pk8PK1$N8k|MvK*)Yp{fc1#;uiAjIth~(HOOLWX8Fzu;N`71yn8Bt^k-3H`D7hyv~ zLk^oc9Du%_?kkf8$HYwK$zT)H_4$P1;@}e~gO%gO#=_n)9FIwn?jp(EvH=L^5KkL* zqoiNi-fwo;1yGYfIi<9{I2cS8)d(kEI1v>{EI5*)kXeqT%eviF(1 zhWOA8IatJsO4r98Y%W)^TRc8h@)WCjL`a@@_>)pvvNSb28**Q8OBtEn`1|W@PaK<6 zG{vDe1{OK_%^UH;1(%h?q~f!M=vhCEY;1^R@wQaXLiDor3CDeDC1xqS`NyD&3SMw! z>=y72#12?C^@h+x5Q>4KbKNQTfZ!~S51gCZ?u&;Xqv@}cAr{ErCdHW*(cgcmg&ulP z)gAhM^&l3j_&;78OH$vT-R#~!IT#6%B}VFIO9;| zT*+OXoWsLIz98F7As<%T1s;7v&i2tszI;d68BwYEXhyQIpAcYG%-xeay<$HiFAt}>zRe-PmcPob5?7Wt6BuhD!{ciY zds&aW3Bxv*rYj+vK;Z8LX|92v_M1HM;OJjV>xI=1e;vF}Cvk|IIkiDax9SydpZs-r zTsNw)qPk8-gD-T*HjVDC4ySDR8|@}dKAx)m!;LE{Dz1S-8t`{y!5N}#Z!DZ=^APqb zpu}+$%mBi6$tI`A>1SE6)|4^luw14!Nt&gr$Cvg|6IH;1ZYd)2BF$NUI@9lw0B|CC z+7^`OD@S}8%L!T<-P47Q4z;+#@C?1bx*p7RM$mFjY@3-YOmX@gcOtr28QBgTm3fLp ztT9C9xJjB79GF5Ykn#zXYmQujS(~@eRIwFZkd>&~o0#7nDj(|I!KSfud34EEKV$oD z6BvyN4{(?xPi854lfN_&3M+$xrONd~s-lIZ&M$$pl^JpK%k5IlQXz|g<=Ys63L&oh z37XAvEv%;8NgMpTk@vLaR#S(M~tI3dU9=*b18IO~6vRd4dILt=h= z20lV2JG)4sE8(IEJT}xtbf@k0p59XTtfy`;WxFi)OuNZ@+W4h5g71ikZ=p82Torwt zukOzA$v1G;NpZAwqL9BlnCEtE^-e#9vzrd>KJ7E;{=vb%JH4WbpO)V3} zG%k5X`LB*$fynN#;zGts^GqMyh^9OygEp<{^9^4!a!VMkk}Bn<3sEL+`Qst0{fY>2 z!r*NdAP`R<06lGHzHOmx)oR)<-UL9M%xe}RqNyl9)G^Svd$>l%)Rf740-pB;ucLMy z*3Cp~W9L*}MbGsVPNkHNWKLC0N?B8C~8l~6+6oV>q?5t zsP)3Y#KxCi|EaXHBMfLbwYj&gG9gzM#lnq1OGE<5bJ0SLp9iqqJw1&9oc`8*qCj3} z1BxhPx5YDMLljpp1surbAcK!x9zZBaQ5pHY`AGWr)6iGRW*0k8Md?>! zNA0o+Q8?9~SVE1j{6?3CRI3F}ijU|nnd#|WF{-m@<$s`!d!fR^$w(iqLbgYI(}rtW zWyp}}uDTBYc>9d(d(Zwn`{|`W8Djkj^sbG%ErNNyb2$!1sj5>U=O9XQr9Ll*s_xJnyE`KbuQ?*zML| z>BDci=N>ajUD8i7x0QrGij8XSBYPqI6MP};8Et%$dP~ylea#F~^vN{txR1DRah7~n zN^JdkiYk`t3+imJNo3>%6b27x-+FgMLsBq8Jns~-tLC|uf@d+A>f*Ata6$R*B}fEQ z2_EMqC~FPlZ*tzdko<-`AQ`IA|5qS7my8Y7SA~~7C?zOvnN{_rO zzE477MR+owRxzR3PIz-ImK5)*yO<~`<2^4A#REd3vsemz{+vidV!05C=H%^*nC-TN zyiQK1CQYSX%!n@DbL5FcCDi#X8-Nw~z{+E6HNEXYK}bqrI+Vp1WRD>!%VQ%4^E=bZr#G#?wZ$P=RiNfD(Z^_2q=q&SXVbK6OdT`ee}8s3po z={aG)v)6O2B)6S-pGUnG^6ks!Bab9M<3T_)t>1*1a{eF|u0h)iDyx2XG}JEBznMK$ z?;4<~g>PeYdn}6rywrn(*uMx5-{?0KSw~J2Hu5H_#G3Q7)Y>uF&Gl)(oLmfaE0YL?dK3X8(o$Q0Y60OZ&jT|spe<4B^5jyEqQx_rQzMqhqDV*?-e%Q zMDO^zi?0*@eK^UJkp1|X#;Z}6(>pRWCO;CpX4zYDL#n$*7~b66pMl|gJOj_4w#hsh zzaxl#r%aCiIbu_!aJrJqt8392QtA5ExAZgHu#mA7Ozo*HFmcxZo>)G#>aqy(RBIO zuU|t8Ro`oezL2&b?O|{B_j(f?gs8-WO2i)T`?%^Qv*%}4JtvEGUamp@!uY(nFeBEf z+ZB)#)bRRMe&7J6`*w^}C4?@fLm=5gPDvoKFAD(}3Y7`|{;W?XH58y|nh><7z3$sk zb8A0o3%Y!hlMguhD_R`VaIvt&4(1!((kcl!5ntv~2G7kEm6ckZhbG*whLC0mX|9mf zB-=OxJ*ON#iq=LVNSdTjnwrhYZ9gYou~7AsXD~>O-N?i1N_jkfx_DrlR2iQ^UUkWP znp{7HmWmo2{XPe3_2ZPwCOe%_qvNlwOw`!bcG<+T| z5-|tIMYKPA1Zrt$ROqT(m(Dp>Qa`ya5b$MPhDVQW{iMFWkN%;Ckon00quDCVpFHb% z#h$;!W4@%48A6K3E#*gA5v2?r2TJxBtbCX4mc0~QiWiP&k5Z(CPYS-lw{CmV+mWrw zz$nw=QciYt^~kRL`f#)wyRgHWj#Fgg7XliAtsv2AA@;-Mq%B)fOCb^~TpYEqV$x49 zF2T!Rg69W#nW~jSKD*J&TS%fPSd;jAha+ZGbYJ>e_m&9%=<>_(Daoh=sM{>@VdY%g z8XLHLXAF9;*)~2tZZVbvsRF`45-(v z#gMfo3&Iad2ptX-F^k;cvk-p6MFMQwUy5jfHD!=KW_vDVJHtz1K{bF#Kp(+)Ae=KV zN}@O-c$skx6H1|_VjW(MaB)ZlZ`nEanVQyPL+tZu@YyMk(C5ojbsXsS+ciC9_%QlO z$#}3-!6(aj-EB`Y%b;zJqaE}E#|&C*d14%~4EYmS87(pY+$~y=CxYf|pH^FTLND4W zt8?4CBQFmM5c_9_3!BZnHoWO=lVXbZx<2!d$UZrYI_y5lx3L=Fry`0;n+xLG8K3$2 z7a=+jpEOPqFVbEg?FYV#59=bW_}ou!huyAUem2I(vhTo~uw852Xw0XB(tZojt?%6L zeXL3FJ~!U$=0hc#H&ECP`gSQ$QTrI-wefMx)w3NHI-E3xri3V%?17SahR20fa5^0Tu zwO&m`bN!-FPCj z-{05PS=~75)LOj;NrEPt*O_zfNIc|>BXbOd4g6ok*2%YCLjU5*V$j=|w7lpfQ@(4A zlAs%z!pNVQ%J0=9<6j+t_GFYjXc~+v0slM&Zn*3BQP;fyH4?|u$e^k7iI(0|57@OM z{6|Wa2JQ4MyN-?K4$)NKRX*j*$zJ=KAjwh}{ZjUPjiB_&ED>aU$|kMw!}Xdv@Lntm zdv}^v;WCE2MDa%|*K{lGE9195>p>a^5_IhOFA8THnwZ71=Ne`j8+Y?)+gImG|9X~l z`BjZZ=u}#Xp^ywzuh=dWeF|D3QM2$VL^G}LV33uU&)t5!B0N~A59xC6gornAw<&h82K8i-lk3J zy#5A1u0$KaB?t1i4a${80X5grOf*Sk;Cy_P;z=`jEHvT*b3^waG#ZlZ+iJG+8F4pN zy>5j1oA`?boUeReMLZJhHkr^d6bquJoSsfp1@ujv_#EiunGQUAT=)|p`6YD!Ip}EN z4l$9UL&?m)L47QTiy7gMsiYt~YPc=x%glOv&E(RhzLayh6mXoYLa$`PLb{Z=i~sv4 z)r8=vHBP6>b$93OAo)Ns<3Nk@LKD8fb0GyX|?q5vtfj1Jx8y`gnNnXJ8eW-4rrBaq3{b(x!TE_inbKqhWojGo} zEG0T3*ZV`-Ev`?8T74Nk9Qx5Mv4Z~6z$iLt!8UzSHY6-!FYD(bel;BM?;0vTN+FF; zmFxW7pdnTMR>x&%FUbABC1ot85|t&@tFmE+|M}1V_smxhdZC9omC%&09#njp`+Nz! zz<80Zjcr1ebO9sC!Ss^~OSagUFqMpT`MDw)lv0NTj>q1lwwu^~6zvpjRX#80Rx;X- zim&xx@IXXRv9Bb-S!m>@YQJGNtu$Z|r;Xwq43T_DF(mW&q-h}8X&$}yxnG3o3=NH% z^s>o8fMpq$*%!p+j-&DNgR}=Db#L8(l{$S**pAnMxKVEjC&%W+6>W6FP`jsdzLzZ| z0dDr7_W$?T)56h&`^VLSkcLM1grC zYPnYRH^ANu0^rkU?6l<;R{CuEr*V;-ot>=%RBzZ^{hrL^G&oGeMKnv}HyEM1AD)6% zT0GvfQ;>O<=m)_!tc|>38(UFNKB|seTu}PQRPvGcvY6{5x$CcA$2a;QQa!q%m_9ZK zzKX*IdtGYf22Xufsa-k#1oqx=IK4EFee;DTRH3=QA>f<$;CzGrjCRCO4@*!wl#s55 zRT8^2J{+l9K)K=$nt4ik+y9olAZtxCA*Q} z%4+iaZ3}Q`?$iu1=K@oi>bkYmO5nLskMREyCC6H0G(k?{A?2=|yRdPx(iFPfCJO#W zYii8-)Q<^Hy?mW7_mtW{Jx$J=g8$ADs8NiOV+P)*5kklP9CbZB;@R%-J}4_=`UFt- z;!2e7=c4KgZa6YrMq=cpZ->#=qwIRwh>=CB_bNFX`kvf4pENmHPZaMq|F?E|E;=nG zel<`Yb3}=>YQUEezyahsmB42;)#qb=Lii@MVuRFgRKasUFgvfXaEuf(TwTadI{$I7 z`fOH;6zr$VWy2^{9>x^bA-nffp=%fKz?b3I8zrV&dVu79`m1CtDx491hBwj>stR>3n*y7FQ2{5vtU7{A=b%ZZIn+c%p!2XDov^;==E@BjPZPQH4n9;*?(mz7&2iL}z0lS*#ig83`@Ui0Zr z;9#*f4H!p@a6g@)l`Lm{eao=Bqf9sk!TZVUP8*5O8Jr%i=(OTj$x!etTKCT;g0dr`=0ogro}OJiy4S!un>K zL9#cWy|j+fwZ)@WjopA~TlP6X3|3UjHQND@pgiA)TZj8b5rr97r$*|DSAERjlen7(7F48CqYrGd=lH3KNN{z=-SeSGhBFsk8Wf0MbvVi%?1fY); z1ALLnrSQ`YdiWU1?zcD0RjoTdk2l-M4?kU|w4o3`Kf0?q>q=!Eoz!z)V2%b>P@rZ+ z`Q`rCPERlWNBd%{TOknd_18R8dzq@D?G)_rAwp6`MJ4dG#DO$Co#Ks+&kn zFLD?8-IRuau@-ozf_#PGrCa6)TAH820V926SdF$z5<`iMiKAbDImzFwPu&N^725S; zr)$?41Oi{+L7={wv#M7YXNi&Ti?c4Jr|Q|4qrCxm4`sUk(lOBJ+Tx&fJm%Svdul;Rx3uJYVLa`|y#RKziZ zg;ltzMmVtotA?C~JL%TNMsX-bpjYoDoOt;bg;D=_svq^_)7uEo#@lG<0}|ljQ5@L+ zoBe9hz8jSa<#K;EZf$*?j*Dw*3uR)^ckMgP4gf7MDC73*%+*^{0ahc!g1kJ+0YsUw zX&|Xn{;2INidIcDknj~UACRX($YFu;aNLcqatXwn!jn4#Gr)7;TUq7(0LotXXOdRt zSu)zJz8m=<9*m@IJ=b#y#6M;k`fWDX=yInDc(9KaYm1fT2;+y&^^thV>G2wP9n{Ab z*oBGci-}x=#5KOeyDV0i0(RVsrZV)^$}ha z{)#%FI}MYi-Kv=1IW|#b^GH?uMqAIig&?=Q@byQ!xA%f%Jj(XKT?3}Q_FIu)<9gF~ zKRgD2`m^=6;(xl}|DJj7k|LSj9>1lW{!Qn2n2_+vw|l3TviR+qV^&bEF1<*9kUpv! z)RBmN`*E|p;ioFr^^zc$7h!QWlW1*Sz4>L|zCfh8~HRSq*f888}R6`mx>*^IYBIbb-@SC zdxe0m{7ns`_`y!n6vYw!u6~k)q_&g%;<}U1cz5+>>wO&HCeG8IRRP1urokUw!iKzt z0z8Eh$qjH|7wD&#rv*tBpI;|1p~6(WzO=I!I*4-BnYtYPZJcD4_wJ)t9h-g&lg00s z$c>QS=+&lPJjWv<`jY1!(+6pXdmwci#nN+#sZq(yjN0enVp^=jz;S>IOZQ!0FlDdM7yOV# zRg;CVas`#y$c*Ml+l?!9Qs;z4vA|)qVo}Ni`lHf;exqeUN#>#CD)uxF5vX z%%I#ReFRyk1T=)||8IdUK#}E3FR-55{I8Qx z+1u>s%6Wy4lcZz_IX)_s;0*)4CiT9|$9OpNcr5g37bJn1(p9(k^OnrmbE%ICTZW)T$DzU4ZA4+$I( zUPRpK)m)CxgFNavcX@vrHu*rM;+TaFf{5A5ZfUfj{kQy`k&ynB1-equx*VQE7t zdm^{^C_=0_v-y=z4XnXe^fJ!uz>!EmmzZHx10pf!Kp%^E(%_m$K3IJpc9m~4Yiy{) zAVoM>`{Rwxz+YCBd12jDoT5PP9?BqI`hn?kU8y7kPs{H~$|-=?Q$=sG!Vr>i*nVYj z8Zad9+W02mjqVI!hEQa?_ko{kv@>iu>bTnhtP&{t4c1Jojbq(XmcMy9(+C@sC9vLr z$Vo}VyzW0(o!qEdoef>#xQ^ft8ni~$B$j9OTlcB1PaAGc%R+bRlW5T@HJ_wVPdqmv zP-^H6P(c-k5N^HsV>h^zN5mO7+gSrxv72F{+!YT>)aC?>23=WFU$ z|0P+@b-rC%MRYh%zj#tN_1HwxkLB6G4U*vRf0La>i80n_=*>)NB5gs7yxL!)FjI%V zOj9JZ2a+6c!05aEZp7U<()Vd~6&j}hx8wYIsMYKE0n_K^E7g~eeaHcwoXk+`QAH2^ zDB@)ie?So}u;EX=8&ZrsX5-of z?OuIo&F0x}gRJ%1R-y}?1M2VIWT2HmTuD6&C!{Q+!Ej#l6NEe7a ziuRK)yj3rLvF=mAMyJnVj)&jhPt=HxHMN%L{~uXb85QT2Ed#;b-L>&RAh^4`ySuvw zm*DQw&^RQxLm;@jyF0;y=5_AKotgIs7OPn-`s;JH)UK+H2|KXGL-=3{8+QyFQt5E` z*puSE5x{*)tKsZbi*X0wf0)B`J9Wn_Q_pUiYt{8U1p4TWqJ1a^G?B?%1v6=V`y7r! zEEC>Paa9@%d-C!ChdOPHM(KT10}9=FUkJ%Ho<{5Z$Y_|XLChT7K=Ecd%eO49P#`uP z%pWw$>Q7IvjHJDu1wA~4Cp7tM;01oR_hVH=xYCb&n2OXg}O^y z(pr=G$8Gbo#e9n|i2ANbp4o90ahrjs?mpLvu+~JF5u0OqqNtm$u;&fvJmI(8L#QOi zN~sPj-|{44NK73O_K?QVDi4g|XV4U}4t7mnAK{P@>xp$yZLU5vD;EtAk=hCBkJ>0D zRO6N!P&){L$hyNML$mb}exe!y2PuM-aSxXA>ELjSBHzQmm)-;GPrANRl~ah!yA@{cAiC zaJK`ACY39cqC=;Li@L+K^A7c}pDOmZN1?E&kqd8sD7{WSLSf*`z6Xtr@;G;F@|cph zEv1Y%5g8MsXhfX$E zDGL;X9@nn#=laa<8?7^Qr2LPA+^EO$S?q~+Eo;!yPiOU$DmFIs`9cUM+5qrd8eye?Q@N{_I7>8me7Ff50*o;ch|vvJd)2`iMufw?064bo`CBBh71#M17YdRTriy_ zL!NVlyY5Wsa`{J{iuQ;JL=1iz^kLdVu-so?-#RzUlUo zgh=~$(bTU?o?unt#|?QiLp?<)F9wJ$Xrcd0*H6W|$F3dy z8Me7hL;tt(GTB65Z zm*;%h6g2OKJDhFjWin{_?BOamkXDrkuEN&HmC_~?ZY-xBe@X2aYx7NIsV@kQ8JWM= z{UnHGw*aIThHrCz-Nazv+}?u7;=kebx$jmjgKhWW)BcD`ryST<#(KN*+kk8go!|GN zBwd@11P`x|*_)}Keq^hsupSx;N+v0jl$lhpr!|u3_vu?ZsB8biH|^}9u6#gGW2#T* z%ex`^n+kQ>DA;2h&0*b*ZPPVYa|)Txj1?@L6b326izt-_>l7ePA;Laga(2^%J~~M1 zhiHJU)=W^Ukz)OYqjCxXP)!ShSqa_)jC0AxD7O^+IaJ4B{#cq;zLW9uIOpaRco~2u zP}9#6Mi?OpvqBCn?T<-d%CP0b_P|yYWVkoxjjpB!x)5dRzq|ml@lWR~(_2P!Ic(Ez z*UMFDqvPVz>=KHV%4uBfM3=NJbEy$j&$8M3*g1+TmDAz~IAm5vjCx^CG&6R+gpziA ze}h#V`(b{4;^2pTLJ#SuTh|Jnx*i@(J9kYI@;h;2FXM2T2#o18hVSG*q3+u)!;r3H z33y!oGMBWHEucc8<~O3#z}MWA~4@Xa&cA7 zUha7xzj4NFb#;bmiTjr42Pi;CNditTR_PK>m&>%T5q2&=@udOz`XUA!>>^b*4ox$6 z`>CDuJCl4i{t4Uv^V`0#ko3%T0m%HX{tsw?BZ_SWfGUGq!3{OtNXrXyO??M-~? z4Dka$K&I}_c5w%OJ*;kwC~yLm^5FGTgmlVqC|4x^#1T{UH1g?!%H+qVsoBZj z=`@NyId3<&ROB9Suf4K!#dI2Bx)y3U2TRVUa=jU*_2Swwj+YSVXu-a-_%-E+D)D~NqJxWD_AE@;%;@8wCB zWyUgQ)KpPWcdh1v4iW_}rHsn?MC+3C|&)&aq+RuyTGrBiCi0$QXd8_PMPKDZ*)mLK+luymqn-> z0isXV(-j=a-&jUpx0b@`99JqQuX>0MVJ(y9=E8U#&z^t2R~RGoTzf8HTyU~ed^p8{ zB$HXk$4Q=V2s85vWrm^_oNJyd>evkeG`73%nRwg*@E}g3Iz$PIpiG2Et~MDr+||Me z#LHVo?}Soh2(2XAzHx+moLet+D1-5bI))cw8Tpo`Ft(emo)8R%IdxjFJUGRyr7z5w z6$ftYox$y&iMI56y3DjRiESc;?|snz;C)(0HOU}|MTx6>-3?Oa)6u3swDpE)Z5Bwx4osH6gw%&g{D5b@$GsrQu$Y+hm2 za5U$mpQ3Tah)#D9+nbI=Y`$0MwYGLvmleEu^;GSF(n{GP z$F97%!#$Pj!ThMojf{4OqF_o%y1P+Fx_Ev6Lx&|f@p2;4%)u1y2hYgMUxr|F`e$fGMM`HSaqbQMh%xDV=ldNDXH}~}iY^vC-Fd@CAs2Wv8975Op`T2}u z^PcfPxJZnar}fJeGd&M@o2#U@wR-$KMwi?tjqt+XSQ@ISha}D%f@%XxHZIxN-ud`M z^t+BA>t{E|@lEU&)i7@gHN^)l`QHz*dJIs-fW%+ht<8LL&2G|Mq~6Q3PDm*49_vh3 zDdcT<_OR1k882gDlo01Z$O)xsr|UcHBoV6IA8`iTsAs2Y<`-f~wMOKm!A)CB=^tHT zzdX}i+BcY&&TS-(p+AB!cJL}7->;~AKi~O-#Y|9*)_Cn?gRE*%z2D^ zp_E;po}a-Ss>)EL&ifVH{t(0iPPB3|{b?4Hc*1rtNG^^Y9i}Hu^nm8=1bPOqnt-Fr z2LE-TzFxG^+uv`OzmLGYnbfWV8DciGNPr2tE7sk1H0~$+6?T!|A7bzweEzCjzvtg& znKV(bRNa2pIH0w)Jr5Dt=Tk`c9`i|dS-LaUoIkgaqx~)6oHAx+`2_6W8y~p_80g{n zSP1gm?l7cWFXQTMWS{7d}XslB%Xt`O%HrpC&5g#Z5B7 zgi$L)DKq?ye|@#)$%8|%g}&5U%qKtfT=ks`78IVrH}DdKE3&o{dbaRP;UjzN)^q&e;HcfWSy(*X#aUErBsO+p61bnP;D zY;1fSJG~jK(M@ZkroZlyW$C118N3B+w9yPE!nI12dck0Dq^%)9%-FH>F3nZBJzL+b zta2T5YdW+o{M}^ofI3s8%2@U<6!II|61F#5SMptxv=Ng}>^HW?J}~ygp3oc}uNnxX z0->ozmbD#kT>GSQ1;3t2K@FRS^|uW|D^iaywpI7v;N&kTk2~W-b5h@g+<4;q$J*CO z2OvT&<_X|doYhi*3F;CEC)s~kOYlLQhl8p<3gr4e68<*Cx32!T-!$dn9J(kaq zuL?1R7j^GT`^vvy0$BMc=^B51^G#ObY5#&PP{4I}d){BVH)A=9GW@u~+5!Jna=P{U zRv-CX`~Ocv{&OtN4XG5Dg&V2Tve}RLOs15mxjuGf+(xO4(5psL5`wg~Ue^z3BpGwX zMnJj;E|t8geGMXo4tx4GYU5Be^~HrP8gM6tPE86Z=s4Tmg52v7h*UY!ayBD{B_-eN zW8ye_pLf-+@FRh{9-$gMgaX}%ixYuCmpd(#E1A`1$3PMMmLvPcnsxZ&(xi9l70y?u zYZH6J$^Fzn9lKv?w83^N^yrlHtsNu`knRu=nJBr#(qplFn?#Z^ig|4@&(4F;6l|o+ zC)noZs=a}Rg+-+P5&_7BQ_ddE+1-wU#F9j+a=vS6^DrxQ=($KWw?Pm;p(2-&&FrOt zbKU|C${>+AU>+h6Yxoc6Oo;}niFg7?%{yCc=Viq9YO1x%HqW3~D3T|+Kx&l!RG3B^ zLoBC6GCsuOiuo9(@%m)^=5XqD@`KxP``+~SqBrDrO}x6yI%VIu*ZzkC8wzNDI>?mP zM}#-cyp;eI`KPlgxO|!1%%WI)*ZFlhU)Yizm6!khw81~BB?~x!w9YtlB#I>X6Z=o{ zKp8w79uP~nh`zdMHx2feSB$|+fj-I9ZXrL(T!Za}OX8nB&-N}|qST(nAY86D(2a?r_%g=f#FIEb+YKAwcMbA=-K zS3)H%qkaK46yx5XkFq+dTGSX3c~4Fgvdpl;rx&ibLrVv=X7-9>z+u7j)LYj!4N`1t zh~%-r3n@@ZB7|E))cPhT?V?puLmN0k`jN2kY_eZC%G@rulYv6SHAOPnP!eM}SPrL# z0Lk}TdyP^xV+po?IMfn;u^j}Ei9OZNTMxwxb6nr;waR*&_ZTD2rB{yb(dlC-3KFR5 zS(Q?~P?QIskCD)KYg5eYsWMEakk)F003Xk3VepP?gJwM6S4T~16P|)mpT*`CH)RxG%saKv3+OY=x1TJ-`LCKp&H9c z8Rd5f{KoMf%ryJp%e@=u$deB_lWqtWx=1E%`(0PU-{UvrMA;d|c!W1pOID9;k>I~t z{`AgSWXf8d1iJgZIbokkXX>sz+Pp=8@VZm3XMOnQZ!{uGS0irupUh*slh^@h1edO>~n_Bf6-O} zko%(FF@y2|O+RN2KVA9Sx<5i>{#00v6+$y~)xDnr@3oH>JqCGx4z&SMpcx z;fobQLdxtng;u_g%;Z*H0|XF2Kt<_?7p0A7y&QZ~5kQfb&?DVO&Ri7MW6r`rQF4?+JGq0dlU6Ee*-FO=3jxfQ zt{3qdRt5^;`_EII0Y*g0ScxRh@0_il9DAa*cTaFHTvRjG_y)wrzht)zy*|B~ zBCZ+vKk`VzJi|*zw~>X-)1w4cI9captT^!?q{&WBU+luS*rj6F@+=F(2EZA5QyL&aN{dyaW_B=m_?ijM8|V=ie)JEhEPCK)lSK)B8wQsH`_<@6J@ zwVRV9$D)R`G^FRpx%jam>1g z*#fc?S7A5~t9?n?uHoViM(ZjsBE8YXzDANKSx*m<`C|C8r$1$Hv``qC5hba2aLhP*}k5^!xbhM5F&h;vD$ zOP7r&H)$JD&}rG3oELb1TCJt}t040dF?wslYUL0DD;1*`M}(Kt>L7PB?7^wZ*mn(`FNfh*&9 z39~|hzND)dzi;aMtCs9lXkmoLW&1TvQ<{3T_xJtW&qwv<5wa~EVX|wIlhz8kJ0fsm z3zQ!{W-z*N$fV=Ho!n%rO9GlJerMX*A@|GLU-KT7UKQJ%Cg#`<3INfQQ$jnIM; zgP&4#7wmUjM7<&M!EmLr3r(QrBsY%zCVR}QhlkjdYgwRj1mqk@(^hSJOk4ai9Ji>- z|0ft0BG+$ySM|-iE(mwOL)tk_jk@Kp9H=cTY`15=o>NfY-+r?-qXapRck6j;U0glt z#!v4n*gRJdB;UK9fU6gu6FeKle|95(f7^lx0;}<**HnrC;z+8+>e$-!lViD&D&8dy zBqi-+G9f?wk18UvmE0%;vv|(|_S$LWtP<6dmj!xPAMyeFPF)H40T3O)jMTzN<&|%# z-7OnTCKSSOkZL>qEAXo~fJY$+;*C_r_bjMIM_`PTLE5vYOTh*dH4!}_ryK1vl(QxL;AP9X6sy)}R+ z9eff*k>sh@4@`Y*DRF`z9v?X?Jo9K36+NwYj)cFZ z_WK=sh9}*UHUX19k#~}PO}2A|ABuqG;3?@Do#adK2(I_Wbd6PccW*fCkI4ys*pF>L zL!28kv!)GK%wH}wHu8F|mgFq&yf_((W(G_SmE7NqncmS{G@qX>5Y{vc7sOo_gkt%!xY|&Ij0OXyFB26~ol@dunr~jtI zN;94Ajy3)OrWoY!CLEyKw&of0c^Hk`U2%AWy*WGizENfW*&-> zVR|4kVjY0GFkwKDQ2uZoU%1{thH3ted0^nTIUL)@2&C+)Iyi;%M178;1qW&bG(OKX z4vBI(l~1=L;wdh~nGEI_Y43Fx!7ahyBU*h?HS%(89JTW19}*lBq!T*4sNcwGi9?#d zEi)P9_InTo9KqV8QjCaqYml^HAPtPrncfnm#P!QBybDis2Dd<$nq4(AstES8?eWRM z_OUzUD66b5#wHjr9INFC#a8bRf6-O)YO*;m1+On(7zdlXl}g3Sj50;A9jnqrK>Bqa zZ79ntl4*a&M7k0st|lC`g%*Zh1kscLhKM8)?+-7l7HrU!(6OrsuI{P|1awip&)owfBQ_<-O3f`_OjF@oN z#Ci$8tHFeGX=x7+Mu%b$NC(8@HBC5pjH+zr96txr487ui2PfnJ-27zPNk?Ji%$$e0 z2vZWQCGQS2$o!9hVsu;FeA^t%xpgvhV_9xwQwRLnA-78pL=((>b+yjZsuGVKoG-XE z<177UW_gSz-}xHCap7KiLa&Q&bfaYmM(l4xT!o2to;niVUqs?wd*$IT%HU{xCVPE| z_YJi|-f*zviEzR0!&40_OPg0q2gu#MtImIuOvNhE&0HW!2edIxH0B<&Mrfcl}5s z8zEq(2l#TL7jmjCsgZ3_i1?KHsO@nJp3()tsnKcze|pqL&c~BINYp-!P7A4gX3w0I zVZcPiq!;TW3q#Z)CA@-AL5BhUyz_<)b>jZ2+mH}ouQmVcOwl*<%$f9I>Q+1N>5b1o za`gwZgRxRhZbi@j2a|-}{&V3t5W>e3Sxx&NvFDZDgP6W+#j6pYN@x;;Xt6$yH0aTI z5y_WuCaHG{m8Y6%zinet`q>(%a<+UF* zldE{vWxrQ5GRM~9y{lMn%~rMI#f+9%hU;e&=;qg|y3maKQEc zsc0wsX%-Y=o)~P6a2PI*s5JC@go;K_C8Q(lN$;m6HNDgtAxwW%woRGhW=^2cN}KmL zONxhKQV&@@Gr!m2Z~gA)^NcAHwZMipE%$c83Pk3-fkLWsS~qsZ{{bqLpy-JpM3aJv zs_lZ+U?v4kgrisT*|#6tUyc1(l~2yk<%wZKm9z5-*$bl%`Q+^b!}N% z9Rvx6Gwv}y7|)y?lF@?MnSUC8H6+L%^(dNhq9i)4cD>_C&v&Jl-T;s`_(dgbxR>#h zY13tB_6+z~24WK{pko=v<*N_uxGBd%*cr`2#4>No!g=q>Aof`t&gSPn=&$m`twHeW zOY^S;7?BcqCRQ}ph|UVK_1AN--iRVFroqC{UpieXrK(Rn#Hm*h5N}hL@@zBKxD%3o z6kC4ghhexTMn@W|&iG}*LajLcHX0jaf|ax;v|B3?;2`;x_u~Ys?89i3khVU-k8=j8wtl&h?VHcG zun&!7@d5RR?cUqS{tcd767*9TrI__HhA{sdL z)d+In%TNX~>TMC=?(i=)-qGe{TkC!cYDHa0J^a|h9&TI<=~~keWcrY1k%1fh;e zEdnfbRo=r5HX z4P;)I;~yg8Ukn;0e(ccb4DZ{6Kx#0#FsF%KR6;`RQE9Wj$ZBu}Y2{Ya#@V}lXP85I zM6cxK8;glVeWa!UDeSDZl}Z2{tO$-69hRy|5=`Fwm!_{ng)r5>dUk)3t#s4H&m=3w z1%iqgQO9DCCm~Y-ja##xf9rO*IG~WcEu`cJ7tl~KYbdu5Fl&TkpAbis6CggIkq_{= z5RGTbsl5!;=n9(@e{_ahY**4TKg31IG#Y7JNh=kQLQY5y@nUrP;u{K&oxtLxZmXtW z^W>y}*wF9N*WzpdMgWs3xW1;!LD8DyO#Paeo|r!yRN6SNE7uY}>1hA?nSb)^-QZ1I zzY94f0ht6FJq(Q#=z}W%`Y8>~s*=s~q(M81 za(ASaaZ2cA7*H{*%@vXH>eRHwx&Ib}{JGsf(B82|huL~I zTpcG2bXcSZp<{i&@K}6F6j^?uTy}X+rw2mI8dC{w;}ce9h_{ z(US>ar6gKmR*_Ok5Dce+PK@NrBtw7VX&DbC)GX^NNXzNHu)M-Xd?e1a(jQ{>Z?2bJ zv`|h1&McO^yAvkQP6Y*+(xe&bx3DCHM2bh^2wnGb(2X5HYv~#5fXNzMhj%-!kC{=U z>T;1HwU$yd7gaonrYh*xHE?Ffk(+3jQ+R7Q?QAVC9p5Aw`9G9o_GyKlV6zx;g~AsR z!ss~gciL;=xmQ^uYTr4vyvxMyJx4OCbMxzlDUpd{8rtA9=-{mK57qZR z)gq%9(wqa4^$K%^cxf;QLhid;BT>A4jPq|#bB5JM&>R|Azq;Lz+9AQYsF*w_P+-cy zdobO(EhoL-vXm;<$a{)Bwnh@W6!2(|nJjr6%j`&A-XUtHr5Oqi0mJQF24$p`ld^5T z9(K4F4uKm9a>{(PsM_WAhd)~MDgv%9u3%zmk4IC9zdqVsso?IGk>Tw zE7h77!X5|$LTbNi-26f04)i|~*MjrFe;tiV^baS>XvH7w6vG%(GrX?fMGp_|qC~4m zhv>{kCvyJk)mr&R6?^YE-cb2Te0X>gjghmxV>cWC7KMdC3y=Jc;Wbna-6@CB8P0kWci~^`8SR~5+#Jz{{8h= zxB`i75Sbedd|`smLlz42DS5zF9|Us0I{?dCLf(BeVc`Bce?x2SaQCqBTd8R&9~g1K zVCE~NcU2SI=~%nl&e0Sc%Qt8RGJ$GGjdxwJcHce1Q^1(fNr`!WiW^q!7%@3IH9Tw} zOPF<VOlwP&w17d|tJQJUv4164G`O2En}YhK9`6GC5A zc-X+B8Q1<7CL*<{_Y;xF#6*5{4PKkgxx!uzM2%BzmaW<>pEIAz(t*-F-y%$ZGBTFjV4j z$=i|xb9IR~$wAGKZ%SbapLu4kYqz!5b;G*S^Vm!6^+2ORG;Qi@|~D*M~06Px zwlXY(mhxfGlMs)Neg^rA>Rv=wZVbDOLIC8JT*^vlUXJkI;Utf`!S^$ULJ;G~9ED-# z%Iyv$idea8jEae)j#K-pwG&$7F|iP@CPhj4K5#J;(4R2+3rT1~{!ef9@6Wmn0ZM2o zmG@KGkMAQoq)?Q4J{qB6tC31AC!v*!WjzI5M9Z~ie0|YJh+<3zr8beLUi4h~HosG~ zH?g(#GGB9TR%v%-jkh zG8=+ic~;hDHTd)CXFvja2Q7sj=armyn%!YL_bYwE9Omx2u7BMB3tYqbp;2+9Z?)-odG?u6)6p>$F@n$arH+y$=x zb?dN#R3*01Fjc=!*6&2;E%W5GaW9G2s_OLroKYC|SnB~w#FyUZtJp`YvHtm;hrb~}y+Qv(r8KLEJB1dDIEZ9sh z)O-<;>r!S7`L^cHt{bUQHYDj@H;OA)K2h&J*Ouy2T{H>Gwqe0(gXQ121tTwKo081Avn8hXBhZrVd ztyP8<4>|vqqfanvLKh;$F;6xexL|J#+l$88#Q&91yTd@B6?x&$H1N@)G}~!i>dK|; zNQ*f0%?Q0YuQplYjX$5D?O)3nvCy!0+KH9ljRkFjMH?H~+?F?S!tB>sPgu*}^F3W^*)A6mOKgCx{62pYfyi6ur|4z#>0jd#M6*{m&AoK%L3Ln8xucau;9j{=_OGpPm z5O$xjxl<{RBCn#_gmrn8?tca&rQt%Zaj#yem>&uuUUI($JnF~g>0Iixmryv>F~|CT zLUcVg!Dmj{zQAVIyMly=yGFs(0r0b1HF9T{&hv6a8vR$4oUZ6DS}P)=C}e@SLp_&FvX86{vaSt$oG@Q zvJ=>hK)6YQ^R_e*^Q1mo-!bdym%#|1hLrdV2Fuc?9Z`%U}$Mc z-L|%&VYyh847rUx_+!qM;AYX}z2cl028jL@FL@PUgz?cKYA)wnm?e~4Zf{c(T9z0 zeO+R*2joj`;$R0FigPCx?e`C+JYab)rIbglt^@?C4RF*7bf!+%HI@I*1Nnp8W0SuN z#tLWB(Bq|7(8d~p0b)^6?}S|oG9=DMhyLu_=H_Gv;pB=~KMWute7P>-HMCYde-4%Q zo=ZRJ2z$TYWEol)JQv^fWXMNNJOo;+5*e9f0P=0n5RVl(8p@zu*UNu3Iwthj`E4E# zo<z`ZNjXMuX355`Jk2IB`~b!OeA>{%3Sqv$jKHgu-X;F;VDOJ1U4abgRQr7J z^4^V|GT*@rw@d4DK?$6iCzP7B&idDcxUVDVV7Gd!)Q56n`v3AK$RU6v<40yi0H+qP z&3xa>&W8h>?S%y#TP#0nL^ff*U?pUf`T!hI>E1M~qFt$|Z=JUGiR^E$LgHNhTJ-j= zSlVscR>q1)*hi&olEMa4dl(R{?nT~r2bts-m_kb*8HjVRs}NH=?6fkCJYt_eb3@@7 zSNi*Xq|OSGe#eo*V@1-E4m}f&N>cqNB}Ql{UsWT3pxoBILd+MNeok!9?|e9l7E(rs zY+Qz#7CRji*_@N=pTPaont7Xa)tS%4=Ja^ur`PQ2_S>0j24G&#)NNRGC3`VtR}F3D zkx$;!4BtOaPS9+;kO)s8z7m*&#|7zZ4B<``loj{{ZZTB}J+#-^Xd(%|JsW|=&@^NP zRMzq>GCTwH;sp(`RO&o<=YOQPe`mvjF}RFNXR-_-5q#k7K){a-ti}xGFBXi)K8q27 z*g-sU#<=Yf_uo5faQDAaxcx{nWxszKaGO4tXG+4N+zuw=rZj!Ep7xY1`}XdUdIK(9UntM+4*fJT9fT@Jezw8OigJp@7x|iQ)6b4zsWu)Vtkrw&|qDik!qj0UXKL=Pq>>+YZ_g@MsNf_5a+TIRmJlA4pixFubgO z4~uH>DT=a115@Co4(P+C7>8Jb%Tm@4pHxE*gj2rD%#dlgd60a!nLO8KKh1rWOA?8S z#$}M^?wZIv98}VZ5w|)x)D0~$TfqWtyvQ;zj--3M=87`}@C?*=6|(P-*lmpY#C4H} zuiT@Q>@F}mbruJe*JdK-6LgDLZtvrlc6LP&6 zqCnM`))1d(u=ledQbNg(Q5Wk3Gs99^Pm`%*6q*b&VLh@C)9!e9QR^$fo+W+6gL`pE z)h9ln#EFZ8kdX?{<9SZ*UbD@wHf$UEf#79joQ|pCvgQk}8nf>^5YOP!)|?+6KZ5=| zU>4iXfn|6V_Hk)CM<)(2?alh3x*gyFns;5yKh|vi)9&cXj!An2QRjFPr}u zPEkpkBP-PXR}B2SDszY1GCrogAS~2_!^NH}y z6dtfPP6>Yam1{YM)^}@nhjB!lA^ZxLnd=z3ig$Yk__{!^RumxZt47T=t*+OPS5bwg ziod-}Z212i*eBU4+A=sb63B7Gtx}Kw%I0dWP+}=`#a8I5xf^m^Y`iumDc2X%AU{`$EWKs{}kt{ z&-y1miSb`?!z97B5x#_9o;Ru)X0^jc+FVK=s-_K{>_Jmxq?_EpT%m{o&u^TT8 zUH4|cKd|EQz;jkmjz%T~kLt351P#IA35P-ne=mnNL!qg=f@WUv&)0a_)5|(|aAQYw zA;pv)MvTGGmd8LbQ`kORQRd0rWDQFcVYw-&t@5gNIv%jCk1wj#eScr;wL*9IwtCp+ zQGTbVS&37ABGaIw2SY5zBm{Oj(Q!h*!=gtHS}k=fA|oYLX`VNa(2}01GX+uq|HcG{ zf-JO%Ts(98R=^+$Aiu>`94Smduq`{Ax5``$u##e7k>$7fCG(}qOtV=o1)TUm-1$^f z8MeDe1H;vSa4s3%EJgIQ;AVML8tu%Zm)BV_8H-dpHNYnWjYyRb`AVr5n>q6N{JEx= zj!VA)QOZ7Mfq4loVk%xhQqx*YW>M zt4f7%-%`~2z;;TR1&lj^Y0r9Z3O}ruGoc&Y=UHtQD@h*xaadSIdQ5u79c!=WK2ccE zciK^%ULfP&-lh1@6?#H7o?}a_M~J~`w>b?cuQaiRsG#hJtYV4&-q7&$0Ag*HWrSm+ zEU-*v>$o8U`A_RIzwHMsgT4YZ&ykpL!4iIuGMH@suap(U6C%_}W;im$R1E1=2F`&0 zegWZ+vH>ATnXp_Qjw}x*Ql&~k4cPEWUz;EI?_g6_35UfhO>ahp2d)X_+=Aa>M`IMz z{(ioRfXe!5#A~x4km6A#i+O;7Fa^YnRAE;k+Qf>Up{|dgnttDkHOuw+?TIzYqh|QC zhT2<{S;}*|&dY&RiU|=7vyC1@V082inEE5vLx#REClRePqRy*1V(-N9A4N_XCEUx+ z*umUaZGN7VG+FeZu9Qhb5}My#As92Rr*M!yS$_fKe~Ptzuot5BzHuef|8@@1LXAQN z88M+iFghCs#JX@@WRwBQInAs*gv||fBrM6 z(cil#9V9DvRIPV#16QA|u}Fki^rsRXvTGBQ>aWj_!S;#(NDw93)~UP~Xdf%ZKuh+| zTgVjxea@g$|Ve-}D743NYp5lRp&-?M7BouX9vhN5R^1J@NEkQ}7rCzmxS6j8zY z+?emBoE{Yo+YfeF3Z6(d!rI~T{r>cY+;Cl&{@+KAkCFT8p z5oC)B-RQ7Uj#0I8@1SR!zZ2i4R)FY#L|;R zLq>)tD^w}r?q!-bg07#al3BpZBr8?T%FRbr$l{}J?ym6NByK)5_Xn$$|VuB*GUrbkJ_9Ez5>4>Bhj>}L!D4S^2jdqSJwQG zeh%D`QA1^zr38xD_1-Z{sYbAGL;!EKRPy?0>^B*L+fbu{6dx+?UGzwvBh91DUXo4E zYESjkz64M9A)f1cYj6u}VU#ejTb~!bqP&Wht3Md&+}YBDXb;ozvemHWiOS$_!UpNEKyQQq3htE86HWpA`EfeLl=XPZNYDg)y3YpK5OA+y=XLioKJe#5o zo@lLs=ddD?I^0|d4S3`VYO48w3X-9hVPa`pgAq)yp61S&$U+m%))sCvx8}jd`7k}w z-!pE)!SK}3zRdCd8_2ooKg`hP$e`$FQ>~DL3unUHOgwq+Ox*W_Ok1HSuGTK5XEfQ8 zQ+(qNz5!<0cyh8>pmYXXjBGHxfg-qjwq6B_wIYq) zWeK*=sxHOx)oXG2@tnz}K~1}M2)8%DgXsm<$WdxuFJV0ea`!C(IdB`PfL4=@$wrrt z4P4I-ReE&_w59~p?={Y)50^5-_-F!^Yk&e1)E}D@3hd7Ncb%HZu3G_Ca zZ%d6LVgBuJXt6egqNf-$`g;rGRDK(lzjMVzR~L&e& z`6K6T|60sfHz;6(Z9+L02pA&mo~dZ=3{dC2V^^AxReSwv!>uUw<^RR$&lFpcpOyVZ zrmfE@7sG%lvvpeDA5LHS-OK`U_0DnIl%F6I$NV*Ej7ZbJIH>SFiWUDncl&yuP z^rFrED;xf!lg5KUi_*=of-0P?*9KG@xu9R^8NkX0i+h z*Yzez_RBSD~3@qBh4-Zxwsm6-W=9{@UEP6JkY{A*V8g}*xeUiKDG z8HJ*oIdtTwx$p;DM*#XTBIg>;AR&XYQjT?)+H>?FJuNU=6h}Wm3U{1OMID+BsN*&z z3I|jTdBkE*kb2*>#OSa>%6fuS`4|!p?YT8_w1jAzpfT=tGi4{A*eJ;K=fiEo*nFTW(#g2Iu?SQ)Z7h;?O%ZUL-Gq;z5$+CTbyE;0V zK{+e2=Vr^9Y*dFru+pNz%7S8@6+{`^!4tO;=mran#z4f*Si^z`Ktn$(9YUb9Kdr$0 z!5HL!D|Vn2CnVGj8f2TyN9RXN`YPqEV1um2`vBDrWT#$#|36zP=vHGD&GBE4B-*te z4=zvT6=UpdTX#ICT|VcdcKS-=BHTX|Qv1eFXUpEV0n|B@3R;jXKvupQ)}Kt~sr9yN zOjUj;TYIm651(rbK&v(mK5(Uxk@Rdb2@S6kx$R;d@5_djO4#HqRa%9iR<}PVgqaS7 zK(-P{mdIyn-02hX_Ylw5ibi3|HA37KAmLg)*$okgpFxR~Tf%4RJz|S1%<7?EUJhU$CiDSZVB0_qEy z9g~U7zzd^{d{2KoSn$UQB7NC;emTXCTDX*UtI(+YgR5)kEAVGV3vGe-7FI=CG$8*g z4gI6uu?hYR;K{UxQl<~3iyyr&APH0WR|@S>5|t?N~bcG5-zodv~aH66|mQBst<$1AP0 zP^AWBdxW~wbLY`z?P#KRb7O#-6u9CC$Gvxa7STX@2LD+(;6etyDE(fvmpl%8Y#;G6 zVJga~IyG=+28^ybbI`XWrRFHb1%D2Gyhdp3UYf5WmwsIWIeftzy|d4T=}|dAYo7~k zG~U7LLq(!GS(B$7!0-2LtC3q1Do0?$bnpq>b-DZoc`<#HC96Nk>8I}2(6m;1{Qf!6 zPNB)N+0iPNHYJL><=uey_jTH}q^_Sg0(6>vhBWop2w{AUnr^lAy*MGa@#BMH&>3Sk zvuYZ7B)gZT;b+du)hkr;VB{tS#CPFVIlg5(EVS_L1pi^O;1l{SXJv^qDMxGtizRIO z0@)E^==1QQHFJA+r?nKNw%R*&H}c@a?(G$3qS3a%hFbehb?h z1U03Xg6-wRo%ogU2oMA$cvRgW3zzm-JLYeNKVQs-$!!K7&{j4BxW3dJ>E@%9?9Ci@ zctPwjUK~>M&cKCUh#BD3rl!uR{+27Skh|UCB)zy?m1IA71}0qpG8i=iVm(Ny@=T9^ zy7)h~z5*(arD;1@un^pBafe{RJ$P^n9w0z)_u%dh!3pl}?(QtQxCD0#{%`KRxi@+L z@0>Y%W?5#YyQ{kDsi$hXMW~yrImr=^8AgzK&1*G+z+kfWG;5#c-(_b1jRBY;g-thL zOMBTC!Al8KT5J{4WdR#jmh0JlU#1a@TQt>vvoKRMAuN`PPRMXey*3}qoYn58DHBB@ zTuG5Z(uA%KU6Eg`rt!U}A@$T5T&mH2jYQH1LmwbM26cM! z*)-d$Gw3W}aZvRKLsMdYYNd&?->3xe>Vc=mG3C0|u4tNP9~XmJOhK*6qX@(eSaJ-ja1ms0F@j`U5R`f($7`7nTV`TY>hJ{)gI^m@5Jw zP!U8xgNImexkn-$&u*JoPOGemU(FCokTa`}KAzqv1W&rY-Be0E+`+$}crsY13PQCp z{?_DZc={R&jqrAq)OU9>qkd{1%A_o0Hn_%X@9WmDwE(u?zjQQq$Y(NujK7dOh}nA} z63zPj@w9F#`jbgyv_!tIZqu;q>9CV_meMbTD$>ZncEGCfE~h~`)^`qj45uH7%H zWj>q?qn0=XRAW5+O6`+ZG&Y_QIY+ArLVN-5CP$ww#c2XBsH?mM{3}!H ze^?~3Ig*x_9AgSqhC@*&#^;5$2?*n35Rj$(o7vcZ z2`TU3ZtcQx7mk^S?#iAQsoFQKd#Ix}D*U2|jT$62zvM)U8S)k-K)%zPt<$Luri7O# z7CuGveY&s;?yw)3Id=2&yOIe2r6r0}MKxj2r763i&wBLE+tp*6NW929mVf1?mpSjAtYwm&AY$Z zNapVd;X!c)=%vkk3*rl5qrUeFtaRtaDp(UEdom!rLvW!cOSHUo^!5Js9)y3#_6@~R zS6dCq(geAJ7QND{@&zDNW%_nxqI_$7e9dvzcsBODDNP15T$FJ114(j6S#}zdnTSx( zhQAx6W#vxg`9h>X>W*zPmG8Y4wSpcF?BQoIZ*!rF0$zV+8X8=vsL$SEr0@Tj|iY>xBKrPt>EP|;oVvHXL+a7c1bL9oJe!Uxh! z1$1^(HfH7!yqo}}`O5mCdtj;j@gy48j>S$R0g-xU{Qn)u^BiF%P1dTPs!1wc!9{dQ!+cqf=R zDjcF;@uP~_nWM>R!{<6A>lXs*HTYdDGQQm1c_i6O#n>dIKV?6m6|##(D>Bf&Rla*w zk?Sv{!o3BLs1b1!Y~R9VI$>3`94wipi5(G##SY%^ow<&T-VVA)$biCt8i!JwZA1Z5nCD z8HE!Itdt`;>ou2MaF{v(IQCsn=!mJyjGZtm8la$;XpvkFw!2knsj_fU-ico^l1lmA|0|;jFEuH45Oxugt#{bPo#7x$DRyp^^gpo*gXV)i*>^d zS{^SwjmBM%=m3XXNZ8loZX9m+UF+y;d49D9mG7GydoU6B&)Ye42aVU9N2Zgj~&sN%PhpJTd^`SZK z?uMc7{QcmjRo(29WXM8t(yR5Q(Et#?AH?J@sA%jZjUg~-X`p54;!C$#94w7fQhv0 zsiLH_R`TY~_VqgJpb$yrNO8#N>oxP`<{d8w+3fOK05^Ku+d1uym#M~SRV_mLz1Q^f9r(8ok^n#NgqBk{2mpmvoy+D_@nDv-)0=b`vA@i{Fiw@ULMM%bVI*%z-^4_$P!+(LUiYlCt>a*?1U z9WHkl)&ODlOx<9b0`##mR_ND#*KVMtiuSjj3D#WqQ zLZUM7^$WYO+R@2y)BE1VR3(rQM(qvKw;B`tw<6T+n;p%TZ$QZb1}_&cVZB8dmH0aH z5*GW0oPOB^Z!bW_?Z?JX-%Vv&t}rA(Zk_8bsV+4#7&iQlLs8C{vZ?j9%ewG~bVbS> zj!X;onOc-NJ;R&Bj&Dkm@cE*?@Qq`ms1gXjeTi^pzQnJfk*88 zg%s{G@ZR$|H>{QlBNW2@M!lEJd~D&CK9Z|u7Tr67k4Zu|wPdKzy8M@=r_YVH0px3_ z-r(J!U4aqd|GFOV$rryD(2|%^sBhP?k0WVwVj7-ZZbU6iuc_1VS6)$ zX^x-Aa(CXzT=qI3oDz#Vx#d}V4?Z&0yBzOIGo@ zr7Tqi7Oo@Bn)5Xbh2mev5G0IE^y-!_RxXGf*Q|kBeoK zoeHLP3w;K;E28a21V-yo*}w!NwVPeR%J!se3p^P zXNBkE0IktC=;Y0abj_DqPxCHd_ct7QWU(K9x7or)^-Lug4SX(VHvZi}2|_~1bObG< zkgE9EV@ajK$j4sH`aAD0fx$w7f+@COik~8ddqDtoMGAU~9fx!)h>b{^y4UVdgHrrT zzPL4;+SGC0PZ?xsKk~1WJ7t9|2v6i^lg#2{h$_4gZyG9zF%=-|+=~*NLlMNA^WARI zeI3Y{I(OXFm{8d4xFid?A^}EA96Fd0!M{NzFl@?Hf}ektRYsUQ23gQAT(~G3zH3^} zS0r4x@O_upA~~RtYk!Tp*C@~;kEqbn6x=Yq35Ea0=t=xzViTy$)jiGSO%Y!;T|tS3 zoLwD-w-<$%oB7I+I|PrXY%2I66c8af3`y3AzntaYF4Kz`RgiD$BV5wgSR4H&$2hzr zR4W-vSvtaU!*%l{#W|^JGwD_{bhT28F<}~R$M=&UHk?3<1bNdwJ^X?_G!w_l@thM2 zsn*c!|60^0(lM=^#g%K{csr)DEfEO~A$OeX9jw4&e0S=IWTh1{OQ+pbt=(V24DXEZ z-LA_ethhniV(~$Tk0>NZ$K8dK^Ow>18&P_RoZpQRiykd5Ux(1@2G2s=^LO&xQd@~`9kbwWt~ z2D~gMrevah1vZiFzea!>`g}*WUM~Kaod?xGdqa-yZcOkm$Z+!HS1-^~{()h?S^@nU0xsK` zLA$9Yi4&oodTO8LKbx7^3jI~8rnP529a;;0t#vCIu|DPn+{1M|0~#h8>3hwi+Z%7v z!kv=&m_M5hcmc=BEzY19w`UKv)@2v+Ke7RTg#gDSCW6|QvI&1)naKc|pox)zAr8vw zef<9~BVS7a%jWL8!)5_kPX9I33z}Q7L#mJmykjI*s~}D;=OQE`$NzNn=T|RXRJiNn z1#6c7z4ZhFL{RqL-rjfrnx-_|@bGXSY~Mfb|If2J?thBnaW06A{7+Hr0>C$js1=(e zT+Sl&8|wa}d6}_r^bJP#R98KgKkw6F2Uc1>Zn!fX_=eAzVGvZ=$n zc_63OA42`dvU$Ho`Pj7lx?}U(|J-6lro%RLgO!Y|yM-ED$j-jG`}-jMbDqfqHzB!5 z7}xqhP#pL)G%P-_{)cLItfL0abw8!qmJzg4iEbq)8rHUaaV9*Y{;Mh85vZJEz!o!=B_ka3v1FfoJG|Vbm zECUtvt!!hL8dKswjW z7Xehd*0S1C9syq;pOHO0eEjB%8eZ(5mgL7vZ}mEQ)dx*KD=o%|-}DvI`-SL~V8c)< z*&hby2LuFMRs!cfCm~#RwedKA7G&Y3bHn@Jo(z8Y1V3c;cf(|;ud?;-OY_NK`)vSL zPscFgNXq}w4oXP7oiTP&8d-6@nv~27wm&-fw}Dy9|Eb!0{@XQwh^%83u7e>Y(+s#; zM!zHS=kx!%a9Iw}a4$E=8uM#L|D*R_W{?v3;2A-X$g>V!8EN37hIXQTL-a4L?nr?L z#23WN0#o`2E2F`t2t2=!)Pgp7VxT?lhp?VBr^gTlsZ4mhE4ls{m?*&=^a}8BqeBQ` zF#TqvfADz6HNbghknV{8(?}gju-w#iuwIA!dgfnK7Sh+NnlnUQi2y0gY+U_+TMh0+ zM>M%$^rghqPU9bw2p|X#$OSKgY&rA?bh3~4Ri$NevP^{M~>Ss9y1*e;s zRiSl1iM2Oyr%#^+*g|rSjQhy`XYf+tK0wm=D^R`BqWE*v|CY?D6j&y$=T+A7f3}4O zn!tZju#7e2t~jW#Pi(c(nSkaGZTrCq!CmG40BQRpO*oS;;!u|BTZX-GG*FI_Jpa!z z)0XUz%1?=oUO#68$4fSiY52cuPcphh@}uMhl6K8?cWJ((!uVkVG^e5cr3>N&qI{1qo#2$?HXr zJP_9bLllZD-Vz&Nl=w&Y1t9|^y&?{~q5HeR!=nl@_XX9p0%#S-6rQR0JMSO~DLyU}gPk;&ZQMq|s;;SQ)UQT&48v=AZUB;4-YD2g6;Y6|D4VK~yPsBD#As_AZIkVs{?oP& zj-OZ}{GdnS_Xb4?)}sy#44_EUAJxU4zx%zbj*y<*W_~4$YV-o0S8n%p9%rcM^DyFr zNGIk|J|PFbsxpoKZu5RPh@9I{mv&S(8^3v1q2#XfXVE za2jf~wO0<=w5%p5KPlec#sW2VvO+#sS&nm7V!}rWdVxLFTivIy!=(d$m;3585Q*Cb z0%W#pzl64XcFTo`W%b#o!fgAojZ)hS8k6(>$a*$V|4*zm2faJsIO?wnR-vC@@Z}nZ zo|WpwbH_OhG}PVInZKTG*nizytA!@$RWOTMRWWSFkSu+CFemm_5%E}PgZk+be!&zi z6i$l|RV~e(SF0y)iRlx;#u8f`W+m^TO2g$gMI^OL*e6%qz95yGMd*&AZgf{rGCa<# zbgo86;mD`n)hVfDUV#)=+HB;uFSl=~Mj_bZ`s`->dRR0zmZDZ1WMEd!Y9XI3e0S$= zXnUE-nK*jD&UaBQT}{Eo_@0q3FLC(oQ(JU|xo3M~F7PPXTtLBdQONZFYd^Xb=}cYi%Nt z!nYV*g-u6ir0{Ud8wW4Cux3|yG!Lm^s8EaWRKRiQ=O*~AFY21)#R;>9~4O?e7T+95~PFV(H@R8JTk z=wd8K%AyEi=?t6Orc`=lLXZwhl$<055X*3v3m>PVmkT);aN&P2fR)F?&4i2jKf?x| zswmBZ2tq(K@j?GzUHwZk5B?pKzMDDqO%$kmv|pbtE*`6_Wzw}0SH{gjj+u&%J8F_D zGQO{*BH}6^4L;=~1m>z-RcYTKoe)wK@{#qZ@JxGmB1m>)6P>aUVF zV}z-&{+V!!uF9lUk+j?AlYEQ?Uwexz{~&!_P!u4kZeB^ChKvQi1faKgRdEY*t2z$yFq5>m!u?l2 zsP1zVVdNVWbQRPN9=01})KrcAvcW{un||ekr+(~{c#4$J_pYmn?OY$P4Gt-|E#V4o zWi}EX$(vpXIVd?czQ;kKi&fseQx#POHX`L@f`_*_OYiI z{V|FV`{q}_skJwmbY`h?)+0LUwt!k#BH=A&pOEtjZg;P?#xk4(>!aKE4`inFtJ>sp zOsnpnsCEpEPw=gT=jWMx*8B)qPj(w!<(kiJHO0=Y9+jCr}_s7{P zO1l7xU5Cf0Q(Z<=q=Xz^$w!yngc!KF#Eo6|=$*M9>xeC-wp!=g1Z73gvQX=C<<^HC z;xi1_V-9a1y|vlL$FwJzTC#fr1S256WtUjJZ|ew>+EmdJZd%^U*37&0zhL7geJ7Up zPFlbrgOuXUl1CXq{+Qk6wZnmqRC4PoC~#kC(|7nTXFZT0W-vMZ7X6V(81No=LhX0! zpaF%AOi-Y{7?k3EsiRrGO4sAWBRz6vE!b?j^9sr&ldAC4xv8lg6Qzw>f7$s`Hv&D*p>T7%dRbYgPC@FaZy=Zs|fI!XwB*#s}_;z|`)qMaWOyOfVO$ zH*U#-QnKFfvppXiTHmsQyDaKd`(dFtM<#24e#g4^N%fO!!n%Qzl$! zsQCCR6FK~*U-?Leh4@)37!_pR`h_qx?vwkcu+)m%p+Zle)eyGWN;o0jXeOn@?%721 z-{3lQEIX_K>}Tjxhx;RT(Jih;Qad>TTY}_B3VOz0^BzUXKg(zgUj;LZoVx$$<}=b2 z@x{40XnUJ#zgn1$>E0n4>ScX-b*WPI3^D zgt##MEb~R%MMCVYmq#K=&Y13TlXl5UWCSxgmsAuQ-s)qv@`$+9zUDaIN2~L(eO@xJ zK5;QzHBQCPH)c&v>zV20$0|#5!*v*gs+bJroMrWUC-<#}F$DNU<<=Lc-H})8zLK<; zZMlq=4Nc;EjeuQYzt*EeZ$&M2CU)*)7EGzotIiOJsfQh?>(1qnKvh6nSFdXVw=V9J z2sr3?){-9c`_0UQ`G^K|@pW=T0(D3sjds|{-G{jfG9LQ$v|sS2<7P>nHHkt1V95N;HYpkMdAS%w2=7qm!%OdwfgG(D*H{sz_fGuvY>+B)--Zj_{ z+25(A1$2f7JmZz9kxYjml=|XXP4kaZ21%#_C@ei4Mo0kq`Yn{n{1vY_i1!n8I7TT0 zvQNO+M{)|Ugh#o+789l7b=>U0!lvt@*}Lm-WF_Xp`5->)y<4-1=w%nRIw!3Lzy0xy zFPJY$q$tG#zn-{zV$z;Hx^8v1_Nj!pAu^8J^-Slfx49!;|+*SQwv z#fmYkZU*$=iq}8td^Y4(%ZSn6PxQzyGszk5y&?2g?tFIoF4$RaBxmNX4}3#dVrd23 zu7y_Ob+&$XYYq>!Z)s8@)ZQM;ADL9{i8L89qdPtISFxL4?2}ui1oOD{<;3E0zXhf! z0C0Sim!A(ZMw}~X#%R74`y)_0Rfj|w`>xHe!>QO>-xe?#`iGvK@|n?CZq6=9cgpoqLb6fQ(yHpTfgJ!t_Mw*;F8ijXTInMmpOTVeS3FjUDe7z~+^kLtTAalAS9vaNvk1}| zYOMYZQtH4dTe-y=@Fuz z9CsDSGk)#>3Kp&*EgB22Yt6=_pfH7Al9vA|mjwns*E{G_ZYR@Zv4hc-hr0SDLJ+hHnUR79yMzjmf(Zm5rK!)M{yzl zC2n?^mEni9!Y+L#2aD6 zcki5z2qL|eZOig7dl7}@?45)B0vDk%YJ90(>t{{q71k+r%tE>QL|6mAs&MMqyh<$z z%DxjFcE`?7GBkVi2y_4nq?QTOXk}X$Ye4*!L=P2l)Fu1QYaM*VtIBhLPUCZiPy>cc z)pW1D(XQSWt0cWck_9dj7R&CeoUQMFDg(xl>K{jY+z~#@6OIOPW70A}Fl(kwDN&z*cC=7S~Dna}Jx#Ar1}wKlYx*QJEIa*MEUiGYAQ;Gq2>r(SDcdJbEpAz{M#%EfaIrl{Ur=Ae~1s!uaS)+ zNfUGj!u?ocsbf6{jiIn8_FiC_l1Z0KA^pxYv89rcDhie8dTE`-4ojkRjFdpj zxU{xOt&`SN=PzF)TD~98vjP&`r z{n^9%a!&fS&X=jkj3?BX%5r$=fCBLlYx{Uz8!fBA>~~5Kb3?Hjp*Ro_m>b8rv}LYl zA8y3Or&QezmYPhpz{e3Muj^jly%R}ApR&4Z~gbY!JbG7d}{|5&|O8#p5&cFhp5G08P$bR>P zF>dhz4cB#3RNR`Jm;JwPfcfpqOZ&%OdWlo`Z{X$Fv#q11L`|(v|IXcX^ux8Z?s}g0 zflg}OfSfP;f96xP(C2j(4HZ8$tqhlf=W!{jwf!e-W#E9c#%7|8GxS=HJy=W93F_-- z$4srm3a0S!oab6JjZ?2v z>~g<|N0>KUxFnrl6B~jyt)wmV)axU>seVgySy6fcn^44;XGpHTSK68XV@=j1)*heT zNplSirzYkBY9lQZHi@8ZwN))@D~1Qb4Sj>{7{oCfPy~+zAPOF^%?M74tewJ7M7#eu zbS8)Y!2=GppFI2{*B*qkx(w&Tv4@fm(qkT&I3&)J30bxR5Ih8m%40;plmMjE4OrfoOZaTn@*v! z026cHZZ#UrA#hnla5F^w={^=Qw=5^i4Ys@bKk`W46 zZDYVQGvq*@p~Y)9V@Q|dP<-y_MM5mQ| zc_R1o4BKbolc-MLmux)dbH}Y19-7uWRk2AJ>T1z$B~1iO zx{a+5=zs+bCiReVirsPS2(LX^bH~H7HTkL(%)-qZHeG0*JOW#nGmF71uIUwiVtQC? zLqbd;c9n_nSjSp;0+JRom7CT@(*5V7)`p?Az)fvc&`rng|CKB~qOjiH#vT^Jlxlx) z&E>t^jXhsWZr0_^Kj?o zf7s_#6cU{0)93m-J?VZXWjpeBtJ8!45@umKo<}N*@*l{PQtAeLBY90uu!iUPU4d}Z zFD<~@2M6869se2dW{W`b1$2oUjmfn~q9B+m4ED!|^7C@=gdVWxF-*pg`nb2lE()k-sMdnd99*pXUKTVM`6kU`uVSt&Co~Tu5CV zL&aSEXhvHp&%bPUH%bxl=ixkwH&m?Dt5>#Mq5?0V%1*1I3q^i=9!pUpZl2<9ui|^| z(7e@gk+I9Z&*WatlV2=5UHF(#4k|Qw0$whF%Sq7qEamgBPma=5EXEUpYWUGG2o8@# zwtxII+JfMsBn#@b*bVA-lz$`+K*PeLtzZ4(yTo@xvQT|8L0natx+u_lC<0F8Lz8+x zG)^B)j~Rt%^C14DdVd~*PUvf%;f@?9jbFA|NVB$ONELUO=) z*qEyUP|W>^_~>`FY5`rTM5`DmrZ2Ruo(_2-A&K+7X<5ByxvpwuAUa=|&Zu%Wr9 zt?^<7rqbm8A6hN~JokB#*)|nxDWnmwOXFIe8RJRDmjIKr$wa{g3vLr5xo@5P#)EQT zbst8;7ETpAxsJ|s3E}ORJ!F>*+>F~xR+9`_^hQ_7$r+14{T=8(>25DNNb8U$bivBy zWqvhxn-ov9iY56U^!$9LjCQ5uz^MER%;7T zH^1KnY#V<7{z5&%tSl^Ftg-&B;UvI?Y>kQC8q>6jb9=t5z_$fKMeT1P;<9tlGLX~Ybiu#nV5^{!R6Z|gERM9WgD{aaFGqk2GS&RkHu+m$n z9#dwjO_!WzRjmwnMWYNX`iD<}t1(R*2$G!cK9}uR`tT9267NDx2XFiD#lDuTB+;!u z#*!ON$#>$U9r&tR4k!3HB}hy5xq@wO!n{F%?r+Sy^8O zuLhbdK9j_%*%@p2RjK{07*_*8gtjKY#K0o0P&e6rPjd9TI>OCyyFg42Yh*&mRdWs2 zA)e9!hU_xbg+Stj9}e!roLLFuQHI;GHh^6nW@-mKG3A{Lws;@C-$fEpKU#7r)78H% z?+J>`)wPNl8sS4*r0Wc7DA2@9Ah0?aR!S(SPPr&Y&iyoqtXM5DZXH1|D;ML*qOu`} zXK_+q>i@ve#v+4ui3jZ;h#Spz5}!M@GslhXu*)re7IUTeu%|)uXjILnJS(vk#Xogm zoG-a?DLI<#x>zQGOD1X?@4*!H2Ia~zmXTk_HPx-8RY0p*tMtOOO^GAbY&F8agla_v z`^<9hKBfAqnQiIIj@{FhKx;tks8jXYvel)=@!K$it93{%^qd({!ddfDU$frVGV5U{ zPhJ8~1B*R!KV1gc`GcJWq&JYcL?ios;eiAgpZ=ptT;7A|{VpqC8U72O!KN`2AM8Yr zUJgJwAz)g+d~i;_Y`??^mXb**f)b6nc#>M+J0SyypcgI#)REDL7GX} z`#lbP8-E&WGiMO28M!+Uu0}vb_pz1iL$_71!pAFG7bZFi~Z&t~g!Z#=d+3qhzymv1PRd z&=>LL5!r~xPgPF+_~Efytfa?#?StBfGJU5A8!Y69i*X8d(X+ZN#eS~~GPH57zMznM zqA%K}*R-%{R$ahKql~MB@;TF+=TX+;H8`6#)ppH-Xdd&YQAeBEX-AaAQv3Q{WQ7>R zW=4u(k0>F}#v$`!%O*yn-o4Teen0F<{?7RO)2<}@P%v;GLTZ(FO^e7X4$pa zJ~+VfoO#^xB(^qeW{b2@k($540ZBvf&7l29==-HpWZc_pZ8Sccpgs2ID<;j$*E=8E zL5$H5v@fsQ4f3E1FEAh(p{Ig6Tel04cLw}sJYy?|iJ1)oI~ttE=&n5+wQl6dqWXw< zwE9d&CxQ<$VuX;3QNHUP$#de9jDSIy#7qLcH%^P?`HA7M91;8R==q5gEn~{ASz3+l z1^=L_?JKa!c5xkRANF&Q8|)Bx?z*&F^Ou`(Z= z#OX@f#eqR=e_L!rMVPzi;l_Pj#dOlN%|#X^_WFoUys#piIn4R-HGDv}&5R?>!>UtW zX+C}N%?=1!ahv%eSM$B+rrMLra$^5ikJkkUQ?UbX-Hzb~`C(CT?h`K}RHbo7-CQKA z89N+r2$_;CXgGA|?Z^7bCC#=;*GvOG@0;)dg=(RvJnF~ef?Z`gdf{d}h4Q|a8I&s9 z#W=PI0m2oG_a=5ct;#r#VWIu>fcL%Kkmwvlu3tZPEmCb4+#Fl<HZ+POBFx|%53+DyBaE1J;>cM);Ij54Iug|>=gk+nCsyMY(OB%aRMR;(OtKuTV&DzI7ME}G=W;Zr_eu9dhA8yqtuWz5 zD6UqB;T7*l^(k{CnPldg+>Tx+lA52#=O@?XFm?`x0?M@aS(HO-VrBiR4)k&|=4Ube>w01J%E1i_*(JKvPz~jowcy-(FkovU}D;*)SQAn*ERp~ z_eHArjVfOV&LdYPKAgO}r!02=h5#&d(zaYGK>37-Rrbars%cK8V@9jx5*GZnnR#5E zy=V^+-+RTl1tFPD#OSvg3vNSI-->20Au2gPGDJw_ zwjoMB%FA}0-lX+#%T1%2&yZR=>jbrDms_T@!IogC#*ikR!|iZ6XK4 zWUNZV?T4dm*qVnRo?gyXZwt;(Uot@m z4fZXZ*naXmnQ!*r`~AW5{o`!F|F-7&vpQ^*{RoJA6T}$ zkbcdvetW1~oLv7yQ<7lFCZ0-3jA=C~nL0$@@hhRXVFN*M|?K%QGtOgxIGkJXji}YE} znT{nwGTvNqR&R`Z`2)5Z;}V;$4ekz`VC8a&$xyt+VAkZht(v>y#bm>tUU>(Tz9OC( z=DUae_nnB|E*~bbhW%J6r9LaB;tm_7ndNZ%GZTfOb~PHfYZS}vjBkv$C*I{nCM>HjXv& z{7;g9cWdu9$+h{2ypqr~2<}Bo^l< z00oA)c*GX(nBY8|gPTaqYALCv=Ci(2>CCvXFPHkdy=l=^WR2^TxgsLuP<^P-yk5Gm zQf3P_h`3*32t<~!tSio|rKrq>Dzk}p-?C(v_8=6aiU!&T0qY+5RZ$x8wt z<1?S4pi0KpO7kcFLUsTBNlm-LP|O~t9M90VU?rZ8>-k4VBM*o_Y@8%@Cs^BhS$Kk{ z;J8m%rV&wr<@u<5RDq9;ouzM+wy3U9q6l>s^!>B_%uCQ>HyY{g>GKLDPr2tJkfe4$ z<@?jUfl{?Y1-HZI%-Mnl4u`FhZ>fO$h~ae0Tw6_W**@BpJyCjHF;gv9N4K)G%uy$S^WZ-!j6389C(HBqb? znI^Iz{;z7_7x2zVU|vDTj^>LeK##U+rogwNwqW*golhf#sg!V2<~}Ojag)nVMoJr) z{7NRs!Kf;=e;kh@eN9bnJ{x~!qwK7R5*E<(soP; zedC%@Btx;vqq+Gauu(<^$;6uK2ZU2fdx>Tl}A zzQv(&AD+?E%7^4GJ8CE=uFYqw8#8vF-NGoco9Ux^0y3kOuzL_fMI3I(%_g^n@T-&Y zjq&&Kx821c^ye8hwzUkw-F8N=ADIT8Bp08EXzMlZ&E=pwVr0xL?-T5H8}8nC{}mE0 zO9J@#mKh@yagJoq|E|UThXDAC25^=ImrIu7Bl4aE|AnN%wb7&TnB$QK(rBNBGPMZ3 zADIMddma0cY{ZeGeDZ24BS7wkT;QnD@3WK^=N4NRw=+|2hw6cw#uz-II%S!r0LxZpA5 zXC(RhV?Ra(%t=|)ay`G5R-Q=4W17hh{BUxKFpWG1ooRIz|K}Qy)q5S!GRed+?h3_K zK(0nMW>#gfV7TVw39{k~WcOpS#61@d9qV>LujPtoB3?AcMZt%5P3zLNm0C#Q6G{us z)&s9s57TlorJ9k8NXG`yw^i{enp%lf@tW^%RQZ&LAv8|pJ#mn%kN5fB=2x(%cR!oc zbP##y@`8(_0Vwc)6&Te0#F(Z*$Oe}m6^+( zvOHg4#s$W51w-7eM@fIuShS@JA+GsBEpzQ`$12rXR{kfRX3&Zy{rkMU z7KxnQj@qvaSnIP2S7`3MLKo5Y(T(Ph&nD7s<|($#93^g>(u{De?vLGO7(+lDbq1Y9 zv7sWSwjql)u48Q>{dw6E0jIwKYsUBPSM0Gs+FMgG^0LveGFP zE3=5G2=d^9c%e@2gF&uekG~VTEy3BDXE)KU9FW`fwW8Tw%5QS|xuHk9j$@+b(YT|Eq_@e|J^WA@^gKe` zln4XHRp#m%;;9vzO!R5KGp%~%`h#}CZd_Wil}x-&>kOMUQeLK1t9a}~@i9%`gxM_H zH2%Byz}b=y%_8z{=zE5u{(3>4)@k~$to2;+ell&~2vVd&R(a&uW%8_)2%!`IRh{l5 z2N{mqr{m~>nl{C(3rl%e%Zetw`y%`rK6-T73_wAOMmD?NIzf0EFED7^aMhAs-+XJL zEt!9zwi%OgbbtBnjp2BxoHdVQzl-qUvFFXUt#v0_?3B~RNZ|XB?;4l~>qVmE&%4UO z1SLKdA?rl7_X{RsC8dio6e&zIPqKt&Wr$NRHhJJWYgc8kzqXyZBh4)4S_@b;Zs-M1 z!IGFFW5AECMhk`*6|asa@=_~^=MD|bJx@e4y3m8^NpRly4LA7J(`G>_W}qAsTfXRj zwHY3QUWM--*GO#0i;zJ;@{^6rZ zVeN?_+oPGpuSuGnGP*S7ZN4*kNqWhjovRpUSvf;yV zHz4gk2&P_w33J;=8OSiXm_~tn5H1+PKb77x3m3`|wwcp7!=PCd!t1URm@?I%c@%MJ1z(lv zamtaejAH00H91!94gG^sE zq79x8hiPUC*_7Cl8oYvat8ms}q|{dBxuqAxb6 zO~bk8@N2IxVt>^?fvKxMk3~|H92z3^(^3X0j~dUb3EjaJ`jps-nVKv%?c=b~@xf+= zxA|X--=>=*D#7YyfCj(G(d= z+QOr7mgUH4-mWteGZlF=Qr?(?Ah*=NP_K|tsv=B6MlpK+yS2s9*RhpAuDL_L&r|E9 zNR`cCv1I3+l#VQ!L4V|wV2Ga3ZcnQuG)XQ74kFs1uZk8M71*sFS-^H;GJSdMGHt&%klK%oR;5Q1G#}q<)z^~PR+6NZ;$bn-w>m2yCZ{*OP zz1&l3K0ANCZ1POy)qVn?wDO##qXaE%?j1fY3GMH?yyP>#ulKw;3Vhy~IaK7wI6S_y zc3VS{2oBwy_~b(D07r~`jSd-TM@%@@Zb0~0KaJ~Te6zFEUVD7ZdmN8;AmI_yp@&j1 zQ&WAfqwR_#ez!tmJ>MHfyi^EUx`P-F#Ol#N zx1Plp0})se@6k|e<2;0>?{e87RO&FZcMs?hGeMfE%c=&ge?dvmL{9|a#TGxJXm;N> zV3%kUc3XfWmM57P!qig-8+O-+3*V>PjYk&#%a{Iz-l7CQqI_j%ntmU*Ck(aeYL`I1 z#_%(u8IC`#vLXNc|JeEppg6j1-Go4ZBv`QE?iSqLJ-EBOyC=B2ySux)1_&+#3d5XZ*_k(VO|;@vv0E|`~ue6eM3W3Int^RLh^GR&)TK9N?-JUvg@1yMxJW;VCy zKQQvYPVq0?$FGu*FQ&e1xK`({S6-#kO_0<#b^0 zn|KwuTTj_S|8+9pv*uGldL`ED%Xa_gLI3puevM#w+A0k3<{vs9Ohlo*Z5BQgP}fq4 z0V|En8Jl8!CqJ87T3#;IIIRDF_N|Df;7j~|8Fk0=ALu(f3|6(o@hqfTGtW4{=Ku4i zb)i>VR7Rt1!fT~z%>-YVN|T?A|7->L_z-Q;MCr|7^8YPx{+E6nJcy?(uQsBiP+P8l zK4IP9ziLBKI6X)3+_auKT*k8Z```8K@6n7z1dp63%wr7Kk*~Lx;rX>m)E1(v_1}ZU zOQ_o-e8vuT`qs=pnTDo+_}2)(fprXiqY&Z@p0K2EIp3~HI-_Xf`=LR)`qcAHZ@&-C zw<6Sk_xF*&-rq(TLqo%F*j67u|Bn&>=dAzPg0dn0^Ttyd2(XoOH6+I|%6+F{v$!>h z@7uR%c0B_FgPY7`lfP9Rr{WMx1#>*?*6^v2t>5`1G=)?g$9P_^O12Vsq%;QlGX5DT z#O7CHnjel!!tVzOS2ZG`RoaQu=eMuJ6E_o{kMl+fQpkzLkg9T5w<& z+2+9fKi>{)ssrEpq~dzBRdmc`3s8{Jk$+0>KmYQp1G052CPHm0VYOo+(S*pa60AP0 zFN3Y~`Pax1Kt%SEC%!sKGm+Q1H`%Eg|7)G|<0JZ|Rw9I)Cs0t{s=be?`i}cor(k+j z1fR*OFo{_V~_7{?p;=toY(#>1$ zOrvxCmmY)oSFA5<*aVH}7|ow=*D2+T0R3=2cac>SwU0yObQN(vfzUEenotlq%^ub? zI$o4Ng2%fyZHf14Q`1CFo~6_Fb~)EADK1G0U3M3r3W7^KB9AsvIdt-R1NMr;-g1Ly zo3~F0J3sgui9$T~l@h_x{wY3=ByXSf8mRVaZT`7@FrOfb;3N}WSeekpDpG8CRB0*f zkmf00YqL6mZF^x^=l!DM-D)qKd?jHCDsO5sAwsdn!*SGYwcmh$7BQmOYh{|A+t&Q2 zGU0c_9xm1h^1M7c+-;}X7|s+**IF#lQczIDgob_yiHuCVy>-Rla;*->WR{5JeRgF( zs-H0&jwX036EX9bh4cj9*Qc)Q_V-7%M#~gKW8=|88VzDPI@!_D(VuU?vyz0JT}>qX z3qhV}RHA|`w~`Dy2<76W&E=$36t!_*BdreK~%9zjdgy%cmP)QV^cEYHwd3=&){*GYeAq3X4 z$L58tO;3NTu#+~N(aB2FADvx~hQ5nkCeoC_n0mbNoogF29EkkP zuBuMNohxXhX|# zWq7XWGA=o|`w=5K6_EmPL8T6pk5=C_zcYNfF@hpaga1hE_l6KLnHTllE`&qrpPT*l zH)1g)zU!LT%_@WGFZ>ozqJ}!En1%tQ9_mmY-er*YKaO-QNpRbYYmdoppZ_zRUE%dD z%24tyd49#A22;@Y0$ICh;7)P94KlY+Fq|AluNv&DI;1I19BB=|qJPr=+0Rl+e%qVG z4v|OW56=7YkN{^oado;@#j7kxwsSZx9MQz>s{f6T4}4rP<8z7-D-y&VaQjB_q-JJ^Q_IOR^wEmax-%J?%)Kr%4y+TYlN;k zt7rgquYXL9W{_9|?j9m><@EN4E1fu{4<%<=zzeKjHxby}y{_djk^Ij}`Iy-^70Rh3OIn)KSv#TPVOOsdD+2F=Zq zWmrgPgfE%w%J|r{f2>MJQ?8yZazd)r=}cTbQX9~yEM2Q=lw@#{h|H#`#R@JU2&YsT zoRne+Gua2lp#qVyA9#l`SMEQXpIPWpX=h@*@*-wFf2V>Fph$GtR{9W&fpCCq)Tm(t zw);qU8U|~+OVI%e!DhXMU*sMits<3zi*pEiC^JP;pLXfEh{dKArG)vKH9IR*CCc`V z6ea3ENz^txs6$QGZ;37CA6YoTBg-4)yR=(LDN$}$E-O)zxPMBou&w;<8V;39_s6AI zCjxeeNh9@iw&)vzOi9S;fv$z5UfYwDL};6tU}y zFW``_oF4cDK7p-IUZ?nqT?>;wF*F93-l<@Jq@zQ2y|VUP5X>NFb6MDBkMG@>!+cKLMB=MP)p5UO++oa=kP zSWE{xwVRSTB-~WjC&0V;Qh!Kj@veRtb_+|{kPkOJ2)~Jv5R59!ALh<2!3iHG>HjEZ zz|BQBG>oE>(ug$cs^RnLl2Tv5srz0*QY!Bn&sjh6XAHB4JQ0rra1IUV!$kmY6-0)c<66HF zR3FcJz4H)}h!3rejG3oW=K-h^zf4`WBfsw-J>{9Q-^e%gh(o1Z9xQg%56KrB!$QX} zM;9kB?OnpaLL$sELK~fwJ+{EEKB*jG^!jvwLo$%yRBNb^v*NI(DP`byf!4zu-VtUQ0SWYI-Y03&>rW9wrt-R7#WSGa)ry4_AwY5|B9n&g2WH) z1-0n*Kr~r#I6zOh98@%=IzCSFzO?%X;Mg}hZo4g_!#a|_ZAL2kB*oN#S|`Dwg1`II z4tKA!HncPX@UcM@$%xY2vs6Fd2<@=y_m~~!b@(?sWJ0r!(kOsE6-dZwoS<{sy#O%` z|1jK^fLy>(h-L%ct$y0xu!+Q=(xllt?k;z zA-&Pq*Bp&@YvEM=K3430gL!ClEms0H^#`%g30`xh78GtGti>{KwP-x}>7T}G9l(aZ z>AADcRyZg3Gl6cU3R06cNi4wnY#`v$rKkwg4;O&w^_lRJz|WFGj^3x4qk{LA2iPbG zlzVZW?p9UHyYk(@msZq60rnAe5QbmOu6;wV_L}ZyW7o=<2o7;y0rD)7wK-8Eif=Rz zx?m%q;M>|nDg-s4+L~?h9OXqGi0|hIO15UV%Q%;P+FsKSc-ty>Vn7DQAVW=_o5 zw(`jJr3_wm6LZ0?JEcPlr=stQAVAUUOAq@m=O7IB7ICp=iMaRIe*z2o(PE# zBXCPiU6p`KPpMuSEvIBq#vO{%is+0V3x9x9k}Da5B!cg(3eNyzZX0tfm$Qu>I1|{ zDKe}@P-4x}8@kS)V2&KJ&f-#jd$D)|4>Jg37z$zFID`kKXxHImp!? ziS6vP-^QuD;2se|Ub8PpJ^x0mlqrjD0(!@?;2FE5Sc$A;O~ZB97lLB=)*IYcK3NQ7 z{M{2qgZoOpP%SpGHVyoS?9Fzzm)`?nDeep3d;fwlPNVt2YVx0{%m=|R`SXYb6Z)U3 zI`2y%p#kZn^E}?MzDG#b5%2(OSs_^PJ&^q%p@h1r&xI?6)Q7R_2C79P^b;ykZl~7Vtf!KK6%A- zeW&dzzYs%CWy?+HBqKBVq&>lN>;8~V6P#pdB99%?`FJKFQSrk87bi!Rm%7P9O(40@ zV1zWS)Fqq?NqVuZRbK)e0gnJq8lsYDaUaTFhNwlWN&c=AXtdMSzE;TG+j3`2J zQ6aE@#0@$2ryzt?#LK>dywsC?{s)m(&9e9OxKhpXCvN%EfmlvwfdVsgL%ds9^w;Q= z$%M~kGN;TQ5}?PBky^zW4Wd=rm4eFOEkawuU{X*X%X*}_?qixL?3&-LR8O}!AKmcn>7!cqU*5M1a{H) z5YTP?^@c-uD7kGm*o#8$G^=DG{f&*8z?d+>T1u!IDJZ%Vxx@#YAdwQEsf-7h%Yb$v z*UUX&&e$^(B~fqF^p=@*%uoI}9ZFcRJH(4(8oJ6Ly~NoK(}Jj%v6_QeRDG0Gbge+P zmV?sm;Ye-UL%?m5-Ma4W?&yOtL#EugoiIjM^eMVbN5@2V;P4y*@>Ha`)}j8o5Ulvq zjRTEp7no&2lZk>ERcra?dV*-eC)#{(n0T8i4sy;p;nZwGUise~(8pC5N{9m=Z~8eZ ztWt9(OxSiN=d3Eg8rj~EP+tR-7;xkO&xTY(yYQdiK4(+nhyv1w;X4#(W zWR|J2hF9Cuq2G39(4W9&^NRUr_`OgK?qtr6RIu{6s*?*F8}^pfZ}(I+mV_+<@Lyt|1<2_6s z>v(dWlUDB)#lz$l07u6(v_RsC#4h{sGXDS+$aoyPllZ8#_VRp1iv!L1XUBVL1tFP4 zo6zF0<33LOH4z-9nVPilDfHbL76gtoiMqTJ*^N)Dw05U`F4xr@t($>XB(QpKSO|Zx z`2(96!D9J}8f8`?RYY%|~T7hQ2=pXeRFeX!<&3x#2 zn=fBCgZY?VlEOv*3U1D)P@up5%Npu^+Gws4f>ebKC2z?~OS6{GI{uT=LSQ#CaPUtUx#u_7~GxL|~_8{7c2y!g7()Hjf3U z+8XEtH~@^Zb23^vHOc0t$%@6)r}zt;vz zN0(}qujD*JIF}mit2G^`QXd0-YyJ9Zy`>;(WQ3@2Y}{lbhQ1^Y6!Wq=mn+@$DW2>P zn15;xF*m19cD&GqmJhf}>{k6+=yWhIE+jPOZn(xE6G>zXYYE14Qd;(P)n)x=@6o#F z?!bY|6?4W}WN@;G?WIb;$`$CLa&sN{PW#8`@!z)`ARlG7q;v9&wBui9U+XmsrfLR% zb+NFLpXCuw)f~FDQ&`rS+kruL zZXCVqD^{+n2}aKh11z7e?@5gJ@$9l;3u(?u*m&B*#k$uTqH*V!LYwXgjBVt_S6ZEL8%xwgh%N60>#e>UmtTY}A0L-fY zAtS0`9`7c763ffCo%qYWME8+5dzx`w*Zqi?{b{*R>;z@#A@+`{v}g`)D(pQf>6$zV zQyvyZH+kK}y&aZu&M!?6TCB!!{s1X{;_Eg(4{4T5wF3vFQS^G2O+fFqg@}lEgl`eS zzQ=*t?4w6Yq2l!^r6_K`T|4v%h!O+Nv4rjBiV7pPP4M(A<4$+{Gzt4tyIskeomtwO z*(iIZo?L-aQOM;}u#O)l|Dc1H>Gj6_YO4s&^v4BkCbP`5Z*PRQo|60q?0ZogCvUH^`%W7-IP+cVLq#DEsxPTjN_Po8G01Sgz!>MI1 zB1`x^R3ncs{B($ezR<{>i}J+aZC{+Lm+g8H+46pPub)+lm|di)4y(`DxpEfu`^}$WN-gq_=52RzN)uA8&^H_Sw`rX#|g3)~oInmaq%{D&dEw`H7~>XFTVv5&FALU!gar!r_JdQeiVpheY$*Rr|T#E zpA27cBAM))%HJ-mTbO4SE_$F1mn{>~R(lWqP(&g+-g#2YMAK0XHX(v@&LQa;g$&ja za1!&+vVq7#I7C*T3IPgv*(CFQfDo!G4L`E93^p1aB1;+bj&*q)@}r-m<%EUF5Q{(H zfmWt2>+``4YsKHG&TkODsruDqauD`_4Lgk+4!*6b?^eBzhWqf6C}Qt7l|boY;1JXm zyW^~e;~sQtaEQ2GpWpeSs82lSq=!&tic98p=M8O9^rmXdpU&vl6Qs851{4_OHMl;_!w(@E`X*g!PcV*{qx@<%CUWyfbF@+~^b~fAZ3^THlJBl_ zb^1*hC(m)C;Ot?jFK1m4mP+He)6sK#1$*Oa#GDd`R=pF7^eWES(X;4lwuzR)L@;D~ zZ1)yF+3;|@ojr(mh8q|78*eRPGpKM(spwH#TjMNU^XuJEEH*%+aoczl0G5f=?#o9# z=nMXt{V#-HeV!ltQhyDg>riLnKHP3bjnOo%*sJc)?=+uR!(T1`nE2x0*WPyDb%YDd zWv(*c&%-j@A7%ta*#NT}hMUGHh>V9j(0K@M+MPPem#>uC9=6{8DF&U=>)@jvoY)(QkHc5`SLyyF>9bgXST>wf|Rg4XEfEfe+ zf(lYyk4QbRWftu}mRT#o9&Q#JP^UE~o~#hK+;zP2%O?#R!(!c4FzKvkAMSLI4&;^R zcorrftDb2uD&2IZqc}uAI{FZ&)h?v)mj6a*V5PnFxt@5(Q<`hB8I%e+?fc8)LCz>iM~NvZ1h$WQ-A6)RZ>Ws`24EKLIFSqdx#zlgvs4@OF*vaH#!J zW8k~O_74nT*i5EhW(x+vpYLS(NPro|L~}}|QevRbONZm(qMUNMYK&1=6V`f+dP9*| zjj4^~Dck2$I=?5fUC_2pZh6Jm&&5bMq;%JnCkhK`Dv{AH+sBtBpTHF6XBcfTP59$x zxdudxs0c}0IwzNz$+oh?a>7wBU0D7sr+lbR?7$+;2Hi#g2@o%RG!Dby8 z{-I|5)=9rw%$Hlbtj|)Mkfp`O#@2e_qHvY$<@N$V#i70w*jlCKD0J$Y@YsNZM(G7@ zCdK=PRyl{zti+QfPtrP88Rns?NR8A2Z2S1A!#jV)&{EQeD5w+4W8|X9v0dS+(bC(3 z+Jj-15?n2f)OZ^TB2F%X1fuDS^Yvs8Qn>g`r>a;z^wq2YVQ;JC);p6s@9~}UL6AYd zgrQT?_^ow=_85+W(M%6>>W|`jrjlRsl~x>zt#k7UZ(1zt8f6pcUT{cc9X+&Cl`J#H zOvcooVw>IEy>#Mam_0^7eLx5V#NzXe63*(c9&jS(?il!JX_1e5G zKRcYcr`ApBAoO}3k{ zs=$yrsUH&$kG4b-trY<6^Jo2B{!cg@Orkrz9qo<)Y^9#wKeOphY(3CMSxl_vqZ?U= zl>`?$g=<*oPz>KMFsp}onG}r!M)JuJG0_$`?i^^NyX##^rR@04ho$$W+k56gBRi}M~T9xo>PM3MOwG}NhIqucblN1e8C87 z>!=!Mef>u77E<|E!{!DGpmtOt>F7J3Pcc-Onq;gE?PCUze~MqrIzjpp6MSi8M-_TBe)Fy9@i1lOK>(csDQNxc8gJ?CJIdcnV14-|*DA zq)J8R;}0AmTHOl5YG?gm0WF*_@dF$wttUe1v%!@_3rx7w@6v#oX1`I98~S{(@Ewkq z31bN4)b=;te}++p2t%V8b)F%U&7@A!a#l+`pDR;IsxcT^ZmiO5kzez?kOu4=wu=KQ z3O@LXsY{&DJgEbdBl&#tE?Ev|@uT67ZJRtf9=a&=14eu*UDf1guqE=)3Tv5K(}NbutQkXuKt`=vSVK2yStvN94b)N;QJ$B*-l zEU-#3_O9*KnK9TbWXaQ%D)Tv!952drWJxV}N zhA&tFG2~7s!tf^Mcm@u<;12_vE>^>q@Zc;II|Hbf7-i=Vb!c7cy~t<0(5|w$U6gs$ zA6wHo9OTVK77}17Ir2JU2&O)(=f+1SUBudRsKri4j?o;*hc5(|V!D#k|X*sj2e1bGt4hdiqCACM4%-Qeo%FpK6IbG>Uv5Ha>0 z>SMmk$=dgw-Ni-KkdP4it0uDiWo05bk&h~HZy&#_~ zPHxSJN2aBFXEq;OxEbz$y>nPJH&f(s;K40|vUfAsPeEbaP%p9+j51O0bkQ5^FB5B# z2Z5eCfv(iZyU>dARRmIzy&l6UVy$e}l__jpjpFyE*HAT#AYJ=*MVKmZ&i?ZwJG=Ke zIFi#tzVr6x-Q8_!*4Aj?JImE)=eeTuOPxIkPJcz$G(IM++$(D-RidiP=PcqPl94*b z;~002lnC(!ALntz^qqz=p2oR8R$|1TkSsQ9=||f|g@vQx7>u>?qQg96v2k%wEs|%c z$edn!;HSKojx5wlmo~;GRpQW`?eE?Bba5Ad|Amos#BcgU@35Abf<>BUkSm$Qs9jJg zB#(XhNOvQi6xeaq=@YfGxIyd2&%23>NBy1v1!C`)G7;!5^F&f2^RYlW!%_Uu5q(B1lKCIcbYExLk3z~`(B{`;_jTS3#GX+iw*Nu~D zpXW!bL%cvY5y)G|VR-am+3Wo7GYCnk1M(+YR}tU?ev>F&*rU+mJKaAN1sx>hfv#F) zhsQcZ0s9+RpUGh$S*(`h^Q_~3vmVfipf9WZ|VD3fg(lgqFbi^Omn+hzpti?BYxo4T+= zj~<}!(B_+y&%nUW2{xHx;Re z88PA^r1SA*Rj@M{2{JyX^*V0Zd1R<@AH3tp%I?u=kb=8A$)?*H^AQ>8H_~abx?#2o z6dM>WwYLK&igsAiI9PUkkj}Rr5ScC$*Flweu~qj5POYBz#nv#`?Wr)d4{e@5N1xd* zpg)n&v$LUWOSiH?z2Eo*LGg)jA=o);jQv>c_Yk}4QmSJ`efn^4g#A=9~lmd-G#0u=|trH z3^uVm7{>~)ltbPcm4oMR_=5!XRQsKsOBIc+XYX&`!?a-`RI(7_WVWm<8CQIq{DMeP zlRUy8}KAV^x|EN+eAs6B^lSkSt$9iziQ|+tqn&5P&?6C z(3ty}X*F2f9o{Lr?{yF*&07lN7Bx!&=*C9&Fl=j?qy=#5m?xCj2rM?1tqGqV!d}e6 z3lZE&0BRY$LjnV37{fvX8!-u($7Y#9I4>Bpzu}2upKHB6(PCzoyjE+tcnk;t%+l}2 zh9uW!4^FE#5PB|suYH8thNLfH%CI{nWu1Ix{Mj<`9-5Hkj_x#u&JrNMC%mx_+vzV= zDoZSJz1SW%x;@?aeuTwp9Z#W9$fkvuP!gSl_x(xx*of8b%U_z1H?Sz`;4=I1sK8`M z9RCKE3;5zyqLNd?^)zFv*5DBWK%S=s@_B1#((dA?ALm-t<*Us6q`JbPx^;gJIv@Z3 zA}*WZ+0nExBj2TujPZ0+78wL}W#Vf7SW0jVK7X>NhL*`oP=26;ht2axQfj-KWtC)D zro-EB7dbsM8>J$w$(!3{JL~b%ys!d$bMvtMSgEU|G?Y!u5AxHopsp7#stgM-z1ApG zD_uLWMlbEY(5BvqyO!-%V~o|)8Ya^Yugs6IQKp+oYCF!a{rH1LYVGnz3URwq2OVw( ziwRa4cYKqt;^(&f{(K6^3>T?J)R9G-Q0#z@~l=eVaV zM32b&?b)Wcg*})U1*Mo|*330|Jvd`9o5vqH$gHJvro>I(>0DPHN9{utn&Cpp*I#ao z>BO_UQwu~TMu|=4xKdvQ!xnj%G>K}4<}1@78Q85ls;_)3M9@emI6|KHEChrmKinVZabEJ=b|A^0v~8I=9BOr4 zCaG~27UQe&m+nGf`rEunUT7SPtq{^(x=;FBJ;~>NTAySHwqf%=z2OEemJnf{H)M*q za+?q`|CA%JuU})-McjVx30M-MT7{67+xOCluE>^%*2#<508&7~{O-H?{3Q4>N zPP;rv9zI!EP;&D*re+K{V+xx!9If0pkmk(A1wu<{1euiJDgpcl1}-LVm9k6~BbhOK zI~(V?02a`w4i={5t6fcHUn}m6hy)17d8l~=#8$RZ>1tsI8U(o3I31OKT0|G2y41dD)%E<)})5(>VP36b)|+_Y8XmF)>6kabHqEm|1|cq zQNz1cfScfiG3U~FuO;jL!Ws_aHn#-*MHk}cY|mvkfBmG;+O-zH{(uS-Z34Yd$SgDv zC2(gG1XrnqkpA_~`KpLirO1mLQ6|WgymitE0!43cE%_l|^V;ZRPe0|)8E;u?M2XY; zlh$E2RPm^M+*ptY`mluqF0!q!+p-P3{jJHj3(lX3m)O?m&iVpqUY+y?F9FVmA9|~BbEqf=|!PU_qYushlFAbAE z5A+|%T1H!PRhYaDz)SOBZ>Rs!6ODkWr(BZ_mGg`4E1b+w}&HwYOcVNgM=a!y=(==;|T9*W+Db8=8H%h=%%%8cRE$f+O<` zix3hmdoLTGOF7%~t~QId)9gtPir@b`P>HR9)n-{2hgXx^Dhb#I^p79vB|Lp_v1!!} zHb8`Z+iNq~s-h-rSrsMSCI~hC<9uq4cl2?C({PX5Ls}@X>BVirb=ln<+J5Z^d%LQf z_r4X?*3}ph2RTr!*Lt+eeRMk3GoO z?5NWlsG*t#IOdM5K^4nQ{hpOyGCQlgDJ?qW@f*ZW8?uqa#O*Qy2hut@Lh~m^KYjVi z2Zod1nzVskvB&kYK^MAzw`O4V89q3*lxdjuOPVA^kAUvC2Tn)=TP+Yu!-?q9H2wLn zCPBktvJQr)j`Ig1d6q{3Q`QHD$den~B^sTd9y)=}*n7=X*}Q`7hYW?CP^#o2cNK z(uXQ@(ic)-t)lTtjm)Tjfdt%}_*v=RO|vurG2^uw{^xn` za214+xYV1=3O8AcV2+!I4n053jF1x8{hW)=lxUGf04?U4%@6l}zTm5Rpn2Jog~L^? zSuTP9#?uG7Z)58iKSquZ(tyTR3{}mf^FoyE-uRF3FIK+pN0}_h{Uo6Y(aaA?7RRfM z5F0BAOR|ML(;yPC8!OClZ56P!TqvlCy6OBGh66%R5B(g>44*}DUnf_D*4H!LosZF3 zWjWl7IF)BNlp2|jrqmS~x#&*_5Z-^#J@A7Yl&rCLgwqMz9y_~T6<&NCVWo@nSmrIr z8K+Cs5^YS;j8*ZOk?~f4P%3n8s8}9B6VJA833RVAHPblsCx16%qfsHS0~r@*Kb8Xr zK_|ZDEX$pdUV`gibfj7rG)t=iq%Kse)H5NH5D|B}`mTxEr8tj!tFo+oU^uE9gW8VQ zWUjixle)PaYWSd zj0awY>@)c8f+YqS9iQhNCq->!nqL;Rd$KxmaCI!V2YI&$;BKI&nCb7L>4Yzi7GqN# zK5SfKS6HfsTOpr&bIKg0Vij+jzS4 zdajI0V6|Uz|DkqUlk4;QV+;O&nVhNTCJbw!>| zPl{KVCEIpgF4WU^KJ~Y7M5fopGolAs`f14xl~H9+mG{AqMyGE?l7VUJt27?W>G-{* z+S*-RDPJ1tTrRit7(>jv;7&Lf4r!`1?|JJm7Es>X;M%~2>&}-ofn2d+Avyeti;Jb= zvX`uE##5OKydExvDT+52bbJ#0Azwhf)SpvV6G1*L(*zqLx1&*s>1=LNgaSkto3lJw z$hlgnYY0daxIVJO7TekkdRIH=$KTPnFvf{io2K|QI$%!BZB7$~ZdJdWeT}$jI5N| zSUFS$=5{UkuMT}LL^t6nUdu<8;`!wc!b~$?Y3SiV$TX<79b=%THU~pNftOGG+Pin1 zuvk%EuC%uG&3T*=y5UTa*Tx{BhIW_LhIhY5JyaqXA?O51HW8+%qZJ-OYcGr+n+^{e z*Hprb30*~!3qEGUr!SI?U}ESSJ?f7?xTn!PRyt8@yBxK z9l5gf)qTiS#!s)wBVeWKG$5BG^7}4?&%hurlg<`%L_07vB$+3&_hREK!Si&faCZ%` zk@gyv1XD|UNY=S|c?IrQ`=8O#la3sqE;_gX8OA-DMA32Ia$Dxfm8Br~#e5AYVCXQ@ z(oG<7AMD!LRh)a3chrd99SWg{zXO{~*@?d)!t@I%Dk4l1#g^WoUrfi!;Ii%nG-FZ| zQV5Lqz8i>Jo!JnF3wZE@h4#qXh7NGu8`Y1>2Yy1DK=X+V98t80o%N2Lxc(q3{^dMp&WB7(xO&3xwBvMLh!auZ%l4v-lzgvo$qn?+Id7@v-j$l& z1=Hv=Q;fPx93r8Cb*?eMwLt$l4+mJHPEsZ0HZdTU@=V9)He8j@L zd{W;w@7XbXaXQ_Xh8o9>=K2cU6)U6`TFe6GjbD_nsO^oCu?Hc!dWojRvhk6N!JE9p z)igIfY3F+nP(q4ey+Q0;mhy@hv4T(L=!p|a`XZf?;}lZwT49Ftg~wARrO+B^xZ2c# z*TqRH+fp?0xbQ^Oto}zJsbSe1?N%E0)HA&nHnK%Kw2HZy(iU6*s>5kGmk;h4)x)byh4Gu#V|T7<$n; z-Se$E+bJ%3;Im#K-w`X0lS->b&GpmcelIpDZgOZZpcdM|c#JaU2i)ySIOVz1Ek`Dp zT)J`h#g&Dbi-e|xKu{VxE@^>eArKhLY~|a*U%%pIS)>?RY|i3gE}Ep?VYI~kz{I=n z88@%n8kjE{>?yS9x+Af$wgpoZ^=qih;>%`UNyxV5#PPwRp98*7C#QvSO$3rS`rw4T z&1_E!{D8yM5${c01Z>&(@2>|-WHbDad=UH+*)ka%?5$CpJnt5Lw832>DP@Dj!bFUi zdb?%e{_VdVV%fgnXD_K=uD~Vyl?BjEOWr03;m7wTjzjo@!ZDe>2_e(juX`+w#j^^R zVmJv_5PnJnuu=q}Zft{6hHFvlsG0_y-xY^ghKM3-0VACJL|mqXo3nK|F15B#O#3ql zR`Y@55cmq;b_jK{xUp=+=-V8?m(SX^`J`x;zU)2v(2@x*9P8vDj}LGi-vmN&Nn{>g z2Zn!>bBuIfo>-zE-=cizQbT~{GVz%2nBMe$;88uH7mw5|8(w-(H=VT*F@rcfm+z55 zRW4{*RUi>4*){mXGApBlLW6QJ7;w769qY~(hYlYJv^VY0ymP|3Vv3ATxFK8Q(#x0| zuzJOg*12nSd%k14^l1|ZK<^zc)j4Puc|nnk0(?GH-UJ`p^1ZY&wM!XW-}V`7BBi=q zj`PBJZyv!RA;-!Zq17MWaCX|LG?nG=5|(j?v|+nIt(XJ{9F;o7xV1gUi(`=*er`eh z?)>9f8$Bj>8o#ha#Yz@|)kiz?;H;8Hh@*GVzz!LWx}~->hWRHqD*I>+OsRmYJ8>kC z#na^66}WV**K+YY=&BQm$j2s&dh*g0fEEFPM7pv>!tDO#>=cYIl>OdNNomJd8Kk2Z z#wtTER6ADm4q;5pcPZ_v)(fzTasdTj;oP{R_+yc?EMel}xh4@snT=#aGPQ`PQdk zWl?O(&F1FiDJZ#_ik&>4LZ!=_v^zus5@Hw%Mn|6WJlZ*URNDkN;cC6| zHK!~PXk|%tV&26(vL6XW8Y4>Msm9Z~EgBd1$!(-l?XHH4a}$@5Jw)8Z<$)l#mBBR$ zA7umZ!ew$^#%(*|z^=~sHG7HXd?Q!Ia4R26%37I91q%&Y*Zywht`2kF4iQRT1>GO; z@sl4s4#)6fje)}o-mJkA*o#%(}?b#d}hOgzzCpQ^j4q^pd9 zxq9Pd925u721qx;f}G=Q<)MyK_O!UD{AvHq*UI;{*>IA}Z9OV&E2zPMnaoTBITH6b zk-!e2KBPZ4Rend?w#%M1JZh-0 zrjF^|9M%+;qm)wx9rOFm;Z=olzT~$^BLyxfaN2T@CRGw|s(2CRkn~Cw>%<4~qd1$0 zIr(~=6-mBWzIxDB8lv{5z~Iu~9vnu&oC>n5ygDSwKc(p4U~SvHk@=s<3RPc#LdivS}}% z#{eovgIE1LaK&eYQNC5EnIbA=&Tl+Wyvp4RDn||7;CO&n79J^RJw`P zXX=bsj4cRmv1GDM zCNtTO^;K1q`}_MB_m^6o%8oj^*8Jx$RHW||CgYWOB>?IfTTcYQQL3&N(MCK+oK&Zs zobYU5GTBNYKWHYA%n1dUu@tq4xpa`)EQMlHwm@k$- z)M*IxwJc)^vHH6P5bRvP5=LMEd~~&o-<7)1jR9ILXnz${1SP5o&AakM2}Rq zN|iRdIZv*5H*y15^IC|zMX*|9AezWQCX-GMexC41?P?NSk2l>-K^51(9#;H;$d51; zddE}m&{gl~BDB>drLt)$^GPW!AOYtgBZY6gDOBvJuF}K@OWB+h@6(e^W(u3Y^J>Fa zhYihJ#Wv4(cUggV9%^KE=-~Ns{>dS8bcwifEYxeI#`&dd+C@{NslZxZHriZ{83aZR z#^5NuJH3orn_*-}CA&<`1uA9m8r20t14gfaPBHJqJqo<8toQXehPE#KcUet_9Ipo1 zO$26~s=NYVYgFjlYq4d-qgnE(UAb~b#d zb=|Tq(oX2Ae3^G0yDkN&bbk4&c8wDiH&nPvdokXB;l_9trkOKo-prR=^fYV(UKsX$ zcXoHifN@%)(_MHlpc~C7m#x6=?w=XdD)~o8u73v8`mGf0P zI)@#iS#H}HZbuD{{8WI1WmL<#(gb3785^C$&7Grb$O#SE8~b_`c7Y%#FEMLVRwTH`)0PSSKo%-3rsiu}EY-c*>a#VJht2 zrR2tFoWaQ}lB;@0wVZZEVs0hxN}B1TiPc#l+{EZzD;&FCOc0KMHTyW|$aJm#-Npez z0d~Cl)D=Xp2ICWFy;5F~qBA85=XDIYMM#FlPwk5(+0$#Qn?Z=rVzEj+!*x=A{*@y* zU9QH+Zrj~v`hH^H6xi9(uamSk$k(0(z5E#21=u{fPuDWA9NTj{<$}7{=45a@eD830 z%SkIV9?HA%+%kl+&P)7F3`Q|hOtxUXq%G1QWOEpq{+LecKfAWQ%BPM&trxGBD7CAt z%Qdq(gO2+)o-*wr6_uSFv3;>pUmxcyVe<7=;~RTFaNtInm-?sZAX*Q(POZz3$HJV8 z$RgY;4@pE)xSf_dfDfeJ+abEg#UpkIC;d6AypF|?kZv$TO)6_NqEBK)A}?E%?3sK_ z%4h}sa7s#s+{}|sI+G)wIz!827Nw|SBd+NpdQlI=LQA%!snq5kuGs#`b>yN<%G!j( z>DI65;##Kewq$?dS)K+75z_5((n0nD7Qxo8<_E%IjTTCya}hzqW0UiOH?xfg4%D|` zQ|stM(F|Z`JU_+YuKo6Kyko^2j8K9K6rQx@J2b6espLkNEqhzEZ?o~Pcs*uJt5Sa2 zy|8OlVR)FP{RBJ~VY7A8&D2!#pv-!-ls(&XzP5KLLJ%-f7I>JfoL=P`O_gAIoD9A( z8kO=-MZ4p45w#Qs;aBC{!?l}>j-AtS6z{XDHZRZgA$-4wTlVZ{x#GadH zil#G)^Doj+&dSP9P3V93i%;N)ff9?{%Hz9FO$VtMH{j3Qitesw<{iuIS8XX;h_koY zc>rb0cw?R*Lqj{u{y)aP0;;ZLX*UoE2MY&xx8M*UxO;GS3&BDN?iPZ32p&90aCdii zcbDMK!FijRJ2RR4|L?80)?TbW!0F!IU0vN>_0?BPRN=Smqp{|iqdz(`wPxpB!;??P zCj*s&O=;$PHRC_78~PN10YpMvE9lO|L# zp3|0jbA4HDDatA+*^go%%&t@p4a5fB?vEVx6hj3sDu{@O$C6mj6i;fPn+evFu*X`*?Tu?dw;fLMk4@`6iEg zfa*gEh)&Nk?vMQz8%wn}T^tu39lfs-mn(C9x(RFPe($wIW}cl4+ywtbQa$}tFaYRJ z7?|U}5t4ehP?R_h+u~kdTqx1x<*BF(v8ssibW52_6r*+Dhx3(RlG`s2`4*zNIVY}@ z-u;OxRcL`1m5}HMV!yYi!L<*<{*LJ$Y=7Iew_yM@7efU9O%5**2RnpvY-pjR^o>M5 zR{+^k1mB!uMIL&|pZ|QDOB4G^2neKMPx8v-AIOfEy&XEWi$8iLv(D_LOXBUTe?6e~ zCLt|(vhET8nN&2EJm4T zqb&*Fg|$bh{zrW~O{No?q5Rbk$rc{y#X5&5 zfV-fz-sf!PRRyf&k3U1oH~75MjW|FLe{|_<902s(qO?p2`6obKvgHV7$?MT$XDlmZ zdG8`iIEnZm?eB2(EAf?C)6^LJPc^O8!nM|HbDyh5{-%8a3J53w7lq%pR{77rhm1mj z1m4%*zwfB|k&!{WY9Q`mwSiM$PUs(om9S^R3xM3}-JI{NYYPbwh1q2wdt=;8M=Tnt z{!@)0G$SBc4p9Do_zM5TZhnUO5+G=AUjYQ-P!IFHT@?yGTf6&PTDWT)fjC!KSMNEs4z$eq055(z;6c?yC zD1Q-CJP9921{zHJc(Yf`+pic*kjY65JJxc?U-F;VYZXfq4MKbUX@S$__MjT}$Zp>H zWfJ1{K*-sqqnC!jKNrrgpO0XIZ}ogBe;+eR5@vXkjRWdbcd|w|NGu$Yv*6{y{5Hg2 zEmuiQJPeQW`3t0Dx5a|LQb;4PdXtF-_mD^Mk}cLd#i5dm_ndC_+rdfz!~mNBDL@Q> z>e+aEUl0w`K&ik*)-f;QLh#os2r`1xsLdG#jbnZv*lX!$&|BHEi5ojVQ)2;b(aiSZ zB8`7S+IxaYQWO3^-{qvV9u>m|lB7y+gfKOI1q_c|d{jL6irBlNix`sSB43UF=~S8l zD$q=8UCKW4UoAfcJHL2Z11KnZ}OLy+*_gr+-q zWOx6yp1$%yi-lmZLzV#61Xa)50}ealr;Rr=E9+W?)HdvLXS{o%{}yt@loR*o8dR_b zjgZ%`=ty7s!kqd56i9bzLw3Kfn+;qjAn+duo*i5cC**lQRiIhseDZD`wGLn`;d0!M z&&7eV?YD)BKp&!SBgmEZ=|jpQZ5` zNLc?t&uP(N>j=OpoC0ClJq<2r?*X-3X55d(W&i6d1tn#|4WLdbsi?&9*vv>|zq3Ua z>DB}KO!p2;wQW!QOz=-NmkJ-+oDno^tnr78+r9I_i|YFBBLAATKjTg}o=Ccq(B8YCz3PDZ<)IYF#9#k6LCx>l`?s=? z4A3xGpZCq*2|WK=bN|dU$uT^0wAqS2c@;A!$eJFu+u&$=J`5!I2Q$Mko{eRTILJcj_{Rw`Q7(PI zv5BPz8Fq;1*0kXETAoLOTdVvK$oZzwTFrkRsMVdk#byhiiRcdkC~wi0ajTmAZH3q~ z%Sc~`!QOGQ$ZK|un)jdB1)c;?@DQ9P`JG2c6TnFTAOCs)kWvwq-U3vw9PUK7?NIJF zX#?96w%&t=$)ReKZ9LPTNJB%m5A`oLc9OCLYOm~)YCkbN9btfH0~>13iNAiB`g-zH zN*v7Tm#4{$4oOl!Ys8U;q4UOcIC?E>ReoP?Z~#3|NCNYrNwuSb;?m~v-uGC7SCEl0 z5%nv~9Si@RqJ&_ZVTNRjJj@-Rxb3>$&d;LnA8a!`9!!1>Ka??|fncB?aL6BW$iW&V z$P{0-VeYv8__t!uzYoniHp4qE(APX`dBY2G(5-SUH_1*@DC%vw^{;aRHj_pvu(wsd z_=MyiRT!m~*%9~eG>(@d11Gmq&%N^evyrF?vJUmXPY(YSdc>fYm+- zn8H4wmHYf7l`9;85#`?h8|L4;9xyI2tWa`!FU}^7=KnqUFbL1R_wT&49s530_H#Xo zM@n1U*(}|()yI&TuQp!pbHT$9c<(P6JTD^u2!`K)h6<0+`P@hQUzPkOO)v-y-aq`R zw)Z*zna{r`_J1h|rc(&Yj|scX&YJ(xMejDr&r3OeCwb+=|8(iMW&P(bhn}`lala>l z`~7jJYU#w`CpQ9U;1Zs7Ck*%ese^w1mn1ZR>k0$$KT%WvdZEu=f8cjiB-6*6bu{te zXV6zT%YU{)T*T70w8c($Rd`YU>eFXpU?t`f8;3MN-ZTz_!1ih)jw^!HD%My-% zZ=kQCT)$y$#iz0407#IIOGXGWnzo@7c z-)GoHoZfA+6f35H?0LTE+x@REB0TX9`dyBdum3+L@Si^^a01*_3`x_;jsFmt{hQM7 zKd=3tU=m^JH0SgKE}s8Q|GP*U+!4y29)IM;gvXErUeE8Wu6+8Rw!RVqOQT#Q?6<8R zIP(zzO9QJ$`g^VjD+giZW_`Hy8DN+)KC?}@yk2kb9p8RGF~DiAN-oT3p`@m^&rb?) zkM@1NjLFnW07v|kl;xo#9-1S!{2<d!D~N1`~c#E2T+0<%c6)0s_nox|ZhLbvdwZ&q>paU*DAn-4^jz%iQ4WzkpIt0`h@ zfAnZkv-aT!f>O16{IW|yFb}C$k$kj}C7onpJTq>ybF+cY!faNs`gU2@=87X`miVl~ zPveE;b{k^anUt@zsu>~!kRfwFqBowkBziAXB<);O`ai7Nim^UEn$+^9M-l#7@9;t~ znNybO#`bh#%tfa}rZYa`pi=hx*QH1h`dSd!P5A}4X@HfVgbteM-D!xozBQbMl9J&k zC$Cp$NB>SdI7??~A@;Ud+&HP-c81$<2Z}4jRovexf45}2O~PrrWH|tBdnh%o((PNO z@kJma=G8GH%XNkO9-azP5`4@;3a=Aldq}c3H-Cd!oqE8=B&W0_Hv?p+ zqn2H2x6s*J&LD|Uii-haU%XW&k0V_)WY~;?iGg*pcHg|dF_B>R#^ft~n@EV%dS6zS zwvU;2k1)CS0@df+xlC2~ExnN52WkAc$ZwtZi7p!hwP$4}R%Owuqt9O0*cN)qDV~hu zOY+a}o|kyct-jh( zBSP%j^QTUPw9~fr0*BR}A}*^{YJ;aGVeX-@{l!Gp{f%}h#B9pOk!>@S29HQ-KpC^P ziP(r};VlLoSym?_P7*uay7Uyk#W$yDvf5yRP!AzFq%8R0Bwt_XVvu1pZ!j9UmUGPgQ<}dC}C|-kocFR3P zFl;C=gb?Lis)eny!a7_wowmFLD!%r>A^qPUj{x%K12Wm&=nz`jMwA zLL(pQMo=B~uu}_`4Lou73%T9Pu$SDJ{ z_jRvZ1Ft#EK=MEWqe-ZZx;LAf1$8p_8G1(ZFSGF?Y<%r4^(rgQKWcROI`9>eEq=Vn z$tzYwo6G;YZkE!g-MwD3e4$tC;uS2qxAx_w1DR}~!M>Bz%Ucg*THjo=NP|3`3`ES8 z62c!T<=yt_V`13p7{0v~ALr`Ysqi{U77u$#g56&`6{AY7X1Q4I;b^1MBMCVhf}+7) z-&JTBboV57YR8BDJc7r3iS%mj;{|ovW&)nQXUbfKtTOSvVLXX;Ex?R1!6T}*xJR7s za}*BX2n0VHu4-cF>-E3F5NbwktN4OHtP?Wj^E zwb+dl%rvy2Evi_Dr+Z7eU7jZ#ORSbT47#)YkXnQR{X_eY@4+`RK`bLVO6KF-&N{-M z{WzRAa|#d<_+qN-oE4gjr7MZttxz4YNz3|;oxSUM0`B>vb+eXPak{kKka-ZTv?fd+ zZ@(2R_v_mWbS#FjD}_??6c{~2#A3;o_z)dGho?64+I=|Ae8&15{Ui5uNSWVw7yFfz z$XZnq5#vesm^}dnqByZ|sFM|rC4NYb#Ug;F^5Q+qAPOzz=J%KAn(4ag;PZ4^ladOFX{4ccRTbG#wZ6ww@+u*P1>NNQbFAgF^3@t(vP?g>VD8N z`=l_1tnH$?^`~mMN!aKS;ACc0A4g5}^us3&y9#1Cfj;W9VjEUQOs){VYGLGt_gRpS zHC%(;WVF(n%7>S%K!i5W^q+Ot;Oj1s!jQ=^ShLTUd1LsxAVBVr zUI7IaL~9&FLzT*mYKhxN_MTBAU2ekXP8I~e)ypfJ;zm1s&qcmso}{_nJIT41u^fk! z3c=(13G$OmDeez8*c8vZ9=y zr&Bg|DjKv!WK_Oz&hOTV*b9`6(Y>McX8zckJSxe!33zR^{O~tq=yTWBQO&d0vM_0& zo7F*-OK-(6tv-(BiI~_tKV=ebD1{l3y^Y!oGp~nT{oEhRSkzqETPEnGe4KRt%Xo)N zI&Swij8;!L)z2VAfLDJ-%K#O2x6P_8U)c#4WhQu+CUDc}m^)x4NMYv?X~E#&ygExJ z7cCp9?XxfjqZwA~mcUdRc^9=uPtv z_PwR{c&I|UcKlf=%|_h6cO_VVvhNn~z;8d@iT5hxjD#DmNQ{dxqz9$mej5HFQ-I<5 z>lCjC4Livuea8_{jv0DNF^k&aXazB$%CC#5;|1?G`;!(*`q(BK!t^C2vAVCFSIc@Dfce~X zuSfwfx(fIei;FM+L#}Xkqu~z>`UZHqKq>i+SDiYeK8<5R)G(JoO1X5jU=E zIfe>gF^A?pS=v$RIkOItU~O)3md}|rzEi?Q@2#oQ7!d*vOcc+co~N!{_`8sc3{vFHm(_HDCaWRn58|?7n=7Hl3M*(xnF}MA{q6ZKm14qhe?}jtUjI>F&<}D?z0Z| z26n2VT;@>`zlwvvO>z9djyHtzZ)~+Y2ikf+5q81OVMC+)Dl!{#a~=()&lJotObofm zp*=DqJkrsZpUYjT(r70adF5aX{yddus^f|?^=-Eho78ZQAEd9ri6!|4@Ljd)3zc(e zhv(qXicip~mc>`7${T^s!J`J0!I+b8Dk-%7G(WJAQuGa8_3 zgYAjEm2Vb&vKEHD6Nk74JDh%8K=_gRc%2{FyFz>xj6QYCXZ(DXQw1B}J%9MF$LzJ|PBU3PdwBq|lb@aQF1N8!!B zzSsEno?Fx6$A?e1A2P1JkSDx~6WtFkH_vuGga}PtIwQ9KL0WjXiLSIg&SGWjl@~v) zkQG=cE2;&p4?Qzxm}bmSfs+%=ASbXO7!QdquT%>kx<{KE$~!19BH#^M)E6*3?UlZ6}V64DYuzXK+B!>9r1d6kfutnXaA zXgp7QCUS!*`1O8lc+4P7xdU&58Vgr2ypBw4_*ie!9usXb(JNEqyS3+2hFh2itt|0u zjbha0%(;k>u>L`xoPP-S5P2v+Z^zAFMV}77uurN* zXM#3NYVGg%uF|-^7j`;=qAgo96v-{O8j1a?OKMWE7VW{ZTL;rhB{fp|H5N~09%}k1 zLNGjxuYig*6uCgbE(?Wj2PVv~24JQGWEUCIc+5#Z69zUFYI@D$=W{R->Kked z#biuaat9Npd9Ijang%Q}bUxceMD6sY*u(k<7hLp$%MNDJs1 zoDJ1#G@ZG%Oe}rkSdgZjn;AWQswv1)b&AN=5qAfJwuOCj7PDQrqF#3h{2|Y)gTbaV zKl1jqB5B|3*OM%1SzcaDR;`d_L})Aon5rb9ER7!`MXGLD@|m0#zDqI;)$j+gGm9pB zQ*$6GzWToR7((Lv_|n)M*R%Y)iX4i3CIHy|S_p45cx@QHo+Eaf^O0{6BnrcaF$9IIY5EPZi z>`U%zIN8Wv>%;s$2cIi$A)&tajP!uc6~lXR@?hA*`wjoyBu98TB1JjR&_WP)4qtUQ zZU?pedO>Em7+nDkl_JWHFrqWM?8JgODA$a*?-m-hiQGF0{WBzHGs}6|N&6$FFvaKl zj4v>4T_Q8>e3n04-@Y4&p6HaVF@;!R8eJNeqPHE9slT!{p6mZ{{N?OD7`BnYDhOXK znZ;GW$IwPK2Fa#lZhMdJCQuT+|AH$!)_-;(i)_PKwr{5G3wJ(~>QSY}J`AbJaVKU6 z%TG?uR$A_j2hkeO8B)T%*w*^-rRSJ>xy-yw8mJT`H6(NX8Ke|ttUhU$euEMd2+nJ* z1r~RZF441rtpfV$>wsrJSXx#N+~(u(2i+`ANKyz(Yl@N(wrfySrkTf%k;DT(@IG%T z*p}k0W*8T9Iog9DBW}G;ywB-x=)T#I25#t;9K1h`_O+MsZ#|kC0!V^J%IPj}3EDC* z9$|G~^Uml_`hA^TS2YtQUdcU(XKZ^jw2$>FjWLv!9?y5WC`Nh0<^;7X!GW*924?8W zj%~kqkBWoF_wd1SN61?K(Rzxg{0cf{U4#A~f8LY1$=d)(y}=e=gf;&bn&PV|jC+E0 zd&f&rB~U<^y%>aSFd3HV>kV!SO8m!b#rC6ju$-rUvxzr(GhX&LOY^74!9qpK{miq^ z%WWw&=gZILvh*tM3dm?~;u*gYvDapOHO(FEhN`unn&5r^ie3 z2CD)hwjN~@Vs0e5X$HjdM3v4r7H!+;$dGNbpF>G3tImZB1(&yt$-LD}?T`n++8&!= z?AVn+eFAOT6Jiq^zwerqj(m*Zwtsrw;CqGSsCg5jsRh75y5f8$LuS1raxEB{xusK}DZ2XywA*}EI)~v>VCm&%Qe!|jk?mKq^DX|tEQIm~rewm16MqKHfrT%);3JXUNLq)`b}%%$>_$gSg@sDSyHATLPa3;cr#WrX zHs)iZ^X{YWB=6Wp98>zm!}5l1m-0;0C(|Pjng~B|?5f*KdCS0{DPeI7eMh~~?n1bA zS#IQwiNZ8QLNH5P3~63hPJ$cEW1@BH)=i<>R@+&FYS)_~g0@fEkJR3s-mzTatOQ1c zrhFpp+S9W$651Xtyww>gF|=RFKw;}5h zYOplPG`{}owUe{e-ta>cb{t)Xppk`7jfBL0G^kg6*IY|lWSe0WbNN`jbWo$_S2fe- z>gGGEha?rT8X?e+V{i)$h)Vi)hk`{oy7sj%iTEMK2Cp!Yl4+D&{4v)i^Q_MebEHr2 z8|MzKEt!pqOHFisu;G5c#w{UrNWnpqOp3PgmuvQs21Tx+*ItS4gr-Y@*qkDs5#>F+fn5i7lJ)$m%z6@m6nST>mxBY_Y1PhPoruN zb`|7g6*U!8*6noc`fE$%)T*KIr7kW;%^9*|rkESbUFH4B$>j)foJ8qPtBT>wO&lNI zgZ4a|ImszJW$R&e!Y}5%UU)6sLd<`uITMPT(ucRP-|1oNUW`hjdrU|Vj~2LHe}1zm zt0#SX?@V>2ca~zL*ky@#n!4o$9k}Ifs?WQ|kyYm)zyrUB(7S$t^d zNHs^|L3-o5%b^@8J>f)Gf?w7GYO%s0VDM9bgTGp&OmS0mz;p&?A8zw=5POK22fub# zMoA;w2e^yuJMr59QxD(y%A*XUd(blSux8TZ+!PDrS*#YRHck8mN zz!m&rdCBP9s~i$PQh)re^T?OU#BZW#V`Xz!n(%iBAz)fR4f$*(-+Vp!7I0@heT@HH zjQ&Z3h2PP$;Uu^%lY*Ds091(Rjd3j$u-9je<2zi8owbd5%C@>L?UK~Jq}+xe6aufH zk9@{=8I|`6#R^Ym;YTX7Y5wrw%X_$#=cyJwJfP|x8@wzR?POf456stf7lOf-ZQGK4 z=HAZ!@S45?=UoaYR5i?oi)C7M*w0Vt8hMT5qNk;%sc$C+l){Rmwl!&2{0Y!s4Xh8` zeAs@Uj+DmG5q!0wrTQDR=?QxuTgA9q> zhJAh!iD9z1H{iBtg?ss!oxWmf_Dc=q(&GRRC2F;U=YLty^v)&1;w+07*z?$gUaD^3 z%Z=n9f^34o-@!2rl_Q~d0ifI|-!9=?z`U(Y2(bwPB=k)GtNtK>LOzaIyE=`l;d@vS`aC`v&Tdo@qW z3vQE*=*nWmrE_r5igAd0e7L_Urs$a<1ro^lGToCf0y%rwn{BATt@dKyVi@(BIZ=f% zE5uMqrtif@L8Y&&I~-}wyuNy9l=FqP|Enb>Wjd0$pPDK;F&kal{>-(?BWD_4v<+q(;Sp$Vo4j+>{>Y{wRDVS9vqnW>Tm4UQD-Ap8Nz=t8EqX5 z+P>nvRAoBsqeFjxb0fMAyHw{|1Fs%hW@*3roUD9C!k|}4s>?rC>*W{PF@TYaL5;ja z6mKYGA|KmmG66f^Ls#R@z^_ZqLw(J@6ju1V5fa%E?cWfbKOAdc!akpA?iuXC&PzW5 z8aye!Cc|@BbUOb)uq(7bZS70e)a^^L52GK5rz8vNA@K7AP=XmEFDc)Sed z#7(2Sh)Cr%YKt+nEz|ZHa1m@2@{=z z3o$ZF$;-#=OvI1WYEg@>|B zK^12p;|1ey;iQbxwyUG{&_FF<8zigNSet-JBU8~MNsbd?4BPR1G+IXFU~*R~ds9Csx?dq4r&y$==%@6l2Qiw2hih`1lfFKVByp^M6INZV6=OhDfe(3mimYSy6G z>?2ufQGRK3E01a2H15Vi#z`Wuwz%7h9~GHj5nsvOQAD=kSY7R@g*_u;?FaYDkz)dU zH*@&074yb_JM0iS<6}zo+wPesr{Q7tva1#Yf z*(`fYBDAv-7ZG+PVv1wU7k|_na!wWIyt8OW^AGEXGFRi;^86kLl5yJ743|sj-`w9I*xOQ zXL<;cBHU>SS@Z=ZZS2xS(+|~-O*$-_6%_wU>f@`fqk+nsN8+o&^nZCl1xtW9ExeEF zp$C*uXgvIMO%YaS-;K^^f zge}mXqZ2H}c9&|vM~qY%*`LDr_s+T1t5>?n)^NjxS1tz(gOwZ!-m=r}Mb2Z8y=Y4@ z^aC)T5`(@-;daJqJuJR2+c$t-hPPGCK>`>HvzKUY2jAm_i^yg1I0zS%ZwXa$P|FEc z_0ko%dYzZybwy#5UKqqH)X7nQ+~>pC-|HXRsP%AZ8pid$BQjOnv$IQ@O?`fJ(49bc z(l&3i72*t)-iBuiW;SqPAF^gV=TU9{F#m`pn3h|3&_;%&ZiHbBxCYRnYnW$4?-TWr>k%-#{E5D}^I(K7%_ zmRGrS1TLg#zmb^qRqd1gI|+M~faHDaaRIc?D<1_2SUMR#VvFP+4r1PYQ{XYA2WOG9 z=lXD3Ap7S^Lft%3M}Mi1;MX(E{J6u4Yg<$c~x0;*?qE_U5(4YuR6GiG4T zj`-C!@b;#_0zQK?VExF)VTrmZ$Gnlf7-WjvirEaS(E)RKc|UA#-36yUJuq7efc}5C z8@7t31#Xp>VS(V3>u0AvqG>2X7?sNgFNe!CpNB*Wa(jQb$|}B#@L2E?Aoa%JrspVP z6>fd^V|=f^E$nC@vNNg z`@pZQptI2JUy2-4)eJeH1+{YHL(oQ7guNw$o{zmx}|Dei1_iu!jD)4z-Dr z6d!gL4T;E)wVhiUJt2rrW1jK8wtcr03sKL>2VZ2j5+jjzT$*1kV&W+wueR@tWNc>2 zUmDxd?rJ&Io&}eNuUfQHt{iaMh_%e0KH!K1$CMx#+qf1_ z%unLOOBQltrJpEZ#}+yV=MZV2d}qX#6lMUUsUitO=JIn#Aid$=$PW@{01SehV$m7< z4FF5D^zO6(_;rhL#SLvS$z^X#zHK^$g%lX8eop=`kU5um+i+GZsS{4%j<*la(PUI1 zngT;sNjtMV{Yv&#T3-VbPXsz212QVfY4r%z;F&B^!UB!K=Qsq|0zlK3|@r>|DBct2^YgNZS|V( zDqLjuS{s?u1g2c+ac;&Iu_!Ti-|xOmQ$ujvw95s$5~{b?{$;&xl>$8Q)_NRO-~TuU zMho5}WSyS7^J)SZDvl3_))(r91f~yUy{`X^%-=)wXQ71^@Dioqo`8P4s65Rt6G$V3 zg&rl(#A^8LN3_>VO^BKLqe_RCB#;+vW14dv2l1HcMWV#N)&#}MQ)PcZZTc+Qmn*f5UZvbatr#t=uE4aTp*;vn1BcG)roI4~>@O%Ye&aBmP-~qT!Re%@0{0 z^#5Xoz~YUt8iQzabk@Ab{zGIl*;967b|=x;PPoimAr}CI)JpG3AJ}GXBzi%%yvS#v z(LlD))jBGwySvVHZfjbJujUHnG_Ut>wrsoQEjG?)&jJ4YCD}Krzpaly2GE_X#k!t< zb*=Sx#Rim%K<}y9u0rD2zruNHTcJxTI`!??olaB?j_Hae(eCQL1z^rKf-TcFFXRv8 zD*Jj6y(}x?Gjl8t!$r$Mt3{XWj4DG;|5Yc2Lf{jIUKBe~_dojMKUzePOo6JvH;L0h zd`&*dcCMf}_9(I`jE-}$`)RP2P-}h|x?F$S1XtKQ=4~zz+jq-J%970lH_Il>}}sUlOQEz0*jaF^$$_ zo!HQ>?K)>6^Q}AV0N)=bWKN;Qkk=2uHqZfo2t-J?gcoyxkzgz zlLGnZ)x~^g4@P-7*AI{3zAD#77lu1i%vxG%C=eChqC;Z78lj@8qk_|Su2MtEPOKwI zn6xYNFR7Dbt+0tpF3_6AR?|B-*5**hD&CsUJMJMjiFg%BM@RDIX(hV5@;UR$ev?m* zncHl4r>!P?BNe;P$p}w9mMN3FHrAk3V{e6VP#QkU3VHkZhGq6a)|yx7`&NN&J+%~L z9&V)h^n;Q4>3W$@m1SozRwV5xsjXOf8JgPIy_I*N0%JpOgiS2sqQSqQ+Ae}5k11Mb zX?DCo;>o{8^JyxipO9NHUTkPRR?sK3ywS9p10b|Q)#w}o)iF?rvTC%w2`2elNgGV+ zG9wA)p&kxIh(*!cBQZA6Jn`ThUAOYEWHFU-W+Q@h{mazo2-BmB8Q{h|#7m|n8{tcely9?%;oQ02i9mebIPFd6j?)j%P9>A1N`(`Gkk?J$oApTox$(M; zCTgtB;(j(GkJdv74Y4nvF=i(= z_dEWkl>ae4T1j5Y6bn^JXHF#~+SF~5wO&9UkNQ@3gULaSKfZc06h21of@vez7YFJD ziYNoInvmnm;M8-PT}?HuygWE_nhR*%JR;M{i461%n~oBzrL0*-yoor_bs z-zLT!+i3T=Rux~2qP-ix^vT^Km~oP9yd>eR%qI+r=z5d?45oN2IFb?|J+t=iKvV?j z<#O^1@u^zGB1Td<>UkW$pSPosC7WD7B0Eqv3o1BpI`^05^N^b|ucgzDjiT9=MIf4t zQ534S#=IcuxGSVv-u_7ZaW~ zV~n#60;)fxW4>x&KCF5tW*R|}eb~$ab7dnwpW2D9)##S*MJhhLaEo$K6j5BKtdnhq zymESdkYs?wk*}rYxa;)jJLSy+g^Fs#_F(q?W1u`1=hYgHDKvc-Qq$gaHACuy!=#O^ zc(>X@WYiXGmjb*4dQpbn3UdWdg(xZ^$4)BBUVLilYM`$f52-<$GFJ2R(f}pOw~b0H zOJu?!_(z(dJTp>+OyTRqmdg&mGI7t2KU!uSShG2=h+8C=l&w;3P~S7OybJzVv}j$B zlKL-ANiq%S;6e6^0w8%K2?spVN2hwZIo-@CGdN0K3Snxbp`MxAqn&L;!*biRJ0RE% zfetFXvCVaJ*6N8D{F6gp-}v|&H}#!w=j}ySic-7X(X?!2xqJkH$Y5Ru(#>j2_#cUx zUl2k;tz71lq%_KPz8{rFjqaN;aHnd&(Y%ac1;v6UZ!ga|$z5ZWp^UDF(8U2in1wM{ z66zFo8}$(DZZYQ>0;$d6R>oL{;&+{*bk>7HVNogc?@nu{KH?xtjob>?4L=~kp2hce z@B@}!G5DRK+oYZS9Nv8r={Mm(+@DMN$P*z}OR*WyrA`X<2&cse%=zozD5x509%7<3 z0nmc%0bNcY9;v`%A_>?GPn9w2K%hwYgvF&mK8*f=gSz6@G%MCS&T`vTvCP%!8~vQ8 zjMx~0qXfo4H72>RiUA#CDnn2deEwzaL1tyMt4hv!j&_kkQ0Aop2}#>{t*&*0X%2vaGUy}*3iYQ9JULO%TD8!Z7 zlf>G8xAa)2vnqqOjOs{$T7L*2qswF2dQc-hYf4cKbN;ZLGPfz+I}j!jrcTu++P-)3 zeG1!_jcL+GN2^m)nnJg1Jsy>hA@UrXkQJFO=5;fn-6Z1qC|zDYr`ubq9CbQgqrIIY zXe=GUnMPG?-lQ>`>y)SyuhMXB=P;uMt!C`YgO1zh-SX0T2<4Iybp-`PEzkR!2&jXKDVuwg=S0k(a%{5F50KYa9Hxe#oNi)GPDNeAux8 zLCoQ^!P$B#1QxuP`}4%(mA{b_GOwLO`&?^cg1-#EV~a1-d^0@O$n>wDyB5t|y-bfpFxveJIY?W!{TJCw2HtU!aDc{%qd(8$m6n9h zFQ{P-XWKlgSERJ|Wa?>6?1m=Y1B=wR?8`KwD$Z?Ocnzwgf=%p})w2$VNjOIw8)u?{ z3T|}Tbu}j%egO-gFzv8&W?p-aLeb5P@CEfFNhVE$#^}%Ct)8Sw zmB={XY`#yrFxSv~QI$tu50Jj)vsUL`6$NXOzVV$ClbnBtS5;+qZ-#uN)8|_P`wfjd zkmM^j9&cu(7q+4E>f*v%F=c`fKTwa`?L#PEm)lur8N1O;`j_Y2FCaTkXOEuC#+RA7 z0VXxB_m>ti*Md3;TilnW$Ir+Z+t$h44}aQS_KxG~&0kWIc+TPTRvLn&!yHL`*+rG; z(@MGsu{hF_nVr^`30AgJyC?JUR~=cp)N;DtbeQ@=IZb(SOj=|AYl1hpfC+X9Pof5L zp!dLghwRypPEL#`si6WvtpKx@eO)n$)tzO#)9If7Nfl!(Jr-GI|3qARq4?XdUvpIl zLrV(}{gbB}aq5{i*I1g4%WqC-UGnA=ZBJ$L+{?}xo(R#)2-ZKe19YUXKdknbovh#` z$HPZuV`}w_B&STfYb3N^aK#dr?-L>isSAH<{sq;oJNv+EcZ*@JT^S|n$DNAQ$%TE8 z!f>R^K4f%TwipPl@YRTEPi!EpwA%eh_}aD8?7C#xsf^oG)0@BzMZQrLO&JYo1UR0* z^&5-1fPC;}E4Eiu>se6B0X2-&H-+kf>3AYCd$6Bt_vBrzf5#qodD+?aEIS2(ps1m0 zwJDX#?S0uXw=d1_+3k%zSz3b&=dR~9_NvYYJR;74Bn z)2%=_&6nd{ZD>;1)HPV#zR5L}tbwtE)c34&`KdoYs*A=HPzSFXK(lMZ#+9(F8HzVu#UHrYv>T*ltW6q8&5YdrOKCEcoa%iMZ#C}aKA9!z@|^c z>tZj4JreGY9t<7x7(CeT8#Rzp*c8M9@60C9oxmfv@VoT@>F}IBjj8m1Sbj_p_UG-uZGtv-W=5)ArS78j{8B=W{Ffr~y8vjG6J8bZLZBVza)w5EAUukF6HK+r)1Pm_Mfi=u?anl zt6T(%c#1lgXD4$NlS(UZnDyZb9g4b%IDifk3er8cb5{_&F}cumafL(L=#bZ#cnu=5 zQrNi&A;dhEzPQmMx14orIlWmdh+H)ebI-V9ByV7~b?V9ssJuv~PY=O+X)$B5aUuF^ z?tr^XZU?6oavg^mAj4_i?Z`ek($JI6BQIjqp2#iNjfcf6I3Q}aGe#;ls1aZgT#6MJ zui10=l!d1#@8*6zqD%-Z)Z!QHHoQ>FPB&O4qdab__E=$Qe%&8CTVbsE-PkyRZcwIw zp%${VCQk4X26glia+YPsr&D9CD7fO8q6bN_8@?Wb)CM$cpLwLfr58k+Vx;HUH2-KE zpfA5A_M#?fTk{<1St$)WS+yGG-H=hMeg{iQHU4iCQ8Rjv`;Mh2F<&@vaU7-R0H>u@gg&A0U?s#96R`VPly0=MvsV=_#=K})M9{(M^6QJN#NFYa zMiKpo4BDOTv&x6L4_~ibqAMzV2#FzUN_=VIn=`1BTXX3B6#-`2l-ALwj22u0$r+Jw zl(Z*5+?^pRx%zcM{OX18EnV!c&(Mgx#QEsoMi%yI7r6~eoO3xRe?pljflU4?6wt5z zPEih#i2`k!Pvr?qsrxdjt&Pdi?BG&B z)Q)I6bsAxT8=W7VNiX@u3p%T@3YX~Efb10FnP0fqmPhih(^emI!Cu*v7eLkvXI5W) z@(#TR988JTUl6^!7*0)l>u{YK48}@me97ClkY7wITy zlkPK}W;|3~Gz`@T+JQ9ZerkS=K5-a_h8KSH%;#NNDS1&mbM@Hpg~fiuLG+<(Sydos zy4jdU`pn$agsXthz-!T2Zb{2p2T{5(4@=!JP+5%l?8+ifCxUsy$S;8CRQ!A4_r9~h zk~I7nYeb>`$dBsINxaikqA6B{WMTc+JpptggyuKqTdwQNZXCW2c2MppA$Uu%ABZ;x zA(}=PjW=#izHEw!C`fxs^YRZ$M03YwT@XCdvf;duI%fCj*!{4HALi;Y)#od-Fv*D+ zM9DS!#AflA%gZ_A=_c?Dd5o$T4>LBhZsBD6?1#!YhkG7|@HZ(fe8IOZ%%OyBzi>7e z!_4h-*MA-v4YaZ`(Bx@#^1+LMEVUtCC0d!!B92iWxhdC)kfaLJmXN7>`~a8`=ONRE zaHFnXrYnj2-`oa&*mik<0KH0djF2Z~Eht20eY{4*;2TS)rwXDtl_B&j)@+vY)|1eK;WI{v%hE+8_iu+6b1?toS4-waiU1|{Q%|Kg|l8E27 zU(U^>Kji&V#@-kxAI#{%cW1oN7kTo0u5_i_Y3<4HENy3k;CS{lc3Yl{ZOx zlnfY%c<>AK-r9|jf%`3p+i-4R)Oq5p=1QwRT}ji!Elb+!`04xcNX;d;V>~{BQhk?E zo!H6lxSVG76mI%zmxl!>Ug+i{KI&~1#&IHtx4r0gkj&=>$yQ9d-oo}PS@k}w$%Afu zbXiU(_!Hip`L~$sxswUt?zC!)@E!$Vr zGe{n+d2cw|J)2!8--%&ypLuwKK+E3PzDv;U+5r)1EEyPDpd=3^!gzyD>_H(g(_~Ml z-WNecTPGwCES~yNUmP**il5BMTHN0b6cL8a%eiV92K&Na2t%yLV(%8T-7OI98ey3J zY_}x}Q%!kO$D^MO#r@6j-Y3dzvVrQ0dlZ|?LO9xBhk+CW2VU0jI^}=IybAJDS+8T8 z^Ak}708W~f85n4fxBc2Eo-{wnBl3x_i(VHjO5dcri>oXoExwn5z|)Er{M_fQlrpir zVa#~vcc`JUPvi113`Ki1>afY@s^Ic5MeWB{#@gohht;%;4Yw&59X<22A_OUX>g1+; z1}~zd!bZlz{#91dev{3)Uw6q@cgWHz2k!tbSRY*su1z+V}nJ!D!ryUuh{6b zToUA%p9e{N0vI16+3J^a8X%!oAqSRn%WhD59HNMJ#n&fG?a%=|{n*B%pDkStVH;|x zb8ZRAXd22a7v18gnLY@;(DPr^EM>KW#%CsQ$B&6GcwDwcotXQd1vOC{p{zN zy$`v1A`2g25-T922?>vB8BAT*3S|dYBI}yQ<~zfzl-9t#Fd81aL1K`?{UaGce+^Ud zGhKB;uloXU!srSx1!CvZ7CPRKADns5SW_ye)J@A~`p&E%IPvRLI=s#GbWsta}#iw`jQhAKA5~ZYZ8>0u`B!&)g#X5I<&Cgg-GIk%C`02V8v^AUZfgL{zwfj8=)C=Vc7GY7!>hVHj zp<#%8d}A8yoNVXqXWz(sCOw$jKV=hY=!a#lxJ}|Hk!$Hyrt%I+e5iQK1M`g#ZR5|> z#-iCX_c)bF3|;Ha_(+km1>xa`j-tnKM@Egmp~j)pT@CD^at|D9KInUyWOkc#oXsqQ z%yt#pEyZ@ZN1@Xl1<#byZoH~B&F~R6upOIuOA$TaE6$W!o&M!9x@e-M%K711vaB)gkAO zs59}W6t6RGI#H!latfTEou-{4mcP^~2g1Su5uGN!Vydz%^D<%Bf?TR+8HZvC^pdZf zCvzTge}Uhht>tYU+~b@cP`6D=cPFC>JI+%w`N%j2|LuU8ShvWx@hNi5LCYLu`Ry!0 z!z<6v9Lol<{q5>uk@E7P;KLs;=iSKd6!ViYu6mDGM*Z znLOe6Crb%EyHgh8v_2I9?Y|FTh1J8ll@Kf@6Auts zb?v4nHI@Pj&e6c|pkx^ zEHM=vkr}FFc+)!xtZ;eRXO&*gHibH)!)#-n^%X&KSrAd|n{RQL^1cNu$^buYs`$)L zi!IIrvwO{b)6UARW4aUIqSqOl{06E}PK0A; ziO;Kxs@|8#-_X_A(Fx9!!s*U@!x+llHp=}^X39$$&fmRd{s`p1iRMq36!mq-bH9&u(MCzX`FzJfX05beQ@nf09+174)?bNu_o^uM~wSA0L>nyAA*yckO~I}ses03tLMwY z%bsx2VzhrkOzj;g#KS-U*T|PCVDwWYx<1Oa>Yg+=rpt~>SnY4`Ucap4AVBaEk4v^2 z6`^ibwr;}QAva3cv)DoFJXT0WBW<5)e?Oh@zP{1IV)%XL32R+Gu9L`K>kBin5JyxX z^y~f2M)Livw8Bmq#EjyIP?h2nwYsLqA@0f9lcIQ^q6@{!jhrCL9n;m_MNL-yWfpPi zY9X{jHPmDMi7%Rl*6FwG+gX-kYtL1$&+Ryck9GYrDfg`l-DHA?91g7N7eo&v))?q) z``Az3Fo*U1#M0wxbD@E$J2SiYEyx*33#wMcPGi@7b~gLIPc1CD`v~`}^n$DN{6)=~ z#dXO23wiuFt}09)kw2mGKd%SX;vS6Qj1|5{OFZZh;JGDdOt#NC3*#gWl=Kh(M#KHt zlCv>tqRl|D$~ua36u&00oX5!L{+?7Rj}ce&psS_5dTqBe<-({E%kwGE%2-9>SHao= z9Nv2FBOmBXU3!6ZKdx>cOtxj-S5xThGTeRNGqux3R_~ixer#jqGe^y;vl6#<#lMV* zfA669>+@IQI~gtBDlvRg@Nhn`5(4;P-;dyQH)bE=B(Cv--k51h2;?Z-v5BaQi$^~kJJg&8T`u1RX<&&BoU!T#msuNzf0^9j}Pw`qP z1*PDsCcyingsB=2y1H39*n@aU`=?(U{a!i#M?`3n8jOVyC8p$XiDUEA4EUKCaB935 zp63D_6K|73pyg}&Z!B#)bjTlhJfbysrpm*C5$I;fL?%dV(79k}L^=J+t?krZS@^jExX;?*36FYD=}nnKw}RDEQwW z&V9Mn^ZG*vRMlX&s74V>5B|4beBbvO(2GiD;HHp-dFaWba4d?$ZtjIH^;5S~3oW(e zeY`HH{pCUSXG>vPLc9~3Qs19#u;R{!db_s7Yc!B+V2VJ4v1d~+?3(a_7aUIk3gfQZ zzYW4Yp7h!~8t|pJ=e=9P`8}8?ZWLoTwMu(Oe>NtI%&&s@Q}1Y( zC;E34{h>c-t$7+U0`tlO@s|cKzSl>vYxqpqx<**V!DFyP=Jz~`9kg1U_a>w~1^?i9 zKEg7Qg8rD;!39>ZN;xRr(eK;Gey?(m7hE`}p6`F&Nodq`?r&MQ51LyhNkC)e=md)Z=uIq}J29z0UDMAJFD=Jib1 z3F74aKQX>E0Hp{U-YaFm+5#2ae-Plsf8F^Oh#`Gk6i?L9qxfq;4H9NALN0AQZZ>UfwYQ>dmop1`Q-aeECE|MY6S(nL_GI^|psJq&&tscbq0qQirSebwR8wa53lt3%m z-wWYg6{HC-k}%tQ>xdQD17T0X9yE0^Eag~V#Vt8(YK zj(J0@r}bp=DRgUhx1jSBc%^UJEttX&qLWZ_s1iAEKPL#b?+C>4*sr6fr&ls*n;>*K zmf~#4>j+cDG_V0Sn@sxs_yj}P3xOykl~ya=HI2*5Iy>KfR9oveS1klhRhp|S*4SiY z@&;K^2JY8TdOGGQmI^7A86-b6f=v6c7wk`zw=(6?G--kbGy9|#+s{lu0(VU zK!VJ$yIwm`Nu_upF0flIcfAzn^CNhn7sx~GmHZPJefFuhTtur9RXojs~+np)dS*IfbqGeaC491Oyc`3Ti~`r=vNH(adZ5j310Zw#|}?I1AJedUyZvXS)CY~Oir zZsXaabYN*w3y6^()ActyzhYrg@ICjIr#OrTt`RcMN(JhTYV&>7Kv5Hi*(`ogrlN9x zqU{{K{1njqFt41^-yhw#*0DK9L6AZff(2E6>zal{AGh{--0{*@hPxQo3MeYyaHg|x3 zReq%dzk0r*PQnGYzkwoCVq1rI*+5}5!yy!@SCXmJ;TKVvbYR}81~ zX&yei-CJj+M~@yU)zR6yn>6sM)Vbc&^x@}1DzCa}3Ppf?YC5ndvkL-i0GSDEwqzgK z+W^7Sm$uHTwWUSp>U1Yc5voJqLa0rQdg#fflOx6?r{mZ{TvkK0GT|RUn7-o#Fmf!%LrhTFoxv%2YoLc%e zmTBrris+qrdjM865bKtQfq@C+e_i>?cr0SIFRlk@w5ABYIYw~4KIlBbOblmSe(23( zP!~Ep!jg%gb42&@rA!Epma)(yO%%dd9~>9m*TLH*j8l1X`CkTuW@mwlvs1uxT)loy zf9e`!~|1>%0Lrc-5u z9z@-#E)Ed|3dMZWGc&_4@0$Yc z@ow3Ni^HgpF>Bv@LC7^AdWFlYyU6-3dJF&So)RBqX%Frt_szeFOvqz`4p!`{EkxaNGX3kZhk& z$CFLj9>=YO*?Q-#_{>z{kg)}uAmlnq-}o<(pn&fs5WxVfI!W~p2ZFIL4L{qyS9Fzl z`*!5weW6M<38_$ES4o{zQ=+9#v|zqcHbU<0`4UnpUcI21l!E7@r^jPscr5Mh?S{p; z@o-PDHF7Ec#5{LrPa+z~aS}=OegC_nD*5iIr7?Qhl)v^Gt6xiW)>&xrE-@OVru)vD zC$KvNjs>+)IP2*uOBjZ(WPV>IE7#lGt35VahB~4Ar>w7uoYtW`g8p?GV9S)DyJPzw za9nMdPF;%1YoGYw;!+MVG7f|oKAC+JMLW3Qw{tRGK1%rC zF-o9SDF1hi)ndd{RHAx)U&v*1WWq4%lyoMdp*9jgJDyrDFYDoZfCzSG;59L3 zgZ>VpbjK$aJUdL`6Cq%?%d-LJjzl5{6~H9X zyECNp`@(3h{MPr@{T6SW#Hx-lj%R^HCYnNs@<)!le}Gpokxe^fp{H2mJNWQde`J<_ zBWPL(F_ylGiAhGf)A2gAi`__}Gnl|OX1b_|(S08-M5y%p`OyG(4-kk?-2!pB`}*Q2 zC?|@}i_bbP3e6}}wbssR{X4YUV!=bD;e(=wr)OX0i)pvD1bbKp=RCKcw;)vvz@%fc z8#dX&5w=tO2q!Rqe(pdZjjfdzO8~2_tio6o6N7zldODU^W%#HEi7fOD?6cGCs5f+_ zivM6VkX5wY1vt&xl=y!{MDNBXjuOMg0_35^W%bCx55e%>OLj1gon#-8i_Hs$gN4nWGhan?1kwrY36x>ymDa3J?p zg?)BRuPM)pNpc@V*Mkm}LbkQP#Oirq9!57cxj!hkr{j>E`*76f!HyCf|0mS_`W+~y zDj6`)QT*!~%&)Lr>DvU(SaJ>y6<-peD%C&f{5x=SCq{ep17pGA?@Wd`8uA_QRdiFy zKQG7K1{NHipLem5{usjD7IwWu0NkoigT4QFrpJu;wV!uPR;9skARzvK<(->QK=-Sy zS8gxszu(MXFY4n1WNfab(8QH#OUDOCz7L$+ruJYBO9Nma zDwYymQ{Sr){Kxo3h>gKpE!As71rOsWFz+2J^$R-@bKdu7-iY0Y`~|)j$cr?ZVY1a5 z8}7mrjC=35Z=$RH2{d=A>tJ)TE2D9P1m%3VG;Aa(yS(*XjW%}oFn!;~ro z1%i;U@Eaf_t}|Qb7^Mg7$f!#72P!`F2PJqTU>Ruw+eupCIA-;bJlqDSlbk@@S7Hcl zogqZy<2y6RNPz`v7GvQRA8+lw`#omaT1s3eU)Z)-=gr1aSLZHqD5?T4 zDGVfT-@fvCw$04U6d0=4*oeB`Ug%Gj>W9R0yQy_YGZb#&E#?hEI7AoPotC>$vgHdC zpAd2=fVdwt{CSkcZf}-uKXhj-KaOWPPoX&F2@aD02M34ADG>$+IdIsD-PH!f%i_)s zoh0{7St3n`|1AYLj)6PDh)Q5{lQE`CnbD}&RJm~`QY#u(vKfjMNZ&e~f-Af_p5ndk z8cKg-TmK1-LTtQ1wVWqm6kn=2k*Q|k=d84}bXn$ZjqMg0E32Y&9~U@trAu@~vt_bB z|1XX4UIfSdT?|b(kb2b4^kNy1Yya#V@S&*>uoO<#(+a;^GJu4+$^T<9_EM`_WPblX zDg1=+J&#F%&mIzbf(LplpKUmt0W%=RPsYzO)}O$R&@#Zsczk>uk^MgNu~$7fDvXoq zR7o=)6c-mO?R#~SSgG;pB(Pd%^n3e<)&Z1@>f)mWv z8PZ-0RPTEfpp2=7dff6ZSEidy`iBD7y4fSjH~aF^=UmS?DmV z^j=83S>&0r1e&h%UjP6Ao7wpNZ2?3HWj#n53lhT{NE$=kzDM1~Y8)Hv7T63>NGF@q zvB3_onkT^j2~f>S+b1+7KTmziCnPi+91Fz;n4-C|xfz0j3=9maNGo7B7j}jwST=d0 zJIXDL zM(>w+^RU^hzoWZtX~onGL@99quU$GhO4q@y5Jjd%1!lPz?cIH;-uU=1|JLJ>dkOMG=mbDHZ=JnU z2sz~XPJv5>0fV%|N0NARfI63f2gsOU$&293L?*RJd@3AcUP&-b*q86Bcq5Q(^G z@tDmbItiWMI#2U0d+}OufRrkcwB;e(qetCIZnxP3lSg_3_&ZeEy9gf4ZIWK7t^oR# zG1Ib8JbSizfU7I@NXUJJxP|V2OW?1a#UpWdn4_UjKKk?fyX{7YmIdNF<|&9q`lb29 znUco5Xb(e)&=X^kg`c*%W=bT$LQSC+x_Xeo0F{_7ZU1-mUGAUalNkLn^!T$BOOt5@ zWEJc>+?{-MGu={yPcUZ-`r??!31h|!)uWSKFGaC<)g&hdQ-x13t5pUo4?HMxEhVtJ z7W)!er`~te;CbD8H$9b{eAyKW;8_(>79D?s zM)!S*8C{BY_7f5Be+(yc|G;xk3#Vnu>_>$-LOdZMp`noNYA=pUgU!Z}(!3`YRm+i{ z&i(uMwN&S7rfc^aa;@zvfS;FhDhU$xCX|~BR=5=UHhf_^I4Ee~3?(PqD_HdrF66`y|Lv@XjGv~|PY0#WC90*g+GtgFy} zzZp53`*NLhYw>S?pWjGbRv~a@i`D1zQ@}+`Q{U%^UwyEtoZfZm9eHmrq{T^>mEo^J z8pA+%1B!}-g5Moo{Jhz7Q{t!x8}CGOnO@Lg(i09z$shWb-b$YY-%QSDIvyDXO3ZaO z4a&u)%5>jT)WpQZzH9ZP;`cl;? z|4Of(^I5<1HP?s^wPZnp)#G{7x542Dh5V6Crv5t0}hcvEDo$%n~jvC@L+sa zY59PlAo2G%>CtMa%*@OLvu8V%b7O02&O5K_>nANIhPmsEHIZd{BB)Z2k~be@F!x?1 z`w|X0ib^#AH^it?Wd6phUG_dWEvu!DpcRn&M}VWj@45UN&Eeod2H0MZCT}2DDHiwB z`vU521L)9YJyZrxGfN@dHd(5E8UH0ZPOx7#;LMJW=@^9{#XeAna-w`LKi? zF;(38Qdk|3o1*rI96_DzHceOE

    V(Umfc(M%9UPraA@w+6L7oM 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 OKUpb7$-EnSe*Xsqs;mG2 diff --git a/source/mkdocs/docs/sample-configurations/standard/index.md b/source/mkdocs/docs/sample-configurations/standard/index.md deleted file mode 100644 index 31a2c59..0000000 --- a/source/mkdocs/docs/sample-configurations/standard/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# Standard Configuration - -This section outlines the standard sample configuration intended for commercial AWS regions. Please continue reading the subpages for important design and architectural considerations when using this sample. - -!!! info "Subpages" - - [Overview](./overview.md) - - [Organization and Account Structure](./org-structure.md) - - [Authentication and Authorization](./authn-authz.md) - - [Logging and Monitoring](./logging-monitoring.md) - - [Networking](./networking.md) - -!!! note "See also" - - [GitHub - LZA Standard Sample Configuration](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations/lza-sample-config) \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/standard/logging-monitoring.md b/source/mkdocs/docs/sample-configurations/standard/logging-monitoring.md deleted file mode 100644 index 11ea815..0000000 --- a/source/mkdocs/docs/sample-configurations/standard/logging-monitoring.md +++ /dev/null @@ -1,56 +0,0 @@ -# Logging and Monitoring - -## Overview - -The sample configuration files for LZA introduce a centralized logging pattern to capture cloud audit logs, security logs, and CloudWatch logs (which can be used to capture and centralize system and application logs). - -![Centralized Logging](./images/lza-centralized-logging.png "Centralized Logging") - -## CloudTrail - -The AWS CloudTrail service provides a comprehensive log of control plane and data plane operations (audit history) of all actions taken against most AWS services, including users logging into accounts. As discussed previously, the recommendation is to deploy LZA with Control Tower. In this case Control Tower enables and enforces organization-wide CloudTrail to capture and centralize all cloud audit logs. - -## VPC Flow Logs - -VPC Flow Logs capture information about the IP traffic going to and from network interfaces in a VPC such as source and destination IPs, protocol, ports, and success/failure of the flow. The [network-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/network-config.yaml) configuration file enables ALL (i.e. both accepted and rejected traffic) logs for all VPCs in all accounts to a local CloudWatch log group. It is important to use custom flow log formats to ensure all fields are captured as important fields are not part of the basic format. More details about VPC Flow Logs are [available here](https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html). -It should be noted that certain categories of network flows are not captured, including traffic to and from the instance metadata service (`169.254.169.254`), and DNS traffic with an Amazon VPC DNS resolver. DNS logs are available by configuring [Route 53 Resolver query logs](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/latest/classes/_aws_accelerator_config.ResolverConfig.html#queryLogs). - -## GuardDuty - -Amazon GuardDuty is a cloud native threat detection and Intrusion Detection Service (IDS) that continuously monitors for malicious activity and unauthorized behavior to protect your AWS accounts and workloads. The service uses machine learning, anomaly detection, and integrated threat intelligence to identify and prioritize potential threats. GuardDuty uses a number of data sources including VPC Flow Logs, DNS logs, CloudTrail logs and several threat feeds. [Amazon GuardDuty](https://aws.amazon.com/guardduty/) is enabled in the [security-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/security-config.yaml) sample configuration file. - -## Config - -[AWS Config](https://docs.aws.amazon.com/config/latest/developerguide/WhatIsConfig.html) provides a detailed view of the resources associated with each account in the AWS Organization, including how they are configured, how they are related to one another, and how the configurations have changed over time. Resources can be evaluated on the basis of their compliance with Config Rules - for example, a Config Rule might continually examine EBS volumes and check that they are encrypted. -Config is enabled at the organization level in the [security-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/security-config.yaml) configuration file - this provides an overall view of the compliance status of all resources across the organization. The AWS Config multi-account multi-region data aggregation capability has been located in both the Organization Management account and the Security account. - -## CloudWatch Logs - -[CloudWatch Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) is the AWS-managed log aggregation service. It is used to monitor, store, and access log files from EC2 instances, AWS CloudTrail, Route 53, and other sources. LZA has a log replication job that stores your CloudWatch Logs in a centralized S3 bucket in the Log Archive account. This workflow is explained in more detail below: - -![Cloudwatch Logs](./images/cloudwatch_logs.jpg "Cloudwatch Logs") - -1. A CloudWatch log group update workflow runs during the **Logging** stage of the pipeline. A CloudFormation custom resource invokes a Lambda function that updates existing log groups to enable encryption using the account-level CloudWatch AWS KMS key, apply a subscription filter, and increase log retention if it's less than the solution's configured log retention period. The destination for the subscription filter is an Amazon Kinesis Data Stream deployed to the **Log Archive** account. -2. An EventBridge rule monitors for new CloudWatch log groups created in core and workload accounts. -3. When new log groups are created, the EventBridge rule invokes a Lambda function that updates the log group with the CloudWatch AWS KMS key, subscription filter, and configured log retention period. The destination for the subscription filter is the Kinesis Data Stream deployed to the **Log Archive** account. -4. Log groups stream their logs to the Kinesis Data Stream. The data stream is encrypted at rest with the replication AWS KMS key. -5. A delivery stream is configured with the Kinesis Data Stream and Kinesis Data Firehose, allowing the logs to be transformed and replicated to Amazon S3. -6. The destination of the Kinesis Data Firehose delivery stream is the `aws-accelerator-central-logs` Amazon S3 bucket. This bucket is encrypted at rest with the central logging AWS KMS key. In addition, the `aws-accelerator-s3-access-logs` and `aws-accelerator-elb-access-logs` buckets are encrypted at rest with Amazon S3-managed server-side encryption (SSE-S3) because these services don't support customer-managed AWS KMS keys. Logs delivered to the `aws-accelerator-elb-access-logs` bucket replicate to the central logs bucket with Amazon S3 replication. - -## Security Hub - -The primary dashboard for Operators to assess the security posture of the AWS footprint is the centralized [AWS Security Hub](https://aws.amazon.com/security-hub/) service. This is enabled in the [security-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/security-config.yaml) configuration file. Security Hub needs to be configured to aggregate findings from Amazon GuardDuty, Amazon Macie, AWS Config, Systems Manager, Firewall Manager, Amazon Detective, Amazon Inspector and IAM Access Analyzers. Events from security integrations are correlated and displayed on the Security Hub dashboard as ‘findings’ with a severity level (informational, low, medium, high, critical). - -## Systems Manager Session Manager and Fleet Manager - -[AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) is a fully managed AWS Systems Manager capability that lets you manage your [Amazon Elastic Compute Cloud (EC2)](https://aws.amazon.com/ec2/) instances, on-premises instances, and virtual machines (VMs) through an interactive one-click browser-based shell, AWS Command Line Interface (AWS CLI), or using a native RDP or SSH client. Session Manager provides secure and auditable instance management without the need to open inbound ports, maintain bastion hosts, or manage SSH keys. It makes it easy to comply with corporate policies that require controlled access to instances while providing end users with one-click cross-platform access to your managed instances. - -With Session Manager, customers can gain quick access to Windows and Linux instances through the AWS console or using their preferred clients. [AWS Systems Manager Fleet Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/fleet.html) additionally allows connecting graphically to Windows desktops directly from the AWS console without the need for command line tools or RDSH/RDP clients. - -The LZA enforces session encryption and stores encrypted session log data in the centralized S3 bucket for auditing purposes. This is enabled in the [global-config.yaml](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/global-config.yaml) configuration file; optionally, session logging can also be enabled for CloudWatch Logs. - -## Other Services - -The following additional services are configured with their organization-wide administrative and visibility capabilities centralized to the Security Tooling account: Macie, Audit Manager, Access Analyzer. The following additional logging and reporting services are configured: CloudWatch Alarms, Cost and Usage Reports, ELB Access Logs. - -Amazon Detective can optionally be enabled 48 hours after an account is deployed. This is due to an underlying [dependency on GuardDuty](https://docs.aws.amazon.com/detective/latest/adminguide/detective-prerequisites.html). \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/standard/networking.md b/source/mkdocs/docs/sample-configurations/standard/networking.md deleted file mode 100644 index dcb63f8..0000000 --- a/source/mkdocs/docs/sample-configurations/standard/networking.md +++ /dev/null @@ -1,31 +0,0 @@ -# Networking - -## Overview - -The **network-config.yaml** sample configuration is intended to set up various centralized networking constructs that you can use to customize and build additional infrastructure. Specific IP address ranges; AWS Transit Gateway routing configurations; and advanced capabilities such as Amazon Route 53 Resolver, Amazon VPC IP Address Manager, and AWS Network Firewall likely require additional customization. The solution doesn't deploy these configuration items as default. - -## Architecture - -![Network Architecture](./images/standard_network.jpg "Network Architecture") - -1. This solution offers optional hybrid connectivity with [AWS Direct Connect](http://aws.amazon.com/directconnect/) to an on-premises data center. AWS Site-to-Site VPN (not depicted) is another option for hybrid connectivity. You can choose to deploy this infrastructure for hybrid connectivity to your AWS environment. The Direct Connect Gateway (or AWS VPN connection) is associated with a central AWS Transit Gateway, which allows communication between your on-premises network and cloud network. - -2. The Inspection VPC provides a central point for deep packet inspection. Optionally, you can use this VPC to centrally manage Network Firewall or third-party intrusion detection system/intrusion prevention system (IDS/IPS) appliances[^1]. You can also use a [Gateway Load Balancer](http://aws.amazon.com/elasticloadbalancing/gateway-load-balancer/) for scalability and high availability of your third-party appliances. The Gateway Load Balancer isn't required for AWS Network Firewall deployments. - - We designed the Inspection VPC generically, and you might require additional configuration if using third-party appliances. For example, a best practice when using Gateway Load Balancer is to separate the load balancer subnet and endpoint subnet so that you can manage network access control lists (ACLs) independently from one another. For similar reasons, you might also want to separate your appliances’ management and data network interfaces into separate subnets. - -3. When you design VPC endpoints in a centralized pattern, you can access multiple VPC endpoints in your environment from a central Endpoints VPC[^2]. This can help you save on your cost and management overhead of deploying interface endpoints to multiple workload VPCs. This solution deploys constructs for managing the centralization of these endpoints and their dependencies (for example, Route 53 private hosted zones). We provide more information about this pattern in [Centralized access to VPC private endpoints](https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/centralized-access-to-vpc-private-endpoints.html). - -4. A central Transit Gateway provides a virtual router that allows you to attach multiple Amazon VPCs and hybrid network connections in a single place. You can use this in combination with routing patterns through Transit Gateway route tables to achieve network isolation, centralized inspection, and other strategies required for your compliance needs. - -5. Optionally, you can use [AWS Resource Access Manager (AWS RAM)](http://aws.amazon.com/ram/) to share networking resources to other core and workload OUs or accounts. For example, you can share Network Firewall policies created in the Network account with workload accounts that require fine-grained network access control and deep packet inspection within application VPCs. - -6. The Shared Services account and VPC provide commonly used patterns for organizations that have resources other than core network infrastructure that the organization needs to be share. Some examples include [AWS Directory Service for Microsoft Active Directory](http://aws.amazon.com/directoryservice/) (AWS Managed Microsoft AD), agile collaboration applications, and package or container repositories. - -7. An optional External Access VPC for shared applications, remote access (RDP/SSH) bastion hosts, or other resources that require public internet access is not included in the sample configuration and is depicted for illustration purposes only. - -8. Additional workload accounts can have application VPCs and Transit Gateway attachments deployed when provisioned by the solution. Deployment of network infrastructure in these workload accounts is dependent on your input to the network-config.yaml file. - -[^1]: For more information on centralized inspection patterns, see the AWS Whitepaper [Building a Scalable and Secure Multi-VPC AWS Network Infrastructure](https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/welcome.html). - -[^2]: Centralized endpoints aren't available in the GovCloud (US) Regions. \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/standard/org-structure.md b/source/mkdocs/docs/sample-configurations/standard/org-structure.md deleted file mode 100644 index 4cc8342..0000000 --- a/source/mkdocs/docs/sample-configurations/standard/org-structure.md +++ /dev/null @@ -1,68 +0,0 @@ -# Organization and Account Structure - -## Overview - -_Landing Zone Accelerator_ uses AWS Accounts to enforce strong isolation between teams, business units and application functions. The sections below discuss the account design, the sample configuration files create through, [AWS Control Tower](https://aws.amazon.com/controltower/) or [AWS Organizations](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html). - -## Organization structure - -The _Landing Zone Accelerator_ includes the following default AWS organization and account structure. - -**Note:** the AWS account structure is strictly a control plane concept - nothing about this structure implies anything about the network architecture or network flows. - -![Organization Structure](./images/organization_structure.png "Organization Structure") - -### Organization Management (root) AWS Account - -The AWS Organization resides in the [Organization Management (root) AWS account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/org-management.html) and is traditionally an organization's first AWS account. This account is not used for workloads - it functions primarily as a billing aggregator, and a gateway to the entire cloud footprint for high-trust principals. Additionally, the Organization Management account is where the automation engine or tooling is installed to automate the deployment of the LZA architecture and its security guardrails. As per the best practices resources described above, access to this account must be carefully governed. - -## Organizational Units - -Underneath the root of the organization, [Organizational Units (OU)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html) (OUs) provide a mechanism for grouping accounts into logical collections. LZA makes use of OUs to enforce specific preventative controls through [service control policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html), resource sharing across the organization through [Resource Access Manager](https://aws.amazon.com/ram/), and the ability to apply LZA configurations to groups of accounts e.g. a specific network pattern deployment. - -The Default sample configuration files OU structure is shown below: - -![Default OU Structure](./images/default_ou_structure.jpg "Default OU Structure") - -For further details to help you plan your OU structure beyond the defaults provided by these configuration files, review the [best practices for organizational units](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/benefits-of-using-ous.html) and also the [recommendations on OUs and accounts](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/recommended-ous-and-accounts.html). - -### Security OU - -The [accounts in this OU](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/security-ou-and-accounts.html) are considered administrative in nature with access often restricted to IT security personnel. The sample configuration files add two accounts to this OU: - -- [Security Tooling account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/security-tooling.html) -- [Log Archive account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/log-archive.html) - -### Infrastructure OU - -The [accounts in this OU](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/infrastructure-ou-and-accounts.html) are also considered administrative in nature with access often restricted to IT operations personnel. The sample configuration files add two accounts to this OU: - -- [Network account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/network.html) -- [Shared Services account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/shared-services.html) - -## Core Accounts - -Core accounts can be defined as accounts that have special significance within the organization. Often these will provide functions shared across accounts within the organization, for example, centralized logging or network services. - -The Landing Zone Accelerator deployment enforces a subset of core accounts as defined in the [mandatory accounts section of the implementation guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/mandatory-accounts.html). The sample configuration adds additional core accounts for the specific functions listed below. - -![Mandatory Accounts](./images/mandatory_accounts.jpg "Mandatory Accounts") - -- [Management account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/org-management.html) -- [Audit account (Security Tooling)](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/security-tooling.html) -- [Log Archive account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/log-archive.html) -- [Network account (Transit)](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/network.html) -- [Shared Services account](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/shared-services.html) - -## Workload Accounts - -[Workload (Application) accounts](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/application.html) are created on demand and placed into an appropriate OU in the organization structure. The purpose of workload accounts is to provide a secure and managed environment where project teams can use AWS resources. They provide an isolated control plane so that the actions of one team in one account cannot inadvertently affect the work of teams in other accounts. - -## Account Level Security Settings - -The LZA sample configuration files enable certain account-wide features on account creation. Namely, these include: - -1. [S3 Public Access Block](https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options) -2. [Default encryption of EBS volumes](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#encryption-by-default) using a customer managed local account KMS key -3. [Tagging policy](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/tagging-policies/org-tag-policy.json) applied to the root OU via the [organization-config.yaml **taggingPolicies** key](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/organization-config.yaml). To help you define a tagging policy that meets your organizations see [AWS tagging best practices](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_tag-policies-best-practices.html). You can then amend the example tagging policy provided by these configuration files. -4. [Backup Policy](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/backup-policies/backup-plan.json) applied to the root OU via the [organization-config.yaml **backupPolicies** key](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/organization-config.yaml). \ No newline at end of file diff --git a/source/mkdocs/docs/sample-configurations/standard/overview.md b/source/mkdocs/docs/sample-configurations/standard/overview.md deleted file mode 100644 index 6a5cf2d..0000000 --- a/source/mkdocs/docs/sample-configurations/standard/overview.md +++ /dev/null @@ -1,68 +0,0 @@ -# Standard Configuration Overview - -The [Landing Zone Accelerator on AWS (LZA)](https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/) is architected to align with AWS best practices and in conformance with multiple, global compliance frameworks. We recommend customers deploy [AWS Control Tower](https://aws.amazon.com/controltower) as the foundational landing zone and enhance their landing zone capabilities with Landing Zone Accelerator. These complementary capabilities provides a comprehensive low-code solution across 35+ AWS services to manage and govern a multi-account environment built to support customers with highly-regulated workloads and complex compliance requirements. AWS Control Tower and Landing Zone Accelerator help you establish platform readiness with security, compliance, and operational capabilities. - -The configuration of LZA is managed through _configuration files_. Configuration files are written in [YAML](https://yaml.org/) and define the AWS account and service configurations that meet specific compliance objectives. Using the configuration files, the solution helps users manage the lifecycle of their landing zone by setting up a baseline security architecture and automating common administrative and operational activities. This reduces the undifferentiated heavy lifting associated with building regulated environments on AWS, allowing organizations to focus on other high value concerns such as operating models, developer agility, and reducing costs. - -After deploying LZA and implementing these configuration files, you can: - -- Configure additional functionality, guardrails, and security services such as [AWS Config](http://aws.amazon.com/config/) Managed Rules and [AWS Security Hub](http://aws.amazon.com/security-hub/) -- Manage your foundational networking topology such as [Amazon Virtual Private Cloud](http://aws.amazon.com/vpc/) (Amazon VPC), [AWS Transit Gateway](http://aws.amazon.com/transit-gateway/), and [AWS Network Firewall](http://aws.amazon.com/network-firewall/) -- Generate additional workload accounts using the [AWS Control Tower Account Factory](https://docs.aws.amazon.com/controltower/latest/userguide/account-factory.html) or [AWS Organizations](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html) - -This guide describes architectural considerations, design, and configuration steps for deploying the LZA sample configuration files. - -We recommend you familiarize yourself with the [best practices for for managing your configuration files](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/configuration-file-best-practices.html) before making any chances to your environment. - -**Note:** This README is focused on the [general sample configuration](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations/lza-sample-config), not the industry specific configuration files which can be found [here](https://github.com/awslabs/landing-zone-accelerator-on-aws/tree/main/reference/sample-configurations). - -## Design Principles - -1. Help customers implement a secure by design multi-account architecture aligned to AWS best practices -2. Maximize agility, scalability, and availability while minimizing cost -3. Enable the full capabilities of the AWS cloud -4. Remove burden from customers by maintaining the deployment engine and configuration files to make use of the latest AWS innovations -5. Offer customers flexibility to add capabilities and reconfigure the environment in an automated manner -6. Reduce scope of impact by implementing logical separation between functions e.g. organizational networking, security, and workloads - -## Architecture Summary - -The architecture and best practices defined in these configuration files are heavily influenced by the AWS whitepaper [Organizing Your AWS Environment Using Multiple Accounts](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/organizing-your-aws-environment.html) and the [AWS Security Reference Architecture](https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/welcome.html). We highly recommend you read this guidance to understand the detail behind the architecture and its application in context of your organization's unique objectives. - -## Document Conventions - -The following conventions are used throughout this document. - -### AWS Account Numbers - -AWS account numbers are decimal-digit pseudorandom identifiers with 12 digits (e.g. `111122223333`). This document will use the convention that an AWS Organization Management (root) account has the account ID `123456789012`, and child accounts are represented by `111122223333`, `444455556666`, etc. -For example the following ARN would refer to a VPC subnet in the `us-east-1` region in the Organization Management (root) account: - -``` -arn:aws:ec2:us-east-1:123456789012:subnet/subnet-0e9801d129EXAMPLE -``` - -### JSON Annotation - -Throughout the document, JSON snippets may be annotated with comments (starting with `//`). The JSON language itself does not define comments as part of the specification; these must be removed prior to use in most situations, including the AWS Console and APIs. -For example: - -```json -{ - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::123456789012:root" // Trust the Organization Management account - }, - "Action": "sts:AssumeRole" -} -``` - -The above is not valid JSON without first removing the comment on the fourth line. - -### IP Addresses - -The sample [network configuration file](https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/reference/sample-configurations/lza-sample-config/network-config.yaml) may make use of [RFC1918](https://tools.ietf.org/html/rfc1918) addresses (e.g. `10.1.0.0/16`) and [RFC6598](https://tools.ietf.org/html/rfc6598) (e.g. `100.96.250.0/23`) for various networks; these will be labeled accordingly. Any specific range or IP shown is purely for illustration purposes only. - -### Customer Naming - -This document will make no reference to specific AWS customers. Where naming is required (e.g. in domain names), this document will use a placeholder name as needed; e.g. `example.com`. \ No newline at end of file diff --git a/source/mkdocs/docs/typedocs/index.md b/source/mkdocs/docs/typedocs/index.md deleted file mode 100644 index 39d3c17..0000000 --- a/source/mkdocs/docs/typedocs/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# TypeDocs - -TypeDocs are a versioned specification for the Landing Zone Accelerator's YAML configuration syntax. You may use these specifications to gain additional flexibility and insight in customizing your landing zone environment. Please select your environment's version number in the navigation sidebar to find the services and features available in your specific release. - -!!! warning "Important" - We strongly recommend reviewing the [Configuration file best practices](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/configuration-file-best-practices.html) section of the solution [Implementation Guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/solution-overview.html) prior to making changes in your configuration files. \ No newline at end of file diff --git a/source/mkdocs/docs/user-guide/config.md b/source/mkdocs/docs/user-guide/config.md deleted file mode 100644 index 69aab64..0000000 --- a/source/mkdocs/docs/user-guide/config.md +++ /dev/null @@ -1,128 +0,0 @@ -# Included Services, Features, and Configuration References - -The latest version of our configuration reference is hosted here: [Latest TypeDocs](../typedocs/latest/index.html). - -Direct links to specific service configuration references are included in the following sections. - -??? note "Documentation for previous releases" - Please see [TypeDocs](../typedocs/index.md) for a full list of our versioned TypeDoc configuration references. - -## Account Configuration - -Used to manage all of the AWS accounts within the AWS Organization. Adding a new account configuration to **accounts-config.yaml** will invoke the account creation process from Landing Zone Accelerator on AWS. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ----------------- | -------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| AWS Accounts | Account | [AccountsConfig](../typedocs/latest/classes/_aws_accelerator_config.AccountsConfig.html) | [AccountConfig](../typedocs/latest/classes/_aws_accelerator_config.AccountConfig.html) / [GovCloudAccountConfig](../typedocs/latest/classes/_aws_accelerator_config.GovCloudAccountConfig.html) | Define commercial or GovCloud (US) accounts to be deployed by the accelerator. | - -## Global Configuration - -Used to manage all of the global properties that can be inherited across the AWS Organization. Defined in **global-config.yaml**. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ----------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| AWS Backup | Backup Vaults | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) | [BackupConfig](../typedocs/latest/classes/_aws_accelerator_config.BackupConfig.html) | Define AWS Backup Vaults that can be used to store backups in accounts across the AWS Organization. | -| AWS Budgets | Budget Reports | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [ReportConfig](../typedocs/latest/classes/_aws_accelerator_config.ReportConfig.html) | [BudgetReportConfig](../typedocs/latest/classes/_aws_accelerator_config.BudgetReportConfig.html) | Define Budget report configurations for account(s) and/or organizational unit(s). | -| AWS CloudTrail | Organization and Account Trails | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [LoggingConfig](../typedocs/latest/classes/_aws_accelerator_config.LoggingConfig.html) | [CloudTrailConfig](../typedocs/latest/classes/_aws_accelerator_config.CloudTrailConfig.html) | When specified, Organization and/or account-level trails are deployed. | -| Amazon CloudWatch | Log Group Dynamic Partitioning | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [LoggingConfig](../typedocs/latest/classes/_aws_accelerator_config.LoggingConfig.html) | [CloudWatchLogsConfig](../typedocs/latest/classes/_aws_accelerator_config.CloudWatchLogsConfig.html) | Custom partition values for CloudWatch Log Groups sent to centralized logging S3 bucket. | -| AWS Control Tower | Control Tower | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) | [ControlTowerConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerConfig.html) | It is recommended that AWS Control Tower is enabled. When enabled, the accelerator will deploy AWS Control Tower in the desired home region for your environment. If AWS Control Tower is already available in the home region of your environment prior to installing the accelerator, the accelerator will integrate with resources and guardrails deployed by AWS Control Tower. | -| AWS Control Tower | Control Tower | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [ControlTowerConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerConfig.html) | [ControlTowerLandingZoneConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerLandingZoneConfig.html) | Define AWS Control Tower LandingZone configuration. When defined, the accelerator will manage AWS Control Tower LandingZone. | -| AWS Control Tower | Control Tower Controls | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [ControlTowerConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerConfig.html) | [ControlTowerControlConfig](../typedocs/latest/classes/_aws_accelerator_config.ControlTowerControlConfig.html) | Define AWS Control Tower controls to be deployed into organizational unit(s). | -| AWS Cost and Usage | Cost and Usage Report | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [ReportConfig](../typedocs/latest/classes/_aws_accelerator_config.ReportConfig.html) | [CostAndUsageReportConfig](../typedocs/latest/classes/_aws_accelerator_config.CostAndUsageReportConfig.html) | Define a global Cost and Usage report configuration for the AWS Organization. | -| AWS Regions | Enabled Regions | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) | [GlobalConfig.enabledRegions](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html#enabledRegions) | Define one or more AWS Regions for the solution to manage. | -| Amazon S3 | Lifecycle Rules | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [LoggingConfig](../typedocs/latest/classes/_aws_accelerator_config.LoggingConfig.html) | [AccessLogBucketConfig](../typedocs/latest/classes/_aws_accelerator_config.AccessLogBucketConfig.html) / [CentralLogBucketConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralLogBucketConfig.html) | Define global lifecycle rules for S3 access log buckets and the central log bucket deployed by the accelerator. | -| AWS Systems Manager Session Manager | Session Manager logging configuration | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) / [LoggingConfig](../typedocs/latest/classes/_aws_accelerator_config.LoggingConfig.html) | [SessionManagerConfig](../typedocs/latest/classes/_aws_accelerator_config.SessionManagerConfig.html) | Define global logging configuration settings for Session Manager. | -| AWS Systems Manager | Parameter Store | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) | [SsmParametersConfig](../typedocs/latest/classes/_aws_accelerator_config.SsmParametersConfig.html) | Define parameters to be stored in SSM Parameter Store. | -| AWS SNS Topics | SNS Topics Configuration | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) | [SnsTopicConfig](../typedocs/latest/classes/_aws_accelerator_config.SnsTopicConfig.html) | Define SNS topics for notifications. | -| AWS Tags | Tags Configuration | [GlobalConfig](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html) | [GlobalConfig.tags](../typedocs/latest/classes/_aws_accelerator_config.GlobalConfig.html#tags) | Define tags to apply to Landing Zone Accelerator created resources. | - -## Identity and Access Management (IAM) Configuration - -Used to manage all of the IAM resources across the AWS Organization. Defined in **iam-config.yaml**. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ------------------------ | ----------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| AWS IAM | Users | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [UserSetConfig](../typedocs/latest/classes/_aws_accelerator_config.UserSetConfig.html) | Define IAM users to be deployed to specified account(s) and/or organizational unit(s). | -| AWS IAM | Groups | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [GroupSetConfig](../typedocs/latest/classes/_aws_accelerator_config.GroupSetConfig.html) | Define IAM groups to be deployed to specified account(s) and/or organizational unit(s). | -| AWS IAM | Policies | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [PolicySetConfig](../typedocs/latest/classes/_aws_accelerator_config.PolicySetConfig.html) | Define customer-managed IAM policies to be deployed to specified account(s) and/or organizational unit(s). | -| AWS IAM | Roles | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [RoleSetConfig](../typedocs/latest/classes/_aws_accelerator_config.RoleSetConfig.html) | Define customer-managed IAM roles to be deployed to specified account(s) and/or organizational unit(s). | -| AWS IAM | SAML identity providers | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [SamlProviderConfig](../typedocs/latest/classes/_aws_accelerator_config.SamlProviderConfig.html) | Define a SAML identity provider to allow federated IAM access to the AWS Organization. | -| AWS IAM Identity Center | Permission sets | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [IdentityCenterConfig](../typedocs/latest/classes/_aws_accelerator_config.IdentityCenterConfig.html) | Define IAM Identity Center (formerly AWS SSO) permission sets and assignments. | -| AWS Managed Microsoft AD | Managed directory | [IamConfig](../typedocs/latest/classes/_aws_accelerator_config.IamConfig.html) | [ManagedActiveDirectoryConfig](../typedocs/latest/classes/_aws_accelerator_config.ManagedActiveDirectoryConfig.html) | Define a Managed Microsoft AD directory. | - -## Network Configuration - -Used to manage and implement network resources to establish a WAN/LAN architecture to support cloud operations and application workloads in AWS. Defined in **network-config.yaml**. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ------------------------------------ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Delete Default Amazon VPC | Default VPC | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [DefaultVpcsConfig](../typedocs/latest/classes/_aws_accelerator_config.DefaultVpcsConfig.html) | If enabled, deletes the default VPC in each account and region managed by the accelerator. | -| AWS Direct Connect | Gateways, virtual interfaces, and gateway associations | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [DxGatewayConfig](../typedocs/latest/classes/_aws_accelerator_config.DxGatewayConfig.html) | Define Direct Connect gateways, virtual interfaces, and Direct Connect Gateway associations. | -| Amazon Elastic Load Balancing | Gateway Load Balancers, endpoint services, and endpoints | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) / [CentralNetworkServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralNetworkServicesConfig.html) | [GwlbConfig](../typedocs/latest/classes/_aws_accelerator_config.GwlbConfig.html) | Define a centrally-managed Gateway Load Balancer with an associated VPC endpoint service. Define Gateway Load Balancer endpoints that consume the service, allowing for deep packet inspection of workloads. | -| AWS Network Firewall | Network Firewalls, policies, and rule groups | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) / [CentralNetworkServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralNetworkServicesConfig.html) | [NfwConfig](../typedocs/latest/classes/_aws_accelerator_config.NfwConfig.html) | Define centrally-managed firewall rule groups and policies. Define Network Firewall endpoints that consume the policies, allowing for deep packet inspection of workloads. | -| Amazon Route 53 Resolver | Resolver endpoints, rules, DNS firewall rule groups, and query logging configurations | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) / [CentralNetworkServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralNetworkServicesConfig.html) | [ResolverConfig](../typedocs/latest/classes/_aws_accelerator_config.ResolverConfig.html) | Define centrally-managed Resolver endpoints, Resolver rules, DNS firewall rule groups, and query logging configurations. DNS firewall rule groups, Resolver rules, and query logging configurations can be associated to VPCs defined in [VpcConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html) / [VpcTemplatesConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcTemplatesConfig.html). | -| AWS Site-to-Site VPN | Customer gateways and VPN connections | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [CustomerGatewayConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomerGatewayConfig.html) | Define Customer gateways and VPN connections that terminate on Transit Gateways or Virtual Private Gateways. | -| AWS Transit Gateway | Transit Gateways and Transit Gateway route tables | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [TransitGatewayConfig](../typedocs/latest/classes/_aws_accelerator_config.TransitGatewayConfig.html) | Define Transit Gateways to deploy to a specified account and region in the AWS Organization. | -| AWS Transit Gateway | Transit Gateway peering connections | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [TransitGatewayPeeringConfig](../typedocs/latest/classes/_aws_accelerator_config.TransitGatewayPeeringConfig.html) | Create Transit Gateway peering connections between two Transit Gateways defined in [TransitGatewayConfig](../typedocs/latest/classes/_aws_accelerator_config.TransitGatewayConfig.html). | -| Amazon VPC | Customer-managed prefix lists | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [PrefixListConfig](../typedocs/latest/classes/_aws_accelerator_config.PrefixListConfig.html) | Define customer-managed prefix lists to deploy to account(s) and region(s) in the AWS Organization. Prefix lists can be referenced in place of CIDR ranges in subnet route tables, security groups, and Transit Gateway route tables. | -| Amazon VPC | DHCP options sets | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [DhcpOptsConfig](../typedocs/latest/classes/_aws_accelerator_config.DhcpOptsConfig.html) | Define custom DHCP options sets to deploy to account(s) and region(s) in the AWS Organization. DHCP options sets can be used by VPCs defined in [VpcConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html) / [VpcTemplatesConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcTemplatesConfig.html). | -| Amazon VPC | Flow Logs (global) | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [VpcFlowLogsConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcFlowLogsConfig.html) | Define a global VPC flow log configuration for VPCs deployed by the accelerator. VPC-specific flow logs can also be created in [VpcConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html) / [VpcTemplatesConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcTemplatesConfig.html). | -| Amazon VPC | VPCs, subnets, security groups, NACLs, route tables, NAT Gateways, and VPC endpoints | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [VpcConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html) | Define VPCs to deploy to a specified account and region in the AWS Organization. | -| Amazon VPC | VPC endpoint policies | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [EndpointPolicyConfig](../typedocs/latest/classes/_aws_accelerator_config.EndpointPolicyConfig.html) | Define custom VPC endpoint policies to deploy to account(s) and region(s) in the AWS Organization. Endpoint policies can be used by interface endpoints and/or gateway endpoints defined in [VpcConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html) / [VpcTemplatesConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcTemplatesConfig.html). | -| Amazon VPC | VPC peering connections | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [VpcPeeringConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcPeeringConfig.html) | Create a peering connection between two VPCs defined in [VpcConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcConfig.html). **NOTE:** Not supported with VPCs deployed using [VpcTemplatesConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcTemplatesConfig.html). | -| Amazon VPC IP Address Manager (IPAM) | IPAM pools and scopes | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) / [CentralNetworkServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralNetworkServicesConfig.html) | [IpamConfig](../typedocs/latest/classes/_aws_accelerator_config.IpamConfig.html) | Enable IPAM delegated administrator and configuration settings for IPAM pools and scopes. **NOTE:** IPAM is required for VPCs and subnets configured to use dynamic IPAM CIDR allocations. | -| Amazon VPC Templates | VPCs, subnets, security groups, NACLs, route tables, NAT Gateways, and VPC endpoints | [NetworkConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkConfig.html) | [VpcTemplatesConfig](../typedocs/latest/classes/_aws_accelerator_config.VpcTemplatesConfig.html) | Deploys a standard-sized VPC to multiple defined account(s) and/or organizational unit(s). | - -## AWS Organizations Configuration - -Used to manage organizational units and policies in the AWS Organization. Defined in **organization-config.yaml**. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ---------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| AWS Account Quarantine | Quarantine | [OrganizationConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationConfig.html) | [QuarantineNewAccountsConfig](../typedocs/latest/classes/_aws_accelerator_config.QuarantineNewAccountsConfig.html) | If enabled, a Service Control Policy (SCP) is applied to newly-created accounts that denies all API actions from principles outside of the accelerator. This SCP is stripped from the new account when the accelerator completes resource provisioning for the new account. | -| AWS Organizations | Backup Policies | [OrganizationConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationConfig.html) | [BackupPolicyConfig](../typedocs/latest/classes/_aws_accelerator_config.BackupPolicyConfig.html) | Define organizational backup policies to be deployed to account(s) and/or organizational unit(s). | -| AWS Organizations | Organizational Units | [OrganizationConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationConfig.html) | [OrganizationalUnitConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationalUnitConfig.html) | Define organizational units (OUs) for the AWS Organization. **NOTE:** When using AWS Control Tower, OUs must be registered in the Control Tower console prior to defining them in the configuration. | -| AWS Organizations | Service Control Policies (SCPs) | [OrganizationConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationConfig.html) | [ServiceControlPolicyConfig](../typedocs/latest/classes/_aws_accelerator_config.ServiceControlPolicyConfig.html) | Define organizational service control policies to be deployed to account(s) and/or organizational unit(s). | -| AWS Organizations | Tag Policies | [OrganizationConfig](../typedocs/latest/classes/_aws_accelerator_config.OrganizationConfig.html) | [TaggingPolicyConfig](../typedocs/latest/classes/_aws_accelerator_config.TaggingPolicyConfig.html) | Define organizational tag policies to be deployed to account(s) and/or organizational unit(s). | - -## Security Configuration - -Used to manage configuration of AWS security services. Defined in **security-config.yaml**. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ------------------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| AWS Audit Manager | Audit Manager | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [AuditManagerConfig](../typedocs/latest/classes/_aws_accelerator_config.AuditManagerConfig.html) | Enable Audit Manager delegated administrator and configuration settings. | -| Amazon CloudWatch | Metrics, Alarms, and Log Groups | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) | [CloudWatchConfig](../typedocs/latest/classes/_aws_accelerator_config.CloudWatchConfig.html) | Define CloudWatch metrics, alarms, and log groups to deploy into account(s) and/or organizational unit(s). You can also import existing log groups into your configuration. | -| AWS Config | Config Recorder, Delivery Channel, Rules, and Remediations | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) | [AwsConfig](../typedocs/latest/classes/_aws_accelerator_config.AwsConfig.html) | Define an AWS Config Recorder, Delivery Channel, and custom and/or managed rule sets to deploy across the AWS Organization. | -| Amazon Detective | Detective | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [DetectiveConfig](../typedocs/latest/classes/_aws_accelerator_config.DetectiveConfig.html) | Enable Detective delegated administrator and configuration settings. **Note:** Requires Amazon GuardDuty to be enabled for at least 48 hours. | -| Amazon EBS | Default Volume Encryption | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [EbsDefaultVolumeEncryptionConfig](../typedocs/latest/classes/_aws_accelerator_config.EbsDefaultVolumeEncryptionConfig.html) | Enable EBS default volume encryption across the AWS Organization. | -| Amazon GuardDuty | GuardDuty | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [GuardDutyConfig](../typedocs/latest/classes/_aws_accelerator_config.GuardDutyConfig.html) | Enable GuardDuty delegated administrator and configuration settings. | -| AWS IAM | Access Analyzer | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) | [AccessAnalyzerConfig](../typedocs/latest/classes/_aws_accelerator_config.AccessAnalyzerConfig.html) | If enabled, IAM Access Analyzer analyzes policies and reports a list of findings for resources that grant public or cross-account access from outside your AWS Organizations in the IAM console and through APIs. | -| AWS IAM | Password Policy | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) | [IamPasswordPolicyConfig](../typedocs/latest/classes/_aws_accelerator_config.IamPasswordPolicyConfig.html) | Define a password policy for IAM users in the AWS Organization. | -| AWS KMS | Customer-Managed Keys | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) | [KeyManagementServiceConfig](../typedocs/latest/classes/_aws_accelerator_config.KeyManagementServiceConfig.html) | Define customer-managed KMS keys to be deployed to account(s) and/or organizational unit(s). | -| Amazon Macie | Macie | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [MacieConfig](../typedocs/latest/classes/_aws_accelerator_config.MacieConfig.html) | Enable Macie delegated administrator and configuration settings. | -| Amazon S3 | S3 Public Access Block | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [S3PublicAccessBlockConfig](../typedocs/latest/classes/_aws_accelerator_config.S3PublicAccessBlockConfig.html) | Enable S3 public access block setting across the AWS Organization. | -| AWS Security Hub | Security Hub | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [SecurityHubConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityHubConfig.html) | Enable Security Hub delegated administrator and configuration settings. | -| Amazon SNS | Subscriptions | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [SnsSubscriptionConfig](../typedocs/latest/classes/_aws_accelerator_config.SnsSubscriptionConfig.html) | Configure email subscriptions for security-related SNS notifications. **NOTE:** **DEPRECATED** Use SnsTopicConfig in the global configuration instead. | -| AWS Systems Manager Automation | Automation Documents | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) / [CentralSecurityServicesConfig](../typedocs/latest/classes/_aws_accelerator_config.CentralSecurityServicesConfig.html) | [SsmAutomationConfig](../typedocs/latest/classes/_aws_accelerator_config.SsmAutomationConfig.html) | Define SSM Automation Documents to be deployed to account(s) and/or organizational unit(s). | -| Resource Policy Enforcement | Resource Policy Enforcement Config | [SecurityConfig](../typedocs/latest/classes/_aws_accelerator_config.SecurityConfig.html) | [ResourcePolicyEnforcementConfig](../typedocs/latest/classes/_aws_accelerator_config.ResourcePolicyEnforcementConfig.html) | Define compliance check and remediation for resource-based policy to be deployed to account(s) and/or organization unit, which will enforce resource policy for all applicable resources in the account and/or OU. | - -## Customization Configuration - -Used to manage configuration of custom applications and CloudFormation stacks. Defined in the optional file **customizations-config.yaml**. - -| Service / Feature | Resource | Base Configuration | Service / Feature Configuration | Details | -| ----------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| AWS CloudFormation | Stacks | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [CustomizationConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationConfig.html) | [CloudFormationStackConfig](../typedocs/latest/classes/_aws_accelerator_config.CloudFormationStackConfig.html) | Define custom CloudFormation Stacks. | -| AWS CloudFormation | StackSets | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [CustomizationConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationConfig.html) | [CloudFormationStackSetConfig](../typedocs/latest/classes/_aws_accelerator_config.CloudFormationStackSetConfig.html) | Define custom CloudFormation Stacksets. | -| Amazon Elastic Load Balancing | Application Load Balancers | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [AppConfigItem](../typedocs/latest/classes/_aws_accelerator_config.AppConfigItem.html) | [ApplicationLoadBalancerConfig](../typedocs/latest/classes/_aws_accelerator_config.ApplicationLoadBalancerConfig.html) | Define an Application Load Balancer to be used for a custom application. | -| Amazon Elastic Load Balancing | Network Load Balancers | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [AppConfigItem](../typedocs/latest/classes/_aws_accelerator_config.AppConfigItem.html) | [NetworkLoadBalancerConfig](../typedocs/latest/classes/_aws_accelerator_config.NetworkLoadBalancerConfig.html) | Define a Network Load Balancer to be used for a custom application. | -| Amazon Elastic Load Balancing | Target Groups | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [AppConfigItem](../typedocs/latest/classes/_aws_accelerator_config.AppConfigItem.html) | [TargetGroupItemConfig](../typedocs/latest/classes/_aws_accelerator_config.TargetGroupItemConfig.html) | Define a Target Group to be used with an Elastic Load Balancer. | -| Amazon EC2 | Autoscaling Groups | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [AppConfigItem](../typedocs/latest/classes/_aws_accelerator_config.AppConfigItem.html) | [AutoScalingConfig](../typedocs/latest/classes/_aws_accelerator_config.AutoScalingConfig.html) | Define an autoscaling group to be used for a custom application. | -| Amazon EC2 | Launch Template | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [AppConfigItem](../typedocs/latest/classes/_aws_accelerator_config.AppConfigItem.html) | [LaunchTemplateConfig](../typedocs/latest/classes/_aws_accelerator_config.LaunchTemplateConfig.html) | Define a launch template to be used for a custom application. | -| Amazon EC2 | Next-generation firewalls (standalone or autoscaling) and firewall management appliances | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) | [Ec2FirewallConfig](../typedocs/latest/classes/_aws_accelerator_config.Ec2FirewallConfig.html) | Define third-party EC2-based firewall appliances. | -| AWS Service Catalog | Portfolios, products, and shares | [CustomizationsConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationsConfig.html) / [CustomizationConfig](../typedocs/latest/classes/_aws_accelerator_config.CustomizationConfig.html) | [PortfolioConfig](../typedocs/latest/classes/_aws_accelerator_config.PortfolioConfig.html) | Define Service Catalog portfolios, products, and grant access permissions. You may also share portfolios to other accounts and OUs. | - -## Other Services and Features - -Other mandatory and non-configurable services/features deployed by the solution are described in the [Architecture overview](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-overview.html) and [Architecture details](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-details.html) section of the solution [Implementation Guide](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/solution-overview.html). \ No newline at end of file diff --git a/source/mkdocs/docs/user-guide/index.md b/source/mkdocs/docs/user-guide/index.md deleted file mode 100644 index c78b8d2..0000000 --- a/source/mkdocs/docs/user-guide/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# User Guide - -This section contains architectural details and configuration references for the Landing Zone Accelerator solution. - -!!! info "Subpages" - - [Services, Features, and Configuration References](./config.md) - - [Centralized Logging](./logging.md) - - [Security Hub Findings](./securityhub-findings.md) - -!!! note "See also" - - [Implementation Guide - Architecture Details](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/architecture-details.html) - - [Implementation Guide - Use the solution](https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/use-the-solution.html) \ No newline at end of file diff --git a/source/mkdocs/docs/user-guide/logging.md b/source/mkdocs/docs/user-guide/logging.md deleted file mode 100644 index 093811d..0000000 --- a/source/mkdocs/docs/user-guide/logging.md +++ /dev/null @@ -1,61 +0,0 @@ -# Centralized Logging - -The Landing Zone Accelerator Centralized Logging solution provides the ability to consolidate and manage log files from various sources into a Centralized Logging Account. This enables users to consolidate logs such as audit logs for access, configuration changes, and billing events. You can also collect Amazon CloudWatch Logs from multiple accounts and AWS Regions. The following sections discuss the types of logs that are centralized and the mechanisms used by the accelerator to centralize them. - -## Supported Log Types - -- ELB Access Logs -- VPC Flow Logs -- Macie Reports -- Cost and Usage Reports -- Config History -- Config Snapshots -- GuardDuty Findings -- CloudWatch Logs -- CloudTrail Digest -- CloudTrail Insights -- CloudTrail Logs -- S3 Server Access Logs -- SSM Inventory -- SSM Session Manager -- Security Hub Findings - -## Log Centralization Methods - -- **S3 Replication** - Log types that do not support service-native central logging methods or logging to CloudWatch Logs are stored in account-specific S3 buckets. These buckets are configured with an S3 replication rule to replicate logs to centralized logging S3 bucket in the central logging account. -- **Service-Native** - The AWS Service writes directly to the centralized logging bucket in the central logging account. -- **Log Streaming** - Some services do not support native centralized logging capability and do not allow writing directly to S3 in a centralized account. In order to enable this functionality, the accelerator utilizes CloudWatch and native log forwarding capabilities via the following workflow: - - Log Group is created in CloudWatch. - - A subscription filter is added to the CloudWatch Log Group. - - The subscription filter points to a Log Destination. - - The Log Destination is a region specific Kinesis Stream in the Central Logging Account. - - Each enabled region has its own Kinesis Stream in the Central Logging Account. - - The Kinesis Streams are forwarded into a Kinesis Firehose in the same specific region. - - The logs are processed by a Lambda function and written to the Central Logging S3 Bucket in the Home Region. -- **Not Replicated** - Log types that are not replicated to the centralized logging S3 bucket. - -| Bucket Type | Bucket Name | Purpose | -| :------------------------- | :-------------------------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| Centralized Logging Bucket | aws-accelerator-central-logs-{account#}-{region} | Stores all Landing Zone Accelerator centralized logs that have been enabled via the accelerator. This mechanism allows the solution to store a combined set of logs in a single account and single region. | -| ELB Access Logs | aws-accelerator-elb-access-logs-{account#}-{region} | Stores ELB Access logs in the centralized logging account on a per region basis. | -| S3 Access Logs | aws-accelerator-s3-access-logs-{account#}-{region} | Stores S3 Access logs on a per account/region basis. | - -| Log Type | S3 Path | Example | Supported Centralization Methods | -| :------------------------------- | :------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -------------------------------: | -| ELB | {account#}/{region}/\* | s3://aws-accelerator-elb-access-logs-123456789016-us-east-1/{account#}/{region}/\*.log.gz | S3 Replication | -| VPC Flow Logs[^1] | vpc-flow-logs/AWSLogs/{account#}/vpcflowlogs/{region}/{year}/{month}/{day}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/vpc-flow-logs/AWSLogs/123456789016/vpcflowlogs/us-east-1/2023/04/14/\*.log.gz | Log Streaming / S3 Replication | -| Macie Reports | macie/{account#}/AWSLogs/{account#}/Macie/{region}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/macie/123456789016/AWSLogs/123456789016/Macie/us-east-1/\*.jsonl.gz | Service-Native | -| Cost and Usage Reports | cur/{account#}/accelerator-cur/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/cur/123456789016/accelerator-cur/20220901-20221001/\*.snappy.parquet | S3 Replication | -| Config History | config/AWSLogs/{account#}/Config/{region}/{year}/{month}/{day}/ConfigHistory/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/AWSLogs/123456789016/Config/us-east-1/2023/4/10/ConfigHistory/\*.json.gz | Service-Native | -| Config Snapshots | config/AWSLogs/{account#}/Config/{region}/{year}/{month}/{day}/ConfigSnapshot/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/AWSLogs/123456789016/Config/us-east-1/2023/4/10/ConfigSnapshot/\*.json.gz | Service-Native | -| GuardDuty | guardduty/AWSLogs/{account#}/GuardDuty/region/{year}/{month}/{day}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/guardduty/AWSLogs/123456789016/GuardDuty/us-east-1/2023/04/08/\*.jsonl.gz | Service-Native | -| CloudWatch Logs | CloudWatchLogs/{year}/{month}/{day}/{hour}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/CloudWatchLogs/2023/04/17/14/\*.parquet | Log Streaming | -| CloudTrail Organization Digest | cloudtrail-organization/AWSLogs/{organizationId}/{account#}/CloudTrail-Digest/{region}/{year}/{month}/{day}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/cloudtrail-organization/AWSLogs/o-abc12cdefg/123456789016/CloudTrail-Digest/us-east-1/2023/04/14/\*.json.gz | Service-Native | -| CloudTrail Organization Insights | cloudtrail-organization/AWSLogs/{organizationID}/{account#}/CloudTrail-Insight/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/cloudtrail-organization/AWSLogs/o-abc12cdefg/123456789016/CloudTrail-Insight/\*.json.gz | Service-Native | -| CloudTrail Organization Logs[^1] | cloudtrail-organization/AWSLogs/{organizationId}/{account#}/CloudTrail/{region}/{year}/{month}/{day}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/cloudtrail-organization/AWSLogs/o-abc12cdefg//123456789016/CloudTrail/us-east-1/2023/04/14/\*.json.gz | Log Streaming / Service-Native | -| S3 Access Logs | aws-accelerator-s3-access-logs-{account#}-{region}/\* | s3://aws-accelerator-s3-access-logs-123456789016-us-east-1/\* | Not Replicated | -| SSM Inventory | ssm-inventory/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/ssm-inventory/AWS:ComplianceSummary/accountid=123456789016/region=us-east-1/resourcetype=ManagedInstanceInventory/\*.json | Service-Native | -| SSM Sessions Manager* | session/{account#}/{region}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/session/123456789016/us-east-1/\*.log | Log Streaming / Service-Native | -| Security Hub | CloudWatchLogs/{year}/{month}/{day}/\* | s3://aws-accelerator-central-logs-123456789016-us-east-1/CloudWatchLogs/2023/04/21/00/\*.parquet | Log Streaming | - -[^1]: These log types are only written to the documented S3 path when configured to store their logs in S3, otherwise they are stored in the CloudWatch Logs S3 path. You may configure [dynamic partitioning](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/latest/classes/_aws_accelerator_config.CloudWatchLogsConfig.html#dynamicPartitioning) of CloudWatch Logs if you would like these logs to be delivered to a custom S3 path. \ No newline at end of file diff --git a/source/mkdocs/docs/user-guide/securityhub-findings.md b/source/mkdocs/docs/user-guide/securityhub-findings.md deleted file mode 100644 index 8f2b94d..0000000 --- a/source/mkdocs/docs/user-guide/securityhub-findings.md +++ /dev/null @@ -1,31 +0,0 @@ -# Security Hub Findings - -The Landing Zone Accelerator on AWS (LZA) solution includes a feature to enable AWS Security Hub, which provides a comprehensive view of the security state and helps you assess your environment against security standards and best practices. When deploying LZA, it is essential to review and address the Security Hub findings to ensure a secure environment based on your requirements. The following table provides guidance on AWS Security Hub findings that you may encounter during the deployment of LZA using sample configurations. - -!!! note - You should regularly review and monitor new findings in AWS Security Hub, as your environment and workloads evolve over time. This guidance applies to the resources created and managed by the Landing Zone Accelerator on AWS solution. For other findings in your environment, it is recommend to follow the [Security Hub controls reference](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-controls-reference.html). - -| ID | Title | Severity | Guidance | -|--- |--- |--- |--- | -| [Account.1](https://docs.aws.amazon.com/securityhub/latest/userguide/account-controls.html#account-1) | Security contact information should be provided for an AWS account | Medium | This finding is generated when security contact information for an AWS account is not provided.

    To add an alternate contact as a security contact to your AWS account, see [Adding, changing, or removing alternate contacts](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/manage-account-payment.html#manage-account-payment-alternate-contacts) in the *AWS Billing and Cost Management User Guide*. | -| [DynamoDB.4](https://docs.aws.amazon.com/securityhub/latest/userguide/dynamodb-controls.html#dynamodb-4) | DynamoDB tables should be present in a backup plan | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following Amazon DynamoDB tables with prefix `{ACCELERATOR_PREFIX}`:

    - `{ACCELERATOR_PREFIX}`-PrepareStack-` {ACCOUNT_ID}`-`{REGION}`AcceleratorConfigTableXXXXX
    - `{ACCELERATOR_PREFIX}`-PrepareStack-`{ACCOUNT_ID}`-`{REGION}`NewCTAccountsXXXX
    - `{ACCELERATOR_PREFIX}`-PrepareStack-`{ACCOUNT_ID}`-`{REGION}`-NewOrgAccountsXXXX

    You can disregard this finding, as these DynamoDB tables are managed by LZA to store environment specific information. All these tables have point-in-time recovery enabled which automate backups, and may be needed for troubleshooting in rare cases. Deleting them will not impact LZA, as they will be repopulated during next pipeline execution. Alternatively, you can choose to suppress the finding for these resources. | -| [DynamoDB.6](https://docs.aws.amazon.com/securityhub/latest/userguide/dynamodb-controls.html#dynamodb-6) | DynamoDB tables should have deletion protection enabled | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following Amazon DynamoDB tables with prefix `{ACCELERATOR_PREFIX}`:

    - `{ACCELERATOR_PREFIX}`-PrepareStack-`{ACCOUNT_ID}`-`{REGION}`AcceleratorConfigTableXXXXX
    - `{ACCELERATOR_PREFIX}`-PrepareStack-`{ACCOUNT_ID}`-`{REGION}`NewCTAccountsXXXX
    - `{ACCELERATOR_PREFIX}`-PrepareStack-`{ACCOUNT_ID}`-`{REGION}`-NewOrgAccountsXXXX

    You can disregard this finding, as these DynamoDB tables are managed by LZA to store environment specific information. Deleting them will not impact LZA, as they will be repopulated during next pipeline execution. Alternatively, you can choose to suppress the finding for these resources.| -| [EC2.10](https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-10) | Amazon EC2 should be configured to use VPC endpoints that are created for the Amazon EC2 service | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following Amazon VPCs:

    - Network-Inspection
    - SharedServices-Main

    You can disregard this finding, as these VPCs are configured to utilize centralized endpoints. AWS Security Hub does not conduct cross-account checks for VPCs that are shared across accounts. Alternatively, you can choose to suppress the finding for these resources. | -| [EC2.21](https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-21) | Network ACLs should not allow ingress from 0.0.0.0/0 to port 22 or port 3389 | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes following Amazon VPCs with network ACLs:

    - Network-Endpoints
    - Network-Inspection
    - SharedServices-Main

    The LZA creates VPCs that is defined in the network-config.yaml file, and your VPCs automatically comes with a modifiable default network ACL that allows all inbound and outbound IPv4 traffic. You can implement additional NACL with specific rules by creating a custom network ACL and associate it with a subnet to allow or deny specific inbound or outbound traffic to address this finding.

    To create custom Network ACLs with defined inbound and outbound rule, you can use add the following [networkACL](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/latest/classes/_aws_accelerator_config.NetworkAclConfig.html) example in the VPCs defined in network-config.yaml configuration file:

    `networkAcls:`
         - `name: accelerator-nacl`
           `subnetAssociations:`
               - `Subnet-A`
           `inboundRules`
               - `rule: 200`
                 `protocol: 6`
                 `fromPort: 22`
                 `action: allow`
                 `source: `
           `outboundRules:`
              - `rule: 200`
                 `protocol: 6`
                 `fromPort: 1024`
                 `toPort: 65535`
                 `action: allow`
                 `destination: ` | -| [EC2.23](https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-23) | Amazon EC2 Transit Gateways should not automatically accept VPC attachment requests | High | When using the Landing Zone Accelerator on AWS solution, this finding includes following AWS Transit Gateway named Network-Main.

    The LZA uses `autoAcceptSharingAttachments: enable` features to shared out transit gateway (via Resource Access Manager) with only accounts shared through `shareTargets` property in the AWS Organization. You can disregard this findings as the automatic acceptance of VPCs attachment request is only allowed for accounts present in the AWS Organization and defined in the `shareTargets` property of transit gateway. Alternatively, you can choose to suppress the finding for these resources. | -| [ECR.1](https://docs.aws.amazon.com/securityhub/latest/userguide/ecr-controls.html#ecr-1) | ECR private repositories should have image scanning configured | High | When using the Landing Zone Accelerator on AWS solution, this finding includes `{cdk-accel}`-container-assets-`{ACCOUNT_ID}`-`{REGION}` Amazon ECR.

    During [bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-template) process with AWS Cloud Development Kit (CDK), the Amazon Elastic Container Registry (ECR) repository is automatically created to store docker images used by AWS CDK. You can disregard this finding, as this repository is not used by LZA for storing assets. Alternatively, you can choose to suppress this finding for these resources. | -| [ECR.3](https://docs.aws.amazon.com/securityhub/latest/userguide/ecr-controls.html#ecr-3) | ECR repositories should have at least one lifecycle policy configured | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes `{cdk-accel}`-container-assets-`{ACCOUNT_ID}`-`{REGION}` Amazon ECR.

    During [bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-template) process with AWS Cloud Development Kit (CDK), the Amazon Elastic Container Registry (ECR) repository is automatically created to store docker images used by AWS CDK. You can disregard this finding, as this repository is not used by LZA for storing assets. Alternatively, you can choose to suppress this finding for these resources. | -| [IAM.6](https://docs.aws.amazon.com/securityhub/latest/userguide/iam-controls.html#iam-6) | Hardware MFA should be enabled for the root user | Critical | This finding is generated when your AWS account is not configured to use a hardware multi-factor authentication (MFA) device to sign in with root user credentials.

    To add a hardware MFA device for the root user, see [Enable a hardware MFA device for the AWS account root user (console)](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_physical.html#enable-hw-mfa-for-root) in the *IAM User Guide* | -| [IAM.9](https://docs.aws.amazon.com/securityhub/latest/userguide/iam-controls.html#iam-9) | MFA should be enabled for the root user | Critical | This finding is generated when your AWS account is not enabled to use a multi-factor authentication (MFA) device to sign in with root user credentials.

    To enable MFA for the root user, see [Activate MFA on the AWS account root user](https://docs.aws.amazon.com/accounts/latest/reference/root-user-mfa.html) in the *AWS Account Management Reference Guide* | -| [IAM.18](https://docs.aws.amazon.com/securityhub/latest/userguide/iam-controls.html#iam-18) | Ensure a support role has been created to manage incidents with AWS Support | Low |This finding is generated when support role is not created to manage incidents with AWS Support.

    To create the role for AWS Support access, you can use add it to the role in the [iam-config.yaml](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/typedocs/latest/classes/_aws_accelerator_config.RoleSetConfig.html) configuration file, as shown in the provided example:

    `roleSets:`
        - `deploymentTargets:`
              `organizationalUnits:`
                  - `Root`
         `roles:`
              - `name: `
              `assumedBy:`
                   - `type: account`
                   `principal: `
              `policies:`
                   `awsManaged:`
                     - `AWSSupportAccess` | -| [KMS.1](https://docs.aws.amazon.com/securityhub/latest/userguide/kms-controls.html#kms-1) | IAM customer managed policies should not allow decryption actions on all KMS keys | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following AWS IAM customer managed policies with prefix `{ACCELERATOR_PREFIX}`:

    - `{ACCELERATOR_PREFIX}`-OperationsStack-`{ACCOUNT_ID}`-`{REGION}`-SsmSessionManagerSettingsSessionManagerPolicyXXXX
    - `{ACCELERATOR_PREFIX}`-SessionManagerLogging

    You can disregard this finding, as these IAM policies are managed by LZA to enable decryption operations on KMS keys. These policies have conditions that restrict their usage to the LZA generated alias of KMS keys, ensuring that the decryption actions are performed only within the specific account and region. Additionally, the usage of these policies is restricted to solution created resources and protected by service control policies in the member accounts. Alternatively, you can choose to suppress the finding for these resources. | -| [KMS.2](https://docs.aws.amazon.com/securityhub/latest/userguide/kms-controls.html#kms-2) | IAM principals should not have IAM inline policies that allow decryption actions on all KMS keys | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following AWS IAM principles with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}`:

    - `{ACCELERATOR_PREFIX}`-InstallerS-UpdatePipelineLambdaRoleXXXX (UpdatePipelineLambdaPolicy284ABC36)
    - `{cdk-accel}`-file-publishing-role-211125696757-us-east-1
    - `{cdk-accel}`-deploy-role-211125696757-us-east-1

    You can disregard this finding related to AWS IAM principles with the prefix `{ACCELERATOR_PREFIX}`, as these roles with inline polices are managed by LZA to enable decryption operations on KMS keys. These policies are used by lambda functions created by the solution and performs decryption actions only within the specific account and region adhering to the principle of least privilege.

    For the roles related to the AWS Cloud Developmental Kit (CDK), enabling the `useManagementAccessRole` option in the `global-config.yaml` file modifies CDK operations to use the IAM role specified in the `managementAccountAccessRole` option rather than the default roles created by CDK. The default CDK roles will still be created but will be only used for initial cdk resources setup. This finding guidance is mentioned in the CDK [documentation](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-securityhub). Therefore, you can disregard those roles.

    Alternatively, you can choose to suppress the finding for these resources. | -| [Lambda.3](https://docs.aws.amazon.com/securityhub/latest/userguide/lambda-controls.html#lambda-3) | Lambda functions should be in a VPC | Low | When using the Landing Zone Accelerator on AWS solution, this finding includes AWS Lambda functions with the prefix `{ACCELERATOR_PREFIX}`.

    You can disregard this finding, as these functions are created and managed by the LZA for infrastructure deployment, and solely used for communicating with AWS services. Alternatively, you can choose to suppress the finding for these resources. | -| [S3.6](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-6) | S3 general purpose bucket policies should restrict access to other AWS accounts | High | When using the Landing Zone Accelerator on AWS solution, this finding includes the following Amazon S3 buckets with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}`:

    - `{ACCELERATOR_PREFIX}`-central-logs-`{ACCOUNT_ID}`-`{REGION}`
    - `{cdk-accel}`-assets-`{ACCOUNT_ID}`-`{REGION}`

    You can disregard this finding, as these buckets are created and managed by the LZA and AWS Cloud Development Kit (CDK). `{ACCELERATOR_PREFIX}` bucket stores solution artifacts and environment logs, while `{cdk-accel}` bucket stores files during bootstrapping process with AWS CDK. These buckets contains resources policies with conditions that restrict their usage to only accounts in the organization with access to limited roles including `{ACCELERATOR_PREFIX}`,`{managementAccountAccessRole}` and `{cdk-accel}`. Additionally, the usage of these roles are protected by service control policies in the members accounts. Alternatively, you can choose to suppress the finding for these resources.| -| [S3.7](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-7) | S3 general purpose buckets should use cross-Region replication | Low | When using the Landing Zone Accelerator on AWS solution, this finding includes Amazon S3 buckets with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}`.

    You can disregard this finding, as `{ACCELERATOR_PREFIX}` bucket is created and managed by the LZA to store solution artifacts and environment logs. These buckets have versioning which keeps multiple variants of an object in the same s3 buckets to preserve, retrieve, and restore earlier versions of an object.

    For the bucket related to `{cdk-accel}`, this bucket is automatically created for storing files during the [bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-template) process with AWS Cloud Development Kit (CDK). You can disregard this finding, as this bucket stores and manages assets, templates, and metadata related to your CDK deployments.

    Alternatively, you can choose to suppress this finding for these resources. | -| [S3.9](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-9) | S3 general purpose buckets should have server access logging enabled | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following Amazon S3 buckets with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}` :

    - `{ACCELERATOR_PREFIX}`-s3-access-logs-`{ACCOUNT_ID}`-`{REGION}`
    - `{ACCELERATOR_PREFIX}`-s3-logs-`{ACCOUNT_ID}`-`{REGION}`
    - `{ACCELERATOR_PREFIX}`-elb-access-logs-`{ACCOUNT_ID}`-`{REGION}`
    - `{cdk-accel}`-assets-`{ACCOUNT_ID}`-`{REGION}`.

    You can disregard this finding for s3-access-logs, s3-logs and elb-access-logs buckets with prefix , `{ACCELERATOR_PREFIX}` as these are target logging buckets and do not require server access logging enabled.

    For the buckets related to `{cdk-accel}`, this bucket is automatically created for storing files during the [bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-template) process with AWS Cloud Development Kit (CDK). You can disregard this finding, as this bucket stores and manages assets, templates, and metadata related to your CDK deployments.

    Alternatively, you can choose to suppress the finding for these resources. | -| [S3.11](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-11) | S3 general purpose buckets should have event notifications enabled | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes all Amazon S3 buckets with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}`.

    You can disregard this finding, as these buckets are created and managed by the LZA and AWS Cloud Development Kit (CDK) to store solution artifacts and environment logs. These buckets have versioning which keeps multiple variants of an object in the same s3 buckets to preserve, retrieve, and restore earlier versions of an object. Alternatively, you can choose to suppress the finding for these resources. | -| [S3.15](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-15) | S3 general purpose buckets should have Object Lock enabled | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the Amazon S3 buckets with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}`.

    You cannot [enable object lock](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock-configure.html)for destination buckets `{ACCELERATOR_PREFIX}`-s3-access-logs-`{ACCOUNT-ID}`-`{REGION}`, `{ACCELERATOR_PREFIX}`-s3-logs-`{ACCOUNT-ID}`-`{REGION}`, `{ACCELERATOR_PREFIX}`-elb-access-logs-`{ACCOUNT-ID}`-`{REGION}`, used for server access logs.

    You can disregard this finding for remaining buckets, as these buckets are created and managed by the LZA and AWS Cloud Development Kit (CDK) to store solution artifacts and environment logs. These buckets have versioning which keeps multiple variants of an object in the same s3 buckets to preserve, retrieve, and restore earlier versions of an object. Alternatively, you can choose to suppress the finding for these resources. | -| [S3.17](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-17) | S3 general purpose buckets should be encrypted at rest with AWS KMS keys | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following Amazon S3 buckets:

    - `{ACCELERATOR_PREFIX}`-assets-logs-`{ACCOUNT_ID}`-`{REGION}`
    - `{ACCELERATOR_PREFIX}`-s3-access-logs-`{ACCOUNT_ID}`-`{REGION}`
    - `{ACCELERATOR_PREFIX}`-cur-`{ACCOUNT_ID}`-`{REGION}`
    - `{ACCELERATOR_PREFIX}`-s3-logs-`{ACCOUNT_ID}`-`{REGION}`

    You can disregard this finding, as these buckets are created and managed by the LZA to store solution artifacts and environment logs, and enabled server side encryption with Amazon S3 managed keys (SSE-S3). Additionally, default server-side encryption with AWS Key Management Service (AWS KMS) keys (SSE-KMS) is not supported for [server access logging buckets](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html). Alternatively, you can choose to suppress the finding for these resources. | -| [S3.20](https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-20) | S3 general purpose buckets should have MFA delete enabled| Low | When using the Landing Zone Accelerator on AWS solution, this finding includes the all Amazon S3 buckets with prefixes `{ACCELERATOR_PREFIX}` and `{cdk-accel}`.

    You can disregard this finding, as these buckets are created and managed by the LZA and AWS Cloud Development Kit (CDK) to store solution artifacts and environment logs. These buckets have Lifecycle configurations setup , and you cannot use [MFA delete](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html) for buckets with lifecycle configurations. Alternatively, you can choose to suppress the finding for these resources. || -| [StepFunctions.1](https://docs.aws.amazon.com/securityhub/latest/userguide/stepfunctions-controls.html#stepfunctions-1)| Step Functions state machines should have logging turned on | Medium | When using the Landing Zone Accelerator on AWS solution, this finding includes the following AWS Step Functions:

    - CreateCTAccountsXXXX
    - CreateOrganizationAccountsXXXX

    You can disregard this finding, as these step functions are created and managed by the LZA to execute Lambda functions for account creation. These Lambda functions associated with Step Functions have enabled cloudwatch logs for monitoring and logging purposes. Alternatively, you can choose to suppress the finding for these resources. | diff --git a/source/mkdocs/mkdocs.yml b/source/mkdocs/mkdocs.yml deleted file mode 100644 index b6f36b3..0000000 --- a/source/mkdocs/mkdocs.yml +++ /dev/null @@ -1,144 +0,0 @@ -site_name: Landing Zone Accelerator on AWS -site_url: - !ENV [SITE_URL, https://awslabs.github.io/landing-zone-accelerator-on-aws/] -site_author: Amazon Web Services -site_description: Documentation for the Landing Zone Accelerator on AWS -repo_url: https://github.com/awslabs/landing-zone-accelerator-on-aws/ - -nav: - - Home: index.md - - Installation: installation.md - - User Guide: - - user-guide/index.md - - Services, Features, and Configuration References: user-guide/config.md - - Centralized Logging: user-guide/logging.md - - SecurityHub Findings: user-guide/securityhub-findings.md - - Developer Guide: - - developer-guide/index.md - - Development Dependencies: developer-guide/dependencies.md - - Command Line Interface and Package Scripts: developer-guide/scripts.md - - Architecture and Design Philosophy: developer-guide/design.md - - Feature Development: developer-guide/features.md - - Documentation Guidelines: developer-guide/doc-guidelines.md - - Sample Configurations: - - sample-configurations/index.md - - Standard: - - sample-configurations/standard/index.md - - Overview: sample-configurations/standard/overview.md - - Organization and Account Structure: sample-configurations/standard/org-structure.md - - Authentication and Authorization: sample-configurations/standard/authn-authz.md - - Logging and Monitoring: sample-configurations/standard/logging-monitoring.md - - Networking: sample-configurations/standard/networking.md - - GovCloud (US): - - sample-configurations/govcloud-us/index.md - - Overview: sample-configurations/govcloud-us/overview.md - - Organization and Account Structure: sample-configurations/govcloud-us/org-structure.md - - Security Controls: sample-configurations/govcloud-us/security-controls.md - - Networking: sample-configurations/govcloud-us/networking.md - - Additional Considerations: sample-configurations/govcloud-us/considerations.md - - FAQ: - - faq/index.md - - General: faq/general.md - - Architecture: faq/architecture.md - - AWS Control Tower and Customizations for Control Tower: faq/ct-cfct.md - - Customizations: faq/customizations.md - - Operations: faq/operations.md - - Networking: - - General: faq/networking/general.md - - Deep Packet Inspection: faq/networking/dpi.md - - AWS Direct Connect: faq/networking/direct-connect.md - - AWS Network Firewall: faq/networking/network-firewall.md - - AWS Gateway Load Balancer: faq/networking/gwlb.md - - Security: faq/security.md - - Logging: - - Amazon CloudWatch Log Group: faq/logging/cwl.md - - TypeDocs: - - typedocs/index.md - - v1.0: - - v1.0.0: typedocs/v1.0.0/index.html - - v1.0.1: typedocs/v1.0.1/index.html - - v1.1: - - v1.1.0: typedocs/v1.1.0/index.html - - v1.2: - - v1.2.0: typedocs/v1.2.0/index.html - - v1.2.1: typedocs/v1.2.1/index.html - - v1.2.2: typedocs/v1.2.2/index.html - - v1.3: - - v1.3.0: typedocs/v1.3.0/index.html - - v1.3.1: typedocs/v1.3.1/index.html - - v1.3.2: typedocs/v1.3.2/index.html - - v1.4: - - v1.4.0: typedocs/v1.4.0/index.html - - v1.4.1: typedocs/v1.4.1/index.html - - v1.4.2: typedocs/v1.4.2/index.html - - v1.4.3: typedocs/v1.4.3/index.html - - v1.5: - - v1.5.0: typedocs/v1.5.0/index.html - - v1.5.1: typedocs/v1.5.1/index.html - - v1.5.2: typedocs/v1.5.2/index.html - - v1.6: - - v1.6.0: typedocs/v1.6.0/index.html - - v1.6.1: typedocs/v1.6.1/index.html - - v1.6.2: typedocs/v1.6.2/index.html - - v1.6.3: typedocs/v1.6.3/index.html - - v1.6.4: typedocs/v1.6.4/index.html - - v1.7: - - v1.7.0: typedocs/v1.7.0/index.html - - v1.7.1: typedocs/v1.7.1/index.html - - v1.8: - - v1.8.0: typedocs/v1.8.0/index.html - - v1.8.1: typedocs/latest/index.html - -theme: - name: material - highlightjs: true - hljs_languages: - - yaml - - typescript - features: - - content.code.copy - - navigation.tracking - - navigation.instant - - navigation.indexes - - navigation.top - icon: - logo: material/rocket-launch - repo: fontawesome/brands/github - palette: - # Palette toggle for light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: teal - accent: teal - toggle: - icon: material/lightbulb - name: Switch to dark mode - # Palette toggle for dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: teal - accent: teal - toggle: - icon: material/lightbulb-outline - name: Switch to light mode - -extra: - version: - provider: mike - -markdown_extensions: - - admonition - - def_list - - footnotes - - pymdownx.details - - pymdownx.highlight: - anchor_linenums: true - line_spans: __span - pygments_lang_class: true - - pymdownx.inlinehilite - - pymdownx.snippets - - pymdownx.superfences - - pymdownx.tasklist: - custom_checkbox: true - - toc: - permalink: ⚓︎ diff --git a/source/package.json b/source/package.json deleted file mode 100644 index d18e828..0000000 --- a/source/package.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "name": "landing-zone-accelerator-on-aws", - "version": "1.8.1", - "private": true, - "description": "Landing Zone Accelerator on AWS", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "workspaces": { - "packages": [ - "./packages/@aws-accelerator/accelerator", - "./packages/@aws-accelerator/accelerator/lib/lambdas/*", - "./packages/@aws-accelerator/config", - "./packages/@aws-accelerator/constructs", - "./packages/@aws-accelerator/constructs/lib/aws-*/*", - "./packages/@aws-accelerator/constructs/lib/data-perimeter/*", - "./packages/@aws-accelerator/installer", - "./packages/@aws-accelerator/govcloud-account-vending", - "./packages/@aws-accelerator/govcloud-account-vending/lib/lambdas/*", - "./packages/@aws-accelerator/modules", - "./packages/@aws-accelerator/tester", - "./packages/@aws-accelerator/tester/lambdas", - "./packages/@aws-accelerator/tools", - "./packages/@aws-accelerator/utils", - "./packages/@aws-cdk-extensions/cdk-extensions", - "./packages/@aws-cdk-extensions/cdk-plugin-assume-role" - ], - "nohoist": [ - "**/deepmerge", - "**/deepmerge/**", - "**/npmlog", - "**/npmlog/**", - "**/@types/npmlog", - "**/@types/npmlog/**", - "**/deep-diff", - "**/deep-diff/**", - "**/@types/deep-diff", - "**/@types/deep-diff/**" - ] - }, - "scripts": { - "build": "lerna run build --stream", - "build-clean": "yarn run git-clean && yarn install && yarn run build", - "cleanup": "lerna run cleanup --stream && rm -rf node_modules", - "cleanup:tsc": "lerna run cleanup:tsc --stream", - "docs": "yarn run typedoc --entryPointStrategy packages './packages/@aws-accelerator/*' './packages/@aws-cdk-extensions/*' --name 'Landing Zone Accelerator on AWS' --includeVersion --disableSources --logLevel Verbose --basePath typedocs", - "generate-all-docs": "../deployment/build-docs.sh", - "git-clean": "cd .. && git clean -Xfd -e !development -e !development/** -e !.vscode -e !.vscode/**", - "postinstall": "cd ../ && husky install source/.husky && cd source", - "lint": "lerna run lint --stream", - "prepare": "cd ../ && husky install source/.husky", - "prettier": "lerna run prettier --stream", - "test": "lerna run test --stream", - "test:clean": "rm -f ./test-reports/*.xml", - "update-snapshots": "../deployment/update-snapshots.sh", - "validate-config": "ts-node ./packages/@aws-accelerator/accelerator/lib/config-validator.ts", - "viper-scan": "cd .. && aws s3 cp s3://viperlight-scanner/latest/viperlight.zip . && unzip -qo viperlight.zip -d ../viperlight && rm -r ./viperlight.zip && ../viperlight/bin/viperlight scan", - "scan-logging": "chmod +x ./log-scanner.sh && ./log-scanner.sh" - }, - "lint-staged": { - "*.{ts,tsx}": "eslint --fix --max-warnings 0 -c .eslintrc.json --ignore-pattern \"*.d.ts\"" - }, - "dependencies": { - "fs-extra": "11.1.0", - "jest": "29.4.3", - "jsii": "1.75.0", - "jsii-pacmak": "1.75.0", - "minimatch": "9.0.0" - }, - "devDependencies": { - "@aws-cdk/integ-runner": "2.93.0-alpha.0", - "@aws-cdk/integ-tests-alpha": "2.93.0-alpha.0", - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "@typescript-eslint/eslint-plugin": "5.53.0", - "@typescript-eslint/parser": "5.53.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.0.12", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-plugin-jest": "27.2.1", - "eslint-plugin-prettier": "4.2.1", - "husky-init": "8.0.0", - "jest": "29.4.3", - "jest-junit": "15.0.0", - "lerna": "7.2.0", - "lint-staged": "^14.0.1", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typedoc": "0.23.25", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/.npmignore b/source/packages/@aws-accelerator/accelerator/.npmignore deleted file mode 100644 index bd6c7fe..0000000 --- a/source/packages/@aws-accelerator/accelerator/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -*.ts -!*.d.ts \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/README.md b/source/packages/@aws-accelerator/accelerator/README.md deleted file mode 100644 index d537ffc..0000000 --- a/source/packages/@aws-accelerator/accelerator/README.md +++ /dev/null @@ -1 +0,0 @@ -# @aws-accelerator/accelerator diff --git a/source/packages/@aws-accelerator/accelerator/bin/app.ts b/source/packages/@aws-accelerator/accelerator/bin/app.ts deleted file mode 100644 index 6567200..0000000 --- a/source/packages/@aws-accelerator/accelerator/bin/app.ts +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import 'source-map-support/register'; - -import * as cdk from 'aws-cdk-lib'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { AcceleratorAspects } from '../lib/accelerator-aspects'; -import { AcceleratorStackProps } from '../lib/stacks/accelerator-stack'; -import { - AcceleratorContext, - AcceleratorEnvironment, - AcceleratorResourcePrefixes, - getContext, - setAcceleratorEnvironment, - setAcceleratorStackProps, - setResourcePrefixes, -} from '../utils/app-utils'; -import { - createAccountsStack, - createBootstrapStack, - createCustomizationsStacks, - createDiagnosticsPackStack, - createFinalizeStack, - createIdentityCenterStack, - createKeyDependencyStacks, - createLoggingStack, - createNetworkAssociationsStacks, - createNetworkPrepStack, - createNetworkVpcStacks, - createOperationsStack, - createOrganizationsStack, - createPipelineStack, - createPrepareStack, - createSecurityAuditStack, - createSecurityResourcesStack, - createSecurityStack, - createTesterStack, - importAseaResourceStack, - saveAseaResourceMapping, -} from '../utils/stack-utils'; -import { AseaResourceMapping } from '@aws-accelerator/config'; - -const logger = createLogger(['app']); - -/** - * Create pipeline and tester pipeline stacks - * @param app - * @param context - * @param acceleratorEnv - * @param resourcePrefixes - */ -function createPipelineStacks( - app: cdk.App, - context: AcceleratorContext, - acceleratorEnv: AcceleratorEnvironment, - resourcePrefixes: AcceleratorResourcePrefixes, - useExistingRoles: boolean, -) { - // - // PIPELINE Stack - createPipelineStack(app, context, acceleratorEnv, resourcePrefixes, useExistingRoles); - // - // TESTER Stack - createTesterStack(app, context, acceleratorEnv, resourcePrefixes); -} - -/** - * Create accelerator stacks that target only the management account in - * either the home region or global region - * @param app - * @param context - * @param props - * @param managementAccountId - * @param homeRegion - * @param globalRegion - */ -function createManagementAccountStacks( - app: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - managementAccountId: string, - homeRegion: string, - globalRegion: string, -) { - // - // PREPARE Stack - createPrepareStack(app, context, props, managementAccountId, homeRegion); - // - // ACCOUNTS Stack - createAccountsStack(app, context, props, managementAccountId, globalRegion); - // - // IDENTITY CENTER Stack - createIdentityCenterStack(app, context, props, managementAccountId, homeRegion); - // - // FINALIZE Stack - createFinalizeStack(app, context, props, managementAccountId, globalRegion); -} - -/** - * Creates accelerator stacks that target a single account in all enabled regions - * @param app - * @param context - * @param props - * @param managementAccountId - * @param auditAccountId - */ -function createSingleAccountMultiRegionStacks( - app: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - managementAccountId: string, - auditAccountId: string, -) { - for (const enabledRegion of props.globalConfig.enabledRegions) { - // - // ORGANIZATIONS Stack - createOrganizationsStack(app, context, props, managementAccountId, enabledRegion); - // - // SECURITY AUDIT Stack - createSecurityAuditStack(app, context, props, auditAccountId, enabledRegion); - } -} - -/** - * Create accelerator stacks that target all accounts and enabled regions - * @param app - * @param context - * @param props - */ -async function createMultiAccountMultiRegionStacks( - app: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, -) { - const aseaResources: AseaResourceMapping[] = []; - for (const enabledRegion of props.globalConfig.enabledRegions) { - let accountId = ''; - for (const accountItem of props.accountsConfig.getAccounts(props.enableSingleAccountMode)) { - // Retrieve account ID and create stack env - try { - accountId = props.accountsConfig.getAccountId(accountItem.name); - } catch (error) { - continue; - } - const env = { - account: accountId, - region: enabledRegion, - }; - // - // Import ASEA resources using CfnInclude - const aseaAccountResources = await importAseaResourceStack(app, context, props, accountId, enabledRegion); - if (aseaAccountResources) aseaResources.push(...aseaAccountResources); - // - // KEY and DEPENDENCIES Stacks - createKeyDependencyStacks(app, context, props, env, accountId, enabledRegion); - // - // BOOTSTRAP Stack - createBootstrapStack(app, context, props, env, accountId, enabledRegion); - // - // LOGGING Stack - createLoggingStack(app, context, props, env, accountId, enabledRegion); - // - // SECURITY Stack - createSecurityStack(app, context, props, env, accountId, enabledRegion); - // - // OPERATIONS Stack - createOperationsStack(app, context, props, env, accountId, enabledRegion, accountItem.warm ?? false); - // - // NETWORK PREP Stack - createNetworkPrepStack(app, context, props, env, accountId, enabledRegion); - // - // SECURITY RESOURCES Stack - createSecurityResourcesStack(app, context, props, env, accountId, enabledRegion); - // - // All NETWORK_VPC stage stacks - createNetworkVpcStacks(app, context, props, env, accountId, enabledRegion); - // - // All NETWORK_ASSOCIATIONS stage stacks - createNetworkAssociationsStacks(app, context, props, env, accountId, enabledRegion); - // - // All CUSTOMIZATIONS stage stacks - createCustomizationsStacks(app, context, props, env, accountId, enabledRegion); - } - } - - if (props.globalConfig.externalLandingZoneResources?.importExternalLandingZoneResources) { - await saveAseaResourceMapping(context, props, aseaResources); - } -} - -async function main() { - logger.info('Begin Accelerator CDK App'); - const app = new cdk.App(); - // - // Read in context inputs - const context = getContext(app); - // - // Set aspects and global region - const useExistingRoles = context.useExistingRoles ?? false; - const aspects = new AcceleratorAspects(app, context.partition, useExistingRoles); - const globalRegion = aspects.globalRegion; - // - // Set various resource name prefixes used in code base - const resourcePrefixes = setResourcePrefixes(process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'); - // - // Set accelerator environment variables - const acceleratorEnv = setAcceleratorEnvironment(process.env, resourcePrefixes, context.stage); - - // - // Create the diagnostics pack resources. The Diagnostics pack stack will be deployed for multi-account environments without utilizing existing roles for deployment. - // - if (!useExistingRoles && !acceleratorEnv.enableSingleAccountMode) { - createDiagnosticsPackStack(app, context, acceleratorEnv, resourcePrefixes); - } - - // - // PIPELINE and TESTER Stacks - createPipelineStacks(app, context, acceleratorEnv, resourcePrefixes, useExistingRoles); - // - // Set accelerator stack props - const props = await setAcceleratorStackProps(context, acceleratorEnv, resourcePrefixes, globalRegion); - - if (props) { - // Set common variables used in stacks - const homeRegion = props.globalConfig.homeRegion; - const managementAccountId = props.accountsConfig.getManagementAccountId(); - const auditAccountId = props.accountsConfig.getAuditAccountId(); - - // - // PREPARE, ACCOUNTS, and FINALIZE Stacks - createManagementAccountStacks(app, context, props, managementAccountId, homeRegion, globalRegion); - // - // ORGANIZATIONS and SECURITY AUDIT Stacks - createSingleAccountMultiRegionStacks(app, context, props, managementAccountId, auditAccountId); - // - // All remaining stacks - createMultiAccountMultiRegionStacks(app, context, props); - } - - logger.info('End Accelerator CDK App'); -} - -(async () => { - try { - await main(); - } catch (err) { - logger.error(err); - throw new Error(`${err}`); - } -})(); diff --git a/source/packages/@aws-accelerator/accelerator/cdk.json b/source/packages/@aws-accelerator/accelerator/cdk.json deleted file mode 100644 index 2e9d7cd..0000000 --- a/source/packages/@aws-accelerator/accelerator/cdk.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/app.ts", - "context": { - "@aws-cdk/core:bootstrapQualifier": "accel", - "@aws-cdk/core:newStyleStackSynthesis": "true" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/cdk.ts b/source/packages/@aws-accelerator/accelerator/cdk.ts deleted file mode 100644 index d5591b5..0000000 --- a/source/packages/@aws-accelerator/accelerator/cdk.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as config from '@aws-accelerator/config'; -import * as fs from 'fs'; -import mri from 'mri'; -import process from 'process'; -import { Accelerator } from './lib/accelerator'; -import { AcceleratorStage } from './lib/accelerator-stage'; -import { AcceleratorToolkit } from './lib/toolkit'; - -(async () => { - const usage = `Usage: cdk.ts --stage STAGE --config-dir CONFIG_DIRECTORY [--account ACCOUNT] [--region REGION]`; - - const args = mri(process.argv.slice(2), { - boolean: ['ec2Creds'], - string: [ - 'require-approval', - 'config-dir', - 'partition', - 'stage', - 'account', - 'region', - 'app', - 'ca-bundle-path', - 'proxy', - ], - alias: { - c: 'config-dir', - s: 'stage', - a: 'account', - r: 'region', - p: 'app', - }, - }); - - const commands = args['_']; - const requireApproval = args['require-approval']; - const configDirPath = args['config-dir']; - const partition = args['partition']; - const stage = args['stage']; - const account = args['account']; - const region = args['region']; - const app = args['app']; - const caBundlePath = args['ca-bundle-path']; - const ec2Creds = args['ec2Creds']; - const proxyAddress = args['proxy']; - const useExistingRoles = args['use-existing-roles'] ?? false; - // - // Validate args: must specify a command - // - if (commands.length === 0) { - console.log(' not set'); - throw new Error(usage); - } - - // - // Validate args: verify command against our sub-list - // - if (!AcceleratorToolkit.isSupportedCommand(commands[0])) { - throw new Error(`Invalid command: ${commands[0]}`); - } - - // - // Validate args: verify config directory - // - if ( - stage !== AcceleratorStage.PIPELINE && - stage !== AcceleratorStage.TESTER_PIPELINE && - stage !== AcceleratorStage.DIAGNOSTICS_PACK - ) { - if (config === undefined || !fs.existsSync(configDirPath)) { - console.log(`Invalid --config-dir ${configDirPath}`); - throw new Error(usage); - } - } - - // Check if the caBundlePath file exits - if (caBundlePath !== undefined) { - if (caBundlePath.length === 0 || !fs.existsSync(caBundlePath)) { - console.log(`Invalid --ca-bundle-path ${caBundlePath}`); - throw new Error(usage); - } - } - - // Boolean to set single account deployment mode - const enableSingleAccountMode = process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] - ? process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] === 'true' - : false; - - /** - * If command is deploy then before command run process to rename all asset.json files - */ - if (commands[0] === 'deploy') { - await AcceleratorToolkit.assetFiles('render', configDirPath); - } - // - // Execute the Accelerator engine - // - await Accelerator.run({ - command: commands[0], - configDirPath, - stage, - account, - region, - partition, - requireApproval, - app, - caBundlePath, - ec2Creds, - proxyAddress, - enableSingleAccountMode: enableSingleAccountMode, - useExistingRoles, - }).catch(function (err) { - console.log(err.message); - process.exit(1); - }); - - /** - * If command is synth then post command run process to rename all asset.json files - */ - if (commands[0] === 'synth') { - await AcceleratorToolkit.assetFiles('parse', configDirPath); - } -})(); diff --git a/source/packages/@aws-accelerator/accelerator/index.ts b/source/packages/@aws-accelerator/accelerator/index.ts deleted file mode 100644 index 2b20265..0000000 --- a/source/packages/@aws-accelerator/accelerator/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/accelerator'; -export * from './lib/accelerator-stage'; -export * from './lib/config-repository'; -export * from './lib/pipeline'; -export * from './lib/stacks/accelerator-stack'; -export * from './lib/stacks/accounts-stack'; -export * from './lib/stacks/bootstrap-stack'; -export * from './lib/stacks/diagnostics-pack-stack'; -export * from './lib/stacks/network-stacks/network-associations-stack/network-associations-stack'; -export * from './lib/stacks/network-stacks/network-prep-stack/network-prep-stack'; -export * from './lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack'; -export * from './lib/stacks/operations-stack'; -export * from './lib/stacks/organizations-stack'; -export * from './lib/stacks/pipeline-stack'; -export * from './lib/stacks/prepare-stack'; -export * from './lib/stacks/finalize-stack'; -export * from './lib/stacks/security-audit-stack'; -export * from './lib/stacks/security-stack'; -export * from './lib/toolkit'; -export * from './lib/validate-environment-config'; diff --git a/source/packages/@aws-accelerator/accelerator/jest.config.js b/source/packages/@aws-accelerator/accelerator/jest.config.js deleted file mode 100644 index b0d6ebc..0000000 --- a/source/packages/@aws-accelerator/accelerator/jest.config.js +++ /dev/null @@ -1,78 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 70, - functions: 92, - lines: 85, - statements: 85, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - setupFiles: ['/jest/setEnvVars.js'], - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/accelerator/jest/setEnvVars.js b/source/packages/@aws-accelerator/accelerator/jest/setEnvVars.js deleted file mode 100644 index bb16efa..0000000 --- a/source/packages/@aws-accelerator/accelerator/jest/setEnvVars.js +++ /dev/null @@ -1 +0,0 @@ -process.env.CONFIG_COMMIT_ID = 'e3cdaecaa6073ad9e4721344cd109eb6de351cfb'; diff --git a/source/packages/@aws-accelerator/accelerator/lib/accelerator-aspects.ts b/source/packages/@aws-accelerator/accelerator/lib/accelerator-aspects.ts deleted file mode 100644 index ecbd787..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/accelerator-aspects.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IConstruct } from 'constructs'; -import { version } from '../../../../package.json'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -const logger = createLogger(['accelerator-aspects']); -/** - * Property overrides for GovCloud environments - */ -class GovCloudOverrides implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.aws_logs.CfnLogGroup) { - node.addPropertyDeletionOverride('KmsKeyId'); - node.addPropertyDeletionOverride('Tags'); - } - if (node instanceof cdk.aws_iam.CfnRole) { - const trustPolicyDoc = node.assumeRolePolicyDocument as cdk.aws_iam.SamlConsolePrincipal; - if (JSON.stringify(trustPolicyDoc).includes('signin.aws.amazon.com')) { - node.addPropertyOverride( - 'AssumeRolePolicyDocument.Statement.0.Condition.StringEquals.SAML:aud', - 'https://signin.amazonaws-us-gov.com/saml', - ); - } - } - } -} - -/** - * Property overrides for ISO-B environments - */ -class IsobOverrides implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.aws_ec2.CfnFlowLog) { - node.addPropertyDeletionOverride('LogFormat'); - node.addPropertyDeletionOverride('Tags'); - node.addPropertyDeletionOverride('MaxAggregationInterval'); - } - if (node instanceof cdk.aws_logs.CfnLogGroup) { - node.addPropertyDeletionOverride('KmsKeyId'); - node.addPropertyDeletionOverride('Tags'); - } - if (node instanceof cdk.aws_cloudtrail.CfnTrail) { - node.addPropertyDeletionOverride('InsightSelectors'); - node.addPropertyDeletionOverride('IsOrganizationTrail'); - } - if (node instanceof cdk.aws_ecr.CfnRepository) { - node.addPropertyDeletionOverride('ImageTagMutability'); - } - if (node instanceof cdk.aws_iam.CfnRole) { - const trustPolicyDoc = node.assumeRolePolicyDocument as cdk.aws_iam.SamlConsolePrincipal; - if (JSON.stringify(trustPolicyDoc).includes('signin.aws.amazon.com')) { - node.addPropertyOverride( - 'AssumeRolePolicyDocument.Statement.0.Condition.StringEquals.SAML:aud', - 'https://signin.sc2shome.sgov.gov/saml', - ); - } - } - } -} - -/** - * Property overrides for ISO environments - */ -class IsoOverrides implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.aws_ec2.CfnFlowLog) { - node.addPropertyDeletionOverride('LogFormat'); - node.addPropertyDeletionOverride('Tags'); - node.addPropertyDeletionOverride('MaxAggregationInterval'); - } - if (node instanceof cdk.aws_logs.CfnLogGroup) { - node.addPropertyDeletionOverride('KmsKeyId'); - node.addPropertyDeletionOverride('Tags'); - } - if (node instanceof cdk.aws_cloudtrail.CfnTrail) { - node.addPropertyDeletionOverride('InsightSelectors'); - node.addPropertyDeletionOverride('IsOrganizationTrail'); - } - if (node instanceof cdk.aws_ecr.CfnRepository) { - node.addPropertyDeletionOverride('ImageTagMutability'); - } - if (node instanceof cdk.aws_iam.CfnRole) { - const trustPolicyDoc = node.assumeRolePolicyDocument as cdk.aws_iam.SamlConsolePrincipal; - if (JSON.stringify(trustPolicyDoc).includes('signin.aws.amazon.com')) { - node.addPropertyOverride( - 'AssumeRolePolicyDocument.Statement.0.Condition.StringEquals.SAML:aud', - 'https://signin.c2shome.ic.gov/saml', - ); - } - } - } -} - -/** - * Property overrides for CN environments - */ -class CnOverrides implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.aws_logs.CfnLogGroup) { - node.addPropertyDeletionOverride('Tags'); - } - if (node instanceof cdk.aws_cloudtrail.CfnTrail) { - node.addPropertyDeletionOverride('IsOrganizationTrail'); - } - } -} - -/** - * Default memory override for Lambda resources - */ -class LambdaDefaultMemoryAspect implements cdk.IAspect { - visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - if (node.cfnResourceType === 'AWS::Lambda::Function') { - const cfnProps = (node as cdk.aws_lambda.CfnFunction)['_cfnProperties']; - let memorySize = cfnProps['MemorySize']?.toString(); - - if (!memorySize) { - memorySize = (node as cdk.aws_lambda.CfnFunction).memorySize; - } - - if (!memorySize || memorySize < 512) { - node.addPropertyOverride('MemorySize', 512); - } - } - } - } -} -/** - * Default deletion override for Service linked role resources - */ -class IamServiceLinkedRoleAspect implements cdk.IAspect { - visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - if (node.cfnResourceType === 'AWS::IAM::ServiceLinkedRole') { - node.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN); - } - } - } -} - -/** - * Solution ID override for Lambda resources - */ -class AwsSolutionAspect implements cdk.IAspect { - visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - if (node.cfnResourceType === 'AWS::Lambda::Function') { - node.addPropertyOverride('Environment.Variables.SOLUTION_ID', `AwsSolution/SO0199/${version}`); - } - } - } -} - -/** - * Existing Role Overrides - */ -class ExistingRoleOverrides implements cdk.IAspect { - public visit(construct: IConstruct): void { - const acceleratorPrefix = process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'; - - if (construct instanceof cdk.CfnResource && construct.cfnResourceType === 'AWS::CloudTrail::Trail') { - this.replaceCloudTrailCloudWatchLogsRole(construct, acceleratorPrefix); - } else if (construct instanceof cdk.CfnResource && construct.cfnResourceType === 'AWS::Lambda::Function') { - this.replaceLambdaFunctionRole(construct, acceleratorPrefix); - } else if ( - construct instanceof cdk.CfnResource && - (construct.cfnResourceType === 'AWS::IAM::Role' || - construct.cfnResourceType === 'AWS::IAM::Policy' || - construct.cfnResourceType === 'AWS::IAM::InstanceProfile' || - construct.cfnResourceType === 'AWS::IAM::ManagedPolicy') - ) { - for (const x of construct.obtainResourceDependencies()) { - construct.removeDependency(x); - } - construct.node.scope?.node.tryRemoveChild(construct.node.id); - } - } - private replaceCloudTrailCloudWatchLogsRole(construct: cdk.CfnResource, acceleratorPrefix: string) { - for (const eachDependency of construct.node.dependencies) { - // in cloudtrail look for logsRole - // this is only created when sendToCloudWatchLogs is set to true - // replace this with pre-existing role - if (eachDependency.node.path.includes('LogsRole')) { - construct.addPropertyDeletionOverride('CloudWatchLogsRoleArn'); - construct.addPropertyOverride( - 'CloudWatchLogsRoleArn.Fn::Sub', - 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/' + acceleratorPrefix + 'CloudTrailCloudWatchRole', - ); - } - } - } - private replaceLambdaFunctionRole(construct: cdk.CfnResource, acceleratorPrefix: string) { - for (const x of construct.obtainResourceDependencies()) { - construct.removeDependency(x); - const parentConstruct = construct.node.scope; - parentConstruct?.node.tryRemoveChild(x.node.id); - } - construct.addPropertyDeletionOverride('Role'); - construct.addPropertyOverride( - 'Role.Fn::Sub', - 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/' + acceleratorPrefix + 'LambdaRole', - ); - } -} - -/** - * Permission boundary aspect - */ -export class PermissionsBoundaryAspect implements cdk.IAspect { - /** - * Account this will be applied in - */ - readonly account: string; - /** - * Partition this will be deployed to - */ - readonly partition: string; - constructor(account: string, partition: string) { - this.account = account; - this.partition = partition; - } - public visit(node: IConstruct): void { - const policyLength = (process.env['ACCELERATOR_PERMISSION_BOUNDARY'] ?? '').trim().length; - // check if node is type of cloudformation resource - if (!(node instanceof cdk.CfnResource)) { - return; - } - // check if node is type of IAM role - if (node.cfnResourceType !== 'AWS::IAM::Role') { - return; - } - // check if deployment is external - if (process.env['MANAGEMENT_ACCOUNT_ID'] && process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']) { - return; - } - // check if its management account - if (this.account !== process.env['PIPELINE_ACCOUNT_ID']!) { - return; - } - // policy name is not empty - if (policyLength === 0) { - return; - } - - try { - // Build permissions boundary ARN from input - const permissionsBoundaryArn = `arn:${this.partition}:iam::${this.account}:policy/${process.env[ - 'ACCELERATOR_PERMISSION_BOUNDARY' - ]!}`; - // convert role in to cfn.role this allows for checking properties - const roleResource = node as cdk.aws_iam.CfnRole; - if (roleResource && roleResource.permissionsBoundary && roleResource.permissionsBoundary > '') { - //do nothing, use existing permission boundary - } else { - // no permission boundary was found, add permission boundary - roleResource.addPropertyOverride('PermissionsBoundary', permissionsBoundaryArn); - } - } catch (error) { - const msg = `Error while applying permission boundary to IAM role ${node.node.path}. Error: ${JSON.stringify( - error, - )}. Permission boundary will not be applied to stacks.`; - logger.error(msg); - throw new Error(msg); - } - } -} - -/** - * Add accelerator specific aspects to the application based on partition - */ -export class AcceleratorAspects { - /** - * The region for global API endpoints - * based on AWS partition - */ - public readonly globalRegion: string; - - constructor(app: cdk.App, partition: string, useExistingRoles: boolean) { - let globalRegion = 'us-east-1'; - // Add partition specific overrides - switch (partition) { - case 'aws-us-gov': - globalRegion = 'us-gov-west-1'; - cdk.Aspects.of(app).add(new GovCloudOverrides()); - break; - case 'aws-iso': - globalRegion = 'us-iso-east-1'; - cdk.Aspects.of(app).add(new IsoOverrides()); - break; - case 'aws-iso-b': - globalRegion = 'us-isob-east-1'; - cdk.Aspects.of(app).add(new IsobOverrides()); - break; - case 'aws-cn': - globalRegion = 'cn-northwest-1'; - cdk.Aspects.of(app).add(new CnOverrides()); - break; - } - // Add default aspects - cdk.Aspects.of(app).add(new LambdaDefaultMemoryAspect()); - cdk.Aspects.of(app).add(new IamServiceLinkedRoleAspect()); - if (useExistingRoles) { - cdk.Aspects.of(app).add(new ExistingRoleOverrides()); - } else { - /** - * when existing roles are recreated the Lambda, invoke fails on custom resource as environment variable is encrypted and it cannot access it with the error below - * @example - * Calling the invoke API action failed with this message: Lambda was unable to decrypt the environment variables because KMS access was denied. Please check the function's KMS key settings. KMS Exception: AccessDeniedExceptionKMS Message: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access - */ - // removing solutions aspect to prevent this error - cdk.Aspects.of(app).add(new AwsSolutionAspect()); - } - // Set global region - this.globalRegion = globalRegion; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/accelerator-resource-names.ts b/source/packages/@aws-accelerator/accelerator/lib/accelerator-resource-names.ts deleted file mode 100644 index e35954c..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/accelerator-resource-names.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorResourcePrefixes } from '../utils/app-utils'; -export interface AcceleratorResourceNamesProps { - readonly prefixes: AcceleratorResourcePrefixes; - /** - * Centralize Logging region - */ - readonly centralizedLoggingRegion: string; -} - -interface RoleNames { - crossAccountCmkArnSsmParameterAccess: string; - ipamSsmParameterAccess: string; - ipamSubnetLookup: string; - crossAccountCentralLogBucketCmkArnSsmParameterAccess: string; - crossAccountCustomerGatewayRoleName: string; - crossAccountLogsRoleName: string; - crossAccountSecretsCmkParameterAccess: string; - crossAccountTgwRouteRoleName: string; - crossAccountVpnRoleName: string; - moveAccountConfig: string; - tgwPeering: string; - madShareAccept: string; - snsTopicCmkArnParameterAccess: string; - crossAccountAssetsBucketCmkArnSsmParameterAccess: string; - crossAccountServiceCatalogPropagation: string; - crossAccountSsmParameterShare: string; - assetFunctionRoleName: string; - firewallConfigFunctionRoleName: string; - diagnosticsPackAssumeRoleName: string; -} -interface ParameterNames { - importedCentralLogBucketCmkArn: string; - centralLogBucketCmkArn: string; - controlTowerDriftDetection: string; - controlTowerLastDriftMessage: string; - configTableArn: string; - configTableName: string; - cloudTrailBucketName: string; - flowLogsDestinationBucketArn: string; - metadataBucketArn: string; - metadataBucketCmkArn: string; - acceleratorCmkArn: string; - ebsDefaultCmkArn: string; - s3CmkArn: string; - secretsManagerCmkArn: string; - cloudWatchLogCmkArn: string; - snsTopicCmkArn: string; - lambdaCmkArn: string; - managementCmkArn: string; - importedAssetsBucketCmkArn: string; - assetsBucketCmkArn: string; - identityCenterInstanceArn: string; - identityStoreId: string; - firehoseRecordsProcessorFunctionName: string; -} -interface CmkDetails { - orgTrailLog: { alias: string; description: string }; - centralLogsBucket: { alias: string; description: string }; - importedCentralLogsBucket: { alias: string; description: string }; - metadataBucket: { alias: string; description: string }; - ebsDefault: { alias: string; description: string }; - s3: { alias: string; description: string }; - cloudWatchLog: { alias: string; description: string }; - cloudWatchLogReplication: { alias: string; description: string }; - awsBackup: { alias: string; description: string }; - sns: { alias: string; description: string }; - snsTopic: { alias: string; description: string }; - secretsManager: { alias: string; description: string }; - lambda: { alias: string; description: string }; - acceleratorKey: { alias: string; description: string }; - managementKey: { alias: string; description: string }; - importedAssetsBucketCmkArn: { alias: string; description: string }; - assetsBucket: { alias: string; description: string }; - ssmKey: { alias: string; description: string }; -} -interface BucketPrefixes { - assetsAccessLog: string; - assets: string; - elbLogs: string; - firewallConfig: string; - costUsage: string; - s3AccessLogs: string; - auditManager: string; - vpcFlowLogs: string; - metadata: string; - centralLogs: string; -} -export class AcceleratorResourceNames { - public roles: RoleNames = { - crossAccountCmkArnSsmParameterAccess: 'PLACE_HOLDER', - ipamSsmParameterAccess: 'PLACE_HOLDER', - ipamSubnetLookup: 'PLACE_HOLDER', - crossAccountCentralLogBucketCmkArnSsmParameterAccess: 'PLACE_HOLDER', - crossAccountCustomerGatewayRoleName: 'PLACE_HOLDER', - crossAccountLogsRoleName: 'PLACE_HOLDER', - crossAccountSecretsCmkParameterAccess: 'PLACE_HOLDER', - crossAccountTgwRouteRoleName: 'PLACE_HOLDER', - crossAccountVpnRoleName: 'PLACE_HOLDER', - moveAccountConfig: 'PLACE_HOLDER', - tgwPeering: 'PLACE_HOLDER', - madShareAccept: 'PLACE_HOLDER', - snsTopicCmkArnParameterAccess: 'PLACE_HOLDER', - crossAccountAssetsBucketCmkArnSsmParameterAccess: 'PLACE_HOLDER', - crossAccountServiceCatalogPropagation: 'PLACE_HOLDER', - crossAccountSsmParameterShare: 'PLACE_HOLDER', - assetFunctionRoleName: 'PLACE_HOLDER', - firewallConfigFunctionRoleName: 'PLACE_HOLDER', - diagnosticsPackAssumeRoleName: 'PLACE_HOLDER', - }; - public parameters: ParameterNames = { - importedCentralLogBucketCmkArn: 'PLACE_HOLDER', - centralLogBucketCmkArn: 'PLACE_HOLDER', - controlTowerDriftDetection: 'PLACE_HOLDER', - controlTowerLastDriftMessage: 'PLACE_HOLDER', - configTableArn: 'PLACE_HOLDER', - configTableName: 'PLACE_HOLDER', - cloudTrailBucketName: 'PLACE_HOLDER', - flowLogsDestinationBucketArn: 'PLACE_HOLDER', - metadataBucketArn: 'PLACE_HOLDER', - metadataBucketCmkArn: 'PLACE_HOLDER', - acceleratorCmkArn: 'PLACE_HOLDER', - ebsDefaultCmkArn: 'PLACE_HOLDER', - s3CmkArn: 'PLACE_HOLDER', - secretsManagerCmkArn: 'PLACE_HOLDER', - cloudWatchLogCmkArn: 'PLACE_HOLDER', - snsTopicCmkArn: 'PLACE_HOLDER', - lambdaCmkArn: 'PLACE_HOLDER', - managementCmkArn: 'PLACE_HOLDER', - importedAssetsBucketCmkArn: 'PLACE_HOLDER', - assetsBucketCmkArn: 'PLACE_HOLDER', - identityCenterInstanceArn: 'PLACE_HOLDER', - identityStoreId: 'PLACE_HOLDER', - firehoseRecordsProcessorFunctionName: 'PLACE_HOLDER', - }; - public customerManagedKeys: CmkDetails = { - orgTrailLog: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - centralLogsBucket: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - importedCentralLogsBucket: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - metadataBucket: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - ebsDefault: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - s3: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - cloudWatchLog: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - cloudWatchLogReplication: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - awsBackup: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - sns: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - snsTopic: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - secretsManager: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - lambda: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - acceleratorKey: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - managementKey: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - importedAssetsBucketCmkArn: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - assetsBucket: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - ssmKey: { alias: 'PLACE_HOLDER', description: 'PLACE_HOLDER' }, - }; - public bucketPrefixes: BucketPrefixes = { - assetsAccessLog: 'PLACE_HOLDER', - assets: 'PLACE_HOLDER', - elbLogs: 'PLACE_HOLDER', - firewallConfig: 'PLACE_HOLDER', - costUsage: 'PLACE_HOLDER', - s3AccessLogs: 'PLACE_HOLDER', - auditManager: 'PLACE_HOLDER', - vpcFlowLogs: 'PLACE_HOLDER', - metadata: 'PLACE_HOLDER', - centralLogs: 'PLACE_HOLDER', - }; - - constructor(props: AcceleratorResourceNamesProps) { - // - // Role name initializations - this.roles.crossAccountCentralLogBucketCmkArnSsmParameterAccess = - props.prefixes.accelerator + '-' + props.centralizedLoggingRegion + '-CentralBucket-KeyArnParam-Role'; - this.roles.ipamSsmParameterAccess = props.prefixes.accelerator + '-Ipam-GetSsmParamRole'; - this.roles.ipamSubnetLookup = props.prefixes.accelerator + '-GetIpamCidrRole'; - this.roles.crossAccountCmkArnSsmParameterAccess = props.prefixes.accelerator + '-CrossAccount-SsmParameter-Role'; - this.roles.crossAccountCustomerGatewayRoleName = props.prefixes.accelerator + '-CrossAccount-CustomerGateway-Role'; - this.roles.crossAccountLogsRoleName = props.prefixes.accelerator + '-CrossAccount-PutLogs-Role'; - this.roles.crossAccountSecretsCmkParameterAccess = props.prefixes.accelerator + '-CrossAccount-SecretsKms-Role'; - this.roles.crossAccountTgwRouteRoleName = props.prefixes.accelerator + '-CrossAccount-TgwRoutes-Role'; - this.roles.crossAccountVpnRoleName = props.prefixes.accelerator + '-CrossAccount-SiteToSiteVpn-Role'; - this.roles.moveAccountConfig = props.prefixes.accelerator + '-MoveAccountConfigRule-Role'; - this.roles.tgwPeering = props.prefixes.accelerator + '-TgwPeering-Role'; - this.roles.madShareAccept = props.prefixes.accelerator + '-MadAccept-Role'; - this.roles.snsTopicCmkArnParameterAccess = props.prefixes.accelerator + '-SnsTopic-KeyArnParam-Role'; - this.roles.crossAccountAssetsBucketCmkArnSsmParameterAccess = - props.prefixes.accelerator + '-AssetsBucket-KeyArnParam-Role'; - this.roles.crossAccountServiceCatalogPropagation = props.prefixes.accelerator + '-CrossAccount-ServiceCatalog-Role'; - this.roles.crossAccountSsmParameterShare = props.prefixes.accelerator + '-CrossAccountSsmParameterShare'; - this.roles.assetFunctionRoleName = props.prefixes.accelerator + '-AssetsAccessRole'; - this.roles.firewallConfigFunctionRoleName = props.prefixes.accelerator + '-FirewallConfigAccessRole'; - // Changing the role name for diagnosticsPackAssumeRoleName will need to be change in diagnostics-pack-stack.ts file, this stack deploys during installer - this.roles.diagnosticsPackAssumeRoleName = props.prefixes.accelerator + '-DiagnosticsPackAccessRole'; - - // - // SSM Parameter initializations - this.parameters.importedCentralLogBucketCmkArn = - props.prefixes.importResourcesSsmParamName + '/logging/central-bucket/kms/arn'; - this.parameters.centralLogBucketCmkArn = props.prefixes.ssmParamName + '/logging/central-bucket/kms/arn'; - this.parameters.controlTowerDriftDetection = props.prefixes.ssmParamName + '/controltower/driftDetected'; - this.parameters.controlTowerLastDriftMessage = props.prefixes.ssmParamName + '/controltower/lastDriftMessage'; - this.parameters.configTableArn = props.prefixes.ssmParamName + '/prepare-stack/configTable/arn'; - this.parameters.configTableName = props.prefixes.ssmParamName + '/prepare-stack/configTable/name'; - this.parameters.cloudTrailBucketName = - props.prefixes.ssmParamName + '/organization/security/cloudtrail/log/bucket-name'; - this.parameters.flowLogsDestinationBucketArn = - props.prefixes.ssmParamName + '/vpc/flow-logs/destination/bucket/arn'; - this.parameters.metadataBucketArn = props.prefixes.ssmParamName + '/metadata/bucket/arn'; - this.parameters.metadataBucketCmkArn = props.prefixes.ssmParamName + '/kms/metadata/key-arn'; - this.parameters.acceleratorCmkArn = props.prefixes.ssmParamName + '/kms/key-arn'; - this.parameters.ebsDefaultCmkArn = props.prefixes.ssmParamName + '/ebs/default-encryption/key-arn'; - this.parameters.s3CmkArn = props.prefixes.ssmParamName + '/kms/s3/key-arn'; - this.parameters.secretsManagerCmkArn = props.prefixes.ssmParamName + '/kms/secrets-manager/key-arn'; - this.parameters.cloudWatchLogCmkArn = props.prefixes.ssmParamName + '/kms/cloudwatch/key-arn'; - this.parameters.snsTopicCmkArn = props.prefixes.ssmParamName + '/kms/snstopic/key-arn'; - this.parameters.lambdaCmkArn = props.prefixes.ssmParamName + '/kms/lambda/key-arn'; - this.parameters.managementCmkArn = props.prefixes.ssmParamName + '/management/kms/key-arn'; - this.parameters.importedAssetsBucketCmkArn = props.prefixes.importResourcesSsmParamName + '/assets/kms/key'; - this.parameters.assetsBucketCmkArn = props.prefixes.ssmParamName + '/assets/kms/key'; - this.parameters.identityCenterInstanceArn = - props.prefixes.ssmParamName + '/organization/security/identity-center/instance-arn'; - this.parameters.identityStoreId = - props.prefixes.ssmParamName + '/organization/security/identity-center/identity-store-id'; - - // - // CMK details initialization - this.customerManagedKeys.orgTrailLog = { - alias: props.prefixes.kmsAlias + '/organizations-cloudtrail/log-group/', - description: 'CloudTrail Log Group CMK', - }; - this.customerManagedKeys.centralLogsBucket = { - alias: props.prefixes.kmsAlias + '/central-logs/s3', - description: 'AWS Accelerator Central Logs Bucket CMK', - }; - this.customerManagedKeys.importedCentralLogsBucket = { - alias: props.prefixes.kmsAlias + '/imported-bucket/central-logs/s3', - description: 'AWS Accelerator Imported Central Logs Bucket CMK', - }; - this.customerManagedKeys.metadataBucket = { - alias: props.prefixes.kmsAlias + '/kms/metadata/key', - description: 'The s3 bucket key for accelerator metadata collection', - }; - this.customerManagedKeys.ebsDefault = { - alias: props.prefixes.kmsAlias + '/ebs/default-encryption/key', - description: 'AWS Accelerator default EBS Volume Encryption key', - }; - this.customerManagedKeys.s3 = { - alias: props.prefixes.kmsAlias + '/kms/s3/key', - description: 'AWS Accelerator S3 Kms Key', - }; - this.customerManagedKeys.cloudWatchLog = { - alias: props.prefixes.kmsAlias + '/kms/cloudwatch/key', - description: 'AWS Accelerator CloudWatch Kms Key', - }; - this.customerManagedKeys.cloudWatchLogReplication = { - alias: props.prefixes.kmsAlias + '/kms/replication/cloudwatch/logs/key', - description: 'AWS Accelerator CloudWatch Logs Replication Kms Key', - }; - this.customerManagedKeys.awsBackup = { - alias: props.prefixes.kmsAlias + '/kms/backup/key', - description: 'AWS Accelerator Backup Kms Key', - }; - this.customerManagedKeys.sns = { - alias: props.prefixes.kmsAlias + '/kms/sns/key', - description: 'AWS Accelerator SNS Kms Key', - }; - this.customerManagedKeys.snsTopic = { - alias: props.prefixes.kmsAlias + '/kms/snstopic/key', - description: 'AWS Accelerator SNS Topic Kms Key', - }; - this.customerManagedKeys.secretsManager = { - alias: props.prefixes.kmsAlias + '/kms/secrets-manager/key', - description: 'AWS Accelerator Secrets Manager Kms Key', - }; - this.customerManagedKeys.lambda = { - alias: props.prefixes.kmsAlias + '/kms/lambda/key', - description: 'AWS Accelerator Lambda Kms Key', - }; - this.customerManagedKeys.acceleratorKey = { - alias: props.prefixes.kmsAlias + '/kms/key', - description: 'AWS Accelerator Kms Key', - }; - this.customerManagedKeys.managementKey = { - alias: props.prefixes.kmsAlias + '/management/kms/key', - description: 'AWS Accelerator Management Account Kms Key', - }; - this.customerManagedKeys.importedAssetsBucketCmkArn = { - alias: props.prefixes.kmsAlias + '/assets/kms/key', - description: 'Key used to encrypt solution assets', - }; - this.customerManagedKeys.assetsBucket = { - alias: props.prefixes.kmsAlias + '/assets/kms/key', - description: 'Key used to encrypt solution assets', - }; - - this.customerManagedKeys.ssmKey = { - alias: props.prefixes.kmsAlias + '/sessionmanager-logs/session', - description: 'AWS Accelerator Session Manager Session Encryption', - }; - // - // Bucket prefixes initialization - this.bucketPrefixes.assetsAccessLog = props.prefixes.bucketName + '-assets-logs'; - this.bucketPrefixes.assets = props.prefixes.bucketName + '-assets'; - this.bucketPrefixes.elbLogs = props.prefixes.bucketName + '-elb-access-logs'; - this.bucketPrefixes.firewallConfig = props.prefixes.bucketName + '-firewall-config'; - this.bucketPrefixes.costUsage = props.prefixes.bucketName + '-cur'; - this.bucketPrefixes.s3AccessLogs = props.prefixes.bucketName + '-s3-access-logs'; - this.bucketPrefixes.auditManager = props.prefixes.bucketName + '-auditmgr'; - this.bucketPrefixes.vpcFlowLogs = props.prefixes.bucketName + '-vpc'; - this.bucketPrefixes.metadata = props.prefixes.bucketName + '-metadata'; - this.bucketPrefixes.centralLogs = props.prefixes.bucketName + '-central-logs'; - - // - // Lambda function name initialization - this.parameters.firehoseRecordsProcessorFunctionName = props.prefixes.accelerator + '-FirehoseRecordsProcessor'; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/accelerator-stage.ts b/source/packages/@aws-accelerator/accelerator/lib/accelerator-stage.ts deleted file mode 100644 index ad6ecf7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/accelerator-stage.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -export enum AcceleratorStage { - PIPELINE = 'pipeline', - /** - * Accelerator Tester Pipeline - */ - TESTER_PIPELINE = 'tester-pipeline', - /** - * Prepare Stage - Verify the configuration files, environment and create accounts - */ - PREPARE = 'prepare', - /** - * DiagnosticsPack Stage - Creates Diagnostics pack resources - */ - DIAGNOSTICS_PACK = 'diagnostics-pack', - ORGANIZATIONS = 'organizations', - KEY = 'key', - LOGGING = 'logging', - /** - * Accounts Stage - Handle all Organization and Accounts actions - */ - ACCOUNTS = 'accounts', - BOOTSTRAP = 'bootstrap', - CUSTOMIZATIONS = 'customizations', - DEPENDENCIES = 'dependencies', - SECURITY = 'security', - SECURITY_RESOURCES = 'security-resources', - OPERATIONS = 'operations', - IDENTITY_CENTER = 'identity-center', - NETWORK_PREP = 'network-prep', - NETWORK_VPC = 'network-vpc', - NETWORK_VPC_ENDPOINTS = 'network-vpc-endpoints', - NETWORK_VPC_DNS = 'network-vpc-dns', - NETWORK_ASSOCIATIONS = 'network-associations', - NETWORK_ASSOCIATIONS_GWLB = 'network-associations-gwlb', - SECURITY_AUDIT = 'security-audit', - RESOURCE_POLICY_ENFORCEMENT = 'resource-policy-enforcement', - FINALIZE = 'finalize', - IMPORT_ASEA_RESOURCES = 'import-asea-resources', - POST_IMPORT_ASEA_RESOURCES = 'post-import-asea-resources', -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/accelerator.ts b/source/packages/@aws-accelerator/accelerator/lib/accelerator.ts deleted file mode 100644 index 2f481b7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/accelerator.ts +++ /dev/null @@ -1,1500 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/* istanbul ignore file */ - -import { PluginHost } from 'aws-cdk/lib/api/plugin'; -import { RequireApproval } from 'aws-cdk/lib/diff'; -import { Command } from 'aws-cdk/lib/settings'; -import * as AWS from 'aws-sdk'; -import * as fs from 'fs'; -import { AssumeRoleCommandOutput } from '@aws-sdk/client-sts'; -import { - SSMClient, - GetParameterCommand, - GetParameterCommandInput, - GetParameterCommandOutput, -} from '@aws-sdk/client-ssm'; -import { S3Client, HeadBucketCommand } from '@aws-sdk/client-s3'; -import { IAMClient, GetRoleCommand, GetRoleCommandInput } from '@aws-sdk/client-iam'; -import { - AccountsConfig, - GlobalConfig, - OrganizationConfig, - CustomizationsConfig, - ReplacementsConfig, - Region, - DeploymentTargets, -} from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - getCrossAccountCredentials, - getGlobalRegion, - getCurrentAccountId, -} from '@aws-accelerator/utils/lib/common-functions'; -import { setStsTokenPreferences } from '@aws-accelerator/utils/lib/set-token-preferences'; - -import { AssumeProfilePlugin } from '@aws-cdk-extensions/cdk-plugin-assume-role'; -import { isBeforeBootstrapStage, writeImportResources } from '../utils/app-utils'; -import { AcceleratorStage } from './accelerator-stage'; -import { AcceleratorToolkit, AcceleratorToolkitProps } from './toolkit'; -import { v4 as uuidv4 } from 'uuid'; -import { getReplacementsConfig } from '../utils/app-utils'; -import * as path from 'path'; - -export type CustomStackRunOrder = { - /** - * Unique stack name for customizations config custom stack - */ - stackName: string; - /** - * Run order of stack - */ - runOrder: number; - /** - * Account Ids where custom stack is deployed to - */ - accounts: string[]; - /** - * Regions where custom stack is deployed to - */ - regions: string[]; -}; - -export type ApplicationStackRunOrder = { - /** - * Unique stack name for application stack - */ - stackName: string; - /** - * Account Ids where application stack is deployed to - */ - accounts: string[]; - /** - * Regions where application stack is deployed to - */ - regions: string[]; -}; -const logger = createLogger(['accelerator']); - -process.on('uncaughtException', err => { - logger.error(err); - throw new Error('Synthesis failed'); -}); - -export const BootstrapVersion = 18; - -// -// The accelerator stack prefix value -// -const stackPrefix = process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'; - -/** - * constant maintaining cloudformation stack names - */ -export const AcceleratorStackNames: Record = { - [AcceleratorStage.PREPARE]: `${stackPrefix}-PrepareStack`, - [AcceleratorStage.DIAGNOSTICS_PACK]: `${stackPrefix}-DiagnosticsPackStack`, - [AcceleratorStage.PIPELINE]: `${stackPrefix}-PipelineStack`, - [AcceleratorStage.TESTER_PIPELINE]: `${stackPrefix}-TesterPipelineStack`, - [AcceleratorStage.ORGANIZATIONS]: `${stackPrefix}-OrganizationsStack`, - [AcceleratorStage.KEY]: `${stackPrefix}-KeyStack`, - [AcceleratorStage.LOGGING]: `${stackPrefix}-LoggingStack`, - [AcceleratorStage.BOOTSTRAP]: `${stackPrefix}-BootstrapStack`, - [AcceleratorStage.ACCOUNTS]: `${stackPrefix}-AccountsStack`, - [AcceleratorStage.DEPENDENCIES]: `${stackPrefix}-DependenciesStack`, - [AcceleratorStage.SECURITY]: `${stackPrefix}-SecurityStack`, - [AcceleratorStage.SECURITY_RESOURCES]: `${stackPrefix}-SecurityResourcesStack`, - [AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT]: `${stackPrefix}-ResourcePolicyEnforcementStack`, - [AcceleratorStage.OPERATIONS]: `${stackPrefix}-OperationsStack`, - [AcceleratorStage.IDENTITY_CENTER]: `${stackPrefix}-IdentityCenterStack`, - [AcceleratorStage.NETWORK_PREP]: `${stackPrefix}-NetworkPrepStack`, - [AcceleratorStage.NETWORK_VPC]: `${stackPrefix}-NetworkVpcStack`, - [AcceleratorStage.NETWORK_VPC_ENDPOINTS]: `${stackPrefix}-NetworkVpcEndpointsStack`, - [AcceleratorStage.NETWORK_VPC_DNS]: `${stackPrefix}-NetworkVpcDnsStack`, - [AcceleratorStage.NETWORK_ASSOCIATIONS]: `${stackPrefix}-NetworkAssociationsStack`, - [AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB]: `${stackPrefix}-NetworkAssociationsGwlbStack`, - [AcceleratorStage.FINALIZE]: `${stackPrefix}-FinalizeStack`, - [AcceleratorStage.SECURITY_AUDIT]: `${stackPrefix}-SecurityAuditStack`, - [AcceleratorStage.CUSTOMIZATIONS]: `${stackPrefix}-CustomizationsStack`, -}; - -/** - * Properties for the accelerator wrapper class - */ -export interface AcceleratorProps { - readonly command: string; - readonly configDirPath: string; - readonly stage?: string; - readonly account?: string; - readonly region?: string; - readonly partition: string; - readonly requireApproval: RequireApproval; - readonly app?: string; - readonly caBundlePath?: string; - readonly ec2Creds?: boolean; - readonly proxyAddress?: string; - readonly enableSingleAccountMode: boolean; - readonly useExistingRoles: boolean; - readonly qualifier?: string; -} -let maxStacks = Number(process.env['MAX_CONCURRENT_STACKS'] ?? 250); - -/** - * Wrapper around the CdkToolkit. The Accelerator defines this wrapper to add - * the following functionality: - * - * - Differentiation between single stack and multiple stack deployments - * - Parallelization of multi-account/multi-region stack deployments - * - Accelerator stage-specific deployment behaviors - */ -export abstract class Accelerator { - static isSupportedStage(stage: AcceleratorStage): boolean { - if (!stage) { - return false; - } - return Object.values(AcceleratorStage).includes(stage); - } - - /** - * Executes commands conditionally based on CLI input - * @returns - */ - static async run(props: AcceleratorProps): Promise { - // - // Set global region - // - const globalRegion = getGlobalRegion(props.partition); - // - // If not pipeline stage, load global config, management account credentials, - // and assume role plugin - // - const configDependentStage = this.isConfigDependentStage(props.stage); - const managementAccountCredentials = configDependentStage - ? await this.getManagementAccountCredentials(props.partition) - : undefined; - const globalConfig = configDependentStage ? GlobalConfig.loadRawGlobalConfig(props.configDirPath) : undefined; - if (globalConfig?.externalLandingZoneResources?.importExternalLandingZoneResources) { - logger.info('Loading ASEA mapping for stacks list'); - await globalConfig.loadExternalMapping(); - logger.info('Loaded ASEA mapping'); - } - await checkDiffStage(props); - - // - // When running parallel, this will be the max concurrent stacks - // - if (props.command === 'deploy') { - maxStacks = globalConfig?.acceleratorSettings?.maxConcurrentStacks - ? globalConfig?.acceleratorSettings?.maxConcurrentStacks - : Number(process.env['MAX_CONCURRENT_STACKS'] ?? 250); - } - if (this.isConfigDependentStage(props.stage)) { - const assumeRoleName = setAssumeRoleName({ - stage: props.stage, - customDeploymentRole: globalConfig?.cdkOptions?.customDeploymentRole, - command: props.command, - managementAccountAccessRole: globalConfig?.managementAccountAccessRole, - }); - const accountsConfig = AccountsConfig.load(props.configDirPath); - const orgsConfig = OrganizationConfig.loadRawOrganizationsConfig(props.configDirPath); - await accountsConfig.loadAccountIds( - props.partition, - props.enableSingleAccountMode, - orgsConfig.enable, - accountsConfig, - ); - - if (props.account !== accountsConfig.getManagementAccountId()) { - await this.initializeAssumeRolePlugin({ - region: props.region ?? globalRegion, - assumeRoleName, - partition: props.partition, - caBundlePath: props.caBundlePath, - credentials: managementAccountCredentials, - }); - } - } - // - // Set toolkit props - // - const toolkitProps: AcceleratorToolkitProps = { - command: props.command, - enableSingleAccountMode: props.enableSingleAccountMode, - partition: props.partition, - stackPrefix, - stage: props.stage, - configDirPath: props.configDirPath, - requireApproval: props.requireApproval, - app: props.app, - caBundlePath: props.caBundlePath, - ec2Creds: props.ec2Creds, - proxyAddress: props.proxyAddress, - centralizeCdkBootstrap: globalConfig?.centralizeCdkBuckets?.enable, - cdkOptions: globalConfig?.cdkOptions, - useExistingRoles: props.useExistingRoles, - // central logs bucket kms key arn is dynamic and will be populated in app-utils - centralLogsBucketKmsKeyArn: undefined, - }; - // - // When an account and region is specified, execute as single stack. - // Synth and diff commands are also treated as a single stack action - // - if (this.isSingleStackAction(props)) { - await this.executeSingleStack(props, toolkitProps); - } else { - // - // Initialize array to enumerate promises created for parallel stack creation - // - const promises: Promise[] = []; - // - // Global config is required for remaining stages - // - if (!globalConfig) { - throw new Error( - `global-config.yaml could not be loaded. Global configuration is required for stage ${props.stage}`, - ); - } - // - // Read in the accounts config file and load account IDs - // if not provided as inputs in accountsConfig - // - const accountsConfig = AccountsConfig.load(props.configDirPath); - const organizationsConfig = OrganizationConfig.load(props.configDirPath); - await accountsConfig.loadAccountIds( - props.partition, - props.enableSingleAccountMode, - organizationsConfig.enable, - accountsConfig, - ); - const replacementsConfig = getReplacementsConfig(props.configDirPath, accountsConfig); - - // - // Set details about mandatory accounts - // - const managementAccountDetails = { - id: accountsConfig.getManagementAccountId(), - name: accountsConfig.getManagementAccount().name, - }; - const logArchiveAccountDetails = { - id: accountsConfig.getLogArchiveAccountId(), - name: accountsConfig.getLogArchiveAccount().name, - centralizedLoggingRegion: globalConfig.logging.centralizedLoggingRegion ?? globalConfig.homeRegion, - }; - const auditAccountDetails = { - id: accountsConfig.getAuditAccountId(), - name: accountsConfig.getAuditAccount().name, - }; - const regionDetails = { - homeRegion: globalConfig.homeRegion, - globalRegion: globalRegion, - enabledRegions: globalConfig.enabledRegions, - }; - - // - // Execute IMPORT_ASEA_RESOURCES Stage - // - await this.executeImportAseaResources(toolkitProps, promises, globalConfig, accountsConfig, maxStacks); - // - // Execute Bootstrap stacks for all identified accounts - // - await this.executeBootstrapStage(toolkitProps, promises, managementAccountDetails, globalConfig, accountsConfig); - // - // Execute PREPARE, ACCOUNTS, and FINALIZE stages in the management account - // - await this.executeManagementAccountStages( - toolkitProps, - globalConfig.homeRegion, - globalRegion, - managementAccountDetails, - ); - // - // Execute ORGANIZATIONS and SECURITY AUDIT stages - // - await this.executeSingleAccountMultiRegionStages( - toolkitProps, - promises, - globalConfig.enabledRegions, - managementAccountDetails, - auditAccountDetails, - maxStacks, - ); - // - // Execute LOGGING stage - // - await this.executeLoggingStage( - toolkitProps, - promises, - accountsConfig, - logArchiveAccountDetails, - regionDetails, - maxStacks, - ); - // - // Execute all remaining stages - // - await this.executeRemainingStages( - toolkitProps, - promises, - accountsConfig, - managementAccountDetails, - globalConfig.enabledRegions, - maxStacks, - replacementsConfig, - ); - - await Promise.all(promises); - } - } - - static async getManagementAccountCredentials(partition: string): Promise { - if (process.env['CREDENTIALS_PATH'] && fs.existsSync(process.env['CREDENTIALS_PATH'])) { - logger.info('Detected Debugging environment. Loading temporary credentials.'); - - const credentialsString = fs.readFileSync(process.env['CREDENTIALS_PATH']).toString(); - const credentials = JSON.parse(credentialsString); - - // Support for V2 SDK - AWS.config.update({ - accessKeyId: credentials.AccessKeyId, - secretAccessKey: credentials.SecretAccessKey, - sessionToken: credentials.SessionToken, - }); - } - if (process.env['MANAGEMENT_ACCOUNT_ID'] && process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']) { - logger.info('set management account credentials'); - logger.info(`managementAccountId => ${process.env['MANAGEMENT_ACCOUNT_ID']}`); - logger.info(`management account role name => ${process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']}`); - - const roleArn = `arn:${partition}:iam::${process.env['MANAGEMENT_ACCOUNT_ID']}:role/${process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']}`; - const stsClient = new AWS.STS({ region: process.env['AWS_REGION'] }); - logger.info(`management account roleArn => ${roleArn}`); - - const assumeRoleCredential = await throttlingBackOff(() => - stsClient.assumeRole({ RoleArn: roleArn, RoleSessionName: 'acceleratorAssumeRoleSession' }).promise(), - ); - - process.env['AWS_ACCESS_KEY_ID'] = assumeRoleCredential.Credentials!.AccessKeyId!; - process.env['AWS_ACCESS_KEY'] = assumeRoleCredential.Credentials!.AccessKeyId!; - process.env['AWS_SECRET_KEY'] = assumeRoleCredential.Credentials!.SecretAccessKey!; - process.env['AWS_SECRET_ACCESS_KEY'] = assumeRoleCredential.Credentials!.SecretAccessKey!; - process.env['AWS_SESSION_TOKEN'] = assumeRoleCredential.Credentials!.SessionToken; - - // Support for V2 SDK - AWS.config.update({ - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - }); - - return assumeRoleCredential.Credentials; - } else { - return undefined; - } - } - - static async initializeAssumeRolePlugin(props: { - region: string | undefined; - assumeRoleName: string | undefined; - partition: string; - caBundlePath: string | undefined; - credentials?: AWS.STS.Credentials; - }): Promise { - const assumeRolePlugin = new AssumeProfilePlugin({ - region: props.region, - assumeRoleName: props.assumeRoleName, - assumeRoleDuration: 3600, - credentials: props.credentials, - partition: props.partition, - caBundlePath: props.caBundlePath, - }); - assumeRolePlugin.init(PluginHost.instance); - return assumeRolePlugin; - } - - /** - * Returns true if the stage is dependent on config directory, except pipeline, tester-pipeline and diagnostic-pack all stages are config dependent - * @param stage - * @returns - */ - private static isConfigDependentStage(stage?: string): boolean { - if (!stage) { - return true; - } - if ( - stage === AcceleratorStage.PIPELINE || - stage === AcceleratorStage.TESTER_PIPELINE || - stage === AcceleratorStage.DIAGNOSTICS_PACK - ) { - return false; - } - return true; - } - - private static isSingleStackAction(props: AcceleratorProps) { - return ( - props.account || props.region || [Command.SYNTH.toString(), Command.SYNTHESIZE.toString()].includes(props.command) - ); - } - - /** - * Executes a single stack if both account and region are specified in the CLI command. - * Also used if synth or diff commands are specified. - * @param props - * @param globalConfig - * @returns - */ - private static async executeSingleStack( - props: AcceleratorProps, - toolkitProps: AcceleratorToolkitProps, - ): Promise { - // For single stack executions, ensure account and region are specified - if (props.account || props.region) { - if (props.account && !props.region) { - logger.error(`Account set to ${props.account}, but region is undefined`); - throw new Error(`CLI command validation failed at runtime.`); - } - if (props.region && !props.account) { - logger.error(`Region set to ${props.region}, but account is undefined`); - throw new Error(`CLI command validation failed at runtime.`); - } - } - return AcceleratorToolkit.execute({ - accountId: props.account, - region: props.region, - ...toolkitProps, - }); - } - - /** - * Execute Bootstrap stage commands - * @param toolkitProps - * @param promises - * @param managementAccountDetails - * @param globalConfig - * @param accountsConfig - */ - private static async executeBootstrapStage( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - managementAccountDetails: { id: string; name: string }, - globalConfig: GlobalConfig, - accountsConfig: AccountsConfig, - ) { - if (toolkitProps.command === Command.BOOTSTRAP) { - // - // Bootstrap the Management account - await this.bootstrapManagementAccount( - toolkitProps, - promises, - managementAccountDetails.id, - globalConfig.enabledRegions, - ); - // - // Bootstrap remaining accounts - await this.bootstrapRemainingAccounts( - toolkitProps, - promises, - accountsConfig, - globalConfig, - managementAccountDetails, - ); - } - } - - /** - * Bootstrap the management account - * @param toolkitProps - * @param promises - * @param managementAccountId - * @param enabledRegions - */ - private static async bootstrapManagementAccount( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - managementAccountId: string, - enabledRegions: string[], - ): Promise { - for (const region of enabledRegions) { - await delay(500); - promises.push( - AcceleratorToolkit.execute({ - accountId: managementAccountId, - region, - trustedAccountId: managementAccountId, - ...toolkitProps, - stage: 'bootstrap', - }), - ); - await Promise.all(promises); - promises.length = 0; - } - } - - /** - * Bootstrap all non-management accounts in the organization - * @param toolkitProps - * @param promises - * @param accountsConfig - * @param globalConfig - * @param managementAccountDetails - * @returns - */ - private static async bootstrapRemainingAccounts( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - managementAccountDetails: { id: string; name: string }, - ): Promise { - const nonManagementAccounts = accountsConfig - .getAccounts(toolkitProps.enableSingleAccountMode) - .filter(accountItem => accountItem.name !== managementAccountDetails.name); - - for (const region of globalConfig.enabledRegions) { - for (const account of nonManagementAccounts) { - const accountId = accountsConfig.getAccountId(account.name); - // Add bootstrap promises - await this.addAccountBootstrapPromise( - toolkitProps, - promises, - globalConfig, - accountId, - region, - managementAccountDetails.id, - ); - - if (promises.length >= 100) { - await Promise.all(promises); - promises.length = 0; - } - } - } - await Promise.all(promises); - } - - /** - * Add a bootstrap promise to the promises array if the account needs bootstrapping - * @param toolkitProps - * @param promises - * @param globalConfig - * @param accountId - * @param region - * @param managementAccountId - */ - private static async addAccountBootstrapPromise( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - globalConfig: GlobalConfig, - accountId: string, - region: string, - managementAccountId: string, - ): Promise { - const needsBootstrapping = await bootstrapRequired({ - accountId, - region, - partition: toolkitProps.partition, - managementAccountAccessRole: globalConfig.managementAccountAccessRole, - centralizedBuckets: globalConfig.centralizeCdkBuckets?.enable || globalConfig.cdkOptions?.centralizeBuckets, - homeRegion: globalConfig.homeRegion, - customDeploymentRoleName: globalConfig.cdkOptions?.customDeploymentRole, - force: globalConfig.cdkOptions?.forceBootstrap, - }); - if (needsBootstrapping) { - await delay(500); - - promises.push( - AcceleratorToolkit.execute({ - accountId, - region, - trustedAccountId: managementAccountId, - ...toolkitProps, - stage: 'bootstrap', - }), - ); - } - } - - /** - * Execute stages that are only deployed to the management account - * @param toolkitProps - * @param homeRegion - * @param globalRegion - * @param managementAccountDetails - * @returns - */ - private static async executeManagementAccountStages( - toolkitProps: AcceleratorToolkitProps, - homeRegion: string, - globalRegion: string, - managementAccountDetails: { id: string; name: string }, - ): Promise { - switch (toolkitProps.stage) { - // - // PREPARE and IDENTITY CENTER stage - case AcceleratorStage.IDENTITY_CENTER: - case AcceleratorStage.PREPARE: - logger.info(`Executing ${toolkitProps.stage} for ${managementAccountDetails.name} account.`); - return AcceleratorToolkit.execute({ - accountId: managementAccountDetails.id, - region: homeRegion, - ...toolkitProps, - }); - - // - // ACCOUNTS and FINALIZE stages - case AcceleratorStage.ACCOUNTS: - case AcceleratorStage.FINALIZE: - logger.info(`Executing ${toolkitProps.stage} for ${managementAccountDetails.name} account.`); - return AcceleratorToolkit.execute({ - accountId: managementAccountDetails.id, - region: globalRegion, - ...toolkitProps, - }); - } - } - - /** - * Execute single account, multi-region stages - * @param toolkitProps - * @param promises - * @param enabledRegions - * @param managementAccountDetails - * @param auditAccountDetails - */ - private static async executeSingleAccountMultiRegionStages( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - enabledRegions: string[], - managementAccountDetails: { id: string; name: string }, - auditAccountDetails: { id: string; name: string }, - maxStacks: number, - ) { - for (const region of enabledRegions) { - switch (toolkitProps.stage) { - // - // ORGANIZATIONS stage - case AcceleratorStage.ORGANIZATIONS: - logger.info( - `Executing ${toolkitProps.stage} for ${managementAccountDetails.name} account in ${region} region.`, - ); - promises.push( - AcceleratorToolkit.execute({ - accountId: managementAccountDetails.id, - region, - ...toolkitProps, - }), - ); - if (promises.length >= maxStacks) { - await Promise.all(promises); - promises.length = 0; - } - break; - // - // SECURITY AUDIT stage - case AcceleratorStage.SECURITY_AUDIT: - logger.info(`Executing ${toolkitProps.stage} for ${auditAccountDetails.name} account in ${region} region.`); - promises.push( - AcceleratorToolkit.execute({ - accountId: auditAccountDetails.id, - region: region, - ...toolkitProps, - }), - ); - if (promises.length >= maxStacks) { - await Promise.all(promises); - promises.length = 0; - } - break; - } - } - } - - /** - * Execute the Logging stage - * @param toolkitProps - * @param promises - * @param accountsConfig - * @param logArchiveAccountDetails - * @param enabledRegions - */ - private static async executeLoggingStage( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - accountsConfig: AccountsConfig, - logArchiveAccountDetails: { id: string; name: string; centralizedLoggingRegion: string }, - regionDetails: { homeRegion: string; globalRegion: string; enabledRegions: string[] }, - maxStacks: number, - ) { - if (toolkitProps.stage === AcceleratorStage.LOGGING) { - // - // Execute in centralized logging region before other regions in LogArchive account. - // Centralized logging region needs to complete before other enabled regions due to cross-account/region dependency on the central logs bucket. - logger.info( - `Executing ${toolkitProps.stage} for ${logArchiveAccountDetails.name} account in ${logArchiveAccountDetails.centralizedLoggingRegion} region.`, - ); - await AcceleratorToolkit.execute({ - accountId: logArchiveAccountDetails.id, - region: logArchiveAccountDetails.centralizedLoggingRegion, - ...toolkitProps, - }); - - // Execute in all other regions in the LogArchive account - await this.executeLogArchiveNonCentralRegions( - toolkitProps, - logArchiveAccountDetails, - regionDetails.enabledRegions, - ); - - // - // Execute in all other regions and accounts - await this.executeRemainingLoggingStage( - toolkitProps, - promises, - accountsConfig, - logArchiveAccountDetails, - regionDetails.enabledRegions, - maxStacks, - ); - - // - // Set STS token to version 2 in home region of every account - // STS token is vended in homeRegion and queried at globalRegion to ensure v1Token can be used - if (toolkitProps.region === regionDetails.homeRegion) { - logger.info(`Setting STS token preferences for ${toolkitProps.accountId} in region ${toolkitProps.region}`); - await setStsTokenPreferences(toolkitProps.accountId!, regionDetails.globalRegion); - } - } - } - - private static async executeLogArchiveNonCentralRegions( - toolkitProps: AcceleratorToolkitProps, - logArchiveAccountDetails: { id: string; name: string; centralizedLoggingRegion: string }, - enabledRegions: string[], - ) { - const nonCentralRegions = enabledRegions.filter( - regionItem => regionItem !== logArchiveAccountDetails.centralizedLoggingRegion, - ); - const loggingAccountPromises = []; - for (const region of nonCentralRegions) { - logger.info(`Executing ${toolkitProps.stage} for ${logArchiveAccountDetails.name} account in ${region} region.`); - loggingAccountPromises.push( - AcceleratorToolkit.execute({ - accountId: logArchiveAccountDetails.id, - region: region, - ...toolkitProps, - }), - ); - } - await Promise.all(loggingAccountPromises); - } - - /** - * Execute Logging stage in all accounts and regions other than the LogArchive account - * @param toolkitProps - * @param promises - * @param accountsConfig - * @param logArchiveAccountDetails - * @param enabledRegions - */ - private static async executeRemainingLoggingStage( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - accountsConfig: AccountsConfig, - logArchiveAccountDetails: { id: string; name: string; centralizedLoggingRegion: string }, - enabledRegions: string[], - maxStacks: number, - ) { - const nonLogArchiveAccounts = [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts].filter( - accountItem => accountItem.name !== logArchiveAccountDetails.name, - ); - - for (const region of enabledRegions) { - for (const account of nonLogArchiveAccounts) { - if ( - !( - account.name === logArchiveAccountDetails.name && - region === logArchiveAccountDetails.centralizedLoggingRegion - ) - ) { - const accountId = accountsConfig.getAccountId(account.name); - logger.info(`Executing ${toolkitProps.stage} for ${account.name} account in ${region} region.`); - promises.push( - AcceleratorToolkit.execute({ - accountId, - region, - ...toolkitProps, - }), - ); - - if (promises.length >= maxStacks) { - await Promise.all(promises); - promises.length = 0; - } - } - } - } - } - - /** - * Execute all remaining stages - * @param toolkitProps - * @param promises - * @param accountsConfig - * @param managementAccountDetails - * @param enabledRegions - */ - private static async executeRemainingStages( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - accountsConfig: AccountsConfig, - managementAccountDetails: { id: string; name: string }, - enabledRegions: string[], - maxStacks: number, - replacementsConfig: ReplacementsConfig, - ) { - if ( - toolkitProps.stage === AcceleratorStage.SECURITY || - toolkitProps.stage === AcceleratorStage.SECURITY_RESOURCES || - toolkitProps.stage === AcceleratorStage.OPERATIONS || - toolkitProps.stage === AcceleratorStage.NETWORK_PREP || - toolkitProps.stage === AcceleratorStage.NETWORK_VPC || - toolkitProps.stage === AcceleratorStage.NETWORK_ASSOCIATIONS || - toolkitProps.stage === AcceleratorStage.CUSTOMIZATIONS || - toolkitProps.stage === AcceleratorStage.KEY - ) { - // - // Execute for all regions in Management account - await this.executeManagementRemainingStages(toolkitProps, promises, managementAccountDetails.id, enabledRegions); - await Promise.all(promises); - promises.length = 0; - // - // Execute for all remaining accounts and regions - await this.executeAllAccountRemainingStages( - toolkitProps, - promises, - accountsConfig, - managementAccountDetails.name, - enabledRegions, - maxStacks, - ); - await Promise.all(promises); - promises.length = 0; - - // check to see if customizations has stacks. If no stacks are specified then do nothing - if ( - fs.existsSync(path.join(toolkitProps.configDirPath!, CustomizationsConfig.FILENAME)) && - toolkitProps.stage === AcceleratorStage.CUSTOMIZATIONS - ) { - this.executeCustomizationsStacks( - toolkitProps, - promises, - replacementsConfig, - accountsConfig, - enabledRegions, - maxStacks, - ); - } - // clearing queue after customizations run - await Promise.all(promises); - promises.length = 0; - } - } - private static async executeCustomizationsStacks( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - replacementsConfig: ReplacementsConfig, - accountsConfig: AccountsConfig, - enabledRegions: string[], - maxStacks: number, - ) { - const customizationsConfig = CustomizationsConfig.load(toolkitProps.configDirPath!, replacementsConfig); - const customStacks = customizationsConfig.getCustomStacks(); - const customizationsStackRunOrderData: CustomStackRunOrder[] = []; - for (const stack of customStacks) { - // get accounts where custom stack is deployed to - const deploymentAccts = accountsConfig.getAccountIdsFromDeploymentTarget(stack.deploymentTargets); - // get regions where custom stack is deployed to - const deploymentRegions = stack.regions.map(a => a.toString()); - customizationsStackRunOrderData.push({ - stackName: stack.name, - runOrder: stack.runOrder, - accounts: deploymentAccts, - regions: deploymentRegions, - }); - } - const groupedRunOrders = groupByRunOrder(customizationsStackRunOrderData); - - for (const groupRunOrder of groupedRunOrders) { - for (const stack of groupRunOrder.stacks) { - logger.info( - `Executing custom stacks ${stack.stackNames.join(', ')} for ${stack.account} account in ${ - stack.region - } region.`, - ); - promises.push( - AcceleratorToolkit.execute({ - accountId: stack.account, - region: stack.region, - ...toolkitProps, - stackNames: stack.stackNames, - }), - ); - if (promises.length >= maxStacks) { - await Promise.all(promises); - promises.length = 0; - } - } - // exhaust each runOrder before proceeding to the next - await Promise.all(promises); - } - - promises.length = 0; - // process application stacks - const appStacks = customizationsConfig.getAppStacks(); - for (const application of appStacks) { - //find out deployment account - const deploymentAccts = accountsConfig.getAccountIdsFromDeploymentTarget(application.deploymentTargets); - //find out deployment region - const deploymentRegions = getRegionsFromDeploymentTarget(application.deploymentTargets, enabledRegions); - for (const deploymentAcct of deploymentAccts) { - for (const deploymentRegion of deploymentRegions) { - const applicationStackName = `${toolkitProps.stackPrefix}-App-${application.name}-${deploymentAcct}-${deploymentRegion}`; - logger.info( - `Executing application stack ${applicationStackName} for ${deploymentAcct} account in ${deploymentRegion} region.`, - ); - promises.push( - AcceleratorToolkit.execute({ - accountId: deploymentAcct, - region: deploymentRegion, - ...toolkitProps, - stackNames: [applicationStackName], - }), - ); - if (promises.length >= maxStacks) { - await Promise.all(promises); - promises.length = 0; - } - } - } - } - // wait for all applications stacks to deploy - await Promise.all(promises); - } - - /** - * Execute all remaining stages for the Management account - * @param toolkitProps - * @param promises - * @param managementAccountId - * @param enabledRegions - */ - private static async executeManagementRemainingStages( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - managementAccountId: string, - enabledRegions: string[], - ) { - for (const region of enabledRegions) { - promises.push( - AcceleratorToolkit.execute({ - accountId: managementAccountId, - region, - ...toolkitProps, - }), - ); - } - await Promise.all(promises); - } - - /** - * Execute all remaining accounts/regions for all remaining stages - * @param toolkitProps - * @param promises - * @param accountsConfig - * @param enabledRegions - */ - private static async executeAllAccountRemainingStages( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - accountsConfig: AccountsConfig, - managementAccountName: string, - enabledRegions: string[], - maxStacks: number, - ) { - const nonManagementAccounts = [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts].filter( - accountItem => accountItem.name !== managementAccountName, - ); - - for (const region of enabledRegions) { - for (const account of nonManagementAccounts) { - const accountId = accountsConfig.getAccountId(account.name); - logger.info(`Executing ${toolkitProps.stage} for ${account.name} account in ${region} region.`); - promises.push( - AcceleratorToolkit.execute({ - accountId, - region, - ...toolkitProps, - }), - ); - if (promises.length >= maxStacks) { - await Promise.all(promises); - promises.length = 0; - } - } - } - } - - private static async executeImportAseaResources( - toolkitProps: AcceleratorToolkitProps, - promises: Promise[], - globalConfig: GlobalConfig, - accountsConfig: AccountsConfig, - maxStacks: number, - ) { - if ( - ![AcceleratorStage.IMPORT_ASEA_RESOURCES, AcceleratorStage.POST_IMPORT_ASEA_RESOURCES].includes( - toolkitProps.stage as AcceleratorStage, - ) - ) { - return; - } - if (!globalConfig.externalLandingZoneResources) { - logger.error(`Stage is ${toolkitProps.stage} but externalLandingZoneResources is not defined in global config.`); - throw new Error(`Configuration validation failed at runtime.`); - } - const aseaPrefix = globalConfig.externalLandingZoneResources.acceleratorPrefix; - const aseaName = globalConfig.externalLandingZoneResources.acceleratorName; - const mapping = globalConfig.externalLandingZoneResources.templateMap; - let previousPhase = '-1'; - for (const phase of ['-1', '0', '1', '2', '3', '4', '5']) { - logger.info(`Deploying Stacks in Phase ${phase}`); - if (previousPhase !== phase) { - await Promise.all(promises).catch(err => { - logger.error(err); - throw new Error(`Configuration validation failed at runtime.`); - }); - previousPhase = phase; - } - for (const region of globalConfig.enabledRegions) { - for (const account of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - const accountId = accountsConfig.getAccountId(account.name); - const stackKeys: string[] = []; - Object.keys(mapping).forEach(key => { - if ( - mapping[key].accountId === accountId && - mapping[key].region === region && - mapping[key].phase === phase && - !mapping[key].parentStack - ) { - stackKeys.push(key); - } - }); - - for (const key of stackKeys) { - const stack = mapping[key]; - promises.push( - AcceleratorToolkit.execute({ - ...toolkitProps, - app: `cdk.out/phase${phase}-${accountId}-${region}`, - stackPrefix: aseaPrefix, - stack: stack.stackName, - // ASEA Adds "AcceleratorName" tag to all stacks - // Adding it to avoid updating all stacks - tags: [ - { - Key: 'AcceleratorName', - Value: aseaName, - }, - ], - }), - ); - if (promises.length >= maxStacks) { - await Promise.all(promises).catch(err => { - logger.error(err); - throw new Error(`Configuration validation failed at runtime.`); - }); - promises.length = 0; - } - } - } - } - } - await Promise.all(promises).catch(err => { - logger.error(err); - throw new Error(`Configuration validation failed at runtime.`); - }); - // Write resource mapping and stacks to s3 - const managementCredentials = await this.getManagementAccountCredentials(toolkitProps.partition); - - await writeImportResources({ - credentials: managementCredentials, - accountsConfig: accountsConfig, - globalConfig: globalConfig, - mapping, - }); - } -} - -export async function checkDiffStage(props: AcceleratorProps) { - const diffPromises: Promise[] = []; - const allStages = [ - AcceleratorStage.ORGANIZATIONS, - AcceleratorStage.KEY, - AcceleratorStage.CUSTOMIZATIONS, - AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT, - AcceleratorStage.DEPENDENCIES, - AcceleratorStage.FINALIZE, - AcceleratorStage.IDENTITY_CENTER, - AcceleratorStage.LOGGING, - AcceleratorStage.NETWORK_ASSOCIATIONS, - AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB, - AcceleratorStage.NETWORK_PREP, - AcceleratorStage.NETWORK_VPC, - AcceleratorStage.NETWORK_VPC_DNS, - AcceleratorStage.NETWORK_VPC_ENDPOINTS, - AcceleratorStage.OPERATIONS, - AcceleratorStage.ORGANIZATIONS, - AcceleratorStage.SECURITY, - AcceleratorStage.SECURITY_AUDIT, - AcceleratorStage.SECURITY_RESOURCES, - ]; - - // if diff command is run and no stage is set then run all stages - if (props.command === Command.DIFF.toString() && !props.stage) { - for (const diffStage of allStages) { - const diffProps: AcceleratorProps = { - app: props.app, - command: props.command, - configDirPath: props.configDirPath, - stage: diffStage, - account: props.account, - region: props.region, - partition: props.partition, - requireApproval: props.requireApproval, - caBundlePath: props.caBundlePath, - ec2Creds: props.ec2Creds, - proxyAddress: props.proxyAddress, - enableSingleAccountMode: props.enableSingleAccountMode, - useExistingRoles: props.useExistingRoles, - }; - diffPromises.push(Accelerator.run(diffProps)); - } - await Promise.all(diffPromises); - } -} - -function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function bootstrapRequired(props: { - accountId: string; - region: string; - partition: string; - managementAccountAccessRole: string; - centralizedBuckets: boolean; - homeRegion: string; - customDeploymentRoleName?: string; - force?: boolean; -}): Promise { - const crossAccountCredentials = await getCrossAccountCredentials( - props.accountId, - props.region, - props.partition, - props.managementAccountAccessRole, - ); - if (props.force) { - return true; - } - if (!props.centralizedBuckets) { - logger.info(`Checking if workload account CDK asset bucket exists in account ${props.accountId}`); - const s3Client = getCrossAccountClient(props.region, crossAccountCredentials, 'S3') as S3Client; - const assetBucketExists = await doesCdkAssetBucketExist(s3Client, props.accountId, props.region); - if (!assetBucketExists) { - return true; - } - } - - if (props.customDeploymentRoleName && props.region === props.homeRegion) { - logger.info( - `Checking account ${props.accountId} in home region ${props.homeRegion} to see if custom deployment role ${props.customDeploymentRoleName} exists`, - ); - const iamClient = getCrossAccountClient(props.region, crossAccountCredentials, 'IAM') as IAMClient; - const deploymentRoleExists = await customDeploymentRoleExists( - iamClient, - props.customDeploymentRoleName, - props.region, - ); - if (!deploymentRoleExists) { - return true; - } - } - const bootstrapVersionName = ' /cdk-bootstrap/accel/version'; - const ssmClient = (await getCrossAccountClient(props.region, crossAccountCredentials, 'SSM')) as SSMClient; - const bootstrapVersionValue = await getSsmParameterValue(bootstrapVersionName, ssmClient); - if (bootstrapVersionValue && Number(bootstrapVersionValue) >= BootstrapVersion) { - logger.info(`Skipping bootstrap for account-region: ${props.accountId}-${props.region}`); - return false; - } - - return true; -} - -async function doesCdkAssetBucketExist(s3Client: S3Client, accountId: string, region: string) { - const commandInput = { - Bucket: `cdk-accel-assets-${accountId}-${region}`, - }; - try { - await throttlingBackOff(() => s3Client.send(new HeadBucketCommand(commandInput))); - return true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.info(`CDK Asset Bucket not found for account ${accountId}, attempting to re-bootstrap`); - return false; - } -} - -async function customDeploymentRoleExists(iamClient: IAMClient, roleName: string, region: string) { - const commandInput: GetRoleCommandInput = { - RoleName: roleName, - }; - try { - await throttlingBackOff(() => iamClient.send(new GetRoleCommand(commandInput))); - return true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.info(`Custom deployment role does not exist in region ${region}, attempting to re-bootstrap`); - return false; - } -} - -async function getSsmParameterValue(parameterName: string, ssmClient: SSMClient) { - const parameterInput: GetParameterCommandInput = { - Name: parameterName, - }; - let parameterOutput: GetParameterCommandOutput | undefined = undefined; - - try { - parameterOutput = await throttlingBackOff(() => ssmClient.send(new GetParameterCommand(parameterInput))); - return parameterOutput.Parameter?.Value ?? ''; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.name === 'ParameterNotFound') { - logger.info(`Value not found for SSM Parameter: ${parameterName}`); - return ''; - } - logger.error(JSON.stringify(e)); - throw new Error(e.message); - } -} - -function getCrossAccountClient( - region: string, - assumeRoleCredential: AssumeRoleCommandOutput, - clientType: string, -): IAMClient | S3Client | SSMClient { - const credentials = { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials?.SessionToken, - }; - let client = undefined; - switch (clientType) { - case 'IAM': - client = new IAMClient({ credentials, region }); - break; - case 'S3': - client = new S3Client({ credentials, region }); - break; - case 'SSM': - client = new SSMClient({ credentials, region }); - break; - default: - if (!client) { - logger.error(`Could not create client for client type ${clientType} in region ${region}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - return client; -} - -function setAssumeRoleName(props: { - managementAccountAccessRole?: string; - stage?: string; - command: string; - customDeploymentRole?: string; -}) { - let assumeRoleName = props.managementAccountAccessRole; - if (!isBeforeBootstrapStage(props.command, props.stage) && props.customDeploymentRole) { - assumeRoleName = props.customDeploymentRole; - } - - return assumeRoleName; -} - -export async function getCentralLogBucketKmsKeyArn( - region: string, - partition: string, - accountId: string, - managementAccountAccessRole: string, - parameterName: string, - orgsEnabled: boolean, -): Promise { - if (!orgsEnabled) { - return uuidv4(); - } - - let ssmClient: SSMClient; - try { - const currentAccountId = await getCurrentAccountId(partition, region); - // if its not the current account then get the credentials from the logArchive account - if (currentAccountId !== accountId) { - const crossAccountCredentials = await getCrossAccountCredentials( - accountId, - region, - partition, - managementAccountAccessRole, - ); - ssmClient = (await getCrossAccountClient(region, crossAccountCredentials, 'SSM')) as SSMClient; - } else { - ssmClient = new SSMClient({ region }); - } - - return await getSsmParameterValue(parameterName, ssmClient); - } catch (error) { - logger.error( - `Error getting central log bucket kms key arn: ${error} using parameter ${parameterName} for account ${accountId} using role ${managementAccountAccessRole}`, - ); - return uuidv4(); - } -} - -/** - * Function to group runOrder in custom stacks - * Example usage: - -const customStackRunOrders: CustomStackRunOrder[] = [ - { - stackName: 'Stack1', - runOrder: 2, - accounts: ['123456789012', '999999999999'], - regions: ['us-east-1', 'eu-west-1'], - }, - { - stackName: 'Stack2', - runOrder: 1, - accounts: ['123456789012'], - regions: ['us-east-1'], - }, - { - stackName: 'Stack3', - runOrder: 2, - accounts: ['999999999999'], - regions: ['eu-west-1'], - }, -]; - -const groupedRunOrders = groupByRunOrder(customStackRunOrders); - -Output: - -[ - { - runOrder: 1, - stacks: [ - { - account: '123456789012', - region: 'us-east-1', - stackNames: ['Stack2-123456789012-us-east-1'] - } - ] - }, - { - runOrder: 2, - stacks: [ - { - account: '123456789012', - region: 'us-east-1', - stackNames: ['Stack1-123456789012-us-east-1'] - }, - { - account: '123456789012', - region: 'eu-west-1', - stackNames: ['Stack1-123456789012-eu-west-1'] - }, - { - account: '999999999999', - region: 'us-east-1', - stackNames: ['Stack1-999999999999-us-east-1'] - }, - { - account: '999999999999', - region: 'eu-west-1', - stackNames: ['Stack1-999999999999-eu-west-1', 'Stack3-999999999999-eu-west-1'] - } - ] - } -] - */ -function groupByRunOrder( - customStackRunOrders: CustomStackRunOrder[], -): { runOrder: number; stacks: { account: string; region: string; stackNames: string[] }[] }[] { - // Sort the array by runOrder - const sortedRunOrders = customStackRunOrders.sort((a, b) => a.runOrder - b.runOrder); - - // Group the objects by runOrder, account, and region - const groupedRunOrders: { [runOrder: number]: { account: string; region: string; stackNames: string[] }[] } = {}; - - sortedRunOrders.forEach(runOrder => { - const { runOrder: order, accounts, regions, stackName } = runOrder; - - if (!groupedRunOrders[order]) { - groupedRunOrders[order] = []; - } - - accounts.forEach(account => { - regions.forEach(region => { - const modifiedStackName = `${stackName}-${account}-${region}`; - const existingStackGroup = groupedRunOrders[order].find( - group => group.account === account && group.region === region, - ); - if (existingStackGroup) { - existingStackGroup.stackNames.push(modifiedStackName); - } else { - groupedRunOrders[order].push({ account, region, stackNames: [modifiedStackName] }); - } - }); - }); - }); - - // Convert the object to an array of objects - return Object.entries(groupedRunOrders).map(([runOrder, stacks]) => ({ - runOrder: parseInt(runOrder), - stacks, - })); -} - -export function getRegionsFromDeploymentTarget( - deploymentTargets: DeploymentTargets, - enabledRegions: string[], -): string[] { - const regions: string[] = []; - regions.push( - ...enabledRegions.filter(region => { - return !deploymentTargets?.excludedRegions?.includes(region as Region); - }), - ); - return regions; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/application-load-balancers.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/application-load-balancers.ts deleted file mode 100644 index 624bf08..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/application-load-balancers.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -//import * as cdk from 'aws-cdk-lib'; -//import { CfnResourceType } from '@aws-accelerator/config'; -import { pascalCase } from 'pascal-case'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { AseaResourceType, CfnResourceType, VpcConfig } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils'; - -const LOAD_BALANCER_RESOURCE_TYPE = 'AWS::ElasticLoadBalancingV2::LoadBalancer'; -const TARGET_GROUP_RESOURCE_TYPE = 'AWS::ElasticLoadBalancingV2::TargetGroup'; -const ASEA_PHASE_NUMBER = '3'; - -/** - * Handles ALBs created by ASEA. - */ -export class ApplicationLoadBalancerResources extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs( - LogLevel.INFO, - `No ${LOAD_BALANCER_RESOURCE_TYPE}s to handle in stack ${props.stackInfo.stackName}`, - ); - return; - } - - const existingTargetGroups = this.scope.importStackResources.getResourcesByType(TARGET_GROUP_RESOURCE_TYPE); - const vpcItemsInScope = this.getVpcsInScope(props.networkConfig.vpcs); - - for (const vpcItem of vpcItemsInScope) { - this.processAlbs(vpcItem); - this.processTargetGroups(existingTargetGroups, vpcItem); - } - } - private processAlbs(vpcItem: VpcConfig) { - if (!vpcItem.loadBalancers) return; - if (!vpcItem.loadBalancers.applicationLoadBalancers) return; - for (const albItem of vpcItem.loadBalancers.applicationLoadBalancers) { - const albResource = this.scope.importStackResources.getResourceByName('Name', albItem.name); - if (!albResource) continue; - // const alb = this.stack.getResource( - // albResource.logicalResourceId, - // ) as cdk.aws_elasticloadbalancingv2.CfnLoadBalancer; - this.scope.addAseaResource(AseaResourceType.APPLICATION_LOAD_BALANCER, albItem.name); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(albItem.name)}LoadBalancer`), - parameterName: this.scope.getSsmPath(SsmResourceType.ALB, [vpcItem.name, albItem.name]), - stringValue: albResource.physicalResourceId!, - }); - } - } - - private processTargetGroups(existingTargetGroups: CfnResourceType[], vpcItem: VpcConfig) { - if (!vpcItem.targetGroups) return; - for (const targetGroupItem of vpcItem.targetGroups) { - const targetGroupResource = existingTargetGroups.find( - existingTargetGroup => existingTargetGroup.resourceMetadata?.['Properties'].Name === targetGroupItem.name, - ); - if (!targetGroupResource) continue; - // const targetGroup = this.stack.getResource( - // targetGroupResource.logicalResourceId, - // ) as cdk.elasticloadbalancingv2.TargetGroup; - this.scope.addAseaResource(AseaResourceType.EC2_TARGET_GROUP, targetGroupItem.name); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/firewall-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/firewall-resources.ts deleted file mode 100644 index 13c68bf..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/firewall-resources.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AseaResourceType, CfnResourceType } from '@aws-accelerator/config/lib/common/types'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils'; - -const EC2_FIREWALL_INSTANCE_TYPE = 'AWS::EC2::Instance'; -const ASEA_PHASE_NUMBER_FIREWALL_INSTANCE = '2'; - -/** - * Handles EC2 Firewall Instances created by ASEA. - * All EC2 Firewall Instances are deployed in Phase-2 - */ -export class FirewallResources extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - const existingFirewallInstances = this.scope.importStackResources.getResourcesByType(EC2_FIREWALL_INSTANCE_TYPE); - if (existingFirewallInstances.length === 0) { - return; - } - this.processFirewallInstances(props, existingFirewallInstances); - } - - private processFirewallInstances(props: AseaResourceProps, existingFirewallInstances: CfnResourceType[]) { - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER_FIREWALL_INSTANCE) { - this.scope.addLogs( - LogLevel.INFO, - `No ${EC2_FIREWALL_INSTANCE_TYPE}s to handle in stack ${props.stackInfo.stackName}`, - ); - return; - } - const firewallConfigInstances = props.customizationsConfig.firewalls?.instances; - - for (const existingFirewallInstance of existingFirewallInstances) { - const firewallInstanceName = this.getAseaFirewallInstanceNameFromTags(existingFirewallInstance); - - const firewallInstanceConfig = firewallConfigInstances?.find( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (firewallConfigInstance: { name: any }) => firewallConfigInstance.name === firewallInstanceName, - ); - - const firewallInstance = this.stack.getResource( - existingFirewallInstance.logicalResourceId, - ) as cdk.aws_ec2.CfnInstance; - - //Leaving as temporary placeholder for deletion handler - firewallInstanceConfig; - firewallInstance; - - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(firewallInstanceName)}`), - parameterName: this.scope.getSsmPath(SsmResourceType.FIREWALL_INSTANCE, [firewallInstanceName]), - stringValue: existingFirewallInstance.physicalResourceId!, - }); - this.scope.addAseaResource(AseaResourceType.FIREWALL_INSTANCE, firewallInstanceName); - } - } - - private getAseaFirewallInstanceNameFromTags(existingFirewallInstance: CfnResourceType, tagName = 'Name') { - const nameTag = existingFirewallInstance.resourceMetadata['Properties'].Tags.find( - (tag: { Key: string; Value: string }) => tag.Key === tagName, - ); - const firewallName = nameTag.Value; - return firewallName; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-groups.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-groups.ts deleted file mode 100644 index 98602dd..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-groups.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { CfnGroup, CfnManagedPolicy } from 'aws-cdk-lib/aws-iam'; -import { GroupConfig, AseaResourceType } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { pascalCase } from 'pascal-case'; -import { AseaResource, AseaResourceProps } from './resource'; - -const RESOURCE_TYPE = 'AWS::IAM::Group'; -const ASEA_PHASE_NUMBER = '1'; - -export interface GroupsProps extends AseaResourceProps { - /** - * Policy constructs defined in configuration - */ - policies: { [key: string]: CfnManagedPolicy }; -} - -/** - * Handles IAM Groups created by ASEA. - * All IAM Groups driven by ASEA configuration are deployed in Phase-1 - */ -export class Groups extends AseaResource { - private readonly policies: { [key: string]: CfnManagedPolicy }; - readonly groups: { [key: string]: CfnGroup } = {}; - constructor(scope: ImportAseaResourcesStack, props: GroupsProps) { - super(scope, props); - this.policies = props.policies; - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE}s to handle in stack ${props.stackInfo.stackName}`); - return; - } - if (props.globalConfig.homeRegion !== this.scope.region) { - return; - } - const groupSets = props.iamConfig.groupSets ?? []; - const groupSetsInScope = groupSets.filter(groupSet => this.scope.isIncluded(groupSet.deploymentTargets)); - const groupsInScope = groupSetsInScope.map(groupSet => groupSet.groups).flat(); - this.addDeletionFlagsForGroups(groupsInScope, RESOURCE_TYPE); - this.updateGroups(groupsInScope); - } - - private getManagedPolicies(groupItem: GroupConfig) { - const managedPolicies: string[] = []; - for (const policyItem of groupItem.policies?.awsManaged ?? []) { - this.scope.addLogs(LogLevel.INFO, `Role - aws managed policy ${policyItem}`); - managedPolicies.push(`arn:${cdk.Aws.PARTITION}:iam::aws:policy/${policyItem}`); - } - for (const policyItem of groupItem.policies?.customerManaged ?? []) { - this.scope.addLogs(LogLevel.INFO, `Role - customer managed policy ${policyItem}`); - const managedPolicy = - this.policies[policyItem]?.ref ?? - this.resourceSsmParameters[this.scope.getSsmPath(SsmResourceType.IAM_POLICY, [policyItem])]; - if (managedPolicy) { - managedPolicies.push(managedPolicy); - } - } - return managedPolicies; - } - private addDeletionFlagsForGroups(groupItems: GroupConfig[], resourceType: string) { - const importGroups = this.scope.importStackResources.getResourcesByType(resourceType); - for (const importGroup of importGroups) { - const groupResource = this.scope.getResource(importGroup.logicalResourceId) as cdk.aws_iam.CfnGroup; - const groupName = groupResource.groupName; - if (!groupName) { - continue; - } - - const groupExistsInConfig = groupItems.find(item => item.name === groupName); - if (!groupExistsInConfig) { - importGroup.isDeleted = true; - this.scope.addDeleteFlagForAseaResource({ - type: resourceType, - identifier: groupName, - logicalId: importGroup.logicalResourceId, - }); - // Add the delete flag to the ssm parameter created with the role. - const ssmResource = this.scope.importStackResources.getSSMParameterByName( - this.scope.getSsmPath(SsmResourceType.IAM_GROUP, [groupName]), - ); - if (ssmResource) { - ssmResource.isDeleted = true; - } - } - } - } - - private updateGroups(groupItems: GroupConfig[]) { - if (groupItems.length === 0) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE}s to handle in stack.`); - return; - } - for (const groupItem of groupItems) { - const existingResource = this.scope.importStackResources.getResourceByProperty('GroupName', groupItem.name); - if (!existingResource) { - continue; - } - this.scope.addLogs(LogLevel.INFO, `Add IAM Group ${groupItem.name}`); - const resource = this.stack.getResource(existingResource.logicalResourceId) as CfnGroup; - resource.groupName = groupItem.name; - resource.managedPolicyArns = this.getManagedPolicies(groupItem); - this.groups[groupItem.name] = resource; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(groupItem.name)}GroupArn`), - parameterName: this.scope.getSsmPath(SsmResourceType.IAM_GROUP, [groupItem.name]), - stringValue: resource.attrArn, - }); - this.scope.addAseaResource(AseaResourceType.IAM_GROUP, groupItem.name); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-roles.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-roles.ts deleted file mode 100644 index a509bbb..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-roles.ts +++ /dev/null @@ -1,246 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { - AccountPrincipal, - CfnInstanceProfile, - CfnManagedPolicy, - Effect, - PolicyDocument, - PolicyStatement, - ServicePrincipal, -} from 'aws-cdk-lib/aws-iam'; -import { RoleConfig, AseaResourceType } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { pascalCase } from 'pascal-case'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; - -const CFN_ROLE_TYPE = 'AWS::IAM::Role'; -const ASEA_PHASE_NUMBER = '1'; - -export interface RolesProps extends AseaResourceProps { - /** - * Policy constructs defined in configuration - */ - policies: { [key: string]: CfnManagedPolicy }; -} -/** - * Handles IAM Roles created by ASEA. - * All IAM Roles driven by ASEA configuration are deployed in Phase-1 - */ -export class Roles extends AseaResource { - readonly props: RolesProps; - constructor(scope: ImportAseaResourcesStack, props: RolesProps) { - super(scope, props); - this.props = props; - const prefix = this.props.globalConfig.externalLandingZoneResources?.acceleratorPrefix ?? 'ASEA'; - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs(LogLevel.INFO, `No ${CFN_ROLE_TYPE}s to handle in stack ${props.stackInfo.stackName}`); - return; - } - if (props.globalConfig.homeRegion !== this.scope.region) { - return; - } - const roleSetItemsInScope = props.iamConfig.roleSets.filter(roleSet => - this.scope.isIncluded(roleSet.deploymentTargets), - ); - const rolesInScope = roleSetItemsInScope.map(roleSetItem => roleSetItem.roles).flat(); - this.addDeletionFlagForRoles(rolesInScope, CFN_ROLE_TYPE, prefix); - this.updateRoles(rolesInScope); - } - - private getManagedPolicies(roleItem: RoleConfig) { - const managedPolicies: string[] = []; - for (const policyItem of roleItem.policies?.awsManaged ?? []) { - this.scope.addLogs(LogLevel.INFO, `Role - aws managed policy ${policyItem}`); - managedPolicies.push(`arn:${cdk.Aws.PARTITION}:iam::aws:policy/${policyItem}`); - } - for (const policyItem of roleItem.policies?.customerManaged ?? []) { - this.scope.addLogs(LogLevel.INFO, `Role - customer managed policy ${policyItem}`); - const managedPolicy = - this.props.policies[policyItem]?.ref ?? - this.resourceSsmParameters[this.scope.getSsmPath(SsmResourceType.IAM_POLICY, [policyItem])]; - if (managedPolicy) { - managedPolicies.push(managedPolicy); - } - } - return managedPolicies; - } - - private getAssumeRolePolicy(roleItem: RoleConfig) { - const statements: PolicyStatement[] = []; - for (const assumedByItem of roleItem.assumedBy ?? []) { - if (assumedByItem.type === 'service') { - statements.push( - new PolicyStatement({ - actions: ['sts:AssumeRole'], - effect: Effect.ALLOW, - principals: [new ServicePrincipal(assumedByItem.principal)], - }), - ); - } - if (assumedByItem.type === 'account') { - const partition = this.props.partition; - const accountIdRegex = /^\d{12}$/; - const accountArnRegex = new RegExp('^arn:' + partition + ':iam::(\\d{12}):root$'); - if (accountIdRegex.test(assumedByItem.principal)) { - statements.push( - new PolicyStatement({ - actions: ['sts:AssumeRole'], - effect: Effect.ALLOW, - principals: [new AccountPrincipal(assumedByItem.principal)], - }), - ); - } else if (accountArnRegex.test(assumedByItem.principal)) { - const accountId = accountArnRegex.exec(assumedByItem.principal); - statements.push( - new PolicyStatement({ - actions: ['sts:AssumeRole'], - effect: Effect.ALLOW, - principals: [new AccountPrincipal(accountId![1])], - }), - ); - } else { - statements.push( - new PolicyStatement({ - actions: ['sts:AssumeRole'], - effect: Effect.ALLOW, - principals: [ - new cdk.aws_iam.AccountPrincipal(this.props.accountsConfig.getAccountId(assumedByItem.principal)), - ], - }), - ); - } - } - } - return new PolicyDocument({ - statements, - }); - } - - private setResourceBoundaryPolicy(roleItem: RoleConfig, resource: cdk.aws_iam.CfnRole) { - if (roleItem.boundaryPolicy) { - const boundaryPolicy = - this.props.policies[roleItem.boundaryPolicy]?.ref ?? - this.resourceSsmParameters[this.scope.getSsmPath(SsmResourceType.IAM_POLICY, [roleItem.boundaryPolicy])]; - if (boundaryPolicy) { - resource.permissionsBoundary = boundaryPolicy; - } - } else if (resource.permissionsBoundary) { - resource.permissionsBoundary = undefined; - } - } - - private setInstanceProfile(roleItem: RoleConfig, resource: cdk.aws_iam.CfnRole) { - const existingInstanceProfile = this.scope.importStackResources.getResourceByName( - 'InstanceProfileName', - `${roleItem.name}-ip`, - ); - if (existingInstanceProfile && !roleItem.instanceProfile) { - this.scope.node.tryRemoveChild(existingInstanceProfile.logicalResourceId); - return; - } - if (!roleItem.instanceProfile) { - return; - } - let instanceProfile: CfnInstanceProfile; - if (!existingInstanceProfile) { - // Creating instance profile since, Role is managed by ASEA and "instanceProfile" flag is changed to true - instanceProfile = new CfnInstanceProfile(this.scope, `${pascalCase(roleItem.name)}InstanceProfile`, { - roles: [resource.ref], - instanceProfileName: `${roleItem.name}-ip`, - }); - } else { - instanceProfile = this.scope.getResource(existingInstanceProfile.logicalResourceId) as CfnInstanceProfile; - instanceProfile.instanceProfileName = `${roleItem.name}-ip`; - } - } - - // add the delete flag to roles that are no longer in the config file - private addDeletionFlagForRoles(roleItems: RoleConfig[], resourceType: string, acceleratorPrefix: string) { - const rolesPrefixesToExclude = [ - `${acceleratorPrefix}-VPC-FlowLog`, - `${acceleratorPrefix}-VPC-PCX`, - `${acceleratorPrefix}-Reports`, - ]; - const importRoles = this.scope.importStackResources.getResourcesByType(resourceType); - for (const importRole of importRoles) { - const roleResource = this.scope.getResource(importRole.logicalResourceId) as cdk.aws_iam.CfnRole; - const roleName = roleResource.roleName; - if (!roleName) { - continue; - } - const excludedRole = rolesPrefixesToExclude.filter(prefix => { - return roleName!.startsWith(prefix); - }); - if (excludedRole.length > 0) { - continue; - } - const roleExistsInConfig = roleItems.find(item => item.name === roleName); - if (!roleExistsInConfig) { - importRole.isDeleted = true; - this.scope.addDeleteFlagForAseaResource({ - type: resourceType, - identifier: roleName, - logicalId: importRole.logicalResourceId, - }); - // Add the delete flag to the ssm parameter created with the role. - const ssmResource = this.scope.importStackResources.getSSMParameterByName( - this.scope.getSsmPath(SsmResourceType.IAM_ROLE, [roleName]), - ); - if (ssmResource) { - ssmResource.isDeleted = true; - } - - const existingInstanceProfile = this.scope.importStackResources.getResourceByName( - 'InstanceProfileName', - `${roleName}-ip`, - ); - if (existingInstanceProfile) { - existingInstanceProfile.isDeleted = true; - } - } - } - } - - private updateRoles(roleItems: RoleConfig[]) { - if (roleItems.length === 0) { - this.scope.addLogs(LogLevel.INFO, `No ${CFN_ROLE_TYPE}s to handle in stack.`); - return; - } - for (const roleItem of roleItems) { - this.scope.addLogs(LogLevel.INFO, `Add role ${roleItem.name}`); - const role = this.scope.importStackResources.getResourceByName('RoleName', roleItem.name); - if (!role) { - continue; - } - const resource = this.scope.getResource(role.logicalResourceId) as cdk.aws_iam.CfnRole; - if (!resource) { - continue; - } - resource.managedPolicyArns = this.getManagedPolicies(roleItem); - resource.assumeRolePolicyDocument = this.getAssumeRolePolicy(roleItem); - this.setResourceBoundaryPolicy(roleItem, resource); - this.setInstanceProfile(roleItem, resource); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(roleItem.name)}RoleArn`), - parameterName: this.scope.getSsmPath(SsmResourceType.IAM_ROLE, [roleItem.name]), - stringValue: resource.attrArn, - }); - this.scope.addAseaResource(AseaResourceType.IAM_ROLE, roleItem.name); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-users.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-users.ts deleted file mode 100644 index a724da5..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/iam-users.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CfnGroup, CfnManagedPolicy, CfnUser } from 'aws-cdk-lib/aws-iam'; -import { AseaResourceType, UserConfig } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { pascalCase } from 'pascal-case'; -import { AseaResource, AseaResourceProps } from './resource'; - -const RESOURCE_TYPE = 'AWS::IAM::User'; -const ASEA_PHASE_NUMBER = '1'; - -export interface UsersProps extends AseaResourceProps { - /** - * Group constructs defined in configuration - */ - groups: { [key: string]: CfnGroup }; - - /** - * Policy constructs defined in configuration - */ - policies: { [key: string]: CfnManagedPolicy }; -} -/** - * Handles IAM Roles created by ASEA. - * All IAM Roles driven by ASEA configuration are deployed in Phase-1 - */ -export class Users extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: UsersProps) { - super(scope, props); - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE}s to handle in stack ${props.stackInfo.stackName}`); - return; - } - - if (props.globalConfig.homeRegion !== this.scope.region) { - return; - } - const userSetsInScope = props.iamConfig.userSets.filter(userSet => - this.scope.isIncluded(userSet.deploymentTargets), - ); - const userItemsInScope = userSetsInScope.map(userSet => userSet.users).flat(); - this.addDeletionFlagsForUsers(userItemsInScope, RESOURCE_TYPE); - this.updateUsers(userItemsInScope, props); - } - - updateUsers(userItems: UserConfig[], props: UsersProps) { - if (userItems.length === 0) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE}s to handle in stack.`); - return; - } - for (const userItem of userItems) { - this.scope.addLogs(LogLevel.INFO, `Add User ${userItem.username}`); - const importUser = this.scope.importStackResources.getResourceByProperty('UserName', userItem.username); - if (!importUser) { - continue; - } - const user = this.stack.getResource(importUser.logicalResourceId) as CfnUser; - const group = - props.groups[userItem.group]?.ref ?? - this.resourceSsmParameters[this.scope.getSsmPath(SsmResourceType.IAM_GROUP, [userItem.group])]; - if (group) { - user.groups = [group]; - } - if (userItem.boundaryPolicy) { - const boundaryPolicy = - props.policies[userItem.boundaryPolicy]?.ref ?? - this.resourceSsmParameters[this.scope.getSsmPath(SsmResourceType.IAM_POLICY, [userItem.boundaryPolicy])]; - if (boundaryPolicy) { - user.permissionsBoundary = boundaryPolicy; - } - } else if (user.permissionsBoundary) { - user.permissionsBoundary = undefined; - } - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(userItem.username)}UserArn`), - parameterName: this.scope.getSsmPath(SsmResourceType.IAM_USER, [userItem.username]), - stringValue: user.attrArn, - }); - this.scope.addAseaResource(AseaResourceType.IAM_USER, userItem.username); - } - } - addDeletionFlagsForUsers(userItems: UserConfig[], resourceType: string) { - const importUsers = this.scope.importStackResources.getResourcesByType(resourceType); - for (const importUser of importUsers) { - const userResource = this.scope.getResource(importUser.logicalResourceId) as CfnUser; - const userName = userResource.userName; - if (!userName) { - continue; - } - - const userExistsInConfig = userItems.find(item => item.username === userName); - if (!userExistsInConfig) { - importUser.isDeleted = true; - this.scope.addDeleteFlagForAseaResource({ - type: resourceType, - identifier: userName, - logicalId: importUser.logicalResourceId, - }); - // Add the delete flag to the ssm parameter created with the user. - const ssmResource = this.scope.importStackResources.getSSMParameterByName( - this.scope.getSsmPath(SsmResourceType.IAM_USER, [userName]), - ); - if (ssmResource) { - ssmResource.isDeleted = true; - } - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-ad-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-ad-resources.ts deleted file mode 100644 index 49aab3a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-ad-resources.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AseaResourceType, CfnResourceType } from '@aws-accelerator/config/lib/common/types'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils'; - -const MANAGED_AD_RESOURCE_TYPE = 'AWS::DirectoryService::MicrosoftAD'; -const ASEA_PHASE_NUMBER_MANAGED_AD = '2'; - -/** - * Handles Managed Active Directories created by ASEA. - * All Managed Active Directories are deployed in Phase-2 - */ -export class ManagedAdResources extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - const existingManagedAds = this.scope.importStackResources.getResourcesByType(MANAGED_AD_RESOURCE_TYPE); - this.updateManagedAD(props, existingManagedAds); - } - - private updateManagedAD(props: AseaResourceProps, existingManagedAds: CfnResourceType[]) { - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER_MANAGED_AD) { - this.scope.addLogs( - LogLevel.INFO, - `No ${MANAGED_AD_RESOURCE_TYPE}s to handle in stack ${props.stackInfo.stackName}`, - ); - return; - } - if (existingManagedAds.length === 0) { - return; - } - - const managedAdConfigs = props.iamConfig.managedActiveDirectories; - - //If there are no MAD Config objects or ASEA MADs deployed, return. - if (!existingManagedAds || !managedAdConfigs) { - return; - } - - for (const managedAdConfig of managedAdConfigs) { - const managedAdConfigName = managedAdConfig.name; - const matchedManagedAd = existingManagedAds.find( - existingManagedAd => existingManagedAd.resourceMetadata['Properties'].Name === managedAdConfigName, - ); - if (!matchedManagedAd || !matchedManagedAd.physicalResourceId) { - continue; - } - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(managedAdConfigName)}`), - parameterName: this.scope.getSsmPath(SsmResourceType.MANAGED_AD, [managedAdConfigName]), - stringValue: matchedManagedAd.physicalResourceId, - }); - this.scope.addAseaResource(AseaResourceType.MANAGED_AD, managedAdConfigName); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-policies.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-policies.ts deleted file mode 100644 index 82249bd..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/managed-policies.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import path from 'path'; -import * as cdk from 'aws-cdk-lib'; -import { CfnManagedPolicy } from 'aws-cdk-lib/aws-iam'; -import { AseaResourceType, CfnResourceType, PolicyConfig } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { pascalCase } from 'pascal-case'; -import { AseaResource, AseaResourceProps } from './resource'; - -const RESOURCE_TYPE = 'AWS::IAM::ManagedPolicy'; -const ASEA_PHASE_NUMBER = '1'; - -/** - * Handles ManagedPolicies created by ASEA. - * All Managed Policies driven by ASEA configuration are deployed in Phase-1 - */ -export class ManagedPolicies extends AseaResource { - readonly policies: { [key: string]: CfnManagedPolicy } = {}; - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - if (this.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE}s to handle in stack ${props.stackInfo.stackName}`); - return; - } - - if (props.globalConfig.homeRegion !== this.scope.region) { - return; - } - - const policySetsInScope = props.iamConfig.policySets.filter(policySet => - this.scope.isIncluded(policySet.deploymentTargets), - ); - const policiesInScope = policySetsInScope.map(policySetItem => policySetItem.policies).flat(); - this.addDeletionFlagsForPolicies(policiesInScope, RESOURCE_TYPE); - this.updatePolicies(policiesInScope, props.configDirPath); - } - - updatePolicies(policyItems: PolicyConfig[], configPath: string) { - if (policyItems.length === 0) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE}s to handle in stack`); - return; - } - - for (const policyItem of policyItems) { - const existingPolicy = this.scope.importStackResources.getResourceByPropertyByPartialMatch( - 'ManagedPolicyName', - policyItem.name, - ); - if (!existingPolicy) { - continue; - } - this.scope.addLogs(LogLevel.INFO, `Add customer managed policy ${policyItem.name}`); - let resource; - try { - resource = this.stack.getResource(existingPolicy.logicalResourceId) as cdk.aws_iam.CfnManagedPolicy; - } catch (e) { - this.scope.addLogs( - LogLevel.WARN, - `Could not find resource ${existingPolicy.logicalResourceId} in stack. Skipping update managed policy.`, - ); - continue; - } - - // Read in the policy document which should be properly formatted json - const policyDocument = JSON.parse( - this.scope.generatePolicyReplacements(path.join(configPath, policyItem.policy), false), - ); - const existingPolicyName = existingPolicy['resourceMetadata']['Properties']['ManagedPolicyName']; - resource.managedPolicyName = existingPolicy['resourceMetadata']['Properties']['ManagedPolicyName']; - resource.policyDocument = policyDocument; - this.policies[existingPolicyName] = resource; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(policyItem.name)}PolicyArn`), - parameterName: this.scope.getSsmPath(SsmResourceType.IAM_POLICY, [policyItem.name]), - stringValue: resource.ref, - }); - this.scope.addAseaResource(AseaResourceType.IAM_POLICY, policyItem.name); - } - } - private addDeletionFlagsForPolicies(policyItems: PolicyConfig[], resourceType: string) { - const importPolicies = this.scope.importStackResources.getResourcesByType(resourceType); - for (const importPolicy of importPolicies) { - const policyResource = this.getPolicyResource(importPolicy); - if (!policyResource) { - continue; - } - const policyName = policyResource.managedPolicyName; - if (!policyName) { - continue; - } - const policyExistsInConfig = policyItems.find(item => policyName.includes(item.name)); - if (!policyExistsInConfig) { - this.addDeletionFlagForPolicy({ - type: resourceType, - identifier: policyName, - logicalId: importPolicy.logicalResourceId, - }); - } - } - } - getPolicyResource(importPolicy: CfnResourceType) { - let policyResource; - try { - policyResource = this.scope.getResource(importPolicy.logicalResourceId) as cdk.aws_iam.CfnManagedPolicy; - } catch (e) { - this.scope.addLogs(LogLevel.WARN, `Could not find resource ${importPolicy.logicalResourceId}`); - const policyName = importPolicy.resourceMetadata['Properties']['ManagedPolicyName']; - this.addDeletionFlagForPolicy({ - type: importPolicy.resourceType, - identifier: policyName, - logicalId: importPolicy.logicalResourceId, - }); - } - return policyResource; - } - addDeletionFlagForPolicy(props: { type: string; identifier: string; logicalId: string }) { - this.scope.addDeleteFlagForAseaResource({ - type: props.type, - identifier: props.identifier, - logicalId: props.logicalId, - }); - const ssmResource = this.scope.importStackResources.getSSMParameterByName( - this.scope.getSsmPath(SsmResourceType.IAM_POLICY, [props.identifier]), - ); - if (ssmResource) { - this.scope.addDeleteFlagForAseaResource({ - logicalId: ssmResource.logicalResourceId, - }); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/resource.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/resource.ts deleted file mode 100644 index 15611d7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/resource.ts +++ /dev/null @@ -1,121 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { - ASEAMapping, - ASEAMappings, - AccountsConfig, - CfnResourceType, - CustomizationsConfig, - GlobalConfig, - IamConfig, - NetworkConfig, - OrganizationConfig, - SecurityConfig, - VpcConfig, -} from '@aws-accelerator/config'; -import { ImportAseaResourcesStack } from '../stacks/import-asea-resources-stack'; -import { AcceleratorStage } from '../accelerator-stage'; -import path from 'path'; -import { CfnInclude } from 'aws-cdk-lib/cloudformation-include'; - -export interface AseaResourceProps { - readonly stackInfo: ASEAMapping; - readonly configDirPath: string; - readonly accountsConfig: AccountsConfig; - readonly globalConfig: GlobalConfig; - readonly iamConfig: IamConfig; - readonly networkConfig: NetworkConfig; - readonly organizationConfig: OrganizationConfig; - readonly securityConfig: SecurityConfig; - readonly customizationsConfig: CustomizationsConfig; - readonly partition: string; - readonly mapping: ASEAMappings; - readonly stage: AcceleratorStage.IMPORT_ASEA_RESOURCES | AcceleratorStage.POST_IMPORT_ASEA_RESOURCES; -} -export class AseaResource { - readonly scope: ImportAseaResourcesStack; - readonly stack: cdk.cloudformation_include.CfnInclude; - readonly resourceSsmParameters: { [key: string]: string } = {}; - readonly stackInfo: ASEAMapping; - readonly props: AseaResourceProps; - protected ssmParameters: { - logicalId: string; - parameterName: string; - stringValue: string; - scope: CfnInclude | ImportAseaResourcesStack; - }[]; - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - this.scope = scope; - this.stack = scope.includedStack; - this.stackInfo = props.stackInfo; - this.props = props; - this.stackInfo.cfnResources = this.loadResourcesFromFile(this.stackInfo); - this.resourceSsmParameters = - props.globalConfig.externalLandingZoneResources?.resourceParameters[ - `${this.scope.account}-${this.scope.region}` - ] ?? {}; - this.ssmParameters = []; - } - - findResourceByName(cfnResources: CfnResourceType[], propertyName: string, propertyValue: string) { - return cfnResources.find(cfnResource => cfnResource.resourceMetadata['Properties'][propertyName] === propertyValue); - } - - filterResourcesByRef(cfnResources: CfnResourceType[], propertyName: string, logicalId: string) { - return cfnResources.filter( - cfnResource => cfnResource.resourceMetadata['Properties'][propertyName].Ref === logicalId, - ); - } - - filterResourcesByType(cfnResources: CfnResourceType[], resourceType: string) { - return cfnResources.filter(cfnResource => cfnResource.resourceType === resourceType); - } - - findResourceByTag(cfnResources: CfnResourceType[], value: string, name = 'Name') { - return cfnResources.find(cfnResource => - cfnResource.resourceMetadata['Properties'].Tags.find( - (tag: { Key: string; Value: string }) => tag.Key === name && tag.Value === value, - ), - ); - } - - findResourceByTypeAndTag(cfnResources: CfnResourceType[], resourceType: string, tagValue: string, tagName = 'Name') { - return cfnResources.find( - cfnResource => - cfnResource.resourceType === resourceType && - cfnResource.resourceMetadata['Properties'].Tags.find( - (tag: { Key: string; Value: string }) => tag.Key === tagName && tag.Value === tagValue, - ), - ); - } - - loadResourcesFromFile(stackInfo: ASEAMapping): CfnResourceType[] { - let cfnResources = stackInfo.cfnResources; - if (!stackInfo.cfnResources || stackInfo.cfnResources.length === 0) { - cfnResources = this.props.globalConfig.loadJsonFromDisk( - path.join('asea-assets', stackInfo.resourcePath), - ) as CfnResourceType[]; - } - return cfnResources; - } - - getVpcsInScope(vpcItems: VpcConfig[]) { - return vpcItems.filter(vpcItem => { - const accountId = this.props.accountsConfig.getAccountId(vpcItem.account); - return accountId === cdk.Stack.of(this.scope).account && vpcItem.region === cdk.Stack.of(this.scope).region; - }); - } - - addSsmParameter(props: { - logicalId: string; - parameterName: string; - stringValue: string; - scope: CfnInclude | ImportAseaResourcesStack; - }) { - this.ssmParameters.push({ - logicalId: props.logicalId, - parameterName: props.parameterName, - stringValue: props.stringValue, - scope: props.scope, - }); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging-association.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging-association.ts deleted file mode 100644 index f1823ee..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging-association.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { AseaResourceType, VpcConfig } from '@aws-accelerator/config'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils'; -import * as cdk from 'aws-cdk-lib'; - -const ASEA_PHASE_NUMBER = '1'; -const enum RESOURCE_TYPE { - ROUTE_53_QUERY_LOGGING_ASSOCIATION = 'AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation', -} -export class Route53ResolverQueryLoggingAssociation extends AseaResource { - readonly props: AseaResourceProps; - // private ssmParameters: { logicalId: string; parameterName: string; stringValue: string }[]; - constructor(scope: ImportAseaResourcesStack, route53ResolverQueryLoggingAssociationProps: AseaResourceProps) { - super(scope, route53ResolverQueryLoggingAssociationProps); - this.props = route53ResolverQueryLoggingAssociationProps; - if (route53ResolverQueryLoggingAssociationProps.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.ROUTE_53_QUERY_LOGGING_ASSOCIATION}s to handle in stack ${route53ResolverQueryLoggingAssociationProps.stackInfo.stackName}`, - ); - return; - } - - const vpcsInScope = this.getVpcsInScope(this.props.networkConfig.vpcs); - // The config converter does not handle RQL yet - // this.addRQLDeletionFlags(vpcsInScope, RESOURCE_TYPE.ROUTE_53_QUERY_LOGGING_ASSOCIATION); - this.updateRQLAssociation(vpcsInScope); - } - - updateRQLAssociation(vpcItems: VpcConfig[]) { - if (vpcItems.length === 0) { - return; - } - for (const vpcItem of vpcItems) { - if (!vpcItem.vpcRoute53Resolver?.queryLogs) { - continue; - } - const associationLogicalId = `RqlAssoc${vpcItem.name}`.replace('_vpc', ''); - const importResource = this.scope.importStackResources.getResourceByLogicalId(associationLogicalId); - const cfnResolverQueryLoggingAssociation = this.scope.getResource( - associationLogicalId, - ) as cdk.aws_route53resolver.CfnResolverQueryLoggingConfigAssociation; - if (importResource?.isDeleted) { - continue; - } - if (!cfnResolverQueryLoggingAssociation || !cfnResolverQueryLoggingAssociation.resourceId) { - continue; - } - this.scope.addLogs( - LogLevel.INFO, - `Updating: Adding Route53 Query Logging Association: ${cfnResolverQueryLoggingAssociation}`, - ); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParamQueryLogAssociation${vpcItem.name}`), - parameterName: this.scope.getSsmPath(SsmResourceType.QUERY_LOGS_ASSOCIATION, [vpcItem.name]), - stringValue: cfnResolverQueryLoggingAssociation.resourceId, - }); - this.scope.addAseaResource(AseaResourceType.ROUTE_53_QUERY_LOGGING_ASSOCIATION, vpcItem.name); - } - } - - // Can't add deletion flags until config converter includes vpcItem.vpcRoute53Resolver?.queryLogs - - // addRQLDeletionFlags(vpcItems: VpcConfig[], resourceType: string) { - // const importItems = this.scope.importStackResources.getResourcesByType(resourceType); - // for (const importItem of importItems) { - // const vpcName = `${importItem.logicalResourceId.replace('RqlAssoc', '')}_vpc`; - // const resourceExistsInConfig = vpcItems.find(item => item.name === vpcName && item.vpcRoute53Resolver?.queryLogs); - // const ssmPhysicalID = this.scope.getSsmPath(SsmResourceType.QUERY_LOGS_ASSOCIATION, [vpcName]); - // if (!resourceExistsInConfig) { - // importItem.isDeleted = true; - // const ssmResource = this.scope.importStackResources.getSSMParameterByName(ssmPhysicalID); - // if (ssmResource) { - // ssmResource.isDeleted = true; - // } - // } - // } - // } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging.ts deleted file mode 100644 index fa6932d..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-query-logging.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { AseaResourceType } from '@aws-accelerator/config'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils'; -import * as cdk from 'aws-cdk-lib'; -const ASEA_PHASE_NUMBER = '1'; -const enum RESOURCE_TYPE { - ROUTE_53_QUERY_LOGGING = 'AWS::Route53Resolver::ResolverQueryLoggingConfig', - ROUTE_53_QUERY_LOGGING_ASSOCIATION = 'AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation', - VPC = 'AWS::EC2::VPC', -} - -// R53 first part query logging -// another class to do the query logging association (another file); import here to do association if exists -export class Route53ResolverQueryLogging extends AseaResource { - readonly props: AseaResourceProps; - constructor(scope: ImportAseaResourcesStack, route53ResolverQueryLoggingProps: AseaResourceProps) { - super(scope, route53ResolverQueryLoggingProps); - this.props = route53ResolverQueryLoggingProps; - this.scope.acceleratorPrefix; - if (route53ResolverQueryLoggingProps.stackInfo.phase !== ASEA_PHASE_NUMBER) { - // Skip Non-Phase 1 resource stacks - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.ROUTE_53_QUERY_LOGGING}s to handle in stack ${route53ResolverQueryLoggingProps.stackInfo.stackName}`, - ); - return; - } - const existingRoute53QueryLoggingResource = this.scope.importStackResources.getResourcesByType( - RESOURCE_TYPE.ROUTE_53_QUERY_LOGGING, - ); - if (existingRoute53QueryLoggingResource.length === 0) { - //Return if no existing Query Logs in Resource Mapping - return; - } - const vpcsInScope = this.getVpcsInScope(this.props.networkConfig.vpcs); - for (const vpcItem of vpcsInScope) { - const queryLogs = vpcItem.vpcRoute53Resolver?.queryLogs; - if (!queryLogs) { - continue; - } - const vpcId = this.getVPCId(vpcItem.name); - if (!vpcId) { - continue; - } - const resolverQueryLogCfn = this.scope.importStackResources.getResourceByName( - 'Name', - `${this.props.globalConfig.externalLandingZoneResources?.acceleratorPrefix}-rql-${vpcItem.name}`.replace( - '_vpc', - '', - ), - ); - if (!resolverQueryLogCfn) { - continue; - } - - const resolverQueryLog = this.scope.getResource( - resolverQueryLogCfn?.logicalResourceId, - ) as cdk.aws_route53resolver.CfnResolverQueryLoggingConfig; - if (resolverQueryLog?.name) { - this.scope.addLogs(LogLevel.INFO, `Updating: Adding Route53 Query Logging: ${resolverQueryLog.name}`); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${resolverQueryLog.name}QueryLog`), - parameterName: this.scope.getSsmPath(SsmResourceType.QUERY_LOGS, [resolverQueryLog.name]), - stringValue: resolverQueryLog.name, - }); - this.scope.addAseaResource(AseaResourceType.ROUTE_53_QUERY_LOGGING, resolverQueryLog.name); - } else { - this.scope.addLogs( - LogLevel.WARN, - `Route 53 Query Logging for VPC ${vpcItem.name} exists in Mapping but not found in resources`, - ); - return; - } - } - } - private getVPCId(vpcName: string) { - if (!this.scope.nestedStackResources) { - return; - } - for (const [, nestedStackResources] of Object.entries(this.scope.nestedStackResources)) { - const vpc = nestedStackResources.getResourceByTypeAndTag(RESOURCE_TYPE.VPC, vpcName); - if (vpc) { - return vpc.physicalResourceId; - } - } - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-resolver-endpoint.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-resolver-endpoint.ts deleted file mode 100644 index fe839aa..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/route-53-resolver-endpoint.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { AseaResourceType } from '@aws-accelerator/config'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils'; - -const ASEA_PHASE_NUMBER = '3'; -const enum RESOURCE_TYPE { - ROUTE_53_RESOLVER_ENDPOINT = 'AWS::Route53Resolver::ResolverEndpoint', - ROUTE_53_QUERY_LOGGING_ASSOCIATION = 'AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation', - VPC = 'AWS::EC2::VPC', -} -export class Route53ResolverEndpoint extends AseaResource { - readonly props: AseaResourceProps; - // private ssmParameters: { logicalId: string; parameterName: string; stringValue: string }[]; - constructor(scope: ImportAseaResourcesStack, route53ResolverEndpointProps: AseaResourceProps) { - super(scope, route53ResolverEndpointProps); - this.props = route53ResolverEndpointProps; - // this.ssmParameters = []; - if (route53ResolverEndpointProps.stackInfo.phase !== ASEA_PHASE_NUMBER) { - // Skip Non-Phase 1 resource stacks - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.ROUTE_53_RESOLVER_ENDPOINT}s to handle in stack ${route53ResolverEndpointProps.stackInfo.stackName}`, - ); - return; - } - const existingRoute53ResolverEndpointResources = this.scope.importStackResources.getResourcesByType( - RESOURCE_TYPE.ROUTE_53_RESOLVER_ENDPOINT, - ); - - if (existingRoute53ResolverEndpointResources.length === 0) { - //Return if no existing Query Logs in Resource Mapping - return; - } - for (const vpcItem of this.props.networkConfig.vpcs) { - for (const endpointItem of vpcItem.vpcRoute53Resolver?.endpoints ?? []) { - if (endpointItem.type === 'OUTBOUND') { - // Set Resolver Endpoint - const resolverEndpointCfn = this.scope.importStackResources.getResourceByName( - 'Name', - `${vpcItem.name} Outbound Endpoint`.replace('_vpc', ''), - ); - if (resolverEndpointCfn) { - // Need to pull the logical ID - const resolverEndpoint = this.scope.getResource( - resolverEndpointCfn?.logicalResourceId, - ) as cdk.aws_route53resolver.CfnResolverEndpoint; - // Handle change to type - resolverEndpoint.direction = endpointItem.type; - if (resolverEndpoint.name) { - this.scope.addLogs(LogLevel.INFO, `Update: Adding Outbound Resolver Endpoint ${resolverEndpoint.name}`); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${vpcItem.name}${resolverEndpoint.name}EndpointName`), - parameterName: this.scope.getSsmPath(SsmResourceType.RESOLVER_ENDPOINT, [ - resolverEndpoint.name.replace(/ /g, ''), - ]), - stringValue: resolverEndpoint.ref, - }); - this.scope.addAseaResource( - AseaResourceType.ROUTE_53_RESOLVER_ENDPOINT, - `${resolverEndpoint.name.replace(/ /g, '')}`, - ); - } - } - } - if (endpointItem.type === 'INBOUND') { - // Set Resolver Endpoint - const resolverEndpointCfn = this.scope.importStackResources.getResourceByName( - 'Name', - `${vpcItem.name} Inbound Endpoint`.replace('_vpc', ''), - ); - if (resolverEndpointCfn) { - // Need to pull the logical ID - const resolverEndpoint = this.scope.getResource( - resolverEndpointCfn?.logicalResourceId, - ) as cdk.aws_route53resolver.CfnResolverEndpoint; - // Handle change to type - resolverEndpoint.direction = endpointItem.type; - if (resolverEndpoint.name) { - this.scope.addLogs(LogLevel.INFO, `Update: Adding Inbound Resolver Endpoint ${resolverEndpoint.name}`); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${vpcItem.name}${resolverEndpoint.name}EndpointName`), - parameterName: this.scope.getSsmPath(SsmResourceType.RESOLVER_ENDPOINT, [ - resolverEndpoint.name.replace(/ /g, ''), - ]), - stringValue: resolverEndpoint.ref, - }); - this.scope.addAseaResource( - AseaResourceType.ROUTE_53_RESOLVER_ENDPOINT, - `${resolverEndpoint.name.replace(/ /g, '')}`, - ); - } - } - } - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/shared-security-groups.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/shared-security-groups.ts deleted file mode 100644 index 8bf0293..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/shared-security-groups.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AseaResourceType, VpcConfig, VpcTemplatesConfig } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { AseaResource, AseaResourceProps } from './resource'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { pascalCase } from 'pascal-case'; - -const enum RESOURCE_TYPE { - SECURITY_GROUP = 'AWS::EC2::SecurityGroup', -} - -const ASEA_PHASE_NUMBER = '2'; - -export class SharedSecurityGroups extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.SECURITY_GROUP}s to handle in stack ${props.stackInfo.stackName}`, - ); - return; - } - const vpcsInScope = this.scope.sharedVpcs; - this.updateSecurityGroups(vpcsInScope); - } - private updateSecurityGroups(vpcItems: (VpcConfig | VpcTemplatesConfig)[]) { - if (vpcItems.length === 0) { - return; - } - for (const vpcItem of vpcItems) { - const vpcStackInfo = this.getSecurityGroupResourceByVpc(vpcItem.name); - if (!vpcStackInfo) { - continue; - } - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - const securityGroupResource = vpcStackInfo.nestedStackResources.getResourceByTag(securityGroupItem.name); - if (!securityGroupResource) { - continue; - } - const securityGroup = vpcStackInfo.nestedStack.includedTemplate.getResource( - securityGroupResource.logicalResourceId, - ); - this.scope.addSsmParameter({ - logicalId: pascalCase( - `SsmParam${pascalCase(vpcItem.name) + pascalCase(securityGroupItem.name)}SecurityGroup`, - ), - parameterName: this.scope.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, securityGroupItem.name]), - stringValue: securityGroup.ref, - scope: vpcStackInfo.stackKey, - }); - this.scope.addAseaResource(AseaResourceType.EC2_SECURITY_GROUP, `${vpcItem.name}/${securityGroupItem.name}`); - } - } - } - private getSecurityGroupResourceByVpc(vpcName: string) { - for (const [, nestedStackResources] of Object.entries(this.scope.nestedStackResources ?? {})) { - const stackKey = nestedStackResources.getStackKey(); - const nestedStack = this.scope.nestedStacks[stackKey]; - const securityGroups = nestedStackResources.getResourcesByType(RESOURCE_TYPE.SECURITY_GROUP); - if (!securityGroups) continue; - if (vpcName.endsWith('_vpc')) vpcName = vpcName.split('_vpc')[0]; - if (nestedStack.stack['_stackName'].includes(`SecurityGroups${vpcName}Shared`)) { - return { nestedStack, nestedStackResources, stackKey }; - } - } - return; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/ssm-inventory.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/ssm-inventory.ts deleted file mode 100644 index 2e11ca3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/ssm-inventory.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { AseaResourceType } from '@aws-accelerator/config'; -import { CfnResourceType } from '@aws-accelerator/config'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -const enum RESOURCE_TYPE { - RESOURCE_DATA_SYNC = 'AWS::SSM::ResourceDataSync', - SSM_ASSOCIATION = 'AWS::SSM::Association', -} - -const ASEA_PHASE_NUMBER = '2'; - -export class SsmInventory extends AseaResource { - readonly props: AseaResourceProps; - constructor(scope: ImportAseaResourcesStack, ssmInventoryProps: AseaResourceProps) { - super(scope, ssmInventoryProps); - this.props = ssmInventoryProps; - if (ssmInventoryProps.stackInfo.phase !== ASEA_PHASE_NUMBER) { - // Skip Non-Phase 2 resource stacks - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.RESOURCE_DATA_SYNC}s or ${RESOURCE_TYPE.SSM_ASSOCIATION}s to handle in stack ${ssmInventoryProps.stackInfo.stackName}`, - ); - return; - } - - const existingResourceDataSyncs = this.scope.importStackResources.getResourcesByType( - RESOURCE_TYPE.RESOURCE_DATA_SYNC, - ); - const existingSSMAssociations = this.scope.importStackResources.getResourcesByType(RESOURCE_TYPE.SSM_ASSOCIATION); - - if (existingResourceDataSyncs.length === 0 && existingSSMAssociations.length === 0) { - //Return if no existing Resource Data Syncs or existing SSM Associations found in Resource Mapping - return; - } - - this.processExistingResourceDataSync(existingResourceDataSyncs); - this.processExistingSSMAssociation(existingSSMAssociations); - } - - private processExistingResourceDataSync(existingResourceDataSyncs: CfnResourceType[]) { - for (const existingResourceDataSync of existingResourceDataSyncs) { - if (!existingResourceDataSync.physicalResourceId) { - continue; - } - const resourceDataSync = this.scope.getResource( - existingResourceDataSync.logicalResourceId, - ) as ssm.CfnResourceDataSync; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(resourceDataSync.syncName)}ResourceDataSync`), - parameterName: this.scope.getSsmPath(SsmResourceType.RESOURCE_DATA_SYNC, [resourceDataSync.syncName]), - stringValue: existingResourceDataSync.physicalResourceId, - }); - this.scope.addAseaResource(AseaResourceType.SSM_RESOURCE_DATA_SYNC, `${resourceDataSync.syncName}`); - } - } - - private processExistingSSMAssociation(existingSSMAssociations: CfnResourceType[]) { - for (const existingSSMAssociation of existingSSMAssociations) { - if (!existingSSMAssociation.physicalResourceId) { - continue; - } - const association = this.scope.getResource(existingSSMAssociation.logicalResourceId) as ssm.CfnAssociation; - this.scope.addAseaResource(AseaResourceType.SSM_ASSOCIATION, `${association.associationName}`); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(association.associationName!)}`), - parameterName: this.scope.getSsmPath(SsmResourceType.ASSOCIATION, [association.associationName!]), - stringValue: existingSSMAssociation.physicalResourceId, - }); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/tgw-cross-account-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/tgw-cross-account-resources.ts deleted file mode 100644 index 6a3735c..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/tgw-cross-account-resources.ts +++ /dev/null @@ -1,230 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { - ASEAMapping, - ASEAMappings, - AseaResourceType, - CfnResourceType, - TransitGatewayAttachmentConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { AseaResource, AseaResourceProps } from './resource'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; - -const enum RESOURCE_TYPE { - VPC = 'AWS::EC2::VPC', - TGW_ASSOCIATION = 'AWS::EC2::TransitGatewayRouteTableAssociation', - TGW_PROPAGATION = 'AWS::EC2::TransitGatewayRouteTablePropagation', - TGW_ATTACHMENT = 'AWS::EC2::TransitGatewayAttachment', - TRANSIT_GATEWAY_ROUTE_TABLE = 'AWS::EC2::TransitGatewayRouteTable', -} -const ASEA_PHASE_NUMBER = '2'; - -export class TgwCrossAccountResources extends AseaResource { - readonly props: AseaResourceProps; - private readonly propagationResources: CfnResourceType[] = []; - private readonly associationResources: CfnResourceType[] = []; - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - this.props = props; - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs(LogLevel.INFO, `No Resources to handle in stack ${props.stackInfo.stackName}`); - return; - } - if (!props.mapping) { - throw new Error('ASEA Mapping is undefined'); - } - - this.propagationResources = this.scope.importStackResources.getResourcesByType(RESOURCE_TYPE.TGW_PROPAGATION); - this.associationResources = this.scope.importStackResources.getResourcesByType(RESOURCE_TYPE.TGW_ASSOCIATION); - for (const vpcItem of this.scope.vpcResources) { - const [accountNames] = this.scope.getTransitGatewayAttachmentAccounts(vpcItem); - this.createTransitGatewayRouteTableAssociationPropagations(vpcItem, accountNames, props.mapping); - } - } - - private createTransitGatewayRouteTableAssociationPropagations( - vpcItem: VpcConfig | VpcTemplatesConfig, - accountNames: string[], - mapping: ASEAMappings, - ) { - if (this.propagationResources.length === 0) return; - if (vpcItem.transitGatewayAttachments?.length === 0) { - this.scope.addLogs(LogLevel.WARN, `TGW Attachment is removed from VPC "${vpcItem.name}" configuration`); - return; - } - for (const tgwAttachmentItem of vpcItem.transitGatewayAttachments ?? []) { - const transitGatewayAttachments: { [name: string]: string } = {}; - this.setTransitGatewayIds(vpcItem, tgwAttachmentItem, accountNames, transitGatewayAttachments); - this.createTransitGatewayRouteTableAssociationPropagation( - accountNames, - mapping, - tgwAttachmentItem, - transitGatewayAttachments, - ); - } - } - - private setTransitGatewayIds( - vpcItem: VpcConfig | VpcTemplatesConfig, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - accountNames: string[], - transitGatewayAttachments: { [name: string]: string }, - ) { - // Loop through attachment owner accounts - for (const owningAccount of accountNames) { - const attachmentKey = `${tgwAttachmentItem.transitGateway.name}_${owningAccount}_${vpcItem.name}`; - const tgwAttachmentId = this.getTgwAttachmentId( - vpcItem.name, - tgwAttachmentItem.name, - this.props.mapping, - owningAccount, - vpcItem.region, - ); - if (tgwAttachmentId) transitGatewayAttachments[attachmentKey] = tgwAttachmentId; - } - } - - /** - * ASEA Creates TransitGatewayAttachment in Phase 1 VPC Stack and only one TGW Attachment is created - * @param vpcName - * @param accountKey - * @param region - * @returns - */ - private getTgwAttachmentId( - vpcName: string, - tgwAttachmentName: string, - mapping: ASEAMappings, - accountKey: string, - region: string, - ) { - const vpcStacksInfo: ASEAMapping[] = []; - Object.keys(mapping).forEach(key => { - const stack = mapping[key]; - if (stack.accountKey === accountKey && stack.phase === '1' && stack.region === region) { - stack.cfnResources = this.loadResourcesFromFile(stack); - vpcStacksInfo.push(stack); - } - }); - - let vpcStack: ASEAMapping | undefined; - for (const vpcStackInfo of vpcStacksInfo) { - const vpcResource = this.findResourceByTypeAndTag(vpcStackInfo.cfnResources, RESOURCE_TYPE.VPC, vpcName); - if (vpcResource) { - vpcStack = vpcStackInfo; - break; - } - } - if (!vpcStack) { - this.scope.addLogs(LogLevel.INFO, `VPC "${vpcName}" didn't find in ASEA Resource mapping`); - return; - } - const tgwAttachmentResources = this.filterResourcesByType(vpcStack.cfnResources, RESOURCE_TYPE.TGW_ATTACHMENT); - if (tgwAttachmentResources.length === 0) return; - const tgwAttachment = tgwAttachmentResources.find(tgwAttachResource => - tgwAttachResource.resourceMetadata['Properties'].Tags.find( - (tag: { Key: string; Value: string }) => tag.Key === 'Name' && tag.Value === tgwAttachmentName, - ), - ); - if (!tgwAttachment) return; - return tgwAttachment.physicalResourceId; - } - - private getTgwRouteTableId(routeTableName: string, mapping: ASEAMappings) { - const tgwStackMappingKey = Object.keys(mapping).find( - key => - mapping[key].accountKey === this.stackInfo.accountKey && - mapping[key].region === this.stackInfo.region && - mapping[key].phase === '0', - ); - - if (!tgwStackMappingKey) { - return; - } - const tgwStackMapping = mapping[tgwStackMappingKey]; - tgwStackMapping.cfnResources = this.loadResourcesFromFile(tgwStackMapping); - const tgwRouteTableResources = this.filterResourcesByType( - tgwStackMapping.cfnResources ?? [], - RESOURCE_TYPE.TRANSIT_GATEWAY_ROUTE_TABLE, - ); - const tgwRouteTableResource = this.findResourceByTag(tgwRouteTableResources, routeTableName); - return tgwRouteTableResource?.physicalResourceId; - } - - private createTransitGatewayRouteTableAssociationPropagation( - accountNames: string[], - mapping: ASEAMappings, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - transitGatewayAttachments: { [name: string]: string }, - ) { - for (const owningAccount of accountNames) { - this.createTgwPropagations(owningAccount, mapping, tgwAttachmentItem, transitGatewayAttachments); - this.createTgwAssociation(owningAccount, mapping, tgwAttachmentItem, transitGatewayAttachments); - } - } - - private createTgwAssociation( - accountName: string, - mapping: ASEAMappings, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - transitGatewayAttachments: { [name: string]: string }, - ) { - for (const routeTableItem of tgwAttachmentItem.routeTableAssociations ?? []) { - const tgwAssociationRes = this.associationResources.find( - association => - association.resourceMetadata['Properties'].TransitGatewayAttachmentId.Ref === - transitGatewayAttachments[tgwAttachmentItem.name] && - association.resourceMetadata['Properties'].TransitGatewayRouteTableId === - this.getTgwRouteTableId(routeTableItem, mapping), - ); - if (!tgwAssociationRes) continue; - const association = this.scope.getResource( - tgwAssociationRes.logicalResourceId, - ) as cdk.aws_ec2.CfnTransitGatewayRouteTableAssociation; - if (!association) { - this.scope.addLogs( - LogLevel.WARN, - `TGW Association for "${tgwAttachmentItem.name}/${routeTableItem}" exists in Mapping but not found in resources`, - ); - } - // Propagation resourceId is not used anywhere in LZA. No need of SSM Parameter. - this.scope.addAseaResource( - AseaResourceType.TRANSIT_GATEWAY_ASSOCIATION, - `${accountName}/${tgwAttachmentItem.transitGateway.name}/${tgwAttachmentItem.name}/${routeTableItem}`, - ); - } - } - - private createTgwPropagations( - accountName: string, - mapping: ASEAMappings, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - transitGatewayAttachments: { [name: string]: string }, - ) { - for (const routeTableItem of tgwAttachmentItem.routeTablePropagations ?? []) { - const tgwPropagationRes = this.propagationResources.find( - propagation => - propagation.resourceMetadata['Properties'].TransitGatewayAttachmentId.Ref === - transitGatewayAttachments[tgwAttachmentItem.name] && - propagation.resourceMetadata['Properties'].TransitGatewayRouteTableId === - this.getTgwRouteTableId(routeTableItem, mapping), - ); - if (!tgwPropagationRes) continue; - const propagation = this.stack.getResource( - tgwPropagationRes.logicalResourceId, - ) as cdk.aws_ec2.CfnTransitGatewayRouteTablePropagation; - if (!propagation) { - this.scope.addLogs( - LogLevel.WARN, - `TGW Propagation for "${tgwAttachmentItem.name}/${routeTableItem}" exists in Mapping but not found in resources`, - ); - } - // Propagation resourceId is not used anywhere in LZA. No need of SSM Parameter. - this.scope.addAseaResource( - AseaResourceType.TRANSIT_GATEWAY_PROPAGATION, - `${accountName}/${tgwAttachmentItem.transitGateway.name}/${tgwAttachmentItem.name}/${routeTableItem}`, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateway-routes.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateway-routes.ts deleted file mode 100644 index 1de86e9..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateway-routes.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { - ASEAMappings, - AseaResourceType, - CfnResourceType, - TransitGatewayConfig, - TransitGatewayRouteEntryConfig, - TransitGatewayRouteTableConfig, - TransitGatewayRouteTableDxGatewayEntryConfig, - TransitGatewayRouteTableTgwPeeringEntryConfig, - TransitGatewayRouteTableVpcEntryConfig, - TransitGatewayRouteTableVpnEntryConfig, - isNetworkType, -} from '@aws-accelerator/config'; -import { AseaResource, AseaResourceProps } from './resource'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { ImportStackResources } from '../../utils/import-stack-resources'; - -enum RESOURCE_TYPE { - TGW_ROUTE = 'AWS::EC2::TransitGatewayRoute', - TGW_ROUTE_TABLE = 'AWS::EC2::TransitGatewayRouteTable', - VPC = 'AWS::EC2::VPC', - TGW_ATTACHMENT = 'AWS::EC2::TransitGatewayAttachment', -} -const ASEA_PHASE_NUMBERS = ['0', '1', '3']; - -export class TransitGatewayRoutes extends AseaResource { - props: AseaResourceProps; - private transitGatewayRouteTables: Map = new Map(); - private allRoutes!: CfnResourceType[]; - private allRouteTables!: CfnResourceType[]; - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - this.props = props; - if (!ASEA_PHASE_NUMBERS.includes(props.stackInfo.phase!)) { - this.scope.addLogs(LogLevel.INFO, `No Resources to handle in stack ${props.stackInfo.stackName}`); - return; - } - const stackRoutes = this.scope.importStackResources.getResourcesByType(RESOURCE_TYPE.TGW_ROUTE); - const nestedStackRoutes = this.getTgwRoutesFromNestedStacks(); - this.allRoutes = [...stackRoutes, ...nestedStackRoutes]; - this.scope.addLogs(LogLevel.INFO, `All routes: ${JSON.stringify(this.allRoutes)}`); - if (this.allRoutes.length === 0) return; - /** - * Load tgw stack resources for current account at once - */ - const mappings = this.props.globalConfig.externalLandingZoneResources?.templateMap || {}; - const tgwStackKey = Object.keys(mappings).find( - key => - // Using accountId instead of accountKey to avoid mismatch of accountKey between ASEA and LZA - mappings[key].accountId === props.stackInfo.accountId && - mappings[key].phase === '0' && - mappings[key].region === this.stackInfo.region, - ); - if (!tgwStackKey) { - return; - } - const tgwStackMapping = mappings[tgwStackKey]; - const tgwResources = ImportStackResources.initSync({ stackMapping: tgwStackMapping }); - - this.allRouteTables = tgwResources.getResourcesByType(RESOURCE_TYPE.TGW_ROUTE_TABLE); - this.scope.addLogs(LogLevel.INFO, `All route tables: ${JSON.stringify(this.allRouteTables)}`); - for (const tgwItem of props.networkConfig.transitGateways.filter( - tgw => tgw.account === props.stackInfo.accountKey && tgw.region === props.stackInfo.region, - ) ?? []) { - this.setTransitGatewayResourcesMap(tgwItem, tgwResources); - } - for (const tgwItem of props.networkConfig.transitGateways ?? []) { - for (const routeTableItem of tgwItem.routeTables ?? []) { - this.scope.addLogs( - LogLevel.INFO, - `Creating static route items for tgw ${tgwItem.name} and route table ${routeTableItem.name}`, - ); - this.scope.addLogs( - LogLevel.INFO, - `Routes identified in route table ${routeTableItem.name}: ${JSON.stringify(routeTableItem.routes)}`, - ); - this.createTransitGatewayStaticRouteItems(tgwItem, routeTableItem); - } - } - } - - /** - * Sets Given TransitGatewayConfig into maps with physicalResourceId to avoid loading multiple times - * @param tgwItem - * @returns - */ - private setTransitGatewayResourcesMap(tgwItem: TransitGatewayConfig, tgwStackMapping: ImportStackResources) { - for (const routeTableItem of tgwItem.routeTables ?? []) { - // ASEA RouteTable name includes TGW Name. No need to use TGW Id since TGW names are unique - const routeTableResource = tgwStackMapping.getResourceByTypeAndTag( - RESOURCE_TYPE.TGW_ROUTE_TABLE, - routeTableItem.name, - ); - if (!routeTableResource || !routeTableResource.physicalResourceId) { - continue; - } - this.transitGatewayRouteTables.set( - `${tgwItem.name}_${routeTableItem.name}`, - routeTableResource.physicalResourceId, - ); - } - } - - /** - * ASEA Creates TransitGatewayAttachment in Phase 1 VPC Stack and only one TGW Attachment is created - * @param vpcName - * @param accountKey - * @param region - * @returns - */ - private getTgwAttachmentId(vpcName: string, accountKey: string, region: string, mappings: ASEAMappings) { - this.scope.addLogs(LogLevel.INFO, `Getting TGW attachment id for vpc ${vpcName} in account ${accountKey}`); - if (!mappings) { - return; - } - const vpcStackKey = Object.keys(mappings).find( - key => - mappings[key].accountId === this.props.accountsConfig.getAccountId(accountKey) && - mappings[key].phase === '1' && - mappings[key].region === region && - mappings[key].nestedStacks, - ); - if (!vpcStackKey) { - return; - } - const vpcResourceMapping = mappings[vpcStackKey]; - const vpcResources = ImportStackResources.initSync({ stackMapping: vpcResourceMapping }); - let tgwVpcResources: ImportStackResources | undefined; - for (const [, vpcStackResources] of Object.entries(vpcResources.nestedStackResources ?? {})) { - const vpcResource = vpcStackResources.getResourceByTypeAndTag(RESOURCE_TYPE.VPC, vpcName); - if (vpcResource) { - tgwVpcResources = vpcStackResources; - break; - } - } - if (!tgwVpcResources) { - this.scope.addLogs(LogLevel.INFO, `VPC "${vpcName}" didn't find in ASEA Resource mapping`); - } - const tgwAttachmentResources = tgwVpcResources?.getResourcesByType(RESOURCE_TYPE.TGW_ATTACHMENT); - if (!tgwAttachmentResources || tgwAttachmentResources?.length === 0) { - return; - } - return tgwAttachmentResources[0].physicalResourceId; - } - - /** - * Function to get static route attachment configuration - * @param routeItem {@link TransitGatewayRouteEntryConfig} - * @param routeTableItem {@link TransitGatewayRouteTableConfig} - * @param tgwItem {@link TransitGatewayConfig} - * @returns - */ - private getStaticRouteAttachmentConfig( - routeItem: TransitGatewayRouteEntryConfig, - routeTableItem: TransitGatewayRouteTableConfig, - tgwItem: TransitGatewayConfig, - ): { - routeId: string; - transitGatewayAttachmentId?: string; - } { - const mappings = this.props.globalConfig.externalLandingZoneResources!.templateMap; - let routeId = ''; - let transitGatewayAttachmentId: string | undefined; - if (routeItem.attachment) { - // If route is for VPC attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableVpcEntryConfig', - routeItem.attachment, - ) - ) { - this.scope.addLogs( - LogLevel.INFO, - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.vpcName}-${routeItem.attachment.account}`; - transitGatewayAttachmentId = this.getTgwAttachmentId( - routeItem.attachment.vpcName, - routeItem.attachment.account, - tgwItem.region, - mappings, - ); - if (!transitGatewayAttachmentId) { - this.scope.addLogs( - LogLevel.INFO, - `TGW attachment not found in account ${routeItem.attachment.account}, looking in ${tgwItem.account}`, - ); - transitGatewayAttachmentId = this.getTgwAttachmentId( - routeItem.attachment.vpcName, - tgwItem.account, - tgwItem.region, - mappings, - ); - } - } - - // If route is for DX Gateway attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableDxGatewayEntryConfig', - routeItem.attachment, - ) - ) { - this.scope.addLogs( - LogLevel.INFO, - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - // routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.directConnectGatewayName}`; - } - - // If route is for VPN attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeItem.attachment, - ) - ) { - this.scope.addLogs( - LogLevel.INFO, - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - // routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.vpnConnectionName}`; - } - - // If route is for TGW peering attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableTgwPeeringEntryConfig', - routeItem.attachment, - ) - ) { - this.scope.addLogs( - LogLevel.INFO, - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - // routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.transitGatewayPeeringName}`; - } - } - - if (routeItem.attachment && !transitGatewayAttachmentId) { - this.scope.addLogs( - LogLevel.ERROR, - `Unable to locate transit gateway attachment ID for route table item ${routeTableItem.name}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - return { routeId: routeId, transitGatewayAttachmentId: transitGatewayAttachmentId }; - } - - /** - * Function to create TGW static route items - * @param tgwItem {@link TransitGatewayConfig} - * @param routeTableItem {@link TransitGatewayRouteTableConfig} - */ - private createTransitGatewayStaticRouteItems( - tgwItem: TransitGatewayConfig, - routeTableItem: TransitGatewayRouteTableConfig, - ): void { - // Get TGW route table ID - const routeTableKey = `${tgwItem.name}_${routeTableItem.name}`; - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(routeTableKey); - if (!transitGatewayRouteTableId) { - this.scope.addLogs(LogLevel.ERROR, `Transit Gateway route table ${routeTableKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - for (const routeItem of routeTableItem.routes ?? []) { - this.createTransitGatewayStaticRouteItem(routeItem, routeTableItem, tgwItem); - } - } - - private createTransitGatewayStaticRouteItem( - routeItem: TransitGatewayRouteEntryConfig, - routeTableItem: TransitGatewayRouteTableConfig, - tgwItem: TransitGatewayConfig, - ) { - const attachmentConfig = this.getStaticRouteAttachmentConfig(routeItem, routeTableItem, tgwItem); - let routeId = attachmentConfig.routeId; - const transitGatewayAttachmentId = attachmentConfig.transitGatewayAttachmentId; - if (routeItem.blackhole) { - this.scope.addLogs( - LogLevel.INFO, - `Adding blackhole route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-blackhole`; - } - const routePhysicalId = this.getRouteFromResources( - this.transitGatewayRouteTables.get(`${tgwItem.name}_${routeTableItem.name}`)!, - transitGatewayAttachmentId!, - routeItem.destinationCidrBlock, - routeItem.blackhole, - ); - if (!routePhysicalId || !routeId) { - return; - } - this.scope.addAseaResource(AseaResourceType.TRANSIT_GATEWAY_ROUTE, routeId); - } - - private getRouteFromResources( - transitGatewayRouteTableId: string, - transitGatewayAttachmentId: string, - destination?: string, - blackhole?: boolean, - ) { - const route = this.allRoutes.find( - ({ resourceMetadata }) => - resourceMetadata['Properties'].TransitGatewayRouteTableId === transitGatewayRouteTableId && - resourceMetadata['Properties'].TransitGatewayAttachmentId === transitGatewayAttachmentId && - ((destination && resourceMetadata['Properties'].DestinationCidrBlock === destination) || - (blackhole && resourceMetadata['Properties'].Blackhole)), - ); - return route?.physicalResourceId; - } - - private getTgwRoutesFromNestedStacks() { - const nestedRoutes = []; - for (const [, nestedStackResources] of Object.entries(this.scope.nestedStackResources ?? {})) { - this.scope.addLogs( - LogLevel.INFO, - `Looking for TGW routes in nested stack ${nestedStackResources.stackMapping.stackName}`, - ); - const tgwRoutes = nestedStackResources.getResourcesByType(RESOURCE_TYPE.TGW_ROUTE); - this.scope.addLogs( - LogLevel.INFO, - `Found ${tgwRoutes.length} TGW routes in nested stack ${nestedStackResources.stackMapping.stackName}`, - ); - nestedRoutes.push(...tgwRoutes); - } - return nestedRoutes; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateways.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateways.ts deleted file mode 100644 index 315371a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/transit-gateways.ts +++ /dev/null @@ -1,88 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { AseaResource, AseaResourceProps } from './resource'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { pascalCase } from 'pascal-case'; -import { AseaResourceType, TransitGatewayConfig } from '@aws-accelerator/config'; - -const enum RESOURCE_TYPE { - TRANSIT_GATEWAY = 'AWS::EC2::TransitGateway', - TRANSIT_GATEWAY_ROUTE_TABLE = 'AWS::EC2::TransitGatewayRouteTable', -} -const ASEA_PHASE_NUMBER = '0'; - -/** - * Handles Transit Gateways created by ASEA. - * All Transit Gateways driven by ASEA configuration are deployed in Phase-0 - */ -export class TransitGateways extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.TRANSIT_GATEWAY}s to handle in stack ${props.stackInfo.stackName}`, - ); - return; - } - const tgwInAsea: string[] = []; - const existingTransitGatewaysResources = this.scope.importStackResources.getResourcesByType( - RESOURCE_TYPE.TRANSIT_GATEWAY, - ); - for (const tgwItem of props.networkConfig.transitGateways ?? []) { - const tgwResource = this.scope.importStackResources.getResourceByTypeAndTag( - RESOURCE_TYPE.TRANSIT_GATEWAY, - tgwItem.name, - ); - if (!tgwResource) continue; - const transitGateway = this.scope.getResource(tgwResource.logicalResourceId) as cdk.aws_ec2.CfnTransitGateway; - transitGateway.amazonSideAsn = tgwItem.asn; - transitGateway.autoAcceptSharedAttachments = tgwItem.autoAcceptSharingAttachments; - transitGateway.defaultRouteTableAssociation = tgwItem.defaultRouteTableAssociation; - transitGateway.defaultRouteTablePropagation = tgwItem.defaultRouteTablePropagation; - transitGateway.dnsSupport = tgwItem.dnsSupport; - transitGateway.vpnEcmpSupport = tgwItem.vpnEcmpSupport; - this.createTgwRouteTables(tgwItem, tgwResource.logicalResourceId); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${tgwItem.name}TransitGatewayId`), - parameterName: this.scope.getSsmPath(SsmResourceType.TGW, [tgwItem.name]), - stringValue: transitGateway.ref, - }); - this.scope.addAseaResource(AseaResourceType.TRANSIT_GATEWAY, tgwItem.name); - tgwInAsea.push(tgwResource.logicalResourceId); - } - - existingTransitGatewaysResources - .filter(tgwResource => !tgwInAsea.includes(tgwResource.logicalResourceId)) - .forEach(tgwResource => { - this.scope.addLogs( - LogLevel.INFO, - `TGW ${tgwResource.logicalResourceId} is in ASEA Cfn but not found in configuration`, - ); - }); - } - - private createTgwRouteTables(tgwItem: TransitGatewayConfig, tgwId: string) { - const allTgwRouteTables = this.scope.importStackResources.getResourcesByType( - RESOURCE_TYPE.TRANSIT_GATEWAY_ROUTE_TABLE, - ); - const tgwRouteTables = this.filterResourcesByRef(allTgwRouteTables, 'TransitGatewayId', tgwId); - if (tgwRouteTables.length === 0) return; - for (const routeTableItem of tgwItem.routeTables ?? []) { - const tgwRouteTableResource = this.findResourceByTag(tgwRouteTables, routeTableItem.name); - if (!tgwRouteTableResource) continue; - const tgwRouteTable = this.stack.getResource( - tgwRouteTableResource.logicalResourceId, - ) as cdk.aws_ec2.CfnTransitGatewayRouteTable; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${tgwItem.name}${routeTableItem.name}TransitGatewayRouteTableId`), - parameterName: this.scope.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [tgwItem.name, routeTableItem.name]), - stringValue: tgwRouteTable.ref, - }); - this.scope.addAseaResource( - AseaResourceType.TRANSIT_GATEWAY_ROUTE_TABLE, - `${tgwItem.name}/${routeTableItem.name}`, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-endpoints.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-endpoints.ts deleted file mode 100644 index 9bd6f79..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-endpoints.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { ASEAMappings, AseaResourceType, NestedStack } from '@aws-accelerator/config'; -import { HostedZone } from '@aws-accelerator/constructs'; -import { CfnHostedZone } from 'aws-cdk-lib/aws-route53'; -const ASEA_PHASE_NUMBER = '2'; -const enum RESOURCE_TYPE { - VPC_ENDPOINT_TYPE = 'AWS::EC2::VPCEndpoint', - VPC = 'AWS::EC2::VPC', - RECORD_SET = 'AWS::Route53::RecordSet', - HOSTED_ZONE = 'AWS::Route53::HostedZone', -} - -export class VpcEndpoints extends AseaResource { - readonly props: AseaResourceProps; - constructor(scope: ImportAseaResourcesStack, vpcEndpointsProps: AseaResourceProps) { - super(scope, vpcEndpointsProps); - this.props = vpcEndpointsProps; - this.ssmParameters = []; - if (vpcEndpointsProps.stackInfo.phase !== ASEA_PHASE_NUMBER) { - // Skip Non-Phase 2 resource stacks - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.VPC_ENDPOINT_TYPE}s to handle in stack ${vpcEndpointsProps.stackInfo.stackName}`, - ); - return; - } - - if (!this.props.globalConfig.externalLandingZoneResources?.templateMap) { - throw new Error('No template map found in global config'); - } - - const existingVpcEndpointResources = this.scope.importStackResources.getResourcesByType( - RESOURCE_TYPE.VPC_ENDPOINT_TYPE, - ); - const existingHostedZoneResources = this.scope.importStackResources.getResourcesByType(RESOURCE_TYPE.HOSTED_ZONE); - const existingRecordSetResources = this.scope.importStackResources.getResourcesByType(RESOURCE_TYPE.RECORD_SET); - - if (!existingVpcEndpointResources || existingVpcEndpointResources.length === 0) { - //Return if no existing VPC Endpoints found in Resource Mapping - return; - } - - for (const vpcItem of this.scope.vpcResources) { - // Set interface endpoint DNS names - const vpcId = this.getVPCId(vpcItem.name, this.props.globalConfig.externalLandingZoneResources.templateMap); - if (!this.findResourceByName(existingVpcEndpointResources, 'VpcId', vpcId!)) { - continue; - } - for (const endpointItem of vpcItem.interfaceEndpoints?.endpoints ?? []) { - const endpointCfn = this.findResourceByName( - existingVpcEndpointResources, - 'ServiceName', - this.interfaceVpcEndpointForRegionAndEndpointName(endpointItem.service), - ); - if (!endpointCfn || !endpointCfn.physicalResourceId) { - continue; - } - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(endpointItem.service)}EndpointId`), - parameterName: this.scope.getSsmPath(SsmResourceType.VPC_ENDPOINT, [vpcItem.name, endpointItem.service]), - stringValue: endpointCfn.physicalResourceId, - }); - this.scope.addAseaResource(AseaResourceType.VPC_ENDPOINT, `${vpcItem.name}/${endpointItem.service}`); - let hostedZoneName = HostedZone.getHostedZoneNameForService(endpointItem.service, this.stackInfo.region); - if (!hostedZoneName.endsWith('.')) { - hostedZoneName += '.'; - } - - const hostedZoneCfnName = this.getCfnHostedZoneName(hostedZoneName); - - const hostedZoneCfn = this.findResourceByName(existingHostedZoneResources, 'Name', hostedZoneCfnName); - if (!hostedZoneCfn) { - continue; - } - const hostedZone = this.stack.getResource(hostedZoneCfn.logicalResourceId) as CfnHostedZone; - this.scope.addSsmParameter({ - logicalId: `SsmParam${pascalCase(vpcItem.name)}Vpc${pascalCase(endpointItem.service)}EpHostedZone`, - parameterName: this.scope.getSsmPath(SsmResourceType.PHZ_ID, [vpcItem.name, endpointItem.service]), - stringValue: hostedZone.attrId, - }); - this.scope.addAseaResource(AseaResourceType.ROUTE_53_PHZ_ID, `${vpcItem.name}/${endpointItem.service}`); - const recordSetName = this.getRecordSetName(hostedZoneName); - const recordSetCfn = this.findResourceByName(existingRecordSetResources, 'Name', recordSetName); - if (!recordSetCfn) { - this.scope.addLogs( - LogLevel.WARN, - `Interface Endpoint "${vpcItem.name}/${endpointItem.serviceName}" is managed by ASEA but no RecordSet found in stack `, - ); - continue; - } - - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${vpcItem.name}${endpointItem.service}Dns`), - parameterName: this.scope.getSsmPath(SsmResourceType.ENDPOINT_DNS, [vpcItem.name, endpointItem.service]), - stringValue: recordSetCfn.resourceMetadata['Properties'].AliasTarget.DNSName, - }); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${vpcItem.name}${endpointItem.service}Phz`), - parameterName: this.scope.getSsmPath(SsmResourceType.ENDPOINT_ZONE_ID, [vpcItem.name, endpointItem.service]), - stringValue: recordSetCfn.resourceMetadata['Properties'].AliasTarget.HostedZoneId, - }); - } - } - } - - private getVPCId(vpcName: string, mapping: ASEAMappings) { - let vpcId: string | undefined; - const parentStackKeys = Object.keys(mapping).filter(key => { - const stack = mapping[key]; - return ( - stack.accountKey === this.stackInfo.accountKey && - stack.phase === '1' && - stack.region === this.stackInfo.region && - stack.nestedStacks - ); - }); - const allNestedStacks = parentStackKeys.map(key => mapping[key].nestedStacks); - const nestedStackList: NestedStack[] = []; - for (const nestedStacks of allNestedStacks) { - if (!nestedStacks) { - continue; - } - Object.entries(nestedStacks).forEach(([, nestedStack]) => { - nestedStackList.push(nestedStack); - }); - } - for (const nestedStack of nestedStackList) { - nestedStack.cfnResources = this.loadResourcesFromFile(nestedStack); - const vpcResource = this.findResourceByTypeAndTag(nestedStack.cfnResources, RESOURCE_TYPE.VPC, vpcName); - if (vpcResource) { - return vpcResource.physicalResourceId; - } - } - return vpcId; - } - - private interfaceVpcEndpointForRegionAndEndpointName(name: string): string { - if (name === 'notebook') { - return `aws.sagemaker.${this.stackInfo.region}.${name}`; - } - return `com.amazonaws.${this.stackInfo.region}.${name}`; - } - - private getCfnHostedZoneName(hostedZoneName: string): string { - const hostedZoneNameArr = hostedZoneName.split('.'); - const hostedZonePrefix = hostedZoneNameArr.shift(); - if (!hostedZonePrefix) { - return hostedZoneName; - } - - switch (hostedZonePrefix) { - case 'ecs-t': - hostedZoneNameArr.unshift('ecs-telemetry'); - break; - case 'ecs-a': - hostedZoneNameArr.unshift('ecs-agent'); - break; - default: - hostedZoneNameArr.unshift(hostedZonePrefix); - } - - return hostedZoneNameArr.join('.'); - } - - private getRecordSetName(hostedZoneName: string): string { - const hostedZoneNameArr = hostedZoneName.split('.'); - const hostedZonePrefix = hostedZoneNameArr.shift(); - if (!hostedZonePrefix) { - return hostedZoneName; - } - - switch (hostedZonePrefix) { - case 'dkr': - hostedZoneNameArr.unshift('dkr'); - hostedZoneNameArr.unshift('*'); - break; - case 'ecs-a': - hostedZoneNameArr.unshift('ecs-agent'); - break; - case 'ecs-t': - hostedZoneNameArr.unshift('ecs-telemetry'); - break; - default: - hostedZoneNameArr.unshift(hostedZonePrefix); - } - return hostedZoneNameArr.join('.'); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-peering-connection.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-peering-connection.ts deleted file mode 100644 index 1ef1ac7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-peering-connection.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { AseaResourceType } from '@aws-accelerator/config'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; - -const enum RESOURCE_TYPE { - PEERING_CONNECTION = 'AWS::EC2::VPCPeeringConnection', -} -const ASEA_PHASE_NUMBER = '2'; - -export class VpcPeeringConnection extends AseaResource { - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs( - LogLevel.INFO, - `No ${RESOURCE_TYPE.PEERING_CONNECTION}s to handle in stack ${props.stackInfo.stackName}`, - ); - return; - } - if (!props.networkConfig.vpcPeering) { - return; - } - for (const peering of props.networkConfig.vpcPeering) { - const peeringConnectionResource = this.scope.importStackResources.getResourceByTypeAndTag( - RESOURCE_TYPE.PEERING_CONNECTION, - peering.name, - ); - if (!peeringConnectionResource) continue; - const peeringConnection = this.stack.getResource(peeringConnectionResource.logicalResourceId); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(peering.name)}VpcPeering`), - parameterName: this.scope.getSsmPath(SsmResourceType.VPC_PEERING, [peering.name]), - stringValue: peeringConnection.ref, - }); - this.scope.addAseaResource(AseaResourceType.EC2_VPC_PEERING, peering.name); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-resources.ts deleted file mode 100644 index 870a195..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/asea-resources/vpc-resources.ts +++ /dev/null @@ -1,1384 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { IPv4CidrRange, IPv6CidrRange } from 'ip-num'; - -import { - CfnInternetGateway, - CfnNatGateway, - CfnRouteTable, - CfnSecurityGroup, - CfnSubnet, - CfnSubnetNetworkAclAssociation, - CfnTransitGatewayAttachment, - CfnVPC, - CfnVPNGateway, -} from 'aws-cdk-lib/aws-ec2'; - -import { NetworkFirewall } from '@aws-accelerator/constructs'; -import { CfnInclude } from 'aws-cdk-lib/cloudformation-include'; -import { - VpcConfig, - VpcTemplatesConfig, - AseaResourceType, - NfwFirewallConfig, - RouteTableConfig, - TransitGatewayAttachmentConfig, - SecurityGroupRuleConfig, - NonEmptyString, - isNetworkType, - SubnetSourceConfig, - SecurityGroupSourceConfig, - CfnResourceType, - NetworkAclConfig, -} from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { ImportAseaResourcesStack, LogLevel } from '../stacks/import-asea-resources-stack'; -import { AseaResource, AseaResourceProps } from './resource'; -import { getSubnetConfig, getVpcConfig } from '../stacks/network-stacks/utils/getter-utils'; -import { AcceleratorStage } from '../accelerator-stage'; -import { ImportStackResources } from '../../utils/import-stack-resources'; - -const enum RESOURCE_TYPE { - VPC = 'AWS::EC2::VPC', - SUBNET = 'AWS::EC2::Subnet', - CIDR_BLOCK = 'AWS::EC2::VPCCidrBlock', - INTERNET_GATEWAY = 'AWS::EC2::InternetGateway', - NAT_GATEWAY = 'AWS::EC2::NatGateway', - VPN_GATEWAY = 'AWS::EC2::VPNGateway', - SECURITY_GROUP = 'AWS::EC2::SecurityGroup', - SECURITY_GROUP_EGRESS = 'AWS::EC2::SecurityGroupEgress', - SECURITY_GROUP_INGRESS = 'AWS::EC2::SecurityGroupIngress', - ROUTE_TABLE = 'AWS::EC2::RouteTable', - TGW_ATTACHMENT = 'AWS::EC2::TransitGatewayAttachment', - TGW_ASSOCIATION = 'AWS::EC2::TransitGatewayRouteTableAssociation', - TGW_PROPAGATION = 'AWS::EC2::TransitGatewayRouteTablePropagation', - TGW_ROUTE = 'AWS::EC2::TransitGatewayRoute', - NETWORK_ACL = 'AWS::EC2::NetworkAcl', - NETWORK_ACL_SUBNET_ASSOCIATION = 'AWS::EC2::SubnetNetworkAclAssociation', - NETWORK_FIREWALL = 'AWS::NetworkFirewall::Firewall', - NETWORK_FIREWALL_POLICY = 'AWS::NetworkFirewall::FirewallPolicy', - NETWORK_FIREWALL_RULE_GROUP = 'AWS::NetworkFirewall::RuleGroup', - NETWORK_FIREWALL_LOGGING = 'AWS::NetworkFirewall::LoggingConfiguration', - VPC_ENDPOINT = 'AWS::EC2::VPCEndpoint', - TRANSIT_GATEWAY_ROUTE_TABLE = 'AWS::EC2::TransitGatewayRouteTable', -} - -type SecurityGroupRuleInfo = { - protocol: string; - source: string; - sourceValue: string; - type?: string; - to?: number; - from?: number; - sourceType?: string; - description?: string; -}; - -const TCP_PROTOCOLS_PORT: { [key: string]: number } = { - RDP: 3389, - SSH: 22, - HTTP: 80, - HTTPS: 443, - MSSQL: 1433, - 'MYSQL/AURORA': 3306, - REDSHIFT: 5439, - POSTGRESQL: 5432, - 'ORACLE-RDS': 1521, -}; - -const ASEA_PHASE_NUMBER = '1'; - -export class VpcResources extends AseaResource { - readonly props: AseaResourceProps; - ssmParameters: { logicalId: string; parameterName: string; stringValue: string; scope: CfnInclude }[]; - constructor(scope: ImportAseaResourcesStack, props: AseaResourceProps) { - super(scope, props); - this.props = props; - this.ssmParameters = []; - if (props.stackInfo.phase !== ASEA_PHASE_NUMBER) { - this.scope.addLogs(LogLevel.INFO, `No ${RESOURCE_TYPE.VPC}s to handle in stack ${props.stackInfo.stackName}`); - return; - } - const vpcsInScope = this.scope.vpcsInScope; - for (const vpcInScope of vpcsInScope) { - // ASEA creates NestedStack for each VPC. All SSM Parameters related to VPC goes to nested stack - const vpcResourceInfo = this.getVpcResourceByTag(vpcInScope.name); - if (!vpcResourceInfo || !vpcResourceInfo.resource.physicalResourceId) { - this.scope.addLogs( - LogLevel.INFO, - `Item Excluded: ${vpcInScope.name} in Account/Region ${props.stackInfo.accountKey}/${props.stackInfo.region}`, - ); - continue; - } - const nestedStack = vpcResourceInfo.nestedStack; - const nestedStackResources = vpcResourceInfo.nestedStackResources; - const vpcResource = vpcResourceInfo.resource; - const vpcPhysicalId = vpcResourceInfo.resource.physicalResourceId; - // This is retrieved the specific VPC resource is loaded so we can modify attributes - const vpc = nestedStack.includedTemplate.getResource(vpcResource.logicalResourceId) as CfnVPC; - this.addTagsToSharedEndpointVpcs(vpc, vpcPhysicalId, props); - this.setupInternetGateway(nestedStackResources, nestedStack, vpcInScope); - this.setupVpnGateway(nestedStackResources, nestedStack, vpcInScope); - // This modifies ASEA vpc attributes to match LZA config - vpc.cidrBlock = vpcInScope.cidrs![0]; // 0th index is always main cidr Block - vpc.enableDnsHostnames = vpcInScope.enableDnsHostnames; - vpc.enableDnsSupport = vpcInScope.enableDnsSupport; - vpc.instanceTenancy = vpcInScope.instanceTenancy; - if (vpcInScope.cidrs!.length > 1) { - const additionalCidrResources = nestedStackResources.getResourcesByType(RESOURCE_TYPE.CIDR_BLOCK); - const existingAdditionalCidrBlocks: string[] = - additionalCidrResources?.map(cfnResource => cfnResource.resourceMetadata['Properties'].CidrBlock) ?? []; - vpcInScope.cidrs!.slice(1).forEach(cidr => { - const additionalCidrResource = additionalCidrResources?.find( - cfnResource => cfnResource.resourceMetadata['Properties'].CidrBlock === cidr, - ); - if (!additionalCidrResource) { - this.scope.addLogs( - LogLevel.INFO, - `Item Excluded: ${vpcInScope.name} CIDR in Account/Region ${props.stackInfo.accountKey}/${props.stackInfo.region}`, - ); - return; - } - this.scope.addAseaResource(AseaResourceType.EC2_VPC_CIDR, `${vpcInScope.name}-${cidr}`); - }); - const removedAseaCidrs = vpcInScope - .cidrs!.slice(1) - .filter(cidr => !existingAdditionalCidrBlocks.includes(cidr)); - this.scope.addLogs(LogLevel.INFO, `Removed Additional CIDR created by ASEA are ${removedAseaCidrs}`); - } - // Create Subnets takes in an LZA VPC Config as 'vpcInScope' object and Existing ASEA stack resource information as 'vpcStackInfo' - const subnets = this.createSubnets(vpcInScope, nestedStackResources, nestedStack.includedTemplate); - this.createNaclSubnetAssociations(vpcInScope, nestedStackResources, nestedStack.includedTemplate); - this.createNatGateways(nestedStackResources, nestedStack.includedTemplate, vpcInScope, subnets); - this.createSecurityGroups(vpcInScope, nestedStackResources, nestedStack.includedTemplate); - const tgwAttachmentMap = this.createTransitGatewayAttachments( - vpcInScope, - nestedStackResources, - nestedStack.includedTemplate, - subnets, - ); - this.createTransitGatewayRouteTablePropagation( - vpcInScope, - nestedStackResources, - nestedStack.includedTemplate, - tgwAttachmentMap ?? {}, - ); - this.createTransitGatewayRouteTableAssociation( - vpcInScope, - nestedStackResources, - nestedStack.includedTemplate, - tgwAttachmentMap ?? {}, - ); - this.createNetworkFirewallResources( - vpcInScope, - nestedStackResources, - nestedStack.includedTemplate, - vpc.ref, - subnets, - ); - this.gatewayEndpoints(vpcInScope, nestedStackResources, nestedStack.includedTemplate); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcInScope.name)}VpcId`), - parameterName: this.scope.getSsmPath(SsmResourceType.VPC, [vpcInScope.name]), - stringValue: vpc.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.EC2_VPC, vpcInScope.name); - } - } - private createNaclSubnetAssociations( - vpcInScope: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - includedTemplate: cdk.cloudformation_include.CfnInclude, - ) { - const naclsConfig = vpcInScope.networkAcls; - for (const naclConfig of naclsConfig ?? []) { - this.processSubnetAssociations(vpcInScope, naclConfig, nestedStackResources, includedTemplate); - } - } - - private processSubnetAssociations( - vpcInScope: VpcConfig | VpcTemplatesConfig, - naclConfig: NetworkAclConfig, - nestedStackResources: ImportStackResources, - includedTemplate: cdk.cloudformation_include.CfnInclude, - ) { - const naclName = naclConfig.name; - const naclId = nestedStackResources.getResourceByTypeAndTag(RESOURCE_TYPE.NETWORK_ACL, naclName); - for (const configSubnetAssociation of naclConfig.subnetAssociations) { - const subnetName = configSubnetAssociation; - const subnetId = nestedStackResources.getResourceByTypeAndTag(RESOURCE_TYPE.SUBNET, subnetName); - const naclSubnetAssociation = this.filterNaclSubnetAssocation(nestedStackResources, naclId, subnetId); - - if (!naclSubnetAssociation) { - continue; - } - let cfnNaclSubnetAssociation = includedTemplate.getResource( - naclSubnetAssociation?.logicalResourceId, - ) as CfnSubnetNetworkAclAssociation; - - if (this.props.stage === AcceleratorStage.POST_IMPORT_ASEA_RESOURCES) { - cfnNaclSubnetAssociation = this.modifyNaclSubnetAssociation(cfnNaclSubnetAssociation, naclId, subnetId); - } - - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(naclConfig.name) + pascalCase(subnetName)}SubnetAssociation`), - parameterName: this.scope.getSsmPath(SsmResourceType.NETWORK_ACL_SUBNET_ASSOCIATION, [ - vpcInScope.name, - naclConfig.name, - subnetName, - ]), - stringValue: cfnNaclSubnetAssociation.ref, - scope: nestedStackResources.getStackKey(), - }); - - this.scope.addAseaResource( - AseaResourceType.EC2_NACL_SUBNET_ASSOCIATION, - `${vpcInScope.name}/${naclConfig.name}/${subnetName}`, - ); - } - } - - private modifyNaclSubnetAssociation( - cfnNaclSubnetAssociation: cdk.aws_ec2.CfnSubnetNetworkAclAssociation, - naclId: CfnResourceType | undefined, - subnetId: CfnResourceType | undefined, - ) { - if (naclId?.physicalResourceId) { - cfnNaclSubnetAssociation.networkAclId = naclId.physicalResourceId; - } - if (subnetId?.physicalResourceId) { - cfnNaclSubnetAssociation.subnetId = subnetId.physicalResourceId; - } - return cfnNaclSubnetAssociation; - } - - private filterNaclSubnetAssocation( - nestedStackResources: ImportStackResources, - naclId: CfnResourceType | undefined, - subnetId: CfnResourceType | undefined, - ) { - const naclSubnetAssociations = nestedStackResources.getResourcesByType( - RESOURCE_TYPE.NETWORK_ACL_SUBNET_ASSOCIATION, - ); - - const naclSubnetAssociation = naclSubnetAssociations.find( - naclSubnetAssociations => - naclSubnetAssociations.resourceMetadata['Properties'].NetworkAclId === naclId?.physicalResourceId && - naclSubnetAssociations.resourceMetadata['Properties'].SubnetId === subnetId?.physicalResourceId, - ); - return naclSubnetAssociation; - } - - private getVPCId(vpcName: string) { - if (!this.props.globalConfig.externalLandingZoneResources?.templateMap) { - return; - } - const vpcStacksInfo = this.scope.nestedStackResources ?? {}; - - let vpcId: string | undefined; - for (const [, vpcStackInfo] of Object.entries(vpcStacksInfo)) { - const vpcResource = this.findResourceByTypeAndTag(vpcStackInfo.cfnResources ?? [], RESOURCE_TYPE.VPC, vpcName); - if (vpcResource) { - vpcId = vpcResource.physicalResourceId; - break; - } - } - return vpcId; - } - - private addTagsToSharedEndpointVpcs(currentVpc: cdk.aws_ec2.CfnVPC, vpcPhysicalId: string, props: AseaResourceProps) { - const vpcs = props.networkConfig.vpcs; - const centralEndpointAccount = this.getCentralEndpointAccount(vpcs); - const accountsConfig = props.accountsConfig; - for (const vpc of vpcs) { - const vpcTemplateId = this.getVPCId(vpc.name); - if (vpcPhysicalId === vpcTemplateId && vpc.useCentralEndpoints && centralEndpointAccount) { - cdk.Tags.of(currentVpc).add('accelerator:use-central-endpoints', 'true'); - cdk.Tags.of(currentVpc).add( - 'accelerator:central-endpoints-account-id', - accountsConfig.getAccountId(centralEndpointAccount!), - ); - } - } - } - - private getCentralEndpointAccount(vpcTemplates: VpcConfig[]) { - let centralEndpointAccount; - for (const vpcTemplate of vpcTemplates) { - if (vpcTemplate.interfaceEndpoints?.central && vpcTemplate.account) { - centralEndpointAccount = vpcTemplate.account!; - } - } - return centralEndpointAccount; - } - - private setupInternetGateway( - nestedStackResources: ImportStackResources, - nestedStack: cdk.cloudformation_include.IncludedNestedStack, - vpcConfig: VpcConfig | VpcTemplatesConfig, - ) { - const internetGatewayInfo = nestedStackResources.getResourcesByType(RESOURCE_TYPE.INTERNET_GATEWAY)?.[0]; - if (vpcConfig.internetGateway && internetGatewayInfo) { - const internetGateway = nestedStack.includedTemplate.getResource( - internetGatewayInfo.logicalResourceId, - ) as CfnInternetGateway; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcConfig.name)}InternetGatewayId`), - parameterName: this.scope.getSsmPath(SsmResourceType.IGW, [vpcConfig.name]), - stringValue: internetGateway.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.EC2_IGW, vpcConfig.name); - } - } - - private setupVpnGateway( - nestedStackResources: ImportStackResources, - nestedStack: cdk.cloudformation_include.IncludedNestedStack, - vpcConfig: VpcConfig | VpcTemplatesConfig, - ) { - const virtualPrivateGatewayInfo = nestedStackResources.cfnResources?.filter( - cfnResource => cfnResource.resourceType === RESOURCE_TYPE.VPN_GATEWAY, - )?.[0]; - if (vpcConfig.virtualPrivateGateway && virtualPrivateGatewayInfo) { - const virtualPrivateGateway = nestedStack.includedTemplate.getResource( - virtualPrivateGatewayInfo.logicalResourceId, - ) as CfnVPNGateway; - virtualPrivateGateway.amazonSideAsn = vpcConfig.virtualPrivateGateway.asn; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcConfig.name)}VirtualPrivateGatewayId`), - parameterName: this.scope.getSsmPath(SsmResourceType.VPN_GW, [vpcConfig.name]), - stringValue: virtualPrivateGateway.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.EC2_VPN_GW, vpcConfig.name); - } - } - - private createNatGateways( - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - vpcItem: VpcConfig | VpcTemplatesConfig, - subnets: { [name: string]: CfnSubnet }, - ) { - if (!vpcItem.natGateways || vpcItem.natGateways?.length === 0) { - this.scope.addLogs(LogLevel.WARN, `NAT Gateways are removed from configuration.`); - return; - } - for (const natGatewayItem of vpcItem.natGateways) { - const natGatewayResource = nestedStackResources.getResourceByTypeAndTag( - RESOURCE_TYPE.NAT_GATEWAY, - natGatewayItem.name, - ); - if (!natGatewayResource) continue; // NAT Gateway is not managed by ASEA - const natGateway = vpcStack.getResource(natGatewayResource.logicalResourceId) as CfnNatGateway; - let subnetId = subnets[natGatewayItem.subnet].ref; - if (!subnetId) { - subnetId = this.scope.getExternalResourceParameter( - this.scope.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, natGateway.subnetId]), - ); - } - if (subnetId) { - // Update SubnetId only if subnet is created - natGateway.subnetId = subnetId; - } - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(natGatewayItem.name)}NatGatewayId`), - parameterName: this.scope.getSsmPath(SsmResourceType.NAT_GW, [vpcItem.name, natGatewayItem.name]), - stringValue: natGateway.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.NAT_GATEWAY, `${vpcItem.name}/${natGatewayItem.name}`); - } - } - - private createSubnets( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - nestedStack: CfnInclude, - ) { - const subnets: { [name: string]: CfnSubnet } = {}; - for (const subnetItem of vpcItem.subnets ?? []) { - const subnetResource = nestedStackResources.getResourceByTypeAndTag(RESOURCE_TYPE.SUBNET, subnetItem.name); - if (!subnetResource) continue; - const subnet = nestedStack.getResource(subnetResource.logicalResourceId) as CfnSubnet; - subnet.cidrBlock = subnetItem.ipv4CidrBlock; - // LZA Config accepts only 'a' for 'us-east-1a' or integer - subnet.availabilityZone = `${vpcItem.region}${subnetItem.availabilityZone}`; - subnet.mapPublicIpOnLaunch = subnetItem.mapPublicIpOnLaunch; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(subnetItem.name)}SubnetId`), - parameterName: this.scope.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - stringValue: subnet.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.EC2_SUBNET, `${vpcItem.name}/${subnetItem.name}`); - subnets[subnetItem.name] = subnet; - } - return subnets; - } - - private createSecurityGroups( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - ) { - const securityGroupsMap = new Map(); - const securityGroupPhysicalIdMap = new Map(); - const securityGroupVpc = vpcItem.name; - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - const existingSecurityGroup = nestedStackResources.getResourceByName('GroupName', securityGroupItem.name); - if (!existingSecurityGroup || !existingSecurityGroup.physicalResourceId) { - continue; - } - const securityGroup = vpcStack.getResource(existingSecurityGroup.logicalResourceId) as CfnSecurityGroup; - this.scope.addLogs( - LogLevel.INFO, - `Adding SSM Parameter for ${pascalCase(vpcItem.name) + pascalCase(securityGroupItem.name)}SecurityGroup`, - ); - - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(securityGroupItem.name)}SecurityGroup`), - parameterName: this.scope.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, securityGroupItem.name]), - stringValue: securityGroup.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.EC2_SECURITY_GROUP, `${vpcItem.name}/${securityGroupItem.name}`); - securityGroupsMap.set(securityGroupItem.name, existingSecurityGroup.logicalResourceId); - securityGroupPhysicalIdMap.set(securityGroupItem.name, existingSecurityGroup.physicalResourceId); - } - - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - const logicalId = securityGroupsMap.get(securityGroupItem.name); - if (!logicalId) continue; - let securityGroupIngressRules: SecurityGroupRuleInfo[] = []; - let securityGroupEgressRules: SecurityGroupRuleInfo[] = []; - securityGroupIngressRules = this.processSecurityGroupIngressSources( - securityGroupItem.inboundRules, - securityGroupIngressRules, - securityGroupsMap, - securityGroupVpc, - ); - securityGroupEgressRules = this.processSecurityGroupEgressSources( - securityGroupItem.outboundRules, - securityGroupEgressRules, - securityGroupsMap, - securityGroupVpc, - ); - - const securityGroup = vpcStack.getResource(logicalId) as CfnSecurityGroup; - this.updateSecurityGroupIngressRules( - securityGroupIngressRules, - securityGroupPhysicalIdMap, - securityGroup, - securityGroupVpc, - ); - this.updateSecurityGroupEgressRules( - securityGroupEgressRules, - securityGroupPhysicalIdMap, - securityGroup, - securityGroupVpc, - ); - } - } - - private processSecurityGroupSources = ( - securityGroupRuleItem: SecurityGroupRuleConfig, - ruleProps: { - protocol: cdk.aws_ec2.Protocol; - type?: string; - from?: number; - to?: number; - }, - securityGroupsMap: Map, - securityGroupVpc: string, - ) => { - const securityGroupRules: SecurityGroupRuleInfo[] = []; - securityGroupRuleItem.sources.forEach(sourceItem => { - if (isNetworkType('NonEmptyString', sourceItem)) { - securityGroupRules.push({ - ...ruleProps, - source: sourceItem, - sourceValue: sourceItem, - description: securityGroupRuleItem.description, - }); - } - if (isNetworkType('ISubnetSourceConfig', sourceItem)) { - const sourceVpcItem = getVpcConfig(this.scope.vpcsInScope, sourceItem.vpc); - sourceItem.subnets.forEach(subnet => - securityGroupRules.push({ - ...ruleProps, - source: `${sourceVpcItem.name}/${subnet}`, - sourceValue: getSubnetConfig(sourceVpcItem, subnet).ipv4CidrBlock!, - sourceType: 'subnet', - description: securityGroupRuleItem.description, - }), - ); - } - if (isNetworkType('ISecurityGroupSourceConfig', sourceItem)) { - sourceItem.securityGroups.forEach(securityGroup => { - const securityGroupId = this.getSecurityGroupId(securityGroupsMap, securityGroup, securityGroupVpc); - if (!securityGroupId) return; - //We do not currently account for cross account or cross vpc sgs, this is not natively supported in LZA. - securityGroupRules.push({ - ...ruleProps, - source: securityGroup, - sourceValue: securityGroupsMap.get(securityGroup) ?? securityGroupId, - sourceType: 'sg', - description: securityGroupRuleItem.description, - }); - }); - } - }); - return securityGroupRules; - }; - - private getSecurityGroupId(securityGroupsMap: Map, securityGroup: string, securityGroupVpc: string) { - let securityGroupId = undefined; - const securityGroupFromSSMParam = this.scope.getExternalResourceParameter( - this.scope.getSsmPath(SsmResourceType.SECURITY_GROUP, [securityGroupVpc, securityGroup]), - ); - // This sets SG if securityGroup exists in ASEA SGs - if (securityGroupsMap.get(securityGroup)) { - securityGroupId = securityGroupsMap.get(securityGroup); - } else if (securityGroupFromSSMParam) { - // This sets SG equal to value if securityGroup exists as a security Group created by LZA - securityGroupId = securityGroupFromSSMParam; - } else { - if (!securityGroupId && this.props.stage === AcceleratorStage.POST_IMPORT_ASEA_RESOURCES) { - throw new Error( - `Security Group Source ${securityGroup} was not found in ASEA SecurityGroup Map or in SSM Parameter path for ${this.scope.getSsmPath( - SsmResourceType.SECURITY_GROUP, - [securityGroupVpc, securityGroup], - )}`, - ); - } - } - return securityGroupId; - } - - private processSecurityGroupIngressSources( - securityGroupRuleIngressItems: SecurityGroupRuleConfig[], - securityGroupIngressRules: SecurityGroupRuleInfo[], - securityGroupsMap: Map, - securityGroupVpc: string, - ) { - for (const ingressRuleItem of securityGroupRuleIngressItems) { - securityGroupIngressRules.push( - ...this.processTcpSources(ingressRuleItem, securityGroupsMap, securityGroupVpc), - ...this.processUdpSources(ingressRuleItem, securityGroupsMap, securityGroupVpc), - ...this.processTypeSources(ingressRuleItem, securityGroupsMap, securityGroupVpc), - ); - } - - return securityGroupIngressRules; - } - - private processSecurityGroupEgressSources( - securityGroupRuleEgressItems: SecurityGroupRuleConfig[], - securityGroupEgressRules: SecurityGroupRuleInfo[], - securityGroupsMap: Map, - securityGroupVpc: string, - ) { - for (const egressRuleItem of securityGroupRuleEgressItems) { - securityGroupEgressRules.push( - ...this.processTcpSources(egressRuleItem, securityGroupsMap, securityGroupVpc), - ...this.processUdpSources(egressRuleItem, securityGroupsMap, securityGroupVpc), - ...this.processTypeSources(egressRuleItem, securityGroupsMap, securityGroupVpc), - ); - } - return securityGroupEgressRules; - } - - private processTypeSources = ( - securityGroupRuleItem: SecurityGroupRuleConfig, - securityGroupsMap: Map, - securityGroupVpc: string, - ) => { - const securityGroupRules: SecurityGroupRuleInfo[] = []; - - for (const ruleType of securityGroupRuleItem.types ?? []) { - if (ruleType === 'ALL') { - const defaultRuleProps = { - protocol: cdk.aws_ec2.Protocol.ALL, - type: ruleType, - }; - securityGroupRules.push( - ...this.processSecurityGroupSources( - securityGroupRuleItem, - defaultRuleProps, - securityGroupsMap, - securityGroupVpc, - ), - ); - } else { - const defaultRuleProps = { - protocol: cdk.aws_ec2.Protocol.TCP, - type: ruleType, - from: TCP_PROTOCOLS_PORT[ruleType], - to: TCP_PROTOCOLS_PORT[ruleType], - }; - securityGroupRules.push( - ...this.processSecurityGroupSources( - securityGroupRuleItem, - defaultRuleProps, - securityGroupsMap, - securityGroupVpc, - ), - ); - } - } - return securityGroupRules; - }; - - private processUdpSources = ( - securityGroupRuleItem: SecurityGroupRuleConfig, - securityGroupsMap: Map, - securityGroupVpc: string, - ) => { - const securityGroupRules: SecurityGroupRuleInfo[] = []; - for (const tcpPort of securityGroupRuleItem.udpPorts ?? []) { - const defaultRuleProps = { - protocol: cdk.aws_ec2.Protocol.UDP, - from: tcpPort, - to: tcpPort, - }; - securityGroupRules.push( - ...this.processSecurityGroupSources( - securityGroupRuleItem, - defaultRuleProps, - securityGroupsMap, - securityGroupVpc, - ), - ); - } - return securityGroupRules; - }; - - private processTcpSources = ( - securityGroupRuleItem: SecurityGroupRuleConfig, - securityGroupsMap: Map, - securityGroupVpc: string, - ) => { - const securityGroupRules: SecurityGroupRuleInfo[] = []; - for (const tcpPort of securityGroupRuleItem.tcpPorts ?? []) { - const defaultRuleProps = { - protocol: cdk.aws_ec2.Protocol.TCP, - from: tcpPort, - to: tcpPort, - }; - securityGroupRules.push( - ...this.processSecurityGroupSources( - securityGroupRuleItem, - defaultRuleProps, - securityGroupsMap, - securityGroupVpc, - ), - ); - } - return securityGroupRules; - }; - - private updateSecurityGroupIngressRules( - securityGroupLzaConfigIngressRules: SecurityGroupRuleInfo[], - securityGroupPhysicalIdMap: Map, - securityGroup: cdk.aws_ec2.CfnSecurityGroup, - securityGroupVpc: string, - ) { - let existingIngressRulesToBeUpdated: CfnSecurityGroup.IngressProperty[] = []; - existingIngressRulesToBeUpdated = this.mapConfigRulesToIngressProperties( - securityGroupLzaConfigIngressRules, - securityGroupPhysicalIdMap, - securityGroupVpc, - ); - - if (existingIngressRulesToBeUpdated && existingIngressRulesToBeUpdated.length > 0) { - this.scope.addLogs(LogLevel.INFO, `'Updating Ingress rules on Security Group ${securityGroup.groupName}`); - this.scope.addLogs( - LogLevel.INFO, - `Pushing on ingress rule(s): ${JSON.stringify(existingIngressRulesToBeUpdated)}`, - ); - if (securityGroup) { - securityGroup.securityGroupIngress = existingIngressRulesToBeUpdated; - } - } - return securityGroup; - } - - private updateSecurityGroupEgressRules( - securityGroupLzaConfigEgressRules: SecurityGroupRuleInfo[], - securityGroupPhysicalIdMap: Map, - securityGroup: cdk.aws_ec2.CfnSecurityGroup, - securityGroupVpc: string, - ) { - let existingEgressRulesToBeUpdated: CfnSecurityGroup.EgressProperty[] = []; - existingEgressRulesToBeUpdated = this.mapConfigRulesToEgressProperties( - securityGroupLzaConfigEgressRules, - securityGroupPhysicalIdMap, - securityGroupVpc, - ); - - if (existingEgressRulesToBeUpdated && existingEgressRulesToBeUpdated.length > 0) { - this.scope.addLogs(LogLevel.INFO, `Updating Egress rules on SG: ${securityGroup.groupName}`); - this.scope.addLogs(LogLevel.INFO, `Pushing on egress rule(s): ${JSON.stringify(existingEgressRulesToBeUpdated)}`); - if (securityGroup) { - securityGroup.securityGroupEgress = existingEgressRulesToBeUpdated; - } - } - return securityGroup; - } - - private mapConfigRulesToEgressProperties( - securityGroupLzaConfigRules: SecurityGroupRuleInfo[], - securityGroupPhysicalIdMap: Map, - securityGroupVpc: string, - ) { - const existingEgressRulesToBeUpdated: CfnSecurityGroup.IngressProperty[] = []; - securityGroupLzaConfigRules.forEach(configEgressRule => { - if (configEgressRule.sourceType === 'sg') { - const securityGroupId = this.getSecurityGroupId( - securityGroupPhysicalIdMap, - configEgressRule.source, - securityGroupVpc, - ); - const existingEgressRuleToBeUpdated: CfnSecurityGroup.EgressProperty = { - ipProtocol: configEgressRule.protocol, - description: configEgressRule.description, - destinationSecurityGroupId: securityGroupId, - fromPort: configEgressRule.from, - toPort: configEgressRule.to, - }; - existingEgressRulesToBeUpdated.push(existingEgressRuleToBeUpdated); - } - - if (configEgressRule.sourceType === 'pl') { - const existingEgressRuleToBeUpdated: CfnSecurityGroup.EgressProperty = { - ipProtocol: configEgressRule.protocol, - description: configEgressRule.description, - destinationPrefixListId: configEgressRule.source, - }; - existingEgressRulesToBeUpdated.push(existingEgressRuleToBeUpdated); - } - if (configEgressRule.sourceType === 'subnet') { - const existingEgressRuleToBeUpdated: CfnSecurityGroup.EgressProperty = { - ipProtocol: configEgressRule.protocol, - description: configEgressRule.description, - cidrIp: configEgressRule.sourceValue, - fromPort: configEgressRule.from, - toPort: configEgressRule.to, - }; - existingEgressRulesToBeUpdated.push(existingEgressRuleToBeUpdated); - } - const sourceCidrType = this.checkCidrFromSource(configEgressRule.source); - - if (sourceCidrType === 'cidrIpv4') { - const existingEgressRuleToBeUpdated: CfnSecurityGroup.EgressProperty = { - ipProtocol: configEgressRule.protocol, - description: configEgressRule.description, - cidrIp: configEgressRule.source, - fromPort: configEgressRule.from, - toPort: configEgressRule.to, - }; - existingEgressRulesToBeUpdated.push(existingEgressRuleToBeUpdated); - } - - if (sourceCidrType === 'cidrIpv6') { - const existingEgressRuleToBeUpdated: CfnSecurityGroup.EgressProperty = { - ipProtocol: configEgressRule.protocol, - description: configEgressRule.description, - cidrIpv6: configEgressRule.source, - fromPort: configEgressRule.from, - toPort: configEgressRule.to, - }; - existingEgressRulesToBeUpdated.push(existingEgressRuleToBeUpdated); - } - }); - return existingEgressRulesToBeUpdated; - } - - private mapConfigRulesToIngressProperties( - securityGroupLzaConfigIngressRules: SecurityGroupRuleInfo[], - securityGroupPhysicalIdMap: Map, - securityGroupVpc: string, - ) { - const existingIngressRulesToBeUpdated: CfnSecurityGroup.IngressProperty[] = []; - securityGroupLzaConfigIngressRules.forEach(configIngressRule => { - if (configIngressRule.sourceType === 'sg') { - const securityGroupId = this.getSecurityGroupId( - securityGroupPhysicalIdMap, - configIngressRule.source, - securityGroupVpc, - ); - const existingIngressRuleToBeUpdated: CfnSecurityGroup.IngressProperty = { - ipProtocol: configIngressRule.protocol, - description: configIngressRule.description, - sourceSecurityGroupId: securityGroupId, - fromPort: configIngressRule.from, - toPort: configIngressRule.to, - }; - existingIngressRulesToBeUpdated.push(existingIngressRuleToBeUpdated); - } - - if (configIngressRule.sourceType === 'pl') { - const existingIngressRuleToBeUpdated: CfnSecurityGroup.IngressProperty = { - ipProtocol: configIngressRule.protocol, - description: configIngressRule.description, - sourcePrefixListId: configIngressRule.source, - }; - existingIngressRulesToBeUpdated.push(existingIngressRuleToBeUpdated); - } - if (configIngressRule.sourceType === 'subnet') { - const existingIngressRuleToBeUpdated: CfnSecurityGroup.IngressProperty = { - ipProtocol: configIngressRule.protocol, - description: configIngressRule.description, - cidrIp: configIngressRule.sourceValue, - fromPort: configIngressRule.from, - toPort: configIngressRule.to, - }; - existingIngressRulesToBeUpdated.push(existingIngressRuleToBeUpdated); - } - const sourceCidrType = this.checkCidrFromSource(configIngressRule.source); - - if (sourceCidrType === 'cidrIpv4') { - const existingIngressRuleToBeUpdated: CfnSecurityGroup.IngressProperty = { - ipProtocol: configIngressRule.protocol, - description: configIngressRule.description, - cidrIp: configIngressRule.source, - fromPort: configIngressRule.from, - toPort: configIngressRule.to, - }; - existingIngressRulesToBeUpdated.push(existingIngressRuleToBeUpdated); - } - - if (sourceCidrType === 'cidrIpv6') { - const existingIngressRuleToBeUpdated: CfnSecurityGroup.IngressProperty = { - ipProtocol: configIngressRule.protocol, - description: configIngressRule.description, - cidrIpv6: configIngressRule.source, - fromPort: configIngressRule.from, - toPort: configIngressRule.to, - }; - existingIngressRulesToBeUpdated.push(existingIngressRuleToBeUpdated); - } - }); - return existingIngressRulesToBeUpdated; - } - - private checkCidrFromSource(source: string) { - let sourceType; - if (this.isValidIpv4Cidr(source)) { - sourceType = 'cidrIpv4'; - } - if (this.isValidIpv6Cidr(source)) { - sourceType = 'cidrIpv6'; - } - return sourceType; - } - - private createTransitGatewayAttachments( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - nestedStack: CfnInclude, - subnetRefs: { [name: string]: CfnSubnet }, - ) { - const tgwAttachmentMap: { [name: string]: string } = {}; - const tgwAttachmentResources = nestedStackResources.getResourcesByType(RESOURCE_TYPE.TGW_ATTACHMENT); - if (tgwAttachmentResources.length === 0) return; - if (vpcItem.transitGatewayAttachments?.length === 0 && tgwAttachmentResources.length > 0) { - this.scope.addLogs(LogLevel.WARN, `TGW Attachment is removed from VPC "${vpcItem.name}" configuration`); - return; - } - for (const tgwAttachmentItem of vpcItem.transitGatewayAttachments ?? []) { - const tgwAttachmentResource = nestedStackResources.getResourceByTypeAndTag( - RESOURCE_TYPE.TGW_ATTACHMENT, - `${tgwAttachmentItem.name}`, - ); - if (!tgwAttachmentResource) continue; - const tgwAttachment = nestedStack.getResource( - tgwAttachmentResource.logicalResourceId, - ) as CfnTransitGatewayAttachment; - const subnetIds: string[] = []; - tgwAttachmentItem.subnets.forEach(subnet => { - let subnetId = subnetRefs[subnet].ref; - if (!subnetId) { - subnetId = this.scope.getExternalResourceParameter( - this.scope.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnet]), - ); - } - if (subnetId) subnetIds.push(subnetId); - }); - // Only Subnets can be updated in TGW Attachment. - tgwAttachment.subnetIds = subnetIds; - this.scope.addSsmParameter({ - logicalId: pascalCase( - `SsmParam${pascalCase(vpcItem.name) + pascalCase(tgwAttachmentItem.name)}TransitGatewayAttachmentId`, - ), - parameterName: this.scope.getSsmPath(SsmResourceType.TGW_ATTACHMENT, [vpcItem.name, tgwAttachmentItem.name]), - stringValue: tgwAttachment.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource( - AseaResourceType.TRANSIT_GATEWAY_ATTACHMENT, - `${vpcItem.name}/${tgwAttachmentItem.name}`, - ); - tgwAttachmentMap[tgwAttachmentItem.name] = tgwAttachmentResource.logicalResourceId; - } - return tgwAttachmentMap; - } - - private getTgwRouteTableId(routeTableName: string) { - if (!this.props.globalConfig.externalLandingZoneResources?.templateMap) { - return; - } - const mapping = this.props.globalConfig.externalLandingZoneResources.templateMap; - const tgwStackMappingKey = Object.keys(mapping).find( - key => - mapping[key].phase === '0' && - mapping[key].accountKey === this.stackInfo.accountKey && - mapping[key].region === this.stackInfo.region, - ); - if (!tgwStackMappingKey) { - return; - } - const tgwRouteTableResources = this.filterResourcesByType( - mapping[tgwStackMappingKey].cfnResources, - RESOURCE_TYPE.TRANSIT_GATEWAY_ROUTE_TABLE, - ); - const tgwRouteTableResource = this.findResourceByTag(tgwRouteTableResources, routeTableName); - return tgwRouteTableResource?.physicalResourceId; - } - - private createTransitGatewayRouteTablePropagation( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - nestedStack: CfnInclude, - tgwAttachMap: { [name: string]: string }, - ) { - const tgwPropagations = nestedStackResources.getResourcesByType(RESOURCE_TYPE.TGW_PROPAGATION); - if (tgwPropagations.length === 0) return; - const createPropagations = (tgwAttachmentItem: TransitGatewayAttachmentConfig) => { - for (const routeTableItem of tgwAttachmentItem.routeTablePropagations ?? []) { - const tgwPropagationRes = tgwPropagations.find( - propagation => - propagation.resourceMetadata['Properties'].TransitGatewayAttachmentId.Ref === - tgwAttachMap[tgwAttachmentItem.name] && - propagation.resourceMetadata['Properties'].TransitGatewayRouteTableId === - this.getTgwRouteTableId(routeTableItem), - ); - if (!tgwPropagationRes) continue; - const tgwPropagation = nestedStack.getResource( - tgwPropagationRes.logicalResourceId, - ) as cdk.aws_ec2.CfnTransitGatewayRouteTablePropagation; - if (!tgwPropagation) { - this.scope.addLogs( - LogLevel.WARN, - `TGW Propagation for "${tgwAttachmentItem.name}/${routeTableItem}" exists in Mapping but not found in resources`, - ); - } - // Propagation resourceId is not used anywhere in LZA. No need of SSM Parameter. - this.scope.addAseaResource( - AseaResourceType.TRANSIT_GATEWAY_PROPAGATION, - `${tgwAttachmentItem.transitGateway.account}/${tgwAttachmentItem.transitGateway.name}/${tgwAttachmentItem.name}/${routeTableItem}`, - ); - } - }; - if (vpcItem.transitGatewayAttachments?.length === 0) { - this.scope.addLogs(LogLevel.WARN, `TGW Attachment is removed from VPC "${vpcItem.name}" configuration`); - return; - } - (vpcItem.transitGatewayAttachments ?? []).map(createPropagations); - } - - private createTransitGatewayRouteTableAssociation( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - nestedStack: CfnInclude, - tgwAttachMap: { [name: string]: string }, - ) { - const tgwAssociations = nestedStackResources.getResourcesByType(RESOURCE_TYPE.TGW_ASSOCIATION); - if (tgwAssociations.length === 0) return; - const createAssociations = (tgwAttachmentItem: TransitGatewayAttachmentConfig) => { - for (const routeTableItem of tgwAttachmentItem.routeTableAssociations ?? []) { - const tgwAssociationRes = tgwAssociations.find( - propagation => - propagation.resourceMetadata['Properties'].TransitGatewayAttachmentId.Ref === - tgwAttachMap[tgwAttachmentItem.name] && - propagation.resourceMetadata['Properties'].TransitGatewayRouteTableId === - this.getTgwRouteTableId(routeTableItem), - ); - if (!tgwAssociationRes) continue; - const tgwAssociation = nestedStack.getResource( - tgwAssociationRes.logicalResourceId, - ) as cdk.aws_ec2.CfnTransitGatewayRouteTableAssociation; - if (!tgwAssociation) { - this.scope.addLogs( - LogLevel.WARN, - `TGW Association for "${tgwAttachmentItem.name}/${routeTableItem}" exists in Mapping but not found in resources`, - ); - } - // Propagation resourceId is not used anywhere in LZA. No need of SSM Parameter. - this.scope.addAseaResource( - AseaResourceType.TRANSIT_GATEWAY_ASSOCIATION, - `${tgwAttachmentItem.transitGateway.account}/${tgwAttachmentItem.transitGateway.name}/${tgwAttachmentItem.name}/${routeTableItem}`, - ); - } - }; - if (vpcItem.transitGatewayAttachments?.length === 0) { - this.scope.addLogs(LogLevel.WARN, `TGW Attachment is removed from VPC "${vpcItem.name}" configuration`); - return; - } - (vpcItem.transitGatewayAttachments ?? []).map(createAssociations); - } - - createRouteTables( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - ) { - const existingRouteTablesMapping = nestedStackResources.getResourcesByType(RESOURCE_TYPE.ROUTE_TABLE); - for (const routeTableItem of vpcItem.routeTables ?? []) { - const routeTableResource = this.findResourceByTag(existingRouteTablesMapping, routeTableItem.name); - if (!routeTableResource) continue; - const routeTable = vpcStack.getResource(routeTableResource.logicalResourceId) as CfnRouteTable; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name)}${pascalCase(routeTableItem.name)}RouteTableId`), - parameterName: this.scope.getSsmPath(SsmResourceType.ROUTE_TABLE, [vpcItem.name, routeTableItem.name]), - stringValue: routeTable.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.ROUTE_TABLE, `${vpcItem.name}/${routeTableItem.name}`); - } - } - - /** - * Find VPC Resource by tag and nestedStackInfo of VPC - * @param vpcName - * @returns - */ - private getVpcResourceByTag(vpcName: string) { - for (const [, nestedStackInfo] of Object.entries(this.scope.nestedStackResources ?? {})) { - const vpcResource = nestedStackInfo.getResourceByTypeAndTag(RESOURCE_TYPE.VPC, vpcName); - if (vpcResource) { - return { - nestedStackResources: nestedStackInfo, - nestedStack: this.scope.nestedStacks[nestedStackInfo.getStackKey()], - resource: vpcResource, - }; - } - } - return; - } - - private deleteAseaNetworkFirewallRuleGroups(nestedStackResources: ImportStackResources) { - const networkFirewallConfig = this.props.networkConfig.centralNetworkServices?.networkFirewall; - const firewallRuleGroupResources = nestedStackResources.getResourcesByType( - RESOURCE_TYPE.NETWORK_FIREWALL_RULE_GROUP, - ); - if (firewallRuleGroupResources.length === 0) { - return; - } - for (const firewallRuleGroupResource of firewallRuleGroupResources) { - const aseaManagedRuleGroupName: string = firewallRuleGroupResource.resourceMetadata['Properties'].RuleGroupName; - const ruleItem = networkFirewallConfig?.rules.find(group => group.name === aseaManagedRuleGroupName); - if (!ruleItem) { - this.scope.addLogs( - LogLevel.INFO, - `No Firewall Rule Group found in configuration and firewall policy present in resource mapping`, - ); - continue; - } - - this.scope.addLogs(LogLevel.INFO, `Removing NFW Rule Group: ${firewallRuleGroupResource.logicalResourceId}`); - this.scope.addDeleteFlagForNestedResource( - nestedStackResources.getStackKey(), - firewallRuleGroupResource.logicalResourceId, - ); - } - } - - private deleteAseaNetworkFirewallPolicy(nestedStack: ImportStackResources) { - const firewallResources = nestedStack.getResourcesByType(RESOURCE_TYPE.NETWORK_FIREWALL_POLICY); - if (firewallResources.length === 0) { - return; - } - const aseaManagedPolicy = firewallResources[0]; - - this.scope.addLogs(LogLevel.INFO, `Removing NFW Policy: ${aseaManagedPolicy.logicalResourceId}`); - this.scope.addDeleteFlagForNestedResource(nestedStack.getStackKey(), aseaManagedPolicy.logicalResourceId); - } - - private addFirewallLoggingConfiguration( - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - firewallItem: NfwFirewallConfig, - ) { - const loggingConfigurationResource = nestedStackResources.getResourcesByType( - RESOURCE_TYPE.NETWORK_FIREWALL_LOGGING, - ); - if (!loggingConfigurationResource) return; - const loggingConfiguration = vpcStack.getResource( - loggingConfigurationResource[0].logicalResourceId, - ) as cdk.aws_networkfirewall.CfnLoggingConfiguration; - const destinationConfigs: cdk.aws_networkfirewall.CfnLoggingConfiguration.LogDestinationConfigProperty[] = []; - for (const logItem of firewallItem.loggingConfiguration ?? []) { - if (logItem.destination === 'cloud-watch-logs') { - // Create log group and log configuration - const logGroup = new cdk.aws_logs.LogGroup( - vpcStack, - `${this.scope.acceleratorPrefix}/Nfw/${firewallItem.name}/${pascalCase(logItem.type)}`, - { - encryptionKey: this.scope.cloudwatchKey, - retention: this.props.globalConfig.cloudwatchLogRetentionInDays, - logGroupName: `${this.scope.acceleratorPrefix}/Nfw/${firewallItem.name}/${pascalCase(logItem.type)}`, - }, - ); - destinationConfigs.push({ - logDestination: { - logGroup: logGroup.logGroupName, - }, - logDestinationType: 'CloudWatchLogs', - logType: logItem.type, - }); - } - - if (logItem.destination === 's3') { - destinationConfigs.push({ - logDestination: { - bucketName: this.scope.firewallBucket.bucketName, - prefix: 'firewall', - }, - logDestinationType: 'S3', - logType: logItem.type, - }); - } - } - loggingConfiguration.loggingConfiguration = { - logDestinationConfigs: destinationConfigs, - }; - } - - private createNetworkFirewall( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - vpcId: string, - subnets: { [name: string]: CfnSubnet }, - ) { - const networkFirewallConfig = this.props.networkConfig.centralNetworkServices?.networkFirewall; - const firewallsConfig = networkFirewallConfig?.firewalls.filter( - firewallConfig => firewallConfig && firewallConfig.vpc === vpcItem.name, - ); - const firewallResources = nestedStackResources.getResourcesByType(RESOURCE_TYPE.NETWORK_FIREWALL); - - // If there are not any ASEA managed firewalls in resource mapping continue - if (firewallResources.length === 0) { - return; - } - - // Delete any ASEA managed firewalls if no firewalls are found in configuration - if (!firewallsConfig || firewallsConfig.length === 0) { - this.scope.addLogs(LogLevel.INFO, `No Firewall found in configuration`); - for (const firewallResource of firewallResources) { - this.scope.addLogs(LogLevel.WARN, `Removing firewall ${firewallResource.physicalResourceId} from ASEA stack`); - this.scope.addDeleteFlagForAseaResource({ - logicalId: firewallResource.logicalResourceId, - type: RESOURCE_TYPE.NETWORK_FIREWALL, - }); - } - return; - } - - // Delete any ASEA managed firewalls that don't exist in configuration. - for (const firewallResource of firewallResources) { - const firewallName = firewallResource.resourceMetadata['Properties']['FirewallName']; - const firewallConfig = firewallsConfig.find(nfw => nfw.name === firewallName); - if (!firewallConfig) { - this.scope.addLogs(LogLevel.WARN, `Removing firewall ${firewallResource.physicalResourceId} from ASEA stack`); - this.scope.addDeleteFlagForNestedResource( - nestedStackResources.getStackKey(), - firewallResource.logicalResourceId, - ); - return; - } - } - - for (const firewallItem of firewallsConfig) { - const firewallResource = this.findResourceByName(firewallResources, 'FirewallName', firewallItem.name); - if (!firewallResource) continue; - const subnetIds: string[] = []; - firewallItem.subnets.forEach(subnet => { - let subnetId = subnets[subnet].ref; - if (!subnetId) { - subnetId = this.scope.getExternalResourceParameter( - this.scope.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnet]), - ); - } - if (subnetId) subnetIds.push(subnetId); - }); - - if (this.props.stage === AcceleratorStage.IMPORT_ASEA_RESOURCES) { - const firewallResource = this.findResourceByName(firewallResources, 'FirewallName', firewallItem.name); - if (!firewallResource || !firewallResource.physicalResourceId) continue; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(firewallItem.vpc) + pascalCase(firewallItem.name)}FirewallArn`), - parameterName: this.scope.getSsmPath(SsmResourceType.NFW, [firewallItem.vpc, firewallItem.name]), - stringValue: firewallResource.physicalResourceId, - scope: nestedStackResources.getStackKey(), - }); - } - - this.scope.addAseaResource(AseaResourceType.NFW, firewallItem.name); - if (this.props.stage === AcceleratorStage.POST_IMPORT_ASEA_RESOURCES) { - const region = nestedStackResources.stackMapping.region; - const partition = this.props.partition; - const delegatedAdminAccountId = this.props.accountsConfig.getAccountId( - this.props.networkConfig.centralNetworkServices?.delegatedAdminAccount ?? '', - ); - const firewallPolicyArn = `arn:${partition}:network-firewall:${region}:${delegatedAdminAccountId}:firewall-policy/${firewallItem.firewallPolicy}`; - const firewall = NetworkFirewall.includedCfnResource(vpcStack, firewallResource.logicalResourceId, { - firewallPolicyArn: firewallPolicyArn, - name: firewallItem.name, - description: firewallItem.description, - subnets: subnetIds, - vpcId: vpcId, - deleteProtection: firewallItem.deleteProtection, - firewallPolicyChangeProtection: firewallItem.firewallPolicyChangeProtection, - subnetChangeProtection: firewallItem.subnetChangeProtection, - }); - this.addFirewallLoggingConfiguration(nestedStackResources, vpcStack, firewallItem); - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(firewallItem.vpc) + pascalCase(firewallItem.name)}FirewallArn`), - parameterName: this.scope.getSsmPath(SsmResourceType.NFW, [firewallItem.vpc, firewallItem.name]), - stringValue: firewall.attrFirewallArn, - scope: nestedStackResources.getStackKey(), - }); - } - } - } - - /** - * Returns true if the given CIDR is valid - * @param cidr - * @returns - */ - private isValidIpv4Cidr(cidr: string): boolean { - try { - IPv4CidrRange.fromCidr(cidr); - } catch (e) { - return false; - } - return true; - } - - /** - * Returns true if valid CIDR is valid - * @param cidr - * @returns - */ - private isValidIpv6Cidr(cidr: string): boolean { - try { - IPv6CidrRange.fromCidr(cidr); - } catch (e) { - return false; - } - return true; - } - - private createNetworkFirewallResources( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - vpcId: string, - subnets: { [name: string]: CfnSubnet }, - ) { - if (this.props.stage === AcceleratorStage.IMPORT_ASEA_RESOURCES) { - this.createNetworkFirewall(vpcItem, nestedStackResources, vpcStack, vpcId, subnets); - } - if (this.props.stage === AcceleratorStage.POST_IMPORT_ASEA_RESOURCES) { - this.deleteAseaNetworkFirewallRuleGroups(nestedStackResources); - this.deleteAseaNetworkFirewallPolicy(nestedStackResources); - this.createNetworkFirewall(vpcItem, nestedStackResources, vpcStack, vpcId, subnets); - } - } - - private gatewayEndpoints( - vpcItem: VpcConfig | VpcTemplatesConfig, - nestedStackResources: ImportStackResources, - vpcStack: CfnInclude, - ) { - /** - * Function to get S3 and DynamoDB route table ids - * @param routeTableItem {@link RouteTableConfig} - * @param routeTableId string - */ - const getS3DynamoDbRouteTableIds = ( - routeTableItem: RouteTableConfig, - routeTableId: string, - s3EndpointRouteTables: string[], - dynamodbEndpointRouteTables: string[], - ) => { - for (const routeTableEntryItem of routeTableItem.routes ?? []) { - // Route: S3 Gateway Endpoint - if (routeTableEntryItem.target === 's3') { - if (!s3EndpointRouteTables.find(item => item === routeTableId)) { - s3EndpointRouteTables.push(routeTableId); - } - } - - // Route: DynamoDb Gateway Endpoint - if (routeTableEntryItem.target === 'dynamodb') { - if (!dynamodbEndpointRouteTables.find(item => item === routeTableId)) { - dynamodbEndpointRouteTables.push(routeTableId); - } - } - } - }; - const s3EndpointRouteTables: string[] = []; - const dynamodbEndpointRouteTables: string[] = []; - for (const routeTableItem of vpcItem.routeTables ?? []) { - const routeTableId = this.scope.getExternalResourceParameter( - this.scope.getSsmPath(SsmResourceType.ROUTE_TABLE, [vpcItem.name, routeTableItem.name]), - ); - if (!routeTableId) continue; // Route table is not created yet - getS3DynamoDbRouteTableIds(routeTableItem, routeTableId, s3EndpointRouteTables, dynamodbEndpointRouteTables); - } - // ASEA Only creates VPC Endpoints in VPC Nested Stack - const gatewayEndpointResources = nestedStackResources.getResourcesByType(RESOURCE_TYPE.VPC_ENDPOINT); - if (gatewayEndpointResources.length === 0) { - return; - } else if (!vpcItem.gatewayEndpoints?.endpoints) { - this.scope.addLogs(LogLevel.WARN, `Endpoints are removed from configuration`); - return; - } - - for (const endpointItem of vpcItem.gatewayEndpoints.endpoints ?? []) { - const gatewayEndpointResource = gatewayEndpointResources.find( - cfnResource => - cfnResource.resourceMetadata['Properties'].ServiceName['Fn::Join'][1].at(-1) === `.${endpointItem.service}`, - ); - if (!gatewayEndpointResource) { - continue; - } - const endpoint = vpcStack.getResource(gatewayEndpointResource.logicalResourceId) as cdk.aws_ec2.CfnVPCEndpoint; - const routeTableIds = endpoint.routeTableIds; - if (!routeTableIds) { - endpoint.routeTableIds = endpointItem.service === 's3' ? s3EndpointRouteTables : dynamodbEndpointRouteTables; - } else { - (endpointItem.service === 's3' ? s3EndpointRouteTables : dynamodbEndpointRouteTables).forEach(routeTableId => { - if (!routeTableIds.includes(routeTableId)) { - routeTableIds.push(routeTableId); - } - }); - } - endpoint.routeTableIds = routeTableIds; - this.scope.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(endpointItem.service)}EndpointId`), - parameterName: this.scope.getSsmPath(SsmResourceType.VPC_ENDPOINT, [vpcItem.name, endpointItem.service]), - stringValue: endpoint.ref, - scope: nestedStackResources.getStackKey(), - }); - this.scope.addAseaResource(AseaResourceType.VPC_ENDPOINT, `${vpcItem.name}/${endpointItem.service}`); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/config-repository.ts b/source/packages/@aws-accelerator/accelerator/lib/config-repository.ts deleted file mode 100644 index fb5093f..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/config-repository.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AccountsConfig, - GlobalConfig, - IamConfig, - NetworkConfig, - OrganizationConfig, - SecurityConfig, - Region, - ControlTowerLandingZoneConfig, -} from '@aws-accelerator/config'; -import * as cdk_extensions from '@aws-cdk-extensions/cdk-extensions'; -import * as cdk from 'aws-cdk-lib'; -import * as s3_assets from 'aws-cdk-lib/aws-s3-assets'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as os from 'os'; -import * as path from 'path'; - -export interface ConfigRepositoryProps { - readonly repositoryName: string; - readonly repositoryBranchName?: string; - readonly description?: string; - readonly managementAccountEmail: string; - readonly logArchiveAccountEmail: string; - readonly auditAccountEmail: string; - readonly controlTowerEnabled: string; - readonly enableSingleAccountMode: boolean; - /** - * AWS Control Tower Landing Zone configuration - */ - readonly controlTowerLandingZoneConfig?: ControlTowerLandingZoneConfig; -} - -/** - * Class to create AWS accelerator configuration repository and initialize the repository with default configuration - */ -export class ConfigRepository extends Construct { - readonly configRepo: cdk_extensions.Repository; - - constructor(scope: Construct, id: string, props: ConfigRepositoryProps) { - super(scope, id); - - // - // Generate default configuration files - // - const tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), 'config-assets-')); - - let controlTowerEnabledValue = true; - let managementAccountAccessRole = 'AWSControlTowerExecution'; - if (props.controlTowerEnabled.toLowerCase() === 'no') { - controlTowerEnabledValue = false; - managementAccountAccessRole = 'OrganizationAccountAccessRole'; - } - - fs.writeFileSync( - path.join(tempDirPath, GlobalConfig.FILENAME), - yaml.dump( - new GlobalConfig({ - homeRegion: cdk.Stack.of(this).region as Region, - controlTower: { enable: controlTowerEnabledValue, landingZone: props.controlTowerLandingZoneConfig }, - managementAccountAccessRole: managementAccountAccessRole, - }), - ), - 'utf8', - ); - - fs.writeFileSync( - path.join(tempDirPath, AccountsConfig.FILENAME), - yaml.dump( - new AccountsConfig({ - managementAccountEmail: props.managementAccountEmail, - logArchiveAccountEmail: props.logArchiveAccountEmail, - auditAccountEmail: props.auditAccountEmail, - }), - ), - 'utf8', - ); - - fs.writeFileSync(path.join(tempDirPath, IamConfig.FILENAME), yaml.dump(new IamConfig()), 'utf8'); - fs.writeFileSync(path.join(tempDirPath, NetworkConfig.FILENAME), yaml.dump(new NetworkConfig()), 'utf8'); - if (props.enableSingleAccountMode) { - const orgConfig = new OrganizationConfig({ - enable: false, - organizationalUnits: [ - { - name: 'Security', - ignore: undefined, - }, - { - name: 'LogArchive', - ignore: undefined, - }, - ], - organizationalUnitIds: [], - serviceControlPolicies: [], - taggingPolicies: [], - backupPolicies: [], - }); - fs.writeFileSync(path.join(tempDirPath, OrganizationConfig.FILENAME), yaml.dump(orgConfig), 'utf8'); - } else { - fs.writeFileSync( - path.join(tempDirPath, OrganizationConfig.FILENAME), - yaml.dump(new OrganizationConfig()), - 'utf8', - ); - } - - fs.writeFileSync(path.join(tempDirPath, SecurityConfig.FILENAME), yaml.dump(new SecurityConfig()), 'utf8'); - - const configurationDefaultsAssets = new s3_assets.Asset(this, 'ConfigurationDefaultsAssets', { - path: tempDirPath, - }); - - this.configRepo = new cdk_extensions.Repository(this, 'Resource', { - repositoryName: props.repositoryName, - repositoryBranchName: props.repositoryBranchName!, - s3BucketName: configurationDefaultsAssets.bucket.bucketName, - s3key: configurationDefaultsAssets.s3ObjectKey, - }); - } - - /** - * Method to get initialized repository object - * - * @return Returns Initialized repository object. - */ - public getRepository(): cdk_extensions.Repository { - return this.configRepo; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/config-validator.ts b/source/packages/@aws-accelerator/accelerator/lib/config-validator.ts deleted file mode 100644 index 805bae3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/config-validator.ts +++ /dev/null @@ -1,376 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as fs from 'fs'; -import * as path from 'path'; - -import { - AccountsConfig, - AccountsConfigValidator, - CustomizationsConfig, - CustomizationsConfigValidator, - GlobalConfig, - GlobalConfigValidator, - IamConfig, - IamConfigValidator, - NetworkConfig, - NetworkConfigValidator, - OrganizationConfig, - OrganizationConfigValidator, - ReplacementsConfig, - SecurityConfig, - SecurityConfigValidator, - ReplacementsConfigValidator, -} from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { Accelerator } from './accelerator'; -import { getReplacementsConfig } from '../utils/app-utils'; - -const logger = createLogger(['config-validator']); -const configDirPath = process.argv[2]; -const homeRegion = GlobalConfig.loadRawGlobalConfig(configDirPath).homeRegion; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const initErrors: { file: string; message: any }[] = []; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const configErrors: any[] = []; - -const fileNameList = [ - AccountsConfig.FILENAME, - CustomizationsConfig.FILENAME, - GlobalConfig.FILENAME, - IamConfig.FILENAME, - NetworkConfig.FILENAME, - OrganizationConfig.FILENAME, - SecurityConfig.FILENAME, -]; - -const enableSingleAccountMode = process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] - ? process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] === 'true' - : false; - -const props = { - partition: process.env['PARTITION'] ?? 'aws', - region: process.env['AWS_REGION'], - account: process.env['ACCOUNT_ID'], - enableSingleAccountMode: enableSingleAccountMode, - replacementsPresent: areReplacementsPresent(), -}; - -if (configDirPath) { - logger.info(`Config source directory - ${configDirPath}`); - validateConfig(props); -} else { - logger.info('Config source directory undefined !!!'); -} - -function areReplacementsPresent() { - // Matches lookup values excluding account lookups such as {{account Management}} - // Account lookups do not require existence of replacements-config.yaml file - const regex = new RegExp('{{(?!.*(account )).*}}'); - let replacementsPresent = false; - - for (const fileName of fileNameList) { - if ( - fileName === CustomizationsConfig.FILENAME && - !fs.existsSync(path.join(configDirPath, CustomizationsConfig.FILENAME)) - ) { - continue; - } else { - replacementsPresent = checkFileForReplacements(regex, fileName); - } - - if (replacementsPresent) { - break; - } - } - return replacementsPresent; -} - -function checkFileForReplacements(regex: RegExp, fileName: string): boolean { - let replacementsFound = false; - const data = fs.readFileSync(path.join(configDirPath, fileName)); - if (regex.test(data.toString())) { - logger.info(`Found replacement variables in ${fileName}`); - replacementsFound = true; - } - return replacementsFound; -} - -async function validateConfig(props: { - partition: string; - region: string | undefined; - enableSingleAccountMode: boolean; - account: string | undefined; - replacementsPresent: boolean; -}) { - await Accelerator.getManagementAccountCredentials(props.partition); - const orgsEnabled = OrganizationConfig.loadRawOrganizationsConfig(configDirPath).enable; - - // Load accounts config - let accountsConfig: AccountsConfig | undefined = undefined; - try { - accountsConfig = AccountsConfig.load(configDirPath); - await accountsConfig.loadAccountIds(props.partition, props.enableSingleAccountMode, orgsEnabled, accountsConfig); - } catch (e) { - initErrors.push({ file: AccountsConfig.FILENAME, message: e }); - } - - // Load replacements config - let replacementsConfig: ReplacementsConfig | undefined = undefined; - try { - replacementsConfig = getReplacementsConfig(configDirPath, accountsConfig!); - const isOrgsEnabled = OrganizationConfig.loadRawOrganizationsConfig(configDirPath).enable; - await replacementsConfig.loadReplacementValues({ region: homeRegion }, isOrgsEnabled); - } catch (e) { - initErrors.push({ file: ReplacementsConfig.FILENAME, message: e }); - } - - // Load global config - let globalConfig: GlobalConfig | undefined = undefined; - try { - globalConfig = GlobalConfig.load(configDirPath, replacementsConfig); - } catch (e) { - initErrors.push({ file: GlobalConfig.FILENAME, message: e }); - } - - // Load IAM config - let iamConfig: IamConfig | undefined = undefined; - try { - iamConfig = IamConfig.load(configDirPath, replacementsConfig); - } catch (e) { - initErrors.push({ file: IamConfig.FILENAME, message: e }); - } - - // Load network config - let networkConfig: NetworkConfig | undefined = undefined; - try { - networkConfig = NetworkConfig.load(configDirPath, replacementsConfig); - } catch (e) { - initErrors.push({ file: NetworkConfig.FILENAME, message: e }); - } - - // Load organization config - let organizationConfig: OrganizationConfig | undefined = undefined; - try { - organizationConfig = OrganizationConfig.load(configDirPath, replacementsConfig); - } catch (e) { - initErrors.push({ file: OrganizationConfig.FILENAME, message: e }); - } - - // Load security config - let securityConfig: SecurityConfig | undefined = undefined; - try { - securityConfig = SecurityConfig.load(configDirPath, replacementsConfig); - } catch (e) { - initErrors.push({ file: SecurityConfig.FILENAME, message: e }); - } - - // Validate optional configuration files if they exist - let customizationsConfig: CustomizationsConfig | undefined = undefined; - if (fs.existsSync(path.join(configDirPath, CustomizationsConfig.FILENAME))) { - try { - customizationsConfig = CustomizationsConfig.load(configDirPath, replacementsConfig); - } catch (e) { - initErrors.push({ file: CustomizationsConfig.FILENAME, message: e }); - } - } - - // - // Run config validators - // - runValidators( - configDirPath, - props.replacementsPresent, - accountsConfig, - customizationsConfig, - globalConfig, - iamConfig, - networkConfig, - organizationConfig, - securityConfig, - replacementsConfig, - ); - - // - // Process errors - // - processErrors(initErrors, configErrors, globalConfig?.cdkOptions?.skipStaticValidation ?? false); -} - -/** - * Run config validation classes - * @param configDirPath - * @param accountsConfig - * @param globalConfig - * @param networkConfig - * @param iamConfig - * @param organizationConfig - * @param securityConfig - */ -function runValidators( - configDirPath: string, - replacementsPresent: boolean, - accountsConfig?: AccountsConfig, - customizationsConfig?: CustomizationsConfig, - globalConfig?: GlobalConfig, - iamConfig?: IamConfig, - networkConfig?: NetworkConfig, - organizationConfig?: OrganizationConfig, - securityConfig?: SecurityConfig, - replacementsConfig?: ReplacementsConfig, -) { - // Accounts config validator - if (accountsConfig && organizationConfig) { - try { - new AccountsConfigValidator(accountsConfig, organizationConfig); - } catch (e) { - configErrors.push(e); - } - } - - // Customizations config validator - if ( - accountsConfig && - customizationsConfig && - globalConfig && - iamConfig && - networkConfig && - organizationConfig && - securityConfig - ) { - try { - new CustomizationsConfigValidator( - customizationsConfig, - accountsConfig, - globalConfig, - iamConfig, - networkConfig, - organizationConfig, - securityConfig, - configDirPath, - ); - } catch (e) { - configErrors.push(e); - } - } - - // Global config validator - if (accountsConfig && globalConfig && iamConfig && organizationConfig && securityConfig) { - try { - new GlobalConfigValidator( - globalConfig, - accountsConfig, - iamConfig, - organizationConfig, - securityConfig, - configDirPath, - ); - } catch (e) { - configErrors.push(e); - } - } - - // IAM config validator - if (accountsConfig && globalConfig && iamConfig && networkConfig && organizationConfig && securityConfig) { - try { - new IamConfigValidator( - iamConfig, - accountsConfig, - networkConfig, - organizationConfig, - securityConfig, - configDirPath, - ); - } catch (e) { - configErrors.push(e); - } - } - - // Network config validator - if (accountsConfig && globalConfig && networkConfig && organizationConfig && securityConfig) { - try { - new NetworkConfigValidator( - networkConfig, - accountsConfig, - globalConfig, - organizationConfig, - securityConfig, - replacementsConfig, - configDirPath, - customizationsConfig, - ); - } catch (e) { - configErrors.push(e); - } - } - - // Organization config validator - if (organizationConfig) { - try { - new OrganizationConfigValidator(organizationConfig, replacementsConfig, configDirPath); - } catch (e) { - configErrors.push(e); - } - } - - // Security config validator - if (accountsConfig && globalConfig && organizationConfig && securityConfig) { - try { - new SecurityConfigValidator( - securityConfig, - accountsConfig, - globalConfig, - organizationConfig, - replacementsConfig, - configDirPath, - ); - } catch (e) { - configErrors.push(e); - } - } - - if (replacementsPresent && replacementsConfig) { - try { - new ReplacementsConfigValidator(replacementsConfig, configDirPath); - } catch (e) { - configErrors.push(e); - } - } -} - -/** - * Process errors encountered during validation - * @param initErrors - * @param configErrors - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function processErrors(initErrors: { file: string; message: any }[], configErrors: any[], skipValidation: boolean) { - if (initErrors.length > 0 || configErrors.length > 0) { - logger.warn(`Config file validation failed !!!`); - // Process initial file load errors - initErrors.forEach(initItem => { - logger.warn(`${initItem.message} in ${initItem.file} config file`); - }); - // Process config validation errors - configErrors.forEach(configItem => { - logger.warn(configItem); - }); - // Exit with error code - if (skipValidation) { - logger.warn(`Errors found in configuration but ignoring since skipStaticValidation is set to true`); - } else { - process.exit(1); - } - } else { - logger.info(`Config file validation successful.`); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/detach-quarantine-scp.ts b/source/packages/@aws-accelerator/accelerator/lib/detach-quarantine-scp.ts deleted file mode 100644 index b9d62f3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/detach-quarantine-scp.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; -import path = require('path'); - -export interface DetachQuarantineScpProps { - readonly scpPolicyId: string; - readonly partition: string; - readonly managementAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class Detach Quarantine SCP - */ -export class DetachQuarantineScp extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: DetachQuarantineScpProps) { - super(scope, id); - - const DETACH_QUARANTINE_SCP_RESOURCE_TYPE = 'Custom::DetachQuarantineScp'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, DETACH_QUARANTINE_SCP_RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'lambdas/detach-quarantine-scp/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - timeout: cdk.Duration.minutes(15), - policyStatements: [ - { - Sid: 'organizations', - Effect: 'Allow', - Action: ['organizations:ListAccounts'], - Resource: '*', - }, - { - Sid: 'detach', - Effect: 'Allow', - Action: ['organizations:DetachPolicy'], - Resource: [ - `arn:${props.partition}:organizations::${props.managementAccountId}:policy/o-*/service_control_policy/${props.scpPolicyId}`, - `arn:${props.partition}:organizations::${props.managementAccountId}:account/o-*/*`, - ], - }, - ], - }); - - // - // Custom Resource definition. We want this resource to be evaluated on - // every CloudFormation update, so we generate a new uuid to force - // re-evaluation. - // - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: DETACH_QUARANTINE_SCP_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - scpPolicyId: props.scpPolicyId, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/index.ts deleted file mode 100644 index 4b99af7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/index.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; -import { delay, throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -const organizationsClient = new AWS.Organizations({ - region: 'us-east-1', - customUserAgent: process.env['SOLUTION_ID'], -}); - -/** - * attach-quarantine-scp - lambda handler - * - * @param event - * @returns - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise { - console.log(event); - - // eslint-disable-next-line prefer-const - const policyName: string = process.env['SCP_POLICY_NAME'] ?? ''; - - const createAccountStatus = JSON.parse(JSON.stringify(event.detail.responseElements.createAccountStatus)); - console.log(`Create Account Request Id: ${createAccountStatus.id}`); - - let statusResponse = await getAccountCreationStatus(createAccountStatus.id); - while (statusResponse.CreateAccountStatus?.State !== 'SUCCEEDED') { - await delay(1000); - statusResponse = await getAccountCreationStatus(createAccountStatus.id); - } - if (statusResponse.CreateAccountStatus?.State === 'SUCCEEDED') { - const policyId = await getPolicyId(policyName); - if (policyId === 'NotFound') { - console.error( - `Policy with name ${policyName} was not found. Policy was not applied to account ${statusResponse.CreateAccountStatus.AccountId}`, - ); - return { - statusCode: 200, - }; - } - await attachQuarantineScp(statusResponse.CreateAccountStatus.AccountId ?? '', policyId ?? ''); - } - console.log(statusResponse); - return { - statusCode: 200, - }; -} - -async function getAccountCreationStatus( - requestId: string, -): Promise { - return throttlingBackOff(() => - organizationsClient.describeCreateAccountStatus({ CreateAccountRequestId: requestId }).promise(), - ); -} - -async function getPolicyId(policyName: string) { - console.log(`Looking for policy named ${policyName}`); - const scpPolicies: AWS.Organizations.PolicySummary[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.listPolicies({ Filter: 'SERVICE_CONTROL_POLICY' }).promise(), - ); - - for (const scpPolicy of page.Policies ?? []) { - console.log(`Policy named ${scpPolicy.Name} added to list`); - scpPolicies.push(scpPolicy); - } - nextToken = page.NextToken; - } while (nextToken); - - const policy = scpPolicies.find(item => item.Name === policyName); - if (policy) { - console.log(policy); - return policy?.Id; - } else { - return 'NotFound'; - } -} - -async function attachQuarantineScp(accountId: string, policyId: string): Promise { - try { - await throttlingBackOff(() => - organizationsClient - .attachPolicy({ - PolicyId: policyId, - TargetId: accountId, - }) - .promise(), - ); - console.log(`Attached Quarantine SCP to account: ${accountId}`); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.code === 'DuplicatePolicyAttachmentException') { - console.log(`Quarantine SCP was previously attached to account: ${accountId}`); - return true; - } else { - console.log(e); - return false; - } - } - return true; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/package.json deleted file mode 100644 index deeb7f7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-attach-quarantine-scp", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/attach-quarantine-scp/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/index.ts deleted file mode 100644 index a89802b..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SNSClient, PublishCommand, PublishCommandInput } from '@aws-sdk/client-sns'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -const snsTopicArn: string = process.env['SNS_TOPIC_ARN'] ?? ''; -const solutionId: string = process.env['SOLUTION_ID'] ?? ''; - -const snsClient = new SNSClient({ customUserAgent: solutionId }); -/** - * control-tower-notification-forwarder - lambda handler - * - * @param event - * @returns - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise { - console.log(JSON.stringify(event)); - console.log(event['Records']); - console.log('Forwarding message to sns topic arn:', snsTopicArn); - let subject = event['Records'][0]['Sns']['Subject'] || undefined; - // skip config compliances changes - if (subject == 'Config Rules Compliance Change') { - console.log('Skipped forwarding message as subject was for config rules compliance change.'); - return; - } - if (subject === undefined) { - console.log('Skipped forwarding message as no subject was provided.'); - return; - } - console.log('Subject: ', subject); - let jsonMessage: Record = {}; - let message = ''; - try { - jsonMessage = JSON.parse(event['Records'][0]['Sns']['Message']); - message = JSON.stringify(jsonMessage); - console.log('Message: ', message); - if (jsonMessage['detail-type']) { - subject = jsonMessage['detail-type']; - } - } catch { - console.log('Error parsing message content'); - } - const params: PublishCommandInput = { - Subject: subject, - Message: message, - TopicArn: snsTopicArn, - }; - const response = await throttlingBackOff(() => snsClient.send(new PublishCommand(params))); - console.log(response); -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/package.json deleted file mode 100644 index 1e0f976..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-control-tower-notifications-forwarder", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-sns": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications-forwarder/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/index.ts deleted file mode 100644 index 603caa2..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { SSMClient, PutParameterCommand, PutParameterCommandInput } from '@aws-sdk/client-ssm'; - -const driftParameterName: string = process.env['DRIFT_PARAMETER_NAME'] ?? ''; -const driftMessageParameterName: string = process.env['DRIFT_MESSAGE_PARAMETER_NAME'] ?? ''; -const solutionId: string = process.env['SOLUTION_ID'] ?? ''; - -const ssmClient = new SSMClient({ customUserAgent: solutionId }); -/** - * Control Tower Notifications - lambda handler - * - * @param event - * @returns - */ - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise { - console.log(JSON.stringify(event)); - console.log(event['Records']); - - const snsMessage = JSON.parse(event['Records'][0]['Sns']['Message']); - console.log('Message:', snsMessage['Message']); - console.log('DriftType', snsMessage['DriftType']); - const driftType = snsMessage['DriftType'] ?? 'Not available. Check the Control Tower console.'; - const driftMessage = snsMessage['Message'] ?? 'Message not found in Control Tower notification. Check Console.'; - switch (driftType) { - case 'ACCOUNT_MOVED_BETWEEN_OUS': - case 'ACCOUNT_REMOVED_FROM_ORGANIZATION': - case 'SCP_DETACHED_FROM_OU': - case 'OrganizationalUnitDeleted': - console.log('Setting drift detected'); - await setDriftDetected(driftMessage); - break; - case 'AccountAddedToOrganization': - case 'ServiceControlPolicyUpdated': - case 'SCP_ATTACHED_TO_OU': - console.log('No action taken'); - return; - } -} - -async function setDriftDetected(driftMessage: string): Promise { - const driftDetectedParams: PutParameterCommandInput = { - Name: driftParameterName, - Overwrite: true, - Value: 'true', - }; - const driftDetectedResponse = await throttlingBackOff(() => - ssmClient.send(new PutParameterCommand(driftDetectedParams)), - ); - console.log(driftDetectedResponse); - - const driftMessageParams: PutParameterCommandInput = { - Name: driftMessageParameterName, - Overwrite: true, - Value: driftMessage, - }; - const driftMessageResponse = await throttlingBackOff(() => - ssmClient.send(new PutParameterCommand(driftMessageParams)), - ); - console.log(driftMessageResponse); -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/package.json deleted file mode 100644 index c1adb2f..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-control-tower-notifications", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ssm": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-notifications/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/index.ts deleted file mode 100644 index df4369c..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/index.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - DynamoDBDocumentClient, - QueryCommand, - QueryCommandInput, - UpdateCommand, - UpdateCommandInput, -} from '@aws-sdk/lib-dynamodb'; -import { - OrganizationsClient, - ListParentsCommand, - DescribeOrganizationalUnitCommand, -} from '@aws-sdk/client-organizations'; - -const configTableName: string = process.env['CONFIG_TABLE_NAME'] ?? ''; -const solutionId: string = process.env['SOLUTION_ID'] ?? ''; - -const organizationsClient = new OrganizationsClient({ customUserAgent: solutionId }); -const dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId }); -const ddbDocumentClient = DynamoDBDocumentClient.from(dynamodbClient); -/** - * Control Tower OU Events - lambda handler - * - * @param event - * @returns - */ - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise { - console.log(JSON.stringify(event)); - - if (event.detailType != 'AWS Service Event via CloudTrail' && event.source != 'aws.controltower') { - console.error('Event was not the proper type.'); - return; - } - - switch (event.detail.eventName) { - case 'RegisterOrganizationalUnit': - const registerOuId = - event.detail.serviceEventDetails.registerOrganizationalUnitStatus.organizationalUnit.organizationalUnitId; - const registerOuName = - event.detail.serviceEventDetails.registerOrganizationalUnitStatus.organizationalUnit.organizationalUnitName; - console.log( - `Updating config with RegisterOrganizationalUnit for OuId ${registerOuId} with name ${registerOuName}`, - ); - const registeredKey = await getAcceleratorKeyForOu(registerOuId, registerOuName); - await updateOrganizationRecord(registeredKey, registerOuId, true); - break; - case 'DeregisterOrganizationalUnit': - const deregisterOuId = - event.detail.serviceEventDetails.registerOrganizationalUnitStatus.organizationalUnit.organizationalUnitId; - const deregisterOuName = - event.detail.serviceEventDetails.registerOrganizationalUnitStatus.organizationalUnit.organizationalUnitName; - console.log( - `Updating config with DeregisterOrganizationalUnit for OuId ${deregisterOuId} with name ${deregisterOuName}`, - ); - const deregisteredKey = await getAcceleratorKeyForOu(deregisterOuId, deregisterOuName); - await updateOrganizationRecord(deregisteredKey, deregisterOuId, false); - break; - default: - console.error('Event Name is not supported'); - } -} - -async function getAcceleratorKeyForOu(ouId: string, ouName: string): Promise { - let acceleratorKey = ouName; - // check if OU exists in config table - const acceleratorKeyLookup = await getAcceleratorKeyWithAwsKey(ouId); - if (acceleratorKeyLookup) { - return acceleratorKeyLookup; - } - // build acceleratorKey by building path to OU - let currentOuType = 'ORGANIZATIONAL_UNIT'; - let currentOuId = ouId; - while (currentOuType == 'ORGANIZATIONAL_UNIT') { - const parentOu = await throttlingBackOff(() => - organizationsClient.send( - new ListParentsCommand({ - ChildId: currentOuId, - }), - ), - ); - - if (parentOu.Parents?.[0].Type == 'ROOT') { - return acceleratorKey; - } else { - currentOuType = parentOu.Parents?.[0].Type ?? ''; - currentOuId = parentOu.Parents?.[0].Id ?? ''; - } - const parentOuDescription = await throttlingBackOff(() => - organizationsClient.send( - new DescribeOrganizationalUnitCommand({ - OrganizationalUnitId: parentOu.Parents?.[0].Id, - }), - ), - ); - acceleratorKey = `${parentOuDescription.OrganizationalUnit?.Name}/${acceleratorKey}`; - } - return ''; -} - -async function updateOrganizationRecord(acceleratorKey: string, awsKey: string, registered: boolean): Promise { - const params: UpdateCommandInput = { - TableName: configTableName, - Key: { - dataType: 'organization', - acceleratorKey: acceleratorKey, - }, - UpdateExpression: 'set #awsKey = :v_awsKey, #registered = :v_registered', - ExpressionAttributeNames: { - '#awsKey': 'awsKey', - '#registered': 'registered', - }, - ExpressionAttributeValues: { - ':v_awsKey': awsKey, - ':v_registered': registered, - }, - }; - await throttlingBackOff(() => ddbDocumentClient.send(new UpdateCommand(params))); -} - -async function getAcceleratorKeyWithAwsKey(awsKey: string): Promise { - const params: QueryCommandInput = { - TableName: configTableName, - IndexName: 'awsResourceKeys', - KeyConditionExpression: 'dataType = :hkey AND awsKey = :awsKey', - ExpressionAttributeValues: { - ':hkey': 'organization', - ':awsKey': awsKey, - }, - ProjectionExpression: 'acceleratorKey, awsKey', - }; - const response = await throttlingBackOff(() => ddbDocumentClient.send(new QueryCommand(params))); - if (response.Items) { - return response.Items[0]['acceleratorKey']; - } else { - return ''; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/package.json deleted file mode 100644 index 1f3f946..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-control-tower-ou-events", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-dynamodb": "3.410.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/lib-dynamodb": "3.410.0", - "@aws-sdk/smithy-client": "3.374.0", - "@aws-sdk/types": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/control-tower-ou-events/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/index.ts deleted file mode 100644 index dee21d6..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -let organizationsClient: AWS.Organizations; - -/** - * detach-quarantine-scp - lambda handler - * - * @param event - * @returns - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(event); - const policyId: string = event.ResourceProperties['scpPolicyId'] ?? ''; - const solutionId = process.env['SOLUTION_ID']; - - organizationsClient = new AWS.Organizations({ customUserAgent: solutionId }); - switch (event.RequestType) { - case 'Create': - case 'Update': - const accountIdList = await getAccountIds(); - - for (const accountId of accountIdList) { - await detachQuarantineScp(accountId ?? '', policyId ?? ''); - } - return { - PhysicalResourceId: 'detach-quarantine-scp', - Status: 'SUCCESS', - }; - case 'Delete': - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -async function getAccountIds(): Promise { - console.log('Getting list of accounts'); - const accountIdList: string[] = []; - let nextToken: string | undefined = undefined; - do { - const params: AWS.Organizations.ListAccountsRequest = { NextToken: nextToken }; - const page = await throttlingBackOff(() => organizationsClient.listAccounts(params).promise()); - for (const account of page.Accounts ?? []) { - accountIdList.push(account.Id!); - } - nextToken = page.NextToken; - } while (nextToken); - return accountIdList; -} - -async function detachQuarantineScp(accountId: string, policyId: string): Promise { - try { - await throttlingBackOff(() => - organizationsClient - .detachPolicy({ - PolicyId: policyId, - TargetId: accountId, - }) - .promise(), - ); - console.log(`Detached Quarantine SCP from account: ${accountId}`); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.code === 'PolicyNotAttachedException') { - return true; - } else { - console.log(e); - return false; - } - } - return true; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/package.json deleted file mode 100644 index 22a9632..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-detach-quarantine-scp", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/detach-quarantine-scp/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/index.ts deleted file mode 100644 index 5acbd99..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/index.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { - getInstallerStackMetadata, - getOrgAccountDetails, - getLzaGlobalConfigMetadata, - getEnabledAccounts, - getStackStatus, - getPipelineStatus, - getLzaConfigFiles, - formatTableCellValue, - uploadReports, -} from './resources/functions'; -import * as fs from 'fs'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise { - console.log(event); - - let lzaPipelineDetailHtmlMessage: string | undefined; - let installerPipelineDetailHtmlMessage: string | undefined; - const htmlStart = ''; - const htmlTableStart = - '

    Landing Zone Accelerator on AWS Diagnostics Report'; - const htmlEnd = '
    ResourceStatus
    '; - - let htmlBodyMessage = htmlStart + htmlTableStart; - - // - // Set variables - // - const installerStackName = process.env['INSTALLER_STACK_NAME'] ?? 'AWSAccelerator-InstallerStack'; - const homeRegion = process.env['HOME_REGION'] ?? 'us-east-1'; - const pipelineAccountId = process.env['PIPELINE_ACCOUNT_ID'] ?? '111111111111'; - const partition = process.env['PARTITION'] ?? 'aws'; - const reportBucketName = process.env['REPORT_BUCKET_NAME'] ?? 'lza-diagnostics-report'; - const daysSincePipelineFailed = process.env['DAYS_SINCE_FAILURE'] ?? '3'; - const managementAccountRoleName = process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']; - const configRepoName = process.env['CONFIG_REPO_NAME'] ?? 'aws-accelerator-config'; - - // Get installer metadata - const installerStackMetadata = await getInstallerStackMetadata(homeRegion, installerStackName); - - // Set resource names - let pipelineStackName = `${installerStackMetadata.prefix}-PipelineStack-${pipelineAccountId}-${homeRegion}`; - let installerPipelineName = `${installerStackMetadata.prefix}-Installer`; - let lzaPipelineName = `${installerStackMetadata.prefix}-Pipeline`; - - if (installerStackMetadata.qualifier) { - pipelineStackName = `${installerStackMetadata.qualifier}-pipeline-stack-${pipelineAccountId}-${homeRegion}`; - installerPipelineName = `${installerStackMetadata.qualifier}-installer`; - lzaPipelineName = `${installerStackMetadata.qualifier}-pipeline`; - } - - // - // Get Organization account details - // - const accountDetails = await getOrgAccountDetails( - homeRegion, - partition, - pipelineAccountId, - installerStackMetadata, - managementAccountRoleName, - ); - - // - // Get LZA Global config metadata - // - const lzaGlobalConfigMetadata = await getLzaGlobalConfigMetadata(homeRegion, configRepoName); - - // - // Get LZA enabled account details - const lzaEnabledAccounts = await getEnabledAccounts(homeRegion, configRepoName, accountDetails); - - // - // Get Installer Stack Status - // - const installerStackStatus = await getStackStatus(homeRegion, installerStackName); - - // - // Get Pipeline Stack Status - // - const pipelineStackStatus = await getStackStatus(homeRegion, pipelineStackName); - - // - // Get Installer Pipeline Status - // - const installerPipelineStatuses = await getPipelineStatus( - installerStackMetadata.prefix, - partition, - homeRegion, - installerPipelineName, - accountDetails, - lzaEnabledAccounts, - lzaGlobalConfigMetadata, - true, - Number(daysSincePipelineFailed), - ); - - // - // Get LZA Pipeline Status - // - const lzaPipelineStatuses = await getPipelineStatus( - installerStackMetadata.prefix, - partition, - homeRegion, - lzaPipelineName, - accountDetails, - lzaEnabledAccounts, - lzaGlobalConfigMetadata, - false, - Number(daysSincePipelineFailed), - ); - - // - // Print LZA External/Internal Deployment - // - const externalDeploymentStatus = installerStackMetadata.isExternal ? 'Yes' : 'No'; - htmlBodyMessage = htmlBodyMessage + 'External Deployment' + externalDeploymentStatus + ''; - - // - // Print LZA Version - // - htmlBodyMessage = htmlBodyMessage + 'Version' + installerStackMetadata.lzaVersion + ''; - - // - // Print home region - // - htmlBodyMessage = htmlBodyMessage + 'Home region' + homeRegion + ''; - - // - // Print enabled regions - // - let regionString = '
      '; - for (const enableRegion of lzaGlobalConfigMetadata.enabledRegions) { - regionString += '
    • ' + enableRegion + '
    • '; - } - regionString += '
    '; - htmlBodyMessage = htmlBodyMessage + 'Enabled regions' + regionString + ''; - - // - // Print Installer Stack Status - // - htmlBodyMessage = - htmlBodyMessage + 'Installer Stack' + formatTableCellValue(installerStackStatus.message) + ''; - - // - // Print Pipeline Stack Status - // - htmlBodyMessage = - htmlBodyMessage + 'Pipeline Stack' + formatTableCellValue(pipelineStackStatus.message) + ''; - - // - // Print Installer Pipeline Status - // - if (installerPipelineStatuses.pipelineStatus.detailStatus.length > 0) { - htmlBodyMessage = htmlBodyMessage + 'Installer Pipeline' + formatTableCellValue('Failed') + ''; - - // - // Get Failed Stage and action details - // - const installerPipelineDetailsHtmlStart = - ''; - const installerPipelineDetailsHtmlTableStart = - '
    Installer Pipeline Status'; - const installerPipelineDetailsHtmlEnd = '
    StageNameStatusActionNameActionLastStatusChangedActionStatusBuildErrorMessages
    '; - installerPipelineDetailHtmlMessage = installerPipelineDetailsHtmlStart + installerPipelineDetailsHtmlTableStart; - for (const installerPipelineStatus of installerPipelineStatuses.pipelineStatus.detailStatus) { - installerPipelineDetailHtmlMessage += - '' + - installerPipelineStatus.stageName + - '' + - installerPipelineStatus.stageLastExecutionStatus + - '' + - installerPipelineStatus.actionName + - '' + - installerPipelineStatus.actionLastExecutionTime + - '' + - installerPipelineStatus.actionLastExecutionStatus + - '' + - installerPipelineStatus.buildErrorMessages; - } - installerPipelineDetailHtmlMessage = installerPipelineDetailHtmlMessage + installerPipelineDetailsHtmlEnd; - } else { - htmlBodyMessage = - htmlBodyMessage + - 'Installer Pipeline' + - formatTableCellValue(installerPipelineStatuses.pipelineStatus.status!) + - ''; - } - - // - // Print LZA Pipeline Status - // - if (lzaPipelineStatuses.pipelineStatus.detailStatus.length > 0) { - htmlBodyMessage = htmlBodyMessage + 'LZA Pipeline' + formatTableCellValue('Failed') + ''; - - // - // Get Failed Stage and action details - // - const lzaPipelineDetailsHtmlStart = - ''; - const lzaPipelineDetailsHtmlTableStart = - '
    LZA Pipeline Status'; - const lzaPipelineDetailsHtmlEnd = '
    StageNameStatusActionNameActionLastStatusChangedActionStatusBuildErrorMessages
    '; - - lzaPipelineDetailHtmlMessage = lzaPipelineDetailsHtmlStart + lzaPipelineDetailsHtmlTableStart; - - for (const lzaPipelineStatus of lzaPipelineStatuses.pipelineStatus.detailStatus) { - lzaPipelineDetailHtmlMessage += - '' + - lzaPipelineStatus.stageName + - '' + - lzaPipelineStatus.stageLastExecutionStatus + - '' + - lzaPipelineStatus.actionName + - '' + - lzaPipelineStatus.actionLastExecutionTime + - '' + - lzaPipelineStatus.actionLastExecutionStatus + - '' + - lzaPipelineStatus.buildErrorMessages; - } - lzaPipelineDetailHtmlMessage = lzaPipelineDetailHtmlMessage + lzaPipelineDetailsHtmlEnd; - } else { - htmlBodyMessage = - htmlBodyMessage + - 'LZA Pipeline' + - formatTableCellValue(lzaPipelineStatuses.pipelineStatus.status!) + - ''; - } - - // Complete the Html - // - htmlBodyMessage = htmlBodyMessage + htmlEnd; - - if (installerPipelineDetailHtmlMessage) { - htmlBodyMessage = htmlBodyMessage + '\n' + installerPipelineDetailHtmlMessage; - } - - if (lzaPipelineDetailHtmlMessage) { - htmlBodyMessage = htmlBodyMessage + '\n' + lzaPipelineDetailHtmlMessage; - } - - // - // Display CFN failure stack details - for (const cfnStatus of lzaPipelineStatuses.cfnStatuses) { - htmlBodyMessage = htmlBodyMessage + '\n' + cfnStatus; - } - - // - // set LZA status - // - let lzaFailed = false; - if ( - lzaPipelineStatuses.pipelineStatus.status === 'Failed' && - installerPipelineStatuses.pipelineStatus.status === 'Failed' && - !installerStackStatus.message.includes('FAIL') && - !pipelineStackStatus.message.includes('FAIL') - ) { - lzaFailed = true; - } - - // - // Create temp directory - // - const tempConfigDirPath = '/tmp/accel-config'; - if (fs.existsSync(tempConfigDirPath)) { - fs.rmdirSync(tempConfigDirPath, { recursive: true }); - } - fs.mkdirSync(tempConfigDirPath); - - // - // Get LZA config files only when config is failed - // - if (lzaFailed) { - await getLzaConfigFiles(homeRegion, accountDetails, configRepoName, tempConfigDirPath); - } - - // - // Upload files to S3 - // - await uploadReports( - installerStackMetadata.prefix, - homeRegion, - htmlBodyMessage, - reportBucketName, - tempConfigDirPath, - lzaFailed, - ); -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/package.json deleted file mode 100644 index c836c75..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@aws-accelerator/diagnostic-pack-reporter", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-sdk/client-cloudformation": "3.410.0", - "@aws-sdk/client-cloudwatch-logs": "3.410.0", - "@aws-sdk/client-codebuild": "3.410.0", - "@aws-sdk/client-codecommit": "3.410.0", - "@aws-sdk/client-codepipeline": "3.410.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/client-s3": "3.412.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/js-yaml": "4.0.5", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts deleted file mode 100644 index 648dd76..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/functions.ts +++ /dev/null @@ -1,705 +0,0 @@ -import { AssumeRoleCommand, STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - CloudFormationClient, - DescribeStackEventsCommand, - DescribeStacksCommand, -} from '@aws-sdk/client-cloudformation'; -import { ListAccountsCommand, OrganizationsClient } from '@aws-sdk/client-organizations'; -import { CodeCommitClient, GetFileCommand } from '@aws-sdk/client-codecommit'; -import { ActionExecution, CodePipelineClient, GetPipelineStateCommand } from '@aws-sdk/client-codepipeline'; -import { CloudWatchLogsClient, FilterLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs'; -import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; - -import * as yaml from 'js-yaml'; -import * as fs from 'fs'; -import * as path from 'path'; - -import { - AccountDetailsType, - DiagnosticAccountsConfigType, - InstallerStackMetadataType, - LzaAccountsConfigType, - LzaGlobalConfigType, - LzaStackEnvironmentType, - PipelineDetailStatusType, - PipelineStatusType, -} from './types'; - -export function formatTableCellValue(cellMessage: string): string { - if (cellMessage === 'Failed') { - return `${cellMessage}`; - } - return `${cellMessage}`; -} - -function getAccountId(email: string, accountDetails: AccountDetailsType[]): string { - for (const account of accountDetails) { - if (account.accountEmail === email) { - return account.accountId; - } - } - - throw new Error(`Account with email ${email} not found in organization accounts`); -} - -function replaceAll(input: string, find: string, replace: string) { - return input.replace(new RegExp(find, 'g'), replace); -} - -async function getBuildLogErrors( - cwlClient: CloudWatchLogsClient, - logGroupName: string, - logStreamName: string, -): Promise { - let buildErrorMessages = '
      '; - - const errorPatterns: string[] = ['error', 'ERROR', 'fail', 'FAIL']; - - for (const errorPattern of errorPatterns) { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - cwlClient.send( - new FilterLogEventsCommand({ - logGroupName: logGroupName, - logStreamNames: [logStreamName], - nextToken: nextToken, - filterPattern: errorPattern, - }), - ), - ); - for (const event of page.events ?? []) { - buildErrorMessages += '
    • ' + event.message + '
    • '; - } - nextToken = page.nextToken; - } while (nextToken); - } - - buildErrorMessages += '
    '; - - return buildErrorMessages; -} - -function maskAccountIdAndEmails(input: string, accountDetails: AccountDetailsType[]): string { - let maskedInput = input; - - for (const accountDetail of accountDetails) { - const maskEmailInput = replaceAll( - maskedInput, - `${accountDetail.accountEmail}`, - `${accountDetail.accountName}-account@email.com`, - ); - const maskAccountIdInput = replaceAll( - maskEmailInput, - accountDetail.accountId, - `[${accountDetail.accountName.toUpperCase()}-ACCOUNT-ID]`, - ); - maskedInput = maskAccountIdInput; - } - return maskedInput; -} - -function getStackNames(actionName: string, prefix: string, accountID: string, region: string): string[] { - const stackNames: string[] = []; - switch (actionName) { - case 'Network_Associations': - stackNames.push(`${prefix}-NetworkAssociationsStack-${accountID}-${region}`); - stackNames.push(`${prefix}-NetworkAssociationsGwlbStack-${accountID}-${region}`); - break; - case 'Network_VPCs': - stackNames.push(`${prefix}-NetworkVpcDnsStack-${accountID}-${region}`); - stackNames.push(`${prefix}-NetworkVpcEndpointsStack-${accountID}-${region}`); - stackNames.push(`${prefix}-NetworkVpcStack-${accountID}-${region}`); - break; - case 'Network_Prepare': - stackNames.push(`${prefix}-NetworkPrepStack-${accountID}-${region}`); - break; - case 'Security_Resources': - stackNames.push(`${prefix}-SecurityResourcesStack-${accountID}-${region}`); - break; - case 'Bootstrap': - stackNames.push(`${prefix}-CDKToolkit-${accountID}-${region}`); - break; - case 'Key': - stackNames.push(`${prefix}-${actionName}Stack-${accountID}-${region}`); - stackNames.push(`${prefix}-DependenciesStack-${accountID}-${region}`); - break; - case 'Install': - stackNames.push(`${prefix}-InstallerStack-${accountID}-${region}`); - break; - default: - stackNames.push(`${prefix}-${actionName}Stack-${accountID}-${region}`); - } - - return stackNames; -} - -async function getManagementAccountCredentials( - partition: string, - homeRegion: string, - pipelineAccountId: string, - managementAccountRoleName?: string, - managementAccountId?: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken?: string } | undefined> { - if (managementAccountRoleName && managementAccountId && pipelineAccountId !== managementAccountId) { - const stsClient = new STSClient({ region: homeRegion }); - const response = await throttlingBackOff(() => - stsClient.send( - new AssumeRoleCommand({ - RoleArn: `arn:${partition}:iam::${managementAccountId}:role/${managementAccountRoleName}`, - RoleSessionName: 'LZADiagnosticReportSession', - DurationSeconds: 900, - }), - ), - ); - - return { - accessKeyId: response.Credentials!.AccessKeyId!, - secretAccessKey: response.Credentials!.SecretAccessKey!, - sessionToken: response.Credentials!.SessionToken, - }; - } else { - return undefined; - } -} - -async function getWorkLoadAccountCredentials( - prefix: string, - partition: string, - region: string, - accountId: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken?: string } | undefined> { - const assumeRoleName = `${prefix}-DiagnosticsPackAccessRole`; - const stsClient = new STSClient({ region: region }); - const callerIdentityResponse = await throttlingBackOff(() => stsClient.send(new GetCallerIdentityCommand({}))); - if (callerIdentityResponse.Account === accountId) { - return undefined; - } - const response = await throttlingBackOff(() => - stsClient.send( - new AssumeRoleCommand({ - RoleArn: `arn:${partition}:iam::${accountId}:role/${assumeRoleName}`, - RoleSessionName: 'LZADiagnosticReportAccountSession', - DurationSeconds: 900, - }), - ), - ); - - return { - accessKeyId: response.Credentials!.AccessKeyId!, - secretAccessKey: response.Credentials!.SecretAccessKey!, - sessionToken: response.Credentials!.SessionToken, - }; -} - -export async function getInstallerStackMetadata( - homeRegion: string, - stackName: string, -): Promise { - const cfnClient = new CloudFormationClient({ region: homeRegion }); - const response = await throttlingBackOff(() => cfnClient.send(new DescribeStacksCommand({ StackName: stackName }))); - const installerMetadata: InstallerStackMetadataType = { - lzaVersion: 'Version 1.0.0.', - isExternal: false, - prefix: 'AWSAccelerator', - releaseBranch: 'main', - }; - - installerMetadata.lzaVersion = response - .Stacks![0].Description!.replace('(SO0199) Landing Zone Accelerator on AWS. Version ', '') - .slice(0, -1); - - for (const parameter of response.Stacks![0].Parameters ?? []) { - switch (parameter.ParameterKey!) { - case 'RepositoryBranchName': - installerMetadata.releaseBranch = parameter.ParameterValue!; - break; - case 'AcceleratorPrefix': - installerMetadata.prefix = parameter.ParameterValue!; - break; - case 'AcceleratorQualifier': - installerMetadata.qualifier = parameter.ParameterValue; - break; - case 'ManagementAccountId': - installerMetadata.managementAccountId = parameter.ParameterValue; - break; - } - } - - // External deployment - if (installerMetadata.qualifier && installerMetadata.managementAccountId) { - installerMetadata.isExternal = true; - } - return installerMetadata; -} - -export async function getOrgAccountDetails( - homeRegion: string, - partition: string, - pipelineAccountId: string, - installerStackMetadata: InstallerStackMetadataType, - managementAccountRoleName?: string, -): Promise { - const managementAccountCredential = await getManagementAccountCredentials( - partition, - homeRegion, - pipelineAccountId, - managementAccountRoleName, - installerStackMetadata.managementAccountId, - ); - - const orgClient = new OrganizationsClient({ region: homeRegion, credentials: managementAccountCredential }); - - const accountDetails: AccountDetailsType[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => orgClient.send(new ListAccountsCommand({ NextToken: nextToken }))); - for (const account of page.Accounts ?? []) { - if (account.Id && account.Name) { - accountDetails.push({ accountName: account.Name, accountEmail: account.Email!, accountId: account.Id }); - } - } - nextToken = page.NextToken; - } while (nextToken); - - if (installerStackMetadata.isExternal) { - accountDetails.push({ - accountName: 'ExternalPipeline', - accountEmail: 'ExternalPipelineAccount@email.com', - accountId: pipelineAccountId, - }); - } - - return accountDetails; -} - -export async function getLzaGlobalConfigMetadata( - homeRegion: string, - repositoryName: string, -): Promise { - const enabledRegions: string[] = []; - - const codeCommitClient = new CodeCommitClient({ region: homeRegion }); - - const response = await throttlingBackOff(() => - codeCommitClient.send( - new GetFileCommand({ - repositoryName: repositoryName, - filePath: 'global-config.yaml', - }), - ), - ); - - if (response.fileContent) { - const textContent = new TextDecoder().decode(response.fileContent); - const config = yaml.load(textContent) as LzaGlobalConfigType; - enabledRegions.push(...config.enabledRegions); - } - - return { - enabledRegions: enabledRegions, - homeRegion: homeRegion, - }; -} - -export async function getEnabledAccounts( - homeRegion: string, - repositoryName: string, - accountDetails: AccountDetailsType[], -): Promise { - const codeCommitClient = new CodeCommitClient({ region: homeRegion }); - const diagnosticsAccountList: DiagnosticAccountsConfigType[] = []; - - const response = await throttlingBackOff(() => - codeCommitClient.send( - new GetFileCommand({ - repositoryName: repositoryName, - filePath: 'accounts-config.yaml', - }), - ), - ); - - if (response.fileContent) { - const textContent = new TextDecoder().decode(response.fileContent); - const config = yaml.load(textContent) as LzaAccountsConfigType; - for (const account of [...config.mandatoryAccounts, ...config.workloadAccounts]) { - const accountId = getAccountId(account.email, accountDetails); - diagnosticsAccountList.push({ - name: account.name, - organizationalUnit: account.organizationalUnit, - id: accountId, - }); - } - } - return diagnosticsAccountList; -} - -/** - * Function to get stack status - * @param homeRegion - * @param stackName - * @returns - */ -export async function getStackStatus( - homeRegion: string, - stackName: string, -): Promise<{ status: string; message: string }> { - const cfnClient = new CloudFormationClient({ region: homeRegion }); - const response = await throttlingBackOff(() => cfnClient.send(new DescribeStacksCommand({ StackName: stackName }))); - if (response.Stacks![0].StackStatus!.includes('FAILED')) { - return { status: 'Failed', message: response.Stacks![0].StackStatus! }; - } - return { status: 'Success', message: response.Stacks![0].StackStatus! }; -} - -export async function getPipelineStatus( - prefix: string, - partition: string, - homeRegion: string, - pipelineName: string, - organizationAccountDetails: AccountDetailsType[], - lzaEnabledAccounts: DiagnosticAccountsConfigType[], - lzaGlobalConfigMetadata: LzaGlobalConfigType, - isInstaller: boolean, - daysSincePipelineFailed: number, -): Promise<{ pipelineStatus: PipelineStatusType; cfnStatuses: string[] }> { - const pipelineClient = new CodePipelineClient({ region: homeRegion }); - const response = await throttlingBackOff(() => - pipelineClient.send(new GetPipelineStateCommand({ name: pipelineName })), - ); - - const responseData: PipelineDetailStatusType[] = []; - const cfnStatuses: string[] = []; - let failCounter = 0; - let pipelineStatus = 'Succeeded'; - - for (const stage of response.stageStates ?? []) { - pipelineStatus = stage.latestExecution!.status!; - if (stage.latestExecution?.status === 'InProgress') { - return { pipelineStatus: { status: stage.latestExecution?.status, detailStatus: [] }, cfnStatuses: [] }; - } - if (stage.latestExecution?.status == 'Failed') { - failCounter += 1; - for (const actionState of stage.actionStates ?? []) { - if (actionState.latestExecution?.status == 'Failed') { - const maskedBuildErrorMessages = await getFailedBuildLogs( - partition, - homeRegion, - prefix, - isInstaller, - daysSincePipelineFailed, - organizationAccountDetails, - lzaGlobalConfigMetadata, - lzaEnabledAccounts, - actionState.latestExecution, - cfnStatuses, - actionState.latestExecution.externalExecutionId, - ); - - responseData.push({ - stageName: stage.stageName!, - stageLastExecutionStatus: stage.latestExecution?.status, - actionName: actionState.actionName!, - actionLastExecutionTime: actionState.latestExecution.lastStatusChange ?? 'NotFound', - actionLastExecutionStatus: actionState.latestExecution.status ?? 'NotFound', - buildErrorMessages: maskedBuildErrorMessages, - }); - } - } - } - } - - if (failCounter > 0) { - return { pipelineStatus: { status: undefined, detailStatus: responseData }, cfnStatuses: cfnStatuses }; - } else { - return { pipelineStatus: { status: pipelineStatus, detailStatus: [] }, cfnStatuses: cfnStatuses }; - } -} - -async function getFailedCloudFormationStackDetails( - partition: string, - isInstaller: boolean, - prefix: string, - daysSincePipelineFailed: number, - buildErrorMessages: string, - organizationAccountDetails: AccountDetailsType[], - lzaGlobalConfigMetadata: LzaGlobalConfigType, - lzaEnabledAccounts: DiagnosticAccountsConfigType[], - cfnStatuses: string[], -): Promise { - if (!isInstaller) { - const lzaStackEnvironments = getAllLzaStackEnvironments(prefix, lzaEnabledAccounts, lzaGlobalConfigMetadata); - - for (const lzaStackEnvironment of lzaStackEnvironments) { - if (buildErrorMessages.includes(lzaStackEnvironment.name)) { - cfnStatuses.push( - await getFailedStackDetails( - prefix, - partition, - lzaStackEnvironment, - organizationAccountDetails, - daysSincePipelineFailed, - ), - ); - } - } - } -} - -async function getFailedBuildLogs( - partition: string, - homeRegion: string, - prefix: string, - isInstaller: boolean, - daysSincePipelineFailed: number, - organizationAccountDetails: AccountDetailsType[], - lzaGlobalConfigMetadata: LzaGlobalConfigType, - lzaEnabledAccounts: DiagnosticAccountsConfigType[], - latestExecution: ActionExecution, - cfnStatuses: string[], - externalExecutionId?: string, -): Promise { - let maskedBuildErrorMessages = 'Build error log not found'; - if (externalExecutionId) { - const logGroupName = `/aws/codebuild/${externalExecutionId.split(':')[0]}`; - const logStreamName = externalExecutionId.split(':')[1]; - const cwlClient = new CloudWatchLogsClient({ region: homeRegion }); - const buildErrorMessages = await getBuildLogErrors(cwlClient, logGroupName, logStreamName); - maskedBuildErrorMessages = maskAccountIdAndEmails(buildErrorMessages, organizationAccountDetails); - - await getFailedCloudFormationStackDetails( - partition, - isInstaller, - prefix, - daysSincePipelineFailed, - buildErrorMessages, - organizationAccountDetails, - lzaGlobalConfigMetadata, - lzaEnabledAccounts, - cfnStatuses, - ); - } else { - maskedBuildErrorMessages = maskAccountIdAndEmails( - latestExecution.errorDetails!.message!, - organizationAccountDetails, - ); - } - - return maskedBuildErrorMessages; -} - -function getAllLzaStackEnvironments( - prefix: string, - lzaEnabledAccounts: DiagnosticAccountsConfigType[], - lzaGlobalConfigMetadata: LzaGlobalConfigType, -): LzaStackEnvironmentType[] { - const lzaPipelineActionNames: string[] = [ - 'Finalize', - 'Customizations', - 'Network_Associations', - 'Security_Resources', - 'Network_VPCs', - 'Operations', - 'Security', - 'Network_Prepare', - 'SecurityAudit', - 'Organizations', - 'Logging', - 'Key', - 'Accounts', - 'Prepare', - 'Bootstrap', - ]; - const stackEnvironments: LzaStackEnvironmentType[] = []; - for (const lzaPipelineActionName of lzaPipelineActionNames) { - for (const lzaEnabledAccount of lzaEnabledAccounts) { - for (const enabledRegion of lzaGlobalConfigMetadata.enabledRegions) { - const stackNames = getStackNames(lzaPipelineActionName, prefix, lzaEnabledAccount.id, enabledRegion); - for (const stackName of stackNames) { - stackEnvironments.push({ name: stackName, region: enabledRegion, accountId: lzaEnabledAccount.id }); - } - } - } - } - return stackEnvironments; -} - -async function getFailedStackDetails( - prefix: string, - partition: string, - failedStackEnvironment: LzaStackEnvironmentType, - organizationAccountDetails: AccountDetailsType[], - daysSincePipelineFailed: number, -): Promise { - const workLoadAccountCredentials = await getWorkLoadAccountCredentials( - prefix, - partition, - failedStackEnvironment.region, - failedStackEnvironment.accountId, - ); - const cfnDetailsHtmlStart = ''; - const cfnDetailsHtmlTableStart = - '
    CloudFormation Stack Failures'; - const cfnDetailsHtmlEnd = '
    StackNameAccountDateTimeErrorMessage
    '; - - let cfnDetailsHtmlMessage = cfnDetailsHtmlStart + cfnDetailsHtmlTableStart; - const cfnClient = new CloudFormationClient({ - region: failedStackEnvironment.region, - credentials: workLoadAccountCredentials, - }); - let nextToken: string | undefined = undefined; - const startDate = new Date(); - startDate.setDate(startDate.getDate() - daysSincePipelineFailed); - do { - const page = await throttlingBackOff(() => - cfnClient.send( - new DescribeStackEventsCommand({ - StackName: failedStackEnvironment.name, - NextToken: nextToken, - }), - ), - ); - for (const stackEvent of page.StackEvents ?? []) { - const eventDate = stackEvent.Timestamp!; - - if (stackEvent.ResourceStatus!.includes('FAILED') && stackEvent.ResourceStatusReason && eventDate >= startDate) { - cfnDetailsHtmlMessage += - '' + - maskAccountIdAndEmails(failedStackEnvironment.name, organizationAccountDetails) + - '' + - maskAccountIdAndEmails(failedStackEnvironment.accountId, organizationAccountDetails) + - '' + - stackEvent.Timestamp + - '' + - maskAccountIdAndEmails(stackEvent.ResourceStatusReason, organizationAccountDetails) + - ''; - } - } - nextToken = page.NextToken; - } while (nextToken); - - cfnDetailsHtmlMessage += cfnDetailsHtmlEnd; - - return cfnDetailsHtmlMessage; -} - -function maskConfigContent(fileName: string, textContent: string, accountDetails: AccountDetailsType[]): string { - let maskedContent = `##=============Masked LZA Config File ${fileName} =============`; - for (const originalLine of textContent.split('\n')) { - let maskedLine = originalLine; - - for (const accountDetail of accountDetails) { - if (originalLine.includes(accountDetail.accountEmail)) { - maskedLine = originalLine.replace(accountDetail.accountEmail, `${accountDetail.accountName}-account@email.com`); - } - if (originalLine.includes(accountDetail.accountId)) { - maskedLine = originalLine.replace( - accountDetail.accountEmail, - `[${accountDetail.accountName.toUpperCase()}-ACCOUNT-ID]`, - ); - } - } - maskedContent = maskedContent + '\n' + maskedLine; - } - - return maskedContent; -} - -export async function getLzaConfigFiles( - homeRegion: string, - accountDetails: AccountDetailsType[], - repositoryName: string, - tempConfigDirPath: string, -): Promise { - const requiredConfigFileNames: string[] = [ - 'accounts-config.yaml', - 'customizations-config.yaml', - 'global-config.yaml', - 'iam-config.yaml', - 'network-config.yaml', - 'organization-config.yaml', - 'replacements-config.yaml', - 'security-config.yaml', - ]; - - const codeCommitClient = new CodeCommitClient({ region: homeRegion }); - - for (const requiredConfigFileName of requiredConfigFileNames) { - try { - const response = await codeCommitClient.send( - new GetFileCommand({ - repositoryName: repositoryName, - filePath: requiredConfigFileName, - }), - ); - - if (response.fileContent) { - const configFilePath = path.join(tempConfigDirPath, requiredConfigFileName); - const textContent = new TextDecoder().decode(response.fileContent); - const maskedContent = maskConfigContent(requiredConfigFileName, textContent, accountDetails); - fs.writeFileSync(configFilePath, maskedContent, 'utf8'); - } - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error: any - ) { - if (error.name === 'FileDoesNotExistException') { - console.warn(`Config file ${requiredConfigFileName} not found !!!!`); - } - } - } -} - -export async function uploadReports( - acceleratorPrefix: string, - region: string, - reportData: string, - bucketName: string, - tempConfigDirPath: string, - lzaFailed: boolean, -) { - try { - const summaryReportName = 'diagnostic-report.html'; - - const s3Client = new S3Client({ region: region }); - const now = new Date(); - const destinationPrefix = - `${acceleratorPrefix}-Diagnostics-Pack/` + - now.getMonth() + - '-' + - now.getDate() + - '-' + - now.getFullYear() + - ' ' + - now.getHours() + - ':' + - now.getMinutes(); - - if (lzaFailed) { - const files = fs.readdirSync(tempConfigDirPath); - for (const file of files) { - await s3Client.send( - new PutObjectCommand({ - Bucket: bucketName, - Body: fs.readFileSync(`${tempConfigDirPath}/${file}`), - Key: `${destinationPrefix}/lza-config/${file}`, - }), - ); - } - } - - fs.writeFileSync(path.join(tempConfigDirPath, summaryReportName), reportData, 'utf8'); - - await s3Client.send( - new PutObjectCommand({ - Bucket: bucketName, - Body: fs.readFileSync(path.join(tempConfigDirPath, summaryReportName)), - Key: `${destinationPrefix}/${summaryReportName}`, - }), - ); - } catch (e) { - console.log(JSON.stringify(e)); - throw e; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/types.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/types.ts deleted file mode 100644 index 099c3e3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/resources/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -export type InstallerStackMetadataType = { - lzaVersion: string; - isExternal: boolean; - prefix: string; - releaseBranch: string; - qualifier?: string; - managementAccountId?: string; -}; - -export type LzaGlobalConfigType = { homeRegion: string; enabledRegions: string[] }; - -export type LzaAccountsConfigType = { - mandatoryAccounts: { name: string; organizationalUnit: string; email: string }[]; - workloadAccounts: { name: string; organizationalUnit: string; email: string }[]; -}; - -export type LzaStackEnvironmentType = { name: string; accountId: string; region: string }; - -export type DiagnosticAccountsConfigType = { - name: string; - id: string; - organizationalUnit: string; -}; - -export type PipelineDetailStatusType = { - stageName: string; - stageLastExecutionStatus: string; - actionName: string; - actionLastExecutionTime: Date | string; - actionLastExecutionStatus: string; - buildErrorMessages: string | undefined; -}; - -export type PipelineStatusType = { - status?: string; - detailStatus: PipelineDetailStatusType[]; -}; - -export type AccountDetailsType = { - accountName: string; - accountEmail: string; - accountId: string; -}; diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/diagnostic-pack/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/index.ts deleted file mode 100644 index 0b17e62..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/index.ts +++ /dev/null @@ -1,389 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient, UpdateCommand, UpdateCommandInput } from '@aws-sdk/lib-dynamodb'; -import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation'; -import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as yaml from 'js-yaml'; -import { - AccountConfig, - AccountsConfig, - OrganizationalUnitConfig, - OrganizationConfig, - parseAccountsConfig, - parseReplacementsConfig, - ReplacementsConfig, -} from '@aws-accelerator/config'; -import { Readable } from 'stream'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export {}; -const marshallOptions = { - convertEmptyValues: false, - //overriding default value of false - removeUndefinedValues: true, - convertClassInstanceToMap: false, -}; -const unmarshallOptions = { - wrapNumbers: false, -}; -const translateConfig = { marshallOptions, unmarshallOptions }; - -let dynamodbClient: DynamoDBClient; -let documentClient: DynamoDBDocumentClient; -let cloudformationClient: CloudFormationClient; -let s3Client: S3Client; - -/** - * load-config-table - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(event); - const configTableName: string = event.ResourceProperties['configTableName']; - const configRepositoryName: string = event.ResourceProperties['configRepositoryName']; - const managementAccountEmail: string = event.ResourceProperties['managementAccountEmail']; - const auditAccountEmail: string = event.ResourceProperties['auditAccountEmail']; - const logArchiveAccountEmail: string = event.ResourceProperties['logArchiveAccountEmail']; - const configS3Bucket: string = event.ResourceProperties['configS3Bucket']; - const organizationsConfigS3Key: string = event.ResourceProperties['organizationsConfigS3Key']; - const accountConfigS3Key: string = event.ResourceProperties['accountConfigS3Key']; - const replacementsConfigS3Key: string = event.ResourceProperties['replacementsConfigS3Key']; - const commitId: string = event.ResourceProperties['commitId'] ?? ''; - const partition: string = event.ResourceProperties['partition']; - const stackName: string = event.ResourceProperties['stackName']; - const solutionId = process.env['SOLUTION_ID']; - const isOrgsEnabled: boolean = event.ResourceProperties['isOrgsEnabled'] === 'true'; - - console.log(`Configuration Table Name: ${configTableName}`); - console.log(`Configuration Repository Name: ${configRepositoryName}`); - - dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId }); - documentClient = DynamoDBDocumentClient.from(dynamodbClient, translateConfig); - cloudformationClient = new CloudFormationClient({ customUserAgent: solutionId }); - s3Client = new S3Client({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log(stackName); - // if stack rollback is in progress don't do anything - // the stack may have failed as the results of errors - // from this construct - // when rolling back this construct will execute and - // fail again preventing stack rollback - if (await isStackInRollback(stackName)) { - console.log('Stack in rollback exiting'); - return { - PhysicalResourceId: 'loadConfigTableNone', - Status: 'SUCCESS', - }; - } - - return onCreateUpdateFunction( - partition, - configTableName, - commitId, - { name: configS3Bucket, organizationsConfigS3Key, accountConfigS3Key, replacementsConfigS3Key }, - { - managementAccount: managementAccountEmail, - auditAccount: auditAccountEmail, - logArchiveAccount: logArchiveAccountEmail, - }, - isOrgsEnabled, - ); - - case 'Delete': - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'Success', - }; - } -} - -async function getConfigFileContents(configFileS3Bucket: string, configFileS3Key: string): Promise { - const response = await throttlingBackOff(() => - s3Client.send(new GetObjectCommand({ Bucket: configFileS3Bucket, Key: configFileS3Key })), - ); - const stream = response.Body as Readable; - return streamToString(stream); -} - -async function streamToString(stream: Readable): Promise { - return new Promise((resolve, reject) => { - const chunks: Uint8Array[] = []; - stream.on('data', chunk => chunks.push(chunk)); - stream.on('error', reject); - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); - }); -} - -async function putOrganizationConfigInTable( - configData: OrganizationalUnitConfig, - configTableName: string, - awsKey: string, - commitId: string, -): Promise { - if (awsKey != '') { - const params: UpdateCommandInput = { - TableName: configTableName, - Key: { - dataType: 'organization', - acceleratorKey: configData.name, - }, - UpdateExpression: 'set #awsKey = :v_awsKey, #dataBag = :v_dataBag, #commitId = :v_commitId', - ExpressionAttributeNames: { - '#awsKey': 'awsKey', - '#dataBag': 'dataBag', - '#commitId': 'commitId', - }, - ExpressionAttributeValues: { - ':v_awsKey': awsKey, - ':v_dataBag': JSON.stringify(configData), - ':v_commitId': commitId, - }, - }; - await throttlingBackOff(() => documentClient.send(new UpdateCommand(params))); - } else { - const params: UpdateCommandInput = { - TableName: configTableName, - Key: { - dataType: 'organization', - acceleratorKey: configData.name, - }, - UpdateExpression: 'set #dataBag = :v_dataBag, #commitId = :v_commitId', - ExpressionAttributeNames: { - '#dataBag': 'dataBag', - '#commitId': 'commitId', - }, - ExpressionAttributeValues: { - ':v_dataBag': JSON.stringify(configData), - ':v_commitId': commitId, - }, - }; - await throttlingBackOff(() => documentClient.send(new UpdateCommand(params))); - } -} - -async function putAccountConfigInTable( - accountType: string, - configData: AccountConfig, - configTableName: string, - awsKey: string, - commitId: string, - ouName: string, -): Promise { - if (awsKey !== '') { - const params: UpdateCommandInput = { - TableName: configTableName, - Key: { - dataType: accountType + 'Account', - acceleratorKey: configData.email, - }, - UpdateExpression: 'set #awsKey = :v_awsKey, #dataBag = :v_dataBag, #commitId = :v_commitId, #ouName = :v_ouName', - ExpressionAttributeNames: { - '#awsKey': 'awsKey', - '#dataBag': 'dataBag', - '#commitId': 'commitId', - '#ouName': 'ouName', - }, - ExpressionAttributeValues: { - ':v_awsKey': awsKey, - ':v_dataBag': JSON.stringify(configData), - ':v_commitId': commitId, - ':v_ouName': ouName, - }, - }; - await throttlingBackOff(() => documentClient.send(new UpdateCommand(params))); - } else { - const params: UpdateCommandInput = { - TableName: configTableName, - Key: { - dataType: accountType + 'Account', - acceleratorKey: configData.email, - }, - UpdateExpression: 'set #dataBag = :v_dataBag, #commitId = :v_commitId, #ouName = :v_ouName', - ExpressionAttributeNames: { - '#dataBag': 'dataBag', - '#commitId': 'commitId', - '#ouName': 'ouName', - }, - ExpressionAttributeValues: { - ':v_dataBag': JSON.stringify(configData), - ':v_commitId': commitId, - ':v_ouName': ouName, - }, - }; - await throttlingBackOff(() => documentClient.send(new UpdateCommand(params))); - } -} - -async function isStackInRollback(stackName: string): Promise { - const response = await throttlingBackOff(() => - cloudformationClient.send(new DescribeStacksCommand({ StackName: stackName })), - ); - console.log(response); - if (response.Stacks && response.Stacks[0].StackStatus == 'UPDATE_ROLLBACK_IN_PROGRESS') { - return true; - } - return false; -} - -async function onCreateUpdateFunction( - partition: string, - configTableName: string, - commitId: string, - bucket: { - name: string; - organizationsConfigS3Key: string; - accountConfigS3Key: string; - replacementsConfigS3Key: string; - }, - emails: { - managementAccount: string; - auditAccount: string; - logArchiveAccount: string; - }, - isOrgsEnabled: boolean, -): Promise<{ - PhysicalResourceId: string | undefined; - Status: string; -}> { - const accountsConfigContent = await getConfigFileContents(bucket.name, bucket.accountConfigS3Key); - const accountsValues = parseAccountsConfig(yaml.load(accountsConfigContent)); - const accountsConfig = new AccountsConfig( - { - managementAccountEmail: emails.managementAccount, - auditAccountEmail: emails.auditAccount, - logArchiveAccountEmail: emails.logArchiveAccount, - }, - accountsValues, - ); - - let replacementsConfig = undefined; - if (bucket.replacementsConfigS3Key) { - const replacementsConfigContent = await getConfigFileContents(bucket.name, bucket.replacementsConfigS3Key); - const replacementsValues = parseReplacementsConfig(yaml.load(replacementsConfigContent)); - - // edge-case: loading without looking up SSM replacements - replacementsConfig = new ReplacementsConfig(replacementsValues, accountsConfig, true); - const orgConfigContent = await getConfigFileContents(bucket.name, bucket.organizationsConfigS3Key); - const isOrgsEnabled = OrganizationConfig.loadFromString(orgConfigContent, replacementsConfig).enable; - replacementsConfig.loadReplacementValues({}, isOrgsEnabled); - } - - const organizationConfigContent = await getConfigFileContents(bucket.name, bucket.organizationsConfigS3Key); - const organizationConfig = OrganizationConfig.loadFromString(organizationConfigContent, replacementsConfig); - await organizationConfig.loadOrganizationalUnitIds(partition); - - await putAllOrganizationConfigInTable(organizationConfig, configTableName, commitId); - - // Boolean to set single account deployment mode - const enableSingleAccountMode = process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] - ? process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] === 'true' - : false; - - await accountsConfig.loadAccountIds(partition, enableSingleAccountMode, isOrgsEnabled, accountsConfig); - - for (const account of accountsConfig.mandatoryAccounts) { - switch (account.name) { - case 'Management': - const managmentId = accountsConfig.getManagementAccountId(); - await putAccountConfigInTable( - 'mandatory', - account, - configTableName, - managmentId, - commitId, - account.organizationalUnit, - ); - break; - case 'LogArchive': - const logArchiveId = accountsConfig.getLogArchiveAccountId(); - await putAccountConfigInTable( - 'mandatory', - account, - configTableName, - logArchiveId, - commitId, - account.organizationalUnit, - ); - break; - case 'Audit': - const auditId = accountsConfig.getAuditAccountId(); - await putAccountConfigInTable( - 'mandatory', - account, - configTableName, - auditId, - commitId, - account.organizationalUnit, - ); - break; - } - const awsKey = accountsConfig.getAccountId(account.name) || ''; - await putAccountConfigInTable('mandatory', account, configTableName, awsKey, commitId, account.organizationalUnit); - } - for (const account of accountsConfig.workloadAccounts) { - let accountId: string; - try { - accountId = accountsConfig.getAccountId(account.name); - } catch { - accountId = ''; - } - await putAccountConfigInTable( - 'workload', - account, - configTableName, - accountId, - commitId, - account.organizationalUnit, - ); - } - return { - PhysicalResourceId: commitId, - Status: 'Success', - }; -} - -async function putAllOrganizationConfigInTable( - organizationConfig: OrganizationConfig, - configTableName: string, - commitId: string, -) { - for (const organizationalUnit of organizationConfig.organizationalUnits) { - let awsKey = ''; - try { - awsKey = organizationConfig.getOrganizationalUnitId(organizationalUnit.name); - } catch (error) { - let message; - - if (error instanceof Error) message = error.message; - else message = String(error); - - if (message.startsWith('configuration validation failed')) awsKey = ''; - else throw error; - } - await putOrganizationConfigInTable(organizationalUnit, configTableName, awsKey, commitId); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/package.json deleted file mode 100644 index 739bc21..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "@aws-accelerator/load-config-table", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/config": "^0.0.0", - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-cloudformation": "3.410.0", - "@aws-sdk/client-dynamodb": "3.410.0", - "@aws-sdk/client-s3": "3.412.0", - "@aws-sdk/lib-dynamodb": "3.410.0", - "@aws-sdk/smithy-client": "3.374.0", - "@aws-sdk/types": "3.410.0", - "js-yaml": "4.1.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/tsconfig.json deleted file mode 100644 index 88a8b36..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/load-config-table/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "moduleResolution": "node", - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/index.ts deleted file mode 100644 index f46e7e7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SNSClient, PublishCommand, PublishCommandInput } from '@aws-sdk/client-sns'; -import { SNSEvent } from '@aws-accelerator/utils/lib/common-types'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -const partition: string = process.env['PARTITION'] ?? 'aws'; -const centralAccount = process.env['SNS_CENTRAL_ACCOUNT']; -const region = process.env['AWS_REGION']; - -const snsClient = new SNSClient({ customUserAgent: process.env['SOLUTION_ID'] }); -/** - * control-tower-notification-forwarder - lambda handler - * - * @param event - * @returns - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: SNSEvent): Promise { - console.log(JSON.stringify(event)); - - const snsNotification = event.Records[0].Sns; - const topicArn = snsNotification.TopicArn; - const topicName = topicArn.split(':').pop(); - - const destinationArn = `arn:${partition}:sns:${region}:${centralAccount}:${topicName}`; - - console.log('Forwarding message to sns topic arn:', destinationArn); - - const subject = snsNotification.Subject; - console.log('Subject: ', subject); - const message = snsNotification.Message; - - const params: PublishCommandInput = { - Subject: subject, - Message: message, - TopicArn: destinationArn, - }; - const response = await throttlingBackOff(() => snsClient.send(new PublishCommand(params))); - console.log(response); -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/package.json deleted file mode 100644 index 6f2dc18..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-sns-topic-forwarder", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda SNS Topic Forwarder", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-sns": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/sns-topic-forwarder/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/index.ts b/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/index.ts deleted file mode 100644 index 07ca8c6..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/index.ts +++ /dev/null @@ -1,931 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - DynamoDBDocumentClient, - PutCommand, - PutCommandInput, - QueryCommand, - QueryCommandInput, - paginateQuery, - DynamoDBDocumentPaginationConfiguration, -} from '@aws-sdk/lib-dynamodb'; -import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation'; -import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -type scpTargetType = 'ou' | 'account'; - -type serviceControlPolicyType = { - name: string; - targetType: scpTargetType; - targets: { name: string; id: string }[]; -}; - -type provisionedProductStatus = { - status: string; - statusMessage: string; -}; - -const marshallOptions = { - convertEmptyValues: false, - //overriding default value of false - removeUndefinedValues: true, - convertClassInstanceToMap: false, -}; -const unmarshallOptions = { - wrapNumbers: false, -}; -const translateConfig = { marshallOptions, unmarshallOptions }; -let paginationConfig: DynamoDBDocumentPaginationConfiguration; -let dynamodbClient: DynamoDBClient; -let documentClient: DynamoDBDocumentClient; -let serviceCatalogClient: AWS.ServiceCatalog; -let cloudformationClient: CloudFormationClient; -let ssmClient: SSMClient; -let organizationsClient: AWS.Organizations; - -type AccountToAdd = { - name: string; - description: string; - email: string; - enableGovCloud?: boolean; - organizationalUnitId: string; -}; - -type ConfigOrganizationalUnitKeys = { - acceleratorKey: string; - awsKey: string; - registered: boolean | undefined; - ignore: boolean; -}; - -type AwsOrganizationalUnitKeys = { - acceleratorKey: string; - awsKey: string; -}; - -type DDBItem = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -}; -type DDBItems = Array; - -const validationErrors: string[] = []; -const ctAccountsToAdd: DDBItems = []; -const orgAccountsToAdd: DDBItems = []; -let mandatoryAccounts: DDBItems = []; -let workloadAccounts: DDBItems = []; -let organizationAccounts: AWS.Organizations.Account[] = []; -let configAllOuKeys: ConfigOrganizationalUnitKeys[] = []; -let configActiveOuKeys: ConfigOrganizationalUnitKeys[] = []; -let configIgnoredOuKeys: ConfigOrganizationalUnitKeys[] = []; -const awsOuKeys: AwsOrganizationalUnitKeys[] = []; -let driftDetectionParameterName = ''; -let driftDetectionMessageParameterName = ''; - -/** - * validate-environment - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const partition = event.ResourceProperties['partition']; - const configTableName = event.ResourceProperties['configTableName']; - const newOrgAccountsTableName = event.ResourceProperties['newOrgAccountsTableName']; - const newCTAccountsTableName = event.ResourceProperties['newCTAccountsTableName']; - const controlTowerEnabled = event.ResourceProperties['controlTowerEnabled']; - const organizationsEnabled = event.ResourceProperties['organizationsEnabled']; - const policyTagKey = event.ResourceProperties['policyTagKey']; - const commitId = event.ResourceProperties['commitId']; - const stackName = event.ResourceProperties['stackName']; - const serviceControlPolicies: serviceControlPolicyType[] = event.ResourceProperties['serviceControlPolicies']; - driftDetectionParameterName = event.ResourceProperties['driftDetectionParameterName']; - driftDetectionMessageParameterName = event.ResourceProperties['driftDetectionMessageParameterName']; - const skipScpValidation = event.ResourceProperties['skipScpValidation']; - - const solutionId = process.env['SOLUTION_ID']; - - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1' }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1' }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - - dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId }); - documentClient = DynamoDBDocumentClient.from(dynamodbClient, translateConfig); - serviceCatalogClient = new AWS.ServiceCatalog({ customUserAgent: solutionId }); - cloudformationClient = new CloudFormationClient({ customUserAgent: solutionId }); - ssmClient = new SSMClient({}); - paginationConfig = { - client: documentClient, - pageSize: 100, - }; - - switch (event.RequestType) { - case 'Create': - case 'Update': - // if stack rollback is in progress don't do anything - // the stack may have failed as the results of errors - // from this construct - // when rolling back this construct will execute and - // fail again preventing stack rollback - if (await isStackInRollback(stackName)) { - return { - Status: 'SUCCESS', - }; - } - console.log(`Configuration repository commit id ${commitId}`); - - if (organizationsEnabled) { - configAllOuKeys = await getConfigOuKeys(configTableName, commitId); - configActiveOuKeys = configAllOuKeys.filter(item => item.ignore === false); - configIgnoredOuKeys = configAllOuKeys.filter(item => item.ignore === true); - - console.debug('Active OU List', configActiveOuKeys); - console.debug('Ignored OU List', configIgnoredOuKeys); - - await getAwsOrganizationalUnitKeys(await getRootId(), ''); - // get accounts from organizations - organizationAccounts = await getOrganizationAccounts(configActiveOuKeys); - } - - mandatoryAccounts = await getConfigFromTableForCommit(configTableName, 'mandatoryAccount', commitId); - workloadAccounts = await getConfigFromTableForCommit(configTableName, 'workloadAccount', commitId); - - if (controlTowerEnabled === 'true') { - await validateControlTower(); - } - - const allOuInConfigErrors = await validateAllOuInConfig(); - validationErrors.push(...allOuInConfigErrors); - - const validateOrganizationalUnits = await validateOrganizationalUnitsExist(configActiveOuKeys); - validationErrors.push(...validateOrganizationalUnits); - - const validateAccountsAreInOu = await validateAccountsInOu(configTableName, configActiveOuKeys); - validationErrors.push(...validateAccountsAreInOu); - - const validateAllAwsAccountsAreInConfig = await validateAllAwsAccountsInConfig(); - validationErrors.push(...validateAllAwsAccountsAreInConfig); - - // find organization accounts that need to be created - console.log(`controlTowerEnabled value: ${controlTowerEnabled}`); - if (controlTowerEnabled === 'false' && mandatoryAccounts) { - for (const mandatoryAccount of mandatoryAccounts) { - const awsOuKey = configAllOuKeys.find(ouKeyItem => ouKeyItem.acceleratorKey === mandatoryAccount['ouName']); - if (awsOuKey?.ignore === false) { - const mandatoryOrganizationAccount = organizationAccounts.find( - item => item.Email == mandatoryAccount['acceleratorKey'], - ); - if (mandatoryOrganizationAccount) { - if (mandatoryOrganizationAccount.Status !== 'ACTIVE') { - validationErrors.push( - `Mandatory account ${mandatoryAccount['acceleratorKey']} is in ${mandatoryOrganizationAccount.Status}`, - ); - } - } else { - orgAccountsToAdd.push(mandatoryAccount); - } - } - } - } - if (workloadAccounts) { - for (const workloadAccount of workloadAccounts) { - const awsOuKey = configAllOuKeys.find(ouKeyItem => ouKeyItem.acceleratorKey === workloadAccount['ouName']); - if (awsOuKey?.ignore === false) { - const organizationAccount = organizationAccounts.find( - item => item.Email == workloadAccount['acceleratorKey'], - ); - if (organizationAccount) { - if (organizationAccount.Status !== 'ACTIVE') { - validationErrors.push( - `Workload account ${workloadAccount['acceleratorKey']} is in ${organizationAccount.Status}`, - ); - } - } else { - const accountConfig = JSON.parse(workloadAccount['dataBag']); - if (controlTowerEnabled === 'false' || accountConfig['enableGovCloud']) { - // check against ignored - orgAccountsToAdd.push(workloadAccount); - } - } - } - } - } - - // put accounts to create in DynamoDb - console.log(`Org Accounts to add: ${JSON.stringify(orgAccountsToAdd)}`); - for (const account of orgAccountsToAdd) { - const accountOu = configActiveOuKeys.find(item => item.acceleratorKey === account['ouName']); - const parsedDataBag = JSON.parse(account['dataBag']); - let accountConfig: AccountToAdd; - if (accountOu?.awsKey) { - accountConfig = { - name: parsedDataBag['name'], - email: account['acceleratorKey'], - description: parsedDataBag['description'], - enableGovCloud: parsedDataBag['enableGovCloud'] || false, - organizationalUnitId: accountOu?.awsKey, - }; - const params: PutCommandInput = { - TableName: newOrgAccountsTableName, - Item: { - accountEmail: accountConfig.email, - accountConfig: JSON.stringify(accountConfig), - }, - }; - await throttlingBackOff(() => documentClient.send(new PutCommand(params))); - } else { - // should not get here we just created and validated all of the ou's. - validationErrors.push( - `Unable to find Organizational Unit ${account['ouName']} in configuration or OU ignore property is set to true`, - ); - } - } - - console.log(`CT Accounts to add: ${JSON.stringify(ctAccountsToAdd)}`); - for (const account of ctAccountsToAdd) { - const accountOu = configActiveOuKeys.find(item => item.acceleratorKey === account['ouName']); - const parsedDataBag = JSON.parse(account['dataBag']); - let accountConfig: AccountToAdd; - if (accountOu?.awsKey) { - accountConfig = { - name: parsedDataBag['name'], - email: account['acceleratorKey'], - description: parsedDataBag['description'], - // formatting for CT requirements to support nested ou's - organizationalUnitId: (await getOuName(account['ouName'])) + ` (${accountOu.awsKey})`, - }; - const params: PutCommandInput = { - TableName: newCTAccountsTableName, - Item: { - accountEmail: accountConfig.email, - accountConfig: JSON.stringify(accountConfig), - }, - }; - await throttlingBackOff(() => documentClient.send(new PutCommand(params))); - } else { - // should not get here we just created and validated all of the ou's. - validationErrors.push( - `Unable to find Organizational Unit ${account['ouName']} in configuration or OU ignore property is set to true`, - ); - } - } - - // - // Validate SCP count - // - if (skipScpValidation.toLowerCase() === 'no') { - await validateServiceControlPolicyCount(organizationsClient, serviceControlPolicies, policyTagKey); - } else { - console.log('Skipping SCP count validation'); - } - - console.log(`validationErrors: ${JSON.stringify(validationErrors)}`); - - if (validationErrors.length > 0) { - throw new Error(validationErrors.toString()); - } - - return { - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Function to validate number of SCPs attached to ou and account is not more than 5 - * @param organizationsClient - * @param serviceControlPolicies - */ -async function validateServiceControlPolicyCount( - organizationsClient: AWS.Organizations, - serviceControlPolicies: serviceControlPolicyType[], - - policyTagKey: string, -) { - const processedTargets: string[] = []; - for (const scpItem of serviceControlPolicies) { - for (const target of scpItem.targets ?? []) { - const existingAttachedScps: string[] = []; - if (processedTargets.indexOf(target.name) === -1) { - const response = await throttlingBackOff(() => - organizationsClient - .listPoliciesForTarget({ - Filter: 'SERVICE_CONTROL_POLICY', - TargetId: target.id, - MaxResults: 10, - }) - .promise(), - ); - - if (response.Policies && response.Policies.length > 0) { - response.Policies.forEach(item => existingAttachedScps.push(item.Name!)); - } - - const totalScps = await getTotalScps( - target.name, - scpItem.targetType, - existingAttachedScps, - serviceControlPolicies, - policyTagKey, - ); - - const totalScpCount = totalScps.length; - - console.log(`Scp count validation started for target ${target.name}, target type is ${scpItem.targetType}`); - console.log(`${target.name} ${scpItem.targetType} existing attached scps are - ${existingAttachedScps}`); - console.log(`${target.name} ${scpItem.targetType} updated list of scps for attachment - ${totalScps}`); - console.log(`${target.name} ${scpItem.targetType} total scp count is ${totalScpCount}`); - - if (totalScpCount > 5) { - console.log( - `${target.name} ${scpItem.targetType} scp count validation failed, total scp count is ${totalScpCount}`, - ); - validationErrors.push( - `Max Allowed SCPs for ${scpItem.targetType} "${target.name}" is 5, found total ${totalScps.length} scps in updated list to attach. Updated list of scps for attachment is ${totalScps}`, - ); - } else { - console.log( - `${target.name} ${scpItem.targetType} scp count validation successful, total scp count is ${totalScpCount}`, - ); - } - - processedTargets.push(target.name); - } - } - } -} - -/** - * Function to get total scps to be attached to the target - * @param targetName - * @param targetType - * @param existingScps - * @param serviceControlPolicies - * @returns - */ -async function getTotalScps( - targetName: string, - targetType: scpTargetType, - existingScps: string[], - serviceControlPolicies: serviceControlPolicyType[], - policyTagKey: string, -): Promise { - const totalScps: string[] = getNewScps(targetName, targetType, existingScps, serviceControlPolicies); - - for (const existingScp of existingScps) { - // check for control tower drift - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient - .listPolicies({ - Filter: 'SERVICE_CONTROL_POLICY', - NextToken: nextToken, - }) - .promise(), - ); - for (const policy of page.Policies ?? []) { - if (policy.Name === existingScp) { - const configScp = serviceControlPolicies.find(item => item.name === existingScp); - const isAwsManaged = policy.AwsManaged ?? false; - const isLzaManaged = isAwsManaged ? false : await isLzaManagedPolicy(policy.Id!, policyTagKey); - - // When attached policy is AWS managed, add to list of policies - if (isAwsManaged) { - totalScps.push(existingScp); - break; - } - - // When attached policy is NOT AWS managed and NOT LZA managed, add to list of policies, policies attached by other sources - if (!isLzaManaged) { - totalScps.push(existingScp); - break; - } - - // When attached policy is LZA managed, check if this is still present in config before adding to list of policies - if (isLzaManaged) { - if ( - configScp && - configScp.targetType === targetType && - configScp.targets.find(item => item.name === targetName) - ) { - totalScps.push(existingScp); - break; - } - } - } - } - nextToken = page.NextToken; - } while (nextToken); - } - - return totalScps; -} - -/** - * Function to check if policy is managed by LZA, this is by checking lzaManaged tag with Yes value - * @param policyId - * @returns - */ -async function isLzaManagedPolicy(policyId: string, policyTagKey: string): Promise { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient - .listTagsForResource({ - ResourceId: policyId, - NextToken: nextToken, - }) - .promise(), - ); - for (const tag of page.Tags ?? []) { - if (tag.Key === policyTagKey && tag.Value === 'Yes') { - return true; - } - } - nextToken = page.NextToken; - } while (nextToken); - - return false; -} - -/** - * Function to get list of new scps to be attached from organization config file - * @param targetName - * @param targetType - * @param existingScps - * @param serviceControlPolicies - * @returns - */ -function getNewScps( - targetName: string, - targetType: scpTargetType, - existingScps: string[], - serviceControlPolicies: serviceControlPolicyType[], -): string[] { - const configScps: string[] = []; - - for (const scpItem of serviceControlPolicies) { - for (const target of scpItem.targets ?? []) { - if (scpItem.targetType === targetType && target.name === targetName) { - configScps.push(scpItem.name); - } - } - } - return configScps.filter(x => existingScps.indexOf(x) === -1); -} - -async function validateControlTower() { - // confirm mandatory accounts exist in aws - for (const mandatoryAccount of mandatoryAccounts) { - const existingAccount = organizationAccounts.find(item => item.Email == mandatoryAccount['acceleratorKey']); - if (existingAccount?.Status == 'ACTIVE') { - console.log(`Mandatory Account ${mandatoryAccount['acceleratorKey']} exists.`); - } else { - validationErrors.push( - `Mandatory account ${mandatoryAccount['acceleratorKey']} does not exist in AWS or is suspended`, - ); - } - } - - // validate that no ou's are deregistered - const validateOrganizationalUnitsRegistered = await validateOrganizationalUnitsAreRegistered(configActiveOuKeys); - validationErrors.push(...validateOrganizationalUnitsRegistered); - - // check for control tower drift - const driftDetected = await throttlingBackOff(() => - ssmClient.send( - new GetParameterCommand({ - Name: driftDetectionParameterName, - }), - ), - ); - - if (driftDetected.Parameter?.Value == 'true') { - const driftDetectedMessage = await throttlingBackOff(() => - ssmClient.send( - new GetParameterCommand({ - Name: driftDetectionMessageParameterName, - }), - ), - ); - validationErrors.push(driftDetectedMessage.Parameter?.Value ?? ''); - } - - if (workloadAccounts) { - for (const workloadAccount of workloadAccounts) { - const accountConfig = JSON.parse(workloadAccount['dataBag']); - const accountName = accountConfig['name']; - const account = organizationAccounts.find(oa => oa.Email == workloadAccount['acceleratorKey']); - - if (!account) { - console.log(`push to ctAccountsToAdd does not exist ${accountName}`); - ctAccountsToAdd.push(workloadAccount); - continue; - } - - const provisionedProductStatus = await getControlTowerProvisionedProductStatus(account.Id!); - if (!provisionedProductStatus) { - console.log(`push to ctAccountsToAdd not enrolled in CT ${accountName}`); - ctAccountsToAdd.push(workloadAccount); - continue; - } - console.log(`Found provisioned account ${accountName}`); - switch (provisionedProductStatus.status) { - case 'AVAILABLE': - break; - case 'TAINTED': - validationErrors.push( - `AWS Account ${workloadAccount['acceleratorKey']} is TAINTED state. Message: ${provisionedProductStatus.statusMessage}. Check Service Catalog`, - ); - break; - case 'ERROR': - validationErrors.push( - `AWS Account ${workloadAccount['acceleratorKey']} is in ERROR state. Message: ${provisionedProductStatus.statusMessage}. Check Service Catalog`, - ); - break; - case 'UNDER_CHANGE': - break; - case 'PLAN_IN_PROGRESS': - break; - } - } - } -} - -async function getOuName(name: string): Promise { - const result = name.split('/').pop(); - if (result === undefined) { - return name; - } - return result; -} - -async function getControlTowerProvisionedProductStatus( - accountId: string, -): Promise { - const provisionedProduct = await throttlingBackOff(() => - serviceCatalogClient - .searchProvisionedProducts({ - Filters: { - SearchQuery: [`physicalId: ${accountId}`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .promise(), - ); - - if (provisionedProduct === undefined || provisionedProduct.ProvisionedProducts === undefined) { - return undefined; - } - - for (const product of provisionedProduct.ProvisionedProducts) { - if (product.Type === 'CONTROL_TOWER_ACCOUNT') { - return { status: product.Status, statusMessage: product.StatusMessage } as provisionedProductStatus; - } - } - - return undefined; -} - -async function getOrganizationAccounts( - organizationalUnitKeys: ConfigOrganizationalUnitKeys[], -): Promise { - const organizationAccounts: AWS.Organizations.Account[] = []; - for (const ouKey of organizationalUnitKeys) { - if (!ouKey.awsKey) { - validationErrors.push(`Organizational Unit "${ouKey.acceleratorKey}" not found.`); - continue; - } - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.listAccountsForParent({ ParentId: ouKey.awsKey, NextToken: nextToken }).promise(), - ); - for (const account of page.Accounts ?? []) { - organizationAccounts.push(account); - } - nextToken = page.NextToken; - } while (nextToken); - } - return organizationAccounts; -} - -async function getConfigFromTableForCommit( - configTableName: string, - dataType: string, - commitId: string, -): Promise { - const params: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': dataType, - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - }; - const items: DDBItems = []; - const paginator = paginateQuery(paginationConfig, params); - for await (const page of paginator) { - if (page.Items) { - for (const item of page.Items) { - items.push(item); - } - } - } - return items; -} - -async function validateOrganizationalUnitsExist( - organizationalUnitKeys: ConfigOrganizationalUnitKeys[], -): Promise { - const errors: string[] = []; - const missingOrganizationalUnits = organizationalUnitKeys.filter(item => item.awsKey === undefined); - - if (missingOrganizationalUnits.length > 0) { - for (const item of missingOrganizationalUnits) { - console.log(`Organizational Unit ${item.acceleratorKey} does not exist in AWS`); - errors.push( - `Organizational Unit ${item.acceleratorKey} does not exist in AWS. Either remove from configuration or add OU via console.`, - ); - } - } - return errors; -} - -async function validateOrganizationalUnitsAreRegistered( - organizationalUnitKeys: ConfigOrganizationalUnitKeys[], -): Promise { - const errors: string[] = []; - const deregisteredOrganizationalUnits = organizationalUnitKeys.filter(item => item.registered === false); - if (deregisteredOrganizationalUnits.length > 0) { - for (const item of deregisteredOrganizationalUnits) { - console.log(`Organizational Unit ${item.acceleratorKey} may not be registered in Control Tower`); - errors.push( - `Organizational Unit ${item.acceleratorKey} may not be registered in Control Tower. Re-register OU in Control Tower to resolve.`, - ); - } - } - // look for ou's that don't have a registration status - // confirm top level ou's have at least one guardrail attached - for (const ouKey of organizationalUnitKeys) { - if (ouKey.registered || !ouKey.awsKey || ouKey.acceleratorKey.split('/').length >>> 1) { - continue; - } - console.log('OU without registration status in config table, checking guardrails', ouKey); - const isGuardRailAttached = await isGuardRailAttachedToOu(ouKey.awsKey); - if (!isGuardRailAttached) { - console.log( - `Organizational Unit ${ouKey.acceleratorKey} may not be registered in Control Tower. No guardrail attached and may not be registered.`, - ); - errors.push( - `Organizational Unit ${ouKey.acceleratorKey} may not be registered in Control Tower. No guardrail is attached and registration status is not available.`, - ); - } - } - return errors; -} - -async function validateAccountsInOu( - configTableName: string, - organizationalUnitKeys: ConfigOrganizationalUnitKeys[], -): Promise { - const errors: string[] = []; - let nextToken: string | undefined = undefined; - - const workloadAccountParams: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'workloadAccount', - }, - ProjectionExpression: 'acceleratorKey, awsKey, ouName', - }; - const workloadAccountResponse = await throttlingBackOff(() => - documentClient.send(new QueryCommand(workloadAccountParams)), - ); - const workloadAccountKeys: { acceleratorKey: string; awsKey: string; ouName: string }[] = []; - if (workloadAccountResponse.Items) { - for (const item of workloadAccountResponse.Items) { - workloadAccountKeys.push({ - acceleratorKey: item['acceleratorKey'], - awsKey: item['awsKey'], - ouName: item['ouName'], - }); - } - } - - const mandatoryAccountParams: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'mandatoryAccount', - }, - ProjectionExpression: 'acceleratorKey, awsKey, ouName', - }; - const mandatoryAccountResponse = await throttlingBackOff(() => - documentClient.send(new QueryCommand(mandatoryAccountParams)), - ); - const mandatoryAccountKeys: { acceleratorKey: string; awsKey: string; ouName: string }[] = []; - if (mandatoryAccountResponse.Items) { - for (const item of mandatoryAccountResponse.Items) { - mandatoryAccountKeys.push({ - acceleratorKey: item['acceleratorKey'], - awsKey: item['awsKey'], - ouName: item['ouName'], - }); - } - } - - const accountKeys = mandatoryAccountKeys; - accountKeys.push(...workloadAccountKeys); - - for (const ou of organizationalUnitKeys) { - const children: string[] = []; - // if we don't have an awsKey then we didn't find the OU don't attempt to lookup child accounts - if (!ou.awsKey) { - continue; - } - do { - const page = await throttlingBackOff(() => - organizationsClient.listChildren({ ChildType: 'ACCOUNT', ParentId: ou.awsKey, NextToken: nextToken }).promise(), - ); - for (const child of page.Children ?? []) { - const account = accountKeys.find(item => item.awsKey === child.Id); - if (account) { - children.push(account.awsKey); - if (account.ouName !== ou.acceleratorKey) { - errors.push( - `Account ${account.acceleratorKey} with account id ${account.awsKey} is not in the correct OU. Account is in the ou named ${ou.acceleratorKey} and should be in ${account.ouName}`, - ); - } - } else { - errors.push(`Found account with id ${child.Id} in OU ${ou.acceleratorKey} that is not in the configuration.`); - } - } - nextToken = page.NextToken; - } while (nextToken); - console.log(`OU Name: ${ou.acceleratorKey} Child Account ID's: ${children}`); - } - - return errors; -} - -async function getConfigOuKeys(configTableName: string, commitId: string): Promise { - const organizationParams: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'organization', - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - ProjectionExpression: 'acceleratorKey, awsKey, registered, dataBag', - }; - const organizationResponse = await throttlingBackOff(() => documentClient.send(new QueryCommand(organizationParams))); - const ouKeys: ConfigOrganizationalUnitKeys[] = []; - if (organizationResponse.Items) { - for (const item of organizationResponse.Items) { - const ouConfig = JSON.parse(item['dataBag']); - const ignored = ouConfig['ignore'] ?? false; - if (ignored) { - console.log(`Organizational Unit ${item['acceleratorKey']} is configured to be ignored`); - } - ouKeys.push({ - acceleratorKey: item['acceleratorKey'], - awsKey: item['awsKey'], - registered: item['registered'] ?? undefined, - ignore: ignored, - }); - } - } - //get root ou key - const rootId = await getRootId(); - ouKeys.push({ - acceleratorKey: 'Root', - awsKey: rootId, - registered: true, - ignore: false, - }); - return ouKeys; -} - -async function isGuardRailAttachedToOu(ouId: string): Promise { - const response = await throttlingBackOff(() => - organizationsClient.listPoliciesForTarget({ TargetId: ouId, Filter: 'SERVICE_CONTROL_POLICY' }).promise(), - ); - for (const policy of response.Policies ?? []) { - if (policy.Name?.startsWith('aws-guardrails-') && policy.AwsManaged === false) { - return true; - } - } - return false; -} - -async function isStackInRollback(stackName: string): Promise { - const response = await throttlingBackOff(() => - cloudformationClient.send(new DescribeStacksCommand({ StackName: stackName })), - ); - if (response.Stacks && response.Stacks[0].StackStatus == 'UPDATE_ROLLBACK_IN_PROGRESS') { - return true; - } - return false; -} - -async function validateAllOuInConfig(): Promise { - const errors: string[] = []; - for (const ouKeys of awsOuKeys) { - if (configAllOuKeys.find(item => item.acceleratorKey === ouKeys.acceleratorKey)) { - continue; - } else { - errors.push( - `Organizational Unit '${ouKeys.acceleratorKey}' with id of '${ouKeys.awsKey}' was not found in the organization configuration.`, - ); - } - } - return errors; -} - -async function validateAllAwsAccountsInConfig(): Promise { - const errors: string[] = []; - for (const account of organizationAccounts) { - if (workloadAccounts.find(item => item['acceleratorKey'] === account.Email!)) { - continue; - } - if (mandatoryAccounts.find(item => item['acceleratorKey'] === account.Email!)) { - continue; - } - //check if ou is ignored - const response = await throttlingBackOff(() => organizationsClient.listParents({ ChildId: account.Id! }).promise()); - if (!configIgnoredOuKeys.find(item => item.awsKey === response.Parents![0].Id)) { - errors.push( - `Account with Id ${account.Id} and email ${account.Email} is not in the accounts configuration and is not a member of an ignored OU.`, - ); - } - } - return errors; -} - -async function getAwsOrganizationalUnitKeys(ouId: string, path: string) { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.listOrganizationalUnitsForParent({ ParentId: ouId, NextToken: nextToken }).promise(), - ); - for (const ou of page.OrganizationalUnits ?? []) { - awsOuKeys.push({ acceleratorKey: `${path}${ou.Name!}`, awsKey: ou.Id! }); - await getAwsOrganizationalUnitKeys(ou.Id!, `${path}${ou.Name!}/`); - } - nextToken = page.NextToken; - } while (nextToken); -} - -async function getRootId(): Promise { - // get root ou id - let rootId = ''; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => organizationsClient.listRoots({ NextToken: nextToken }).promise()); - for (const item of page.Roots ?? []) { - if (item.Name === 'Root' && item.Id && item.Arn) { - rootId = item.Id; - } - } - nextToken = page.NextToken; - } while (nextToken); - return rootId; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/package.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/package.json deleted file mode 100644 index b480de1..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/lambdas-validate-environment-config", - "version": "0.0.0", - "private": true, - "description": "Accelerator Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-cloudformation": "3.410.0", - "@aws-sdk/client-dynamodb": "3.410.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/lib-dynamodb": "3.410.0", - "@aws-sdk/smithy-client": "3.374.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@aws-sdk/types": "3.410.0", - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/tsconfig.json b/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/lambdas/validate-environment/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/load-config-table.ts b/source/packages/@aws-accelerator/accelerator/lib/load-config-table.ts deleted file mode 100644 index 5b8e895..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/load-config-table.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; -import path = require('path'); - -export interface LoadAcceleratorConfigTableProps { - readonly acceleratorConfigTable: cdk.aws_dynamodb.ITable; - readonly configRepositoryName: string; - readonly managementAccountEmail: string; - readonly logArchiveAccountEmail: string; - readonly auditAccountEmail: string; - readonly configS3Bucket: string; - readonly organizationsConfigS3Key: string; - readonly accountConfigS3Key: string; - readonly replacementsConfigS3Key?: string; - readonly commitId: string; - readonly partition: string; - readonly managementAccountId: string; - readonly region: string; - readonly stackName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Boolean for single account mode (i.e. AWS Jam or Workshop) - */ - readonly enableSingleAccountMode: boolean; - /** - * Boolean for organization - */ - readonly isOrgsEnabled: boolean; -} - -/** - * Class Load Accelerator Config Table - */ -export class LoadAcceleratorConfigTable extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: LoadAcceleratorConfigTableProps) { - super(scope, id); - - const LOAD_CONFIG_TABLE_RESOURCE_TYPE = 'Custom::LoadAcceleratorConfigTable'; - - const environment: { - [key: string]: string; - } = {}; - - if (props.enableSingleAccountMode) { - environment['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] = 'true'; - } - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, LOAD_CONFIG_TABLE_RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'lambdas/load-config-table/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - timeout: cdk.Duration.minutes(15), - memorySize: cdk.Size.mebibytes(1024), - policyStatements: [ - { - Sid: 'organizations', - Effect: 'Allow', - Action: [ - 'organizations:ListAccounts', - 'organizations:ListRoots', - 'organizations:ListOrganizationalUnitsForParent', - ], - Resource: '*', - }, - { - Sid: 'getReplacements', - Effect: 'Allow', - Action: ['ssm:GetParameter'], - Resource: '*', - }, - { - Sid: 'configTable', - Effect: 'Allow', - Action: ['dynamodb:UpdateItem', 'dynamodb:PutItem'], - Resource: [props.acceleratorConfigTable.tableArn], - }, - { - Sid: 'kms', - Effect: 'Allow', - Action: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - Resource: [props.acceleratorConfigTable.encryptionKey?.keyArn], - }, - { - Sid: 's3', - Effect: 'Allow', - Action: ['s3:GetObject'], - Resource: [ - `arn:${cdk.Stack.of(this).partition}:s3:::cdk-accel-assets-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }/*`, - ], - }, - { - Sid: 'cloudFormation', - Effect: 'Allow', - Action: ['cloudformation:DescribeStacks'], - Resource: [ - `arn:${props.partition}:cloudformation:${props.region}:${props.managementAccountId}:stack/${props.stackName}*`, - ], - }, - ], - }); - - // - // Custom Resource definition. We want this resource to be evaluated on - // every CloudFormation update, so we generate a new uuid to force - // re-evaluation. - // - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: LOAD_CONFIG_TABLE_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - configTableName: props.acceleratorConfigTable.tableName, - configRepositoryName: props.configRepositoryName, - managementAccountEmail: props.managementAccountEmail, - auditAccountEmail: props.auditAccountEmail, - logArchiveAccountEmail: props.logArchiveAccountEmail, - configS3Bucket: props.configS3Bucket, - organizationsConfigS3Key: props.organizationsConfigS3Key, - accountConfigS3Key: props.accountConfigS3Key, - replacementsConfigS3Key: props.replacementsConfigS3Key, - commitId: props.commitId, - partition: props.partition, - stackName: props.stackName, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - isOrgsEnabled: props.isOrgsEnabled, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/pipeline.ts b/source/packages/@aws-accelerator/accelerator/lib/pipeline.ts deleted file mode 100644 index 72a0081..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/pipeline.ts +++ /dev/null @@ -1,927 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import * as codebuild from 'aws-cdk-lib/aws-codebuild'; -import * as codecommit from 'aws-cdk-lib/aws-codecommit'; -import * as codepipeline from 'aws-cdk-lib/aws-codepipeline'; -import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import { Construct } from 'constructs'; - -import { Bucket, BucketEncryptionType, ServiceLinkedRole } from '@aws-accelerator/constructs'; - -import { AcceleratorStage } from './accelerator-stage'; -import * as config_repository from './config-repository'; -import { AcceleratorToolkitCommand } from './toolkit'; -import { Repository } from '@aws-cdk-extensions/cdk-extensions'; -import { CONTROL_TOWER_LANDING_ZONE_VERSION } from '@aws-accelerator/utils/lib/control-tower'; - -/** - * - */ -export interface AcceleratorPipelineProps { - readonly toolkitRole: cdk.aws_iam.Role; - readonly awsCodeStarSupportedRegions: string[]; - readonly sourceRepository: string; - readonly sourceRepositoryOwner: string; - readonly sourceRepositoryName: string; - readonly sourceBranchName: string; - readonly enableApprovalStage: boolean; - readonly qualifier?: string; - readonly managementAccountId?: string; - readonly managementAccountRoleName?: string; - readonly managementAccountEmail: string; - readonly logArchiveAccountEmail: string; - readonly auditAccountEmail: string; - readonly controlTowerEnabled: string; - /** - * List of email addresses to be notified when pipeline is waiting for manual approval stage. - * If pipeline do not have approval stage enabled, this value will have no impact. - */ - readonly approvalStageNotifyEmailList?: string; - readonly partition: string; - /** - * Flag indicating installer using existing CodeCommit repository - */ - readonly useExistingConfigRepo: boolean; - /** - * User defined pre-existing config repository name - */ - readonly configRepositoryName: string; - /** - * User defined pre-existing config repository branch name - */ - readonly configRepositoryBranchName: string; - /** - * Accelerator resource name prefixes - */ - readonly prefixes: { - /** - * Use this prefix value to name resources like - - AWS IAM Role names, AWS Lambda Function names, AWS Cloudwatch log groups names, AWS CloudFormation stack names, AWS CodePipeline names, AWS CodeBuild project names - * - */ - readonly accelerator: string; - /** - * Use this prefix value to name AWS CodeCommit repository - */ - readonly repoName: string; - /** - * Use this prefix value to name AWS S3 bucket - */ - readonly bucketName: string; - /** - * Use this prefix value to name AWS SSM parameter - */ - readonly ssmParamName: string; - /** - * Use this prefix value to name AWS KMS alias - */ - readonly kmsAlias: string; - /** - * Use this prefix value to name AWS SNS topic - */ - readonly snsTopicName: string; - /** - * Use this prefix value to name AWS Secrets - */ - readonly secretName: string; - /** - * Use this prefix value to name AWS CloudTrail CloudWatch log group - */ - readonly trailLogName: string; - /** - * Use this prefix value to name AWS Glue database - */ - readonly databaseName: string; - }; - /** - * Boolean for single account mode (i.e. AWS Jam or Workshop) - */ - readonly enableSingleAccountMode: boolean; - /** - * Accelerator pipeline account id, for external deployment it will be pipeline account otherwise management account - */ - pipelineAccountId: string; - /** - * Flag indicating existing role - */ - readonly useExistingRoles: boolean; -} - -enum BuildLogLevel { - ERROR = 'error', - INFO = 'info', -} - -/** - * AWS Accelerator Pipeline Class, which creates the pipeline for AWS Landing zone - */ -export class AcceleratorPipeline extends Construct { - private readonly pipelineRole: iam.Role; - private readonly toolkitProject: codebuild.PipelineProject; - private readonly buildOutput: codepipeline.Artifact; - private readonly acceleratorRepoArtifact: codepipeline.Artifact; - private readonly configRepoArtifact: codepipeline.Artifact; - - private readonly pipeline: codepipeline.Pipeline; - private readonly props: AcceleratorPipelineProps; - private readonly installerKey: cdk.aws_kms.Key; - - constructor(scope: Construct, id: string, props: AcceleratorPipelineProps) { - super(scope, id); - - this.props = props; - - // - // Fields can be changed based on qualifier property - let acceleratorKeyArnSsmParameterName = `${props.prefixes.ssmParamName}/installer/kms/key-arn`; - let secureBucketName = `${props.prefixes.bucketName}-pipeline-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`; - let serverAccessLogsBucketNameSsmParam = `${props.prefixes.ssmParamName}/installer-access-logs-bucket-name`; - let pipelineName = `${props.prefixes.accelerator}-Pipeline`; - let buildProjectName = `${props.prefixes.accelerator}-BuildProject`; - let toolkitProjectName = `${props.prefixes.accelerator}-ToolkitProject`; - - // - // Change the fields when qualifier is present - if (this.props.qualifier) { - acceleratorKeyArnSsmParameterName = `${props.prefixes.ssmParamName}/${this.props.qualifier}/installer/kms/key-arn`; - secureBucketName = `${this.props.qualifier}-pipeline-${cdk.Stack.of(this).account}-${cdk.Stack.of(this).region}`; - serverAccessLogsBucketNameSsmParam = `${props.prefixes.ssmParamName}/${this.props.qualifier}/installer-access-logs-bucket-name`; - pipelineName = `${this.props.qualifier}-pipeline`; - buildProjectName = `${this.props.qualifier}-build-project`; - toolkitProjectName = `${this.props.qualifier}-toolkit-project`; - } - - let pipelineAccountEnvVariables: { [p: string]: codebuild.BuildEnvironmentVariable } | undefined; - - if (this.props.managementAccountId && this.props.managementAccountRoleName) { - pipelineAccountEnvVariables = { - MANAGEMENT_ACCOUNT_ID: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.props.managementAccountId, - }, - MANAGEMENT_ACCOUNT_ROLE_NAME: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.props.managementAccountRoleName, - }, - }; - } - - let enableSingleAccountModeEnvVariables: { [p: string]: codebuild.BuildEnvironmentVariable } | undefined; - if (props.enableSingleAccountMode) { - enableSingleAccountModeEnvVariables = { - ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: true, - }, - }; - } - - const enableAseaMigration = process.env['ENABLE_ASEA_MIGRATION']?.toLowerCase?.() === 'true'; - - let aseaMigrationModeEnvVariables: { [p: string]: codebuild.BuildEnvironmentVariable } | undefined; - if (enableAseaMigration) { - aseaMigrationModeEnvVariables = { - ENABLE_ASEA_MIGRATION: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: 'true', - }, - ASEA_MAPPING_BUCKET: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: `${props.prefixes.accelerator}-lza-resource-mapping-${cdk.Stack.of(this).account}`.toLowerCase(), - }, - ASEA_MAPPING_FILE: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: 'aseaResources.json', - }, - }; - } - - // Get installer key - this.installerKey = cdk.aws_kms.Key.fromKeyArn( - this, - 'AcceleratorKey', - cdk.aws_ssm.StringParameter.valueForStringParameter(this, acceleratorKeyArnSsmParameterName), - ) as cdk.aws_kms.Key; - - const bucket = new Bucket(this, 'SecureBucket', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: secureBucketName, - kmsKey: this.installerKey, - serverAccessLogsBucketName: cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - serverAccessLogsBucketNameSsmParam, - ), - }); - - // When non default config repository name provided - let configRepository: cdk.aws_codecommit.IRepository | Repository; - let configRepositoryBranchName = 'main'; - - if (props.useExistingConfigRepo) { - configRepository = cdk.aws_codecommit.Repository.fromRepositoryName( - this, - 'ConfigRepository', - props.configRepositoryName, - ); - configRepositoryBranchName = props.configRepositoryBranchName ?? 'main'; - } else { - configRepository = new config_repository.ConfigRepository(this, 'ConfigRepository', { - repositoryName: props.configRepositoryName, - repositoryBranchName: configRepositoryBranchName, - description: - 'AWS Accelerator configuration repository, created and initialized with default config file by pipeline', - managementAccountEmail: this.props.managementAccountEmail, - logArchiveAccountEmail: this.props.logArchiveAccountEmail, - auditAccountEmail: this.props.auditAccountEmail, - controlTowerEnabled: this.props.controlTowerEnabled, - controlTowerLandingZoneConfig: - this.props.controlTowerEnabled.toLocaleLowerCase() === 'yes' - ? { - version: CONTROL_TOWER_LANDING_ZONE_VERSION, - logging: { - loggingBucketRetentionDays: 365, - accessLoggingBucketRetentionDays: 3650, - organizationTrail: true, - }, - security: { enableIdentityCenterAccess: true }, - } - : undefined, - enableSingleAccountMode: this.props.enableSingleAccountMode, - }).getRepository(); - - const cfnRepository = configRepository.node.defaultChild as codecommit.CfnRepository; - cfnRepository.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN, { applyToUpdateReplacePolicy: true }); - } - - /** - * Pipeline - */ - this.pipelineRole = new iam.Role(this, 'PipelineRole', { - assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'), - }); - - this.pipeline = new codepipeline.Pipeline(this, 'Resource', { - pipelineName: pipelineName, - artifactBucket: bucket.getS3Bucket(), - role: this.pipelineRole, - }); - - this.acceleratorRepoArtifact = new codepipeline.Artifact('Source'); - this.configRepoArtifact = new codepipeline.Artifact('Config'); - - let sourceAction: - | cdk.aws_codepipeline_actions.CodeCommitSourceAction - | cdk.aws_codepipeline_actions.GitHubSourceAction; - - if (this.props.sourceRepository === 'codecommit') { - sourceAction = new codepipeline_actions.CodeCommitSourceAction({ - actionName: 'Source', - repository: codecommit.Repository.fromRepositoryName(this, 'SourceRepo', this.props.sourceRepositoryName), - branch: this.props.sourceBranchName, - output: this.acceleratorRepoArtifact, - trigger: codepipeline_actions.CodeCommitTrigger.NONE, - }); - } else { - sourceAction = new cdk.aws_codepipeline_actions.GitHubSourceAction({ - actionName: 'Source', - owner: this.props.sourceRepositoryOwner, - repo: this.props.sourceRepositoryName, - branch: this.props.sourceBranchName, - oauthToken: cdk.SecretValue.secretsManager('accelerator/github-token'), - output: this.acceleratorRepoArtifact, - trigger: cdk.aws_codepipeline_actions.GitHubTrigger.NONE, - }); - } - - this.pipeline.addStage({ - stageName: 'Source', - actions: [ - sourceAction, - new codepipeline_actions.CodeCommitSourceAction({ - actionName: 'Configuration', - repository: configRepository, - branch: configRepositoryBranchName, - output: this.configRepoArtifact, - trigger: codepipeline_actions.CodeCommitTrigger.NONE, - variablesNamespace: 'Config-Vars', - }), - ], - }); - - /** - * Build Stage - */ - const buildRole = new iam.Role(this, 'BuildRole', { - assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'), - }); - - const validateConfigPolicyDocument = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['organizations:ListAccounts', 'ssm:GetParameter'], - resources: ['*'], - }), - ], - }); - - const validateConfigPolicy = new cdk.aws_iam.ManagedPolicy(this, 'ValidateConfigPolicyDocument', { - document: validateConfigPolicyDocument, - }); - buildRole.addManagedPolicy(validateConfigPolicy); - - if (this.props.managementAccountId && this.props.managementAccountRoleName) { - const assumeExternalDeploymentRolePolicyDocument = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.props.partition}:iam::${this.props.managementAccountId}:role/${this.props.managementAccountRoleName}`, - ], - }), - ], - }); - - /** - * Create an IAM Policy for the build role to be able to lookup replacement parameters in the external deployment - * target account - */ - const assumeExternalDeploymentRolePolicy = new cdk.aws_iam.ManagedPolicy(this, 'AssumeExternalDeploymentPolicy', { - document: assumeExternalDeploymentRolePolicyDocument, - }); - buildRole.addManagedPolicy(assumeExternalDeploymentRolePolicy); - } - - // Pipeline/BuildRole/Resource AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - NagSuppressions.addResourceSuppressionsByPath( - cdk.Stack.of(this), - `${cdk.Stack.of(this).stackName}/Pipeline/BuildRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Managed policy for External Pipeline Deployment Lookups attached.', - }, - ], - ); - - const buildProject = new codebuild.PipelineProject(this, 'BuildProject', { - projectName: buildProjectName, - encryptionKey: this.installerKey, - role: buildRole, - buildSpec: codebuild.BuildSpec.fromObject({ - version: '0.2', - phases: { - install: { - 'runtime-versions': { - nodejs: 18, - }, - }, - pre_build: { - commands: [ - `export PACKAGE_VERSION=$(cat source/package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[:space:]')`, - `if [ "$ACCELERATOR_CHECK_VERSION" = "yes" ]; then - if [ "$PACKAGE_VERSION" != "$ACCELERATOR_PIPELINE_VERSION" ]; then - echo "ERROR: Accelerator package version in Source does not match currently installed LZA version. Please ensure that the Installer stack has been updated prior to updating the Source code in CodePipeline." - exit 1 - fi - fi`, - ], - }, - build: { - commands: [ - 'env', - 'cd source', - `if [ "${cdk.Stack.of(this).partition}" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - yarn config set registry https://registry.npmmirror.com - fi`, - 'yarn install', - 'yarn build', - 'yarn validate-config $CODEBUILD_SRC_DIR_Config', - ], - }, - }, - artifacts: { - files: ['**/*'], - 'enable-symlinks': 'yes', - }, - }), - environment: { - buildImage: codebuild.LinuxBuildImage.STANDARD_7_0, - privileged: false, - computeType: codebuild.ComputeType.MEDIUM, - environmentVariables: { - NODE_OPTIONS: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '--max_old_space_size=8192', - }, - PARTITION: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: cdk.Stack.of(this).partition, - }, - ACCELERATOR_PIPELINE_VERSION: { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: `${props.prefixes.ssmParamName}/${cdk.Stack.of(this).stackName}/version`, - }, - ACCELERATOR_CHECK_VERSION: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: 'yes', - }, - ...enableSingleAccountModeEnvVariables, - ...pipelineAccountEnvVariables, - }, - }, - cache: codebuild.Cache.local(codebuild.LocalCacheMode.SOURCE), - }); - - this.buildOutput = new codepipeline.Artifact('Build'); - - this.pipeline.addStage({ - stageName: 'Build', - actions: [ - new codepipeline_actions.CodeBuildAction({ - actionName: 'Build', - project: buildProject, - input: this.acceleratorRepoArtifact, - extraInputs: [this.configRepoArtifact], - outputs: [this.buildOutput], - role: this.pipelineRole, - }), - ], - }); - - /** - * Deploy Stage - */ - - this.toolkitProject = new codebuild.PipelineProject(this, 'ToolkitProject', { - projectName: toolkitProjectName, - encryptionKey: this.installerKey, - role: this.props.toolkitRole, - timeout: cdk.Duration.hours(8), - buildSpec: codebuild.BuildSpec.fromObject({ - version: '0.2', - phases: { - install: { - 'runtime-versions': { - nodejs: 18, - }, - }, - build: { - commands: [ - 'env', - 'cd source', - `if [ "prepare" = "\${ACCELERATOR_STAGE}" ]; then set -e && export LOG_LEVEL=${ - BuildLogLevel.INFO - } && yarn run ts-node packages/@aws-accelerator/modules/bin/runner.ts --module control-tower --partition ${ - cdk.Aws.PARTITION - } --use-existing-role ${ - this.props.useExistingRoles ? 'Yes' : 'No' - } --config-dir $CODEBUILD_SRC_DIR_Config && if [ -z "\${ACCELERATOR_NO_ORG_MODULE}" ]; then yarn run ts-node packages/@aws-accelerator/modules/bin/runner.ts --module aws-organizations --partition ${ - cdk.Aws.PARTITION - } --use-existing-role ${ - this.props.useExistingRoles ? 'Yes' : 'No' - } --config-dir $CODEBUILD_SRC_DIR_Config; else echo "Module aws-organizations execution skipped by environment settings."; fi && export LOG_LEVEL=${ - BuildLogLevel.ERROR - } ; fi`, - `if [ "prepare" = "\${ACCELERATOR_STAGE}" ]; then set -e && yarn run ts-node packages/@aws-accelerator/accelerator/lib/prerequisites.ts --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION} --minimal; fi`, - 'cd packages/@aws-accelerator/accelerator', - 'export FULL_SYNTH="true"', - 'if [ $ASEA_MAPPING_BUCKET ]; then aws s3api head-object --bucket $ASEA_MAPPING_BUCKET --key $ASEA_MAPPING_FILE >/dev/null 2>&1 || export FULL_SYNTH="false"; fi;', - `if [ -z "\${ACCELERATOR_STAGE}" ] && [ $CDK_OPTIONS = 'bootstrap' ] && [ $FULL_SYNTH = "true" ]; then for STAGE in "key" "logging" "organizations" "security-audit" "network-prep" "security" "operations" "identity-center" "network-vpc" "security-resources" "network-associations" "customizations" "finalize" "bootstrap"; do set -e && yarn run ts-node --transpile-only cdk.ts synth --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION} --stage $STAGE; done; fi`, - `if [ -z "\${ACCELERATOR_STAGE}" ] && [ $CDK_OPTIONS = 'diff' ] && [ $FULL_SYNTH = "true" ]; then for STAGE in "key" "logging" "organizations" "security-audit" "network-prep" "security" "operations" "identity-center" "network-vpc" "security-resources" "network-associations" "customizations" "finalize" "bootstrap"; do set -e && yarn run ts-node --transpile-only cdk.ts synth --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION} --stage $STAGE; done; fi`, - `if [ -z "\${ACCELERATOR_STAGE}" ] && [ $CDK_OPTIONS = 'bootstrap' ] && [ $FULL_SYNTH = "false" ]; then for STAGE in "bootstrap"; do set -e && yarn run ts-node --transpile-only cdk.ts synth --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION} --stage $STAGE; done; fi`, - `if [ ! -z "\${ACCELERATOR_STAGE}" ]; then yarn run ts-node --transpile-only cdk.ts synth --stage $ACCELERATOR_STAGE --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION}; fi`, - `if [ "diff" != "\${CDK_OPTIONS}" ]; then yarn run ts-node --transpile-only cdk.ts --require-approval never $CDK_OPTIONS --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION} --app cdk.out; fi`, - `if [ "diff" = "\${CDK_OPTIONS}" ]; then for STAGE in "key" "logging" "organizations" "security-audit" "network-prep" "security" "operations" "identity-center" "network-vpc" "security-resources" "network-associations" "customizations" "finalize" "bootstrap"; do set -e && yarn run ts-node --transpile-only cdk.ts --require-approval never $CDK_OPTIONS --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION} --app cdk.out --stage $STAGE; done; find ./cdk.out -type f -name "*.diff" -exec cat "{}" \\;; fi`, - `if [ "prepare" = "\${ACCELERATOR_STAGE}" ]; then cd ../../../ && set -e && yarn run ts-node packages/@aws-accelerator/accelerator/lib/prerequisites.ts --config-dir $CODEBUILD_SRC_DIR_Config --partition ${cdk.Aws.PARTITION}; fi`, - ], - }, - }, - }), - environment: { - buildImage: codebuild.LinuxBuildImage.STANDARD_7_0, - privileged: false, // Allow access to the Docker daemon - computeType: codebuild.ComputeType.LARGE, - environmentVariables: { - LOG_LEVEL: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: BuildLogLevel.ERROR, - }, - NODE_OPTIONS: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '--max_old_space_size=12288', - }, - CDK_METHOD: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: 'direct', - }, - CDK_NEW_BOOTSTRAP: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '1', - }, - ACCELERATOR_QUALIFIER: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.props.qualifier ? this.props.qualifier : 'aws-accelerator', - }, - ACCELERATOR_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.accelerator, - }, - ACCELERATOR_REPO_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.repoName, - }, - ACCELERATOR_BUCKET_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.bucketName, - }, - ACCELERATOR_KMS_ALIAS_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.kmsAlias, - }, - ACCELERATOR_SSM_PARAM_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.ssmParamName, - }, - ACCELERATOR_SNS_TOPIC_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.snsTopicName, - }, - ACCELERATOR_SECRET_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.secretName, - }, - ACCELERATOR_TRAIL_LOG_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.trailLogName, - }, - ACCELERATOR_DATABASE_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.databaseName, - }, - PIPELINE_ACCOUNT_ID: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.pipelineAccountId, - }, - ENABLE_DIAGNOSTICS_PACK: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: process.env['ENABLE_DIAGNOSTICS_PACK'] ?? 'Yes', - }, - INSTALLER_STACK_NAME: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: process.env['INSTALLER_STACK_NAME'] ?? '', - }, - ACCELERATOR_PERMISSION_BOUNDARY: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: process.env['ACCELERATOR_PERMISSION_BOUNDARY'] ?? '', - }, - ...enableSingleAccountModeEnvVariables, - ...pipelineAccountEnvVariables, - ...aseaMigrationModeEnvVariables, - }, - }, - cache: codebuild.Cache.local(codebuild.LocalCacheMode.SOURCE), - }); - - // /** - // * The Prepare stage is used to verify that all prerequisites have been made and that the - // * Accelerator can be deployed into the environment - // * Creates the accounts - // * Creates the ou's if control tower is not enabled - // */ - this.pipeline.addStage({ - stageName: 'Prepare', - actions: [this.createToolkitStage({ actionName: 'Prepare', command: 'deploy', stage: AcceleratorStage.PREPARE })], - }); - - this.pipeline.addStage({ - stageName: 'Accounts', - actions: [ - this.createToolkitStage({ actionName: 'Accounts', command: 'deploy', stage: AcceleratorStage.ACCOUNTS }), - ], - }); - - this.pipeline.addStage({ - stageName: 'Bootstrap', - actions: [this.createToolkitStage({ actionName: 'Bootstrap', command: `bootstrap` })], - }); - - // - // Add review stage based on parameter - this.addReviewStage(); - - /** - * The Logging stack establishes all the logging assets that are needed in - * all the accounts and will configure: - * - * - An S3 Access Logs bucket for every region in every account - * - The Central Logs bucket in the log-archive account - * - */ - this.pipeline.addStage({ - stageName: 'Logging', - actions: [ - this.createToolkitStage({ actionName: 'Key', command: 'deploy', stage: AcceleratorStage.KEY, runOrder: 1 }), - this.createToolkitStage({ - actionName: 'Logging', - command: 'deploy', - stage: AcceleratorStage.LOGGING, - runOrder: 2, - }), - ], - }); - - // Adds ASEA Import Resources stage - if (enableAseaMigration) { - this.pipeline.addStage({ - stageName: 'ImportAseaResources', - actions: [ - this.createToolkitStage({ - actionName: 'Import_Asea_Resources', - command: `deploy`, - stage: AcceleratorStage.IMPORT_ASEA_RESOURCES, - }), - ], - }); - } - - this.pipeline.addStage({ - stageName: 'Organization', - actions: [ - this.createToolkitStage({ - actionName: 'Organizations', - command: 'deploy', - stage: AcceleratorStage.ORGANIZATIONS, - }), - ], - }); - - this.pipeline.addStage({ - stageName: 'SecurityAudit', - actions: [ - this.createToolkitStage({ - actionName: 'SecurityAudit', - command: 'deploy', - stage: AcceleratorStage.SECURITY_AUDIT, - }), - ], - }); - - this.pipeline.addStage({ - stageName: 'Deploy', - actions: [ - this.createToolkitStage({ - actionName: 'Network_Prepare', - command: 'deploy', - stage: AcceleratorStage.NETWORK_PREP, - runOrder: 1, - }), - this.createToolkitStage({ - actionName: 'Security', - command: 'deploy', - stage: AcceleratorStage.SECURITY, - runOrder: 1, - }), - this.createToolkitStage({ - actionName: 'Operations', - command: 'deploy', - stage: AcceleratorStage.OPERATIONS, - runOrder: 1, - }), - this.createToolkitStage({ - actionName: 'Network_VPCs', - command: 'deploy', - stage: AcceleratorStage.NETWORK_VPC, - runOrder: 2, - }), - this.createToolkitStage({ - actionName: 'Security_Resources', - command: 'deploy', - stage: AcceleratorStage.SECURITY_RESOURCES, - runOrder: 2, - }), - this.createToolkitStage({ - actionName: 'Identity_Center', - command: 'deploy', - stage: AcceleratorStage.IDENTITY_CENTER, - runOrder: 2, - }), - this.createToolkitStage({ - actionName: 'Network_Associations', - command: 'deploy', - stage: AcceleratorStage.NETWORK_ASSOCIATIONS, - runOrder: 3, - }), - this.createToolkitStage({ - actionName: 'Customizations', - command: 'deploy', - stage: AcceleratorStage.CUSTOMIZATIONS, - runOrder: 4, - }), - this.createToolkitStage({ - actionName: 'Finalize', - command: 'deploy', - stage: AcceleratorStage.FINALIZE, - runOrder: 5, - }), - ], - }); - - // Add ASEA Import Resources - if (enableAseaMigration) { - this.pipeline.addStage({ - stageName: 'PostImportAseaResources', - actions: [ - this.createToolkitStage({ - actionName: 'Post_Import_Asea_Resources', - command: `deploy`, - stage: AcceleratorStage.POST_IMPORT_ASEA_RESOURCES, - }), - ], - }); - } - - // Enable pipeline notification for commercial partition - this.enablePipelineNotification(); - } - - /** - * Add review stage based on parameter - */ - private addReviewStage() { - if (this.props.enableApprovalStage) { - const notificationTopic = new cdk.aws_sns.Topic(this, 'ManualApprovalActionTopic', { - topicName: - (this.props.qualifier ? this.props.qualifier : this.props.prefixes.snsTopicName) + '-pipeline-review-topic', - displayName: - (this.props.qualifier ? this.props.qualifier : this.props.prefixes.snsTopicName) + '-pipeline-review-topic', - masterKey: this.installerKey, - }); - - let notifyEmails: string[] | undefined = undefined; - - if (notificationTopic) { - if (this.props.approvalStageNotifyEmailList) { - notifyEmails = this.props.approvalStageNotifyEmailList.split(','); - } - } - - this.pipeline.addStage({ - stageName: 'Review', - actions: [ - this.createToolkitStage({ actionName: 'Diff', command: 'diff', runOrder: 1 }), - new codepipeline_actions.ManualApprovalAction({ - actionName: 'Approve', - runOrder: 2, - additionalInformation: 'See previous stage (Diff) for changes.', - notificationTopic, - notifyEmails, - }), - ], - }); - } - } - - private createToolkitStage(stageProps: { - actionName: string; - command: string; - stage?: string; - runOrder?: number; - }): codepipeline_actions.CodeBuildAction { - let cdkOptions; - if ( - stageProps.command === AcceleratorToolkitCommand.BOOTSTRAP.toString() || - stageProps.command === AcceleratorToolkitCommand.DIFF.toString() - ) { - cdkOptions = stageProps.command; - } else { - cdkOptions = `${stageProps.command} --stage ${stageProps.stage}`; - } - - const environmentVariables: { - [name: string]: cdk.aws_codebuild.BuildEnvironmentVariable; - } = { - CDK_OPTIONS: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: cdkOptions, - }, - CONFIG_COMMIT_ID: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '#{Config-Vars.CommitId}', - }, - }; - - if (stageProps.stage) { - environmentVariables['ACCELERATOR_STAGE'] = { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: stageProps.stage ?? '', - }; - } - - return new codepipeline_actions.CodeBuildAction({ - actionName: stageProps.actionName, - runOrder: stageProps.runOrder, - project: this.toolkitProject, - input: this.buildOutput, - extraInputs: [this.configRepoArtifact], - role: this.pipelineRole, - environmentVariables, - }); - } - - /** - * Enable pipeline notification for commercial partition and supported regions - */ - private enablePipelineNotification() { - if (this.props.enableSingleAccountMode) { - return; - } - - // We can Enable pipeline notification only for regions with AWS CodeStar being available - if (this.props.awsCodeStarSupportedRegions.includes(cdk.Stack.of(this).region)) { - const codeStarNotificationsRole = new ServiceLinkedRole(this, 'AWSServiceRoleForCodeStarNotifications', { - environmentEncryptionKmsKey: this.installerKey, - cloudWatchLogKmsKey: this.installerKey, - // specifying this as it will be overwritten with global retention in logging stack - cloudWatchLogRetentionInDays: 7, - awsServiceName: 'codestar-notifications.amazonaws.com', - description: 'Allows AWS CodeStar Notifications to access Amazon CloudWatch Events on your behalf', - roleName: 'AWSServiceRoleForCodeStarNotifications', - }); - - this.pipeline.node.addDependency(codeStarNotificationsRole); - - const acceleratorStatusTopic = new cdk.aws_sns.Topic(this, 'AcceleratorStatusTopic', { - topicName: - (this.props.qualifier ? this.props.qualifier : this.props.prefixes.snsTopicName) + '-pipeline-status-topic', - displayName: - (this.props.qualifier ? this.props.qualifier : this.props.prefixes.snsTopicName) + '-pipeline-status-topic', - masterKey: this.installerKey, - }); - - acceleratorStatusTopic.grantPublish(this.pipeline.role); - - this.pipeline.notifyOn('AcceleratorPipelineStatusNotification', acceleratorStatusTopic, { - events: [ - cdk.aws_codepipeline.PipelineNotificationEvents.MANUAL_APPROVAL_FAILED, - cdk.aws_codepipeline.PipelineNotificationEvents.MANUAL_APPROVAL_NEEDED, - cdk.aws_codepipeline.PipelineNotificationEvents.MANUAL_APPROVAL_SUCCEEDED, - cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_CANCELED, - cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_FAILED, - cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_RESUMED, - cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_STARTED, - cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_SUCCEEDED, - cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_SUPERSEDED, - ], - }); - - // Pipeline failure status topic and alarm - const acceleratorFailedStatusTopic = new cdk.aws_sns.Topic(this, 'AcceleratorFailedStatusTopic', { - topicName: - (this.props.qualifier ? this.props.qualifier : this.props.prefixes.snsTopicName) + - '-pipeline-failed-status-topic', - displayName: - (this.props.qualifier ? this.props.qualifier : this.props.prefixes.snsTopicName) + - '-pipeline-failed-status-topic', - masterKey: this.installerKey, - }); - - acceleratorFailedStatusTopic.grantPublish(this.pipeline.role); - - this.pipeline.notifyOn('AcceleratorPipelineFailureNotification', acceleratorFailedStatusTopic, { - events: [cdk.aws_codepipeline.PipelineNotificationEvents.PIPELINE_EXECUTION_FAILED], - }); - - acceleratorFailedStatusTopic - .metricNumberOfMessagesPublished() - .createAlarm(this, 'AcceleratorPipelineFailureAlarm', { - threshold: 1, - evaluationPeriods: 1, - datapointsToAlarm: 1, - treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, - alarmName: this.props.qualifier - ? this.props.qualifier + '-pipeline-failed-alarm' - : `${this.props.prefixes.accelerator}FailedAlarm`, - alarmDescription: 'AWS Accelerator pipeline failure alarm, created by accelerator', - }); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/prerequisites.ts b/source/packages/@aws-accelerator/accelerator/lib/prerequisites.ts deleted file mode 100644 index 4d266fd..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/prerequisites.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import mri from 'mri'; -import * as fs from 'fs'; -import process from 'process'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { evaluateLimits } from '@aws-accelerator/utils/lib/evaluate-limits'; -import { getCurrentAccountId, getGlobalRegion } from '@aws-accelerator/utils/lib/common-functions'; -import { AccountsConfig, GlobalConfig, OrganizationConfig } from '@aws-accelerator/config'; - -const logger = createLogger(['prerequisites']); - -// (async () => { -const usage = `Usage: prerequisites.ts --config-dir CONFIG_DIRECTORY --partition PARTITION [--minimal] [--account ACCOUNT] [--region REGION]`; -/** - * Config directory is required to pull information about homeRegion, accounts and enabled regions. - * Partition is required to get account information. - * - * When minimal flag is used, the command will check for codebuild and lambda limits in management account for homeRegion and globalRegion only. - * - * If minimal flag is not specified, the prerequisites is run for all accounts and all enabled regions. - * To trigger a specific account in developer mode, users can specify an account and region. - * This will make it run only in specified account and region. - * - * Setting ACCELERATOR_SKIP_PREREQUISITES to true will skip running this code completely. - */ - -export function checkPrerequisiteParameters( - account: string | undefined, - region: string | undefined, - minimal: boolean | undefined, - configDirPath: string, - partition: string, -) { - // if minimal, and (account or region) are specified throw error. Installer is standalone - logger.debug(`Account is: ${account}`); - logger.debug(`Region is: ${region}`); - logger.debug(`Installer is: ${minimal}`); - logger.debug(`Config directory is: ${configDirPath}`); - logger.debug(`Partition is: ${partition}`); - - if (minimal) { - logger.debug(`Installer is defined`); - if (account || region) { - logger.error(`When minimal is specified, do not specify account or region`); - throw new Error(usage); - } - // minimal is false - } else { - if ((!account && region) || (account && !region)) { - logger.error(`Both account and region must be specified`); - throw new Error(usage); - } - } - - // check if config exists - logger.debug('Checking config directory'); - if (fs.existsSync(configDirPath)) { - logger.debug(`Config directory ${configDirPath} exists`); - } else { - logger.error(`Invalid --config-dir ${configDirPath}`); - throw new Error(usage); - } - - return true; -} -export async function main( - accountArgs: string | undefined, - regionArgs: string | undefined, - minimalArgs: boolean | undefined, - configDirPathArgs: string, - partitionArgs: string, -) { - checkPrerequisiteParameters(accountArgs, regionArgs, minimalArgs, configDirPathArgs, partitionArgs); - const accountsConfig = AccountsConfig.load(configDirPathArgs); - const orgsEnabled = OrganizationConfig.loadRawOrganizationsConfig(configDirPathArgs).enable; - const enableSingleAccountMode = process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] - ? process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] === 'true' - : false; - await accountsConfig.loadAccountIds(partitionArgs, enableSingleAccountMode, orgsEnabled, accountsConfig); - const globalConfig = GlobalConfig.loadRawGlobalConfig(configDirPathArgs); - const allAccounts = accountsConfig.getAccounts(enableSingleAccountMode); - logger.debug(`All accounts are ${JSON.stringify(allAccounts)}`); - - const enabledRegions = globalConfig.enabledRegions; - logger.debug(`Enabled regions are ${JSON.stringify(enabledRegions)}`); - - const currentAccountId = await getCurrentAccountId(partitionArgs, globalConfig.homeRegion); - logger.debug(`Current account id is ${currentAccountId}`); - - if (minimalArgs) { - // minimal will only check for management account in homeRegion and globalRegion - const homeRegion = globalConfig.homeRegion; - const globalRegion = getGlobalRegion(partitionArgs); - logger.debug(`Checking limits in account ${accountsConfig.getManagementAccountId()} in region ${homeRegion}`); - await evaluateLimits( - homeRegion, - accountsConfig.getManagementAccountId(), - partitionArgs, - globalConfig.managementAccountAccessRole, - currentAccountId, - ); - logger.debug(`Checking limits in account ${accountsConfig.getManagementAccountId()} in region ${globalRegion}`); - await evaluateLimits( - globalRegion, - accountsConfig.getManagementAccountId(), - partitionArgs, - globalConfig.managementAccountAccessRole, - currentAccountId, - ); - } else if (accountArgs && regionArgs) { - // account and region is specified then only check for that account and region - logger.debug(`Checking limits in account ${accountArgs} in region ${regionArgs}`); - await evaluateLimits( - regionArgs, - accountArgs, - partitionArgs, - globalConfig.managementAccountAccessRole, - currentAccountId, - ); - } else { - // check all accounts and all regions - for (const account of allAccounts) { - for (const enabledRegion of enabledRegions) { - const accountId = accountsConfig.getAccountId(account.name); - logger.debug(`Checking limits in account ${accountId} in region ${enabledRegion}`); - await evaluateLimits( - enabledRegion, - accountId, - partitionArgs, - globalConfig.managementAccountAccessRole, - currentAccountId, - ); - } - } - } -} - -(async () => { - const skipPrerequisites = process.env['ACCELERATOR_SKIP_PREREQUISITES'] ?? 'true'; - logger.debug(`ACCELERATOR_SKIP_PREREQUISITES is ${skipPrerequisites}`); - if (skipPrerequisites.toLowerCase().trim() === 'true') { - logger.warn(`Skipping prerequisites since environment variable ACCELERATOR_SKIP_PREREQUISITES was set to true`); - } else { - const args = mri(process.argv.slice(2), { - boolean: ['minimal'], - string: ['config-dir', 'account', 'region', 'partition'], - }); - - const accountArgs = args['account']; - const regionArgs = args['region']; - const minimalArgs = args['minimal']; - const configDirPathArgs = args['config-dir']; - const partitionArgs = args['partition']; - await main(accountArgs, regionArgs, minimalArgs, configDirPathArgs, partitionArgs); - } -})(); diff --git a/source/packages/@aws-accelerator/accelerator/lib/resources/kms-key-resource.ts b/source/packages/@aws-accelerator/accelerator/lib/resources/kms-key-resource.ts deleted file mode 100644 index 3c8a2fe..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/resources/kms-key-resource.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { AcceleratorKeyType, AcceleratorStack, AcceleratorStackProps } from '../stacks/accelerator-stack'; -export class KmsKeyResource { - private stack: AcceleratorStack; - - readonly props: AcceleratorStackProps; - readonly cloudwatchKey: cdk.aws_kms.IKey | undefined; - readonly lambdaKey: cdk.aws_kms.IKey | undefined; - - constructor(stack: AcceleratorStack, props: AcceleratorStackProps) { - this.stack = stack; - this.props = props; - - // - // Get or create cloudwatch key - // - this.cloudwatchKey = this.createOrGetCloudWatchKey(props); - - // - // Get or create lambda key - // - this.lambdaKey = this.createOrGetLambdaKey(props); - } - - /** - * Function to create or get cloudwatch key - * @param props {@link AccountsStackProps} - * @returns cdk.aws_kms.IKey - * - * @remarks - * Use existing management account CloudWatch log key if in the home region otherwise create new kms key. - * CloudWatch key was created in management account region by prepare stack. - */ - private createOrGetCloudWatchKey(props: AcceleratorStackProps): cdk.aws_kms.IKey | undefined { - if (!this.stack.isLambdaCMKEnabled) { - return undefined; - } - if (props.globalConfig.homeRegion == cdk.Stack.of(this.stack).region) { - return this.stack.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - } else { - const key = new cdk.aws_kms.Key(this.stack, 'AcceleratorCloudWatchKey', { - alias: this.stack.acceleratorResourceNames.customerManagedKeys.cloudWatchLog.alias, - description: this.stack.acceleratorResourceNames.customerManagedKeys.cloudWatchLog.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // Allow Cloudwatch logs to use the encryption key - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [ - new cdk.aws_iam.ServicePrincipal( - `logs.${cdk.Stack.of(this.stack).region}.${cdk.Stack.of(this.stack).urlSuffix}`, - ), - ], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this.stack).partition}:logs:${ - cdk.Stack.of(this.stack).region - }:${cdk.Stack.of(this.stack).account}:log-group:*`, - }, - }, - }), - ); - - this.stack.addSsmParameter({ - logicalId: 'AcceleratorCloudWatchKmsArnParameter', - parameterName: this.stack.acceleratorResourceNames.parameters.cloudWatchLogCmkArn, - stringValue: key.keyArn, - }); - - return key; - } - } - - /** - * Function to create or get lambda key - * @param props {@link AccountsStackProps} - * @returns cdk.aws_kms.IKey | undefined - * - * @remarks - * Use existing management account Lambda log key if in the home region otherwise create new kms key. - * Lambda key was created in management account region by prepare stack. - */ - private createOrGetLambdaKey(props: AcceleratorStackProps): cdk.aws_kms.IKey | undefined { - if (!this.stack.isLambdaCMKEnabled) { - return undefined; - } - if (props.globalConfig.homeRegion == cdk.Stack.of(this.stack).region) { - return this.stack.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - } else { - // Create KMS Key for Lambda environment variable encryption - const key = new cdk.aws_kms.Key(this.stack, 'AcceleratorLambdaKey', { - alias: this.stack.acceleratorResourceNames.customerManagedKeys.lambda.alias, - description: this.stack.acceleratorResourceNames.customerManagedKeys.lambda.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - this.stack.addSsmParameter({ - logicalId: 'AcceleratorLambdaKmsArnParameter', - parameterName: this.stack.acceleratorResourceNames.parameters.lambdaCmkArn, - stringValue: key.keyArn, - }); - - return key; - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/resources/scp-resource.ts b/source/packages/@aws-accelerator/accelerator/lib/resources/scp-resource.ts deleted file mode 100644 index 5d2a2fd..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/resources/scp-resource.ts +++ /dev/null @@ -1,400 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { AcceleratorStack, AcceleratorStackProps, NagSuppressionRuleIds } from '../stacks/accelerator-stack'; -import { - EnablePolicyType, - Policy, - PolicyAttachment, - PolicyType, - PolicyTypeEnum, - RevertScpChanges, -} from '@aws-accelerator/constructs'; -import winston from 'winston'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import path from 'path'; -import { ServiceControlPolicyConfig } from '@aws-accelerator/config'; -import { pascalCase } from 'pascal-case'; - -/** - * Scp Item type - */ -export type scpItem = { - /** - * Name of the scp - */ - name: string; - /** - * Scp id - */ - id: string; -}; - -/** - * Scp generated file path type - */ -export type scpGeneratedFilePath = { - /** - * Name of the scp - */ - name: string; - /** - * The relative path to the file containing the policy document in the config repo - */ - path: string; - /** - * The path to the temp file returned by generatePolicyReplacements() - */ - tempPath: string; -}; - -export class ScpResource { - private stack: AcceleratorStack; - protected logger: winston.Logger; - - readonly props: AcceleratorStackProps; - readonly cloudwatchKey: cdk.aws_kms.IKey | undefined; - readonly lambdaKey: cdk.aws_kms.IKey | undefined; - readonly scpGeneratedFilePathList: scpGeneratedFilePath[] = []; - - constructor( - stack: AcceleratorStack, - cloudwatchKey: cdk.aws_kms.IKey | undefined, - lambdaKey: cdk.aws_kms.IKey | undefined, - props: AcceleratorStackProps, - ) { - this.stack = stack; - this.logger = createLogger(['scp-resource']); - this.props = props; - this.cloudwatchKey = cloudwatchKey; - this.lambdaKey = lambdaKey; - - // - // Generate replacements for policy files - // - this.loadPolicyReplacements(props); - } - - /** - * Create and attach SCPs to OU and Accounts. - * @param props {@link AccountsStackProps} - * @returns - */ - public createAndAttachScps(props: AcceleratorStackProps): scpItem[] { - const scpItems: scpItem[] = []; - // SCP is not supported in China Region. - if (props.organizationConfig.enable && props.partition !== 'aws-cn') { - const enablePolicyTypeScp = new EnablePolicyType(this.stack, 'enablePolicyTypeScp', { - policyType: PolicyTypeEnum.SERVICE_CONTROL_POLICY, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - // Deploy SCPs - for (const serviceControlPolicy of props.organizationConfig.serviceControlPolicies) { - this.logger.info(`Adding service control policy (${serviceControlPolicy.name})`); - - const scp = this.createScp(props, serviceControlPolicy); - - scp.node.addDependency(enablePolicyTypeScp); - - // - // Attach scp to organization units - // - this.attachScpToOu( - props, - scp, - serviceControlPolicy.name, - serviceControlPolicy.deploymentTargets.organizationalUnits ?? [], - ); - - // - // Attach scp to accounts - // - this.attachScpToAccounts( - props, - scp, - serviceControlPolicy.name, - serviceControlPolicy.deploymentTargets.accounts ?? [], - ); - - scpItems.push({ name: serviceControlPolicy.name, id: scp.id }); - } - } - - return scpItems; - } - - /** - * Function to create SCP - * @param props {@link AccountsStackProps} - * @param serviceControlPolicy - */ - public createScp(props: AcceleratorStackProps, serviceControlPolicy: ServiceControlPolicyConfig): Policy { - const scp = new Policy(this.stack, serviceControlPolicy.name, { - description: serviceControlPolicy.description, - name: serviceControlPolicy.name, - partition: props.partition, - path: this.scpGeneratedFilePathList.find(policy => policy.name === serviceControlPolicy.name)!.tempPath, - type: PolicyType.SERVICE_CONTROL_POLICY, - strategy: serviceControlPolicy.strategy, - acceleratorPrefix: props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - - if ( - serviceControlPolicy.name == props.organizationConfig.quarantineNewAccounts?.scpPolicyName && - props.partition == 'aws' - ) { - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${scp.name}ScpPolicyId`), - parameterName: this.stack.getSsmPath(SsmResourceType.SCP, [scp.name]), - stringValue: scp.id, - }); - } - - return scp; - } - - /** - * Function to attach scp to Organization units - * @param props {@link AccountsStackProps} - * @param scp - * @param policyName - * @param organizationalUnits - */ - public attachScpToOu( - props: AcceleratorStackProps, - scp: Policy, - policyName: string, - organizationalUnits: string[], - ): void { - for (const organizationalUnit of organizationalUnits) { - this.logger.info( - `Attaching service control policy (${policyName}) to organizational unit (${organizationalUnit})`, - ); - - const ouPolicyAttachment = new PolicyAttachment( - this.stack, - pascalCase(`Attach_${scp.name}_${organizationalUnit}`), - { - policyId: scp.id, - targetId: props.organizationConfig.getOrganizationalUnitId(organizationalUnit), - type: PolicyType.SERVICE_CONTROL_POLICY, - strategy: scp.strategy, - configPolicyNames: this.stack.getScpNamesForTarget(organizationalUnit, 'ou'), - acceleratorPrefix: props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }, - ); - ouPolicyAttachment.node.addDependency(scp); - } - } - - /** - * Function to attach scp to accounts - * @param props {@link AccountsStackProps} - * @param scp - * @param policyName - * @param accounts - */ - public attachScpToAccounts(props: AcceleratorStackProps, scp: Policy, policyName: string, accounts: string[]) { - for (const account of accounts) { - this.logger.info(`Attaching service control policy (${policyName}) to account (${account})`); - - const accountPolicyAttachment = new PolicyAttachment(this.stack, pascalCase(`Attach_${scp.name}_${account}`), { - policyId: scp.id, - targetId: props.accountsConfig.getAccountId(account), - type: PolicyType.SERVICE_CONTROL_POLICY, - strategy: scp.strategy, - configPolicyNames: this.stack.getScpNamesForTarget(account, 'account'), - acceleratorPrefix: props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - accountPolicyAttachment.node.addDependency(scp); - } - } - - /** - * Function to configure EventBridge Rule to revert SCP changes made outside of the solution - * @param props {@link AccountsStackProps} - */ - public configureRevertScpChanges(props: AcceleratorStackProps) { - if (props.securityConfig.centralSecurityServices?.scpRevertChangesConfig?.enable) { - this.logger.info(`Creating resources to revert modifications to scps`); - new RevertScpChanges(this.stack, 'RevertScpChanges', { - acceleratorPrefix: props.prefixes.accelerator, - configDirPath: props.configDirPath, - homeRegion: props.globalConfig.homeRegion, - kmsKeyCloudWatch: this.cloudwatchKey, - kmsKeyLambda: this.lambdaKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorTopicNamePrefix: props.prefixes.snsTopicName, - snsTopicName: props.securityConfig.centralSecurityServices.scpRevertChangesConfig?.snsTopicName, - scpFilePaths: this.scpGeneratedFilePathList, - singleAccountMode: props.enableSingleAccountMode, - organizationEnabled: props.organizationConfig.enable, - }); - } - } - - /** - * Function to configure and attach Quarantine Scp - * @param scpItems {@link scpItem} - * @param props {@link AccountsStackProps} - */ - public configureAndAttachQuarantineScp(scpItems: scpItem[], props: AcceleratorStackProps) { - if (props.organizationConfig.quarantineNewAccounts?.enable === true && props.partition === 'aws') { - const quarantineScpItem = scpItems.find( - item => item.name === props.organizationConfig.quarantineNewAccounts?.scpPolicyName, - ); - let quarantineScpId = ''; - if (quarantineScpItem) { - quarantineScpId = quarantineScpItem.id; - } - - // Create resources to attach quarantine scp to - // new accounts created in organizations - this.logger.info(`Creating resources to quarantine new accounts`); - const orgPolicyRead = new cdk.aws_iam.PolicyStatement({ - sid: 'OrgRead', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['organizations:ListPolicies', 'organizations:DescribeCreateAccountStatus'], - resources: ['*'], - }); - - const orgPolicyWrite = new cdk.aws_iam.PolicyStatement({ - sid: 'OrgWrite', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['organizations:AttachPolicy'], - resources: [ - `arn:${ - this.stack.partition - }:organizations::${props.accountsConfig.getManagementAccountId()}:policy/o-*/service_control_policy/${quarantineScpId}`, - `arn:${this.stack.partition}:organizations::${props.accountsConfig.getManagementAccountId()}:account/o-*/*`, - ], - }); - - this.logger.info(`Creating function to attach quarantine scp to accounts`); - const attachQuarantineFunction = new cdk.aws_lambda.Function(this.stack, 'AttachQuarantineScpFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '../lambdas/attach-quarantine-scp/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to attach quarantine scp to new accounts', - timeout: cdk.Duration.minutes(5), - environment: { - SCP_POLICY_NAME: props.organizationConfig.quarantineNewAccounts?.scpPolicyName ?? '', - }, - environmentEncryption: this.lambdaKey, - initialPolicy: [orgPolicyRead, orgPolicyWrite], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stack.stackName}/AttachQuarantineScpFunction/ServiceRole/Resource`, - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stack.stackName}/AttachQuarantineScpFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Allows only specific policy.', - }, - ], - }); - - const createAccountEventRule = new cdk.aws_events.Rule(this.stack, 'CreateAccountRule', { - eventPattern: { - source: ['aws.organizations'], - detailType: ['AWS API Call via CloudTrail'], - detail: { - eventSource: ['organizations.amazonaws.com'], - eventName: ['CreateAccount'], - }, - }, - description: 'Rule to notify when a new account is created.', - }); - - createAccountEventRule.addTarget( - new cdk.aws_events_targets.LambdaFunction(attachQuarantineFunction, { - maxEventAge: cdk.Duration.hours(4), - retryAttempts: 2, - }), - ); - - //If any GovCloud accounts are configured also - //watch for any GovCloudCreateAccount events - if (props.accountsConfig.anyGovCloudAccounts()) { - this.logger.info(`Creating EventBridge rule to attach quarantine scp to accounts when GovCloud is enabled`); - const createGovCloudAccountEventRule = new cdk.aws_events.Rule(this.stack, 'CreateGovCloudAccountRule', { - eventPattern: { - source: ['aws.organizations'], - detailType: ['AWS API Call via CloudTrail'], - detail: { - eventSource: ['organizations.amazonaws.com'], - eventName: ['CreateGovCloudAccount'], - }, - }, - description: 'Rule to notify when a new account is created using the create govcloud account api.', - }); - - createGovCloudAccountEventRule.addTarget( - new cdk.aws_events_targets.LambdaFunction(attachQuarantineFunction, { - maxEventAge: cdk.Duration.hours(4), - retryAttempts: 2, - }), - ); - } - - new cdk.aws_logs.LogGroup(this.stack, `${attachQuarantineFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${attachQuarantineFunction.functionName}`, - retention: props.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: this.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - } - } - - /** - * Function to load replacements within the provided SCP policy documents - * @param props {@link AccountsStackProps} - * @returns - */ - private loadPolicyReplacements(props: AcceleratorStackProps): void { - for (const serviceControlPolicy of props.organizationConfig.serviceControlPolicies) { - this.logger.info(`Adding service control policy (${serviceControlPolicy.name})`); - - this.scpGeneratedFilePathList.push({ - name: serviceControlPolicy.name, - path: serviceControlPolicy.policy, - tempPath: this.stack.generatePolicyReplacements( - path.join(props.configDirPath, serviceControlPolicy.policy), - true, - this.stack.organizationId, - ), - }); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/accelerator-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/accelerator-stack.ts deleted file mode 100644 index a3a0ae6..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/accelerator-stack.ts +++ /dev/null @@ -1,1912 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; -import * as winston from 'winston'; -import { NagSuppressions } from 'cdk-nag'; - -import { PrincipalOrgIdConditionType } from '@aws-accelerator/utils/lib/common-resources'; - -import { - AccountConfig, - AccountsConfig, - BlockDeviceMappingItem, - CustomizationsConfig, - CloudWatchLogDataProtectionCategories, - DeploymentTargets, - EbsItemConfig, - GlobalConfig, - GovCloudAccountConfig, - IamConfig, - LifeCycleRule, - NetworkConfig, - OrganizationConfig, - Region, - ReplacementsConfig, - S3EncryptionConfig, - SecurityConfig, - ServiceEncryptionConfig, - ShareTargets, - VpcConfig, - VpcTemplatesConfig, - isNetworkType, - SecurityHubConfig, - GuardDutyConfig, -} from '@aws-accelerator/config'; -import { KeyLookup, S3LifeCycleRule, ServiceLinkedRole } from '@aws-accelerator/constructs'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { SsmParameterPath, SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { policyReplacements } from '@aws-accelerator/utils/lib/policy-replacements'; - -import { version } from '../../../../../package.json'; -import { AcceleratorResourceNames } from '../accelerator-resource-names'; -import { AcceleratorResourcePrefixes } from '../../utils/app-utils'; - -/** - * Accelerator Key type enum - */ -export enum AcceleratorKeyType { - /** - * Central Log Bucket key - */ - CENTRAL_LOG_BUCKET = 'central-log-bucket', - /** - * Cloudwatch key - */ - CLOUDWATCH_KEY = 'cloudwatch-key', - /** - * Imported Central Log Bucket key - */ - IMPORTED_CENTRAL_LOG_BUCKET = 'imported-central-log-bucket', - /** - * Lambda key - */ - LAMBDA_KEY = 'lambda-key', - /** - * S3 key - */ - S3_KEY = 's3-key', -} - -/** - * Service Linked Role type enum - */ -export enum ServiceLinkedRoleType { - /** - * Access Analyzer SLR - */ - ACCESS_ANALYZER = 'access-analyzer', - /** - * GUARDDUTY SLR - */ - GUARDDUTY = 'guardduty', - /** - * MACIE SLR - */ - MACIE = 'macie', - /** - * SECURITYHUB SLR - */ - SECURITY_HUB = 'securityhub', - /** - * AUTOSCALING SLR - */ - AUTOSCALING = 'autoscaling', - /** - * AWSCloud9 SLR - */ - AWS_CLOUD9 = 'cloud9', - /** - * AWS Firewall Manager SLR - */ - FMS = 'fms', -} - -/** - * Allowed rule id type for NagSuppression - */ -export enum NagSuppressionRuleIds { - DDB3 = 'DDB3', - EC28 = 'EC28', - EC29 = 'EC29', - IAM4 = 'IAM4', - IAM5 = 'IAM5', - SMG4 = 'SMG4', - VPC3 = 'VPC3', - S1 = 'S1', - KDS3 = 'KDS3', - AS3 = 'AS3', -} - -/** - * NagSuppression Detail Type - */ -export type NagSuppressionDetailType = { - /** - * Suppressions rule id - */ - id: NagSuppressionRuleIds; - /** - * Suppressions details - */ - details: { - /** - * Resource path - */ - path: string; - /** - * Suppressions reason - */ - reason: string; - }[]; -}; - -/** - * List of CloudWatch log data protection identifiers for given categories. - * - * @remarks - * More information can be found [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types.html) - * - * - [Data identifier ARNs for credential data types](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types-credentials.html) - */ -export const CloudWatchDataProtectionIdentifiers: Record = - { - Credentials: ['AwsSecretKey', 'OpenSshPrivateKey', 'PgpPrivateKey', 'PkcsPrivateKey', 'PuttyPrivateKey'], - }; - -export interface AcceleratorStackProps extends cdk.StackProps { - readonly configDirPath: string; - readonly accountsConfig: AccountsConfig; - readonly globalConfig: GlobalConfig; - readonly iamConfig: IamConfig; - readonly networkConfig: NetworkConfig; - readonly organizationConfig: OrganizationConfig; - readonly securityConfig: SecurityConfig; - readonly customizationsConfig: CustomizationsConfig; - readonly replacementsConfig: ReplacementsConfig; - readonly partition: string; - readonly configRepositoryName: string; - readonly qualifier?: string; - readonly configCommitId?: string; - readonly globalRegion: string; - readonly centralizedLoggingRegion: string; - /** - * Accelerator resource name prefixes - */ - readonly prefixes: AcceleratorResourcePrefixes; - readonly enableSingleAccountMode: boolean; - /** - * Use existing roles for deployment - */ - readonly useExistingRoles: boolean; - /** - * Central logs kms key arn - * @remarks - * this is only possible after logging stack is run in centralizedLoggingRegion - * It will be used in - * - logging stack for replication to s3 bucket - * - organizations stack for org trail - * - security-audit stack for AWS config service, SSM session manager, account trail - * - security stack for macie and guard duty - */ - centralLogsBucketKmsKeyArn?: string; - /** - * Flag indicating diagnostic pack enabled - */ - isDiagnosticsPackEnabled: string; - /** - * Accelerator pipeline account id, for external deployment it will be pipeline account otherwise management account - */ - pipelineAccountId: string; -} - -process.on('uncaughtException', err => { - const logger = createLogger(['accelerator']); - logger.error(err); - throw new Error('Synthesis failed'); -}); - -export abstract class AcceleratorStack extends cdk.Stack { - protected logger: winston.Logger; - protected props: AcceleratorStackProps; - - /** - * Nag suppression input list - */ - protected nagSuppressionInputs: NagSuppressionDetailType[] = []; - - /** - * Accelerator SSM parameters - * This array is used to store SSM parameters that are created per-stack. - */ - protected ssmParameters: { logicalId: string; parameterName: string; stringValue: string; scope?: string }[]; - - protected centralLogsBucketName: string; - - public readonly organizationId: string | undefined; - - /** - * Flag indicating external deployment - */ - public readonly isExternalDeployment: boolean; - - public acceleratorResourceNames: AcceleratorResourceNames; - - public stackParameters: Map; - - /** - * Flag indicating if AWS KMS CMK is enabled for AWS Lambda environment encryption - */ - public readonly isLambdaCMKEnabled: boolean; - - /** - * Flag indicating if AWS KMS CMK is enabled for AWS CloudWatch log group data encryption - */ - public readonly isCloudWatchLogsGroupCMKEnabled: boolean; - - /** - * Flag indicating if AWS KMS CMK is enabled for AWS S3 bucket encryption - */ - public readonly isS3CMKEnabled: boolean; - - /** - * Flag indicating if S3 access logs bucket is enabled - */ - public readonly isAccessLogsBucketEnabled: boolean; - - /** - * External resource SSM parameters - * These parameters are loaded along with externalResourceMapping from SSM - */ - private externalResourceParameters: { [key: string]: string } | undefined; - - protected constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - this.logger = createLogger([cdk.Stack.of(this).stackName]); - this.props = props; - this.ssmParameters = []; - this.organizationId = props.organizationConfig.getOrganizationId(); - this.isExternalDeployment = - props.pipelineAccountId !== props.accountsConfig.getManagementAccountId() ? true : false; - // - // Initialize resource names - this.acceleratorResourceNames = new AcceleratorResourceNames({ - prefixes: props.prefixes, - centralizedLoggingRegion: props.centralizedLoggingRegion, - }); - - // - // Get CentralLogBucket name - this.centralLogsBucketName = this.getCentralLogBucketName(); - - // - // Get external resource ssm parameters from pre loaded globalConfig - this.externalResourceParameters = - props.globalConfig.externalLandingZoneResources?.resourceParameters?.[`${this.account}-${this.region}`]; - - this.stackParameters = new Map(); - if (!this.stackName.includes('Phase')) { - this.stackParameters.set( - 'StackId', - new cdk.aws_ssm.StringParameter(this, 'SsmParamStackId', { - parameterName: this.getSsmPath(SsmResourceType.STACK_ID, [cdk.Stack.of(this).stackName]), - stringValue: cdk.Stack.of(this).stackId, - }), - ); - - this.stackParameters.set( - 'StackVersion', - new cdk.aws_ssm.StringParameter(this, 'SsmParamAcceleratorVersion', { - parameterName: this.getSsmPath(SsmResourceType.VERSION, [cdk.Stack.of(this).stackName]), - stringValue: version, - }), - ); - } - - // - // Set if AWS KMS CMK is enabled for Lambda environment encryption - // - this.isLambdaCMKEnabled = this.isCmkEnabledServiceEncryption(this.props.globalConfig.lambda?.encryption); - - // - // Set if AWS KMS CMK is enabled for AWS CloudWatch log group data encryption - // - this.isCloudWatchLogsGroupCMKEnabled = this.isCmkEnabledServiceEncryption( - this.props.globalConfig.logging.cloudwatchLogs?.encryption, - ); - - // - // Set if AWS KMS CMK is enabled for AWS S3 bucket encryption - // - this.isS3CMKEnabled = this.isCmkEnabledS3Encryption(this.props.globalConfig.s3?.encryption); - - // - // Set if S3 access log bucket is enabled - // - this.isAccessLogsBucketEnabled = this.accessLogsBucketEnabled(); - } - - /** - * Evaluates if inputConfig is enabled and either excludeRegions or deploymentTargets is defined. Returns false if region is excluded - * @param inputConfig {@link SecurityHubConfig} | {@link GuardDutyConfig} - * @returns boolean - */ - protected validateExcludeRegionsAndDeploymentTargets(inputConfig: SecurityHubConfig | GuardDutyConfig): boolean { - return ( - inputConfig.enable && - (inputConfig.excludeRegions - ? inputConfig.excludeRegions.indexOf(this.region as Region) === -1 - : inputConfig.deploymentTargets?.excludedRegions - ? inputConfig.deploymentTargets?.excludedRegions?.indexOf(this.region as Region) === -1 - : true) - ); - } - - /** - * Function to get server access logs bucket name - * @returns - * - * @remarks - * If importedBucket used returns imported server access logs bucket name else return solution defined bucket name - */ - protected getServerAccessLogsBucketName(): string | undefined { - if (this.props.globalConfig.logging.accessLogBucket?.importedBucket?.name) { - return this.getBucketNameReplacement(this.props.globalConfig.logging.accessLogBucket.importedBucket.name); - } - if (!this.isAccessLogsBucketEnabled) { - return undefined; - } - return `${this.acceleratorResourceNames.bucketPrefixes.s3AccessLogs}-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`; - } - - /** - * Function to get ELB logs bucket name - * @returns - * - * @remarks - * If importedBucket used returns imported ELB logs bucket name else solution defined bucket name - */ - protected getElbLogsBucketName(): string { - if (this.props.globalConfig.logging.elbLogBucket?.importedBucket?.name) { - return this.getBucketNameReplacement(this.props.globalConfig.logging.elbLogBucket.importedBucket.name); - } else { - return `${ - this.acceleratorResourceNames.bucketPrefixes.elbLogs - }-${this.props.accountsConfig.getLogArchiveAccountId()}-${cdk.Stack.of(this).region}`; - } - } - - /** - * Function to get Central Log bucket name - * @returns - */ - private getCentralLogBucketName(): string { - if (this.props.globalConfig.logging.centralLogBucket?.importedBucket) { - return this.getBucketNameReplacement(this.props.globalConfig.logging.centralLogBucket.importedBucket.name); - } - return `${ - this.acceleratorResourceNames.bucketPrefixes.centralLogs - }-${this.props.accountsConfig.getLogArchiveAccountId()}-${this.props.centralizedLoggingRegion}`; - } - - /** - * Function to get CentralLogs bucket key - * @param customResourceLambdaCloudWatchLogKmsKey {@link cdk.aws_kms.IKey} - * - * @returns key {@link cdk.aws_kms.IKey} - * - * @remarks - * If importedBucket used returns imported CentralLogs bucket cmk arn else return solution defined CentralLogs bucket cmk arn - */ - protected getCentralLogsBucketKey(customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey): cdk.aws_kms.IKey { - if (this.props.globalConfig.logging.centralLogBucket?.importedBucket?.name) { - return this.getAcceleratorKey( - AcceleratorKeyType.IMPORTED_CENTRAL_LOG_BUCKET, - customResourceLambdaCloudWatchLogKmsKey, - )!; - } else { - return this.getAcceleratorKey(AcceleratorKeyType.CENTRAL_LOG_BUCKET, customResourceLambdaCloudWatchLogKmsKey)!; - } - } - - /** - * List of supported partitions for Service Linked Role creation - */ - protected serviceLinkedRoleSupportedPartitionList: string[] = ['aws', 'aws-cn', 'aws-us-gov', 'aws-iso', 'aws-iso-b']; - - /** - * Create Access Analyzer Service Linked role - * - * @remarks - * Access Analyzer Service linked role is created when organization is enabled and accessAnalyzer flag is ON. - */ - protected createAccessAnalyzerServiceLinkedRole(key: { cloudwatch?: cdk.aws_kms.IKey; lambda?: cdk.aws_kms.IKey }) { - if ( - this.props.organizationConfig.enable && - this.props.securityConfig.accessAnalyzer.enable && - this.serviceLinkedRoleSupportedPartitionList.includes(this.props.partition) - ) { - this.createServiceLinkedRole(ServiceLinkedRoleType.ACCESS_ANALYZER, { - cloudwatch: key.cloudwatch, - lambda: key.lambda, - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AccessAnalyzerServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AccessAnalyzerServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AccessAnalyzerServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AccessAnalyzerServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - } - } - - /** - * Create GuardDuty Service Linked role - * - * @remarks - * GuardDuty Service linked role is created when organization is enabled and guardduty flag is ON. - */ - protected createGuardDutyServiceLinkedRole(key: { cloudwatch?: cdk.aws_kms.IKey; lambda?: cdk.aws_kms.IKey }) { - if ( - this.props.organizationConfig.enable && - this.props.securityConfig.centralSecurityServices.guardduty.enable && - this.serviceLinkedRoleSupportedPartitionList.includes(this.props.partition) - ) { - this.createServiceLinkedRole(ServiceLinkedRoleType.GUARDDUTY, { cloudwatch: key.cloudwatch, lambda: key.lambda }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/GuardDutyServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/GuardDutyServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/GuardDutyServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/GuardDutyServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - } - } - - /** - * Create SecurityHub Service Linked role - * - * @remarks - * SecurityHub Service linked role is created when organization is enabled and securityHub flag is ON. - */ - protected createSecurityHubServiceLinkedRole(key: { cloudwatch?: cdk.aws_kms.IKey; lambda?: cdk.aws_kms.IKey }) { - if ( - this.props.organizationConfig.enable && - this.props.securityConfig.centralSecurityServices.securityHub.enable && - this.serviceLinkedRoleSupportedPartitionList.includes(this.props.partition) - ) { - this.createServiceLinkedRole(ServiceLinkedRoleType.SECURITY_HUB, { - cloudwatch: key.cloudwatch, - lambda: key.lambda, - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/SecurityHubServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/SecurityHubServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/SecurityHubServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/SecurityHubServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - } - } - - /** - * Create Macie Service Linked role - * - * @remarks - * Macie Service linked role is created when organization is enabled and macie flag is ON. - */ - protected createMacieServiceLinkedRole(key: { cloudwatch?: cdk.aws_kms.IKey; lambda?: cdk.aws_kms.IKey }) { - if ( - this.props.organizationConfig.enable && - this.props.securityConfig.centralSecurityServices.macie.enable && - this.serviceLinkedRoleSupportedPartitionList.includes(this.props.partition) - ) { - this.createServiceLinkedRole(ServiceLinkedRoleType.MACIE, { cloudwatch: key.cloudwatch, lambda: key.lambda }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/MacieServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/MacieServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/MacieServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/MacieServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - } - } - - /** - * Create AutoScaling Service Linked role - * - * @remarks - * AutoScaling when ebsDefaultVolumeEncryption flag is ON. Or when firewall is used. - */ - protected createAutoScalingServiceLinkedRole(key: { - cloudwatch?: cdk.aws_kms.IKey; - lambda?: cdk.aws_kms.IKey; - }): ServiceLinkedRole | undefined { - if ( - this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.enable && - this.serviceLinkedRoleSupportedPartitionList.includes(this.props.partition) - ) { - const serviceLinkedRole = this.createServiceLinkedRole(ServiceLinkedRoleType.AUTOSCALING, { - cloudwatch: key.cloudwatch, - lambda: key.lambda, - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - return serviceLinkedRole; - } - return; - } - - /** - * Function to get active account ids - * @returns accountIds string - * - * @remarks - * Get only non suspended OUs account ids - */ - protected getActiveAccountIds() { - const accountNames: string[] = []; - const accountIds: string[] = []; - const suspendedOuItems = this.props.organizationConfig.organizationalUnits.filter(item => item.ignore); - const suspendedOuNames = suspendedOuItems.flatMap(item => item.name); - - for (const accountItem of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - if (!suspendedOuNames.includes(accountItem.organizationalUnit)) { - accountNames.push(accountItem.name); - } - } - - accountNames.forEach(item => accountIds.push(this.props.accountsConfig.getAccountId(item))); - return accountIds; - } - - /** - * Create AWS CLOUD9 Service Linked role - * - * @remarks - * AWS CLOUD9 when ebsDefaultVolumeEncryption flag is ON and partition is 'aws' - */ - protected createAwsCloud9ServiceLinkedRole(key: { - cloudwatch?: cdk.aws_kms.IKey; - lambda?: cdk.aws_kms.IKey; - }): ServiceLinkedRole | undefined { - if ( - this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.enable && - this.props.partition === 'aws' - ) { - const serviceLinkedRole = this.createServiceLinkedRole(ServiceLinkedRoleType.AWS_CLOUD9, { - cloudwatch: key.cloudwatch, - lambda: key.lambda, - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - return serviceLinkedRole; - } - return; - } - - /** - * Create AWS Firewall Manager Service Linked role - * - * @remarks - * Service linked role is created in the partitions that allow it. - * Since it is used for delegated admin organizations need to be enabled - */ - protected createAwsFirewallManagerServiceLinkedRole(key: { - cloudwatch?: cdk.aws_kms.IKey; - lambda?: cdk.aws_kms.IKey; - }): ServiceLinkedRole { - // create service linked roles only in the partitions that allow it - const serviceLinkedRole = this.createServiceLinkedRole(ServiceLinkedRoleType.FMS, { - cloudwatch: key.cloudwatch, - lambda: key.lambda, - }); - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - return serviceLinkedRole; - } - /** - * Function to create Service Linked Role for given type - * @param roleType {@link ServiceLinkedRoleType} - * @returns ServiceLinkedRole - * - * @remarks - * Service Linked Role creation is depended on the service configuration. - */ - private createServiceLinkedRole( - roleType: string, - key: { cloudwatch?: cdk.aws_kms.IKey; lambda?: cdk.aws_kms.IKey }, - ): ServiceLinkedRole { - let serviceLinkedRole: ServiceLinkedRole | undefined; - - switch (roleType) { - case ServiceLinkedRoleType.ACCESS_ANALYZER: - this.logger.debug('Create AccessAnalyzerServiceLinkedRole'); - serviceLinkedRole = new ServiceLinkedRole(this, 'AccessAnalyzerServiceLinkedRole', { - awsServiceName: 'access-analyzer.amazonaws.com', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForAccessAnalyzer', - }); - - break; - case ServiceLinkedRoleType.GUARDDUTY: - this.logger.debug('Create GuardDutyServiceLinkedRole'); - serviceLinkedRole = new ServiceLinkedRole(this, 'GuardDutyServiceLinkedRole', { - awsServiceName: 'guardduty.amazonaws.com', - description: 'A service-linked role required for Amazon GuardDuty to access your resources. ', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForAmazonGuardDuty', - }); - - break; - case ServiceLinkedRoleType.SECURITY_HUB: - if ( - this.props.organizationConfig.enable && - this.props.securityConfig.centralSecurityServices.securityHub.enable - ) { - this.logger.debug('Create SecurityHubServiceLinkedRole'); - serviceLinkedRole = new ServiceLinkedRole(this, 'SecurityHubServiceLinkedRole', { - awsServiceName: 'securityhub.amazonaws.com', - description: 'A service-linked role required for AWS Security Hub to access your resources.', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForSecurityHub', - }); - } - break; - case ServiceLinkedRoleType.MACIE: - if (this.props.organizationConfig.enable && this.props.securityConfig.centralSecurityServices.macie.enable) { - this.logger.debug('Create MacieServiceLinkedRole'); - serviceLinkedRole = new ServiceLinkedRole(this, 'MacieServiceLinkedRole', { - awsServiceName: 'macie.amazonaws.com', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForAmazonMacie', - }); - } - break; - case ServiceLinkedRoleType.AUTOSCALING: - this.logger.debug('Create AutoScalingServiceLinkedRole'); - serviceLinkedRole = new ServiceLinkedRole(this, 'AutoScalingServiceLinkedRole', { - awsServiceName: 'autoscaling.amazonaws.com', - description: - 'Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForAutoScaling', - }); - break; - case ServiceLinkedRoleType.AWS_CLOUD9: - if ( - this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.enable && - this.props.partition === 'aws' - ) { - this.logger.debug('Create Aws Cloud9 Service Linked Role'); - serviceLinkedRole = new ServiceLinkedRole(this, 'AWSServiceRoleForAWSCloud9', { - awsServiceName: 'cloud9.amazonaws.com', - description: 'Service linked role for AWS Cloud9', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForAWSCloud9', - }); - } - break; - case ServiceLinkedRoleType.FMS: - this.logger.debug('Create FirewallManagerServiceLinkedRole'); - serviceLinkedRole = new ServiceLinkedRole(this, 'FirewallManagerServiceLinkedRole', { - awsServiceName: 'fms.amazonaws.com', - environmentEncryptionKmsKey: key.lambda, - cloudWatchLogKmsKey: key.cloudwatch, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - roleName: 'AWSServiceRoleForFMS', - }); - break; - default: - throw new Error(`Invalid service linked role type ${roleType}`); - } - return serviceLinkedRole!; - } - - /** - * Function to get Accelerator key for given key type - * @param keyType {@type AcceleratorKeyType} - * @param customResourceLambdaCloudWatchLogKmsKey {@link cdk.aws_kms.IKey} - * @returns cdk.aws_kms.IKey - */ - public getAcceleratorKey( - keyType: AcceleratorKeyType, - customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey, - ): cdk.aws_kms.IKey | undefined { - let key: cdk.aws_kms.IKey | undefined; - switch (keyType) { - case AcceleratorKeyType.S3_KEY: - key = this.isS3CMKEnabled - ? cdk.aws_kms.Key.fromKeyArn( - this, - 'AcceleratorS3KeyLookup', - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.acceleratorResourceNames.parameters.s3CmkArn, - ), - ) - : undefined; - break; - case AcceleratorKeyType.CLOUDWATCH_KEY: - if (!this.stackName.includes('Phase')) { - key = this.isCloudWatchLogsGroupCMKEnabled - ? cdk.aws_kms.Key.fromKeyArn( - this, - 'AcceleratorGetCloudWatchKey', - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.acceleratorResourceNames.parameters.cloudWatchLogCmkArn, - ), - ) - : undefined; - } - break; - case AcceleratorKeyType.LAMBDA_KEY: - key = this.isLambdaCMKEnabled - ? cdk.aws_kms.Key.fromKeyArn( - this, - 'AcceleratorGetLambdaKey', - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.acceleratorResourceNames.parameters.lambdaCmkArn, - ), - ) - : undefined; - break; - case AcceleratorKeyType.CENTRAL_LOG_BUCKET: - key = new KeyLookup(this, 'AcceleratorCentralLogBucketKeyLookup', { - accountId: this.props.accountsConfig.getLogArchiveAccountId(), - keyRegion: this.props.centralizedLoggingRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountCentralLogBucketCmkArnSsmParameterAccess, - keyArnParameterName: this.acceleratorResourceNames.parameters.centralLogBucketCmkArn, - kmsKey: customResourceLambdaCloudWatchLogKmsKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorPrefix: this.props.prefixes.accelerator, - kmsKeyArn: this.props.centralLogsBucketKmsKeyArn, - }).getKey(); - - break; - case AcceleratorKeyType.IMPORTED_CENTRAL_LOG_BUCKET: - key = new KeyLookup(this, 'AcceleratorImportedCentralLogBucketKeyLookup', { - accountId: this.props.accountsConfig.getLogArchiveAccountId(), - keyRegion: this.props.centralizedLoggingRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - keyArnParameterName: this.acceleratorResourceNames.parameters.importedCentralLogBucketCmkArn, - kmsKey: customResourceLambdaCloudWatchLogKmsKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorPrefix: this.props.prefixes.accelerator, - kmsKeyArn: this.props.centralLogsBucketKmsKeyArn, - }).getKey(); - break; - default: - throw new Error(`Invalid key type ${keyType}`); - } - - return key!; - } - - /** - * Function to get replacement bucket name - * @param name - * @returns - */ - protected getBucketNameReplacement(name: string): string { - return name.replace('${REGION}', cdk.Stack.of(this).region).replace('${ACCOUNT_ID}', cdk.Stack.of(this).account); - } - - /** - * This method creates SSM parameters stored in the `AcceleratorStack.ssmParameters` array. - * If more than five parameters are defined, the method adds a `dependsOn` statement - * to remaining parameters in order to avoid API throttling issues. - */ - protected createSsmParameters(): void { - let index = 1; - const parameterMap = new Map(); - - for (const parameterItem of this.ssmParameters) { - // Create parameter - const parameter = new cdk.aws_ssm.StringParameter(this, parameterItem.logicalId, { - parameterName: parameterItem.parameterName, - stringValue: parameterItem.stringValue, - }); - parameterMap.set(index, parameter); - - // Add a dependency for every 5 parameters - if (index > 5) { - const dependsOnParam = parameterMap.get(index - (index % 5)); - if (!dependsOnParam) { - this.logger.error( - `Error creating SSM parameter ${parameterItem.parameterName}: previous SSM parameter undefined`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - parameter.node.addDependency(dependsOnParam); - } - // Increment index - index += 1; - } - } - - /** - * Function to add resource suppressions by path - */ - protected addResourceSuppressionsByPath(): void { - for (const nagSuppressionInput of this.nagSuppressionInputs) { - for (const detail of nagSuppressionInput.details) { - NagSuppressions.addResourceSuppressionsByPath(this, detail.path, [ - { id: `AwsSolutions-${nagSuppressionInput.id}`, reason: detail.reason }, - ]); - } - } - } - - /** - * Function to check if LZA deployed CMK is enabled for a generic Service Encryption Config - * @param encryptionConfig {@link ServiceEncryptionConfig} - * @returns boolean - */ - protected isCmkEnabledServiceEncryption(encryptionConfig?: ServiceEncryptionConfig): boolean { - let isCmkEnable = true; - if (!encryptionConfig) { - return isCmkEnable; - } - - isCmkEnable = encryptionConfig.useCMK; - const deploymentTargets = encryptionConfig.deploymentTargets; - - if (!deploymentTargets) { - return isCmkEnable; - } - - return this.isIncluded(deploymentTargets) ? isCmkEnable : !isCmkEnable; - } - - /** - * Function to check if LZA deployed CMK is enabled for an S3 Encryption Config - * @param encryptionConfig {@link S3EncryptionConfig} - * @returns boolean - */ - protected isCmkEnabledS3Encryption(encryptionConfig?: S3EncryptionConfig): boolean { - let isCmkEnable = true; - if (!encryptionConfig) { - return isCmkEnable; - } - - isCmkEnable = encryptionConfig.createCMK; - const deploymentTargets = encryptionConfig.deploymentTargets; - - if (!deploymentTargets) { - return isCmkEnable; - } - - return this.isIncluded(deploymentTargets) ? isCmkEnable : !isCmkEnable; - } - - /** - * Function to check if LZA deployed S3 access logs bucket is enabled - * - * @remarks - * LogArchive account centralized logging region server access log bucket is always enabled since the solution deployed CentralLogs bucket requires access to the log bucket. - * - * @returns boolean - */ - protected accessLogsBucketEnabled(): boolean { - if ( - cdk.Stack.of(this).account === this.props.accountsConfig.getLogArchiveAccountId() && - cdk.Stack.of(this).region == this.props.centralizedLoggingRegion - ) { - return true; - } - - const isEnable = this.props.globalConfig.logging.accessLogBucket?.enable ?? true; - const deploymentTargets = this.props.globalConfig.logging.accessLogBucket?.deploymentTargets ?? undefined; - - if (!deploymentTargets) { - return isEnable; - } - - return this.isIncluded(deploymentTargets) ? isEnable : !isEnable; - } - - public isIncluded(deploymentTargets: DeploymentTargets): boolean { - // Explicit Denies - if ( - this.isRegionExcluded(deploymentTargets.excludedRegions) || - this.isAccountExcluded(deploymentTargets.excludedAccounts) - ) { - return false; - } - - // Explicit Allows - if ( - this.isAccountIncluded(deploymentTargets.accounts) || - this.isOrganizationalUnitIncluded(deploymentTargets.organizationalUnits) - ) { - return true; - } - - // Implicit Deny - return false; - } - - /** - * Private helper function to get account names from Accounts array of DeploymentTarget - * @param accounts - * @returns Array of account names - * - * @remarks Used only in getAccountNamesFromDeploymentTarget function. - */ - private getAccountNamesFromDeploymentTargetAccountNames(accounts: string[]): string[] { - const accountNames: string[] = []; - for (const account of accounts ?? []) { - accountNames.push(account); - } - return accountNames; - } - - /** - * Private helper function to get account names from given list of account configs - * @param ouName - * @param accountConfigs - * @returns Array of account names - * - * @remarks Used only in getAccountNamesFromDeploymentTarget function. - */ - private getAccountNamesFromAccountConfigs( - ouName: string, - accountConfigs: (AccountConfig | GovCloudAccountConfig)[], - ): string[] { - const accountNames: string[] = []; - if (ouName === 'Root') { - for (const account of accountConfigs) { - accountNames.push(account.name); - } - } else { - for (const account of accountConfigs) { - if (ouName === account.organizationalUnit) { - accountNames.push(account.name); - } - } - } - - return accountNames; - } - - /** - * Function to get list of account names from given DeploymentTargets. - * @param deploymentTargets - * @returns Array of account names - */ - protected getAccountNamesFromDeploymentTarget(deploymentTargets: DeploymentTargets): string[] { - const accountNames: string[] = []; - - for (const ou of deploymentTargets.organizationalUnits ?? []) { - accountNames.push( - ...this.getAccountNamesFromAccountConfigs(ou, [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]), - ); - } - - accountNames.push(...this.getAccountNamesFromDeploymentTargetAccountNames(deploymentTargets.accounts)); - - return [...new Set(accountNames)]; - } - - // Helper function to add an account id to the list - private _addAccountId(ids: string[], accountId: string) { - if (!ids.includes(accountId)) { - ids.push(accountId); - } - } - - /** - * Private helper function to append account ids from Accounts array of DeploymentTarget or ShareTargets - * @param accounts - * @param accountIds - List where processed account ids from Accounts array of DeploymentTarget or ShareTargets to be appended to. - * @returns Array of Account Ids - * - * @remarks Used only in getAccountIdsFromDeploymentTargets function. - */ - private appendAccountIdsFromDeploymentTargetAccounts( - deploymentTargets: DeploymentTargets | ShareTargets, - accountIds: string[], - ): void { - for (const accountName of deploymentTargets.accounts ?? []) { - const accountId = this.props.accountsConfig.getAccountId(accountName); - this._addAccountId(accountIds, accountId); - } - } - - /** - * Private helper function to append account ids from given list of account configs - * @param ouName - * @param accountConfigs - * @param accountIds - List where processed account ids from accountConfigs to be appended to. - * @returns Array of Account Ids - * - * @remarks Used only in getAccountIdsFromDeploymentTargets function. - */ - private appendAccountIdsFromAccountConfigs( - ouName: string, - accountConfigs: (AccountConfig | GovCloudAccountConfig)[], - accountIds: string[], - ): void { - if (ouName === 'Root') { - for (const accountConfig of accountConfigs) { - const accountId = this.props.accountsConfig.getAccountId(accountConfig.name); - this._addAccountId(accountIds, accountId); - } - } else { - for (const accountConfig of accountConfigs) { - if (ouName === accountConfig.organizationalUnit) { - const accountId = this.props.accountsConfig.getAccountId(accountConfig.name); - this._addAccountId(accountIds, accountId); - } - } - } - } - - /** - * Function to get account ids from given DeploymentTarget - * @param deploymentTargets - * @returns string[] - */ - public getAccountIdsFromDeploymentTargets(deploymentTargets: DeploymentTargets): string[] { - const accountIds: string[] = []; - - for (const ou of deploymentTargets.organizationalUnits ?? []) { - this.appendAccountIdsFromAccountConfigs( - ou, - [...this.props.accountsConfig.mandatoryAccounts, ...this.props.accountsConfig.workloadAccounts], - accountIds, - ); - } - - this.appendAccountIdsFromDeploymentTargetAccounts(deploymentTargets, accountIds); - - const excludedAccountIds = this.getExcludedAccountIds(deploymentTargets); - const filteredAccountIds = accountIds.filter(item => !excludedAccountIds.includes(item)); - - return filteredAccountIds; - } - - protected getExcludedAccountIds(deploymentTargets: DeploymentTargets): string[] { - const accountIds: string[] = []; - - if (deploymentTargets.excludedAccounts) { - deploymentTargets.excludedAccounts.forEach(account => - this._addAccountId(accountIds, this.props.accountsConfig.getAccountId(account)), - ); - } - - return accountIds; - } - - public getRegionsFromDeploymentTarget(deploymentTargets: DeploymentTargets): Region[] { - const regions: Region[] = []; - const enabledRegions = this.props.globalConfig.enabledRegions; - regions.push( - ...enabledRegions.filter(region => { - return !deploymentTargets?.excludedRegions?.includes(region); - }), - ); - return regions; - } - - public getVpcAccountIds(vpcItem: VpcConfig | VpcTemplatesConfig): string[] { - let vpcAccountIds: string[]; - - if (isNetworkType('IVpcConfig', vpcItem)) { - vpcAccountIds = [this.props.accountsConfig.getAccountId(vpcItem.account)]; - } else { - const excludedAccountIds = this.getExcludedAccountIds(vpcItem.deploymentTargets); - vpcAccountIds = this.getAccountIdsFromDeploymentTargets(vpcItem.deploymentTargets).filter( - item => !excludedAccountIds.includes(item), - ); - } - - return vpcAccountIds; - } - - /** - * Function to get central endpoint vpc - * @returns VpcConfig {@link VpcConfig} - */ - protected getCentralEndpointVpc(): VpcConfig { - let centralEndpointVpc = undefined; - const centralEndpointVpcs = this.props.networkConfig.vpcs.filter( - item => - item.interfaceEndpoints?.central && - this.props.accountsConfig.getAccountId(item.account) === cdk.Stack.of(this).account && - item.region === cdk.Stack.of(this).region, - ); - - if (this.props.partition !== 'aws' && this.props.partition !== 'aws-cn' && centralEndpointVpcs.length > 0) { - this.logger.error('Central Endpoint VPC is only possible in commercial regions'); - throw new Error(`Configuration validation failed at runtime.`); - } - - if (centralEndpointVpcs.length > 1) { - this.logger.error(`multiple (${centralEndpointVpcs.length}) central endpoint vpcs detected, should only be one`); - throw new Error(`Configuration validation failed at runtime.`); - } - centralEndpointVpc = centralEndpointVpcs[0]; - - return centralEndpointVpc; - } - - /** - * Function to get account ids from ShareTarget - * @param shareTargets - * @returns - */ - public getAccountIdsFromShareTarget(shareTargets: ShareTargets): string[] { - const accountIds: string[] = []; - - for (const ou of shareTargets.organizationalUnits ?? []) { - this.appendAccountIdsFromAccountConfigs( - ou, - [...this.props.accountsConfig.mandatoryAccounts, ...this.props.accountsConfig.workloadAccounts], - accountIds, - ); - } - - this.appendAccountIdsFromDeploymentTargetAccounts(shareTargets, accountIds); - - return accountIds; - } - - public isRegionExcluded(regions: string[]): boolean { - if (regions?.includes(cdk.Stack.of(this).region)) { - this.logger.info(`${cdk.Stack.of(this).region} region explicitly excluded`); - return true; - } - return false; - } - - public isAccountExcluded(accounts: string[]): boolean { - for (const account of accounts ?? []) { - if (cdk.Stack.of(this).account === this.props.accountsConfig.getAccountId(account)) { - this.logger.info(`${account} account explicitly excluded`); - return true; - } - } - return false; - } - - protected isAccountIncluded(accounts: string[]): boolean { - for (const account of accounts ?? []) { - if (cdk.Stack.of(this).account === this.props.accountsConfig.getAccountId(account)) { - const accountConfig = this.props.accountsConfig.getAccount(account); - if (this.props.organizationConfig.isIgnored(accountConfig.organizationalUnit)) { - this.logger.info(`Account ${account} was not included as it is a member of an ignored organizational unit.`); - return false; - } - this.logger.info(`${account} account explicitly included`); - return true; - } - } - return false; - } - - protected isOrganizationalUnitIncluded(organizationalUnits: string[]): boolean { - if (organizationalUnits) { - // Full list of all accounts - const accounts = [...this.props.accountsConfig.mandatoryAccounts, ...this.props.accountsConfig.workloadAccounts]; - - // Find the account with the matching ID - const account = accounts.find( - item => this.props.accountsConfig.getAccountId(item.name) === cdk.Stack.of(this).account, - ); - - if (account) { - if (organizationalUnits.indexOf(account.organizationalUnit) != -1 || organizationalUnits.includes('Root')) { - const ignored = this.props.organizationConfig.isIgnored(account.organizationalUnit); - if (ignored) { - this.logger.info(`${account.organizationalUnit} is ignored and not included`); - } - this.logger.info(`${account.organizationalUnit} organizational unit included`); - return true; - } - } - } - - return false; - } - - /** - * Function to get S3 life cycle rules - * @param lifecycleRules - * @returns - */ - protected getS3LifeCycleRules(lifecycleRules: LifeCycleRule[] | undefined): S3LifeCycleRule[] { - const rules: S3LifeCycleRule[] = []; - for (const lifecycleRule of lifecycleRules ?? []) { - const noncurrentVersionTransitions = []; - for (const noncurrentVersionTransition of lifecycleRule.noncurrentVersionTransitions ?? []) { - noncurrentVersionTransitions.push({ - storageClass: noncurrentVersionTransition.storageClass, - transitionAfter: noncurrentVersionTransition.transitionAfter, - }); - } - const transitions = []; - for (const transition of lifecycleRule.transitions ?? []) { - transitions.push({ - storageClass: transition.storageClass, - transitionAfter: transition.transitionAfter, - }); - } - const rule: S3LifeCycleRule = { - abortIncompleteMultipartUploadAfter: lifecycleRule.abortIncompleteMultipartUpload, - enabled: lifecycleRule.enabled, - expiration: lifecycleRule.expiration, - expiredObjectDeleteMarker: lifecycleRule.expiredObjectDeleteMarker, - id: lifecycleRule.id, - noncurrentVersionExpiration: lifecycleRule.noncurrentVersionExpiration, - noncurrentVersionTransitions, - transitions, - prefix: lifecycleRule.prefix, - }; - rules.push(rule); - } - return rules; - } - - /** - * Returns the SSM parameter path for the given resource type and replacement strings. - * @see {@link SsmParameterPath} for resource type schema - * - * @param resourceType - * @param replacements - * @returns - */ - public getSsmPath(resourceType: SsmResourceType, replacements: string[]) { - // Prefix applied to all SSM parameters - // Static for now, but leaving option to modify for future iterations - const ssmPrefix = this.props.prefixes.ssmParamName; - return new SsmParameterPath(ssmPrefix, resourceType, replacements).parameterPath; - } - - /** - * Function to get list of targets by type organization unit or account for given scp - * @param targetName - * @param targetType - * @returns - */ - public getScpNamesForTarget(targetName: string, targetType: 'ou' | 'account'): string[] { - const scps: string[] = []; - - for (const serviceControlPolicy of this.props.organizationConfig.serviceControlPolicies) { - if (targetType === 'ou' && serviceControlPolicy.deploymentTargets.organizationalUnits) { - if (serviceControlPolicy.deploymentTargets.organizationalUnits.indexOf(targetName) !== -1) { - scps.push(serviceControlPolicy.name); - } - } - if (targetType === 'account' && serviceControlPolicy.deploymentTargets.accounts) { - if (serviceControlPolicy.deploymentTargets.accounts.indexOf(targetName) !== -1) { - scps.push(serviceControlPolicy.name); - } - } - } - return scps; - } - - /** - * Get the IAM condition context key for the organization. - * @param organizationId string | undefined - * @returns - */ - protected getPrincipalOrgIdCondition(organizationId: string | undefined): PrincipalOrgIdConditionType { - if (this.props.partition === 'aws-cn' || !this.props.organizationConfig.enable) { - const accountIds = this.props.accountsConfig.getAccountIds(); - if (accountIds) { - return { - 'aws:PrincipalAccount': accountIds, - }; - } - } - if (organizationId) { - return { - 'aws:PrincipalOrgID': organizationId, - }; - } - this.logger.error('Organization ID not found or account IDs not found'); - throw new Error(`Configuration validation failed at runtime.`); - } - - /** - * Get the IAM principals for the organization. - */ - public getOrgPrincipals(organizationId: string | undefined, withPrefixCondition?: boolean): cdk.aws_iam.IPrincipal { - if (this.props.partition === 'aws-cn' || !this.props.organizationConfig.enable) { - const accountIds = this.props.accountsConfig.getAccountIds(); - if (accountIds) { - const principals: cdk.aws_iam.PrincipalBase[] = []; - accountIds.forEach(accountId => { - principals.push(new cdk.aws_iam.AccountPrincipal(accountId)); - }); - return withPrefixCondition - ? new cdk.aws_iam.CompositePrincipal(...principals).withConditions({ - ArnLike: { - 'aws:PrincipalArn': `arn:${this.partition}:iam::*:role/${this.props.prefixes.accelerator}*`, - }, - }) - : new cdk.aws_iam.CompositePrincipal(...principals); - } - } - if (organizationId) { - return withPrefixCondition - ? new cdk.aws_iam.OrganizationPrincipal(organizationId).withConditions({ - ArnLike: { - 'aws:PrincipalArn': `arn:${this.partition}:iam::*:role/${this.props.prefixes.accelerator}*`, - }, - }) - : new cdk.aws_iam.OrganizationPrincipal(organizationId); - } - this.logger.error('Organization ID not found or account IDs not found'); - throw new Error(`Configuration validation failed at runtime.`); - } - - /** - * Generate replacements and optionally return a temp path - * to the transformed document - * @param policyPath - * @param returnTempPath - * @param organizationId - * @param tempFileName - * @returns - */ - public generatePolicyReplacements( - policyPath: string, - returnTempPath: boolean, - organizationId?: string, - tempFileName?: string, - parameters?: { [key: string]: string | string[] }, - ): string { - // Transform policy document - let policyContent: string = fs.readFileSync(policyPath, 'utf8'); - const acceleratorPrefix = this.props.prefixes.accelerator; - const acceleratorPrefixNoDash = acceleratorPrefix.endsWith('-') - ? acceleratorPrefix.slice(0, -1) - : acceleratorPrefix; - - const additionalReplacements: { [key: string]: string | string[] } = { - '\\${ACCELERATOR_DEFAULT_PREFIX_SHORTHAND}': acceleratorPrefix.substring(0, 4).toUpperCase(), - '\\${ACCELERATOR_PREFIX_ND}': acceleratorPrefixNoDash, - '\\${ACCELERATOR_PREFIX_LND}': acceleratorPrefixNoDash.toLowerCase(), - '\\${ACCELERATOR_SSM_PREFIX}': this.props.prefixes.ssmParamName, - '\\${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}': this.centralLogsBucketName, - '\\${ACCOUNT_ID}': cdk.Stack.of(this).account, - '\\${AUDIT_ACCOUNT_ID}': this.props.accountsConfig.getAuditAccountId(), - '\\${HOME_REGION}': this.props.globalConfig.homeRegion, - '\\${LOGARCHIVE_ACCOUNT_ID}': this.props.accountsConfig.getLogArchiveAccountId(), - '\\${MANAGEMENT_ACCOUNT_ID}': this.props.accountsConfig.getManagementAccountId(), - '\\${REGION}': cdk.Stack.of(this).region, - '\\${ACCOUNT_NAME}': this.props.accountsConfig.getAccountNameById(cdk.Stack.of(this).account)!, - }; - - if (organizationId) { - additionalReplacements['\\${ORG_ID}'] = organizationId; - } - - const policyParams: { [key: string]: string | string[] } = { - ...this.props.replacementsConfig.placeholders, - ...parameters, - }; - - for (const key of Object.keys(policyParams)) { - additionalReplacements[`\\\${${ReplacementsConfig.POLICY_PARAMETER_PREFIX}:${key}}`] = policyParams[key]; - } - - policyContent = policyReplacements({ - content: policyContent, - acceleratorPrefix, - managementAccountAccessRole: this.props.globalConfig.managementAccountAccessRole, - partition: this.props.partition, - additionalReplacements, - acceleratorName: this.props.globalConfig.externalLandingZoneResources?.acceleratorName || 'lza', - networkConfig: this.props.networkConfig, - accountsConfig: this.props.accountsConfig, - }); - if (path.extname(policyPath) === '.json') { - // Validate and remove all unnecessary spaces in JSON string - policyContent = JSON.stringify(JSON.parse(policyContent)); - } - if (returnTempPath) { - return this.createTempFile(policyContent, policyPath, tempFileName); - } else { - return policyContent; - } - } - - /** - * Create a temp file of a transformed policy document - * @param content - * @param tempFileName - * @returns - */ - private createTempFile(content: string, inputFileName: string, tempFileName?: string): string { - // Generate unique file path in temporary directory - let tempDir: string; - if (process.platform === 'win32') { - try { - fs.accessSync(process.env['Temp']!, fs.constants.W_OK); - } catch (e) { - this.logger.error(`Unable to write files to temp directory: ${e}`); - } - tempDir = path.join(process.env['Temp']!, 'temp-transformed-documents'); - } else { - try { - fs.accessSync('/tmp', fs.constants.W_OK); - } catch (e) { - this.logger.error(`Unable to write files to temp directory: ${e}`); - } - tempDir = path.join('/tmp', 'temp-transformed-documents'); - } - const tempPath = path.join(tempDir, tempFileName ?? `${uuidv4()}.${path.extname(inputFileName)}`); - // Write transformed file - if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir); - } - fs.writeFileSync(tempPath, content, 'utf-8'); - - return tempPath; - } - - protected convertMinutesToIso8601(s: number) { - const days = Math.floor(s / 1440); - s = s - days * 1440; - const hours = Math.floor(s / 60); - s = s - hours * 60; - - let dur = 'PT'; - if (days > 0) { - dur += days + 'D'; - } - if (hours > 0) { - dur += hours + 'H'; - } - dur += s + 'M'; - - return dur.toString(); - } - - protected processBlockDeviceReplacements(blockDeviceMappings: BlockDeviceMappingItem[], appName: string) { - const mappings: BlockDeviceMappingItem[] = []; - blockDeviceMappings.forEach(device => - mappings.push({ - deviceName: device.deviceName, - ebs: device.ebs ? this.processKmsKeyReplacements(device, appName) : undefined, - }), - ); - - return mappings; - } - - protected processKmsKeyReplacements(device: BlockDeviceMappingItem, appName: string): EbsItemConfig { - if (device.ebs!.kmsKeyId) { - return this.replaceKmsKeyIdProvided(device, appName); - } - if (device.ebs!.encrypted && this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.enable) { - return this.replaceKmsKeyDefaultEncryption(device, appName); - } - - return { - deleteOnTermination: device.ebs!.deleteOnTermination, - encrypted: device.ebs!.encrypted, - iops: device.ebs!.iops, - kmsKeyId: device.ebs!.kmsKeyId, - snapshotId: device.ebs!.snapshotId, - throughput: device.ebs!.throughput, - volumeSize: device.ebs!.volumeSize, - volumeType: device.ebs!.volumeType, - }; - } - - protected replaceKmsKeyDefaultEncryption(device: BlockDeviceMappingItem, appName: string): EbsItemConfig { - let ebsEncryptionKey: cdk.aws_kms.IKey; - // user set encryption as true and has default ebs encryption enabled - // user defined kms key is provided - if (this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.kmsKey) { - ebsEncryptionKey = cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.kmsKey) + - pascalCase(`AcceleratorGetKey-${appName}-${device.deviceName}`) + - `-KmsKey`, - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${this.props.prefixes.ssmParamName}/kms/${this.props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.kmsKey}/key-arn`, - ), - ); - } else { - // user set encryption as true and has default ebs encryption enabled - // no kms key is provided - ebsEncryptionKey = cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(`AcceleratorGetKey-${appName}-${device.deviceName}-${device.ebs!.kmsKeyId}`), - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${this.props.prefixes.ssmParamName}/security-stack/ebsDefaultVolumeEncryptionKeyArn`, - ), - ); - } - return { - deleteOnTermination: device.ebs!.deleteOnTermination, - encrypted: device.ebs!.encrypted, - iops: device.ebs!.iops, - kmsKeyId: ebsEncryptionKey.keyId, - snapshotId: device.ebs!.snapshotId, - throughput: device.ebs!.throughput, - volumeSize: device.ebs!.volumeSize, - volumeType: device.ebs!.volumeType, - }; - } - - protected replaceKmsKeyIdProvided(device: BlockDeviceMappingItem, appName: string): EbsItemConfig { - const kmsKeyEntity = cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(`AcceleratorGetKey-${appName}-${device.deviceName}-${device.ebs!.kmsKeyId}`), - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${this.props.prefixes.ssmParamName}/kms/${device.ebs!.kmsKeyId}/key-arn`, - ), - ); - return { - deleteOnTermination: device.ebs!.deleteOnTermination, - encrypted: device.ebs!.encrypted, - iops: device.ebs!.iops, - kmsKeyId: kmsKeyEntity.keyId, - snapshotId: device.ebs!.snapshotId, - throughput: device.ebs!.throughput, - volumeSize: device.ebs!.volumeSize, - volumeType: device.ebs!.volumeType, - }; - } - - protected replaceImageId(imageId: string) { - if (imageId.match('\\${ACCEL_LOOKUP::ImageId:(.*)}')) { - const imageIdMatch = imageId.match('\\${ACCEL_LOOKUP::ImageId:(.*)}'); - return cdk.aws_ssm.StringParameter.valueForStringParameter(this, imageIdMatch![1]); - } else { - return imageId; - } - } - - /** - * Public accessor method to add SSM parameters - * @param props - */ - public addSsmParameter(props: { logicalId: string; parameterName: string; stringValue: string; scope?: string }) { - this.ssmParameters.push({ - logicalId: props.logicalId, - parameterName: props.parameterName, - stringValue: props.stringValue, - scope: props.scope, - }); - } - - /** - * Helper function to verify if resource managed by ASEA or not by looking in resource mapping - * Can be replaced with LZA Configuration check. Not using configuration check to avoid errors/mistakes in configuration by user - * - * @param resourceType - * @param resourceIdentifier - * @returns - */ - public isManagedByAsea(resourceType: string, resourceIdentifier: string): boolean { - if (!this.props.globalConfig.externalLandingZoneResources?.importExternalLandingZoneResources) return false; - const aseaResourceList = this.props.globalConfig.externalLandingZoneResources.resourceList; - return !!aseaResourceList.find( - r => - r.accountId === cdk.Stack.of(this).account && - r.region === cdk.Stack.of(this).region && - r.resourceType === resourceType && - r.resourceIdentifier === resourceIdentifier && - !r.isDeleted, - ); - } - - /** - * Helper function to verify if resource managed by ASEA or not by looking in resource mapping - * Different than isManagedByAsea() because it does not filter for region or account id. - * - * @param resourceType - * @param resourceIdentifier - * @returns - */ - public isManagedByAseaGlobal(resourceType: string, resourceIdentifier: string): boolean { - if (!this.props.globalConfig.externalLandingZoneResources?.importExternalLandingZoneResources) return false; - const aseaResourceList = this.props.globalConfig.externalLandingZoneResources.resourceList; - return !!aseaResourceList.find( - r => r.resourceType === resourceType && r.resourceIdentifier === resourceIdentifier && !r.isDeleted, - ); - } - - public getExternalResourceParameter(name: string) { - if (!this.externalResourceParameters) { - throw new Error(`No ssm parameter "${name}" found in account and region`); - } - return this.externalResourceParameters[name]; - } - - public addNagSuppression(nagSuppression: NagSuppressionDetailType) { - this.nagSuppressionInputs.push(nagSuppression); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/accounts-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/accounts-stack.ts deleted file mode 100644 index 0fd174b..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/accounts-stack.ts +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { MoveAccountRule } from '@aws-accelerator/constructs'; -import { AcceleratorStack, AcceleratorStackProps, NagSuppressionRuleIds } from './accelerator-stack'; -import { ScpResource } from '../resources/scp-resource'; -import { KmsKeyResource } from '../resources/kms-key-resource'; - -export interface AccountsStackProps extends AcceleratorStackProps { - readonly configDirPath: string; -} - -export class AccountsStack extends AcceleratorStack { - private keyResource: KmsKeyResource; - - constructor(scope: Construct, id: string, props: AccountsStackProps) { - super(scope, id, props); - - this.keyResource = new KmsKeyResource(this, props); - - // - // Create MoveAccountRule - // - this.createMoveAccountRule(props); - - // - // Global Organizations actions - // - if (props.globalRegion === cdk.Stack.of(this).region) { - // - // Create and attach scps - // - const scpResource = new ScpResource(this, this.keyResource.cloudwatchKey, this.keyResource.lambdaKey, props); - const scpItems = scpResource.createAndAttachScps(props); - - // - // Create Access Analyzer Service Linked Role - // - this.createAccessAnalyzerServiceLinkedRole({ - cloudwatch: this.keyResource.cloudwatchKey, - lambda: this.keyResource.lambdaKey, - }); - - // - // Create Access GuardDuty Service Linked Role - // - this.createGuardDutyServiceLinkedRole({ - cloudwatch: this.keyResource.cloudwatchKey, - lambda: this.keyResource.lambdaKey, - }); - - // - // Create Access SecurityHub Service Linked Role - // - this.createSecurityHubServiceLinkedRole({ - cloudwatch: this.keyResource.cloudwatchKey, - lambda: this.keyResource.lambdaKey, - }); - - // - // Create Access Macie Service Linked Role - // - this.createMacieServiceLinkedRole({ - cloudwatch: this.keyResource.cloudwatchKey, - lambda: this.keyResource.lambdaKey, - }); - - // - // Configure and attach quarantine scp - // - scpResource.configureAndAttachQuarantineScp(scpItems, props); - - // - // End of Stack functionality - // - this.logger.debug(`Stack synthesis complete`); - } - - // - // Create SSM parameters - // - this.createSsmParameters(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Function to create MoveAccountRule - * @param props {@link AccountsStackProps} - * @returns MoveAccountRule | undefined - * - * @remarks - * Create MoveAccountRule only in global region for ControlTower and Organization is enabled. - */ - private createMoveAccountRule(props: AccountsStackProps): MoveAccountRule | undefined { - let moveAccountRule: MoveAccountRule | undefined; - if (props.globalRegion === cdk.Stack.of(this).region) { - if (props.organizationConfig.enable && !props.globalConfig.controlTower.enable) { - moveAccountRule = new MoveAccountRule(this, 'MoveAccountRule', { - globalRegion: props.globalRegion, - homeRegion: props.globalConfig.homeRegion, - moveAccountRoleName: this.acceleratorResourceNames.roles.moveAccountConfig, - commitId: props.configCommitId ?? '', - acceleratorPrefix: props.prefixes.accelerator, - configTableNameParameterName: this.acceleratorResourceNames.parameters.configTableName, - configTableArnParameterName: this.acceleratorResourceNames.parameters.configTableArn, - kmsKey: this.keyResource.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/MoveAccountRule/MoveAccountRole/Policy/Resource`, - reason: 'AWS Custom resource provider role created by cdk.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/MoveAccountRule/MoveAccountTargetFunction/ServiceRole/Resource`, - reason: 'AWS Custom resource provider role created by cdk.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/MoveAccountRule/MoveAccountTargetFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'AWS Custom resource provider role created by cdk.', - }, - ], - }); - } - } - return moveAccountRule; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/applications-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/applications-stack.ts deleted file mode 100644 index 0d8b39a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/applications-stack.ts +++ /dev/null @@ -1,756 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { - AcceleratorStack, - AcceleratorStackProps, - AcceleratorKeyType, - NagSuppressionRuleIds, -} from './accelerator-stack'; -import { - AppConfigItem, - VpcConfig, - VpcTemplatesConfig, - ApplicationLoadBalancerListenerConfig, - TargetGroupItemConfig, - LaunchTemplateConfig, - AutoScalingConfig, - NetworkLoadBalancerConfig, - ApplicationLoadBalancerConfig, -} from '@aws-accelerator/config'; -import { - TargetGroup, - NetworkLoadBalancer, - ApplicationLoadBalancer, - LaunchTemplate, - AutoscalingGroup, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -export type PrivateIpAddressConfig = { - primary: boolean | undefined; - privateIpAddress: string | undefined; -}; -export type NetworkInterfaceItemConfig = { - associateCarrierIpAddress: boolean | undefined; - associateElasticIp: boolean | undefined; - associatePublicIpAddress: boolean | undefined; - deleteOnTermination: boolean | undefined; - description: string | undefined; - deviceIndex: number | undefined; - groups: string[] | undefined; - interfaceType: string | undefined; - networkCardIndex: number | undefined; - networkInterfaceId: string | undefined; - privateIpAddress: string | undefined; - secondaryPrivateIpAddressCount: number | undefined; - sourceDestCheck: boolean | undefined; - subnetId: string | undefined; - privateIpAddresses: PrivateIpAddressConfig[] | undefined; -}; -export type EbsProperty = { - deleteOnTermination?: boolean; - encrypted?: boolean; - iops?: number; - kmsKeyId?: string; - snapshotId?: string; - throughput?: number; - volumeSize?: number; - volumeType?: string; -}; - -export type BlockDeviceMappingItem = { - deviceName: string; - ebs?: EbsProperty; -}; -export type TargetGroupItem = { - name: string; - targetGroup: TargetGroup; -}; - -export type AlbListenerConfig = { - name: string; - port: number; - protocol: 'HTTP' | 'HTTPS'; - type: 'fixed-response' | 'forward' | 'redirect'; - certificate: string | undefined; - sslPolicy?: string; - targetGroup: string; - fixedResponseConfig?: { - messageBody?: string; - contentType?: string; - statusCode: string; - }; - forwardConfig?: { - targetGroupStickinessConfig?: { - durationSeconds?: number; - enabled?: boolean; - }; - }; - order?: number; - redirectConfig?: { - statusCode?: string; - host?: string; - path?: string; - port?: number; - protocol?: string; - query?: string; - }; -}; -export interface ApplicationStackProps extends AcceleratorStackProps { - readonly appConfigItem: AppConfigItem; -} - -export class ApplicationsStack extends AcceleratorStack { - private securityGroupMap: Map; - private subnetMap: Map; - private vpcMap: Map; - constructor(scope: Construct, id: string, props: ApplicationStackProps) { - super(scope, id, props); - this.props = props; - const allVpcItems = [...props.networkConfig.vpcs, ...(props.networkConfig.vpcTemplates ?? [])] ?? []; - const allAppConfigs: AppConfigItem[] = props.customizationsConfig.applications ?? []; - const elbLogsBucketName = this.getElbLogsBucketName(); - - // Set initial private properties - [this.securityGroupMap, this.subnetMap, this.vpcMap] = this.setInitialMaps(allVpcItems, allAppConfigs); - - const lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - - const cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - - //Create application config resources - this.createApplicationConfigResources( - props.appConfigItem, - { securityGroupMap: this.securityGroupMap, subnetMap: this.subnetMap, vpcMap: this.vpcMap }, - props.configDirPath, - allVpcItems, - elbLogsBucketName, - { key: cloudwatchKey, logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays }, - lambdaKey, - ); - - // Create SSM parameters - this.createSsmParameters(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Set security group, subnet, and VPC maps for this stack's account and region - * @param props ApplicationStackProps - * @returns Map of security group, subnet and VPC - */ - private setInitialMaps( - allVpcItems: (VpcConfig | VpcTemplatesConfig)[], - allAppConfigs: AppConfigItem[], - ): Map[] { - let securityGroupMap = new Map(); - let subnetMap = new Map(); - let vpcMap = new Map(); - - for (const appConfigItem of allAppConfigs) { - [vpcMap, subnetMap, securityGroupMap] = this.setInitialMapProcessApp( - appConfigItem, - allVpcItems, - vpcMap, - subnetMap, - securityGroupMap, - ); - } - return [securityGroupMap, subnetMap, vpcMap]; - } - private setInitialMapProcessApp( - appConfigItem: AppConfigItem, - allVpcItems: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - subnetMap: Map, - securityGroupMap: Map, - ) { - for (const vpcItem of allVpcItems) { - //only process items in the same vpc - if (vpcItem.name === appConfigItem.vpc) { - [vpcMap, subnetMap, securityGroupMap] = this.setInitialMapProcessAppVpcItem( - vpcItem, - vpcMap, - subnetMap, - securityGroupMap, - ); - } - } - return [vpcMap, subnetMap, securityGroupMap]; - } - - /** - * Function to get Account IDs where VPC is created and subnets are shared to. - * @param vpcItem - * @returns - */ - private getVpcAccountIdsWithShared(vpcItem: VpcConfig | VpcTemplatesConfig): string[] { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - const sharedSubnets = vpcItem.subnets ? vpcItem.subnets.filter(subnet => subnet.shareTargets) : []; - for (const subnetItem of sharedSubnets) { - const subnetAccountIds = this.getAccountIdsFromShareTarget(subnetItem.shareTargets!); - subnetAccountIds.forEach(accountId => { - if (!vpcAccountIds.includes(accountId)) { - vpcAccountIds.push(accountId); - } - }); - } - return vpcAccountIds; - } - - private setInitialMapProcessAppVpcItem( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcMap: Map, - subnetMap: Map, - securityGroupMap: Map, - ) { - // Get account IDs - const vpcAccountIds = this.getVpcAccountIdsWithShared(vpcItem); - if (vpcAccountIds.includes(cdk.Stack.of(this).account) && vpcItem.region === cdk.Stack.of(this).region) { - // Set VPC ID - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - vpcMap.set(vpcItem.name, vpcId); - // Set subnet IDs - const ownedVpcIds = this.getVpcAccountIds(vpcItem); - for (const subnetItem of vpcItem.subnets ?? []) { - // Lookup all subnet ids if VPC is owned by this account - // otherwise only lookup shared subnet ids - if ( - ownedVpcIds.includes(cdk.Stack.of(this).account) || - (subnetItem.shareTargets && - this.getAccountIdsFromShareTarget(subnetItem.shareTargets).includes(cdk.Stack.of(this).account)) - ) { - const subnetId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - ); - subnetMap.set(`${vpcItem.name}_${subnetItem.name}`, subnetId); - } - } - // Set security group IDs - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - const securityGroupId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, securityGroupItem.name]), - ); - securityGroupMap.set(`${vpcItem.name}_${securityGroupItem.name}`, securityGroupId); - } - } - return [vpcMap, subnetMap, securityGroupMap]; - } - - private createApplicationConfigResources( - appConfigItem: AppConfigItem, - maps: { securityGroupMap: Map; subnetMap: Map; vpcMap: Map }, - configDirPath: string, - allVpcItems: (VpcConfig | VpcTemplatesConfig)[], - accessLogsBucket: string, - cloudwatch: { key?: cdk.aws_kms.IKey; logRetentionInDays: number }, - lambdaKey?: cdk.aws_kms.IKey, - ) { - for (const vpcItem of allVpcItems) { - if (vpcItem.name === appConfigItem.vpc) { - // Get account IDs - const vpcAccountIds = this.getVpcAccountIdsWithShared(vpcItem); - if (vpcAccountIds.includes(cdk.Stack.of(this).account) && vpcItem.region === cdk.Stack.of(this).region) { - // Create target group resource - const targetGroups = this.createTargetGroup( - appConfigItem.targetGroups ?? undefined, - maps.vpcMap, - appConfigItem.vpc, - appConfigItem.name, - )!; - - // Create network load balancer resource - this.createNetworkLoadBalancer( - appConfigItem.networkLoadBalancer ?? undefined, - appConfigItem.name, - appConfigItem.vpc, - targetGroups, - maps.subnetMap, - accessLogsBucket, - ); - - // Create application load balancer resource - this.createApplicationLoadBalancer( - appConfigItem.applicationLoadBalancer ?? undefined, - appConfigItem.vpc, - appConfigItem.name, - targetGroups, - maps.securityGroupMap, - maps.subnetMap, - accessLogsBucket, - ); - - // create launch template resource - const lt = this.createLaunchTemplate( - appConfigItem.launchTemplate, - appConfigItem.vpc, - appConfigItem.name, - maps.securityGroupMap, - maps.subnetMap, - configDirPath, - ); - // create autoscaling group resource only if launch template and autoscaling are defined - - if (lt && appConfigItem.autoscaling) { - this.createAutoScalingGroup( - { - autoscaling: appConfigItem.autoscaling, - vpcName: appConfigItem.vpc, - name: appConfigItem.name, - }, - targetGroups, - lt, - maps.subnetMap, - { key: cloudwatch.key, logRetentionInDays: cloudwatch.logRetentionInDays }, - lambdaKey, - ); - } - } - } - } - } - - private createApplicationLoadBalancer( - applicationLoadBalancer: ApplicationLoadBalancerConfig | undefined, - vpcName: string, - appName: string, - targetGroups: TargetGroupItem[] | undefined, - securityGroupMap: Map, - subnetMap: Map, - accessLogsBucket: string, - ) { - if (applicationLoadBalancer) { - const subnets = this.getSubnets(applicationLoadBalancer.subnets ?? [], vpcName, subnetMap)!; - const getSecurityGroups = this.getSecurityGroups( - applicationLoadBalancer.securityGroups ?? [], - vpcName, - securityGroupMap, - ); - // alb needs atleast 2 subnets - if (subnets.length < 2) { - throw new Error( - `[customizations-application-stack] Found ${applicationLoadBalancer.subnets.length} ALB subnets: ${applicationLoadBalancer.subnets}. Minium 2 subnets in 2 AZs are required`, - ); - } - return new ApplicationLoadBalancer(this, `ApplicationLoadBalancer_${appName}`, { - name: applicationLoadBalancer.name, - ssmPrefix: this.props.prefixes.ssmParamName, - subnets, - securityGroups: getSecurityGroups!, - scheme: applicationLoadBalancer.scheme! ?? 'internal', - accessLogsBucket, - attributes: applicationLoadBalancer.attributes ?? undefined, - listeners: this.getAlbListenerTargetGroupArn(applicationLoadBalancer?.listeners ?? undefined, targetGroups), - }); - } else { - return undefined; - } - } - private getAlbListenerTargetGroupArn( - listeners: AlbListenerConfig[] | undefined, - targetGroups: TargetGroupItem[] | undefined, - ) { - const output = []; - // if listener is provided look up target group arn - // if no target group is provided return undefined - if (listeners && targetGroups) { - for (const listener of listeners) { - const targetGroupValues = targetGroups ?? []; - - const filteredTargetGroup = targetGroupValues.find(element => { - return element.name === listener.targetGroup; - }); - if (!filteredTargetGroup) { - this.logger.error(`ALB Listener ${listener.name} does not have a valid target group ${listener.targetGroup}`); - throw new Error(`Configuration validation failed at runtime.`); - } - listener.targetGroup = filteredTargetGroup.targetGroup.targetGroupArn; - output.push(listener as ApplicationLoadBalancerListenerConfig); - } - } else { - return undefined; - } - - return output; - } - - private createLaunchTemplate( - launchTemplate: LaunchTemplateConfig | undefined, - vpcName: string, - appName: string, - securityGroupMap: Map, - subnetMap: Map, - configDirPath: string, - ) { - if (launchTemplate) { - const getSecurityGroups = this.getSecurityGroups(launchTemplate.securityGroups ?? [], vpcName, securityGroupMap); - const blockDeviceMappingsValue = this.processBlockDeviceReplacements( - launchTemplate.blockDeviceMappings ?? [], - appName, - ); - const networkInterfacesValue = this.replaceNetworkInterfaceValues( - launchTemplate.networkInterfaces ?? [], - vpcName, - securityGroupMap, - subnetMap, - ); - const imageIdValue = this.replaceImageId(launchTemplate.imageId ?? ''); - return new LaunchTemplate(this, `LaunchTemplate-${pascalCase(appName)}-${pascalCase(launchTemplate.name)}`, { - name: launchTemplate.name, - appName: appName, - vpc: vpcName, - blockDeviceMappings: blockDeviceMappingsValue, - userData: - launchTemplate.userData && - // Applies replacements and return temp path if userData is defined in configuration - this.generatePolicyReplacements(path.join(configDirPath, launchTemplate.userData), true), - securityGroups: getSecurityGroups ?? undefined, - networkInterfaces: networkInterfacesValue ?? undefined, - instanceType: launchTemplate.instanceType, - keyPair: launchTemplate.keyPair ?? undefined, - iamInstanceProfile: launchTemplate.iamInstanceProfile ?? undefined, - imageId: imageIdValue, - enforceImdsv2: launchTemplate.enforceImdsv2 ?? true, - }); - } else { - return undefined; - } - } - private replaceNetworkInterfaceValues( - networkInterfaces: NetworkInterfaceItemConfig[], - vpc: string, - securityGroupMap: Map, - subnetMap: Map, - ) { - for (const networkInterface of networkInterfaces) { - const securityGroups: string[] | undefined = this.getSecurityGroups( - networkInterface.groups! ?? [], - vpc, - securityGroupMap, - ); - if (securityGroups) { - networkInterface.groups = securityGroups; - } - if (networkInterface.subnetId) { - const subnetIdValue = subnetMap.get(`${vpc}_${networkInterface.subnetId}`); - if (!subnetIdValue) { - this.logger.error(`Network Interfaces: subnet ${networkInterface.subnetId} not found in VPC ${vpc}`); - throw new Error(`Configuration validation failed at runtime.`); - } - networkInterface.subnetId = subnetIdValue; - } - } - - return networkInterfaces; - } - - private createAutoScalingGroup( - appConfigItem: { autoscaling: AutoScalingConfig; vpcName: string; name: string }, - targetGroupsInput: TargetGroupItem[] | undefined, - lt: LaunchTemplate, - subnetMap: Map, - cloudwatch: { key?: cdk.aws_kms.IKey; logRetentionInDays: number }, - lambdaKey?: cdk.aws_kms.IKey, - ) { - let finalTargetGroupArns: string[] = []; - // if input array is provided filter out targetGroup based on name - if (targetGroupsInput) { - const filteredTargetGroups = targetGroupsInput.filter(obj => { - if (appConfigItem.autoscaling.targetGroups?.includes(obj.name)) { - return obj; - } else { - return undefined; - } - }); - // get TargetGroup arn - const filteredTargetGroupArns = filteredTargetGroups.map(obj => { - return obj.targetGroup.targetGroupArn; - }); - // merge targetGroup arns into array - finalTargetGroupArns = [...filteredTargetGroupArns, ...finalTargetGroupArns]; - } - - // if array is empty returned undefined an input of [] is passed to api - let finalTargetGroups: string[] | undefined; - if (finalTargetGroupArns.length === 0) { - finalTargetGroups = undefined; - } else { - finalTargetGroups = finalTargetGroupArns; - } - - const subnets: string[] = []; - for (const subnet of appConfigItem.autoscaling.subnets ?? []) { - const subnetId = subnetMap.get(`${appConfigItem.vpcName}_${subnet}`); - if (!subnetId) { - throw new Error( - `[customizations-application-stack] Create Autoscaling Groups: subnet ${subnet} not found in VPC ${appConfigItem.vpcName}`, - ); - } - subnets.push(subnetId); - } - const cloudWatchLogKmsKey = cloudwatch.key; - const cloudWatchLogRetentionInDays = cloudwatch.logRetentionInDays; - const asg = new AutoscalingGroup( - this, - `AutoScalingGroup${pascalCase(appConfigItem.name)}${pascalCase(appConfigItem.autoscaling.name)}`, - { - name: appConfigItem.autoscaling.name, - minSize: appConfigItem.autoscaling.minSize, - maxSize: appConfigItem.autoscaling.maxSize, - desiredSize: appConfigItem.autoscaling.desiredSize, - launchTemplateVersion: lt.version, - launchTemplateId: lt.launchTemplateId, - healthCheckGracePeriod: appConfigItem.autoscaling.healthCheckGracePeriod ?? undefined, - healthCheckType: appConfigItem.autoscaling.healthCheckType ?? undefined, - targetGroups: finalTargetGroups, - subnets, - lambdaKey, - cloudWatchLogKmsKey, - cloudWatchLogRetentionInDays, - maxInstanceLifetime: appConfigItem.autoscaling.maxInstanceLifetime ?? undefined, - }, - ); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.AS3, - details: [ - { - path: `${this.stackName}/AutoScalingGroup${pascalCase(appConfigItem.name)}${pascalCase( - appConfigItem.autoscaling.name, - )}/Resource`, - reason: 'Scaling policies are not offered as a part of this solution', - }, - ], - }); - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AutoScalingGroup${pascalCase(appConfigItem.name)}${pascalCase( - appConfigItem.autoscaling.name, - )}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AutoScalingGroup${pascalCase(appConfigItem.name)}${pascalCase( - appConfigItem.autoscaling.name, - )}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/AutoScalingGroup${pascalCase(appConfigItem.name)}${pascalCase( - appConfigItem.autoscaling.name, - )}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AutoScalingGroup${pascalCase(appConfigItem.name)}${pascalCase( - appConfigItem.autoscaling.name, - )}/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource Lambda role policy.', - }, - ], - }); - - return asg; - } - - private createNetworkLoadBalancer( - networkLoadBalancer: NetworkLoadBalancerConfig | undefined, - appName: string, - vpcName: string, - targetGroups: TargetGroupItem[] | undefined, - subnetMap: Map, - accessLogsBucket: string, - ) { - if (networkLoadBalancer) { - const subnets = this.getSubnets(networkLoadBalancer.subnets ?? [], vpcName, subnetMap)!; - // if empty array is passed for subnets throw error that there are no subnets - // if subnets are not found which happens error is thrown in getSubnets function - if (!subnets) { - throw new Error( - `[customizations-application-stack] NLB subnets: ${networkLoadBalancer.subnets} not found in vpc ${vpcName}`, - ); - } - const nlb = new NetworkLoadBalancer(this, pascalCase(`AppNlb${appName}${networkLoadBalancer.name}`), { - name: networkLoadBalancer.name, - ssmPrefix: this.props.prefixes.ssmParamName, - appName: appName, - vpcName: vpcName, - subnets: subnets, - scheme: networkLoadBalancer?.scheme ?? undefined, - deletionProtection: networkLoadBalancer.deletionProtection ?? undefined, - crossZoneLoadBalancing: networkLoadBalancer.crossZoneLoadBalancing ?? undefined, - accessLogsBucket, - }); - - for (const listener of networkLoadBalancer.listeners ?? []) { - const targetGroupValues = targetGroups! ?? []; - const filteredTargetGroup = targetGroupValues.find(element => { - return element.name === listener.targetGroup; - }); - if (!filteredTargetGroup) { - this.logger.error(`NLB Listener ${listener.name} does not have a valid target group ${listener.targetGroup}`); - throw new Error(`Configuration validation failed at runtime.`); - } - const getCertificateValue = this.getCertificate(listener.certificate); - new cdk.aws_elasticloadbalancingv2.CfnListener(this, pascalCase(`Listener${appName}${listener.name}`), { - defaultActions: [ - { - type: 'forward', - forwardConfig: { - targetGroups: [ - { - targetGroupArn: filteredTargetGroup.targetGroup.targetGroupArn, - }, - ], - }, - targetGroupArn: filteredTargetGroup.targetGroup.targetGroupArn, - }, - ], - loadBalancerArn: nlb.networkLoadBalancerArn, - alpnPolicy: [listener.alpnPolicy!], - certificates: [{ certificateArn: getCertificateValue }], - port: listener.port!, - protocol: listener.protocol!, - sslPolicy: listener.sslPolicy!, - }); - } - return nlb; - } else { - return undefined; - } - } - - private getCertificate(certificate: string | undefined) { - if (certificate) { - //check if user provided arn. If so do nothing, if not get it from ssm - if (certificate.match('\\arn:*')) { - return certificate; - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ACM_CERT, [certificate]), - ); - } - } - return undefined; - } - private getSubnets(subnets: string[], vpc: string, subnetMap: Map) { - const output: string[] = []; - for (const subnet of subnets ?? []) { - const subnetId = subnetMap.get(`${vpc}_${subnet}`); - if (!subnetId) { - this.logger.error(`Subnet ${subnet} not found in VPC ${vpc}`); - throw new Error(`Configuration validation failed at runtime.`); - } - output.push(subnetId); - } - if (output.length === 0) { - return undefined; - } else { - return output; - } - } - private getSecurityGroups(securityGroups: string[], vpc: string, securityGroupMap: Map) { - const output: string[] = []; - for (const sg of securityGroups ?? []) { - const sgId = securityGroupMap.get(`${vpc}_${sg}`); - if (!sgId) { - this.logger.error(`Security group ${sg} does not exist in VPC ${vpc}`); - throw new Error(`Configuration validation failed at runtime.`); - } - output.push(sgId); - } - if (output.length === 0) { - return undefined; - } else { - return output; - } - } - - private createTargetGroup( - targetGroupsInput: TargetGroupItemConfig[] | undefined, - vpcMap: Map, - vpcName: string, - appName: string, - ) { - const output = []; - const vpcId = vpcMap.get(vpcName); - if (!vpcId) { - this.logger.error(`Unable to locate VPC ${vpcName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - if (targetGroupsInput) { - for (const targetGroup of targetGroupsInput) { - const tg = new TargetGroup(this, pascalCase(`AppTargetGroup${appName}${targetGroup.name}`), { - name: targetGroup.name, - port: targetGroup.port, - protocol: targetGroup.protocol, - protocolVersion: targetGroup.protocolVersion! || undefined, - type: targetGroup.type, - attributes: targetGroup.attributes ?? undefined, - healthCheck: targetGroup.healthCheck ?? undefined, - threshold: targetGroup.threshold ?? undefined, - matcher: targetGroup.matcher ?? undefined, - vpc: vpcId, - }); - const outputItem = { name: targetGroup.name, targetGroup: tg }; - output.push(outputItem); - - this.ssmParameters.push({ - logicalId: pascalCase(`SsmTg${appName}${vpcName}${targetGroup.name}Arn`), - parameterName: this.getSsmPath(SsmResourceType.TARGET_GROUP, [appName, vpcName, targetGroup.name]), - stringValue: tg.targetGroupArn, - }); - } - } - if (output.length === 0) { - return undefined; - } else { - return output; - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/bootstrap-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/bootstrap-stack.ts deleted file mode 100644 index 66d4909..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/bootstrap-stack.ts +++ /dev/null @@ -1,687 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import { AcceleratorStack, AcceleratorStackProps } from './accelerator-stack'; -import { BootstrapVersion } from '../accelerator'; -import { IPrincipal } from 'aws-cdk-lib/aws-iam'; - -export class BootstrapStack extends AcceleratorStack { - readonly qualifier: string; - readonly managementAccount: string; - readonly s3KmsKeyOutputValue: string; - readonly assetBucketName: string; - constructor(scope: Construct, id: string, props: AcceleratorStackProps, bootstrapQualifier?: string) { - super(scope, id, props); - const customDeploymentRole = this.props.globalConfig.cdkOptions?.customDeploymentRole; - this.qualifier = bootstrapQualifier ?? 'accel'; - this.managementAccount = props.accountsConfig.getManagementAccountId(); - this.assetBucketName = this.getAssetBucketName(); - - if (!this.account || !this.region) { - throw new Error('Must pass account and region to the bootstrap stack'); - } - - // Create Cfn Parameters - // These parameters are required to exist due to how cdk creates a change set during bootstrapping - new cdk.CfnParameter(this, 'CloudFormationExecutionPolicies'); - new cdk.CfnParameter(this, 'ContainerAssetsRepositoryName', { default: '' }); - new cdk.CfnParameter(this, 'FileAssetsBucketKmsKeyId', { default: '' }); - new cdk.CfnParameter(this, 'FileAssetsBucketName', { default: '' }); - new cdk.CfnParameter(this, 'PublicAccessBlockConfiguration'); - new cdk.CfnParameter(this, 'Qualifier'); - new cdk.CfnParameter(this, 'TrustedAccountsForLookup'); - const trustedAccountsParam = new cdk.CfnParameter(this, 'TrustedAccounts', { type: 'CommaDelimitedList' }); - - if (this.account === this.managementAccount) { - this.logger.info(`Creating bucket for region ${this.region} in account ${this.account}`); - - const s3KmsKey = this.createBucketCmk({ - managementAccountId: this.managementAccount, - customDeploymentRole, - }); - this.s3KmsKeyOutputValue = s3KmsKey.keyArn; - this.createAssetBucket({ - kmsKey: s3KmsKey, - customDeploymentRole, - }); - } else { - this.s3KmsKeyOutputValue = '-'; - } - - // Create SSM Parameter for CDK Bootstrap Version - const cdkBootstrapVersionParam = new cdk.aws_ssm.StringParameter(this, 'CdkBootstrapVersion', { - parameterName: `/cdk-bootstrap/${this.qualifier}/version`, - stringValue: BootstrapVersion.toString(), - }); - // Override logical Id - const cfnCdkBootstrapVersionParam = cdkBootstrapVersionParam.node.defaultChild as cdk.aws_ssm.CfnParameter; - cfnCdkBootstrapVersionParam.overrideLogicalId('CdkBootstrapVersion'); - - if (!props.useExistingRoles) { - // Create CDK roles for default CDK stack synthesis - this.createCdkRoles(cdkBootstrapVersionParam.parameterName, trustedAccountsParam.valueAsList); - - this.createCustomDeploymentRole(customDeploymentRole, this.props.globalConfig.homeRegion); - } - // Create ECR repository - this.createEcrRepository(); - - // Outputs - new cdk.CfnOutput(this, 'BootstrapVersionOutput', { - value: BootstrapVersion.toString(), - description: 'The version of the bootstrap resources that are currently mastered in this stack', - }); - - new cdk.CfnOutput(this, 'BucketNameOutput', { - value: this.assetBucketName, - description: 'The name of the S3 bucket owned by the CDK toolkit stack', - }); - - new cdk.CfnOutput(this, 'BucketDomainNameOutput', { - value: this.getAssetBucketDomainName(), - description: 'The domain name of the S3 bucket owned by the CDK toolkit stack', - }); - - new cdk.CfnOutput(this, 'FileAssetKeyArnOutput', { - value: this.s3KmsKeyOutputValue, - description: 'The ARN of the KMS key used to encrypt the asset bucket ', - exportName: `CdkBootstrap-${this.qualifier}-FileAssetKeyArn`, - }); - - new cdk.CfnOutput(this, 'ImageRepositoryNameOutput', { - value: this.getEcrRepoName(), - description: 'The name of the ECR repository which hosts docker image assets ', - }); - } - - createCdkRoles(bootstrapParameterName: string, trustedAccounts: string[]) { - // Create file publishing role - this.createFilePublishingRole(); - - // Create image publishing role - this.createImagePublishingRole(); - - // Create lookup role - this.createLookupRole(); - - // Create cloudformation execution role - const cloudFormationExecutionRole = this.createExecutionRole(); - - // Create deployment role - this.createDeploymentRole(bootstrapParameterName, cloudFormationExecutionRole.roleArn, trustedAccounts); - } - - createCustomDeploymentRole(customRoleName: string | undefined, homeRegion: string) { - if (!customRoleName) { - return; - } - if (cdk.Stack.of(this).region !== homeRegion) { - return; - } - const customDeploymentRole = new cdk.aws_iam.Role(this, 'CustomDeploymentRole', { - assumedBy: this.setCompositePrincipals({ - managementAccount: this.managementAccount, - cfnServicePrincipal: true, - }), - roleName: customRoleName, - }); - this.setAssumeSelfPermissions(customDeploymentRole); - customDeploymentRole.addManagedPolicy(cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')); - // Override logical Id - const cfnCustomDeploymentRole = customDeploymentRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnCustomDeploymentRole.overrideLogicalId('CustomDeploymentRole'); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/CustomDeploymentRole/Resource`, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - } - - createDeploymentRole(bootstrapParameterName: string, cfnExecRoleArn: string, trustedAccounts: string[]) { - const deploymentActionRole = new cdk.aws_iam.Role(this, 'DeploymentActionRole', { - assumedBy: this.setCompositePrincipals({ managementAccount: this.managementAccount }), - roleName: `cdk-${this.qualifier}-deploy-role-${this.account}-${this.region}`, - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudFormationPermissions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'cloudformation:CreateChangeSet', - 'cloudformation:DeleteChangeSet', - 'cloudformation:DescribeChangeSet', - 'cloudformation:DescribeStacks', - 'cloudformation:ExecuteChangeSet', - 'cloudformation:CreateStack', - 'cloudformation:UpdateStack', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'PipelineCrossAccountArtifactsBucket', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetObject*', 's3:GetBucket*', 's3:List*', 's3:Abort*', 's3:DeleteObject*', 's3:PutObject*'], - resources: [ - `arn:${this.partition}:s3:::${this.assetBucketName}`, - `arn:${this.partition}:s3:::${this.assetBucketName}/*`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'CliPermissions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['iam:PassRole'], - resources: [cfnExecRoleArn], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'CfnPermissions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'cloudformation:DescribeStackEvents', - 'cloudformation:GetTemplate', - 'cloudformation:DeleteStack', - 'cloudformation:UpdateTerminationProtection', - 'sts:GetCallerIdentity', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'CliStagingBucket', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], - resources: [ - `arn:${this.partition}:s3:::${this.assetBucketName}`, - `arn:${this.partition}:s3:::${this.assetBucketName}/*`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'ReadVersion', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter'], - resources: [ - `arn:${this.partition}:ssm:${this.region}:${this.account}:parameter${bootstrapParameterName}`, - ], - }), - ], - }), - }, - }); - - if (trustedAccounts && trustedAccounts.length > 0) { - deploymentActionRole.attachInlinePolicy( - new cdk.aws_iam.Policy(this, 'PipelineCrossAccountArtifactsKey', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'PipelineCrossAccountArtifactsKey', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*'], - resources: cdk.Fn.split( - '|', - cdk.Fn.sub('arn:${AWS::Partition}:kms:*:${JoinedAccounts}:*', { - JoinedAccounts: cdk.Fn.join(':*|arn:${AWS::Partition}:kms:*:', trustedAccounts), - }), - ), - conditions: { - StringEquals: { - 'kms:ViaService': `s3.${this.region}.amazonaws.com`, - }, - }, - }), - ], - }), - ); - } - - // Override logical Id - const cfnDeploymentActionRole = deploymentActionRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnDeploymentActionRole.overrideLogicalId('DeploymentActionRole'); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/DeploymentActionRole/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/PipelineCrossAccountArtifactsKey/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ]); - } - - createExecutionRole() { - const cloudFormationExecutionRole = new cdk.aws_iam.Role(this, 'CloudFormationExecutionRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('cloudformation.amazonaws.com'), - roleName: `cdk-${this.qualifier}-cfn-exec-role-${this.account}-${this.region}`, - }); - cloudFormationExecutionRole.addManagedPolicy( - cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'), - ); - // Override logical Id - const cfnCloudFormationExecutionRole = cloudFormationExecutionRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnCloudFormationExecutionRole.overrideLogicalId('CloudFormationExecutionRole'); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/CloudFormationExecutionRole/Resource`, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - return cloudFormationExecutionRole; - } - - createLookupRole() { - const lookupRole = new cdk.aws_iam.Role(this, 'LookupRole', { - assumedBy: this.setCompositePrincipals({ managementAccount: this.managementAccount }), - roleName: `cdk-${this.qualifier}-lookup-role-${this.account}-${this.region}`, - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], - inlinePolicies: { - LookupRolePolicy: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'DontReadSecrets', - effect: cdk.aws_iam.Effect.DENY, - actions: ['kms:Decrypt'], - resources: ['*'], - }), - ], - }), - }, - }); - - lookupRole.addManagedPolicy(cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); - // Override logical ids - const cfnLookupRole = lookupRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnLookupRole.overrideLogicalId('LookupRole'); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/LookupRole/Resource`, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/LookupRole/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ]); - } - - createImagePublishingRole() { - const imagePublishingRole = new cdk.aws_iam.Role(this, 'ImagePublishingRole', { - assumedBy: this.setCompositePrincipals({ managementAccount: this.managementAccount }), - roleName: `cdk-${this.qualifier}-image-publishing-role-${this.account}-${this.region}`, - }); - - const imagePublishingRoleDefaultPolicy = new cdk.aws_iam.Policy(this, 'ImagePublishingRoleDefaultPolicy', { - policyName: `cdk-${this.qualifier}-image-publishing-role-default-policy-${this.account}-${this.region}`, - roles: [imagePublishingRole], - document: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'ecr:PutImage', - 'ecr:InitiateLayerUpload', - 'ecr:UploadLayerPart', - 'ecr:CompleteLayerUpload', - 'ecr:BatchCheckLayerAvailability', - 'ecr:DescribeRepositories', - 'ecr:DescribeImages', - 'ecr:BatchGetImage', - 'ecr:GetDownloadUrlForLayer', - ], - resources: [`arn:${this.partition}:ecr:${this.region}:${this.account}:repository/${this.getEcrRepoName()}`], - effect: cdk.aws_iam.Effect.ALLOW, - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['ecr:GetAuthorizationToken'], - resources: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - }), - ], - }), - }); - // Override logical ids - const cfnImagePublishingRole = imagePublishingRole.node.defaultChild as cdk.aws_iam.CfnRole; - const cfnImagePublishingRoleDefaultPolicy = imagePublishingRoleDefaultPolicy.node - .defaultChild as cdk.aws_iam.CfnPolicy; - - cfnImagePublishingRole.overrideLogicalId('ImagePublishingRole'); - cfnImagePublishingRoleDefaultPolicy.overrideLogicalId('ImagePublishingRoleDefaultPolicy'); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/ImagePublishingRoleDefaultPolicy/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ]); - } - - createFilePublishingRole() { - const filePublishingRole = new cdk.aws_iam.Role(this, 'FilePublishingRole', { - assumedBy: this.setCompositePrincipals({ managementAccount: this.managementAccount }), - roleName: `cdk-${this.qualifier}-file-publishing-role-${this.account}-${this.region}`, - }); - - const filePublishingRoleDefaultPolicy = new cdk.aws_iam.Policy(this, 'FilePublishingRoleDefaultPolicy', { - policyName: `cdk-${this.qualifier}-file-publishing-role-default-policy-${this.account}-${this.region}`, - roles: [filePublishingRole], - document: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:GetEncryptionConfiguration', - 's3:List*', - 's3:DeleteObject*', - 's3:PutObject*', - 's3:Abort*', - ], - resources: [ - `arn:${this.partition}:s3:::${this.assetBucketName}`, - `arn:${this.partition}:s3:::${this.assetBucketName}/*`, - ], - effect: cdk.aws_iam.Effect.ALLOW, - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*'], - resources: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - }), - ], - }), - }); - // Override logical ids - const cfnFilePublishingRole = filePublishingRole.node.defaultChild as cdk.aws_iam.CfnRole; - const cfnFilePublishingRoleDefaultPolicy = filePublishingRoleDefaultPolicy.node - .defaultChild as cdk.aws_iam.CfnPolicy; - - cfnFilePublishingRole.overrideLogicalId('FilePublishingRole'); - cfnFilePublishingRoleDefaultPolicy.overrideLogicalId('FilePublishingRoleDefaultPolicy'); - - // AwsSolutions-IAM5: Reason the IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/FilePublishingRoleDefaultPolicy/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ]); - } - - createEcrRepository() { - const containerAssetRepo = new cdk.aws_ecr.Repository(this, 'ContainerAssetRepo', { - imageTagMutability: cdk.aws_ecr.TagMutability.IMMUTABLE, - removalPolicy: cdk.RemovalPolicy.RETAIN, - repositoryName: this.getEcrRepoName(), - }); - containerAssetRepo.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'LambdaECRImageRetrievalPolicy-insecure-connections', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'], - principals: [new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com')], - conditions: { - StringLike: { - 'aws:sourceArn': `arn:${this.partition}:lambda:${this.region}:${this.account}:function:*`, - }, - }, - }), - ); - - //Override logical Id - const cfnContainerAssetRepo = containerAssetRepo.node.defaultChild as cdk.aws_ecr.CfnRepository; - cfnContainerAssetRepo.overrideLogicalId('ContainerAssetsRepository'); - } - - createBucketCmk(props: { managementAccountId: string; customDeploymentRole?: string }) { - const conditions = this.setBootstrapResourceConditions( - this.props.organizationConfig.enable, - props.customDeploymentRole, - ); - const principals = this.setBootstrapResourcePrincipals(this.props.organizationConfig.enable); - const s3Key = new cdk.aws_kms.Key(this, 'AssetEncryptionKey', { - alias: `${this.props.prefixes.kmsAlias}/kms/cdk/key`, - description: 'Key used to encrypt centralized CDK assets', - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // Allow management account access - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Management Actions', - principals: [new cdk.aws_iam.AccountPrincipal(props.managementAccountId)], - actions: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - 'kms:TagResource', - 'kms:UntagResource', - ], - resources: ['*'], - }), - ); - - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow S3 to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:ViaService': `s3.${cdk.Stack.of(this).region}.amazonaws.com`, - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow org to perform encryption', - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions, - principals, - }), - ); - return s3Key; - } - - createAssetBucket(props: { kmsKey: cdk.aws_kms.Key; customDeploymentRole?: string }) { - const lifecycleRules: cdk.aws_s3.LifecycleRule[] = [ - { - id: 'CleanupOldVersions', - enabled: true, - noncurrentVersionExpiration: cdk.Duration.days(365), - }, - ]; - - const assetBucket = new cdk.aws_s3.Bucket(this, 'StagingBucket', { - accessControl: cdk.aws_s3.BucketAccessControl.PRIVATE, - blockPublicAccess: cdk.aws_s3.BlockPublicAccess.BLOCK_ALL, - bucketName: this.assetBucketName, - encryption: cdk.aws_s3.BucketEncryption.KMS, - encryptionKey: props.kmsKey, - lifecycleRules: lifecycleRules, - objectOwnership: cdk.aws_s3.ObjectOwnership.BUCKET_OWNER_PREFERRED, - removalPolicy: cdk.RemovalPolicy.RETAIN, - versioned: true, - }); - const principals = this.setBootstrapResourcePrincipals(this.props.organizationConfig.enable); - const conditions = this.setBootstrapResourceConditions( - this.props.organizationConfig.enable, - props.customDeploymentRole, - ); - - assetBucket.grantReadWrite(new cdk.aws_iam.ServicePrincipal('cloudformation.amazonaws.com')); - assetBucket.grantReadWrite(new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com')); - - assetBucket.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'cdk-read-write-access', - effect: cdk.aws_iam.Effect.ALLOW, - resources: [assetBucket.arnForObjects('*'), assetBucket.bucketArn], - actions: ['s3:*'], - principals, - conditions, - }), - ); - - assetBucket.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'deny-insecure-connections', - effect: cdk.aws_iam.Effect.DENY, - actions: ['s3:*'], - resources: [assetBucket.arnForObjects('*')], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - Bool: { - 'aws:SecureTransport': 'false', - }, - }, - }), - ); - - //Override logical Id - const cfnAssetBucket = assetBucket.node.defaultChild as cdk.aws_s3.CfnBucket; - cfnAssetBucket.overrideLogicalId('StagingBucket'); - - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/StagingBucket/Resource`, [ - { - id: 'AwsSolutions-S1', - reason: 'StagingBucket has server access logs disabled until the task for access logging completed.', - }, - ]); - - // AwsSolutions-S10: The S3 Bucket does not require requests to use SSL. - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/StagingBucket/Resource`, [ - { - id: 'AwsSolutions-S10', - reason: 'StagingBucket denies insecure requests via the bucket policy.', - }, - ]); - - return assetBucket; - } - - getAssetBucketDomainName() { - return `cdk-${this.qualifier}-assets-${this.managementAccount}-${this.region}.s3.${this.region}.amazonaws.com`; - } - getAssetBucketName() { - return `cdk-${this.qualifier}-assets-${this.managementAccount}-${this.region}`; - } - getWorkloadBucketName(accountId: string) { - return `cdk-${this.qualifier}-assets-${accountId}-${this.region}`; - } - getEcrRepoName() { - return `cdk-${this.qualifier}-container-assets-${this.account}-${this.region}`; - } - - setBootstrapResourceConditions(isOrgsEnabled: boolean, customDeploymentRole?: string) { - const roleArns = [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/cdk-${this.qualifier}*`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.globalConfig.managementAccountAccessRole}`, - ]; - if (customDeploymentRole) { - roleArns.push(`arn:${cdk.Stack.of(this).partition}:iam::*:role/${customDeploymentRole}`); - } - const conditions: { [key: string]: unknown } = { - ArnLike: { - 'aws:PrincipalARN': roleArns, - }, - }; - if (isOrgsEnabled) { - conditions['StringEquals'] = { - 'aws:PrincipalOrgID': this.organizationId, - }; - } - return conditions; - } - setBootstrapResourcePrincipals(isOrgsEnabled: boolean) { - let principals = [new cdk.aws_iam.AnyPrincipal()]; - if (!isOrgsEnabled) { - if (!this.props.accountsConfig.accountIds) { - this.logger.error(`Could not load account ids.`); - throw new Error(`Configuration validation failed at runtime.`); - } - principals = this.props.accountsConfig.accountIds?.map( - accountId => new cdk.aws_iam.AccountPrincipal(accountId.accountId), - ); - } - return principals; - } - setCompositePrincipals(props: { managementAccount: string; cfnServicePrincipal?: boolean }) { - const principals: IPrincipal[] = [new cdk.aws_iam.AccountPrincipal(props.managementAccount)]; - if (cdk.Stack.of(this).account !== props.managementAccount) { - principals.push( - new cdk.aws_iam.ArnPrincipal( - `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.props.globalConfig.managementAccountAccessRole - }`, - ), - ); - } - - if (props.cfnServicePrincipal) { - principals.push(new cdk.aws_iam.ServicePrincipal('cloudformation.amazonaws.com')); - } - - return new cdk.aws_iam.CompositePrincipal(...principals); - } - - setAssumeSelfPermissions(role: cdk.aws_iam.Role) { - role.assumeRolePolicy?.addStatements( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.AccountRootPrincipal()], - actions: ['sts:AssumeRole'], - conditions: { - StringEquals: { - 'AWS:PrincipalArn': `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.props.globalConfig.cdkOptions?.customDeploymentRole - }`, - }, - }, - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/custom-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/custom-stack.ts deleted file mode 100644 index db48ef7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/custom-stack.ts +++ /dev/null @@ -1,281 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -import { - AccountsConfig, - CloudFormationStackConfig, - CustomizationsConfig, - DeploymentTargets, - GlobalConfig, - IamConfig, - NetworkConfig, - OrganizationConfig, - Region, - SecurityConfig, - CfnParameter, -} from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { version } from '../../../../../package.json'; - -const logger = createLogger(['custom-stack']); - -export interface CustomStackProps extends cdk.StackProps { - readonly configDirPath: string; - readonly accountsConfig: AccountsConfig; - readonly globalConfig: GlobalConfig; - readonly iamConfig: IamConfig; - readonly networkConfig: NetworkConfig; - readonly organizationConfig: OrganizationConfig; - readonly securityConfig: SecurityConfig; - readonly customizationsConfig: CustomizationsConfig; - readonly partition: string; - readonly qualifier?: string; - readonly configCommitId?: string; - readonly globalRegion?: string; - readonly centralizedLoggingRegion: string; - readonly runOrder: number; - readonly stackName: string; - readonly templateFile: string; - readonly terminationProtection: boolean; - readonly parameters?: CfnParameter[]; - readonly ssmParamNamePrefix: string; -} -export class CustomStack extends cdk.Stack { - protected props: CustomStackProps; - public runOrder: number; - constructor(scope: Construct, id: string, props: CustomStackProps) { - super(scope, id, props); - this.props = props; - this.runOrder = props.runOrder; - - new cdk.cloudformation_include.CfnInclude(this, props.stackName, { - templateFile: path.join(props.configDirPath, props.templateFile), - parameters: transformCfnParametersArrayToObject(props.parameters), - }); - - new cdk.aws_ssm.StringParameter(this, 'SsmParamStackId', { - parameterName: `${props.ssmParamNamePrefix}/${cdk.Stack.of(this).stackName}/stack-id`, - stringValue: cdk.Stack.of(this).stackId, - }); - - new cdk.aws_ssm.StringParameter(this, 'SsmParamAcceleratorVersion', { - parameterName: `${props.ssmParamNamePrefix}/${cdk.Stack.of(this).stackName}/version`, - stringValue: version, - }); - } -} - -export function isIncluded( - deploymentTargets: DeploymentTargets, - region: string, - accountId: string, - accountsConfig: AccountsConfig, - organizationConfig: OrganizationConfig, -): boolean { - // Explicit Denies - if ( - isRegionExcluded(deploymentTargets.excludedRegions, region) || - isAccountExcluded(deploymentTargets.excludedAccounts, accountId, accountsConfig) - ) { - return false; - } - - // Explicit Allows - if ( - isAccountIncluded(deploymentTargets.accounts, accountId, accountsConfig, organizationConfig) || - isOrganizationalUnitIncluded(deploymentTargets.organizationalUnits, accountId, accountsConfig, organizationConfig) - ) { - return true; - } - - // Implicit Deny - return false; -} - -export function isRegionExcluded(regions: string[], currentRegion: string): boolean { - if (regions?.includes(currentRegion)) { - logger.info(`${currentRegion} region explicitly excluded`); - return true; - } - return false; -} - -export function isAccountExcluded(accounts: string[], currentAccount: string, accountsConfig: AccountsConfig): boolean { - for (const account of accounts ?? []) { - if (currentAccount === accountsConfig.getAccountId(account)) { - logger.info(`${account} account explicitly excluded`); - return true; - } - } - return false; -} - -export function isAccountIncluded( - accounts: string[], - currentAccount: string, - accountsConfig: AccountsConfig, - organizationConfig: OrganizationConfig, -): boolean { - for (const account of accounts ?? []) { - if (currentAccount === accountsConfig.getAccountId(account)) { - const accountConfig = accountsConfig.getAccount(account); - if (organizationConfig.isIgnored(accountConfig.organizationalUnit)) { - logger.info(`Account ${account} was not included as it is a member of an ignored organizational unit.`); - return false; - } - logger.info(`${account} account explicitly included`); - return true; - } - } - return false; -} - -export function isOrganizationalUnitIncluded( - organizationalUnits: string[], - currentAccount: string, - accountsConfig: AccountsConfig, - organizationConfig: OrganizationConfig, -): boolean { - if (organizationalUnits) { - // Full list of all accounts - const accounts = [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]; - - // Find the account with the matching ID - const account = accounts.find(item => accountsConfig.getAccountId(item.name) === currentAccount); - - if (account) { - if (organizationalUnits.indexOf(account.organizationalUnit) != -1 || organizationalUnits.includes('Root')) { - const ignored = organizationConfig.isIgnored(account.organizationalUnit); - if (ignored) { - logger.info(`${account.organizationalUnit} is ignored and not included`); - } - logger.info(`${account.organizationalUnit} organizational unit included`); - return true; - } - } - } - - return false; -} - -export function mapRegionToString(regionList: Region[]): string[] { - return regionList.map(item => { - return item.toString(); - }); -} - -export type customStackMapping = { - account: string; - dependsOn: string[]; - region: string; - runOrder: number; - stackConfig: CloudFormationStackConfig; - stackObj?: cdk.Stack; -}; - -// -// Identify which custom stacks should be deployed based on current account and region -// -export function generateCustomStackMappings( - accountsConfig: AccountsConfig, - organizationConfig: OrganizationConfig, - customizationsConfig: CustomizationsConfig, - accountId: string, - region: string, -): customStackMapping[] { - const customStackList = customizationsConfig.customizations.cloudFormationStacks; - const mappingList = []; - - for (const stack of customStackList ?? []) { - const deploymentRegions = mapRegionToString(stack.regions); - - if ( - isIncluded(stack.deploymentTargets ?? [], region, accountId, accountsConfig, organizationConfig) && - deploymentRegions.includes(region) - ) { - logger.debug(`New stack ${stack.name} mapped to account ${accountId} in region ${region}`); - - mappingList.push({ - account: accountId, - dependsOn: [], - region: region, - runOrder: stack.runOrder, - stackConfig: stack, - }); - } - } - return getStackDependencies(mappingList); -} - -// -// Sort stack mappings by runOrder property -// -export function sortStackMappings(stackMappingList: customStackMapping[]) { - if (stackMappingList.length == 0) { - return []; - } - stackMappingList.sort((a: customStackMapping, b: customStackMapping) => a.runOrder - b.runOrder); - return stackMappingList; -} - -// -// Determine if each stack should depend on other stacks based on runOrder -// This function supports discontinuous, non-unique arrays of runOrder values (ex. [1, 3, 3, 4]) -// -export function getStackDependencies(stackMappings: customStackMapping[]): customStackMapping[] { - // sort all mappings by runOrder - const sortedMappings = sortStackMappings(stackMappings); - // extract all runOrder values, including duplicates - const runOrderList = sortedMappings.map(a => a.runOrder); - // extract unique runOrder values - const runOrderValues = [...new Set(runOrderList)]; - - // if there is more than one stack, set dependencies - if (runOrderValues.length > 1) { - for (const mapping of sortedMappings) { - // Get the index of the runOrder of the current stack - const currentRunOrderIndex = runOrderValues.indexOf(mapping.runOrder); - // If the stack has the lowest runOrder value, it will not depend on any other stacks - if (currentRunOrderIndex !== 0) { - // Get the index of the unique runOrder value immediately before the current stack - const dependencyIndex = currentRunOrderIndex - 1; - // Get the corresponding runOrder value - const dependencyValue = runOrderValues[dependencyIndex]; - // Find all stacks with a matching runOrder value and add them as dependencies. - const stackDependencies = sortedMappings.filter(e => e.runOrder == dependencyValue) ?? []; - for (const stack of stackDependencies) { - mapping.dependsOn.push(stack.stackConfig.name); - } - } - } - } - return sortedMappings; -} - -function transformCfnParametersArrayToObject( - parameters?: CfnParameter[], -): { [parameterName: string]: string } | undefined { - if (parameters) { - const parameterObject: { [key: string]: string } = {}; - for (const parameter of parameters) { - parameterObject[parameter.name] = parameter.value; - } - return parameterObject; - } - return undefined; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/customizations-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/customizations-stack.ts deleted file mode 100644 index ccc6613..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/customizations-stack.ts +++ /dev/null @@ -1,602 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { AcceleratorKeyType, AcceleratorStack, AcceleratorStackProps } from './accelerator-stack'; -import { - PortfolioAssociationConfig, - PortfolioConfig, - ProductConfig, - ProductConstraintConfig, -} from '@aws-accelerator/config'; -import { - IdentityCenterGetPermissionRoleArn, - IdentityCenterGetPermissionRoleArnProvider, - SharePortfolioWithOrg, - PropagatePortfolioAssociations, -} from '@aws-accelerator/constructs'; - -export class CustomizationsStack extends AcceleratorStack { - /** - * StackSet Administrator Account Id - */ - private stackSetAdministratorAccount: string; - - /** - * KMS Key used to encrypt CloudWatch logs, when undefined default AWS managed key will be used - */ - private cloudwatchKey: cdk.aws_kms.IKey | undefined; - - /** - * Constructor for CustomizationsStack - * - * @param scope - * @param id - * @param props - */ - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - this.props = props; - this.stackSetAdministratorAccount = props.accountsConfig.getManagementAccountId(); - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - - // Create CloudFormation StackSets - if (props.customizationsConfig?.customizations?.cloudFormationStackSets) { - this.deployCustomStackSets(); - } - - // Create Service Catalog Portfolios - if (props.customizationsConfig?.customizations?.serviceCatalogPortfolios?.length > 0) { - const serviceToken = this.getPortfolioAssociationsRoleArnProviderServiceToken(); - this.createServiceCatalogResources(serviceToken); - } - - this.logger.info('Completed stack synthesis'); - } - - private getAssetUrl(stacksetName: string, localPath: string): string { - const asset = new cdk.aws_s3_assets.Asset(this, pascalCase(`${stacksetName}Asset`), { - path: path.join(this.props.configDirPath, localPath), - }); - return asset.httpUrl; - } - - // - // Create custom CloudFormation StackSets - // - private deployCustomStackSets() { - this.logger.info(`[customizations-stack] Deploying CloudFormation StackSets`); - if ( - this.account === this.stackSetAdministratorAccount && - this.props.globalConfig.homeRegion === cdk.Stack.of(this).region && - this.props.customizationsConfig?.customizations?.cloudFormationStackSets - ) { - const customStackSetList = this.props.customizationsConfig.customizations.cloudFormationStackSets; - for (const stackSet of customStackSetList ?? []) { - this.logger.info(`New stack set ${stackSet.name}`); - const deploymentTargetAccounts: string[] | undefined = this.getAccountIdsFromDeploymentTargets( - stackSet.deploymentTargets, - ); - const templateUrl = this.getAssetUrl(stackSet.name, stackSet.template); - - const parameters = stackSet.parameters?.map(parameter => { - return { parameterKey: parameter.name, parameterValue: parameter.value }; - }); - - new cdk.aws_cloudformation.CfnStackSet( - this, - pascalCase(`${this.props.prefixes.accelerator}-Custom-${stackSet.name}`), - { - permissionModel: 'SELF_MANAGED', - stackSetName: stackSet.name, - capabilities: stackSet.capabilities, - description: stackSet.description, - operationPreferences: { - failureTolerancePercentage: 25, - maxConcurrentPercentage: 35, - regionConcurrencyType: 'PARALLEL', - }, - stackInstancesGroup: [ - { - deploymentTargets: { - accounts: deploymentTargetAccounts, - }, - regions: stackSet.regions, - }, - ], - templateUrl: templateUrl, - parameters, - }, - ); - } - } - } - - /** - * Create Service Catalog resources - */ - private createServiceCatalogResources(serviceToken: string) { - const serviceCatalogPortfolios = this.props.customizationsConfig?.customizations?.serviceCatalogPortfolios; - for (const portfolioItem of serviceCatalogPortfolios ?? []) { - const regions = portfolioItem.regions.map(item => { - return item.toString(); - }); - const accountId = this.props.accountsConfig.getAccountId(portfolioItem.account); - if (accountId === cdk.Stack.of(this).account && regions.includes(cdk.Stack.of(this).region)) { - // Create portfolios - const portfolio = this.createPortfolios(portfolioItem); - - // Create portfolio shares - this.createPortfolioShares(portfolio, portfolioItem); - - // Create products for the portfolio - this.createPortfolioProducts(portfolio, portfolioItem); - - // Create portfolio associations - this.createPortfolioAssociations(portfolio, portfolioItem, serviceToken); - } - } - } - - /** - * Create Service Catalog portfolios - * @param portfolio - * @param portfolioItem - */ - private createPortfolios(portfolioItem: PortfolioConfig): cdk.aws_servicecatalog.Portfolio { - this.logger.info(`Creating Service Catalog portfolio ${portfolioItem.name}`); - - // Create portfolio TagOptions - let tagOptions: cdk.aws_servicecatalog.TagOptions | undefined = undefined; - if (portfolioItem.tagOptions) { - const tagOptionsTags: { [key: string]: string[] } = {}; - portfolioItem.tagOptions.forEach(tag => (tagOptionsTags[tag.key] = tag.values)); - tagOptions = new cdk.aws_servicecatalog.TagOptions(this, pascalCase(`${portfolioItem.name}TagOptions`), { - allowedValuesForTags: tagOptionsTags, - }); - } - - // Create portfolio - const portfolio = new cdk.aws_servicecatalog.Portfolio(this, pascalCase(`${portfolioItem.name}Portfolio`), { - displayName: portfolioItem.name, - providerName: portfolioItem.provider, - tagOptions, - }); - - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${portfolioItem.name}PortfolioId`), - parameterName: `${this.props.prefixes.ssmParamName}/servicecatalog/portfolios/${portfolioItem.name}/id`, - stringValue: portfolio.portfolioId, - }); - return portfolio; - } - - /** - * Function to share portfolio with organization - * @param portfolio {@link cdk.aws_servicecatalog.Portfolio} - * @param portfolioItem {@link PortfolioConfig} - * - * @remarks - * Share portfolio with organizational units via Custom Resource - */ - private sharePortfolioWithOrg(portfolio: cdk.aws_servicecatalog.Portfolio, portfolioItem: PortfolioConfig) { - const organizationalUnitIds: string[] = []; - let shareToEntireOrg = false; - for (const ou of portfolioItem?.shareTargets?.organizationalUnits ?? []) { - if (ou === 'Root') { - shareToEntireOrg = true; - } else { - organizationalUnitIds.push(this.props.organizationConfig.getOrganizationalUnitId(ou)); - } - } - if (organizationalUnitIds.length > 0 || shareToEntireOrg) { - const portfolioOrgShare = new SharePortfolioWithOrg(this, `${portfolioItem.name}-Share`, { - portfolioId: portfolio.portfolioId, - organizationalUnitIds: organizationalUnitIds, - tagShareOptions: portfolioItem.shareTagOptions ?? false, - organizationId: shareToEntireOrg && this.organizationId ? this.organizationId : '', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - portfolioOrgShare.node.addDependency(portfolio); - } - } - - /** - * Create account and OU-level Service Catalog portfolio shares - * @param portfolio - * @param portfolioItem - */ - private createPortfolioShares(portfolio: cdk.aws_servicecatalog.Portfolio, portfolioItem: PortfolioConfig): void { - // Create account shares - if (portfolioItem.shareTargets) { - // share portfolio with accounts via native CDK - for (const account of portfolioItem?.shareTargets?.accounts ?? []) { - const accountId = this.props.accountsConfig.getAccountId(account); - if (accountId !== cdk.Stack.of(this).account) { - portfolio.shareWithAccount(accountId, { - shareTagOptions: portfolioItem.shareTagOptions ?? false, - }); - } - } - - // share portfolio with organizational units via Custom Resource - const managementAccountId = this.props.accountsConfig.getManagementAccountId(); - if (cdk.Stack.of(this).account === managementAccountId) { - this.sharePortfolioWithOrg(portfolio, portfolioItem); - } - } - } - - /** - * Create Service Catalog products - * @param portfolio - * @param portfolioItem - */ - private createPortfolioProducts(portfolio: cdk.aws_servicecatalog.Portfolio, portfolioItem: PortfolioConfig): void { - // Get the Product Version list - for (const productItem of portfolioItem.products ?? []) { - const productVersions = this.getPortfolioProductVersions(productItem); - - // Create product TagOptions - const tagOptions = this.getPortfolioProductTagOptions(productItem); - - //Create a Service Catalog Cloudformation Product. - this.logger.info(`Creating product ${productItem.name} in Service Catalog portfolio ${portfolioItem.name}`); - const product = new cdk.aws_servicecatalog.CloudFormationProduct( - this, - pascalCase(`${portfolioItem.name}Portfolio${productItem.name}Product`), - { - productName: productItem.name, - owner: productItem.owner, - distributor: productItem.distributor, - productVersions, - description: productItem.description, - supportDescription: productItem.support?.description, - supportEmail: productItem.support?.email, - supportUrl: productItem.support?.url, - tagOptions, - }, - ); - - //Associate Portfolio with the Product. - portfolio.addProduct(product); - - //Add Constraints to Product. - - if (productItem.constraints) { - this.createProductConstraints(portfolio, product, portfolioItem, productItem); - } - } - } - - /** - * Create Service Catalog Product Constraints - * @param portfolio - * @param product - * @param portfolioItem - * @param productItem - */ - private createProductConstraints( - portfolio: cdk.aws_servicecatalog.Portfolio, - product: cdk.aws_servicecatalog.CloudFormationProduct, - portfolioItem: PortfolioConfig, - productItem: ProductConfig, - ) { - const constraints: ProductConstraintConfig = productItem.constraints!; - - if (constraints.launch) { - this.getProductLaunchConstraint(portfolio, product, portfolioItem.name, productItem.name, constraints.launch); - } - - if (constraints.tagUpdate !== undefined) { - this.getProductTagUpdateConstraint(portfolio, product, constraints.tagUpdate); - } - - if (constraints.notifications) { - this.getProductNotificationConstraint( - portfolio, - product, - portfolioItem.name, - productItem.name, - constraints.notifications, - ); - } - } - - /** - * Create Product Notification Service Catalog constraint - * @param portfolio - * @param product - * @param portfolioName - * @param productName - * @param notifications - */ - private getProductNotificationConstraint( - portfolio: cdk.aws_servicecatalog.Portfolio, - product: cdk.aws_servicecatalog.CloudFormationProduct, - portfolioName: string, - productName: string, - notifications: string[], - ) { - const account = cdk.Stack.of(this).account; - const partition = cdk.Stack.of(this).partition; - const topicArns = []; - - for (const topic of notifications) { - const topicArn = `arn:${partition}:sns:${this.region}:${account}:${topic}`; - - topicArns.push(topicArn); - } - - this.logger.info(`Adding topics ${topicArns}`); - - new cdk.aws_servicecatalog.CfnLaunchNotificationConstraint( - this, - `${portfolioName}${productName}NotificationConstraint`, - { - notificationArns: topicArns, - portfolioId: portfolio.portfolioId, - productId: product.productId, - description: `Notify topics for ${productName}`, - }, - ); - } - - /** - * Create Service Catalog Product TagUpdate Constraint - * tagUpdate can be either true or false to disable. - * @param portfolio - * @param product - * @param tagUpdate - * - */ - private getProductTagUpdateConstraint( - portfolio: cdk.aws_servicecatalog.Portfolio, - product: cdk.aws_servicecatalog.CloudFormationProduct, - tagUpdate: boolean, - ) { - this.logger.info(`Creating TagUpdate constraint with allow: ${tagUpdate} on product ${product.productId}`); - - portfolio.constrainTagUpdates(product, { - allow: tagUpdate, - }); - } - - /** - * Create Service Catalog Product Launch Constraint - * - * Note: if you use a 'Role' type this may affect shared portfolios and prevent them from - * working as expected as the role will not exist in shared account. - * - * @param portfolio - * @param product - * @param portfolioName - * @param productName - * @param launchConstraint - */ - private getProductLaunchConstraint( - portfolio: cdk.aws_servicecatalog.Portfolio, - product: cdk.aws_servicecatalog.CloudFormationProduct, - portfolioName: string, - productName: string, - launchConstraint: { type: 'Role' | 'LocalRole'; role: string }, - ) { - const account = cdk.Stack.of(this).account; - const partition = cdk.Stack.of(this).partition; - - this.logger.info(`Creating launch constraint on product ${product.productId}`); - - if (launchConstraint.type === 'Role') { - //specific role only - - const launchRoleArn = `arn:${partition}:iam::${account}:role/${launchConstraint.role}`; - new cdk.aws_servicecatalog.CfnLaunchRoleConstraint( - this, - pascalCase(`${portfolioName}Portfolio${productName}ProductLaunchConstraint`), - { - portfolioId: portfolio.portfolioId, - productId: product.productId, - roleArn: launchRoleArn, - }, - ); - } - - if (launchConstraint.type === 'LocalRole') { - new cdk.aws_servicecatalog.CfnLaunchRoleConstraint( - this, - pascalCase(`${portfolioName}Portfolio${productName}ProductLaunchConstraint`), - { - portfolioId: portfolio.portfolioId, - productId: product.productId, - localRoleName: launchConstraint.role, - }, - ); - } - } - - /** - * Get list of Service Catalog portfolio product versions - * @param portfolio - * @param portfolioItem - */ - private getPortfolioProductVersions( - productItem: ProductConfig, - ): cdk.aws_servicecatalog.CloudFormationProductVersion[] { - const productVersions: cdk.aws_servicecatalog.CloudFormationProductVersion[] = []; - for (const productVersionItem of productItem.versions ?? []) { - productVersions.push({ - productVersionName: productVersionItem.name, - description: productVersionItem.description, - cloudFormationTemplate: cdk.aws_servicecatalog.CloudFormationTemplate.fromAsset( - path.join(this.props.configDirPath, productVersionItem.template), - ), - validateTemplate: true, - }); - } - return productVersions; - } - - /** - * Get Service Catalog tag options - * @param portfolio - * @param portfolioItem - */ - private getPortfolioProductTagOptions(productItem: ProductConfig): cdk.aws_servicecatalog.TagOptions | undefined { - let tagOptions: cdk.aws_servicecatalog.TagOptions | undefined = undefined; - if (productItem.tagOptions) { - const tagOptionsTags: { [key: string]: string[] } = {}; - productItem.tagOptions.forEach(tag => (tagOptionsTags[tag.key] = tag.values)); - tagOptions = new cdk.aws_servicecatalog.TagOptions(this, pascalCase(`${productItem.name}TagOptions`), { - allowedValuesForTags: tagOptionsTags, - }); - } - return tagOptions; - } - - /** - * Get service token of the IdentityCenterGetPermissionRoleArnProvider - */ - private getPortfolioAssociationsRoleArnProviderServiceToken(): string { - const provider = new IdentityCenterGetPermissionRoleArnProvider( - this, - 'Custom::PortfolioAssociationsRoleArnProvider', - ); - return provider.serviceToken; - } - - /** - * Create portfolio principal associations - * @param portfolio - * @param portfolioItem - * @param serviceToken - */ - private createPortfolioAssociations( - portfolio: cdk.aws_servicecatalog.Portfolio, - portfolioItem: PortfolioConfig, - serviceToken: string, - ): void { - // Add portfolio Associations - let propagateAssociationsFlag = false; - for (const portfolioAssociation of portfolioItem.portfolioAssociations ?? []) { - const principalType = 'IAM'; - - const principalArn = this.getPrincipalArnForAssociation(portfolioAssociation, portfolioItem.name, serviceToken); - new cdk.aws_servicecatalog.CfnPortfolioPrincipalAssociation( - this, - `${portfolioItem.name}-${portfolioAssociation.name}-${portfolioAssociation.type}`, - { - portfolioId: portfolio.portfolioId, - principalArn: principalArn, - principalType: principalType, - }, - ); - - if (portfolioAssociation.propagateAssociation) { - propagateAssociationsFlag = true; - } - } - - if (propagateAssociationsFlag) { - this.propagatePortfolioAssociations(portfolio, portfolioItem); - } - } - - /** - * Get the IAM resource ARN to associate with a portfolio - * @param portfolioAssociation - * @param portfolioName - * @param serviceToken - */ - private getPrincipalArnForAssociation( - portfolioAssociation: PortfolioAssociationConfig, - portfolioName: string, - serviceToken: string, - ): string { - const associationType = portfolioAssociation.type.toLowerCase(); - const account = cdk.Stack.of(this).account; - const partition = cdk.Stack.of(this).partition; - let principalArn = ''; - - if (associationType === 'permissionset') { - principalArn = this.getPermissionSetRoleArn(portfolioName, portfolioAssociation.name, account, serviceToken); - if (principalArn === '') { - throw new Error( - `Role ARN for SSO Permission Set ${portfolioAssociation.name} not found for Service Catalog portfolio ${portfolioName}`, - ); - } - } else { - principalArn = `arn:${partition}:iam::${account}:${associationType}/${portfolioAssociation.name}`; - } - return principalArn; - } - - /** - * Retrieve the ARN of an IAM Role associated with an SSO Permission Set - * @param permissionSetName - * @param accountId - * @param serviceToken - */ - private getPermissionSetRoleArn( - portfolioName: string, - permissionSetName: string, - accountId: string, - serviceToken: string, - ): string { - this.logger.info( - `Looking up IAM Role ARN associated with AWS Identity Center Permission Set ${permissionSetName} in account ${accountId}`, - ); - const permissionSetRoleArn = new IdentityCenterGetPermissionRoleArn( - this, - pascalCase(`${portfolioName}-${permissionSetName}-${accountId}`), - { - permissionSetName: permissionSetName, - accountId: accountId, - serviceToken: serviceToken, - }, - ); - return permissionSetRoleArn.roleArn; - } - - /** - * Propagate the IAM Principal Associations to other AWS accounts the portfolio is shared with - * @param portfolio - * @param portfolioItem - */ - private propagatePortfolioAssociations( - portfolio: cdk.aws_servicecatalog.Portfolio, - portfolioItem: PortfolioConfig, - ): void { - // propagate portfolio Associations - if (!portfolioItem.shareTargets) { - this.logger.warn( - `Cannot propagate principal associations for portfolio ${portfolioItem.name} because portfolio has no shareTargets`, - ); - return; - } - - this.logger.info(`Propagating portfolio associations for portfolio ${portfolioItem.name}`); - const propagateAssociations = new PropagatePortfolioAssociations(this, `${portfolioItem.name}-Propagation`, { - shareAccountIds: this.getAccountIdsFromShareTarget(portfolioItem.shareTargets), - crossAccountRole: this.acceleratorResourceNames.roles.crossAccountServiceCatalogPropagation, - portfolioId: portfolio.portfolioId, - portfolioDefinition: portfolioItem, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - propagateAssociations.node.addDependency(portfolio); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/dependencies-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/dependencies-stack.ts deleted file mode 100644 index 7089b4d..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/dependencies-stack.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import { AcceleratorStack, AcceleratorStackProps } from '../accelerator-stack'; -import { IdentityCenter } from './identity-center'; -import { DiagnosticsPack } from './diagnostics-pack'; - -/** - * Enum for log level - */ -export enum LogLevel { - INFO = 'info', - WARN = 'warn', - ERROR = 'error', -} - -export class DependenciesStack extends AcceleratorStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - // Create put SSM parameter role - if (cdk.Stack.of(this).region === props.globalConfig.homeRegion) { - this.logger.info('Creating cross-account/cross-region put SSM parameter role in home region'); - this.createPutSsmParameterRole(props.prefixes.ssmParamName, props.partition, this.organizationId); - } - - // - // Create Identity Center dependent resources - // - new IdentityCenter(this, props); - - // - // Create the diagnostics pack dependent resources. The Diagnostics pack will be deployed for multi-account environments without utilizing existing roles for deployment. - // - if (!props.enableSingleAccountMode && !props.useExistingRoles) { - new DiagnosticsPack(this, props); - } - } - - /** - * Public accessor method to add logs to logger - * @param logLevel - * @param message - */ - public addLogs(logLevel: LogLevel, message: string) { - switch (logLevel) { - case 'info': - this.logger.info(message); - break; - - case 'warn': - this.logger.warn(message); - break; - - case 'error': - this.logger.error(message); - break; - } - } - - /** - * Create a role that can be assumed to put and read cross-account/cross-region SSM parameters - * @param ssmPrefix - * @param partition - * @param organizationId - * @returns - */ - private createPutSsmParameterRole(ssmPrefix: string, partition: string, organizationId?: string): cdk.aws_iam.Role { - const role = new cdk.aws_iam.Role(this, 'PutSsmParameterRole', { - assumedBy: this.getOrgPrincipals(organizationId, true), - roleName: this.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:PutParameter', 'ssm:DeleteParameter', 'ssm:GetParameter'], - resources: [`arn:${partition}:ssm:*:*:parameter${ssmPrefix}*`], - }), - ], - }), - }, - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressions(role, [ - { - id: 'AwsSolutions-IAM5', - reason: 'This role is required to give permissions to put/delete SSM parameters across accounts and regions', - }, - ]); - return role; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/diagnostics-pack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/diagnostics-pack.ts deleted file mode 100644 index 71886b7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/diagnostics-pack.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { DependenciesStack } from './dependencies-stack'; -import { AcceleratorStackProps } from '../accelerator-stack'; - -/** - * There is a class for diagnostics pack dependent resources. - * - * @remarks - * The Diagnostics Pack will be deployed for multi-account environments without utilizing existing roles for deployment. - */ -export class DiagnosticsPack { - private stack: DependenciesStack; - constructor(dependenciesStack: DependenciesStack, props: AcceleratorStackProps) { - this.stack = dependenciesStack; - - // - // Diagnostics pack assume role creation - // - this.createDiagnosticsPackAssumeRole(props); - } - - /** - * Function to deploy diagnostics pack assume role - * @param props - * @returns - */ - private createDiagnosticsPackAssumeRole(props: AcceleratorStackProps) { - const isDiagnosticsPackEnabled = props.isDiagnosticsPackEnabled === 'Yes' ? true : false; - - const managementAccountId = props.accountsConfig.getManagementAccountId(); - - // For non external deployment diagnostics resources are deployed in management account by installer pipeline, no need to create the role in this case - if (this.stack.account === managementAccountId && !this.stack.isExternalDeployment) { - return; - } - - const assumeByAccountId = - props.pipelineAccountId !== managementAccountId ? props.pipelineAccountId : managementAccountId; - - // Create diagnostic role in every account home region except management account for non external deployment - if (isDiagnosticsPackEnabled && this.stack.region === props.globalConfig.homeRegion) { - let diagnosticsPackLambdaRoleNamePrefix = props.prefixes.accelerator; - if (props.qualifier && props.qualifier !== 'aws-accelerator') { - diagnosticsPackLambdaRoleNamePrefix = props.qualifier; - } - - const role = new cdk.aws_iam.Role(this.stack, 'DiagnosticsPackAssumeRole', { - roleName: this.stack.acceleratorResourceNames.roles.diagnosticsPackAssumeRoleName, - assumedBy: new cdk.aws_iam.ArnPrincipal( - `arn:${ - cdk.Stack.of(this.stack).partition - }:iam::${assumeByAccountId}:role/${diagnosticsPackLambdaRoleNamePrefix}-DiagnosticsPackLambdaRole`, - ), - }); - - role.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudformationAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['cloudformation:DescribeStackEvents', 'cloudformation:DescribeStacks'], - resources: [ - `arn:${cdk.Stack.of(this.stack).partition}:cloudformation:${cdk.Stack.of(this.stack).region}:${ - cdk.Stack.of(this.stack).account - }:stack/${props.prefixes.accelerator}*`, - ], - }), - ); - - if (this.stack.account === managementAccountId) { - role.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'OrganizationsAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['organizations:ListAccounts'], - resources: ['*'], - }), - ); - } - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/DiagnosticsPackAssumeRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Diagnostics pack role needs access to every accelerator stacks.', - }, - ], - ); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/identity-center.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/identity-center.ts deleted file mode 100644 index 0c155b2..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/dependencies-stack/identity-center.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { DependenciesStack, LogLevel } from './dependencies-stack'; -import path from 'path'; -import { NagSuppressions } from 'cdk-nag'; -import * as cdk from 'aws-cdk-lib'; -import { AcceleratorStackProps } from '../accelerator-stack'; -import { pascalCase } from 'pascal-case'; -export class IdentityCenter { - public acceleratorManagedPolicies: Map[] = []; - private stack: DependenciesStack; - constructor(dependenciesStack: DependenciesStack, props: AcceleratorStackProps) { - this.stack = dependenciesStack; - - if (this.stack.region === props.globalConfig.homeRegion) { - // - // Create Identity Center Permission Set Accelerator managed policies in home region only - // - this.acceleratorManagedPolicies = this.createIdentityCenterPermissionSetAcceleratorManagedPolicies(props); - } - } - - /** - * Function to create Identity Center Permission Set Accelerator managed policies - * @param props - */ - private createIdentityCenterPermissionSetAcceleratorManagedPolicies( - props: AcceleratorStackProps, - ): Map[] { - const policies: Map[] = []; - for (const policySetItem of props.iamConfig.policySets ?? []) { - if (!this.stack.isIncluded(policySetItem.deploymentTargets) || !policySetItem.identityCenterDependency) { - this.stack.addLogs(LogLevel.INFO, `Item excluded`); - continue; - } - - for (const policyItem of policySetItem.policies) { - this.stack.addLogs(LogLevel.INFO, `Add customer managed policy ${policyItem.name}`); - - // Read in the policy document which should be properly formatted json - const policyDocument = JSON.parse( - this.stack.generatePolicyReplacements(path.join(props.configDirPath, policyItem.policy), false), - ); - - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - const policy = new Map(); - policy.set( - policyItem.name, - new cdk.aws_iam.ManagedPolicy(this.stack, pascalCase(policyItem.name), { - managedPolicyName: policyItem.name, - statements, - }), - ); - - policies.push(policy); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${pascalCase(policyItem.name)}/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Policies definition are derived from accelerator iam-config boundary-policy file', - }, - ], - ); - } - } - return policies; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/diagnostics-pack-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/diagnostics-pack-stack.ts deleted file mode 100644 index 659012f..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/diagnostics-pack-stack.ts +++ /dev/null @@ -1,268 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; -import path from 'path'; - -export interface DiagnosticsPackStackProps extends cdk.StackProps { - readonly acceleratorPrefix: string; - readonly ssmParamPrefix: string; - readonly bucketNamePrefix: string; - readonly installerStackName: string; - readonly configRepositoryName: string; - readonly qualifier?: string; -} - -export class DiagnosticsPackStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: DiagnosticsPackStackProps) { - super(scope, id, props); - - let keyParameterName = `${props.ssmParamPrefix}/installer/kms/key-arn`; - let reportDestinationBucketName = `${props.bucketNamePrefix}-installer-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`; - let pipelineAccountResourcesPrefix = props.acceleratorPrefix; - let diagnosticsPackLambdaRoleNamePrefix = props.acceleratorPrefix; - - if (props.qualifier) { - keyParameterName = `${props.ssmParamPrefix}/${props.qualifier}/installer/kms/key-arn`; - reportDestinationBucketName = `${props.qualifier}-installer-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`; - pipelineAccountResourcesPrefix = props.qualifier; - diagnosticsPackLambdaRoleNamePrefix = props.qualifier; - } - - const destinationBucket = cdk.aws_s3.Bucket.fromBucketName(this, 'DestinationBucket', reportDestinationBucketName); - const diagnosticsPackAccessRole = `${props.acceleratorPrefix}-DiagnosticsPackAccessRole`; - - const diagnosticsPackLambdaRole = new cdk.aws_iam.Role(this, 'DiagnosticsPackLambdaRole', { - roleName: `${diagnosticsPackLambdaRoleNamePrefix}-DiagnosticsPackLambdaRole`, - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.urlSuffix}`), - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], - }); - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${diagnosticsPackAccessRole}`], - }), - ); - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'AllowStsCallerIdentityActions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:GetCallerIdentity'], - resources: ['*'], - }), - ); - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudformationAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['cloudformation:DescribeStackEvents', 'cloudformation:DescribeStacks'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:cloudformation:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:stack/${pipelineAccountResourcesPrefix}*`, - ], - }), - ); - - if (!props.qualifier) { - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'OrganizationsAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['organizations:ListAccounts'], - resources: ['*'], - }), - ); - } - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'CodecommitAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['codecommit:GetFile'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:codecommit:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:${ - props.configRepositoryName - }`, - ], - }), - ); - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'CodepipelineAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['codepipeline:GetPipelineState'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:codepipeline:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:${pipelineAccountResourcesPrefix}*`, - ], - }), - ); - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudwatchLogsAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:FilterLogEvents'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:/aws/codebuild/${pipelineAccountResourcesPrefix}*`, - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:/aws/lambda/${pipelineAccountResourcesPrefix}*`, - ], - }), - ); - - diagnosticsPackLambdaRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'DiagnosticsBucketWriteAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 's3:PutObject', - 's3:GetObject', - 's3:AbortMultipartUpload', - 's3:ListBucket', - 's3:DeleteObject', - 's3:GetObjectVersion', - 's3:ListMultipartUploadParts', - ], - resources: [destinationBucket.bucketArn, destinationBucket.arnForObjects('*')], - }), - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/DiagnosticsPackLambdaRole/Resource`, [ - { - id: 'AwsSolutions-IAM4', - reason: 'Custom resource Lambda role policy.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/DiagnosticsPackLambdaRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Custom resource Lambda role policy.', - }, - ], - ); - - const diagnosticFunction = new cdk.aws_lambda.Function(this, 'DiagnosticsFunction', { - role: diagnosticsPackLambdaRole, - description: 'Accelerator diagnostics report lambda function.', - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '../lambdas/diagnostic-pack/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - memorySize: 512, - timeout: cdk.Duration.minutes(15), - handler: 'index.handler', - environment: { - INSTALLER_STACK_NAME: props.installerStackName, - HOME_REGION: cdk.Stack.of(this).region, - PIPELINE_ACCOUNT_ID: cdk.Stack.of(this).account, - PARTITION: cdk.Stack.of(this).partition, - DAYS_PIPELINE_IN_FAILED_STATUS: '1', - REPORT_BUCKET_NAME: destinationBucket.bucketName, - CONFIG_REPO_NAME: props.configRepositoryName, - MANAGEMENT_ACCOUNT_ROLE_NAME: diagnosticsPackAccessRole, - }, - }); - - new cdk.aws_logs.LogGroup(this, `${diagnosticFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${diagnosticFunction.functionName}`, - retention: 30, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const key = cdk.aws_kms.Key.fromKeyArn( - this, - 'DiagnosticsProjectEncryptionKey', - cdk.aws_ssm.StringParameter.valueForStringParameter(this, keyParameterName).toString(), - ); - - const buildProject = new cdk.aws_codebuild.Project(this, 'DiagnosticsProject', { - projectName: `${pipelineAccountResourcesPrefix}-DiagnosticProject`, - encryptionKey: key, - description: `Accelerator diagnostic project. You can execute this project to generate an error report and store it into the ${destinationBucket.bucketName} bucket.`, - buildSpec: cdk.aws_codebuild.BuildSpec.fromObjectToYaml({ - version: '0.2', - phases: { - build: { - commands: [ - `set -e`, - `aws lambda update-function-configuration --function-name ${diagnosticFunction.functionArn} --region ${ - cdk.Stack.of(this).region - } --environment Variables="{INSTALLER_STACK_NAME=${props.installerStackName},HOME_REGION=${ - cdk.Stack.of(this).region - },PIPELINE_ACCOUNT_ID=${cdk.Stack.of(this).account},PARTITION=${ - cdk.Stack.of(this).partition - },DAYS_PIPELINE_IN_FAILED_STATUS=$DAYS_PIPELINE_IN_FAILED_STATUS,REPORT_BUCKET_NAME=${ - destinationBucket.bucketName - },CONFIG_REPO_NAME=${ - props.configRepositoryName - },MANAGEMENT_ACCOUNT_ROLE_NAME=${diagnosticsPackAccessRole}}" --output text`, - `aws lambda wait function-updated --function-name ${diagnosticFunction.functionArn} --region ${ - cdk.Stack.of(this).region - }`, - `aws lambda invoke --function-name ${diagnosticFunction.functionArn} --region ${ - cdk.Stack.of(this).region - } --payload {} /tmp/response.json`, - `error_count=$(grep "error" /tmp/response.json | wc -l)`, - `echo $error_count`, - `if [ $error_count -gt 0 ]; then - echo "Diagnostics Lambda execution failed with below error !!!!"; - cat /tmp/response.json; - exit 1; - fi`, - ], - }, - }, - }), - environment: { - buildImage: cdk.aws_codebuild.LinuxBuildImage.STANDARD_7_0, - privileged: false, - computeType: cdk.aws_codebuild.ComputeType.SMALL, - environmentVariables: { - DAYS_PIPELINE_IN_FAILED_STATUS: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '1', - }, - }, - }, - }); - - buildProject.role?.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'AllowLambdaAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['lambda:InvokeFunction', 'lambda:UpdateFunctionConfiguration', 'lambda:GetFunctionConfiguration'], - resources: [diagnosticFunction.functionArn], - }), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/DiagnosticsProject/Role/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Diagnostic CodeBuild project role.', - }, - ], - ); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/finalize-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/finalize-stack.ts deleted file mode 100644 index 5fa2c7e..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/finalize-stack.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { AcceleratorStack, AcceleratorStackProps, AcceleratorKeyType } from './accelerator-stack'; -import { DetachQuarantineScp } from '../detach-quarantine-scp'; -import { ScpResource } from '../resources/scp-resource'; - -export class FinalizeStack extends AcceleratorStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - if (props.globalRegion === cdk.Stack.of(this).region) { - this.logger.debug(`Retrieving CloudWatch kms key`); - - const lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - const cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - const scpResource = new ScpResource(this, cloudwatchKey, lambdaKey, props); - - // - // Update SCP with dynamic parameters - // - scpResource.createAndAttachScps(props); - - // - // Configure revert scp changes rule - // - scpResource.configureRevertScpChanges(props); - - if (process.env['CONFIG_COMMIT_ID']) { - this.logger.debug(`Storing configuration commit id in SSM`); - new cdk.aws_ssm.StringParameter(this, 'AcceleratorCommitIdParameter', { - parameterName: `${props.prefixes.ssmParamName}/configuration/configCommitId`, - stringValue: process.env['CONFIG_COMMIT_ID'], - description: `The commit hash of the latest ${props.configRepositoryName} commit to deploy successfully`, - }); - } - - if (props.organizationConfig.quarantineNewAccounts?.enable && props.partition === 'aws') { - this.logger.debug(`Creating resources to detach quarantine scp`); - const policyId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${props.prefixes.ssmParamName}/organizations/scp/${props.organizationConfig.quarantineNewAccounts?.scpPolicyName}/id`, - ); - - new DetachQuarantineScp(this, 'DetachQuarantineScp', { - scpPolicyId: policyId, - managementAccountId: props.accountsConfig.getManagementAccountId(), - partition: props.partition, - kmsKey: cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - } - this.logger.info('Completed stack synthesis'); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/identity-center-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/identity-center-stack.ts deleted file mode 100644 index 2e287f7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/identity-center-stack.ts +++ /dev/null @@ -1,346 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -import { - IdentityCenterAssignmentConfig, - IdentityCenterConfig, - IdentityCenterPermissionSetConfig, -} from '@aws-accelerator/config'; -import { IdentityCenterAssignments, IdentityCenterInstance } from '@aws-accelerator/constructs'; -import { AcceleratorKeyType, AcceleratorStack, AcceleratorStackProps } from './accelerator-stack'; - -interface PermissionSetMapping { - name: string; - arn: string; - permissionSet: cdk.aws_sso.CfnPermissionSet; -} -export class IdentityCenterStack extends AcceleratorStack { - /** - * KMS Key used to encrypt CloudWatch logs, when undefined default AWS managed key will be used - */ - private cloudwatchKey: cdk.aws_kms.IKey | undefined; - /** - * KMS Key used to encrypt custom resource Lambda environment variables, when undefined default AWS managed key will be used - */ - private lambdaKey: cdk.aws_kms.IKey | undefined; - /** - * Identity Center Instance ARN - */ - private identityCenterInstanceArn: string | undefined; - /** - * Identity Center Identity Store Id - */ - private identityCenterIdentityStoreId: string | undefined; - - /** - * Constructor for Identity-Center Stack - * - * @param scope - * @param id - * @param props - */ - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - - // - // Only deploy Identity Center resources into the home region - // - if ( - props.globalConfig.homeRegion === cdk.Stack.of(this).region && - cdk.Stack.of(this).account === props.accountsConfig.getManagementAccountId() - ) { - this.getIdentityCenterProperties(); - this.addIdentityCenterResources(); - } - - // - // Create SSM parameters - // - this.createSsmParameters(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Function to create Identity Center Permission Sets - * @param identityCenterItem - * @param identityCenterInstanceArn - * @returns - */ - private addIdentityCenterPermissionSets( - identityCenterItem: IdentityCenterConfig, - identityCenterInstanceArn: string, - ): PermissionSetMapping[] { - const permissionSetMap: PermissionSetMapping[] = []; - - for (const identityCenterPermissionSet of identityCenterItem.identityCenterPermissionSets ?? []) { - const permissionSet = this.createPermissionsSet( - identityCenterPermissionSet, - identityCenterInstanceArn, - permissionSetMap, - ); - permissionSetMap.push(permissionSet); - } - - return permissionSetMap; - } - - /** - * Function to get CustomerManaged Policy References List - * @param identityCenterPermissionSet {@link IdentityCenterPermissionSetConfig} - * @returns customerManagedPolicyReferencesList {@link cdk.aws_sso.CfnPermissionSet.CustomerManagedPolicyReferenceProperty}[] - */ - private getCustomerManagedPolicyReferencesList( - identityCenterPermissionSet: IdentityCenterPermissionSetConfig, - ): cdk.aws_sso.CfnPermissionSet.CustomerManagedPolicyReferenceProperty[] { - const customerManagedPolicyReferencesList: cdk.aws_sso.CfnPermissionSet.CustomerManagedPolicyReferenceProperty[] = - []; - - if (identityCenterPermissionSet.policies) { - this.logger.info(`Adding Identity Center Permission Set ${identityCenterPermissionSet.name}`); - - // Add Customer managed and LZA managed policies - for (const policy of [ - ...(identityCenterPermissionSet.policies.customerManaged ?? []), - ...(identityCenterPermissionSet.policies.acceleratorManaged ?? []), - ]) { - customerManagedPolicyReferencesList.push({ name: policy }); - } - } - - return customerManagedPolicyReferencesList; - } - - /** - * Function to get AWS Managed permissionsets - * @param identityCenterPermissionSet {@link IdentityCenterPermissionSetConfig} - * @returns awsManagedPolicies string[] - */ - private getAwsManagedPolicies(identityCenterPermissionSet: IdentityCenterPermissionSetConfig): string[] { - const awsManagedPolicies: string[] = []; - - for (const awsManagedPolicy of identityCenterPermissionSet?.policies?.awsManaged ?? []) { - if (awsManagedPolicy.startsWith('arn:')) { - awsManagedPolicies.push(awsManagedPolicy); - } else { - awsManagedPolicies.push(cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName(awsManagedPolicy).managedPolicyArn); - } - } - - return awsManagedPolicies; - } - - /** - * Function to get permission boundary - * @param identityCenterPermissionSet {@link IdentityCenterPermissionSetConfig} - * @returns permissionsBoundary {@link cdk.aws_sso.CfnPermissionSet.PermissionsBoundaryProperty} | undefined - */ - private getPermissionBoundary( - identityCenterPermissionSet: IdentityCenterPermissionSetConfig, - ): cdk.aws_sso.CfnPermissionSet.PermissionsBoundaryProperty | undefined { - let permissionsBoundary: cdk.aws_sso.CfnPermissionSet.PermissionsBoundaryProperty | undefined; - - if (identityCenterPermissionSet.policies?.permissionsBoundary) { - if (identityCenterPermissionSet.policies.permissionsBoundary.customerManagedPolicy) { - permissionsBoundary = { - customerManagedPolicyReference: { - name: identityCenterPermissionSet.policies.permissionsBoundary.customerManagedPolicy.name, - path: identityCenterPermissionSet.policies.permissionsBoundary.customerManagedPolicy.path, - }, - }; - } - if (identityCenterPermissionSet.policies.permissionsBoundary.awsManagedPolicyName) { - permissionsBoundary = { - managedPolicyArn: cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - identityCenterPermissionSet.policies.permissionsBoundary.awsManagedPolicyName, - ).managedPolicyArn, - }; - } - } - - return permissionsBoundary; - } - - /** - * Create Identity Center Permission sets - * @param identityCenterPermissionSet - * @param identityCenterInstanceArn - * @returns - */ - private createPermissionsSet( - identityCenterPermissionSet: IdentityCenterPermissionSetConfig, - identityCenterInstanceArn: string, - permissionSetMap: PermissionSetMapping[], - ): PermissionSetMapping { - const customerManagedPolicyReferencesList = - this.getCustomerManagedPolicyReferencesList(identityCenterPermissionSet); - - let convertedSessionDuration: string | undefined; - - if (identityCenterPermissionSet.sessionDuration) { - convertedSessionDuration = this.convertMinutesToIso8601(identityCenterPermissionSet.sessionDuration); - } - - const awsManagedPolicies = this.getAwsManagedPolicies(identityCenterPermissionSet); - - const permissionsBoundary = this.getPermissionBoundary(identityCenterPermissionSet); - - let permissionSetProps: cdk.aws_sso.CfnPermissionSetProps = { - name: identityCenterPermissionSet.name, - instanceArn: identityCenterInstanceArn, - managedPolicies: awsManagedPolicies.length > 0 ? awsManagedPolicies : undefined, - customerManagedPolicyReferences: - customerManagedPolicyReferencesList.length > 0 ? customerManagedPolicyReferencesList : undefined, - sessionDuration: convertedSessionDuration, - permissionsBoundary: permissionsBoundary, - }; - - if (identityCenterPermissionSet.policies?.inlinePolicy) { - // Read in the policy document which should be properly formatted json - const inlinePolicyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, identityCenterPermissionSet.policies?.inlinePolicy), - false, - this.organizationId, - ), - ); - permissionSetProps = { - name: identityCenterPermissionSet.name, - instanceArn: identityCenterInstanceArn, - managedPolicies: awsManagedPolicies.length > 0 ? awsManagedPolicies : undefined, - customerManagedPolicyReferences: - customerManagedPolicyReferencesList.length > 0 ? customerManagedPolicyReferencesList : undefined, - sessionDuration: convertedSessionDuration ?? undefined, - inlinePolicy: inlinePolicyDocument, - permissionsBoundary: permissionsBoundary, - }; - } - - const permissionSet = new cdk.aws_sso.CfnPermissionSet( - this, - `${pascalCase(identityCenterPermissionSet.name)}IdentityCenterPermissionSet`, - permissionSetProps, - ); - - // Create dependency for CfnPermissionSet - for (const item of permissionSetMap) { - permissionSet.node.addDependency(item.permissionSet); - } - - return { name: permissionSet.name, arn: permissionSet.attrPermissionSetArn, permissionSet: permissionSet }; - } - - private addIdentityCenterAssignments( - identityCenterItem: IdentityCenterConfig, - identityCenterInstanceArn: string, - permissionSetMap: PermissionSetMapping[], - ) { - for (const assignment of identityCenterItem.identityCenterAssignments ?? []) { - this.createAssignment( - assignment, - permissionSetMap, - identityCenterInstanceArn, - this.identityCenterIdentityStoreId!, - ); - } - } - - private createAssignment( - assignment: IdentityCenterAssignmentConfig, - permissionSetMap: PermissionSetMapping[], - identityCenterInstanceArn: string, - identityStoreId: string, - ) { - const targetAccountIds = this.getAccountIdsFromDeploymentTargets(assignment.deploymentTargets); - const permissionSetArnValue = this.getPermissionSetArn(permissionSetMap, assignment.permissionSetName); - new IdentityCenterAssignments(this, `${pascalCase(`IdentityCenterAssignment-${assignment.name}`)}`, { - identityStoreId: identityStoreId, - identityCenterInstanceArn: identityCenterInstanceArn, - principals: assignment.principals, - principalType: assignment.principalType, - principalId: assignment.principalId, - permissionSetArnValue: permissionSetArnValue, - accountIds: targetAccountIds, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - - private getPermissionSetArn(permissionSetMap: PermissionSetMapping[], name: string) { - let permissionSetArn = ''; - for (const permissionSet of permissionSetMap) { - if (permissionSet.name == name && permissionSet.arn) { - permissionSetArn = permissionSet.arn; - } - } - return permissionSetArn; - } - - /** - * Function to add Identity Center Resources - * @param securityAdminAccountId - */ - private addIdentityCenterResources() { - if (this.props.iamConfig.identityCenter) { - const permissionSetList = this.addIdentityCenterPermissionSets( - this.props.iamConfig.identityCenter, - this.identityCenterInstanceArn!, - ); - - this.addIdentityCenterAssignments( - this.props.iamConfig.identityCenter, - this.identityCenterInstanceArn!, - permissionSetList, - ); - } - } - - /** - * Function to retrieve IDC instance ARN - * @param securityAdminAccountId - */ - private getIdentityCenterProperties() { - if (this.props.iamConfig.identityCenter) { - const identityCenterInstance = new IdentityCenterInstance(this, 'IdentityCenterInstance', { - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey!, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - this.identityCenterInstanceArn = identityCenterInstance.instanceArn; - this.identityCenterIdentityStoreId = identityCenterInstance.instanceStoreId; - - new cdk.aws_ssm.StringParameter(this, 'IdentityCenterInstanceArnSsmParameter', { - parameterName: this.acceleratorResourceNames.parameters.identityCenterInstanceArn, - stringValue: this.identityCenterInstanceArn, - }); - - new cdk.aws_ssm.StringParameter(this, 'IdentityCenterIdentityStoreIdSsmParameter', { - parameterName: this.acceleratorResourceNames.parameters.identityStoreId, - stringValue: this.identityCenterIdentityStoreId, - }); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/import-asea-resources-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/import-asea-resources-stack.ts deleted file mode 100644 index 0e1be64..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/import-asea-resources-stack.ts +++ /dev/null @@ -1,405 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { Construct } from 'constructs'; -import * as cdk from 'aws-cdk-lib'; -import * as fs from 'fs'; -import { AcceleratorStackProps } from './accelerator-stack'; -import { - DeploymentTargets, - AseaResourceMapping, - VpcConfig, - VpcTemplatesConfig, - isNetworkType, - ASEAMapping, - ASEAMappings, -} from '@aws-accelerator/config'; -import { ManagedPolicies } from '../asea-resources/managed-policies'; -import { Roles } from '../asea-resources/iam-roles'; -import { Groups } from '../asea-resources/iam-groups'; -import { Users } from '../asea-resources/iam-users'; -import { VpcResources } from '../asea-resources/vpc-resources'; -import { AcceleratorStage } from '../accelerator-stage'; -import { TransitGateways } from '../asea-resources/transit-gateways'; -import { VpcPeeringConnection } from '../asea-resources/vpc-peering-connection'; -import { SharedSecurityGroups } from '../asea-resources/shared-security-groups'; -import { NetworkStack } from './network-stacks/network-stack'; -import { TgwCrossAccountResources } from '../asea-resources/tgw-cross-account-resources'; -import { TransitGatewayRoutes } from '../asea-resources/transit-gateway-routes'; -import { VpcEndpoints } from '../asea-resources/vpc-endpoints'; -import { SsmInventory } from '../asea-resources/ssm-inventory'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { FirewallResources } from '../asea-resources/firewall-resources'; -import { Route53ResolverQueryLogging } from '../asea-resources/route-53-query-logging'; -import { Route53ResolverQueryLoggingAssociation } from '../asea-resources/route-53-query-logging-association'; -import { Route53ResolverEndpoint } from '../asea-resources/route-53-resolver-endpoint'; -import { ManagedAdResources } from '../asea-resources/managed-ad-resources'; -import { ApplicationLoadBalancerResources } from '../asea-resources/application-load-balancers'; -import path from 'path'; -import { ImportStackResources } from '../../utils/import-stack-resources'; -import { NestedStack } from '@aws-accelerator/config'; - -/** - * Enum for log level - */ -export enum LogLevel { - INFO = 'info', - WARN = 'warn', - ERROR = 'error', -} - -export interface ImportAseaResourcesStackProps extends AcceleratorStackProps { - /** - * Current stack info. - * Retrieved from ASEA CloudFormation stacks - */ - stackInfo: ASEAMapping; - - /** - * Nested Stacks in current stack - * ASEA creates Nested stacks in Phase1 for VPCs - */ - - mapping: ASEAMappings; - - stage: AcceleratorStage.IMPORT_ASEA_RESOURCES | AcceleratorStage.POST_IMPORT_ASEA_RESOURCES; -} - -export interface ImportAseaResourcesStackConstructorProps extends ImportAseaResourcesStackProps { - importStackResources: ImportStackResources; -} - -export interface NestedStacks extends ASEAMapping { - logicalResourceId: string; -} -/** - * Extending from NetworkStack since most of the reusable functions are from NetworkStack - */ -export class ImportAseaResourcesStack extends NetworkStack { - includedStack: cdk.cloudformation_include.CfnInclude; - readonly ssmParameters: { - logicalId: string; - parameterName: string; - stringValue: string; - scope?: string; - }[]; - private readonly stackInfo: ASEAMapping; - public resourceMapping: AseaResourceMapping[] = []; - public firewallBucket: cdk.aws_s3.IBucket; - public importStackResources: ImportStackResources; - public nestedStackResources?: { [key: string]: ImportStackResources }; - public nestedStacks: { [key: string]: cdk.cloudformation_include.IncludedNestedStack } = {}; - constructor(scope: Construct, id: string, props: ImportAseaResourcesStackConstructorProps) { - super(scope, id, props); - this.ssmParameters = []; - this.logger = createLogger([ - `${cdk.Stack.of(this).account}-${cdk.Stack.of(this).stackName}-${cdk.Stack.of(this).region}`, - ]); - this.stackInfo = props.stackInfo; - this.importStackResources = props.importStackResources; - this.nestedStackResources = props.importStackResources.nestedStackResources; - this.firewallBucket = cdk.aws_s3.Bucket.fromBucketName(this, 'FirewallLogsBucket', this.centralLogsBucketName); - this.includedStack = new cdk.cloudformation_include.CfnInclude(this, `stack`, { - templateFile: path.join('asea-assets', this.stackInfo.templatePath), - preserveLogicalIds: true, - loadNestedStacks: {}, - }); - this.loadNestedStacks(this.stackInfo.nestedStacks); - const { policies } = new ManagedPolicies(this, props); - new Roles(this, { ...props, policies }); - const { groups } = new Groups(this, { ...props, policies }); - new Users(this, { ...props, policies, groups }); - new TransitGateways(this, props); - new VpcResources(this, { ...props }); - new VpcPeeringConnection(this, props); - new SharedSecurityGroups(this, { ...props }); - new TgwCrossAccountResources(this, props); - new TransitGatewayRoutes(this, { ...props }); - new VpcEndpoints(this, props); - new SsmInventory(this, props); - new ManagedAdResources(this, props); - new FirewallResources(this, props); - new Route53ResolverQueryLogging(this, props); - new Route53ResolverQueryLoggingAssociation(this, props); - new Route53ResolverEndpoint(this, props); - new ApplicationLoadBalancerResources(this, props); - this.createSsmParameters(); - this.deleteResources(); - } - - public static async init(scope: Construct, id: string, props: ImportAseaResourcesStackProps) { - const importStackResources = await ImportStackResources.init({ stackMapping: props.stackInfo }); - - const constructorProps: ImportAseaResourcesStackConstructorProps = { - ...props, - importStackResources, - }; - - return new ImportAseaResourcesStack(scope, id, constructorProps); - } - /** - * Get account names and excluded account IDs for transit gateway attachments - * @param vpcItem - * @returns - */ - getTransitGatewayAttachmentAccounts(vpcItem: VpcConfig | VpcTemplatesConfig): [string[], string[]] { - let accountNames: string[]; - let excludedAccountIds: string[] = []; - if (isNetworkType('IVpcConfig', vpcItem)) { - accountNames = [vpcItem.account]; - } else { - accountNames = this.getAccountNamesFromDeploymentTarget(vpcItem.deploymentTargets); - excludedAccountIds = this.getExcludedAccountIds(vpcItem.deploymentTargets); - } - return [accountNames, excludedAccountIds]; - } - - /** - * Public accessor method to add ASEA Resource Mapping - * @param type - * @param identifier - */ - public addAseaResource(type: string, identifier: string) { - this.resourceMapping.push({ - accountId: this.stackInfo.accountId, - region: this.stackInfo.region, - resourceType: type, - resourceIdentifier: identifier, - }); - } - - public addDeleteFlagForAseaResource(props: { type?: string; identifier?: string; logicalId: string }) { - const mappingResource = this.resourceMapping.find( - resource => - resource.resourceType === props.type && - resource.resourceIdentifier === props.identifier && - resource.accountId === cdk.Stack.of(this).account && - resource.region === cdk.Stack.of(this).region, - ); - const importResource = this.importStackResources.getResourceByLogicalId(props.logicalId); - if (mappingResource) { - mappingResource.isDeleted = true; - } - if (importResource) { - importResource.isDeleted = true; - } - } - - /** - * Public accessor method to add logs to logger - * @param logLevel - * @param message - */ - public addLogs(logLevel: LogLevel, message: string) { - switch (logLevel) { - case 'info': - this.logger.info(message); - break; - - case 'warn': - this.logger.warn(message); - break; - - case 'error': - this.logger.error(message); - break; - } - } - - getExcludedAccountIds(deploymentTargets: DeploymentTargets): string[] { - return super.getExcludedAccountIds(deploymentTargets); - } - - protected createSsmParameters() { - this.createMainSsmParameters(this.ssmParameters); - this.createNestedStackSSMParameters(this.ssmParameters); - } - - protected createMainSsmParameters( - parameterItems: { logicalId: string; parameterName: string; stringValue: string; scope?: string }[], - ): void { - let index = 1; - const parameterMap = new Map(); - for (const parameterItem of parameterItems) { - if (parameterItem.scope) { - continue; - } - let cfnParameter; - const parameter = this.importStackResources.getResourceByPropertyIgnoreDeletionFlag( - 'Name', - parameterItem.parameterName, - ); - if (parameter?.isDeleted) { - continue; - } - if (!parameter) { - cfnParameter = new cdk.aws_ssm.CfnParameter(this, parameterItem.logicalId, { - name: parameterItem.parameterName, - value: parameterItem.stringValue, - type: 'String', - }); - } else { - try { - cfnParameter = this.getResource(parameter.logicalResourceId) as cdk.aws_ssm.CfnParameter; - this.logger.debug(`Updating ${parameterItem.logicalId} ssm Parameter`); - cfnParameter.addPropertyOverride('Name', parameterItem.parameterName); - cfnParameter.addPropertyOverride('Value', parameterItem.stringValue); - } catch (err) { - this.logger.debug(`${parameterItem.logicalId} not found creating new ssm Parameter`); - cfnParameter = new cdk.aws_ssm.CfnParameter(this, parameterItem.logicalId, { - name: parameterItem.parameterName, - value: parameterItem.stringValue, - type: 'String', - }); - } - } - - if (cfnParameter) { - parameterMap.set(index, cfnParameter); - } - // Increment index - index += 1; - } - } - private createNestedStackSSMParameters( - parameterItems: { logicalId: string; parameterName: string; stringValue: string; scope?: string }[], - ) { - let index = 1; - const parameterMap = new Map(); - for (const parameterItem of parameterItems) { - if (!parameterItem.scope || !this.nestedStackResources) { - continue; - } - const nestedStackImportResources = this.nestedStackResources[parameterItem.scope]; - const nestedStack = this.nestedStacks[parameterItem.scope]; - let cfnParameter; - const parameter = nestedStackImportResources.getSSMParameterByName(parameterItem.parameterName); - if (parameter?.isDeleted) { - continue; - } - if (!parameter) { - cfnParameter = new cdk.aws_ssm.CfnParameter(nestedStack.stack, parameterItem.logicalId, { - name: parameterItem.parameterName, - value: parameterItem.stringValue, - type: 'String', - }); - } else { - try { - cfnParameter = nestedStack.includedTemplate.getResource( - parameter.logicalResourceId, - ) as cdk.aws_ssm.CfnParameter; - this.logger.debug(`Updating ${parameterItem.logicalId} ssm Parameter`); - cfnParameter.addPropertyOverride('Name', parameterItem.parameterName); - cfnParameter.addPropertyOverride('Value', parameterItem.stringValue); - } catch (err) { - this.logger.debug(`${parameterItem.logicalId} not found creating new ssm Parameter`); - cfnParameter = new cdk.aws_ssm.CfnParameter(nestedStack.stack, parameterItem.logicalId, { - name: parameterItem.parameterName, - value: parameterItem.stringValue, - type: 'String', - }); - } - } - if (cfnParameter) { - parameterMap.set(index, cfnParameter); - } - // Increment index - index += 1; - } - } - private loadNestedStacks(nestedStacks: { [key: string]: NestedStack } | undefined) { - if (nestedStacks) { - Object.keys(nestedStacks).forEach(key => { - const nestedStack = nestedStacks[key]; - this.nestedStacks[key] = this.includedStack.loadNestedStack(nestedStack.logicalResourceId, { - templateFile: path.join('asea-assets', nestedStack.templatePath), - preserveLogicalIds: true, - }); - }); - } - } - private deleteResources() { - this.deleteMainResources(); - this.deleteNestedStackResources(); - } - private deleteMainResources() { - const logicalIdsToDelete = this.importStackResources.cfnResources - .filter(importResource => importResource.isDeleted) - .map(importResource => importResource.logicalResourceId); - for (const logicalId of logicalIdsToDelete) { - this.includedStack.node.tryRemoveChild(logicalId); - } - } - private deleteNestedStackResources() { - if (!this.nestedStackResources) { - return; - } - for (const nestedStackKey of Object.keys(this.nestedStackResources)) { - const nestedStack = this.nestedStackResources[nestedStackKey]; - const logicalIdsToDelete = nestedStack.cfnResources - .filter(importResource => importResource.isDeleted) - .map(importResource => importResource.logicalResourceId); - for (const logicalId of logicalIdsToDelete) { - this.nestedStacks[nestedStackKey].includedTemplate.node.tryRemoveChild(logicalId); - } - } - } - - public getResource(logicalId: string): cdk.CfnResource | undefined { - return this.includedStack.getResource(logicalId); - } - public addDeleteFlagForNestedResource(nestedStackKey: string, logicalId: string) { - const resource = this.nestedStackResources?.[nestedStackKey].cfnResources.find( - resource => resource.logicalResourceId === logicalId, - ); - if (resource) { - resource.isDeleted = true; - } - } - public getNestedStack(stackKey: string): cdk.cloudformation_include.IncludedNestedStack { - return this.nestedStacks[stackKey]; - } - - public async saveLocalResourceFile() { - const resourcePathArr = this.stackInfo.resourcePath.split('/'); - const resourceFileName = resourcePathArr.pop(); - resourcePathArr.unshift('new'); - resourcePathArr.unshift('asea-assets'); - const newResourcePath = resourcePathArr.join('/'); - await fs.promises.mkdir(newResourcePath, { recursive: true }); - await fs.promises.writeFile( - path.join(newResourcePath, resourceFileName!), - JSON.stringify(this.importStackResources.cfnResources, null, 2), - 'utf8', - ); - if (this.stackInfo.nestedStacks) { - for (const nestedStackKey of Object.keys(this.stackInfo.nestedStacks)) { - const nestedStack = this.stackInfo.nestedStacks[nestedStackKey]; - const nestedStackResources = this.nestedStackResources?.[nestedStackKey].cfnResources; - if (!nestedStackResources) { - continue; - } - const nestedResourcePathArr = nestedStack.resourcePath.split('/'); - const nestedResourceFileName = nestedResourcePathArr.pop(); - nestedResourcePathArr.unshift('new'); - nestedResourcePathArr.unshift('asea-assets'); - const newNestedResourcePath = nestedResourcePathArr.join('/'); - await fs.promises.mkdir(newNestedResourcePath, { recursive: true }); - await fs.promises.writeFile( - path.join(newNestedResourcePath, nestedResourceFileName!), - JSON.stringify(nestedStackResources, null, 2), - 'utf8', - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/key-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/key-stack.ts deleted file mode 100644 index 9c8f217..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/key-stack.ts +++ /dev/null @@ -1,283 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import { AcceleratorStack, AcceleratorStackProps } from './accelerator-stack'; - -export class KeyStack extends AcceleratorStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - if (cdk.Stack.of(this).account === props.accountsConfig.getAuditAccountId()) { - // - // Create Accelerator Key - // - this.createAcceleratorKey(props); - - // - // Create cross account ssm parameter access role - // - this.createCrossAccountAcceleratorSsmParamAccessRole(props.accountsConfig.getAccountIds(), props); - - // - // Create SSM Parameters - // - this.createSsmParameters(); - } - } - - /** - * Function to create Accelerator Key - * @param props {@link AcceleratorStackProps} - */ - private createAcceleratorKey(props: AcceleratorStackProps): cdk.aws_kms.IKey { - const key = new cdk.aws_kms.Key(this, 'AcceleratorKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.acceleratorKey.alias, - description: this.acceleratorResourceNames.customerManagedKeys.acceleratorKey.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - if (props.organizationConfig.enable) { - // Allow Accelerator Role to use the encryption key - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Accelerator Role to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`], - }, - }, - }), - ); - } - - // Allow Cloudwatch logs to use the encryption key - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [ - new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - ], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this).partition}:logs:${ - cdk.Stack.of(this).region - }:*:log-group:*`, - }, - }, - }), - ); - - // Add all services we want to allow usage - const allowedServicePrincipals: { name: string; principal: string }[] = [ - { name: 'Sns', principal: 'sns.amazonaws.com' }, - { name: 'Lambda', principal: 'lambda.amazonaws.com' }, - { name: 'Cloudwatch', principal: 'cloudwatch.amazonaws.com' }, - { name: 'Sqs', principal: 'sqs.amazonaws.com' }, - // Add similar objects for any other service principal needs access to this key - ]; - - // Deprecated - if (props.securityConfig.centralSecurityServices.macie.enable) { - allowedServicePrincipals.push({ name: 'Macie', principal: 'macie.amazonaws.com' }); - } - // Deprecated - if (props.securityConfig.centralSecurityServices.guardduty.enable) { - allowedServicePrincipals.push({ name: 'Guardduty', principal: 'guardduty.amazonaws.com' }); - } - // Deprecated - if (props.securityConfig.centralSecurityServices.auditManager?.enable) { - allowedServicePrincipals.push({ name: 'AuditManager', principal: 'auditmanager.amazonaws.com' }); - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Audit Manager service to provision encryption key grants`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:CreateGrant'], - conditions: { - StringLike: { - 'kms:ViaService': 'auditmanager.*.amazonaws.com', - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - Bool: { 'kms:GrantIsForAWSResource': 'true' }, - }, - resources: ['*'], - }), - ); - } - - allowedServicePrincipals.forEach(item => { - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow ${item.name} service to use the encryption key`, - principals: [new cdk.aws_iam.ServicePrincipal(item.principal)], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - }), - ); - }); - - this.ssmParameters.push({ - logicalId: 'AcceleratorKmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.acceleratorCmkArn, - - stringValue: key.keyArn, - }); - - return key; - } - - /** - * Create cross account accelerator ssm parameter access role - * @param accountIds - * @param props {@link AcceleratorStackProps} - * @returns cdk.aws_iam.Role | undefined - */ - private createCrossAccountAcceleratorSsmParamAccessRole( - accountIds: string[], - props: AcceleratorStackProps, - ): cdk.aws_iam.Role | undefined { - let role: cdk.aws_iam.Role | undefined; - // IAM Role to get access to accelerator organization level SSM parameters - // Only create this role in the home region stack - if (cdk.Stack.of(this).region === props.globalConfig.homeRegion) { - if (props.organizationConfig.enable) { - role = new cdk.aws_iam.Role(this, 'CrossAccountAcceleratorSsmParamAccessRole', { - roleName: this.acceleratorResourceNames.roles.crossAccountCmkArnSsmParameterAccess, - assumedBy: this.getOrgPrincipals(this.organizationId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:*:${cdk.Stack.of(this).account}:parameter${ - this.acceleratorResourceNames.parameters.acceleratorCmkArn - }`, - `arn:${cdk.Stack.of(this).partition}:ssm:*:${cdk.Stack.of(this).account}:parameter${ - this.acceleratorResourceNames.parameters.s3CmkArn - }`, - ], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`, - ], - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:DescribeParameters'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ], - }), - }, - }); - } else { - const principals: cdk.aws_iam.PrincipalBase[] = []; - accountIds.forEach(accountId => { - principals.push(new cdk.aws_iam.AccountPrincipal(accountId)); - }); - role = new cdk.aws_iam.Role(this, 'CrossAccountAcceleratorSsmParamAccessRole', { - roleName: this.acceleratorResourceNames.roles.crossAccountCmkArnSsmParameterAccess, - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:*:${cdk.Stack.of(this).account}:parameter${ - this.acceleratorResourceNames.parameters.acceleratorCmkArn - }`, - `arn:${cdk.Stack.of(this).partition}:ssm:*:${cdk.Stack.of(this).account}:parameter${ - this.acceleratorResourceNames.parameters.s3CmkArn - }`, - ], - conditions: { - StringEquals: { - 'aws:PrincipalAccount': [...accountIds], - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`, - ], - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:DescribeParameters'], - resources: ['*'], - conditions: { - StringEquals: { - 'aws:PrincipalAccount': [...accountIds], - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ], - }), - }, - }); - } - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/CrossAccountAcceleratorSsmParamAccessRole/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: - 'This policy is required to give access to ssm parameters in every region where accelerator deployed. Various accelerator roles need permission to describe SSM parameters.', - }, - ], - ); - } - - return role; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/logging-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/logging-stack.ts deleted file mode 100644 index 424d74d..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/logging-stack.ts +++ /dev/null @@ -1,3050 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import { pascalCase } from 'pascal-case'; -import path from 'path'; - -import { - SnsTopicConfig, - VpcFlowLogsConfig, - CloudWatchLogsExclusionConfig, - CentralLogBucketConfig, - ElbLogBucketConfig, - AccessLogBucketConfig, - AssetBucketConfig, - AseaResourceType, -} from '@aws-accelerator/config'; -import * as t from '@aws-accelerator/config/lib/common/types'; -import { - Bucket, - BucketEncryption, - BucketEncryptionType, - BucketPolicy, - BucketPrefix, - BucketPrefixProps, - BucketReplicationProps, - CentralLogsBucket, - CloudWatchDestination, - CloudWatchLogsSubscriptionFilter, - CloudWatchToS3Firehose, - KmsEncryption, - NewCloudWatchLogEvent, - S3PublicAccessBlock, - SsmParameterLookup, - ValidateBucket, - PutSsmParameter, - BucketPolicyProps, - ServiceLinkedRole, - CloudWatchLogDataProtection, -} from '@aws-accelerator/constructs'; - -import { - AcceleratorImportedBucketType, - AwsPrincipalAccessesType, - BucketAccessType, - PrincipalOrgIdConditionType, -} from '@aws-accelerator/utils/lib/common-resources'; -import { AcceleratorElbRootAccounts, OptInRegions } from '@aws-accelerator/utils/lib/regions'; - -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - CloudWatchDataProtectionIdentifiers, - NagSuppressionRuleIds, -} from './accelerator-stack'; - -export type cloudwatchExclusionProcessedItem = { - account: string; - region: string; - excludeAll?: boolean; - logGroupNames?: string[]; -}; - -type excludeUniqueItemType = { account: string; region: string }; - -type CentralLogsBucketPrincipalAndPrefixesType = { - awsPrincipalAccesses: AwsPrincipalAccessesType[]; - bucketPrefixes: string[]; -}; - -type PolicyAttachmentsType = { - policy: string; -}; - -export class LoggingStack extends AcceleratorStack { - private cloudwatchKey: cdk.aws_kms.IKey | undefined; - private lambdaKey: cdk.aws_kms.IKey | undefined; - private centralLogsBucket: CentralLogsBucket | undefined; - private centralLogBucketKey: cdk.aws_kms.IKey | undefined; - private centralSnsKey: cdk.aws_kms.IKey | undefined; - private snsForwarderFunction: cdk.aws_lambda.IFunction | undefined; - private importedCentralLogBucket: cdk.aws_s3.IBucket | undefined; - private importedCentralLogBucketKey: cdk.aws_kms.IKey | undefined; - - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - // Get principal organization condition - const principalOrgIdCondition = this.getPrincipalOrgIdCondition(this.organizationId); - - // Create S3 Key in all account - const s3Key = this.createS3Key(); - - // - // Create Managed active directory admin user secrets key - // - this.createManagedDirectoryAdminSecretsManagerKey(); - - // - // Create CloudWatch key - // - this.cloudwatchKey = this.createCloudWatchKey(props); - - // - // Create Lambda key - // - this.lambdaKey = this.createLambdaKey(props); - - // - // Create Auto scaling service linked role - // - const autoScalingSlr = this.createAutoScalingServiceLinkedRole({ - cloudwatch: this.cloudwatchKey, - lambda: this.lambdaKey, - }); - - // - // Create AWS Cloud9 service linked role - // - const cloud9Slr = this.createAwsCloud9ServiceLinkedRole({ cloudwatch: this.cloudwatchKey, lambda: this.lambdaKey }); - - // - // Create KMS keys defined in config - this.createKeys(autoScalingSlr, cloud9Slr); - - // Create Notification Role for FMS Notifications if enabled - this.createFMSNotificationRole(); - - // - // Configure block S3 public access - // - this.configureS3PublicAccessBlock(props); - - // - // SNS Topics creation - // - this.createSnsTopics(props); - - // - // Create S3 Bucket for Access Logs - this is required - // - const serverAccessLogsBucket = this.createOrGetServerAccessLogBucket(); - - // - // Create or get existing central log bucket - this.createOrGetCentralLogsBucket(serverAccessLogsBucket!, principalOrgIdCondition); - - // - // Get central log bucket encryption key - // - this.centralLogBucketKey = this.getCentralLogBucketKey(); - - const replicationProps: BucketReplicationProps = { - destination: { - bucketName: this.centralLogsBucketName, - accountId: this.props.accountsConfig.getLogArchiveAccountId(), - keyArn: this.centralLogBucketKey.keyArn, - }, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - useExistingRoles: this.props.useExistingRoles ?? false, - acceleratorPrefix: this.props.prefixes.accelerator, - }; - - // - // Create VPC Flow logs destination bucket - this.createVpcFlowLogsBucket(replicationProps, s3Key, serverAccessLogsBucket); - - // - // Create or get ELB access logs bucket - // - this.createOrGetElbAccessLogsBucket(principalOrgIdCondition, replicationProps); - - // - // Configure CloudWatchLogs to S3 replication - // - this.configureCloudWatchLogReplication(props); - - // - // Set certificate assets - // - this.setupCertificateAssets(props, principalOrgIdCondition); - - // - // Create Metadata Bucket - // - this.createMetadataBucket(serverAccessLogsBucket); - - // - // Create SSM Parameters - // - this.createSsmParameters(); - - // - // Configure Account level CloudWatch Log data protection policy - // - this.configureAccountDataProtectionPolicy(); - - this.logger.debug(`Stack synthesis complete`); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - } - - /** - * Function to configure CloudWatch log replication - * @param props {@link AcceleratorStackProps} - * - * @remarks - * First, logs receiving account will setup Kinesis DataStream and Firehose in LogArchive account home region KMS to encrypt Kinesis, Firehose and any Lambda environment variables for CloudWatchLogs to S3 replication - * - * CloudWatch logs replication requires Kinesis Data stream, Firehose and AWS Organizations. - * Some or all of these services may not be available in all regions. - * Only deploy in standard and GovCloud partitions - * - * Check to see if users specified enable on CloudWatch logs in global config. - * Defaults to true if undefined. If set to false, no resources are created. - */ - private configureCloudWatchLogReplication(props: AcceleratorStackProps): void { - if (props.globalConfig.logging.cloudwatchLogs?.enable ?? true) { - if (props.partition === 'aws' || props.partition === 'aws-us-gov' || props.partition === 'aws-cn') { - if (cdk.Stack.of(this).account === props.accountsConfig.getLogArchiveAccountId()) { - const receivingLogs = this.cloudwatchLogReceivingAccount(this.centralLogsBucketName, this.lambdaKey); - const creatingLogs = this.cloudwatchLogCreatingAccount(); - - // Log receiving setup should be complete before logs creation setup can start or else there will be errors about destination not ready. - creatingLogs.node.addDependency(receivingLogs); - } else { - // Any account in LZA needs to setup log subscriptions for CloudWatch Logs - // The destination needs to be present before its setup - this.cloudwatchLogCreatingAccount(); - } - } - } - } - - /** - * Function to create or get ELB access log bucket - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - * @param replicationProps {@link BucketReplicationProps} - * @returns - */ - private createOrGetElbAccessLogsBucket( - principalOrgIdCondition: PrincipalOrgIdConditionType, - replicationProps?: BucketReplicationProps, - ): cdk.aws_s3.IBucket | undefined { - /** - * Create S3 Bucket for ELB Access Logs, this is created in log archive account - * For ELB to write access logs bucket is needed to have SSE-S3 server-side encryption - */ - if (cdk.Stack.of(this).account === this.props.accountsConfig.getLogArchiveAccountId()) { - const elbAccountId = this.getElbAccountId(); - if (this.props.globalConfig.logging.elbLogBucket?.importedBucket) { - const bucket = this.getImportedBucket( - this.props.globalConfig.logging.elbLogBucket.importedBucket.name, - AcceleratorImportedBucketType.ELB_LOGS_BUCKET, - 's3', - ).bucket; - - this.updateImportedBucketResourcePolicy({ - bucketConfig: this.props.globalConfig.logging.elbLogBucket, - importedBucket: bucket, - bucketType: AcceleratorImportedBucketType.ELB_LOGS_BUCKET, - overridePolicyFile: this.props.globalConfig.logging.elbLogBucket.customPolicyOverrides?.policy, - principalOrgIdCondition, - elbAccountId: elbAccountId, - organizationId: this.organizationId, - }); - - return bucket; - } else { - return this.createElbAccessLogsBucket(replicationProps, elbAccountId); - } - } - return undefined; - } - - /** - * Function to get ELB account id - * @returns - */ - private getElbAccountId() { - let elbAccountId = undefined; - if (AcceleratorElbRootAccounts.get(cdk.Stack.of(this).region)) { - elbAccountId = AcceleratorElbRootAccounts.get(cdk.Stack.of(this).region); - } - if (this.props.networkConfig.elbAccountIds?.find(item => item.region === cdk.Stack.of(this).region)) { - elbAccountId = this.props.networkConfig.elbAccountIds?.find( - item => item.region === cdk.Stack.of(this).region, - )!.accountId; - } - - return elbAccountId; - } - - /** - * Function to create ELB access logs bucket - * @param replicationProps {@link BucketReplicationProps} - * @param elbAccountId string - * - * @returns bucket {@link cdk.aws_s3.IBucket} | undefined - * - * @remarks - * Create S3 Bucket for ELB Access Logs, this is created in log archive account. - * For ELB to write access logs bucket is needed to have SSE-S3 server-side encryption - */ - private createElbAccessLogsBucket( - replicationProps?: BucketReplicationProps, - elbAccountId?: string, - ): cdk.aws_s3.IBucket | undefined { - const elbAccessLogsBucket = new Bucket(this, 'ElbAccessLogsBucket', { - encryptionType: BucketEncryptionType.SSE_S3, // ELB Access Logs bucket does not support SSE-KMS - s3BucketName: this.getElbLogsBucketName(), - replicationProps, - s3LifeCycleRules: this.getS3LifeCycleRules(this.props.globalConfig.logging.elbLogBucket?.lifecycleRules), - }); - - // To make sure central log bucket created before elb access log bucket, this is required when logging stack executes in home region - if (this.centralLogsBucket) { - elbAccessLogsBucket.node.addDependency(this.centralLogsBucket); - } - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: - `/${this.stackName}/ElbAccessLogsBucket/ElbAccessLogsBucketReplication/` + - pascalCase(this.centralLogsBucketName) + - '-ReplicationRole/DefaultPolicy/Resource', - reason: 'Allows only specific policy.', - }, - ], - }); - - let elbPrincipal; - if (elbAccountId) { - elbPrincipal = new iam.AccountPrincipal(`${elbAccountId}`); - } else { - elbPrincipal = new iam.ServicePrincipal(`logdelivery.elasticloadbalancing.amazonaws.com`); - } - const policies = [ - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow get acl access for SSM principal', - effect: iam.Effect.ALLOW, - actions: ['s3:GetBucketAcl'], - principals: [new iam.ServicePrincipal('ssm.amazonaws.com')], - resources: [`${elbAccessLogsBucket.getS3Bucket().bucketArn}`], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow write access for ELB Account principal', - effect: iam.Effect.ALLOW, - actions: ['s3:PutObject'], - principals: [elbPrincipal], - resources: [`${elbAccessLogsBucket.getS3Bucket().bucketArn}/*`], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow write access for delivery logging service principal', - effect: iam.Effect.ALLOW, - actions: ['s3:PutObject'], - principals: [new iam.ServicePrincipal('delivery.logs.amazonaws.com')], - resources: [`${elbAccessLogsBucket.getS3Bucket().bucketArn}/*`], - conditions: { - StringEquals: { - 's3:x-amz-acl': 'bucket-owner-full-control', - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow read bucket ACL access for delivery logging service principal', - effect: iam.Effect.ALLOW, - actions: ['s3:GetBucketAcl'], - principals: [new iam.ServicePrincipal('delivery.logs.amazonaws.com')], - resources: [`${elbAccessLogsBucket.getS3Bucket().bucketArn}`], - }), - ]; - policies.forEach(item => { - elbAccessLogsBucket.getS3Bucket().addToResourcePolicy(item); - }); - - elbAccessLogsBucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization principals to use of the bucket', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetBucketLocation', 's3:PutObject'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [ - `${elbAccessLogsBucket.getS3Bucket().bucketArn}`, - `${elbAccessLogsBucket.getS3Bucket().bucketArn}/*`, - ], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `${this.stackName}/ElbAccessLogsBucket/Resource/Resource`, - reason: 'ElbAccessLogsBucket has server access logs disabled till the task for access logging completed.', - }, - ], - }); - - this.elbLogBucketAddResourcePolicies(elbAccessLogsBucket.getS3Bucket()); - - return elbAccessLogsBucket.getS3Bucket(); - } - - /** - * Function to get or create server access log bucket - * @returns bucket {@link cdk.aws_s3.IBucket} | undefined - */ - private createOrGetServerAccessLogBucket(): cdk.aws_s3.IBucket | undefined { - if (this.props.globalConfig.logging.accessLogBucket?.importedBucket) { - const bucket = this.getImportedBucket( - this.props.globalConfig.logging.accessLogBucket.importedBucket.name, - AcceleratorImportedBucketType.SERVER_ACCESS_LOGS_BUCKET, - 's3', - ).bucket; - - this.updateImportedBucketResourcePolicy({ - bucketConfig: this.props.globalConfig.logging.accessLogBucket, - importedBucket: bucket, - bucketType: AcceleratorImportedBucketType.SERVER_ACCESS_LOGS_BUCKET, - overridePolicyFile: this.props.globalConfig.logging.accessLogBucket.customPolicyOverrides?.policy, - organizationId: this.organizationId, - }); - - return bucket; - } - - if (!this.isAccessLogsBucketEnabled) { - this.logger.info( - `AWS S3 access log bucket disable for ${cdk.Stack.of(this).account} account in ${ - cdk.Stack.of(this).region - } region, server access logs bucket creation excluded`, - ); - return undefined; - } - - return this.createServerAccessLogBucket(); - } - - /** - * Function to create server access log bucket. - * @returns bucket {@link cdk.aws_s3.IBucket} - */ - private createServerAccessLogBucket(): cdk.aws_s3.IBucket { - // - // Create S3 Bucket for Access Logs - this is required - // - const serverAccessLogsBucket = new Bucket(this, 'AccessLogsBucket', { - encryptionType: BucketEncryptionType.SSE_S3, // Server access logging does not support SSE-KMS - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.s3AccessLogs}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - s3LifeCycleRules: this.getS3LifeCycleRules(this.props.globalConfig.logging.accessLogBucket?.lifecycleRules), - }); - - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `${this.stackName}/AccessLogsBucket/Resource/Resource`, - reason: 'AccessLogsBucket has server access logs disabled till the task for access logging completed.', - }, - ], - }); - - serverAccessLogsBucket.getS3Bucket().addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'Allow write access for logging service principal', - effect: iam.Effect.ALLOW, - actions: ['s3:PutObject'], - principals: [new iam.ServicePrincipal('logging.s3.amazonaws.com')], - resources: [serverAccessLogsBucket.getS3Bucket().arnForObjects('*')], - conditions: { - StringEquals: { - 'aws:SourceAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - - return serverAccessLogsBucket.getS3Bucket(); - } - - /** - * Function to get existing or solution defined Central Log Bucket Encryption Key - * @returns cdk.aws_kms.IKey - * - * @remarks - * For stacks in logging account and centralizedLoggingRegion region, bucket will be present to get key arn. - * All other environment stacks will need custom resource to get key arn from ssm parameter. - */ - private getCentralLogBucketKey(): cdk.aws_kms.IKey { - if (this.props.globalConfig.logging.centralLogBucket?.importedBucket?.name) { - if (this.importedCentralLogBucket) { - return this.importedCentralLogBucketKey!; - } else { - return this.getAcceleratorKey(AcceleratorKeyType.IMPORTED_CENTRAL_LOG_BUCKET, this.cloudwatchKey)!; - } - } else { - if (this.centralLogsBucket) { - return this.centralLogsBucket.getS3Bucket().getKey(); - } else { - return this.getAcceleratorKey(AcceleratorKeyType.CENTRAL_LOG_BUCKET, this.cloudwatchKey)!; - } - } - } - /** - * Function to create CloudWatch key - * @param props {@link AcceleratorStackProps} - * @returns cdk.aws_kms.IKey - */ - private createCloudWatchKey(props: AcceleratorStackProps): cdk.aws_kms.IKey | undefined { - if (!this.isCloudWatchLogsGroupCMKEnabled) { - this.logger.info( - `CloudWatch Encryption CMK disable for ${cdk.Stack.of(this).account} account in ${ - cdk.Stack.of(this).region - } region, CMK creation excluded`, - ); - return undefined; - } - // Create kms key for CloudWatch logs the CloudWatch key. Management account home region this key was created in prepare stack - if ( - cdk.Stack.of(this).account === props.accountsConfig.getManagementAccountId() && - (cdk.Stack.of(this).region === this.props.globalConfig.homeRegion || - cdk.Stack.of(this).region === this.props.globalRegion) - ) { - return this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - } else { - const cloudwatchKey = new cdk.aws_kms.Key(this, 'AcceleratorCloudWatchKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.cloudWatchLog.alias, - description: this.acceleratorResourceNames.customerManagedKeys.cloudWatchLog.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - cloudwatchKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [ - new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - ], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${this.props.partition}:logs:${ - cdk.Stack.of(this).region - }:*:log-group:*`, - }, - }, - }), - ); - - cloudwatchKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow EventBridge to send to encrypted CloudWatch log groups`, - principals: [new cdk.aws_iam.ServicePrincipal('events.amazonaws.com')], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEqualsIfExists: { - 'aws:SourceAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - - this.ssmParameters.push({ - logicalId: 'AcceleratorCloudWatchKmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.cloudWatchLogCmkArn, - stringValue: cloudwatchKey.keyArn, - }); - - return cloudwatchKey; - } - } - - /** - * Function to create Lambda key - * @param props {@link AcceleratorStackProps} - * @returns cdk.aws_kms.IKey - */ - private createLambdaKey(props: AcceleratorStackProps): cdk.aws_kms.IKey | undefined { - if (!this.isLambdaCMKEnabled) { - this.logger.info( - `Lambda Encryption CMK disable for ${cdk.Stack.of(this).account} account in ${ - cdk.Stack.of(this).region - } region, CMK creation excluded`, - ); - return undefined; - } - // Create kms key for Lambda environment encryption - // the Lambda environment encryption key for the management account - // in the home region is created in the prepare stack - if ( - cdk.Stack.of(this).account === props.accountsConfig.getManagementAccountId() && - (cdk.Stack.of(this).region === this.props.globalConfig.homeRegion || - cdk.Stack.of(this).region === this.props.globalRegion) - ) { - return this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - } else { - const key = new cdk.aws_kms.Key(this, 'AcceleratorLambdaKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.lambda.alias, - description: this.acceleratorResourceNames.customerManagedKeys.lambda.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - this.ssmParameters.push({ - logicalId: 'AcceleratorLambdaKmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.lambdaCmkArn, - stringValue: key.keyArn, - }); - - return key; - } - } - - /** - * Function to configure block S3 public access - * @param props {@link AcceleratorStackProps} - * @returns S3PublicAccessBlock | undefined - */ - private configureS3PublicAccessBlock(props: AcceleratorStackProps): S3PublicAccessBlock | undefined { - // - // Block Public Access; S3 is global, only need to call in home region. This is done in the - // logging-stack instead of the security-stack since initial buckets are created in this stack. - // - let s3PublicAccessBlock: S3PublicAccessBlock | undefined; - if ( - cdk.Stack.of(this).region === this.props.globalConfig.homeRegion && - !this.isAccountExcluded(props.securityConfig.centralSecurityServices.s3PublicAccessBlock.excludeAccounts ?? []) - ) { - if (props.securityConfig.centralSecurityServices.s3PublicAccessBlock.enable) { - s3PublicAccessBlock = new S3PublicAccessBlock(this, 'S3PublicAccessBlock', { - blockPublicAcls: true, - blockPublicPolicy: true, - ignorePublicAcls: true, - restrictPublicBuckets: true, - accountId: cdk.Stack.of(this).account, - kmsKey: this.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - - return s3PublicAccessBlock; - } - - /** - * Function to create SNS topics - * @param props {@link AcceleratorStackProps} - */ - private createSnsTopics(props: AcceleratorStackProps): void { - // SNS Topics creation - if ( - props.globalConfig.snsTopics && - cdk.Stack.of(this).account === props.accountsConfig.getLogArchiveAccountId() && - !this.isRegionExcluded(props.globalConfig.snsTopics?.deploymentTargets.excludedRegions ?? []) - ) { - this.createCentralSnsKey(); - - for (const snsTopic of props.globalConfig.snsTopics?.topics ?? []) { - this.createLoggingAccountSnsTopic(snsTopic, this.centralSnsKey!); - } - } - - if ( - this.isIncluded(props.globalConfig.snsTopics?.deploymentTargets ?? new t.DeploymentTargets()) && - cdk.Stack.of(this).account !== props.accountsConfig.getLogArchiveAccountId() - ) { - const snsKey = this.createSnsKey(); - this.createSnsForwarderFunction(); - for (const snsTopic of props.globalConfig.snsTopics?.topics ?? []) { - this.createSnsTopic(snsTopic, snsKey); - } - } - } - - /** - * Function to create S3 Key - * @returns cdk.aws_kms.IKey | undefined - */ - private createS3Key(): cdk.aws_kms.IKey | undefined { - if (!this.isS3CMKEnabled) { - this.logger.info( - `AWS S3 Encryption CMK disable for ${cdk.Stack.of(this).account} account in ${ - cdk.Stack.of(this).region - } region, CMK creation excluded`, - ); - return undefined; - } - // - // Crete S3 key in every account except audit account, - // this is required for SSM automation to get right KMS key to encrypt unencrypted bucket - if (cdk.Stack.of(this).account !== this.props.accountsConfig.getAuditAccountId()) { - this.logger.debug(`Create S3 Key`); - const s3Key = new cdk.aws_kms.Key(this, 'Accelerator3Key', { - alias: this.acceleratorResourceNames.customerManagedKeys.s3.alias, - description: this.acceleratorResourceNames.customerManagedKeys.s3.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow S3 to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:ViaService': `s3.${cdk.Stack.of(this).region}.amazonaws.com`, - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow AWS Services to encrypt and describe logs', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyPair', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - ], - principals: [new cdk.aws_iam.ServicePrincipal(`delivery.logs.amazonaws.com`)], - resources: ['*'], - }), - ); - - this.ssmParameters.push({ - logicalId: 'AcceleratorS3KmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.s3CmkArn, - stringValue: s3Key.keyArn, - }); - - return s3Key; - } else { - return this.createAuditAccountS3Key(); - } - } - - /** - * Function to create Audit account S3 bucket encryption Key - * @returns cdk.aws_kms.IKey | undefined - */ - private createAuditAccountS3Key(): cdk.aws_kms.IKey | undefined { - if (!this.isS3CMKEnabled) { - this.logger.info( - `AWS S3 Encryption CMK disable for ${cdk.Stack.of(this).account} account in ${ - cdk.Stack.of(this).region - } region, CMK creation excluded`, - ); - return undefined; - } - this.logger.debug(`Create S3 Key`); - const s3Key = new cdk.aws_kms.Key(this, 'AcceleratorAuditS3Key', { - alias: this.acceleratorResourceNames.customerManagedKeys.s3.alias, - description: this.acceleratorResourceNames.customerManagedKeys.s3.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow S3 to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:ViaService': `s3.${cdk.Stack.of(this).region}.amazonaws.com`, - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow services to confirm encryption', - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Decrypt', 'kms:GenerateDataKey'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - const allowedServicePrincipals: { name: string; principal: string }[] = []; - - allowedServicePrincipals.push({ name: 'CloudTrail', principal: 'cloudtrail.amazonaws.com' }); - - if (this.props.securityConfig.centralSecurityServices.auditManager?.enable) { - allowedServicePrincipals.push({ name: 'AuditManager', principal: 'auditmanager.amazonaws.com' }); - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Audit Manager service to provision encryption key grants`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:CreateGrant'], - conditions: { - StringLike: { - 'kms:ViaService': 'auditmanager.*.amazonaws.com', - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - Bool: { 'kms:GrantIsForAWSResource': 'true' }, - }, - resources: ['*'], - }), - ); - } - - allowedServicePrincipals.forEach(item => { - s3Key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow ${item.name} service to use the encryption key`, - principals: [new cdk.aws_iam.ServicePrincipal(item.principal)], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - }), - ); - }); - - this.ssmParameters.push({ - logicalId: 'AcceleratorS3KmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.s3CmkArn, - stringValue: s3Key.keyArn, - }); - - return s3Key; - } - - /** - * Function to get VPC flow logs configuration when any VPC have S3 flow logs destination - */ - private getS3FlowLogsDestinationConfig(): VpcFlowLogsConfig | undefined { - let vpcFlowLogs: VpcFlowLogsConfig | undefined; - for (const vpcItem of [...this.props.networkConfig.vpcs, ...(this.props.networkConfig.vpcTemplates ?? [])] ?? []) { - // Get account IDs - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - if (vpcAccountIds.includes(cdk.Stack.of(this).account) && vpcItem.region === cdk.Stack.of(this).region) { - if (vpcItem.vpcFlowLogs) { - vpcFlowLogs = vpcItem.vpcFlowLogs; - } else { - vpcFlowLogs = this.props.networkConfig.vpcFlowLogs; - } - if (vpcFlowLogs && vpcFlowLogs.destinations.includes('s3')) { - return vpcFlowLogs; - } - } - } - return undefined; - } - - /** - * Function to create VPC FlowLogs bucket. - * This bucket depends on Central Logs bucket and Server access logs bucket. - * This bucket also depends on local S3 key. - * @param replicationProps {@link BucketReplicationProps} - * @param s3Key {@link cdk.aws_kms.IKey} | undefined - * @param serverAccessLogsBucket {@link cdk.aws_s3.IBucket} | undefined - */ - private createVpcFlowLogsBucket( - replicationProps: BucketReplicationProps, - s3Key?: cdk.aws_kms.IKey, - serverAccessLogsBucket?: cdk.aws_s3.IBucket, - ) { - const vpcFlowLogsConfig = this.getS3FlowLogsDestinationConfig(); - if (vpcFlowLogsConfig) { - this.logger.info(`Create S3 bucket for VPC flow logs destination`); - - const vpcFlowLogsBucket = new Bucket(this, 'AcceleratorVpcFlowLogsBucket', { - encryptionType: this.isS3CMKEnabled ? BucketEncryptionType.SSE_KMS : BucketEncryptionType.SSE_S3, - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.vpcFlowLogs}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - kmsKey: s3Key, - serverAccessLogsBucket, - s3LifeCycleRules: this.getS3LifeCycleRules(vpcFlowLogsConfig.destinationsConfig?.s3?.lifecycleRules), - replicationProps: replicationProps, - }); - - if (!serverAccessLogsBucket) { - // AwsSolutions-S1: The S3 Bucket has server access logs disabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `/${this.stackName}/AcceleratorVpcFlowLogsBucket/Resource/Resource`, - reason: 'Due to configuration settings, server access logs have been disabled.', - }, - ], - }); - } - - vpcFlowLogsBucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow read bucket ACL access for delivery logging service principal', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetBucketAcl'], - principals: [new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com')], - resources: [`${vpcFlowLogsBucket.getS3Bucket().bucketArn}`], - }), - ); - - vpcFlowLogsBucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - principals: [new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com')], - actions: ['s3:GetBucketAcl', 's3:ListBucket'], - resources: [vpcFlowLogsBucket.getS3Bucket().bucketArn], - }), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: - `${this.stackName}/AcceleratorVpcFlowLogsBucket/AcceleratorVpcFlowLogsBucketReplication/` + - pascalCase(this.centralLogsBucketName) + - '-ReplicationRole/DefaultPolicy/Resource', - reason: 'Allows only specific policy.', - }, - ], - }); - - this.ssmParameters.push({ - logicalId: 'AcceleratorVpcFlowLogsBucketArnParameter', - parameterName: this.acceleratorResourceNames.parameters.flowLogsDestinationBucketArn, - stringValue: vpcFlowLogsBucket.getS3Bucket().bucketArn, - }); - } - } - - private cloudwatchLogReceivingAccount(centralLogsBucketName: string, lambdaKey?: cdk.aws_kms.IKey) { - const logsReplicationKmsKey = new cdk.aws_kms.Key(this, 'LogsReplicationKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.cloudWatchLogReplication.alias, - description: this.acceleratorResourceNames.customerManagedKeys.cloudWatchLogReplication.description, - enableKeyRotation: true, - // kms is used to encrypt kinesis data stream, - // unlike data store like s3, rds, dynamodb no snapshot/object is encrypted - // it can be destroyed as encrypts service - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - // // Create Kinesis Data Stream - // Kinesis Stream - data stream which will get data from CloudWatch logs - const logsKinesisStreamCfn = new cdk.aws_kinesis.CfnStream(this, 'LogsKinesisStreamCfn', { - retentionPeriodHours: 24, - shardCount: 1, - streamEncryption: { - encryptionType: 'KMS', - keyId: logsReplicationKmsKey.keyArn, - }, - }); - const logsKinesisStream = cdk.aws_kinesis.Stream.fromStreamArn( - this, - 'LogsKinesisStream', - logsKinesisStreamCfn.attrArn, - ); - - // LogsKinesisStream/Resource AwsSolutions-KDS3 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.KDS3, - details: [ - { - path: `${this.stackName}/LogsKinesisStreamCfn`, - reason: 'Customer managed key is being used to encrypt Kinesis Data Stream', - }, - ], - }); - - // Cloudwatch logs destination which points to Kinesis Data Stream - const cloudwatchCfnDestination = new CloudWatchDestination(this, 'LogsDestinationSetup', { - kinesisKmsKey: logsReplicationKmsKey, - kinesisStream: logsKinesisStream, - organizationId: this.organizationId, - partition: this.props.partition, - accountIds: - this.props.partition === 'aws-cn' || !this.organizationId - ? this.props.accountsConfig.getAccountIds() - : undefined, - acceleratorPrefix: this.props.prefixes.accelerator, - useExistingRoles: this.props.useExistingRoles ?? false, - }); - - // Setup Firehose to take records from Kinesis and place in S3 - // Dynamic partition incoming records - // so files from particular log group can be placed in their respective S3 prefix - const cloudWatchToS3Firehose = new CloudWatchToS3Firehose(this, 'FirehoseToS3Setup', { - dynamicPartitioningValue: this.props.globalConfig.logging.cloudwatchLogs?.dynamicPartitioning ?? undefined, - bucketName: centralLogsBucketName, - kinesisStream: logsKinesisStream, - firehoseKmsKey: this.centralLogBucketKey!, // for firehose to access s3 - kinesisKmsKey: logsReplicationKmsKey, // for firehose to access kinesis - homeRegion: this.props.globalConfig.homeRegion, - lambdaKey: lambdaKey, // to encrypt lambda environment - configDir: this.props.configDirPath, - acceleratorPrefix: this.props.prefixes.accelerator, - useExistingRoles: this.props.useExistingRoles ?? false, - firehoseRecordsProcessorFunctionName: - this.acceleratorResourceNames.parameters.firehoseRecordsProcessorFunctionName, - logsKmsKey: this.cloudwatchKey, - logsRetentionInDaysValue: this.props.globalConfig.cloudwatchLogRetentionInDays.toString(), - }); - - if (this.centralLogsBucket) { - cloudWatchToS3Firehose.node.addDependency(this.centralLogsBucket); - } - - return cloudwatchCfnDestination; - } - private cloudwatchLogCreatingAccount() { - const logsDestinationArnValue = - 'arn:' + - this.props.partition + - ':logs:' + - cdk.Stack.of(this).region + - ':' + - this.props.accountsConfig.getLogArchiveAccountId() + - ':destination:' + - `${this.props.prefixes.accelerator}CloudWatchToS3`; - - // Since this is deployed organization wide, this role is required - // https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CreateSubscriptionFilter-IAMrole.html - const subscriptionFilterRole = new cdk.aws_iam.Role(this, 'SubscriptionFilterRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - description: 'Role used by Subscription Filter to allow access to CloudWatch Destination', - inlinePolicies: { - accessLogEvents: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - resources: ['*'], - actions: ['logs:PutLogEvents'], - }), - ], - }), - }, - }); - - const exclusionAccountMap: cloudwatchExclusionProcessedItem[] = this.prepareCloudWatchExclusionList( - this.props.globalConfig.logging.cloudwatchLogs?.exclusions ?? [], - ); - let accountRegionExclusion: cloudwatchExclusionProcessedItem | undefined; - if (exclusionAccountMap.length > 0) { - const accountSpecificExclusion = exclusionAccountMap.filter(obj => { - return obj.account === cdk.Stack.of(this).account && obj.region === cdk.Stack.of(this).region; - }); - if (accountSpecificExclusion.length > 1) { - this.logger.error( - `(Multiple cloudwatch exclusions ${JSON.stringify(accountSpecificExclusion)} found for account: ${ - cdk.Stack.of(this).account - } in region: ${cdk.Stack.of(this).region}`, - ); - } else { - accountRegionExclusion = exclusionAccountMap.find(obj => { - return obj.account === cdk.Stack.of(this).account && obj.region === cdk.Stack.of(this).region; - }); - } - } - // Run a custom resource to update subscription, KMS and retention for all existing log groups - const customResourceExistingLogs = new CloudWatchLogsSubscriptionFilter(this, 'LogsSubscriptionFilter', { - logDestinationArn: logsDestinationArnValue, - logsKmsKey: this.cloudwatchKey, - logArchiveAccountId: this.props.accountsConfig.getLogArchiveAccountId(), - logsRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays.toString(), - subscriptionFilterRoleArn: subscriptionFilterRole.roleArn, - logExclusionOption: accountRegionExclusion, - replaceLogDestinationArn: this.props.globalConfig.logging.cloudwatchLogs?.replaceLogDestinationArn, - acceleratorPrefix: this.props.prefixes.accelerator, - useExistingRoles: this.props.useExistingRoles ?? false, - }); - - //For every new log group that is created, set up subscription, KMS and retention - const newLogCreationEvent = new NewCloudWatchLogEvent(this, 'NewCloudWatchLogsCreateEvent', { - logDestinationArn: logsDestinationArnValue, - lambdaEnvKey: this.lambdaKey, - logsKmsKey: this.cloudwatchKey, - logArchiveAccountId: this.props.accountsConfig.getLogArchiveAccountId(), - logsRetentionInDaysValue: this.props.globalConfig.cloudwatchLogRetentionInDays.toString(), - subscriptionFilterRoleArn: subscriptionFilterRole.roleArn, - exclusionSetting: accountRegionExclusion!, - acceleratorPrefix: this.props.prefixes.accelerator, - useExistingRoles: this.props.useExistingRoles ?? false, - }); - - // create custom resource before the new log group logic is created. - newLogCreationEvent.node.addDependency(customResourceExistingLogs); - - // SubscriptionFilterRole AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/SubscriptionFilterRole/Resource`, - reason: 'Access is needed to ready all log events across all log groups for replication to S3.', - }, - ], - }); - - // SetLogRetentionSubscriptionFunction AwsSolutions-IAM4 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/SetLogRetentionSubscriptionFunction/ServiceRole/Resource`, - reason: 'AWS Managed policy for Lambda basic execution attached.', - }, - ], - }); - - // SetLogRetentionSubscriptionFunction AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/NewCloudWatchLogsCreateEvent/SetLogRetentionSubscriptionFunction/ServiceRole/DefaultPolicy/Resource`, - reason: - 'This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.', - }, - ], - }); - - // SetLogRetentionSubscriptionFunction AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/NewCloudWatchLogsCreateEvent/SetLogRetentionSubscriptionFunction/ServiceRole/Resource`, - reason: - 'This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.', - }, - ], - }); - - return customResourceExistingLogs; - } - - /** - * Function to get CloudWatch Exclusion Processed Items - * @param exclusionList {@link CloudWatchLogsExclusionConfig}[] - * @returns cloudwatchExclusionProcessedItem[] {@link cloudwatchExclusionProcessedItem} - */ - private getCloudWatchExclusionProcessedItems( - exclusionList: CloudWatchLogsExclusionConfig[], - ): cloudwatchExclusionProcessedItem[] { - const processedItems: cloudwatchExclusionProcessedItem[] = []; - for (const exclusion of exclusionList) { - processedItems.push(...this.convertCloudWatchExclusionToAccountIds(exclusion)); - } - return processedItems; - } - - /** - * Find the unique account, region pair in the given input - * @param processedItems {@link cloudwatchExclusionProcessedItem} - * @returns excludeUniqueItemType[] {@link excludeUniqueItemType} - */ - private getCloudWatchExcludeUniqueMap(processedItems: cloudwatchExclusionProcessedItem[]): excludeUniqueItemType[] { - const excludeItemsMapUnique: excludeUniqueItemType[] = []; - processedItems.forEach(item => { - const output = { account: item.account, region: item.region }; - const findItem = excludeItemsMapUnique.find(obj => { - return obj.account === output.account && obj.region === output.region; - }); - - if (!findItem) { - excludeItemsMapUnique.push(output); - } - }); - return excludeItemsMapUnique; - } - - private prepareCloudWatchExclusionList(exclusionList: CloudWatchLogsExclusionConfig[]) { - exclusionList.push({ - accounts: ['LogArchive'], - regions: this.props.globalConfig.enabledRegions, - logGroupNames: [`/aws/lambda/${this.props.prefixes.accelerator}-FirehoseRecordsProcessor`], - organizationalUnits: undefined, - excludeAll: undefined, - }); - - // Input will be an array of OUs and account. - // Decompose input to account Ids with single regions - const processedItems = this.getCloudWatchExclusionProcessedItems(exclusionList); - - const excludeItemsMapUnique = this.getCloudWatchExcludeUniqueMap(processedItems); - - const output: cloudwatchExclusionProcessedItem[] = []; - for (const uniqueElement of excludeItemsMapUnique) { - //pick objects from main array which match uniqueElement - const filteredItems: cloudwatchExclusionProcessedItem[] | undefined = processedItems.filter(item => { - return item.account === uniqueElement.account && item.region === uniqueElement.region; - }); - if (filteredItems) { - // merge excludeAll - if for an account/region there is even one excludeAll then exclude (like IAM policies do Deny) - // merge logGroupsNames - merge all arrays and run Set to remove duplicates - - const allLogGroupNames: string[] = []; - let globalExclude: boolean | undefined = undefined; - filteredItems.forEach(obj => { - if (obj.excludeAll) { - globalExclude = true; - } - }); - filteredItems.forEach(obj => { - if (obj.logGroupNames) { - allLogGroupNames.push(...obj.logGroupNames); - } - }); - output.push({ - account: uniqueElement.account, - region: uniqueElement.region, - excludeAll: globalExclude, - logGroupNames: Array.from(new Set(allLogGroupNames)), - }); - } - } - return output; - } - - private convertCloudWatchExclusionToAccountIds(exclusion: CloudWatchLogsExclusionConfig) { - const output: cloudwatchExclusionProcessedItem[] = []; - if (exclusion.organizationalUnits) { - const accountsNamesInOu = this.getAccountsFromOu(exclusion.organizationalUnits); - const getOuExclusionList: cloudwatchExclusionProcessedItem[] = - this.convertCloudWatchExclusionAccountsToAccountIds(accountsNamesInOu, exclusion); - - output.push(...getOuExclusionList); - } - if (exclusion.accounts) { - const getAccountExclusionList: cloudwatchExclusionProcessedItem[] = - this.convertCloudWatchExclusionAccountsToAccountIds(exclusion.accounts, exclusion); - output.push(...getAccountExclusionList); - } - return output; - } - private getAccountsFromOu(ouNames: string[]) { - const allAccounts = [...this.props.accountsConfig.mandatoryAccounts, ...this.props.accountsConfig.workloadAccounts]; - const allAccountNames: string[] = []; - if (ouNames.includes('Root')) { - // root means all accounts - for (const allAccountItem of allAccounts) { - allAccountNames.push(allAccountItem.name); - } - } else { - for (const ouName of ouNames) { - // look in all accounts for specific OU - for (const allAccountItem of allAccounts) { - if (ouName === allAccountItem.organizationalUnit) { - allAccountNames.push(allAccountItem.name); - } - } - } - } - return allAccountNames; - } - - private convertCloudWatchExclusionAccountsToAccountIds( - accountsList: string[], - exclusion: CloudWatchLogsExclusionConfig, - ) { - const output: cloudwatchExclusionProcessedItem[] = []; - for (const accountItem of accountsList) { - const outputItem: cloudwatchExclusionProcessedItem[] = this.reduceCloudWatchExclusionAccountByRegion( - accountItem, - exclusion, - ); - output.push(...outputItem); - } - return output; - } - private reduceCloudWatchExclusionAccountByRegion(accountItem: string, exclusion: CloudWatchLogsExclusionConfig) { - const processedItems: cloudwatchExclusionProcessedItem[] = []; - for (const regionItem of exclusion.regions ?? this.props.globalConfig.enabledRegions) { - const singleProcessedItem: cloudwatchExclusionProcessedItem = { - account: this.props.accountsConfig.getAccountId(accountItem), - region: regionItem, - excludeAll: exclusion.excludeAll, - logGroupNames: exclusion.logGroupNames, - }; - processedItems.push(singleProcessedItem); - } - return processedItems; - } - - /** - * Function to Create Managed active directory admin user secrets key - */ - private createManagedDirectoryAdminSecretsManagerKey() { - for (const managedActiveDirectory of this.props.iamConfig.managedActiveDirectories ?? []) { - if (this.isManagedByAseaGlobal(AseaResourceType.MANAGED_AD, managedActiveDirectory.name)) { - this.logger.info(`${managedActiveDirectory.name} is managed by ASEA, skipping creation of resources.`); - return; - } - const madAccountId = this.props.accountsConfig.getAccountId(managedActiveDirectory.account); - const madAdminSecretAccountId = this.props.accountsConfig.getAccountId( - this.props.iamConfig.getManageActiveDirectorySecretAccountName(managedActiveDirectory.name), - ); - const madAdminSecretRegion = this.props.iamConfig.getManageActiveDirectorySecretRegion( - managedActiveDirectory.name, - ); - - if (cdk.Stack.of(this).account == madAdminSecretAccountId && cdk.Stack.of(this).region == madAdminSecretRegion) { - const key = new cdk.aws_kms.Key(this, 'AcceleratorSecretsManagerKmsKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.secretsManager.alias, - description: this.acceleratorResourceNames.customerManagedKeys.secretsManager.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow MAD instance role to access the key`, - principals: [new cdk.aws_iam.AccountPrincipal(madAccountId)], - actions: ['kms:Decrypt'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:ViaService': `secretsmanager.${cdk.Stack.of(this).region}.amazonaws.com`, - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - StringLike: { - 'kms:EncryptionContext:SecretARN': `arn:${cdk.Stack.of(this).partition}:secretsmanager:${ - cdk.Stack.of(this).region - }:${madAdminSecretAccountId}:secret:${this.props.prefixes.secretName}/ad-user/*`, - }, - }, - }), - ); - - const secretsManagerKmsKeyArnParameter = new cdk.aws_ssm.StringParameter( - this, - 'AcceleratorSecretsManagerKmsKeyArnParameter', - { - parameterName: this.acceleratorResourceNames.parameters.secretsManagerCmkArn, - stringValue: key.keyArn, - }, - ); - - // Create role to give access to Secret manager KSM arn parameter, this will be used by MAD account to give access to this KMS for MAD instance - new cdk.aws_iam.Role(this, 'CrossAccountAcceleratorSecretsKmsArnSsmParamAccessRole', { - roleName: this.acceleratorResourceNames.roles.crossAccountSecretsCmkParameterAccess, - assumedBy: this.getOrgPrincipals(this.organizationId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [secretsManagerKmsKeyArnParameter.parameterArn], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:DescribeParameters'], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ], - }), - }, - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/CrossAccountAcceleratorSecretsKmsArnSsmParamAccessRole/Resource`, - reason: 'Cross account kms arn SSM parameter needs access from other accounts', - }, - ], - }); - - return; // Create only one kms key even if there are multiple AD - } - } - } - - /** - * Function to create KMS Keys defined in config file - */ - private createKeys(autoScalingSlr?: ServiceLinkedRole, cloud9Slr?: ServiceLinkedRole) { - if (!this.props.securityConfig.keyManagementService) { - return; - } - - for (const keyItem of this.props.securityConfig.keyManagementService.keySets) { - if (!this.isIncluded(keyItem.deploymentTargets)) { - this.logger.info(`KMS Key ${keyItem.name} excluded`); - continue; - } - this.logger.debug(`Create KMS Key ${keyItem.name}`); - - const key = new cdk.aws_kms.Key(this, 'AcceleratorKmsKey-' + pascalCase(keyItem.name), { - alias: keyItem.alias, - description: keyItem.description, - enabled: keyItem.enabled, - enableKeyRotation: keyItem.enableKeyRotation, - removalPolicy: keyItem.removalPolicy as cdk.RemovalPolicy, - }); - // Add dependency on service-linked roles - // This is required for KMS keys to reference SLRs - // in their key policies - if (autoScalingSlr) { - key.node.addDependency(autoScalingSlr.resource); - } - if (cloud9Slr) { - key.node.addDependency(cloud9Slr.resource); - } - - if (keyItem.policy) { - // Read in the policy document which should be properly formatted json - const policyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, keyItem.policy), - false, - this.organizationId, - ), - ); - - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - // Attach statements to key policy - statements.forEach(item => key.addToResourcePolicy(item)); - } - - // Create SSM parameter - this.ssmParameters.push({ - logicalId: 'AcceleratorKmsArnParameter-' + pascalCase(keyItem.name), - parameterName: `${this.props.prefixes.ssmParamName}/kms/${keyItem.name}/key-arn`, - stringValue: key.keyArn, - }); - - // AwsSolutions-S1: The KMS Symmetric key does not have automatic key rotation enabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `${this.stackName}` + '/AcceleratorKmsKey-' + pascalCase(keyItem.name) + `/Resource`, - reason: 'CMK policy defined by customer provided policy definition file.', - }, - ], - }); - } - } - - /** - * Create list of principal needs access to CentralLogs bucket - * @returns - */ - private createCentralLogsBucketPrincipalAndPrefixes(): CentralLogsBucketPrincipalAndPrefixesType { - const awsPrincipalAccesses: AwsPrincipalAccessesType[] = []; - const bucketPrefixes: string[] = []; - if (this.props.securityConfig.centralSecurityServices.macie.enable) { - awsPrincipalAccesses.push({ - name: 'Macie', - principal: 'macie.amazonaws.com', - accessType: BucketAccessType.READWRITE, - }); - } - - if (this.props.securityConfig.centralSecurityServices.guardduty.enable) { - awsPrincipalAccesses.push({ - name: 'Guardduty', - principal: 'guardduty.amazonaws.com', - accessType: BucketAccessType.READWRITE, - }); - let guardDutyPrefix: string | undefined = 'guardduty'; - if ( - this.props.securityConfig.centralSecurityServices.guardduty.exportConfiguration.overrideGuardDutyPrefix - ?.useCustomPrefix - ) { - guardDutyPrefix = - this.props.securityConfig.centralSecurityServices.guardduty.exportConfiguration.overrideGuardDutyPrefix - ?.customOverride ?? undefined; - } - if (guardDutyPrefix) { - bucketPrefixes.push(guardDutyPrefix); - } - - for (const region of this.props.globalConfig.enabledRegions) { - if (OptInRegions.includes(region)) { - awsPrincipalAccesses.push({ - name: `Guardduty-${region}`, - principal: `guardduty.${region}.amazonaws.com`, - accessType: BucketAccessType.READWRITE, - }); - let guardDutyPrefix: string | undefined = 'guardduty'; - if ( - !this.props.securityConfig.centralSecurityServices.guardduty.exportConfiguration.overrideGuardDutyPrefix - ?.useCustomPrefix - ) { - guardDutyPrefix = - this.props.securityConfig.centralSecurityServices.guardduty.exportConfiguration.overrideGuardDutyPrefix - ?.customOverride ?? undefined; - } - if (guardDutyPrefix) { - bucketPrefixes.push(guardDutyPrefix); - } - } - } - } - - if (this.props.securityConfig.centralSecurityServices.auditManager?.enable) { - awsPrincipalAccesses.push({ - name: 'AuditManager', - principal: 'auditmanager.amazonaws.com', - accessType: BucketAccessType.READWRITE, - }); - } - - if (this.props.globalConfig.logging.sessionManager.sendToS3) { - this.logger.debug(`Grant Session Manager access to Central Logs Bucket.`); - awsPrincipalAccesses.push({ - name: 'SessionManager', - principal: 'session-manager.amazonaws.com', - accessType: BucketAccessType.NO_ACCESS, - }); - } - - return { awsPrincipalAccesses: awsPrincipalAccesses, bucketPrefixes: bucketPrefixes }; - } - - /** - * Function to get existing bucket - * @param importedBucketName string - * @param bucketType {@link AcceleratorImportedBucketType} - * @param encryptionType 'kms' | 's3' - * @returns bucket {@link cdk.aws_s3.IBucket} - */ - private getImportedBucket( - importedBucketName: string, - bucketType: AcceleratorImportedBucketType, - encryptionType: 'kms' | 's3', - ): { bucket: cdk.aws_s3.IBucket; bucketKmsArn: string | undefined } { - // Get existing bucket - const bucket = cdk.aws_s3.Bucket.fromBucketName( - this, - pascalCase(`Imported${bucketType}Bucket`), - this.getBucketNameReplacement(importedBucketName), - ); - - const validateBucket = new ValidateBucket(this, pascalCase(`ValidateImported${bucketType}Bucket`), { - bucket: bucket, - validationCheckList: ['encryption'], - encryptionType, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - return { bucket: bucket, bucketKmsArn: validateBucket.bucketKmsArn }; - } - - private getExternalPolicyStatements(externalPolicyFilePaths: string[]): cdk.aws_iam.PolicyStatement[] { - const policyStatements: cdk.aws_iam.PolicyStatement[] = []; - for (const externalPolicyFilePath of externalPolicyFilePaths) { - const policyDocument = JSON.parse( - this.generatePolicyReplacements(externalPolicyFilePath, false, this.organizationId), - ); - - for (const statement of policyDocument.Statement) { - policyStatements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - } - - return policyStatements; - } - - /** - * Function to create kms policy statements for imported bucket - * @param overridePolicy boolean - * @param applyAcceleratorManagedPolicy boolean - * @param bucketType {@link AcceleratorImportedBucketType} - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - * @param centralLogsBucketPrincipalAndPrefixes {@link CentralLogsBucketPrincipalAndPrefixesType} - * @returns policyStatements {@link cdk.aws_iam.PolicyStatement}[] - */ - private createImportedBucketKmsPolicyStatements( - overridePolicy: boolean, - applyAcceleratorManagedPolicy: boolean, - bucketType: AcceleratorImportedBucketType, - principalOrgIdCondition: PrincipalOrgIdConditionType, - centralLogsBucketPrincipalAndPrefixes?: CentralLogsBucketPrincipalAndPrefixesType, - ): cdk.aws_iam.PolicyStatement[] { - if (overridePolicy) { - return []; - } - - const policyStatements: cdk.aws_iam.PolicyStatement[] = []; - - if (bucketType === AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET && applyAcceleratorManagedPolicy) { - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - sid: 'Enable IAM User Permissions', - principals: [new cdk.aws_iam.AccountRootPrincipal()], - actions: ['kms:*'], - resources: ['*'], - }), - ); - - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow S3 use of the key', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:GenerateRandom', - 'kms:GetKeyPolicy', - 'kms:GetKeyRotationStatus', - 'kms:ListAliases', - 'kms:ListGrants', - 'kms:ListKeyPolicies', - 'kms:ListKeys', - 'kms:ListResourceTags', - 'kms:ListRetirableGrants', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - ], - principals: [new cdk.aws_iam.ServicePrincipal('s3.amazonaws.com')], - resources: ['*'], - }), - ); - - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow AWS Services to encrypt and describe logs', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyPair', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - ], - principals: [ - new cdk.aws_iam.ServicePrincipal('config.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('cloudtrail.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('ssm.amazonaws.com'), - ], - resources: ['*'], - }), - ); - - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization use of the key', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyPair', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - 'kms:ListAliases', - ], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: ['*'], - conditions: { - StringEquals: { - ...principalOrgIdCondition, - }, - }, - }), - ); - - if (centralLogsBucketPrincipalAndPrefixes?.awsPrincipalAccesses) { - const awsPrincipalAccesses = centralLogsBucketPrincipalAndPrefixes.awsPrincipalAccesses; - // Allow bucket encryption key for given aws principals - awsPrincipalAccesses - .filter(item => item.accessType !== BucketAccessType.NO_ACCESS) - .forEach(item => { - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow ${item.name} service to use the encryption key`, - principals: [new cdk.aws_iam.ServicePrincipal(item.principal)], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - }), - ); - }); - } - } - - return policyStatements; - } - - private getExternalPolicyFilePaths( - overridePolicyFile?: string, - attachmentPolicies?: PolicyAttachmentsType[], - ): string[] { - const policyFilePaths: string[] = []; - - if (overridePolicyFile) { - return [ - this.generatePolicyReplacements( - path.join(this.props.configDirPath, overridePolicyFile), - true, - undefined, - `${cdk.Stack.of(this).account}-${cdk.Stack.of(this).region}-replaced-${path.parse(overridePolicyFile).base}`, - ), - ]; - } - for (const attachmentPolicy of attachmentPolicies ?? []) { - policyFilePaths.push( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, attachmentPolicy.policy), - true, - undefined, - `${cdk.Stack.of(this).account}-${cdk.Stack.of(this).region}-replaced-${ - path.parse(attachmentPolicy.policy).base - }`, - ), - ); - } - - return policyFilePaths; - } - - /** - * Function to update imported bucket encryption key - * @param options - */ - private updateImportedBucketEncryption(options: { - bucketConfig: CentralLogBucketConfig | AssetBucketConfig; - bucketType: AcceleratorImportedBucketType; - bucketItem: { bucket: cdk.aws_s3.IBucket; bucketKmsArn: string | undefined }; - principalOrgIdCondition: PrincipalOrgIdConditionType; - centralLogsBucketPrincipalAndPrefixes?: CentralLogsBucketPrincipalAndPrefixesType; - bucketKmsArnParameterName?: string; - organizationId?: string; - }) { - const applyAcceleratorManagedPolicy = - options.bucketConfig.importedBucket!.applyAcceleratorManagedBucketPolicy ?? false; - const createAcceleratorManagedKey = options.bucketConfig.importedBucket!.createAcceleratorManagedKey ?? false; - const externalPolicyFilePaths: string[] = []; - let overridePolicy = false; - let bucketKeyArn = options.bucketItem.bucketKmsArn; - - if (options.bucketConfig.customPolicyOverrides?.kmsPolicy) { - overridePolicy = true; - externalPolicyFilePaths.push( - ...this.getExternalPolicyFilePaths(options.bucketConfig.customPolicyOverrides.kmsPolicy), - ); - } else { - externalPolicyFilePaths.push( - ...this.getExternalPolicyFilePaths(undefined, options.bucketConfig.kmsResourcePolicyAttachments), - ); - } - - if (createAcceleratorManagedKey) { - const key = new cdk.aws_kms.Key(this, pascalCase(`Imported${options.bucketType}BucketKey`), { - enableKeyRotation: true, - alias: this.acceleratorResourceNames.customerManagedKeys.importedCentralLogsBucket.alias, - description: this.acceleratorResourceNames.customerManagedKeys.importedCentralLogsBucket.description, - }); - - bucketKeyArn = key.keyArn; - - const policyStatements: cdk.aws_iam.PolicyStatement[] = [ - ...this.getExternalPolicyStatements(externalPolicyFilePaths), - ]; - - policyStatements.push( - ...this.createImportedBucketKmsPolicyStatements( - overridePolicy, - applyAcceleratorManagedPolicy, - options.bucketType, - options.principalOrgIdCondition, - options.centralLogsBucketPrincipalAndPrefixes, - ), - ); - - for (const policyStatement of policyStatements) { - key.addToResourcePolicy(policyStatement); - } - - // Set bucket encryption with accelerator created key - new BucketEncryption(this, pascalCase(`Imported${options.bucketType}BucketEncryption`), { - bucket: options.bucketItem.bucket, - kmsKey: key, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } else { - if (externalPolicyFilePaths.length > 0 && options.bucketItem.bucketKmsArn) { - // Update imported bucket kms policy - new KmsEncryption(this, pascalCase(`Imported${options.bucketType}BucketKmsEncryption`), { - kmsArn: options.bucketItem.bucketKmsArn, - policyFilePaths: externalPolicyFilePaths, - organizationId: options.organizationId, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - - if (options.bucketKmsArnParameterName && options.bucketType === AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET) { - // Store existing central log bucket's encryption key arn in ssm parameter for future usage, - // parameter will be created in every account central logging region only - new PutSsmParameter(this, pascalCase(`PutImported${options.bucketType}BucketKmsArnParameter`), { - accountIds: [this.props.accountsConfig.getLogArchiveAccountId()], - region: this.props.centralizedLoggingRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - parameters: [ - { - name: options.bucketKmsArnParameterName, - value: bucketKeyArn!, - }, - ], - invokingAccountId: cdk.Stack.of(this).account, - acceleratorPrefix: this.props.prefixes.accelerator, - }); - - this.importedCentralLogBucketKey = cdk.aws_kms.Key.fromKeyArn( - this, - 'ImportedCentralLogsBucketKey', - bucketKeyArn!, - ); - } - } - - /** - * Function to update imported bucket's resource policy - * @param applyAcceleratorManagedPolicy boolean - * @param importedBucket {@link cdk.aws_s3.IBucket} - * @param bucketType {@link AcceleratorImportedBucketType} - * @param overridePolicyFile string - * @param s3ResourcePolicyAttachments {@link PolicyAttachmentsType}[] - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - * @param centralLogsBucketPrincipalAndPrefixes {@link CentralLogsBucketPrincipalAndPrefixesType} - * @param elbAccountId string - */ - private updateImportedBucketResourcePolicy(options: { - bucketConfig: CentralLogBucketConfig | ElbLogBucketConfig | AccessLogBucketConfig | AssetBucketConfig; - importedBucket: cdk.aws_s3.IBucket; - bucketType: AcceleratorImportedBucketType; - overridePolicyFile?: string; - principalOrgIdCondition?: PrincipalOrgIdConditionType; - centralLogsBucketPrincipalAndPrefixes?: CentralLogsBucketPrincipalAndPrefixesType; - elbAccountId?: string; - organizationId?: string; - }) { - const externalPolicyFilePaths: string[] = []; - - let attachmentPolicyFiles: PolicyAttachmentsType[] | undefined; - - if (!options.overridePolicyFile) { - attachmentPolicyFiles = options.bucketConfig.s3ResourcePolicyAttachments; - } - - externalPolicyFilePaths.push(...this.getExternalPolicyFilePaths(options.overridePolicyFile, attachmentPolicyFiles)); - - const applyAcceleratorManagedPolicy = - options.bucketConfig.importedBucket!.applyAcceleratorManagedBucketPolicy ?? false; - - let props: BucketPolicyProps = { - bucketType: AcceleratorImportedBucketType.SERVER_ACCESS_LOGS_BUCKET, - applyAcceleratorManagedPolicy, - bucket: options.importedBucket, - bucketPolicyFilePaths: externalPolicyFilePaths, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }; - - if (options.bucketType === AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET) { - props = { - bucketType: AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET, - applyAcceleratorManagedPolicy, - bucket: options.importedBucket, - bucketPolicyFilePaths: externalPolicyFilePaths, - principalOrgIdCondition: options.principalOrgIdCondition, - awsPrincipalAccesses: options.centralLogsBucketPrincipalAndPrefixes!.awsPrincipalAccesses, - organizationId: options.organizationId, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }; - } - - if (options.bucketType === AcceleratorImportedBucketType.ELB_LOGS_BUCKET) { - props = { - bucketType: AcceleratorImportedBucketType.ELB_LOGS_BUCKET, - applyAcceleratorManagedPolicy, - bucket: options.importedBucket, - bucketPolicyFilePaths: externalPolicyFilePaths, - principalOrgIdCondition: options.principalOrgIdCondition, - organizationId: options.organizationId, - elbAccountId: options.elbAccountId, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }; - } - - if (options.bucketType === AcceleratorImportedBucketType.ASSETS_BUCKET) { - props = { - bucketType: AcceleratorImportedBucketType.ASSETS_BUCKET, - applyAcceleratorManagedPolicy, - bucket: options.importedBucket, - bucketPolicyFilePaths: externalPolicyFilePaths, - principalOrgIdCondition: options.principalOrgIdCondition, - organizationId: options.organizationId, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - firewallRoles: [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-AssetsAccessRole`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-FirewallConfigAccessRole`, - ], - }; - } - new BucketPolicy(this, pascalCase(`Imported${options.bucketType}BucketPolicy`), props); - } - - /** - * Function to create or get existing CentralLogBucket - * @param serverAccessLogsBucket {@link Bucket} | {@link cdk.aws_s3.IBucket} - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - * - * @remarks - * When imported bucket is used solution will lookup the existing bucket else solution will deploy central log bucket. - */ - private createOrGetCentralLogsBucket( - serverAccessLogsBucket: cdk.aws_s3.IBucket, - principalOrgIdCondition: PrincipalOrgIdConditionType, - ) { - if ( - cdk.Stack.of(this).region === this.props.centralizedLoggingRegion && - cdk.Stack.of(this).account === this.props.accountsConfig.getLogArchiveAccountId() - ) { - const centralLogsBucketPrincipalAndPrefixes = this.createCentralLogsBucketPrincipalAndPrefixes(); - - if (this.props.globalConfig.logging.centralLogBucket?.importedBucket) { - const importedBucketItem = this.getImportedBucket( - this.props.globalConfig.logging.centralLogBucket.importedBucket.name, - AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET, - 'kms', - ); - this.importedCentralLogBucket = importedBucketItem.bucket; - - this.updateImportedBucketEncryption({ - bucketConfig: this.props.globalConfig.logging.centralLogBucket, - bucketType: AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET, - bucketItem: importedBucketItem, - principalOrgIdCondition, - centralLogsBucketPrincipalAndPrefixes, - bucketKmsArnParameterName: this.acceleratorResourceNames.parameters.importedCentralLogBucketCmkArn, - organizationId: this.organizationId, - }); - - this.updateImportedBucketResourcePolicy({ - bucketConfig: this.props.globalConfig.logging.centralLogBucket, - importedBucket: this.importedCentralLogBucket, - bucketType: AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET, - overridePolicyFile: this.props.globalConfig.logging.centralLogBucket.customPolicyOverrides?.s3Policy, - principalOrgIdCondition, - centralLogsBucketPrincipalAndPrefixes, - organizationId: this.organizationId, - }); - - this.createImportedLogBucketPrefixes( - this.importedCentralLogBucket, - centralLogsBucketPrincipalAndPrefixes.bucketPrefixes, - ); - } else { - this.createCentralLogsBucket(serverAccessLogsBucket, centralLogsBucketPrincipalAndPrefixes); - } - } - } - - /** - * Function to create CentralLogs bucket in LogArchive account home region only. - * @param serverAccessLogsBucket cdk.aws_s3.IBucket | undefined - * - * @remarks - * When existing central log bucket not used then create Central Logs Bucket - This is done only in the home region of the log-archive account. - * This is the destination bucket for all logs such as AWS CloudTrail, AWS Config, and VPC Flow logs. - * Addition logs can also be sent to this bucket through AWS CloudWatch Logs, such as application logs, OS logs, or server logs. - */ - private createCentralLogsBucket( - serverAccessLogsBucket: cdk.aws_s3.IBucket, - centralLogsBucketPrincipalAndPrefixes: { - awsPrincipalAccesses: AwsPrincipalAccessesType[]; - bucketPrefixes: string[]; - }, - ) { - if ( - cdk.Stack.of(this).region === this.props.centralizedLoggingRegion && - cdk.Stack.of(this).account === this.props.accountsConfig.getLogArchiveAccountId() - ) { - const bucketPrefixProps: BucketPrefixProps = { - source: { - bucketName: this.centralLogsBucketName, - }, - bucketPrefixes: centralLogsBucketPrincipalAndPrefixes.bucketPrefixes, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }; - - this.centralLogsBucket = new CentralLogsBucket(this, 'CentralLogsBucket', { - s3BucketName: this.centralLogsBucketName, - serverAccessLogsBucket: serverAccessLogsBucket, - kmsAliasName: this.acceleratorResourceNames.customerManagedKeys.centralLogsBucket.alias, - kmsDescription: this.acceleratorResourceNames.customerManagedKeys.centralLogsBucket.description, - principalOrgIdCondition: this.getPrincipalOrgIdCondition(this.organizationId), - orgPrincipals: this.getOrgPrincipals(this.organizationId), - s3LifeCycleRules: this.getS3LifeCycleRules(this.props.globalConfig.logging.centralLogBucket?.lifecycleRules), - awsPrincipalAccesses: centralLogsBucketPrincipalAndPrefixes.awsPrincipalAccesses, - bucketPrefixProps, - acceleratorPrefix: this.props.prefixes.accelerator, - crossAccountAccessRoleName: - this.acceleratorResourceNames.roles.crossAccountCentralLogBucketCmkArnSsmParameterAccess, - cmkArnSsmParameterName: this.acceleratorResourceNames.parameters.centralLogBucketCmkArn, - managementAccountAccessRole: this.props.globalConfig.managementAccountAccessRole, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/CentralLogsBucket/CrossAccountCentralBucketKMSArnSsmParamAccessRole/Resource`, - reason: 'Central logs bucket arn SSM parameter needs access from other accounts', - }, - ], - }); - - this.centralLogBucketAddResourcePolicies(this.centralLogsBucket); - } - } - - private createLoggingAccountSnsTopic(snsTopic: SnsTopicConfig, snsKey: cdk.aws_kms.IKey): cdk.aws_sns.Topic { - this.logger.info('Creating SNS topic in log archive account home region.'); - - const topic = new cdk.aws_sns.Topic(this, `${pascalCase(snsTopic.name)}SNSTopic`, { - displayName: `${this.props.prefixes.snsTopicName}-${snsTopic.name}`, - topicName: `${this.props.prefixes.snsTopicName}-${snsTopic.name}`, - masterKey: snsKey, - }); - for (const email of snsTopic.emailAddresses) { - topic.addSubscription(new cdk.aws_sns_subscriptions.EmailSubscription(email)); - } - - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('cloudwatch.amazonaws.com'), - }); - - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), - }); - - if (this.props.securityConfig.centralSecurityServices.securityHub.snsTopicName === snsTopic.name) { - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('events.amazonaws.com'), - }); - } - - topic.grantPublish({ - grantPrincipal: this.getOrgPrincipals(this.organizationId), - }); - - return topic; - } - - private createSnsForwarderFunction() { - const centralSnsKeyArn = new SsmParameterLookup(this, 'LookupCentralSnsKeyArnParameter', { - name: this.acceleratorResourceNames.parameters.snsTopicCmkArn, - accountId: this.props.accountsConfig.getLogArchiveAccountId(), - parameterRegion: cdk.Stack.of(this).region, - roleName: this.acceleratorResourceNames.roles.snsTopicCmkArnParameterAccess, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - - this.snsForwarderFunction = new cdk.aws_lambda.Function(this, 'SnsTopicForwarderFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '../lambdas/sns-topic-forwarder/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to forward Accelerator SNS Topics to log archive account', - timeout: cdk.Duration.minutes(2), - environmentEncryption: this.lambdaKey, - environment: { - SNS_CENTRAL_ACCOUNT: this.props.accountsConfig.getLogArchiveAccountId(), - PARTITION: `${cdk.Stack.of(this).partition}`, - }, - }); - - new cdk.aws_logs.LogGroup(this, 'SnsForwarderFunctionLogGroup', { - logGroupName: `/aws/lambda/${this.snsForwarderFunction.functionName}`, - retention: this.props.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: this.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - this.snsForwarderFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'sns', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sns:Publish'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:sns:${ - cdk.Stack.of(this).region - }:${this.props.accountsConfig.getLogArchiveAccountId()}:${this.props.prefixes.snsTopicName}-*`, - ], - }), - ); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `/${this.stackName}/SnsTopicForwarderFunction/ServiceRole/Resource`, - reason: 'Lambda function managed policy', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `/${this.stackName}/SnsTopicForwarderFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Allows only specific topics.', - }, - ], - }); - - this.snsForwarderFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'kms', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:GenerateDataKey'], - resources: [centralSnsKeyArn], - }), - ); - } - - private createSnsTopic(snsTopic: SnsTopicConfig, snsKey: cdk.aws_kms.IKey): cdk.aws_sns.Topic { - this.logger.info(`Creating SNS topic ${snsTopic.name} in ${cdk.Stack.of(this).account}`); - - const topic = new cdk.aws_sns.Topic(this, `${pascalCase(snsTopic.name)}SNSTopic`, { - displayName: `${this.props.prefixes.snsTopicName}-${snsTopic.name}`, - topicName: `${this.props.prefixes.snsTopicName}-${snsTopic.name}`, - masterKey: snsKey, - }); - - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('cloudwatch.amazonaws.com'), - }); - - if (this.props.securityConfig.centralSecurityServices.securityHub.snsTopicName === snsTopic.name) { - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('events.amazonaws.com'), - }); - } - - const fmsDelegatedAdminAccount = this.props.networkConfig.firewallManagerService?.delegatedAdminAccount; - if ( - fmsDelegatedAdminAccount && - cdk.Stack.of(this).account === this.props.accountsConfig.getAccountId(fmsDelegatedAdminAccount) - ) { - topic.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'fms', - actions: ['sns:Publish'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [topic.topicArn], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.props.prefixes.accelerator - }-FMS`, - ], - }, - }, - }), - ); - } - topic.addSubscription(new cdk.aws_sns_subscriptions.LambdaSubscription(this.snsForwarderFunction!)); - - return topic; - } - - private createCentralSnsKey() { - this.centralSnsKey = new cdk.aws_kms.Key(this, 'AcceleratorSnsTopicKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.snsTopic.alias, - description: this.acceleratorResourceNames.customerManagedKeys.snsTopic.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - this.centralSnsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'sns', - principals: [new cdk.aws_iam.ServicePrincipal('sns.amazonaws.com')], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - this.centralSnsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'cloudwatch', - principals: [new cdk.aws_iam.ServicePrincipal('cloudwatch.amazonaws.com')], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEquals: { - 'aws:SourceAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - - this.centralSnsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'events', - principals: [new cdk.aws_iam.ServicePrincipal('events.amazonaws.com')], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEqualsIfExists: { - 'aws:SourceAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - - this.centralSnsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'crossaccount', - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - new cdk.aws_ssm.StringParameter(this, 'AcceleratorCentralSnsKmsArnParameter', { - parameterName: this.acceleratorResourceNames.parameters.snsTopicCmkArn, - stringValue: this.centralSnsKey.keyArn, - }); - - if (cdk.Stack.of(this).region === this.props.globalConfig.homeRegion) { - // SSM parameter access IAM Role for central sns topic key - new cdk.aws_iam.Role(this, 'CrossAccountCentralSnsTopicKMSArnSsmParamAccessRole', { - roleName: this.acceleratorResourceNames.roles.snsTopicCmkArnParameterAccess, - assumedBy: this.getOrgPrincipals(this.organizationId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:*:*:parameter${ - this.acceleratorResourceNames.parameters.snsTopicCmkArn - }`, - ], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:DescribeParameters'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ], - }), - }, - }); - } - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `/${this.stackName}/CrossAccountCentralSnsTopicKMSArnSsmParamAccessRole`, - reason: 'Allows only specific role arns.', - }, - ], - }); - } - - private createSnsKey(): cdk.aws_kms.IKey { - const snsKey = new cdk.aws_kms.Key(this, 'AcceleratorSnsTopicKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.snsTopic.alias, - description: this.acceleratorResourceNames.customerManagedKeys.snsTopic.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - snsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'sns', - principals: [new cdk.aws_iam.ServicePrincipal('sns.amazonaws.com')], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEquals: { - 'aws:SourceAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - const fmsDelegatedAdminAccount = this.props.networkConfig.firewallManagerService?.delegatedAdminAccount; - if ( - fmsDelegatedAdminAccount && - cdk.Stack.of(this).account === this.props.accountsConfig.getAccountId(fmsDelegatedAdminAccount) - ) { - snsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Accelerator Role to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.props.prefixes.accelerator - }-FMS-Notifications`, - ], - }, - }, - }), - ); - } - - snsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'cloudwatch', - principals: [ - new cdk.aws_iam.ServicePrincipal('cloudwatch.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('events.amazonaws.com'), - ], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - StringEqualsIfExists: { - 'aws:SourceAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - - snsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'accelerator-role', - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:GenerateDataKey', 'kms:Decrypt'], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.props.prefixes.accelerator - }-*`, - ], - }, - }, - }), - ); - new cdk.aws_ssm.StringParameter(this, 'AcceleratorCentralSnsKmsArnParameter', { - parameterName: this.acceleratorResourceNames.parameters.snsTopicCmkArn, - stringValue: snsKey.keyArn, - }); - - return snsKey; - } - - private centralLogBucketAddResourcePolicies(centralLogsBucket: CentralLogsBucket) { - this.logger.info(`Adding central log bucket resource policies to KMS`); - const centralLogsBucketKey = centralLogsBucket.getS3Bucket().getKey(); - for (const attachment of this.props.globalConfig.logging.centralLogBucket?.kmsResourcePolicyAttachments ?? []) { - const policyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, attachment.policy), - false, - this.organizationId, - ), - ); - - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - for (const statement of statements) { - centralLogsBucketKey?.addToResourcePolicy(statement); - } - } - } - this.logger.info(`Adding central log bucket resource policies to S3`); - const realCentralLogBucket = centralLogsBucket?.getS3Bucket().getS3Bucket(); - for (const attachment of this.props.globalConfig.logging.centralLogBucket?.s3ResourcePolicyAttachments ?? []) { - const policyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, attachment.policy), - false, - this.organizationId, - ), - ); - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - for (const statement of statements) { - realCentralLogBucket?.addToResourcePolicy(statement); - } - } - } - - private createFMSNotificationRole() { - const fmsConfiguration = this.props.networkConfig.firewallManagerService; - - // Exit if Notification channels don't exist. - if ( - !fmsConfiguration?.notificationChannels || - fmsConfiguration.notificationChannels.length === 0 || - !this.props.networkConfig.firewallManagerService?.delegatedAdminAccount - ) { - return; - } - if ( - cdk.Stack.of(this).region === this.props.globalConfig.homeRegion && - cdk.Stack.of(this).account === - this.props.accountsConfig.getAccountId(this.props.networkConfig.firewallManagerService?.delegatedAdminAccount) - ) { - const roleName = `${this.props.prefixes.accelerator}-FMS-Notifications`; - const auditAccountId = this.props.accountsConfig.getAuditAccountId(); - - //Create Role for SNS Topic access from security config and global config - this.logger.info('Creating FMS Notification Channel Role AWSAccelerator - FMS'); - const fmsRole = new cdk.aws_iam.Role(this, `aws-accelerator-fms`, { - roleName, - assumedBy: new cdk.aws_iam.ServicePrincipal('fms.amazonaws.com'), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sns:Publish'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:kms:*:${auditAccountId}:key/*`, - `arn:${cdk.Stack.of(this).partition}:kms:*:${cdk.Stack.of(this).account}:key/*`, - ], - }), - ], - }), - }, - }); - - NagSuppressions.addResourceSuppressions(fmsRole, [ - { id: 'AwsSolutions-IAM5', reason: 'Allow cross-account resources to encrypt KMS under this path.' }, - ]); - } - } - - private elbLogBucketAddResourcePolicies(elbLogBucket: cdk.aws_s3.IBucket) { - this.logger.info(`Adding elb log bucket resource policies to S3`); - for (const attachment of this.props.globalConfig.logging.elbLogBucket?.s3ResourcePolicyAttachments ?? []) { - const policyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, attachment.policy), - false, - this.organizationId, - ), - ); - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - for (const statement of statements) { - elbLogBucket.addToResourcePolicy(statement); - } - } - } - - /** - * Function to setup certificate assets - * @param props {@link AcceleratorStackProps} - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - * - * @remarks - * Setup s3 bucket with CMK to only allow specific role access to the key. This bucket will be used to store private key material for the solution. - * Central assets bucket will only be created in the management account in home region - */ - private setupCertificateAssets( - props: AcceleratorStackProps, - principalOrgIdCondition: PrincipalOrgIdConditionType, - ): void { - if ( - cdk.Stack.of(this).account === props.accountsConfig.getManagementAccountId() && - cdk.Stack.of(this).region === props.globalConfig.homeRegion - ) { - // This is key is always created regardless of the S3 encryption setting - // This bucket may contain sensitive data - const assetsKmsKey = new cdk.aws_kms.Key(this, 'AssetsKmsKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.assetsBucket.alias, - description: this.acceleratorResourceNames.customerManagedKeys.assetsBucket.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - // Allow management account access - assetsKmsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Management Actions', - principals: [new cdk.aws_iam.AccountPrincipal(cdk.Stack.of(this).account)], - actions: [ - 'kms:Create*', - 'kms:Describe*', - 'kms:Enable*', - 'kms:List*', - 'kms:Put*', - 'kms:Update*', - 'kms:Revoke*', - 'kms:Disable*', - 'kms:Get*', - 'kms:Delete*', - 'kms:ScheduleKeyDeletion', - 'kms:CancelKeyDeletion', - 'kms:GenerateDataKey', - ], - resources: ['*'], - }), - ); - - //grant s3 service access - assetsKmsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow S3 to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:ViaService': `s3.${cdk.Stack.of(this).region}.amazonaws.com`, - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - //grant AssetsAccessRole access to KMS - assetsKmsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions: { - StringLike: { - 'aws:PrincipalARN': `arn:${cdk.Stack.of(this).partition}:iam::*:role/${ - props.prefixes.accelerator - }-AssetsAccessRole`, - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - const assetBucketKmsKeyArnSsmParameter = new cdk.aws_ssm.StringParameter( - this, - 'SsmParamAssetsAccountBucketKMSArn', - { - parameterName: this.acceleratorResourceNames.parameters.assetsBucketCmkArn, - stringValue: assetsKmsKey.keyArn, - }, - ); - - // SSM parameter access IAM Role for - new cdk.aws_iam.Role(this, 'CrossAccountAssetsBucketKMSArnSsmParamAccessRole', { - roleName: this.acceleratorResourceNames.roles.crossAccountAssetsBucketCmkArnSsmParameterAccess, - assumedBy: this.getOrgPrincipals(this.organizationId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [assetBucketKmsKeyArnSsmParameter.parameterArn], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`, - ], - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:DescribeParameters'], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ], - }), - }, - }); - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/CrossAccountAssetsBucketKMSArnSsmParamAccessRole/Resource`, - reason: 'Allows only specific policy.', - }, - ], - }); - - if (this.props.globalConfig.logging.assetBucket?.importedBucket) { - this.importAssetsBucket(principalOrgIdCondition); - } else { - this.createAssetsBucket(assetsKmsKey); - } - } - } - - /** - * Function to import S3 Asset Bucket - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - */ - private importAssetsBucket(principalOrgIdCondition: PrincipalOrgIdConditionType): void { - const bucketName = this.props.globalConfig.logging.assetBucket!.importedBucket!.name; - const importedBucketItem = this.getImportedBucket(bucketName, AcceleratorImportedBucketType.ASSETS_BUCKET, 'kms'); - - this.updateImportedBucketResourcePolicy({ - bucketConfig: this.props.globalConfig.logging.assetBucket!, - importedBucket: importedBucketItem.bucket, - bucketType: AcceleratorImportedBucketType.ASSETS_BUCKET, - overridePolicyFile: this.props.globalConfig.logging.assetBucket!.customPolicyOverrides?.s3Policy, - principalOrgIdCondition, - organizationId: this.organizationId, - }); - - this.updateImportedBucketEncryption({ - bucketConfig: this.props.globalConfig.logging.assetBucket!, - bucketType: AcceleratorImportedBucketType.ASSETS_BUCKET, - bucketItem: importedBucketItem, - principalOrgIdCondition, - bucketKmsArnParameterName: this.acceleratorResourceNames.parameters.importedAssetsBucketCmkArn, - organizationId: this.organizationId, - }); - - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `${this.stackName}/AssetsAccessLogsBucket/Resource/Resource`, - reason: 'AccessLogsBucket has server access logs disabled until the task for access logging completed.', - }, - ], - }); - - new cdk.CfnOutput(this, 'AWSAcceleratorAssetsBucket', { - value: importedBucketItem.bucket.bucketName, - description: 'Name of the bucket which hosts solution assets ', - }); - } - - /** - * Function to create S3 Asset Bucket - * @param assetsKmsKey {@link cdk.aws_kms.Key} - * @param serverAccessLogsBucket {@link cdk.aws_s3.IBucket} | undefined - * @param principalOrgIdCondition {@link PrincipalOrgIdConditionType} - * - */ - private createAssetsBucket(assetsKmsKey: cdk.aws_kms.Key | undefined) { - // Create the server access logs bucket for the Assets S3 Bucket - const serverAccessLogsBucket = new Bucket(this, 'AssetsAccessLogsBucket', { - encryptionType: BucketEncryptionType.SSE_S3, // Server access logging does not support SSE-KMS - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.assetsAccessLog}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - s3LifeCycleRules: this.getS3LifeCycleRules(this.props.globalConfig.logging.accessLogBucket?.lifecycleRules), - }); - - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `${this.stackName}/AssetsAccessLogsBucket/Resource/Resource`, - reason: 'AccessLogsBucket has server access logs disabled until the task for access logging completed.', - }, - ], - }); - - // Create S3 Assets bucket - const assetsBucket = new Bucket(this, 'CertificateAssetBucket', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.assets}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - kmsKey: assetsKmsKey, - serverAccessLogsBucketName: serverAccessLogsBucket.getS3Bucket().bucketName, - }); - - assetsBucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['s3:GetObject*', 's3:ListBucket'], - resources: [assetsBucket.getS3Bucket().bucketArn, `${assetsBucket.getS3Bucket().bucketArn}/*`], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - StringLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-AssetsAccessRole`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${ - this.props.prefixes.accelerator - }-FirewallConfigAccessRole`, - ], - }, - }, - }), - ); - - this.logger.info(`Adding Assets bucket resource policies to S3`); - if (this.props.globalConfig.logging.assetBucket?.s3ResourcePolicyAttachments) { - for (const attachment of this.props.globalConfig.logging.assetBucket.s3ResourcePolicyAttachments ?? []) { - const policyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, attachment.policy), - false, - this.organizationId, - ), - ); - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - for (const statement of statements) { - assetsBucket.getS3Bucket().addToResourcePolicy(statement); - } - } - } - - new cdk.CfnOutput(this, 'AWSAcceleratorAssetsBucket', { - value: assetsBucket.getS3Bucket().bucketName, - description: 'Name of the bucket which hosts solution assets ', - }); - } - - /** - * Function to create Server access logs bucket - * @param serverAccessLogsBucket {@link cdk.aws_s3.IBucket} | undefined - */ - private createMetadataBucket(serverAccessLogsBucket?: cdk.aws_s3.IBucket) { - if (this.props.globalConfig.acceleratorMetadata?.enable) { - if ( - cdk.Stack.of(this).region === this.props.globalConfig.homeRegion && - cdk.Stack.of(this).account === - this.props.accountsConfig.getAccountId(this.props.globalConfig.acceleratorMetadata.account) - ) { - const metadataBucket = new Bucket(this, 'AcceleratorMetadataBucket', { - encryptionType: this.isS3CMKEnabled ? BucketEncryptionType.SSE_KMS : BucketEncryptionType.SSE_S3, - kmsAliasName: this.acceleratorResourceNames.customerManagedKeys.metadataBucket.alias, - kmsDescription: this.acceleratorResourceNames.customerManagedKeys.metadataBucket.description, - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.metadata}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - serverAccessLogsBucket, - }); - - const bucket = metadataBucket.getS3Bucket(); - bucket.grantReadWrite(new cdk.aws_iam.AccountPrincipal(this.props.accountsConfig.getManagementAccountId())); - this.ssmParameters.push({ - logicalId: 'AcceleratorS3MetadataBucket', - parameterName: this.acceleratorResourceNames.parameters.metadataBucketArn, - stringValue: bucket.bucketArn, - }); - - if (!serverAccessLogsBucket) { - // AwsSolutions-S1: The S3 Bucket has server access logs disabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `/${this.stackName}/AcceleratorMetadataBucket/Resource/Resource`, - reason: 'Due to configuration settings, server access logs have been disabled.', - }, - ], - }); - } - - if (!this.isS3CMKEnabled) { - return; - } - - const key = metadataBucket.getKey(); - key.grantEncryptDecrypt(new cdk.aws_iam.AccountPrincipal(this.props.accountsConfig.getManagementAccountId())); - bucket.addToResourcePolicy( - new iam.PolicyStatement({ - actions: ['s3:Get*', 's3:List*'], - resources: [bucket.bucketArn, bucket.arnForObjects('*')], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': `arn:${cdk.Stack.of(this).partition}:iam::*:role/${ - this.props.prefixes.accelerator - }-*`, - }, - }, - }), - ); - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow org to perform encryption', - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey', 'kms:Describe*'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': `arn:${cdk.Stack.of(this).partition}:iam::*:role/${ - this.props.prefixes.accelerator - }-*`, - }, - }, - }), - ); - const readOnlyAccessRoleArns = this.props.globalConfig.acceleratorMetadata.readOnlyAccessRoleArns; - if (readOnlyAccessRoleArns && readOnlyAccessRoleArns.length > 0) { - const principals = readOnlyAccessRoleArns.map((roleArn: string) => new cdk.aws_iam.ArnPrincipal(roleArn)); - - key.addToResourcePolicy( - new iam.PolicyStatement({ - actions: ['kms:Decrypt', 'kms:DescribeKey', 'kms:GenerateDataKey'], - principals, - resources: ['*'], - }), - ); - - bucket.addToResourcePolicy( - new iam.PolicyStatement({ - actions: ['s3:GetObject', 's3:ListBucket'], - resources: [bucket.bucketArn, bucket.arnForObjects('*')], - principals, - }), - ); - } - - this.ssmParameters.push({ - logicalId: 'AcceleratorKmsMetadataKey', - parameterName: this.acceleratorResourceNames.parameters.metadataBucketCmkArn, - stringValue: key.keyArn, - }); - } - } - } - - /** - * Function to create imported central log bucket prefixes - * @param centralLogBucket {@link cdk.aws_s3.IBucket} - * @param bucketPrefixes {@link string[]} - */ - private createImportedLogBucketPrefixes(centralLogBucket: cdk.aws_s3.IBucket, bucketPrefixes: string[]) { - // Configure prefix creation - if (bucketPrefixes) { - new BucketPrefix(this, 'ImportedLogBucketPrefix', { - source: { bucket: centralLogBucket }, - bucketPrefixes: bucketPrefixes, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - - /** - * Function to configure account level CloudWatch log data protection - * @returns - */ - private configureAccountDataProtectionPolicy() { - if (!this.props.globalConfig.logging.cloudwatchLogs?.dataProtection) { - return; - } - - if (this.props.globalConfig.logging.cloudwatchLogs.dataProtection.deploymentTargets) { - if (!this.isIncluded(this.props.globalConfig.logging.cloudwatchLogs.dataProtection.deploymentTargets)) { - this.logger.info( - `CloudWatch log data protection ignored for account ${cdk.Stack.of(this).account}, region ${ - cdk.Stack.of(this).region - }`, - ); - return; - } - } - - this.logger.info( - `CloudWatch log data protection will be configured for account ${cdk.Stack.of(this).account}, region ${ - cdk.Stack.of(this).region - }`, - ); - const identifierNames: string[] = []; - - for (const category of this.props.globalConfig.logging.cloudwatchLogs.dataProtection.managedDataIdentifiers - .categories) { - identifierNames.push(...this.getDataIdentifierNamesForCategory(category)); - } - - new CloudWatchLogDataProtection(this, 'AcceleratorCloudWatchDataProtection', { - centralLogBucketName: this.centralLogsBucketName, - identifierNames: identifierNames, - overrideExisting: this.props.globalConfig.logging.cloudwatchLogs.dataProtection.overrideExisting ?? false, - customResourceLambdaEnvironmentEncryptionKmsKey: this.lambdaKey, - customResourceLambdaCloudWatchLogKmsKey: this.cloudwatchKey, - customResourceLambdaLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - - /** - * Function to get CloudWatch log data identifier names for given category - * @param category string - * @returns names string[] - */ - private getDataIdentifierNamesForCategory(category: string): string[] { - const identifierNames: string[] = []; - if (category === t.CloudWatchLogDataProtectionCategories.Credentials) { - return CloudWatchDataProtectionIdentifiers.Credentials; - } - return identifierNames; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/firewall-vpn-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/firewall-vpn-resources.ts deleted file mode 100644 index ecb7808..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/firewall-vpn-resources.ts +++ /dev/null @@ -1,1196 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CustomerGatewayConfig, - TransitGatewayConfig, - TransitGatewayRouteEntryConfig, - TransitGatewayRouteTableConfig, - TransitGatewayRouteTableVpnEntryConfig, - VpnConnectionConfig, - isNetworkType, -} from '@aws-accelerator/config'; -import { - CustomerGateway, - FirewallInstance, - FirewallVpnProps, - LzaLambda, - TransitGatewayAttachment, - TransitGatewayAttachmentType, - TransitGatewayPrefixListReference, - TransitGatewayRouteTableAssociation, - TransitGatewayRouteTablePropagation, - TransitGatewayStaticRoute, - VpnConnection, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps, NagSuppressionRuleIds } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { - getCustomerGatewayName, - getTgwConfig, - getTgwVpnConnection, - getVpcConfig, - getVpnAttachmentId, -} from '../utils/getter-utils'; -import { hasAdvancedVpnOptions, isIpv4 } from '../utils/validation-utils'; -import { NetworkAssociationsGwlbStack } from './network-associations-gwlb-stack'; - -export class FirewallVpnResources { - public readonly cgwMap: Map; - public readonly vpnMap: Map; - public readonly vpnAttachmentMap: Map; - private stack: NetworkAssociationsGwlbStack; - - constructor( - networkAssociationsGwlbStack: NetworkAssociationsGwlbStack, - props: AcceleratorStackProps, - instanceMap: Map, - ) { - this.stack = networkAssociationsGwlbStack; - - // - // Filter customer gateways to those in scope - const customerGatewaysInScope = this.setCgwsInScope(props, instanceMap); - // - // Create the custom resource handlers if needed - const requiresCrossAccountVpn = this.requiresCrossAccountVpn(props, customerGatewaysInScope); - const hasAdvancedFirewallVpn = this.hasAdvancedFirewallVpn(customerGatewaysInScope); - const hasCrossAccountTgwVpns = this.hasCrossAccountTgwVpns(props, customerGatewaysInScope); - const cgwCustomResourceHandler = requiresCrossAccountVpn ? this.createCgwOnEventHandler() : undefined; - const vpnCustomResourceHandler = - requiresCrossAccountVpn || hasAdvancedFirewallVpn ? this.stack.createVpnOnEventHandler() : undefined; - const [ - tgwAssociationCustomResourceHandler, - tgwPropagationCustomResourceHandler, - tgwStaticRouteCustomResourceHandler, - ] = hasCrossAccountTgwVpns ? this.createTgwRouteResourceHandlers() : [undefined, undefined, undefined]; - // - // Create customer gateways - [this.cgwMap, this.vpnMap] = this.createCustomerGateways( - customerGatewaysInScope, - instanceMap, - props, - cgwCustomResourceHandler, - vpnCustomResourceHandler, - ); - // - // Create TGW VPN associations/propagations - this.vpnAttachmentMap = this.setVpnAttachmentMap(props, customerGatewaysInScope); - this.createTgwAssociationsAndPropagations( - props, - customerGatewaysInScope, - tgwAssociationCustomResourceHandler, - tgwPropagationCustomResourceHandler, - ); - // - // Create TGW VPN static routes - this.createTgwStaticRoutes(props, customerGatewaysInScope, tgwStaticRouteCustomResourceHandler); - } - - /** - * Set customer gateways referencing firewall instances - * that exist in this stack context - * @param props AcceleratorStackProps - * @param instanceMap Map - * @returns CustomerGatewayConfig[] - */ - private setCgwsInScope( - props: AcceleratorStackProps, - instanceMap: Map, - ): CustomerGatewayConfig[] { - const customerGatewaysInScope: CustomerGatewayConfig[] = []; - for (const cgw of props.networkConfig.customerGateways ?? []) { - if (!isIpv4(cgw.ipAddress) && this.cgwInScope(cgw, instanceMap)) { - customerGatewaysInScope.push(cgw); - } - } - return customerGatewaysInScope; - } - - /** - * Determines if a cross-account VPN exists in the stack context - * @param props AcceleratorStackProps - * @param customerGateways CustomerGatewayConfig[] - * @returns boolean - */ - private requiresCrossAccountVpn(props: AcceleratorStackProps, customerGateways: CustomerGatewayConfig[]): boolean { - for (const cgw of customerGateways) { - const cgwAccountId = props.accountsConfig.getAccountId(cgw.account); - if (!this.stack.isTargetStack([cgwAccountId], [cgw.region])) { - return true; - } - } - return false; - } - - /** - * Determines if a firewall VPN exists that requires advanced VPN options - * @param customerGateways CustomerGatewayConfig[] - * @returns boolean - */ - private hasAdvancedFirewallVpn(customerGateways: CustomerGatewayConfig[]) { - for (const cgw of customerGateways) { - for (const vpnItem of cgw.vpnConnections ?? []) { - if (hasAdvancedVpnOptions(vpnItem)) { - return true; - } - } - } - return false; - } - - /** - * Returns true if the firewall instance referenced in a customer gateway `ipAddress` property - * is in scope of the stack context - * @param customerGateway CustomerGatewayConfig - * @param instanceMap Map - * @returns boolean - */ - private cgwInScope(customerGateway: CustomerGatewayConfig, instanceMap: Map): boolean { - return instanceMap.has(this.parseFirewallName(customerGateway.ipAddress)); - } - - /** - * Returns true if there are transit gateway VPNs attached to the CGWs in scope - * @param customerGateways CustomerGatewayConfig[] - * @returns boolean - */ - private hasCrossAccountTgwVpns(props: AcceleratorStackProps, customerGateways: CustomerGatewayConfig[]): boolean { - for (const cgw of customerGateways) { - for (const vpnItem of cgw.vpnConnections ?? []) { - if (vpnItem.transitGateway && this.requiresCrossAccountVpn(props, [cgw])) { - return true; - } - } - } - return false; - } - - /** - * Returns a FirewallInstance object if the firewall instance referenced in a customer gateway - * is in scope of the stack context - * @param customerGateway CustomerGatewayConfig - * @param instanceMap Map - * @returns FirewallInstance - */ - private getFirewallInstance( - customerGateway: CustomerGatewayConfig, - instanceMap: Map, - ): FirewallInstance { - const instance = instanceMap.get(this.parseFirewallName(customerGateway.ipAddress)); - if (!instance) { - throw new Error( - `Unable to find firewall instance referenced in customer gateway reference variable "${customerGateway.ipAddress}"`, - ); - } - return instance; - } - - /** - * Returns the name of the firewall instance referenced in a customer gateway `ipAddress` - * property. - * - * Example variable syntax: `${ACCEL_LOOKUP::EC2:ENI_0:accelerator-firewall}` - * @param firewallReference - * @returns string - */ - private parseFirewallName(firewallReference: string): string { - try { - return firewallReference.split(':')[4].replace('}', ''); - } catch (e) { - throw new Error(`Unable to parse firewall name from provided reference variable "${firewallReference}". ${e}`); - } - } - - /** - * Create the CGW custom resource handler - * @returns cdk.aws_lambda.IFunction - */ - private createCgwOnEventHandler(): cdk.aws_lambda.IFunction { - const lambdaExecutionPolicy = cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSLambdaBasicExecutionRole', - ); - - const managedCgwPolicy = new cdk.aws_iam.ManagedPolicy(this.stack, 'CgwOnEventHandlerPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'CGWAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.stack.partition}:iam::*:role/${this.stack.acceleratorResourceNames.roles.crossAccountCustomerGatewayRoleName}`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'CGWCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateCustomerGateway', 'ec2:CreateTags', 'ec2:DeleteCustomerGateway', 'ec2:DeleteTags'], - resources: ['*'], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedCgwPolicy.node.path, - reason: 'Managed policy allows access for CGW CRUD operations', - }, - ], - }); - // - // Create event handler role - const cgwRole = new cdk.aws_iam.Role(this.stack, 'CgwRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.stack.urlSuffix}`), - description: 'Landing Zone Accelerator site-to-site VPN customer gateway access role', - managedPolicies: [managedCgwPolicy, lambdaExecutionPolicy], - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: cgwRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - // - // Create Lambda handler - return new LzaLambda(this.stack, 'CgwOnEventHandler', { - assetPath: '../constructs/lib/aws-ec2/cross-account-customer-gateway/dist', - environmentEncryptionKmsKey: this.stack.lambdaKey, - cloudWatchLogKmsKey: this.stack.cloudwatchKey, - cloudWatchLogRetentionInDays: this.stack.logRetention, - description: 'Custom resource onEvent handler for site-to-site VPN customer gateways', - role: cgwRole, - timeOut: cdk.Duration.seconds(30), - nagSuppressionPrefix: 'CgwOnEventHandler', - }).resource; - } - - /** - * Returns the referenced firewall instance public IP address - * @param firewallInstance FirewallInstance - * @param firewallReference string - * @returns string - */ - private getFirewallInstanceIpAddress(firewallInstance: FirewallInstance, firewallReference: string): string { - try { - const deviceIndex = Number(firewallReference.split(':')[3].split('_')[1]); - return firewallInstance.getPublicIpAddress(deviceIndex); - } catch (e) { - throw new Error(`Unable to parse firewall reference variable "${firewallReference}". ${e}`); - } - } - - /** - * Create customer gateways and VPN connections for a firewall instance - * @param customerGateways CustomerGatewayConfig[] - * @param instanceMap Map - * @param props AcceleratorStackProps - * @param cgwCustomResourceHandler cdk.aws_lambda.IFunction | undefined - * @param vpnCustomResourceHandler cdk.aws_lambda.IFunction | undefined - * @returns Map[] - */ - private createCustomerGateways( - customerGateways: CustomerGatewayConfig[], - instanceMap: Map, - props: AcceleratorStackProps, - cgwCustomResourceHandler?: cdk.aws_lambda.IFunction, - vpnCustomResourceHandler?: cdk.aws_lambda.IFunction, - ): [Map, Map] { - const cgwMap = new Map(); - const vpnMap = new Map(); - - for (const cgwItem of customerGateways) { - // - // Set custom resource props, if required - const cgwAccountId = props.accountsConfig.getAccountId(cgwItem.account); - const requiresCrossAccountVpn = this.requiresCrossAccountVpn(props, [cgwItem]); - const cgwCustomResourceProps = requiresCrossAccountVpn - ? { - customResourceHandler: cgwCustomResourceHandler, - owningAccountId: cgwAccountId !== this.stack.account ? cgwAccountId : undefined, - owningRegion: cgwItem.region !== this.stack.region ? cgwItem.region : undefined, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountCustomerGatewayRoleName, - } - : {}; - // - // Get firewall instance from map - const firewallInstance = this.getFirewallInstance(cgwItem, instanceMap); - const firewallIpAddress = this.getFirewallInstanceIpAddress(firewallInstance, cgwItem.ipAddress); - // - // Create customer gateway - this.stack.addLogs( - LogLevel.INFO, - `Creating customer gateway ${cgwItem.name} for EC2 firewall instance ${firewallInstance.name}`, - ); - const cgw = new CustomerGateway(this.stack, pascalCase(`${cgwItem.name}CustomerGateway`), { - name: cgwItem.name, - bgpAsn: cgwItem.asn, - ipAddress: firewallIpAddress, - tags: cgwItem.tags, - ...cgwCustomResourceProps, - }); - cgwMap.set(cgwItem.name, cgw.customerGatewayId); - // - // Create VPN connections - for (const vpnItem of cgwItem.vpnConnections ?? []) { - const vpn = this.createVpnConnection( - vpnItem, - cgw, - cgwAccountId, - cgwItem.region, - firewallInstance, - requiresCrossAccountVpn, - vpnCustomResourceHandler, - ); - const vpnKey = vpnItem.transitGateway ? `${vpnItem.transitGateway}_${vpnItem.name}` : vpnItem.name; - vpnMap.set(vpnKey, vpn); - } - } - return [cgwMap, vpnMap]; - } - - /** - * Create a VPN connection - * @param vpnItem VpnConnectionConfig - * @param cgw CustomerGateway - * @param cgwAccountId string - * @param cgwRegion string - * @param firewallInstance FirewallInstance - * @param requiresCrossAccountVpn boolean - * @param vpnCustomResourceHandler cdk.aws_lambda.IFunction | undefined - * @returns VpnConnection - */ - private createVpnConnection( - vpnItem: VpnConnectionConfig, - cgw: CustomerGateway, - cgwAccountId: string, - cgwRegion: string, - firewallInstance: FirewallInstance, - requiresCrossAccountVpn: boolean, - vpnCustomResourceHandler?: cdk.aws_lambda.IFunction, - ): VpnConnection { - // - // Get TGW or VGW ID - const transitGatewayId = vpnItem.transitGateway - ? this.getTgwSsmParameter(cgw.name, vpnItem.transitGateway, requiresCrossAccountVpn) - : undefined; - const virtualPrivateGateway = vpnItem.vpc - ? this.getVgwSsmParameter(cgw.name, vpnItem.vpc, requiresCrossAccountVpn) - : undefined; - // - // Set cross-account resource props, if required - const vpnCustomResourceProps = - hasAdvancedVpnOptions(vpnItem) || requiresCrossAccountVpn - ? { - customResourceHandler: vpnCustomResourceHandler, - owningAccountId: cgwAccountId !== this.stack.account ? cgwAccountId : undefined, - owningRegion: cgwRegion !== this.stack.region ? cgwRegion : undefined, - } - : {}; - - this.stack.addLogs( - LogLevel.INFO, - `Creating VPN connection ${vpnItem.name} to EC2 firewall instance ${firewallInstance.name}`, - ); - const vpn = new VpnConnection( - this.stack, - pascalCase(`${vpnItem.name}VpnConnection`), - this.stack.setVpnProps({ - vpnItem, - customerGatewayId: cgw.customerGatewayId, - transitGatewayId, - virtualPrivateGateway, - ...vpnCustomResourceProps, - }), - ); - // - // Add dependency to firewall instance. - // We want the VPN to be created first so that - // we can retrieve the VPN details when we perform - // userdata replacements - firewallInstance.ec2Instance.node.addDependency(vpn); - firewallInstance.vpnConnections.push(this.processVpnConnectionDetails(vpn, vpnItem, cgw, cgwAccountId, cgwRegion)); - return vpn; - } - - /** - * Returns the TGW SSM parameter value for the given TGW name - * @param cgwName string - * @param tgwName string - * @param requiresCrossAccountVpn boolean - * @returns string - */ - private getTgwSsmParameter(cgwName: string, tgwName: string, requiresCrossAccountVpn: boolean): string { - if (requiresCrossAccountVpn) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_TGW, [cgwName, tgwName]), - ); - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.TGW, [tgwName]), - ); - } - } - - /** - * Returns the VGW SSM parameter value for the given VPC name - * @param cgwName string - * @param vpcName string - * @param requiresCrossAccountVpn boolean - * @returns string - */ - private getVgwSsmParameter(cgwName: string, vpcName: string, requiresCrossAccountVpn: boolean): string { - if (requiresCrossAccountVpn) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_VGW, [cgwName, vpcName]), - ); - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.VPN_GW, [vpcName]), - ); - } - } - - /** - * Process firewall VPN connection details - * @param vpn VpnConnection - * @param vpnItem VpnConnectionConfig - * @param cgw CustomerGateway - * @param cgwAccountId string - * @param cgwRegion string - * @returns FirewallVpnProps - */ - private processVpnConnectionDetails( - vpn: VpnConnection, - vpnItem: VpnConnectionConfig, - cgw: CustomerGateway, - cgwAccountId: string, - cgwRegion: string, - ): FirewallVpnProps { - const tgwConfig = vpnItem.transitGateway - ? getTgwConfig(this.stack.networkConfig.transitGateways, vpnItem.transitGateway) - : undefined; - const vpcConfig = vpnItem.vpc ? getVpcConfig(this.stack.vpcResources, vpnItem.vpc) : undefined; - let awsBgpAsn: number; - - if (tgwConfig) { - awsBgpAsn = tgwConfig.asn; - } else if (vpcConfig) { - if (!vpcConfig.virtualPrivateGateway?.asn) { - throw new Error(`No ASN defined for VPC "${vpcConfig.name}" virtual private gateway`); - } - awsBgpAsn = vpcConfig.virtualPrivateGateway.asn; - } else { - awsBgpAsn = 65000; - } - - return { - name: vpnItem.name, - awsBgpAsn, - cgwBgpAsn: cgw.bgpAsn, - cgwOutsideIp: cgw.ipAddress, - id: vpn.vpnConnectionId, - owningAccountId: cgwAccountId !== this.stack.account ? cgwAccountId : undefined, - owningRegion: cgwRegion !== this.stack.region ? cgwRegion : undefined, - }; - } - - /** - * Create custom resource onEvent handlers for TGW routes and route table associations - * @returns cdk.aws_lambda.IFunction[] - */ - private createTgwRouteResourceHandlers(): cdk.aws_lambda.IFunction[] { - return [ - this.createTgwAssociationOnEventHandler(), - this.createTgwPropagationOnEventHandler(), - this.createTgwStaticRouteOnEventHandler(), - ]; - } - - /** - * Create TGW association custom resource handler - * @returns cdk.aws_lambda.IFunction - */ - private createTgwAssociationOnEventHandler(): cdk.aws_lambda.IFunction { - const lambdaExecutionPolicy = cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSLambdaBasicExecutionRole', - ); - - const managedTgwAssociationPolicy = new cdk.aws_iam.ManagedPolicy( - this.stack, - 'TgwAssociationOnEventHandlerPolicy', - { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'TgwAssociationAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.stack.partition}:iam::*:role/${this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName}`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'TgwAssociationCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:AssociateTransitGatewayRouteTable', 'ec2:DisassociateTransitGatewayRouteTable'], - resources: ['*'], - }), - ], - }, - ); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedTgwAssociationPolicy.node.path, - reason: 'Managed policy allows access for TGW association CRUD operations', - }, - ], - }); - // - // Create event handler role - const tgwAssociationRole = new cdk.aws_iam.Role(this.stack, 'TgwAssociationRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.stack.urlSuffix}`), - description: 'Landing Zone Accelerator TGW route table association access role', - managedPolicies: [managedTgwAssociationPolicy, lambdaExecutionPolicy], - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: tgwAssociationRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - // - // Create Lambda handler - return new LzaLambda(this.stack, 'TgwAssociationOnEventHandler', { - assetPath: '../constructs/lib/aws-ec2/transit-gateway-association/dist', - environmentEncryptionKmsKey: this.stack.lambdaKey, - cloudWatchLogKmsKey: this.stack.cloudwatchKey, - cloudWatchLogRetentionInDays: this.stack.logRetention, - description: 'Custom resource onEvent handler for transit gateway route table associations', - role: tgwAssociationRole, - timeOut: cdk.Duration.seconds(15), - nagSuppressionPrefix: 'TgwAssociationOnEventHandler', - }).resource; - } - - /** - * Create TGW propagation custom resource handler - * @returns cdk.aws_lambda.IFunction - */ - private createTgwPropagationOnEventHandler(): cdk.aws_lambda.IFunction { - const lambdaExecutionPolicy = cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSLambdaBasicExecutionRole', - ); - - const managedTgwPropagationPolicy = new cdk.aws_iam.ManagedPolicy( - this.stack, - 'TgwPropagationOnEventHandlerPolicy', - { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'TgwPropagationAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.stack.partition}:iam::*:role/${this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName}`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'TgwPropagationCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ec2:EnableTransitGatewayRouteTablePropagation', - 'ec2:DisableTransitGatewayRouteTablePropagation', - ], - resources: ['*'], - }), - ], - }, - ); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedTgwPropagationPolicy.node.path, - reason: 'Managed policy allows access for TGW association CRUD operations', - }, - ], - }); - // - // Create event handler role - const tgwPropagationRole = new cdk.aws_iam.Role(this.stack, 'TgwPropagationRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.stack.urlSuffix}`), - description: 'Landing Zone Accelerator TGW route table propagation access role', - managedPolicies: [managedTgwPropagationPolicy, lambdaExecutionPolicy], - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: tgwPropagationRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - // - // Create Lambda handler - return new LzaLambda(this.stack, 'TgwPropagationOnEventHandler', { - assetPath: '../constructs/lib/aws-ec2/transit-gateway-propagation/dist', - environmentEncryptionKmsKey: this.stack.lambdaKey, - cloudWatchLogKmsKey: this.stack.cloudwatchKey, - cloudWatchLogRetentionInDays: this.stack.logRetention, - description: 'Custom resource onEvent handler for transit gateway route table propagations', - role: tgwPropagationRole, - timeOut: cdk.Duration.seconds(15), - nagSuppressionPrefix: 'TgwPropagationOnEventHandler', - }).resource; - } - - /** - * Create TGW static route custom resource handler - * @returns cdk.aws_lambda.IFunction - */ - private createTgwStaticRouteOnEventHandler(): cdk.aws_lambda.IFunction { - const lambdaExecutionPolicy = cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSLambdaBasicExecutionRole', - ); - - const managedTgwStaticRoutePolicy = new cdk.aws_iam.ManagedPolicy( - this.stack, - 'TgwStaticRouteOnEventHandlerPolicy', - { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'TgwStaticRouteAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.stack.partition}:iam::*:role/${this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName}`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'TgwStaticRouteCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateTransitGatewayRoute', 'ec2:DeleteTransitGatewayRoute'], - resources: ['*'], - }), - ], - }, - ); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedTgwStaticRoutePolicy.node.path, - reason: 'Managed policy allows access for TGW static route CRUD operations', - }, - ], - }); - // - // Create event handler role - const tgwStaticRouteRole = new cdk.aws_iam.Role(this.stack, 'TgwStaticRouteRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.stack.urlSuffix}`), - description: 'Landing Zone Accelerator TGW static route access role', - managedPolicies: [managedTgwStaticRoutePolicy, lambdaExecutionPolicy], - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: tgwStaticRouteRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - // - // Create Lambda handler - return new LzaLambda(this.stack, 'TgwStaticRouteOnEventHandler', { - assetPath: '../constructs/lib/aws-ec2/cross-account-transit-gateway-route/dist', - environmentEncryptionKmsKey: this.stack.lambdaKey, - cloudWatchLogKmsKey: this.stack.cloudwatchKey, - cloudWatchLogRetentionInDays: this.stack.logRetention, - description: 'Custom resource onEvent handler for transit gateway static routes', - role: tgwStaticRouteRole, - timeOut: cdk.Duration.seconds(15), - nagSuppressionPrefix: 'TgwStaticRouteOnEventHandler', - }).resource; - } - - /** - * Set VPN attachment map for VPNs in scope - * @param props AcceleratorStackProps - * @param customerGateways CustomerGatewayConfig[] - * @returns Map - */ - private setVpnAttachmentMap( - props: AcceleratorStackProps, - customerGateways: CustomerGatewayConfig[], - ): Map { - const vpnAttachmentMap = new Map(); - - for (const cgw of customerGateways) { - const cgwAccountId = props.accountsConfig.getAccountId(cgw.account); - const requiresCrossAccountRoutes = this.requiresCrossAccountVpn(props, [cgw]); - - for (const vpnItem of cgw.vpnConnections ?? []) { - if (vpnItem.transitGateway) { - vpnAttachmentMap.set( - `${vpnItem.transitGateway}_${vpnItem.name}`, - this.lookupVpnAttachment(vpnItem, cgwAccountId, cgw, requiresCrossAccountRoutes), - ); - } - } - } - return vpnAttachmentMap; - } - - /** - * Lookup EC2 firewall TGW VPN attachments - * @param vpnItem VpnConnectionConfig - * @param cgwAccountId string - * @param cgwRegion string - * @param requiresCrossAccountRoutes boolean - * @returns string - */ - private lookupVpnAttachment( - vpnItem: VpnConnectionConfig, - cgwAccountId: string, - cgw: CustomerGatewayConfig, - requiresCrossAccountRoutes: boolean, - ): string { - // - // Set VPN lookup custom resource props - const crossAccountVpnOptions = requiresCrossAccountRoutes - ? { - owningAccountId: cgwAccountId !== this.stack.account ? cgwAccountId : undefined, - owningRegion: cgw.region !== this.stack.region ? cgw.region : undefined, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName, - } - : {}; - // - // Get TGW ID and VPN connection construct - const tgwId = this.getTgwSsmParameter(cgw.name, vpnItem.transitGateway!, requiresCrossAccountRoutes); - const vpn = getTgwVpnConnection(this.vpnMap, vpnItem.transitGateway!, vpnItem.name); - // - // Lookup VPN attachment - const attachmentLookup = TransitGatewayAttachment.fromLookup( - this.stack, - pascalCase(`${vpnItem.name}VpnTransitGatewayAttachment`), - { - name: vpnItem.name, - owningAccountId: cgwAccountId, - transitGatewayId: tgwId, - type: TransitGatewayAttachmentType.VPN, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - crossAccountVpnOptions, - }, - ); - // Set dependency on VPN connection - attachmentLookup.node.addDependency(vpn); - - return attachmentLookup.transitGatewayAttachmentId; - } - - /** - * Create TGW route table associations and propagations - * @param props AcceleratorStackProps - * @param customerGateways CustomerGatewayConfig[] - * @param tgwAssociationCustomResourceHandler cdk.aws_lambda.IFunction | undefined - * @param tgwPropagationCustomResourceHandler cdk.aws_lambda.IFunction | undefined - */ - private createTgwAssociationsAndPropagations( - props: AcceleratorStackProps, - customerGateways: CustomerGatewayConfig[], - tgwAssociationCustomResourceHandler?: cdk.aws_lambda.IFunction, - tgwPropagationCustomResourceHandler?: cdk.aws_lambda.IFunction, - ) { - for (const cgw of customerGateways) { - const cgwAccountId = props.accountsConfig.getAccountId(cgw.account); - const requiresCrossAccountRoutes = this.requiresCrossAccountVpn(props, [cgw]); - - for (const vpnItem of cgw.vpnConnections ?? []) { - this.createTransitGatewayAssociations( - vpnItem, - cgwAccountId, - cgw, - requiresCrossAccountRoutes, - tgwAssociationCustomResourceHandler, - ); - this.createTransitGatewayPropagations( - vpnItem, - cgwAccountId, - cgw, - requiresCrossAccountRoutes, - tgwPropagationCustomResourceHandler, - ); - } - } - } - - /** - * Returns the TGW route table SSM parameter value for the given route table name - * @param cgwName string - * @param tgwName string - * @param routeTableName string - * @param requiresCrossAccountRoutes boolean - * @returns string - */ - private getTgwRouteTableSsmParameter( - cgwName: string, - tgwName: string, - routeTableName: string, - requiresCrossAccountRoutes: boolean, - ): string { - if (requiresCrossAccountRoutes) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_TGW_ROUTE_TABLE, [cgwName, tgwName, routeTableName]), - ); - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [tgwName, routeTableName]), - ); - } - } - - /** - * Create TGW route table associations - * @param vpnItem VpnConnectionConfig - * @param cgwAccountId string - * @param cgw CustomerGatewayConfig - * @param requiresCrossAccountRoutes boolean - * @param tgwAssociationCustomResourceHandler cdk.aws_lambda.IFunction | undefined - */ - private createTransitGatewayAssociations( - vpnItem: VpnConnectionConfig, - cgwAccountId: string, - cgw: CustomerGatewayConfig, - requiresCrossAccountRoutes: boolean, - tgwAssociationCustomResourceHandler?: cdk.aws_lambda.IFunction, - ) { - for (const associationItem of vpnItem.routeTableAssociations ?? []) { - // - // Set custom resource props - const customResourceProps = requiresCrossAccountRoutes - ? { - customResourceHandler: tgwAssociationCustomResourceHandler, - owningAccountId: cgwAccountId !== this.stack.account ? cgwAccountId : undefined, - owningRegion: cgw.region !== this.stack.region ? cgw.region : undefined, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName, - } - : {}; - // - // Get TGW route table and attachment IDs - const transitGatewayRouteTableId = this.getTgwRouteTableSsmParameter( - cgw.name, - vpnItem.transitGateway!, - associationItem, - requiresCrossAccountRoutes, - ); - const transitGatewayAttachmentId = getVpnAttachmentId( - this.vpnAttachmentMap, - vpnItem.transitGateway!, - vpnItem.name, - ); - // - // Create TGW route table association - this.stack.addLogs( - LogLevel.INFO, - `Associating TGW route table ${associationItem} with EC2 firewall VPN connection ${vpnItem.name}`, - ); - new TransitGatewayRouteTableAssociation( - this.stack, - `${pascalCase(vpnItem.name)}${pascalCase(associationItem)}Association`, - { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - ...customResourceProps, - }, - ); - } - } - - /** - * Create TGW route table propagations - * @param vpnItem VpnConnectionConfig - * @param cgwAccountId string - * @param cgw string - * @param requiresCrossAccountRoutes boolean - * @param tgwPropagationCustomResourceHandler cdk.aws_lambda.IFunction | undefined - */ - private createTransitGatewayPropagations( - vpnItem: VpnConnectionConfig, - cgwAccountId: string, - cgw: CustomerGatewayConfig, - requiresCrossAccountRoutes: boolean, - tgwPropagationCustomResourceHandler?: cdk.aws_lambda.IFunction, - ) { - for (const propagationItem of vpnItem.routeTablePropagations ?? []) { - // - // Set custom resource props - const customResourceProps = requiresCrossAccountRoutes - ? { - customResourceHandler: tgwPropagationCustomResourceHandler, - owningAccountId: cgwAccountId !== this.stack.account ? cgwAccountId : undefined, - owningRegion: cgw.region !== this.stack.region ? cgw.region : undefined, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName, - } - : {}; - // - // Get TGW route table and attachment IDs - const transitGatewayRouteTableId = this.getTgwRouteTableSsmParameter( - cgw.name, - vpnItem.transitGateway!, - propagationItem, - requiresCrossAccountRoutes, - ); - const transitGatewayAttachmentId = getVpnAttachmentId( - this.vpnAttachmentMap, - vpnItem.transitGateway!, - vpnItem.name, - ); - // - // Create TGW route table propagation - this.stack.addLogs( - LogLevel.INFO, - `Propagating EC2 firewall VPN connection ${vpnItem.name} to TGW route table ${propagationItem}`, - ); - new TransitGatewayRouteTablePropagation( - this.stack, - `${pascalCase(vpnItem.name)}${pascalCase(propagationItem)}Propagation`, - { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - ...customResourceProps, - }, - ); - } - } - - /** - * Create TGW VPN static routes for EC2 firewall VPN connections - * @param props AcceleratorStackProps - * @param transitGateways TransitGatewayConfig[] - * @param customerGateways CustomerGatewayConfig[] - * @param tgwStaticRouteCustomResourceHandler - */ - private createTgwStaticRoutes( - props: AcceleratorStackProps, - customerGateways: CustomerGatewayConfig[], - tgwStaticRouteCustomResourceHandler?: cdk.aws_lambda.IFunction, - ) { - // - // Set array of VPN names in scope - const ec2FirewallVpnNames = this.setEc2FirewallVpnNames(customerGateways); - - for (const tgwItem of props.networkConfig.transitGateways) { - const tgwAccountId = props.accountsConfig.getAccountId(tgwItem.account); - for (const routeTableItem of tgwItem.routeTables) { - // - // Set an array of EC2 firewall VPN static routes - const ec2FirewallVpnRoutes = routeTableItem.routes.filter( - routeItem => - routeItem.attachment && - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeItem.attachment, - ) && - ec2FirewallVpnNames.includes(routeItem.attachment.vpnConnectionName), - ); - // - // Create static routes - this.createTgwStaticRouteItems( - ec2FirewallVpnRoutes, - customerGateways, - tgwItem, - tgwAccountId, - routeTableItem, - tgwStaticRouteCustomResourceHandler, - ); - } - } - } - - /** - * Returns an array of EC2 firewall VPN connection logical names - * that are in scope of the stack context - * @param customerGateways CustomerGatewayConfig[] - * @returns string[] - */ - private setEc2FirewallVpnNames(customerGateways: CustomerGatewayConfig[]): string[] { - const vpnNames: string[] = []; - - for (const cgw of customerGateways) { - for (const vpnItem of cgw.vpnConnections ?? []) { - vpnNames.push(vpnItem.name); - } - } - return vpnNames; - } - - /** - * Create TGW VPN static routes and prefix list references - * @param ec2FirewallVpnRoutes TransitGatewayRouteEntryConfig[] - * @param customerGateways CustomerGatewayConfig[] - * @param tgwItem TransitGatewayConfig - * @param tgwAccountId string - * @param routeTableItem TransitGatewayRouteTableConfig - * @param tgwStaticRouteCustomResourceHandler cdk.aws_lambda.IFunction | undefined - */ - private createTgwStaticRouteItems( - ec2FirewallVpnRoutes: TransitGatewayRouteEntryConfig[], - customerGateways: CustomerGatewayConfig[], - tgwItem: TransitGatewayConfig, - tgwAccountId: string, - routeTableItem: TransitGatewayRouteTableConfig, - tgwStaticRouteCustomResourceHandler?: cdk.aws_lambda.IFunction, - ) { - for (const routeEntryItem of ec2FirewallVpnRoutes) { - if ( - routeEntryItem.attachment && - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeEntryItem.attachment, - ) - ) { - // - // Set custom resource props - const requiresCrossAccountRoutes = tgwAccountId !== this.stack.account || tgwItem.region !== this.stack.region; - const customResourceProps = requiresCrossAccountRoutes - ? { - customResourceHandler: tgwStaticRouteCustomResourceHandler, - owningAccountId: tgwAccountId !== this.stack.account ? tgwAccountId : undefined, - owningRegion: tgwItem.region !== this.stack.region ? tgwItem.region : undefined, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName, - } - : {}; - // - // Get TGW route table and attachment ID - const cgwName = getCustomerGatewayName(customerGateways, routeEntryItem.attachment.vpnConnectionName); - const transitGatewayRouteTableId = this.getTgwRouteTableSsmParameter( - cgwName, - tgwItem.name, - routeTableItem.name, - requiresCrossAccountRoutes, - ); - const transitGatewayAttachmentId = getVpnAttachmentId( - this.vpnAttachmentMap, - tgwItem.name, - routeEntryItem.attachment.vpnConnectionName, - ); - // - // Create static route or prefix list reference - if (routeEntryItem.destinationCidrBlock) { - this.stack.addLogs( - LogLevel.INFO, - `Creating static route on TGW route table ${routeTableItem.name} for destination ${routeEntryItem.destinationCidrBlock} targeting VPN attachment ${routeEntryItem.attachment.vpnConnectionName}`, - ); - new TransitGatewayStaticRoute( - this.stack, - pascalCase( - `${routeTableItem.name}${routeEntryItem.destinationCidrBlock}${routeEntryItem.attachment.vpnConnectionName}`, - ), - { - destinationCidrBlock: routeEntryItem.destinationCidrBlock, - transitGatewayAttachmentId, - transitGatewayRouteTableId, - ...customResourceProps, - }, - ); - } - if (routeEntryItem.destinationPrefixList) { - const prefixListId = this.getPrefixListSsmParameter( - cgwName, - routeEntryItem.destinationPrefixList, - requiresCrossAccountRoutes, - ); - this.stack.addLogs( - LogLevel.INFO, - `Creating prefix list reference on TGW route table ${routeTableItem.name} for destination prefix list ${routeEntryItem.destinationPrefixList} targeting VPN attachment ${routeEntryItem.attachment.vpnConnectionName}`, - ); - new TransitGatewayPrefixListReference( - this.stack, - pascalCase( - `${routeTableItem.name}${routeEntryItem.destinationPrefixList}${routeEntryItem.attachment.vpnConnectionName}`, - ), - { - prefixListId, - transitGatewayAttachmentId, - transitGatewayRouteTableId, - logGroupKmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - ...customResourceProps, - }, - ); - } - } - } - } - - /** - * Returns the prefix list SSM parameter value for the given prefix list name - * @param cgwName string - * @param prefixListName string - * @param requiresCrossAccountRoutes boolean - * @returns string - */ - private getPrefixListSsmParameter( - cgwName: string, - prefixListName: string, - requiresCrossAccountRoutes: boolean, - ): string { - if (requiresCrossAccountRoutes) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_PREFIX_LIST, [cgwName, prefixListName]), - ); - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.PREFIX_LIST, [prefixListName]), - ); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/network-associations-gwlb-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/network-associations-gwlb-stack.ts deleted file mode 100644 index 1c34a13..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-gwlb-stack/network-associations-gwlb-stack.ts +++ /dev/null @@ -1,1242 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; - -import { - AseaResourceType, - AutoScalingConfig, - Ec2FirewallAutoScalingGroupConfig, - Ec2FirewallInstanceConfig, - GwlbConfig, - GwlbEndpointConfig, - LaunchTemplateConfig, - NetworkInterfaceItemConfig, - RouteTableEntryConfig, - TargetGroupItemConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { - CrossAccountRoute, - CrossAccountRouteFramework, - FirewallAutoScalingGroup, - FirewallConfigReplacements, - FirewallInstance, - SsmParameterLookup, - TargetGroup, - VpcEndpoint, - VpcEndpointType, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { NetworkStack } from '../network-stack'; -import { - getNetworkInterfaceLookupDetails, - getRouteTable, - getVpc, - getVpcConfig, - getVpcOwnerAccountName, -} from '../utils/getter-utils'; -import { setIpamSubnetRouteTableEntryArray } from '../utils/setter-utils'; -import { FirewallVpnResources } from './firewall-vpn-resources'; - -interface FirewallConfigDetails { - /** - * The asset bucket name - */ - assetBucketName: string; - /** - * The config bucket name - */ - configBucketName: string; - /** - * The custom resource role - */ - customResourceRole: cdk.aws_iam.IRole; -} - -interface networkInterfaceRouteDetails { - /** - * Details of the route entry - */ - routeEntry: RouteTableEntryConfig; - /** - * The name of the VPC route table - */ - routeTableName: string; - /** - * The name of the VPC containing the route - */ - vpcName: string; - /** - * True if the route's VPC is owned by the current account - */ - vpcOwnedByAccount: boolean; - /** - * The index of the firewall ENI, if applicable - */ - eniIndex?: number; - /** - * The name of the firewall target, if applicable - */ - firewallName?: string; - /** - * True if the route's target firewall is owned by the current account - */ - firewallOwnedByAccount?: boolean; -} - -export class NetworkAssociationsGwlbStack extends NetworkStack { - private firewallConfigDetails: FirewallConfigDetails; - private gwlbMap: Map; - private instanceMap: Map; - private routeTableMap: Map; - private securityGroupMap: Map; - // Map to store subnet IDs of owned and shared subnets - private subnetMap: Map; - private ipamSubnetArray: string[]; - private targetGroupMap: Map; - // Map to store all vpc mapping of owned and shared VPCs - private vpcMap: Map; - // Map to store only local vpcs which are created in account - private vpcsInScopeMap: Map; - private crossAcctRouteProvider?: cdk.custom_resources.Provider; - private networkInterfaceRouteArray: networkInterfaceRouteDetails[]; - - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - // Set initial private properties - // Since VPC names are unique there is only one VPC in the list which is either shared or native to account with name - const vpcs = [...this.vpcsInScope, ...this.sharedVpcs]; - this.vpcMap = this.setVpcMap(vpcs); - this.vpcsInScopeMap = this.setVpcMap(this.vpcsInScope); - const ownedSubnetsMap = this.setSubnetMap(this.vpcsInScope); - this.subnetMap = new Map([...ownedSubnetsMap.entries(), ...this.getSharedSubnetsMap().entries()]); - this.ipamSubnetArray = setIpamSubnetRouteTableEntryArray(vpcs); - this.routeTableMap = this.setRouteTableMap(this.vpcsInScope); - this.securityGroupMap = this.setSecurityGroupMap(vpcs); - this.gwlbMap = this.setInitialMaps(this.vpcsInScope); - - // Set firewall config custom resource details - this.firewallConfigDetails = { - assetBucketName: `${ - this.acceleratorResourceNames.bucketPrefixes.assets - }-${props.accountsConfig.getManagementAccountId()}-${props.globalConfig.homeRegion}`, - configBucketName: `${this.acceleratorResourceNames.bucketPrefixes.firewallConfig}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - customResourceRole: cdk.aws_iam.Role.fromRoleName( - this, - 'FirewallConfigRole', - this.acceleratorResourceNames.roles.firewallConfigFunctionRoleName, - ), - }; - // - // Create firewall instances and target groups - // - this.instanceMap = this.createFirewallInstances(); - this.targetGroupMap = this.createFirewallTargetGroups(this.instanceMap); - // - // Set network interface route array - // - this.networkInterfaceRouteArray = this.setNetworkInterfaceRouteArray(); - // - // Create cross-account route provider, if required - // - this.crossAcctRouteProvider = this.createCrossAcctRouteProvider(); - // - // Crete firewall VPN resources - // - new FirewallVpnResources(this, props, this.instanceMap); - // - // Create firewall autoscaling groups - // - this.createFirewallAutoScalingGroups(); - // - // Create Gateway Load Balancer resources - // - this.createGwlbResources(); - // - // Create ENI Routes - // - this.createNetworkInterfaceRouteTableEntries(); - // - // Add nag suppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Set route table, subnet, and VPC maps for this stack's account and region - * @returns - */ - private setInitialMaps(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const gwlbMap = new Map(); - - for (const vpcItem of vpcResources) { - // Retrieve Gateway Load balancers - const gwlbItemMap = this.setGwlbMap(vpcItem); - gwlbItemMap.forEach((value, key) => gwlbMap.set(key, value)); - } - return gwlbMap; - } - - /** - * Returns a map of Gateway Load Balancer items for a given VPC configuration - * @param vpcItem - * @returns - */ - private setGwlbMap(vpcItem: VpcConfig | VpcTemplatesConfig): Map { - const gwlbMap = new Map(); - for (const gwlbItem of this.props.networkConfig.centralNetworkServices?.gatewayLoadBalancers ?? []) { - if (gwlbItem.vpc === vpcItem.name) { - const gwlbArn = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.GWLB_ARN, [gwlbItem.name]), - ); - gwlbMap.set(gwlbItem.name, gwlbArn); - } - } - return gwlbMap; - } - - /** - * Returns a map of VPC routes targeting elastic network interfaces (ENIs) - * @param vpcItem - * @returns - */ - private setNetworkInterfaceRouteArray(): networkInterfaceRouteDetails[] { - const eniRouteArray: networkInterfaceRouteDetails[] = []; - for (const vpcItem of this.vpcResources) { - // only look in VpcConfig, skip VpcTemplateConfig - if ('account' in vpcItem) { - for (const routeTableItem of vpcItem.routeTables ?? []) { - for (const routeTableEntryItem of routeTableItem.routes ?? []) { - if (routeTableEntryItem.type === 'networkInterface') { - const routeDetails = this.getNetworkInterfaceRouteDetails( - routeTableEntryItem, - routeTableItem.name, - vpcItem.name, - ); - eniRouteArray.push(routeDetails); - } - } - } - } - } - - return eniRouteArray; - } - - /** - * Returns details of an ENI route and the target ENI - * @param routeEntry - * @param routeTableName - * @param vpcItem - * @returns - */ - private getNetworkInterfaceRouteDetails( - routeEntry: RouteTableEntryConfig, - routeTableName: string, - vpcName: string, - ): networkInterfaceRouteDetails { - let firewallName: string | undefined; - let eniIndex: number | undefined; - let firewallOwnedByAccount: boolean | undefined; - - if (this.isNetworkInterfaceTargetLookup(routeEntry.target, routeEntry.name)) { - firewallName = getNetworkInterfaceLookupDetails('FIREWALL_NAME', routeEntry.name, routeEntry.target); - eniIndex = parseInt(getNetworkInterfaceLookupDetails('ENI_INDEX', routeEntry.name, routeEntry.target)); - firewallOwnedByAccount = this.instanceMap.has(firewallName); - } - return { - routeEntry, - routeTableName, - vpcName, - vpcOwnedByAccount: this.vpcsInScopeMap.has(vpcName), - firewallName, - eniIndex, - firewallOwnedByAccount, - }; - } - - /** - * Returns a map of subnet IDs of shared subnets - * @returns - */ - private getSharedSubnetsMap(): Map { - const subnetMap = new Map(); - for (const vpcItem of this.sharedVpcs) { - for (const subnetItem of vpcItem.subnets ?? []) { - if ( - subnetItem.shareTargets && - (this.isOrganizationalUnitIncluded(subnetItem.shareTargets.organizationalUnits) || - this.isAccountIncluded(subnetItem.shareTargets.accounts)) - ) { - const subnetId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - ); - subnetMap.set(`${vpcItem.name}_${subnetItem.name}`, subnetId); - } - } - } - return subnetMap; - } - - /** - * Function to check scope of resource based on vpcName and account. - * @param vpcName - * @param account - * @returns - */ - private isInScope(vpcName: string, account?: string) { - return ( - (account && - cdk.Stack.of(this).account === this.props.accountsConfig.getAccountId(account) && - this.vpcMap.has(vpcName)) || - (!account && this.vpcsInScopeMap.has(vpcName)) - ); - } - - /** - * Create EC2-based firewall and firewall management instances - * @returns - */ - private createFirewallInstances(): Map { - const instanceMap = new Map(); - const firewallInstances = [ - ...(this.props.customizationsConfig.firewalls?.instances ?? []), - ...(this.props.customizationsConfig.firewalls?.managerInstances ?? []), - ]; - for (const firewallInstance of firewallInstances) { - if (this.isManagedByAsea(AseaResourceType.FIREWALL_INSTANCE, firewallInstance.name)) { - this.logger.info(`Firewall Instance ${firewallInstance.name} is managed by ASEA`); - continue; - } - if (this.isInScope(firewallInstance.vpc, firewallInstance.account)) { - instanceMap.set(firewallInstance.name, this.createFirewallInstance(firewallInstance)); - } - } - return instanceMap; - } - - /** - * Create a firewall instance - * @param firewallInstance - * @returns - */ - private createFirewallInstance(firewallInstance: Ec2FirewallInstanceConfig): FirewallInstance { - const launchTemplate: LaunchTemplateConfig = this.processLaunchTemplateReplacements( - firewallInstance.launchTemplate, - firewallInstance.vpc, - firewallInstance.name, - ); - - this.logger.info(`Creating standalone firewall instance ${firewallInstance.name} in VPC ${firewallInstance.vpc}`); - const instance = new FirewallInstance(this, pascalCase(`${firewallInstance.vpc}${firewallInstance.name}Firewall`), { - name: firewallInstance.name, - configBucketName: this.firewallConfigDetails.configBucketName, - configDir: this.props.configDirPath, - launchTemplate, - vpc: firewallInstance.vpc, - detailedMonitoring: firewallInstance.detailedMonitoring, - terminationProtection: firewallInstance.terminationProtection, - tags: firewallInstance.tags, - }); - - if (!firewallInstance.detailedMonitoring) { - NagSuppressions.addResourceSuppressions(instance, [ - { id: 'AwsSolutions-EC28', reason: 'Detailed monitoring not enabled by configuration.' }, - ]); - } - if (!firewallInstance.terminationProtection) { - NagSuppressions.addResourceSuppressions(instance, [ - { id: 'AwsSolutions-EC29', reason: 'Termination protection not enabled by configuration.' }, - ]); - } - // - // Generate replacements - if (firewallInstance.configFile || firewallInstance.configDir || firewallInstance.licenseFile) { - new FirewallConfigReplacements( - this, - pascalCase(`${firewallInstance.vpc}${firewallInstance.name}ConfigReplacements`), - { - cloudWatchLogKey: this.cloudwatchKey, - cloudWatchLogRetentionInDays: this.logRetention, - environmentEncryptionKey: this.lambdaKey, - properties: [ - { assetBucketName: this.firewallConfigDetails.assetBucketName }, - { configBucketName: this.firewallConfigDetails.configBucketName }, - { configFile: firewallInstance.configFile }, - { configDir: firewallInstance.configDir }, - { firewallName: instance.name }, - { instanceId: instance.instanceId }, - { licenseFile: firewallInstance.licenseFile }, - { staticReplacements: firewallInstance.staticReplacements }, - { vpcId: getVpc(this.vpcMap, firewallInstance.vpc) as string }, - { roleName: this.acceleratorResourceNames.roles.crossAccountVpnRoleName }, - { vpnConnections: instance.vpnConnections }, - { managementAccountId: this.props.accountsConfig.getManagementAccountId() }, - ], - role: this.firewallConfigDetails.customResourceRole, - }, - ); - } - return instance; - } - - /** - * Create EC2-based firewall target groups - * @param instanceMap - * @returns - */ - private createFirewallTargetGroups(instanceMap: Map): Map { - const targetGroupMap = new Map(); - for (const group of this.props.customizationsConfig.firewalls?.targetGroups ?? []) { - // Check for instance targets in group - if (group.targets && this.includesTargets(group, instanceMap)) { - const vpcName = this.getVpcNameFromTargets(group); - const targets = this.processFirewallInstanceReplacements(group.targets as string[], instanceMap); - targetGroupMap.set(group.name, this.createTargetGroup(group, vpcName, targets)); - } - // Check if any autoscaling groups reference the target group - for (const asg of this.props.customizationsConfig.firewalls?.autoscalingGroups ?? []) { - const asgTargetGroups = asg.autoscaling.targetGroups; - if (asgTargetGroups && asgTargetGroups[0] === group.name && this.isInScope(asg.vpc, asg.account)) { - targetGroupMap.set(group.name, this.createTargetGroup(group, asg.vpc)); - } - } - } - return targetGroupMap; - } - - /** - * Create a target group - * @param group - * @param vpcName - * @param targets - * @returns - */ - private createTargetGroup(group: TargetGroupItemConfig, vpcName: string, targets?: string[]): TargetGroup { - const vpcId = this.vpcMap.get(vpcName); - if (!vpcId) { - this.logger.error(`unable to retrieve VPC ${vpcName} for firewall target group ${group.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - this.logger.info(`Creating firewall target group ${group.name} in VPC ${vpcName}`); - return new TargetGroup(this, `${vpcName}${group.name}FirewallTargetGroup`, { - name: group.name, - port: group.port, - protocol: group.protocol, - protocolVersion: group.protocolVersion, - type: group.type, - vpc: vpcId, - attributes: group.attributes, - healthCheck: group.healthCheck, - targets: targets, - threshold: group.threshold, - matcher: group.matcher, - }); - } - - /** - * Returns true if this stack has matching firewall instances to target - * @param group - * @param instanceMap - * @returns - */ - private includesTargets(group: TargetGroupItemConfig, instanceMap: Map): boolean { - for (const target of (group.targets as string[]) ?? []) { - if (!instanceMap.has(target)) { - return false; - } - } - return true; - } - - /** - * From a given target group, retrieve the VPC name for the instance targets - * @param group - * @returns - */ - private getVpcNameFromTargets(group: TargetGroupItemConfig): string { - // Retrieve instance configs - const config = this.props.customizationsConfig.firewalls!.instances!; - const instances: Ec2FirewallInstanceConfig[] = []; - group.targets!.forEach(target => instances.push(config.find(item => item.name === target)!)); - - // Map VPCs - const vpcs = instances.map(item => { - return item.vpc; - }); - - if (vpcs.some(vpc => vpc !== vpcs[0])) { - this.logger.error(`firewall target group ${group.name} targeted instances are in separate VPCs`); - throw new Error(`Configuration validation failed at runtime.`); - } - return vpcs[0]; - } - - /** - * Process and return instance ID replacements - * @param targets - * @param instanceMap - * @returns - */ - private processFirewallInstanceReplacements(targets: string[], instanceMap: Map): string[] { - const instances: string[] = []; - if (targets.length > 0) { - targets.forEach(target => { - const instance = instanceMap.get(target); - if (!instance) { - this.logger.error(`Unable to retrieve instance ${target} for target group`); - throw new Error(`Configuration validation failed at runtime.`); - } - instances.push(instance.instanceId); - }); - } - return instances; - } - - /** - * Create EC2-based firewall autoscaling groups - */ - private createFirewallAutoScalingGroups() { - for (const group of this.props.customizationsConfig.firewalls?.autoscalingGroups ?? []) { - if (this.isInScope(group.vpc, group.account)) { - this.createFirewallAutoScalingGroup(group); - } - } - } - - /** - * Create an EC2-based firewall autoscaling group - * @param group - */ - private createFirewallAutoScalingGroup(group: Ec2FirewallAutoScalingGroupConfig) { - const launchTemplate: LaunchTemplateConfig = this.processLaunchTemplateReplacements( - group.launchTemplate, - group.vpc, - group.name, - ); - const autoscaling: AutoScalingConfig = this.processAutoScalingReplacements(group.autoscaling, group.vpc); - - const resourceName = pascalCase(`${group.vpc}${group.name}FirewallAsg`); - this.logger.info(`Creating firewall autoscaling group ${group.name} in VPC ${group.vpc}`); - new FirewallAutoScalingGroup(this, resourceName, { - name: group.name, - autoscaling, - configBucketName: this.firewallConfigDetails.configBucketName, - configDir: this.props.configDirPath, - launchTemplate, - vpc: group.vpc, - tags: group.tags, - lambdaKey: this.lambdaKey, - cloudWatchLogKmsKey: this.cloudwatchKey, - cloudWatchLogRetentionInDays: this.logRetention, - }); - - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${resourceName}/Resource/Resource`, [ - { id: 'AwsSolutions-AS3', reason: 'Scaling policies are not offered as a part of this solution.' }, - ]); - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/${resourceName}/Resource/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - [{ id: 'AwsSolutions-IAM4', reason: 'Custom resource Lambda role policy.' }], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/${resourceName}/Resource/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleFunction/ServiceRole/DefaultPolicy/Resource`, - [{ id: 'AwsSolutions-IAM5', reason: 'Custom resource Lambda role policy.' }], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/${resourceName}/Resource/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - [{ id: 'AwsSolutions-IAM4', reason: 'Custom resource Lambda role policy.' }], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/${resourceName}/Resource/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [{ id: 'AwsSolutions-IAM5', reason: 'Custom resource Lambda role policy.' }], - ); - - // - // Generate replacements - if (group.configFile || group.configDir || group.licenseFile) { - new FirewallConfigReplacements(this, pascalCase(`${group.vpc}${group.name}ConfigReplacements`), { - cloudWatchLogKey: this.cloudwatchKey, - cloudWatchLogRetentionInDays: this.logRetention, - environmentEncryptionKey: this.lambdaKey, - properties: [ - { assetBucketName: this.firewallConfigDetails.assetBucketName }, - { configBucketName: this.firewallConfigDetails.configBucketName }, - { configFile: group.configFile }, - { configDir: group.configDir }, - { licenseFile: group.licenseFile }, - { staticReplacements: group.staticReplacements }, - { vpcId: getVpc(this.vpcMap, group.vpc) as string }, - { managementAccountId: this.props.accountsConfig.getManagementAccountId() }, - ], - role: this.firewallConfigDetails.customResourceRole, - }); - } - } - - /** - * Process and return launch template replacements - * @param launchTemplate - * @param vpc - * @param firewallName - * @returns - */ - private processLaunchTemplateReplacements( - launchTemplate: LaunchTemplateConfig, - vpc: string, - firewallName: string, - ): LaunchTemplateConfig { - return { - name: launchTemplate.name, - blockDeviceMappings: launchTemplate.blockDeviceMappings - ? this.processBlockDeviceReplacements(launchTemplate.blockDeviceMappings, firewallName) - : undefined, - securityGroups: this.processSecurityGroups(launchTemplate.securityGroups ?? [], vpc), - keyPair: launchTemplate.keyPair, - iamInstanceProfile: launchTemplate.iamInstanceProfile, - imageId: this.replaceImageId(launchTemplate.imageId), - instanceType: launchTemplate.instanceType, - enforceImdsv2: launchTemplate.enforceImdsv2, - networkInterfaces: launchTemplate.networkInterfaces - ? this.processNetworkInterfaces(launchTemplate.networkInterfaces, vpc) - : undefined, - userData: launchTemplate.userData, - }; - } - - /** - * Process and return network interface replacements - * @param networkInterfaces - * @param vpc - * @returns - */ - private processNetworkInterfaces( - networkInterfaces: NetworkInterfaceItemConfig[], - vpc: string, - ): NetworkInterfaceItemConfig[] { - const interfaceConfig: NetworkInterfaceItemConfig[] = []; - networkInterfaces.forEach(networkInterface => - interfaceConfig.push({ - associateCarrierIpAddress: networkInterface.associateCarrierIpAddress, - associateElasticIp: networkInterface.associateElasticIp, - associatePublicIpAddress: networkInterface.associatePublicIpAddress, - deleteOnTermination: networkInterface.deleteOnTermination, - description: networkInterface.description, - deviceIndex: networkInterface.deviceIndex, - groups: networkInterface.groups ? this.processSecurityGroups(networkInterface.groups, vpc) : undefined, - interfaceType: networkInterface.interfaceType, - networkCardIndex: networkInterface.networkCardIndex, - networkInterfaceId: networkInterface.networkInterfaceId, - privateIpAddress: networkInterface.privateIpAddress, - privateIpAddresses: networkInterface.privateIpAddresses, - secondaryPrivateIpAddressCount: networkInterface.secondaryPrivateIpAddressCount, - sourceDestCheck: networkInterface.sourceDestCheck, - subnetId: networkInterface.subnetId ? this.subnetMap.get(`${vpc}_${networkInterface.subnetId}`) : undefined, - }), - ); - return interfaceConfig; - } - - /** - * Process and return and array of security group IDs - * @param groups - * @param vpc - * @returns - */ - private processSecurityGroups(groups: string[], vpc: string): string[] { - const securityGroups: string[] = []; - if (groups.length > 0) { - groups.forEach(group => { - const securityGroupItem = this.securityGroupMap.get(`${vpc}_${group}`); - if (!securityGroupItem) { - this.logger.error(`Unable to retrieve security group ${group} from VPC ${vpc}`); - throw new Error(`Configuration validation failed at runtime.`); - } - securityGroups.push(securityGroupItem); - }); - } - - return securityGroups; - } - - /** - * Process and return replacements for an autoscaling config - * @param group - * @param vpc - * @returns - */ - private processAutoScalingReplacements(group: AutoScalingConfig, vpc: string): AutoScalingConfig { - return { - name: group.name, - minSize: group.minSize, - maxSize: group.maxSize, - desiredSize: group.desiredSize, - launchTemplate: group.launchTemplate, - healthCheckGracePeriod: group.healthCheckGracePeriod, - healthCheckType: group.healthCheckType, - targetGroups: group.targetGroups ? this.processTargetGroups(group.targetGroups) : undefined, - subnets: this.processSubnets(group.subnets, vpc), - maxInstanceLifetime: group.maxInstanceLifetime, - }; - } - - /** - * Process and return subnet ID replacements - * @param subnets - * @param vpc - * @returns - */ - private processSubnets(subnets: string[], vpc: string): string[] { - const processedSubnets: string[] = []; - if (subnets.length > 0) { - subnets.forEach(subnet => { - const subnetItem = this.subnetMap.get(`${vpc}_${subnet}`); - if (!subnetItem) { - this.logger.error(`Unable to retrieve subnet ${subnet} from VPC ${vpc}`); - throw new Error(`Configuration validation failed at runtime.`); - } - processedSubnets.push(subnetItem); - }); - } - - return processedSubnets; - } - - /** - * Process and return target group ARN replacements - * @param groups - * @returns - */ - private processTargetGroups(groups: string[]): string[] { - const targetGroups: string[] = []; - if (groups.length > 0) { - groups.forEach(group => { - const groupItem = this.targetGroupMap.get(group); - if (!groupItem) { - this.logger.error(`Unable to retrieve target group ${group}`); - throw new Error(`Configuration validation failed at runtime.`); - } - targetGroups.push(groupItem.targetGroupArn); - }); - } - - return targetGroups; - } - - /** - * Create Gateway Load Balancer resources. - */ - private createGwlbResources(): void { - // Create GWLB listeners - this.createGwlbListeners(); - // Create GWLB endpoints - this.createGwlbEndpointResources(); - } - - /** - * Create Gateway Load Balancer listeners - */ - private createGwlbListeners() { - for (const gwlbItem of this.props.networkConfig.centralNetworkServices?.gatewayLoadBalancers ?? []) { - if (gwlbItem.targetGroup && this.targetGroupMap.has(gwlbItem.targetGroup)) { - this.createGwlbListener(gwlbItem); - } - } - } - - /** - * Create a Gateway Load Balancer listener - * @param gwlbItem - */ - private createGwlbListener(gwlbItem: GwlbConfig) { - const loadBalancerArn = this.gwlbMap.get(gwlbItem.name); - const targetGroupArn = this.targetGroupMap.get(gwlbItem.targetGroup!)?.targetGroupArn; - if (!loadBalancerArn) { - this.logger.error(`Unable to retrieve Gateway Load Balancer ARN for ${gwlbItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!targetGroupArn) { - this.logger.error(`Unable to retrieve target group ARN for ${gwlbItem.targetGroup}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - this.logger.info( - `Creating listener on Gateway Load Balancer ${gwlbItem.name}: forwarding to target group ${gwlbItem.targetGroup}`, - ); - new cdk.aws_elasticloadbalancingv2.CfnListener(this, pascalCase(`${gwlbItem.vpc}${gwlbItem.name}Listener`), { - defaultActions: [ - { - type: 'forward', - targetGroupArn, - }, - ], - loadBalancerArn, - }); - } - - /** - * Create Gateway Load Balancer endpoint resources - */ - private createGwlbEndpointResources() { - for (const vpcItem of this.vpcsInScope) { - // Get account IDs - const vpcId = getVpc(this.vpcMap, vpcItem.name) as string; - // Create GWLB endpoints and set map - const gwlbEndpointMap = this.createGwlbEndpoints(vpcItem, vpcId); - - // Create GWLB route table entries - this.createGwlbRouteTableEntries(vpcItem, gwlbEndpointMap); - } - } - - /** - * Create GWLB endpoints for this stack's account ID and region - * @param vpcItem - * @param vpcId - * @returns - */ - private createGwlbEndpoints(vpcItem: VpcConfig | VpcTemplatesConfig, vpcId: string): Map { - const gwlbEndpointMap = new Map(); - if (this.props.networkConfig.centralNetworkServices?.gatewayLoadBalancers) { - const loadBalancers = this.props.networkConfig.centralNetworkServices.gatewayLoadBalancers; - // Create GWLB endpoints and add them to a map - for (const loadBalancerItem of loadBalancers) { - const lbItemEndpointMap = this.createGwlbEndpointMap(vpcId, vpcItem, loadBalancerItem, this.props.partition); - lbItemEndpointMap.forEach((endpoint, name) => gwlbEndpointMap.set(name, endpoint)); - } - } - return gwlbEndpointMap; - } - - /** - * Create Gateway Load Balancer endpoint map. - * @param vpcId - * @param vpcItem - * @param loadBalancerItem - * @param delegatedAdminAccountId - * @returns - */ - private createGwlbEndpointMap( - vpcId: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - loadBalancerItem: GwlbConfig, - partition: string, - ): Map { - const endpointMap = new Map(); - let endpointServiceId: string | undefined = undefined; - for (const endpointItem of loadBalancerItem.endpoints) { - if (endpointItem.vpc === vpcItem.name) { - // Get endpoint service ID - endpointServiceId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.GWLB_SERVICE, [loadBalancerItem.name]), - ); - // Create endpoint and add to map - const endpoint = this.createGwlbEndpointItem(endpointItem, vpcId, endpointServiceId, partition); - endpointMap.set(endpointItem.name, endpoint); - } - } - return endpointMap; - } - - /** - * Create Gateway Load Balancer endpoint item. - * - * @param endpointItem - * @param vpcId - * @param endpointServiceId - */ - private createGwlbEndpointItem( - endpointItem: GwlbEndpointConfig, - vpcId: string, - endpointServiceId: string, - partition: string, - ): VpcEndpoint { - const subnetKey = `${endpointItem.vpc}_${endpointItem.subnet}`; - const subnet = this.subnetMap.get(subnetKey); - - if (!subnet) { - this.logger.error( - `Create Gateway Load Balancer endpoint: subnet ${endpointItem.subnet} not found in VPC ${endpointItem.vpc}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create endpoint - this.logger.info( - `Add Gateway Load Balancer endpoint ${endpointItem.name} to VPC ${endpointItem.vpc} subnet ${endpointItem.subnet}`, - ); - return new VpcEndpoint(this, `${pascalCase(endpointItem.vpc)}Vpc${pascalCase(endpointItem.name)}GwlbEp`, { - service: endpointServiceId, - vpcEndpointType: VpcEndpointType.GWLB, - vpcId, - subnets: [subnet], - partition: partition, - }); - } - - /** - * Create GWLB endpoint route table entries. - * @param vpcItem - * @param gwlbEndpointMap - */ - private createGwlbRouteTableEntries( - vpcItem: VpcConfig | VpcTemplatesConfig, - gwlbEndpointMap: Map, - ): void { - for (const routeTableItem of vpcItem.routeTables ?? []) { - for (const routeTableEntryItem of routeTableItem.routes ?? []) { - this.createGwlbRouteTableEntryItem(vpcItem.name, routeTableItem.name, routeTableEntryItem, gwlbEndpointMap); - } - } - } - - /** - * Create GWLB route table entry item. - * @param vpcName - * @param routeTableName - * @param routeTableEntryItem - * @param gwlbEndpointMap - */ - private createGwlbRouteTableEntryItem( - vpcName: string, - routeTableName: string, - routeTableEntryItem: RouteTableEntryConfig, - gwlbEndpointMap: Map, - ): void { - const endpointRouteId = - pascalCase(`${vpcName}Vpc`) + pascalCase(`${routeTableName}RouteTable`) + pascalCase(routeTableEntryItem.name); - - if (routeTableEntryItem.type && routeTableEntryItem.type === 'gatewayLoadBalancerEndpoint') { - // Get endpoint and route table items - const gwlbEndpoint = gwlbEndpointMap.get(routeTableEntryItem.target!); - const routeTableId = this.routeTableMap.get(`${vpcName}_${routeTableName}`); - const [destination, ipv6Destination] = this.setRouteEntryDestination( - routeTableEntryItem, - this.ipamSubnetArray, - vpcName, - ); - - // Check if route table exists im map - if (!routeTableId) { - this.logger.error(`Unable to locate route table ${routeTableName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - if (!gwlbEndpoint) { - this.logger.error(`Unable to locate endpoint ${routeTableEntryItem.target}`); - throw new Error(`Configuration validation failed at runtime.`); - } - // Add route - this.logger.info(`Adding Gateway Load Balancer endpoint Route Table Entry ${routeTableEntryItem.name}`); - gwlbEndpoint.createEndpointRoute(endpointRouteId, routeTableId, destination, ipv6Destination); - } - } - - /** - * Create network interface route table entries. - */ - private createNetworkInterfaceRouteTableEntries(): void { - const crossAccountRouteTableMap = this.getCrossAccountRouteTableIds(this.networkInterfaceRouteArray); - - for (const route of this.networkInterfaceRouteArray) { - if (route.vpcOwnedByAccount) { - this.createNetworkInterfaceRouteForOwnedVpc(route); - } else if (route.firewallOwnedByAccount) { - this.createNetworkInterfaceRouteForOwnedFirewall(route, crossAccountRouteTableMap); - } - } - } - - /** - * Retrieves a map of cross-account route table IDs - * @param routeDetailsArray networkInterfaceRouteDetails[] - * @returns Map - */ - private getCrossAccountRouteTableIds(routeDetailsArray: networkInterfaceRouteDetails[]): Map { - const crossAccountRouteTableMap = new Map(); - for (const route of routeDetailsArray) { - if (route.firewallOwnedByAccount && !crossAccountRouteTableMap.has(route.routeTableName)) { - const vpcOwnerAccount = getVpcOwnerAccountName(this.props.networkConfig.vpcs, route.vpcName); - const vpcOwnerAccountId = this.props.accountsConfig.getAccountId(vpcOwnerAccount); - // get cross-account route table id - const routeTableId = new SsmParameterLookup(this, pascalCase(`SsmParamLookup${route.routeTableName}`), { - name: this.getSsmPath(SsmResourceType.ROUTE_TABLE, [route.vpcName, route.routeTableName]), - accountId: vpcOwnerAccountId, - parameterRegion: cdk.Stack.of(this).region, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${cdk.Stack.of(this).region}`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - crossAccountRouteTableMap.set(`${route.routeTableName}`, routeTableId); - } - } - return crossAccountRouteTableMap; - } - - /** - * Create network interface route table entries for entries in VPCs shared to this account. - */ - private createNetworkInterfaceRouteForOwnedFirewall( - routeDetails: networkInterfaceRouteDetails, - crossAccountRouteTableMap: Map, - ): void { - if (routeDetails.firewallName && routeDetails.eniIndex !== undefined) { - const networkInterfaceId = this.getNetworkInterfaceIdFromFirewall( - routeDetails.firewallName, - routeDetails.eniIndex!, - ); - this.logger.info( - `Creating cross-account route targeting ENI of firewall ${routeDetails.firewallName} owned by this account in VPC ${routeDetails.vpcName}`, - ); - - this.createCrossAccountNetworkInterfaceRoute( - routeDetails.vpcName, - routeDetails.routeTableName, - routeDetails.routeEntry, - networkInterfaceId, - crossAccountRouteTableMap, - ); - } - } - - /** - * Create network interface route table entries for entries in VPCs owned by this account. - */ - private createNetworkInterfaceRouteForOwnedVpc(routeDetails: networkInterfaceRouteDetails): void { - let networkInterfaceId: string; - if (routeDetails.firewallName && routeDetails.firewallOwnedByAccount) { - this.logger.info( - `Creating route targeting ENI of firewall ${routeDetails.firewallName} in VPC ${routeDetails.vpcName}`, - ); - networkInterfaceId = this.getNetworkInterfaceIdFromFirewall(routeDetails.firewallName, routeDetails.eniIndex!); - } else if (routeDetails.firewallName && !routeDetails.firewallOwnedByAccount) { - this.logger.debug( - `Skipping route targeting ENI of firewall ${routeDetails.firewallName} owned by different account in VPC ${routeDetails.vpcName}`, - ); - return; - } else { - this.logger.info( - `Creating route targeting explicit ENI ${routeDetails.routeEntry.target}} in VPC ${routeDetails.vpcName}`, - ); - networkInterfaceId = routeDetails.routeEntry.target!; - } - this.createNetworkInterfaceRoute( - routeDetails.vpcName, - routeDetails.routeTableName, - routeDetails.routeEntry, - networkInterfaceId, - ); - } - - /** - * Create VPC Route Table route targeting Elastic Network Interface (ENI) in another account - */ - private createCrossAccountNetworkInterfaceRoute( - vpcName: string, - routeTableName: string, - routeTableEntryItem: RouteTableEntryConfig, - networkInterfaceId: string, - crossAccountRouteTableMap: Map, - ): void { - this.logger.info(`Adding cross-account Network Interface Route Table Entry ${routeTableEntryItem.name}`); - const routeId = - pascalCase(`${vpcName}Vpc`) + pascalCase(`${routeTableName}RouteTable`) + pascalCase(routeTableEntryItem.name); - - const vpcOwnerAccount = getVpcOwnerAccountName(this.props.networkConfig.vpcs, vpcName); - const vpcOwnerAccountId = this.props.accountsConfig.getAccountId(vpcOwnerAccount); - const routeTableId = crossAccountRouteTableMap.get(routeTableName); - - if (!routeTableId) { - this.logger.error( - `Attempting to create cross-account route ${routeTableEntryItem.name} but route table does not exist`, - ); - throw new Error('No cross-account route table target'); - } - if (!this.crossAcctRouteProvider) { - this.logger.error( - `Attempting to create cross-account route ${routeTableEntryItem.name} but cross-account route provider does not exist`, - ); - throw new Error('No cross route provider'); - } - new CrossAccountRoute(this, routeId, { - ownerAccount: vpcOwnerAccountId, - ownerRegion: cdk.Stack.of(this).region, - partition: cdk.Stack.of(this).partition, - provider: this.crossAcctRouteProvider, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${cdk.Stack.of(this).region}`, - routeTableId: routeTableId, - destination: routeTableEntryItem.destination, - ipv6Destination: routeTableEntryItem.ipv6Destination, - networkInterfaceId: networkInterfaceId, - }); - } - - /** - * Create VPC Route Table route targeting Elastic Network Interface (ENI) - */ - private createNetworkInterfaceRoute( - vpcName: string, - routeTableName: string, - routeTableEntryItem: RouteTableEntryConfig, - networkInterfaceId: string, - ): void { - this.logger.info(`Adding Network Interface Route Table Entry ${routeTableEntryItem.name}`); - const routeTableId = getRouteTable(this.routeTableMap, vpcName, routeTableName) as string; - const routeId = - pascalCase(`${vpcName}Vpc`) + pascalCase(`${routeTableName}RouteTable`) + pascalCase(routeTableEntryItem.name); - - new cdk.aws_ec2.CfnRoute(this, routeId, { - destinationCidrBlock: routeTableEntryItem.destination, - destinationIpv6CidrBlock: routeTableEntryItem.ipv6Destination, - networkInterfaceId: networkInterfaceId, - routeTableId, - }); - } - - /** - * Returns true if the target of the networkInterface route is an EC2 firewall instance - * @param vpcItem - * @param gwlbEndpointMap - */ - private isNetworkInterfaceTargetLookup(routeTarget: string | undefined, routeTableEntryName: string): boolean { - if (routeTarget?.startsWith('eni-')) { - return false; - } else if (routeTarget?.match('\\${ACCEL_LOOKUP::EC2:ENI_([a-zA-Z0-9-/:]*)}')) { - return true; - } else { - this.logger.error(`Unable to retrieve target ${routeTarget} for route table entry ${routeTableEntryName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Get Id of the network interface (ENI) associated with a firewall instance. - * @param firewallName - * @param deviceIndex - */ - private getNetworkInterfaceIdFromFirewall(firewallName: string, deviceIndex: number): string { - const firewall = this.instanceMap.get(firewallName); - const eni = firewall!.getNetworkInterface(deviceIndex); - - if (!eni.networkInterfaceId) { - this.logger.error( - `Could not retrieve network interface id for eni at index ${deviceIndex.toString()} from firewall ${firewallName}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - return eni.networkInterfaceId; - } - - /** - * Function to check for cross-account route table entries - * @returns boolean - */ - private isCrossAccountRouteFramework(): boolean { - const crossAccountVpcs: (VpcConfig | VpcTemplatesConfig)[] = this.getCrossAccountVpcsWithFirewalls(); - if (crossAccountVpcs.length > 0) { - return true; - } - return false; - } - - /** - * Returns a list of VPC configs not owned by this account that host firewalls owned by this account - * @returns - */ - private getCrossAccountVpcsWithFirewalls(): (VpcConfig | VpcTemplatesConfig)[] { - const crossAccountVpcs: (VpcConfig | VpcTemplatesConfig)[] = []; - if (this.instanceMap.size > 0) { - const firewallInstances = [ - ...(this.props.customizationsConfig.firewalls?.instances ?? []), - ...(this.props.customizationsConfig.firewalls?.managerInstances ?? []), - ]; - for (const firewallInstance of firewallInstances) { - if ( - firewallInstance.account && - this.props.accountsConfig.getAccountId(firewallInstance.account) === cdk.Stack.of(this).account - ) { - this.logger.info( - `Firewall ${firewallInstance.name} owned by this account ${firewallInstance.account} is deployed in VPC ${firewallInstance.vpc} owned by another account`, - ); - const vpcConfig = getVpcConfig(this.vpcResources, firewallInstance.vpc); - crossAccountVpcs.push(vpcConfig); - } - } - } - return crossAccountVpcs; - } - - /** - * Create a custom resource provider to handle cross-account VPC peering routes - * @returns - */ - private createCrossAcctRouteProvider(): cdk.custom_resources.Provider | undefined { - if (this.isCrossAccountRouteFramework()) { - const provider = new CrossAccountRouteFramework(this, 'CrossAccountRouteFramework', { - acceleratorPrefix: this.props.prefixes.accelerator, - logGroupKmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }).provider; - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteFunction/ServiceRole/Resource`, - [{ id: 'AwsSolutions-IAM4', reason: 'Custom resource Lambda role policy.' }], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteFunction/ServiceRole/DefaultPolicy/Resource`, - [{ id: 'AwsSolutions-IAM5', reason: 'Custom resource Lambda role policy.' }], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteProvider/framework-onEvent/ServiceRole/Resource`, - [{ id: 'AwsSolutions-IAM4', reason: 'Custom resource Lambda role policy.' }], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [{ id: 'AwsSolutions-IAM5', reason: 'Custom resource Lambda role policy.' }], - ); - - return provider; - } - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/network-associations-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/network-associations-stack.ts deleted file mode 100644 index f3bcae7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/network-associations-stack.ts +++ /dev/null @@ -1,3822 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; - -import { - ApplicationLoadBalancerConfig, - ApplicationLoadBalancerListenerConfig, - AseaResourceType, - CustomerGatewayConfig, - DxGatewayConfig, - DxTransitGatewayAssociationConfig, - ManagedActiveDirectoryConfig, - NetworkAclConfig, - NetworkAclInboundRuleConfig, - NetworkAclOutboundRuleConfig, - NetworkAclSubnetSelection, - NetworkLoadBalancerConfig, - NlbTargetTypeConfig, - RouteTableConfig, - RouteTableEntryConfig, - SubnetConfig, - Tag, - TargetGroupItemConfig, - TransitGatewayAttachmentConfig, - TransitGatewayConfig, - TransitGatewayRouteEntryConfig, - TransitGatewayRouteTableConfig, - TransitGatewayRouteTableDxGatewayEntryConfig, - TransitGatewayRouteTableTgwPeeringEntryConfig, - TransitGatewayRouteTableVpcEntryConfig, - TransitGatewayRouteTableVpnEntryConfig, - VpcConfig, - VpcPeeringConfig, - VpcTemplatesConfig, - VpnConnectionConfig, - isNetworkType, -} from '@aws-accelerator/config'; -import { ShareTargets } from '@aws-accelerator/config/dist/lib/common/types'; -import { - ActiveDirectory, - ActiveDirectoryConfiguration, - ActiveDirectoryResolverRule, - AssociateHostedZones, - CrossAccountRouteFramework, - DirectConnectGatewayAssociation, - DirectConnectGatewayAssociationProps, - IpamSubnet, - KeyLookup, - NLBAddresses, - PutSsmParameter, - QueryLoggingConfigAssociation, - ResolverFirewallRuleGroupAssociation, - ResolverRuleAssociation, - ShareActiveDirectory, - ShareSubnetTags, - SsmParameterLookup, - SubnetIdLookup, - TargetGroup, - TransitGatewayAttachment, - TransitGatewayAttachmentType, - TransitGatewayConnect, - TransitGatewayPrefixListReference, - TransitGatewayRouteTableAssociation, - TransitGatewayRouteTablePropagation, - TransitGatewayStaticRoute, - UserDataScriptsType, - VpcIdLookup, - VpcPeering, - albListenerActionProperty, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -import path from 'path'; -import { AcceleratorStackProps, NagSuppressionRuleIds } from '../../accelerator-stack'; -import { NetworkStack } from '../network-stack'; -import { getPrefixList } from '../utils/getter-utils'; -import { isEc2FirewallVpnRoute, isIpv4 } from '../utils/validation-utils'; -import { SharedResources } from './shared-resources'; - -interface Peering { - name: string; - requester: VpcConfig | VpcTemplatesConfig; - accepter: VpcConfig | VpcTemplatesConfig; - tags: cdk.CfnTag[] | undefined; - crossAccount: boolean; -} - -export class NetworkAssociationsStack extends NetworkStack { - private dnsFirewallMap: Map; - private dxGatewayMap: Map; - private peeringList: Peering[]; - private prefixListMap: Map; - private queryLogMap: Map; - private resolverRuleMap: Map; - private routeTableMap: Map; - private transitGateways: Map; - private transitGatewayRouteTables: Map; - private transitGatewayAttachments: Map; - private crossAcctRouteProvider?: cdk.custom_resources.Provider; - - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - try { - // Set private properties - this.dnsFirewallMap = new Map(); - this.dxGatewayMap = new Map(); - this.queryLogMap = new Map(); - this.resolverRuleMap = new Map(); - this.transitGateways = new Map(); - this.transitGatewayAttachments = new Map(); - this.transitGatewayRouteTables = new Map(); - - // - // Build VPC peering list - // - this.peeringList = this.setPeeringList(); - - // - // Create cross-account peering route provider, if required - // - this.crossAcctRouteProvider = this.createCrossAcctRouteProvider(); - - // - // Build prefix list map - // - this.prefixListMap = this.setPrefixListMap(props); - - // - // Build route table map - // - this.routeTableMap = this.setRouteTableMap(this.vpcsInScope); - // Get cross-account prefix list IDs as needed - const crossAcctRouteTables = this.setCrossAcctRouteTables(); - crossAcctRouteTables.forEach((value, key) => this.routeTableMap.set(key, value)); - - // - // Create transit gateway route table associations, propagations, - // for VPC and VPN attachments - // - this.createTransitGatewayResources(props); - - // - // Create Route 53 private hosted zone associations - // - this.createHostedZoneAssociations(); - - // - // Create central network service VPC associations - // - this.createCentralNetworkAssociations(props); - - // - // Create central network service VPC associations - // - this.createRoute53LocalVpcResources(props); - - // - // Create VPC peering connections - // - this.createVpcPeeringConnections(); - - // - // Create Direct Connect resources - // - this.createDirectConnectResources(props); - - // - // Create transit gateway static routes, blackhole - // routes, and prefix list references - // - this.createTransitGatewayStaticRoutes(props); - - // - // Create cross-account NACL Rules for IPAM Subnets - // - this.createCrossAccountNaclRules(); - - // - // Creates target groups for ALBs and NLBs - // - const targetGroupMap = this.createIpAndInstanceTargetGroups(); - - // - // Creates ALB Listeners - // - const albListenerMap = this.createAlbListeners(targetGroupMap); - - // - // Create ALB target Groups after ALB listeners have been created - // - const albTargetGroupMap = this.createAlbTargetGroups(albListenerMap); - - // - // Create NLB Listeners - // - const allTargetGroupsMap = new Map([...targetGroupMap, ...albTargetGroupMap]); - this.createNlbListeners(allTargetGroupsMap); - - // - // Apply tags to shared VPC/subnet resources - // - this.shareSubnetTags(); - - // - // Create resources for RAM shared subnets - // - const sharedVpcMap = this.setVpcMap(this.sharedVpcs); - new SharedResources(this, sharedVpcMap, this.prefixListMap, targetGroupMap, props); - - // - // Create SSM parameters - // - this.createSsmParameters(); - // Create managed active directories - // - this.createManagedActiveDirectories(); - - // - // Create Transit Gateway Connect resources for Vpc Attachments - // - this.createTransitGatewayConnect(props); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } catch (err) { - this.logger.error(`${err}`); - throw err; - } - } - - /** - * Function to create listeners for given network load balancer - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param nlbItem {@link NetworkLoadBalancerConfig} - * @param nlbId string - * @param targetGroupMap Map - * - */ - private createNetworkLoadBalancerListeners( - vpcItem: VpcConfig | VpcTemplatesConfig, - nlbItem: NetworkLoadBalancerConfig, - nlbId: string, - targetGroupMap: Map, - ): void { - for (const listener of nlbItem.listeners ?? []) { - const targetGroup = targetGroupMap.get(`${vpcItem.name}-${listener.targetGroup}`); - if (!targetGroup) { - this.logger.error( - `The Listener ${listener.name} contains an invalid target group name ${listener.targetGroup} please ensure that the the target group name references a valid target group`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - new cdk.aws_elasticloadbalancingv2.CfnListener( - this, - pascalCase(`Listener${vpcItem.name}${nlbItem.name}${listener.name}`), - { - defaultActions: [ - { - type: 'forward', - forwardConfig: { - targetGroups: [ - { - targetGroupArn: targetGroup.targetGroupArn, - }, - ], - }, - targetGroupArn: targetGroup.targetGroupArn, - }, - ], - loadBalancerArn: nlbId, - alpnPolicy: [listener.alpnPolicy!], - certificates: [{ certificateArn: this.getCertificate(listener.certificate) }], - port: listener.port!, - protocol: listener.protocol!, - sslPolicy: listener.sslPolicy!, - }, - ); - } - } - - private createNlbListeners(targetGroupMap: Map) { - for (const vpcItem of this.vpcResources) { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - for (const nlbItem of vpcItem.loadBalancers?.networkLoadBalancers ?? []) { - const nlbId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.NLB, [vpcItem.name, nlbItem.name]), - ); - this.createNetworkLoadBalancerListeners(vpcItem, nlbItem, nlbId, targetGroupMap); - } - } - } - } - - /** - * Function to create Application load balancer - * @param vpcName string - * @param vpcId string - * @param targetGroupItem {@link TargetGroupItemConfig} - * @param albNames string[] - * @param loadBalancerListenerMap Map - * @returns TargetGroup {@link TargetGroup} - */ - private createApplicationLoadBalancerTargetGroup( - vpcName: string, - vpcId: string, - targetGroupItem: TargetGroupItemConfig, - albNames: string[], - loadBalancerListenerMap: Map, - ): TargetGroup { - const updatedTargets = targetGroupItem.targets?.map(target => { - if (albNames.includes(target as string)) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ALB, [vpcName, target as string]), - ); - } - return target; - }) as string[]; - - const targetGroup = new TargetGroup(this, pascalCase(`TargetGroup${targetGroupItem.name}`), { - name: targetGroupItem.name, - port: targetGroupItem.port, - protocol: targetGroupItem.protocol, - protocolVersion: targetGroupItem.protocolVersion! || undefined, - type: targetGroupItem.type, - attributes: targetGroupItem.attributes ?? undefined, - healthCheck: targetGroupItem.healthCheck ?? undefined, - threshold: targetGroupItem.threshold ?? undefined, - matcher: targetGroupItem.matcher ?? undefined, - targets: updatedTargets, - vpc: vpcId, - }); - for (const [key, value] of loadBalancerListenerMap.entries()) { - if (key.startsWith(vpcName)) { - targetGroup.node.addDependency(value); - } - } - - return targetGroup; - } - - /** - * Function to create application load balancer target groups - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param loadBalancerListenerMap Map - * @param targetGroupMap Map - */ - private createApplicationLoadBalancerTargetGroups( - vpcItem: VpcConfig | VpcTemplatesConfig, - loadBalancerListenerMap: Map, - targetGroupMap: Map, - ): void { - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - const albTargetGroups = vpcItem.targetGroups?.filter(targetGroup => targetGroup.type === 'alb') ?? []; - const albNames = vpcItem.loadBalancers?.applicationLoadBalancers?.map(alb => alb.name) ?? []; - - for (const targetGroupItem of albTargetGroups) { - if (this.isManagedByAsea(AseaResourceType.EC2_TARGET_GROUP, `${targetGroupItem.name}`)) { - this.logger.info(`Application Load Balancer Target Group ${targetGroupItem.name} is managed externally.`); - continue; - } - const targetGroup = this.createApplicationLoadBalancerTargetGroup( - vpcItem.name, - vpcId, - targetGroupItem, - albNames, - loadBalancerListenerMap, - ); - - targetGroupMap.set(`${vpcItem.name}-${targetGroupItem.name}`, targetGroup); - } - } - - private createAlbTargetGroups(albListenerMap: Map) { - const targetGroupMap = new Map(); - for (const vpcItem of this.vpcResources) { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - this.createApplicationLoadBalancerTargetGroups(vpcItem, albListenerMap, targetGroupMap); - } - } - return targetGroupMap; - } - - /** - * Function to create AApplication LoadBalancer listeners - * @param vpcItem {@link VpcConfig } | {@link VpcTemplatesConfig} - * @param albItem {@link ApplicationLoadBalancerConfig} - * @param albArn string - * @param targetGroupMap Map - * @param listenerMap Map - */ - private createApplicationLoadBalancerListeners( - vpcItem: VpcConfig | VpcTemplatesConfig, - albItem: ApplicationLoadBalancerConfig, - albArn: string, - targetGroupMap: Map, - listenerMap: Map, - ): void { - for (const listener of albItem.listeners ?? []) { - if (this.isManagedByAsea(AseaResourceType.APPLICATION_LOAD_BALANCER, `${albItem.name}`)) { - this.logger.info(`Application Load Balancer ${albItem.name} is managed externally.`); - continue; - } - const targetGroup = targetGroupMap.get(`${vpcItem.name}-${listener.targetGroup}`); - if (!targetGroup) { - this.logger.error( - `The Listener ${listener.name} contains an invalid target group name ${listener.targetGroup} please ensure that the the target group name references a valid target group`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - const listenerAction: cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty = this.getListenerAction( - listener, - targetGroup.targetGroupArn, - ); - - const listenerResource = new cdk.aws_elasticloadbalancingv2.CfnListener( - this, - pascalCase(`Listener${vpcItem.name}${albItem.name}${listener.name}`), - { - defaultActions: [listenerAction], - loadBalancerArn: albArn, - certificates: [{ certificateArn: this.getCertificate(listener.certificate) }], - port: listener.port, - protocol: listener.protocol, - sslPolicy: listener.sslPolicy!, - }, - ); - listenerMap.set(`${vpcItem.name}-${albItem.name}-${listener.name}`, listenerResource); - } - } - - private createAlbListeners(targetGroupMap: Map) { - try { - const listenerMap = new Map(); - for (const vpcItem of this.props.networkConfig.vpcs ?? []) { - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers ?? []) { - // Logic to determine that ALBs that are not shared are only created in the account of the VPC. - const accountId = this.props.accountsConfig.getAccountId(vpcItem.account); - if (!albItem.shareTargets && this.isTargetStack([accountId], [vpcItem.region])) { - const albArn = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ALB, [vpcItem.name, albItem.name]), - ); - this.createApplicationLoadBalancerListeners(vpcItem, albItem, albArn, targetGroupMap, listenerMap); - } - } - } - return listenerMap; - } catch (err) { - this.logger.error(err); - throw err; - } - } - public getCertificate(certificate: string | undefined) { - if (certificate) { - //check if user provided arn. If so do nothing, if not get it from ssm - if (certificate.match('\\arn:*')) { - return certificate; - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ACM_CERT, [certificate]), - ); - } - } - return undefined; - } - - public getListenerAction( - listener: ApplicationLoadBalancerListenerConfig, - targetGroupArn: string, - ): cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty { - const listenerTargetGroupArn = targetGroupArn; - const actionValues: albListenerActionProperty = { - type: listener.type, - order: listener.order, - targetGroupArn: listenerTargetGroupArn, - }; - if (listener.type === 'forward') { - actionValues.forwardConfig = { - targetGroups: [{ targetGroupArn: targetGroupArn }], - targetGroupStickinessConfig: listener.forwardConfig?.targetGroupStickinessConfig, - }; - } else if (listener.type === 'redirect') { - if (listener.redirectConfig) { - actionValues.redirectConfig = { - host: listener.redirectConfig.host, - path: listener.redirectConfig.path, - port: listener.redirectConfig.port?.toString() ?? undefined, - protocol: listener.redirectConfig.protocol, - query: listener.redirectConfig.query, - statusCode: listener.redirectConfig.statusCode, - }; - } else { - this.logger.error(`Listener ${listener.name} is set to redirect but redirectConfig is not defined`); - throw new Error(`Configuration validation failed at runtime.`); - } - } else if (listener.type === 'fixed-response') { - if (listener.fixedResponseConfig) { - actionValues.fixedResponseConfig = { - contentType: listener.fixedResponseConfig.contentType, - messageBody: listener.fixedResponseConfig.messageBody, - statusCode: listener.fixedResponseConfig.statusCode, - }; - } else { - this.logger.error(`Listener ${listener.name} is set to fixed-response but fixedResponseConfig is not defined`); - throw new Error(`Configuration validation failed at runtime.`); - } - } else { - this.logger.error(`Listener ${listener.name} is not set to forward, fixed-response or redirect`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return actionValues as cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty; - } - /** - * Create Ip based target group - */ - private createIpTargetGroup( - targetGroupItem: TargetGroupItemConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - ) { - // Create IP Target Group if no Targets are specified, otherwise, evaluate NLB/static IP targets - if (!targetGroupItem.targets) { - return new TargetGroup(this, `${vpcItem.name}-${targetGroupItem.name}`, { - name: targetGroupItem.name, - port: targetGroupItem.port, - protocol: targetGroupItem.protocol, - protocolVersion: targetGroupItem.protocolVersion, - type: targetGroupItem.type, - attributes: targetGroupItem.attributes, - healthCheck: targetGroupItem.healthCheck, - threshold: targetGroupItem.threshold, - matcher: targetGroupItem.matcher, - vpc: vpcId, - }); - } else { - try { - //Get NLB Targets in Ip Targets for lookup - const nlbTargets = targetGroupItem.targets?.filter(target => { - return typeof target !== 'string'; - }) as NlbTargetTypeConfig[]; - - //Set AccountIds for lookup Custom Resource - const nlbTargetsWithAccountIds = - (nlbTargets.map(nlbTarget => { - const accountId = this.props.accountsConfig.getAccountId(nlbTarget.account); - return { - account: accountId, - region: nlbTarget.region, - nlbName: nlbTarget.nlbName, - }; - }) as NlbTargetTypeConfig[]) ?? []; - - //Get targets containing an IP Address only - const staticIpTargets: (NlbTargetTypeConfig | string)[] = - (targetGroupItem.targets?.filter(target => typeof target === 'string') as string[]) ?? []; - // If NLB targets exist, send both static ips and NLB targets to custom resource to create one entry for the target group - if (nlbTargetsWithAccountIds && nlbTargetsWithAccountIds.length > 0) { - const nlbAddresses = new NLBAddresses(this, `${targetGroupItem.name}-ipLookup`, { - targets: [...nlbTargetsWithAccountIds, ...staticIpTargets], - assumeRoleName: `${this.props.prefixes.accelerator}-GetNLBIPAddressLookup`, - partition: cdk.Stack.of(this).partition, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - return new TargetGroup(this, `${vpcItem.name}-${targetGroupItem.name}`, { - name: targetGroupItem.name, - port: targetGroupItem.port, - protocol: targetGroupItem.protocol, - protocolVersion: targetGroupItem.protocolVersion, - type: targetGroupItem.type, - attributes: targetGroupItem.attributes, - healthCheck: targetGroupItem.healthCheck, - threshold: targetGroupItem.threshold, - matcher: targetGroupItem.matcher, - targets: nlbAddresses.ipAddresses, - vpc: vpcId, - }); - } else { - // If only IP addresses exist, skip CR Lookup and make the target group directly - return new TargetGroup(this, `${vpcItem.name}-${targetGroupItem.name}`, { - name: targetGroupItem.name, - port: targetGroupItem.port, - protocol: targetGroupItem.protocol, - protocolVersion: targetGroupItem.protocolVersion, - type: targetGroupItem.type, - attributes: targetGroupItem.attributes, - healthCheck: targetGroupItem.healthCheck, - threshold: targetGroupItem.threshold, - matcher: targetGroupItem.matcher, - targets: staticIpTargets as string[], - vpc: vpcId, - }); - } - } catch (err) { - this.logger.error( - `Could not create target group for ${targetGroupItem.name} in vpc ${vpcItem.name}. Please review the target group configuration`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - } - } - - private createInstanceTargetGroups( - targetGroupItem: TargetGroupItemConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - ) { - return new TargetGroup(this, `${vpcItem.name}-${targetGroupItem.name}`, { - name: targetGroupItem.name, - port: targetGroupItem.port, - protocol: targetGroupItem.protocol, - protocolVersion: targetGroupItem.protocolVersion, - type: targetGroupItem.type, - attributes: targetGroupItem.attributes, - healthCheck: targetGroupItem.healthCheck, - threshold: targetGroupItem.threshold, - matcher: targetGroupItem.matcher, - targets: targetGroupItem.targets as string[], - vpc: vpcId, - }); - } - - /** - * Function to create instance or IP target groups - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param vpcId string - * @param targetGroupItem {@link TargetGroupItemConfig} - * @param targetGroupMap Map - */ - private createInstanceOrIpTargetGroups( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - targetGroupItem: TargetGroupItemConfig, - targetGroupMap: Map, - ): void { - if (targetGroupItem.type === 'ip') { - const targetGroup = this.createIpTargetGroup(targetGroupItem, vpcItem, vpcId); - targetGroupMap.set(`${vpcItem.name}-${targetGroupItem.name}`, targetGroup); - } - if (targetGroupItem.type === 'instance') { - const targetGroup = this.createInstanceTargetGroups(targetGroupItem, vpcItem, vpcId); - targetGroupMap.set(`${vpcItem.name}-${targetGroupItem.name}`, targetGroup); - } - } - - private createIpAndInstanceTargetGroups() { - try { - const targetGroupMap = new Map(); - for (const vpcItem of this.props.networkConfig.vpcs) { - for (const targetGroupItem of vpcItem.targetGroups ?? []) { - if (targetGroupItem.shareTargets) { - const sharedTargetGroup = this.checkResourceShare(targetGroupItem.shareTargets); - if (sharedTargetGroup && vpcItem.region === cdk.Stack.of(this).region) { - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - this.createInstanceOrIpTargetGroups(vpcItem, vpcId, targetGroupItem, targetGroupMap); - } - } - const vpcAccountId = this.props.accountsConfig.getAccountId(vpcItem.account); - if (!targetGroupItem.shareTargets && this.isTargetStack([vpcAccountId], [vpcItem.region])) { - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - this.createInstanceOrIpTargetGroups(vpcItem, vpcId, targetGroupItem, targetGroupMap); - } - } - } - - return targetGroupMap; - } catch (err) { - this.logger.error(err); - throw err; - } - } - /** - * Create an array of peering connections - */ - private setPeeringList() { - const peeringList: Peering[] = []; - /** - * Verifies if given peering configuration is cross account or not. - * @param peering - * @returns - * False: If one of the peering vpcs is from a vpcTemplate and there is only one and same account matching vpcs. - * True: If one of the peering vpcs is from a vpcTemplate and length of vpc deployment target is not same or there is additional account in deployment target apart from accepter. - * True: If region of VPCs is different. - * True: If both vpcs are from vpcs configuration with different account. - */ - const isCrossAccountPeering = (peering: VpcPeeringConfig) => { - const requesterVpc = this.vpcResources.find(item => item.name === peering.vpcs[0])!; - const accepterVpc = this.vpcResources.find(item => item.name === peering.vpcs[1])!; - const requesterAccountIds = this.getVpcAccountIds(requesterVpc); - const accepterAccountIds = this.getVpcAccountIds(accepterVpc); - let crossAccount = false; - if ( - isNetworkType('IVpcTemplatesConfig', requesterVpc) || - isNetworkType('IVpcTemplatesConfig', accepterVpc) - ) { - crossAccount = - requesterVpc.region !== accepterVpc.region || - requesterAccountIds.length !== accepterAccountIds.length || - requesterAccountIds.filter(requesterAccountId => !accepterAccountIds.includes(requesterAccountId)).length > 0; - } else { - crossAccount = requesterVpc.account !== accepterVpc.account || requesterVpc.region !== accepterVpc.region; - } - return crossAccount; - }; - - for (const peering of this.props.networkConfig.vpcPeering ?? []) { - // Get requester and accepter VPC configurations - const requesterVpc = this.vpcResources.find(item => item.name === peering.vpcs[0])!; - const accepterVpc = this.vpcResources.find(item => item.name === peering.vpcs[1])!; - const requesterAccountIds = this.getVpcAccountIds(requesterVpc); - // Check if requester VPC is in this account and region - if (this.isTargetStack(requesterAccountIds, [requesterVpc.region])) { - peeringList.push({ - name: peering.name, - requester: requesterVpc, - accepter: accepterVpc, - tags: peering.tags, - crossAccount: isCrossAccountPeering(peering), - }); - } - } - return peeringList; - } - - /** - * Function to check route table entry if Cross Account Route Framework or not - * @returns boolean - */ - private isCrossAccountRouteFramework(): boolean { - let crossAccountRouteFramework = false; - for (const peering of this.peeringList) { - for (const routeTable of peering.accepter.routeTables ?? []) { - for (const routeTableEntry of routeTable.routes ?? []) { - if ( - routeTableEntry.type && - routeTableEntry.type === 'vpcPeering' && - routeTableEntry.target === peering.name - ) { - crossAccountRouteFramework = peering.crossAccount; - } - } - } - } - - return crossAccountRouteFramework; - } - - /** - * Create a custom resource provider to handle cross-account VPC peering routes - * @returns - */ - private createCrossAcctRouteProvider(): cdk.custom_resources.Provider | undefined { - if (this.isCrossAccountRouteFramework()) { - const provider = new CrossAccountRouteFramework(this, 'CrossAccountRouteFramework', { - acceleratorPrefix: this.props.prefixes.accelerator, - logGroupKmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }).provider; - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteFunction/ServiceRole/Resource`, - reason: 'Custom resource provider requires managed policy', - }, - { - path: `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource provider requires managed policy', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource provider requires access to assume cross-account role', - }, - { - path: `${this.stackName}/CrossAccountRouteFramework/CrossAccountRouteProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource provider requires access to assume cross-account role', - }, - ], - }); - - return provider; - } - return undefined; - } - - /** - * Create a map of prefix list IDs - * @param props - */ - private setPrefixListMap(props: AcceleratorStackProps): Map { - const prefixListMap = new Map(); - for (const prefixListItem of props.networkConfig.prefixLists ?? []) { - const prefixListTargets = this.getPrefixListTargets(prefixListItem); - - if (this.isTargetStack(prefixListTargets.accountIds, prefixListTargets.regions)) { - const prefixListId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.PREFIX_LIST, [prefixListItem.name]), - ); - prefixListMap.set(prefixListItem.name, prefixListId); - } - } - - // Get cross-account prefix list IDs as needed - const crossAcctPrefixLists = this.setCrossAcctPrefixLists(); - crossAcctPrefixLists.forEach((value, key) => prefixListMap.set(key, value)); - return prefixListMap; - } - - /** - * Get cross-account prefix list IDs - */ - private setCrossAcctPrefixLists(): Map { - const prefixListMap = new Map(); - for (const peering of this.peeringList) { - // Get account IDs - const accepterAccountIds = this.getVpcAccountIds(peering.accepter); - accepterAccountIds.forEach(accepterAccountId => { - for (const routeTable of peering.accepter.routeTables ?? []) { - for (const routeTableEntry of routeTable.routes ?? []) { - const mapKey = `${accepterAccountId}_${peering.accepter.region}_${routeTableEntry.destinationPrefixList}`; - if ( - routeTableEntry.type && - routeTableEntry.type === 'vpcPeering' && - routeTableEntry.target === peering.name && - peering.crossAccount && - routeTableEntry.destinationPrefixList && - !prefixListMap.has(mapKey) - ) { - const prefixListId = new SsmParameterLookup( - this, - pascalCase(`SsmParamLookup${routeTableEntry.destinationPrefixList}`), - { - name: this.getSsmPath(SsmResourceType.PREFIX_LIST, [routeTableEntry.destinationPrefixList]), - accountId: accepterAccountId, - parameterRegion: peering.accepter.region, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${peering.accepter.region}`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }, - ).value; - prefixListMap.set(mapKey, prefixListId); - } - } - } - }); - } - return prefixListMap; - } - - /** - * Get cross-account route tables - */ - private setCrossAcctRouteTables(): Map { - const routeTableMap = new Map(); - for (const peering of this.peeringList) { - // Get account IDs - const accepterAccountIds = this.getVpcAccountIds(peering.accepter); - accepterAccountIds.forEach(accepterAccountId => { - for (const routeTable of peering.accepter.routeTables ?? []) { - for (const routeTableEntry of routeTable.routes ?? []) { - if ( - routeTableEntry.type && - routeTableEntry.type === 'vpcPeering' && - routeTableEntry.target === peering.name && - peering.crossAccount && - this.account !== accepterAccountId && - !routeTableMap.has(`${peering.accepter.name}_${accepterAccountId}_${routeTable.name}`) - ) { - const routeTableId = new SsmParameterLookup( - this, - pascalCase(`SsmParamLookup${peering.accepter.name}${accepterAccountId}${routeTable.name}`), - { - name: this.getSsmPath(SsmResourceType.ROUTE_TABLE, [peering.accepter.name, routeTable.name]), - accountId: accepterAccountId, - parameterRegion: peering.accepter.region, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${peering.accepter.region}`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }, - ).value; - routeTableMap.set(`${peering.accepter.name}_${accepterAccountId}_${routeTable.name}`, routeTableId); - } - } - } - }); - } - return routeTableMap; - } - - /** - * Create transit gateway resources - * @param props - */ - private createTransitGatewayResources(props: AcceleratorStackProps) { - // - // Build Transit Gateway Maps - // - for (const tgwItem of props.networkConfig.transitGateways ?? []) { - this.setTransitGatewayMap(tgwItem); - this.setTransitGatewayRouteTableMap(tgwItem); - } - - // - // Create Transit Gateway route table associations and propagations - // for VPC attachments - // - for (const vpcItem of this.vpcResources) { - this.setTransitGatewayVpcAttachmentsMap(vpcItem); - this.createVpcTransitGatewayAssociations(vpcItem); - this.createVpcTransitGatewayPropagations(vpcItem); - } - - // - // Create Transit Gateway route table associations and propagations - // for VPN attachments - // - for (const cgwItem of props.networkConfig.customerGateways ?? []) { - if (isIpv4(cgwItem.ipAddress)) { - for (const vpnItem of cgwItem.vpnConnections ?? []) { - this.setTransitGatewayVpnAttachmentsMap(props, cgwItem, vpnItem); - this.createVpnTransitGatewayAssociations(cgwItem, vpnItem); - this.createVpnTransitGatewayPropagations(cgwItem, vpnItem); - } - } - } - } - - /** - * Set transit gateways map - * @param tgwItem - */ - private setTransitGatewayMap(tgwItem: TransitGatewayConfig): void { - const accountId = this.props.accountsConfig.getAccountId(tgwItem.account); - if (this.isTargetStack([accountId], [tgwItem.region])) { - const transitGatewayId = ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.TGW, [tgwItem.name]), - ); - this.transitGateways.set(tgwItem.name, transitGatewayId); - } - } - - /** - * Set transit gateway route table map - * @param tgwItem - */ - private setTransitGatewayRouteTableMap(tgwItem: TransitGatewayConfig): void { - const accountId = this.props.accountsConfig.getAccountId(tgwItem.account); - if (this.isTargetStack([accountId], [tgwItem.region])) { - for (const routeTableItem of tgwItem.routeTables ?? []) { - const transitGatewayRouteTableId = ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [tgwItem.name, routeTableItem.name]), - ); - const key = `${tgwItem.name}_${routeTableItem.name}`; - this.transitGatewayRouteTables.set(key, transitGatewayRouteTableId); - } - } - } - - /** - * Get account names and excluded account IDs for transit gateway attachments - * @param vpcItem - * @returns - */ - private getTransitGatewayAttachmentAccounts(vpcItem: VpcConfig | VpcTemplatesConfig): [string[], string[]] { - let accountNames: string[]; - let excludedAccountIds: string[] = []; - if (isNetworkType('IVpcConfig', vpcItem)) { - accountNames = [vpcItem.account]; - } else { - accountNames = this.getAccountNamesFromDeploymentTarget(vpcItem.deploymentTargets); - excludedAccountIds = this.getExcludedAccountIds(vpcItem.deploymentTargets); - } - return [accountNames, excludedAccountIds]; - } - - /** - * Function to create TGW attachments map - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param tgwAttachmentItem {@link TransitGatewayAttachmentConfig} - * @param accountNames string[] - * @param excludedAccountIds string[] - * @param accountId string - * @param transitGatewayId string - */ - private createTransitGatewayAttachmentsMap( - vpcItem: VpcConfig | VpcTemplatesConfig, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - accountNames: string[], - excludedAccountIds: string[], - accountId: string, - transitGatewayId: string, - ): void { - for (const owningAccount of accountNames) { - let transitGatewayAttachmentId; - const owningAccountId = this.props.accountsConfig.getAccountId(owningAccount); - const attachmentKey = `${tgwAttachmentItem.transitGateway.name}_${owningAccount}_${vpcItem.name}`; - // Skip iteration if account is excluded - if (excludedAccountIds.includes(owningAccountId)) { - continue; - } - - if (accountId === owningAccountId) { - this.logger.info( - `Update route tables for attachment ${tgwAttachmentItem.name} from local account ${owningAccountId}`, - ); - transitGatewayAttachmentId = ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.TGW_ATTACHMENT, [vpcItem.name, tgwAttachmentItem.name]), - ); - this.transitGatewayAttachments.set(attachmentKey, transitGatewayAttachmentId); - } else { - this.logger.info( - `Update route tables for attachment ${tgwAttachmentItem.name} from external account ${owningAccountId}`, - ); - - const transitGatewayAttachment = TransitGatewayAttachment.fromLookup( - this, - pascalCase(`${tgwAttachmentItem.name}${owningAccount}VpcTransitGatewayAttachment`), - { - name: tgwAttachmentItem.name, - owningAccountId, - transitGatewayId, - type: TransitGatewayAttachmentType.VPC, - roleName: `${this.props.prefixes.accelerator}-DescribeTgwAttachRole-${cdk.Stack.of(this).region}`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ); - // Build Transit Gateway Attachment Map - transitGatewayAttachmentId = transitGatewayAttachment.transitGatewayAttachmentId; - this.transitGatewayAttachments.set(attachmentKey, transitGatewayAttachmentId); - } - } - } - - /** - * Create a map of transit gateway attachments - * @param vpcItem - */ - private setTransitGatewayVpcAttachmentsMap(vpcItem: VpcConfig | VpcTemplatesConfig) { - // Get account names for attachment keys - const [accountNames, excludedAccountIds] = this.getTransitGatewayAttachmentAccounts(vpcItem); - - if (vpcItem.region === cdk.Stack.of(this).region) { - for (const tgwAttachmentItem of vpcItem.transitGatewayAttachments ?? []) { - const accountId = this.props.accountsConfig.getAccountId(tgwAttachmentItem.transitGateway.account); - - if (accountId === cdk.Stack.of(this).account) { - // Get the Transit Gateway ID - const transitGatewayId = this.transitGateways.get(tgwAttachmentItem.transitGateway.name); - if (!transitGatewayId) { - this.logger.error(`Transit Gateway ${tgwAttachmentItem.transitGateway.name} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Get the Transit Gateway Attachment ID - this.createTransitGatewayAttachmentsMap( - vpcItem, - tgwAttachmentItem, - accountNames, - excludedAccountIds, - accountId, - transitGatewayId, - ); - } - } - } - } - - /** - * Function to create TGW route table association - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param tgwAttachmentItem {@link TransitGatewayAttachmentConfig} - * @param owningAccount string - * @param attachmentKey string - */ - private createTransitGatewayRouteTableAssociation( - vpcItem: VpcConfig | VpcTemplatesConfig, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - owningAccount: string, - attachmentKey: string, - ): void { - // Get transit gateway attachment ID - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(attachmentKey); - if (!transitGatewayAttachmentId) { - this.logger.error(`Transit Gateway attachment ${attachmentKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - for (const routeTableItem of tgwAttachmentItem.routeTableAssociations ?? []) { - if ( - this.isManagedByAsea( - AseaResourceType.TRANSIT_GATEWAY_ASSOCIATION, - `${owningAccount}/${tgwAttachmentItem.transitGateway.name}/${tgwAttachmentItem.name}/${routeTableItem}`, - ) - ) - continue; - const associationsKey = `${tgwAttachmentItem.transitGateway.name}_${routeTableItem}`; - let associationId: string; - if (isNetworkType('IVpcConfig', vpcItem)) { - associationId = `${pascalCase(tgwAttachmentItem.name)}${pascalCase(routeTableItem)}Association`; - } else { - associationId = `${pascalCase(tgwAttachmentItem.name)}${pascalCase(routeTableItem)}${pascalCase( - owningAccount, - )}Association`; - } - - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(associationsKey); - if (transitGatewayRouteTableId === undefined) { - this.logger.error(`Transit Gateway Route Table ${associationsKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - new TransitGatewayRouteTableAssociation(this, associationId, { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - }); - } - } - - /** - * Function to process Transit Gateway attachment account list and create Transit Gateway Route Table Associations - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param tgwAttachmentItem {@link TransitGatewayAttachmentConfig} - * @param accountNames string[] - * @param excludedAccountIds string[] - */ - private processTransitGatewayAttachmentAccountsToCreateTgwRtAssociations( - vpcItem: VpcConfig | VpcTemplatesConfig, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - accountNames: string[], - excludedAccountIds: string[], - ) { - // Get the Transit Gateway Attachment ID - for (const owningAccount of accountNames) { - const owningAccountId = this.props.accountsConfig.getAccountId(owningAccount); - const attachmentKey = `${tgwAttachmentItem.transitGateway.name}_${owningAccount}_${vpcItem.name}`; - // Skip iteration if account is excluded - if (excludedAccountIds.includes(owningAccountId)) { - continue; - } - // Create TGW route table association - this.createTransitGatewayRouteTableAssociation(vpcItem, tgwAttachmentItem, owningAccount, attachmentKey); - } - } - - /** - * Create transit gateway route table associations for VPC attachments - * @param vpcItem - */ - private createVpcTransitGatewayAssociations(vpcItem: VpcConfig | VpcTemplatesConfig): void { - // Get account names for attachment keys - const [accountNames, excludedAccountIds] = this.getTransitGatewayAttachmentAccounts(vpcItem); - - if (vpcItem.region === cdk.Stack.of(this).region) { - for (const tgwAttachmentItem of vpcItem.transitGatewayAttachments ?? []) { - const accountId = this.props.accountsConfig.getAccountId(tgwAttachmentItem.transitGateway.account); - if (accountId === cdk.Stack.of(this).account) { - // Get the Transit Gateway Attachment ID - this.processTransitGatewayAttachmentAccountsToCreateTgwRtAssociations( - vpcItem, - tgwAttachmentItem, - accountNames, - excludedAccountIds, - ); - } - } - } - } - - /** - * Function to create TGW route table propagation - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param tgwAttachmentItem {@link TransitGatewayAttachmentConfig} - * @param owningAccount string - * @param attachmentKey string - */ - private createTransitGatewayRouteTablePropagation( - vpcItem: VpcConfig | VpcTemplatesConfig, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - owningAccount: string, - attachmentKey: string, - ): void { - // Get transit gateway attachment ID - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(attachmentKey); - if (!transitGatewayAttachmentId) { - this.logger.error(`Transit Gateway attachment ${attachmentKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - for (const routeTableItem of tgwAttachmentItem.routeTablePropagations ?? []) { - if ( - this.isManagedByAsea( - AseaResourceType.TRANSIT_GATEWAY_PROPAGATION, - `${owningAccount}/${tgwAttachmentItem.transitGateway.name}/${tgwAttachmentItem.name}/${routeTableItem}`, - ) - ) - continue; - const propagationsKey = `${tgwAttachmentItem.transitGateway.name}_${routeTableItem}`; - let propagationId: string; - if (isNetworkType('IVpcConfig', vpcItem)) { - propagationId = `${pascalCase(tgwAttachmentItem.name)}${pascalCase(routeTableItem)}Propagation`; - } else { - propagationId = `${pascalCase(tgwAttachmentItem.name)}${pascalCase(routeTableItem)}${pascalCase( - owningAccount, - )}Propagation`; - } - - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(propagationsKey); - if (!transitGatewayRouteTableId) { - this.logger.error(`Transit Gateway Route Table ${propagationsKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - new TransitGatewayRouteTablePropagation(this, propagationId, { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - }); - } - } - - /** - * Function to process Transit Gateway attachment account list and create Transit Gateway Route Table Propagations - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param tgwAttachmentItem {@link TransitGatewayAttachmentConfig} - * @param accountNames string[] - * @param excludedAccountIds string[] - */ - private processTransitGatewayAttachmentAccountsToCreateTgwRtPropagations( - vpcItem: VpcConfig | VpcTemplatesConfig, - tgwAttachmentItem: TransitGatewayAttachmentConfig, - accountNames: string[], - excludedAccountIds: string[], - ) { - // Loop through attachment owner accounts - for (const owningAccount of accountNames) { - const owningAccountId = this.props.accountsConfig.getAccountId(owningAccount); - const attachmentKey = `${tgwAttachmentItem.transitGateway.name}_${owningAccount}_${vpcItem.name}`; - // Skip iteration if account is excluded - if (excludedAccountIds.includes(owningAccountId)) { - continue; - } - - // Create TGW route table propagation - this.createTransitGatewayRouteTablePropagation(vpcItem, tgwAttachmentItem, owningAccount, attachmentKey); - } - } - /** - * Create transit gateway route table propagations for VPC attachments - * @param vpcItem - */ - private createVpcTransitGatewayPropagations(vpcItem: VpcConfig | VpcTemplatesConfig): void { - // Get account names for attachment keys - const [accountNames, excludedAccountIds] = this.getTransitGatewayAttachmentAccounts(vpcItem); - - if (vpcItem.region === cdk.Stack.of(this).region) { - for (const tgwAttachmentItem of vpcItem.transitGatewayAttachments ?? []) { - const accountId = this.props.accountsConfig.getAccountId(tgwAttachmentItem.transitGateway.account); - if (accountId === cdk.Stack.of(this).account) { - this.processTransitGatewayAttachmentAccountsToCreateTgwRtPropagations( - vpcItem, - tgwAttachmentItem, - accountNames, - excludedAccountIds, - ); - } - } - } - } - - /** - * Create transit gateway connection resources - * @param props - */ - private createTransitGatewayConnect(props: AcceleratorStackProps): void { - for (const connectItem of props.networkConfig.transitGatewayConnects ?? []) { - const accountId = this.props.accountsConfig.getAccountId(connectItem.transitGateway.account); - if (accountId === cdk.Stack.of(this).account && connectItem.region === cdk.Stack.of(this).region) { - const isVpcAttachment = connectItem.vpc; - const vpcAttachmentItem = props.networkConfig.vpcs.find(item => item.name === connectItem.vpc?.vpcName); - const attachmentKey = isVpcAttachment - ? `${connectItem.transitGateway.name}_${vpcAttachmentItem?.account}_${connectItem.vpc.vpcName}` - : `${connectItem.directConnect}_${connectItem.transitGateway.name}`; - - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(attachmentKey); - - if (!transitGatewayAttachmentId) { - this.logger.error(`Transit Gateway attachment ${attachmentKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - this.logger.info(`Creating a TGW Connect for: ${connectItem.name}`); - new TransitGatewayConnect(this, pascalCase(`${connectItem.name}TransitGatewayConnectAttachment`), { - name: connectItem.name, - transitGatewayAttachmentId: transitGatewayAttachmentId, - options: connectItem.options!, - tags: connectItem.tags, - }); - } - } - } - - /** - * Set VPN attachment items in TGW map - * @param props - * @param cgwItem - * @param vpnItem - */ - private setTransitGatewayVpnAttachmentsMap( - props: AcceleratorStackProps, - cgwItem: CustomerGatewayConfig, - vpnItem: VpnConnectionConfig, - ): void { - const accountId = props.accountsConfig.getAccountId(cgwItem.account); - if ( - accountId === cdk.Stack.of(this).account && - cgwItem.region === cdk.Stack.of(this).region && - vpnItem.transitGateway - ) { - // Lookup TGW attachment ID for VPN - const tgw = props.networkConfig.transitGateways.find(tgwItem => tgwItem.name === vpnItem.transitGateway)!; - const transitGatewayId = this.transitGateways.get(tgw.name); - - if (!transitGatewayId) { - this.logger.error(`Transit Gateway ${tgw.name} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const vpnAttachmentId = TransitGatewayAttachment.fromLookup( - this, - pascalCase(`${vpnItem.name}VpnTransitGatewayAttachment`), - { - name: vpnItem.name, - owningAccountId: accountId, - transitGatewayId, - type: TransitGatewayAttachmentType.VPN, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ).transitGatewayAttachmentId; - // Add to Transit Gateway Attachment Map - this.transitGatewayAttachments.set(`${vpnItem.name}_${tgw.name}`, vpnAttachmentId); - } - } - - /** - * Create VPN TGW route table associations - * @param cgwItem - * @param vpnItem - */ - private createVpnTransitGatewayAssociations(cgwItem: CustomerGatewayConfig, vpnItem: VpnConnectionConfig): void { - const accountId = this.props.accountsConfig.getAccountId(cgwItem.account); - if ( - accountId === cdk.Stack.of(this).account && - cgwItem.region === cdk.Stack.of(this).region && - vpnItem.routeTableAssociations - ) { - // Lookup TGW attachment ID for VPN - const attachmentKey = `${vpnItem.name}_${vpnItem.transitGateway}`; - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(attachmentKey); - - if (!transitGatewayAttachmentId) { - this.logger.error(`Transit Gateway attachment ${attachmentKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create route table associations - for (const routeTableItem of vpnItem.routeTableAssociations ?? []) { - const routeTableKey = `${vpnItem.transitGateway}_${routeTableItem}`; - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(routeTableKey); - - if (!transitGatewayRouteTableId) { - this.logger.error(`Transit Gateway Route Table ${routeTableKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - new TransitGatewayRouteTableAssociation( - this, - `${pascalCase(vpnItem.name)}${pascalCase(routeTableItem)}Association`, - { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - }, - ); - } - } - } - - /** - * Create VPN TGW route table propagations - * @param cgwItem - * @param vpnItem - */ - private createVpnTransitGatewayPropagations(cgwItem: CustomerGatewayConfig, vpnItem: VpnConnectionConfig): void { - const accountId = this.props.accountsConfig.getAccountId(cgwItem.account); - if ( - accountId === cdk.Stack.of(this).account && - cgwItem.region === cdk.Stack.of(this).region && - vpnItem.routeTablePropagations - ) { - // Lookup TGW attachment ID for VPN - const attachmentKey = `${vpnItem.name}_${vpnItem.transitGateway}`; - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(attachmentKey); - - if (!transitGatewayAttachmentId) { - this.logger.error(`Transit Gateway attachment ${attachmentKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create route table propagations - for (const routeTableItem of vpnItem.routeTablePropagations ?? []) { - const routeTableKey = `${vpnItem.transitGateway}_${routeTableItem}`; - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(routeTableKey); - - if (!transitGatewayRouteTableId) { - this.logger.error(`Transit Gateway Route Table ${routeTableKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - new TransitGatewayRouteTablePropagation( - this, - `${pascalCase(vpnItem.name)}${pascalCase(routeTableItem)}Propagation`, - { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - }, - ); - } - } - } - - /** - * Function to get zone association account ids - * @returns string[] - * - * @remarks - * Generate list of accounts with VPCs that needed to set up share. - */ - private createZoneAssociationAccountIdList(): string[] { - // Generate list of accounts with VPCs that needed to set up share - const zoneAssociationAccountIds: string[] = []; - for (const vpcItem of this.vpcResources) { - // Get account IDs - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - - if (vpcItem.region === cdk.Stack.of(this).region && vpcItem.useCentralEndpoints) { - for (const accountId of vpcAccountIds) { - if (!zoneAssociationAccountIds.includes(accountId)) { - zoneAssociationAccountIds.push(accountId); - } - } - } - } - - return zoneAssociationAccountIds; - } - - /** - * Function to create hosted zone id list from SSM parameters - * @param centralEndpointVpc {@link VpcConfig} - * @returns string[] - * - */ - private createHostedZoneIdList(centralEndpointVpc: VpcConfig): string[] { - const hostedZoneIds: string[] = []; - for (const endpointItem of centralEndpointVpc.interfaceEndpoints?.endpoints ?? []) { - const hostedZoneId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.PHZ_ID, [centralEndpointVpc.name, endpointItem.service]), - ); - hostedZoneIds.push(hostedZoneId); - } - - return hostedZoneIds; - } - - /** - * Create Route 53 private hosted zone associations for centralized interface endpoints - */ - private createHostedZoneAssociations(): void { - // Get Central end point vpc - const centralEndpointVpc = this.getCentralEndpointVpc(); - - if (centralEndpointVpc) { - this.logger.info('Central endpoints VPC detected, share private hosted zone with member VPCs'); - - // Generate list of accounts with VPCs that needed to set up share - const zoneAssociationAccountIds = this.createZoneAssociationAccountIdList(); - - // Create list of hosted zone ids from SSM Params - const hostedZoneIds = this.createHostedZoneIdList(centralEndpointVpc); - - // Custom resource to associate hosted zones - new AssociateHostedZones(this, 'AssociateHostedZones', { - accountIds: zoneAssociationAccountIds, - hostedZoneIds, - hostedZoneAccountId: cdk.Stack.of(this).account, - roleName: `${this.props.prefixes.accelerator}-EnableCentralEndpointsRole-${cdk.Stack.of(this).region}`, - tagFilters: [ - { - key: 'accelerator:use-central-endpoints', - value: 'true', - }, - { - key: 'accelerator:central-endpoints-account-id', - value: this.props.accountsConfig.getAccountId(centralEndpointVpc.account), - }, - ], - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - } - - /** - * Create central network service associations - * @param props - */ - private createCentralNetworkAssociations(props: AcceleratorStackProps) { - // - // Create Route 53 Resolver VPC associations - // - if (props.networkConfig.centralNetworkServices) { - for (const vpcItem of this.vpcResources) { - // Get account IDs - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - const delegatedAdminAccountId = this.props.accountsConfig.getAccountId( - props.networkConfig.centralNetworkServices.delegatedAdminAccount, - ); - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - this.createDnsFirewallAssociations(vpcItem, delegatedAdminAccountId); - this.createQueryLogConfigAssociations(vpcItem, delegatedAdminAccountId); - this.createResolverRuleAssociations(vpcItem, delegatedAdminAccountId); - } - } - } - } - - /** - * Create local VPC Route53 Resources - * @param props - */ - private createRoute53LocalVpcResources(props: AcceleratorStackProps) { - for (const vpcItem of props.networkConfig.vpcs) { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - const accountId = cdk.Stack.of(this).account; - this.createVpcQueryLogConfigAssociations(vpcItem, accountId); - } - } - } - /** - * Create Route 53 Resolver DNS Firewall VPC associations - * @param vpcItem - * @param owningAccountId - */ - private createDnsFirewallAssociations(vpcItem: VpcConfig | VpcTemplatesConfig, owningAccountId: string): void { - for (const firewallItem of vpcItem.dnsFirewallRuleGroups ?? []) { - // Get VPC ID - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - - // Skip lookup if already added to map - if (!this.dnsFirewallMap.has(firewallItem.name)) { - // Get SSM parameter if this is the owning account - if (owningAccountId === cdk.Stack.of(this).account) { - const ruleId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.DNS_RULE_GROUP, [firewallItem.name]), - ); - this.dnsFirewallMap.set(firewallItem.name, ruleId); - } else { - // Get ID from the resource share - const ruleId = this.getResourceShare( - `${firewallItem.name}_ResolverFirewallRuleGroupShare`, - 'route53resolver:FirewallRuleGroup', - owningAccountId, - this.cloudwatchKey, - ).resourceShareItemId; - this.dnsFirewallMap.set(firewallItem.name, ruleId); - } - } - - // Create association - if (!this.dnsFirewallMap.get(firewallItem.name)) { - this.logger.error(`Could not find existing DNS firewall rule group ${firewallItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - this.logger.info(`Add DNS firewall rule group ${firewallItem.name} to ${vpcItem.name}`); - - new ResolverFirewallRuleGroupAssociation( - this, - pascalCase(`${vpcItem.name}${firewallItem.name}RuleGroupAssociation`), - { - firewallRuleGroupId: this.dnsFirewallMap.get(firewallItem.name)!, - priority: firewallItem.priority, - vpcId: vpcId, - mutationProtection: firewallItem.mutationProtection, - tags: firewallItem.tags, - }, - ); - } - } - - /** - * Create Query logging config association map - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param vpcId string - * @param configNames string[] - */ - private createQueryLoggingConfigAssociation( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - configNames: string[], - ): void { - // Create association - for (const nameItem of configNames) { - if (this.isManagedByAsea(AseaResourceType.ROUTE_53_QUERY_LOGGING_ASSOCIATION, vpcItem.name)) { - this.logger.info( - `Route 53 Query Logging is already associated with VPC "${vpcItem.name}" and is managed externally.`, - ); - break; - } else if (!this.queryLogMap.get(nameItem)) { - this.logger.error(`Could not find existing DNS query log config ${nameItem}`); - throw new Error(`Configuration validation failed at runtime.`); - } else { - this.logger.info(`Add DNS query log config ${nameItem} to ${vpcItem.name}`); - new QueryLoggingConfigAssociation(this, pascalCase(`${vpcItem.name}${nameItem}QueryLogAssociation`), { - resolverQueryLogConfigId: this.queryLogMap.get(nameItem), - vpcId: vpcId, - partition: this.props.partition, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - } - } - - /** - * Function to create query log map - * @param configNames string[] - * @param owningAccountId string - */ - private createQueryLogMap(configNames: string[], owningAccountId: string): void { - // Get SSM parameter if this is the owning account - for (const nameItem of configNames) { - // Skip lookup if already added to map - if (!this.queryLogMap.has(nameItem)) { - if (owningAccountId === cdk.Stack.of(this).account) { - if (this.isManagedByAsea(AseaResourceType.ROUTE_53_QUERY_LOGGING, nameItem.replace('-cwl', ''))) { - this.logger.info(`DNS Logging for VPC "${nameItem.replace('-cwl', '')}" is managed externally`); - break; - } - const configId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.QUERY_LOGS, [nameItem]), - ); - this.queryLogMap.set(nameItem, configId); - } else { - // Get ID from the resource share - const configId = this.getResourceShare( - `${nameItem}_QueryLogConfigShare`, - 'route53resolver:ResolverQueryLogConfig', - owningAccountId, - this.cloudwatchKey, - ).resourceShareItemId; - this.queryLogMap.set(nameItem, configId); - } - } - } - } - - /** - * Create Route 53 Resolver query log config VPC associations - * @param vpcItem - * @param owningAccountId - */ - private createQueryLogConfigAssociations(vpcItem: VpcConfig | VpcTemplatesConfig, owningAccountId: string): void { - for (const configItem of vpcItem.queryLogs ?? []) { - // Get VPC ID - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - - // Determine query log destination(s) - const centralNetworkConfig = this.props.networkConfig.centralNetworkServices!; - const configNames: string[] = []; - if (centralNetworkConfig.route53Resolver?.queryLogs?.destinations.includes('s3')) { - configNames.push(`${configItem}-s3`); - } - if (centralNetworkConfig.route53Resolver?.queryLogs?.destinations.includes('cloud-watch-logs')) { - configNames.push(`${configItem}-cwl`); - } - - this.createQueryLogMap(configNames, owningAccountId); - - // Create association - this.createQueryLoggingConfigAssociation(vpcItem, vpcId, configNames); - } - } - - private createVpcQueryLogConfigAssociations(vpcItem: VpcConfig, owningAccountId: string) { - if (vpcItem.vpcRoute53Resolver?.queryLogs?.name) { - // Get VPC ID - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - // Determine query log destination(s) - const configItem = vpcItem.vpcRoute53Resolver?.queryLogs.name; - const vpcConfig = vpcItem.vpcRoute53Resolver!; - const configNames: string[] = []; - if (vpcConfig.queryLogs?.destinations.includes('s3')) { - configNames.push(`${configItem}-s3`); - } - if (vpcConfig.queryLogs?.destinations.includes('cloud-watch-logs')) { - configNames.push(`${configItem}-cwl`); - } - - this.createQueryLogMap(configNames, owningAccountId); - - // Create association - this.createQueryLoggingConfigAssociation(vpcItem, vpcId, configNames); - } - } - - /** - * Create Route 53 Resolver rule VPC associations - * @param vpcItem - * @param owningAccountId - */ - private createResolverRuleAssociations(vpcItem: VpcConfig | VpcTemplatesConfig, owningAccountId: string): void { - for (const ruleItem of vpcItem.resolverRules ?? []) { - // Get VPC ID - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - - // Skip lookup if already added to map - if (!this.resolverRuleMap.has(ruleItem)) { - // Get SSM parameter if this is the owning account - if (owningAccountId === cdk.Stack.of(this).account) { - const ruleId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.RESOLVER_RULE, [ruleItem]), - ); - this.resolverRuleMap.set(ruleItem, ruleId); - } else { - // Get ID from the resource share - const ruleId = this.getResourceShare( - `${ruleItem}_ResolverRule`, - 'route53resolver:ResolverRule', - owningAccountId, - this.cloudwatchKey, - ).resourceShareItemId; - this.resolverRuleMap.set(ruleItem, ruleId); - } - } - - // Create association - if (!this.resolverRuleMap.get(ruleItem)) { - this.logger.error(`Could not find existing Route 53 Resolver rule ${ruleItem}`); - throw new Error(`Configuration validation failed at runtime.`); - } - this.logger.info(`Add Route 53 Resolver rule ${ruleItem} to ${vpcItem.name}`); - new ResolverRuleAssociation(this, pascalCase(`${vpcItem.name}${ruleItem}RuleAssociation`), { - resolverRuleId: this.resolverRuleMap.get(ruleItem)!, - vpcId: vpcId, - }); - } - } - - /** - * Create VPC peering connections - */ - private createVpcPeeringConnections(): void { - // Create VPC peering connections - for (const peering of this.peeringList) { - // Get account IDs - const accepterAccountIds = this.getVpcAccountIds(peering.accepter); - - // Get SSM parameters - const requesterVpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [peering.requester.name]), - ); - - let vpcPeering; - let aseaPeerMappingName = peering.name; - let peeringConstructId = `${peering.name}VpcPeering`; - let vpcPeerSsmConstructId = `SsmParam${pascalCase(peering.name)}VpcPeering`; - - for (const accepterAccountId of accepterAccountIds) { - let accepterVpcId: string; - let accepterRoleName: string | undefined = undefined; - if (peering.crossAccount && accepterAccountId !== this.account) { - accepterVpcId = new SsmParameterLookup(this, pascalCase(`SsmParamLookup${peering.name}`), { - name: this.getSsmPath(SsmResourceType.VPC, [peering.accepter.name]), - accountId: accepterAccountId, - parameterRegion: peering.accepter.region, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${peering.accepter.region}`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - - accepterRoleName = `${this.props.prefixes.accelerator}-VpcPeeringRole-${peering.accepter.region}`; - } else { - accepterVpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [peering.accepter.name]), - ); - } - if ( - // isNetworkType('IVpcTemplatesConfig', peering.requester) || - isNetworkType('IVpcTemplatesConfig', peering.accepter) - ) { - aseaPeerMappingName = `${peering.name}_${accepterAccountId}`; - peeringConstructId = `${peering.name}VpcPeering${accepterAccountId}`; - vpcPeerSsmConstructId = `SsmParam${pascalCase(peering.name)}VpcPeering${accepterAccountId}`; - } - if (this.isManagedByAsea(AseaResourceType.EC2_VPC_PEERING, peering.name)) { - const peeringId = this.getExternalResourceParameter( - this.getSsmPath(SsmResourceType.VPC_PEERING, [aseaPeerMappingName]), - ); - vpcPeering = VpcPeering.fromPeeringAttributes(this, peeringConstructId, { - name: peering.name, - peeringId: peeringId, - }); - } else { - // Create VPC peering - this.logger.info( - `Create VPC peering ${peering.name} between ${peering.requester.name} and ${peering.accepter.name}`, - ); - vpcPeering = new VpcPeering(this, peeringConstructId, { - name: peering.name, - peerOwnerId: accepterAccountId, - peerRegion: peering.accepter.region, - peerVpcId: accepterVpcId, - peerRoleName: accepterRoleName, - vpcId: requesterVpcId, - tags: peering.tags ?? [], - }); - this.ssmParameters.push({ - logicalId: pascalCase(vpcPeerSsmConstructId), - parameterName: this.getSsmPath(SsmResourceType.VPC_PEERING, [ - isNetworkType('IVpcTemplatesConfig', peering.accepter) - ? // There will be multiple peering connections in requester account if accepter is VpcTemplate. Add accepterAccountId to avoid duplicate ssm parameter name - `${peering.name}/${accepterAccountId}` - : peering.name, - ]), - stringValue: vpcPeering.peeringId, - }); - } - - // Put cross-account SSM parameter if necessary - if (peering.crossAccount && accepterAccountId !== this.account) { - new PutSsmParameter(this, pascalCase(`CrossAcctSsmParam${pascalCase(peering.name)}VpcPeering`), { - accountIds: [accepterAccountId], - region: peering.accepter.region, - roleName: this.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - parameters: [ - { - name: this.getSsmPath(SsmResourceType.VPC_PEERING, [peering.name]), - value: vpcPeering.peeringId, - }, - ], - invokingAccountId: cdk.Stack.of(this).account, - acceleratorPrefix: this.props.prefixes.accelerator, - }); - } - - // Create peering routes - this.createVpcPeeringRoutes(accepterAccountId, peering.requester, peering.accepter, vpcPeering); - } - } - } - - /** - * Create VPC peering routes - * @param accepterAccountId - * @param requesterVpc - * @param accepterVpc - * @param peering - */ - private createVpcPeeringRoutes( - accepterAccountId: string, - requesterVpc: VpcConfig | VpcTemplatesConfig, - accepterVpc: VpcConfig | VpcTemplatesConfig, - peering: VpcPeering, - ): void { - // Create requester VPC routes - this.createRequesterVpcPeeringRoutes(requesterVpc, accepterVpc, accepterAccountId, peering); - - // Create accepter account routes - this.createAccepterVpcPeeringRoutes(accepterAccountId, accepterVpc, requesterVpc, peering); - } - - /** - * Function to add requester peering route - * @param requesterVpc {@link VpcConfig} - * @param accepterVpc {@link VpcConfig} - * @param accepterAccountId - * @param peering {@link VpcPeering} - * @param routeTable {@link RouteTableConfig} - * @param routeTableEntry {@link RouteTableEntryConfig} - */ - private addRequesterPeeringRoute( - requesterVpc: VpcConfig | VpcTemplatesConfig, - accepterVpc: VpcConfig | VpcTemplatesConfig, - accepterAccountId: string, - peering: VpcPeering, - routeTable: RouteTableConfig, - routeTableEntry: RouteTableEntryConfig, - ): void { - if (routeTableEntry.type && routeTableEntry.type === 'vpcPeering' && routeTableEntry.target === peering.name) { - this.logger.info(`Add route ${routeTableEntry.name} targeting VPC peer ${peering.name}`); - const routeTableId = this.routeTableMap.get(`${requesterVpc.name}_${routeTable.name}`); - const routeId = - pascalCase(`${requesterVpc.name}Vpc`) + - pascalCase(`${routeTable.name}RouteTable`) + - pascalCase(routeTableEntry.name); - if (!routeTableId) { - this.logger.error(`Route Table ${routeTable.name} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const requesterPeeringRouteDetails = this.getRequesterPeeringRouteDestinationConfig( - accepterVpc, - accepterAccountId, - routeTableEntry, - ); - - peering.addPeeringRoute( - routeId, - routeTableId, - requesterPeeringRouteDetails.destination, - requesterPeeringRouteDetails.destinationPrefixListId, - requesterPeeringRouteDetails.ipv6Destination, - this.cloudwatchKey, - this.logRetention, - ); - } - } - - /** - * Create requester peering routes - * @param requesterVpc - * @param accepterVpc - * @param accepterAccountId - * @param peering - */ - private createRequesterVpcPeeringRoutes( - requesterVpc: VpcConfig | VpcTemplatesConfig, - accepterVpc: VpcConfig | VpcTemplatesConfig, - accepterAccountId: string, - peering: VpcPeering, - ): void { - for (const routeTable of requesterVpc.routeTables ?? []) { - for (const routeTableEntry of routeTable.routes ?? []) { - this.addRequesterPeeringRoute( - requesterVpc, - accepterVpc, - accepterAccountId, - peering, - routeTable, - routeTableEntry, - ); - } - } - } - - /** - * Function to get accepter peering route destination configuration - * @param accepterVpc {@link VpcConfig} - * @param requesterVpc {@link VpcConfig} - * @param accepterAccountId - * @param routeTableEntry {@link RouteTableEntryConfig} - * @returns - */ - private getAccepterPeeringRouteDestinationConfig( - accepterVpc: VpcConfig | VpcTemplatesConfig, - requesterVpc: VpcConfig | VpcTemplatesConfig, - accepterAccountId: string, - routeTableEntry: RouteTableEntryConfig, - ): { destinationPrefixListId?: string; destination?: string; ipv6Destination?: string } { - let destination: string | undefined = undefined; - let destinationPrefixListId: string | undefined = undefined; - const requesterAccountIds = this.getVpcAccountIds(requesterVpc); - let ipv6Destination: string | undefined = undefined; - if (requesterAccountIds.includes(accepterAccountId) && requesterVpc.region === accepterVpc.region) { - if (routeTableEntry.destinationPrefixList) { - // Get PL ID from map - destinationPrefixListId = this.prefixListMap.get(routeTableEntry.destinationPrefixList); - if (!destinationPrefixListId) { - this.logger.error(`Prefix list ${routeTableEntry.destinationPrefixList} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - } else if (routeTableEntry.destination || routeTableEntry.ipv6Destination) { - destination = routeTableEntry.destination; - ipv6Destination = routeTableEntry.ipv6Destination; - } else { - destination = this.getVpcCidr(requesterVpc, this.account); - } - } else { - if (routeTableEntry.destinationPrefixList) { - // Get PL ID from map - destinationPrefixListId = this.prefixListMap.get( - `${accepterAccountId}_${accepterVpc.region}_${routeTableEntry.destinationPrefixList}`, - ); - } else if (routeTableEntry.destination || routeTableEntry.ipv6Destination) { - destination = routeTableEntry.destination; - ipv6Destination = routeTableEntry.ipv6Destination; - } else { - destination = this.getVpcCidr(requesterVpc, this.account); - } - } - return { destinationPrefixListId: destinationPrefixListId, destination, ipv6Destination }; - } - - /** - * Function to get accepter peering route destination configuration - * @param accepterVpc {@link VpcConfig} - * @param accepterAccountId - * @param routeTableEntry {@link RouteTableEntryConfig} - * @returns - */ - private getRequesterPeeringRouteDestinationConfig( - accepterVpc: VpcConfig | VpcTemplatesConfig, - accepterAccountId: string, - routeTableEntry: RouteTableEntryConfig, - ): { - destinationPrefixListId?: string; - destination?: string; - ipv6Destination?: string; - } { - let destination: string | undefined = undefined; - let destinationPrefixListId: string | undefined = undefined; - let ipv6Destination: string | undefined = undefined; - if (routeTableEntry.destinationPrefixList) { - // Get PL ID from map - destinationPrefixListId = getPrefixList(this.prefixListMap, routeTableEntry.destinationPrefixList) as string; - } else if (routeTableEntry.destination || routeTableEntry.ipv6Destination) { - destination = routeTableEntry.destination; - ipv6Destination = routeTableEntry.ipv6Destination; - } else { - destination = this.getVpcCidr(accepterVpc, accepterAccountId); - } - - return { destinationPrefixListId, destination, ipv6Destination }; - } - - /** - * Function to create accepter peering route - * @param accepterAccountId string - * @param accepterVpc {@link VpcConfig} - * @param requesterVpc {@link VpcConfig} - * @param peering {@link VpcPeering} - * @param routeTableEntry {@link RouteTableEntryConfig} - * @param routeId string - * @param routeTableId string - */ - private createAccepterPeeringRoute( - accepterAccountId: string, - accepterVpc: VpcConfig | VpcTemplatesConfig, - requesterVpc: VpcConfig | VpcTemplatesConfig, - peering: VpcPeering, - routeTableEntry: RouteTableEntryConfig, - routeId: string, - routeTableId: string, - ): void { - const accepterPeeringRouteDestinationConfig = this.getAccepterPeeringRouteDestinationConfig( - accepterVpc, - requesterVpc, - accepterAccountId, - routeTableEntry, - ); - const destination = accepterPeeringRouteDestinationConfig.destination; - const destinationPrefixListId = accepterPeeringRouteDestinationConfig.destinationPrefixListId; - const ipv6Destination = accepterPeeringRouteDestinationConfig.ipv6Destination; - - if (this.account === accepterAccountId && requesterVpc.region === accepterVpc.region) { - peering.addPeeringRoute( - routeId, - routeTableId, - destination, - destinationPrefixListId, - ipv6Destination, - this.cloudwatchKey, - this.logRetention, - ); - } else { - if (!this.crossAcctRouteProvider) { - this.logger.error(`Cross-account route provider not created but required for ${routeTableEntry.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - peering.addCrossAcctPeeringRoute({ - id: routeId, - ownerAccount: accepterAccountId, - ownerRegion: accepterVpc.region, - partition: this.props.partition, - provider: this.crossAcctRouteProvider, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${accepterVpc.region}`, - routeTableId, - destination, - destinationPrefixListId, - ipv6Destination, - }); - } - } - - /** - * Create accepter VPC routes - * @param accepterAccountId - * @param accepterVpc - * @param requesterVpc - * @param peering - */ - private createAccepterVpcPeeringRoutes( - accepterAccountId: string, - accepterVpc: VpcConfig | VpcTemplatesConfig, - requesterVpc: VpcConfig | VpcTemplatesConfig, - peering: VpcPeering, - ): void { - for (const routeTable of accepterVpc.routeTables ?? []) { - for (const routeTableEntry of routeTable.routes ?? []) { - if (routeTableEntry.type && routeTableEntry.type === 'vpcPeering' && routeTableEntry.target === peering.name) { - this.logger.info(`Add route ${routeTableEntry.name} targeting VPC peer ${peering.name}`); - - const routeId = - pascalCase(`${accepterVpc.name}Vpc`) + - pascalCase(`${routeTable.name}RouteTable`) + - pascalCase(routeTableEntry.name); - const routeTableId = this.routeTableMap.get( - requesterVpc.region !== accepterVpc.region || this.account !== accepterAccountId - ? `${accepterVpc.name}_${accepterAccountId}_${routeTable.name}` - : `${accepterVpc.name}_${routeTable.name}`, - ); - if (!routeTableId) { - this.logger.error(`Route Table ${routeTable.name} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - this.createAccepterPeeringRoute( - accepterAccountId, - accepterVpc, - requesterVpc, - peering, - routeTableEntry, - routeId, - routeTableId, - ); - } - } - } - } - - /** - * Function to create Dx TGW route table associations and propagations - * @param associationItem {@link DxTransitGatewayAssociationConfig} - * @param tgw {@link TransitGatewayConfig} - * @param tgwAccountId string - * @param dxgwItem {@link DxGatewayConfig} - * - * @remarks - * Create transit gateway route table associations and propagations for DX Gateway attachments - */ - private createDxTgwRouteTableAssociationsAndPropagations( - associationItem: DxTransitGatewayAssociationConfig, - tgw: TransitGatewayConfig, - tgwAccountId: string, - dxgwItem: DxGatewayConfig, - ): void { - for (const routeTableAssociationItem of associationItem.routeTableAssociations ?? []) { - this.createDxTgwRouteTableAssociations(dxgwItem, tgw, routeTableAssociationItem, tgwAccountId); - } - for (const routeTablePropagationItem of associationItem.routeTablePropagations ?? []) { - this.createDxTgwRouteTablePropagations(dxgwItem, tgw, routeTablePropagationItem, tgwAccountId); - } - } - - /** - * Create Direct Connect resources - * @param props - */ - private createDirectConnectResources(props: AcceleratorStackProps) { - for (const dxgwItem of props.networkConfig.directConnectGateways ?? []) { - for (const associationItem of dxgwItem.transitGatewayAssociations ?? []) { - const tgw = props.networkConfig.transitGateways.find( - item => item.name === associationItem.name && item.account === associationItem.account, - ); - if (!tgw) { - this.logger.error(`Unable to locate transit gateway ${associationItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - const tgwAccountId = this.props.accountsConfig.getAccountId(tgw.account); - // - // Set DX Gateway ID map - // - this.setDxGatewayMap(dxgwItem, tgw, tgwAccountId); - // - // Create DX Gateway associations to transit gateways - // - this.createDxGatewayTgwAssociations(dxgwItem, tgw, associationItem, tgwAccountId); - // - // Create transit gateway route table associations - // and propagations for DX Gateway attachments - // - this.createDxTgwRouteTableAssociationsAndPropagations(associationItem, tgw, tgwAccountId, dxgwItem); - } - } - } - - /** - * Set Direct Connect Gateway map - * @param dxgwItem - */ - private setDxGatewayMap(dxgwItem: DxGatewayConfig, tgw: TransitGatewayConfig, tgwAccountId: string): void { - // If DX gateway and transit gateway accounts differ, get cross-account SSM parameter - if (dxgwItem.account !== tgw.account && this.isTargetStack([tgwAccountId], [tgw.region])) { - const directConnectGatewayOwnerAccount = this.props.accountsConfig.getAccountId(dxgwItem.account); - const dxgwId = new SsmParameterLookup(this, pascalCase(`SsmParamLookup${dxgwItem.name}`), { - name: this.getSsmPath(SsmResourceType.DXGW, [dxgwItem.name]), - accountId: directConnectGatewayOwnerAccount, - parameterRegion: this.props.globalConfig.homeRegion, - roleName: `${this.props.prefixes.accelerator}-Get${pascalCase(dxgwItem.name)}SsmParamRole-${ - this.props.globalConfig.homeRegion - }`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - this.dxGatewayMap.set(dxgwItem.name, dxgwId); - } - - // If DX gateway and transit gateway accounts match, get local SSM parameter - if (dxgwItem.account === tgw.account && this.isTargetStack([tgwAccountId], [tgw.region])) { - if (tgw.region === this.props.globalConfig.homeRegion) { - const dxgwId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.DXGW, [dxgwItem.name]), - ); - this.dxGatewayMap.set(dxgwItem.name, dxgwId); - } else { - const dxgwId = new SsmParameterLookup(this, pascalCase(`SsmParamLookup${dxgwItem.name}`), { - name: this.getSsmPath(SsmResourceType.DXGW, [dxgwItem.name]), - accountId: cdk.Stack.of(this).account, - parameterRegion: this.props.globalConfig.homeRegion, - roleName: `${this.props.prefixes.accelerator}-Get${pascalCase(dxgwItem.name)}SsmParamRole-${ - this.props.globalConfig.homeRegion - }`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - this.dxGatewayMap.set(dxgwItem.name, dxgwId); - } - } - } - - /** - * Function to create DirectConnect Gateway Association - * @param dxgwItem {@link DxGatewayConfig} - * @param tgwItem {@link TransitGatewayConfig} - * @param createAssociation boolean - * @param associationLogicalId string - * @param associationProps {@link DirectConnectGatewayAssociationProps} - */ - private createDirectConnectGatewayAssociation( - dxgwItem: DxGatewayConfig, - tgwItem: TransitGatewayConfig, - createAssociation: boolean, - associationLogicalId?: string, - associationProps?: DirectConnectGatewayAssociationProps, - ): void { - if (createAssociation) { - if (!associationLogicalId || !associationProps) { - this.logger.error( - `Create DX Gateway associations: unable to process properties for association between DX Gateway ${dxgwItem.name} and transit gateway ${tgwItem.name}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - const association = new DirectConnectGatewayAssociation(this, associationLogicalId, associationProps); - // Add attachment ID to map if exists - if (association.transitGatewayAttachmentId) { - this.transitGatewayAttachments.set(`${dxgwItem.name}_${tgwItem.name}`, association.transitGatewayAttachmentId); - } - } - } - - /** - * Create Direct Connect Gateway associations to transit gateways - * @param dxgw - * @param tgw - * @param associationItem - * @param tgwAccountId - */ - private createDxGatewayTgwAssociations( - dxgwItem: DxGatewayConfig, - tgw: TransitGatewayConfig, - associationItem: DxTransitGatewayAssociationConfig, - tgwAccountId: string, - ): void { - // Condition-based variables - let createAssociation = false; - let associationLogicalId: string | undefined = undefined; - let associationProps: DirectConnectGatewayAssociationProps | undefined = undefined; - - // If DX gateway and transit gateway accounts differ, create association proposal - if (dxgwItem.account !== tgw.account && this.isTargetStack([tgwAccountId], [tgw.region])) { - this.logger.info( - `Creating association proposal between DX Gateway ${dxgwItem.name} and transit gateway ${tgw.name}`, - ); - createAssociation = true; - const directConnectGatewayId = this.dxGatewayMap.get(dxgwItem.name); - const directConnectGatewayOwnerAccount = this.props.accountsConfig.getAccountId(dxgwItem.account); - associationLogicalId = pascalCase(`${dxgwItem.name}${tgw.name}DxGatewayAssociationProposal`); - const gatewayId = this.transitGateways.get(tgw.name); - - if (!directConnectGatewayId) { - this.logger.error(`Create DX Gateway associations: unable to locate DX Gateway ID for ${dxgwItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!gatewayId) { - this.logger.error(`Create DX Gateway associations: unable to locate transit gateway ID for ${tgw.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - associationProps = { - allowedPrefixes: associationItem.allowedPrefixes, - directConnectGatewayId, - directConnectGatewayOwnerAccount, - gatewayId, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }; - } - - // If DX gateway and transit gateway accounts match, create association - if (dxgwItem.account === tgw.account && this.isTargetStack([tgwAccountId], [tgw.region])) { - this.logger.info(`Creating association between DX Gateway ${dxgwItem.name} and transit gateway ${tgw.name}`); - createAssociation = true; - const directConnectGatewayId = this.dxGatewayMap.get(dxgwItem.name); - associationLogicalId = pascalCase(`${dxgwItem.name}${tgw.name}DxGatewayAssociation`); - const gatewayId = this.transitGateways.get(tgw.name); - - if (!directConnectGatewayId) { - this.logger.error(`Create DX Gateway associations: unable to locate DX Gateway ID for ${dxgwItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!gatewayId) { - this.logger.error(`Create DX Gateway associations: unable to locate transit gateway ID for ${tgw.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - associationProps = { - allowedPrefixes: associationItem.allowedPrefixes, - directConnectGatewayId, - gatewayId, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }; - } - - this.createDirectConnectGatewayAssociation( - dxgwItem, - tgw, - createAssociation, - associationLogicalId, - associationProps, - ); - } - - /** - * Create transit gateway route table associations for DX Gateway attachments - * @param dxgwItem - * @param tgw - * @param tgwRouteTableName - * @param tgwAccountId - */ - private createDxTgwRouteTableAssociations( - dxgwItem: DxGatewayConfig, - tgw: TransitGatewayConfig, - tgwRouteTableName: string, - tgwAccountId: string, - ): void { - if (this.isTargetStack([tgwAccountId], [tgw.region])) { - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(`${dxgwItem.name}_${tgw.name}`); - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(`${tgw.name}_${tgwRouteTableName}`); - - if (!transitGatewayAttachmentId) { - this.logger.error( - `Create DX TGW route table associations: unable to locate attachment ${dxgwItem.name}_${tgw.name}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!transitGatewayRouteTableId) { - this.logger.error( - `Create DX TGW route table associations: unable to locate route table ${tgw.name}_${tgwRouteTableName}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create association - this.logger.info(`Creating TGW route table association to ${tgwRouteTableName} for DX Gateway ${dxgwItem.name}`); - new TransitGatewayRouteTableAssociation(this, pascalCase(`${dxgwItem.name}${tgwRouteTableName}Association`), { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - }); - } - } - - /** - * Create transit gateway route table propagations for DX Gateway attachments - * @param dxgwItem - * @param tgw - * @param tgwRouteTableName - * @param tgwAccountId - */ - private createDxTgwRouteTablePropagations( - dxgwItem: DxGatewayConfig, - tgw: TransitGatewayConfig, - tgwRouteTableName: string, - tgwAccountId: string, - ): void { - if (this.isTargetStack([tgwAccountId], [tgw.region])) { - const transitGatewayAttachmentId = this.transitGatewayAttachments.get(`${dxgwItem.name}_${tgw.name}`); - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(`${tgw.name}_${tgwRouteTableName}`); - - if (!transitGatewayAttachmentId) { - this.logger.error( - `Create DX TGW route table associations: unable to locate attachment ${dxgwItem.name}_${tgw.name}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!transitGatewayRouteTableId) { - this.logger.error( - `Create DX TGW route table associations: unable to locate route table ${tgw.name}_${tgwRouteTableName}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create association - this.logger.info( - `Creating TGW route table propagation for DX Gateway ${dxgwItem.name} to route table ${tgwRouteTableName}`, - ); - new TransitGatewayRouteTablePropagation(this, pascalCase(`${dxgwItem.name}${tgwRouteTableName}Propagation`), { - transitGatewayAttachmentId, - transitGatewayRouteTableId, - }); - } - } - - /** - * Function to create TGW static route items - * @param tgwItem {@link TransitGatewayConfig} - * @param routeTableItem {@link TransitGatewayRouteTableConfig} - */ - private createTransitGatewayStaticRouteItems( - tgwItem: TransitGatewayConfig, - routeTableItem: TransitGatewayRouteTableConfig, - ): void { - // Get TGW route table ID - const routeTableKey = `${tgwItem.name}_${routeTableItem.name}`; - const transitGatewayRouteTableId = this.transitGatewayRouteTables.get(routeTableKey); - if (!transitGatewayRouteTableId) { - this.logger.error(`Transit Gateway route table ${routeTableKey} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - for (const routeItem of routeTableItem.routes ?? []) { - this.createTransitGatewayStaticRouteItem(tgwItem, routeTableItem, routeItem, transitGatewayRouteTableId); - } - } - - /** - * Create transit gateway static routes, blackhole routes, - * and prefix list references for VPC and DX Gateway attachments - * @param props - */ - private createTransitGatewayStaticRoutes(props: AcceleratorStackProps) { - for (const tgwItem of props.networkConfig.transitGateways ?? []) { - const accountId = this.props.accountsConfig.getAccountId(tgwItem.account); - if (this.isTargetStack([accountId], [tgwItem.region])) { - for (const routeTableItem of tgwItem.routeTables ?? []) { - this.createTransitGatewayStaticRouteItems(tgwItem, routeTableItem); - } - } - } - } - - /** - * Function to get static route attachment configuration - * @param routeItem {@link TransitGatewayRouteEntryConfig} - * @param routeTableItem {@link TransitGatewayRouteTableConfig} - * @param tgwItem {@link TransitGatewayConfig} - * @returns - */ - private getStaticRouteAttachmentConfig( - routeItem: TransitGatewayRouteEntryConfig, - routeTableItem: TransitGatewayRouteTableConfig, - tgwItem: TransitGatewayConfig, - ): { - routeId: string; - transitGatewayAttachmentId?: string; - } { - let routeId = ''; - let transitGatewayAttachmentId: string | undefined; - if (routeItem.attachment) { - // If route is for VPC attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableVpcEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.vpcName}-${routeItem.attachment.account}`; - transitGatewayAttachmentId = this.transitGatewayAttachments.get( - `${tgwItem.name}_${routeItem.attachment.account}_${routeItem.attachment.vpcName}`, - ); - } - - // If route is for DX Gateway attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableDxGatewayEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.directConnectGatewayName}`; - - // Get TGW attachment ID - transitGatewayAttachmentId = this.transitGatewayAttachments.get( - `${routeItem.attachment.directConnectGatewayName}_${tgwItem.name}`, - ); - } - - // If route is for VPN attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.vpnConnectionName}`; - - // Get TGW attachment ID - transitGatewayAttachmentId = this.transitGatewayAttachments.get( - `${routeItem.attachment.vpnConnectionName}_${tgwItem.name}`, - ); - } - - // If route is for TGW peering attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableTgwPeeringEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-${routeItem.attachment.transitGatewayPeeringName}`; - - // Get TGW attachment ID - transitGatewayAttachmentId = this.getTgwPeeringAttachmentId( - routeItem.attachment.transitGatewayPeeringName, - tgwItem, - ); - } - } - - if (routeItem.attachment && !transitGatewayAttachmentId) { - this.logger.error(`Unable to locate transit gateway attachment ID for route table item ${routeTableItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - return { routeId: routeId, transitGatewayAttachmentId: transitGatewayAttachmentId }; - } - - /** - * Function to get prefix list reference configuration - * @param routeItem {@link TransitGatewayRouteEntryConfig} - * @param routeTableItem {@link TransitGatewayRouteTableConfig} - * @param tgwItem {@link TransitGatewayConfig} - * @returns - */ - private getPrefixListReferenceConfig( - routeItem: TransitGatewayRouteEntryConfig, - routeTableItem: TransitGatewayRouteTableConfig, - tgwItem: TransitGatewayConfig, - ): { - plRouteId: string; - transitGatewayAttachmentId?: string; - } { - let plRouteId = ''; - let transitGatewayAttachmentId: string | undefined = undefined; - if (routeItem.blackhole) { - this.logger.info( - `Adding blackhole prefix list reference ${routeItem.destinationPrefixList} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - plRouteId = pascalCase(`${routeTableItem.name}${routeItem.destinationPrefixList}Blackhole`); - } - - if (routeItem.attachment) { - // If route is for VPC attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableVpcEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding prefix list reference ${routeItem.destinationPrefixList} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - plRouteId = pascalCase( - `${routeTableItem.name}${routeItem.destinationPrefixList}${routeItem.attachment.vpcName}${routeItem.attachment.account}`, - ); - - // Get TGW attachment ID - transitGatewayAttachmentId = this.transitGatewayAttachments.get( - `${tgwItem.name}_${routeItem.attachment.account}_${routeItem.attachment.vpcName}`, - ); - } - - // If route is for DX Gateway attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableDxGatewayEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding prefix list reference ${routeItem.destinationPrefixList} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - plRouteId = pascalCase( - `${routeTableItem.name}${routeItem.destinationPrefixList}${routeItem.attachment.directConnectGatewayName}`, - ); - - // Get TGW attachment ID - transitGatewayAttachmentId = this.transitGatewayAttachments.get( - `${routeItem.attachment.directConnectGatewayName}_${tgwItem.name}`, - ); - } - - // If route is for VPN attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding prefix list reference ${routeItem.destinationPrefixList} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - plRouteId = pascalCase( - `${routeTableItem.name}${routeItem.destinationPrefixList}${routeItem.attachment.vpnConnectionName}`, - ); - - // Get TGW attachment ID - transitGatewayAttachmentId = this.transitGatewayAttachments.get( - `${routeItem.attachment.vpnConnectionName}_${tgwItem.name}`, - ); - } - - // If route is for TGW peering attachment - if ( - isNetworkType( - 'ITransitGatewayRouteTableTgwPeeringEntryConfig', - routeItem.attachment, - ) - ) { - this.logger.info( - `Adding prefix list reference ${routeItem.destinationPrefixList} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - plRouteId = pascalCase( - `${routeTableItem.name}${routeItem.destinationPrefixList}${routeItem.attachment.transitGatewayPeeringName}`, - ); - - // Get TGW attachment ID - transitGatewayAttachmentId = this.getTgwPeeringAttachmentId( - routeItem.attachment.transitGatewayPeeringName, - tgwItem, - ); - } - } - - if (routeItem.attachment && !transitGatewayAttachmentId) { - this.logger.error(`Unable to locate transit gateway attachment ID for route table item ${routeTableItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - return { plRouteId: plRouteId, transitGatewayAttachmentId: transitGatewayAttachmentId }; - } - - /** - * Create transit gateway static routes, blackhole routes, and prefix list references - * @param tgwItem - * @param routeTableItem - * @param routeItem - * @param transitGatewayRouteTableId - */ - private createTransitGatewayStaticRouteItem( - tgwItem: TransitGatewayConfig, - routeTableItem: TransitGatewayRouteTableConfig, - routeItem: TransitGatewayRouteEntryConfig, - transitGatewayRouteTableId: string, - ): void { - // - // Create static routes - // - if ( - routeItem.destinationCidrBlock && - !isEc2FirewallVpnRoute(this.props.networkConfig.customerGateways ?? [], routeItem) - ) { - const attachmentConfig = this.getStaticRouteAttachmentConfig(routeItem, routeTableItem, tgwItem); - let routeId = attachmentConfig.routeId; - const transitGatewayAttachmentId = attachmentConfig.transitGatewayAttachmentId; - if (routeItem.blackhole) { - this.logger.info( - `Adding blackhole route ${routeItem.destinationCidrBlock} to TGW route table ${routeTableItem.name} for TGW ${tgwItem.name} in account: ${tgwItem.account}`, - ); - routeId = `${routeTableItem.name}-${routeItem.destinationCidrBlock}-blackhole`; - } - - if (this.isManagedByAsea(AseaResourceType.TRANSIT_GATEWAY_ROUTE, routeId)) { - return; - } - - // Create static route - new TransitGatewayStaticRoute(this, routeId, { - transitGatewayRouteTableId, - blackhole: routeItem.blackhole, - destinationCidrBlock: routeItem.destinationCidrBlock, - transitGatewayAttachmentId, - }); - } - - // - // Create prefix list references - // - if ( - routeItem.destinationPrefixList && - !isEc2FirewallVpnRoute(this.props.networkConfig.customerGateways ?? [], routeItem) - ) { - // Get PL ID from map - const prefixListId = this.prefixListMap.get(routeItem.destinationPrefixList); - if (!prefixListId) { - this.logger.error(`Prefix list ${routeItem.destinationPrefixList} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const prefixListReferenceConfig = this.getPrefixListReferenceConfig(routeItem, routeTableItem, tgwItem); - - const plRouteId = prefixListReferenceConfig.plRouteId; - const transitGatewayAttachmentId = prefixListReferenceConfig.transitGatewayAttachmentId; - - if (this.isManagedByAsea(AseaResourceType.TRANSIT_GATEWAY_ROUTE, plRouteId)) { - return; - } - - // Create prefix list reference - new TransitGatewayPrefixListReference(this, plRouteId, { - prefixListId, - blackhole: routeItem.blackhole, - transitGatewayAttachmentId, - transitGatewayRouteTableId, - logGroupKmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - } - - /** - * Function to get transit gateway peering attachment ID - * @param transitGatewayPeeringName - * @param tgwItem - * @returns - */ - private getTgwPeeringAttachmentId(transitGatewayPeeringName: string, tgwItem: TransitGatewayConfig): string { - const requesterConfig = this.props.networkConfig.getTgwPeeringRequesterAccepterConfig( - transitGatewayPeeringName, - 'requester', - ); - const accepterConfig = this.props.networkConfig.getTgwPeeringRequesterAccepterConfig( - transitGatewayPeeringName, - 'accepter', - ); - - if (!requesterConfig || !accepterConfig) { - this.logger.error(`Transit gateway peering ${transitGatewayPeeringName} not found !!!`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Get TGW attachment ID for requester - if (this.props.accountsConfig.getAccountId(requesterConfig.account) === cdk.Stack.of(this).account) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.TGW_PEERING, [tgwItem.name, transitGatewayPeeringName]), - ); - } - - // Get TGW attachment ID for accepter - if (this.props.accountsConfig.getAccountId(accepterConfig.account) === cdk.Stack.of(this).account) { - const transitGatewayId = this.transitGateways.get(accepterConfig.transitGatewayName); - if (!transitGatewayId) { - this.logger.error(`Transit Gateway ${accepterConfig.transitGatewayName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - this.logger.info( - `Looking up transit gateway peering attachment id of accepter account ${accepterConfig.account}`, - ); - return TransitGatewayAttachment.fromLookup( - this, - pascalCase(`${accepterConfig.account}${transitGatewayPeeringName}TransitGatewayPeeringAttachment`), - { - name: transitGatewayPeeringName, - owningAccountId: cdk.Stack.of(this).account, - transitGatewayId, - type: TransitGatewayAttachmentType.PEERING, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ).transitGatewayAttachmentId; - } - - this.logger.error(`Transit Gateway attachment id not found for ${transitGatewayPeeringName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - /** - * Check if resource is shared with stack. - * - * @param shareTargets - */ - public checkResourceShare(shareTargets: ShareTargets): boolean { - let included = false; - included = this.isOrganizationalUnitIncluded(shareTargets.organizationalUnits); - - if (included) { - return included; - } - - included = this.isAccountIncluded(shareTargets.accounts); - - return included; - } - - /** - * Function to create outbound nacl entry - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param naclItem {@link NetworkAclConfig} - * @param nacl {@link cdk.aws_ec2.INetworkAcl} - */ - private createOutboundNaclEntry( - vpcItem: VpcConfig | VpcTemplatesConfig, - naclItem: NetworkAclConfig, - nacl: cdk.aws_ec2.INetworkAcl, - ): void { - for (const outboundRuleItem of naclItem.outboundRules ?? []) { - if (this.isIpamCrossAccountNaclSource(outboundRuleItem.destination)) { - this.logger.info(`Checking outbound rule ${outboundRuleItem.rule} to ${naclItem.name}`); - const outboundAclTargetProps = this.getIpamSubnetCidr( - vpcItem.name, - naclItem, - outboundRuleItem.destination as NetworkAclSubnetSelection, - outboundRuleItem, - 'Egress', - ); - const ruleAction = outboundRuleItem.action === 'allow' ? cdk.aws_ec2.Action.ALLOW : cdk.aws_ec2.Action.DENY; - new cdk.aws_ec2.CfnNetworkAclEntry( - this, - `${vpcItem.name}-${naclItem.name}-outbound-${naclItem.outboundRules?.indexOf(outboundRuleItem)}`, - { - protocol: outboundRuleItem.protocol, - networkAclId: nacl.networkAclId, - ruleAction: ruleAction, - ruleNumber: outboundRuleItem.rule, - cidrBlock: outboundAclTargetProps.ipv4CidrBlock, - egress: true, - portRange: { - from: outboundRuleItem.fromPort, - to: outboundRuleItem.toPort, - }, - }, - ); - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.VPC3, - details: [ - { - path: `${this.stackName}/${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}Nacl/${pascalCase( - vpcItem.name, - )}Vpc${pascalCase(naclItem.name)}-Outbound-${outboundRuleItem.rule}`, - reason: 'NACL added to VPC', - }, - ], - }); - } - } - } - - /** - * Function to create inbound nacl entry - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param naclItem {@link NetworkAclConfig} - * @param nacl {@link cdk.aws_ec2.INetworkAcl} - */ - private createInboundNaclEntry( - vpcItem: VpcConfig | VpcTemplatesConfig, - naclItem: NetworkAclConfig, - nacl: cdk.aws_ec2.INetworkAcl, - ): void { - for (const inboundRuleItem of naclItem.inboundRules ?? []) { - if (this.isIpamCrossAccountNaclSource(inboundRuleItem.source)) { - this.logger.info(`Checking inbound rule ${inboundRuleItem.rule} to ${naclItem.name}`); - const inboundAclTargetProps = this.getIpamSubnetCidr( - vpcItem.name, - naclItem, - inboundRuleItem.source as NetworkAclSubnetSelection, - inboundRuleItem, - 'Ingress', - ); - const ruleAction = inboundRuleItem.action === 'allow' ? cdk.aws_ec2.Action.ALLOW : cdk.aws_ec2.Action.DENY; - new cdk.aws_ec2.CfnNetworkAclEntry( - this, - `${vpcItem.name}-${naclItem.name}-inbound-${naclItem.inboundRules?.indexOf(inboundRuleItem)}`, - { - protocol: inboundRuleItem.protocol, - networkAclId: nacl.networkAclId, - ruleAction: ruleAction, - ruleNumber: inboundRuleItem.rule, - cidrBlock: inboundAclTargetProps.ipv4CidrBlock, - egress: false, - portRange: { - from: inboundRuleItem.fromPort, - to: inboundRuleItem.toPort, - }, - }, - ); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.VPC3, - details: [ - { - path: `${this.stackName}/${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}Nacl/${pascalCase( - vpcItem.name, - )}Vpc${pascalCase(naclItem.name)}-Inbound-${inboundRuleItem.rule}`, - reason: 'NACL added to VPC', - }, - ], - }); - } - } - } - - /** - * Function to retrieve cross-account NACLs - * @returns - */ - private createCrossAccountNaclRules() { - for (const vpcItem of this.vpcResources) { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - for (const naclItem of vpcItem.networkAcls ?? []) { - const naclId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.NACL, [vpcItem.name, naclItem.name]), - ); - const nacl = cdk.aws_ec2.NetworkAcl.fromNetworkAclId(this, `${naclItem.name}-${vpcItem.name}`, naclId); - - // Create inbound nacl entry - this.createInboundNaclEntry(vpcItem, naclItem, nacl); - - // Create outbound nacl entry - this.createOutboundNaclEntry(vpcItem, naclItem, nacl); - } - } - } - } - - /** - * Retrieve IPAM Subnet CIDR - * @param vpcName - * @param naclItem - * @param target - */ - private getIpamSubnetCidr( - vpcName: string, - naclItem: NetworkAclConfig, - target: NetworkAclSubnetSelection, - naclRule: NetworkAclInboundRuleConfig | NetworkAclOutboundRuleConfig, - trafficType: string, - ) { - this.logger.info( - `Retrieve IPAM Subnet CIDR for account:${target.account} vpc:${target.vpc} subnet:[${target.subnet}] in region:[${target.region}]`, - ); - const accountId = this.props.accountsConfig.getAccountId(target.account); - return IpamSubnet.fromLookup( - this, - pascalCase(`${vpcName}-${naclItem.name}${naclRule.rule}${trafficType}NaclRule`), - { - owningAccountId: accountId, - ssmSubnetIdPath: this.getSsmPath(SsmResourceType.SUBNET, [target.vpc, target.subnet]), - region: target.region ?? cdk.Stack.of(this).region, - roleName: this.acceleratorResourceNames.roles.ipamSubnetLookup, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ); - } - - /** - * Function to create share subnet tags - * @param vpc {@link VpcConfig} - * @param subnet {@link SubnetConfig} - * @param owningAccountId string - * - * @remarks - * Only get the shared subnets that have tags configured - */ - private createShareSubnetTags(vpc: VpcConfig, subnet: SubnetConfig, owningAccountId: string): void { - if (subnet.shareTargets) { - const shared = this.checkResourceShare(subnet.shareTargets); - if (shared) { - const sharedSubnet = this.getResourceShare( - `${subnet.name}_SubnetShare`, - 'ec2:Subnet', - owningAccountId, - this.cloudwatchKey, - vpc.name, - ); - const vpcTags = this.setVpcTags(vpc); - const subnetTags = this.setSubnetTags(subnet); - const sharedSubnetId = sharedSubnet.resourceShareItemId; - this.logger.info('Applying subnet and vpc tags for RAM shared resources'); - new ShareSubnetTags(this, `ShareSubnetTags${vpc.account}-${subnet.name}`, { - vpcTags, - subnetTags, - sharedSubnetId, - owningAccountId, - vpcName: vpc.name, - subnetName: subnet.name, - resourceLoggingKmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorSsmParamPrefix: this.props.prefixes.ssmParamName, - }); - } - } - } - - private setVpcTags(vpc: VpcConfig) { - const vpcTags: Tag[] = []; - if (vpc.tags) { - vpcTags.push(...vpc.tags); - } - const vpcNameTagExists = vpcTags.find(tag => tag.key === 'Name'); - if (!vpcNameTagExists) { - vpcTags.push({ key: 'Name', value: vpc.name }); - } - return vpcTags; - } - - private setSubnetTags(subnet: SubnetConfig) { - const subnetTags: Tag[] = []; - if (subnet.tags) { - subnetTags.push(...subnet.tags); - } - const subnetNameExists = subnetTags.find(tag => tag.key === 'Name'); - - if (!subnetNameExists) { - subnetTags.push({ key: 'Name', value: subnet.name }); - } - - return subnetTags; - } - private shareSubnetTags() { - for (const vpc of this.props.networkConfig.vpcs) { - const owningAccountId = this.props.accountsConfig.getAccountId(vpc.account); - if (owningAccountId !== cdk.Stack.of(this).account && vpc.region === cdk.Stack.of(this).region) { - for (const subnet of vpc.subnets ?? []) { - this.createShareSubnetTags(vpc, subnet, owningAccountId); - } - } - } - } - - /** - * Function to share active directory - * @param managedActiveDirectory {@link ManagedActiveDirectoryConfig} - * @param activeDirectory {@link ActiveDirectory} - * @returns string[] - * - * @remarks - * Returns list of account names MAD shared with. - */ - private shareActiveDirectory( - managedActiveDirectory: ManagedActiveDirectoryConfig, - activeDirectory: ActiveDirectory, - ): string[] { - const sharedAccountNames = this.props.iamConfig.getManageActiveDirectorySharedAccountNames( - managedActiveDirectory.name, - this.props.configDirPath, - ); - - const sharedAccountIds: string[] = []; - for (const account of sharedAccountNames) { - sharedAccountIds.push(this.props.accountsConfig.getAccountId(account)); - } - if (sharedAccountIds.length > 0) { - this.logger.info(`Sharing Managed active directory ${managedActiveDirectory.name}`); - const shareActiveDirectory = new ShareActiveDirectory( - this, - `${pascalCase(managedActiveDirectory.name)}ShareDirectory`, - { - directoryId: activeDirectory.id, - sharedTargetAccountIds: sharedAccountIds, - accountAccessRoleName: this.acceleratorResourceNames.roles.madShareAccept, - lambdaKey: this.lambdaKey, - cloudwatchKey: this.cloudwatchKey, - cloudwatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }, - ); - - shareActiveDirectory.node.addDependency(activeDirectory); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}ShareDirectory/ShareManageActiveDirectoryFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda needs to access to directory service.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}ShareDirectory/ShareManageActiveDirectoryFunction/ServiceRole/Resource`, - reason: 'Custom resource lambda needs to access to directory service.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}ShareDirectory/ShareManageActiveDirectoryProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource lambda needs to access to directory service.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}ShareDirectory/ShareManageActiveDirectoryProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda needs to access to directory service.', - }, - ], - }); - } - - return sharedAccountNames; - } - - private getMadInstanceSubnetConfig( - managedActiveDirectory: ManagedActiveDirectoryConfig, - madVpcLookup: VpcIdLookup, - ): { madSubnetIds: string[]; madInstanceSubnetId: string } { - const madSubnetIds: string[] = []; - let madInstanceSubnetId: string | undefined; - for (const madSubnet of managedActiveDirectory.vpcSettings.subnets ?? []) { - const madSubnetLookup = new SubnetIdLookup( - this, - `${pascalCase(managedActiveDirectory.name)}${pascalCase(madSubnet)}SubnetLookup`, - { - subnetName: madSubnet, - vpcId: madVpcLookup.vpcId, - lambdaKey: this.lambdaKey, - cloudwatchKey: this.cloudwatchKey, - cloudwatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }, - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}${pascalCase( - madSubnet, - )}SubnetLookup/SubnetIdLookupFunction/ServiceRole/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}${pascalCase( - madSubnet, - )}SubnetLookup/SubnetIdLookupProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}${pascalCase( - madSubnet, - )}SubnetLookup/SubnetIdLookupProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}${pascalCase( - madSubnet, - )}SubnetLookup/SubnetIdLookupFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - madSubnetIds.push(madSubnetLookup.subnetId); - - if ( - managedActiveDirectory.activeDirectoryConfigurationInstance && - madSubnet === managedActiveDirectory.activeDirectoryConfigurationInstance.subnetName - ) { - madInstanceSubnetId = madSubnetLookup.subnetId; - } - } - - return { madInstanceSubnetId: madInstanceSubnetId!, madSubnetIds: madSubnetIds }; - } - - /** - * Create Managed active directories - */ - private createManagedActiveDirectories() { - for (const managedActiveDirectory of this.props.iamConfig.managedActiveDirectories ?? []) { - if (this.isManagedByAsea(AseaResourceType.MANAGED_AD, managedActiveDirectory.name)) { - this.logger.info(`${managedActiveDirectory.name} is managed by ASEA, skipping creation of resources.`); - return; - } - const madAccountId = this.props.accountsConfig.getAccountId(managedActiveDirectory.account); - - if (this.isTargetStack([madAccountId], [managedActiveDirectory.region])) { - this.logger.info(`Creating Managed active directory ${managedActiveDirectory.name}`); - - const madVpcLookup = new VpcIdLookup(this, `${pascalCase(managedActiveDirectory.name)}VpcLookup`, { - vpcName: managedActiveDirectory.vpcSettings.vpcName, - lambdaKey: this.lambdaKey, - cloudwatchKey: this.cloudwatchKey, - cloudwatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}VpcLookup/VpcIdLookupFunction/ServiceRole/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}VpcLookup/VpcIdLookupProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}VpcLookup/VpcIdLookupFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase( - managedActiveDirectory.name, - )}VpcLookup/VpcIdLookupProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda needs this access.', - }, - ], - }); - - const madInstanceSubnetConfig = this.getMadInstanceSubnetConfig(managedActiveDirectory, madVpcLookup); - - let logGroupName = `/aws/directoryservice/${managedActiveDirectory.name}`; - - if (managedActiveDirectory.logs) { - logGroupName = managedActiveDirectory.logs.groupName; - } - - const secretName = `${this.props.prefixes.secretName}/ad-user/${ - managedActiveDirectory.name - }/${this.props.iamConfig.getManageActiveDirectoryAdminSecretName(managedActiveDirectory.name)}`; - - const madAdminSecretAccountId = this.props.accountsConfig.getAccountId( - this.props.iamConfig.getManageActiveDirectorySecretAccountName(managedActiveDirectory.name), - ); - - const madAdminSecretRegion = this.props.iamConfig.getManageActiveDirectorySecretRegion( - managedActiveDirectory.name, - ); - - const adminSecretArn = `arn:${ - cdk.Stack.of(this).partition - }:secretsmanager:${madAdminSecretRegion}:${madAdminSecretAccountId}:secret:${secretName}`; - - const adminSecretValue = cdk.SecretValue.secretsManager(adminSecretArn); - - const activeDirectory = new ActiveDirectory(this, `${pascalCase(managedActiveDirectory.name)}ActiveDirectory`, { - directoryName: managedActiveDirectory.name, - dnsName: managedActiveDirectory.dnsName, - vpcId: madVpcLookup.vpcId, - madSubnetIds: madInstanceSubnetConfig.madSubnetIds, - adminSecretValue, - edition: managedActiveDirectory.edition, - netBiosDomainName: managedActiveDirectory.netBiosDomainName, - logGroupName: logGroupName, - logRetentionInDays: - managedActiveDirectory.logs?.retentionInDays ?? this.props.globalConfig.cloudwatchLogRetentionInDays, - lambdaKey: this.lambdaKey, - cloudwatchKey: this.cloudwatchKey, - cloudwatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}ActiveDirectory/${pascalCase( - managedActiveDirectory.name, - )}LogSubscription/ManageActiveDirectoryLogSubscriptionFunction/ServiceRole/Resource`, - reason: 'CDK created IAM user, role, or group.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}ActiveDirectory/${pascalCase( - managedActiveDirectory.name, - )}LogSubscription/ManageActiveDirectoryLogSubscriptionProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'CDK created IAM user, role, or group.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}ActiveDirectory/${pascalCase( - managedActiveDirectory.name, - )}LogSubscription/ManageActiveDirectoryLogSubscriptionFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'CDK created IAM service role entity.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}ActiveDirectory/${pascalCase( - managedActiveDirectory.name, - )}LogSubscription/ManageActiveDirectoryLogSubscriptionProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'CDK created IAM service role entity.', - }, - ], - }); - - // Share active directory - const sharedAccountNames = this.shareActiveDirectory(managedActiveDirectory, activeDirectory); - - // Update resolver group rule with mad dns ips - this.updateActiveDirectoryResolverGroupRule( - managedActiveDirectory.name, - activeDirectory.dnsIpAddresses, - managedActiveDirectory.resolverRuleName, - ); - - // Configure managed active directory using provisioned EC2 instance user data - this.configureManagedActiveDirectory({ - managedActiveDirectory, - activeDirectory, - adminSecretArn, - adSecretAccountId: madAdminSecretAccountId, - adSecretRegion: madAdminSecretRegion, - sharedAccountNames, - madInstanceSubnetId: madInstanceSubnetConfig.madInstanceSubnetId, - vpcId: madVpcLookup.vpcId, - }); - } - } - } - - /** - * Function to update resolver rule with MAD dns ips - * @param directoryName - * @param dnsIpAddresses - * @param resolverRuleName - */ - private updateActiveDirectoryResolverGroupRule( - directoryName: string, - dnsIpAddresses: string[], - resolverRuleName?: string, - ) { - if (!resolverRuleName) { - return; - } - - this.logger.info(`Updating resolver group for directory ${directoryName}`); - new ActiveDirectoryResolverRule(this, `${pascalCase(directoryName)}ResolverRule`, { - route53ResolverRuleName: resolverRuleName, - targetIps: dnsIpAddresses, - roleName: `${this.props.prefixes.accelerator}-MAD-${resolverRuleName}`, - lambdaKmsKey: this.lambdaKey, - cloudWatchLogsKmsKey: this.cloudwatchKey, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase( - directoryName, - )}ResolverRule/UpdateResolverRuleFunction/ServiceRole/Resource`, - reason: 'CDK created IAM user, role, or group.', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase( - directoryName, - )}ResolverRule/UpdateResolverRuleProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'CDK created IAM user, role, or group.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase( - directoryName, - )}ResolverRule/UpdateResolverRuleFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'CDK created IAM service role entity.', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase( - directoryName, - )}ResolverRule/UpdateResolverRuleProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'CDK created IAM service role entity.', - }, - ], - }); - } - - /** - * Function to configure MAD - * @param managedActiveDirectory - */ - private configureManagedActiveDirectory(props: { - managedActiveDirectory: ManagedActiveDirectoryConfig; - activeDirectory: ActiveDirectory; - adminSecretArn: string; - adSecretAccountId: string; - adSecretRegion: string; - sharedAccountNames: string[]; - madInstanceSubnetId: string; - vpcId: string; - }) { - if (props.managedActiveDirectory.activeDirectoryConfigurationInstance) { - const adInstanceConfig = props.managedActiveDirectory.activeDirectoryConfigurationInstance; - const inboundRules: cdk.aws_ec2.CfnSecurityGroup.IngressProperty[] = []; - - for (const securityGroupInboundSource of adInstanceConfig.securityGroupInboundSources) { - inboundRules.push({ ipProtocol: 'tcp', cidrIp: securityGroupInboundSource, fromPort: 3389, toPort: 3389 }); - inboundRules.push({ ipProtocol: 'tcp', cidrIp: securityGroupInboundSource, fromPort: 443, toPort: 443 }); - } - - const securityGroup = new cdk.aws_ec2.CfnSecurityGroup( - this, - `${pascalCase(props.managedActiveDirectory.name)}SecurityGroup`, - { - groupDescription: `${pascalCase( - props.managedActiveDirectory.name, - )} managed active directory instance security group`, - securityGroupEgress: [{ ipProtocol: '-1', cidrIp: '0.0.0.0/0' }], - securityGroupIngress: inboundRules, - groupName: `${props.managedActiveDirectory.name}_mad_instance_sg`, - vpcId: props.vpcId, - tags: [ - { key: 'Name', value: `${props.managedActiveDirectory.name}_mad_instance_sg` }, - { - key: 'Description', - value: `Security group for ${props.managedActiveDirectory.name} managed active directory instance`, - }, - ], - }, - ); - - const userDataScripts: UserDataScriptsType[] = []; - if (adInstanceConfig.userDataScripts.length > 0) { - for (const userDataScript of adInstanceConfig.userDataScripts) { - userDataScripts.push({ - name: userDataScript.scriptName, - path: path.join(this.props.configDirPath, userDataScript.scriptFilePath), - }); - } - } - - const secretKey = new KeyLookup(this, 'SecretsKmsKeyLookup', { - accountId: props.adSecretAccountId, - keyRegion: props.adSecretRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountSecretsCmkParameterAccess, - keyArnParameterName: this.acceleratorResourceNames.parameters.secretsManagerCmkArn, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorPrefix: this.props.prefixes.accelerator, - }).getKey(); - - const activeDirectoryConfiguration = new ActiveDirectoryConfiguration( - this, - `${pascalCase(props.managedActiveDirectory.name)}ConfigInstance`, - { - instanceType: adInstanceConfig.instanceType, - imagePath: adInstanceConfig.imagePath, - managedActiveDirectoryName: props.managedActiveDirectory.name, - managedActiveDirectorySecretAccountId: props.adSecretAccountId, - managedActiveDirectorySecretRegion: props.adSecretRegion, - dnsName: props.managedActiveDirectory.dnsName, - netBiosDomainName: props.managedActiveDirectory.netBiosDomainName, - adminPwdSecretArn: props.adminSecretArn, - secretKeyArn: secretKey.keyArn, - subnetId: props.madInstanceSubnetId, - securityGroupId: securityGroup.ref, - instanceRoleName: adInstanceConfig.instanceRole, - enableTerminationProtection: adInstanceConfig.enableTerminationProtection ?? false, - userDataScripts, - adGroups: adInstanceConfig.adGroups, - adPerAccountGroups: adInstanceConfig.adPerAccountGroups, - adConnectorGroup: adInstanceConfig.adConnectorGroup, - adUsers: adInstanceConfig.adUsers, - secretPrefix: this.props.prefixes.secretName, - adPasswordPolicy: adInstanceConfig.adPasswordPolicy, - accountNames: props.sharedAccountNames, - }, - ); - - activeDirectoryConfiguration.node.addDependency(props.activeDirectory); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: - `${this.stackName}/` + - pascalCase(props.managedActiveDirectory.name) + - 'ConfigInstance/' + - pascalCase(props.managedActiveDirectory.name) + - 'InstanceRole/Resource', - reason: 'AD config instance needs to access user data bucket and bucket encryption key.', - }, - ], - }); - - // AwsSolutions-EC28: The EC2 instance/AutoScaling launch configuration does not have detailed monitoring enabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.EC28, - details: [ - { - path: - `${this.stackName}/` + - pascalCase(props.managedActiveDirectory.name) + - 'ConfigInstance/' + - pascalCase(props.managedActiveDirectory.name) + - 'InstanceRole/Instance', - reason: 'AD config instance just used to configure MAD through user data.', - }, - ], - }); - - // AwsSolutions-EC29: The EC2 instance is not part of an ASG and has Termination Protection disabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.EC29, - details: [ - { - path: - `${this.stackName}/` + - pascalCase(props.managedActiveDirectory.name) + - 'ConfigInstance/' + - pascalCase(props.managedActiveDirectory.name) + - 'InstanceRole/Instance', - reason: 'AD config instance just used to configure MAD through user data.', - }, - ], - }); - - // AwsSolutions-EC28: The EC2 instance/AutoScaling launch configuration does not have detailed monitoring enabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.EC28, - details: [ - { - path: - `${this.stackName}/` + - pascalCase(props.managedActiveDirectory.name) + - 'ConfigInstance/' + - pascalCase(props.managedActiveDirectory.name) + - 'Instance', - reason: 'AD config instance just used to configure MAD through user data.', - }, - ], - }); - - // AwsSolutions-EC29: The EC2 instance is not part of an ASG and has Termination Protection disabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.EC29, - details: [ - { - path: - `${this.stackName}/` + - pascalCase(props.managedActiveDirectory.name) + - 'ConfigInstance/' + - pascalCase(props.managedActiveDirectory.name) + - 'Instance', - reason: 'AD config instance just used to configure MAD through user data.', - }, - ], - }); - - for (const adUser of adInstanceConfig.adUsers ?? []) { - // AwsSolutions-SMG4: The secret does not have automatic rotation scheduled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.SMG4, - details: [ - { - path: - `${this.stackName}/` + - pascalCase(props.managedActiveDirectory.name) + - 'ConfigInstance/' + - pascalCase(adUser.name) + - 'Secret/Resource', - reason: 'AD user secret.', - }, - ], - }); - } - } - } - - /** - * Return Primary CIDR Block of VPC - * @param vpcName - * @param naclItem - * @param target - */ - private getVpcCidr(vpcItem: VpcConfig | VpcTemplatesConfig, vpcAccountId: string): string { - if (vpcItem.cidrs && vpcItem.cidrs.length > 0) { - return vpcItem.cidrs[0]; - } - this.logger.info( - `Retrieve VPC CIDR for account:${vpcAccountId} vpc:${vpcItem.name} in region:[${cdk.Stack.of(this).region}]`, - ); - if (this.account === vpcAccountId) { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC_IPV4_CIDR_BLOCK, [vpcItem.name]), - ); - } else { - const cidrLookup = - (this.node.tryFindChild(pascalCase(`SsmParamLookup${vpcItem.name}${vpcAccountId}`)) as SsmParameterLookup) ?? - new SsmParameterLookup(this, pascalCase(`SsmParamLookup${vpcItem.name}${vpcAccountId}`), { - name: this.getSsmPath(SsmResourceType.VPC_IPV4_CIDR_BLOCK, [vpcItem.name]), - accountId: vpcAccountId, - parameterRegion: vpcItem.region, - roleName: `${this.props.prefixes.accelerator}-VpcPeeringRole-${vpcItem.region}`, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - acceleratorPrefix: this.props.prefixes.accelerator, - }); - return cidrLookup.value; - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/shared-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/shared-resources.ts deleted file mode 100644 index 992a5a0..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-associations-stack/shared-resources.ts +++ /dev/null @@ -1,254 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - ApplicationLoadBalancer, - IIpamSubnet, - IpamSubnet, - SecurityGroup, - TargetGroup, -} from '@aws-accelerator/constructs'; -import { ApplicationLoadBalancerConfig, VpcConfig, VpcTemplatesConfig } from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { setIpamSubnetSourceArray } from '../utils/security-group-utils'; -import { NetworkAssociationsStack } from './network-associations-stack'; - -export class SharedResources { - public readonly sharedSecurityGroupMap: Map; - - private stack: NetworkAssociationsStack; - - constructor( - networkAssociationsStack: NetworkAssociationsStack, - vpcMap: Map, - prefixListMap: Map, - targetGroupMap: Map, - props: AcceleratorStackProps, - ) { - this.stack = networkAssociationsStack; - - // Retrieve and look up IPAM subnets - const ipamSubnets = setIpamSubnetSourceArray(this.stack.vpcResources, this.stack.sharedVpcs); - const ipamSubnetMap = this.lookupIpamSubnets(ipamSubnets, props); - - this.sharedSecurityGroupMap = this.stack.createSecurityGroups( - this.stack.sharedVpcs, - vpcMap, - ipamSubnetMap, - prefixListMap, - ); - const albMap = this.createApplicationLoadBalancers(props); - this.createSharedAlbListeners(albMap, targetGroupMap); - } - - /** - * Lookup IPAM subnets for a given array of subnet keys - * @param ipamSubnets - * @param props - * @returns - */ - private lookupIpamSubnets(ipamSubnets: string[], props: AcceleratorStackProps): Map { - const ipamSubnetMap = new Map(); - - for (const subnetKey of ipamSubnets) { - const stringSplit = subnetKey.split('_'); - const vpcName = stringSplit[0]; - const accountName = stringSplit[1]; - const subnetName = stringSplit[2]; - const mapKey = `${vpcName}_${subnetName}`; - - // Lookup IPAM subnet - this.stack.addLogs( - LogLevel.INFO, - `Retrieve IPAM Subnet CIDR for account:[${accountName}] vpc:[${vpcName}] subnet:[${subnetName}] in region:[${ - cdk.Stack.of(this.stack).region - }]`, - ); - const accountId = props.accountsConfig.getAccountId(accountName); - const subnet = IpamSubnet.fromLookup(this.stack, pascalCase(`${vpcName}${subnetName}IpamSubnetLookup`), { - owningAccountId: accountId, - ssmSubnetIdPath: this.stack.getSsmPath(SsmResourceType.SUBNET, [vpcName, subnetName]), - region: cdk.Stack.of(this.stack).region, - roleName: this.stack.acceleratorResourceNames.roles.ipamSubnetLookup, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - }); - ipamSubnetMap.set(mapKey, subnet); - } - return ipamSubnetMap; - } - - /** - * Create application load balancers - * @param props - * @returns - */ - private createApplicationLoadBalancers(props: AcceleratorStackProps): Map { - const albMap = new Map(); - const sharedSubnets: string[] = []; - const sharedSubnetMap = new Map(); - const accessLogsBucketName = `${ - this.stack.acceleratorResourceNames.bucketPrefixes.elbLogs - }-${props.accountsConfig.getLogArchiveAccountId()}-${cdk.Stack.of(this.stack).region}`; - for (const vpcItem of this.stack.vpcResources) { - const subnets = vpcItem.subnets?.filter(subnetItem => subnetItem.shareTargets) ?? []; - for (const subnetItem of subnets) { - sharedSubnets.push(subnetItem.name); - } - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers || []) { - if (albItem.shareTargets) { - const sharedAlb = this.stack.checkResourceShare(albItem.shareTargets); - if (sharedAlb && vpcItem.region === cdk.Stack.of(this.stack).region) { - for (const subnetItem of albItem.subnets ?? []) { - const subnetId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - `${this.stack.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem])}`, - ).toString(); - sharedSubnetMap.set(`${vpcItem.name}_${subnetItem}`, subnetId); - } - const securityGroupLookups = albItem.securityGroups.map(securityGroupName => - this.sharedSecurityGroupMap.get(`${vpcItem.name}_${securityGroupName}`), - ); - const nonNullSecurityGroups = securityGroupLookups.filter(group => group) as SecurityGroup[]; - const securityGroupIds = nonNullSecurityGroups.map(securityGroup => securityGroup.securityGroupId); - const subnetIds = albItem.subnets.map(subnetName => - sharedSubnetMap.get(`${vpcItem.name}_${subnetName}`), - ) as string[]; - this.createApplicationLoadBalancer({ - vpcName: vpcItem.name, - albItem, - accessLogsBucketName, - subnetIds, - securityGroupIds, - albMap, - securityGroupLookups, - props, - }); - } - } - } - } - return albMap; - } - - /** - * Function to create Application Load Balancer - * @param options - */ - private createApplicationLoadBalancer(options: { - vpcName: string; - albItem: ApplicationLoadBalancerConfig; - accessLogsBucketName: string; - subnetIds: string[]; - securityGroupIds: string[]; - albMap: Map; - securityGroupLookups: (SecurityGroup | undefined)[]; - props: AcceleratorStackProps; - }) { - const alb = new ApplicationLoadBalancer(this.stack, `${options.albItem.name}-${options.vpcName}`, { - name: options.albItem.name, - ssmPrefix: options.props.prefixes.ssmParamName, - subnets: options.subnetIds, - securityGroups: options.securityGroupIds ?? undefined, - scheme: options.albItem.scheme ?? 'internal', - accessLogsBucket: options.accessLogsBucketName, - attributes: options.albItem.attributes ?? undefined, - }); - options.albMap.set(`${options.vpcName}_${options.albItem.name}`, alb); - - for (const securityGroup of options.securityGroupLookups || []) { - if (securityGroup) { - alb.node.addDependency(securityGroup); - } - } - this.stack.addSsmParameter({ - logicalId: `${options.albItem.name}-${options.vpcName}-ssm`, - parameterName: this.stack.getSsmPath(SsmResourceType.ALB, [options.vpcName, options.albItem.name]), - stringValue: alb.applicationLoadBalancerArn, - }); - } - - /** - * Function to create Application LoadBalancer listeners - * @param vpcItem {@link VpcConfig } | {@link VpcTemplatesConfig} - * @param albItem {@link ApplicationLoadBalancerConfig} - * @param albArn string - * @param targetGroupMap Map - * @param listenerMap Map - */ - private createSharedApplicationLoadBalancerListeners( - vpcItem: VpcConfig | VpcTemplatesConfig, - albItem: ApplicationLoadBalancerConfig, - albMap: Map, - targetGroupMap: Map, - listenerMap: Map, - ): void { - for (const listener of albItem.listeners ?? []) { - if (albItem.shareTargets) { - const sharedAlb = this.stack.checkResourceShare(albItem.shareTargets); - if (sharedAlb && vpcItem.region === cdk.Stack.of(this.stack).region) { - const targetGroup = targetGroupMap.get(`${vpcItem.name}-${listener.targetGroup}`); - if (!targetGroup) { - this.stack.addLogs( - LogLevel.ERROR, - `The Listener ${listener.name} contains an invalid target group name ${listener.targetGroup} please ensure that the the target group name references a valid target group`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - const listenerAction: cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty = - this.stack.getListenerAction(listener, targetGroup.targetGroupArn); - - const albValue = albMap.get(`${vpcItem.name}_${albItem.name}`); - - const listenerResource = new cdk.aws_elasticloadbalancingv2.CfnListener( - this.stack, - pascalCase(`Listener${vpcItem.name}${albItem.name}${listener.name}`), - { - defaultActions: [listenerAction], - loadBalancerArn: albValue?.applicationLoadBalancerArn as string, - certificates: [{ certificateArn: this.stack.getCertificate(listener.certificate) }], - port: listener.port, - protocol: listener.protocol, - sslPolicy: listener.sslPolicy!, - }, - ); - listenerMap.set(`${vpcItem.name}-${albItem.name}-${listener.name}`, listenerResource); - } - } - } - } - - private createSharedAlbListeners( - albMap: Map, - targetGroupMap: Map, - ) { - try { - const listenerMap = new Map(); - for (const vpcItem of this.stack.vpcResources) { - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers ?? []) { - if (this.stack.region === vpcItem.region) { - this.createSharedApplicationLoadBalancerListeners(vpcItem, albItem, albMap, targetGroupMap, listenerMap); - } - } - } - return listenerMap; - } catch (err) { - this.stack.addLogs(LogLevel.ERROR, `${err}`); - throw err; - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/central-network-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/central-network-resources.ts deleted file mode 100644 index 19b1228..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/central-network-resources.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { IpamResources } from './ipam-resources'; -import { NetworkPrepStack } from './network-prep-stack'; -import { NfwResources } from './nfw-resources'; -import { ResolverResources } from './resolver-resources'; - -export class CentralNetworkResources { - public readonly ipamResources: IpamResources; - public readonly nfwResources: NfwResources; - public readonly resolverResources: ResolverResources; - private stack: NetworkPrepStack; - - constructor(networkPrepStack: NetworkPrepStack, props: AcceleratorStackProps, orgId?: string) { - this.stack = networkPrepStack; - - // Retrieve central network config and delegated admin account ID - const centralConfig = props.networkConfig.centralNetworkServices!; - const delegatedAdminAccountId = props.accountsConfig.getAccountId(centralConfig.delegatedAdminAccount); - - // Create IPAM resources - this.ipamResources = new IpamResources( - this.stack, - delegatedAdminAccountId, - centralConfig, - props.globalConfig.homeRegion, - props.prefixes.ssmParamName, - orgId, - ); - // Create Route 53 resolver resources - this.resolverResources = new ResolverResources(this.stack, delegatedAdminAccountId, centralConfig, props, orgId); - // Create network firewall resources - this.nfwResources = new NfwResources(this.stack, delegatedAdminAccountId, centralConfig, props); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/dx-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/dx-resources.ts deleted file mode 100644 index 215e1e3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/dx-resources.ts +++ /dev/null @@ -1,314 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DxGatewayConfig, DxTransitGatewayAssociationConfig } from '@aws-accelerator/config'; -import { DirectConnectGateway, VirtualInterface, VirtualInterfaceProps } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class DxResources { - public readonly dxGatewayMap: Map; - public readonly ssmRoleMap: Map; - public readonly vifMap: Map; - private stack: NetworkPrepStack; - - constructor(networkPrepStack: NetworkPrepStack, props: AcceleratorStackProps) { - this.stack = networkPrepStack; - - // Create DX gateways and virtual interfaces - [this.dxGatewayMap, this.vifMap] = this.createDirectConnectResources(props); - // Create cross-account SSM role if required - this.ssmRoleMap = this.validateSsmRole(props); - } - - /** - * Create Direct Connect resources - * @param props - * @returns - */ - private createDirectConnectResources(props: AcceleratorStackProps): Map[] { - const vifMap = new Map(); - // Create DX gateways - const dxGatewayMap = this.createDirectConnectGateways(props); - // Create virtual interfaces - for (const dxgwItem of props.networkConfig.directConnectGateways ?? []) { - const dxgwItemVifMap = this.validateVirtualInterfaceProps(dxgwItem, dxGatewayMap, props); - dxgwItemVifMap.forEach((value, key) => vifMap.set(key, value)); - } - return [dxGatewayMap, vifMap]; - } - - /** - * Create Direct Connect Gateway - * @param props - * @returns - */ - private createDirectConnectGateways(props: AcceleratorStackProps): Map { - const dxGatewayMap = new Map(); - - for (const dxgwItem of props.networkConfig.directConnectGateways ?? []) { - const accountId = props.accountsConfig.getAccountId(dxgwItem.account); - // DXGW is a global object -- only create in home region - if (this.stack.isTargetStack([accountId], [props.globalConfig.homeRegion])) { - this.stack.addLogs(LogLevel.INFO, `Creating Direct Connect Gateway ${dxgwItem.name}`); - const dxGateway = new DirectConnectGateway(this.stack, pascalCase(`${dxgwItem.name}DxGateway`), { - gatewayName: dxgwItem.gatewayName, - asn: dxgwItem.asn, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - }); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${dxgwItem.name}DirectConnectGateway`), - parameterName: this.stack.getSsmPath(SsmResourceType.DXGW, [dxgwItem.name]), - stringValue: dxGateway.directConnectGatewayId, - }); - dxGatewayMap.set(dxgwItem.name, dxGateway.directConnectGatewayId); - } - } - return dxGatewayMap; - } - - /** - * Validate Direct Connect virtual interface properties - * and create interfaces - * @param dxgwItem - * @param dxgwMap - * @param props - * @returns - */ - private validateVirtualInterfaceProps( - dxgwItem: DxGatewayConfig, - dxGatewayMap: Map, - props: AcceleratorStackProps, - ): Map { - const vifMap = new Map(); - - for (const vifItem of dxgwItem.virtualInterfaces ?? []) { - const connectionOwnerAccountId = props.accountsConfig.getAccountId(vifItem.ownerAccount); - let createVif = false; - let vifLogicalId: string | undefined = undefined; - let vifProps: VirtualInterfaceProps | undefined = undefined; - - // If DXGW and connection owner account do not match, create a VIF allocation - if ( - dxgwItem.account !== vifItem.ownerAccount && - this.stack.isTargetStack([connectionOwnerAccountId], [props.globalConfig.homeRegion]) - ) { - this.stack.addLogs( - LogLevel.INFO, - `Creating virtual interface allocation ${vifItem.name} to Direct Connect Gateway ${dxgwItem.name}`, - ); - createVif = true; - vifLogicalId = pascalCase(`${dxgwItem.name}${vifItem.name}VirtualInterfaceAllocation`); - const vifOwnerAccountId = props.accountsConfig.getAccountId(dxgwItem.account); - vifProps = { - connectionId: vifItem.connectionId, - customerAsn: vifItem.customerAsn, - interfaceName: vifItem.interfaceName, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - type: vifItem.type, - region: vifItem.region, - vlan: vifItem.vlan, - addressFamily: vifItem.addressFamily, - amazonAddress: vifItem.amazonAddress, - customerAddress: vifItem.customerAddress, - enableSiteLink: vifItem.enableSiteLink, - jumboFrames: vifItem.jumboFrames, - ownerAccount: vifOwnerAccountId, - tags: vifItem.tags, - acceleratorPrefix: props.prefixes.accelerator, - }; - } - - // If DXGW and connection owner account do match, create a VIF - if ( - dxgwItem.account === vifItem.ownerAccount && - this.stack.isTargetStack([connectionOwnerAccountId], [props.globalConfig.homeRegion]) - ) { - this.stack.addLogs( - LogLevel.INFO, - `Creating virtual interface ${vifItem.name} to Direct Connect Gateway ${dxgwItem.name}`, - ); - createVif = true; - const directConnectGatewayId = dxGatewayMap.get(dxgwItem.name); - if (!directConnectGatewayId) { - this.stack.addLogs(LogLevel.ERROR, `Unable to locate Direct Connect Gateway ${dxgwItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - vifLogicalId = pascalCase(`${dxgwItem.name}${vifItem.name}VirtualInterface`); - vifProps = { - connectionId: vifItem.connectionId, - customerAsn: vifItem.customerAsn, - interfaceName: vifItem.interfaceName, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - type: vifItem.type, - region: vifItem.region, - vlan: vifItem.vlan, - addressFamily: vifItem.addressFamily, - amazonAddress: vifItem.amazonAddress, - customerAddress: vifItem.customerAddress, - directConnectGatewayId, - enableSiteLink: vifItem.enableSiteLink, - jumboFrames: vifItem.jumboFrames, - tags: vifItem.tags, - acceleratorPrefix: props.prefixes.accelerator, - }; - } - - // Create the VIF or VIF allocation - if (createVif) { - const vif = this.createVirtualInterface(dxgwItem.name, vifItem.name, vifLogicalId, vifProps); - vifMap.set(`${dxgwItem.name}_${vifItem.name}`, vif.virtualInterfaceId); - } - } - return vifMap; - } - - /** - * Create Direct connect virtual interface - * @param dxgwName - * @param vifName - * @param vifLogicalId - * @param vifProps - */ - private createVirtualInterface( - dxgwName: string, - vifName: string, - vifLogicalId?: string, - vifProps?: VirtualInterfaceProps, - ): VirtualInterface { - if (!vifLogicalId || !vifProps) { - this.stack.addLogs( - LogLevel.ERROR, - `Create virtual interfaces: unable to process properties for virtual interface ${vifName}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - const virtualInterface = new VirtualInterface(this.stack, vifLogicalId, vifProps); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${dxgwName}${vifName}VirtualInterface`), - parameterName: this.stack.getSsmPath(SsmResourceType.DXVIF, [dxgwName, vifName]), - stringValue: virtualInterface.virtualInterfaceId, - }); - - return virtualInterface; - } - - /** - * Function to get TGW account ids - * @param dxgwItem {@link DxGatewayConfig} - * @param associationItem {@link DxTransitGatewayAssociationConfig} - * @param props {@link AcceleratorStackProps} - * @param accountIds string[] - */ - private getTgwAccountIds( - dxgwItem: DxGatewayConfig, - associationItem: DxTransitGatewayAssociationConfig, - props: AcceleratorStackProps, - accountIds: string[], - ) { - const tgw = props.networkConfig.transitGateways.find( - item => item.name === associationItem.name && item.account === associationItem.account, - ); - if (!tgw) { - this.stack.addLogs(LogLevel.ERROR, `Unable to locate transit gateway ${associationItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const tgwAccountId = props.accountsConfig.getAccountId(tgw.account); - - // Add to accountIds if accounts do not match - if (dxgwItem.account !== tgw.account && !accountIds.includes(tgwAccountId)) { - accountIds.push(tgwAccountId); - } - // Add to accountIds if regions don't match - if (tgw.region !== cdk.Stack.of(this.stack).region && !accountIds.includes(tgwAccountId)) { - accountIds.push(tgwAccountId); - } - } - - /** - * Validate whether a cross-account SSM role should be created in this stack - * @param props - * @returns - */ - private validateSsmRole(props: AcceleratorStackProps) { - const accountIds: string[] = []; - const ssmRoleMap = new Map(); - - if (props.globalConfig.homeRegion === cdk.Stack.of(this.stack).region) { - for (const dxgwItem of props.networkConfig.directConnectGateways ?? []) { - for (const associationItem of dxgwItem.transitGatewayAssociations ?? []) { - this.getTgwAccountIds(dxgwItem, associationItem, props, accountIds); - } - // Create role - if (accountIds.length > 0) { - const role = this.createDxGatewaySsmRole(props, dxgwItem, accountIds); - ssmRoleMap.set(`${dxgwItem.name}`, role); - } - } - } - return ssmRoleMap; - } - - /** - * Create a cross-account role to access SSM parameters - * @param props - * @param dxgwItem - * @param accountIds - */ - private createDxGatewaySsmRole( - props: AcceleratorStackProps, - dxgwItem: DxGatewayConfig, - accountIds: string[], - ): cdk.aws_iam.Role { - this.stack.addLogs(LogLevel.INFO, `Direct Connect Gateway: Create IAM cross-account access role`); - - const principals: cdk.aws_iam.PrincipalBase[] = []; - accountIds.forEach(accountId => { - principals.push(new cdk.aws_iam.AccountPrincipal(accountId)); - }); - const role = new cdk.aws_iam.Role(this.stack, `Get${pascalCase(dxgwItem.name)}SsmParamRole`, { - roleName: `${props.prefixes.accelerator}-Get${pascalCase(dxgwItem.name)}SsmParamRole-${ - cdk.Stack.of(this.stack).region - }`, - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter'], - resources: [ - `arn:${cdk.Aws.PARTITION}:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter${props.prefixes.ssmParamName}/network/directConnectGateways/${dxgwItem.name}/*`, - ], - }), - ], - }), - }, - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressions(role, [ - { id: 'AwsSolutions-IAM5', reason: 'Allow cross-account resources to get SSM parameters under this path.' }, - ]); - - return role; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/fms-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/fms-resources.ts deleted file mode 100644 index b22a0d2..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/fms-resources.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { FMSNotificationChannel } from '@aws-accelerator/constructs'; -import * as cdk from 'aws-cdk-lib'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class FmsResources { - private stack: NetworkPrepStack; - public readonly notificationChannelMap?: Map; - - constructor(networkPrepStack: NetworkPrepStack, props: AcceleratorStackProps) { - this.stack = networkPrepStack; - - // Create FMS notification channels - this.notificationChannelMap = this.createFMSNotificationChannels(props); - } - - /** - * Creates FMS Notification Channels - */ - private createFMSNotificationChannels(props: AcceleratorStackProps): Map | undefined { - const fmsConfiguration = props.networkConfig.firewallManagerService; - // Exit if Notification channels don't exist. - if (!fmsConfiguration?.notificationChannels || fmsConfiguration.notificationChannels.length === 0) { - return undefined; - } - const accountId = props.accountsConfig.getAccountId(fmsConfiguration.delegatedAdminAccount); - const auditAccountId = props.accountsConfig.getAuditAccountId(); - const notificationChannelMap = new Map(); - const roleArn = `arn:${cdk.Stack.of(this.stack).partition}:iam::${cdk.Stack.of(this.stack).account}:role/${ - props.prefixes.accelerator - }-FMS-Notifications`; - - for (const notificationChannel of fmsConfiguration.notificationChannels) { - const snsTopicName = notificationChannel.snsTopic; - if (this.stack.isTargetStack([accountId], [notificationChannel.region])) { - const snsTopicsSecurity = - props.securityConfig.centralSecurityServices.snsSubscriptions?.map( - snsSubscription => snsSubscription.level, - ) || []; - const snsTopicsGlobal = props.globalConfig.snsTopics?.topics.map(snsTopic => snsTopic.name) || []; - const snsTopics = [...snsTopicsSecurity, ...snsTopicsGlobal]; - if (!snsTopics.includes(snsTopicName)) { - this.stack.addLogs( - LogLevel.ERROR, - `SNS Topic level ${snsTopicName} does not exist in the security config SNS Topics`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - let snsTopicArn = `arn:${cdk.Stack.of(this.stack).partition}:sns:${cdk.Stack.of(this.stack).region}:${ - cdk.Stack.of(this.stack).account - }:${props.prefixes.snsTopicName}-${snsTopicName}`; - - if (snsTopicsSecurity.includes(snsTopicName)) { - snsTopicArn = `arn:${cdk.Stack.of(this.stack).partition}:sns:${ - cdk.Stack.of(this.stack).region - }:${auditAccountId}:${props.prefixes.snsTopicName}-${snsTopicName}Notifications`; - } - this.stack.addLogs( - LogLevel.INFO, - `Adding FMS notification channel for ${fmsConfiguration.delegatedAdminAccount} in region ${notificationChannel.region} to topic ${snsTopicArn}`, - ); - - const channel = new FMSNotificationChannel( - this.stack, - `fmsNotification-${this.stack.account}-${this.stack.region}`, - { - snsTopicArn, - snsRoleArn: roleArn, - }, - ); - notificationChannelMap.set(snsTopicName, channel.snsTopicArn); - - this.stack.addLogs(LogLevel.INFO, `Created FMS notification Channel`); - } - } - return notificationChannelMap; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/ipam-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/ipam-resources.ts deleted file mode 100644 index 29a9748..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/ipam-resources.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CentralNetworkServicesConfig, IpamConfig, IpamPoolConfig } from '@aws-accelerator/config'; -import { Ipam, IpamPool, IpamScope } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { LogLevel } from '../network-stack'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class IpamResources { - public readonly ipamMap: Map; - public readonly poolMap: Map; - public readonly scopeMap: Map; - public readonly ssmRole?: cdk.aws_iam.Role; - - private stack: NetworkPrepStack; - - constructor( - networkPrepStack: NetworkPrepStack, - delegatedAdminAccountId: string, - centralConfig: CentralNetworkServicesConfig, - homeRegion: string, - ssmParamNamePrefix: string, - orgId?: string, - ) { - this.stack = networkPrepStack; - - // Create IPAMs - [this.ipamMap, this.scopeMap, this.poolMap] = this.createIpamResources(delegatedAdminAccountId, centralConfig); - // Create cross-account SSM role - this.ssmRole = this.createIpamSsmRole( - centralConfig, - delegatedAdminAccountId, - homeRegion, - ssmParamNamePrefix, - orgId, - ); - } - - /** - * Create IPAM resources - * @param accountId - * @param centralConfig - */ - private createIpamResources(accountId: string, centralConfig: CentralNetworkServicesConfig): Map[] { - const ipamMap = new Map(); - const poolMap = new Map(); - const scopeMap = new Map(); - - for (const ipamItem of centralConfig.ipams ?? []) { - if (this.stack.isTargetStack([accountId], [ipamItem.region])) { - this.stack.addLogs(LogLevel.INFO, `Add IPAM ${ipamItem.name}`); - - // Create IPAM - const ipam = new Ipam(this.stack, pascalCase(`${ipamItem.name}Ipam`), { - name: ipamItem.name, - description: ipamItem.description, - operatingRegions: ipamItem.operatingRegions, - tags: ipamItem.tags, - }); - ipamMap.set(ipamItem.name, ipam.ipamId); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${ipamItem.name}IpamId`), - parameterName: this.stack.getSsmPath(SsmResourceType.IPAM, [ipamItem.name]), - stringValue: ipam.ipamId, - }); - - // Create scopes - const ipamItemScopeMap = this.createIpamScopes(ipam, ipamItem); - ipamItemScopeMap.forEach((value, key) => scopeMap.set(key, value)); - - // Create pools - if (ipamItem.pools) { - const ipamBasePoolMap = this.createIpamBasePools(ipam, ipamItem, ipamItemScopeMap); - - // Create nested pools - const ipamAllPoolsMap = this.createIpamNestedPools(ipam, ipamItem, ipamBasePoolMap, ipamItemScopeMap); - ipamAllPoolsMap.forEach((value, key) => poolMap.set(key, value)); - } - } - } - return [ipamMap, scopeMap, poolMap]; - } - - /** - * Create IPAM scopes for a given IPAM - * @param ipam - * @param ipamItem - * @returns - */ - private createIpamScopes(ipam: Ipam, ipamItem: IpamConfig): Map { - const scopeMap = new Map(); - - for (const scopeItem of ipamItem.scopes ?? []) { - this.stack.addLogs(LogLevel.INFO, `Add IPAM scope ${scopeItem.name}`); - const ipamScope = new IpamScope(this.stack, pascalCase(`${scopeItem.name}Scope`), { - ipamId: ipam.ipamId, - name: scopeItem.name, - description: scopeItem.description, - tags: scopeItem.tags ?? [], - }); - scopeMap.set(`${ipamItem.name}_${scopeItem.name}`, ipamScope.ipamScopeId); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${scopeItem.name}ScopeId`), - parameterName: this.stack.getSsmPath(SsmResourceType.IPAM_SCOPE, [scopeItem.name]), - stringValue: ipamScope.ipamScopeId, - }); - } - return scopeMap; - } - - /** - * Create IPAM base pools - * @param ipam - * @param ipamItem - * @param scopeMap - * @returns - */ - private createIpamBasePools(ipam: Ipam, ipamItem: IpamConfig, scopeMap: Map): Map { - const poolMap = new Map(); - - const basePools = ipamItem.pools!.filter(item => { - return !item.sourceIpamPool; - }); - for (const poolItem of basePools ?? []) { - this.stack.addLogs(LogLevel.INFO, `Add IPAM top-level pool ${poolItem.name}`); - let poolScope: string | undefined; - - if (poolItem.scope) { - poolScope = scopeMap.get(`${ipamItem.name}_${poolItem.scope}`); - - if (!poolScope) { - this.stack.addLogs(LogLevel.ERROR, `Unable to locate IPAM scope ${poolItem.scope} for pool ${poolItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - // Create base pool - const pool = this.createIpamPool(ipam, poolItem, poolScope); - poolMap.set(`${ipamItem.name}_${poolItem.name}`, pool.ipamPoolId); - } - return poolMap; - } - - /** - * Function to check whether IPAM source pool exists in given poolItem - * @param poolItem {@link IpamPoolConfig} - * @param sourcePoolExists {@link IpamPoolConfig} - * - * @remarks - * Check for case where the source pool hasn't been created yet - */ - private checkIpamSourcePoolExists(poolItem: IpamPoolConfig, sourcePoolExists?: IpamPoolConfig): void { - if (!sourcePoolExists) { - this.stack.addLogs( - LogLevel.ERROR, - `Unable to locate source IPAM pool ${poolItem.sourceIpamPool} for pool ${poolItem.name}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to check whether IPAM scope exists for given pooleItem - * @param poolItem {@link IpamPoolConfig} - * @param scopeMap Map - */ - private checkIpamScopeExists(poolItem: IpamPoolConfig, scopeMap: Map): string | undefined { - let poolScope: string | undefined; - - if (poolItem.scope) { - poolScope = scopeMap.get(poolItem.scope); - - if (!poolScope) { - this.stack.addLogs(LogLevel.ERROR, `Unable to locate IPAM scope ${poolItem.scope} for pool ${poolItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - return poolScope; - } - - /** - * Create IPAM nested pools - * @param ipam - * @param ipamItem - * @param poolMap - * @param scopeMap - */ - private createIpamNestedPools( - ipam: Ipam, - ipamItem: IpamConfig, - poolMap: Map, - scopeMap: Map, - ): Map { - const nestedPools = ipamItem.pools!.filter(item => { - return item.sourceIpamPool; - }); - - // Use while loop for iteration - while (poolMap.size < ipamItem.pools!.length) { - for (const poolItem of nestedPools) { - // Check if source pool name has been created or exists in the config array - const sourcePool = poolMap.get(`${ipamItem.name}_${poolItem.sourceIpamPool!}`); - if (!sourcePool) { - // Check for case where the source pool hasn't been created yet - const sourcePoolExists = nestedPools.find(item => item.name === poolItem.sourceIpamPool); - this.checkIpamSourcePoolExists(poolItem, sourcePoolExists); - // Skip iteration if source pool exists but has not yet been created - continue; - } - // Check if this item has already been created - const poolExists = poolMap.get(`${ipamItem.name}_${poolItem.name}`); - - if (sourcePool && !poolExists) { - this.stack.addLogs(LogLevel.INFO, `Add IPAM nested pool ${poolItem.name}`); - const poolScope = this.checkIpamScopeExists(poolItem, scopeMap); - - // Create nested pool - const pool = this.createIpamPool(ipam, poolItem, poolScope, sourcePool); - poolMap.set(`${ipamItem.name}_${poolItem.name}`, pool.ipamPoolId); - } - } - } - return poolMap; - } - - /** - * Create IPAM pool - * @param ipam - * @param poolItem - * @param poolScope - * @param sourcePool - * @returns - */ - private createIpamPool(ipam: Ipam, poolItem: IpamPoolConfig, poolScope?: string, sourcePool?: string): IpamPool { - const pool = new IpamPool(this.stack, pascalCase(`${poolItem.name}Pool`), { - addressFamily: poolItem.addressFamily ?? 'ipv4', - ipamScopeId: poolScope ?? ipam.privateDefaultScopeId, - name: poolItem.name, - allocationDefaultNetmaskLength: poolItem.allocationDefaultNetmaskLength, - allocationMaxNetmaskLength: poolItem.allocationMaxNetmaskLength, - allocationMinNetmaskLength: poolItem.allocationMinNetmaskLength, - allocationResourceTags: poolItem.allocationResourceTags, - autoImport: poolItem.autoImport, - description: poolItem.description, - locale: poolItem.locale, - provisionedCidrs: poolItem.provisionedCidrs, - publiclyAdvertisable: poolItem.publiclyAdvertisable, - sourceIpamPoolId: sourcePool, - tags: poolItem.tags, - }); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${poolItem.name}PoolId`), - parameterName: this.stack.getSsmPath(SsmResourceType.IPAM_POOL, [poolItem.name]), - stringValue: pool.ipamPoolId, - }); - - // Add resource shares - if (poolItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share IPAM pool ${poolItem.name}`); - this.stack.addResourceShare(poolItem, `${poolItem.name}_IpamPoolShare`, [pool.ipamPoolArn]); - } - return pool; - } - - /** - * Create cross-account SSM role - * - * @param centralConfig - * @param delegatedAdminAccountId - * @param homeRegion - * @param ssmParamNamePrefix - * @param orgId - */ - private createIpamSsmRole( - centralConfig: CentralNetworkServicesConfig, - delegatedAdminAccountId: string, - homeRegion: string, - ssmParamNamePrefix: string, - orgId?: string, - ): cdk.aws_iam.Role | undefined { - if ( - this.stack.isTargetStack([delegatedAdminAccountId], [homeRegion]) && - centralConfig.ipams && - centralConfig.ipams.length > 0 - ) { - this.stack.addLogs(LogLevel.INFO, `IPAM Pool: Create IAM role for cross-account SSM Parameter pulls`); - - const role = new cdk.aws_iam.Role(this.stack, `GetIpamSsmParamRole`, { - roleName: this.stack.acceleratorResourceNames.roles.ipamSsmParameterAccess, - assumedBy: this.stack.getOrgPrincipals(orgId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter', 'ssm:GetParameters'], - resources: [ - `arn:${cdk.Aws.PARTITION}:ssm:*:${cdk.Aws.ACCOUNT_ID}:parameter${ssmParamNamePrefix}/network/ipam/pools/*/id`, - ], - }), - ], - }), - }, - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressions(role, [ - { id: 'AwsSolutions-IAM5', reason: 'Allow cross-account resources to get SSM parameters under this path.' }, - ]); - - return role; - } - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/mad-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/mad-resources.ts deleted file mode 100644 index 1e8a771..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/mad-resources.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class MadResources { - public readonly role?: cdk.aws_iam.Role; - - private stack: NetworkPrepStack; - - constructor(networkPrepStack: NetworkPrepStack, props: AcceleratorStackProps) { - this.stack = networkPrepStack; - this.role = this.createManagedActiveDirectoryShareAcceptRole(props); - } - - /** - * Function to create Managed active directory share accept role. This role is used to assume by MAD account to auto accept share request - * This role is created only if account is a shared target for MAD. - * This role gets created only in home region - * @returns - */ - private createManagedActiveDirectoryShareAcceptRole(props: AcceleratorStackProps): cdk.aws_iam.Role | undefined { - for (const managedActiveDirectory of props.iamConfig.managedActiveDirectories ?? []) { - const madAccountId = props.accountsConfig.getAccountId(managedActiveDirectory.account); - const sharedAccountNames = props.iamConfig.getManageActiveDirectorySharedAccountNames( - managedActiveDirectory.name, - props.configDirPath, - ); - - const sharedAccountIds: string[] = []; - for (const account of sharedAccountNames) { - sharedAccountIds.push(props.accountsConfig.getAccountId(account)); - } - - // Create role in shared account home region only - if (this.stack.isTargetStack(sharedAccountIds, [props.globalConfig.homeRegion])) { - const role = new cdk.aws_iam.Role(this.stack, 'MadShareAcceptRole', { - roleName: this.stack.acceleratorResourceNames.roles.madShareAccept, - assumedBy: new cdk.aws_iam.PrincipalWithConditions(new cdk.aws_iam.AccountPrincipal(madAccountId), { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this.stack).partition}:iam::${madAccountId}:role/${props.prefixes.accelerator}-*`, - ], - }, - }), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ds:AcceptSharedDirectory'], - resources: ['*'], - }), - ], - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/MadShareAcceptRole/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'MAD share accept role needs access to directory for acceptance ', - }, - ], - ); - - return role; - } - } - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/network-prep-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/network-prep-stack.ts deleted file mode 100644 index c85dc1c..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/network-prep-stack.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Construct } from 'constructs'; - -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { NetworkStack } from '../network-stack'; -import { CentralNetworkResources } from './central-network-resources'; -import { DxResources } from './dx-resources'; -import { FmsResources } from './fms-resources'; -import { MadResources } from './mad-resources'; -import { TgwResources } from './tgw-resources'; -import { VpnResources } from './vpn-resources'; - -export class NetworkPrepStack extends NetworkStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - // - // Generate Transit Gateways and - // Transit Gateway peering role - // - const tgwResources = new TgwResources(this, props); - - // - // Create Managed active directory accept share role - // - new MadResources(this, props); - - // - // Create Site-to-Site VPN connections - // - new VpnResources(this, tgwResources.transitGatewayMap, props); - - // - // Create Direct Connect Gateways and virtual interfaces - // - new DxResources(this, props); - - // - // Central network services - // - if (props.networkConfig.centralNetworkServices) { - // Retrieve org ID - const organizationId = this.organizationId; - new CentralNetworkResources(this, props, organizationId); - } - // - // FMS Notification Channel - // - new FmsResources(this, props); - // - // Create SSM Parameters - // - this.createSsmParameters(); - // - // Add nag suppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/nfw-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/nfw-resources.ts deleted file mode 100644 index 21bc2fc..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/nfw-resources.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AseaResourceType, - CentralNetworkServicesConfig, - NfwRuleGroupConfig, - NfwRuleGroupRuleConfig, - NfwStatefulRuleGroupReferenceConfig, - NfwStatelessRuleGroupReferenceConfig, -} from '@aws-accelerator/config'; -import { FirewallPolicyProperty, NetworkFirewallPolicy, NetworkFirewallRuleGroup } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import fs from 'fs'; -import { pascalCase } from 'pascal-case'; -import path from 'path'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class NfwResources { - public readonly policyMap: Map; - public readonly ruleGroupMap: Map; - private stack: NetworkPrepStack; - constructor( - networkPrepStack: NetworkPrepStack, - delegatedAdminAccountId: string, - centralConfig: CentralNetworkServicesConfig, - props: AcceleratorStackProps, - ) { - this.stack = networkPrepStack; - - // Create NFW rule groups - this.ruleGroupMap = this.createNfwRuleGroups(delegatedAdminAccountId, centralConfig, props); - // Create NFW policies - this.policyMap = this.createNfwPolicies(delegatedAdminAccountId, this.ruleGroupMap, centralConfig); - } - - /** - * Create AWS Network Firewall rule groups - * @param accountId - * @param ruleItem - */ - private createNfwRuleGroups( - accountId: string, - centralConfig: CentralNetworkServicesConfig, - props: AcceleratorStackProps, - ): Map { - const ruleGroupMap = new Map(); - - for (const ruleItem of centralConfig.networkFirewall?.rules ?? []) { - const regions = ruleItem.regions.map(item => { - return item.toString(); - }); - - // Create regional rule groups in the delegated admin account - if (this.stack.isTargetStack([accountId], regions)) { - this.stack.addLogs(LogLevel.INFO, `Create network firewall rule group ${ruleItem.name}`); - let rule; - if (this.stack.isManagedByAsea(AseaResourceType.NFW_RULE_GROUP, ruleItem.name)) { - const groupArn = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.NFW_RULE_GROUP, [ruleItem.name]), - ); - rule = NetworkFirewallRuleGroup.fromAttributes( - this.stack, - pascalCase(`${ruleItem.name}NetworkFirewallRuleGroup`), - { - groupArn, - groupName: ruleItem.name, - }, - ); - } else { - // - // Create rule group - rule = new NetworkFirewallRuleGroup(this.stack, pascalCase(`${ruleItem.name}NetworkFirewallRuleGroup`), { - capacity: ruleItem.capacity, - name: ruleItem.name, - type: ruleItem.type, - description: ruleItem.description, - ruleGroup: this.getRuleGroupRuleConfig(ruleItem, props), - tags: ruleItem.tags ?? [], - }); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${ruleItem.name}NetworkFirewallRuleGroup`), - parameterName: this.stack.getSsmPath(SsmResourceType.NFW_RULE_GROUP, [ruleItem.name]), - stringValue: rule.groupArn, - }); - - if (ruleItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share Network Firewall rule group ${ruleItem.name}`); - this.stack.addResourceShare(ruleItem, `${ruleItem.name}_NetworkFirewallRuleGroupShare`, [rule.groupArn]); - } - } - ruleGroupMap.set(ruleItem.name, rule.groupArn); - } - } - return ruleGroupMap; - } - - /** - * Get rule group rule configuration for a given rule group item - * @param ruleItem - * @param props - * @returns - */ - private getRuleGroupRuleConfig( - ruleItem: NfwRuleGroupConfig, - props: AcceleratorStackProps, - ): NfwRuleGroupRuleConfig | undefined { - return ruleItem.ruleGroup?.rulesSource.rulesFile - ? { - rulesSource: { - rulesString: this.getSuricataRules( - ruleItem.ruleGroup?.rulesSource.rulesFile, - fs.readFileSync(path.join(props.configDirPath, ruleItem.ruleGroup?.rulesSource.rulesFile), 'utf8'), - ), - rulesSourceList: undefined, - statefulRules: undefined, - statelessRulesAndCustomActions: undefined, - rulesFile: undefined, - }, - ruleVariables: ruleItem.ruleGroup?.ruleVariables, - statefulRuleOptions: ruleItem.ruleGroup.statefulRuleOptions, - } - : ruleItem.ruleGroup; - } - - /** - * Function to read suricata rule file and get rule definition - * @param fileName - * @param fileContent - * @returns - */ - private getSuricataRules(fileName: string, fileContent: string): string { - const rules: string[] = []; - // Suricata supported action type list - // @link https://suricata.readthedocs.io/en/suricata-6.0.2/rules/intro.html#action - const suricataRuleActionType = ['alert', 'pass', 'drop', 'reject', 'rejectsrc', 'rejectdst', 'rejectboth']; - fileContent.split(/\r?\n/).forEach(line => { - const ruleAction = line.split(' ')[0]; - if (suricataRuleActionType.includes(ruleAction)) { - rules.push(line); - } - }); - - if (rules.length > 0) { - return rules.join('\n'); - } else { - this.stack.addLogs(LogLevel.ERROR, `No rule definition found in suricata rules file ${fileName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Create AWS Network Firewall policy - * @param accountId - * @param policyItem - */ - private createNfwPolicies( - accountId: string, - ruleGroupMap: Map, - centralConfig: CentralNetworkServicesConfig, - ): Map { - const policyMap = new Map(); - - for (const policyItem of centralConfig.networkFirewall?.policies ?? []) { - const regions = policyItem.regions.map(item => { - return item.toString(); - }); - - // Create regional rule groups in the delegated admin account - if (this.stack.isTargetStack([accountId], regions)) { - // Instantiate firewall policy construct - this.stack.addLogs(LogLevel.INFO, `Create network firewall policy ${policyItem.name}`); - let policy; - if (this.stack.isManagedByAsea(AseaResourceType.NFW_POLICY, policyItem.name)) { - const policyArn = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.NFW_POLICY, [policyItem.name]), - ); - policy = NetworkFirewallPolicy.fromAttributes( - this.stack, - pascalCase(`${policyItem.name}NetworkFirewallPolicy`), - { - policyArn, - policyName: policyItem.name, - }, - ); - } else { - // Create new firewall policy object with rule group references - const firewallPolicy: FirewallPolicyProperty = { - statelessDefaultActions: policyItem.firewallPolicy.statelessDefaultActions, - statelessFragmentDefaultActions: policyItem.firewallPolicy.statelessFragmentDefaultActions, - statefulDefaultActions: policyItem.firewallPolicy.statefulDefaultActions, - statefulEngineOptions: policyItem.firewallPolicy.statefulEngineOptions, - statefulRuleGroupReferences: policyItem.firewallPolicy.statefulRuleGroups - ? this.getStatefulRuleGroupReferences(policyItem.firewallPolicy.statefulRuleGroups, ruleGroupMap) - : [], - statelessCustomActions: policyItem.firewallPolicy.statelessCustomActions, - statelessRuleGroupReferences: policyItem.firewallPolicy.statelessRuleGroups - ? this.getStatelessRuleGroupReferences(policyItem.firewallPolicy.statelessRuleGroups, ruleGroupMap) - : [], - }; - policy = new NetworkFirewallPolicy(this.stack, pascalCase(`${policyItem.name}NetworkFirewallPolicy`), { - name: policyItem.name, - firewallPolicy, - description: policyItem.description, - tags: policyItem.tags ?? [], - }); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${policyItem.name}NetworkFirewallPolicy`), - parameterName: this.stack.getSsmPath(SsmResourceType.NFW_POLICY, [policyItem.name]), - stringValue: policy.policyArn, - }); - - if (policyItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share Network Firewall policy ${policyItem.name}`); - this.stack.addResourceShare(policyItem, `${policyItem.name}_NetworkFirewallPolicyShare`, [ - policy.policyArn, - ]); - } - } - policyMap.set(policyItem.name, policy.policyArn); - } - } - return policyMap; - } - - /** - * Return stateful rule group references - * @param ruleGroupReferences - * @param ruleGroupMap - * @returns - */ - private getStatefulRuleGroupReferences( - ruleGroupReferences: NfwStatefulRuleGroupReferenceConfig[], - ruleGroupMap: Map, - ): { resourceArn: string; priority?: number }[] { - const references: { resourceArn: string; priority?: number }[] = []; - - for (const reference of ruleGroupReferences) { - if (!ruleGroupMap.get(reference.name)) { - this.stack.addLogs(LogLevel.ERROR, `Stateful rule group ${reference.name} not found in rule map`); - throw new Error(`Configuration validation failed at runtime.`); - } - references.push({ resourceArn: ruleGroupMap.get(reference.name)!, priority: reference.priority }); - } - return references; - } - - /** - * Return stateless rule group references - * @param ruleGroupReferences - * @param ruleGroupMap - * @returns - */ - private getStatelessRuleGroupReferences( - ruleGroupReferences: NfwStatelessRuleGroupReferenceConfig[], - ruleGroupMap: Map, - ): { priority: number; resourceArn: string }[] { - const references: { priority: number; resourceArn: string }[] = []; - - for (const reference of ruleGroupReferences) { - if (!ruleGroupMap.get(reference.name)) { - this.stack.addLogs(LogLevel.ERROR, `Stateless rule group ${reference.name} not found in rule map`); - throw new Error(`Configuration validation failed at runtime.`); - } - references.push({ priority: reference.priority, resourceArn: ruleGroupMap.get(reference.name)! }); - } - return references; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/resolver-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/resolver-resources.ts deleted file mode 100644 index 41221d3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/resolver-resources.ts +++ /dev/null @@ -1,346 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AseaResourceType, - CentralNetworkServicesConfig, - DnsFirewallRuleGroupConfig, - DnsQueryLogsConfig, -} from '@aws-accelerator/config'; -import { - QueryLoggingConfig, - ResolverFirewallDomainList, - ResolverFirewallDomainListType, - ResolverFirewallRuleGroup, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import path from 'path'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class ResolverResources { - public readonly domainListMap: Map; - public readonly queryLogsMap: Map; - public readonly ruleGroupMap: Map; - - private stack: NetworkPrepStack; - - constructor( - networkPrepStack: NetworkPrepStack, - delegatedAdminAccountId: string, - centralConfig: CentralNetworkServicesConfig, - props: AcceleratorStackProps, - orgId?: string, - ) { - this.stack = networkPrepStack; - - // Create DNS firewall rule groups - [this.domainListMap, this.ruleGroupMap] = this.createDnsFirewallRuleGroups( - delegatedAdminAccountId, - centralConfig, - props, - ); - // Create Resolver query logs - this.queryLogsMap = this.createResolverQueryLogs( - delegatedAdminAccountId, - props, - centralConfig.route53Resolver?.queryLogs, - orgId, - ); - } - - /** - * Create DNS firewall rule groups - * @param accountId - * @param firewallItem - */ - private createDnsFirewallRuleGroups( - accountId: string, - centralConfig: CentralNetworkServicesConfig, - props: AcceleratorStackProps, - ): Map[] { - const domainMap = new Map(); - const ruleGroupMap = new Map(); - - for (const firewallItem of centralConfig.route53Resolver?.firewallRuleGroups ?? []) { - const regions = firewallItem.regions.map(item => { - return item.toString(); - }); - - // Create regional rule groups in the delegated admin account - if (this.stack.isTargetStack([accountId], regions)) { - // Create domain lists for the rule group - const ruleItemDomainMap = this.createDomainLists(firewallItem, domainMap, props); - ruleItemDomainMap.forEach((value, key) => domainMap.set(key, value)); - - // Build new rule list with domain list ID - const ruleList = this.setRuleList(firewallItem, domainMap); - - // Create rule group - this.stack.addLogs(LogLevel.INFO, `Creating DNS firewall rule group ${firewallItem.name}`); - const ruleGroup = new ResolverFirewallRuleGroup(this.stack, pascalCase(`${firewallItem.name}RuleGroup`), { - firewallRules: ruleList, - name: firewallItem.name, - tags: firewallItem.tags ?? [], - }); - ruleGroupMap.set(firewallItem.name, ruleGroup.groupId); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${firewallItem.name}RuleGroup`), - parameterName: this.stack.getSsmPath(SsmResourceType.DNS_RULE_GROUP, [firewallItem.name]), - stringValue: ruleGroup.groupId, - }); - - if (firewallItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share DNS firewall rule group ${firewallItem.name}`); - this.stack.addResourceShare(firewallItem, `${firewallItem.name}_ResolverFirewallRuleGroupShare`, [ - ruleGroup.groupArn, - ]); - } - } - } - return [domainMap, ruleGroupMap]; - } - - /** - * Create/look up domain lists as needed for a rule group - * @param firewallItem - * @param domainMap - * @param props - * @returns - */ - private createDomainLists( - firewallItem: DnsFirewallRuleGroupConfig, - domainMap: Map, - props: AcceleratorStackProps, - ): Map { - for (const ruleItem of firewallItem.rules) { - let domainListType: ResolverFirewallDomainListType; - let filePath: string | undefined = undefined; - let listName: string; - let message: string; - // Check to ensure both types aren't defined - if (ruleItem.customDomainList) { - domainListType = ResolverFirewallDomainListType.CUSTOM; - filePath = path.join(props.configDirPath, ruleItem.customDomainList); - try { - listName = ruleItem.customDomainList.split('/')[1].split('.')[0]; - message = `Creating DNS firewall custom domain list ${listName}`; - } catch (e) { - this.stack.addLogs(LogLevel.ERROR, `Error creating DNS firewall domain list: ${e}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } else { - domainListType = ResolverFirewallDomainListType.MANAGED; - listName = ruleItem.managedDomainList!; - message = `Looking up DNS firewall managed domain list ${listName}`; - } - - // Create or look up domain list - if (!domainMap.has(listName)) { - this.stack.addLogs(LogLevel.INFO, message); - const domainList = new ResolverFirewallDomainList(this.stack, pascalCase(`${listName}DomainList`), { - name: listName, - path: filePath, - tags: [], - type: domainListType, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - }); - domainMap.set(listName, domainList.listId); - } - } - return domainMap; - } - - /** - * Set the list of rules for a given rule group - * @param firewallItem - * @param domainMap - * @returns - */ - private setRuleList( - firewallItem: DnsFirewallRuleGroupConfig, - domainMap: Map, - ): cdk.aws_route53resolver.CfnFirewallRuleGroup.FirewallRuleProperty[] { - const ruleList: cdk.aws_route53resolver.CfnFirewallRuleGroup.FirewallRuleProperty[] = []; - let domainListName: string; - - for (const ruleItem of firewallItem.rules) { - // Check the type of domain list - if (ruleItem.customDomainList) { - try { - domainListName = ruleItem.customDomainList.split('/')[1].split('.')[0]; - } catch (e) { - this.stack.addLogs(LogLevel.ERROR, `Error parsing list name from ${ruleItem.customDomainList}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } else { - domainListName = ruleItem.managedDomainList!; - } - - // Create the DNS firewall rule list - if (!domainMap.get(domainListName)) { - this.stack.addLogs(LogLevel.ERROR, `Domain list ${domainListName} not found in domain map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - ruleList.push({ - action: ruleItem.action.toString(), - firewallDomainListId: domainMap.get(domainListName)!, - priority: ruleItem.priority, - blockOverrideDnsType: - ruleItem.action === 'BLOCK' && ruleItem.blockResponse === 'OVERRIDE' ? 'CNAME' : undefined, - blockOverrideDomain: ruleItem.blockOverrideDomain, - blockOverrideTtl: ruleItem.blockOverrideTtl, - blockResponse: ruleItem.blockResponse, - }); - } - return ruleList; - } - - /** - * Create Route 53 Resolver query logs - * @param delegatedAdminAccountId - * @param props - * @param logItem - */ - private createResolverQueryLogs( - delegatedAdminAccountId: string, - props: AcceleratorStackProps, - logItem?: DnsQueryLogsConfig, - orgId?: string, - ): Map { - const queryLogsMap = new Map(); - - const centralLogsBucket = cdk.aws_s3.Bucket.fromBucketName( - this.stack, - 'CentralLogsBucket', - `${ - this.stack.acceleratorResourceNames.bucketPrefixes.centralLogs - }-${props.accountsConfig.getLogArchiveAccountId()}-${props.centralizedLoggingRegion}`, - ); - - if (logItem && delegatedAdminAccountId === cdk.Stack.of(this.stack).account) { - if (logItem.destinations.includes('s3')) { - this.stack.addLogs(LogLevel.INFO, `Create DNS query log ${logItem.name}-s3 for central S3 destination`); - const s3QueryLogConfig = this.createQueryLogItem(logItem, centralLogsBucket, props.partition, orgId); - queryLogsMap.set(`${logItem.name}-s3`, s3QueryLogConfig.logId); - } - - if (logItem.destinations.includes('cloud-watch-logs')) { - this.stack.addLogs( - LogLevel.INFO, - `Create DNS query log ${logItem.name}-cwl for central CloudWatch logs destination`, - ); - - const logGroup = new cdk.aws_logs.LogGroup(this.stack, 'QueryLogsLogGroup', { - encryptionKey: this.stack.cloudwatchKey, - retention: this.stack.logRetention, - }); - - const cwlQueryLogConfig = this.createQueryLogItem(logItem, logGroup, props.partition, orgId); - queryLogsMap.set(`${logItem.name}-cwl`, cwlQueryLogConfig.logId); - } - } - for (const vpcItem of props.networkConfig.vpcs ?? []) { - const queryLogName = vpcItem.vpcRoute53Resolver?.queryLogs?.name; - if (vpcItem.vpcRoute53Resolver?.queryLogs) { - if (this.stack.isManagedByAsea(AseaResourceType.ROUTE_53_QUERY_LOGGING, queryLogName!)) { - this.stack.addLogs(LogLevel.INFO, `DNS Logging for VPC "${vpcItem.name}" is managed externally`); - break; - } - if (vpcItem.vpcRoute53Resolver.queryLogs.destinations.includes('s3')) { - this.stack.addLogs(LogLevel.INFO, `Create DNS query log ${queryLogName}-s3 for central S3 destination`); - const s3QueryLogConfig = this.createQueryLogItem( - vpcItem.vpcRoute53Resolver.queryLogs, - centralLogsBucket, - props.partition, - orgId, - ); - queryLogsMap.set(`${queryLogName}-s3`, s3QueryLogConfig.logId); - } - } - if (vpcItem.vpcRoute53Resolver?.queryLogs?.destinations.includes('cloud-watch-logs')) { - this.stack.addLogs( - LogLevel.INFO, - `Create DNS query log ${queryLogName}-cwl for central CloudWatch logs destination`, - ); - - const logGroup = new cdk.aws_logs.LogGroup(this.stack, pascalCase(`${queryLogName}QueryLogsLogGroup`), { - encryptionKey: this.stack.cloudwatchKey, - retention: this.stack.logRetention, - }); - - const cwlQueryLogConfig = this.createQueryLogItem( - vpcItem.vpcRoute53Resolver?.queryLogs, - logGroup, - props.partition, - orgId, - ); - queryLogsMap.set(`${queryLogName}-cwl`, cwlQueryLogConfig.logId); - } - } - return queryLogsMap; - } - - /** - * Create a resolver query logging config item - * @param logItem - * @param destination - * @param partition - * @param organizationId - * @returns - */ - private createQueryLogItem( - logItem: DnsQueryLogsConfig, - destination: cdk.aws_s3.IBucket | cdk.aws_logs.LogGroup, - partition: string, - organizationId?: string, - ): QueryLoggingConfig { - let logicalId: string; - let logName: string; - - if (destination instanceof cdk.aws_logs.LogGroup) { - logicalId = `${logItem.name}CwlQueryLogConfig`; - logName = `${logItem.name}-cwl`; - } else { - logicalId = `${logItem.name}S3QueryLogConfig`; - logName = `${logItem.name}-s3`; - } - - const queryLogsConfig = new QueryLoggingConfig(this.stack, pascalCase(`${logicalId}`), { - destination, - name: logName, - organizationId, - partition, - logRetentionInDays: this.stack.logRetention, - kmsKey: this.stack.cloudwatchKey, - }); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${logicalId}`), - parameterName: this.stack.getSsmPath(SsmResourceType.QUERY_LOGS, [`${logName}`]), - stringValue: queryLogsConfig.logId, - }); - - if (logItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share DNS query log config ${logName}`); - this.stack.addResourceShare(logItem, `${logName}_QueryLogConfigShare`, [queryLogsConfig.logArn]); - } - return queryLogsConfig; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/tgw-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/tgw-resources.ts deleted file mode 100644 index fb01ae5..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/tgw-resources.ts +++ /dev/null @@ -1,331 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AseaResourceType, CustomerGatewayConfig, TransitGatewayConfig } from '@aws-accelerator/config'; -import { - PutSsmParameter, - SsmParameterProps, - TransitGateway, - TransitGatewayRouteTable, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { getTgwConfig, getTgwRouteTableId, getTransitGatewayId } from '../utils/getter-utils'; -import { isIpv4 } from '../utils/validation-utils'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class TgwResources { - public readonly sharedParameterMap: Map; - public readonly tgwRouteTableMap: Map; - public readonly transitGatewayMap: Map; - public readonly ssmRole?: cdk.aws_iam.Role; - - private stack: NetworkPrepStack; - - constructor(networkPrepStack: NetworkPrepStack, props: AcceleratorStackProps) { - // Set private stack property - this.stack = networkPrepStack; - // Create transit gateways - [this.transitGatewayMap, this.tgwRouteTableMap] = this.createTransitGateways(this.stack.tgwsInScope); - // Create TGW peering role - this.ssmRole = this.createTransitGatewayPeeringRole(props); - // Put cross-account/cross-region parameters - this.sharedParameterMap = this.createSharedParameters( - this.stack.tgwsInScope, - this.transitGatewayMap, - this.tgwRouteTableMap, - props.networkConfig.customerGateways, - ); - } - - /** - * Create transit gateways - * @param props - */ - private createTransitGateways(transitGateways: TransitGatewayConfig[]): Map[] { - const transitGatewayMap = new Map(); - const tgwRouteTableMap = new Map(); - - for (const tgwItem of transitGateways) { - const tgw = this.createTransitGatewayItem(tgwItem); - transitGatewayMap.set(tgwItem.name, tgw.transitGatewayId); - const routeTables = this.createOrImportTgwRouteTables(tgwItem, tgw.transitGatewayId); - routeTables.forEach((routeTableId, routeTableName) => tgwRouteTableMap.set(routeTableName, routeTableId)); - } - return [transitGatewayMap, tgwRouteTableMap]; - } - - /** - * Create transit gateway - * @param tgwItem - */ - private createTransitGatewayItem(tgwItem: TransitGatewayConfig): TransitGateway { - this.stack.addLogs(LogLevel.INFO, `Add Transit Gateway ${tgwItem.name}`); - let tgw; - if (this.stack.isManagedByAsea(AseaResourceType.TRANSIT_GATEWAY, tgwItem.name)) { - const tgwId = this.stack.getExternalResourceParameter(this.stack.getSsmPath(SsmResourceType.TGW, [tgwItem.name])); - tgw = TransitGateway.fromTransitGatewayAttributes(this.stack, pascalCase(`${tgwItem.name}TransitGateway`), { - transitGatewayId: tgwId, - transitGatewayName: tgwItem.name, - }); - } else { - // Handle case where partition doesn't support TGW Cidr Blocks - let transitGatewayCidrBlocks: string[] | undefined = undefined; - if (tgwItem.transitGatewayCidrBlocks || tgwItem.transitGatewayIpv6CidrBlocks) { - transitGatewayCidrBlocks = [ - ...(tgwItem.transitGatewayCidrBlocks ?? []), - ...(tgwItem.transitGatewayIpv6CidrBlocks ?? []), - ]; - } else { - transitGatewayCidrBlocks = undefined; - } - // Create TGW - tgw = new TransitGateway(this.stack, pascalCase(`${tgwItem.name}TransitGateway`), { - name: tgwItem.name, - amazonSideAsn: tgwItem.asn, - autoAcceptSharedAttachments: tgwItem.autoAcceptSharingAttachments, - defaultRouteTableAssociation: tgwItem.defaultRouteTableAssociation, - defaultRouteTablePropagation: tgwItem.defaultRouteTablePropagation, - dnsSupport: tgwItem.dnsSupport, - vpnEcmpSupport: tgwItem.vpnEcmpSupport, - transitGatewayCidrBlocks: transitGatewayCidrBlocks, - tags: tgwItem.tags, - }); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${tgwItem.name}TransitGatewayId`), - parameterName: this.stack.getSsmPath(SsmResourceType.TGW, [tgwItem.name]), - stringValue: tgw.transitGatewayId, - }); - } - - if (tgwItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share transit gateway ${tgwItem.name}`); - this.stack.addResourceShare(tgwItem, `${tgwItem.name}_TransitGatewayShare`, [tgw.transitGatewayArn]); - } - return tgw; - } - - /** - * Create or import transit gateway route tables - * @param tgwItem TransitGatewayConfig - * @param transitGatewayId string - * @returns Map - */ - private createOrImportTgwRouteTables(tgwItem: TransitGatewayConfig, transitGatewayId: string): Map { - const routeTables = new Map(); - - for (const routeTableItem of tgwItem.routeTables ?? []) { - this.stack.addLogs(LogLevel.INFO, `Add Transit Gateway Route Table ${routeTableItem.name}`); - let routeTable; - if ( - this.stack.isManagedByAsea( - AseaResourceType.TRANSIT_GATEWAY_ROUTE_TABLE, - `${tgwItem.name}/${routeTableItem.name}`, - ) - ) { - const routeTableId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [tgwItem.name, routeTableItem.name]), - ); - routeTable = TransitGatewayRouteTable.fromRouteTableId( - this.stack, - pascalCase(`${routeTableItem.name}TransitGatewayRouteTable`), - routeTableId, - ); - } else { - routeTable = new TransitGatewayRouteTable( - this.stack, - pascalCase(`${routeTableItem.name}TransitGatewayRouteTable`), - { - transitGatewayId, - name: routeTableItem.name, - tags: routeTableItem.tags, - }, - ); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${tgwItem.name}${routeTableItem.name}TransitGatewayRouteTableId`), - parameterName: this.stack.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [tgwItem.name, routeTableItem.name]), - stringValue: routeTable.id, - }); - } - routeTables.set(`${tgwItem.name}_${routeTableItem.name}`, routeTable.id); - } - return routeTables; - } - - /** - * Function to create TGW peering role. This role is used to access acceptor TGW information. - * This role will be assumed by requestor to complete acceptance of peering request. - * This role is created only if account is used as accepter in TGW peering. - * This role gets created only in home region - * @returns - */ - private createTransitGatewayPeeringRole(props: AcceleratorStackProps): cdk.aws_iam.Role | undefined { - for (const transitGatewayPeeringItem of props.networkConfig.transitGatewayPeering ?? []) { - const accepterAccountId = props.accountsConfig.getAccountId(transitGatewayPeeringItem.accepter.account); - - if (this.stack.isTargetStack([accepterAccountId], [props.globalConfig.homeRegion])) { - const principals: cdk.aws_iam.PrincipalBase[] = []; - - const requestorAccounts = props.networkConfig.getTgwRequestorAccountNames( - transitGatewayPeeringItem.accepter.account, - ); - - requestorAccounts.forEach(item => { - principals.push(new cdk.aws_iam.AccountPrincipal(props.accountsConfig.getAccountId(item))); - }); - - const role = new cdk.aws_iam.Role(this.stack, 'TgwPeeringRole', { - roleName: this.stack.acceleratorResourceNames.roles.tgwPeering, - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [ - `arn:${cdk.Aws.PARTITION}:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter${props.prefixes.ssmParamName}/network/transitGateways/*`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ec2:DescribeTransitGatewayPeeringAttachments', - 'ec2:AcceptTransitGatewayPeeringAttachment', - 'ec2:AssociateTransitGatewayRouteTable', - 'ec2:DisassociateTransitGatewayRouteTable', - 'ec2:DescribeTransitGatewayAttachments', - 'ec2:CreateTags', - ], - resources: ['*'], - }), - ], - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath(this.stack, `${this.stack.stackName}/TgwPeeringRole/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'TgwPeeringRole needs access to create peering connections for TGWs in the account ', - }, - ]); - - return role; // So that same env (account & region) do not try to create duplicate role, if there is multiple tgw peering for same account - } - } - return undefined; - } - - /** - * Create cross-account/cross-region SSM parameters for site-to-site VPN connections - * that must reference the TGW/TGW route table in cross-account VPN scenarios - * @param transitGateways TransitGatewayConfig[] - * @param transitGatewayMap Map - * @param tgwRouteTableMap Map - * @param customerGateways CustomerGatewayConfig[] - * @returns Map - */ - private createSharedParameters( - transitGateways: TransitGatewayConfig[], - transitGatewayMap: Map, - tgwRouteTableMap: Map, - customerGateways?: CustomerGatewayConfig[], - ): Map { - const sharedParameterMap = new Map(); - const tgwNames = transitGateways.map(tgw => tgw.name); - const tgwVpnCustomerGateways = customerGateways - ? customerGateways.filter(cgw => cgw.vpnConnections?.filter(vpn => tgwNames.includes(vpn.transitGateway ?? ''))) - : []; - const crossAcctFirewallReferenceCgws = tgwVpnCustomerGateways.filter( - cgw => !isIpv4(cgw.ipAddress) && !this.stack.firewallVpcInScope(cgw), - ); - - for (const crossAcctCgw of crossAcctFirewallReferenceCgws) { - const firewallVpcConfig = this.stack.getFirewallVpcConfig(crossAcctCgw); - const accountIds = this.stack.getVpcAccountIds(firewallVpcConfig); - const parameters = this.setCrossAccountSsmParameters( - crossAcctCgw, - transitGateways, - transitGatewayMap, - tgwRouteTableMap, - ); - - if (parameters.length > 0) { - // Put SSM parameters - new PutSsmParameter(this.stack, pascalCase(`${crossAcctCgw.name}TgwVpnSharedParameters`), { - accountIds, - region: firewallVpcConfig.region, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - parameters, - invokingAccountId: this.stack.account, - acceleratorPrefix: this.stack.acceleratorPrefix, - }); - sharedParameterMap.set(crossAcctCgw.name, parameters); - } - } - return sharedParameterMap; - } - - /** - * Returns an array of SSM parameters for cross-account TGW VPN connections - * @param cgw CustomerGatewayConfig - * @param transitGateways TransitGatewayConfig[] - * @param transitGatewayMap Map - * @param tgwRouteTableMap Map - * @returns SsmParameterProps[] - */ - private setCrossAccountSsmParameters( - cgw: CustomerGatewayConfig, - transitGateways: TransitGatewayConfig[], - transitGatewayMap: Map, - tgwRouteTableMap: Map, - ) { - const ssmParameters: SsmParameterProps[] = []; - - for (const vpnItem of cgw.vpnConnections ?? []) { - if (vpnItem.transitGateway && transitGatewayMap.has(vpnItem.transitGateway)) { - // - // Set TGW ID - const tgwConfig = getTgwConfig(transitGateways, vpnItem.transitGateway); - ssmParameters.push({ - name: this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_TGW, [cgw.name, tgwConfig.name]), - value: getTransitGatewayId(transitGatewayMap, tgwConfig.name), - }); - // - // Set TGW Route Table IDs - for (const routeTableItem of tgwConfig.routeTables ?? []) { - ssmParameters.push({ - name: this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_TGW_ROUTE_TABLE, [ - cgw.name, - tgwConfig.name, - routeTableItem.name, - ]), - value: getTgwRouteTableId(tgwRouteTableMap, tgwConfig.name, routeTableItem.name), - }); - } - } - } - return [...new Set(ssmParameters)]; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/vpn-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/vpn-resources.ts deleted file mode 100644 index 013ef78..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-prep-stack/vpn-resources.ts +++ /dev/null @@ -1,421 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CustomerGatewayConfig, VpnConnectionConfig } from '@aws-accelerator/config'; -import { CustomerGateway, VpnConnection } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps, NagSuppressionRuleIds } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { getTransitGatewayId } from '../utils/getter-utils'; -import { isIpv4 } from '../utils/validation-utils'; -import { NetworkPrepStack } from './network-prep-stack'; - -export class VpnResources { - public readonly cgwMap: Map; - public readonly vpnMap: Map; - public readonly crossAccountCgwRole?: cdk.aws_iam.Role; - public readonly crossAccountLogsRole?: cdk.aws_iam.Role; - public readonly crossAccountTgwRoutesRole?: cdk.aws_iam.Role; - public readonly crossAccountVpnRole?: cdk.aws_iam.Role; - private stack: NetworkPrepStack; - private transitGatewayMap: Map; - - constructor( - networkPrepStack: NetworkPrepStack, - transitGatewayMap: Map, - props: AcceleratorStackProps, - ) { - // Set private properties - this.stack = networkPrepStack; - this.transitGatewayMap = transitGatewayMap; - - // Create CGWs and VPN connections - const customResourceHandler = this.stack.advancedVpnTypes.includes('tgw') - ? this.stack.createVpnOnEventHandler() - : undefined; - [this.cgwMap, this.vpnMap] = this.createVpnConnectionResources(props, customResourceHandler); - - // Create cross-account VPN roles, if needed - const [hasCrossAcctVpn, hasCrossAcctTgwVpn] = this.hasCrossAccountVpn(props); - if (hasCrossAcctVpn) { - this.crossAccountCgwRole = this.createCrossAccountCgwRole(); - this.crossAccountLogsRole = this.createCrossAccountLogsRole(); - this.crossAccountVpnRole = this.createCrossAccountVpnRole(); - } - - // Create cross-account TGW VPN role, if needed - if (hasCrossAcctTgwVpn) { - this.crossAccountTgwRoutesRole = this.createCrossAccountTgwRoutesRole(); - } - } - - /** - * Create VPN connection resources - * @param props - */ - private createVpnConnectionResources( - props: AcceleratorStackProps, - customResourceHandler?: cdk.aws_lambda.IFunction, - ): Map[] { - const cgwMap = new Map(); - const vpnMap = new Map(); - // - // Generate Customer Gateways - // - for (const cgwItem of props.networkConfig.customerGateways ?? []) { - const accountId = props.accountsConfig.getAccountId(cgwItem.account); - if (this.stack.isTargetStack([accountId], [cgwItem.region]) && isIpv4(cgwItem.ipAddress)) { - this.stack.addLogs(LogLevel.INFO, `Add Customer Gateway ${cgwItem.name} in ${cgwItem.region}`); - const cgw = new CustomerGateway(this.stack, pascalCase(`${cgwItem.name}CustomerGateway`), { - name: cgwItem.name, - bgpAsn: cgwItem.asn, - ipAddress: cgwItem.ipAddress, - tags: cgwItem.tags, - }); - cgwMap.set(cgwItem.name, cgw.customerGatewayId); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${cgwItem.name}CustomerGateway`), - parameterName: this.stack.getSsmPath(SsmResourceType.CGW, [cgwItem.name]), - stringValue: cgw.customerGatewayId, - }); - - for (const vpnItem of cgwItem.vpnConnections ?? []) { - // Make sure that VPN Connections are created for TGWs in this stack only. - if (vpnItem.transitGateway) { - const vpn = this.createVpnConnection(cgw, cgwItem, vpnItem, customResourceHandler); - vpnMap.set(vpnItem.name, vpn.vpnConnectionId); - } - } - } - } - return [cgwMap, vpnMap]; - } - - /** - * Create VPN connection item - * @param cgw CustomerGateway - * @param cgwItem CustomerGatewayConfig - * @param vpnConnectItem VpnConnectionConfig - * @param customResourceHandler cdk.aws_lambda.IFunction | undefined - */ - private createVpnConnection( - cgw: CustomerGateway, - cgwItem: CustomerGatewayConfig, - vpnItem: VpnConnectionConfig, - customResourceHandler?: cdk.aws_lambda.IFunction, - ): VpnConnection { - // Get the Transit Gateway ID - const transitGatewayId = getTransitGatewayId(this.transitGatewayMap, vpnItem.transitGateway!); - - this.stack.addLogs( - LogLevel.INFO, - `Attaching Customer Gateway ${cgwItem.name} to ${vpnItem.transitGateway} in ${cgwItem.region}`, - ); - const vpnConnection = new VpnConnection( - this.stack, - pascalCase(`${vpnItem.name}VpnConnection`), - this.stack.setVpnProps({ - vpnItem, - customerGatewayId: cgw.customerGatewayId, - customResourceHandler, - transitGatewayId, - }), - ); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${vpnItem.name}VpnConnection`), - parameterName: this.stack.getSsmPath(SsmResourceType.TGW_VPN, [vpnItem.name]), - stringValue: vpnConnection.vpnConnectionId, - }); - - return vpnConnection; - } - - /** - * Returns true in the home region if there are CGWs referencing a firewall instance deployed to a different account - * than the CGW's target account - * @param props AcceleratorStackProps - * @returns boolean[] - */ - private hasCrossAccountVpn(props: AcceleratorStackProps): boolean[] { - let [hasCrossAcctVpn, hasCrossAcctTgwVpn] = [false, false]; - - for (const cgw of props.networkConfig.customerGateways ?? []) { - const cgwAccountId = props.accountsConfig.getAccountId(cgw.account); - if ( - this.stack.isTargetStack([cgwAccountId], [props.globalConfig.homeRegion]) && - !isIpv4(cgw.ipAddress) && - !this.firewallVpcInAccount(cgw, props) - ) { - hasCrossAcctVpn = true; - // - // Check if the CGW has VPNs to a transit gateway - if (cgw.vpnConnections?.find(vpn => vpn.transitGateway)) { - hasCrossAcctTgwVpn = true; - } - } - } - return [hasCrossAcctVpn, hasCrossAcctTgwVpn]; - } - - /** - * Returns a boolean indicating if the VPC a given firewall is deployed to - * is in the same account as the customer gateway - * @param customerGateway CustomerGatewayConfig - * @returns boolean - */ - private firewallVpcInAccount(customerGateway: CustomerGatewayConfig, props: AcceleratorStackProps): boolean { - const cgwAccountId = props.accountsConfig.getAccountId(customerGateway.account); - const firewallVpcConfig = this.stack.getFirewallVpcConfig(customerGateway); - const vpcAccountIds = this.stack.getVpcAccountIds(firewallVpcConfig); - - return vpcAccountIds.includes(cgwAccountId); - } - - /** - * Create cross-account CGW role to allow CGW CRUD operations - * @returns cdk.aws_iam.Role - */ - private createCrossAccountCgwRole(): cdk.aws_iam.Role { - const managedCgwPolicy = new cdk.aws_iam.ManagedPolicy(this.stack, 'CrossAccountCgwPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'CGWCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateCustomerGateway', 'ec2:CreateTags', 'ec2:DeleteCustomerGateway', 'ec2:DeleteTags'], - resources: ['*'], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedCgwPolicy.node.path, - reason: 'Cross account policy allows access for CGW CRUD operations', - }, - ], - }); - - const crossAccountCgwRole = new cdk.aws_iam.Role(this.stack, 'CrossAccountCgwRole', { - assumedBy: this.stack.getOrgPrincipals(this.stack.organizationId, true), - managedPolicies: [managedCgwPolicy], - roleName: this.stack.acceleratorResourceNames.roles.crossAccountCustomerGatewayRoleName, - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: crossAccountCgwRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - return crossAccountCgwRole; - } - - /** - * Create cross-account CloudWatch Logs role - * @returns cdk.aws_iam.Role - */ - private createCrossAccountLogsRole(): cdk.aws_iam.Role { - const managedLogsPolicy = new cdk.aws_iam.ManagedPolicy(this.stack, 'CrossAccountLogsPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'LogsList', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:DescribeKey', 'kms:ListKeys', 'logs:DescribeLogGroups'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'LogsCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:AssociateKmsKey', 'logs:CreateLogGroup', 'logs:DeleteLogGroup', 'logs:PutRetentionPolicy'], - resources: [ - `arn:${this.stack.partition}:logs:*:${this.stack.account}:log-group:${this.stack.acceleratorPrefix}*`, - ], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedLogsPolicy.node.path, - reason: 'Cross account policy allows access for Logs CRUD operations', - }, - ], - }); - - const crossAccountLogsRole = new cdk.aws_iam.Role(this.stack, 'CrossAccountLogsRole', { - assumedBy: this.stack.getOrgPrincipals(this.stack.organizationId, true), - managedPolicies: [managedLogsPolicy], - roleName: this.stack.acceleratorResourceNames.roles.crossAccountLogsRoleName, - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: crossAccountLogsRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - return crossAccountLogsRole; - } - - /** - * Create cross-account VPN role - * @returns cdk.aws_iam.Role - */ - private createCrossAccountVpnRole(): cdk.aws_iam.Role { - const managedVpnPolicy = new cdk.aws_iam.ManagedPolicy(this.stack, 'CrossAccountVpnPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'S2SVPNCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ec2:CreateTags', - 'ec2:CreateVpnConnection', - 'ec2:DeleteTags', - 'ec2:DeleteVpnConnection', - 'ec2:DescribeVpnConnections', - 'ec2:ModifyVpnConnectionOptions', - 'ec2:ModifyVpnTunnelOptions', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'LogDeliveryCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'logs:CreateLogDelivery', - 'logs:GetLogDelivery', - 'logs:UpdateLogDelivery', - 'logs:DeleteLogDelivery', - 'logs:ListLogDeliveries', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S2SVPNLoggingCWL', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:PutResourcePolicy', 'logs:DescribeResourcePolicies', 'logs:DescribeLogGroups'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'SecretsManagerReadOnly', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['secretsmanager:GetSecretValue', 'kms:Decrypt'], - resources: ['*'], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedVpnPolicy.node.path, - reason: 'Cross account policy allows access for VPN CRUD operations', - }, - ], - }); - const crossAccountVpnRole = new cdk.aws_iam.Role(this.stack, 'CrossAccountVpnRole', { - assumedBy: this.stack.getOrgPrincipals(this.stack.organizationId, true), - managedPolicies: [managedVpnPolicy], - roleName: this.stack.acceleratorResourceNames.roles.crossAccountVpnRoleName, - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: crossAccountVpnRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - return crossAccountVpnRole; - } - - /** - * Create cross-account TGW routes role - * @returns cdk.aws_iam.Role - */ - private createCrossAccountTgwRoutesRole(): cdk.aws_iam.Role { - const managedTgwRoutesPolicy = new cdk.aws_iam.ManagedPolicy(this.stack, 'CrossAccountTgwRoutesPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'TGWRouteCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ec2:AssociateTransitGatewayRouteTable', - 'ec2:CreateTransitGatewayRoute', - 'ec2:CreateTransitGatewayPrefixListReference', - 'ec2:DeleteTransitGatewayPrefixListReference', - 'ec2:DeleteTransitGatewayRoute', - 'ec2:DescribeTransitGatewayAttachments', - 'ec2:DescribeVpnConnections', - 'ec2:DisableTransitGatewayRouteTablePropagation', - 'ec2:DisassociateTransitGatewayRouteTable', - 'ec2:EnableTransitGatewayRouteTablePropagation', - 'ec2:ModifyTransitGatewayPrefixListReference', - ], - resources: ['*'], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedTgwRoutesPolicy.node.path, - reason: 'Cross account policy allows access for TGW route CRUD operations', - }, - ], - }); - const crossAccountTgwRoutesRole = new cdk.aws_iam.Role(this.stack, 'CrossAccountTgwRoutesRole', { - assumedBy: this.stack.getOrgPrincipals(this.stack.organizationId, true), - managedPolicies: [managedTgwRoutesPolicy], - roleName: this.stack.acceleratorResourceNames.roles.crossAccountTgwRouteRoleName, - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.stack.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: crossAccountTgwRoutesRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - return crossAccountTgwRoutesRole; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-stack.ts deleted file mode 100644 index 5e9f7ca..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-stack.ts +++ /dev/null @@ -1,1325 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AseaResourceType, - CustomerGatewayConfig, - DnsFirewallRuleGroupConfig, - DnsQueryLogsConfig, - IpamAllocationConfig, - IpamPoolConfig, - NetworkAclSubnetSelection, - NetworkConfig, - NfwFirewallConfig, - NfwFirewallPolicyConfig, - NfwRuleGroupConfig, - PrefixListConfig, - ResolverRuleConfig, - RouteTableEntryConfig, - SecurityGroupConfig, - SubnetConfig, - TransitGatewayAttachmentConfig, - TransitGatewayConfig, - VpcConfig, - VpcTemplatesConfig, - VpnConnectionConfig, -} from '@aws-accelerator/config'; -import { - CloudWatchLogGroups, - IIpamSubnet, - IResourceShareItem, - IpamSubnet, - LzaLambda, - PrefixList, - ResourceShare, - ResourceShareItem, - ResourceShareOwner, - SecurityGroup, - SecurityGroupEgressRuleProps, - SecurityGroupIngressRuleProps, - SsmParameterLookup, - Subnet, - Vpc, - VpnConnectionProps, - VpnTunnelOptionsSpecifications, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import { pascalCase } from 'pascal-case'; -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionDetailType, - NagSuppressionRuleIds, -} from '../accelerator-stack'; -import { getFirewallInstanceConfig, getSecurityGroup, getVpc, getVpcConfig } from './utils/getter-utils'; -import { - containsAllIngressRule, - processSecurityGroupEgressRules, - processSecurityGroupIngressRules, - processSecurityGroupSgEgressSources, - processSecurityGroupSgIngressSources, -} from './utils/security-group-utils'; -import { hasAdvancedVpnOptions, isIpv4 } from './utils/validation-utils'; - -/** - * Resource share type for RAM resource shares - */ -type ResourceShareType = - | DnsFirewallRuleGroupConfig - | DnsQueryLogsConfig - | IpamPoolConfig - | NfwRuleGroupConfig - | NfwFirewallPolicyConfig - | SubnetConfig - | ResolverRuleConfig - | TransitGatewayConfig; - -/** - * Enum for log level - */ -export enum LogLevel { - INFO = 'info', - WARN = 'warn', - ERROR = 'error', -} - -/** - * Abstract class definition and methods for network stacks - */ -export abstract class NetworkStack extends AcceleratorStack { - /** - * The accelerator prefix value - */ - public readonly acceleratorPrefix: string; - /** - * Cloudwatch KMS key - */ - public readonly cloudwatchKey: cdk.aws_kms.IKey | undefined; - /** - * Flag to determine if there is an advanced VPN in scope of the current stack context - */ - public readonly containsAdvancedVpn: boolean; - /** - * Advanced VPN types that exist in the current stack context - */ - public readonly advancedVpnTypes: string[] = []; - /** - * KMS Key used to encrypt custom resource lambda environment variables, when undefined default AWS managed key will be used - */ - public readonly lambdaKey: cdk.aws_kms.IKey | undefined; - /** - * Global CloudWatch logs retention setting - */ - public readonly logRetention: number; - /** - * Accelerator network configuration - */ - public readonly networkConfig: NetworkConfig; - /** - * VPCs with subnets shared via Resource Access Manager (RAM) in scope of the current stack context - */ - public readonly sharedVpcs: (VpcConfig | VpcTemplatesConfig)[]; - /** - * Transit gateways in scope of the current stack context - */ - public readonly tgwsInScope: TransitGatewayConfig[]; - /** - * VPCs and VPC templates in scope of the current stack context - */ - public readonly vpcsInScope: (VpcConfig | VpcTemplatesConfig)[]; - /** - * All VPC and VPC template resources in the network configuration - */ - public readonly vpcResources: (VpcConfig | VpcTemplatesConfig)[]; - - protected constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - // Set properties - this.acceleratorPrefix = props.prefixes.accelerator; - this.containsAdvancedVpn = this.setAdvancedVpnFlag(props); - this.logRetention = props.globalConfig.cloudwatchLogRetentionInDays; - this.networkConfig = props.networkConfig; - this.vpcResources = [...props.networkConfig.vpcs, ...(props.networkConfig.vpcTemplates ?? [])]; - this.sharedVpcs = this.getSharedVpcs(this.vpcResources); - this.vpcsInScope = this.getVpcsInScope(this.vpcResources); - this.tgwsInScope = this.getTgwsInScope(props.networkConfig.transitGateways); - - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - } - - /** - * - * - * Network Stack helper methods - * - */ - - /** - * Get VPCs with shared subnets in scope of the current stack context - * @param vpcResources - * @returns - */ - private getSharedVpcs(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): (VpcConfig | VpcTemplatesConfig)[] { - const sharedVpcs: (VpcConfig | VpcTemplatesConfig)[] = []; - - for (const vpcItem of vpcResources) { - const accountIds: string[] = []; - const sharedSubnets = vpcItem.subnets ? vpcItem.subnets.filter(subnet => subnet.shareTargets) : []; - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - - for (const subnetItem of sharedSubnets) { - const subnetAccountIds = this.getAccountIdsFromShareTarget(subnetItem.shareTargets!); - subnetAccountIds.forEach(accountId => { - if (!accountIds.includes(accountId) && !vpcAccountIds.includes(accountId)) { - accountIds.push(accountId); - } - }); - } - // Add VPC to array if it has shared subnets in scope of the current stack - if (this.isTargetStack(accountIds, [vpcItem.region])) { - sharedVpcs.push(vpcItem); - } - } - return sharedVpcs; - } - /** - * Get VPCs in current scope of the stack context - * @param vpcResources - * @returns - */ - private getVpcsInScope(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): (VpcConfig | VpcTemplatesConfig)[] { - const vpcsInScope: (VpcConfig | VpcTemplatesConfig)[] = []; - - for (const vpcItem of vpcResources) { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - vpcsInScope.push(vpcItem); - } - } - return vpcsInScope; - } - - /** - * Determines if any of the VPN connections require advanced configuration options. - * @param props AcceleratorStackProps - * @returns boolean - */ - private setAdvancedVpnFlag(props: AcceleratorStackProps): boolean { - for (const cgw of props.networkConfig.customerGateways ?? []) { - const cgwAccount = props.accountsConfig.getAccountId(cgw.account); - for (const vpnItem of cgw.vpnConnections ?? []) { - if (this.isTargetStack([cgwAccount], [cgw.region]) && hasAdvancedVpnOptions(vpnItem) && isIpv4(cgw.ipAddress)) { - this.setAdvancedVpnType(vpnItem); - return true; - } - } - } - return false; - } - - /** - * Sets the advanced VPN type that is included in this stack context. - * Used to determine when to create a VPN custom resource handlers. - * @param vpnItem VpnConnectionConfig - */ - private setAdvancedVpnType(vpnItem: VpnConnectionConfig) { - if (vpnItem.vpc && !this.advancedVpnTypes.includes('vpc')) { - this.advancedVpnTypes.push('vpc'); - } else if (vpnItem.transitGateway && !this.advancedVpnTypes.includes('tgw')) { - this.advancedVpnTypes.push('tgw'); - } - } - - /** - * Gets TGWs in scope of the current stack context - * @param transitGateways TransitGatewaysConfig[] - * @returns TransitGatewayConfig[] - */ - private getTgwsInScope(transitGateways: TransitGatewayConfig[]): TransitGatewayConfig[] { - const tgwsInScope: TransitGatewayConfig[] = []; - - for (const tgw of transitGateways) { - const tgwAccount = this.props.accountsConfig.getAccountId(tgw.account); - if (this.isTargetStack([tgwAccount], [tgw.region])) { - tgwsInScope.push(tgw); - } - } - return tgwsInScope; - } - - /** - * Returns true if provided account ID and region parameters match contextual values for the current stack - * @param accountIds - * @param regions - * @returns - */ - public isTargetStack(accountIds: string[], regions: string[]): boolean { - return accountIds.includes(cdk.Stack.of(this).account) && regions.includes(cdk.Stack.of(this).region); - } - - /** - * Public Get account and region deployment targets for prefix lists - * @param prefixListItem - */ - public getPrefixListTargets(prefixListItem: PrefixListConfig): { accountIds: string[]; regions: string[] } { - // Check if the set belongs in this account/region - if (prefixListItem.accounts && prefixListItem.deploymentTargets) { - this.logger.error( - `prefix list ${prefixListItem.name} has both accounts and deploymentTargets defined. Please use deploymentTargets only.`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - const accountIds = []; - const regions = []; - if (prefixListItem.accounts && prefixListItem.regions) { - // Check if the set belongs in this account/region - accountIds.push( - ...prefixListItem.accounts.map(item => { - return this.props.accountsConfig.getAccountId(item); - }), - ); - regions.push( - ...prefixListItem.regions.map(item => { - return item.toString(); - }), - ); - } - if (prefixListItem.deploymentTargets) { - accountIds.push(...this.getAccountIdsFromDeploymentTargets(prefixListItem.deploymentTargets)); - regions.push(...this.getRegionsFromDeploymentTarget(prefixListItem.deploymentTargets)); - } - if (accountIds.length === 0) { - throw new Error(`No account targets specified for prefix list ${prefixListItem.name}`); - } - if (regions.length === 0) { - throw new Error(`No region targets specified for prefix list ${prefixListItem.name}`); - } - - return { accountIds, regions }; - } - /** - * Public accessor method to add logs to logger - * @param logLevel - * @param message - */ - public addLogs(logLevel: LogLevel, message: string) { - switch (logLevel) { - case 'info': - this.logger.info(message); - break; - - case 'warn': - this.logger.warn(message); - break; - - case 'error': - this.logger.error(message); - break; - } - } - - /** - * Public mutator method to add cdk-nag suppressions - * @param nagSuppression NagSuppressionDetailType - */ - public addNagSuppression(nagSuppression: NagSuppressionDetailType) { - this.nagSuppressionInputs.push(nagSuppression); - } - - /** - * Returns a map of VPC IDs for the target stack - * @param vpcResources - * @returns - */ - protected setVpcMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const vpcMap = new Map(); - - for (const vpcItem of vpcResources) { - const vpcId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - ); - vpcMap.set(vpcItem.name, vpcId); - } - return vpcMap; - } - - /** - * Returns a map of subnet IDs for the target stack - * @param vpcResources - * @returns - */ - protected setSubnetMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const subnetMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const subnetItem of vpcItem.subnets ?? []) { - const subnetId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - ); - subnetMap.set(`${vpcItem.name}_${subnetItem.name}`, subnetId); - } - } - return subnetMap; - } - - /** - * Returns a map of route table IDs for the target stack - * @param vpcResources - * @returns - */ - protected setRouteTableMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const routeTableMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const routeTableItem of vpcItem.routeTables ?? []) { - const routeTableId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ROUTE_TABLE, [vpcItem.name, routeTableItem.name]), - ); - routeTableMap.set(`${vpcItem.name}_${routeTableItem.name}`, routeTableId); - } - } - return routeTableMap; - } - - /** - * Update security group endpoint map for VPC Endpoint security groups - * @param values - */ - protected setEndpointSecurityGroupMap(values: (VpcConfig | VpcTemplatesConfig)[]): Map { - const securityGroupEndpointMap = new Map(); - - for (const vpcItem of values ?? []) { - for (const endpointItem of vpcItem.interfaceEndpoints?.endpoints ?? []) { - if (endpointItem.securityGroup) { - if (!securityGroupEndpointMap.has(`${vpcItem.name}_${endpointItem.securityGroup}`)) { - const endpointSecurityGroupId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, endpointItem.securityGroup]), - ); - securityGroupEndpointMap.set( - `${vpcItem.name}_${endpointItem.securityGroup}`, - SecurityGroup.fromSecurityGroupId(this, endpointSecurityGroupId), - ); - } - } - } - } - return securityGroupEndpointMap; - } - - /** - * Returns a map of security group IDs for the target stack - * @param vpcResources - * @returns - */ - protected setSecurityGroupMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const securityGroupMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - const securityGroupId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, securityGroupItem.name]), - ); - securityGroupMap.set(`${vpcItem.name}_${securityGroupItem.name}`, securityGroupId); - } - } - return securityGroupMap; - } - - /** - * Returns a map of transit gateway resources being targeted based on the - * attachments for VPCs in a given stack - * @param vpcResources - * @returns - */ - public setVpcTransitGatewayMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const transitGatewayMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const attachment of vpcItem.transitGatewayAttachments ?? []) { - // If the map does not have the TGW ID, set it - if (!transitGatewayMap.has(attachment.transitGateway.name)) { - transitGatewayMap.set(attachment.transitGateway.name, this.getTransitGatewayItem(attachment)); - } - } - } - return transitGatewayMap; - } - - /** - * Returns the transit gateway ID for a given VPC attachment - * @param attachment - */ - protected getTransitGatewayItem(attachment: TransitGatewayAttachmentConfig): string { - const owningAccountId = this.props.accountsConfig.getAccountId(attachment.transitGateway.account); - let tgwId: string; - // If owning account is this account, transit gateway id can be - // retrieved from ssm parameter store - if (owningAccountId === cdk.Stack.of(this).account) { - tgwId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.TGW, [attachment.transitGateway.name]), - ); - } - // Else, need to get the transit gateway from the resource shares - else { - // Get the resource share related to the transit gateway - tgwId = this.getResourceShare( - `${attachment.transitGateway.name}_TransitGatewayShare`, - 'ec2:TransitGateway', - owningAccountId, - this.cloudwatchKey, - ).resourceShareItemId; - } - return tgwId; - } - - /** - * Returns maps of DNS zone details if central interface endpoint VPC is enabled in the target stack - * @param vpcResources - * @returns - */ - protected setInterfaceEndpointDnsMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map[] { - const endpointMap = new Map(); - const zoneMap = new Map(); - - for (const vpcItem of vpcResources) { - if (vpcItem.interfaceEndpoints?.central) { - // Set interface endpoint DNS names - for (const endpointItem of vpcItem.interfaceEndpoints?.endpoints ?? []) { - const endpointDns = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ENDPOINT_DNS, [vpcItem.name, endpointItem.service]), - ); - const zoneId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.ENDPOINT_ZONE_ID, [vpcItem.name, endpointItem.service]), - ); - endpointMap.set(`${vpcItem.name}_${endpointItem.service}`, endpointDns); - zoneMap.set(`${vpcItem.name}_${endpointItem.service}`, zoneId); - } - } - } - return [endpointMap, zoneMap]; - } - - /** - * Returns a map of Route 53 resolver endpoint IDs if enabled in the target stack - * @param vpcResources - */ - protected setResolverEndpointMap(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): Map { - const endpointMap = new Map(); - - for (const vpcItem of vpcResources) { - if (this.props.networkConfig.centralNetworkServices?.route53Resolver?.endpoints) { - const endpoints = this.props.networkConfig.centralNetworkServices?.route53Resolver?.endpoints; - - for (const endpointItem of endpoints) { - // Only map endpoints for relevant VPCs - if (endpointItem.vpc === vpcItem.name) { - const endpointId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.RESOLVER_ENDPOINT, [endpointItem.name]), - ); - endpointMap.set(`${vpcItem.name}_${endpointItem.name}`, endpointId); - } - } - } - } - return endpointMap; - } - - /** - * Function to create MAP of FW policy and policy ARN - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param firewalls {@link NfwFirewallConfig}[] - * @param policyMap Map - * @param props {@link AcceleratorStackProps} - */ - private createFwPolicyMap( - vpcItem: VpcConfig | VpcTemplatesConfig, - firewalls: NfwFirewallConfig[], - policyMap: Map, - props: AcceleratorStackProps, - ) { - const delegatedAdminAccountId = this.props.accountsConfig.getAccountId( - props.networkConfig.centralNetworkServices!.delegatedAdminAccount, - ); - for (const firewallItem of firewalls) { - if (firewallItem.vpc === vpcItem.name && !policyMap.has(firewallItem.firewallPolicy)) { - // Get firewall policy ARN - let policyArn: string; - - if (delegatedAdminAccountId === cdk.Stack.of(this).account) { - policyArn = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.NFW_POLICY, [firewallItem.firewallPolicy]), - ); - } else { - policyArn = this.getResourceShare( - `${firewallItem.firewallPolicy}_NetworkFirewallPolicyShare`, - 'network-firewall:FirewallPolicy', - delegatedAdminAccountId, - this.cloudwatchKey, - ).resourceShareItemArn; - } - policyMap.set(firewallItem.firewallPolicy, policyArn); - } - } - } - - /** - * Set Network Firewall policy map - * @param props - * @returns - */ - protected setNfwPolicyMap(props: AcceleratorStackProps): Map { - const policyMap = new Map(); - - if (props.networkConfig.centralNetworkServices?.networkFirewall?.firewalls) { - const firewalls = props.networkConfig.centralNetworkServices?.networkFirewall?.firewalls; - - for (const vpcItem of this.vpcResources) { - // Get account IDs - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - this.createFwPolicyMap(vpcItem, firewalls, policyMap, props); - } - } - } - return policyMap; - } - - /** - * Get the resource ID from a RAM share. - * - * @param resourceShareName - * @param itemType - * @param owningAccountId - */ - protected getResourceShare( - resourceShareName: string, - itemType: string, - owningAccountId: string, - kmsKey?: cdk.aws_kms.IKey, - vpcName?: string, - ): IResourceShareItem { - // Generate a logical ID - const resourceShareNameArr = resourceShareName.split('_'); - let resourceName = resourceShareName.split('_')[0]; - if (resourceShareNameArr.length > 2) { - resourceShareNameArr.pop(); - resourceName = resourceShareNameArr.join('_'); - } - const logicalId = vpcName - ? `${vpcName}${resourceName}${itemType.split(':')[1]}` - : `${resourceName}${itemType.split(':')[1]}`; - // Lookup resource share - const resourceShare = ResourceShare.fromLookup(this, pascalCase(`${logicalId}Share`), { - resourceShareOwner: ResourceShareOwner.OTHER_ACCOUNTS, - resourceShareName: resourceShareName, - owningAccountId, - }); - - // Represents the item shared by RAM - return ResourceShareItem.fromLookup(this, pascalCase(`${logicalId}`), { - resourceShare, - resourceShareItemType: itemType, - kmsKey, - logRetentionInDays: this.logRetention, - }); - } - - /** - * Add RAM resource shares to the stack. - * - * @param item - * @param resourceShareName - * @param resourceArns - */ - public addResourceShare(item: ResourceShareType, resourceShareName: string, resourceArns: string[]) { - // Build a list of principals to share to - const principals: string[] = []; - - // Loop through all the defined OUs - for (const ouItem of item.shareTargets?.organizationalUnits ?? []) { - let ouArn = this.props.organizationConfig.getOrganizationalUnitArn(ouItem); - // AWS::RAM::ResourceShare expects the organizations ARN if - // sharing with the entire org (Root) - if (ouItem === 'Root') { - ouArn = ouArn.substring(0, ouArn.lastIndexOf('/')).replace('root', 'organization'); - } - this.logger.info(`Share ${resourceShareName} with Organizational Unit ${ouItem}: ${ouArn}`); - principals.push(ouArn); - } - - // Loop through all the defined accounts - for (const account of item.shareTargets?.accounts ?? []) { - const accountId = this.props.accountsConfig.getAccountId(account); - this.logger.info(`Share ${resourceShareName} with Account ${account}: ${accountId}`); - principals.push(accountId); - } - - // Create the Resource Share - new ResourceShare(this, `${pascalCase(resourceShareName)}ResourceShare`, { - name: resourceShareName, - principals, - resourceArns: resourceArns, - }); - } - - /** - * Returns true if the NACL resource is referencing a cross-account subnet - * @param naclItem - * @returns - */ - public isIpamCrossAccountNaclSource(naclItem: string | NetworkAclSubnetSelection): boolean { - if (typeof naclItem === 'string') { - return false; - } - const accountId = cdk.Stack.of(this).account; - const naclAccount = naclItem.account ? this.props.accountsConfig.getAccountId(naclItem.account) : accountId; - const region = cdk.Stack.of(this).region; - const naclRegion = naclItem.region; - - const crossAccountCondition = naclRegion - ? accountId !== naclAccount || region !== naclRegion - : accountId !== naclAccount; - - if (crossAccountCondition) { - const vpcItem = this.vpcResources.find(item => item.name === naclItem.vpc); - if (!vpcItem) { - this.logger.error(`Specified VPC ${naclItem.vpc} not defined`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const subnetItem = vpcItem.subnets?.find(item => item.name === naclItem.subnet); - if (!subnetItem) { - this.logger.error(`Specified subnet ${naclItem.subnet} not defined`); - throw new Error(`Configuration validation failed at runtime.`); - } - - if (subnetItem.ipamAllocation) { - return true; - } else { - return false; - } - } else { - return false; - } - } - - /** - * Function to create IPAM pool map of pool name and pool id - * @param ipamAllocations {@link IpamAllocationConfig}[] - * @param poolMap Map, - * @param props {@link AcceleratorStackProps} - */ - private createIpamPoolMap( - ipamAllocations: IpamAllocationConfig[], - poolMap: Map, - props: AcceleratorStackProps, - ) { - const delegatedAdminAccountId = props.accountsConfig.getAccountId( - props.networkConfig.centralNetworkServices!.delegatedAdminAccount, - ); - for (const alloc of ipamAllocations) { - const ipamPool = props.networkConfig.centralNetworkServices!.ipams?.find(item => - item.pools?.find(item => item.name === alloc.ipamPoolName), - ); - if (ipamPool === undefined) { - this.logger.error(`Specified Ipam Pool not defined`); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!poolMap.has(alloc.ipamPoolName)) { - let poolId: string; - if (delegatedAdminAccountId === cdk.Stack.of(this).account && ipamPool.region === cdk.Stack.of(this).region) { - poolId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.getSsmPath(SsmResourceType.IPAM_POOL, [alloc.ipamPoolName]), - ); - } else if (ipamPool.region !== cdk.Stack.of(this).region) { - poolId = this.getCrossRegionPoolId(delegatedAdminAccountId, alloc.ipamPoolName, ipamPool.region); - } else { - poolId = this.getResourceShare( - `${alloc.ipamPoolName}_IpamPoolShare`, - 'ec2:IpamPool', - delegatedAdminAccountId, - this.cloudwatchKey, - ).resourceShareItemId; - } - poolMap.set(alloc.ipamPoolName, poolId); - } - } - } - - /** - * Set IPAM pool map - * @param props - * @returns - */ - protected setIpamPoolMap(props: AcceleratorStackProps): Map { - const poolMap = new Map(); - - if (props.networkConfig.centralNetworkServices?.ipams) { - for (const vpcItem of this.vpcsInScope) { - this.createIpamPoolMap(vpcItem.ipamAllocations ?? [], poolMap, props); - } - } - return poolMap; - } - - /** - * Method to retrieve IPAM Pool ID from cross-region - * @param delegatedAdminAccountId - * @param poolName - * @param ipamPoolRegion - */ - private getCrossRegionPoolId(delegatedAdminAccountId: string, poolName: string, ipamPoolRegion: string): string { - let poolId: string | undefined = undefined; - if (delegatedAdminAccountId !== cdk.Stack.of(this).account) { - poolId = new SsmParameterLookup(this, pascalCase(`SsmParamLookup${poolName}`), { - name: this.getSsmPath(SsmResourceType.IPAM_POOL, [poolName]), - accountId: delegatedAdminAccountId, - parameterRegion: ipamPoolRegion, - roleName: this.acceleratorResourceNames.roles.ipamSsmParameterAccess, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays ?? 365, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - } else { - poolId = new SsmParameterLookup(this, pascalCase(`SsmParamLookup${poolName}`), { - name: this.getSsmPath(SsmResourceType.IPAM_POOL, [poolName]), - accountId: delegatedAdminAccountId, - parameterRegion: ipamPoolRegion, - acceleratorPrefix: this.props.prefixes.accelerator, - }).value; - } - return poolId; - } - - /** - * Create security group resources - * @param vpcResources - * @param vpcMap - * @param subnetMap - * @param prefixListMap - * @returns - */ - public createSecurityGroups( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map | Map, - subnetMap: Map | Map, - prefixListMap: Map | Map, - ): Map { - const securityGroupMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - this.logger.info(`Processing rules for ${securityGroupItem.name} in VPC ${vpcItem.name}`); - - // Process configured rules - const processedIngressRules = processSecurityGroupIngressRules( - this.vpcResources, - securityGroupItem, - subnetMap, - prefixListMap, - ); - - const allIngressRule = containsAllIngressRule(processedIngressRules); - const processedEgressRules = processSecurityGroupEgressRules( - this.vpcResources, - securityGroupItem, - subnetMap, - prefixListMap, - ); - - // Get VPC - const vpc = getVpc(vpcMap, vpcItem.name); - - // Create security group - const securityGroup = this.createSecurityGroupItem( - vpcItem, - vpc, - securityGroupItem, - processedIngressRules, - processedEgressRules, - allIngressRule, - ); - securityGroupMap.set(`${vpcItem.name}_${securityGroupItem.name}`, securityGroup); - } - // Create security group rules that reference other security groups - this.createSecurityGroupSgSources(vpcItem, subnetMap, prefixListMap, securityGroupMap); - } - - return securityGroupMap; - } - - /** - * Create security group rules that reference other security groups - * @param vpcItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - */ - private createSecurityGroupSgSources( - vpcItem: VpcConfig | VpcTemplatesConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap: Map, - ) { - for (const securityGroupItem of vpcItem.securityGroups ?? []) { - const securityGroup = getSecurityGroup(securityGroupMap, vpcItem.name, securityGroupItem.name) as SecurityGroup; - const ingressRules = processSecurityGroupSgIngressSources( - this.vpcResources, - vpcItem, - securityGroupItem, - subnetMap, - prefixListMap, - securityGroupMap, - ); - const egressRules = processSecurityGroupSgEgressSources( - this.vpcResources, - vpcItem, - securityGroupItem, - subnetMap, - prefixListMap, - securityGroupMap, - ); - - // Create ingress rules - ingressRules.forEach(ingressRule => { - securityGroup.addIngressRule(ingressRule.logicalId, { - sourceSecurityGroup: ingressRule.rule.targetSecurityGroup, - ...ingressRule.rule, - }); - }); - - // Create egress rules - egressRules.forEach(egressRule => { - securityGroup.addEgressRule(egressRule.logicalId, { - destinationSecurityGroup: egressRule.rule.targetSecurityGroup, - ...egressRule.rule, - }); - }); - } - } - - /** - * Create a security group item - * @param vpcItem - * @param vpc - * @param securityGroupItem - * @param processedIngressRules - * @param processedEgressRules - * @param allIngressRule - * @returns - */ - private createSecurityGroupItem( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpc: Vpc | string, - securityGroupItem: SecurityGroupConfig, - processedIngressRules: SecurityGroupIngressRuleProps[], - processedEgressRules: SecurityGroupEgressRuleProps[], - allIngressRule: boolean, - ): SecurityGroup { - this.logger.info(`Adding Security Group ${securityGroupItem.name} in VPC ${vpcItem.name}`); - let securityGroup; - if (this.isManagedByAsea(AseaResourceType.EC2_SECURITY_GROUP, `${vpcItem.name}/${securityGroupItem.name}`)) { - const ssmPath = this.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, securityGroupItem.name]); - const securityGroupId = this.getExternalResourceParameter(ssmPath); - if (securityGroupId) { - securityGroup = SecurityGroup.fromSecurityGroupId(this, securityGroupId); - } else { - this.logger.error( - `Security group ${ssmPath} for ${cdk.Stack.of(this).stackName}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - } exists in resource mapping but does not have an ssm parameter. Skipping import. This error should only occur in the bootstrapping phase.`, - ); - } - } - if (!securityGroup) { - securityGroup = new SecurityGroup( - this, - pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${securityGroupItem.name}Sg`), - { - securityGroupName: securityGroupItem.name, - securityGroupEgress: processedEgressRules, - securityGroupIngress: processedIngressRules, - description: securityGroupItem.description, - vpc: typeof vpc === 'object' ? vpc : undefined, - vpcId: typeof vpc === 'string' ? vpc : undefined, - tags: securityGroupItem.tags, - }, - ); - this.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(securityGroupItem.name)}SecurityGroup`), - parameterName: this.getSsmPath(SsmResourceType.SECURITY_GROUP, [vpcItem.name, securityGroupItem.name]), - stringValue: securityGroup.securityGroupId, - }); - } - - // AwsSolutions-EC23: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access. - if (allIngressRule) { - NagSuppressions.addResourceSuppressions(securityGroup, [ - { id: 'AwsSolutions-EC23', reason: 'User defined an all ingress rule in configuration.' }, - ]); - } - return securityGroup; - } - - /** - * Lookup same account+region IPAM subnet - * @param vpcName - * @param subnetName - */ - public lookupLocalIpamSubnet(vpcName: string, subnetName: string): IIpamSubnet { - this.logger.info(`Retrieve IPAM Subnet CIDR for vpc:[${vpcName}] subnet:[${subnetName}]`); - - return IpamSubnet.fromLookup(this, pascalCase(`${vpcName}${subnetName}IpamSubnetLookup`), { - owningAccountId: cdk.Stack.of(this).account, - ssmSubnetIdPath: this.getSsmPath(SsmResourceType.SUBNET, [vpcName, subnetName]), - region: cdk.Stack.of(this).region, - roleName: this.acceleratorResourceNames.roles.ipamSubnetLookup, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - - /** - * Set route table destination based on CIDR or subnet reference - * @param routeTableEntryItem - * @param subnetMap - * @param vpcName - * @returns [string | undefined, string | undefined] - */ - public setRouteEntryDestination( - routeTableEntryItem: RouteTableEntryConfig, - ipamSubnetArray: string[], - vpcName: string, - ): [string | undefined, string | undefined] { - let destination: string | undefined = undefined; - let ipv6Destination: string | undefined = undefined; - - const subnetKey = `${vpcName}_${routeTableEntryItem.ipv6Destination ?? routeTableEntryItem.destination!}`; - - if (ipamSubnetArray.includes(subnetKey)) { - destination = this.lookupLocalIpamSubnet(vpcName, routeTableEntryItem.destination!).ipv4CidrBlock; - } else { - destination = routeTableEntryItem.destination; - ipv6Destination = routeTableEntryItem.ipv6Destination; - } - - return [destination, ipv6Destination]; - } - - /** - * Returns the VPC configuration that a given firewall instance is deployed to - * @param customerGateway - * @returns VpcConfig | VpcTemplatesConfig - */ - public getFirewallVpcConfig(customerGateway: CustomerGatewayConfig): VpcConfig | VpcTemplatesConfig { - try { - const firewallName = customerGateway.ipAddress.split(':')[4].replace('}', ''); - const firewallConfig = getFirewallInstanceConfig( - firewallName, - this.props.customizationsConfig.firewalls?.instances, - ); - return getVpcConfig(this.vpcResources, firewallConfig.vpc); - } catch (e) { - throw new Error(`Error while processing customer gateway firewall reference variable: ${e}`); - } - } - - /** - * Returns a boolean indicating if the VPC a given firewall is deployed to - * is in the same account+region as the customer gateway - * @param customerGateway CustomerGatewayConfig - * @returns boolean - */ - public firewallVpcInScope(customerGateway: CustomerGatewayConfig): boolean { - const cgwAccountId = this.props.accountsConfig.getAccountId(customerGateway.account); - const firewallVpcConfig = this.getFirewallVpcConfig(customerGateway); - const vpcAccountIds = this.getVpcAccountIds(firewallVpcConfig); - - return vpcAccountIds.includes(cgwAccountId) && firewallVpcConfig.region === this.region; - } - - /** - * Creates a custom resource onEventHandler for VPN connections - * requiring advanced configuration parameters - * @param props AcceleratorStackProps - * @returns cdk.aws_lambda.IFunction - */ - public createVpnOnEventHandler(): cdk.aws_lambda.IFunction { - const lambdaExecutionPolicy = cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSLambdaBasicExecutionRole', - ); - - const managedVpnPolicy = new cdk.aws_iam.ManagedPolicy(this, 'VpnOnEventHandlerPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'S2SVPNCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ec2:CreateTags', - 'ec2:CreateVpnConnection', - 'ec2:DeleteTags', - 'ec2:DeleteVpnConnection', - 'ec2:DescribeVpnConnections', - 'ec2:ModifyVpnConnectionOptions', - 'ec2:ModifyVpnTunnelOptions', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'LogDeliveryCRUD', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'logs:CreateLogDelivery', - 'logs:GetLogDelivery', - 'logs:UpdateLogDelivery', - 'logs:DeleteLogDelivery', - 'logs:ListLogDeliveries', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S2SVPNLoggingCWL', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:PutResourcePolicy', 'logs:DescribeResourcePolicies', 'logs:DescribeLogGroups'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S2SVPNAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.partition}:iam::*:role/${this.acceleratorResourceNames.roles.crossAccountVpnRoleName}`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'SecretsManagerReadOnly', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['secretsmanager:GetSecretValue', 'kms:Decrypt'], - resources: ['*'], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.addNagSuppression({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: managedVpnPolicy.node.path, - reason: 'Managed policy allows access for VPN CRUD operations', - }, - ], - }); - // - // Create event handler role - const vpnRole = new cdk.aws_iam.Role(this, 'VpnRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.urlSuffix}`), - description: 'Landing Zone Accelerator site-to-site VPN custom resource access role', - managedPolicies: [managedVpnPolicy, lambdaExecutionPolicy], - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.addNagSuppression({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: vpnRole.node.path, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - // - // Create Lambda handler - return new LzaLambda(this, 'VpnOnEventHandler', { - assetPath: '../constructs/lib/aws-ec2/custom-vpn-connection/dist', - environmentEncryptionKmsKey: this.lambdaKey, - cloudWatchLogKmsKey: this.cloudwatchKey, - cloudWatchLogRetentionInDays: this.logRetention, - description: 'Custom resource onEvent handler for site-to-site VPN', - role: vpnRole, - timeOut: cdk.Duration.minutes(15), - nagSuppressionPrefix: 'VpnOnEventHandler', - }).resource; - } - - /** - * Set site-to-site VPN connection properties. - * @param options - * @returns VpnConnectionProps - */ - public setVpnProps(options: { - vpnItem: VpnConnectionConfig; - customerGatewayId: string; - customResourceHandler?: cdk.aws_lambda.IFunction; - owningAccountId?: string; - owningRegion?: string; - transitGatewayId?: string; - virtualPrivateGateway?: string; - }): VpnConnectionProps { - const hasCrossAccountOptions = options.owningAccountId || options.owningRegion ? true : false; - - return { - name: options.vpnItem.name, - customerGatewayId: options.customerGatewayId, - amazonIpv4NetworkCidr: options.vpnItem.amazonIpv4NetworkCidr, - customerIpv4NetworkCidr: options.vpnItem.customerIpv4NetworkCidr, - customResourceHandler: - hasAdvancedVpnOptions(options.vpnItem) || hasCrossAccountOptions ? options.customResourceHandler : undefined, - enableVpnAcceleration: options.vpnItem.enableVpnAcceleration, - owningAccountId: options.owningAccountId, - owningRegion: options.owningRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountVpnRoleName, - staticRoutesOnly: options.vpnItem.staticRoutesOnly, - tags: options.vpnItem.tags, - transitGatewayId: options.transitGatewayId, - virtualPrivateGateway: options.virtualPrivateGateway, - vpnTunnelOptionsSpecifications: this.setVpnTunnelOptions( - options.vpnItem, - hasCrossAccountOptions, - options.owningAccountId, - options.owningRegion, - ), - }; - } - - /** - * Set VPN tunnel options properties - * @param vpnItem VpnConnectionConfig - * @param hasCrossAccountOptions boolean - * @param owningAccountId string | undefined - * @param owningRegion string | undefined - * @returns VpnTunnelOptionsSpecifications[] | undefined - */ - private setVpnTunnelOptions( - vpnItem: VpnConnectionConfig, - hasCrossAccountOptions: boolean, - owningAccountId?: string, - owningRegion?: string, - ): VpnTunnelOptionsSpecifications[] | undefined { - if (!vpnItem.tunnelSpecifications) { - return; - } - const vpnTunnelOptions: VpnTunnelOptionsSpecifications[] = []; - - for (const [index, tunnel] of vpnItem.tunnelSpecifications.entries()) { - let loggingConfig: { enable?: boolean; logGroupArn?: string; outputFormat?: string } | undefined = undefined; - let preSharedKeyValue: string | undefined = undefined; - // - // Rewrite logging config with log group ARN - if (tunnel.logging?.enable) { - loggingConfig = { - enable: true, - logGroupArn: this.createVpnLogGroup( - vpnItem, - index, - tunnel.logging.logGroupName, - owningAccountId, - owningRegion, - ), - outputFormat: tunnel.logging.outputFormat, - }; - } - // - // Rewrite PSK - if (tunnel.preSharedKey) { - const preSharedKeySecret = cdk.aws_secretsmanager.Secret.fromSecretNameV2( - this, - pascalCase(`${vpnItem.name}${tunnel.preSharedKey}Tunnel${index}PreSharedKeySecret`), - tunnel.preSharedKey, - ); - const suffixLength = preSharedKeySecret.secretName.split('-').at(-1)!.length + 1; - const secretName = preSharedKeySecret.secretName.slice(0, -suffixLength); - // - // If advanced or cross-account VPN, use the secret name. Otherwise, retrieve the secret value - preSharedKeyValue = - hasAdvancedVpnOptions(vpnItem) || hasCrossAccountOptions - ? secretName - : preSharedKeySecret.secretValue.toString(); - } - - vpnTunnelOptions.push({ - dpdTimeoutAction: tunnel.dpdTimeoutAction, - dpdTimeoutSeconds: tunnel.dpdTimeoutSeconds, - ikeVersions: tunnel.ikeVersions, - logging: loggingConfig, - phase1: tunnel.phase1, - phase2: tunnel.phase2, - preSharedKey: preSharedKeyValue, - rekeyFuzzPercentage: tunnel.rekeyFuzzPercentage, - rekeyMarginTimeSeconds: tunnel.rekeyMarginTimeSeconds, - replayWindowSize: tunnel.replayWindowSize, - startupAction: tunnel.startupAction, - tunnelInsideCidr: tunnel.tunnelInsideCidr, - tunnelLifecycleControl: tunnel.tunnelLifecycleControl, - }); - } - return vpnTunnelOptions; - } - - /** - * Returns the ARN of a CloudWatch Log group created for the VPN tunnel. - * @param vpnItem VpnConnectionConfig - * @param index number - * @param logGroupName string | undefined - * @param owningAccountId string | undefined - * @param owningRegion string | undefined - * @returns string - */ - private createVpnLogGroup( - vpnItem: VpnConnectionConfig, - index: number, - logGroupName?: string, - owningAccountId?: string, - owningRegion?: string, - ): string { - const logicalId = pascalCase(`${vpnItem.name}Tunnel${index}LogGroup`); - - if (owningAccountId || owningRegion) { - return new CloudWatchLogGroups(this, logicalId, { - logGroupName: logGroupName ? `${this.acceleratorPrefix}${logGroupName}` : undefined, - logRetentionInDays: this.logRetention, - customLambdaLogKmsKey: this.cloudwatchKey, - customLambdaLogRetention: this.logRetention, - owningAccountId, - owningRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountLogsRoleName, - }).logGroupArn; - } else { - return new cdk.aws_logs.LogGroup(this, logicalId, { - logGroupName: logGroupName ? `${this.acceleratorPrefix}${logGroupName}` : undefined, - encryptionKey: this.cloudwatchKey, - retention: this.logRetention, - }).logGroupArn; - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-dns-stack/network-vpc-dns-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-dns-stack/network-vpc-dns-stack.ts deleted file mode 100644 index 12a61c7..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-dns-stack/network-vpc-dns-stack.ts +++ /dev/null @@ -1,463 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -import { - AseaResourceType, - InterfaceEndpointServiceConfig, - Region, - ResolverEndpointConfig, - ResolverRuleConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { HostedZone, RecordSet, ResolverRule } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { NetworkStack } from '../network-stack'; - -export class NetworkVpcDnsStack extends NetworkStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - // - // Store VPC IDs, interface endpoint DNS, and Route 53 resolver endpoints - // - const vpcMap = this.setVpcMap(this.vpcsInScope); - const [endpointMap, zoneMap] = this.setInterfaceEndpointDnsMap(this.vpcsInScope); - const resolverMap = this.setResolverEndpointMap(this.vpcsInScope); - - // - // Create Private Hosted Zones - // - this.createPrivateHostedZones({ vpc: vpcMap, endpoint: endpointMap, zone: zoneMap, resolver: resolverMap }, props); - - // - // configure SYSTEM rules - // - this.configureSystemRules(props); - - // - // Create SSM Parameters - // - this.createSsmParameters(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Function to configure system rules - * @param props - */ - private configureSystemRules(props: AcceleratorStackProps) { - if (props.networkConfig.centralNetworkServices?.route53Resolver?.rules) { - const delegatedAdminAccountId = this.props.accountsConfig.getAccountId( - props.networkConfig.centralNetworkServices.delegatedAdminAccount, - ); - - // Only deploy in the delegated admin account - if (delegatedAdminAccountId === cdk.Stack.of(this).account) { - this.createSystemRules(props.networkConfig.centralNetworkServices?.route53Resolver?.rules); - } - } - } - - /** - * Function to create private hosted zones - * @param maps { - vpc: Map; - endpoint: Map; - zone: Map; - resolver: Map; - } - * @param props {@link AcceleratorStackProps} - */ - private createPrivateHostedZones( - maps: { - vpc: Map; - endpoint: Map; - zone: Map; - resolver: Map; - }, - props: AcceleratorStackProps, - ) { - for (const vpcItem of this.vpcsInScope) { - const vpcId = maps.vpc.get(vpcItem.name); - - if (!vpcId) { - this.logger.error(`Unable to locate VPC ${vpcItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - // Create private hosted zones - if (vpcItem.interfaceEndpoints?.central) { - this.createHostedZones(vpcItem, vpcId, maps.endpoint, maps.zone); - } - - // - // Create resolver rules - // - - // FORWARD rules - if (props.networkConfig.centralNetworkServices?.route53Resolver?.endpoints) { - const endpoints = props.networkConfig.centralNetworkServices?.route53Resolver?.endpoints; - - for (const endpointItem of endpoints) { - if (endpointItem.vpc === vpcItem.name && endpointItem.type === 'OUTBOUND') { - this.createForwardRules(vpcItem, endpointItem, maps.resolver); - } - } - } - } - } - - /** - * Create private hosted zones - * - * @param vpcItem - * @param vpcId - * @param endpointMap - * @param zoneMap - */ - private createHostedZones( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - endpointMap: Map, - zoneMap: Map, - ): void { - for (const endpointItem of vpcItem.interfaceEndpoints?.endpoints ?? []) { - if (this.isManagedByAsea(AseaResourceType.ROUTE_53_PHZ_ID, `${vpcItem.name}/${endpointItem.service}`)) { - this.logger.info( - `PHZ for Interface Endpoint "${endpointItem.service}", VPC "${vpcItem.name}" is managed externally`, - ); - continue; - } else { - // Create the private hosted zone - this.logger.info(`Creating private hosted zone for VPC:${vpcItem.name} endpoint:${endpointItem.service}`); - const hostedZoneName = HostedZone.getHostedZoneNameForService(endpointItem.service, cdk.Stack.of(this).region); - const tags = vpcItem.interfaceEndpoints?.tags; - const hostedZone = new HostedZone( - this, - `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpointItem.service)}EpHostedZone`, - { - hostedZoneName, - vpcId, - tags, - }, - ); - this.ssmParameters.push({ - logicalId: `SsmParam${pascalCase(vpcItem.name)}Vpc${pascalCase(endpointItem.service)}EpHostedZone`, - parameterName: this.getSsmPath(SsmResourceType.PHZ_ID, [vpcItem.name, endpointItem.service]), - stringValue: hostedZone.hostedZoneId, - }); - // Create additional S3 record sets - this.createRecordSets(vpcItem, endpointItem, endpointMap, zoneMap, hostedZoneName, hostedZone); - if (endpointItem.service === 's3') { - this.createAdditionalS3Records(vpcItem, vpcId, endpointMap, zoneMap, endpointItem); - } - } - } - } - - /** - * Create record sets for centralized interface endpoints - * @param vpcItem - * @param endpointItem - * @param endpointMap - * @param zoneMap - * @param hostedZoneName - * @param hostedZone - */ - private createRecordSets( - vpcItem: VpcConfig | VpcTemplatesConfig, - endpointItem: InterfaceEndpointServiceConfig, - endpointMap: Map, - zoneMap: Map, - hostedZoneName: string, - hostedZone: HostedZone, - ) { - // Create the record set - let recordSetName = hostedZoneName; - const wildcardServices = [ - 'appsync-api', - 'codeartifact.repositories', - 'deviceadvisor.iot', - 'ecr.dkr', - 'execute-api', - 'grafana-workspace', - 'lorawan.cups', - 'lorawan.lns', - 'notebook', - 'studio', - 's3', - 's3-global.accesspoint', - 'transfer.server', - ]; - if (wildcardServices.includes(endpointItem.service)) { - recordSetName = `*.${hostedZoneName}`; - } - - // Check mapping for DNS name - const endpointKey = `${vpcItem.name}_${endpointItem.service}`; - const dnsName = endpointMap.get(endpointKey); - const zoneId = zoneMap.get(endpointKey); - if (!dnsName) { - this.logger.error(`Unable to locate DNS name for VPC:${vpcItem.name} endpoint:${endpointItem.service}`); - throw new Error(`Configuration validation failed at runtime.`); - } - if (!zoneId) { - this.logger.error(`Unable to locate hosted zone ID for VPC:${vpcItem.name} endpoint:${endpointItem.service}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create alias record for hosted zone - this.logger.info(`Creating alias record for VPC:${vpcItem.name} endpoint:${endpointItem.service}`); - new RecordSet(this, `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpointItem.service)}EpRecordSet`, { - type: 'A', - name: recordSetName, - hostedZone: hostedZone, - dnsName: dnsName, - hostedZoneId: zoneId, - }); - - // Create additional record for wildcard services endpoints - if (wildcardServices.includes(endpointItem.service)) { - this.logger.info(`Creating additional record for VPC:${vpcItem.name} endpoint:${endpointItem.service}`); - new RecordSet(this, `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpointItem.service)}EpRecordSetNonWildcard`, { - type: 'A', - name: hostedZoneName, - hostedZone: hostedZone, - dnsName: dnsName, - hostedZoneId: zoneId, - }); - } - } - - /** - * Create record sets for centralized interface endpoints - * @param vpcItem - * @param endpointItem - * @param endpointMap - * @param zoneMap - * @param hostedZoneName - * @param hostedZone - */ - - private createAdditionalS3Records( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - endpointMap: Map, - zoneMap: Map, - endpointItem: InterfaceEndpointServiceConfig, - ) { - const endpointKey = `${vpcItem.name}_${endpointItem.service}`; - const dnsName = endpointMap.get(endpointKey); - const zoneId = zoneMap.get(endpointKey); - const s3EndpointsArray = ['s3-control', 's3-accesspoint']; - - s3EndpointsArray.forEach(endpoint => { - this.logger.info(`Creating private hosted zone for VPC:${vpcItem.name} endpoint:${endpoint}`); - const hostedZone = new HostedZone(this, `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpoint)}EpHostedZone`, { - hostedZoneName: `${endpoint}.${cdk.Stack.of(this).region}.amazonaws.com`, - vpcId, - }); - this.ssmParameters.push({ - logicalId: `SsmParam${pascalCase(vpcItem.name)}Vpc${pascalCase(endpoint)}EpHostedZone`, - parameterName: this.getSsmPath(SsmResourceType.PHZ_ID, [vpcItem.name, endpoint]), - stringValue: hostedZone.hostedZoneId, - }); - this.logger.info(`Creating additional record for VPC:${vpcItem.name} endpoint:${endpoint}`); - new RecordSet(this, `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpoint)}EpRecordWildcard`, { - type: 'A', - name: `*.${hostedZone.hostedZoneName}`, - hostedZone: hostedZone, - dnsName: dnsName, - hostedZoneId: zoneId, - }); - - new RecordSet(this, `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpoint)}EpRecordSetNonWildcard`, { - type: 'A', - name: hostedZone.hostedZoneName, - hostedZone: hostedZone, - dnsName: dnsName, - hostedZoneId: zoneId, - }); - }); - } - - /** - * Create Route 53 resolver FORWARD rules - * - * @param vpcItem - * @param endpointItem - * @param resolverMap - */ - private createForwardRules( - vpcItem: VpcConfig | VpcTemplatesConfig, - endpointItem: ResolverEndpointConfig, - resolverMap: Map, - ): void { - // Check for endpoint in map - const endpointKey = `${vpcItem.name}_${endpointItem.name}`; - const endpointId = resolverMap.get(endpointKey); - if (!endpointId) { - this.logger.error(`Create resolver rule: unable to locate resolver endpoint ${endpointItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create rules - for (const ruleItem of endpointItem.rules ?? []) { - this.logger.info(`Add Route 53 Resolver FORWARD rule ${ruleItem.name} to endpoint ${endpointItem.name}`); - - // Check whether there is an inbound endpoint target - let inboundTarget: string | undefined = undefined; - if (ruleItem.inboundEndpointTarget) { - const targetKey = `${vpcItem.name}_${ruleItem.inboundEndpointTarget}`; - inboundTarget = resolverMap.get(targetKey); - if (!inboundTarget) { - this.logger.error(`Target inbound endpoint: ${ruleItem.inboundEndpointTarget} not found in endpoint map`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - // Create resolver rule and SSM parameter - const rule = new ResolverRule(this, `${pascalCase(endpointItem.name)}ResolverRule${pascalCase(ruleItem.name)}`, { - domainName: ruleItem.domainName, - name: ruleItem.name, - ruleType: ruleItem.ruleType, - resolverEndpointId: endpointId, - targetIps: ruleItem.targetIps, - tags: ruleItem.tags, - targetInbound: inboundTarget, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${ruleItem.name}ResolverRule`), - parameterName: this.getSsmPath(SsmResourceType.RESOLVER_RULE, [ruleItem.name]), - stringValue: rule.ruleId, - }); - - // create role to be assumed by MAD account to update resolver rule - this.createManagedADResolverRuleUpdateRole(ruleItem.name, rule.ruleArn); - - if (ruleItem.shareTargets) { - this.logger.info(`Share Route 53 Resolver FORWARD rule ${ruleItem.name}`); - this.addResourceShare(ruleItem, `${ruleItem.name}_ResolverRule`, [rule.ruleArn]); - } - } - } - - /** - * Create IAM role to be assumed by MAD account to update resolver rule in central account - * @param ruleName - * @param ruleArn - * @returns - */ - private createManagedADResolverRuleUpdateRole(ruleName: string, ruleArn: string) { - for (const managedActiveDirectory of this.props.iamConfig.managedActiveDirectories ?? []) { - const madAccountId = this.props.accountsConfig.getAccountId(managedActiveDirectory.account); - const delegatedAdminAccountId = this.props.accountsConfig.getAccountId( - this.props.networkConfig.centralNetworkServices!.delegatedAdminAccount, - ); - - if (madAccountId === delegatedAdminAccountId) { - this.logger.info( - `Managed AD account is same as delegated admin account, skipping role creation for ${managedActiveDirectory.name} active directory}`, - ); - return; - } - if ( - delegatedAdminAccountId === cdk.Stack.of(this).account && - cdk.Stack.of(this).region === this.props.globalConfig.homeRegion && - ruleName === managedActiveDirectory.resolverRuleName - ) { - this.logger.info( - `Create Managed AD resolver rule update role for ${managedActiveDirectory.name} active directory`, - ); - new cdk.aws_iam.Role(this, pascalCase(`${this.props.prefixes.accelerator}-MAD-${ruleName}`), { - roleName: `${this.props.prefixes.accelerator}-MAD-${ruleName}`, - assumedBy: new cdk.aws_iam.PrincipalWithConditions(new cdk.aws_iam.AccountPrincipal(madAccountId), { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::${madAccountId}:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['route53resolver:ListResolverRules'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['route53resolver:UpdateResolverRule'], - resources: [ruleArn], - }), - ], - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/` + pascalCase(`${this.props.prefixes.accelerator}-MAD-${ruleName}`) + '/Resource', - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Role needs access to list resolver rules and update', - }, - ], - ); - } - } - } - - /** - * Create Route 53 resolver SYSTEM rules - * @param rules - */ - private createSystemRules(rules: ResolverRuleConfig[]): void { - // Process SYSTEM rules - for (const ruleItem of rules ?? []) { - // Only deploy if the region isn't excluded - if (!ruleItem.excludedRegions?.includes(cdk.Stack.of(this).region as Region) || !ruleItem.excludedRegions) { - this.logger.info(`Add Route 53 Resolver SYSTEM rule ${ruleItem.name}`); - - const rule = new ResolverRule(this, `SystemResolverRule${pascalCase(ruleItem.name)}`, { - domainName: ruleItem.domainName, - name: ruleItem.name, - ruleType: ruleItem.ruleType ?? 'SYSTEM', - tags: ruleItem.tags, - }); - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${ruleItem.name}ResolverRule`), - parameterName: this.getSsmPath(SsmResourceType.RESOLVER_RULE, [ruleItem.name]), - stringValue: rule.ruleId, - }); - - if (ruleItem.shareTargets) { - this.logger.info(`Share Route 53 Resolver SYSTEM rule ${ruleItem.name}`); - this.addResourceShare(ruleItem, `${ruleItem.name}_ResolverRule`, [rule.ruleArn]); - } - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-endpoints-stack/network-vpc-endpoints-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-endpoints-stack/network-vpc-endpoints-stack.ts deleted file mode 100644 index e3f2893..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-endpoints-stack/network-vpc-endpoints-stack.ts +++ /dev/null @@ -1,1032 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -import { - AseaResourceType, - GatewayEndpointServiceConfig, - InterfaceEndpointServiceConfig, - NfwFirewallConfig, - ResolverEndpointConfig, - RouteTableConfig, - RouteTableEntryConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { - INetworkFirewall, - NetworkFirewall, - ResolverEndpoint, - SecurityGroup, - SecurityGroupEgressRuleProps, - SecurityGroupIngressRuleProps, - VpcEndpoint, - VpcEndpointType, -} from '@aws-accelerator/constructs'; -import { getAvailabilityZoneMap } from '@aws-accelerator/utils/lib/regions'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { NetworkStack } from '../network-stack'; -import { getSecurityGroup } from '../utils/getter-utils'; -import { setIpamSubnetRouteTableEntryArray } from '../utils/setter-utils'; - -export class NetworkVpcEndpointsStack extends NetworkStack { - private nfwPolicyMap: Map; - - private vpcMap: Map; - private subnetMap: Map; - private routeTableMap: Map; - private firewallMap: Map; - private securityGroupEndpointMap: Map; - - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - // - // Store VPC, subnet, and route table IDs - // - this.vpcMap = this.setVpcMap(this.vpcsInScope); - this.subnetMap = this.setSubnetMap(this.vpcsInScope); - this.routeTableMap = this.setRouteTableMap(this.vpcsInScope); - this.securityGroupEndpointMap = this.setEndpointSecurityGroupMap(this.vpcsInScope); - this.firewallMap = new Map(); - - // - // Set Network Firewall policy map - // - this.nfwPolicyMap = this.setNfwPolicyMap(props); - - const firewallLogBucket = cdk.aws_s3.Bucket.fromBucketName(this, 'FirewallLogsBucket', this.centralLogsBucketName); - - // - // Iterate through VPCs in this account and region - // - for (const vpcItem of this.vpcsInScope) { - // Get Vpc Id - const vpcId = this.getVpcId(vpcItem.name); - - // - // Create VPC endpoints - // - this.createVpcEndpoints(vpcItem, vpcId); - - // - // Create Network Firewalls - // - this.createNetworkFirewalls(vpcItem, vpcId, firewallLogBucket, props); - - // - // Create endpoint routes - // - this.createEndpointRoutes(vpcItem, props); - - // - // Create Route 53 Resolver Endpoints - // - this.createRoute53ResolverEndpoints(vpcItem, vpcId, props); - } - - // - // Create Route 53 Resolver Endpoints for VPC - // - this.createVpcRoute53ResolverEndpoints(props); - - // - // Create SSM parameters - // - this.createSsmParameters(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Function to get Vpc id for given vpc - * @param vpcName string - * @returns - */ - private getVpcId(vpcName: string): string { - const vpcId = this.vpcMap.get(vpcName); - if (!vpcId) { - this.logger.error(`Unable to locate VPC ${vpcName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return vpcId; - } - - /** - * Function to Create VPC endpoints - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param vpcId string - * - * @remarks - * Function creates Gateway and Interface endpoints for the given VPC. - */ - private createVpcEndpoints(vpcItem: VpcConfig | VpcTemplatesConfig, vpcId: string) { - // Create VPC endpoints - // - if (vpcItem.gatewayEndpoints) { - this.createGatewayEndpoints(vpcItem, vpcId); - } - - if (vpcItem.interfaceEndpoints) { - this.createInterfaceEndpoints(vpcItem, vpcId); - } - } - - /** - * Function to get firewall subnet ids - * @param firewallItem {@link NfwFirewallConfig} - * @returns subnetIds string[] - */ - private getFirewallSubnetIds(firewallItem: NfwFirewallConfig): string[] { - const firewallSubnets: string[] = []; - - // Check if VPC has matching subnets - for (const subnetItem of firewallItem.subnets) { - const subnetKey = `${firewallItem.vpc}_${subnetItem}`; - const subnetId = this.subnetMap.get(subnetKey); - if (subnetId) { - firewallSubnets.push(subnetId); - } else { - this.logger.error(`Create Network Firewall: subnet ${subnetItem} not found in VPC ${firewallItem.vpc}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - return firewallSubnets; - } - - /** - * Function to create Network firewalls - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param vpcId string - * @param firewallLogBucket {@link cdk.aws_s3.IBucket} - * @param props {@link AcceleratorStackProps} - */ - private createNetworkFirewalls( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - firewallLogBucket: cdk.aws_s3.IBucket, - props: AcceleratorStackProps, - ) { - if (props.networkConfig.centralNetworkServices?.networkFirewall?.firewalls) { - const firewalls = props.networkConfig.centralNetworkServices.networkFirewall.firewalls; - - for (const firewallItem of firewalls) { - if (firewallItem.vpc === vpcItem.name) { - //Get firewall subnets - const firewallSubnetIds = this.getFirewallSubnetIds(firewallItem); - - // Create firewall - if (firewallSubnetIds.length > 0) { - const nfw = this.createNetworkFirewall(firewallItem, vpcId, firewallSubnetIds, firewallLogBucket); - this.firewallMap.set(firewallItem.name, nfw); - } - } - } - } - } - - private createEndpointRoute( - vpcName: string, - routeTableItem: RouteTableConfig, - routeTableEntryItem: RouteTableEntryConfig, - ) { - const endpointRouteId = - pascalCase(`${vpcName}Vpc`) + - pascalCase(`${routeTableItem.name}RouteTable`) + - pascalCase(routeTableEntryItem.name); - - if (routeTableEntryItem.type && routeTableEntryItem.type === 'networkFirewall') { - const routeTableId = this.routeTableMap.get(`${vpcName}_${routeTableItem.name}`); - const [destination, ipv6Destination] = this.setRouteEntryDestination( - routeTableEntryItem, - setIpamSubnetRouteTableEntryArray(this.vpcsInScope), - vpcName, - ); - - // Check if route table exists im map - if (!routeTableId) { - this.logger.error(`Unable to locate route table ${routeTableItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Get Network Firewall - const firewall = this.firewallMap.get(routeTableEntryItem.target!); - const endpointAz = - typeof routeTableEntryItem.targetAvailabilityZone === 'number' - ? `${getAvailabilityZoneMap(cdk.Stack.of(this).region)}${routeTableEntryItem.targetAvailabilityZone}` - : `${cdk.Stack.of(this).region}${routeTableEntryItem.targetAvailabilityZone}`; - - if (!firewall) { - this.logger.error(`Unable to locate Network Firewall ${routeTableEntryItem.target}`); - throw new Error(`Configuration validation failed at runtime.`); - } - // Add route - this.logger.info(`Adding Network Firewall Route Table Entry ${routeTableEntryItem.name}`); - firewall.addNetworkFirewallRoute( - endpointRouteId, - endpointAz, - this.logRetention, - routeTableId, - destination, - ipv6Destination, - this.cloudwatchKey, - ); - } - } - - /** - * Function to create Endpoint routes - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param props {@link AcceleratorStackProps} - */ - private createEndpointRoutes(vpcItem: VpcConfig | VpcTemplatesConfig, props: AcceleratorStackProps) { - if (props.networkConfig.centralNetworkServices?.networkFirewall?.firewalls) { - for (const routeTableItem of vpcItem.routeTables ?? []) { - // Check if endpoint routes exist - for (const routeTableEntryItem of routeTableItem.routes ?? []) { - //Create endpoint route - this.createEndpointRoute(vpcItem.name, routeTableItem, routeTableEntryItem); - } - } - } - } - - /** - * Function to validate route 53 resolver vpc account - * @param delegatedAdminAccountId - * - * @remarks - * Check route 53 resolver vpc account is the delegated admin account. - */ - private validateRoute53ResolverVpcAccount(delegatedAdminAccountId: string) { - if (cdk.Stack.of(this).account !== delegatedAdminAccountId) { - this.logger.error( - 'VPC for Route 53 Resolver endpoints must be located in the delegated network administrator account', - ); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to get Route53 resolver endpoint subnet ids - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param endpointItem {@link ResolverEndpointConfig} - * @param endpointSubnets string[] - */ - private getRoute53ResolverEndpointSubnetIds( - vpcItem: VpcConfig | VpcTemplatesConfig, - endpointItem: ResolverEndpointConfig, - endpointSubnets: string[], - ) { - for (const subnetItem of endpointItem.subnets) { - const subnetKey = `${vpcItem.name}_${subnetItem}`; - const subnetId = this.subnetMap.get(subnetKey); - if (subnetId) { - endpointSubnets.push(subnetId); - } else { - this.logger.error(`Create Route 53 Resolver endpoint: subnet not found in VPC ${vpcItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - } - - /** - * Function to create Route 53 Resolver Endpoints - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param vpcId string - * @param props {@link AcceleratorStackProps} - */ - private createRoute53ResolverEndpoints( - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcId: string, - props: AcceleratorStackProps, - ) { - if (props.networkConfig.centralNetworkServices?.route53Resolver?.endpoints) { - const endpoints = props.networkConfig.centralNetworkServices?.route53Resolver?.endpoints; - - // Check if the VPC has matching subnets - for (const endpointItem of endpoints) { - if (vpcItem.name === endpointItem.vpc) { - const endpointSubnets: string[] = []; - - // Check if this is the delegated admin account - this.validateRoute53ResolverVpcAccount( - this.props.accountsConfig.getAccountId(props.networkConfig.centralNetworkServices.delegatedAdminAccount), - ); - - // Get route 53 resolver endpoint subnet ids - this.getRoute53ResolverEndpointSubnetIds(vpcItem, endpointItem, endpointSubnets); - - // Create endpoint - if (endpointSubnets.length > 0) { - this.createResolverEndpoint(endpointItem, vpcId, endpointSubnets); - } - } - } - } - } - - /** - * Function to create non-centralized Route 53 Resolver Endpoints - * @param props {@link AcceleratorStackProps} - */ - private createVpcRoute53ResolverEndpoints(props: AcceleratorStackProps) { - // Loop through the VPCs that specify Route53 configurations - for (const vpcItem of props.networkConfig.vpcs ?? []) { - const accountId = this.props.accountsConfig.getAccountId(vpcItem.account); - if (cdk.Stack.of(this).region === vpcItem.region && cdk.Stack.of(this).account === accountId) { - // Get Vpc Id - const vpcId = this.getVpcId(vpcItem.name); - if (vpcItem.vpcRoute53Resolver?.endpoints) { - { - const endpoints = vpcItem.vpcRoute53Resolver.endpoints; - for (const endpointItem of endpoints) { - if (this.isManagedByAsea(AseaResourceType.ROUTE_53_RESOLVER_ENDPOINT, `${endpointItem.name}`)) { - this.logger.info( - `Resolver Endpoint "${endpointItem.name}" for VPC "${vpcItem.name}" is managed externally`, - ); - continue; - } - const endpointSubnets: string[] = []; - // Get route 53 resolver endpoint subnet ids - this.getRoute53ResolverEndpointSubnetIds(vpcItem, endpointItem, endpointSubnets); - - // Create endpoint - if (endpointSubnets.length > 0) { - this.createResolverEndpoint(endpointItem, vpcId, endpointSubnets); - } - } - } - } - } - } - } - - /** - * Create a Network Firewall in the specified VPC and subnets. - * - * @param firewallItem - * @param vpcId - * @param subnets - * @param owningAccountId - * @returns - */ - private createNetworkFirewall( - firewallItem: NfwFirewallConfig, - vpcId: string, - subnets: string[], - firewallLogBucket: cdk.aws_s3.IBucket, - ): INetworkFirewall { - this.logger.info(`Add Network Firewall ${firewallItem.name} to VPC ${firewallItem.vpc}`); - - let nfw; - if (this.isManagedByAsea(AseaResourceType.NFW, firewallItem.name)) { - const nfwArn = this.getExternalResourceParameter( - this.getSsmPath(SsmResourceType.NFW, [firewallItem.vpc, firewallItem.name]), - ); - nfw = NetworkFirewall.fromAttributes(this, pascalCase(`${firewallItem.vpc}${firewallItem.name}NetworkFirewall`), { - firewallArn: nfwArn, - firewallName: firewallItem.name, - }); - } else { - // Fetch policy ARN - const policyArn = this.nfwPolicyMap.get(firewallItem.firewallPolicy); - if (!policyArn) { - this.logger.error(`Unable to locate Network Firewall policy ${firewallItem.firewallPolicy}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - // Create firewall - nfw = new NetworkFirewall(this, pascalCase(`${firewallItem.vpc}${firewallItem.name}NetworkFirewall`), { - firewallPolicyArn: policyArn, - name: firewallItem.name, - description: firewallItem.description, - subnets: subnets, - vpcId: vpcId, - deleteProtection: firewallItem.deleteProtection, - firewallPolicyChangeProtection: firewallItem.firewallPolicyChangeProtection, - subnetChangeProtection: firewallItem.subnetChangeProtection, - tags: firewallItem.tags ?? [], - }); - // Create SSM parameters - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${pascalCase(firewallItem.vpc) + pascalCase(firewallItem.name)}FirewallArn`), - parameterName: this.getSsmPath(SsmResourceType.NFW, [firewallItem.vpc, firewallItem.name]), - stringValue: nfw.firewallArn, - }); - } - - // Add logging configurations - const destinationConfigs: cdk.aws_networkfirewall.CfnLoggingConfiguration.LogDestinationConfigProperty[] = []; - for (const logItem of firewallItem.loggingConfiguration ?? []) { - if (logItem.destination === 'cloud-watch-logs') { - // Create log group and log configuration - this.logger.info(`Add CloudWatch ${logItem.type} logs for Network Firewall ${firewallItem.name}`); - const logGroup = new cdk.aws_logs.LogGroup(this, pascalCase(`${firewallItem.name}${logItem.type}LogGroup`), { - encryptionKey: this.cloudwatchKey, - retention: this.logRetention, - }); - destinationConfigs.push({ - logDestination: { - logGroup: logGroup.logGroupName, - }, - logDestinationType: 'CloudWatchLogs', - logType: logItem.type, - }); - } - - if (logItem.destination === 's3') { - this.logger.info(`Add S3 ${logItem.type} logs for Network Firewall ${firewallItem.name}`); - - destinationConfigs.push({ - logDestination: { - bucketName: firewallLogBucket.bucketName, - prefix: 'firewall', - }, - logDestinationType: 'S3', - logType: logItem.type, - }); - } - } - - if (!this.isManagedByAsea(AseaResourceType.NFW, firewallItem.name)) { - // Add logging configuration - const config = { - logDestinationConfigs: destinationConfigs, - }; - nfw.addLogging(config); - } - - return nfw; - } - - /** - * Function to get route table id - * @param vpcName string - * @param routeTableName string - * @returns - */ - private getRouteTableId(vpcName: string, routeTableName: string): string { - const routeTableKey = `${vpcName}_${routeTableName}`; - const routeTableId = this.routeTableMap.get(routeTableKey); - - if (!routeTableId) { - this.logger.error(`Route Table ${routeTableName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return routeTableId; - } - - /** - * Function to get S3 and DynamoDB route table ids - * @param routeTableItem {@link RouteTableConfig} - * @param routeTableId string - */ - private getS3DynamoDbRouteTableIds( - routeTableItem: RouteTableConfig, - routeTableId: string, - s3EndpointRouteTables: string[], - dynamodbEndpointRouteTables: string[], - ) { - for (const routeTableEntryItem of routeTableItem.routes ?? []) { - // Route: S3 Gateway Endpoint - if (routeTableEntryItem.target === 's3') { - if (!s3EndpointRouteTables.find(item => item === routeTableId)) { - s3EndpointRouteTables.push(routeTableId); - } - } - - // Route: DynamoDb Gateway Endpoint - if (routeTableEntryItem.target === 'dynamodb') { - if (!dynamodbEndpointRouteTables.find(item => item === routeTableId)) { - dynamodbEndpointRouteTables.push(routeTableId); - } - } - } - } - - /** - * Create gateway endpoints for the specified VPC. - * - * @param vpcItem - * @param vpc - */ - private createGatewayEndpoints(vpcItem: VpcConfig | VpcTemplatesConfig, vpcId: string): void { - // Create a list of related route tables that will need to be updated with the gateway routes - const s3EndpointRouteTables: string[] = []; - const dynamodbEndpointRouteTables: string[] = []; - for (const routeTableItem of vpcItem.routeTables ?? []) { - const routeTableId = this.getRouteTableId(vpcItem.name, routeTableItem.name); - - this.getS3DynamoDbRouteTableIds(routeTableItem, routeTableId, s3EndpointRouteTables, dynamodbEndpointRouteTables); - } - - // - // Add Gateway Endpoints (AWS Services) - // - for (const gatewayEndpointItem of vpcItem.gatewayEndpoints?.endpoints ?? []) { - this.logger.info(`Adding Gateway Endpoint for ${gatewayEndpointItem.service}`); - if (this.isManagedByAsea(AseaResourceType.VPC_ENDPOINT, `${vpcItem.name}/${gatewayEndpointItem.service}`)) { - this.logger.info(`Endpoint "${gatewayEndpointItem.service}" for VPC "${vpcItem.name}" is managed externally`); - continue; - } - if (gatewayEndpointItem.service === 's3') { - new VpcEndpoint(this, pascalCase(`${vpcItem.name}Vpc`) + pascalCase(gatewayEndpointItem.service), { - vpcId, - vpcEndpointType: VpcEndpointType.GATEWAY, - service: gatewayEndpointItem.service, - routeTables: s3EndpointRouteTables, - policyDocument: this.createVpcEndpointPolicy(vpcItem, gatewayEndpointItem, true), - serviceName: gatewayEndpointItem.serviceName ?? undefined, - }); - } - if (gatewayEndpointItem.service === 'dynamodb') { - new VpcEndpoint(this, pascalCase(`${vpcItem.name}Vpc`) + pascalCase(gatewayEndpointItem.service), { - vpcId, - vpcEndpointType: VpcEndpointType.GATEWAY, - service: gatewayEndpointItem.service, - routeTables: dynamodbEndpointRouteTables, - policyDocument: this.createVpcEndpointPolicy(vpcItem, gatewayEndpointItem, true), - serviceName: gatewayEndpointItem.serviceName ?? undefined, - }); - } - } - } - - /** - * Function to get Subnet ids for each interface endpoints - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @returns string[] - */ - private getInterfaceEndpointsSubnetIds(vpcItem: VpcConfig | VpcTemplatesConfig): string[] { - // Create list of subnet IDs for each interface endpoint - const subnets: string[] = []; - for (const subnetItem of vpcItem.interfaceEndpoints?.subnets ?? []) { - const subnetKey = `${vpcItem.name}_${subnetItem}`; - const subnet = this.subnetMap.get(subnetKey); - if (subnet) { - subnets.push(subnet); - } else { - this.logger.error(`Attempting to add interface endpoints to subnet that does not exist (${subnetItem})`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - return subnets; - } - - /** - * Function to get interface endpoint security group properties - * @param endpointItem {@link InterfaceEndpointServiceConfig} - * @param securityGroupMap Map - * @returns - */ - private getInterfaceEndpointSecurityGroupItem( - endpointItem: InterfaceEndpointServiceConfig, - securityGroupMap: Map, - ): { endpointSg?: SecurityGroup; port: number; trafficType: string } { - let endpointSg: SecurityGroup | undefined; - let port: number; - let trafficType: string; - if (endpointItem.service !== 'cassandra') { - endpointSg = securityGroupMap.get('https'); - port = 443; - trafficType = 'https'; - } else { - endpointSg = securityGroupMap.get('cassandra'); - port = 9142; - trafficType = 'cassandra'; - } - - return { endpointSg: endpointSg, port: port, trafficType: trafficType }; - } - - /** - * Function to create or get interface endpoint security group - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param endpointItem {@link InterfaceEndpointServiceConfig} - * @param securityGroupMap Map - * @param vpcId string - * @returns SecurityGroup {@link SecurityGroup} - */ - private createOrGetInterfaceEndpointSecurityGroup( - vpcItem: VpcConfig | VpcTemplatesConfig, - endpointItem: InterfaceEndpointServiceConfig, - securityGroupMap: Map, - vpcId: string, - ): SecurityGroup { - if (endpointItem.securityGroup) { - return getSecurityGroup(this.securityGroupEndpointMap, vpcItem.name, endpointItem.securityGroup) as SecurityGroup; - } - const interfaceEndpointSecurityGroupItem = this.getInterfaceEndpointSecurityGroupItem( - endpointItem, - securityGroupMap, - ); - if (!interfaceEndpointSecurityGroupItem.endpointSg) { - const ingressRules: SecurityGroupIngressRuleProps[] = []; - const egressRules: SecurityGroupEgressRuleProps[] = []; - let includeNagSuppression = false; - - // Add ingress and egress CIDRs - for (const ingressCidr of vpcItem.interfaceEndpoints?.allowedCidrs || ['0.0.0.0/0']) { - this.logger.info( - `Interface endpoints: adding ingress cidr ${ingressCidr} TCP:${interfaceEndpointSecurityGroupItem.port}`, - ); - ingressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.TCP, - fromPort: interfaceEndpointSecurityGroupItem.port, - toPort: interfaceEndpointSecurityGroupItem.port, - cidrIp: ingressCidr, - }); - - // AwsSolutions-EC23: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access. - // rule suppression with evidence for this permission. - if (ingressCidr === '0.0.0.0/0') { - includeNagSuppression = true; - } - } - - // Adding Egress '127.0.0.1/32' to avoid default Egress rule - egressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.ALL, - cidrIp: '127.0.0.1/32', - }); - - // Create Security Group - this.logger.info( - `Adding Security Group to VPC ${vpcItem.name} for interface endpoints -- ${interfaceEndpointSecurityGroupItem.trafficType} traffic`, - ); - const securityGroup = new SecurityGroup( - this, - pascalCase(`${vpcItem.name}Vpc${interfaceEndpointSecurityGroupItem.trafficType}EpSecurityGroup`), - { - securityGroupName: `interface_ep_${interfaceEndpointSecurityGroupItem.trafficType}_sg`, - securityGroupEgress: egressRules, - securityGroupIngress: ingressRules, - description: `Security group for interface endpoints -- ${interfaceEndpointSecurityGroupItem.trafficType} traffic`, - vpcId, - }, - ); - interfaceEndpointSecurityGroupItem.endpointSg = securityGroup; - securityGroupMap.set(interfaceEndpointSecurityGroupItem.trafficType, securityGroup); - - // AwsSolutions-EC23: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access. - // rule suppression with evidence for this permission. - if (includeNagSuppression) { - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/${pascalCase(vpcItem.name)}Vpc${ - interfaceEndpointSecurityGroupItem.trafficType - }EpSecurityGroup`, - [ - { - id: 'AwsSolutions-EC23', - reason: 'Allowed access for interface endpoints', - }, - ], - ); - } - } - - return interfaceEndpointSecurityGroupItem.endpointSg; - } - - /** - * Create interface endpoints for the specified VPC. - * - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param vpcId string - */ - private createInterfaceEndpoints(vpcItem: VpcConfig | VpcTemplatesConfig, vpcId: string): void { - // - // Add Interface Endpoints (AWS Services) - // - // Create list of subnet IDs for each interface endpoint - const subnets = this.getInterfaceEndpointsSubnetIds(vpcItem); - - // Create the interface endpoint - const securityGroupMap = new Map(); - const privateDnsValue = !vpcItem.interfaceEndpoints?.central ?? true; - - for (const endpointItem of vpcItem.interfaceEndpoints?.endpoints ?? []) { - if (this.isManagedByAsea(AseaResourceType.VPC_ENDPOINT, `${vpcItem.name}/${endpointItem.service}`)) { - this.logger.info( - `Interface Endpoint "${endpointItem.service}" for VPC "${vpcItem.name}" is managed externally`, - ); - continue; - } - this.logger.info(`Adding Interface Endpoint for ${endpointItem.service}`); - - // Create or get interface endpoint security group - const endpointSg = this.createOrGetInterfaceEndpointSecurityGroup(vpcItem, endpointItem, securityGroupMap, vpcId); - - // Create the interface endpoint - const endpoint = new VpcEndpoint(this, `${pascalCase(vpcItem.name)}Vpc${pascalCase(endpointItem.service)}Ep`, { - vpcId, - vpcEndpointType: VpcEndpointType.INTERFACE, - service: endpointItem.service, - serviceName: endpointItem.serviceName, - subnets, - securityGroups: [endpointSg], - privateDnsEnabled: privateDnsValue, - policyDocument: this.createVpcEndpointPolicy(vpcItem, endpointItem), - }); - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${vpcItem.name}${endpointItem.service}Dns`), - parameterName: this.getSsmPath(SsmResourceType.ENDPOINT_DNS, [vpcItem.name, endpointItem.service]), - stringValue: endpoint.dnsName!, - }); - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${vpcItem.name}${endpointItem.service}Phz`), - parameterName: this.getSsmPath(SsmResourceType.ENDPOINT_ZONE_ID, [vpcItem.name, endpointItem.service]), - stringValue: endpoint.hostedZoneId!, - }); - } - } - - /** - * Function of validate inbound endpoint - * @param endpointItem {@link ResolverEndpointConfig} - * - * @remarks - * Validate there are no rules associated with an inbound endpoint - */ - private validateInboundEndpoint(endpointItem: ResolverEndpointConfig) { - if (endpointItem.type === 'INBOUND' && endpointItem.rules) { - this.logger.error('Route 53 Resolver inbound endpoints cannot have rules.'); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to prepare route 53 resolver endpoint security group ingress rules - * @param endpointItem {@link ResolverEndpointConfig} - * @param ingressRules {@link SecurityGroupIngressRuleProps}[] - * @returns - */ - private prepareRoute53ResolverEndpointSecurityGroupIngressRules( - endpointItem: ResolverEndpointConfig, - ingressRules: SecurityGroupIngressRuleProps[], - ): boolean { - let includeNagSuppression = false; - for (const ingressCidr of endpointItem.allowedCidrs || ['0.0.0.0/0']) { - const port = 53; - - this.logger.info(`Route 53 resolver: adding ingress cidr ${ingressCidr} TCP:${port}`); - ingressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.TCP, - fromPort: port, - toPort: port, - cidrIp: ingressCidr, - }); - - this.logger.info(`Route 53 resolver: adding ingress cidr ${ingressCidr} UDP:${port}`); - ingressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.UDP, - fromPort: port, - toPort: port, - cidrIp: ingressCidr, - }); - - if (ingressCidr === '0.0.0.0/0') { - // AwsSolutions-EC23: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access. - // rule suppression with evidence for this permission. - includeNagSuppression = true; - } - } - - return includeNagSuppression; - } - - /** - * Prepare non standard port map of ip and port - * @param endpointItem {@link ResolverEndpointConfig} - * @param portMap Map - */ - private prepareNonStandardPortMap(endpointItem: ResolverEndpointConfig, portMap: Map) { - // Check if non-standard ports exist in rules - for (const ruleItem of endpointItem.rules ?? []) { - for (const targetItem of ruleItem.targetIps ?? []) { - if (targetItem.port) { - portMap.set(targetItem.ip, targetItem.port); - } - } - } - } - - /** - * Function to prepare route 53 resolver endpoint security group egress rules - * @param endpointItem {@link ResolverEndpointConfig} - * @param egressRules {@link SecurityGroupEgressRuleProps}[] - * @param portMap Map, - */ - private prepareRoute53ResolverEndpointSecurityGroupEgressRules( - endpointItem: ResolverEndpointConfig, - egressRules: SecurityGroupEgressRuleProps[], - portMap: Map, - ) { - for (const egressCidr of endpointItem.allowedCidrs || ['0.0.0.0/0']) { - let port = 53; - const nonStandardPort = portMap.get(egressCidr.split('/')[0]); //Split at the prefix to match target IP - - // Check if mapping includes non-standard port - if (nonStandardPort) { - port = +nonStandardPort; - } - - this.logger.info(`Route 53 resolver: adding egress cidr ${egressCidr} TCP:${port}`); - egressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.TCP, - fromPort: port, - toPort: port, - cidrIp: egressCidr, - }); - - this.logger.info(`Route 53 resolver: adding egress cidr ${egressCidr} UDP:${port}`); - egressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.UDP, - fromPort: port, - toPort: port, - cidrIp: egressCidr, - }); - } - } - - // - // Create Route 53 Resolver endpoints - // - private createResolverEndpoint(endpointItem: ResolverEndpointConfig, vpcId: string, subnets: string[]): void { - // Validate there are no rules associated with an inbound endpoint - this.validateInboundEndpoint(endpointItem); - - // Begin creation of Route 53 resolver endpoint - this.logger.info(`Add Route 53 Resolver ${endpointItem.type} endpoint ${endpointItem.name}`); - const ingressRules: SecurityGroupIngressRuleProps[] = []; - const egressRules: SecurityGroupEgressRuleProps[] = []; - let includeNagSuppression = false; - - if (endpointItem.type === 'INBOUND') { - // Prepare route 53 resolver endpoint security group ingress rules - includeNagSuppression = this.prepareRoute53ResolverEndpointSecurityGroupIngressRules(endpointItem, ingressRules); - - // Adding Egress '127.0.0.1/32' to avoid default Egress rule - egressRules.push({ - ipProtocol: cdk.aws_ec2.Protocol.ALL, - cidrIp: '127.0.0.1/32', - }); - } else { - // Check if non-standard ports exist in rules - const portMap = new Map(); - this.prepareNonStandardPortMap(endpointItem, portMap); - - // Prepare egress rules - this.prepareRoute53ResolverEndpointSecurityGroupEgressRules(endpointItem, egressRules, portMap); - } - - // Create security group - this.logger.info(`Adding Security Group for Route 53 Resolver endpoint ${endpointItem.name}`); - const securityGroup = new SecurityGroup(this, pascalCase(`${endpointItem.name}EpSecurityGroup`), { - securityGroupName: `ep_${endpointItem.name}_sg`, - securityGroupEgress: egressRules, - securityGroupIngress: ingressRules, - description: `AWS Route 53 Resolver endpoint - ${endpointItem.name}`, - vpcId, - }); - - // AwsSolutions-EC23: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access. - // rule suppression with evidence for this permission. - if (includeNagSuppression) { - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/${pascalCase(endpointItem.name)}EpSecurityGroup`, - [ - { - id: 'AwsSolutions-EC23', - reason: 'Allowed access for interface endpoints', - }, - ], - ); - } - - // Create resolver endpoint - - const endpoint = new ResolverEndpoint(this, `${pascalCase(endpointItem.name)}ResolverEndpoint`, { - direction: endpointItem.type, - ipAddresses: subnets, - name: endpointItem.name, - securityGroupIds: [securityGroup.securityGroupId], - tags: endpointItem.tags ?? [], - }); - this.ssmParameters.push({ - logicalId: pascalCase(`SsmParam${endpointItem.name}ResolverEndpoint`), - parameterName: this.getSsmPath(SsmResourceType.RESOLVER_ENDPOINT, [endpointItem.name]), - stringValue: endpoint.endpointId, - }); - } - - /** - * Creates a cdk.aws_iam.PolicyDocument for the given endpoint. - * @param vpcItem - * @param endpointItem - * @param isGatewayEndpoint - * @returns - */ - private createVpcEndpointPolicy( - vpcItem: VpcConfig | VpcTemplatesConfig, - endpointItem: GatewayEndpointServiceConfig | InterfaceEndpointServiceConfig, - isGatewayEndpoint?: boolean, - ): cdk.aws_iam.PolicyDocument | undefined { - // See https://docs.aws.amazon.com/vpc/latest/privatelink/integrated-services-vpce-list.html - // for the services that integrates with AWS PrivateLink, but does not support VPC endpoint policies - const policiesUnsupported = [ - 'appmesh-envoy-management', - 'appstream.api', - 'appstream.streaming', - 'cloudtrail', - 'codeguru-profiler', - 'codeguru-reviewer', - 'codepipeline', - 'datasync', - 'deviceadvisor.iot', - 'ebs', - 'elastic-inference.runtime', - 'iot.data', - 'iotwireless.api', - 'lorawan.cups', - 'lorawan.lns', - 'iotsitewise.api', - 'iotsitewise.data', - 'macie2', - 'aps', - 'aps-workspaces', - 'awsconnector', - 'sms', - 'sms-fips', - 'email-smtp', - 'storagegateway', - 'transfer', - 'transfer.server', - ]; - - if (policiesUnsupported.includes(endpointItem.service)) { - return undefined; - } - - // by default, if nothing is specified policy is applied - if (endpointItem.applyPolicy ?? true) { - // Identify if custom policy is specified, create custom or default policy - let policyName: string | undefined; - let policyDocument: cdk.aws_iam.PolicyDocument | undefined = undefined; - if (endpointItem.policy) { - this.logger.info(`Add custom endpoint policy for ${endpointItem.service}`); - policyName = endpointItem.policy; - } else if (!endpointItem.policy && isGatewayEndpoint) { - this.logger.info(`Add default endpoint policy for gateway endpoint ${endpointItem.service}`); - policyName = vpcItem.gatewayEndpoints?.defaultPolicy; - } else { - this.logger.info(`Add default endpoint policy for interface endpoint ${endpointItem.service}`); - policyName = vpcItem.interfaceEndpoints?.defaultPolicy; - } - - // Find matching endpoint policy item - if (!policyName) { - this.logger.error(`Create endpoint policy: unable to set a policy name.`); - throw new Error(`Configuration validation failed at runtime.`); - } - const policyItem = this.props.networkConfig.endpointPolicies.filter(item => item.name === policyName); - - // Set location and fetch document - const document = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, policyItem[0].document), - false, - this.organizationId, - ), - ); - - policyDocument = cdk.aws_iam.PolicyDocument.fromJson(document); - return policyDocument; - } else { - return undefined; - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/acm-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/acm-resources.ts deleted file mode 100644 index 1fc7eed..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/acm-resources.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CertificateConfig } from '@aws-accelerator/config'; -import { Certificate } from '@aws-accelerator/constructs'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel, NetworkStack } from '../network-stack'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; - -export class AcmResources { - public readonly certificateMap: Map; - private stack: NetworkStack; - - constructor(networkStack: NetworkStack, props: AcceleratorStackProps) { - this.stack = networkStack; - - // Create certificates - this.certificateMap = this.createCertificates(props); - } - - /** - * Create ACM certificates - check whether ACM should be deployed - */ - private createCertificates(props: AcceleratorStackProps): Map { - const certificateMap = new Map(); - this.stack.addLogs(LogLevel.INFO, 'Evaluating AWS Certificate Manager certificates.'); - for (const certificate of props.networkConfig.certificates ?? []) { - if (!this.stack.isIncluded(certificate.deploymentTargets)) { - this.stack.addLogs( - LogLevel.INFO, - `Account (${cdk.Stack.of(this.stack).account}) ACM certificate ${certificate.name} excluded.`, - ); - continue; - } - this.stack.addLogs( - LogLevel.INFO, - `Account (${cdk.Stack.of(this.stack).account}) should be included, deploying ACM certificate ${ - certificate.name - }.`, - ); - const certificateResource = this.createAcmCertificates(certificate, props); - certificateMap.set(certificate.name, certificateResource); - } - - return certificateMap; - } - /** - * Create ACM certificates - */ - private createAcmCertificates(certificate: CertificateConfig, props: AcceleratorStackProps): Certificate { - const resourceName = pascalCase(`${certificate.name}`); - - const acmCertificate = new Certificate(this.stack, resourceName, { - parameterName: this.stack.getSsmPath(SsmResourceType.ACM_CERT, [certificate.name]), - type: certificate.type, - privKey: certificate.privKey, - cert: certificate.cert, - chain: certificate.chain, - validation: certificate.validation, - domain: certificate.domain, - san: certificate.san, - homeRegion: props.globalConfig.homeRegion, - assetFunctionRoleName: this.stack.acceleratorResourceNames.roles.assetFunctionRoleName, - assetBucketName: `${ - this.stack.acceleratorResourceNames.bucketPrefixes.assets - }-${props.accountsConfig.getManagementAccountId()}-${props.globalConfig.homeRegion}`, - cloudWatchLogsKmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${resourceName}/AssetsRole/Policy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Policy permissions are part of managed role and rest is to get access from s3 bucket', - }, - ], - ); - - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${resourceName}/Custom::CreateAcmCerts/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Policy permissions are part cdk provider framework', - }, - ], - ); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${resourceName}/Function/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - ); - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${resourceName}/Custom::CreateAcmCerts/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'IAM Role created by custom resource framework', - }, - ], - ); - - return acmCertificate; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/dhcp-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/dhcp-resources.ts deleted file mode 100644 index 1bad979..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/dhcp-resources.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DhcpOptions } from '@aws-accelerator/constructs'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel, NetworkStack } from '../network-stack'; - -export class DhcpResources { - public readonly dhcpOptionsIds: Map; - private stack: NetworkStack; - - constructor(networkStack: NetworkStack, props: AcceleratorStackProps) { - this.stack = networkStack; - - // Create DHCP options sets - this.dhcpOptionsIds = this.createDhcpOptions(props); - } - - /** - * Create DHCP options sets for the current stack context - * @param props - * @returns - */ - private createDhcpOptions(props: AcceleratorStackProps): Map { - const dhcpOptionsIds = new Map(); - - for (const dhcpItem of props.networkConfig.dhcpOptions ?? []) { - // Check if the set belongs in this account/region - const accountIds = dhcpItem.accounts.map(item => { - return props.accountsConfig.getAccountId(item); - }); - const regions = dhcpItem.regions.map(item => { - return item.toString(); - }); - - if (this.stack.isTargetStack(accountIds, regions)) { - this.stack.addLogs(LogLevel.INFO, `Adding DHCP options set ${dhcpItem.name}`); - - const optionSet = new DhcpOptions(this.stack, pascalCase(`${dhcpItem.name}DhcpOpts`), { - name: dhcpItem.name, - domainName: dhcpItem.domainName, - domainNameServers: dhcpItem.domainNameServers, - netbiosNameServers: dhcpItem.netbiosNameServers, - netbiosNodeType: dhcpItem.netbiosNodeType, - ntpServers: dhcpItem.ntpServers, - tags: dhcpItem.tags ?? [], //Default passing an empty array for name tag - }); - dhcpOptionsIds.set(optionSet.name, optionSet.dhcpOptionsId); - } - } - return dhcpOptionsIds; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/ipam-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/ipam-resources.ts deleted file mode 100644 index 963e4be..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/ipam-resources.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { VpcConfig, VpcTemplatesConfig } from '@aws-accelerator/config'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class IpamResources { - private stack: NetworkVpcStack; - public readonly role?: cdk.aws_iam.Role; - - constructor(networkVpcStack: NetworkVpcStack, homeRegion: string, orgId?: string) { - this.stack = networkVpcStack; - - this.role = this.createGetIpamCidrRole(this.stack.vpcResources, homeRegion, orgId); - } - - /** - * Create cross-account role to allow custom resource to describe IPAM subnets - * @param vpcResources - * @param homeRegion - * @param acceleratorPrefix - * @param orgId - * @returns - */ - private createGetIpamCidrRole(vpcResources: (VpcConfig | VpcTemplatesConfig)[], homeRegion: string, orgId?: string) { - const vpcAccountIds = []; - for (const vpcItem of vpcResources) { - vpcAccountIds.push(...this.stack.getVpcAccountIds(vpcItem)); - } - const accountIds = [...new Set(vpcAccountIds)]; - if (cdk.Stack.of(this.stack).region === homeRegion && accountIds.includes(cdk.Stack.of(this.stack).account)) { - const role = new cdk.aws_iam.Role(this.stack, `GetIpamCidrRole`, { - roleName: this.stack.acceleratorResourceNames.roles.ipamSubnetLookup, - assumedBy: this.stack.getOrgPrincipals(orgId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeSubnets', 'ssm:GetParameter'], - resources: ['*'], - }), - ], - }), - }, - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressions(role, [ - { id: 'AwsSolutions-IAM5', reason: 'Allow read role to get CIDRs from dynamic IPAM resources.' }, - ]); - - return role; - } - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/load-balancer-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/load-balancer-resources.ts deleted file mode 100644 index 7ef8314..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/load-balancer-resources.ts +++ /dev/null @@ -1,483 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - ApplicationLoadBalancerConfig, - AseaResourceType, - GwlbConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { - ApplicationLoadBalancer, - GatewayLoadBalancer, - NetworkLoadBalancer, - PutSsmParameter, - SecurityGroup, - SsmParameterProps, - Subnet, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { getSubnet } from '../utils/getter-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class LoadBalancerResources { - public readonly albMap: Map; - public readonly gwlbMap: Map; - public readonly nlbMap: Map; - private stack: NetworkVpcStack; - - constructor( - networkVpcStack: NetworkVpcStack, - subnetMap: Map, - securityGroupMap: Map, - props: AcceleratorStackProps, - ) { - this.stack = networkVpcStack; - - // Create GWLB resources - this.gwlbMap = this.createGwlbs(this.stack.vpcsInScope, subnetMap, props); - - // Create ALBs - this.albMap = this.createApplicationLoadBalancers(this.stack.vpcsInScope, subnetMap, securityGroupMap, props); - - // Create NLBs - this.nlbMap = this.createNetworkLoadBalancers(this.stack.vpcsInScope, subnetMap, props); - } - - /** - * Set allowed account principals for a given GWLB item - * @param loadBalancerItem - * @param props - * @returns - */ - private setGwlbAllowedPrincipals(loadBalancerItem: GwlbConfig, props: AcceleratorStackProps): string[] { - const allowedPrincipals: string[] = []; - - // Set account principals - for (const endpointItem of loadBalancerItem.endpoints) { - const accountId = props.accountsConfig.getAccountId(endpointItem.account); - if (!allowedPrincipals.includes(accountId)) { - allowedPrincipals.push(accountId); - } - } - return allowedPrincipals; - } - - /** - * Create gateway load balancers - * @param vpcResources - * @param subnetMap - * @param props - * @returns - */ - private createGwlbs( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - subnetMap: Map, - props: AcceleratorStackProps, - ): Map { - const gwlbMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const loadBalancerItem of props.networkConfig.centralNetworkServices?.gatewayLoadBalancers ?? []) { - if (vpcItem.name === loadBalancerItem.vpc) { - const allowedPrincipals = this.setGwlbAllowedPrincipals(loadBalancerItem, props); - const gwlb = this.createGwlb(vpcItem, loadBalancerItem, subnetMap, allowedPrincipals); - gwlbMap.set(loadBalancerItem.name, gwlb); - this.setGwlbEndpointParameters(gwlb, loadBalancerItem, allowedPrincipals, vpcItem.name); - } - } - } - return gwlbMap; - } - - /** - * Create gateway load balancer item - * @param vpcItem - * @param loadBalancerItem - * @param subnetMap - * @param allowedPrincipals - * @returns - */ - private createGwlb( - vpcItem: VpcConfig | VpcTemplatesConfig, - loadBalancerItem: GwlbConfig, - subnetMap: Map, - allowedPrincipals: string[], - ): GatewayLoadBalancer { - // Set subnets - const subnets: string[] = []; - for (const subnetItem of loadBalancerItem.subnets) { - const subnet = getSubnet(subnetMap, vpcItem.name, subnetItem) as Subnet; - - if (!subnets.includes(subnet.subnetId)) { - subnets.push(subnet.subnetId); - } - } - - // Create GWLB - this.stack.addLogs( - LogLevel.INFO, - `Add Gateway Load Balancer ${loadBalancerItem.name} to VPC ${loadBalancerItem.vpc}`, - ); - const loadBalancer = new GatewayLoadBalancer( - this.stack, - `${pascalCase(loadBalancerItem.name)}GatewayLoadBalancer`, - { - name: loadBalancerItem.name, - allowedPrincipals, - subnets, - crossZoneLoadBalancing: loadBalancerItem.crossZoneLoadBalancing, - deletionProtection: loadBalancerItem.deletionProtection, - tags: loadBalancerItem.tags, - }, - ); - - // Add SSM parameters - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(loadBalancerItem.name)}GwlbServiceId`), - parameterName: this.stack.getSsmPath(SsmResourceType.GWLB_SERVICE, [loadBalancerItem.name]), - stringValue: loadBalancer.endpointServiceId, - }); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(loadBalancerItem.name)}GwlbArn`), - parameterName: this.stack.getSsmPath(SsmResourceType.GWLB_ARN, [loadBalancerItem.name]), - stringValue: loadBalancer.loadBalancerArn, - }); - - // AwsSolutions-ELB2: The ELB does not have access logs enabled. - NagSuppressions.addResourceSuppressions(loadBalancer, [ - { id: 'AwsSolutions-ELB2', reason: 'Gateway Load Balancers do not support access logging.' }, - ]); - - return loadBalancer; - } - - /** - * Create gateway load balancer parameter stores for applicable accounts. - * @param gwlb Gateway Load Balancer config - * @param loadBalancerItem Gateway Load Balancer - * @param allowedPrincipals Allowed Principals - * @param vpcName Name of the VPC - */ - private setGwlbEndpointParameters( - gwlb: GatewayLoadBalancer, - loadBalancerItem: GwlbConfig, - allowedPrincipals: string[], - vpcName: string, - ) { - const accountIds: string[] = []; - - allowedPrincipals.forEach(account => { - if (account !== cdk.Stack.of(this.stack).account) { - accountIds.push(account); - } - }); - - const parameters = this.setCrossAccountGwlbSsmParameters(gwlb, loadBalancerItem); - if (accountIds.length > 0 && parameters.length > 0) { - new PutSsmParameter(this.stack, pascalCase(`${loadBalancerItem.name}-${vpcName}-SharedSsmParameters`), { - accountIds, - region: cdk.Stack.of(this.stack).region, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - parameters, - invokingAccountId: this.stack.account, - acceleratorPrefix: this.stack.acceleratorPrefix, - }); - } - } - - /** - * Returns an array of SSM parameters for cross-account Gateway Load Balancer Service Endpoints - * @param gwlb Gateway Load Balancer config - * @param loadBalancerItem Gateway Load Balancer - * @returns SsmParameterProps[] - */ - private setCrossAccountGwlbSsmParameters(gwlb: GatewayLoadBalancer, loadBalancerItem: GwlbConfig) { - const ssmParameters: SsmParameterProps[] = []; - - ssmParameters.push({ - name: this.stack.getSsmPath(SsmResourceType.GWLB_SERVICE, [loadBalancerItem.name]), - value: gwlb.endpointServiceId, - }); - return [...new Set(ssmParameters)]; - } - - /** - * Validate subnet presence for given ALB - * @param subnetIds string[] - * @param albName string - */ - private validateAlbSubnetId(subnetIds: string[], albName: string) { - if (subnetIds.length === 0) { - this.stack.addLogs(LogLevel.ERROR, `Could not find subnets for ALB Item ${albName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to create Application Load Balancer - * @param options - */ - private createApplicationLoadBalancer(options: { - vpcName: string; - albItem: ApplicationLoadBalancerConfig; - accessLogsBucketName: string; - subnetIds: string[]; - securityGroupIds: string[]; - albMap: Map; - subnetMap: Map; - subnetLookups: (Subnet | undefined)[]; - securityGroupLookups: (SecurityGroup | undefined)[]; - props: AcceleratorStackProps; - }) { - const alb = new ApplicationLoadBalancer(this.stack, `${options.albItem.name}-${options.vpcName}`, { - name: options.albItem.name, - ssmPrefix: options.props.prefixes.ssmParamName, - subnets: options.subnetIds, - securityGroups: options.securityGroupIds ?? undefined, - scheme: options.albItem.scheme ?? 'internal', - accessLogsBucket: options.accessLogsBucketName, - attributes: options.albItem.attributes ?? undefined, - }); - options.albMap.set(`${options.vpcName}_${options.albItem.name}`, alb); - - for (const subnet of options.albItem.subnets || []) { - const subnetLookup = options.subnetMap.get(`${options.vpcName}_${subnet}`); - if (subnetLookup) { - alb.node.addDependency(subnetLookup); - } - } - - for (const subnet of options.subnetLookups || []) { - if (subnet) { - alb.node.addDependency(subnet); - } - } - - for (const securityGroup of options.securityGroupLookups || []) { - if (securityGroup) { - alb.node.addDependency(securityGroup); - } - } - - this.stack.addSsmParameter({ - logicalId: `${options.albItem.name}-${options.vpcName}-ssm`, - parameterName: this.stack.getSsmPath(SsmResourceType.ALB, [options.vpcName, options.albItem.name]), - stringValue: alb.applicationLoadBalancerArn, - }); - } - - /** - * Create application load balancers - * @param vpcResources - * @param subnetMap - * @param securityGroupMap - * @param props - * @returns - */ - private createApplicationLoadBalancers( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - subnetMap: Map, - securityGroupMap: Map, - props: AcceleratorStackProps, - ): Map { - const albMap = new Map(); - const accessLogsBucketName = `${ - this.stack.acceleratorResourceNames.bucketPrefixes.elbLogs - }-${props.accountsConfig.getLogArchiveAccountId()}-${cdk.Stack.of(this.stack).region}`; - - for (const vpcItem of vpcResources) { - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers || []) { - if (this.stack.isManagedByAsea(AseaResourceType.APPLICATION_LOAD_BALANCER, `${albItem.name}`)) { - continue; - } - // Logic to only create Application Load Balancers that don't include the shareTargets property - if (!albItem.shareTargets) { - const subnetLookups = albItem.subnets.map(subnetName => subnetMap.get(`${vpcItem.name}_${subnetName}`)); - const nonNullsubnets = subnetLookups.filter(subnet => subnet) as Subnet[]; - const subnetIds = nonNullsubnets.map(subnet => subnet.subnetId); - const securityGroupLookups = albItem.securityGroups.map(securityGroupName => - securityGroupMap.get(`${vpcItem.name}_${securityGroupName}`), - ); - const nonNullSecurityGroups = securityGroupLookups.filter(group => group) as SecurityGroup[]; - const securityGroupIds = nonNullSecurityGroups.map(securityGroup => securityGroup.securityGroupId); - - this.validateAlbSubnetId(subnetIds, albItem.name); - - // Create application load balancer - this.createApplicationLoadBalancer({ - vpcName: vpcItem.name, - albItem, - accessLogsBucketName, - subnetIds, - securityGroupIds, - albMap, - subnetMap, - subnetLookups, - securityGroupLookups, - props, - }); - } - } - } - return albMap; - } - - /** - * Function to create Network Load Balancer - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param accessLogsBucketName string - * @param nlbMap Map - * @param Map - * @param props {@link AcceleratorStackProps} - */ - private createNetworkLoadBalancer( - vpcItem: VpcConfig | VpcTemplatesConfig, - accessLogsBucketName: string, - nlbMap: Map, - subnetMap: Map, - props: AcceleratorStackProps, - ) { - for (const nlbItem of vpcItem.loadBalancers?.networkLoadBalancers || []) { - const subnetLookups = nlbItem.subnets.map(subnetName => subnetMap.get(`${vpcItem.name}_${subnetName}`)); - const nonNullsubnets = subnetLookups.filter(subnet => subnet) as Subnet[]; - const subnetIds = nonNullsubnets.map(subnet => subnet.subnetId); - if (subnetIds.length === 0) { - this.stack.addLogs(LogLevel.ERROR, `Could not find subnets for NLB Item ${nlbItem.name}`); - throw new Error(`Configuration validation failed at runtime.`); - } - const nlb = new NetworkLoadBalancer(this.stack, `${nlbItem.name}-${vpcItem.name}`, { - name: nlbItem.name, - ssmPrefix: props.prefixes.ssmParamName, - appName: `${nlbItem.name}-${vpcItem.name}-app`, - subnets: subnetIds, - vpcName: vpcItem.name, - scheme: nlbItem.scheme, - deletionProtection: nlbItem.deletionProtection, - crossZoneLoadBalancing: nlbItem.crossZoneLoadBalancing, - accessLogsBucket: accessLogsBucketName, - }); - nlbMap.set(`${vpcItem.name}_${nlbItem.name}`, nlb); - - for (const subnet of nlbItem.subnets || []) { - const subnetLookup = subnetMap.get(`${vpcItem.name}_${subnet}`); - if (subnetLookup) { - nlb.node.addDependency(subnetLookup); - } - } - - this.stack.addSsmParameter({ - logicalId: `${nlbItem.name}-${vpcItem.name}-ssm`, - parameterName: this.stack.getSsmPath(SsmResourceType.NLB, [vpcItem.name, nlbItem.name]), - stringValue: nlb.networkLoadBalancerArn, - }); - } - } - - /** - * Create network load balancers - * @param vpcResources - * @param subnetMap - * @param props - * @returns - */ - private createNetworkLoadBalancers( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - subnetMap: Map, - props: AcceleratorStackProps, - ) { - const nlbMap = new Map(); - - const accessLogsBucketName = `${ - this.stack.acceleratorResourceNames.bucketPrefixes.elbLogs - }-${props.accountsConfig.getLogArchiveAccountId()}-${cdk.Stack.of(this.stack).region}`; - - for (const vpcItem of vpcResources) { - // Set account IDs - const principals = this.setNlbPrincipalIds(vpcItem, props); - - this.createNetworkLoadBalancer(vpcItem, accessLogsBucketName, nlbMap, subnetMap, props); - - if ( - cdk.Stack.of(this.stack).region === props.globalConfig.homeRegion && - vpcItem.loadBalancers?.networkLoadBalancers && - vpcItem.loadBalancers?.networkLoadBalancers.length > 0 - ) { - new cdk.aws_iam.Role(this.stack, `GetNLBIPAddressLookup`, { - roleName: `${props.prefixes.accelerator}-GetNLBIPAddressLookup`, - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals!), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeNetworkInterfaces'], - resources: ['*'], - }), - ], - }), - }, - }); - - NagSuppressions.addResourceSuppressionsByPath(this.stack, `/${this.stack.stackName}/GetNLBIPAddressLookup`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific role arns.', - }, - ]); - } - } - return nlbMap; - } - - /** - * Set principal account IDs for a given VPC item - * @param vpcItem - * @param props - * @returns - */ - private setNlbPrincipalIds( - vpcItem: VpcConfig | VpcTemplatesConfig, - props: AcceleratorStackProps, - ): cdk.aws_iam.AccountPrincipal[] | void { - if (!vpcItem.loadBalancers?.networkLoadBalancers || vpcItem.loadBalancers.networkLoadBalancers.length === 0) { - return; - } - const vpcItemsWithTargetGroups = props.networkConfig.vpcs.filter( - vpcItem => vpcItem.targetGroups && vpcItem.targetGroups.length > 0, - ); - const vpcTemplatesWithTargetGroups = - props.networkConfig.vpcTemplates?.filter(vpcItem => vpcItem.targetGroups && vpcItem.targetGroups.length > 0) ?? - []; - const accountIdTargetsForVpcs = vpcItemsWithTargetGroups.map(vpcItem => - props.accountsConfig.getAccountId(vpcItem.account), - ); - const accountIdTargetsForVpcTemplates = - vpcTemplatesWithTargetGroups?.map(vpcTemplate => - this.stack.getAccountIdsFromDeploymentTargets(vpcTemplate.deploymentTargets), - ) ?? []; - const principalAccountIds = [...accountIdTargetsForVpcs, ...accountIdTargetsForVpcTemplates]; - principalAccountIds.push(cdk.Stack.of(this.stack).account); - const principalIds = [...new Set(principalAccountIds)]; - - return principalIds.map(accountId => new cdk.aws_iam.AccountPrincipal(accountId)) ?? undefined; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nacl-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nacl-resources.ts deleted file mode 100644 index 2eb94d9..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nacl-resources.ts +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - isNetworkType, - NetworkAclConfig, - NetworkAclSubnetSelection, - NonEmptyString, - VpcConfig, - VpcTemplatesConfig, - AseaResourceType, -} from '@aws-accelerator/config'; -import { NetworkAcl, Subnet, Vpc } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { LogLevel } from '../network-stack'; -import { getSubnet, getSubnetConfig, getVpc, getVpcConfig } from '../utils/getter-utils'; -import { isIpv6Cidr } from '../utils/validation-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class NaclResources { - public readonly naclMap: Map; - private stack: NetworkVpcStack; - - constructor(networkVpcStack: NetworkVpcStack, vpcMap: Map, subnetMap: Map) { - this.stack = networkVpcStack; - - // Create NACLs - this.naclMap = this.createNacls(this.stack.vpcsInScope, vpcMap, subnetMap); - } - - private createNacls( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - subnetMap: Map, - ): Map { - const naclMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const naclItem of vpcItem.networkAcls ?? []) { - // Retrieve VPC from map - const vpc = getVpc(vpcMap, vpcItem.name) as Vpc; - - // Create NACL - this.stack.addLogs(LogLevel.INFO, `Adding Network ACL ${naclItem.name} in VPC ${vpcItem.name}`); - - const networkAcl = new NetworkAcl( - this.stack, - `${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}Nacl`, - { - networkAclName: naclItem.name, - vpc, - tags: naclItem.tags, - }, - ); - naclMap.set(`${vpcItem.name}_${naclItem.name}`, networkAcl); - - // Suppression for AwsSolutions-VPC3: A Network ACL or Network ACL entry has been implemented. - NagSuppressions.addResourceSuppressions( - networkAcl, - [{ id: 'AwsSolutions-VPC3', reason: 'NACL added to VPC' }], - true, - ); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name)}${pascalCase(naclItem.name)}Nacl`), - parameterName: this.stack.getSsmPath(SsmResourceType.NACL, [vpcItem.name, naclItem.name]), - stringValue: networkAcl.networkAclId, - }); - - // Create subnet associations - this.createNaclSubnetAssociations(vpcItem, naclItem, networkAcl, subnetMap); - // Create NACL entries - this.createNaclEntries(vpcItem, naclItem, networkAcl, subnetMap); - } - } - return naclMap; - } - - /** - * Create network ACL subnet associations - * @param vpcItem - * @param naclItem - * @param networkAcl - * @param subnetMap - */ - private createNaclSubnetAssociations( - vpcItem: VpcConfig | VpcTemplatesConfig, - naclItem: NetworkAclConfig, - networkAcl: NetworkAcl, - subnetMap: Map, - ) { - for (const subnetItem of naclItem.subnetAssociations) { - const naclSubnetAssociation = `${vpcItem.name}/${naclItem.name}/${subnetItem}`; - if (this.stack.isManagedByAsea(AseaResourceType.EC2_NACL_SUBNET_ASSOCIATION, naclSubnetAssociation)) { - this.stack.addLogs(LogLevel.INFO, `Nacl Subnet Association ${naclSubnetAssociation} is managed by ASEA`); - continue; - } - this.stack.addLogs(LogLevel.INFO, `Associate ${naclItem.name} to subnet ${subnetItem}`); - const subnet = getSubnet(subnetMap, vpcItem.name, subnetItem) as Subnet; - networkAcl.associateSubnet( - `${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}NaclAssociate${pascalCase(subnetItem)}`, - { - subnet, - }, - ); - } - } - - /** - * Create NACL inbound and outbound entries - * @param vpcItem - * @param naclItem - * @param networkAcl - */ - private createNaclEntries( - vpcItem: VpcConfig | VpcTemplatesConfig, - naclItem: NetworkAclConfig, - networkAcl: NetworkAcl, - subnetMap: Map, - ) { - for (const inboundRuleItem of naclItem.inboundRules ?? []) { - // If logic to determine if the VPC is not IPAM-based - if (!this.stack.isIpamCrossAccountNaclSource(inboundRuleItem.source)) { - this.stack.addLogs(LogLevel.INFO, `Adding inbound rule ${inboundRuleItem.rule} to ${naclItem.name}`); - - const inboundAclTargetProps: { cidrBlock?: string; ipv6CidrBlock?: string } = this.processNetworkAclTarget( - inboundRuleItem.source, - subnetMap, - ); - - networkAcl.addEntry( - `${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}-Inbound-${inboundRuleItem.rule}`, - { - egress: false, - protocol: inboundRuleItem.protocol, - ruleAction: inboundRuleItem.action, - ruleNumber: inboundRuleItem.rule, - portRange: { - from: inboundRuleItem.fromPort, - to: inboundRuleItem.toPort, - }, - ...inboundAclTargetProps, - }, - ); - - // Suppression for AwsSolutions-VPC3: A Network ACL or Network ACL entry has been implemented. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}Nacl/${pascalCase( - vpcItem.name, - )}Vpc${pascalCase(naclItem.name)}-Inbound-${inboundRuleItem.rule}`, - [{ id: 'AwsSolutions-VPC3', reason: 'NACL added to VPC' }], - ); - } - } - - for (const outboundRuleItem of naclItem.outboundRules ?? []) { - if (!this.stack.isIpamCrossAccountNaclSource(outboundRuleItem.destination)) { - this.stack.addLogs(LogLevel.INFO, `Adding outbound rule ${outboundRuleItem.rule} to ${naclItem.name}`); - - const outboundAclTargetProps: { cidrBlock?: string; ipv6CidrBlock?: string } = this.processNetworkAclTarget( - outboundRuleItem.destination, - subnetMap, - ); - - networkAcl.addEntry( - `${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}-Outbound-${outboundRuleItem.rule}`, - { - egress: true, - protocol: outboundRuleItem.protocol, - ruleAction: outboundRuleItem.action, - ruleNumber: outboundRuleItem.rule, - portRange: { - from: outboundRuleItem.fromPort, - to: outboundRuleItem.toPort, - }, - ...outboundAclTargetProps, - }, - ); - } - // Suppression for AwsSolutions-VPC3: A Network ACL or Network ACL entry has been implemented. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/${pascalCase(vpcItem.name)}Vpc${pascalCase(naclItem.name)}Nacl/${pascalCase( - vpcItem.name, - )}Vpc${pascalCase(naclItem.name)}-Outbound-${outboundRuleItem.rule}`, - [{ id: 'AwsSolutions-VPC3', reason: 'NACL added to VPC' }], - ); - } - } - - /** - * Process target source/destination for NACL - * @param target - * @returns - */ - private processNetworkAclTarget( - target: string | NetworkAclSubnetSelection, - subnetMap: Map, - ): { - cidrBlock?: string; - ipv6CidrBlock?: string; - } { - // - // IP target - // - if (isNetworkType('NonEmptyString', target)) { - this.stack.addLogs(LogLevel.INFO, `Evaluate IP Target ${target}`); - if (isIpv6Cidr(target)) { - return { ipv6CidrBlock: target }; - } else { - return { cidrBlock: target }; - } - } - // - // Subnet Source target - // - if (isNetworkType('INetworkAclSubnetSelection', target)) { - this.stack.addLogs( - LogLevel.INFO, - `Evaluate Subnet Source account:${target.account} vpc:${target.vpc} subnets:[${target.subnet}]`, - ); - // - // Locate the VPC - const vpcConfigItem = getVpcConfig(this.stack.vpcResources, target.vpc); - // - // Locate the Subnet - const subnetConfigItem = getSubnetConfig(vpcConfigItem, target.subnet); - - if (subnetConfigItem.ipamAllocation) { - const subnetItem = getSubnet(subnetMap, vpcConfigItem.name, subnetConfigItem.name) as Subnet; - return { cidrBlock: subnetItem.ipv4CidrBlock }; - } else { - return target.ipv6 - ? { ipv6CidrBlock: subnetConfigItem.ipv6CidrBlock } - : { cidrBlock: subnetConfigItem.ipv4CidrBlock }; - } - } - - this.stack.addLogs(LogLevel.ERROR, `Invalid input to processNetworkAclTargets`); - throw new Error(`Configuration validation failed at runtime.`); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nat-gw-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nat-gw-resources.ts deleted file mode 100644 index eabffa2..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/nat-gw-resources.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AseaResourceType, VpcConfig, VpcTemplatesConfig } from '@aws-accelerator/config'; -import { INatGateway, NatGateway, Subnet } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { pascalCase } from 'pascal-case'; -import { LogLevel } from '../network-stack'; -import { getSubnet } from '../utils/getter-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class NatGwResources { - public readonly natGatewayMap: Map; - private stack: NetworkVpcStack; - - constructor(networkVpcStack: NetworkVpcStack, subnetMap: Map) { - this.stack = networkVpcStack; - - // Create NAT gateways - this.natGatewayMap = this.createNatGateways(this.stack.vpcsInScope, subnetMap); - } - - /** - * Create NAT gateways for the current stack context - * @param vpcResources - * @param subnetMap - * @returns - */ - private createNatGateways( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - subnetMap: Map, - ): Map { - const natGatewayMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const natGatewayItem of vpcItem.natGateways ?? []) { - const subnet = getSubnet(subnetMap, vpcItem.name, natGatewayItem.subnet) as Subnet; - - this.stack.addLogs( - LogLevel.INFO, - `Adding NAT Gateway ${natGatewayItem.name} to VPC ${vpcItem.name} subnet ${natGatewayItem.subnet}`, - ); - let natGateway; - if (this.stack.isManagedByAsea(AseaResourceType.NAT_GATEWAY, `${vpcItem.name}/${natGatewayItem.name}`)) { - const natGatewayId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.NAT_GW, [vpcItem.name, natGatewayItem.name]), - ); - natGateway = NatGateway.fromAttributes( - this.stack, - pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${natGatewayItem.name}NatGateway`), - { - natGatewayId, - natGatewayName: natGatewayItem.name, - }, - ); - } else { - natGateway = new NatGateway( - this.stack, - pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${natGatewayItem.name}NatGateway`), - { - name: natGatewayItem.name, - allocationId: natGatewayItem.allocationId, - private: natGatewayItem.private, - subnet, - tags: natGatewayItem.tags, - }, - ); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(natGatewayItem.name)}NatGatewayId`), - parameterName: this.stack.getSsmPath(SsmResourceType.NAT_GW, [vpcItem.name, natGatewayItem.name]), - stringValue: natGateway.natGatewayId, - }); - } - natGatewayMap.set(`${vpcItem.name}_${natGatewayItem.name}`, natGateway); - } - } - return natGatewayMap; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack.ts deleted file mode 100644 index d0fc58e..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack.ts +++ /dev/null @@ -1,338 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Construct } from 'constructs'; - -import { OutpostsConfig, VpcConfig, VpcTemplatesConfig } from '@aws-accelerator/config'; -import { - INatGateway, - ITransitGatewayAttachment, - IVpc, - PutSsmParameter, - SsmParameterProps, - ISubnet, -} from '@aws-accelerator/constructs'; - -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { NetworkStack } from '../network-stack'; -import { AcmResources } from './acm-resources'; -import { DhcpResources } from './dhcp-resources'; -import { IpamResources } from './ipam-resources'; -import { LoadBalancerResources } from './load-balancer-resources'; -import { NaclResources } from './nacl-resources'; -import { NatGwResources } from './nat-gw-resources'; -import { PrefixListResources } from './prefix-list-resources'; -import { RouteEntryResources } from './route-entry-resources'; -import { RouteTableResources } from './route-table-resources'; -import { SecurityGroupResources } from './security-group-resources'; -import { SubnetResources } from './subnet-resources'; -import { TgwResources } from './tgw-resources'; -import { VpcResources } from './vpc-resources'; -import { pascalCase } from 'pascal-case'; -import { SsmResourceType } from '@aws-accelerator/utils'; -import { getVpcConfig } from '../utils/getter-utils'; -export class NetworkVpcStack extends NetworkStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - // - // Create ACM Certificates - // - new AcmResources(this, props); - // - // Create DHCP options sets - // - const dhcpResources = new DhcpResources(this, props); - // - // Create prefix lists - // - const plResources = new PrefixListResources(this, props); - // - // Create VPC resources - // - const ipamPoolMap = this.setIpamPoolMap(props); - - const vpcResources = new VpcResources( - this, - ipamPoolMap, - dhcpResources.dhcpOptionsIds, - this.vpcResources, - { - acceleratorPrefix: props.prefixes.accelerator, - ssmParamName: props.prefixes.ssmParamName, - partition: props.partition, - useExistingRoles: props.useExistingRoles, - }, - { - defaultVpcsConfig: props.networkConfig.defaultVpc, - centralEndpointVpc: props.networkConfig.vpcs.find(vpc => vpc.interfaceEndpoints?.central), - vpcFlowLogsConfig: props.networkConfig.vpcFlowLogs, - customerGatewayConfigs: props.networkConfig.customerGateways, - vpcPeeringConfigs: props.networkConfig.vpcPeering, - firewalls: this.getFirewallInfo(props, this.vpcResources), - }, - ); - // - // Create VPC and outpost route table resources - // - const routeTableResources = new RouteTableResources(this, vpcResources.vpcMap); - // - // Create subnet resources - // - const outpostMap = this.setOutpostsMap(props.networkConfig.vpcs); - - const subnetResources = new SubnetResources( - this, - vpcResources.vpcMap, - routeTableResources.routeTableMap, - outpostMap, - props.networkConfig.centralNetworkServices?.ipams, - ); - // - // Create NAT gateway resources - // - const natGatewayResources = new NatGwResources(this, subnetResources.subnetMap); - // - // Create transit gateway resources - // - const transitGatewayIds = this.setVpcTransitGatewayMap(this.vpcsInScope); - const tgwResources = new TgwResources( - this, - transitGatewayIds, - vpcResources.vpcMap, - subnetResources.subnetMap, - props, - ); - // - // Create route table entries - // - new RouteEntryResources( - this, - routeTableResources.routeTableMap, - transitGatewayIds, - tgwResources.tgwAttachmentMap, - subnetResources.subnetMap, - natGatewayResources.natGatewayMap, - plResources.prefixListMap, - outpostMap, - ); - // - // Create security groups - // - const sgResources = new SecurityGroupResources( - this, - vpcResources.vpcMap, - subnetResources.subnetMap, - plResources.prefixListMap, - ); - // - // Create NACLs - // - new NaclResources(this, vpcResources.vpcMap, subnetResources.subnetMap); - // - // Create load balancer resources - // - new LoadBalancerResources(this, subnetResources.subnetMap, sgResources.securityGroupMap, props); - // - // Create Get IPAM Cidr Role - // - new IpamResources(this, props.globalConfig.homeRegion, this.organizationId); - // - // Create Stack resource SSM Parameters - // - this.createStackResourceParameters(vpcResources.vpcMap, subnetResources.subnetMap); - // - // Create SSM Parameters - // - this.createSsmParameters(); - // - // Add nag suppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Creates SSM Parameters for stack - * - * * Creates SSM Parameters for VPC and Subnet CIDR Blocks - */ - private createStackResourceParameters(vpcMap: Map, subnetMap: Map) { - const parameters: SsmParameterProps[] = []; - vpcMap.forEach((vpc, key) => { - parameters.push({ - name: this.getSsmPath(SsmResourceType.VPC_IPV4_CIDR_BLOCK, [key]), - value: vpc.cidrBlock, - }); - }); - for (const vpcItem of this.vpcsInScope) { - for (const subnetItem of vpcItem.subnets ?? []) { - const subnet = subnetMap.get(`${vpcItem.name}_${subnetItem.name}`)!; - if (subnet.ipv4CidrBlock) { - parameters.push({ - name: this.getSsmPath(SsmResourceType.SUBNET_IPV4_CIDR_BLOCK, [vpcItem.name, subnetItem.name]), - value: subnet.ipv4CidrBlock, - }); - } - } - } - if (parameters.length === 0) return; - new PutSsmParameter(this, pascalCase(`PutNetworkVPCStackResourceParameters`), { - accountIds: [this.account], - region: this.region, - roleName: this.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - parameters, - invokingAccountId: this.account, - acceleratorPrefix: this.props.prefixes.accelerator, - }); - } - - /** - * Returns a map of outpost configurations for the current stack context - * @param vpcs - * @returns - */ - private setOutpostsMap(vpcs: VpcConfig[]): Map { - const outpostMap = new Map(); - - for (const vpcItem of vpcs) { - const vpcAccountIds = this.getVpcAccountIds(vpcItem); - - if (this.isTargetStack(vpcAccountIds, [vpcItem.region])) { - for (const outpost of vpcItem.outposts ?? []) { - outpostMap.set(`${vpcItem.name}_${outpost.name}`, outpost); - } - } - } - return outpostMap; - } - - /** - * Returns a transit gateway attachment object from a given map if it exists - * @param tgwAttachmentMap - * @param vpcName - * @param transitGatewayName - * @returns - */ - public getTgwAttachment( - tgwAttachmentMap: Map, - vpcName: string, - transitGatewayName: string, - ): ITransitGatewayAttachment { - const key = `${vpcName}_${transitGatewayName}`; - - if (!tgwAttachmentMap.get(key)) { - this.logger.error(`VPC ${vpcName} attachment for TGW ${transitGatewayName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return tgwAttachmentMap.get(key)!; - } - - /** - * Returns a NAT gateway object from a given map if it exists - * @param natGatewayMap - * @param vpcName - * @param natGatewayName - * @returns - */ - public getNatGateway(natGatewayMap: Map, vpcName: string, natGatewayName: string): INatGateway { - const key = `${vpcName}_${natGatewayName}`; - - if (!natGatewayMap.get(key)) { - this.logger.error(`VPC ${vpcName} NAT gateway ${natGatewayName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return natGatewayMap.get(key)!; - } - - /** - * Returns a Local gateway object from a given map if it exists - * Requires iterating over all outposts - * @param outpostMap - * @param vpcName - * @param localGatewayName - * @returns - */ - public getLocalGatewayFromOutpostMap( - outpostMap: Map, - vpcName: string, - localGatewayName: string, - ): string { - let localGatewayId = undefined; - - for (const outpost of outpostMap.values()) { - if (outpost.localGateway && outpost.localGateway.name === localGatewayName) { - localGatewayId = outpost.localGateway.id; - } - } - - if (!localGatewayId) { - this.logger.error(`VPC ${vpcName} Local Gateway ${localGatewayName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - return localGatewayId; - } - - /** - * Returns an outpost object from a given map if it exists - * @param outpostMap - * @param vpcName - * @param outpostName - * @returns - */ - public getOutpost(outpostMap: Map, vpcName: string, outpostName: string): OutpostsConfig { - const key = `${vpcName}_${outpostName}`; - - if (!outpostMap.get(key)) { - this.logger.error(`VPC ${vpcName} Outpost ${outpostName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return outpostMap.get(key)!; - } - - /** - * Return an array of cross-account ENI target account IDs - * if a VPC containing relevant route table exists in this account+region - * @param props - * @returns - */ - private getFirewallInfo( - props: AcceleratorStackProps, - vpcResourcesToDeploy: (VpcConfig | VpcTemplatesConfig)[], - ): { accountId: string; firewallVpc: VpcConfig | VpcTemplatesConfig }[] { - const firewallAccountInfo: { accountId: string; firewallVpc: VpcConfig | VpcTemplatesConfig }[] = []; - - for (const firewallInstance of [ - ...(props.customizationsConfig.firewalls?.instances ?? []), - ...(props.customizationsConfig.firewalls?.managerInstances ?? []), - ]) { - // check for potential targets - - const vpcConfig = getVpcConfig(vpcResourcesToDeploy, firewallInstance.vpc); - for (const routeTable of vpcConfig.routeTables ?? []) { - for (const route of routeTable.routes ?? []) { - if (route.type === 'networkInterface' && route?.target?.includes(firewallInstance.name)) { - const firewallOwner = this.getVpcAccountIds(vpcConfig).join(); - firewallAccountInfo.push({ accountId: firewallOwner, firewallVpc: vpcConfig }); - } - } - } - } - return firewallAccountInfo; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/prefix-list-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/prefix-list-resources.ts deleted file mode 100644 index ffc6b3b..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/prefix-list-resources.ts +++ /dev/null @@ -1,205 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CustomerGatewayConfig, - TransitGatewayConfig, - TransitGatewayRouteEntryConfig, - TransitGatewayRouteTableVpnEntryConfig, - isNetworkType, -} from '@aws-accelerator/config'; -import { PrefixList, PutSsmParameter, SsmParameterProps } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel, NetworkStack } from '../network-stack'; -import { getPrefixList } from '../utils/getter-utils'; -import { isEc2FirewallVpnRoute } from '../utils/validation-utils'; - -export class PrefixListResources { - public readonly prefixListMap: Map; - public readonly sharedParameterMap: Map; - private stack: NetworkStack; - - constructor(networkStack: NetworkStack, props: AcceleratorStackProps) { - this.stack = networkStack; - - // Create prefix lists - this.prefixListMap = this.createPrefixLists(props); - // Create shared parameters - this.sharedParameterMap = this.createSharedParameters( - props, - props.networkConfig.transitGateways, - props.networkConfig.customerGateways ?? [], - ); - } - - /** - * Create prefix lists for the current stack context - * @param props - * @returns - */ - private createPrefixLists(props: AcceleratorStackProps): Map { - const prefixListMap = new Map(); - for (const prefixListItem of props.networkConfig.prefixLists ?? []) { - const prefixListTargets = this.stack.getPrefixListTargets(prefixListItem); - if (this.stack.isTargetStack(prefixListTargets.accountIds, prefixListTargets.regions)) { - this.stack.addLogs(LogLevel.INFO, `Adding Prefix List ${prefixListItem.name}`); - - const prefixList = new PrefixList(this.stack, pascalCase(`${prefixListItem.name}PrefixList`), { - name: prefixListItem.name, - addressFamily: prefixListItem.addressFamily, - maxEntries: prefixListItem.maxEntries, - entries: prefixListItem.entries, - tags: prefixListItem.tags ?? [], - }); - prefixListMap.set(prefixListItem.name, prefixList); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(prefixListItem.name)}PrefixList`), - parameterName: this.stack.getSsmPath(SsmResourceType.PREFIX_LIST, [prefixListItem.name]), - stringValue: prefixList.prefixListId, - }); - } - } - return prefixListMap; - } - - /** - * Create cross-account/cross-region SSM parameters for site-to-site VPN connections - * that must reference a prefix list in cross-account VPN scenarios - * @param transitGateways TransitGatewayConfig[] - * @param customerGateways CustomerGatewayConfig[] - * @returns Map - */ - private createSharedParameters( - props: AcceleratorStackProps, - transitGateways: TransitGatewayConfig[], - customerGateways: CustomerGatewayConfig[], - ): Map { - const sharedParameterMap = new Map(); - // - // Get EC2 firewall TGW prefix list routes - const ec2FirewallPrefixListRoutes = this.setEc2FirewallPrefixListRoutes(transitGateways, customerGateways); - // - // Set CGWs in scope - const plRouteVpnNames = ec2FirewallPrefixListRoutes.map(plRoute => { - if ( - plRoute.attachment && - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - plRoute.attachment, - ) - ) { - return plRoute.attachment.vpnConnectionName; - } - return ''; - }); - const cgwsInScope = customerGateways.filter(cgw => - cgw.vpnConnections?.find(vpn => plRouteVpnNames.includes(vpn.name)), - ); - const crossAcctFirewallReferenceCgws = cgwsInScope.filter( - cgwItem => - this.stack.isTargetStack([props.accountsConfig.getAccountId(cgwItem.account)], [cgwItem.region]) && - !this.stack.firewallVpcInScope(cgwItem), - ); - // - // Create shared parameters - for (const crossAcctCgw of crossAcctFirewallReferenceCgws) { - const firewallVpcConfig = this.stack.getFirewallVpcConfig(crossAcctCgw); - const accountIds = this.stack.getVpcAccountIds(firewallVpcConfig); - const parameters = this.setCrossAccountSsmParameters(crossAcctCgw, ec2FirewallPrefixListRoutes); - - if (parameters.length > 0) { - // Put SSM parameters - new PutSsmParameter(this.stack, pascalCase(`${crossAcctCgw.name}PrefixListSharedParameters`), { - accountIds, - region: firewallVpcConfig.region, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - parameters, - invokingAccountId: this.stack.account, - acceleratorPrefix: this.stack.acceleratorPrefix, - }); - sharedParameterMap.set(crossAcctCgw.name, parameters); - } - } - - return sharedParameterMap; - } - - /** - * Returns an array of TGW prefix list routes that target an EC2 firewall VPN connection - * @param transitGateways TransitGatewayConfig[] - * @param customerGateways CustomerGatewayConfig[] - * @returns TransitGatewayRouteEntryConfig[] - */ - private setEc2FirewallPrefixListRoutes( - transitGateways: TransitGatewayConfig[], - customerGateways: CustomerGatewayConfig[], - ): TransitGatewayRouteEntryConfig[] { - const ec2FirewallPrefixListRoutes: TransitGatewayRouteEntryConfig[] = []; - for (const tgwItem of transitGateways) { - for (const routeTableItem of tgwItem.routeTables ?? []) { - const prefixListRoutesInScope = routeTableItem.routes.filter( - routeItem => - routeItem.attachment && - routeItem.destinationPrefixList && - this.prefixListMap.has(routeItem.destinationPrefixList), - ); - prefixListRoutesInScope.forEach(plRoute => { - if (isEc2FirewallVpnRoute(customerGateways, plRoute)) { - ec2FirewallPrefixListRoutes.push(plRoute); - } - }); - } - } - return ec2FirewallPrefixListRoutes; - } - - /** - * Returns an array of SSM parameters for cross-account prefix list routes - * @param customerGateway CustomerGatewayConfig - * @param ec2FirewallPrefixListRoutes TransitGatewayRouteEntryConfig[] - * @returns SsmParameterProps[] - */ - private setCrossAccountSsmParameters( - customerGateway: CustomerGatewayConfig, - ec2FirewallPrefixListRoutes: TransitGatewayRouteEntryConfig[], - ): SsmParameterProps[] { - const parameters: SsmParameterProps[] = []; - for (const vpnItem of customerGateway.vpnConnections ?? []) { - const route = ec2FirewallPrefixListRoutes.find( - routeItem => - routeItem.attachment && - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeItem.attachment, - ) && - routeItem.attachment.vpnConnectionName === vpnItem.name, - ); - if (route && route.destinationPrefixList) { - const prefixList = getPrefixList(this.prefixListMap, route.destinationPrefixList) as PrefixList; - parameters.push({ - name: this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_PREFIX_LIST, [ - customerGateway.name, - route.destinationPrefixList, - ]), - value: prefixList.prefixListId, - }); - } - } - return [...new Set(parameters)]; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-entry-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-entry-resources.ts deleted file mode 100644 index bb74fcb..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-entry-resources.ts +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - OutpostsConfig, - RouteTableConfig, - RouteTableEntryConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { - INatGateway, - ITransitGatewayAttachment, - PrefixList, - PrefixListRoute, - RouteTable, - Subnet, -} from '@aws-accelerator/constructs'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { LogLevel } from '../network-stack'; -import { getPrefixList, getRouteTable, getSubnet, getTransitGatewayId } from '../utils/getter-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class RouteEntryResources { - public readonly routeTableEntryMap: Map; - private stack: NetworkVpcStack; - - constructor( - networkVpcStack: NetworkVpcStack, - routeTableMap: Map, - transitGatewayIds: Map, - tgwAttachmentMap: Map, - subnetMap: Map, - natGatewayMap: Map, - prefixListMap: Map, - outpostMap: Map, - ) { - this.stack = networkVpcStack; - - // Create route table entries - this.routeTableEntryMap = this.createRouteEntries( - this.stack.vpcsInScope, - routeTableMap, - transitGatewayIds, - tgwAttachmentMap, - subnetMap, - natGatewayMap, - prefixListMap, - outpostMap, - ); - } - - /** - * Create route table entries - * @param vpcResources - * @param routeTableMap - * @param transitGatewayIds - * @param tgwAttachmentMap - * @param subnetMap - * @param natGatewayMap - * @param prefixListMap - * @returns - */ - private createRouteEntries( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - routeTableMap: Map, - transitGatewayIds: Map, - tgwAttachmentMap: Map, - subnetMap: Map, - natGatewayMap: Map, - prefixListMap: Map, - outpostMap: Map, - ): Map { - const routeTableEntryMap = new Map(); - - for (const vpcItem of vpcResources) { - for (const routeTableItem of vpcItem.routeTables ?? []) { - const routeTable = getRouteTable(routeTableMap, vpcItem.name, routeTableItem.name) as RouteTable; - const routeTableItemEntryMap = this.createRouteTableItemEntries(vpcItem, routeTableItem, routeTable, { - transitGatewayIds: transitGatewayIds, - tgwAttachments: tgwAttachmentMap, - subnets: subnetMap, - natGateways: natGatewayMap, - prefixLists: prefixListMap, - outposts: outpostMap, - }); - routeTableItemEntryMap.forEach((value, key) => routeTableEntryMap.set(key, value)); - } - } - return routeTableEntryMap; - } - - /** - * Create route entries for a given route table item - * @param vpcItem - * @param routeTableItem - * @param routeTable - * @param transitGatewayIds - * @param tgwAttachmentMap - * @param subnetMap - * @param natGatewayMap - * @param prefixListMap - * @returns - */ - private createRouteTableItemEntries( - vpcItem: VpcConfig | VpcTemplatesConfig, - routeTableItem: RouteTableConfig, - routeTable: RouteTable, - maps: { - transitGatewayIds: Map; - tgwAttachments: Map; - subnets: Map; - natGateways: Map; - prefixLists: Map; - outposts: Map; - }, - ): Map { - const routeTableItemEntryMap = new Map(); - - for (const routeTableEntryItem of routeTableItem.routes ?? []) { - const routeId = - pascalCase(`${vpcItem.name}Vpc`) + - pascalCase(`${routeTableItem.name}RouteTable`) + - pascalCase(routeTableEntryItem.name); - const entryTypes = [ - 'transitGateway', - 'internetGateway', - 'egressOnlyIgw', - 'natGateway', - 'virtualPrivateGateway', - 'localGateway', - ]; - - // Check if using a prefix list or CIDR as the destination - if (routeTableEntryItem.type && entryTypes.includes(routeTableEntryItem.type)) { - // Set destination type - const [destination, destinationPrefixListId, ipv6Destination] = this.setRouteEntryDestination( - routeTableEntryItem, - maps.prefixLists, - maps.subnets, - vpcItem.name, - ); - - switch (routeTableEntryItem.type) { - // Route: Transit Gateway - case 'transitGateway': - this.stack.addLogs(LogLevel.INFO, `Adding Transit Gateway Route Table Entry ${routeTableEntryItem.name}`); - - const transitGatewayId = getTransitGatewayId(maps.transitGatewayIds, routeTableEntryItem.target!); - const transitGatewayAttachment = this.stack.getTgwAttachment( - maps.tgwAttachments, - vpcItem.name, - routeTableEntryItem.target!, - ); - - const tgwRoute = routeTable.addTransitGatewayRoute( - routeId, - transitGatewayId, - transitGatewayAttachment, - destination, - destinationPrefixListId, - ipv6Destination, - this.stack.cloudwatchKey, - this.stack.logRetention, - ); - routeTableItemEntryMap.set(`${vpcItem.name}_${routeTableItem.name}_${routeTableEntryItem.name}`, tgwRoute); - break; - case 'natGateway': - // Route: NAT Gateway - this.stack.addLogs(LogLevel.INFO, `Adding NAT Gateway Route Table Entry ${routeTableEntryItem.name}`); - - const natGateway = this.stack.getNatGateway(maps.natGateways, vpcItem.name, routeTableEntryItem.target!); - - const natRoute = routeTable.addNatGatewayRoute( - routeId, - natGateway.natGatewayId, - destination, - destinationPrefixListId, - ipv6Destination, - this.stack.cloudwatchKey, - this.stack.logRetention, - ); - routeTableItemEntryMap.set(`${vpcItem.name}_${routeTableItem.name}_${routeTableEntryItem.name}`, natRoute); - break; - // Route: Internet Gateway - case 'internetGateway': - this.stack.addLogs(LogLevel.INFO, `Adding Internet Gateway Route Table Entry ${routeTableEntryItem.name}`); - const igwRoute = routeTable.addInternetGatewayRoute( - routeId, - destination, - destinationPrefixListId, - ipv6Destination, - this.stack.cloudwatchKey, - this.stack.logRetention, - ); - routeTableItemEntryMap.set(`${vpcItem.name}_${routeTableItem.name}_${routeTableEntryItem.name}`, igwRoute); - break; - case 'egressOnlyIgw': - this.stack.addLogs( - LogLevel.INFO, - `Adding Egress-only Internet Gateway Route Table Entry ${routeTableEntryItem.name}`, - ); - const eigwRoute = routeTable.addEgressOnlyIgwRoute( - routeId, - destination, - destinationPrefixListId, - ipv6Destination, - this.stack.cloudwatchKey, - this.stack.logRetention, - ); - routeTableItemEntryMap.set(`${vpcItem.name}_${routeTableItem.name}_${routeTableEntryItem.name}`, eigwRoute); - break; - case 'virtualPrivateGateway': - this.stack.addLogs( - LogLevel.INFO, - `Adding Virtual Private Gateway Route Table Entry ${routeTableEntryItem.name}`, - ); - const vgwRoute = routeTable.addVirtualPrivateGatewayRoute( - routeId, - destination, - destinationPrefixListId, - ipv6Destination, - this.stack.cloudwatchKey, - this.stack.logRetention, - ); - routeTableItemEntryMap.set(`${vpcItem.name}_${routeTableItem.name}_${routeTableEntryItem.name}`, vgwRoute); - break; - case 'localGateway': - this.stack.addLogs(LogLevel.INFO, `Adding Local Gateway Route Table Entry ${routeTableEntryItem.name}`); - - const localGatewayId = this.stack.getLocalGatewayFromOutpostMap( - maps.outposts, - vpcItem.name, - routeTableEntryItem.target!, - ); - - const lgwRoute = routeTable.addLocalGatewayRoute( - routeId, - localGatewayId, - destination, - destinationPrefixListId, - ipv6Destination, - this.stack.cloudwatchKey, - this.stack.logRetention, - ); - routeTableItemEntryMap.set(`${vpcItem.name}_${routeTableItem.name}_${routeTableEntryItem.name}`, lgwRoute); - break; - } - } - } - return routeTableItemEntryMap; - } - - /** - * Determine whether to set prefix list, CIDR, or subnet reference for route destination - * @param routeTableEntryItem - * @param prefixListMap - * @param subnetMap - * @param vpcName - * @returns - */ - private setRouteEntryDestination( - routeTableEntryItem: RouteTableEntryConfig, - prefixListMap: Map, - subnetMap: Map, - vpcName: string, - ): [string | undefined, string | undefined, string | undefined] { - let destination: string | undefined = undefined; - let destinationPrefixListId: string | undefined = undefined; - let ipv6Destination: string | undefined = undefined; - if (routeTableEntryItem.destinationPrefixList) { - // Get PL ID from map - const prefixList = getPrefixList(prefixListMap, routeTableEntryItem.destinationPrefixList) as PrefixList; - destinationPrefixListId = prefixList.prefixListId; - } else { - const subnetKey = `${vpcName}_${routeTableEntryItem.ipv6Destination ?? routeTableEntryItem.destination!}`; - - if (subnetMap.get(subnetKey)) { - const subnet = getSubnet( - subnetMap, - vpcName, - routeTableEntryItem.ipv6Destination ?? routeTableEntryItem.destination!, - ) as Subnet; - [destination, ipv6Destination] = this.getSubnetCidrBlock(routeTableEntryItem, subnet); - } else { - destination = routeTableEntryItem.destination; - ipv6Destination = routeTableEntryItem.ipv6Destination; - } - } - return [destination, destinationPrefixListId, ipv6Destination]; - } - - /** - * Returns either the IPv4 or IPv6 CIDR block of a dynamic subnet target. - * @param routeTableEntryItem RouteTableEntryConfig - * @param subnet Subnet - * @returns [string | undefined, string | undefined] - */ - private getSubnetCidrBlock( - routeTableEntryItem: RouteTableEntryConfig, - subnet: Subnet, - ): [string | undefined, string | undefined] { - let destination: string | undefined = undefined; - let ipv6Destination: string | undefined = undefined; - - if (routeTableEntryItem.ipv6Destination) { - ipv6Destination = subnet.ipv6CidrBlock; - } else { - destination = subnet.ipv4CidrBlock; - } - return [destination, ipv6Destination]; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-table-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-table-resources.ts deleted file mode 100644 index c56752a..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/route-table-resources.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AseaResourceType, VpcConfig, VpcTemplatesConfig, isNetworkType } from '@aws-accelerator/config'; -import { RouteTable, Vpc } from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { getVpc } from '../utils/getter-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class RouteTableResources { - public readonly routeTableMap: Map; - private stack: NetworkVpcStack; - - constructor(networkVpcStack: NetworkVpcStack, vpcMap: Map) { - this.stack = networkVpcStack; - - // Create route table resources - this.routeTableMap = this.createRouteTableResources(this.stack.vpcsInScope, vpcMap); - } - - /** - * Create route tables - * @param vpcResources - * @param vpcMap - * @param props - */ - private createRouteTableResources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - ): Map { - const routeTableMap = new Map(); - - for (const vpcItem of vpcResources) { - const vpc = getVpc(vpcMap, vpcItem.name) as Vpc; - // - // Create outpost route tables - // - const outpostRouteTableMap = this.associateOutpostRouteTables(vpc, vpcItem); - outpostRouteTableMap.forEach((value, key) => routeTableMap.set(key, value)); - // - // Create VPC route tables - // - const vpcRouteTableMap = this.createRouteTables(vpc, vpcItem); - vpcRouteTableMap.forEach((value, key) => routeTableMap.set(key, value)); - } - return routeTableMap; - } - - /** - * Creates local route table associations and returns - * a map of outpost route tables for a given VPC - * @param vpc - * @param vpcItem - */ - private associateOutpostRouteTables(vpc: Vpc, vpcItem: VpcConfig | VpcTemplatesConfig) { - let outpostRouteTableMap = new Map(); - if (isNetworkType('IVpcConfig', vpcItem)) { - outpostRouteTableMap = this.getOutpostRouteTables(vpc, vpcItem); - this.associateLocalGatewayRouteTablesToVpc({ - vpcAccountName: vpcItem.account, - routeTables: outpostRouteTableMap, - vpcId: vpc.vpcId, - vpcName: vpcItem.name, - }); - } - return outpostRouteTableMap; - } - - /** - * Returns a map of outpost route tables for a given VPC - * @param vpcItem - * @param vpc - * @returns - */ - private getOutpostRouteTables(vpc: Vpc, vpcItem: VpcConfig): Map { - const outpostRouteTableMap = new Map(); - for (const outpost of vpcItem.outposts ?? []) { - for (const routeTableItem of outpost.localGateway?.routeTables ?? []) { - const outpostRouteTable = { routeTableId: routeTableItem.id, vpc } as RouteTable; - outpostRouteTableMap.set(`${vpcItem.name}_${routeTableItem.name}`, outpostRouteTable); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name)}${pascalCase(routeTableItem.name)}RouteTableId`), - parameterName: this.stack.getSsmPath(SsmResourceType.ROUTE_TABLE, [vpcItem.name, routeTableItem.name]), - stringValue: outpostRouteTable.routeTableId, - }); - } - } - - return outpostRouteTableMap; - } - - /** - * Associates local route tables to a given VPC - * @param localGateway - */ - private associateLocalGatewayRouteTablesToVpc(localGateway: { - vpcAccountName: string; - vpcName: string; - vpcId: string; - routeTables: Map; - }): void { - for (const [name, routeTable] of localGateway.routeTables) { - new cdk.aws_ec2.CfnLocalGatewayRouteTableVPCAssociation( - this.stack, - `${name}-${localGateway.vpcName}-${localGateway.vpcAccountName}`, - { - vpcId: localGateway.vpcId, - localGatewayRouteTableId: routeTable.routeTableId, - }, - ); - } - } - - /** - * Create route tables for a given VPC - * @param vpcItem - * @param vpc - * @returns - */ - private createRouteTables(vpc: Vpc, vpcItem: VpcConfig | VpcTemplatesConfig): Map { - const routeTableMap = new Map(); - for (const routeTableItem of vpcItem.routeTables ?? []) { - let routeTable; - if (this.stack.isManagedByAsea(AseaResourceType.ROUTE_TABLE, `${vpcItem.name}/${routeTableItem.name}`)) { - const routeTableId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.ROUTE_TABLE, [vpcItem.name, routeTableItem.name]), - ); - routeTable = RouteTable.fromRouteTableAttributes( - this.stack, - pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${routeTableItem.name}RouteTable`), - { - routeTableId, - vpc, - }, - ); - } else { - routeTable = new RouteTable( - this.stack, - pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${routeTableItem.name}RouteTable`), - { - name: routeTableItem.name, - vpc, - tags: routeTableItem.tags, - }, - ); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name)}${pascalCase(routeTableItem.name)}RouteTableId`), - parameterName: this.stack.getSsmPath(SsmResourceType.ROUTE_TABLE, [vpcItem.name, routeTableItem.name]), - stringValue: routeTable.routeTableId, - }); - } - - // Add gateway association if configured - if (routeTableItem.gatewayAssociation) { - routeTable.addGatewayAssociation(routeTableItem.gatewayAssociation); - } - routeTableMap.set(`${vpcItem.name}_${routeTableItem.name}`, routeTable); - } - return routeTableMap; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/security-group-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/security-group-resources.ts deleted file mode 100644 index 2415ef2..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/security-group-resources.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { PrefixList, SecurityGroup, Subnet, Vpc } from '@aws-accelerator/constructs'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class SecurityGroupResources { - public readonly securityGroupMap: Map; - private stack: NetworkVpcStack; - - constructor( - networkVpcStack: NetworkVpcStack, - vpcMap: Map, - subnetMap: Map, - prefixListMap: Map, - ) { - this.stack = networkVpcStack; - - // Create security groups - this.securityGroupMap = this.stack.createSecurityGroups(this.stack.vpcsInScope, vpcMap, subnetMap, prefixListMap); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/subnet-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/subnet-resources.ts deleted file mode 100644 index cf860fc..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/subnet-resources.ts +++ /dev/null @@ -1,362 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AseaResourceType, - IpamConfig, - OutpostsConfig, - SubnetConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { PutSsmParameter, RouteTable, SsmParameterProps, Subnet, Vpc } from '@aws-accelerator/constructs'; -import { getAvailabilityZoneMap } from '@aws-accelerator/utils/lib/regions'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'pascal-case'; -import { LogLevel } from '../network-stack'; -import { getRouteTable, getSubnet, getVpc } from '../utils/getter-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class SubnetResources { - public readonly sharedParameterMap: Map; - public readonly subnetMap: Map; - private stack: NetworkVpcStack; - - constructor( - networkVpcStack: NetworkVpcStack, - vpcMap: Map, - routeTableMap: Map, - outpostMap: Map, - ipamConfig?: IpamConfig[], - ) { - this.stack = networkVpcStack; - - // Create subnets - this.subnetMap = this.createSubnets(this.stack.vpcsInScope, vpcMap, routeTableMap, outpostMap, ipamConfig); - // Create shared SSM parameters - this.sharedParameterMap = this.createSharedParameters(this.stack.vpcsInScope, vpcMap, this.subnetMap); - } - - /** - * Function to create Subnet - * @param vpcItem {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param subnetItem {@link SubnetConfig} - * @param maps - * @param index number - * @param ipamConfig {@link IpamConfig} [] - * @returns - */ - private createSubnet( - vpcItem: VpcConfig | VpcTemplatesConfig, - subnetItem: SubnetConfig, - maps: { - vpcs: Map; - routeTables: Map; - subnets: Map; - ipamSubnets: Map; - outposts: Map; - }, - index: number, - ipamConfig?: IpamConfig[], - ): number { - // Retrieve items required to create subnet - const vpc = getVpc(maps.vpcs, vpcItem.name) as Vpc; - const routeTable = subnetItem.routeTable - ? (getRouteTable(maps.routeTables, vpcItem.name, subnetItem.routeTable) as RouteTable) - : undefined; - const basePool = subnetItem.ipamAllocation ? this.getBasePool(subnetItem, ipamConfig) : undefined; - const outpost = subnetItem.outpost - ? this.stack.getOutpost(maps.outposts, vpcItem.name, subnetItem.outpost) - : undefined; - const availabilityZone = this.setAvailabilityZone(subnetItem, outpost); - - // Create subnet - const subnet = this.createSubnetItem(vpcItem, subnetItem, availabilityZone, vpc, routeTable, basePool, outpost); - maps.subnets.set(`${vpcItem.name}_${subnetItem.name}`, subnet); - - // Need to ensure IPAM subnets are created one at a time to avoid duplicate allocations - // Add dependency on previously-created IPAM subnet, if it exists - if (subnetItem.ipamAllocation) { - maps.ipamSubnets.set(index, subnet); - - if (index > 0) { - const lastSubnet = maps.ipamSubnets.get(index - 1); - - if (!lastSubnet) { - this.stack.addLogs( - LogLevel.ERROR, - `Error creating subnet ${subnetItem.name}: previous IPAM subnet undefined`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - subnet.node.addDependency(lastSubnet); - } - index += 1; - } - - return index; - } - - /** - * Create subnets for each VPC - * @param vpcResources - * @param vpcMap - * @param routeTableMap - * @param outpostMap - * @param ipamConfig - * @returns - */ - private createSubnets( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - routeTableMap: Map, - outpostMap: Map, - ipamConfig?: IpamConfig[], - ): Map { - const subnetMap = new Map(); - - for (const vpcItem of vpcResources) { - // Create map and index to track IPAM subnets - const ipamSubnetMap = new Map(); - let index = 0; - - for (const subnetItem of vpcItem.subnets ?? []) { - index = this.createSubnet( - vpcItem, - subnetItem, - { - vpcs: vpcMap, - routeTables: routeTableMap, - subnets: subnetMap, - ipamSubnets: ipamSubnetMap, - outposts: outpostMap, - }, - index, - ipamConfig, - ); - } - } - return subnetMap; - } - - /** - * Get base IPAM pool CIDR ranges for a given subnet - * @param subnetItem - * @param ipamConfig - * @returns - */ - private getBasePool(subnetItem: SubnetConfig, ipamConfig?: IpamConfig[]): string[] { - let basePool: string[] | undefined; - - for (const ipam of ipamConfig ?? []) { - const pool = ipam.pools?.find(item => item.name === subnetItem.ipamAllocation!.ipamPoolName); - basePool = pool?.provisionedCidrs; - } - - if (!basePool) { - this.stack.addLogs( - LogLevel.ERROR, - `Error creating subnet ${subnetItem.name}: IPAM pool ${subnetItem.ipamAllocation!.ipamPoolName} not defined`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - return basePool; - } - - /** - * Set availability zone for a given subnet item - * @param subnetItem - * @param outpost - * @returns - */ - private setAvailabilityZone(subnetItem: SubnetConfig, outpost?: OutpostsConfig): string { - let availabilityZone = outpost?.availabilityZone ? outpost.availabilityZone : subnetItem.availabilityZone; - - if (!availabilityZone) { - this.stack.addLogs(LogLevel.ERROR, `Error creating subnet ${subnetItem.name}: Availability Zone not defined.`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return (availabilityZone = - typeof availabilityZone === 'string' - ? `${cdk.Stack.of(this.stack).region}${availabilityZone}` - : `${getAvailabilityZoneMap(cdk.Stack.of(this.stack).region)}${availabilityZone}`); - } - - /** - * Create subnet item - * @param vpcItem - * @param subnetItem - * @param availabilityZone - * @param routeTable - * @param vpc - * @param ipamSubnetMap - * @param index - * @param basePool - * @param outpost - * @returns - */ - private createSubnetItem( - vpcItem: VpcConfig | VpcTemplatesConfig, - subnetItem: SubnetConfig, - availabilityZone: string, - vpc: Vpc, - routeTable?: RouteTable, - basePool?: string[], - outpost?: OutpostsConfig, - ): Subnet { - this.stack.addLogs(LogLevel.INFO, `Adding subnet ${subnetItem.name} to VPC ${vpcItem.name}`); - - const isAvailabilityZoneId = !availabilityZone.includes(cdk.Stack.of(this.stack).region); - let subnet; - if (this.stack.isManagedByAsea(AseaResourceType.EC2_SUBNET, `${vpcItem.name}/${subnetItem.name}`)) { - const subnetId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - ); - subnet = Subnet.fromSubnetAttributes( - this.stack, - pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${subnetItem.name}Subnet`), - { - subnetId, - routeTable, - name: subnetItem.name, - ipv4CidrBlock: subnetItem.ipv4CidrBlock ?? '', // Import Subnet is only supported for static cidr configuration - }, - ); - } else { - subnet = new Subnet(this.stack, pascalCase(`${vpcItem.name}Vpc`) + pascalCase(`${subnetItem.name}Subnet`), { - name: subnetItem.name, - assignIpv6OnCreation: subnetItem.assignIpv6OnCreation, - availabilityZone: isAvailabilityZoneId ? undefined : availabilityZone, - availabilityZoneId: isAvailabilityZoneId ? availabilityZone : undefined, - basePool, - enableDns64: subnetItem.enableDns64, - ipamAllocation: subnetItem.ipamAllocation, - ipv4CidrBlock: subnetItem.ipv4CidrBlock, - ipv6CidrBlock: subnetItem.ipv6CidrBlock, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - mapPublicIpOnLaunch: subnetItem.mapPublicIpOnLaunch, - privateDnsOptions: subnetItem.privateDnsOptions, - routeTable, - vpc, - tags: subnetItem.tags, - outpost, - }); - - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(vpcItem.name) + pascalCase(subnetItem.name)}SubnetId`), - parameterName: this.stack.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - stringValue: subnet.subnetId, - }); - // If the VPC has additional CIDR blocks, depend on those CIDRs to be associated - for (const cidr of [...vpc.cidrs.ipv4, ...vpc.cidrs.ipv6]) { - subnet.node.addDependency(cidr); - } - } - if (subnetItem.shareTargets) { - this.stack.addLogs(LogLevel.INFO, `Share subnet ${subnetItem.name}`); - this.stack.addResourceShare(subnetItem, `${subnetItem.name}_SubnetShare`, [subnet.subnetArn]); - } - return subnet; - } - - /** - * Create SSM parameters in shared subnet accounts - * @param vpcResources - * @param vpcMap - * @param subnetMap - * @returns - */ - private createSharedParameters( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - subnetMap: Map, - ): Map { - const sharedParameterMap = new Map(); - - for (const vpcItem of vpcResources) { - const accountIds: string[] = []; - const parameters: SsmParameterProps[] = []; - const vpc = getVpc(vpcMap, vpcItem.name) as Vpc; - const sharedSubnets = vpcItem.subnets ? vpcItem.subnets.filter(subnet => subnet.shareTargets) : []; - - // Add VPC to parameters - if (sharedSubnets.length > 0) { - parameters.push({ - name: this.stack.getSsmPath(SsmResourceType.VPC, [vpcItem.name]), - value: vpc.vpcId, - }); - - // Add shared subnet parameters and account IDs - this.setSharedSubnetParameters(vpcItem, sharedSubnets, subnetMap, parameters, accountIds); - - // Put SSM parameters - new PutSsmParameter(this.stack, pascalCase(`${vpcItem.name}VpcSharedParameters`), { - accountIds, - region: cdk.Stack.of(this.stack).region, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - parameters, - invokingAccountId: cdk.Stack.of(this.stack).account, - acceleratorPrefix: this.stack.acceleratorPrefix, - }); - sharedParameterMap.set(vpcItem.name, parameters); - } - } - return sharedParameterMap; - } - - /** - * Set shared subnet parameters and account IDs - * @param vpcItem - * @param sharedSubnets - * @param subnetMap - * @param parameters - * @param accountIds - */ - private setSharedSubnetParameters( - vpcItem: VpcConfig | VpcTemplatesConfig, - sharedSubnets: SubnetConfig[], - subnetMap: Map, - parameters: SsmParameterProps[], - accountIds: string[], - ) { - for (const subnetItem of sharedSubnets) { - // Add subnet to parameters - const subnet = getSubnet(subnetMap, vpcItem.name, subnetItem.name) as Subnet; - parameters.push({ - name: this.stack.getSsmPath(SsmResourceType.SUBNET, [vpcItem.name, subnetItem.name]), - value: subnet.subnetId, - }); - if (subnet.ipv4CidrBlock) { - parameters.push({ - name: this.stack.getSsmPath(SsmResourceType.SUBNET_IPV4_CIDR_BLOCK, [vpcItem.name, subnetItem.name]), - value: subnet.ipv4CidrBlock, - }); - } - - // Retrieve accounts to share parameters with - const subnetAccountIds = this.stack.getAccountIdsFromShareTarget(subnetItem.shareTargets!); - subnetAccountIds.forEach(accountId => { - // Only add account IDs not already in the array and are not for this account - // SSM parameters for this account are managed by native SSM resources - if (!accountIds.includes(accountId) && accountId !== cdk.Stack.of(this.stack).account) { - accountIds.push(accountId); - } - }); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/tgw-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/tgw-resources.ts deleted file mode 100644 index 89159af..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/tgw-resources.ts +++ /dev/null @@ -1,452 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AseaResourceType, - TransitGatewayAttachmentConfig, - TransitGatewayPeeringConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { - ITransitGatewayAttachment, - PutSsmParameter, - SsmParameterLookup, - Subnet, - TransitGatewayAttachment, - TransitGatewayPeering, - Vpc, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { AcceleratorStackProps } from '../../accelerator-stack'; -import { LogLevel } from '../network-stack'; -import { getSubnet, getTransitGatewayId, getVpc } from '../utils/getter-utils'; -import { NetworkVpcStack } from './network-vpc-stack'; - -export class TgwResources { - public readonly tgwAttachmentMap: Map; - public readonly tgwPeeringMap: Map; - public readonly vpcAttachmentRole?: cdk.aws_iam.Role; - - private stack: NetworkVpcStack; - - constructor( - networkVpcStack: NetworkVpcStack, - transitGatewayIds: Map, - vpcMap: Map, - subnetMap: Map, - props: AcceleratorStackProps, - ) { - this.stack = networkVpcStack; - - // Create cross-account access role for TGW attachments, if applicable - this.vpcAttachmentRole = this.createTgwAttachmentRole(this.stack.vpcsInScope, props); - // Create TGW attachments - this.tgwAttachmentMap = this.createTgwAttachments( - this.stack.vpcsInScope, - transitGatewayIds, - vpcMap, - subnetMap, - props.partition, - ); - // Create TGW peerings - this.tgwPeeringMap = this.createTransitGatewayPeering(props); - } - - /** - * Create a cross-account access role to describe TGW attachments - * if the target TGW resides in an external account - * @param vpcResources - * @param props - * @returns - */ - private createTgwAttachmentRole( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - props: AcceleratorStackProps, - ): cdk.aws_iam.Role | undefined { - // Get account IDs of external accounts hosting TGWs - const transitGatewayAccountIds = this.getTgwOwningAccountIds(vpcResources, props); - - // Create cross account access role to read transit gateway attachments if - // there are other accounts in the list - if (transitGatewayAccountIds.length > 0) { - this.stack.addLogs(LogLevel.INFO, `Create IAM Cross Account Access Role for TGW attachments`); - - const principals: cdk.aws_iam.PrincipalBase[] = []; - transitGatewayAccountIds.forEach(accountId => { - principals.push(new cdk.aws_iam.AccountPrincipal(accountId)); - }); - const role = new cdk.aws_iam.Role(this.stack, 'DescribeTgwAttachRole', { - roleName: `${props.prefixes.accelerator}-DescribeTgwAttachRole-${cdk.Stack.of(this.stack).region}`, - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeTransitGatewayAttachments'], - resources: ['*'], - }), - ], - }), - }, - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/DescribeTgwAttachRole/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: - 'DescribeTgwAttachRole needs access to every describe each transit gateway attachment in the account', - }, - ], - ); - return role; - } - return undefined; - } - - /** - * Return an array of owning account IDs of TGWs - * that reside in an external accounts if there are - * VPC attachments in scope - * @param vpcResources - * @param props - * @returns - */ - private getTgwOwningAccountIds( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - props: AcceleratorStackProps, - ): string[] { - const transitGatewayAccountIds: string[] = []; - - for (const vpcItem of vpcResources) { - for (const attachment of vpcItem.transitGatewayAttachments ?? []) { - const owningAccountId = props.accountsConfig.getAccountId(attachment.transitGateway.account); - - if ( - owningAccountId !== cdk.Stack.of(this.stack).account && - !transitGatewayAccountIds.includes(owningAccountId) - ) { - transitGatewayAccountIds.push(owningAccountId); - } - } - } - return transitGatewayAccountIds; - } - - /** - * Create TGW attachments for VPCs in stack context - * @param vpcResources - * @param transitGatewayIds - * @param vpcMap - * @param subnetMap - * @param partition - * @returns - */ - private createTgwAttachments( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - transitGatewayIds: Map, - vpcMap: Map, - subnetMap: Map, - partition: string, - ): Map { - const transitGatewayAttachments = new Map(); - - for (const vpcItem of vpcResources) { - for (const tgwAttachmentItem of vpcItem.transitGatewayAttachments ?? []) { - // Retrieve resources from maps - const transitGatewayId = getTransitGatewayId(transitGatewayIds, tgwAttachmentItem.transitGateway.name); - const vpc = getVpc(vpcMap, vpcItem.name) as Vpc; - const subnetIds = this.getAttachmentSubnetIds(tgwAttachmentItem, vpcItem.name, subnetMap); - - this.stack.addLogs( - LogLevel.INFO, - `Adding Transit Gateway Attachment to VPC ${vpcItem.name} for TGW ${tgwAttachmentItem.transitGateway.name}`, - ); - let attachment; - if ( - this.stack.isManagedByAsea( - AseaResourceType.TRANSIT_GATEWAY_ATTACHMENT, - `${vpcItem.name}/${tgwAttachmentItem.name}`, - ) - ) { - const attachmentId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.TGW_ATTACHMENT, [vpcItem.name, tgwAttachmentItem.name]), - ); - attachment = TransitGatewayAttachment.fromTransitGatewayAttachmentId( - this.stack, - pascalCase(`${tgwAttachmentItem.name}VpcTransitGatewayAttachment`), - { - attachmentId, - attachmentName: tgwAttachmentItem.name, - }, - ); - } else { - attachment = new TransitGatewayAttachment( - this.stack, - pascalCase(`${tgwAttachmentItem.name}VpcTransitGatewayAttachment`), - { - name: tgwAttachmentItem.name, - partition, - transitGatewayId, - subnetIds, - vpcId: vpc.vpcId, - options: tgwAttachmentItem.options, - tags: tgwAttachmentItem.tags, - }, - ); - this.stack.addSsmParameter({ - logicalId: pascalCase( - `SsmParam${pascalCase(vpcItem.name) + pascalCase(tgwAttachmentItem.name)}TransitGatewayAttachmentId`, - ), - parameterName: this.stack.getSsmPath(SsmResourceType.TGW_ATTACHMENT, [ - vpcItem.name, - tgwAttachmentItem.name, - ]), - stringValue: attachment.transitGatewayAttachmentId, - }); - } - transitGatewayAttachments.set(`${vpcItem.name}_${tgwAttachmentItem.transitGateway.name}`, attachment); - } - } - return transitGatewayAttachments; - } - - /** - * Get subnet IDs for a given TGW attachment - * @param tgwAttachmentItem - * @param vpcName - * @param subnetMap - * @returns - */ - private getAttachmentSubnetIds( - tgwAttachmentItem: TransitGatewayAttachmentConfig, - vpcName: string, - subnetMap: Map, - ): string[] { - const subnetIds: string[] = []; - for (const subnetItem of tgwAttachmentItem.subnets ?? []) { - const subnet = getSubnet(subnetMap, vpcName, subnetItem) as Subnet; - subnetIds.push(subnet.subnetId); - } - return subnetIds; - } - - /** - * Function to get Transit Gateway peering requestor tags - * @param transitGatewayPeeringItem {@link TransitGatewayPeeringConfig} - * @returns - */ - private getTgwPeeringRequestorTags(transitGatewayPeeringItem: TransitGatewayPeeringConfig): cdk.CfnTag[] | undefined { - let requesterTags: cdk.CfnTag[] | undefined; - - if (transitGatewayPeeringItem.requester.tags) { - if (transitGatewayPeeringItem.requester.tags.length > 0) { - requesterTags = transitGatewayPeeringItem.requester.tags; - } - } - - return requesterTags; - } - - /** - * Function to create SSM parameter in accepter environment for Transit Gateway peering attachment id - * @param transitGatewayPeeringItem - * @param crossAccountCondition - * @param peeringAttachmentId - * @param props - * - * @remarks - * Create SSM parameter for peering attachment ID in accepter account/region. - */ - private createTgwPeeringSsmParameterInAccepterEnvironment( - transitGatewayPeeringItem: TransitGatewayPeeringConfig, - crossAccountCondition: boolean, - peeringAttachmentId: string, - props: AcceleratorStackProps, - ) { - // Create SSM parameter for peering attachment ID in accepter account/region if different than requester account/region - if (crossAccountCondition) { - new PutSsmParameter( - this.stack, - pascalCase( - `CrossAcctSsmParam${transitGatewayPeeringItem.accepter.transitGatewayName}${transitGatewayPeeringItem.name}PeeringAttachmentId`, - ), - { - accountIds: [props.accountsConfig.getAccountId(transitGatewayPeeringItem.accepter.account)], - region: transitGatewayPeeringItem.accepter.region, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - parameters: [ - { - name: this.stack.getSsmPath(SsmResourceType.TGW_PEERING, [ - transitGatewayPeeringItem.accepter.transitGatewayName, - transitGatewayPeeringItem.name, - ]), - value: peeringAttachmentId, - }, - ], - invokingAccountId: cdk.Stack.of(this.stack).account, - acceleratorPrefix: props.prefixes.accelerator, - }, - ); - } else { - // Create SSM parameter for peering attachment ID in accepter account/region if same as requester account/region - this.stack.addSsmParameter({ - logicalId: pascalCase( - `SsmParam${transitGatewayPeeringItem.accepter.transitGatewayName}${transitGatewayPeeringItem.name}PeeringAttachmentId`, - ), - parameterName: this.stack.getSsmPath(SsmResourceType.TGW_PEERING, [ - transitGatewayPeeringItem.accepter.transitGatewayName, - transitGatewayPeeringItem.name, - ]), - stringValue: peeringAttachmentId, - }); - } - } - - /** - * Function to create TGW peering - */ - private createTransitGatewayPeering(props: AcceleratorStackProps): Map { - const tgwPeeringMap = new Map(); - - for (const transitGatewayPeeringItem of props.networkConfig.transitGatewayPeering ?? []) { - // Get account IDs - const requesterAccountId = props.accountsConfig.getAccountId(transitGatewayPeeringItem.requester.account); - const accepterAccountId = props.accountsConfig.getAccountId(transitGatewayPeeringItem.accepter.account); - const crossAccountCondition = - accepterAccountId !== requesterAccountId || - transitGatewayPeeringItem.accepter.region !== transitGatewayPeeringItem.requester.region; - - if (this.stack.isTargetStack([requesterAccountId], [transitGatewayPeeringItem.requester.region])) { - this.stack.addLogs( - LogLevel.INFO, - `Creating transit gateway peering for tgw ${transitGatewayPeeringItem.requester.transitGatewayName} with accepter tgw ${transitGatewayPeeringItem.accepter.transitGatewayName}`, - ); - - const requesterTransitGatewayRouteTableId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [ - transitGatewayPeeringItem.requester.transitGatewayName, - transitGatewayPeeringItem.requester.routeTableAssociations, - ]), - ); - - const accepterTransitGatewayId = new SsmParameterLookup( - this.stack, - pascalCase( - `${transitGatewayPeeringItem.requester.transitGatewayName}-${transitGatewayPeeringItem.accepter.transitGatewayName}-AccepterTransitGatewayIdLookup`, - ), - { - name: this.stack.getSsmPath(SsmResourceType.TGW, [transitGatewayPeeringItem.accepter.transitGatewayName]), - accountId: props.accountsConfig.getAccountId(transitGatewayPeeringItem.accepter.account), - parameterRegion: transitGatewayPeeringItem.accepter.region, - roleName: this.stack.acceleratorResourceNames.roles.tgwPeering, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention ?? 365, - acceleratorPrefix: props.prefixes.accelerator, - }, - ).value; - - const accepterTransitGatewayRouteTableId = new SsmParameterLookup( - this.stack, - pascalCase( - `${transitGatewayPeeringItem.requester.transitGatewayName}-${transitGatewayPeeringItem.accepter.transitGatewayName}-AccepterTransitGatewayRouteTableIdLookup`, - ), - { - name: this.stack.getSsmPath(SsmResourceType.TGW_ROUTE_TABLE, [ - transitGatewayPeeringItem.accepter.transitGatewayName, - transitGatewayPeeringItem.accepter.routeTableAssociations, - ]), - accountId: props.accountsConfig.getAccountId(transitGatewayPeeringItem.accepter.account), - parameterRegion: transitGatewayPeeringItem.accepter.region, - roleName: this.stack.acceleratorResourceNames.roles.tgwPeering, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention ?? 365, - acceleratorPrefix: props.prefixes.accelerator, - }, - ).value; - - // Get peering requestor tags - const requesterTags = this.getTgwPeeringRequestorTags(transitGatewayPeeringItem); - - const peeringAttachmentId = new TransitGatewayPeering( - this.stack, - pascalCase( - `${transitGatewayPeeringItem.requester.transitGatewayName}-${transitGatewayPeeringItem.accepter.transitGatewayName}-Peering`, - ), - { - requester: { - accountName: transitGatewayPeeringItem.requester.account, - transitGatewayId: cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.TGW, [transitGatewayPeeringItem.requester.transitGatewayName]), - ), - transitGatewayName: transitGatewayPeeringItem.requester.transitGatewayName, - transitGatewayRouteTableId: requesterTransitGatewayRouteTableId, - tags: requesterTags, - }, - accepter: { - accountId: props.accountsConfig.getAccountId(transitGatewayPeeringItem.accepter.account), - accountAccessRoleName: this.stack.acceleratorResourceNames.roles.tgwPeering, - region: transitGatewayPeeringItem.accepter.region, - transitGatewayName: transitGatewayPeeringItem.accepter.transitGatewayName, - transitGatewayId: accepterTransitGatewayId, - transitGatewayRouteTableId: accepterTransitGatewayRouteTableId, - applyTags: transitGatewayPeeringItem.accepter.applyTags ?? false, - autoAccept: transitGatewayPeeringItem.accepter.autoAccept ?? true, - }, - customLambdaLogKmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention ?? 365, - }, - ).peeringAttachmentId; - tgwPeeringMap.set(transitGatewayPeeringItem.name, peeringAttachmentId); - - // Create SSM parameter for peering attachment ID in requester region - this.stack.addSsmParameter({ - logicalId: pascalCase( - `SsmParam${transitGatewayPeeringItem.requester.transitGatewayName}${transitGatewayPeeringItem.name}PeeringAttachmentId`, - ), - parameterName: this.stack.getSsmPath(SsmResourceType.TGW_PEERING, [ - transitGatewayPeeringItem.requester.transitGatewayName, - transitGatewayPeeringItem.name, - ]), - stringValue: peeringAttachmentId, - }); - - // Create SSM parameter for peering attachment ID in accepter account/region. - this.createTgwPeeringSsmParameterInAccepterEnvironment( - transitGatewayPeeringItem, - crossAccountCondition, - peeringAttachmentId, - props, - ); - - this.stack.addLogs( - LogLevel.INFO, - `Completed transit gateway peering for tgw ${transitGatewayPeeringItem.requester.transitGatewayName} with accepter tgw ${transitGatewayPeeringItem.accepter.transitGatewayName}`, - ); - } - } - return tgwPeeringMap; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/vpc-resources.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/vpc-resources.ts deleted file mode 100644 index c44c344..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/network-vpc-stack/vpc-resources.ts +++ /dev/null @@ -1,921 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AseaResourceType, - CustomerGatewayConfig, - DefaultVpcsConfig, - isNetworkType, - VpcConfig, - VpcTemplatesConfig, - VpcPeeringConfig, -} from '@aws-accelerator/config'; -import { VpcFlowLogsConfig } from '@aws-accelerator/config/dist/lib/common/types'; -import { - DeleteDefaultSecurityGroupRules, - DeleteDefaultVpc, - PutSsmParameter, - SsmParameterProps, - Vpc, - VpnConnection, -} from '@aws-accelerator/constructs'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'pascal-case'; -import { LogLevel, NetworkStack } from '../network-stack'; -import { getVpc, getVpcConfig } from '../utils/getter-utils'; -import { isIpv4 } from '../utils/validation-utils'; - -type Ipv4VpcCidrBlock = { cidrBlock: string } | { ipv4IpamPoolId: string; ipv4NetmaskLength: number }; -type Ipv6VpcCidrBlock = { - amazonProvidedIpv6CidrBlock?: boolean; - ipv6CidrBlock?: string; - ipv6IpamPoolId?: string; - ipv6NetmaskLength?: number; - ipv6Pool?: string; -}; - -export class VpcResources { - public readonly deleteDefaultVpc?: DeleteDefaultVpc; - public readonly sharedParameterMap: Map; - public readonly vpcMap: Map; - public readonly vpnMap: Map; - public readonly centralEndpointRole?: cdk.aws_iam.Role; - - private stack: NetworkStack; - - constructor( - networkStack: NetworkStack, - ipamPoolMap: Map, - dhcpOptionsIds: Map, - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - acceleratorData: { acceleratorPrefix: string; ssmParamName: string; partition: string; useExistingRoles: boolean }, - configData: { - defaultVpcsConfig: DefaultVpcsConfig; - centralEndpointVpc: VpcConfig | undefined; - vpcFlowLogsConfig: VpcFlowLogsConfig | undefined; - customerGatewayConfigs: CustomerGatewayConfig[] | undefined; - vpcPeeringConfigs: VpcPeeringConfig[] | undefined; - firewalls: { accountId: string; firewallVpc: VpcConfig | VpcTemplatesConfig }[]; - }, - ) { - this.stack = networkStack; - - // Delete default VPC - this.deleteDefaultVpc = this.deleteDefaultVpcMethod(configData.defaultVpcsConfig); - // Create central endpoints role - this.centralEndpointRole = this.createCentralEndpointRole( - acceleratorData.partition, - vpcResources, - configData.centralEndpointVpc, - acceleratorData.acceleratorPrefix, - ); - // Create VPCs - - this.vpcMap = this.createVpcs( - this.stack.vpcsInScope, - ipamPoolMap, - dhcpOptionsIds, - configData.centralEndpointVpc, - configData.vpcFlowLogsConfig, - acceleratorData.useExistingRoles, - acceleratorData.acceleratorPrefix, - ); - // Create cross-account route role - this.createCrossAccountRouteRole( - configData.vpcPeeringConfigs, - acceleratorData.acceleratorPrefix, - acceleratorData.ssmParamName, - configData.firewalls, - ); - // - // Create VPN custom resource handler if needed - const customResourceHandler = this.stack.advancedVpnTypes.includes('vpc') - ? this.stack.createVpnOnEventHandler() - : undefined; - // - // Create VPN connections - this.vpnMap = this.createVpnConnections(this.vpcMap, configData.customerGatewayConfigs, customResourceHandler); - // - // Create cross-account/cross-region SSM parameters - this.sharedParameterMap = this.createSharedParameters( - this.stack.vpcsInScope, - this.vpcMap, - configData.customerGatewayConfigs, - ); - } - - /** - * Delete default VPC in the current account+region - * @param props - * @returns - */ - private deleteDefaultVpcMethod(defaultVpc: DefaultVpcsConfig): DeleteDefaultVpc | undefined { - const accountExcluded = defaultVpc.excludeAccounts && this.stack.isAccountExcluded(defaultVpc.excludeAccounts); - const regionExcluded = defaultVpc.excludeRegions && this.stack.isRegionExcluded(defaultVpc.excludeRegions); - - if (defaultVpc.delete && !accountExcluded && !regionExcluded) { - this.stack.addLogs(LogLevel.INFO, 'Add DeleteDefaultVpc'); - return new DeleteDefaultVpc(this.stack, 'DeleteDefaultVpc', { - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - }); - } - return; - } - - /** - * Create a cross-account role to assume if useCentralEndpoints VPC - * does not reside in the same account as the central endpoints VPC - * @param props - * @returns - */ - private createCentralEndpointRole( - partition: string, - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - centralEndpointVpc: VpcConfig | undefined, - acceleratorPrefix: string, - ): cdk.aws_iam.Role | undefined { - if (this.useCentralEndpoints(vpcResources, partition)) { - if (!centralEndpointVpc) { - this.stack.addLogs(LogLevel.ERROR, `useCentralEndpoints set to true, but no central endpoint VPC detected`); - throw new Error(`Configuration validation failed at runtime.`); - } else { - const centralEndpointVpcAccountId = this.stack.getVpcAccountIds(centralEndpointVpc).join(); - if (centralEndpointVpcAccountId !== cdk.Stack.of(this.stack).account) { - this.stack.addLogs( - LogLevel.INFO, - 'Central endpoints VPC is in an external account, create a role to enable central endpoints', - ); - const role = new cdk.aws_iam.Role(this.stack, 'EnableCentralEndpointsRole', { - roleName: `${acceleratorPrefix}-EnableCentralEndpointsRole-${cdk.Stack.of(this.stack).region}`, - assumedBy: new cdk.aws_iam.AccountPrincipal(centralEndpointVpcAccountId), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeVpcs', 'route53:AssociateVPCWithHostedZone'], - resources: ['*'], - }), - ], - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath( - this.stack, - `${this.stack.stackName}/EnableCentralEndpointsRole/Resource/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'EnableCentralEndpointsRole needs access to every describe every VPC in the account ', - }, - ], - ); - return role; - } - } - } - return undefined; - } - - /** - * Determine if any VPCs in the current stack context have useCentralEndpoints enabled - * @param vpcResources - * @param partition - */ - private useCentralEndpoints(vpcResources: (VpcConfig | VpcTemplatesConfig)[], partition: string): boolean { - for (const vpcItem of vpcResources) { - if (vpcItem.useCentralEndpoints) { - if (partition !== 'aws' && partition !== 'aws-cn') { - this.stack.addLogs( - LogLevel.ERROR, - 'useCentralEndpoints set to true, but AWS Partition is not commercial. Please change it to false.', - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - return true; - } - } - return false; - } - - /** - * Add necessary permissions to cross-account role if VPC peering is implemented - * @param props - */ - private getCrossAccountRoutePolicies(peeringAccountIds: string[], ssmPrefix: string) { - const policyStatements = [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateRoute', 'ec2:DeleteRoute'], - resources: [`arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:route-table/*`], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter'], - resources: [ - `arn:${cdk.Aws.PARTITION}:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter${ssmPrefix}/network/*`, - ], - }), - ]; - - if (peeringAccountIds.length > 0) { - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ec2:AcceptVpcPeeringConnection', - 'ec2:CreateVpcPeeringConnection', - 'ec2:DeleteVpcPeeringConnection', - ], - resources: [ - `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:vpc/*`, - `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:vpc-peering-connection/*`, - ], - }), - ); - } - return policyStatements; - } - - /** - * Create cross-account route role if target ENIs exist in external account(s) or peering connections defined - * @param props - */ - private createCrossAccountRouteRole( - vpcPeeringConfig: VpcPeeringConfig[] | undefined, - acceleratorPrefix: string, - ssmParamNamePrefix: string, - firewallInfo: { accountId: string; firewallVpc: VpcConfig | VpcTemplatesConfig }[], - ): cdk.aws_iam.Role | undefined { - const crossAccountEniAccountIds = this.getCrossAccountEniAccountIds(firewallInfo); - const vpcPeeringAccountIds = this.getVpcPeeringAccountIds(vpcPeeringConfig); - const policyList = this.getCrossAccountRoutePolicies(vpcPeeringAccountIds, ssmParamNamePrefix); - - // - // Create cross account route role - // - const accountIdSet = [...new Set([...(crossAccountEniAccountIds ?? []), ...(vpcPeeringAccountIds ?? [])])]; - if (accountIdSet.length > 0) { - this.stack.addLogs( - LogLevel.INFO, - `Creating cross-account role for the creation of VPC peering connections and routes targeting ENIs`, - ); - - const principals: cdk.aws_iam.PrincipalBase[] = []; - for (const accountId of accountIdSet) { - principals.push(new cdk.aws_iam.AccountPrincipal(accountId)); - } - - const role = new cdk.aws_iam.Role(this.stack, 'VpcPeeringRole', { - roleName: `${acceleratorPrefix}-VpcPeeringRole-${cdk.Stack.of(this.stack).region}`, - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: policyList, - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressionsByPath(this.stack, `${this.stack.stackName}/VpcPeeringRole/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'VpcPeeringRole needs access to create routes for VPCs in the account', - }, - ]); - return role; - } - return undefined; - } - /** - * Return an array of cross-account account IDs for VPCs with firewall VPC endpoints - * @param props - * @returns - */ - private getCrossAccountEniAccountIds( - firewallInfo: { accountId: string; firewallVpc: VpcConfig | VpcTemplatesConfig }[], - ) { - const crossAccountEniAccountIds: string[] = []; - - for (const firewallItem of firewallInfo) { - if (this.isFirewallOwnedByDifferentAccount(firewallItem)) { - crossAccountEniAccountIds.push(...firewallItem.accountId); - } - } - // firewalls can be deployed in same account across regions so removing duplicates. - return [...new Set(crossAccountEniAccountIds)]; - } - - private isFirewallOwnedByDifferentAccount(firewallItem: { - accountId: string; - firewallVpc: VpcConfig | VpcTemplatesConfig; - }) { - // Check that firewall account is not this account - - // Firewall can be deployed to same account but different region - // Check that the firewall's target VPC is deployed in this account - - return firewallItem.accountId !== this.stack.account && this.vpcMap.has(firewallItem.firewallVpc.name); - } - - /** - * Return an array of VPC peering requester account IDs - * if an accepter VPC exists in this account+region - * @param props - * @returns - */ - private getVpcPeeringAccountIds(vpcPeering: VpcPeeringConfig[] | undefined): string[] { - // - // Loop through VPC peering entries. Determine if accepter VPC is in external account. - // - const vpcPeeringAccountIds: string[] = []; - for (const peering of vpcPeering ?? []) { - // Get requester and accepter VPC configurations - const requesterVpc = this.stack.vpcResources.find(item => item.name === peering.vpcs[0])!; - const accepterVpc = this.stack.vpcResources.find(item => item.name === peering.vpcs[1])!; - const requesterAccountIds = this.stack.getVpcAccountIds(requesterVpc); - const accepterAccountIds = this.stack.getVpcAccountIds(accepterVpc); - let crossAccountCondition = false; - - // Check for different account peering -- only add IAM role to accepter account - if (this.stack.isTargetStack(accepterAccountIds, [accepterVpc.region])) { - if ( - isNetworkType('IVpcTemplatesConfig', requesterVpc) || - isNetworkType('IVpcTemplatesConfig', accepterVpc) - ) { - crossAccountCondition = - // true: If VPCs in peering connection are cross region - accepterVpc.region !== requesterVpc.region || - // true: If requester or accepter has more accounts - requesterAccountIds.length !== accepterAccountIds.length || - // true: If requester has any other accounts apart from accepter - requesterAccountIds.filter(requesterAccountId => requesterAccountId !== this.stack.account).length > 0; - } else { - crossAccountCondition = - requesterVpc.account !== accepterVpc.account || requesterVpc.region !== accepterVpc.region; - } - if (crossAccountCondition) { - vpcPeeringAccountIds.push(...requesterAccountIds); - } - if (requesterVpc.region !== accepterVpc.region) { - vpcPeeringAccountIds.push(this.stack.account); - } - } - } - return [...new Set(vpcPeeringAccountIds)]; - } - - /** - * Create VPCs for this stack context - * @param vpcResources - * @param ipamPoolMap - * @param dhcpOptionsIds - * @param props - */ - private createVpcs( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - ipamPoolMap: Map, - dhcpOptionsIds: Map, - centralEndpointVpc: VpcConfig | undefined, - vpcFlowLogsNetworkConfig: VpcFlowLogsConfig | undefined, - useExistingRoles: boolean, - acceleratorPrefix: string, - ): Map { - const vpcMap = new Map(); - - for (const vpcItem of vpcResources) { - const vpc = this.createVpcItem( - vpcItem, - dhcpOptionsIds, - ipamPoolMap, - centralEndpointVpc, - vpcFlowLogsNetworkConfig, - useExistingRoles, - acceleratorPrefix, - ); - vpcMap.set(vpcItem.name, vpc); - } - return vpcMap; - } - - /** - * Create a VPC from a given configuration item - * @param vpcItem - * @param dhcpOptionsIds - * @param ipamPoolMap - * @param props - * @returns - */ - private createVpcItem( - vpcItem: VpcConfig | VpcTemplatesConfig, - dhcpOptionsIds: Map, - ipamPoolMap: Map, - centralEndpointVpc: VpcConfig | undefined, - vpcFlowLogsNetworkConfig: VpcFlowLogsConfig | undefined, - useExistingRoles: boolean, - acceleratorPrefix: string, - ): Vpc { - this.stack.addLogs(LogLevel.INFO, `Adding VPC ${vpcItem.name}`); - // - // Determine if using IPAM or manual CIDRs - // - let cidr: string | undefined = undefined; - let poolId: string | undefined = undefined; - let poolNetmask: number | undefined = undefined; - // Get first CIDR in array - if (vpcItem.cidrs) { - cidr = vpcItem.cidrs[0]; - } - - // Get IPAM details - if (vpcItem.ipamAllocations) { - poolId = ipamPoolMap.get(vpcItem.ipamAllocations[0].ipamPoolName); - if (!poolId) { - this.stack.addLogs( - LogLevel.ERROR, - `${vpcItem.name}: unable to locate IPAM pool ${vpcItem.ipamAllocations[0].ipamPoolName}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - poolNetmask = vpcItem.ipamAllocations[0].netmaskLength; - } - // Create or import VPC - const vpc = this.createOrImportVpc({ - vpcItem, - dhcpOptionsIds, - cidr, - poolId, - poolNetmask, - }); - // - // Create additional IPv4 CIDRs - // - this.createAdditionalIpv4Cidrs(vpc, vpcItem, ipamPoolMap); - // - // Create IPv6 CIDRs - // - this.createIpv6Cidrs(vpc, vpcItem); - // - // Add central endpoint tags - // - this.addCentralEndpointTags(vpc, vpcItem, centralEndpointVpc); - // - // Add flow logs, if configured - // - this.getVpcFlowLogConfig(vpc, vpcItem, vpcFlowLogsNetworkConfig, useExistingRoles, acceleratorPrefix); - // - // Delete default security group rules - // - this.deleteDefaultSgRules(vpc, vpcItem); - // - // Add dependency on default VPC deletion - // - this.addDefaultVpcDependency(vpc, vpcItem); - return vpc; - } - - /** - * Create or import the configured VPC - * @param options - * @returns Vpc - */ - private createOrImportVpc(options: { - vpcItem: VpcConfig | VpcTemplatesConfig; - dhcpOptionsIds: Map; - cidr?: string; - poolId?: string; - poolNetmask?: number; - }): Vpc { - let vpc: Vpc; - - if (this.stack.isManagedByAsea(AseaResourceType.EC2_VPC, options.vpcItem.name)) { - // - // Import VPC - // - const vpcId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.VPC, [options.vpcItem.name]), - ); - const internetGatewayId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.IGW, [options.vpcItem.name]), - ); - const virtualPrivateGatewayId = this.stack.getExternalResourceParameter( - this.stack.getSsmPath(SsmResourceType.VPN_GW, [options.vpcItem.name]), - ); - vpc = Vpc.fromVpcAttributes(this.stack, pascalCase(`${options.vpcItem.name}Vpc`), { - name: options.vpcItem.name, - vpcId, - internetGatewayId, - virtualPrivateGatewayId, - // ASEA VPC Resources are all cidr specified resources. IPAM is not supported during migration. - cidrBlock: options.vpcItem.cidrs?.[0] ?? '', - }); - if (options.vpcItem.internetGateway && !internetGatewayId) { - vpc.addInternetGateway(); - } - if (options.vpcItem.virtualPrivateGateway && !virtualPrivateGatewayId) { - vpc.addVirtualPrivateGateway(options.vpcItem.virtualPrivateGateway.asn); - } - if (options.vpcItem.dhcpOptions) { - vpc.setDhcpOptions(options.vpcItem.dhcpOptions); - } - } else { - // - // Create VPC - // - vpc = new Vpc(this.stack, pascalCase(`${options.vpcItem.name}Vpc`), { - name: options.vpcItem.name, - ipv4CidrBlock: options.cidr, - internetGateway: options.vpcItem.internetGateway, - dhcpOptions: options.dhcpOptionsIds.get(options.vpcItem.dhcpOptions ?? ''), - egressOnlyIgw: options.vpcItem.egressOnlyIgw, - enableDnsHostnames: options.vpcItem.enableDnsHostnames ?? true, - enableDnsSupport: options.vpcItem.enableDnsSupport ?? true, - instanceTenancy: options.vpcItem.instanceTenancy ?? 'default', - ipv4IpamPoolId: options.poolId, - ipv4NetmaskLength: options.poolNetmask, - tags: options.vpcItem.tags, - virtualPrivateGateway: options.vpcItem.virtualPrivateGateway, - }); - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(options.vpcItem.name)}VpcId`), - parameterName: this.stack.getSsmPath(SsmResourceType.VPC, [options.vpcItem.name]), - stringValue: vpc.vpcId, - }); - - if (vpc.virtualPrivateGatewayId) { - this.stack.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(options.vpcItem.name)}VpnGatewayId`), - parameterName: this.stack.getSsmPath(SsmResourceType.VPN_GW, [options.vpcItem.name]), - stringValue: vpc.virtualPrivateGatewayId!, - }); - } - } - return vpc; - } - - /** - * Create additional IPv4 CIDR blocks for a given VPC - * @param vpc - * @param vpcItem - * @param ipamPoolMap - * @returns - */ - private createAdditionalIpv4Cidrs( - vpc: Vpc, - vpcItem: VpcConfig | VpcTemplatesConfig, - ipamPoolMap: Map, - ): Ipv4VpcCidrBlock[] { - const additionalCidrs: Ipv4VpcCidrBlock[] = []; - - if (vpcItem.cidrs && vpcItem.cidrs.length > 1) { - for (const vpcCidr of vpcItem.cidrs.slice(1)) { - if (this.stack.isManagedByAsea(AseaResourceType.EC2_VPC_CIDR, `${vpcItem.name}-${vpcCidr}`)) { - // CIDR is created by external source. Skipping creation - continue; - } - this.stack.addLogs(LogLevel.INFO, `Adding secondary CIDR ${vpcCidr} to VPC ${vpcItem.name}`); - vpc.addIpv4Cidr({ cidrBlock: vpcCidr }); - additionalCidrs.push({ cidrBlock: vpcCidr }); - } - } - - if (vpcItem.ipamAllocations && vpcItem.ipamAllocations.length > 1) { - for (const alloc of vpcItem.ipamAllocations.slice(1)) { - this.stack.addLogs( - LogLevel.INFO, - `Adding secondary IPAM allocation with netmask ${alloc.netmaskLength} to VPC ${vpcItem.name}`, - ); - const poolId = ipamPoolMap.get(alloc.ipamPoolName); - if (!poolId) { - this.stack.addLogs(LogLevel.ERROR, `${vpcItem.name}: unable to locate IPAM pool ${alloc.ipamPoolName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - vpc.addIpv4Cidr({ ipv4IpamPoolId: poolId, ipv4NetmaskLength: alloc.netmaskLength }); - additionalCidrs.push({ ipv4IpamPoolId: poolId, ipv4NetmaskLength: alloc.netmaskLength }); - } - } - return additionalCidrs; - } - - /** - * Create IPv6 CIDRs for a given VPC - * @param vpc Vpc - * @param vpcItem VpcConfig | VpcTemplatesConfig - * @returns Ipv6VpcCidrBlock[] - */ - private createIpv6Cidrs(vpc: Vpc, vpcItem: VpcConfig | VpcTemplatesConfig): Ipv6VpcCidrBlock[] { - const ipv6Cidrs: Ipv6VpcCidrBlock[] = []; - - for (const vpcCidr of vpcItem.ipv6Cidrs ?? []) { - const cidrProps = { - amazonProvidedIpv6CidrBlock: vpcCidr.amazonProvided, - ipv6CidrBlock: vpcCidr.cidrBlock, - ipv6Pool: vpcCidr.byoipPoolId, - }; - vpc.addIpv6Cidr(cidrProps); - ipv6Cidrs.push(cidrProps); - } - return ipv6Cidrs; - } - - /** - * Add central endpoint tags to the given VPC if useCentralEndpoints is enabled - * @param vpc - * @param vpcItem - * @param props - * @returns - */ - private addCentralEndpointTags( - vpc: Vpc, - vpcItem: VpcConfig | VpcTemplatesConfig, - centralEndpointVpc: VpcConfig | undefined, - ): boolean { - if (vpcItem.useCentralEndpoints) { - if (!centralEndpointVpc) { - this.stack.addLogs(LogLevel.ERROR, 'Attempting to use central endpoints with no Central Endpoints defined'); - throw new Error(`Configuration validation failed at runtime.`); - } - const centralEndpointVpcAccountId = this.stack.getVpcAccountIds(centralEndpointVpc).join(); - if (!centralEndpointVpcAccountId) { - this.stack.addLogs( - LogLevel.ERROR, - 'Attempting to use central endpoints without an account ID for the Central Endpoints defined', - ); - throw new Error(`Configuration validation failed at runtime.`); - } - cdk.Tags.of(vpc).add('accelerator:use-central-endpoints', 'true'); - cdk.Tags.of(vpc).add('accelerator:central-endpoints-account-id', centralEndpointVpcAccountId!); - return true; - } - return false; - } - - /** - * Determines whether flow logs are created for a given VPC - * @param vpc - * @param vpcItem - * @param props - * - */ - private getVpcFlowLogConfig( - vpc: Vpc, - vpcItem: VpcConfig | VpcTemplatesConfig, - vpcFlowLogsNetworkConfig: VpcFlowLogsConfig | undefined, - useExistingRoles: boolean, - acceleratorPrefix: string, - ) { - let vpcFlowLogs: VpcFlowLogsConfig | undefined; - - if (vpcItem.vpcFlowLogs) { - vpcFlowLogs = vpcItem.vpcFlowLogs; - } else { - vpcFlowLogs = vpcFlowLogsNetworkConfig; - } - - if (vpcFlowLogs) { - this.createVpcFlowLogs(vpc, vpcFlowLogs, useExistingRoles, acceleratorPrefix); - } else { - NagSuppressions.addResourceSuppressions(vpc, [ - { id: 'AwsSolutions-VPC7', reason: 'VPC does not have flow logs configured' }, - ]); - } - } - - /** - * Function to create VPC flow logs - * @param vpc - * @param vpcItem - * @param props - * - */ - private createVpcFlowLogs( - vpc: Vpc, - vpcFlowLogs: VpcFlowLogsConfig, - useExistingRoles: boolean, - acceleratorPrefix: string, - ) { - let logFormat: string | undefined = undefined; - let destinationBucketArn: string | undefined; - let overrideS3LogPath: string | undefined = undefined; - - if (vpcFlowLogs.destinations.includes('s3')) { - destinationBucketArn = cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.acceleratorResourceNames.parameters.flowLogsDestinationBucketArn, - ); - - if (vpcFlowLogs.destinationsConfig?.s3?.overrideS3LogPath) { - overrideS3LogPath = vpcFlowLogs.destinationsConfig?.s3?.overrideS3LogPath; - } - } - - if (!vpcFlowLogs.defaultFormat) { - logFormat = vpcFlowLogs.customFields.map(c => `$\{${c}}`).join(' '); - } - - vpc.addFlowLogs({ - destinations: vpcFlowLogs.destinations, - trafficType: vpcFlowLogs.trafficType, - maxAggregationInterval: vpcFlowLogs.maxAggregationInterval, - logFormat, - logRetentionInDays: vpcFlowLogs.destinationsConfig?.cloudWatchLogs?.retentionInDays ?? this.stack.logRetention, - encryptionKey: this.stack.cloudwatchKey, - bucketArn: destinationBucketArn, - useExistingRoles, - acceleratorPrefix, - overrideS3LogPath, - }); - } - - /** - * Delete default security group rules for a given VPC - * @param vpc - * @param vpcItem - * @returns - */ - private deleteDefaultSgRules(vpc: Vpc, vpcItem: VpcConfig | VpcTemplatesConfig): boolean { - if (vpcItem.defaultSecurityGroupRulesDeletion) { - this.stack.addLogs(LogLevel.INFO, `Delete default security group ingress and egress rules for ${vpcItem.name}`); - new DeleteDefaultSecurityGroupRules(this.stack, pascalCase(`DeleteSecurityGroupRules-${vpcItem.name}`), { - vpcId: vpc.vpcId, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - }); - return true; - } - return false; - } - - /** - * Add dependency on deleting the default VPC to reduce risk of exceeding service limits - * @param vpc - * @param vpcItem - * @returns - */ - private addDefaultVpcDependency(vpc: Vpc, vpcItem: VpcConfig | VpcTemplatesConfig): void { - if (this.deleteDefaultVpc) { - this.stack.addLogs(LogLevel.INFO, `Adding dependency on deletion of the default VPC for ${vpcItem.name}`); - vpc.node.addDependency(this.deleteDefaultVpc); - } - } - - /** - * Create a VPC connection for a given VPC - * @param vpcMap Map - * @param props AcceleratorStackProps - * @param customResourceHandler cdk.aws_lambda.IFunction | undefined - * @returns Map - */ - private createVpnConnections( - vpcMap: Map, - customerGatewayConfig: CustomerGatewayConfig[] | undefined, - customResourceHandler?: cdk.aws_lambda.IFunction, - ): Map { - const vpnMap = new Map(); - const ipv4Cgws = customerGatewayConfig?.filter(cgw => isIpv4(cgw.ipAddress)); - - for (const cgw of ipv4Cgws ?? []) { - for (const vpnItem of cgw.vpnConnections ?? []) { - if (vpnItem.vpc && vpcMap.has(vpnItem.vpc)) { - // - // Get CGW ID and VPC - const customerGatewayId = cdk.aws_ssm.StringParameter.valueForStringParameter( - this.stack, - this.stack.getSsmPath(SsmResourceType.CGW, [cgw.name]), - ); - const vpc = getVpc(vpcMap, vpnItem.vpc) as Vpc; - - this.stack.addLogs( - LogLevel.INFO, - `Creating Vpn Connection with Customer Gateway ${cgw.name} to the VPC ${vpnItem.vpc}`, - ); - const vpn = new VpnConnection( - this.stack, - this.setVgwVpnLogicalId(vpc, vpnItem.name), - this.stack.setVpnProps({ - vpnItem, - customerGatewayId, - customResourceHandler, - virtualPrivateGateway: vpc.virtualPrivateGatewayId, - }), - ); - vpnMap.set(`${vpc.name}_${vpnItem.name}`, vpn.vpnConnectionId); - vpc.vpnConnections.push(vpn); - } - } - } - return vpnMap; - } - - /** - * Sets the logical ID of the VGW VPN. - * Required for backward compatibility with previous versions -- - * takes into account the possibility of multiple VPNs to the same VGW. - * @param vpc - * @param vpnName - * @returns - */ - private setVgwVpnLogicalId(vpc: Vpc, vpnName: string): string { - if (vpc.vpnConnections.length === 0) { - return pascalCase(`${vpc.name}-VgwVpnConnection`); - } else { - return pascalCase(`${vpc.name}${vpnName}-VgwVpnConnection`); - } - } - - /** - * Create cross-account/cross-region SSM parameters for site-to-site VPN connections - * that must reference the TGW/TGW route table in cross-account VPN scenarios - * @param vpcResources (VpcConfig | VpcTemplatesConfig)[] - * @param vpcMap Map - * @param customerGateways CustomerGatewayConfig[] - * @returns Map - */ - private createSharedParameters( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - customerGateways?: CustomerGatewayConfig[], - ): Map { - const sharedParameterMap = new Map(); - const vpcNames = vpcResources.map(vpc => vpc.name); - const vgwVpnCustomerGateways = customerGateways - ? customerGateways.filter(cgw => cgw.vpnConnections?.filter(vpn => vpcNames.includes(vpn.vpc ?? ''))) - : []; - const crossAcctFirewallReferenceCgws = vgwVpnCustomerGateways.filter( - cgw => !isIpv4(cgw.ipAddress) && !this.stack.firewallVpcInScope(cgw), - ); - - for (const crossAcctCgw of crossAcctFirewallReferenceCgws) { - const firewallVpcConfig = this.stack.getFirewallVpcConfig(crossAcctCgw); - const accountIds = this.stack.getVpcAccountIds(firewallVpcConfig); - const parameters = this.setCrossAccountSsmParameters(crossAcctCgw, vpcResources, vpcMap); - - if (parameters.length > 0) { - this.stack.addLogs( - LogLevel.INFO, - `Putting cross-account/cross-region SSM parameters for VPC ${firewallVpcConfig.name}`, - ); - // Put SSM parameters - new PutSsmParameter(this.stack, pascalCase(`${crossAcctCgw.name}VgwVpnSharedParameters`), { - accountIds, - region: firewallVpcConfig.region, - roleName: this.stack.acceleratorResourceNames.roles.crossAccountSsmParameterShare, - kmsKey: this.stack.cloudwatchKey, - logRetentionInDays: this.stack.logRetention, - parameters, - invokingAccountId: this.stack.account, - acceleratorPrefix: this.stack.acceleratorPrefix, - }); - sharedParameterMap.set(crossAcctCgw.name, parameters); - } - } - return sharedParameterMap; - } - - /** - * Returns an array of SSM parameters for cross-account VGW VPN connections - * @param cgw CustomerGatewayConfig - * @param vpcResources (VpcConfig | VpcTemplatesConfig)[] - * @param vpcMap Map - * @returns SsmParameterProps[] - */ - private setCrossAccountSsmParameters( - cgw: CustomerGatewayConfig, - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcMap: Map, - ) { - const ssmParameters: SsmParameterProps[] = []; - - for (const vpnItem of cgw.vpnConnections ?? []) { - if (vpnItem.vpc && vpcMap.has(vpnItem.vpc)) { - // - // Set VGW ID - const vpcConfig = getVpcConfig(vpcResources, vpnItem.vpc); - const vpc = getVpc(vpcMap, vpnItem.vpc) as Vpc; - ssmParameters.push({ - name: this.stack.getSsmPath(SsmResourceType.CROSS_ACCOUNT_VGW, [cgw.name, vpcConfig.name]), - value: vpc.virtualPrivateGatewayId ?? '', - }); - } - } - return [...new Set(ssmParameters)]; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/getter-utils.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/getter-utils.ts deleted file mode 100644 index b8b8978..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/getter-utils.ts +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CustomerGatewayConfig, - Ec2FirewallInstanceConfig, - SubnetConfig, - TransitGatewayConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { - IIpamSubnet, - PrefixList, - RouteTable, - SecurityGroup, - Subnet, - Vpc, - VpnConnection, -} from '@aws-accelerator/constructs'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -const logger = createLogger(['getter-utils']); - -/** - * Returns a prefix list object or tokenized ID from a given map if it exists - * @param prefixListMap - * @param prefixListName - * @returns - */ -export function getPrefixList( - prefixListMap: Map | Map, - prefixListName: string, -): PrefixList | string { - const prefixList = prefixListMap.get(prefixListName); - - if (!prefixList) { - logger.error(`Prefix list ${prefixListName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return prefixList; -} - -/** - * Returns a route table construct object from a given map if it exists - * @param routeTableMap - * @param vpcName - * @param routeTableName - * @returns - */ -export function getRouteTable( - routeTableMap: Map | Map, - vpcName: string, - routeTableName: string, -): RouteTable | string { - const key = `${vpcName}_${routeTableName}`; - const routeTable = routeTableMap.get(key); - - if (!routeTable) { - logger.error(`VPC ${vpcName} route table ${routeTableName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return routeTable; -} - -/** - * Returns a security group construct object or tokenized ID from a given map if it exists - * @param securityGroupMap - * @param vpcName - * @param securityGroupName - * @returns - */ -export function getSecurityGroup( - securityGroupMap: Map | Map, - vpcName: string, - securityGroupName: string, -): SecurityGroup | string { - const key = `${vpcName}_${securityGroupName}`; - const securityGroup = securityGroupMap.get(key); - - if (!securityGroup) { - logger.error(`VPC ${vpcName} security group ${securityGroupName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return securityGroup; -} - -/** - * Returns a subnet construct object or tokenized ID from a given map if it exists - * @param subnetMap - * @param vpcName - * @param subnetName - * @returns - */ -export function getSubnet( - subnetMap: Map | Map, - vpcName: string, - subnetName: string, - accountName?: string, -): Subnet | IIpamSubnet { - const key = accountName ? `${vpcName}_${accountName}_${subnetName}` : `${vpcName}_${subnetName}`; - const subnet = subnetMap.get(key); - - if (!subnet) { - logger.error(`VPC ${vpcName} subnet ${subnetName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return subnet; -} - -/** - * Get Transit Gateway ID from a given map, if it exists - * @param transitGatewayMap - * @param tgwName - * @returns - */ -export function getTransitGatewayId(transitGatewayMap: Map, tgwName: string): string { - const tgwId = transitGatewayMap.get(tgwName); - - if (!tgwId) { - logger.error(`Transit Gateway ${tgwName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return tgwId; -} - -/** - * Get Transit Gateway route table ID from a given map, if it exists - * @param tgwRouteTableMap Map - * @param tgwName string - * @param routeTableName string - * @returns string - */ -export function getTgwRouteTableId( - tgwRouteTableMap: Map, - tgwName: string, - routeTableName: string, -): string { - const key = `${tgwName}_${routeTableName}`; - const routeTableId = tgwRouteTableMap.get(key); - - if (!routeTableId) { - logger.error(`Transit Gateway ${tgwName} route table ${routeTableName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return routeTableId; -} - -/** - * Returns a VPC construct object from a given map if it exists - * @param vpcMap - * @param vpcName - * @returns - */ -export function getVpc(vpcMap: Map | Map, vpcName: string): Vpc | string { - const vpc = vpcMap.get(vpcName); - - if (!vpc) { - logger.error(`VPC ${vpcName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return vpc; -} - -/** - * Returns a TGW VPN connection construct object from a given map if it exists - * @param vpnMap Map - * @param tgwName string - * @param vpnName string - * @returns VpnConnection - */ -export function getTgwVpnConnection( - vpnMap: Map, - tgwName: string, - vpnName: string, -): VpnConnection { - const key = `${tgwName}_${vpnName}`; - const vpn = vpnMap.get(key); - - if (!vpn) { - logger.error(`VPN connection ${vpnName} for transit gateway ${tgwName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return vpn; -} - -/** - * Returns a TGW VPN attachment ID from a given map if it exists - * @param attachmentMap Map - * @param tgwName string - * @param vpnName string - * @returns string - */ -export function getVpnAttachmentId(attachmentMap: Map, tgwName: string, vpnName: string): string { - const key = `${tgwName}_${vpnName}`; - const attachmentId = attachmentMap.get(key); - - if (!attachmentId) { - logger.error(`VPN attachment ID for VPN ${vpnName} to transit gateway ${tgwName} does not exist in map`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return attachmentId; -} - -/** - * Returns a Transit Gateway configuration object from a given list of configurations if it exists - * @param tgwResources TransitGatewayConfig[] - * @param tgwName string - * @returns TransitGatewayConfig - */ -export function getTgwConfig(tgwResources: TransitGatewayConfig[], tgwName: string): TransitGatewayConfig { - const tgwConfig = tgwResources.find(tgw => tgw.name === tgwName); - if (!tgwConfig) { - logger.error(`Transit Gateway configuration for TGW ${tgwName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - return tgwConfig; -} - -/** - * Returns a VPC configuration object from a given list of configurations if it exists - * @param vpcResources - * @param vpcName - * @returns - */ -export function getVpcConfig( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcName: string, -): VpcConfig | VpcTemplatesConfig { - const vpcConfig = vpcResources.find(vpc => vpc.name === vpcName); - if (!vpcConfig) { - logger.error(`VPC configuration for VPC ${vpcName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - return vpcConfig; -} - -/** - * Returns the name of the account owner of a VPC name from a given list of configurations if it exists - * @param vpcResources - * @param vpcName - * @returns - */ -export function getVpcOwnerAccountName(vpcResources: (VpcConfig | VpcTemplatesConfig)[], vpcName: string): string { - const vpcConfig = getVpcConfig(vpcResources, vpcName); - - if (vpcConfig instanceof VpcTemplatesConfig) { - logger.error(`VPC Template ${vpcName} does not include 'account' property`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return (vpcConfig as VpcConfig).account; -} - -/** - * Returns a subnet configuration object from a given list of configurations if it exists - * @param vpcItem - * @param subnetName - * @returns - */ -export function getSubnetConfig(vpcItem: VpcConfig | VpcTemplatesConfig, subnetName: string): SubnetConfig { - const subnetConfig = vpcItem.subnets?.find(subnet => subnet.name === subnetName); - if (!subnetConfig) { - logger.error(`Subnet configuration for VPC ${vpcItem.name} subnet ${subnetName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - return subnetConfig; -} - -/** - * Returns a firewall instance configuration object from a given list of configurations if it exists - * @param firewallName string - * @param firewallInstanceConfig Ec2FirewallInstanceConfig[] | undefined - * @returns Ec2FirewallInstanceConfig - */ -export function getFirewallInstanceConfig( - firewallName: string, - firewallInstanceConfig?: Ec2FirewallInstanceConfig[], -): Ec2FirewallInstanceConfig { - if (!firewallInstanceConfig) { - logger.error( - `Firewall instance configuration for firewall ${firewallName} not found. Firewall instances are not configured in customizations-config.yaml.`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - const instanceConfig = firewallInstanceConfig.find(firewall => firewall.name === firewallName); - if (!instanceConfig) { - logger.error(`Firewall instance configuration for firewall ${firewallName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - return instanceConfig; -} - -/** - * Returns a customer gateway name associated with the given VPN connection name - * @param customerGateway CustomerGatewayConfig[] - * @param vpnName string - * @returns string - */ -export function getCustomerGatewayName(customerGateways: CustomerGatewayConfig[], vpnName: string): string { - const customerGatewayName = customerGateways.find(cgw => cgw.vpnConnections?.find(vpn => vpn.name === vpnName))?.name; - if (!customerGatewayName) { - logger.error(`Customer gateway name for VPN ${vpnName} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - return customerGatewayName; -} - -/** - * Returns all keys with defined values for a given object - * @param obj object - * @returns string[] - */ -export function getObjectKeys(obj: object): string[] { - const keys: string[] = []; - for (const [key, value] of Object.entries(obj)) { - if (value !== undefined) { - keys.push(key); - } - } - return keys; -} - -/** - * Parse the details of an ENI lookup on a firewall instance - * @param lookupType - * @param routeTableEntryName - * @param routeTarget - */ -export function getNetworkInterfaceLookupDetails( - lookupType: 'ENI_INDEX' | 'FIREWALL_NAME', - routeTableEntryName: string, - routeTarget: string | undefined, -): string { - if (!routeTarget) { - logger.error(`Unable to retrieve target ${routeTarget} for route table entry ${routeTableEntryName}`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const lookupComponents = routeTarget.split(':'); - const eniIndex = lookupComponents[3].split('_').pop(); - const firewallName = lookupComponents[4].replace(/\}$/, ''); - - if (!eniIndex) { - logger.error( - `Unable to retrieve deviceIndex from lookup ${routeTarget} for route table entry ${routeTableEntryName}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - - if (lookupType === 'ENI_INDEX') { - return eniIndex; - } else if (lookupType === 'FIREWALL_NAME') { - return firewallName; - } else { - logger.error(`Invalid lookup type passed`); - throw new Error(`Configuration validation failed at runtime.`); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/security-group-utils.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/security-group-utils.ts deleted file mode 100644 index bb77dc4..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/security-group-utils.ts +++ /dev/null @@ -1,847 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - PrefixListSourceConfig, - SecurityGroupConfig, - SecurityGroupRuleConfig, - SecurityGroupSourceConfig, - SubnetSourceConfig, - VpcConfig, - VpcTemplatesConfig, - isNetworkType, - NonEmptyString, -} from '@aws-accelerator/config'; -import { - IIpamSubnet, - PrefixList, - SecurityGroup, - SecurityGroupEgressRuleProps, - SecurityGroupIngressRuleProps, - Subnet, -} from '@aws-accelerator/constructs'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import * as cdk from 'aws-cdk-lib'; -import { getPrefixList, getSecurityGroup, getSubnet, getSubnetConfig, getVpcConfig } from './getter-utils'; -import { isIpv6Cidr } from './validation-utils'; - -/** - * Security group rule properties - */ -interface SecurityGroupRuleProps { - ipProtocol: string; - cidrIp?: string; - cidrIpv6?: string; - fromPort?: number; - toPort?: number; - targetSecurityGroup?: SecurityGroup; - targetPrefixList?: string; - description?: string; -} - -/** - * Security group application TCP port mapping - */ -const TCP_PROTOCOLS_PORT: { [key: string]: number } = { - RDP: 3389, - SSH: 22, - HTTP: 80, - HTTPS: 443, - MSSQL: 1433, - 'MYSQL/AURORA': 3306, - REDSHIFT: 5439, - POSTGRESQL: 5432, - 'ORACLE-RDS': 1521, -}; - -/** - * A logger for the utility functions - */ -const logger = createLogger(['security-group-utils']); - -/** - * Function to set IPAM subnets for a given array of VPC resources - * @param vpcResources - * @param subnetMap - */ -export function setIpamSubnetSourceArray( - allVpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcResources: (VpcConfig | VpcTemplatesConfig)[], -): string[] { - const ipamSubnets: string[] = []; - - for (const vpcItem of vpcResources) { - for (const sgItem of vpcItem.securityGroups ?? []) { - ipamSubnets.push(...setSgItemIpamSubnets(allVpcResources, sgItem)); - } - } - return [...new Set(ipamSubnets)]; -} - -/** - * Sets an array of IPAM subnets for a single security group config item - * @param allVpcResources - * @param sgItem - * @returns - */ -function setSgItemIpamSubnets( - allVpcResources: (VpcConfig | VpcTemplatesConfig)[], - sgItem: SecurityGroupConfig, -): string[] { - const ipamSubnets: string[] = []; - - for (const ruleItem of [...sgItem.inboundRules, ...sgItem.outboundRules]) { - for (const source of ruleItem.sources) { - if (isNetworkType('ISubnetSourceConfig', source)) { - ipamSubnets.push(...parseSubnetConfigs(allVpcResources, source.vpc, source.account, source.subnets)); - } - } - } - return ipamSubnets; -} - -/** - * Parse individual subnet configurations to determine if they are IPAM subnets - * @param allVpcResources - * @param vpcName - * @param accountName - * @param subnets - * @returns - */ -function parseSubnetConfigs( - allVpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcName: string, - accountName: string, - subnets: string[], -): string[] { - const ipamSubnets: string[] = []; - - for (const subnet of subnets) { - const vpcConfig = getVpcConfig(allVpcResources, vpcName); - const subnetConfig = getSubnetConfig(vpcConfig, subnet); - const key = `${vpcConfig.name}_${accountName}_${subnetConfig.name}`; - - if (subnetConfig.ipamAllocation && !ipamSubnets.includes(key)) { - ipamSubnets.push(key); - } - } - return ipamSubnets; -} - -/** - * Process and set security group ingress rules - * @param vpcResources - * @param securityGroupItem - * @param subnetMap - * @param prefixListMap - * @returns - */ -export function processSecurityGroupIngressRules( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - securityGroupItem: SecurityGroupConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, -) { - const processedIngressRules: SecurityGroupIngressRuleProps[] = []; - - for (const [ruleId, ingressRuleItem] of securityGroupItem.inboundRules.entries() ?? []) { - logger.info(`Adding ingress rule ${ruleId} to ${securityGroupItem.name}`); - const ingressRules: SecurityGroupRuleProps[] = processSecurityGroupRules( - vpcResources, - ingressRuleItem, - subnetMap, - prefixListMap, - ); - processedIngressRules.push(...setSecurityGroupIngressRules(ingressRules)); - } - return processedIngressRules; -} - -/** - * Sets ingress rules based on type - * @param ingressRules - * @returns - */ -function setSecurityGroupIngressRules(ingressRules: SecurityGroupRuleProps[]): SecurityGroupIngressRuleProps[] { - const processedIngressRules: SecurityGroupIngressRuleProps[] = []; - logger.info(`Adding ${ingressRules.length} ingress rules`); - - for (const ingressRule of ingressRules) { - if (ingressRule.targetPrefixList) { - processedIngressRules.push({ - description: ingressRule.description, - fromPort: ingressRule.fromPort, - ipProtocol: ingressRule.ipProtocol, - sourcePrefixListId: ingressRule.targetPrefixList, - toPort: ingressRule.toPort, - }); - } else { - processedIngressRules.push({ ...ingressRule }); - } - } - return processedIngressRules; -} - -/** - * Returns true if any ingress rules contain an all ingress rule - * @param ingressRules - * @returns - */ -export function containsAllIngressRule(ingressRules: SecurityGroupIngressRuleProps[]): boolean { - let allIngressRule = false; - - for (const ingressRule of ingressRules) { - if (ingressRule.cidrIp && ingressRule.cidrIp === '0.0.0.0/0') { - allIngressRule = true; - } - } - return allIngressRule; -} - -/** - * Process and set security group ingress rules - * @param vpcResources - * @param securityGroupItem - * @param subnetMap - * @param prefixListMap - * @returns - */ -export function processSecurityGroupEgressRules( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - securityGroupItem: SecurityGroupConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, -) { - const processedEgressRules: SecurityGroupEgressRuleProps[] = []; - - for (const [ruleId, egressRuleItem] of securityGroupItem.outboundRules.entries() ?? []) { - logger.info(`Adding egress rule ${ruleId} to ${securityGroupItem.name}`); - - const egressRules: SecurityGroupRuleProps[] = processSecurityGroupRules( - vpcResources, - egressRuleItem, - subnetMap, - prefixListMap, - ); - - processedEgressRules.push(...setSecurityGroupEgressRules(egressRules)); - } - return processedEgressRules; -} - -/** - * Sets egress rules based on type - * @param egressRules - * @returns - */ -function setSecurityGroupEgressRules(egressRules: SecurityGroupRuleProps[]): SecurityGroupEgressRuleProps[] { - const processedEgressRules: SecurityGroupEgressRuleProps[] = []; - logger.info(`Adding ${egressRules.length} egress rules`); - - for (const egressRule of egressRules) { - if (egressRule.targetPrefixList) { - processedEgressRules.push({ - description: egressRule.description, - destinationPrefixListId: egressRule.targetPrefixList, - fromPort: egressRule.fromPort, - ipProtocol: egressRule.ipProtocol, - toPort: egressRule.toPort, - }); - } else { - processedEgressRules.push({ ...egressRule }); - } - } - return processedEgressRules; -} - -/** - * Returns true if any rule contains a security group source - * @param rule - * @returns - */ -function includesSecurityGroupSource(rule: SecurityGroupRuleConfig): boolean { - for (const source of rule.sources) { - if (isNetworkType('ISecurityGroupSourceConfig', source)) { - return true; - } - } - return false; -} - -/** - * Create security group ingress rules that reference other security groups - * @param vpcResources - * @param vpcItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - */ -export function processSecurityGroupSgIngressSources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcItem: VpcConfig | VpcTemplatesConfig, - securityGroupItem: SecurityGroupConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap: Map, -): { logicalId: string; rule: SecurityGroupRuleProps }[] { - const securityGroupSources: { logicalId: string; rule: SecurityGroupRuleProps }[] = []; - for (const [ruleId, ingressRuleItem] of securityGroupItem.inboundRules.entries() ?? []) { - // Add security group sources if they exist - if (includesSecurityGroupSource(ingressRuleItem)) { - const ingressRules: SecurityGroupRuleProps[] = processSecurityGroupRules( - vpcResources, - ingressRuleItem, - subnetMap, - prefixListMap, - securityGroupMap, - vpcItem.name, - ); - securityGroupSources.push(...setSecurityGroupSgIngressSources(securityGroupItem, ingressRules, ruleId)); - } - } - - return securityGroupSources; -} - -/** - * Sets ingress rules for security group sources - * @param ingressRules - * @returns - */ -function setSecurityGroupSgIngressSources( - securityGroupItem: SecurityGroupConfig, - ingressRules: SecurityGroupRuleProps[], - ruleId: number, -): { logicalId: string; rule: SecurityGroupRuleProps }[] { - const securityGroupSources: { logicalId: string; rule: SecurityGroupRuleProps }[] = []; - - for (const [ingressRuleIndex, ingressRule] of ingressRules.entries()) { - if (ingressRule.targetSecurityGroup) { - securityGroupSources.push({ - logicalId: `${securityGroupItem.name}-Ingress-${ruleId}-${ingressRuleIndex}`, - rule: ingressRule, - }); - } - } - return securityGroupSources; -} - -/** - * Create security group egress rules that reference other security groups - * @param vpcResources - * @param vpcItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - */ -export function processSecurityGroupSgEgressSources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - vpcItem: VpcConfig | VpcTemplatesConfig, - securityGroupItem: SecurityGroupConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap: Map, -): { logicalId: string; rule: SecurityGroupRuleProps }[] { - const securityGroupSources: { logicalId: string; rule: SecurityGroupRuleProps }[] = []; - - for (const [ruleId, egressRuleItem] of securityGroupItem.outboundRules.entries() ?? []) { - // Add security group sources if they exist - if (includesSecurityGroupSource(egressRuleItem)) { - const egressRules: SecurityGroupRuleProps[] = processSecurityGroupRules( - vpcResources, - egressRuleItem, - subnetMap, - prefixListMap, - securityGroupMap, - vpcItem.name, - ); - securityGroupSources.push(...setSecurityGroupSgEgressSources(securityGroupItem, egressRules, ruleId)); - } - } - return securityGroupSources; -} - -/** - * Set egress rules for security group sources - * @param securityGroupItem - * @param egressRules - * @param ruleId - * @returns - */ -function setSecurityGroupSgEgressSources( - securityGroupItem: SecurityGroupConfig, - egressRules: SecurityGroupRuleProps[], - ruleId: number, -): { logicalId: string; rule: SecurityGroupRuleProps }[] { - const securityGroupSources: { logicalId: string; rule: SecurityGroupRuleProps }[] = []; - - for (const [egressRuleIndex, egressRule] of egressRules.entries()) { - if (egressRule.targetSecurityGroup) { - securityGroupSources.push({ - logicalId: `${securityGroupItem.name}-Egress-${ruleId}-${egressRuleIndex}`, - rule: egressRule, - }); - } - } - return securityGroupSources; -} - -/** - * Process security group rules based on configured type - * @param vpcResources - * @param item - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - * @returns - */ -function processSecurityGroupRules( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - item: SecurityGroupRuleConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap?: Map, - vpcName?: string, -): SecurityGroupRuleProps[] { - const rules: SecurityGroupRuleProps[] = []; - - if (!item.types) { - rules.push( - ...processTcpSources(vpcResources, item, subnetMap, prefixListMap, securityGroupMap, vpcName), - ...processUdpSources(vpcResources, item, subnetMap, prefixListMap, securityGroupMap, vpcName), - ...processIpProtocols(vpcResources, item, subnetMap, prefixListMap, securityGroupMap, vpcName), - ); - } else { - rules.push(...processTypeSources(vpcResources, item, subnetMap, prefixListMap, securityGroupMap, vpcName)); - } - - return rules; -} - -/** - * Process IP Protocols for security group rules - * @param vpcResources - * @param securityGroupRuleItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - * @param vpcName - * @returns - */ -function processIpProtocols( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - securityGroupRuleItem: SecurityGroupRuleConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap?: Map, - vpcName?: string, -): SecurityGroupRuleProps[] { - const ipProtocolRules: SecurityGroupRuleProps[] = []; - for (const protocolItem of securityGroupRuleItem.ipProtocols ?? []) { - ipProtocolRules.push( - ...processSecurityGroupRuleSources( - vpcResources, - securityGroupRuleItem.sources, - subnetMap, - prefixListMap, - { - ipProtocol: cdk.aws_ec2.Protocol[protocolItem as keyof typeof cdk.aws_ec2.Protocol], - description: securityGroupRuleItem.description, - }, - securityGroupMap, - vpcName, - ), - ); - } - return ipProtocolRules; -} - -/** - * Process TCP ports for security group rules - * @param vpcResources - * @param securityGroupRuleItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - * @param vpcName - * @returns - */ -function processTcpSources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - securityGroupRuleItem: SecurityGroupRuleConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap?: Map, - vpcName?: string, -): SecurityGroupRuleProps[] { - const tcpRules: SecurityGroupRuleProps[] = []; - - for (const tcpPort of securityGroupRuleItem.tcpPorts ?? []) { - logger.info(`Evaluate TCP Port ${tcpPort}`); - tcpRules.push( - ...processSecurityGroupRuleSources( - vpcResources, - securityGroupRuleItem.sources, - subnetMap, - prefixListMap, - { - ipProtocol: cdk.aws_ec2.Protocol.TCP, - fromPort: tcpPort, - toPort: tcpPort, - description: securityGroupRuleItem.description, - }, - securityGroupMap, - vpcName, - ), - ); - } - return tcpRules; -} - -/** - * Process UDP ports for security group rules - * @param vpcResources - * @param securityGroupRuleItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - * @param vpcName - * @returns - */ -function processUdpSources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - securityGroupRuleItem: SecurityGroupRuleConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap?: Map, - vpcName?: string, -): SecurityGroupRuleProps[] { - const udpRules: SecurityGroupRuleProps[] = []; - - for (const udpPort of securityGroupRuleItem.udpPorts ?? []) { - logger.info(`Evaluate UDP Port ${udpPort}`); - udpRules.push( - ...processSecurityGroupRuleSources( - vpcResources, - securityGroupRuleItem.sources, - subnetMap, - prefixListMap, - { - ipProtocol: cdk.aws_ec2.Protocol.UDP, - fromPort: udpPort, - toPort: udpPort, - description: securityGroupRuleItem.description, - }, - securityGroupMap, - vpcName, - ), - ); - } - return udpRules; -} - -/** - * Process security group rules based on configured type - * @param vpcResources - * @param securityGroupRuleItem - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - * @param vpcName - * @returns - */ -function processTypeSources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - securityGroupRuleItem: SecurityGroupRuleConfig, - subnetMap: Map | Map, - prefixListMap: Map | Map, - securityGroupMap?: Map, - vpcName?: string, -): SecurityGroupRuleProps[] { - const typeRules: SecurityGroupRuleProps[] = []; - - for (const type of securityGroupRuleItem.types ?? []) { - logger.info(`Evaluate Type ${type}`); - if (type === 'ALL') { - typeRules.push( - ...processSecurityGroupRuleSources( - vpcResources, - securityGroupRuleItem.sources, - subnetMap, - prefixListMap, - { - ipProtocol: cdk.aws_ec2.Protocol.ALL, - description: securityGroupRuleItem.description, - }, - securityGroupMap, - vpcName, - ), - ); - } else if (Object.keys(TCP_PROTOCOLS_PORT).includes(type)) { - typeRules.push( - ...processSecurityGroupRuleSources( - vpcResources, - securityGroupRuleItem.sources, - subnetMap, - prefixListMap, - { - ipProtocol: cdk.aws_ec2.Protocol.TCP, - fromPort: TCP_PROTOCOLS_PORT[type], - toPort: TCP_PROTOCOLS_PORT[type], - description: securityGroupRuleItem.description, - }, - securityGroupMap, - vpcName, - ), - ); - } else { - typeRules.push( - ...processSecurityGroupRuleSources( - vpcResources, - securityGroupRuleItem.sources, - subnetMap, - prefixListMap, - { - ipProtocol: type, - fromPort: securityGroupRuleItem.fromPort, - toPort: securityGroupRuleItem.toPort, - description: securityGroupRuleItem.description, - }, - securityGroupMap, - vpcName, - ), - ); - } - } - logger.info(`****************** `); - return typeRules; -} - -/** - * Function to prepare Security group rule properties - * @param vpcResources {@link VpcConfig} | {@link VpcTemplatesConfig} - * @param source string | {@link SecurityGroupSourceConfig} | {@link PrefixListSourceConfig} | {@link SubnetSourceConfig} - * @param rules {@link SecurityGroupRuleProps}[] - * @param props - * @param prefixListMap - * @param subnetMap - * @param securityGroupMap - * @param vpcName - */ -function prepareSecurityGroupRuleProps( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - source: string | SecurityGroupSourceConfig | PrefixListSourceConfig | SubnetSourceConfig, - rules: SecurityGroupRuleProps[], - props: { - ipProtocol: string; - fromPort?: number; - toPort?: number; - description?: string; - }, - maps: { - prefixLists: Map | Map; - subnets: Map | Map; - securityGroups?: Map; - }, - - vpcName?: string, -) { - // Conditional to only process non-security group sources - if (!maps.securityGroups) { - // - // IP source - // - if (isNetworkType('NonEmptyString', source)) { - rules.push(processIpSource(source, props)); - } - // - // Subnet source - // - if (isNetworkType('ISubnetSourceConfig', source)) { - rules.push(...processSubnetSource(vpcResources, maps.subnets, source, props)); - } - // - // Prefix List Source - // - if (isNetworkType('IPrefixListSourceConfig', source)) { - rules.push(...processPrefixListSource(maps.prefixLists, source, props)); - } - } else { - // - // Security Group Source - // - if (isNetworkType('ISecurityGroupSourceConfig', source) && vpcName) { - rules.push(...processSecurityGroupSource(maps.securityGroups, vpcName, source, props)); - } - } -} - -/** - * Processes individual security group source references. - * @param vpcResources - * @param sources - * @param subnetMap - * @param prefixListMap - * @param securityGroupMap - * @param props - * @returns - */ -function processSecurityGroupRuleSources( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - sources: (string | SecurityGroupSourceConfig | PrefixListSourceConfig | SubnetSourceConfig)[], - subnetMap: Map | Map, - prefixListMap: Map | Map, - props: { - ipProtocol: string; - fromPort?: number; - toPort?: number; - description?: string; - }, - securityGroupMap?: Map, - vpcName?: string, -): SecurityGroupRuleProps[] { - const rules: SecurityGroupRuleProps[] = []; - - for (const source of sources ?? []) { - prepareSecurityGroupRuleProps( - vpcResources, - source, - rules, - props, - { prefixLists: prefixListMap, subnets: subnetMap, securityGroups: securityGroupMap }, - vpcName, - ); - } - return rules; -} - -/** - * Process IP address source - * @param source - * @param props - * @returns - */ -function processIpSource( - source: string, - props: { ipProtocol: string; fromPort?: number; toPort?: number; description?: string }, -): SecurityGroupRuleProps { - logger.info(`Evaluate IP Source ${source}`); - if (isIpv6Cidr(source)) { - return { - cidrIpv6: source, - ...props, - }; - } - return { - cidrIp: source, - ...props, - }; -} - -/** - * Process Subnet source - * @param vpcResources - * @param subnetMap - * @param source - * @param props - * @returns - */ -function processSubnetSource( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - subnetMap: Map | Map, - source: SubnetSourceConfig, - props: { ipProtocol: string; fromPort?: number; toPort?: number; description?: string }, -): SecurityGroupRuleProps[] { - const subnetRules: SecurityGroupRuleProps[] = []; - logger.info(`Evaluate Subnet Source account:${source.account} vpc:${source.vpc} subnets:[${source.subnets}]`); - - // Locate the VPC - const vpcItem = getVpcConfig(vpcResources, source.vpc); - - for (const subnet of source.subnets) { - // Locate the Subnet - const subnetConfigItem = getSubnetConfig(vpcItem, subnet); - - if (subnetConfigItem.ipamAllocation) { - const subnetItem = getSubnet(subnetMap, vpcItem.name, subnetConfigItem.name); - subnetRules.push({ - cidrIp: subnetItem.ipv4CidrBlock, - ...props, - }); - } else { - const ruleProps = source.ipv6 - ? { cidrIpv6: subnetConfigItem.ipv6CidrBlock, ...props } - : { - cidrIp: subnetConfigItem.ipv4CidrBlock, - ...props, - }; - subnetRules.push(ruleProps); - } - } - return subnetRules; -} - -/** - * Process Prefix List source - * @param prefixListMap - * @param source - * @param props - * @returns - */ -function processPrefixListSource( - prefixListMap: Map | Map, - source: PrefixListSourceConfig, - props: { ipProtocol: string; fromPort?: number; toPort?: number; description?: string }, -): SecurityGroupRuleProps[] { - const prefixListRules: SecurityGroupRuleProps[] = []; - logger.info(`Evaluate Prefix List Source prefixLists:[${source.prefixLists}]`); - - for (const prefixList of source.prefixLists ?? []) { - const targetPrefixList = getPrefixList(prefixListMap, prefixList); - prefixListRules.push({ - targetPrefixList: typeof targetPrefixList === 'string' ? targetPrefixList : targetPrefixList.prefixListId, - ...props, - }); - } - return prefixListRules; -} - -/** - * Process Security Group source - * @param securityGroupMap - * @param source - * @param props - */ -function processSecurityGroupSource( - securityGroupMap: Map, - vpcName: string, - source: SecurityGroupSourceConfig, - props: { - ipProtocol: string; - fromPort?: number; - toPort?: number; - description?: string; - }, -): SecurityGroupRuleProps[] { - const sgRules: SecurityGroupRuleProps[] = []; - logger.info(`Evaluate Security Group Source securityGroups:[${source.securityGroups}]`); - - for (const securityGroup of source.securityGroups ?? []) { - const targetSecurityGroup = getSecurityGroup(securityGroupMap, vpcName, securityGroup) as SecurityGroup; - sgRules.push({ - targetSecurityGroup, - ...props, - }); - } - return sgRules; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/setter-utils.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/setter-utils.ts deleted file mode 100644 index e00be17..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/setter-utils.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { RouteTableEntryConfig, VpcConfig, VpcTemplatesConfig } from '@aws-accelerator/config'; -import { isIpv4Cidr } from './validation-utils'; - -/** - * Returns an array containing the keys of IPAM subnets that need to be looked up - * @param vpcResources - */ -export function setIpamSubnetRouteTableEntryArray(vpcResources: (VpcConfig | VpcTemplatesConfig)[]): string[] { - const ipamSubnets: string[] = []; - - for (const vpcItem of vpcResources) { - for (const routeTableItem of vpcItem.routeTables ?? []) { - ipamSubnets.push(...parseIpamSubnetRouteTableEntries(routeTableItem.routes, vpcItem)); - } - } - return [...new Set(ipamSubnets)]; -} - -/** - * Determines if a route entry is a dynamic reference - * @param routes - * @param vpcItem - * @returns - */ -function parseIpamSubnetRouteTableEntries( - routes: RouteTableEntryConfig[] | undefined, - vpcItem: VpcConfig | VpcTemplatesConfig, -): string[] { - const ipamSubnets: string[] = []; - - for (const route of routes ?? []) { - if (route.destination && !isIpv4Cidr(route.destination)) { - const key = `${vpcItem.name}_${route.destination}`; - ipamSubnets.push(key); - } - } - return ipamSubnets; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/validation-utils.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/validation-utils.ts deleted file mode 100644 index 533f48d..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/network-stacks/utils/validation-utils.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CustomerGatewayConfig, - TransitGatewayRouteEntryConfig, - TransitGatewayRouteTableVpnEntryConfig, - VpnConnectionConfig, - isNetworkType, -} from '@aws-accelerator/config'; -import { IPv4, IPv4CidrRange, IPv6CidrRange } from 'ip-num'; -import { getObjectKeys } from './getter-utils'; - -/** - * Determines if a VPN connection has advanced options. - * Advanced options are options that are not supported by the native VPN provider. - * @param vpn VpnConnectionConfig - * @returns boolean - */ -export function hasAdvancedVpnOptions(vpn: VpnConnectionConfig): boolean { - // - // Get configured options keys - const inputVpnKeys = getObjectKeys(vpn); - const inputVpnOptionsKeys = vpn.tunnelSpecifications - ? [...new Set([...getObjectKeys(vpn.tunnelSpecifications[0]), ...getObjectKeys(vpn.tunnelSpecifications[1])])] - : []; - // - // Set native resource keys - const nativeVpnKeys = [ - 'name', - 'transitGateway', - 'vpc', - 'routeTableAssociations', - 'routeTablePropagations', - 'staticRoutesOnly', - 'tunnelSpecifications', - 'tags', - ]; - const nativeVpnOptionsKeys = ['preSharedKey', 'tunnelInsideCidr']; - // - // Compare input keys against native resource keys - if (inputVpnKeys.some(key => !nativeVpnKeys.includes(key))) { - return true; - } - if (inputVpnOptionsKeys.some(optionKey => !nativeVpnOptionsKeys.includes(optionKey))) { - return true; - } - return false; -} - -/** - * Returns true if the passed string is a valid IPv4 CIDR. - * - * @param cidr string - * @returns boolean - */ -export function isIpv4Cidr(cidr: string): boolean { - try { - IPv4CidrRange.fromCidr(cidr); - return true; - } catch (e) { - return false; - } -} - -/** - * Returns true if the passed string is a valid IPv6 CIDR. - * - * @param cidr string - * @returns boolean - */ -export function isIpv6Cidr(cidr: string): boolean { - try { - IPv6CidrRange.fromCidr(cidr); - return true; - } catch (e) { - return false; - } -} - -/** - * Returns true if the passed string is a valid IPv4 address. - * - * @param ip string - * @returns boolean - */ -export function isIpv4(ip: string): boolean { - try { - IPv4.fromString(ip); - return true; - } catch (e) { - return false; - } -} - -/** - * Returns true if the passed TGW route item belongs to a dynamic EC2 firewall customer gateway - * @param customerGateways CustomerGatewayConfig[] - * @param routeItem TransitGatewayRouteEntryConfig - * @returns boolean - */ -export function isEc2FirewallVpnRoute( - customerGateways: CustomerGatewayConfig[], - routeItem: TransitGatewayRouteEntryConfig, -): boolean { - if ( - routeItem.attachment && - isNetworkType( - 'ITransitGatewayRouteTableVpnEntryConfig', - routeItem.attachment, - ) - ) { - const vpnName = routeItem.attachment.vpnConnectionName; - const cgw = customerGateways.find(cgwItem => cgwItem.vpnConnections?.find(vpnItem => vpnItem.name === vpnName)); - - if (!cgw) { - throw new Error(`VPN connection "${vpnName}" not found in customer gateway configuration.`); - } else { - return !isIpv4(cgw.ipAddress); - } - } - return false; -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/operations-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/operations-stack.ts deleted file mode 100644 index edd9977..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/operations-stack.ts +++ /dev/null @@ -1,1415 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -import { - AseaResourceType, - Ec2FirewallAutoScalingGroupConfig, - Ec2FirewallConfig, - Ec2FirewallInstanceConfig, - Region, - RoleConfig, - RoleSetConfig, - VaultConfig, - VpcConfig, - VpcTemplatesConfig, -} from '@aws-accelerator/config'; -import { SsmResourceType } from '@aws-accelerator/utils/lib/ssm-parameter-path'; -import { - Bucket, - BucketEncryptionType, - BudgetDefinition, - Inventory, - KeyLookup, - LimitsDefinition, - SsmSessionManagerPolicy, - WarmAccount, -} from '@aws-accelerator/constructs'; -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionRuleIds, -} from './accelerator-stack'; -import { getVpcConfig } from './network-stacks/utils/getter-utils'; - -export interface OperationsStackProps extends AcceleratorStackProps { - readonly accountWarming: boolean; -} -export class OperationsStack extends AcceleratorStack { - /** - * List of all the defined SAML Providers - */ - private providers: { [name: string]: cdk.aws_iam.SamlProvider } = {}; - - /** - * List of all the defined IAM Policies - */ - private policies: { [name: string]: cdk.aws_iam.IManagedPolicy } = {}; - - /** - * List of all the defined IAM Roles - */ - private roles: { [name: string]: cdk.aws_iam.IRole } = {}; - - /** - * List of all the defined IAM Groups - */ - private groups: { [name: string]: cdk.aws_iam.IGroup } = {}; - - /** - * List of all the defined IAM Users - */ - private users: { [name: string]: cdk.aws_iam.IUser } = {}; - - /** - * KMS Key used to encrypt CloudWatch logs, when undefined default AWS managed key will be used - */ - private cloudwatchKey: cdk.aws_kms.IKey | undefined; - - /** - * KMS Key for central S3 Bucket - */ - private centralLogsBucketKey: cdk.aws_kms.IKey; - - /** - * Constructor for OperationsStack - * - * @param scope - * @param id - * @param props - */ - constructor(scope: Construct, id: string, props: OperationsStackProps) { - super(scope, id, props); - - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - - this.centralLogsBucketKey = this.getCentralLogsBucketKey(this.cloudwatchKey); - - // - // Look up asset bucket KMS key - const vpcResources = [...props.networkConfig.vpcs, ...(props.networkConfig.vpcTemplates ?? [])]; - const firewallRoles = this.getFirewallRolesInScope(vpcResources, props.customizationsConfig.firewalls); - const assetBucketKmsKey = this.lookupAssetBucketKmsKey(props, firewallRoles); - - // - // Only deploy IAM and CUR resources into the home region - // - if (props.globalConfig.homeRegion === cdk.Stack.of(this).region) { - this.addProviders(); - this.addManagedPolicies(); - this.addRoles(); - this.addGroups(); - this.addUsers(); - this.createStackSetRoles(); - // - // - // Budgets - // - this.enableBudgetReports(); - - // Create Accelerator Access Role in every region - this.createAssetAccessRole(assetBucketKmsKey); - - // Create Cross Account Service Catalog Role - this.createServiceCatalogPropagationRole(); - - // Create Session Manager IAM Policy - if ( - this.props.globalConfig.logging.sessionManager.sendToCloudWatchLogs || - this.props.globalConfig.logging.sessionManager.sendToS3 - ) { - this.createSessionManagerPolicy(); - } - - // warm account here - this.warmAccount(props.accountWarming); - } - - // - // Service Quota Limits - // - this.increaseLimits(); - - // - // Backup Vaults - // - this.addBackupVaults(); - - if ( - this.props.globalConfig.ssmInventory?.enable && - this.isIncluded(this.props.globalConfig.ssmInventory.deploymentTargets) - ) { - this.enableInventory(); - } - - // - // Add SSM Parameters - // - this.addSsmParameters(); - - // - // Create firewall configuration S3 bucket - // - this.createFirewallConfigBucket(props, firewallRoles, assetBucketKmsKey); - - // - // Create SSM parameters - // - this.createSsmParameters(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /* - * Create Session Manager IAM Policy and Attach to IAM Role(s) - */ - private createSessionManagerPolicy() { - const cloudWatchLogGroupList: string[] = this.getCloudWatchLogGroupList(); - const sessionManagerCloudWatchLogGroupList: string[] = this.getSessionManagerCloudWatchLogGroupList(); - const s3BucketList: string[] = this.getS3BucketList(); - - // Set up Session Manager Logging - const ssmSessionManagerPolicy = new SsmSessionManagerPolicy(this, 'SsmSessionManagerSettings', { - roleSets: this.props.iamConfig.roleSets, - homeRegion: this.props.globalConfig.homeRegion, - s3BucketName: this.centralLogsBucketName, - s3BucketKeyArn: this.centralLogsBucketKey.keyArn, - sendToCloudWatchLogs: this.props.globalConfig.logging.sessionManager.sendToCloudWatchLogs, - sendToS3: this.props.globalConfig.logging.sessionManager.sendToS3, - attachPolicyToIamRoles: this.props.globalConfig.logging.sessionManager.attachPolicyToIamRoles, - region: cdk.Stack.of(this).region, - enabledRegions: this.props.globalConfig.enabledRegions, - cloudWatchLogGroupList: cloudWatchLogGroupList ?? undefined, - sessionManagerCloudWatchLogGroupList: sessionManagerCloudWatchLogGroupList ?? undefined, - s3BucketList: s3BucketList ?? undefined, - prefixes: { - accelerator: this.props.prefixes.accelerator, - ssmLog: this.props.prefixes.ssmLogName, - }, - ssmKeyDetails: { - alias: this.acceleratorResourceNames.customerManagedKeys.ssmKey.alias, - description: this.acceleratorResourceNames.customerManagedKeys.ssmKey.description, - }, - }); - const roleNames = this.props.globalConfig.logging.sessionManager.attachPolicyToIamRoles || []; - roleNames.forEach(roleName => { - const role = this.roles[roleName]; - if (role) { - ssmSessionManagerPolicy.node.addDependency(role); - } - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/SsmSessionManagerSettings/SessionManagerEC2Policy/Resource`, - reason: 'Policy needed access to all S3 objects for the account to put objects into the access log bucket', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/SsmSessionManagerSettings/SessionManagerEC2Role/Resource`, - reason: 'Create an IAM managed Policy for users to be able to use Session Manager with KMS encryption', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `/${this.stackName}/SsmSessionManagerSettings/SessionManagerPolicy/Resource`, - reason: 'Allows only specific log group', - }, - ], - }); - } - - /* Enable AWS Service Quota Limits - * - */ - private increaseLimits() { - const globalServices = ['account', 'cloudfront', 'iam', 'organizations', 'route53']; - - for (const limit of this.props.globalConfig.limits ?? []) { - if (this.isIncluded(limit.deploymentTargets ?? [])) { - if (globalServices.includes(limit.serviceCode) && this.props.globalRegion === cdk.Stack.of(this).region) { - this.logger.info( - `Creating service quota increase for global service ${limit.serviceCode} in ${this.props.globalRegion}`, - ); - new LimitsDefinition(this, `ServiceQuotaUpdates${limit.quotaCode}` + `${limit.desiredValue}`, { - serviceCode: limit.serviceCode, - quotaCode: limit.quotaCode, - desiredValue: limit.desiredValue, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } else if (limit.regions && limit.regions.includes(cdk.Stack.of(this).region as Region)) { - this.logger.info(`Regions explicitly defined for service quota increase ${limit.quotaCode}`); - new LimitsDefinition(this, `ServiceQuotaUpdates${limit.quotaCode}` + `${limit.desiredValue}`, { - serviceCode: limit.serviceCode, - quotaCode: limit.quotaCode, - desiredValue: limit.desiredValue, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } else if (this.props.globalConfig.homeRegion === cdk.Stack.of(this).region) { - this.logger.info( - `Regions property not specified, creating service quota increase ${limit.quotaCode} in home region`, - ); - new LimitsDefinition(this, `ServiceQuotaUpdates${limit.quotaCode}` + `${limit.desiredValue}`, { - serviceCode: limit.serviceCode, - quotaCode: limit.quotaCode, - desiredValue: limit.desiredValue, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - } - } - - /** - * Adds SAML Providers - */ - private addProviders() { - for (const providerItem of this.props.iamConfig.providers ?? []) { - this.logger.info(`Add Provider ${providerItem.name}`); - this.providers[providerItem.name] = new cdk.aws_iam.SamlProvider( - this, - `${pascalCase(providerItem.name)}SamlProvider`, - { - name: providerItem.name, - metadataDocument: cdk.aws_iam.SamlMetadataDocument.fromFile( - path.join(this.props.configDirPath, providerItem.metadataDocument), - ), - }, - ); - } - } - - /** - * Adds IAM Managed Policies - */ - private addManagedPolicies() { - for (const policySetItem of this.props.iamConfig.policySets ?? []) { - if (!this.isIncluded(policySetItem.deploymentTargets) || policySetItem.identityCenterDependency) { - this.logger.info(`Item excluded`); - continue; - } - - for (const policyItem of policySetItem.policies) { - if (this.isManagedByAsea(AseaResourceType.IAM_POLICY, policyItem.name)) { - this.logger.info(`Customer managed policy ${policyItem.name} is managed by ASEA`); - this.policies[policyItem.name] = cdk.aws_iam.ManagedPolicy.fromManagedPolicyName( - this, - pascalCase(policyItem.name), - policyItem.name, - ); - continue; - } - this.logger.info(`Add customer managed policy ${policyItem.name}`); - - // Read in the policy document which should be properly formatted json - const policyDocument = JSON.parse( - this.generatePolicyReplacements( - path.join(this.props.configDirPath, policyItem.policy), - false, - this.organizationId, - ), - ); - - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - // Construct the ManagedPolicy - this.policies[policyItem.name] = new cdk.aws_iam.ManagedPolicy(this, pascalCase(policyItem.name), { - managedPolicyName: policyItem.name, - statements, - }); - this.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(policyItem.name)}PolicyArn`), - parameterName: this.getSsmPath(SsmResourceType.IAM_POLICY, [policyItem.name]), - stringValue: this.policies[policyItem.name].managedPolicyArn, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(policyItem.name)}/Resource`, - reason: 'Policies definition are derived from accelerator iam-config boundary-policy file', - }, - ], - }); - } - } - } - - /** - * Generates the list of role principals for the provided roleItem - * - * @param roleItem - * @returns List of cdk.aws_iam.PrincipalBase - */ - private getRolePrincipals(roleItem: RoleConfig): cdk.aws_iam.PrincipalBase[] { - const principals: cdk.aws_iam.PrincipalBase[] = []; - - for (const assumedByItem of roleItem.assumedBy ?? []) { - this.logger.info(`Role - assumed by type(${assumedByItem.type}) principal(${assumedByItem.principal})`); - - switch (assumedByItem.type) { - case 'service': - principals.push(new cdk.aws_iam.ServicePrincipal(assumedByItem.principal)); - break; - case 'account': - const partition = this.props.partition; - const accountIdRegex = /^\d{12}$/; - const accountArnRegex = new RegExp('^arn:' + partition + ':iam::(\\d{12}):root$'); - - // test if principal length exceeds IAM Role length limit of 2048 characters. - // Ref: https://docs.aws.amazon.com/IAM/latest/APIReference/API_Role.html - // this will mitigate polynomial regular expression used on uncontrolled data - if (assumedByItem.principal!.length > 2048) { - throw new Error(`The principal defined in arn ${assumedByItem.principal} is too long`); - } - if (accountIdRegex.test(assumedByItem.principal)) { - principals.push(new cdk.aws_iam.AccountPrincipal(assumedByItem.principal)); - } else if (accountArnRegex.test(assumedByItem.principal)) { - const accountId = accountArnRegex.exec(assumedByItem.principal); - principals.push(new cdk.aws_iam.AccountPrincipal(accountId![1])); - } else { - principals.push( - new cdk.aws_iam.AccountPrincipal(this.props.accountsConfig.getAccountId(assumedByItem.principal)), - ); - } - break; - case 'provider': - // workaround due to https://github.com/aws/aws-cdk/issues/22091 - if (this.props.partition === 'aws-cn') { - principals.push( - new cdk.aws_iam.FederatedPrincipal( - this.providers[assumedByItem.principal].samlProviderArn, - { - StringEquals: { - 'SAML:aud': 'https://signin.amazonaws.cn/saml', - }, - }, - 'sts:AssumeRoleWithSAML', - ), - ); - } else { - principals.push(new cdk.aws_iam.SamlConsolePrincipal(this.providers[assumedByItem.principal])); - } - break; - case 'principalArn': - principals.push(new cdk.aws_iam.ArnPrincipal(assumedByItem.principal)); - } - } - - return principals; - } - - /** - * Generates the list of managed policies for the provided roleItem - * - * @param roleItem - * @returns List of cdk.aws_iam.IManagedPolicy - */ - private getManagedPolicies(roleItem: RoleConfig): cdk.aws_iam.IManagedPolicy[] { - const managedPolicies: cdk.aws_iam.IManagedPolicy[] = []; - - for (const policyItem of roleItem.policies?.awsManaged ?? []) { - this.logger.info(`Role - aws managed policy ${policyItem}`); - managedPolicies.push(cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName(policyItem)); - } - for (const policyItem of roleItem.policies?.customerManaged ?? []) { - this.logger.info(`Role - customer managed policy ${policyItem}`); - managedPolicies.push(this.policies[policyItem]); - } - - return managedPolicies; - } - - /** - * Create IAM role - * @param roleItem {@link RoleConfig} - * @param roleSetItem {@link RoleSetConfig} - * @returns role {@link cdk.aws_iam.Role} - */ - private createRole(roleItem: RoleConfig, roleSetItem: RoleSetConfig): cdk.aws_iam.Role { - const principals = this.getRolePrincipals(roleItem); - const managedPolicies = this.getManagedPolicies(roleItem); - let assumedBy: cdk.aws_iam.IPrincipal; - if (roleItem.assumedBy.find(item => item.type === 'provider')) { - // Since a SamlConsolePrincipal creates conditions, we can not - // use the CompositePrincipal. Verify that it is alone - if (principals.length > 1) { - this.logger.error('More than one principal found when adding provider'); - throw new Error(`Configuration validation failed at runtime.`); - } - assumedBy = principals[0]; - } else { - assumedBy = new cdk.aws_iam.CompositePrincipal(...principals); - } - - const role = new cdk.aws_iam.Role(this, pascalCase(roleItem.name), { - roleName: roleItem.name, - externalIds: roleItem.externalIds, - assumedBy, - managedPolicies, - path: roleSetItem.path, - permissionsBoundary: this.policies[roleItem.boundaryPolicy], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(roleItem.name)}/Resource`, - reason: 'IAM Role created as per accelerator iam-config needs AWS managed policy', - }, - ], - }); - - return role; - } - - /** - * Adds IAM Roles - */ - private addRoles() { - for (const roleSetItem of this.props.iamConfig.roleSets ?? []) { - if (!this.isIncluded(roleSetItem.deploymentTargets)) { - this.logger.info(`Item excluded`); - continue; - } - - for (const roleItem of roleSetItem.roles) { - if (this.isManagedByAsea(AseaResourceType.IAM_ROLE, roleItem.name)) { - this.logger.info(`IAM Role ${roleItem.name} is managed by ASEA`); - this.roles[roleItem.name] = cdk.aws_iam.Role.fromRoleName(this, pascalCase(roleItem.name), roleItem.name); - continue; - } - this.logger.info(`Add role ${roleItem.name}`); - - // Create IAM role - const role = this.createRole(roleItem, roleSetItem); - - // Create instance profile - if (roleItem.instanceProfile) { - this.logger.info(`Role - creating instance profile for ${roleItem.name}`); - new cdk.aws_iam.CfnInstanceProfile(this, `${pascalCase(roleItem.name)}InstanceProfile`, { - // Use role object to force use of Ref - instanceProfileName: role.roleName, - roles: [role.roleName], - }); - } - - this.grantManagedActiveDirectorySecretAccess(roleItem.name, role); - - // Add to roles list - this.roles[roleItem.name] = role; - this.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(roleItem.name)}RoleArn`), - parameterName: this.getSsmPath(SsmResourceType.IAM_ROLE, [roleItem.name]), - stringValue: role.roleArn, - }); - } - } - } - - /** - * Function to grant managed active directory secret access to instance role if the role is used in managed ad instance - * @param role - */ - private grantManagedActiveDirectorySecretAccess(roleName: string, role: cdk.aws_iam.Role) { - for (const managedActiveDirectory of this.props.iamConfig.managedActiveDirectories ?? []) { - const madAccountId = this.props.accountsConfig.getAccountId(managedActiveDirectory.account); - if (managedActiveDirectory.activeDirectoryConfigurationInstance) { - if ( - managedActiveDirectory.activeDirectoryConfigurationInstance.instanceRole === roleName && - madAccountId === cdk.Stack.of(this).account && - managedActiveDirectory.region === cdk.Stack.of(this).region - ) { - const madAdminSecretAccountId = this.props.accountsConfig.getAccountId( - this.props.iamConfig.getManageActiveDirectorySecretAccountName(managedActiveDirectory.name), - ); - const madAdminSecretRegion = this.props.iamConfig.getManageActiveDirectorySecretRegion( - managedActiveDirectory.name, - ); - - const secretArn = `arn:${ - cdk.Stack.of(this).partition - }:secretsmanager:${madAdminSecretRegion}:${madAdminSecretAccountId}:secret:${ - this.props.prefixes.secretName - }/ad-user/${managedActiveDirectory.name}/*`; - // Attach MAD instance role access to MAD secrets - this.logger.info(`Granting mad secret access to ${roleName}`); - role.attachInlinePolicy( - new cdk.aws_iam.Policy( - this, - `${pascalCase(managedActiveDirectory.name)}${pascalCase(roleName)}SecretsAccess`, - { - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['secretsmanager:GetSecretValue'], - resources: [secretArn], - }), - ], - }, - ), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}${pascalCase( - roleName, - )}SecretsAccess/Resource`, - reason: 'MAD instance role need access to more than one mad user secrets', - }, - ], - }); - } - } - } - } - - /** - * Adds IAM Groups - */ - private addGroups() { - for (const groupSetItem of this.props.iamConfig.groupSets ?? []) { - if (!this.isIncluded(groupSetItem.deploymentTargets)) { - this.logger.info(`Item excluded`); - continue; - } - - for (const groupItem of groupSetItem.groups) { - if (this.isManagedByAsea(AseaResourceType.IAM_GROUP, groupItem.name)) { - this.logger.info(`IAM Group ${groupItem.name} is managed by ASEA`); - this.groups[groupItem.name] = cdk.aws_iam.Group.fromGroupName( - this, - pascalCase(groupItem.name), - groupItem.name, - ); - continue; - } - this.logger.info(`Add group ${groupItem.name}`); - - const managedPolicies: cdk.aws_iam.IManagedPolicy[] = []; - for (const policyItem of groupItem.policies?.awsManaged ?? []) { - this.logger.info(`Group - aws managed policy ${policyItem}`); - managedPolicies.push(cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName(policyItem)); - } - for (const policyItem of groupItem.policies?.customerManaged ?? []) { - this.logger.info(`Group - customer managed policy ${policyItem}`); - managedPolicies.push(this.policies[policyItem]); - } - - this.groups[groupItem.name] = new cdk.aws_iam.Group(this, pascalCase(groupItem.name), { - groupName: groupItem.name, - managedPolicies, - }); - - this.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(groupItem.name)}GroupArn`), - parameterName: this.getSsmPath(SsmResourceType.IAM_GROUP, [groupItem.name]), - stringValue: this.groups[groupItem.name].groupArn, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(groupItem.name)}/Resource`, - reason: 'Groups created as per accelerator iam-config needs AWS managed policy', - }, - ], - }); - } - } - } - - /** - * Adds IAM Users - */ - private addUsers() { - for (const userSet of this.props.iamConfig.userSets ?? []) { - if (!this.isIncluded(userSet.deploymentTargets)) { - this.logger.info(`Item excluded`); - continue; - } - - for (const user of userSet.users ?? []) { - if (this.isManagedByAsea(AseaResourceType.IAM_USER, user.username)) { - this.logger.info(`IAM User ${user.username} is managed by ASEA`); - this.users[user.username] = cdk.aws_iam.User.fromUserName(this, pascalCase(user.username), user.username); - continue; - } - this.logger.info(`Add user ${user.username}`); - - const secret = new cdk.aws_secretsmanager.Secret(this, pascalCase(`${user.username}Secret`), { - generateSecretString: { - secretStringTemplate: JSON.stringify({ username: user.username }), - generateStringKey: 'password', - }, - secretName: `${this.props.prefixes.secretName}/${user.username}`, - }); - - // AwsSolutions-SMG4: The secret does not have automatic rotation scheduled. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.SMG4, - details: [ - { - path: `${this.stackName}/${pascalCase(user.username)}Secret/Resource`, - reason: 'Accelerator users created as per iam-config file, MFA usage is enforced with boundary policy', - }, - ], - }); - - this.logger.info(`User - password stored to ${this.props.prefixes.secretName}/${user.username}`); - - this.users[user.username] = new cdk.aws_iam.User(this, pascalCase(user.username), { - userName: user.username, - password: secret.secretValueFromJson('password'), - groups: [this.groups[user.group]], - permissionsBoundary: this.policies[user.boundaryPolicy], - passwordResetRequired: true, - }); - - this.addSsmParameter({ - logicalId: pascalCase(`SsmParam${pascalCase(user.username)}UserArn`), - parameterName: this.getSsmPath(SsmResourceType.IAM_USER, [user.username]), - stringValue: this.users[user.username].userArn, - }); - } - } - } - - /** - * Enables budget reports - */ - private enableBudgetReports() { - if (this.props.globalConfig.reports?.budgets && this.props.partition != 'aws-us-gov') { - for (const budget of this.props.globalConfig.reports.budgets ?? []) { - if (this.isIncluded(budget.deploymentTargets ?? [])) { - this.logger.info(`Add budget ${budget.name}`); - new BudgetDefinition(this, `${budget.name}BudgetDefinition`, { - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - amount: budget.amount, - includeCredit: budget.includeCredit, - includeDiscount: budget.includeDiscount, - includeOtherSubscription: budget.includeOtherSubscription, - includeRecurring: budget.includeRecurring, - includeRefund: budget.includeRefund, - includeSubscription: budget.includeSubscription, - includeSupport: budget.includeSupport, - includeTax: budget.includeTax, - includeUpfront: budget.includeUpfront, - name: budget.name, - notifications: budget.notifications, - timeUnit: budget.timeUnit, - type: budget.type, - useAmortized: budget.useAmortized, - useBlended: budget.useBlended, - unit: budget.unit, - }); - } - } - } - } - - /** - * Adds Backup Vaults as defined in the global-config.yaml. These Vaults can - * be referenced in AWS Organizations Backup Policies - */ - private addBackupVaults() { - let backupKey: cdk.aws_kms.IKey | undefined = undefined; - for (const vault of this.props.globalConfig.backup?.vaults ?? []) { - if (this.isIncluded(vault.deploymentTargets)) { - // Only create the key if a vault is defined for this account - if (backupKey === undefined) { - backupKey = new cdk.aws_kms.Key(this, 'BackupKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.awsBackup.alias, - description: this.acceleratorResourceNames.customerManagedKeys.awsBackup.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - } - - const vaultPolicy = this.getBackupVaultAccessPolicy(vault); - new cdk.aws_backup.BackupVault(this, `BackupVault_${vault.name}`, { - accessPolicy: vaultPolicy, - backupVaultName: vault.name, - encryptionKey: backupKey, - }); - } - } - } - - private getBackupVaultAccessPolicy(vault: VaultConfig) { - if (vault.policy) { - const policyDocument = JSON.parse( - this.generatePolicyReplacements(path.join(this.props.configDirPath, vault.policy), false, this.organizationId), - ); - - // Create a statements list using the PolicyStatement factory - const statements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - return new cdk.aws_iam.PolicyDocument({ - statements: statements, - }); - } else { - return undefined; - } - } - - private enableInventory() { - this.logger.info('Enabling SSM Inventory'); - const resourceDataSyncName = `${this.props.prefixes.accelerator}-${cdk.Stack.of(this).account}-Inventory`; - const associationName = `${this.props.prefixes.accelerator}-${cdk.Stack.of(this).account}-InventoryCollection`; - - if ( - this.isManagedByAsea(AseaResourceType.SSM_RESOURCE_DATA_SYNC, resourceDataSyncName) && - this.isManagedByAsea(AseaResourceType.SSM_ASSOCIATION, associationName) - ) { - return; - } - new Inventory(this, 'AcceleratorSsmInventory', { - bucketName: this.centralLogsBucketName, - bucketRegion: this.props.centralizedLoggingRegion, - accountId: cdk.Stack.of(this).account, - prefix: this.props.prefixes.bucketName, - }); - } - - /** - * Creates CloudFormation roles required for StackSets if stacksets are defined in customizations-config.yaml - * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html#prereqs-self-managed-permissions - */ - private createStackSetRoles() { - if (this.props.customizationsConfig?.customizations?.cloudFormationStackSets) { - const managementAccountId = this.props.accountsConfig.getManagementAccountId(); - if (cdk.Stack.of(this).account == managementAccountId) { - this.createStackSetAdminRole(); - } - this.createStackSetExecutionRole(managementAccountId); - } - } - - private createStackSetAdminRole() { - this.logger.info(`Creating StackSet Administrator Role`); - new cdk.aws_iam.Role(this, 'StackSetAdminRole', { - roleName: 'AWSCloudFormationStackSetAdministrationRole', - assumedBy: new cdk.aws_iam.ServicePrincipal('cloudformation.amazonaws.com'), - description: 'Assumes AWSCloudFormationStackSetExecutionRole in workload accounts to deploy StackSets', - inlinePolicies: { - AssumeRole: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: ['arn:*:iam::*:role/AWSCloudFormationStackSetExecutionRole'], - }), - ], - }), - }, - }); - } - - private createServiceCatalogPropagationRole() { - new cdk.aws_iam.Role(this, 'ServiceCatalogPropagationRole', { - roleName: this.acceleratorResourceNames.roles.crossAccountServiceCatalogPropagation, - assumedBy: this.getOrgPrincipals(this.organizationId, true), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'iam:GetGroup', - 'iam:GetRole', - 'iam:GetUser', - 'iam:ListRoles', - 'servicecatalog:AcceptPortfolioShare', - 'servicecatalog:AssociatePrincipalWithPortfolio', - 'servicecatalog:DisassociatePrincipalFromPortfolio', - 'servicecatalog:ListAcceptedPortfolioShares', - 'servicecatalog:ListPrincipalsForPortfolio', - ], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ], - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ServiceCatalogPropagationRole/Resource`, - reason: 'Policy must have access to all Service Catalog Portfolios and IAM Roles', - }, - ], - }); - } - - private createStackSetExecutionRole(managementAccountId: string) { - this.logger.info(`Creating StackSet Execution Role`); - new cdk.aws_iam.Role(this, 'StackSetExecutionRole', { - roleName: 'AWSCloudFormationStackSetExecutionRole', - assumedBy: new cdk.aws_iam.AccountPrincipal(managementAccountId), - description: 'Used to deploy StackSets', - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/StackSetExecutionRole/Resource`, - reason: 'IAM Role created as per accelerator iam-config needs AWS managed policy', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/StackSetAdminRole/Resource`, - reason: 'Policies definition are derived from accelerator iam-config boundary-policy file', - }, - ], - }); - } - - /** - * Lookup asset bucket KMS key to use for ACM certificates and - * EC2 firewall configurations - * @param props {@link AcceleratorStackProps} - * @param firewallRoles string[] - * @returns cdk.aws_kms.Key | undefined - */ - private lookupAssetBucketKmsKey(props: AcceleratorStackProps, firewallRoles: string[]): cdk.aws_kms.IKey | undefined { - if (props.globalConfig.homeRegion === cdk.Stack.of(this).region || firewallRoles.length > 0) { - return new KeyLookup(this, 'AssetsBucketKms', { - accountId: this.props.accountsConfig.getManagementAccountId(), - keyRegion: this.props.globalConfig.homeRegion, - roleName: this.acceleratorResourceNames.roles.crossAccountAssetsBucketCmkArnSsmParameterAccess, - keyArnParameterName: this.acceleratorResourceNames.parameters.assetsBucketCmkArn, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorPrefix: this.props.prefixes.accelerator, - }).getKey(); - } - return; - } - - /** - * Create ACM certificate asset bucket access role - * @param assetBucketKmsKey - */ - private createAssetAccessRole(assetBucketKmsKey?: cdk.aws_kms.IKey) { - if (!assetBucketKmsKey) { - throw new Error( - `Asset bucket KMS key is undefined. KMS key must be defined so permissions can be added to the custom resource role.`, - ); - } - - const accessBucketArn = `arn:${this.props.partition}:s3:::${ - this.acceleratorResourceNames.bucketPrefixes.assets - }-${this.props.accountsConfig.getManagementAccountId()}-${this.props.globalConfig.homeRegion}`; - - const accountId = cdk.Stack.of(this).account; - - const accessRoleResourceName = `AssetAccessRole${accountId}`; - const assetsAccessRole = new cdk.aws_iam.Role(this, accessRoleResourceName, { - roleName: `${this.props.prefixes.accelerator}-AssetsAccessRole`, - assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), - description: 'AWS Accelerator assets access role in workload accounts deploy ACM imported certificates.', - }); - assetsAccessRole.addManagedPolicy( - cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), - ); - assetsAccessRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - resources: [`${accessBucketArn}`, `${accessBucketArn}/*`], - actions: ['s3:GetObject*', 's3:ListBucket'], - }), - ); - assetsAccessRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - resources: [`arn:${this.props.partition}:acm:*:${accountId}:certificate/*`], - actions: ['acm:ImportCertificate'], - }), - ); - assetsAccessRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - resources: ['*'], - actions: ['acm:RequestCertificate', 'acm:DeleteCertificate'], - }), - ); - assetsAccessRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - resources: [`arn:${this.props.partition}:ssm:*:${accountId}:parameter/*`], - actions: ['ssm:PutParameter', 'ssm:DeleteParameter', 'ssm:GetParameter'], - }), - ); - - assetsAccessRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - resources: [assetBucketKmsKey.keyArn], - actions: ['kms:Decrypt'], - }), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${accessRoleResourceName}/DefaultPolicy/Resource`, - reason: 'Policy permissions are part of managed role and rest is to get access from s3 bucket', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${accessRoleResourceName}/Resource`, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - } - - private warmAccount(warm: boolean) { - if (!warm) { - return; - } - new WarmAccount(this, 'WarmAccount', { - cloudwatchKmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - ssmPrefix: this.props.prefixes.ssmParamName, - }); - } - - /** - * Creates a bucket for storing third-party firewall configuration and license files - * @param props {@link AcceleratorStackProps} - * @param firewallRoles string[] - * @param assetBucketKmsKey cdk.aws_kms.IKey | undefined - * @returns Bucket | undefined - */ - private createFirewallConfigBucket( - props: AcceleratorStackProps, - firewallRoles: string[], - assetBucketKmsKey?: cdk.aws_kms.IKey, - ): Bucket | undefined { - if (firewallRoles.length > 0) { - // Create firewall config bucket - const serverAccessLogsBucketName = this.getServerAccessLogsBucketName(); - const firewallConfigBucket = new Bucket(this, 'FirewallConfigBucket', { - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.firewallConfig}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - encryptionType: this.isS3CMKEnabled ? BucketEncryptionType.SSE_KMS : BucketEncryptionType.SSE_S3, - kmsKey: this.isS3CMKEnabled ? this.getAcceleratorKey(AcceleratorKeyType.S3_KEY)! : undefined, - serverAccessLogsBucketName, - }); - - if (!serverAccessLogsBucketName) { - // AwsSolutions-S1: The S3 Bucket has server access logs disabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `/${this.stackName}/FirewallConfigBucket/Resource/Resource`, - reason: 'Due to configuration settings, server access logs have been disabled.', - }, - ], - }); - } - - // Create IAM policy and role for config replacement custom resource - this.createFirewallConfigCustomResourceRole(props, firewallConfigBucket, assetBucketKmsKey); - - // Grant read access to all firewall roles in scope - for (const role of firewallRoles) { - firewallConfigBucket.getS3Bucket().grantRead(this.roles[role]); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${this.roles[role].node.id}/DefaultPolicy/Resource`, - reason: 'Access to read from the S3 bucket is required for this IAM instance profile', - }, - ], - }); - if (this.isManagedByAsea(AseaResourceType.IAM_ROLE, this.roles[role].roleName)) { - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${this.roles[role].node.id}/Policy/Resource`, - reason: 'Access to read from the S3 bucket is required for this IAM instance profile', - }, - ], - }); - } - } - return firewallConfigBucket; - } - return; - } - - /** - * Returns an array of IAM instance profile names that are in scope of the stack. - * @param vpcResources ({@link VpcConfig} | {@link VpcTemplatesConfig})[] - * @param firewallConfig {@link Ec2FirewallConfig} - * @returns string[] - */ - private getFirewallRolesInScope( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - firewallConfig?: Ec2FirewallConfig, - ): string[] { - const firewalls = [...(firewallConfig?.autoscalingGroups ?? []), ...(firewallConfig?.instances ?? [])]; - const firewallRoles: string[] = []; - - for (const firewall of firewalls) { - if ( - this.isFirewallInScope(vpcResources, firewall) && - firewall.launchTemplate.iamInstanceProfile && - (firewall.configFile || firewall.configDir || firewall.licenseFile) - ) { - firewallRoles.push(firewall.launchTemplate.iamInstanceProfile); - } - } - return firewallRoles; - } - - /** - * Returns true if the firewall is in scope of the stack. - * @param vpcResources ({@link VpcConfig} | {@link VpcTemplatesConfig})[] - * @param firewall {@link Ec2FirewallInstanceConfig} | {@link Ec2FirewallAutoScalingGroupConfig} - * @returns boolean - */ - private isFirewallInScope( - vpcResources: (VpcConfig | VpcTemplatesConfig)[], - firewall: Ec2FirewallInstanceConfig | Ec2FirewallAutoScalingGroupConfig, - ): boolean { - const vpc = getVpcConfig(vpcResources, firewall.vpc); - const vpcAccountIds = this.getVpcAccountIds(vpc); - // If no account specified in Firewall Config Firewall is in scope of VPC. - const instanceAccountIds = firewall.account - ? [this.props.accountsConfig.getAccountId(firewall.account)] - : vpcAccountIds; - return instanceAccountIds.includes(cdk.Stack.of(this).account) && vpc.region === cdk.Stack.of(this).region; - } - - /** - * Create Lambda custom resource role for firewall config replacements - * @param props {@link AcceleratorStackProps} - * @param firewallConfigBucket {@link Bucket} - * @param assetBucketKmsKey cdk.aws_kms.IKey | undefined - * @returns cdk.aws_iam.Role - */ - private createFirewallConfigCustomResourceRole( - props: AcceleratorStackProps, - firewallConfigBucket: Bucket, - assetBucketKmsKey?: cdk.aws_kms.IKey, - ): cdk.aws_iam.Role { - if (!assetBucketKmsKey) { - throw new Error( - `Asset bucket KMS key is undefined. KMS key must be defined so permissions can be added to the custom resource role.`, - ); - } - - const assetBucketArn = `arn:${props.partition}:s3:::${ - this.acceleratorResourceNames.bucketPrefixes.assets - }-${props.accountsConfig.getManagementAccountId()}-${props.globalConfig.homeRegion}`; - - const lambdaExecutionPolicy = cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSLambdaBasicExecutionRole', - ); - - const firewallConfigPolicy = new cdk.aws_iam.ManagedPolicy(this, 'FirewallConfigPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeInstances', 'ec2:DescribeSubnets', 'ec2:DescribeVpcs', 'ec2:DescribeVpnConnections'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetObject*', 's3:ListBucket'], - resources: [assetBucketArn, `${assetBucketArn}/*`], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt'], - resources: [assetBucketKmsKey.keyArn], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${this.partition}:iam::*:role/${this.acceleratorResourceNames.roles.crossAccountVpnRoleName}`, - ], - }), - // - // secretsmanager:GetSecretValue and kms:Decrypt permissions to management account resources - // to apply replacements from management account - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['secretsmanager:GetSecretValue'], - resources: [ - `arn:${this.partition}:secretsmanager:*:${this.props.accountsConfig.getManagementAccountId()}:secret:*`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt'], - resources: [`arn:${this.partition}:kms:*:${this.props.accountsConfig.getManagementAccountId()}:key/*`], - }), - ], - }); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${firewallConfigPolicy.node.id}/Resource`, - reason: 'Policy permissions are part of managed role and rest is to get access from s3 bucket', - }, - ], - }); - - const firewallConfigRole = new cdk.aws_iam.Role(this, 'FirewallConfigRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`lambda.${this.urlSuffix}`), - description: 'Landing Zone Accelerator firewall configuration custom resource access role', - managedPolicies: [firewallConfigPolicy, lambdaExecutionPolicy], - roleName: this.acceleratorResourceNames.roles.firewallConfigFunctionRoleName, - }); - firewallConfigBucket.getS3Bucket().grantPut(firewallConfigRole); - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${firewallConfigRole.node.id}/DefaultPolicy/Resource`, - reason: 'Policy permissions are part of managed role and rest is to get access from s3 bucket', - }, - ], - }); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${firewallConfigRole.node.id}/Resource`, - reason: 'IAM Role for lambda needs AWS managed policy', - }, - ], - }); - - return firewallConfigRole; - } - - /** - * Add SSM Parameters - */ - private addSsmParameters() { - let index = 1; - const parameterMap = new Map(); - - for (const ssmParametersItem of this.props.globalConfig.ssmParameters ?? []) { - if (!this.isIncluded(ssmParametersItem.deploymentTargets)) { - continue; - } - - for (const parameterItem of ssmParametersItem.parameters) { - this.logger.info(`[operations-stack] Add SSM Parameter ${parameterItem.path}`); - // Create parameter - const parameter = new cdk.aws_ssm.StringParameter(this, pascalCase(`SSMParameter-${parameterItem.name}`), { - parameterName: parameterItem.path, - stringValue: parameterItem.value, - }); - parameterMap.set(index, parameter); - - // Add a dependency for every 5 parameters - if (index > 5) { - const dependsOnParam = parameterMap.get(index - (index % 5)); - if (!dependsOnParam) { - this.logger.error(`Error creating SSM parameter ${parameterItem.name}: previous SSM parameter undefined`); - throw new Error(`Configuration validation failed at runtime.`); - } - parameter.node.addDependency(dependsOnParam); - } - // Increment index - index += 1; - } - } - } - - /** - * Function returns a list of CloudWatch Log Group ARNs - */ - private getCloudWatchLogGroupList(): string[] { - const cloudWatchLogGroupListResources: string[] = []; - for (const regionItem of this.props.globalConfig.enabledRegions ?? []) { - const logGroupItem = `arn:${cdk.Stack.of(this).partition}:logs:${regionItem}:${ - cdk.Stack.of(this).account - }:log-group:*`; - - // Already in the list, skip - if (cloudWatchLogGroupListResources.includes(logGroupItem)) { - continue; - } - - // Exclude regions is not used - if (this.props.globalConfig.logging.sessionManager.excludeRegions) { - // If exclude regions is defined, ensure not excluded - if (!this.props.globalConfig.logging.sessionManager.excludeRegions.includes(regionItem)) { - cloudWatchLogGroupListResources.push(logGroupItem); - } - } - // Exclude regions is not being used, add logGroupItem - else { - cloudWatchLogGroupListResources.push(logGroupItem); - } - } - return cloudWatchLogGroupListResources; - } - - /** - * Function returns a list of CloudWatch Log Group Name ARNs - */ - private getSessionManagerCloudWatchLogGroupList(): string[] { - const logGroupName = `${this.props.prefixes.ssmLogName}-sessionmanager-logs`; - const cloudWatchLogGroupListResources: string[] = []; - for (const regionItem of this.props.globalConfig.enabledRegions ?? []) { - const logGroupItem = `arn:${cdk.Stack.of(this).partition}:logs:${regionItem}:${ - cdk.Stack.of(this).account - }:log-group:${logGroupName}:*`; - // Already in the list, skip - if (cloudWatchLogGroupListResources.includes(logGroupItem)) { - continue; - } - - // Exclude regions is not used - if (this.props.globalConfig.logging.sessionManager.excludeRegions) { - // If exclude regions is defined, ensure not excluded - if (!this.props.globalConfig.logging.sessionManager.excludeRegions.includes(regionItem)) { - cloudWatchLogGroupListResources.push(logGroupItem); - } - } - // Exclude regions is not being used, add logGroupItem - else { - cloudWatchLogGroupListResources.push(logGroupItem); - } - } - return cloudWatchLogGroupListResources; - } - - /** - * Function returns a list of centralized S3 Bucket ARNs - */ - private getS3BucketList(): string[] { - const s3BucketResourcesList: string[] = []; - for (const regionItem of this.props.globalConfig.enabledRegions ?? []) { - const s3Item = `arn:${cdk.Stack.of(this).partition}:s3:::${this.centralLogsBucketName}/session/${ - cdk.Stack.of(this).account - }/${regionItem}/*`; - // Already in the list, skip - if (s3BucketResourcesList.includes(s3Item)) { - continue; - } - - // Exclude regions is not used - if (this.props.globalConfig.logging.sessionManager.excludeRegions) { - // If exclude regions is defined, ensure not excluded - if (!this.props.globalConfig.logging.sessionManager.excludeRegions.includes(regionItem)) { - s3BucketResourcesList.push(s3Item); - } - } - // Exclude regions is not being used, add s3Item - else { - s3BucketResourcesList.push(s3Item); - } - } - return s3BucketResourcesList; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/organizations-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/organizations-stack.ts deleted file mode 100644 index 9f119b3..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/organizations-stack.ts +++ /dev/null @@ -1,818 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { pascalCase } from 'pascal-case'; -import * as path from 'path'; - -import { - CentralSecurityServicesConfig, - GuardDutyConfig, - IdentityCenterAssignmentConfig, - IdentityCenterPermissionSetConfig, - Region, -} from '@aws-accelerator/config'; -import { - AuditManagerOrganizationAdminAccount, - Bucket, - BucketEncryptionType, - BucketReplicationProps, - DetectiveOrganizationAdminAccount, - EnableAwsServiceAccess, - EnablePolicyType, - EnableSharingWithAwsOrganization, - FMSOrganizationAdminAccount, - GuardDutyOrganizationAdminAccount, - IpamOrganizationAdminAccount, - MacieOrganizationAdminAccount, - Policy, - PolicyAttachment, - PolicyType, - PolicyTypeEnum, - RegisterDelegatedAdministrator, - ReportDefinition, - SecurityHubOrganizationAdminAccount, - IdentityCenterOrganizationAdminAccount, -} from '@aws-accelerator/constructs'; -import * as cdk_extensions from '@aws-cdk-extensions/cdk-extensions'; - -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionRuleIds, -} from './accelerator-stack'; - -export interface OrganizationsStackProps extends AcceleratorStackProps { - configDirPath: string; -} - -/** - * The Organizations stack is executed in all enabled regions in the - * Organizations Management (Root) account - */ -export class OrganizationsStack extends AcceleratorStack { - /** - * KMS Key used to encrypt custom resource CloudWatch environment variables, when undefined default AWS managed key will be used - */ - private cloudwatchKey: cdk.aws_kms.IKey | undefined; - private centralLogsBucketKey: cdk.aws_kms.IKey; - private bucketReplicationProps: BucketReplicationProps; - private logRetention: number; - private stackProperties: AcceleratorStackProps; - private centralSecurityServices: CentralSecurityServicesConfig; - - /** - * KMS Key used to encrypt custom resource Lambda environment variables, when undefined default AWS managed key will be used - */ - private lambdaKey: cdk.aws_kms.IKey | undefined; - - constructor(scope: Construct, id: string, props: OrganizationsStackProps) { - super(scope, id, props); - - // Set private properties - this.stackProperties = props; - this.logRetention = this.stackProperties.globalConfig.cloudwatchLogRetentionInDays; - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - this.centralLogsBucketKey = this.getCentralLogsBucketKey(this.cloudwatchKey); - this.centralSecurityServices = this.stackProperties.securityConfig.centralSecurityServices; - this.bucketReplicationProps = { - destination: { - bucketName: this.centralLogsBucketName, - accountId: this.stackProperties.accountsConfig.getLogArchiveAccountId(), - keyArn: this.centralLogsBucketKey.keyArn, - }, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.stackProperties.globalConfig.cloudwatchLogRetentionInDays, - useExistingRoles: this.props.useExistingRoles ?? false, - acceleratorPrefix: this.props.prefixes.accelerator, - }; - - // Only deploy resources in this stack if organizations is enabled - if (!props.organizationConfig.enable) { - return; - } - - // Security Services delegated admin account configuration - // Global decoration for security services - const delegatedAdminAccount = props.securityConfig.centralSecurityServices.delegatedAdminAccount; - const securityAdminAccountId = props.accountsConfig.getAccountId(delegatedAdminAccount); - - this.logger.debug(`homeRegion: ${props.globalConfig.homeRegion}`); - - // - // Global Organizations actions, only execute in the home region - // - if (this.stackProperties.globalConfig.homeRegion === cdk.Stack.of(this).region) { - // - // Organizational CloudTrail - // - this.configureOrganizationCloudTrail(); - - // - // Enable Backup Policy - // - this.addBackupPolicies(); - - // - // Enable Cost and Usage Reports - // - this.addCostAndUsageReport(); - - // - // IAM Access Analyzer (Does not have a native service enabler) - // - this.enableIamAccessAnalyzer(); - - // - // Enable RAM organization sharing - // - this.enableRamOrganizationSharing(); - - // - // Enable Service Catalog - // - this.enableServiceCatalog(); - - // - // Enable IPAM delegated administrator - // - this.enableIpamDelegatedAdminAccount(); - - // - // Enable FMS Delegated Admin Account - // - this.enableFMSDelegatedAdminAccount({ cloudwatch: this.cloudwatchKey, lambda: this.lambdaKey }); - - //IdentityCenter Config - this.enableIdentityCenterDelegatedAdminAccount(securityAdminAccountId); - - //Enable Config Recorder Delegated Admin - this.enableConfigRecorderDelegatedAdminAccount(); - - // Enable Control Tower controls - this.enableControlTowerControls(); - } - - // Macie Configuration - this.enableMacieDelegatedAdminAccount(securityAdminAccountId); - - //GuardDuty Config - this.enableGuardDutyDelegatedAdminAccount(securityAdminAccountId); - - //Audit Manager Config - this.enableAuditManagerDelegatedAdminAccount(securityAdminAccountId); - - //Detective Config - this.enableDetectiveDelegatedAdminAccount(securityAdminAccountId); - - //SecurityHub Config - this.enableSecurityHubDelegatedAdminAccount(securityAdminAccountId); - - // - // Tagging Policies Config - // - this.addTaggingPolicies(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - // - // Configure Trusted Services and Delegated Management Accounts - // - // - this.logger.info('Completed stack synthesis'); - } - - /** - * Function to enable Control Tower Controls - * Only optional controls are supported (both Strongly Recommended and Elective) - * https://docs.aws.amazon.com/controltower/latest/userguide/optional-controls.html - */ - private enableControlTowerControls() { - if (this.stackProperties.globalConfig.controlTower.enable) { - for (const control of this.stackProperties.globalConfig.controlTower.controls ?? []) { - this.logger.info(`Control ${control.identifier} status: ${control.enable}`); - - if (control.enable) { - for (const orgUnit of control.deploymentTargets.organizationalUnits) { - const orgUnitArn = this.stackProperties.organizationConfig.getOrganizationalUnitArn(orgUnit); - const controlArn = `arn:${this.props.partition}:controltower:${this.region}::control/${control.identifier}`; - - new cdk.aws_controltower.CfnEnabledControl(this, pascalCase(`${control.identifier}-${orgUnit}`), { - controlIdentifier: controlArn, - targetIdentifier: orgUnitArn, - }); - } - } - } - } - } - - /** - * Function to add backup policies - */ - private addBackupPolicies() { - if (this.stackProperties.organizationConfig.backupPolicies.length > 0) { - this.logger.info(`Adding Backup Policies`); - - const enablePolicyTypeBackup = new EnablePolicyType(this, 'enablePolicyTypeBackup', { - policyType: PolicyTypeEnum.BACKUP_POLICY, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - - for (const backupPolicies of this.stackProperties.organizationConfig.backupPolicies ?? []) { - const policy = new Policy(this, backupPolicies.name, { - description: backupPolicies.description, - name: backupPolicies.name, - partition: this.props.partition, - path: this.generatePolicyReplacements( - path.join(this.stackProperties.configDirPath, backupPolicies.policy), - true, - this.organizationId, - ), - type: PolicyType.BACKUP_POLICY, - acceleratorPrefix: this.props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - - policy.node.addDependency(enablePolicyTypeBackup); - - for (const orgUnit of backupPolicies.deploymentTargets.organizationalUnits) { - const backupPolicyAttachment = new PolicyAttachment( - this, - pascalCase(`Attach_${backupPolicies.name}_${orgUnit}`), - { - policyId: policy.id, - targetId: this.stackProperties.organizationConfig.getOrganizationalUnitId(orgUnit), - type: PolicyType.BACKUP_POLICY, - configPolicyNames: this.getScpNamesForTarget(orgUnit, 'ou'), - acceleratorPrefix: this.props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ); - - backupPolicyAttachment.node.addDependency(policy); - } - } - } - } - - /** - * Function to add Cost and Usage Report - */ - private addCostAndUsageReport() { - if (this.stackProperties.globalConfig.reports?.costAndUsageReport && this.props.partition != 'aws-us-gov') { - this.logger.info('Adding Cost and Usage Reports'); - - const serverAccessLogsBucketName = this.getServerAccessLogsBucketName(); - - const reportBucket = new Bucket(this, 'ReportBucket', { - encryptionType: BucketEncryptionType.SSE_S3, // CUR does not support KMS CMK - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.costUsage}-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - serverAccessLogsBucketName, - s3LifeCycleRules: this.getS3LifeCycleRules( - this.stackProperties.globalConfig.reports.costAndUsageReport.lifecycleRules, - ), - replicationProps: this.bucketReplicationProps, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: - `/${this.stackName}/ReportBucket/ReportBucketReplication/` + - pascalCase(this.centralLogsBucketName) + - '-ReplicationRole/DefaultPolicy/Resource', - reason: 'Allows only specific policy.', - }, - ], - }); - - if (!serverAccessLogsBucketName) { - // AwsSolutions-S1: The S3 Bucket has server access logs disabled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `/${this.stackName}/ReportBucket/Resource/Resource`, - reason: 'Due to configuration settings, server access logs have been disabled.', - }, - ], - }); - } - - new ReportDefinition(this, 'ReportDefinition', { - compression: this.stackProperties.globalConfig.reports.costAndUsageReport.compression, - format: this.stackProperties.globalConfig.reports.costAndUsageReport.format, - refreshClosedReports: this.stackProperties.globalConfig.reports.costAndUsageReport.refreshClosedReports, - reportName: this.stackProperties.globalConfig.reports.costAndUsageReport.reportName, - reportVersioning: this.stackProperties.globalConfig.reports.costAndUsageReport.reportVersioning, - s3Bucket: reportBucket.getS3Bucket(), - s3Prefix: `${this.stackProperties.globalConfig.reports.costAndUsageReport.s3Prefix}/${ - cdk.Stack.of(this).account - }/`, - s3Region: cdk.Stack.of(this).region, - timeUnit: this.stackProperties.globalConfig.reports.costAndUsageReport.timeUnit, - additionalArtifacts: this.stackProperties.globalConfig.reports.costAndUsageReport.additionalArtifacts, - additionalSchemaElements: this.stackProperties.globalConfig.reports.costAndUsageReport.additionalSchemaElements, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - partition: this.props.partition, - }); - } - } - - /** - * Function to Enable Service Access for access-analyzer.amazonaws.com' - */ - private enableIamAccessAnalyzer() { - if (this.stackProperties.securityConfig.accessAnalyzer.enable) { - this.logger.debug('Enable Service Access for access-analyzer.amazonaws.com'); - - const enableAccessAnalyzer = new EnableAwsServiceAccess(this, 'EnableAccessAnalyzer', { - servicePrincipal: 'access-analyzer.amazonaws.com', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - - const registerDelegatedAdministratorAccessAnalyzer = new RegisterDelegatedAdministrator( - this, - 'RegisterDelegatedAdministratorAccessAnalyzer', - { - accountId: this.stackProperties.accountsConfig.getAuditAccountId(), - servicePrincipal: 'access-analyzer.amazonaws.com', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ); - - registerDelegatedAdministratorAccessAnalyzer.node.addDependency(enableAccessAnalyzer); - } - } - - /** - * Function to enable RAM organization sharing - */ - private enableRamOrganizationSharing() { - if (this.stackProperties.organizationConfig.enable) { - new EnableSharingWithAwsOrganization(this, 'EnableSharingWithAwsOrganization', { - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - } - // - // Enable Service Catalog - // - private enableServiceCatalog() { - if (this.props.customizationsConfig?.customizations?.serviceCatalogPortfolios?.length > 0) { - new EnableAwsServiceAccess(this, 'EnableOrganizationsServiceCatalog', { - servicePrincipal: 'servicecatalog.amazonaws.com', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - } - /** - * Function to enable IPAM delegated admin account - */ - private enableIpamDelegatedAdminAccount() { - if (this.stackProperties.networkConfig.centralNetworkServices?.ipams) { - // Get delegated admin account - const networkAdminAccountId = this.stackProperties.accountsConfig.getAccountId( - this.stackProperties.networkConfig.centralNetworkServices.delegatedAdminAccount, - ); - - // Create delegated admin if the account ID is not the management account - if (networkAdminAccountId !== cdk.Stack.of(this).account) { - this.logger.info(`Enabling IPAM delegated administrator for account ${networkAdminAccountId}`); - - new IpamOrganizationAdminAccount(this, 'IpamAdminAccount', { - accountId: networkAdminAccountId, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } - } - } - - /** - * Function to enable FMS delegated admin account - */ - private enableFMSDelegatedAdminAccount(key: { cloudwatch?: cdk.aws_kms.IKey; lambda?: cdk.aws_kms.IKey }) { - const fmsConfig = this.stackProperties.networkConfig.firewallManagerService; - if ( - fmsConfig && - cdk.Stack.of(this).region === this.stackProperties.globalConfig.homeRegion && - this.props.organizationConfig.enable && - (this.props.partition === 'aws' || this.props.partition === 'aws-us-gov' || this.props.partition === 'aws-cn') - ) { - const fmsServiceLinkedRole = this.createAwsFirewallManagerServiceLinkedRole({ - cloudwatch: key.cloudwatch, - lambda: key.lambda, - }); - - if (fmsServiceLinkedRole) { - const adminAccountName = fmsConfig.delegatedAdminAccount; - const adminAccountId = this.stackProperties.accountsConfig.getAccountId(adminAccountName); - const createFmsDelegatedAdmin = new FMSOrganizationAdminAccount(this, 'FMSOrganizationAdminAccount', { - adminAccountId, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - assumeRole: this.stackProperties.globalConfig.managementAccountAccessRole, - }); - // Add dependency to prevent race condition between delegated admin and service linked role - createFmsDelegatedAdmin.node.addDependency(fmsServiceLinkedRole); - } - } - } - - /** - * Function to enable Config Recorder delegated admin account - */ - private enableConfigRecorderDelegatedAdminAccount() { - if ( - this.stackProperties.securityConfig.awsConfig.aggregation?.enable && - this.stackProperties.securityConfig.awsConfig.aggregation.delegatedAdminAccount && - !this.stackProperties.globalConfig.controlTower.enable && - this.stackProperties.organizationConfig.enable - ) { - this.logger.debug('enableConfigRecorderDelegateAdminAccount'); - const enableConfigServiceAccess = new EnableAwsServiceAccess(this, 'EnableConfigAccess', { - servicePrincipal: 'config.amazonaws.com', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - - const registerConfigDelegatedAdministrator = new RegisterDelegatedAdministrator( - this, - 'RegisterConfigDelegatedAdministrator', - { - accountId: this.stackProperties.accountsConfig.getAccountId( - this.stackProperties.securityConfig.awsConfig.aggregation.delegatedAdminAccount, - ), - servicePrincipal: 'config.amazonaws.com', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ); - - registerConfigDelegatedAdministrator.node.addDependency(enableConfigServiceAccess); - } - } - - /** - * Function to enable Macie delegated admin account - * @param adminAccountId - */ - private enableMacieDelegatedAdminAccount(adminAccountId: string) { - if (this.centralSecurityServices.macie.enable) { - if (this.centralSecurityServices.macie.excludeRegions.indexOf(cdk.Stack.of(this).region as Region) == -1) { - this.logger.debug( - `Starts macie admin account delegation to the account with email ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account in ${cdk.Stack.of(this).region} region`, - ); - - this.logger.debug(`Macie Admin Account ID is ${adminAccountId}`); - new MacieOrganizationAdminAccount(this, 'MacieOrganizationAdminAccount', { - adminAccountId, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } else { - this.logger.debug( - `${cdk.Stack.of(this).region} region was in macie excluded list so ignoring this region for ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account`, - ); - } - } - } - - /** - * Function to enable GuardDuty delegated admin account - * @param adminAccountId - */ - private enableGuardDutyDelegatedAdminAccount(adminAccountId: string) { - const guardDutyConfig: GuardDutyConfig = this.centralSecurityServices.guardduty; - if (guardDutyConfig.enable) { - if (this.validateExcludeRegionsAndDeploymentTargets(guardDutyConfig)) { - this.logger.debug( - `Starts guardduty admin account delegation to the account with email ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account in ${cdk.Stack.of(this).region} region`, - ); - - this.logger.debug(`Guardduty Admin Account ID is ${adminAccountId}`); - new GuardDutyOrganizationAdminAccount(this, 'GuardDutyEnableOrganizationAdminAccount', { - adminAccountId, - logRetentionInDays: this.logRetention, - kmsKey: this.cloudwatchKey, - }); - } else { - this.logger.debug( - `${cdk.Stack.of(this).region} region was in guardduty excluded list so ignoring this region for ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account`, - ); - } - } - } - - /** - * Function to enable AuditManager delegated admin account - * @param adminAccountId - */ - private enableAuditManagerDelegatedAdminAccount(adminAccountId: string) { - if (this.centralSecurityServices.auditManager?.enable) { - if ( - this.centralSecurityServices.auditManager?.excludeRegions.indexOf(cdk.Stack.of(this).region as Region) == -1 - ) { - this.logger.debug( - `Starts audit manager admin account delegation to the account with email ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account in ${cdk.Stack.of(this).region} region`, - ); - - this.logger.debug(`AuditManager Admin Account ID is ${adminAccountId}`); - new AuditManagerOrganizationAdminAccount(this, 'AuditManagerEnableOrganizationAdminAccount', { - adminAccountId, - logRetentionInDays: this.logRetention, - kmsKey: this.cloudwatchKey, - }); - } else { - this.logger.debug( - `${cdk.Stack.of(this).region} region was in auditmanager excluded list so ignoring this region for ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account`, - ); - } - } - } - - /** - * Function to enable Detective delegated admin account - * @param adminAccountId - */ - private enableDetectiveDelegatedAdminAccount(adminAccountId: string) { - if (this.centralSecurityServices.detective?.enable) { - if (this.centralSecurityServices.detective?.excludeRegions.indexOf(cdk.Stack.of(this).region as Region) == -1) { - this.logger.debug( - `Starts detective admin account delegation to the account with email ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account in ${cdk.Stack.of(this).region} region`, - ); - - this.logger.debug(`Detective Admin Account ID is ${adminAccountId}`); - new DetectiveOrganizationAdminAccount(this, 'DetectiveOrganizationAdminAccount', { - adminAccountId, - logRetentionInDays: this.logRetention, - kmsKey: this.cloudwatchKey, - }); - } else { - this.logger.debug( - `${cdk.Stack.of(this).region} region was in detective excluded list so ignoring this region for ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account`, - ); - } - } - } - /** - * Function to enable SecurityHub delegated admin account - * @param adminAccountId - */ - private enableSecurityHubDelegatedAdminAccount(adminAccountId: string) { - if (this.centralSecurityServices.securityHub.enable) { - if (this.validateExcludeRegionsAndDeploymentTargets(this.centralSecurityServices.securityHub)) { - this.logger.debug( - `Starts SecurityHub admin account delegation to the account with email ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account in ${cdk.Stack.of(this).region} region`, - ); - - this.logger.debug(`SecurityHub Admin Account ID is ${adminAccountId}`); - new SecurityHubOrganizationAdminAccount(this, 'SecurityHubOrganizationAdminAccount', { - adminAccountId, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - } else { - this.logger.debug( - `${cdk.Stack.of(this).region} region was in SecurityHub excluded list so ignoring this region for ${ - this.stackProperties.accountsConfig.getAuditAccount().email - } account`, - ); - } - } - } - - /** - * Function to add Tagging policies - */ - private addTaggingPolicies() { - if (this.stackProperties.organizationConfig.taggingPolicies.length > 0) { - this.logger.info(`Adding Tagging Policies`); - const enablePolicyTypeTag = new EnablePolicyType(this, 'enablePolicyTypeTag', { - policyType: PolicyTypeEnum.TAG_POLICY, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - for (const taggingPolicy of this.stackProperties.organizationConfig.taggingPolicies ?? []) { - const policy = new Policy(this, `${taggingPolicy.name}`, { - description: taggingPolicy.description, - name: `${taggingPolicy.name}`, - partition: this.props.partition, - path: this.generatePolicyReplacements( - path.join(this.stackProperties.configDirPath, taggingPolicy.policy), - true, - this.organizationId, - ), - type: PolicyType.TAG_POLICY, - acceleratorPrefix: this.props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - policy.node.addDependency(enablePolicyTypeTag); - for (const orgUnit of taggingPolicy.deploymentTargets.organizationalUnits ?? []) { - const tagPolicyAttachment = new PolicyAttachment( - this, - pascalCase(`Attach_${taggingPolicy.name}_${orgUnit}`), - { - policyId: policy.id, - targetId: this.stackProperties.organizationConfig.getOrganizationalUnitId(orgUnit), - type: PolicyType.TAG_POLICY, - configPolicyNames: this.getScpNamesForTarget(orgUnit, 'ou'), - acceleratorPrefix: this.props.prefixes.accelerator, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }, - ); - tagPolicyAttachment.node.addDependency(policy); - } - } - } - } - - /** - * Custom resource to check if Identity Center Delegated Administrator - * needs to be updated. - * @param adminAccountId - */ - private enableIdentityCenterDelegatedAdminAccount(adminAccountId: string) { - let lzaManagedPermissionSets: IdentityCenterPermissionSetConfig[] = []; - let lzaManagedAssignments: IdentityCenterAssignmentConfig[] = []; - let assignmentList: { [x: string]: string[] }[] = []; - let delegatedAdminAccountId = adminAccountId; - - const identityCenterDelgatedAdminOverrideId = this.props.iamConfig.identityCenter?.delegatedAdminAccount; - if (identityCenterDelgatedAdminOverrideId) { - delegatedAdminAccountId = this.props.accountsConfig.getAccountId(identityCenterDelgatedAdminOverrideId); - } - - if (this.props.iamConfig.identityCenter?.identityCenterPermissionSets) { - lzaManagedPermissionSets = this.props.iamConfig.identityCenter.identityCenterPermissionSets; - } - - if (this.props.iamConfig.identityCenter?.identityCenterAssignments) { - lzaManagedAssignments = this.props.iamConfig.identityCenter.identityCenterAssignments; - assignmentList = lzaManagedAssignments.map(assignment => ({ - [assignment.permissionSetName]: this.getAccountIdsFromDeploymentTargets(assignment.deploymentTargets), - })); - } - - if (this.props.partition === 'aws' || this.props.partition === 'aws-us-gov') { - new IdentityCenterOrganizationAdminAccount(this, `IdentityCenterAdmin`, { - adminAccountId: delegatedAdminAccountId, - lzaManagedPermissionSets: lzaManagedPermissionSets, - lzaManagedAssignments: assignmentList, - }); - this.logger.info(`Delegated Admin account for Identity Center is: ${delegatedAdminAccountId}`); - } - } - - private configureOrganizationCloudTrail() { - this.logger.debug(`logging.cloudtrail.enable: ${this.stackProperties.globalConfig.logging.cloudtrail.enable}`); - this.logger.debug( - `logging.cloudtrail.organizationTrail: ${this.stackProperties.globalConfig.logging.cloudtrail.organizationTrail}`, - ); - - if ( - !this.stackProperties.globalConfig.logging.cloudtrail.enable || - !this.stackProperties.globalConfig.logging.cloudtrail.organizationTrail - ) { - return; - } - - this.logger.info('Enable CloudTrail Service Access'); - const enableCloudtrailServiceAccess = new EnableAwsServiceAccess(this, 'EnableOrganizationsCloudTrail', { - servicePrincipal: 'cloudtrail.amazonaws.com', - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.logRetention, - }); - - this.logger.info('Adding Organizations CloudTrail'); - - const cloudTrailCloudWatchCmk = new cdk.aws_kms.Key(this, 'CloudTrailCloudWatchCmk', { - enableKeyRotation: true, - description: this.acceleratorResourceNames.customerManagedKeys.orgTrailLog.description, - alias: this.acceleratorResourceNames.customerManagedKeys.orgTrailLog.alias, - }); - cloudTrailCloudWatchCmk.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Account use of the key', - actions: ['kms:*'], - principals: [new cdk.aws_iam.AccountRootPrincipal()], - resources: ['*'], - }), - ); - cloudTrailCloudWatchCmk.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow logs use of the key', - actions: ['kms:*'], - principals: [ - new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - ], - resources: ['*'], - conditions: { - ArnEquals: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this).partition}:logs:${ - cdk.Stack.of(this).region - }:${cdk.Stack.of(this).account}:*`, - }, - }, - }), - ); - - const cloudTrailCloudWatchCmkLogGroup = new cdk.aws_logs.LogGroup(this, 'CloudTrailCloudWatchLogGroup', { - retention: this.stackProperties.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: cloudTrailCloudWatchCmk, - logGroupName: `${this.props.prefixes.trailLogName}-cloudtrail-logs`, - }); - - let managementEventType = cdk.aws_cloudtrail.ReadWriteType.ALL; - if (this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings !== undefined) { - if (this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings.managementEvents === false) { - managementEventType = cdk.aws_cloudtrail.ReadWriteType.NONE; - } - } - const organizationsTrail = new cdk_extensions.Trail(this, 'OrganizationsCloudTrail', { - bucket: cdk.aws_s3.Bucket.fromBucketName(this, 'CentralLogsBucket', this.centralLogsBucketName), - s3KeyPrefix: 'cloudtrail-organization', - cloudWatchLogGroup: cloudTrailCloudWatchCmkLogGroup, - cloudWatchLogsRetention: cdk.aws_logs.RetentionDays.TEN_YEARS, - enableFileValidation: true, - encryptionKey: this.centralLogsBucketKey, - includeGlobalServiceEvents: - this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.globalServiceEvents ?? true, - isMultiRegionTrail: - this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.multiRegionTrail ?? true, - isOrganizationTrail: true, - apiCallRateInsight: - this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.apiCallRateInsight ?? false, - apiErrorRateInsight: - this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.apiErrorRateInsight ?? false, - managementEvents: managementEventType, - sendToCloudWatchLogs: - this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.sendToCloudWatchLogs ?? true, - trailName: `${this.props.prefixes.accelerator}-Organizations-CloudTrail`, - }); - - if (this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.s3DataEvents ?? true) { - organizationsTrail.addEventSelector(cdk.aws_cloudtrail.DataResourceType.S3_OBJECT, [ - `arn:${cdk.Stack.of(this).partition}:s3:::`, - ]); - } - - if (this.stackProperties.globalConfig.logging.cloudtrail.organizationTrailSettings?.lambdaDataEvents ?? true) { - organizationsTrail.addEventSelector(cdk.aws_cloudtrail.DataResourceType.LAMBDA_FUNCTION, [ - `arn:${cdk.Stack.of(this).partition}:lambda`, - ]); - } - - organizationsTrail.node.addDependency(enableCloudtrailServiceAccess); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/pipeline-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/pipeline-stack.ts deleted file mode 100644 index b64340c..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/pipeline-stack.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; - -import { version } from '../../../../../package.json'; -import * as pipeline from '../pipeline'; - -export interface PipelineStackProps extends cdk.StackProps { - readonly sourceRepository: string; - readonly sourceRepositoryOwner: string; - readonly sourceRepositoryName: string; - readonly sourceBranchName: string; - readonly enableApprovalStage: boolean; - readonly qualifier?: string; - readonly managementAccountId?: string; - readonly managementAccountRoleName?: string; - readonly managementAccountEmail: string; - readonly logArchiveAccountEmail: string; - readonly auditAccountEmail: string; - readonly controlTowerEnabled: string; - /** - * List of email addresses to be notified when pipeline is waiting for manual approval stage. - * If pipeline do not have approval stage enabled, this value will have no impact. - */ - readonly approvalStageNotifyEmailList?: string; - readonly partition: string; - /** - * Flag indicating installer using existing CodeCommit repository - */ - readonly useExistingConfigRepo: boolean; - /** - * User defined pre-existing config repository name - */ - readonly configRepositoryName: string; - /** - * User defined pre-existing config repository branch name - */ - readonly configRepositoryBranchName: string; - /** - * Accelerator resource name prefixes - */ - readonly prefixes: { - /** - * Accelerator prefix - used for resource name prefix for resources which do not have explicit prefix - */ - readonly accelerator: string; - readonly repoName: string; - readonly bucketName: string; - readonly ssmParamName: string; - readonly kmsAlias: string; - readonly snsTopicName: string; - readonly secretName: string; - readonly trailLogName: string; - readonly databaseName: string; - }; - /** - * Boolean for single account mode (i.e. AWS Jam or Workshop) - */ - readonly enableSingleAccountMode: boolean; - /** - * Accelerator pipeline account id, for external deployment it will be pipeline account otherwise management account - */ - pipelineAccountId: string; - /** - * Flag indicating existing role - */ - readonly useExistingRoles: boolean; -} - -export class PipelineStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: PipelineStackProps) { - super(scope, id, props); - new cdk.aws_ssm.StringParameter(this, 'SsmParamStackId', { - parameterName: `${props.prefixes.ssmParamName}/${cdk.Stack.of(this).stackName}/stack-id`, - stringValue: cdk.Stack.of(this).stackId, - }); - - new cdk.aws_ssm.StringParameter(this, 'SsmParamAcceleratorVersion', { - parameterName: `${props.prefixes.ssmParamName}/${cdk.Stack.of(this).stackName}/version`, - stringValue: version, - }); - - const toolkitRole = new cdk.aws_iam.Role(this, 'AdminCdkToolkitRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('codebuild.amazonaws.com'), - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], - maxSessionDuration: cdk.Duration.hours(4), - }); - - // List of regions with AWS CodeStar being supported. For details, see documentation: - // https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ - const awsCodeStarSupportedRegions = [ - 'us-east-1', - 'us-east-2', - 'us-west-1', - 'us-west-2', - 'ap-northeast-2', - 'ap-southeast-1', - 'ap-southeast-2', - 'ap-northeast-1', - 'ca-central-1', - 'eu-central-1', - 'eu-west-1', - 'eu-west-2', - 'eu-north-1', - ]; - - new pipeline.AcceleratorPipeline(this, 'Pipeline', { - toolkitRole, - awsCodeStarSupportedRegions, - ...props, - }); - - // cdk-nag suppressions - const iam4SuppressionPaths = [ - 'AdminCdkToolkitRole/Resource', - 'Pipeline/AWSServiceRoleForCodeStarNotifications/ServiceLinkedRoleCodestarNotificationsAmazonawsCom/ServiceLinkedRoleCodestarNotificationsAmazonawsComFunction/ServiceRole/Resource', - 'Pipeline/AWSServiceRoleForCodeStarNotifications/ServiceLinkedRoleCodestarNotificationsAmazonawsCom/Resource/framework-onEvent/ServiceRole/Resource', - ]; - - const iam5SuppressionPaths = [ - 'Pipeline/PipelineRole/DefaultPolicy/Resource', - 'Pipeline/Resource/Source/Source/CodePipelineActionRole/DefaultPolicy/Resource', - 'Pipeline/Resource/Source/Configuration/CodePipelineActionRole/DefaultPolicy/Resource', - 'Pipeline/BuildRole/DefaultPolicy/Resource', - 'AdminCdkToolkitRole/DefaultPolicy/Resource', - ]; - - const cb3SuppressionPaths = ['Pipeline/ToolkitProject/Resource', 'Pipeline/BuildProject/Resource']; - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - for (const path of iam4SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { id: 'AwsSolutions-IAM4', reason: 'Managed policies required for IAM role.' }, - ]); - } - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - for (const path of iam5SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { id: 'AwsSolutions-IAM5', reason: 'IAM role requires wildcard permissions.' }, - ]); - } - - // AwsSolutions-CB3: The CodeBuild project has privileged mode enabled. - for (const path of cb3SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { - id: 'AwsSolutions-CB3', - reason: 'Project requires access to the Docker daemon.', - }, - ]); - } - - // Add NagSuppressions for CodeStar notification in applicable regions - if (awsCodeStarSupportedRegions.includes(cdk.Stack.of(this).region)) { - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/Pipeline/AWSServiceRoleForCodeStarNotifications/CreateServiceLinkedRoleFunction/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'CodeStar Notification SLR needs managed policies.', - }, - ], - ); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/Pipeline/AWSServiceRoleForCodeStarNotifications/CreateServiceLinkedRoleProvider/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'CodeStar Notification SLR needs managed policies.', - }, - ], - ); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/prepare-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/prepare-stack.ts deleted file mode 100644 index 4ba490c..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/prepare-stack.ts +++ /dev/null @@ -1,890 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SnsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import path from 'path'; - -import { - Account, - CreateControlTowerAccounts, - CreateOrganizationAccounts, - GetPortfolioId, - MoveAccounts, - OrganizationalUnits, -} from '@aws-accelerator/constructs'; - -import { LoadAcceleratorConfigTable } from '../load-config-table'; -import { ValidateEnvironmentConfig } from '../validate-environment-config'; -import { AcceleratorStack, AcceleratorStackProps, NagSuppressionRuleIds } from './accelerator-stack'; -import { - AccountsConfig, - OrganizationConfig, - ReplacementsConfig, - ServiceControlPolicyConfig, -} from '@aws-accelerator/config'; - -type scpTargetType = 'ou' | 'account'; - -/** - * Service Control Policy Type - */ -type serviceControlPolicyType = { - name: string; - targetType: scpTargetType; - strategy: string; - targets: { name: string; id: string }[]; -}; -export class PrepareStack extends AcceleratorStack { - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - let organizationAccounts: CreateOrganizationAccounts | undefined; - let controlTowerAccounts: CreateControlTowerAccounts | undefined; - - if ( - cdk.Stack.of(this).region === props.globalConfig.homeRegion && - cdk.Stack.of(this).account === props.accountsConfig.getManagementAccountId() - ) { - this.logger.info(`homeRegion: ${props.globalConfig.homeRegion}`); - this.ssmParameters.push({ - logicalId: 'Parameter', - parameterName: `${props.prefixes.ssmParamName}/prepare-stack/validate`, - stringValue: 'value', - }); - - // - // Create Management Account Key - // - const managementAccountKey = this.createManagementKey(props); - - // - // Create Management Account CloudWatch Key - // - const cloudwatchKey = this.createManagementAccountCloudWatchKey(); - - // - // Create Management Account Lambda Key - // - const lambdaKey = this.createManagementAccountLambdaKey(); - - const commitId = props.configCommitId || ''; - - // Make assets from the configuration directory - this.logger.info(`Configuration assets creation`); - const accountConfigAsset = new cdk.aws_s3_assets.Asset(this, 'AccountConfigAsset', { - path: path.join(props.configDirPath, AccountsConfig.FILENAME), - }); - const organizationsConfigAsset = new cdk.aws_s3_assets.Asset(this, 'OrganizationConfigAsset', { - path: path.join(props.configDirPath, OrganizationConfig.FILENAME), - }); - let replacementsConfigAsset = undefined; - if (fs.existsSync(path.join(props.configDirPath, ReplacementsConfig.FILENAME))) { - replacementsConfigAsset = new cdk.aws_s3_assets.Asset(this, 'ReplacementsConfigAsset', { - path: path.join(props.configDirPath, ReplacementsConfig.FILENAME), - }); - } - - const driftDetectedParameter = new cdk.aws_ssm.StringParameter(this, 'AcceleratorControlTowerDriftParameter', { - parameterName: this.acceleratorResourceNames.parameters.controlTowerDriftDetection, - stringValue: 'false', - allowedPattern: '^(true|false)$', - }); - - const driftMessageParameter = new cdk.aws_ssm.StringParameter( - this, - 'AcceleratorControlTowerDriftMessageParameter', - { - parameterName: this.acceleratorResourceNames.parameters.controlTowerLastDriftMessage, - stringValue: 'none', - }, - ); - - if (props.organizationConfig.enable) { - const configTable = new cdk.aws_dynamodb.Table(this, 'AcceleratorConfigTable', { - partitionKey: { name: 'dataType', type: cdk.aws_dynamodb.AttributeType.STRING }, - sortKey: { name: 'acceleratorKey', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: managementAccountKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - pointInTimeRecovery: true, - }); - - configTable.addLocalSecondaryIndex({ - indexName: 'awsResourceKeys', - sortKey: { name: 'awsKey', type: cdk.aws_dynamodb.AttributeType.STRING }, - projectionType: cdk.aws_dynamodb.ProjectionType.KEYS_ONLY, - }); - - // AwsSolutions-DDB3: The DynamoDB table does not have Point-in-time Recovery enabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.DDB3, - details: [ - { - path: `${this.stackName}/AcceleratorConfigTable/Resource`, - reason: - 'AcceleratorConfigTable DynamoDB table do not need point in time recovery, data can be re-created', - }, - ], - }); - - new cdk.aws_ssm.StringParameter(this, 'ConfigTableArnParameter', { - parameterName: this.acceleratorResourceNames.parameters.configTableArn, - stringValue: configTable.tableArn, - }); - - new cdk.aws_ssm.StringParameter(this, 'ConfigTableNameParameter', { - parameterName: this.acceleratorResourceNames.parameters.configTableName, - stringValue: configTable.tableName, - }); - - new cdk.aws_iam.Role(this, 'AcceleratorMoveAccountRole', { - roleName: this.acceleratorResourceNames.roles.moveAccountConfig, - assumedBy: new cdk.aws_iam.AccountPrincipal(cdk.Stack.of(this).account), - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [ - `arn:${cdk.Aws.PARTITION}:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter${props.prefixes.ssmParamName}/prepare-stack/configTable/*`, - ], - }), - ], - }), - }, - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/AcceleratorMoveAccountRole/Resource`, - reason: 'CDK generated role', - }, - ], - }); - - this.logger.info(`Load Config Table`); - const loadAcceleratorConfigTable = new LoadAcceleratorConfigTable(this, 'LoadAcceleratorConfigTable', { - acceleratorConfigTable: configTable, - configRepositoryName: props.configRepositoryName, - managementAccountEmail: props.accountsConfig.getManagementAccount().email, - auditAccountEmail: props.accountsConfig.getAuditAccount().email, - logArchiveAccountEmail: props.accountsConfig.getLogArchiveAccount().email, - configS3Bucket: organizationsConfigAsset.s3BucketName, - organizationsConfigS3Key: organizationsConfigAsset.s3ObjectKey, - accountConfigS3Key: accountConfigAsset.s3ObjectKey, - replacementsConfigS3Key: replacementsConfigAsset?.s3ObjectKey, - commitId, - partition: props.partition, - region: cdk.Stack.of(this).region, - managementAccountId: props.accountsConfig.getManagementAccountId(), - stackName: cdk.Stack.of(this).stackName, - kmsKey: cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - enableSingleAccountMode: props.enableSingleAccountMode, - isOrgsEnabled: props.organizationConfig.enable, - }); - - this.logger.info(`Call create ou construct`); - const createOrganizationalUnits = new OrganizationalUnits(this, 'CreateOrganizationalUnits', { - acceleratorConfigTable: configTable, - commitId, - controlTowerEnabled: props.globalConfig.controlTower.enable, - organizationsEnabled: props.organizationConfig.enable, - kmsKey: cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - - createOrganizationalUnits.node.addDependency(loadAcceleratorConfigTable); - - // Invite Accounts to Organization (GovCloud) - this.logger.info(`Invite Accounts To OU`); - const inviteAccountsToOu = new Account(this, 'InviteAccountsToOu', { - acceleratorConfigTable: configTable, - commitId, - assumeRoleName: props.globalConfig.managementAccountAccessRole, - kmsKey: cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - }); - inviteAccountsToOu.node.addDependency(createOrganizationalUnits); - - // Move accounts to OU based on config - this.logger.info(`Move Accounts To OU`); - const moveAccounts = new MoveAccounts(this, 'MoveAccounts', { - globalRegion: props.globalRegion, - configTable: configTable, - commitId, - managementAccountId: props.accountsConfig.getManagementAccountId(), - lambdaKmsKey: lambdaKey, - cloudWatchLogsKmsKey: cloudwatchKey, - cloudWatchLogRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - controlTower: props.globalConfig.controlTower.enable, - }); - moveAccounts.node.addDependency(inviteAccountsToOu); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/MoveAccounts/MoveAccountsFunction/ServiceRole/Resource`, - reason: 'Custom resource lambda require access to other services', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/MoveAccounts/MoveAccountsProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Custom resource lambda require access to other services', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/MoveAccounts/MoveAccountsFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda require access to other services', - }, - ], - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/MoveAccounts/MoveAccountsProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Custom resource lambda require access to other services', - }, - ], - }); - - // - // Create Account Management Configuration DynamoDB tables - this.createConfigurationTables({ - props, - configTable, - loadAcceleratorConfigTable, - organizationAccounts, - controlTowerAccounts, - driftDetectedParameter, - driftMessageParameter, - moveAccounts, - managementAccountKey, - cloudwatchKey, - }); - } - } - // - // Create SSM Parameters - // - this.createSsmParameters(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - private createConfigurationTables(options: { - props: AcceleratorStackProps; - configTable: cdk.aws_dynamodb.Table; - loadAcceleratorConfigTable: LoadAcceleratorConfigTable; - organizationAccounts?: CreateOrganizationAccounts; - controlTowerAccounts?: CreateControlTowerAccounts; - driftDetectedParameter: cdk.aws_ssm.StringParameter; - driftMessageParameter: cdk.aws_ssm.StringParameter; - moveAccounts: MoveAccounts; - managementAccountKey: cdk.aws_kms.IKey; - cloudwatchKey?: cdk.aws_kms.IKey; - }) { - this.logger.info(`Tables`); - if ( - options.props.partition === 'aws' || - options.props.partition === 'aws-us-gov' || - options.props.partition === 'aws-cn' - ) { - this.logger.info(`Create mapping table`); - let govCloudAccountMappingTable: cdk.aws_dynamodb.ITable | undefined; - this.logger.info(`newOrgAccountsTable`); - const newOrgAccountsTable = new cdk.aws_dynamodb.Table(this, 'NewOrgAccounts', { - partitionKey: { name: 'accountEmail', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: options.managementAccountKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - pointInTimeRecovery: true, - }); - - // AwsSolutions-DDB3: The DynamoDB table does not have Point-in-time Recovery enabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.DDB3, - details: [ - { - path: `${this.stackName}/NewOrgAccounts/Resource`, - reason: 'NewOrgAccounts DynamoDB table do not need point in time recovery, data can be re-created', - }, - ], - }); - - this.logger.info(`newControlTowerAccountsTable`); - const newCTAccountsTable = new cdk.aws_dynamodb.Table(this, 'NewCTAccounts', { - partitionKey: { name: 'accountEmail', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: options.managementAccountKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - pointInTimeRecovery: true, - }); - - // AwsSolutions-DDB3: The DynamoDB table does not have Point-in-time Recovery enabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.DDB3, - details: [ - { - path: `${this.stackName}/NewCTAccounts/Resource`, - reason: 'NewCTAccounts DynamoDB table do not need point in time recovery, data can be re-created', - }, - ], - }); - - this.logger.info(`Table Parameter`); - this.ssmParameters.push({ - logicalId: 'NewCTAccountsTableNameParameter', - parameterName: `${options.props.prefixes.ssmParamName}/prepare-stack/NewCTAccountsTableName`, - stringValue: newCTAccountsTable.tableName, - }); - - if (options.props.partition === 'aws' && options.props.accountsConfig.anyGovCloudAccounts()) { - this.logger.info(`Create GovCloudAccountsMappingTable`); - govCloudAccountMappingTable = new cdk.aws_dynamodb.Table(this, 'govCloudAccountMapping', { - partitionKey: { name: 'commercialAccountId', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: options.managementAccountKey, - pointInTimeRecovery: true, - }); - - this.ssmParameters.push({ - logicalId: 'GovCloudAccountMappingTableNameParameter', - parameterName: `${options.props.prefixes.ssmParamName}/prepare-stack/govCloudAccountMappingTableName`, - stringValue: govCloudAccountMappingTable.tableName, - }); - } - - this.ssmParameters.push({ - logicalId: 'NewOrgAccountsTableNameParameter', - parameterName: `${options.props.prefixes.ssmParamName}/prepare-stack/NewOrgAccountsTableName`, - stringValue: newOrgAccountsTable.tableName, - }); - - this.logger.info(`Validate Environment`); - const validation = new ValidateEnvironmentConfig(this, 'ValidateEnvironmentConfig', { - acceleratorConfigTable: options.configTable, - newOrgAccountsTable: newOrgAccountsTable, - newCTAccountsTable: newCTAccountsTable, - controlTowerEnabled: options.props.globalConfig.controlTower.enable, - organizationsEnabled: options.props.organizationConfig.enable, - commitId: options.loadAcceleratorConfigTable.id, - stackName: cdk.Stack.of(this).stackName, - region: cdk.Stack.of(this).region, - managementAccountId: options.props.accountsConfig.getManagementAccountId(), - partition: options.props.partition, - kmsKey: options.cloudwatchKey, - serviceControlPolicies: this.createScpListsForValidation(), - policyTagKey: `${options.props.prefixes.accelerator}Managed`, - logRetentionInDays: options.props.globalConfig.cloudwatchLogRetentionInDays, - driftDetectionParameter: options.driftDetectedParameter, - driftDetectionMessageParameter: options.driftMessageParameter, - }); - - validation.node.addDependency(options.moveAccounts); - - this.logger.info(`Create new organization accounts`); - options.organizationAccounts = new CreateOrganizationAccounts(this, 'CreateOrganizationAccounts', { - newOrgAccountsTable: newOrgAccountsTable, - govCloudAccountMappingTable: govCloudAccountMappingTable, - accountRoleName: options.props.globalConfig.managementAccountAccessRole, - kmsKey: options.cloudwatchKey, - logRetentionInDays: options.props.globalConfig.cloudwatchLogRetentionInDays, - }); - options.organizationAccounts.node.addDependency(validation); - - // cdk-nag suppressions - const orgAccountsIam4SuppressionPaths = [ - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/framework-onEvent/ServiceRole/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/framework-onTimeout/ServiceRole/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/framework-isComplete/ServiceRole/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccounts/ServiceRole/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountStatus/ServiceRole/Resource', - ]; - - const orgAccountsIam5SuppressionPaths = [ - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/framework-isComplete/ServiceRole/DefaultPolicy/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/framework-onTimeout/ServiceRole/DefaultPolicy/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountStatus/ServiceRole/DefaultPolicy/Resource', - 'CreateOrganizationAccounts/CreateOrganizationAccountsProvider/waiter-state-machine/Role/DefaultPolicy/Resource', - ]; - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.createNagSuppressionsInputs(NagSuppressionRuleIds.IAM4, orgAccountsIam4SuppressionPaths); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - this.createNagSuppressionsInputs(NagSuppressionRuleIds.IAM5, orgAccountsIam5SuppressionPaths); - - if (options.props.globalConfig.controlTower.enable) { - // Allow security/audit account access - options.managementAccountKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'auditAccount', - principals: [new cdk.aws_iam.AccountPrincipal(options.props.accountsConfig.getAuditAccountId())], - actions: ['kms:GenerateDataKey', 'kms:Encrypt', 'kms:Decrypt', 'kms:DescribeKey'], - resources: ['*'], - }), - ); - - this.logger.info(`Get Portfolio Id`); - const portfolioResults = new GetPortfolioId(this, 'GetPortFolioId', { - displayName: 'AWS Control Tower Account Factory Portfolio', - providerName: 'AWS Control Tower', - kmsKey: options.cloudwatchKey, - logRetentionInDays: options.props.globalConfig.cloudwatchLogRetentionInDays, - }); - this.logger.info(`Create new control tower accounts`); - options.controlTowerAccounts = new CreateControlTowerAccounts(this, 'CreateCTAccounts', { - table: newCTAccountsTable, - portfolioId: portfolioResults.portfolioId, - kmsKey: options.cloudwatchKey, - logRetentionInDays: options.props.globalConfig.cloudwatchLogRetentionInDays, - }); - options.controlTowerAccounts.node.addDependency(validation); - options.controlTowerAccounts.node.addDependency(options.organizationAccounts); - - // cdk-nag suppressions - const ctAccountsIam4SuppressionPaths = [ - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/framework-onTimeout/ServiceRole/Resource', - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/framework-isComplete/ServiceRole/Resource', - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/framework-onEvent/ServiceRole/Resource', - 'CreateCTAccounts/CreateControlTowerAccountStatus/ServiceRole/Resource', - 'CreateCTAccounts/CreateControlTowerAccount/ServiceRole/Resource', - ]; - - const ctAccountsIam5SuppressionPaths = [ - 'CreateCTAccounts/CreateControlTowerAccountStatus/ServiceRole/DefaultPolicy/Resource', - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource', - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/framework-isComplete/ServiceRole/DefaultPolicy/Resource', - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/framework-onTimeout/ServiceRole/DefaultPolicy/Resource', - 'CreateCTAccounts/CreateControlTowerAcccountsProvider/waiter-state-machine/Role/DefaultPolicy/Resource', - ]; - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.createNagSuppressionsInputs(NagSuppressionRuleIds.IAM4, ctAccountsIam4SuppressionPaths); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - this.createNagSuppressionsInputs(NagSuppressionRuleIds.IAM5, ctAccountsIam5SuppressionPaths); - - // resources for control tower lifecycle events - const controlTowerOuEventsFunction = new cdk.aws_lambda.Function(this, 'ControlTowerOuEventsFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '../lambdas/control-tower-ou-events/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to process ControlTower OU events from CloudTrail', - timeout: cdk.Duration.minutes(5), - environment: { - CONFIG_TABLE_NAME: options.configTable.tableName, - }, - }); - - controlTowerOuEventsFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'dynamodb', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:UpdateItem', 'dynamodb:PutItem'], - resources: [options.configTable.tableArn], - }), - ); - - controlTowerOuEventsFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'organizations', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['organizations:DescribeOrganizationalUnit', 'organizations:ListParents'], - resources: [ - `arn:${ - cdk.Stack.of(this).partition - }:organizations::${options.props.accountsConfig.getManagementAccountId()}:account/o-*/*`, - ], - }), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ControlTowerOuEventsFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Requires access to all org units.', - }, - ], - }); - - new cdk.aws_logs.LogGroup(this, `${controlTowerOuEventsFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${controlTowerOuEventsFunction.functionName}`, - retention: options.props.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: options.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/ControlTowerOuEventsFunction/ServiceRole/Resource`, - reason: 'AWS Basic Lambda execution permissions.', - }, - ], - }); - - const controlTowerOuEventsRule = new cdk.aws_events.Rule(this, 'ControlTowerOuEventsRule', { - description: 'Rule to monitor for Control Tower OU registration and de-registration events', - eventPattern: { - source: ['aws.controltower'], - detailType: ['AWS Service Event via CloudTrail'], - detail: { - eventName: ['RegisterOrganizationalUnit', 'DeregisterOrganizationalUnit'], - }, - }, - }); - - controlTowerOuEventsRule.addTarget( - new cdk.aws_events_targets.LambdaFunction(controlTowerOuEventsFunction, { retryAttempts: 3 }), - ); - - const controlTowerNotificationTopic = new cdk.aws_sns.Topic(this, 'ControlTowerNotification', { - //Check this if it causes any issue changing topic name - topicName: `${options.props.prefixes.accelerator}-ControlTowerNotification`, - displayName: 'ForwardedControlTowerNotifications', - masterKey: options.managementAccountKey, - }); - - controlTowerNotificationTopic.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'auditAccount', - principals: [new cdk.aws_iam.AccountPrincipal(options.props.accountsConfig.getAuditAccountId())], - actions: ['sns:Publish'], - resources: [controlTowerNotificationTopic.topicArn], - }), - ); - - // function to process control tower notifications - const controlTowerNotificationsFunction = new cdk.aws_lambda.Function( - this, - 'ControlTowerNotificationsFunction', - { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '../lambdas/control-tower-notifications/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to process ControlTower notifications from audit account', - timeout: cdk.Duration.minutes(5), - environment: { - DRIFT_PARAMETER_NAME: options.driftDetectedParameter.parameterName, - DRIFT_MESSAGE_PARAMETER_NAME: options.driftMessageParameter.parameterName, - }, - }, - ); - - new cdk.aws_logs.LogGroup(this, `${controlTowerNotificationsFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${controlTowerNotificationsFunction.functionName}`, - retention: options.props.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: options.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - controlTowerNotificationsFunction.addEventSource(new SnsEventSource(controlTowerNotificationTopic)); - controlTowerNotificationsFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'ssm', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:PutParameter'], - resources: [options.driftDetectedParameter.parameterArn, options.driftMessageParameter.parameterArn], - }), - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/ControlTowerNotificationsFunction/ServiceRole/Resource`, - reason: 'AWS Basic Lambda execution permissions.', - }, - ], - }); - } - } - } - - /** - * Create Management Key - * @param props - * @returns key {@link cdk.aws_kms.IKey} - */ - private createManagementKey(props: AcceleratorStackProps): cdk.aws_kms.IKey { - this.logger.info(`Creating Management Encryption Key`); - const key = new cdk.aws_kms.Key(this, 'ManagementKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.managementKey.alias, - description: this.acceleratorResourceNames.customerManagedKeys.managementKey.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // Allow Accelerator Role to use the encryption key - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Accelerator Role in this account to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - props.prefixes.accelerator - }-*`, - ], - }, - }, - }), - ); - - // Allow Cloudwatch logs to use the encryption key - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [ - new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - ], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this).partition}:logs:${ - cdk.Stack.of(this).region - }:${cdk.Stack.of(this).account}:log-group:*`, - }, - }, - }), - ); - - // Allow sns to use the encryption key - key.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'sns', - principals: [new cdk.aws_iam.ServicePrincipal('sns.amazonaws.com')], - actions: ['kms:GenerateDataKey', 'kms:Encrypt'], - resources: ['*'], - }), - ); - - this.ssmParameters.push({ - logicalId: 'AcceleratorManagementKmsArnParameter', - parameterName: `${props.prefixes.ssmParamName}/management/kms/key-arn`, - stringValue: key.keyArn, - }); - - return key; - } - - /** - * Create Management account CloudWatch key - * @returns cloudwatchKey {@link cdk.aws_kms.Key} - */ - private createManagementAccountCloudWatchKey(): cdk.aws_kms.IKey | undefined { - if (!this.isCloudWatchLogsGroupCMKEnabled) { - this.logger.info(`CloudWatch Encryption CMK disable for Management account home region, CMK creation excluded`); - return undefined; - } - this.logger.info(`CloudWatch Encryption Key`); - const cloudwatchKey = new cdk.aws_kms.Key(this, 'AcceleratorManagementCloudWatchKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.cloudWatchLog.alias, - description: this.acceleratorResourceNames.customerManagedKeys.cloudWatchLog.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // Allow Cloudwatch logs to use the encryption key - cloudwatchKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [ - new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - ], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this).partition}:logs:${ - cdk.Stack.of(this).region - }:${cdk.Stack.of(this).account}:log-group:*`, - }, - }, - }), - ); - - this.ssmParameters.push({ - logicalId: 'AcceleratorCloudWatchKmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.cloudWatchLogCmkArn, - stringValue: cloudwatchKey.keyArn, - }); - - return cloudwatchKey; - } - - /** - * Create Management Account Lambda key - * @returns lambdaKey {@link cdk.aws_kms.Key} - */ - private createManagementAccountLambdaKey(): cdk.aws_kms.IKey | undefined { - if (!this.isLambdaCMKEnabled) { - this.logger.info(`Lambda Encryption CMK disable for Management account home region, CMK creation excluded`); - return undefined; - } - this.logger.info(`Lambda Encryption Key`); - const lambdaKey = new cdk.aws_kms.Key(this, 'AcceleratorManagementLambdaKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.lambda.alias, - description: this.acceleratorResourceNames.customerManagedKeys.lambda.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - this.ssmParameters.push({ - logicalId: 'AcceleratorLambdaKmsArnParameter', - parameterName: this.acceleratorResourceNames.parameters.lambdaCmkArn, - stringValue: lambdaKey.keyArn, - }); - - return lambdaKey; - } - - /** - * Create NagSuppressions inputs - * @param inputs - */ - private createNagSuppressionsInputs(type: NagSuppressionRuleIds, inputs: string[]) { - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - for (const input of inputs) { - this.nagSuppressionInputs.push({ - id: type, - details: [ - { - path: `${this.stackName}/${input}`, - reason: 'AWS Custom resource provider role created by cdk.', - }, - ], - }); - } - } - - /** - * Function to create Service Control Policy list SCP with target organizationalUnits - * @param scpItem {@link ServiceControlPolicyConfig} - * @param serviceControlPolicies {@link serviceControlPolicyType}[] - */ - private createOrganizationalUnitsScpLists( - scpItem: ServiceControlPolicyConfig, - serviceControlPolicies: serviceControlPolicyType[], - ) { - if (scpItem.deploymentTargets.organizationalUnits && scpItem.deploymentTargets.organizationalUnits.length > 0) { - const targets: { name: string; id: string }[] = []; - scpItem.deploymentTargets.organizationalUnits.forEach(item => - targets.push({ name: item, id: this.props.organizationConfig.getOrganizationalUnitId(item) }), - ); - - if (targets.length > 0) { - const strategy: string = scpItem.strategy ? scpItem.strategy : 'deny-list'; - serviceControlPolicies.push({ - name: scpItem.name, - targetType: 'ou', - strategy, - targets, - }); - } - } - } - - /** - * Function to create Service Control Policy list SCP with target accounts - * @param scpItem {@link ServiceControlPolicyConfig} - * @param serviceControlPolicies {@link serviceControlPolicyType}[] - */ - private createAccountsScpLists( - scpItem: ServiceControlPolicyConfig, - serviceControlPolicies: serviceControlPolicyType[], - ) { - if (scpItem.deploymentTargets.accounts && scpItem.deploymentTargets.accounts.length > 0) { - const targets: { name: string; id: string }[] = []; - - scpItem.deploymentTargets.accounts.forEach(item => { - try { - targets.push({ name: item, id: this.props.accountsConfig.getAccountId(item) }); - } catch { - this.logger.warn(`Account ID not found for ${item}. Scp count validation skipped for the account ${item}.`); - } - }); - - if (targets.length > 0) { - const strategy: string = scpItem.strategy ? scpItem.strategy : 'deny-list'; - serviceControlPolicies.push({ - name: scpItem.name, - targetType: 'account', - strategy, - targets, - }); - } - } - } - - private createScpListsForValidation(): { - name: string; - targetType: scpTargetType; - targets: { name: string; id: string }[]; - }[] { - const serviceControlPolicies: serviceControlPolicyType[] = []; - - for (const scpItem of this.props.organizationConfig.serviceControlPolicies) { - // Create SCP list with target as OrganizationUnits - this.createOrganizationalUnitsScpLists(scpItem, serviceControlPolicies); - - // Create SCP list with target as accounts - this.createAccountsScpLists(scpItem, serviceControlPolicies); - } - return serviceControlPolicies; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/resource-policy-enforcement-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/resource-policy-enforcement-stack.ts deleted file mode 100644 index d8cb1d9..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/resource-policy-enforcement-stack.ts +++ /dev/null @@ -1,396 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import * as path from 'path'; - -import { Construct } from 'constructs'; -import { DetectResourcePolicy, RemediateResourcePolicy } from '@aws-accelerator/constructs'; - -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionRuleIds, -} from './accelerator-stack'; -import { - ResourcePolicyEnforcementConfig, - ResourcePolicyConfig, - ResourcePolicySetConfig, - ResourceTypeEnum, -} from '@aws-accelerator/config'; -import { RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY, ResourceType } from '@aws-accelerator/utils/lib/common-resources'; - -/** - * Resource-based policy generated file path type - */ -type rbpGeneratedFilePath = { - /** - * Name of the rbp - */ - name: string; - /** - * The relative path to the file containing the policy document in the config repo - */ - path: string; - /** - * The path to the temp file returned by generatePolicyReplacements() - */ - tempPath: string; -}; - -const SUPPORTED_RESOURCE_TYPE = [ - cdk.aws_config.ResourceType.S3_BUCKET, - cdk.aws_config.ResourceType.KMS_KEY, - cdk.aws_config.ResourceType.IAM_ROLE, - cdk.aws_config.ResourceType.SECRETS_MANAGER_SECRET, - cdk.aws_config.ResourceType.ECR_REPOSITORY, - cdk.aws_config.ResourceType.OPENSEARCH_DOMAIN, - cdk.aws_config.ResourceType.SNS_TOPIC, - cdk.aws_config.ResourceType.SQS_QUEUE, - cdk.aws_config.ResourceType.APIGATEWAY_REST_API, - cdk.aws_config.ResourceType.of('AWS::Lex::Bot'), - cdk.aws_config.ResourceType.EFS_FILE_SYSTEM, - cdk.aws_config.ResourceType.EVENTBRIDGE_EVENTBUS, - cdk.aws_config.ResourceType.BACKUP_BACKUP_VAULT, - cdk.aws_config.ResourceType.of('AWS::CodeArtifact::Repository'), - cdk.aws_config.ResourceType.of('AWS::ACMPCA::CertificateAuthority'), - cdk.aws_config.ResourceType.LAMBDA_FUNCTION, -]; - -/** - * Security Perimeter Stack, configures resources to enforce resource policy (Config Rule and SSM) - */ -export class ResourcePolicyEnforcementStack extends AcceleratorStack { - readonly cloudwatchKey: cdk.aws_kms.IKey | undefined; - readonly lambdaKey: cdk.aws_kms.IKey | undefined; - rbpGeneratedFilePathList: rbpGeneratedFilePath[] = []; - - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - this.logger.info('Begin stack synthesis'); - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - - if (!props.securityConfig.resourcePolicyEnforcement || !props.securityConfig.resourcePolicyEnforcement.enable) - return; - - // - // Config Rules - // - this.setupConfigRule(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('End stack synthesis'); - } - - /** - * Function to setup AWS Config rules - */ - private setupConfigRule() { - const applicablePolicySets = []; - for (const policySet of this.props.securityConfig.resourcePolicyEnforcement?.policySets || []) { - if (this.isIncluded(policySet.deploymentTargets)) { - applicablePolicySets.push(policySet); - } - } - - // Find the Root policy set and the policy set with the nearest deployment target to the current account - const rootPolicySet = this.getRootPolicySet(applicablePolicySets); - const policySet = this.getNearestPolicySet(applicablePolicySets, rootPolicySet); - if (!policySet) return; - - // - // Load policy replacement for resource based policy - // - this.loadPolicyReplacements(this.props, policySet, rootPolicySet); - - const acceleratorPrefix = this.props.prefixes.accelerator; - const detectResourcePolicy = new DetectResourcePolicy(this, 'DetectResourcePolicy', { - acceleratorPrefix, - configDirPath: this.props.configDirPath, - homeRegion: this.props.globalConfig.homeRegion, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - rbpFilePaths: this.rbpGeneratedFilePathList, - kmsKeyCloudWatch: this.cloudwatchKey, - kmsKeyLambda: this.lambdaKey, - inputParameters: { - ...policySet.inputParameters, - ORG_ID: this.organizationId!, - ACCOUNT_ID: this.account, - }, - }); - - const ruleName = `${acceleratorPrefix}-${ResourcePolicyEnforcementConfig.DEFAULT_RULE_NAME}`; - const configRule = new cdk.aws_config.CustomRule(this, pascalCase(ruleName), { - configRuleName: ruleName, - lambdaFunction: detectResourcePolicy.lambdaFunction, - periodic: true, - description: 'Config rule to detect non-compliant resource based policy', - ruleScope: { - resourceTypes: SUPPORTED_RESOURCE_TYPE, - }, - configurationChanges: true, - }); - configRule.node.addDependency(detectResourcePolicy.lambdaFunction); - - const documentName = `${this.props.prefixes.accelerator}-${ResourcePolicyEnforcementConfig.DEFAULT_SSM_DOCUMENT_NAME}`; - const remediationConfig = this.props.securityConfig.resourcePolicyEnforcement!.remediation; - - const remediationRole = this.createRemediationRole( - ruleName, - `arn:${cdk.Stack.of(this).partition}:ssm:${ - cdk.Stack.of(this).region - }:${this.props.accountsConfig.getAuditAccountId()}:document/${documentName}`, - true, - ); - - const remediateResourcePolicy = new RemediateResourcePolicy(this, 'RemediateResourcePolicy', { - acceleratorPrefix: this.props.prefixes.accelerator, - configDirPath: this.props.configDirPath, - homeRegion: this.props.globalConfig.homeRegion, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - rbpFilePaths: this.rbpGeneratedFilePathList, - kmsKeyCloudWatch: this.cloudwatchKey, - kmsKeyLambda: this.lambdaKey, - inputParameters: { - ...policySet.inputParameters, - ORG_ID: this.organizationId!, - ACCOUNT_ID: this.account, - }, - }); - - remediationRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['lambda:InvokeFunction'], - resources: [remediateResourcePolicy.lambdaFunction.functionArn], - }), - ); - - new cdk.aws_config.CfnRemediationConfiguration(this, pascalCase('Detect-Res-Remediation'), { - configRuleName: ruleName, - targetId: `arn:${cdk.Stack.of(this).partition}:ssm:${ - cdk.Stack.of(this).region - }:${this.props.accountsConfig.getAuditAccountId()}:document/${documentName}`, - targetType: 'SSM_DOCUMENT', - - automatic: remediationConfig.automatic, - maximumAutomaticAttempts: remediationConfig.maximumAutomaticAttempts, - retryAttemptSeconds: remediationConfig.retryAttemptSeconds, - parameters: { - ResourceId: { - ResourceValue: { - Value: 'RESOURCE_ID', - }, - }, - AutomationAssumeRole: { - StaticValue: { - Values: [remediationRole.roleArn], - }, - }, - FunctionName: { - StaticValue: { - Values: [remediateResourcePolicy.lambdaFunction.functionArn], - }, - }, - ConfigRuleName: { - StaticValue: { - Values: [ruleName], - }, - }, - }, - }).node.addDependency(configRule); - } - - /** - * Get the root policy set from applicable policy sets. The policy set will be used as default policy set if there is - * no policy set with higher priority - * - * @param applicablePolicySets - * @returns - */ - private getRootPolicySet(applicablePolicySets: ResourcePolicySetConfig[]): ResourcePolicySetConfig | undefined { - for (const policySet of applicablePolicySets) { - const organizationalUnits = policySet.deploymentTargets.organizationalUnits; - - if (organizationalUnits && organizationalUnits.includes('Root')) { - return policySet; - } - } - - return undefined; - } - - /** - * Get the nearest policy set that can be applied to current account. The priority is ordered by - * 1. Account level matching - * 2. Organizational Unit level matching - * 3. Root - * @param policySets - * @param rootPolicySet - * @returns - */ - private getNearestPolicySet( - policySets: ResourcePolicySetConfig[], - rootPolicySet: ResourcePolicySetConfig | undefined, - ): ResourcePolicySetConfig | undefined { - // Find most specific matching policy set -> account level policy set - const accountLevelPolicySet = policySets.find(policySet => - this.isAccountIncluded(policySet.deploymentTargets.accounts), - ); - if (accountLevelPolicySet) return accountLevelPolicySet; - - // Find 2nd specific matching policy set -> nearest organization unit - for (const policySet of policySets) { - const organizationalUnits = policySet.deploymentTargets.organizationalUnits; - const accounts = [...this.props.accountsConfig.mandatoryAccounts, ...this.props.accountsConfig.workloadAccounts]; - - const account = accounts.find( - item => this.props.accountsConfig.getAccountId(item.name) === cdk.Stack.of(this).account, - ); - - if (account && organizationalUnits.indexOf(account.organizationalUnit) !== -1) { - return policySet; - } - } - - return rootPolicySet; - } - - /** - * Function to create remediation role - * @param ruleName - * @param resources - * @param isLambdaRole - * @private - */ - private createRemediationRole(ruleName: string, resources?: string, isLambdaRole = false): cdk.aws_iam.Role { - const principals: cdk.aws_iam.PrincipalBase[] = [new cdk.aws_iam.ServicePrincipal('ssm.amazonaws.com')]; - if (isLambdaRole) { - principals.push(new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com')); - } - - const role = new cdk.aws_iam.Role(this, pascalCase(ruleName) + '-RemediationRole', { - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - }); - - role.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ssm:GetAutomationExecution', - 'ssm:StartAutomationExecution', - 'ssm:GetParameters', - 'ssm:GetParameter', - 'ssm:PutParameter', - ], - resources: [resources ?? '*'], - }), - ); - role.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], - resources: ['*'], - }), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(ruleName)}-RemediationRole/DefaultPolicy/Resource`, - reason: 'AWS Config rule remediation role, created by the permission provided in config repository', - }, - ], - }); - - return role; - } - - /** - * Function to load replacements within the provided resource based policy documents - * @param props {@link AcceleratorStackProps} - * @returns - */ - private loadPolicyReplacements( - props: AcceleratorStackProps, - nearestPolicySet: ResourcePolicySetConfig, - rootPolicySet: ResourcePolicySetConfig | undefined, - ): void { - this.rbpGeneratedFilePathList = []; - - const resourcePolicies = rootPolicySet - ? this.getResourcePolicies(nearestPolicySet, rootPolicySet.resourcePolicies) - : nearestPolicySet.resourcePolicies; - - this.validateResourcePolicy(resourcePolicies); - for (const policy of resourcePolicies) { - this.logger.info(`Create resource based policy (${policy.resourceType}) from template`); - - this.rbpGeneratedFilePathList.push({ - name: policy.resourceType, - path: policy.document, - tempPath: this.generatePolicyReplacements( - path.join(props.configDirPath, policy.document), - true, - this.organizationId, - ), - }); - } - } - - /** - * Validate if there is template for each resource types for current account - * - * @param resourcePolicies - */ - private validateResourcePolicy(resourcePolicies: ResourcePolicyConfig[]) { - const requiredResourceTypes = Object.values(ResourceTypeEnum).filter( - r => !RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY.includes(ResourceType[r as keyof typeof ResourceType]), - ); - - const currentResourceTypes = new Set(resourcePolicies.map(r => r.resourceType)); - const missingResourceType = requiredResourceTypes.find(r => !currentResourceTypes.has(r)); - if (missingResourceType) { - throw new Error(`Missing resource policy type ${missingResourceType} for account ${cdk.Stack.of(this).account}`); - } - } - - private getResourcePolicies( - policySet: ResourcePolicySetConfig, - defaultPolicies: ResourcePolicyConfig[], - ): ResourcePolicyConfig[] { - const resourcePolicies = defaultPolicies.reduce((acc, policy) => { - acc[policy.resourceType] = { - resourceType: policy.resourceType, - document: policy.document, - }; - return acc; - }, {} as { [key: string]: ResourcePolicyConfig }); - - for (const policy of policySet.resourcePolicies) { - resourcePolicies[policy.resourceType] = policy; - } - return Object.values(resourcePolicies); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/security-audit-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/security-audit-stack.ts deleted file mode 100644 index 1e69671..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/security-audit-stack.ts +++ /dev/null @@ -1,656 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import { pascalCase } from 'pascal-case'; -import * as path from 'path'; - -import { - ResourcePolicyEnforcementConfig, - GuardDutyConfig, - Region, - DeploymentTargets, - SecurityHubConfig, -} from '@aws-accelerator/config'; -import { - Bucket, - BucketEncryptionType, - BucketReplicationProps, - Document, - GuardDutyDetectorConfig, - AuditManagerDefaultReportsDestination, - AuditManagerDefaultReportsDestinationTypes, - GuardDutyMembers, - DetectiveGraphConfig, - DetectiveMembers, - MacieMembers, - SecurityHubMembers, - SecurityHubRegionAggregation, - RemediationSsmDocument, -} from '@aws-accelerator/constructs'; - -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionRuleIds, -} from './accelerator-stack'; -import { SnsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; - -export class SecurityAuditStack extends AcceleratorStack { - private readonly s3Key: cdk.aws_kms.IKey | undefined; - private readonly cloudwatchKey: cdk.aws_kms.IKey | undefined; - private readonly centralLogsBucketKey: cdk.aws_kms.IKey; - private readonly replicationProps: BucketReplicationProps; - private readonly securityHubConfig: SecurityHubConfig; - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - this.s3Key = this.getAcceleratorKey(AcceleratorKeyType.S3_KEY); - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.centralLogsBucketKey = this.getCentralLogsBucketKey(this.cloudwatchKey); - this.securityHubConfig = this.props.securityConfig.centralSecurityServices.securityHub; - this.replicationProps = { - destination: { - bucketName: this.centralLogsBucketName, - accountId: props.accountsConfig.getLogArchiveAccountId(), - keyArn: this.centralLogsBucketKey.keyArn, - }, - kmsKey: this.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - acceleratorPrefix: this.props.prefixes.accelerator, - useExistingRoles: this.props.useExistingRoles ?? false, - }; - - // - // Macie configuration - // - this.configureMacie(); - - // - // GuardDuty configuration - // - this.configureGuardDuty(this.props.securityConfig.centralSecurityServices.guardduty); - - // - // Audit Manager configuration - // - this.configureAuditManager(); - - // Detective configuration - // - this.configureDetective(); - - // - // SecurityHub configuration - // - this.configureSecurityHub(); - - // - // SSM Automation Docs - // - this.configureSsmDocuments(); - - // - // SSM Automation doc for Data Perimeter - // - if (this.props.securityConfig.resourcePolicyEnforcement?.enable) { - this.configureResourcePolicyEnforcementSsmDocument(); - } - - // - // IAM Access Analyzer (Does not have a native service enabler) - // - this.configureIamAnalyzer(); - - // - // SNS Notification Topics and Subscriptions - // - this.configureSnsNotifications(); - - // - // create lambda function to forward - // control tower notifications to the management account - // - this.configureControlTowerNotification(); - - // - // Create SSM Parameters - // - this.createSsmParameters(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - private configureResourcePolicyEnforcementSsmDocument() { - const documentName = `${this.props.prefixes.accelerator}-${ResourcePolicyEnforcementConfig.DEFAULT_SSM_DOCUMENT_NAME}`; - new RemediationSsmDocument(this, 'ResourcePolicyEnforcementRemediationDocument', { - documentName, - sharedAccountIds: this.props.accountsConfig.getAccountIds(), - globalConfig: this.props.globalConfig, - cloudwatchKey: this.cloudwatchKey, - }); - } - - /** - * Function to configure Macie - */ - private configureMacie() { - this.logger.debug( - `centralSecurityServices.macie.enable: ${this.props.securityConfig.centralSecurityServices.macie.enable}`, - ); - - if (this.props.securityConfig.centralSecurityServices.macie.enable) { - this.logger.info(`Configuring Macie`); - - if ( - this.props.securityConfig.centralSecurityServices.macie.excludeRegions.indexOf( - cdk.Stack.of(this).region as Region, - ) === -1 - ) { - this.logger.info('Adding Macie'); - - new MacieMembers(this, 'MacieMembers', { - adminAccountId: cdk.Stack.of(this).account, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - } - - /** - * Function to configure GuardDuty - * @param guardDutyConfig GuardDutyConfig - */ - private configureGuardDuty(guardDutyConfig: GuardDutyConfig) { - this.logger.debug(`centralSecurityServices.guardduty.enable: ${guardDutyConfig.enable}`); - - if (this.validateExcludeRegionsAndDeploymentTargets(guardDutyConfig)) { - const guardDutyMemberAccountIds: string[] = []; - if (guardDutyConfig.deploymentTargets) { - guardDutyMemberAccountIds.push( - ...this.getAccountIdsFromDeploymentTargets(guardDutyConfig.deploymentTargets as DeploymentTargets), - ); - } - - this.logger.info( - guardDutyMemberAccountIds.length > 0 - ? `Enabling GuardDuty for accounts defined in GuardDuty deploymentTargets` - : 'Enabling GuardDuty for all existing accounts', - ); - - // Determine S3 and EKS protection - const [s3Protection, eksProtection] = this.processGuardDutyProtectionConfig(guardDutyConfig); - // Determine whether to update export frequency - const updateExportFrequency = - guardDutyConfig.exportConfiguration.enable && guardDutyConfig.exportConfiguration.overrideExisting; - - const guardDutyMembers = new GuardDutyMembers(this, 'GuardDutyMembers', { - enableS3Protection: s3Protection, - enableEksProtection: eksProtection, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - guardDutyMemberAccountIds, - autoEnableOrgMembers: guardDutyConfig.autoEnableOrgMembers ?? true, - }); - - if (s3Protection || eksProtection || updateExportFrequency) { - new GuardDutyDetectorConfig(this, 'GuardDutyDetectorConfig', { - exportFrequency: updateExportFrequency ? guardDutyConfig.exportConfiguration.exportFrequency : undefined, - enableS3Protection: s3Protection, - enableEksProtection: eksProtection, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }).node.addDependency(guardDutyMembers); - } - } - } - - /** - * Determine the GuardDuty protection settings based on configuration - * @param guardDutyConfig GuardDutyConfig - * @returns boolean[] - */ - private processGuardDutyProtectionConfig(guardDutyConfig: GuardDutyConfig): boolean[] { - // Set default values if excludeRegions is not configured - let s3Protection = guardDutyConfig.s3Protection.enable; - let eksProtection = guardDutyConfig.eksProtection?.enable ?? false; - // Determine S3 protection exclusion for this region - if (guardDutyConfig.s3Protection?.excludeRegions) { - s3Protection = - guardDutyConfig.s3Protection.excludeRegions.indexOf(cdk.Stack.of(this).region as Region) === -1 - ? guardDutyConfig.s3Protection.enable - : false; - } - // Determine EKS protection exclusion for this region - if (guardDutyConfig.eksProtection?.excludeRegions) { - eksProtection = - guardDutyConfig.eksProtection.excludeRegions.indexOf(cdk.Stack.of(this).region as Region) === -1 - ? guardDutyConfig.eksProtection.enable - : false; - } - - return [s3Protection, eksProtection]; - } - - /** - * Function to configure Audit manager - */ - private configureAuditManager() { - this.logger.debug( - `centralSecurityServices.auditManager?.enable: ${this.props.securityConfig.centralSecurityServices.auditManager?.enable}`, - ); - - if (this.props.securityConfig.centralSecurityServices.auditManager?.enable) { - if ( - this.props.securityConfig.centralSecurityServices.auditManager.excludeRegions.includes( - cdk.Stack.of(this).region as Region, - ) - ) { - this.logger.info(`Audit Manager enabled, but excluded in ${cdk.Stack.of(this).region} region.`); - return; - } - - this.logger.info('Adding Audit Manager '); - const serverAccessLogsBucketName = this.getServerAccessLogsBucketName(); - const bucket = new Bucket(this, 'AuditManagerPublishingDestinationBucket', { - encryptionType: this.isS3CMKEnabled ? BucketEncryptionType.SSE_KMS : BucketEncryptionType.SSE_S3, - s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.auditManager}-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - kmsKey: this.s3Key, - serverAccessLogsBucketName, - s3LifeCycleRules: this.getS3LifeCycleRules( - this.props.securityConfig.centralSecurityServices.auditManager?.lifecycleRules, - ), - replicationProps: this.replicationProps, - }); - - cdk.Tags.of(bucket).add(`aws-cdk:auto-audit-manager-access-bucket`, 'true'); - - if (!serverAccessLogsBucketName) { - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.S1, - details: [ - { - path: `${this.stackName}/AuditManagerPublishingDestinationBucket/Resource/Resource`, - reason: 'Due to configuration settings, server access logs have been disabled.', - }, - ], - }); - } - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: - `/${this.stackName}/AuditManagerPublishingDestinationBucket/AuditManagerPublishingDestinationBucketReplication/` + - pascalCase(this.centralLogsBucketName) + - '-ReplicationRole/DefaultPolicy/Resource', - reason: 'Allows only specific policy.', - }, - ], - }); - - this.ssmParameters.push({ - logicalId: 'SsmParamOrganizationAuditManagerPublishingDestinationBucketArn', - parameterName: `${this.props.prefixes.ssmParamName}/organization/security/auditManager/publishing-destination/bucket-arn`, - stringValue: bucket.getS3Bucket().bucketArn, - }); - - // Grant audit manager access to the bucket - bucket.getS3Bucket().grantReadWrite(new cdk.aws_iam.ServicePrincipal('auditmanager.amazonaws.com')); - - // Grant organization principals to use the bucket - bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization principals to use of the bucket', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetBucketLocation', 's3:PutObject'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [bucket.getS3Bucket().bucketArn, `${bucket.getS3Bucket().bucketArn}/*`], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - // We also tag the bucket to record the fact that it has access for guardduty principal. - cdk.Tags.of(bucket).add('aws-cdk:auto-auditManager-access-bucket', 'true'); - - if (this.props.securityConfig.centralSecurityServices.auditManager?.defaultReportsConfiguration.enable) { - new AuditManagerDefaultReportsDestination(this, 'AuditManagerDefaultReportsDestination', { - defaultReportsDestinationType: AuditManagerDefaultReportsDestinationTypes.S3, - bucketKmsKey: this.s3Key, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - bucket: `s3://'${bucket.getS3Bucket().bucketName}/audit-manager/${cdk.Stack.of(this).account}/`, - }); - } - } - } - - /** - * Function to Configure Detective - */ - private configureDetective() { - this.logger.debug( - `centralSecurityServices.detective?.enable: ${this.props.securityConfig.centralSecurityServices.detective?.enable}`, - ); - - if (this.props.securityConfig.centralSecurityServices.detective?.enable) { - if ( - this.props.securityConfig.centralSecurityServices.detective?.excludeRegions.indexOf( - cdk.Stack.of(this).region as Region, - ) === -1 - ) { - this.logger.info('Adding Detective '); - - const detectiveMembers = new DetectiveMembers(this, 'DetectiveMembers', { - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - - new DetectiveGraphConfig(this, 'DetectiveGraphConfig', { - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }).node.addDependency(detectiveMembers); - } - } - } - - /** - * Function to configure SecurityHub - */ - private configureSecurityHub() { - this.logger.debug(`centralSecurityServices.securityHub.enable: ${this.securityHubConfig.enable}`); - if (this.validateExcludeRegionsAndDeploymentTargets(this.securityHubConfig)) { - const securityHubMemberAccountIds: string[] = []; - if (this.securityHubConfig.deploymentTargets) { - securityHubMemberAccountIds.push( - ...this.getAccountIdsFromDeploymentTargets(this.securityHubConfig.deploymentTargets as DeploymentTargets), - ); - } - - this.logger.info( - securityHubMemberAccountIds.length > 0 - ? `Enabling SecurityHub for accounts defined in SecurityHub deploymentTargets` - : 'Enabling SecurityHub for all existing accounts', - ); - - this.logger.info('Adding SecurityHub '); - - new SecurityHubMembers(this, 'SecurityHubMembers', { - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - securityHubMemberAccountIds, - autoEnableOrgMembers: this.securityHubConfig.autoEnableOrgMembers ?? true, - }); - } - - this.logger.debug( - `centralSecurityServices.securityHub.regionAggregation: ${this.securityHubConfig.regionAggregation}`, - ); - if ( - this.securityHubConfig.enable && - this.securityHubConfig.regionAggregation && - this.props.globalConfig.homeRegion == cdk.Stack.of(this).region - ) { - this.logger.info('Enabling region aggregation for SecurityHub in the Home Region'); - - new SecurityHubRegionAggregation(this, 'SecurityHubRegionAggregation', { - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - - /** - * Function to configure SSM documents - */ - private configureSsmDocuments() { - this.logger.info(`Adding SSM Automation Docs`); - if ( - this.props.securityConfig.centralSecurityServices.ssmAutomation.excludeRegions === undefined || - this.props.securityConfig.centralSecurityServices.ssmAutomation.excludeRegions.indexOf( - cdk.Stack.of(this).region as Region, - ) === -1 - ) { - // - for (const documentSetItem of this.props.securityConfig.centralSecurityServices.ssmAutomation.documentSets ?? - []) { - // Create list of accounts to share with - const accountIds: string[] = this.getAccountIdsFromShareTarget(documentSetItem.shareTargets); - - for (const documentItem of documentSetItem.documents ?? []) { - this.logger.info(`Adding ${documentItem.name}`); - - // Read in the document which should be properly formatted - const buffer = fs.readFileSync(path.join(this.props.configDirPath, documentItem.template), 'utf8'); - - let content; - if (documentItem.template.endsWith('.json')) { - content = JSON.parse(buffer); - } else { - content = yaml.load(buffer); - } - - // Create the document - new Document(this, pascalCase(documentItem.name), { - name: documentItem.name, - content, - documentType: 'Automation', - sharedWithAccountIds: accountIds, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - targetType: documentItem.targetType, - }); - } - } - } - } - - /** - * Function to configure IAM Analyzer - */ - private configureIamAnalyzer() { - this.logger.debug(`accessAnalyzer.enable: ${this.props.securityConfig.accessAnalyzer.enable}`); - if ( - this.props.securityConfig.accessAnalyzer.enable && - this.props.globalConfig.homeRegion === cdk.Stack.of(this).region - ) { - this.logger.info('Adding IAM Access Analyzer '); - new cdk.aws_accessanalyzer.CfnAnalyzer(this, 'AccessAnalyzer', { - type: 'ORGANIZATION', - }); - } - } - - /** - * Function to configure SNS Notifications - */ - private configureSnsNotifications() { - this.logger.info(`Create SNS Topics and Subscriptions`); - - // - // Create KMS Key for SNS topic when there are SNS topics are to be created - let snsKey: cdk.aws_kms.IKey | undefined; - if (this.props.securityConfig.centralSecurityServices.snsSubscriptions ?? [].length > 0) { - snsKey = new cdk.aws_kms.Key(this, 'AcceleratorSnsKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.sns.alias, - description: this.acceleratorResourceNames.customerManagedKeys.sns.description, - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - if (this.props.organizationConfig.enable) { - snsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Accelerator Role to use the encryption key`, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${this.props.prefixes.accelerator}-*`, - ], - }, - }, - }), - ); - } - } - - // Loop through all the subscription entries - for (const snsSubscriptionItem of this.props.securityConfig.centralSecurityServices.snsSubscriptions ?? []) { - this.logger.info(`Create SNS Topic: ${snsSubscriptionItem.level}`); - const topic = new cdk.aws_sns.Topic(this, `${pascalCase(snsSubscriptionItem.level)}SnsTopic`, { - displayName: `AWS Accelerator - ${snsSubscriptionItem.level} Notifications`, - topicName: `${this.props.prefixes.snsTopicName}-${snsSubscriptionItem.level}Notifications`, - masterKey: snsKey, - }); - - // Allowing Publish from CloudWatch Service from any account - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('cloudwatch.amazonaws.com'), - }); - - // Allowing Publish from Lambda Service from any account - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), - }); - - // Allowing Publish from Organization - topic.grantPublish({ - grantPrincipal: this.getOrgPrincipals(this.organizationId), - }); - - topic.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization list topic', - actions: ['sns:ListSubscriptionsByTopic', 'sns:ListTagsForResource', 'sns:GetTopicAttributes'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [topic.topicArn], - conditions: { - StringEquals: { - ...this.getPrincipalOrgIdCondition(this.organizationId), - }, - }, - }), - ); - - this.logger.info(`Create SNS Subscription: ${snsSubscriptionItem.email}`); - topic.addSubscription(new cdk.aws_sns_subscriptions.EmailSubscription(snsSubscriptionItem.email)); - } - } - - /** - * Function to configure CT notification - */ - private configureControlTowerNotification() { - if ( - this.props.globalConfig.controlTower.enable && - cdk.Stack.of(this).region == this.props.globalConfig.homeRegion - ) { - const mgmtAccountSnsTopicArn = `arn:${cdk.Stack.of(this).partition}:sns:${ - cdk.Stack.of(this).region - }:${this.props.accountsConfig.getManagementAccountId()}:${ - this.props.prefixes.snsTopicName - }-ControlTowerNotification`; - const controlTowerNotificationsForwarderFunction = new cdk.aws_lambda.Function( - this, - 'ControlTowerNotificationsForwarderFunction', - { - code: cdk.aws_lambda.Code.fromAsset( - path.join(__dirname, '../lambdas/control-tower-notifications-forwarder/dist'), - ), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to forward ControlTower notifications to management account', - timeout: cdk.Duration.minutes(2), - environment: { - SNS_TOPIC_ARN: mgmtAccountSnsTopicArn, - }, - }, - ); - controlTowerNotificationsForwarderFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'sns', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sns:Publish'], - resources: [mgmtAccountSnsTopicArn], - }), - ); - - controlTowerNotificationsForwarderFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'kms', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:DescribeKey', 'kms:GenerateDataKey', 'kms:Decrypt', 'kms:Encrypt'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:kms:${ - cdk.Stack.of(this).region - }:${this.props.accountsConfig.getManagementAccountId()}:key/*`, - ], - }), - ); - - const existingControlTowerSNSTopic = cdk.aws_sns.Topic.fromTopicArn( - this, - 'ControlTowerSNSTopic', - `arn:${cdk.Stack.of(this).partition}:sns:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:aws-controltower-AggregateSecurityNotifications`, - ); - - controlTowerNotificationsForwarderFunction.addEventSource(new SnsEventSource(existingControlTowerSNSTopic)); - } - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/ControlTowerNotificationsForwarderFunction/ServiceRole/Resource`, - reason: 'AWS Custom resource provider lambda role created by cdk.', - }, - ], - }); - - // AwsSolutions-IAM5: TThe IAM entity contains wildcard permissions - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ControlTowerNotificationsForwarderFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Require access to all keys in management account', - }, - ], - }); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/security-resources-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/security-resources-stack.ts deleted file mode 100644 index c702967..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/security-resources-stack.ts +++ /dev/null @@ -1,1538 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import path from 'path'; -import { Tag as ConfigRuleTag } from '@aws-sdk/client-config-service'; -import { - AccountCloudTrailConfig, - AwsConfigRuleSet, - ConfigRule, - Region, - Tag, - IsPublicSsmDoc, - AseaResourceType, -} from '@aws-accelerator/config'; - -import { - ConfigServiceRecorder, - CloudWatchLogGroups, - ConfigServiceTags, - SsmSessionManagerSettings, - SecurityHubEventsLog, -} from '@aws-accelerator/constructs'; -import * as cdk_extensions from '@aws-cdk-extensions/cdk-extensions'; - -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionRuleIds, -} from './accelerator-stack'; - -enum ACCEL_LOOKUP_TYPE { - KMS = 'KMS', - Bucket = 'Bucket', - CUSTOMER_MANAGED_POLICY = 'CustomerManagedPolicy', - INSTANCE_PROFILE = 'InstanceProfile', - ORGANIZATION_ID = 'OrgId', - ACCOUNT_ID = 'AccountId', - REMEDIATION_FUNCTION_NAME = 'RemediationFunctionName', -} - -interface RemediationParameters { - [key: string]: { - StaticValue?: { - Values: string[]; - }; - ResourceValue?: { - Value: 'RESOURCE_ID'; - }; - }; -} - -type CustomConfigRuleType = cdk.aws_config.ManagedRule | cdk.aws_config.CustomRule | undefined; - -/** - * Security Stack, configures local account security services - */ -export class SecurityResourcesStack extends AcceleratorStack { - readonly centralLogsBucketKey: cdk.aws_kms.IKey; - readonly cloudwatchKey: cdk.aws_kms.IKey | undefined; - readonly lambdaKey: cdk.aws_kms.IKey | undefined; - readonly auditAccountId: string; - readonly logArchiveAccountId: string; - readonly stackProperties: AcceleratorStackProps; - - private snsKey: cdk.aws_kms.IKey | undefined; - - configRecorder: cdk.aws_config.CfnConfigurationRecorder | undefined; - configServiceUpdater: ConfigServiceRecorder | undefined; - deliveryChannel: cdk.aws_config.CfnDeliveryChannel | undefined; - accountTrailCloudWatchLogGroups: Map; - - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - - this.logger.info('Begin stack synthesis'); - this.accountTrailCloudWatchLogGroups = new Map(); - this.stackProperties = props; - this.auditAccountId = props.accountsConfig.getAuditAccountId(); - this.logArchiveAccountId = props.accountsConfig.getLogArchiveAccountId(); - - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.lambdaKey = this.getAcceleratorKey(AcceleratorKeyType.LAMBDA_KEY); - this.centralLogsBucketKey = this.getCentralLogsBucketKey(this.cloudwatchKey); - - // - // Initialize SNS key - // - this.initializeSnsKey(); - - // AWS Config - Set up recorder and delivery channel, only if Control Tower - // is not being used. Else the Control Tower SCP will block these calls from - // member accounts - // - // If Control Tower is enabled, make sure to set up AWS Config in the - // management account since this is not enabled by default by Control Tower. - // - // An AWS Control Tower preventive guardrail is enforced with AWS - // Organizations using Service Control Policies (SCPs) that disallows - // configuration changes to AWS Config. - // - this.setupConfigRecorderAndDeliveryChannel(); - - // - // Config Rules - // - this.setupAwsConfigRules(); - - // - // Configure Account CloudTrail Logs - // - this.configureAccountCloudTrails(); - - // - // CloudWatch Metrics - // - this.configureCloudWatchMetrics(); - - // - // CloudWatch Alarms - // - this.configureCloudwatchAlarm(); - - // - // CloudWatch Log Groups - // - this.configureCloudwatchLogGroups(); - - // - // SessionManager Configuration - // - this.setupSessionManager(); - - // SecurityHub Log event to CloudWatch - this.securityHubEventForwardToLogs(); - - // - // Create Managed Active Directory secrets - // - this.createManagedActiveDirectorySecrets(); - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('End stack synthesis'); - } - - /** - * Function to initialize SNS key - */ - private initializeSnsKey() { - // if sns topics defined and this is the log archive account or - // sns topics defined and this is a deployment target for sns topics - // get sns key - if ( - (this.props.globalConfig.snsTopics && - cdk.Stack.of(this).account === this.props.accountsConfig.getLogArchiveAccountId()) || - (this.props.globalConfig.snsTopics && this.isIncluded(this.props.globalConfig.snsTopics.deploymentTargets)) - ) { - this.snsKey = cdk.aws_kms.Key.fromKeyArn( - this, - 'AcceleratorGetSnsKey', - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.acceleratorResourceNames.parameters.snsTopicCmkArn, - ), - ); - } - } - - /** - * Function to create Managed active directory secrets for admin user and ad users - */ - private createManagedActiveDirectorySecrets() { - for (const managedActiveDirectory of this.props.iamConfig.managedActiveDirectories ?? []) { - if (this.isManagedByAseaGlobal(AseaResourceType.MANAGED_AD, managedActiveDirectory.name)) { - this.logger.info(`${managedActiveDirectory.name} is managed by ASEA, skipping creation of resources.`); - return; - } - const madAccountId = this.props.accountsConfig.getAccountId(managedActiveDirectory.account); - const madRegion = managedActiveDirectory.region; - - const secretName = `${this.props.prefixes.secretName}/ad-user/${ - managedActiveDirectory.name - }/${this.props.iamConfig.getManageActiveDirectoryAdminSecretName(managedActiveDirectory.name)}`; - const madAdminSecretAccountId = this.props.accountsConfig.getAccountId( - this.props.iamConfig.getManageActiveDirectorySecretAccountName(managedActiveDirectory.name), - ); - const madAdminSecretRegion = this.props.iamConfig.getManageActiveDirectorySecretRegion( - managedActiveDirectory.name, - ); - - if (cdk.Stack.of(this).account == madAdminSecretAccountId && cdk.Stack.of(this).region == madAdminSecretRegion) { - const key = cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(`${managedActiveDirectory.name}AdminSecretKeyLookup`), - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - this.acceleratorResourceNames.parameters.secretsManagerCmkArn, - ), - ); - const adminSecret = new cdk.aws_secretsmanager.Secret( - this, - pascalCase(`${managedActiveDirectory.name}AdminSecret`), - { - generateSecretString: { - passwordLength: 16, - requireEachIncludedType: true, - }, - secretName, - encryptionKey: key, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }, - ); - - // AwsSolutions-SMG4: The secret does not have automatic rotation scheduled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.SMG4, - details: [ - { - path: `${this.stackName}/${pascalCase(managedActiveDirectory.name)}AdminSecret/Resource`, - reason: 'Managed AD secret.', - }, - ], - }); - - new cdk.aws_ssm.StringParameter(this, pascalCase(`${managedActiveDirectory.name}AdminSecretArnParameter`), { - parameterName: `${this.props.prefixes.ssmParamName}/secrets-manager/${managedActiveDirectory.name}/admin-secret/secret-arn`, - stringValue: adminSecret.secretArn, - }); - - let madAccessRoleArn: string; - if (this.props.globalConfig.cdkOptions?.useManagementAccessRole) { - madAccessRoleArn = `arn:${cdk.Stack.of(this).partition}:iam::${madAccountId}:role/${ - this.props.globalConfig.managementAccountAccessRole - }`; - } else if (this.props.globalConfig.cdkOptions?.customDeploymentRole) { - madAccessRoleArn = `arn:${cdk.Stack.of(this).partition}:iam::${madAccountId}:role/${ - this.props.globalConfig.cdkOptions.customDeploymentRole - }`; - } else { - madAccessRoleArn = `arn:${ - cdk.Stack.of(this).partition - }:iam::${madAccountId}:role/cdk-accel-cfn-exec-role-${madAccountId}-${madRegion}`; - } - - // Attach MAD creation stack role to have access to the secret - adminSecret.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.ArnPrincipal(madAccessRoleArn)], - actions: ['secretsmanager:GetSecretValue'], - resources: ['*'], - }), - ); - - if (managedActiveDirectory.activeDirectoryConfigurationInstance) { - const activeDirectoryInstance = managedActiveDirectory.activeDirectoryConfigurationInstance; - - const instanceRole = cdk.aws_iam.Role.fromRoleArn( - this, - pascalCase(managedActiveDirectory.name) + pascalCase(activeDirectoryInstance.instanceRole), - `arn:${cdk.Stack.of(this).partition}:iam::${madAccountId}:role/${activeDirectoryInstance.instanceRole}`, - ); - - // Attach MAD instance role access to secrets resource policy - adminSecret.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.ArnPrincipal(instanceRole.roleArn)], - actions: ['secretsmanager:GetSecretValue'], - resources: ['*'], - }), - ); - - // Create ad user secrets for instance user data script - for (const adUser of activeDirectoryInstance.adUsers ?? []) { - const secret = new cdk.aws_secretsmanager.Secret(this, pascalCase(`${adUser.name}Secret`), { - description: `Password for Managed Active Directory user ${adUser.name}`, - generateSecretString: { - passwordLength: 16, - requireEachIncludedType: true, - }, - secretName: `${this.props.prefixes.secretName}/ad-user/${managedActiveDirectory.name}/${adUser.name}`, - encryptionKey: key, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // AwsSolutions-SMG4: The secret does not have automatic rotation scheduled - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.SMG4, - details: [ - { - path: `${this.stackName}/${pascalCase(adUser.name)}Secret/Resource`, - reason: 'Managed AD secret.', - }, - ], - }); - - new cdk.aws_ssm.StringParameter( - this, - pascalCase(`${managedActiveDirectory.name}${pascalCase(adUser.name)}SecretArnParameter`), - { - parameterName: `${this.props.prefixes.ssmParamName}/secrets-manager/${ - managedActiveDirectory.name - }/${pascalCase(adUser.name)}-secret/secret-arn`, - stringValue: adminSecret.secretArn, - }, - ); - - // Attach MAD instance role access to secret resource policy - secret.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.ArnPrincipal(instanceRole.roleArn)], - actions: ['secretsmanager:GetSecretValue'], - resources: ['*'], - }), - ); - } - } - } - } - } - - /** - * Function to configure cloudwatch metrics - */ - private configureCloudWatchMetrics() { - for (const metricSetItem of this.props.securityConfig.cloudWatch.metricSets ?? []) { - if (!metricSetItem.regions?.includes(cdk.Stack.of(this).region as Region)) { - continue; - } - - if (!this.isIncluded(metricSetItem.deploymentTargets)) { - continue; - } - - for (const metricItem of metricSetItem.metrics ?? []) { - const metricFilter = new cdk.aws_logs.MetricFilter(this, pascalCase(metricItem.filterName), { - logGroup: cdk.aws_logs.LogGroup.fromLogGroupName( - this, - `${pascalCase(metricItem.filterName)}_${pascalCase(metricItem.logGroupName)}`, - metricItem.logGroupName, - ), - metricNamespace: metricItem.metricNamespace, - metricName: metricItem.metricName, - filterPattern: cdk.aws_logs.FilterPattern.literal(metricItem.filterPattern), - metricValue: metricItem.metricValue, - }); - - if (this.accountTrailCloudWatchLogGroups.get(metricItem.logGroupName)) { - metricFilter.node.addDependency(this.accountTrailCloudWatchLogGroups.get(metricItem.logGroupName)!); - } - } - } - } - - /** - * Function to configure CW alarms - */ - private configureCloudwatchAlarm() { - for (const alarmSetItem of this.props.securityConfig.cloudWatch.alarmSets ?? []) { - if (!alarmSetItem.regions?.includes(cdk.Stack.of(this).region as Region)) { - continue; - } - - if (!this.isIncluded(alarmSetItem.deploymentTargets)) { - continue; - } - - for (const alarmItem of alarmSetItem.alarms ?? []) { - const alarm = new cdk.aws_cloudwatch.Alarm(this, pascalCase(alarmItem.alarmName), { - alarmName: alarmItem.alarmName, - alarmDescription: alarmItem.alarmDescription, - metric: new cdk.aws_cloudwatch.Metric({ - metricName: alarmItem.metricName, - namespace: alarmItem.namespace, - period: cdk.Duration.seconds(alarmItem.period), - statistic: alarmItem.statistic, - }), - comparisonOperator: this.getComparisonOperator(alarmItem.comparisonOperator), - evaluationPeriods: alarmItem.evaluationPeriods, - threshold: alarmItem.threshold, - treatMissingData: this.getTreatMissingData(alarmItem.treatMissingData), - }); - - if (this.props.globalConfig.snsTopics) { - alarm.addAlarmAction( - new cdk.aws_cloudwatch_actions.SnsAction( - cdk.aws_sns.Topic.fromTopicArn( - this, - `${pascalCase(alarmItem.alarmName)}Topic`, - cdk.Stack.of(this).formatArn({ - service: 'sns', - region: cdk.Stack.of(this).region, - account: cdk.Stack.of(this).account, - resource: `${this.props.prefixes.snsTopicName}-${alarmItem.snsTopicName}`, - arnFormat: cdk.ArnFormat.NO_RESOURCE_NAME, - }), - ), - ), - ); - } else { - alarm.addAlarmAction( - new cdk.aws_cloudwatch_actions.SnsAction( - cdk.aws_sns.Topic.fromTopicArn( - this, - `${pascalCase(alarmItem.alarmName)}Topic`, - cdk.Stack.of(this).formatArn({ - service: 'sns', - region: cdk.Stack.of(this).region, - account: this.props.accountsConfig.getAuditAccountId(), - resource: `${this.props.prefixes.snsTopicName}-${alarmItem.snsAlertLevel}Notifications`, - arnFormat: cdk.ArnFormat.NO_RESOURCE_NAME, - }), - ), - ), - ); - } - } - } - } - - private configureCloudwatchLogGroups() { - for (const logGroupItem of this.props.securityConfig.cloudWatch.logGroups ?? []) { - if (this.isIncluded(logGroupItem.deploymentTargets)) { - let keyArn: string | undefined = undefined; - if (logGroupItem.encryption?.kmsKeyName) { - keyArn = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${this.props.prefixes.ssmParamName}/kms/${logGroupItem.encryption?.kmsKeyName}/key-arn`, - ).toString(); - } else if (logGroupItem.encryption?.useLzaManagedKey && this.cloudwatchKey) { - keyArn = this.cloudwatchKey.keyArn; - } else if (logGroupItem.encryption?.kmsKeyArn) { - keyArn = logGroupItem.encryption?.kmsKeyArn; - } - new CloudWatchLogGroups(this, pascalCase(logGroupItem.logGroupName) + '-LogGroup', { - logGroupName: logGroupItem.logGroupName, - logRetentionInDays: logGroupItem.logRetentionInDays, - keyArn, - terminationProtected: logGroupItem.terminationProtected ?? false, - customLambdaLogKmsKey: this.cloudwatchKey, - customLambdaLogRetention: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - } - - /** - * Function to setup AWS Config - recorder and delivery channel - */ - private setupConfigRecorderAndDeliveryChannel() { - if ( - (!this.props.globalConfig.controlTower.enable || - this.props.accountsConfig.getManagementAccountId() === cdk.Stack.of(this).account) && - this.props.securityConfig.awsConfig.enableConfigurationRecorder && - (this.props.securityConfig.awsConfig.deploymentTargets - ? this.isIncluded(this.props.securityConfig.awsConfig.deploymentTargets) - : true) - ) { - // declaring variable here as this value is called twice and synth can run into duplicate construct name error - const configRecorderRoleArn = this.createConfigRecorderRole(); - /** - * These resources are deprecated - * They eventually will be removed and only - * the custom resource will remain - * 3/30/2023 - */ - if (!this.props.securityConfig.awsConfig.overrideExisting) { - this.configRecorder = new cdk.aws_config.CfnConfigurationRecorder(this, 'ConfigRecorder', { - roleArn: configRecorderRoleArn, - recordingGroup: { - allSupported: true, - includeGlobalResourceTypes: true, - }, - }); - - this.deliveryChannel = new cdk.aws_config.CfnDeliveryChannel(this, 'ConfigDeliveryChannel', { - s3BucketName: `${this.centralLogsBucketName}`, - configSnapshotDeliveryProperties: { - deliveryFrequency: 'One_Hour', - }, - }); - } - - if (this.props.securityConfig.awsConfig.overrideExisting) { - this.configServiceUpdater = new ConfigServiceRecorder(this, 'ConfigRecorderDeliveryChannel', { - s3BucketName: `${this.centralLogsBucketName}`, - s3BucketKmsKey: this.centralLogsBucketKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - configRecorderRoleArn: configRecorderRoleArn, - cloudwatchKmsKey: this.cloudwatchKey, - lambdaKmsKey: this.lambdaKey, - partition: this.partition, - acceleratorPrefix: this.props.prefixes.accelerator, - }); - - if (this.configRecorder && this.deliveryChannel) { - this.configServiceUpdater.node.addDependency(this.configRecorder); - this.configServiceUpdater.node.addDependency(this.deliveryChannel); - } - } - // AwsSolutions-IAM4 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigServiceRecorderFunction/ServiceRole/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - - // AwsSolutions-IAM5 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigRecorderRole/DefaultPolicy/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - - // AwsSolutions-IAM4 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigServiceRecorderProvider/framework-onEvent/ServiceRole/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - - // AwsSolutions-IAM5 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigServiceRecorderProvider/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - - // AwsSolutions-IAM5 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigServiceRecorderFunction/ServiceRole/DefaultPolicy/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - - // AwsSolutions-IAM4 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigServiceRecorderFunctionRole/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - - // AwsSolutions-IAM5 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ConfigRecorderDeliveryChannel/ConfigServiceRecorderFunctionRole/Resource`, - reason: 'Lambda managed policy', - }, - ], - }); - } - } - - private createConfigRecorderRole() { - if (this.props.useExistingRoles === true) { - return `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.props.prefixes.accelerator - }ConfigRecorderRole`; - } - const configRecorderRole = new cdk.aws_iam.Role(this, 'ConfigRecorderRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('config.amazonaws.com'), - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWS_ConfigRole')], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - // rule suppression with evidence for this permission. - NagSuppressions.addResourceSuppressions( - configRecorderRole, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'ConfigRecorderRole needs managed policy service-role/AWS_ConfigRole to administer config rules', - }, - { - id: 'AwsSolutions-IAM5', - reason: 'ConfigRecorderRole DefaultPolicy is built by cdk.', - }, - ], - true, - ); - - /** - * As per the documentation, the config role should have - * the s3:PutObject permission to avoid access denied issues - * while AWS config tries to check the s3 bucket (in another account) write permissions - * https://docs.aws.amazon.com/config/latest/developerguide/s3-bucket-policy.html - * - */ - configRecorderRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'S3WriteAccess', - actions: ['s3:PutObject', 's3:PutObjectAcl'], - resources: [ - `arn:${this.props.partition}:s3:::${this.acceleratorResourceNames.bucketPrefixes.centralLogs}-${this.logArchiveAccountId}-${this.props.centralizedLoggingRegion}/*`, - ], - conditions: { - StringLike: { - 's3:x-amz-acl': 'bucket-owner-full-control', - }, - }, - }), - ); - configRecorderRole.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'S3GetAclAccess', - actions: ['s3:GetBucketAcl'], - resources: [ - `arn:${this.props.partition}:s3:::${this.acceleratorResourceNames.bucketPrefixes.centralLogs}-${this.logArchiveAccountId}-${this.props.centralizedLoggingRegion}`, - ], - }), - ); - - // AwsSolutions-IAM5 - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/ConfigRecorderRole/DefaultPolicy/Resource`, - reason: 'Single bucket', - }, - ], - }); - - return configRecorderRole.roleArn; - } - - /** - * Function to create AWS Managed Config rule - * @param rule - * @returns - */ - private createManagedConfigRule(rule: ConfigRule): CustomConfigRuleType { - const resourceTypes: cdk.aws_config.ResourceType[] = []; - for (const resourceType of rule.complianceResourceTypes ?? []) { - resourceTypes.push(cdk.aws_config.ResourceType.of(resourceType)); - } - - const managedConfigRule = new cdk.aws_config.ManagedRule(this, pascalCase(rule.name), { - configRuleName: rule.name, - description: rule.description, - identifier: rule.identifier ?? rule.name, - inputParameters: this.getRuleParameters(rule.name, rule.inputParameters), - ruleScope: { - resourceTypes, - }, - }); - - return managedConfigRule; - } - - /** - * Function to create AWS custom config rule - * @param rule - * @returns - */ - private createCustomConfigRule(rule: ConfigRule): CustomConfigRuleType { - let ruleScope: cdk.aws_config.RuleScope | undefined; - - if (rule.customRule.triggeringResources.lookupType == 'ResourceTypes') { - const ruleScopeResources: cdk.aws_config.ResourceType[] = []; - for (const item of rule.customRule.triggeringResources.lookupValue) { - ruleScopeResources.push(cdk.aws_config.ResourceType.of(item)); - } - ruleScope = cdk.aws_config.RuleScope.fromResources(ruleScopeResources); - } - - if (rule.customRule.triggeringResources.lookupType == 'ResourceId') { - ruleScope = cdk.aws_config.RuleScope.fromResource( - cdk.aws_config.ResourceType.of(rule.customRule.triggeringResources.lookupKey), - rule.customRule.triggeringResources.lookupValue[0], - ); - } - - if (rule.customRule.triggeringResources.lookupType == 'Tag') { - ruleScope = cdk.aws_config.RuleScope.fromTag( - rule.customRule.triggeringResources.lookupKey, - rule.customRule.triggeringResources.lookupValue[0], - ); - } - - /** - * Lambda function for config custom role - * Single lambda function can not be used for multiple config custom role, there is a pending issue with CDK team on this - * https://github.com/aws/aws-cdk/issues/17582 - */ - const lambdaFunction = new cdk.aws_lambda.Function(this, pascalCase(rule.name) + '-Function', { - runtime: new cdk.aws_lambda.Runtime(rule.customRule.lambda.runtime), - handler: rule.customRule.lambda.handler, - code: cdk.aws_lambda.Code.fromAsset(path.join(this.props.configDirPath, rule.customRule.lambda.sourceFilePath)), - description: `AWS Config custom rule function used for "${rule.name}" rule`, - timeout: cdk.Duration.seconds(rule.customRule.lambda.timeout ?? 3), - }); - - // Configure lambda log file with encryption and log retention - new cdk.aws_logs.LogGroup(this, pascalCase(rule.name) + '-LogGroup', { - logGroupName: `/aws/lambda/${lambdaFunction.functionName}`, - retention: this.props.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: this.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - // Read in the policy document which should be properly formatted json - const policyDocument = require(path.join(this.props.configDirPath, rule.customRule.lambda.rolePolicyFile)); - // Create a statements list using the PolicyStatement factory - const policyStatements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - policyStatements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - // Assign policy to Lambda - lambdaFunction.role?.attachInlinePolicy( - new cdk.aws_iam.Policy(this, pascalCase(rule.name) + '-LambdaRolePolicy', { - statements: [...policyStatements], - }), - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(rule.name)}-LambdaRolePolicy/Resource`, - reason: 'AWS Config rule custom lambda role, created by the permission provided in config repository', - }, - ], - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(rule.name)}-Function/ServiceRole/Resource`, - reason: 'AWS Config custom rule needs managed readonly access policy', - }, - ], - }); - - const managedConfigRule = new cdk.aws_config.CustomRule(this, pascalCase(rule.name), { - configRuleName: rule.name, - lambdaFunction: lambdaFunction, - periodic: rule.customRule.periodic, - inputParameters: this.getRuleParameters(rule.name, rule.inputParameters), - description: rule.description, - maximumExecutionFrequency: - rule.customRule.maximumExecutionFrequency === undefined - ? undefined - : (rule.customRule.maximumExecutionFrequency as cdk.aws_config.MaximumExecutionFrequency), - ruleScope: ruleScope, - configurationChanges: rule.customRule.configurationChanges, - }); - managedConfigRule.node.addDependency(lambdaFunction); - - return managedConfigRule; - } - - /** - * Function to setup AWS Config rule remediation - * @param rule - * @param configRule - */ - private setupConfigRuleRemediation( - rule: ConfigRule, - configRule: cdk.aws_config.ManagedRule | cdk.aws_config.CustomRule, - ) { - const remediationRole = this.createRemediationRole( - rule.name, - path.join(this.props.configDirPath, rule.remediation.rolePolicyFile), - `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${ - rule.remediation.targetAccountName - ? this.props.accountsConfig.getAccountId(rule.remediation.targetAccountName) - : this.props.accountsConfig.getAuditAccountId() - }:document/${rule.remediation.targetId}`, - !!rule.remediation.targetDocumentLambda, - ); - - // If remediation document use action as aws:invokeLambdaFunction, create the lambda function - let remediationLambdaFunction: cdk.aws_lambda.Function | undefined; - if (rule.remediation.targetDocumentLambda) { - remediationLambdaFunction = new cdk.aws_lambda.Function(this, pascalCase(rule.name) + '-RemediationFunction', { - role: remediationRole, - runtime: new cdk.aws_lambda.Runtime(rule.remediation.targetDocumentLambda.runtime), - handler: rule.remediation.targetDocumentLambda.handler, - code: cdk.aws_lambda.Code.fromAsset( - path.join(this.props.configDirPath, rule.remediation.targetDocumentLambda.sourceFilePath), - ), - description: `Function used in ${rule.remediation.targetId} SSM document for "${rule.name}" custom config rule to remediation`, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/${pascalCase(rule.name)}-RemediationFunction/ServiceRole/Resource`, - reason: 'AWS Config custom rule needs managed readonly access policy', - }, - ], - }); - - // Configure lambda log file with encryption and log retention - new cdk.aws_logs.LogGroup(this, pascalCase(rule.name) + '-RemediationLogGroup', { - logGroupName: `/aws/lambda/${remediationLambdaFunction.functionName}`, - retention: this.props.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: this.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - } - - let remediationTargetId = `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${ - rule.remediation.targetAccountName - ? this.props.accountsConfig.getAccountId(rule.remediation.targetAccountName) - : this.props.accountsConfig.getAuditAccountId() - }:document/${rule.remediation.targetId}`; - - if (IsPublicSsmDoc(rule.remediation.targetId)) { - remediationTargetId = rule.remediation.targetId; - } - - new cdk.aws_config.CfnRemediationConfiguration(this, pascalCase(rule.name) + '-Remediation', { - configRuleName: rule.name, - targetId: remediationTargetId, - targetVersion: rule.remediation.targetVersion, - targetType: 'SSM_DOCUMENT', - automatic: rule.remediation.automatic, - maximumAutomaticAttempts: rule.remediation.maximumAutomaticAttempts, - retryAttemptSeconds: rule.remediation.retryAttemptSeconds, - parameters: this.getRemediationParameters( - rule.name, - rule.remediation.parameters as string[], - [remediationRole.roleArn], - remediationLambdaFunction ? remediationLambdaFunction.functionName : undefined, - ), - }).node.addDependency(configRule); - } - - /** - * Function to setup tagging for AWS Config services - * @param rule - * @param configRule - */ - private setupConfigServicesTagging( - rule: ConfigRule, - configRule: cdk.aws_config.ManagedRule | cdk.aws_config.CustomRule, - ) { - if (rule.tags) { - const configRuleTags = this.convertAcceleratorTags(rule.tags); - new ConfigServiceTags(this, pascalCase(rule.name + 'tags'), { - resourceArn: configRule.configRuleArn, - tags: configRuleTags, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - kmsKey: this.cloudwatchKey, - partition: this.props.partition, - accountId: cdk.Stack.of(this).account, - }); - } - } - - /** - * Function to create AWS Config rules (Managed and Custom) - * @param ruleSet - */ - private createAwsConfigRules(ruleSet: AwsConfigRuleSet) { - for (const rule of ruleSet.rules) { - let configRule: CustomConfigRuleType; - - if (rule.type && rule.type === 'Custom') { - configRule = this.createCustomConfigRule(rule); - } else { - configRule = this.createManagedConfigRule(rule); - } - - if (configRule) { - // Tag rule - this.setupConfigServicesTagging(rule, configRule); - // Create remediation for config rule - if ( - rule.remediation && - (rule.remediation.excludeRegions ?? []).indexOf(cdk.Stack.of(this).region as Region) === -1 - ) { - this.setupConfigRuleRemediation(rule, configRule); - } - - if (this.configRecorder) { - configRule.node.addDependency(this.configRecorder); - } - if (this.configServiceUpdater) { - configRule.node.addDependency(this.configServiceUpdater); - } - } - } - } - - /** - * Function to setup AWS Config rules - */ - private setupAwsConfigRules() { - for (const ruleSet of this.props.securityConfig.awsConfig.ruleSets) { - if (!this.isIncluded(ruleSet.deploymentTargets)) { - continue; - } - - this.createAwsConfigRules(ruleSet); - } - } - - private getComparisonOperator(comparisonOperator: string): cdk.aws_cloudwatch.ComparisonOperator { - if (comparisonOperator === 'GreaterThanOrEqualToThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD; - } - if (comparisonOperator === 'GreaterThanThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD; - } - if (comparisonOperator === 'LessThanThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD; - } - if (comparisonOperator === 'LessThanOrEqualToThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD; - } - if (comparisonOperator === 'LessThanLowerOrGreaterThanUpperThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD; - } - if (comparisonOperator === 'GreaterThanUpperThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.GREATER_THAN_UPPER_THRESHOLD; - } - if (comparisonOperator === 'LessThanLowerThreshold') { - return cdk.aws_cloudwatch.ComparisonOperator.LESS_THAN_LOWER_THRESHOLD; - } - return cdk.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD; - } - - private getTreatMissingData(treatMissingData: string): cdk.aws_cloudwatch.TreatMissingData { - if (treatMissingData === 'breaching') { - return cdk.aws_cloudwatch.TreatMissingData.BREACHING; - } - if (treatMissingData === 'notBreaching') { - return cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING; - } - if (treatMissingData === 'ignore') { - return cdk.aws_cloudwatch.TreatMissingData.IGNORE; - } - if (treatMissingData === 'missing') { - return cdk.aws_cloudwatch.TreatMissingData.MISSING; - } - return cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING; - } - - private getReplaceValues(value: string, ruleName: string): string[] { - const replacementValues: string[] = []; - for (const item of value.split(',')) { - const parameterReplacementNeeded = item.match('\\${ACCEL_LOOKUP::([a-zA-Z0-9-/:]*)}'); - if (parameterReplacementNeeded) { - const replacementValue = this.getReplacementValue(ruleName, parameterReplacementNeeded, 'Rule-Parameter'); - replacementValues.push(replacementValue?.split(',')[0] ?? ''); - } - } - return replacementValues; - } - - private getRemediationReplacementValues(value: string, ruleName: string, configFunctionName?: string): string[] { - const replacementValues: string[] = []; - for (const item of value.split(',')) { - const parameterReplacementNeeded = item.match('\\${ACCEL_LOOKUP::([a-zA-Z0-9-/:]*)}'); - if (parameterReplacementNeeded) { - const replacementValue = this.getReplacementValue( - ruleName, - parameterReplacementNeeded, - 'Remediation-Parameter', - configFunctionName, - ); - replacementValues.push(replacementValue ?? ''); - } - } - - return replacementValues; - } - /** - * Function to prepare config rule parameters - * @param ruleName - * @param params - * @private - */ - private getRuleParameters(ruleName: string, params?: { [key: string]: string }): { [key: string]: string } { - if (params) { - const returnParams: { [key: string]: string } = {}; - for (const [key, value] of Object.entries(params)) { - const replacementValues: string[] = this.getReplaceValues(value, ruleName); - if (replacementValues.length > 0) { - returnParams[key] = replacementValues.join(','); - } else { - returnParams[key] = value; - } - } - return returnParams; - } else { - return {}; - } - } - - private getParamValue(replacementValues: string[], parameterType: string): string[] { - if (parameterType === 'StringList') { - return replacementValues; - } else { - return [replacementValues.join(',')]; - } - } - - /** - * Function to get remediation parameters - * @param ruleName - * @param params - * @param assumeRoleArn - * @param configFunctionName - * @private - */ - private getRemediationParameters( - ruleName: string, - params?: string[], - assumeRoleArn?: string[], - configFunctionName?: string, - ): RemediationParameters | undefined { - if (!params) { - return undefined; - } - - const returnParams: RemediationParameters = {}; - if (assumeRoleArn) { - returnParams['AutomationAssumeRole'] = { - StaticValue: { - Values: assumeRoleArn, - }, - }; - } - - for (const param of params) { - let parameterName: string | undefined; - let parameterValue: string | undefined; - let parameterType = 'List'; - for (const [key, value] of Object.entries(param)) { - switch (key) { - case 'name': - parameterName = value; - break; - case 'value': - parameterValue = value; - break; - case 'type': - parameterType = value; - break; - } - } - - const replacementValues: string[] = this.getRemediationReplacementValues( - parameterValue as string, - ruleName, - configFunctionName, - ); - - if (replacementValues.length > 0) { - returnParams[parameterName!] = { - StaticValue: { - Values: this.getParamValue(replacementValues, parameterType), - }, - }; - } else { - if (parameterValue === 'RESOURCE_ID') { - returnParams[parameterName!] = { - ResourceValue: { - Value: 'RESOURCE_ID', - }, - }; - } else { - returnParams[parameterName!] = { - StaticValue: { - Values: parameterValue!.split(','), - }, - }; - } - } - } - return returnParams; - } - - /** - * Function to get replacement function name - * @param ruleName string - * @param replacementArray string[] - * @param remediationFunctionName string - * @returns remediationFunctionName string - */ - private getReplacementFunctionName( - ruleName: string, - replacementArray: string[], - remediationFunctionName?: string, - ): string { - if (remediationFunctionName) { - return remediationFunctionName; - } else { - this.logger.error( - `Remediation function for ${ruleName} rule is undefined. Invalid lookup value ${replacementArray[1]}`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to get organization id replacement value - * @param lookupType string - * @param replacementArray string[] - * @param ruleName string - * @returns organizationId string - */ - private getOrganizationIdReplacementValue(ruleName: string): string { - if (this.organizationId) { - return this.organizationId; - } else { - this.logger.error(`${ruleName} parameter error !! Organization not enabled can not retrieve organization id`); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to get kms key arn replacement value - * @param ruleName string - * @param replacementArray string[] - * @returns KeyArn string - */ - private getKmsArnReplacementValue(ruleName: string, replacementArray: string[]): string | undefined { - if (replacementArray.length === 1) { - if (!this.isS3CMKEnabled) { - return undefined; - } - return cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(ruleName) + '-AcceleratorGetS3Key', - cdk.aws_ssm.StringParameter.valueForStringParameter(this, this.acceleratorResourceNames.parameters.s3CmkArn), - ).keyArn; - } else { - // When specific Key ID is given - return cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(ruleName) + '-AcceleratorGetS3Key', - `arn:${cdk.Stack.of(this).partition}:kms:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:key/${ - replacementArray[1] - }`, - ).keyArn; - } - } - - /** - * Function to get bucket name replacement value - * @param ruleName string - * @param replacementArray string[] - * @param replacementType string - * @returns bucketName string - */ - private getBucketNameReplacementValue(ruleName: string, replacementArray: string[], replacementType: string): string { - if (replacementArray[1].toLowerCase() === 'elbLogs'.toLowerCase()) { - return this.getElbLogsBucketName(); - } else { - return cdk.aws_s3.Bucket.fromBucketName( - this, - `${pascalCase(ruleName)}-${pascalCase(replacementType)}-InputBucket`, - replacementArray[1].toLowerCase(), - ).bucketName; - } - } - - /** - * Validate lookup data - * @param lookupType string - * @param replacementArray string[] - */ - private validateLookupData(lookupType: string, replacementArray: string[]) { - let isError = false; - if (lookupType === ACCEL_LOOKUP_TYPE.REMEDIATION_FUNCTION_NAME && replacementArray.length !== 1) { - isError = true; - } - if (lookupType === ACCEL_LOOKUP_TYPE.ORGANIZATION_ID && replacementArray.length !== 1) { - isError = true; - } - if (lookupType === ACCEL_LOOKUP_TYPE.INSTANCE_PROFILE && replacementArray.length !== 2) { - isError = true; - } - if (lookupType === ACCEL_LOOKUP_TYPE.CUSTOMER_MANAGED_POLICY && replacementArray.length !== 2) { - isError = true; - } - if (lookupType === ACCEL_LOOKUP_TYPE.KMS && replacementArray.length > 1) { - isError = true; - } - if (lookupType === ACCEL_LOOKUP_TYPE.Bucket && replacementArray.length !== 2) { - isError = true; - } - - if (isError) { - this.logger.error(`Invalid replacement options ${replacementArray}`); - throw new Error(`Invalid replacement options ${replacementArray}`); - } - } - - /** - * Function to get Config rule remediation parameter replacement value - * @param ruleName - * @param replacement - * @param replacementType - * @param remediationFunctionName - * @private - */ - private getReplacementValue( - ruleName: string, - replacement: RegExpMatchArray, - replacementType: string, - remediationFunctionName?: string, - ): string | undefined { - const replacementArray = replacement[1].split(':'); - const lookupType = replacementArray[0]; - - let returnValue: string | undefined; - - // Validate lookup data - this.validateLookupData(lookupType, replacementArray); - - switch (lookupType) { - case ACCEL_LOOKUP_TYPE.REMEDIATION_FUNCTION_NAME: - returnValue = this.getReplacementFunctionName(ruleName, replacementArray, remediationFunctionName); - break; - case ACCEL_LOOKUP_TYPE.ORGANIZATION_ID: - returnValue = this.getOrganizationIdReplacementValue(lookupType); - break; - case ACCEL_LOOKUP_TYPE.ACCOUNT_ID: - returnValue = this.getOrganizationIdReplacementValue(lookupType); - break; - case ACCEL_LOOKUP_TYPE.INSTANCE_PROFILE: - returnValue = replacementArray[1]; - break; - case ACCEL_LOOKUP_TYPE.CUSTOMER_MANAGED_POLICY: - returnValue = cdk.aws_iam.ManagedPolicy.fromManagedPolicyName( - this, - `${pascalCase(ruleName)} + ${pascalCase(replacementArray[1])}-${pascalCase(replacementType)}`, - replacementArray[1], - ).managedPolicyArn; - break; - case ACCEL_LOOKUP_TYPE.KMS: - returnValue = this.getKmsArnReplacementValue(ruleName, replacementArray); - break; - case ACCEL_LOOKUP_TYPE.Bucket: - returnValue = this.getBucketNameReplacementValue(ruleName, replacementArray, replacementType); - break; - default: - this.logger.error(`Config rule replacement key ${replacement.input} not found`); - throw new Error(`Configuration validation failed at runtime.`); - } - - return returnValue; - } - - /** - * Function to create remediation role - * @param ruleName - * @param policyFilePath - * @param resources - * @param isLambdaRole - * @private - */ - private createRemediationRole( - ruleName: string, - policyFilePath: string, - resources?: string, - isLambdaRole = false, - ): cdk.aws_iam.IRole { - // Read in the policy document which should be properly formatted json - const policyDocument = require(policyFilePath); - // Create a statements list using the PolicyStatement factory - const policyStatements: cdk.aws_iam.PolicyStatement[] = []; - for (const statement of policyDocument.Statement) { - policyStatements.push(cdk.aws_iam.PolicyStatement.fromJson(statement)); - } - - const principals: cdk.aws_iam.PrincipalBase[] = [new cdk.aws_iam.ServicePrincipal('ssm.amazonaws.com')]; - if (isLambdaRole) { - principals.push(new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com')); - } - - const role = new cdk.aws_iam.Role(this, pascalCase(ruleName) + '-RemediationRole', { - assumedBy: new cdk.aws_iam.CompositePrincipal(...principals), - }); - - role.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ssm:GetAutomationExecution', - 'ssm:StartAutomationExecution', - 'ssm:GetParameters', - 'ssm:GetParameter', - 'ssm:PutParameter', - ], - resources: [resources ?? '*'], - }), - ); - - policyStatements.forEach(policyStatement => { - role.addToPolicy(policyStatement); - }); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - // rule suppression with evidence for this permission. - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `${this.stackName}/${pascalCase(ruleName)}-RemediationRole/DefaultPolicy/Resource`, - reason: 'AWS Config rule remediation role, created by the permission provided in config repository', - }, - ], - }); - - return role; - } - - private convertAcceleratorTags(acceleratorTags: Tag[]): ConfigRuleTag[] { - const tags: ConfigRuleTag[] = []; - for (const tag of acceleratorTags) { - tags.push({ Key: tag.key, Value: tag.value }); - } - return tags; - } - - /** - * Function to setup Session manager - */ - private setupSessionManager() { - if ( - !this.isAccountExcluded(this.props.globalConfig.logging.sessionManager.excludeAccounts) && - !this.isRegionExcluded(this.props.globalConfig.logging.sessionManager.excludeRegions) - // remove region exclude, set to home region - ) { - if ( - this.props.globalConfig.logging.sessionManager.sendToCloudWatchLogs || - this.props.globalConfig.logging.sessionManager.sendToS3 - ) { - // Set up Session Manager Logging - new SsmSessionManagerSettings(this, 'SsmSessionManagerSettings', { - s3BucketName: this.centralLogsBucketName, - s3KeyPrefix: `session/${cdk.Aws.ACCOUNT_ID}/${cdk.Stack.of(this).region}`, - s3BucketKeyArn: this.centralLogsBucketKey.keyArn, - sendToCloudWatchLogs: this.props.globalConfig.logging.sessionManager.sendToCloudWatchLogs, - sendToS3: this.props.globalConfig.logging.sessionManager.sendToS3, - cloudWatchEncryptionEnabled: - this.props.partition !== 'aws-us-gov' && - this.props.globalConfig.logging.sessionManager.sendToCloudWatchLogs, - cloudWatchEncryptionKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - region: cdk.Stack.of(this).region, - rolesInAccounts: this.props.globalConfig.iamRoleSsmParameters, - prefixes: { - accelerator: this.props.prefixes.accelerator, - ssmLog: this.props.prefixes.ssmLogName, - }, - ssmKeyDetails: { - alias: this.acceleratorResourceNames.customerManagedKeys.ssmKey.alias, - description: this.acceleratorResourceNames.customerManagedKeys.ssmKey.description, - }, - }); - } - } - } - - private securityHubEventForwardToLogs() { - const securityHubConfig = this.props.securityConfig.centralSecurityServices.securityHub; - // only forward events if Security Hub is enabled and logging is enabled. If logging is undefined, its assumed to be true. - if (securityHubConfig.enable && (securityHubConfig.logging?.cloudWatch?.enable ?? true)) { - if (!securityHubConfig.deploymentTargets || this.isIncluded(securityHubConfig.deploymentTargets)) { - new SecurityHubEventsLog(this, 'SecurityHubEventsLog', { - acceleratorPrefix: this.props.prefixes.accelerator, - snsTopicArn: `arn:${cdk.Stack.of(this).partition}:sns:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:${this.props.prefixes.snsTopicName}-${securityHubConfig.snsTopicName}`, - snsKmsKey: this.snsKey, - notificationLevel: securityHubConfig.notificationLevel, - lambdaKey: this.lambdaKey, - cloudWatchLogRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - logLevel: securityHubConfig.logging?.cloudWatch?.logLevel, - logGroupName: securityHubConfig.logging?.cloudWatch?.logGroupName, - }); - } - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `/${this.stackName}/SecurityHubEventsLog/SecurityHubEventsFunction/ServiceRole/Resource`, - reason: 'Managed policy for lambda to write logs to cloudwatch.', - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `/${this.stackName}/SecurityHubEventsFunction/ServiceRole/DefaultPolicy/Resource`, - reason: `Allows only access to /${this.props.prefixes.accelerator}-SecurityHub log group.`, - }, - ], - }); - - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM5, - details: [ - { - path: `/${this.stackName}/SecurityHubEventsLog/SecurityHubEventsFunction/ServiceRole/DefaultPolicy/Resource`, - reason: `Allows only access to /${this.props.prefixes.accelerator}-SecurityHub log group.`, - }, - ], - }); - } - } - - /** - * Function to configure Account CloudTrail - * @param accountTrail {@link AccountCloudTrailConfig} - */ - private configureAccountCloudTrail(accountTrail: AccountCloudTrailConfig) { - const trailName = `${this.props.prefixes.accelerator}-CloudTrail-${accountTrail.name}`; - - let accountTrailCloudWatchLogGroup: cdk.aws_logs.LogGroup | undefined = undefined; - if (accountTrail.settings.sendToCloudWatchLogs) { - const logGroupName = `${this.props.prefixes.trailLogName}-cloudtrail-${accountTrail.name}`; - accountTrailCloudWatchLogGroup = new cdk.aws_logs.LogGroup(this, `CloudTrailLogGroup-${accountTrail.name}`, { - retention: this.stackProperties.globalConfig.cloudwatchLogRetentionInDays, - encryptionKey: this.cloudwatchKey, - logGroupName, - }); - this.accountTrailCloudWatchLogGroups.set(logGroupName, accountTrailCloudWatchLogGroup); - } - - let managementEventType = cdk.aws_cloudtrail.ReadWriteType.NONE; - if (accountTrail.settings.managementEvents) { - managementEventType = cdk.aws_cloudtrail.ReadWriteType.ALL; - } - - const accountCloudTrailLog = new cdk_extensions.Trail(this, `AcceleratorCloudTrail-${accountTrail.name}`, { - bucket: cdk.aws_s3.Bucket.fromBucketName(this, 'CloudTrailLogBucket', this.centralLogsBucketName), - s3KeyPrefix: `cloudtrail-${accountTrail.name}`, - cloudWatchLogGroup: accountTrailCloudWatchLogGroup, - cloudWatchLogsRetention: this.stackProperties.globalConfig.cloudwatchLogRetentionInDays, - enableFileValidation: true, - encryptionKey: this.centralLogsBucketKey, - includeGlobalServiceEvents: accountTrail.settings.globalServiceEvents ?? false, - isMultiRegionTrail: accountTrail.settings.multiRegionTrail ?? false, - isOrganizationTrail: false, - apiCallRateInsight: accountTrail.settings.apiCallRateInsight ?? false, - apiErrorRateInsight: accountTrail.settings.apiErrorRateInsight ?? false, - managementEvents: managementEventType, - sendToCloudWatchLogs: accountTrail.settings.sendToCloudWatchLogs ?? false, - trailName: trailName, - }); - - if (accountTrail.settings.s3DataEvents) { - accountCloudTrailLog.addEventSelector(cdk.aws_cloudtrail.DataResourceType.S3_OBJECT, [ - `arn:${cdk.Stack.of(this).partition}:s3:::`, - ]); - } - - if (accountTrail.settings.lambdaDataEvents) { - accountCloudTrailLog.addEventSelector(cdk.aws_cloudtrail.DataResourceType.LAMBDA_FUNCTION, [ - `arn:${cdk.Stack.of(this).partition}:lambda`, - ]); - } - } - - private configureAccountCloudTrails() { - // Don't create any CloudTrail resources unless CloudTrail is enabled. - if (!this.stackProperties.globalConfig.logging.cloudtrail.enable) { - return; - } - for (const accountTrail of this.stackProperties.globalConfig.logging.cloudtrail.accountTrails ?? []) { - if (!accountTrail.regions?.includes(cdk.Stack.of(this).region)) { - continue; - } - - if (!this.isIncluded(accountTrail.deploymentTargets)) { - continue; - } - - // Configure Account CloudTrail - this.configureAccountCloudTrail(accountTrail); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/security-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/security-stack.ts deleted file mode 100644 index e74ce69..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/security-stack.ts +++ /dev/null @@ -1,469 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import { Construct } from 'constructs'; - -import { EbsDefaultVolumeEncryptionConfig, GuardDutyConfig, Region, SecurityHubConfig } from '@aws-accelerator/config'; -import { - AcceleratorMetadata, - EbsDefaultEncryption, - GuardDutyPublishingDestination, - MacieExportConfigClassification, - PasswordPolicy, - SecurityHubStandards, - ConfigAggregation, -} from '@aws-accelerator/constructs'; - -import { - AcceleratorKeyType, - AcceleratorStack, - AcceleratorStackProps, - NagSuppressionRuleIds, -} from './accelerator-stack'; -import { pascalCase } from 'pascal-case'; - -/** - * Security Stack, configures local account security services - */ -export class SecurityStack extends AcceleratorStack { - readonly auditAccountId: string; - readonly logArchiveAccountId: string; - readonly auditAccountName: string; - readonly centralLogsBucketKey: cdk.aws_kms.IKey; - readonly configAggregationAccountId: string; - readonly cloudwatchKey?: cdk.aws_kms.IKey; - readonly metadataRule: AcceleratorMetadata | undefined; - readonly securityHubConfig: SecurityHubConfig; - constructor(scope: Construct, id: string, props: AcceleratorStackProps) { - super(scope, id, props); - const elbLogBucketName = this.getElbLogsBucketName(); - this.auditAccountName = props.securityConfig.getDelegatedAccountName(); - this.auditAccountId = props.accountsConfig.getAuditAccountId(); - this.logArchiveAccountId = props.accountsConfig.getLogArchiveAccountId(); - this.configAggregationAccountId = props.accountsConfig.getManagementAccountId(); - if ( - props.securityConfig.awsConfig.aggregation?.enable && - props.securityConfig.awsConfig.aggregation.delegatedAdminAccount - ) { - this.configAggregationAccountId = props.accountsConfig.getAccountId( - props.securityConfig.awsConfig.aggregation.delegatedAdminAccount, - ); - } - this.cloudwatchKey = this.getAcceleratorKey(AcceleratorKeyType.CLOUDWATCH_KEY); - this.centralLogsBucketKey = this.getCentralLogsBucketKey(this.cloudwatchKey); - this.securityHubConfig = this.props.securityConfig.centralSecurityServices.securityHub; - - // - // MacieSession configuration - // - this.configureMacie(); - - // - // GuardDuty configuration - // - this.configureGuardDuty(); - - // - // SecurityHub configuration - // - this.configureSecurityHub(); - - // - // Ebs Default Volume Encryption configuration - // - this.configureDefaultEbsEncryption(props.securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption); - - // - // Update IAM Password Policy - // - this.updateIamPasswordPolicy(); - // - // Create Accelerator Metadata Rule - // - - this.metadataRule = this.acceleratorMetadataRule( - props, - this.centralLogsBucketName, - elbLogBucketName, - this.cloudwatchKey, - ); - - // - // Create SSM Parameters - // - this.createSsmParameters(); - - if ( - this.props.securityConfig.awsConfig.aggregation?.enable && - this.configAggregationAccountId === cdk.Stack.of(this).account - ) { - this.enableConfigAggregation(); - } - - // - // Create NagSuppressions - // - this.addResourceSuppressionsByPath(); - - this.logger.info('Completed stack synthesis'); - } - - /** - * Validate Delegated Admin Account name for the given security service is part of account config - * @param securityServiceName string - */ - private validateDelegatedAdminAccountName(securityServiceName: string) { - if (!this.props.accountsConfig.containsAccount(this.auditAccountName)) { - this.logger.error( - `${securityServiceName} audit delegated admin account name "${this.auditAccountName}" not found.`, - ); - throw new Error(`Configuration validation failed at runtime.`); - } - } - - /** - * Function to configure Macie - */ - private configureMacie() { - if ( - this.props.securityConfig.centralSecurityServices.macie.enable && - this.props.securityConfig.centralSecurityServices.macie.excludeRegions.indexOf( - cdk.Stack.of(this).region as Region, - ) === -1 - ) { - // Validate Delegated Admin Account name is part of account config - this.validateDelegatedAdminAccountName('Macie'); - - new MacieExportConfigClassification(this, 'AwsMacieUpdateExportConfigClassification', { - bucketName: this.centralLogsBucketName, - bucketKmsKey: this.centralLogsBucketKey, - logKmsKey: this.cloudwatchKey, - keyPrefix: `macie/${cdk.Stack.of(this).account}/`, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - - /** - * Function to configure GuardDuty - */ - private configureGuardDuty() { - const guardDutyConfig: GuardDutyConfig = this.props.securityConfig.centralSecurityServices.guardduty; - if ( - guardDutyConfig.enable && - (guardDutyConfig.excludeRegions - ? guardDutyConfig.excludeRegions.indexOf(this.region as Region) === -1 - : guardDutyConfig.deploymentTargets - ? this.isIncluded(guardDutyConfig.deploymentTargets) - : true) - ) { - if (guardDutyConfig.exportConfiguration.enable) { - // Validate Delegated Admin Account name is part of account config - this.validateDelegatedAdminAccountName('Guardduty'); - let destinationPrefix = 'guardduty'; - if (guardDutyConfig.exportConfiguration.overrideGuardDutyPrefix?.useCustomPrefix) { - destinationPrefix = guardDutyConfig.exportConfiguration.overrideGuardDutyPrefix?.customOverride ?? ''; - } - - const destinationArn = `arn:${cdk.Stack.of(this).partition}:s3:::${ - this.centralLogsBucketName - }/${destinationPrefix}`; - - new GuardDutyPublishingDestination(this, 'GuardDutyPublishingDestination', { - exportDestinationType: guardDutyConfig.exportConfiguration.destinationType, - exportDestinationOverride: guardDutyConfig.exportConfiguration.overrideExisting ?? false, - destinationArn: destinationArn, - destinationKmsKey: this.centralLogsBucketKey, - logKmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - } - - /** - * Function to initialize SecurityHub standards - * @returns - */ - private initializeSecurityHubStandards(): { name: string; enable: boolean; controlsToDisable: string[] }[] { - const standards: { name: string; enable: boolean; controlsToDisable: string[] }[] = []; - for (const standard of this.securityHubConfig.standards) { - if (standard.deploymentTargets) { - if (!this.isIncluded(standard.deploymentTargets)) { - this.logger.info(`Item excluded`); - continue; - } - } - // add to standards list - standards.push({ - name: standard.name, - enable: standard.enable, - controlsToDisable: standard.controlsToDisable, - }); - } - - return standards; - } - - /** - * Function to configure SecurityHub - */ - private configureSecurityHub() { - if (this.validateExcludeRegionsAndDeploymentTargets(this.securityHubConfig)) { - // Validate Delegated Admin Account name is part of account config - this.validateDelegatedAdminAccountName('SecurityHub'); - - const standards = this.initializeSecurityHubStandards(); - - if (standards.length > 0) { - new SecurityHubStandards(this, 'SecurityHubStandards', { - standards, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - } - - /** - * Function to configure default EBS encryption - * @param ebsEncryptionConfig EbsDefaultVolumeEncryptionConfig - */ - private configureDefaultEbsEncryption(ebsEncryptionConfig: EbsDefaultVolumeEncryptionConfig) { - if (ebsEncryptionConfig.enable && this.deployEbsEncryption(ebsEncryptionConfig)) { - new EbsDefaultEncryption(this, 'EbsDefaultVolumeEncryption', { - ebsEncryptionKmsKey: this.getOrCreateEbsEncryptionKey(ebsEncryptionConfig), - logGroupKmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - - /** - * Determines if EBS default volume encryption should be deployed to - * this stack's account/region - * @param ebsEncryptionConfig EbsDefaultVolumeEncryptionConfig - * @returns boolean - */ - private deployEbsEncryption(ebsEncryptionConfig: EbsDefaultVolumeEncryptionConfig): boolean { - if (ebsEncryptionConfig.excludeRegions) { - return ebsEncryptionConfig.excludeRegions.indexOf(this.region as Region) === -1; - } else { - return ebsEncryptionConfig.deploymentTargets ? this.isIncluded(ebsEncryptionConfig.deploymentTargets) : true; - } - } - - /** - * Get custom key or create LZA-managed KMS key - * @param ebsEncryptionConfig EbsDefaultVolumeEncryptionConfig - * @returns cdk.aws_kms.IKey - */ - private getOrCreateEbsEncryptionKey(ebsEncryptionConfig: EbsDefaultVolumeEncryptionConfig): cdk.aws_kms.IKey { - let ebsEncryptionKey: cdk.aws_kms.IKey; - - if (ebsEncryptionConfig.kmsKey) { - ebsEncryptionKey = cdk.aws_kms.Key.fromKeyArn( - this, - pascalCase(ebsEncryptionConfig.kmsKey) + `-KmsKey`, - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${this.props.prefixes.ssmParamName}/kms/${ebsEncryptionConfig.kmsKey}/key-arn`, - ), - ); - } else { - ebsEncryptionKey = new cdk.aws_kms.Key(this, 'EbsEncryptionKey', { - alias: this.acceleratorResourceNames.customerManagedKeys.ebsDefault.alias, - description: this.acceleratorResourceNames.customerManagedKeys.ebsDefault.description, - removalPolicy: cdk.RemovalPolicy.RETAIN, - enableKeyRotation: true, - }); - ebsEncryptionKey.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'Allow service-linked role use', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:GenerateDataKey*', 'kms:ReEncrypt*'], - principals: [ - new cdk.aws_iam.ArnPrincipal( - `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling`, - ), - ], - resources: ['*'], - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'Allow Autoscaling to create grant', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:CreateGrant'], - principals: [ - new cdk.aws_iam.ArnPrincipal( - `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling`, - ), - ], - resources: ['*'], - conditions: { Bool: { 'kms:GrantIsForAWSResource': 'true' } }, - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'Account Access', - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.AccountPrincipal(cdk.Stack.of(this).account)], - actions: ['kms:*'], - resources: ['*'], - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'ec2', - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.AnyPrincipal()], - actions: ['kms:*'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:CallerAccount': cdk.Stack.of(this).account, - 'kms:ViaService': `ec2.${cdk.Stack.of(this).region}.${cdk.Aws.URL_SUFFIX}`, - }, - }, - }), - ); - if (this.props.partition === 'aws') { - ebsEncryptionKey.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'Allow cloud9 service-linked role use', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:GenerateDataKey*', 'kms:ReEncrypt*'], - principals: [ - new cdk.aws_iam.ArnPrincipal( - `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/aws-service-role/cloud9.amazonaws.com/AWSServiceRoleForAWSCloud9`, - ), - ], - resources: ['*'], - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'Allow cloud9 attachment of persistent resources', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:CreateGrant', 'kms:ListGrants', 'kms:RevokeGrant'], - principals: [ - new cdk.aws_iam.ArnPrincipal( - `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/aws-service-role/cloud9.amazonaws.com/AWSServiceRoleForAWSCloud9`, - ), - ], - resources: ['*'], - conditions: { Bool: { 'kms:GrantIsForAWSResource': 'true' } }, - }), - ); - } - } - this.ssmParameters.push({ - logicalId: 'EbsDefaultVolumeEncryptionParameter', - parameterName: `${this.props.prefixes.ssmParamName}/security-stack/ebsDefaultVolumeEncryptionKeyArn`, - stringValue: ebsEncryptionKey.keyArn, - }); - - return ebsEncryptionKey; - } - - /** - * Function to update IAM password policy - */ - private updateIamPasswordPolicy() { - if (this.props.enableSingleAccountMode || this.props.useExistingRoles) { - return; - } else { - if (this.props.globalConfig.homeRegion === cdk.Stack.of(this).region) { - this.logger.info(`Setting the IAM Password policy`); - new PasswordPolicy(this, 'IamPasswordPolicy', { - ...this.props.securityConfig.iamPasswordPolicy, - kmsKey: this.cloudwatchKey, - logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays, - }); - } - } - } - - /** - * Function to config aggregator - */ - private enableConfigAggregation() { - this.logger.info('Enabling Config Aggregation'); - new ConfigAggregation(this, 'EnableConfigAggregation', { - acceleratorPrefix: this.props.prefixes.accelerator, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - this.nagSuppressionInputs.push({ - id: NagSuppressionRuleIds.IAM4, - details: [ - { - path: `${this.stackName}/EnableConfigAggregation/ConfigAggregatorRole/Resource`, - reason: 'AWS Config managed role required.', - }, - ], - }); - } - - private acceleratorMetadataRule( - acceleratorProps: AcceleratorStackProps, - centralLogBucketName: string, - elbLogBucketName: string, - cloudwatchKmsKey?: cdk.aws_kms.IKey, - ): AcceleratorMetadata | undefined { - const isManagementAccountAndHomeRegion = - cdk.Stack.of(this).account === acceleratorProps.accountsConfig.getManagementAccountId() && - cdk.Stack.of(this).region === acceleratorProps.globalConfig.homeRegion; - // if accelerator metadata is not defined in config then return - if (!acceleratorProps.globalConfig.acceleratorMetadata) { - return; - } - // if the stack is not in management account and home region then return - if (!isManagementAccountAndHomeRegion) { - return; - } - const metadataLogBucketName = `${ - this.acceleratorResourceNames.bucketPrefixes.metadata - }-${this.props.accountsConfig.getAccountId(acceleratorProps.globalConfig.acceleratorMetadata?.account)}-${ - this.props.globalConfig.homeRegion - }`; - - return new AcceleratorMetadata(this, 'AcceleratorMetadata', { - acceleratorConfigRepositoryName: acceleratorProps.configRepositoryName, - acceleratorPrefix: this.props.prefixes.accelerator, - acceleratorSsmParamPrefix: this.props.prefixes.ssmParamName, - assumeRole: acceleratorProps.globalConfig.managementAccountAccessRole, - centralLogBucketName, - elbLogBucketName, - cloudwatchKmsKey, - loggingAccountId: acceleratorProps.accountsConfig.getAccountId( - acceleratorProps.globalConfig.acceleratorMetadata.account, - ), - logRetentionInDays: acceleratorProps.globalConfig.cloudwatchLogRetentionInDays, - metadataLogBucketName: metadataLogBucketName, - organizationId: this.organizationId ?? '', - globalRegion: acceleratorProps.globalRegion, - }); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/stacks/tester-pipeline-stack.ts b/source/packages/@aws-accelerator/accelerator/lib/stacks/tester-pipeline-stack.ts deleted file mode 100644 index 1c8bfe1..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/stacks/tester-pipeline-stack.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; - -import { TesterPipeline } from '../tester-pipeline'; - -/** - * TesterPipelineStackProps - */ -export interface TesterPipelineStackProps extends cdk.StackProps { - readonly sourceRepositoryName: string; - readonly sourceBranchName: string; - readonly managementCrossAccountRoleName: string; - readonly qualifier?: string; - readonly managementAccountId?: string; - readonly managementAccountRoleName?: string; - /** - * Accelerator resource name prefixes - */ - readonly prefixes: { - /** - * Accelerator prefix - used for resource name prefix for resources which do not have explicit prefix - */ - readonly accelerator: string; - readonly repoName: string; - readonly bucketName: string; - readonly ssmParamName: string; - readonly kmsAlias: string; - }; -} - -/** - * TesterPipelineStack class - */ -export class TesterPipelineStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: TesterPipelineStackProps) { - super(scope, id, props); - - new TesterPipeline(this, 'TesterPipeline', { - sourceRepositoryName: props.sourceRepositoryName, - sourceBranchName: props.sourceBranchName, - managementCrossAccountRoleName: props.managementCrossAccountRoleName, - qualifier: props.qualifier, - managementAccountId: props.managementAccountId, - managementAccountRoleName: props.managementAccountRoleName, - prefixes: props.prefixes, - }); - - // cdk-nag suppressions - const iam4SuppressionPaths = ['TesterPipeline/DeployAdminRole/Resource']; - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - for (const path of iam4SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { id: 'AwsSolutions-IAM4', reason: 'Managed policies required for IAM role.' }, - ]); - } - - const iam5SuppressionPaths = [ - 'TesterPipeline/DeployAdminRole/DefaultPolicy/Resource', - 'TesterPipeline/PipelineRole/DefaultPolicy/Resource', - 'TesterPipeline/Resource/Source/Source/CodePipelineActionRole/DefaultPolicy/Resource', - 'TesterPipeline/Resource/Source/Configuration/CodePipelineActionRole/DefaultPolicy/Resource', - ]; - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - for (const path of iam5SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { id: 'AwsSolutions-IAM5', reason: 'IAM role requires wildcard permissions.' }, - ]); - } - - // AwsSolutions-CB3: The CodeBuild project has privileged mode enabled. - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/TesterPipeline/TesterProject/Resource`, [ - { - id: 'AwsSolutions-CB3', - reason: 'Pipeline tester project allow access to the Docker daemon.', - }, - ]); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/tester-pipeline.ts b/source/packages/@aws-accelerator/accelerator/lib/tester-pipeline.ts deleted file mode 100644 index b2fd30f..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/tester-pipeline.ts +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as codebuild from 'aws-cdk-lib/aws-codebuild'; -import * as codecommit from 'aws-cdk-lib/aws-codecommit'; -import * as codepipeline from 'aws-cdk-lib/aws-codepipeline'; -import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as s3_assets from 'aws-cdk-lib/aws-s3-assets'; -import { Construct } from 'constructs'; -import fs from 'fs'; -import * as yaml from 'js-yaml'; -import os from 'os'; -import path from 'path'; - -import { Bucket, BucketEncryptionType } from '@aws-accelerator/constructs'; -import * as cdk_extensions from '@aws-cdk-extensions/cdk-extensions'; - -/** - * TesterPipelineProps - */ -export interface TesterPipelineProps { - readonly sourceRepositoryName: string; - readonly sourceBranchName: string; - readonly managementCrossAccountRoleName: string; - readonly qualifier?: string; - readonly managementAccountId?: string; - readonly managementAccountRoleName?: string; - /** - * Accelerator resource name prefixes - */ - readonly prefixes: { - /** - * Use this prefix value to name resources like - - AWS IAM Role names, AWS Lambda Function names, AWS Cloudwatch log groups names, AWS CloudFormation stack names, AWS CodePipeline names, AWS CodeBuild project names - * - */ - readonly accelerator: string; - /** - * Use this prefix value to name AWS CodeCommit repository - */ - readonly repoName: string; - /** - * Use this prefix value to name AWS S3 bucket - */ - readonly bucketName: string; - /** - * Use this prefix value to name AWS SSM parameter - */ - readonly ssmParamName: string; - /** - * Use this prefix value to name AWS KMS alias - */ - readonly kmsAlias: string; - }; -} - -/** - * AWS Accelerator Functional Test Pipeline Class, which creates the pipeline for Accelerator test - */ -export class TesterPipeline extends Construct { - private readonly pipelineRole: iam.Role; - private readonly deployOutput: codepipeline.Artifact; - private readonly acceleratorRepoArtifact: codepipeline.Artifact; - private readonly configRepoArtifact: codepipeline.Artifact; - - constructor(scope: Construct, id: string, props: TesterPipelineProps) { - super(scope, id); - - let targetAcceleratorEnvVariables: { [p: string]: codebuild.BuildEnvironmentVariable } | undefined; - - const tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), 'test-config-assets-')); - fs.writeFileSync(path.join(tempDirPath, 'config.yaml'), yaml.dump({ tests: [] }), 'utf8'); - - const configurationDefaultAssets = new s3_assets.Asset(this, 'ConfigurationDefaultAssets', { - path: tempDirPath, - }); - - const configRepository = new cdk_extensions.Repository(this, 'ConfigRepository', { - repositoryName: `${props.qualifier ?? props.prefixes.repoName}-test-config`, - repositoryBranchName: 'main', - s3BucketName: configurationDefaultAssets.bucket.bucketName, - s3key: configurationDefaultAssets.s3ObjectKey, - description: 'AWS Accelerator functional test configuration repository', - }); - - const cfnRepository = configRepository.node.defaultChild as codecommit.CfnRepository; - cfnRepository.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN, { applyToUpdateReplacePolicy: true }); - - if (props.managementAccountId && props.managementAccountRoleName) { - targetAcceleratorEnvVariables = { - MANAGEMENT_ACCOUNT_ID: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.managementAccountId, - }, - MANAGEMENT_ACCOUNT_ROLE_NAME: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.managementAccountRoleName, - }, - }; - } - - // Get installer key - const installerKey = cdk.aws_kms.Key.fromKeyArn( - this, - 'AcceleratorKey', - cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - props.qualifier - ? `${props.prefixes.ssmParamName}/${props.qualifier}/installer/kms/key-arn` - : `${props.prefixes.ssmParamName}/installer/kms/key-arn`, - ), - ) as cdk.aws_kms.Key; - - const bucket = new Bucket(this, 'SecureBucket', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: `${props.qualifier ?? props.prefixes.bucketName}-tester-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }`, - kmsKey: installerKey, - serverAccessLogsBucketName: cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - props.qualifier - ? `${props.prefixes.ssmParamName}/${props.qualifier}/installer-access-logs-bucket-name` - : `${props.prefixes.ssmParamName}/installer-access-logs-bucket-name`, - ), - }); - - /** - * Functional test pipeline role - */ - this.pipelineRole = new iam.Role(this, 'PipelineRole', { - assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'), - }); - - /** - * Functional test pipeline - */ - const pipeline = new codepipeline.Pipeline(this, 'Resource', { - pipelineName: props.qualifier - ? `${props.qualifier}-tester-pipeline` - : `${props.prefixes.accelerator}-TesterPipeline`, - artifactBucket: bucket.getS3Bucket(), - role: this.pipelineRole, - }); - - this.configRepoArtifact = new codepipeline.Artifact('Config'); - this.acceleratorRepoArtifact = new codepipeline.Artifact('Source'); - - pipeline.addStage({ - stageName: 'Source', - actions: [ - new codepipeline_actions.CodeCommitSourceAction({ - actionName: 'Source', - repository: codecommit.Repository.fromRepositoryName(this, 'SourceRepo', props.sourceRepositoryName), - branch: props.sourceBranchName, - output: this.acceleratorRepoArtifact, - trigger: codepipeline_actions.CodeCommitTrigger.NONE, - }), - new codepipeline_actions.CodeCommitSourceAction({ - actionName: 'Configuration', - repository: configRepository, - branch: 'main', - output: this.configRepoArtifact, - trigger: codepipeline_actions.CodeCommitTrigger.EVENTS, - }), - ], - }); - - /** - * Deploy Stage - */ - const deployRole = new iam.Role(this, 'DeployAdminRole', { - assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'), - managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], - }); - - const testerProject = new codebuild.PipelineProject(this, 'TesterProject', { - projectName: props.qualifier - ? `${props.qualifier}-tester-project` - : `${props.prefixes.accelerator}-TesterProject`, - encryptionKey: installerKey, - role: deployRole, - buildSpec: codebuild.BuildSpec.fromObjectToYaml({ - version: '0.2', - phases: { - install: { - 'runtime-versions': { - nodejs: 18, - }, - }, - build: { - commands: [ - 'cd source', - 'yarn install', - 'yarn build', - 'cd packages/@aws-accelerator/tester', - 'env', - `if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then yarn run cdk deploy --require-approval never --context acceleratorPrefix=${props.prefixes.accelerator} --context account=${cdk.Aws.ACCOUNT_ID} --context region=${cdk.Aws.REGION} --context management-cross-account-role-name=${props.managementCrossAccountRoleName} --context qualifier=${props.qualifier} --context config-dir=$CODEBUILD_SRC_DIR_Config --context management-account-id=${props.managementAccountId} --context management-account-role-name=${props.managementAccountRoleName}; else yarn run cdk deploy --require-approval never --context acceleratorPrefix=${props.prefixes.accelerator} --context account=${cdk.Aws.ACCOUNT_ID} --context region=${cdk.Aws.REGION} --context management-cross-account-role-name=${props.managementCrossAccountRoleName} --context config-dir=$CODEBUILD_SRC_DIR_Config; fi`, - ], - }, - }, - }), - environment: { - buildImage: codebuild.LinuxBuildImage.STANDARD_7_0, - privileged: true, // Allow access to the Docker daemon - computeType: codebuild.ComputeType.MEDIUM, - environmentVariables: { - NODE_OPTIONS: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '--max_old_space_size=4096', - }, - ACCELERATOR_REPOSITORY_NAME: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.sourceRepositoryName, - }, - ACCELERATOR_REPOSITORY_BRANCH_NAME: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.sourceBranchName, - }, - ACCELERATOR_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.accelerator, - }, - ACCELERATOR_REPO_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.repoName, - }, - ACCELERATOR_BUCKET_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.bucketName, - }, - ACCELERATOR_KMS_ALIAS_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.kmsAlias, - }, - ACCELERATOR_SSM_PARAM_NAME_PREFIX: { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.prefixes.ssmParamName, - }, - ...targetAcceleratorEnvVariables, - }, - }, - cache: codebuild.Cache.local(codebuild.LocalCacheMode.SOURCE), - }); - - this.deployOutput = new codepipeline.Artifact('DeployOutput'); - - pipeline.addStage({ - stageName: 'Deploy', - actions: [ - new codepipeline_actions.CodeBuildAction({ - actionName: 'Deploy', - project: testerProject, - input: this.acceleratorRepoArtifact, - extraInputs: [this.configRepoArtifact], - outputs: [this.deployOutput], - role: this.pipelineRole, - }), - ], - }); - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/toolkit.ts b/source/packages/@aws-accelerator/accelerator/lib/toolkit.ts deleted file mode 100644 index c298d6d..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/toolkit.ts +++ /dev/null @@ -1,912 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; -import { BootstrapEnvironmentOptions, Bootstrapper, BootstrapSource } from 'aws-cdk/lib/api/bootstrap'; -import { Deployments } from 'aws-cdk/lib/api/deployments'; -import { StackSelector } from 'aws-cdk/lib/api/cxapp/cloud-assembly'; -import { CloudExecutable } from 'aws-cdk/lib/api/cxapp/cloud-executable'; -import { execProgram } from 'aws-cdk/lib/api/cxapp/exec'; -import { ILock } from 'aws-cdk/lib/api/util/rwlock'; -import { ToolkitInfo } from 'aws-cdk/lib/api/toolkit-info'; -import { CdkToolkit } from 'aws-cdk/lib/cdk-toolkit'; -import { RequireApproval } from 'aws-cdk/lib/diff'; -import { Command, Configuration } from 'aws-cdk/lib/settings'; -import { HotswapMode } from 'aws-cdk/lib/api/hotswap/common'; -import * as fs from 'fs'; -import * as path from 'path'; - -import { cdkOptionsConfig, GlobalConfig } from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { getCloudFormationTemplate } from '@aws-accelerator/utils/lib/get-template'; -import { getAllFilesInPattern, checkDiffFiles } from '@aws-accelerator/utils/lib/common-functions'; -import { printStackDiff } from '@aws-accelerator/utils/lib/diff-stack'; -import { isBeforeBootstrapStage } from '../utils/app-utils'; - -import { AcceleratorStackNames } from './accelerator'; -import { AcceleratorStage } from './accelerator-stage'; - -const logger = createLogger(['toolkit']); -process.on('unhandledRejection', err => { - logger.error(err); - throw new Error('Runtime Error'); -}); - -/** - * CDK toolkit commands - */ -export enum AcceleratorToolkitCommand { - BOOTSTRAP = Command.BOOTSTRAP, - DEPLOY = Command.DEPLOY, - DIFF = Command.DIFF, - SYNTH = Command.SYNTH, - SYNTHESIZE = Command.SYNTHESIZE, -} - -interface Tag { - readonly Key: string; - readonly Value: string; -} -/** - * Accelerator extended CDK toolkit properties - */ -export interface AcceleratorToolkitProps { - /** - * CDK toolkit command - */ - command: string; - /** - * Enable single account deployment - */ - enableSingleAccountMode: boolean; - /** - * The AWS partition - */ - partition: string; - /** - * The accelerator stack prefix value - */ - stackPrefix: string; - /** - * The AWS account ID - */ - accountId?: string; - /** - * The AWS region - */ - region?: string; - /** - * The accelerator stage - */ - stage?: string; - /** - * The accelerator configuration directory path - */ - configDirPath?: string; - /** - * Require approval flag - */ - requireApproval?: RequireApproval; - /** - * Trusted account ID - */ - trustedAccountId?: string; - /** - * App output file location - */ - app?: string; - /** - * CA bundle path - */ - caBundlePath?: string; - /** - * EC2 credentials flag - */ - ec2Creds?: boolean; - /** - * Proxy address - */ - proxyAddress?: string; - /** - * Centralize CDK bootstrapping - */ - centralizeCdkBootstrap?: boolean; - /** - * Custom CDK options for the accelerator - */ - cdkOptions?: cdkOptionsConfig; - /** - * Stack to be deployed. This stack is added to stackName list - * For IMPORT_ASEA_RESOURCES/POST_IMPORT_ASEA_RESOURCES should be ASEA stack name - */ - stack?: string; - - /** - * Tags to be applied for CloudFormation stack - */ - tags?: Tag[]; - - /** - * Use existing roles for deployment - */ - useExistingRoles: boolean; - /** - * Central logs kms key arn - * @remarks - * this is only possible after logging stack is run in centralizedLoggingRegion - * It will be used in - * - logging stack for replication to s3 bucket - * - organizations stack for org trail - * - security-audit stack for AWS config service, SSM session manager, account trail - * - security stack for macie and guard duty - */ - centralLogsBucketKmsKeyArn?: string; - /** - * Accelerator qualifier used for external deployment - */ - qualifier?: string; - /** - * Stack names for custom stack deployment - */ - stackNames?: string[]; -} - -/** - * Wrapper around the CdkToolkit. The Accelerator defines this wrapper to add - * the following functionality: - * - * - Add custom app context and configuration options - * - Enable custom stage-based implementation - */ -export class AcceleratorToolkit { - /** - * - * @returns - */ - static isSupportedCommand(command: string): boolean { - if (command === undefined) { - return false; - } - return Object.values(AcceleratorToolkitCommand).includes(command as unknown as AcceleratorToolkitCommand); - } - - /** - * Accelerator customized execution of the CDKToolkit based on - * aws-cdk/packages/aws-cdk/bin/cdk.ts - * - * - * @param options {@link AcceleratorToolkitProps} - */ - static async execute(options: AcceleratorToolkitProps): Promise { - // - // Validate options - AcceleratorToolkit.validateOptions(options); - - // - // build the context - const context = AcceleratorToolkit.buildExecutionContext(options); - - const configuration = new Configuration({ - commandLineArguments: { - _: [options.command as Command, ...[]], - versionReporting: false, - pathMetadata: false, - assetMetadata: false, - staging: false, - lookups: false, - app: options.app, - context, - }, - }); - await configuration.load(); - - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: configuration.settings.get(['profile']), - ec2creds: options.ec2Creds, - httpOptions: { - proxyAddress: options.proxyAddress, - caBundlePath: options.caBundlePath, - }, - }); - - const deployments = new Deployments({ sdkProvider }); - - let outDirLock: ILock | undefined; - const cloudExecutable = new CloudExecutable({ - configuration, - sdkProvider, - synthesizer: async (aws, config) => { - await outDirLock?.release(); - const { assembly, lock } = await execProgram(aws, config); - outDirLock = lock; - return assembly; - }, - }); - - const toolkitStackName: string = ToolkitInfo.determineName(`${options.stackPrefix}-CDKToolkit`); - - const cli = new CdkToolkit({ - cloudExecutable, - deployments, - configuration, - sdkProvider, - }); - - switch (options.command) { - case Command.BOOTSTRAP: - await AcceleratorToolkit.bootstrapToolKitStacks(context, configuration, toolkitStackName, options); - break; - case Command.DIFF: - await AcceleratorToolkit.diffStacks(options); - break; - - case Command.DEPLOY: - await AcceleratorToolkit.deployStacks(context, toolkitStackName, options); - break; - case Command.SYNTHESIZE: - case Command.SYNTH: - await AcceleratorToolkit.synthStacks(cli, options); - break; - - default: - logger.error(`Unsupported command: ${options.command}`); - throw new Error(`Unsupported command: ${options.command}`); - } - } - /** - * Function that will recursively parse through asset.json file for each stack - * In each file, the files.[fileHash].path is analyzed - * File paths are either files or directories which are - * - in git repo - * - outside git repo - * - * Aim of the parser is to collect all the files locally and change the path prefix - * Path prefix change is needed for asset upload in deploy phase - * - * Aim of render is to take the parsed content and apply the current directory path - * Path prefix is reinstated to allow asset upload before deployment - * - * All the asset.json files are overwritten so synth can be decoupled from stage - */ - static async assetFiles(action: 'render' | 'parse', configDirPath: string) { - // Point to root cdk.out and get all file path of pattern assets.json - const assetFiles = await getAllFilesInPattern(path.join(__dirname, '..', 'cdk.out'), '.assets.json', true); - logger.debug(`Found ${assetFiles.length} asset.json files`); - // Filter out placeholders and undefined stacks which are autogenerated - const filteredFiles = assetFiles.filter(item => !item.includes('placeHolder') && !item.includes('undefined')); - // Process each file asynchronously - const promisesAsset = []; - for (const eachFile of filteredFiles) { - promisesAsset.push(AcceleratorToolkit.processAssetFile(eachFile, action, configDirPath)); - } - // wait till all the files are processed - await Promise.all(promisesAsset); - } - /** - * - * @param assetFile - * @param action - * Function to process each asset json file - * Since concurrent writes are not possible on a single file this function is single threaded - */ - private static async processAssetFile(assetFile: string, action: 'render' | 'parse', configDirPath: string) { - const gitRoot = process.env['CODEBUILD_SRC_DIR'] ?? path.join(__dirname, '..', 'cdk.out'); - const assetJson: AssetManifest = JSON.parse(fs.readFileSync(assetFile, 'utf-8')); - const processAssetPromises = []; - // iterate through entire files array in asset.json to check source path - // Iterate through files and check source packaging - try { - switch (action) { - case 'render': - processAssetPromises.push(AcceleratorToolkit.renderAssets(assetJson, gitRoot, configDirPath)); - break; - case 'parse': - processAssetPromises.push(AcceleratorToolkit.parseAssets(assetJson, gitRoot, configDirPath, assetFile)); - break; - } - // wait till all the fileHash paths are processed - await Promise.all(processAssetPromises); - - // write processedFile - fs.writeFileSync(assetFile, JSON.stringify(assetJson, undefined, 2), 'utf-8'); - } catch (error) { - const msg = `Error processing asset file: ${assetFile} on action: ${action}`; - logger.error(msg); - throw new Error(`${msg}. Got an exception ${JSON.stringify(error)}`); - } - } - private static async parseAssets( - assetJson: AssetManifest, - gitRoot: string, - configDirPath: string, - assetFile: string, - ) { - const parseAssetsPromises = []; - for (const [fileHash, file] of Object.entries(assetJson.files)) { - logger.debug(`Processing file hash ${fileHash}`); - logger.debug(`Config root is ${configDirPath}`); - logger.debug(`Git root is ${gitRoot}`); - // file has path that is in gitRoot - if (file.source.path.includes(gitRoot)) { - // rename the path to GIT_ROOT - file.source.path = file.source.path.replace(gitRoot!, 'GIT_ROOT'); - // file is in config root - } else if (file.source.path.includes(configDirPath)) { - // rename the path to CONFIG_ROOT - file.source.path = file.source.path.replace(configDirPath, 'CONFIG_ROOT'); - // file is not local inside the repo nor in gitRoot or configRoot example: /tmp - } else if ( - file.source.path.includes('/') && - !file.source.path.includes('GIT_ROOT') && - !file.source.path.includes('CONFIG_ROOT') - ) { - parseAssetsPromises.push( - AcceleratorToolkit.moveAssetLocally( - { file, fileHash, assetFile, assetJson }, - { gitRoot, configRoot: configDirPath }, - ), - ); - } - } - // wait for files to move before returning value - await Promise.all(parseAssetsPromises); - return assetJson; - } - private static async renderAssets(assetJson: AssetManifest, gitRoot: string, configDirPath: string) { - for (const file of Object.values(assetJson.files)) { - // file has path that is in gitRoot - if (file.source.path.includes('GIT_ROOT')) { - // rename the path to GIT_ROOT - file.source.path = file.source.path.replace('GIT_ROOT', gitRoot); - // file is in config root - } else if (file.source.path.includes('CONFIG_ROOT')) { - // rename the path to CONFIG_ROOT - file.source.path = file.source.path.replace('CONFIG_ROOT', configDirPath); - } - } - return assetJson; - } - /** - * - * @param file - file object from asset.json - * @param fileHash - file hash - * @param assetFile - asset.json file path - */ - private static async moveAssetLocally( - assetData: { file: File; fileHash: string; assetFile: string; assetJson: AssetManifest }, - paths: { gitRoot: string; configRoot: string }, - ) { - //packaging zip means its a directory that needs to be zipped - if (assetData.file.source.packaging === 'zip') { - await copyDirectory(assetData.file.source.path, path.join(path.dirname(assetData.assetFile), assetData.fileHash)); - // replace file.source.path - assetData.assetJson.files[assetData.fileHash].source.path = path - .join(path.dirname(assetData.assetFile), assetData.fileHash) - .replace(paths.gitRoot, 'GIT_ROOT') - .replace(paths.configRoot, 'CONFIG_ROOT'); - // files are individual entities that need to be copied to local directory - } else if (assetData.file.source.packaging === 'file') { - const destFilePath = path.join( - path.dirname(assetData.assetFile), - assetData.fileHash, - path.parse(assetData.file.source.path).base, - ); - const destDirPath = path.dirname(destFilePath); - // fsPromises does not have existSync - if (!fs.existsSync(destDirPath)) { - fs.mkdirSync(destDirPath, { recursive: true }); - } - fs.copyFileSync(assetData.file.source.path, destFilePath); - // replace file.source.path in asset.json file - assetData.assetJson.files[assetData.fileHash].source.path = destFilePath - .replace(paths.gitRoot, 'GIT_ROOT') - .replace(paths.configRoot, 'CONFIG_ROOT'); - } - return assetData.assetJson; - } - - /** - * Function to validate toolkit execution options - * @param options {@link AcceleratorToolkitProps} - * - */ - private static validateOptions(options: AcceleratorToolkitProps) { - if (options.accountId || options.region) { - if (options.stage) { - logger.info( - `Executing cdk ${options.command} ${options.stage} for aws://${options.accountId}/${options.region}`, - ); - } else { - logger.info(`Executing cdk ${options.command} for aws://${options.accountId}/${options.region}`); - } - } else if (options.stage) { - logger.info(`Executing cdk ${options.command} ${options.stage}`); - } else { - logger.info(`Executing cdk ${options.command}`); - } - } - - /** - * Function to build toolkit execution context - * @param options {@link AcceleratorToolkitProps} - * @returns context string[] - */ - private static buildExecutionContext(options: AcceleratorToolkitProps): string[] { - // build the context - const context: string[] = []; - if (options.configDirPath) { - context.push(`config-dir=${options.configDirPath}`); - } - if (options.stage) { - context.push(`stage=${options.stage}`); - } - if (options.accountId) { - context.push(`account=${options.accountId}`); - } - if (options.region) { - context.push(`region=${options.region}`); - } - if (options.partition) { - context.push(`partition=${options.partition}`); - } - if (options.useExistingRoles) { - context.push(`useExistingRoles=true`); - } - - return context; - } - - /** - * Function to Bootstrap the CDK Toolkit stack in the accounts used by the specified stack(s). - * @param cli {@link CdkToolkit} - * @param configuration {@link Configuration} - * @param toolkitStackName string - * @param options {@link AcceleratorToolkitProps} - */ - private static async bootstrapToolKitStacks( - context: string[], - configuration: Configuration, - toolkitStackName: string, - options: AcceleratorToolkitProps, - ) { - let source: BootstrapSource; - - const environments = [`aws://${options.accountId}/${options.region}`]; - const trustedAccounts: string[] = []; - if (options.trustedAccountId && options.trustedAccountId != options.accountId) { - trustedAccounts.push(options.trustedAccountId); - } - - let bootstrapEnvOptions: BootstrapEnvironmentOptions = { - toolkitStackName: toolkitStackName, - parameters: { - bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']), - kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']), - qualifier: 'accel', - trustedAccounts, - cloudFormationExecutionPolicies: [`arn:${options.partition}:iam::aws:policy/AdministratorAccess`], - }, - }; - const bootstrapStackName = `${AcceleratorStackNames[AcceleratorStage.BOOTSTRAP]}-${options.accountId}-${ - options.region - }`; - - // Use custom bootstrapping template if cdk options are set - if ( - options.centralizeCdkBootstrap || - options.cdkOptions?.centralizeBuckets || - options.cdkOptions?.useManagementAccessRole || - options.cdkOptions?.customDeploymentRole || - options.useExistingRoles - ) { - if (options.cdkOptions?.customDeploymentRole) { - bootstrapEnvOptions = { - ...bootstrapEnvOptions, - force: options.cdkOptions?.forceBootstrap, - }; - } - process.env['CDK_NEW_BOOTSTRAP'] = '1'; - const templatePath = `./cdk.out/${bootstrapStackName}/${bootstrapStackName}.template.json`; - source = { source: 'custom', templateFile: templatePath }; - } else { - source = { source: 'default' }; - } - - const bootstrapper = new Bootstrapper(source); - const cli = await AcceleratorToolkit.getCdkToolKit(context, options, bootstrapStackName); - - try { - await cli.bootstrap(environments, bootstrapper, bootstrapEnvOptions); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.code === 'ExpiredToken' || e.name === 'ExpiredToken') { - throw new Error( - `Credentials expired for account ${options.accountId} in region ${options.region} running command ${options.command}`, - ); - } - } - } - - /** - * Function to validate and get stage name for deploy - * @param options {@link AcceleratorToolkitProps} - */ - private static validateAndGetDeployStage(options: AcceleratorToolkitProps): string { - if (options.stage === undefined) { - logger.error('trying to deploy with an undefined stage'); - throw new Error('trying to deploy with an undefined stage'); - } - - return options.stage; - } - - /** - * Function to initialize stack name which are not dependent on config, such as PIPELINE, TESTER PIPELINE and DIAGNOSTICS_PACK stack name - * @param stageName {@link AcceleratorStage} - * @param props - * @returns - */ - public static getNonConfigDependentStackName( - stageName: AcceleratorStage, - props: { stage: string; accountId?: string; region?: string }, - ) { - if (stageName === AcceleratorStage.DIAGNOSTICS_PACK) { - return process.env['ACCELERATOR_QUALIFIER'] - ? `${process.env['ACCELERATOR_QUALIFIER']}-DiagnosticsPackStack-${props.accountId}-${props.region}` - : `${AcceleratorStackNames[props.stage]}-${props.accountId}-${props.region}`; - } - - return process.env['ACCELERATOR_QUALIFIER'] - ? `${process.env['ACCELERATOR_QUALIFIER']}-${stageName}-stack-${props.accountId}-${props.region}` - : `${AcceleratorStackNames[props.stage]}-${props.accountId}-${props.region}`; - } - - /** - * Function to deploy stacks - * @param cli {@link CdkToolkit} - * @param toolkitStackName string - * @param options {@link AcceleratorToolkitProps} - */ - private static async deployStacks(context: string[], toolkitStackName: string, options: AcceleratorToolkitProps) { - const stackName = await AcceleratorToolkit.getStackNames(options); - let roleArn; - if (!isBeforeBootstrapStage(options.command, options.stage)) { - roleArn = getDeploymentRoleArn({ - account: options.accountId, - region: options.region, - cdkOptions: options.cdkOptions, - partition: options.partition, - }); - } - - const deployPromises: Promise[] = []; - for (const stack of stackName) { - deployPromises.push(AcceleratorToolkit.runDeployStackCli(context, options, stack, toolkitStackName, roleArn)); - } - await Promise.all(deployPromises); - } - - /** - * Function to diff stacks - * @param cli {@link CdkToolkit} - * @param toolkitStackName string - * @param options {@link AcceleratorToolkitProps} - */ - private static async diffStacks(options: AcceleratorToolkitProps) { - const stackName = await AcceleratorToolkit.getStackNames(options); - - const diffPromises: Promise[] = []; - for (const stack of stackName) { - diffPromises.push(AcceleratorToolkit.runDiffStackCli(options, stack)); - } - await Promise.all(diffPromises); - } - /** - * Function to get stack names for bootstrapping - * @param options {@link AcceleratorToolkitProps} - */ - private static async getStackNames(options: AcceleratorToolkitProps): Promise { - if (options.stackNames) { - return options.stackNames; - } - const stageName = AcceleratorToolkit.validateAndGetDeployStage(options); - let stackName = [`${AcceleratorStackNames[stageName]}-${options.accountId}-${options.region}`]; - switch (options.stage) { - case AcceleratorStage.PIPELINE: - stackName = [ - AcceleratorToolkit.getNonConfigDependentStackName(AcceleratorStage.PIPELINE, { - stage: options.stage, - accountId: options.accountId, - region: options.region, - }), - ]; - break; - case AcceleratorStage.TESTER_PIPELINE: - stackName = [ - AcceleratorToolkit.getNonConfigDependentStackName(AcceleratorStage.TESTER_PIPELINE, { - stage: options.stage, - accountId: options.accountId, - region: options.region, - }), - ]; - break; - case AcceleratorStage.DIAGNOSTICS_PACK: - stackName = [ - AcceleratorToolkit.getNonConfigDependentStackName(AcceleratorStage.DIAGNOSTICS_PACK, { - stage: options.stage, - accountId: options.accountId, - region: options.region, - }), - ]; - break; - case AcceleratorStage.KEY: - stackName = [ - `${AcceleratorStackNames[AcceleratorStage.KEY]}-${options.accountId}-${options.region}`, - `${AcceleratorStackNames[AcceleratorStage.DEPENDENCIES]}-${options.accountId}-${options.region}`, - ]; - break; - case AcceleratorStage.NETWORK_VPC: - stackName = [ - `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC_DNS]}-${options.accountId}-${options.region}`, - ]; - break; - case AcceleratorStage.NETWORK_ASSOCIATIONS: - stackName = [ - `${AcceleratorStackNames[AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB]}-${options.accountId}-${options.region}`, - ]; - break; - case AcceleratorStage.CUSTOMIZATIONS: - stackName.push( - `${AcceleratorStackNames[AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT]}-${options.accountId}-${ - options.region - }`, - ); - break; - case AcceleratorStage.IMPORT_ASEA_RESOURCES: - case AcceleratorStage.POST_IMPORT_ASEA_RESOURCES: - stackName = [options.stack!]; - break; - } - - return stackName; - } - /** - * Function to synth stacks - * @param cli {@link CdkToolkit} - * @param toolkitStackName string - * @param options {@link AcceleratorToolkitProps} - */ - private static async synthStacks(cli: CdkToolkit, options: AcceleratorToolkitProps) { - await cli.synth([], false, true).catch(err => { - logger.error(err); - logger.error(`Options were: ${JSON.stringify(options)}`); - throw new Error(`Synthesis of stacks failed`); - }); - } - - private static async getCdkToolKit(context: string[], options: AcceleratorToolkitProps, stackName: string) { - const app = await AcceleratorToolkit.setOutputDirectory(options, stackName); - const configuration = new Configuration({ - commandLineArguments: { - _: [options.command as Command, ...[]], - versionReporting: false, - pathMetadata: false, - assetMetadata: false, - staging: false, - lookups: false, - app, - context, - }, - }); - await configuration.load(); - - const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ - profile: configuration.settings.get(['profile']), - ec2creds: options.ec2Creds, - httpOptions: { - proxyAddress: options.proxyAddress, - caBundlePath: options.caBundlePath, - }, - }); - - const deployments = new Deployments({ sdkProvider }); - - let outDirLock: ILock | undefined; - const cloudExecutable = new CloudExecutable({ - configuration, - sdkProvider, - synthesizer: async (aws, config) => { - await outDirLock?.release(); - const { assembly, lock } = await execProgram(aws, config); - outDirLock = lock; - return assembly; - }, - }); - - return new CdkToolkit({ - cloudExecutable, - deployments, - configuration, - sdkProvider, - }); - } - private static async runDeployStackCli( - context: string[], - options: AcceleratorToolkitProps, - stack: string, - toolkitStackName: string, - roleArn: string | undefined, - ) { - const cli = await AcceleratorToolkit.getCdkToolKit(context, options, stack); - const selector: StackSelector = { - patterns: [stack], - }; - await cli - .deploy({ - selector, - toolkitStackName, - requireApproval: options.requireApproval, - deploymentMethod: { - method: 'change-set', - changeSetName: `${stack}-change-set`, - }, - hotswap: HotswapMode.FULL_DEPLOYMENT, - tags: options.tags, - roleArn: roleArn, - }) - .catch(err => { - logger.error(err); - throw new Error('Deployment failed'); - }); - } - private static async runDiffStackCli(options: AcceleratorToolkitProps, stack: string) { - const saveDirectory = await AcceleratorToolkit.setOutputDirectory(options, stack); - const savePath = path.join(__dirname, '..', saveDirectory!); - const stacksInFolder = await getAllFilesInPattern(savePath, '.template.json'); - - const roleName = GlobalConfig.loadRawGlobalConfig(options.configDirPath!).managementAccountAccessRole; - - for (const eachStack of stacksInFolder) { - logger.debug( - `Running diff for stack ${eachStack} in stage ${options.stage} for account ${options.accountId} in region ${options.region}`, - ); - await getCloudFormationTemplate( - options.accountId!, - options.region!, - options.partition!, - options.stage, - eachStack, - savePath, - roleName, - ); - const stream = fs.createWriteStream(path.join(savePath, `${eachStack}.diff`), { flags: 'w' }); - await stream.write(`\nStack: ${stack} \n`); - await printStackDiff( - path.join(savePath, `${eachStack}.json`), - path.join(savePath, `${eachStack}.template.json`), - false, - 3, - false, - stream, - ); - await stream.close(); - } - await checkDiffFiles(savePath, '.template.json', '.diff'); - } - - private static async setOutputDirectory( - options: AcceleratorToolkitProps, - stackName: string, - ): Promise { - if ( - options.stage === AcceleratorStage.PIPELINE || - options.stage === AcceleratorStage.TESTER_PIPELINE || - options.stage === AcceleratorStage.DIAGNOSTICS_PACK || - options.stage === AcceleratorStage.IMPORT_ASEA_RESOURCES || - options.stage === AcceleratorStage.POST_IMPORT_ASEA_RESOURCES - ) { - return options.app; - } else if (options.stage === AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB) { - return `cdk.out/${ - AcceleratorStackNames[AcceleratorStage.NETWORK_ASSOCIATIONS] - }-${options.accountId!}-${options.region!}`; - } else if ( - options.stage === AcceleratorStage.NETWORK_VPC_ENDPOINTS || - options.stage === AcceleratorStage.NETWORK_VPC - ) { - return `cdk.out/${ - AcceleratorStackNames[AcceleratorStage.NETWORK_VPC_DNS] - }-${options.accountId!}-${options.region!}`; - } else if (options.stage === AcceleratorStage.CUSTOMIZATIONS) { - return `cdk.out/${ - AcceleratorStackNames[AcceleratorStage.CUSTOMIZATIONS] - }-${options.accountId!}-${options.region!}`; - } else { - return `cdk.out/${stackName}`; - } - } -} - -function getDeploymentRoleArn(props: { - account?: string; - region?: string; - cdkOptions?: cdkOptionsConfig; - partition: string; -}) { - if (!props.account || !props.region) { - return; - } - let roleArn; - if (props.cdkOptions?.customDeploymentRole) { - roleArn = `arn:${props.partition}:iam::${props.account}:role/${props.cdkOptions.customDeploymentRole}`; - } - return roleArn; -} - -interface Source { - path: string; - packaging: string; -} - -interface Destination { - bucketName: string; - objectKey: string; - region: string; -} - -interface Destinations { - [key: string]: Destination; -} - -interface File { - source: Source; - destinations: Destinations; -} - -interface Files { - [key: string]: File; -} - -interface AssetManifest { - version: string; - files: Files; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dockerImages: { [key: string]: any }; -} - -function copyDirectory(src: string, dest: string) { - // Create the destination directory if it doesn't exist - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest, { recursive: true }); - } - - // Read the contents of the source directory - const files = fs.readdirSync(src); - - // Loop through each file/directory in the source directory - for (const file of files) { - const srcPath = path.join(src, file); - const destPath = path.join(dest, file); - - // Check if the current item is a file or a directory - const isDirectory = fs.lstatSync(srcPath).isDirectory(); - - if (isDirectory) { - // If it's a directory, recursively copy its contents - copyDirectory(srcPath, destPath); - } else { - // If it's a file, copy it to the destination directory - fs.copyFileSync(srcPath, destPath); - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/lib/validate-environment-config.ts b/source/packages/@aws-accelerator/accelerator/lib/validate-environment-config.ts deleted file mode 100644 index f01b6fb..0000000 --- a/source/packages/@aws-accelerator/accelerator/lib/validate-environment-config.ts +++ /dev/null @@ -1,205 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; -import path = require('path'); -import { NagSuppressions } from 'cdk-nag'; - -export interface ValidateEnvironmentConfigProps { - readonly acceleratorConfigTable: cdk.aws_dynamodb.ITable; - readonly newOrgAccountsTable: cdk.aws_dynamodb.ITable; - readonly newCTAccountsTable: cdk.aws_dynamodb.ITable; - readonly controlTowerEnabled: boolean; - readonly organizationsEnabled: boolean; - readonly commitId: string; - readonly stackName: string; - readonly region: string; - readonly managementAccountId: string; - readonly partition: string; - readonly driftDetectionParameter: cdk.aws_ssm.IParameter; - readonly driftDetectionMessageParameter: cdk.aws_ssm.IParameter; - readonly serviceControlPolicies: { - name: string; - targetType: 'ou' | 'account'; - targets: { name: string; id: string }[]; - }[]; - readonly policyTagKey: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class Validate Environment Config - */ -export class ValidateEnvironmentConfig extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: ValidateEnvironmentConfigProps) { - super(scope, id); - - const stack = cdk.Stack.of(scope); - const VALIDATE_ENVIRONMENT_RESOURCE_TYPE = 'Custom::ValidateEnvironmentConfiguration'; - - const organizationsPolicy = new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - sid: 'OrganizationsLookup', - actions: [ - 'organizations:ListAccounts', - 'servicecatalog:SearchProvisionedProducts', - 'organizations:ListChildren', - 'organizations:ListPoliciesForTarget', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:ListRoots', - 'organizations:ListAccountsForParent', - 'organizations:ListParents', - 'organizations:ListPolicies', - 'organizations:ListTagsForResource', - ], - resources: ['*'], - }); - const ddbPutItemPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'dynamodb', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:PutItem'], - resources: [props.newOrgAccountsTable.tableArn, props.newCTAccountsTable?.tableArn], - }); - const ddbConfigTablePolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'dynamodbConfigTable', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:Query', 'dynamodb:UpdateItem'], - resources: [props.acceleratorConfigTable.tableArn], - }); - const kmsPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'kms', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: [props.newOrgAccountsTable.encryptionKey!.keyArn, props.newCTAccountsTable.encryptionKey!.keyArn], - }); - const cloudformationPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'cloudformation', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['cloudformation:DescribeStacks'], - resources: [ - `arn:${props.partition}:cloudformation:${props.region}:${props.managementAccountId}:stack/${props.stackName}*`, - ], - }); - const ssmPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'sms', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter'], - resources: [props.driftDetectionParameter.parameterArn, props.driftDetectionMessageParameter.parameterArn], - }); - - const providerLambda = new cdk.aws_lambda.Function(this, 'ValidateEnvironmentFunction', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, './lambdas/validate-environment/dist')), - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - description: 'Validate Environment Configuration', - memorySize: 1024, - }); - providerLambda.addToRolePolicy(organizationsPolicy); - providerLambda.addToRolePolicy(ddbPutItemPolicy); - providerLambda.addToRolePolicy(ddbConfigTablePolicy); - providerLambda.addToRolePolicy(kmsPolicy); - providerLambda.addToRolePolicy(cloudformationPolicy); - providerLambda.addToRolePolicy(ssmPolicy); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, VALIDATE_ENVIRONMENT_RESOURCE_TYPE, { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'ValidateEnvironmentResource', { - resourceType: VALIDATE_ENVIRONMENT_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - configTableName: props.acceleratorConfigTable.tableName, - newOrgAccountsTableName: props.newOrgAccountsTable.tableName, - newCTAccountsTableName: props.newCTAccountsTable?.tableName || '', - controlTowerEnabled: props.controlTowerEnabled, - organizationsEnabled: props.organizationsEnabled, - commitId: props.commitId, - stackName: props.stackName, - partition: props.partition, - driftDetectionParameterName: props.driftDetectionParameter.parameterName, - driftDetectionMessageParameterName: props.driftDetectionMessageParameter.parameterName, - serviceControlPolicies: props.serviceControlPolicies, - skipScpValidation: process.env['ACCELERATOR_SKIP_SCP_VALIDATION'] ?? 'no', - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/ValidateEnvironmentConfig/ValidateEnvironmentFunction/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'CDK created resource', - }, - ], - ); - - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/ValidateEnvironmentConfig/${VALIDATE_ENVIRONMENT_RESOURCE_TYPE}/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'CDK created resource', - }, - ], - ); - - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/ValidateEnvironmentConfig/ValidateEnvironmentFunction/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'CDK created resource', - }, - ], - ); - - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/ValidateEnvironmentConfig/${VALIDATE_ENVIRONMENT_RESOURCE_TYPE}/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Policy permissions are part cdk provider framework', - }, - ], - ); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/package.json b/source/packages/@aws-accelerator/accelerator/package.json deleted file mode 100644 index 8bdc36d..0000000 --- a/source/packages/@aws-accelerator/accelerator/package.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "@aws-accelerator/accelerator", - "version": "0.0.0", - "private": true, - "description": "The core of the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "test": "export ACCELERATOR_PREFIX=AWSAccelerator && jest --coverage --ci", - "watch": "tsc -w" - }, - "dependencies": { - "@aws-accelerator/config": "^0.0.0", - "@aws-accelerator/constructs": "^0.0.0", - "@aws-accelerator/utils": "^0.0.0", - "@aws-cdk-extensions/cdk-plugin-assume-role": "^0.0.0", - "@aws-cdk/cloud-assembly-schema": "2.93.0", - "@aws-sdk/client-config-service": "3.410.0", - "@aws-sdk/client-service-quotas": "3.410.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/client-sts": "3.410.0", - "@types/js-yaml": "4.0.5", - "@types/semver": "7.5.0", - "change-case": "4.1.2", - "fp-ts": "2.13.1", - "fs-extra": "11.1.0", - "hash-sum": "2.0.0", - "ip-num": "1.5.0", - "js-yaml": "4.1.0", - "monocle-ts": "2.3.13", - "newtype-ts": "0.3.5", - "pascal-case": "3.1.2", - "semver": "7.5.2", - "tempy": "3.0.0", - "winston": "3.8.2" - }, - "devDependencies": { - "@aws-cdk/cx-api": "2.93.0", - "@aws-cdk/region-info": "2.93.0", - "@types/fs-extra": "11.0.1", - "@types/jest": "^29.4.0", - "@types/mri": "1.1.1", - "@types/node": "18.14.0", - "@types/promptly": "3.0.2", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "cdk-assets": "2.93.0", - "chokidar": "3.5.3", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-n": "15.6.1", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "eslint-plugin-promise": "6.1.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "mri": "1.2.0", - "prettier": "2.8.4", - "promptly": "3.2.0", - "proxy-agent": "6.3.0", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test.ts b/source/packages/@aws-accelerator/accelerator/test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/accounts-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/accounts-stack.test.ts.snap deleted file mode 100644 index f331238..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/accounts-stack.test.ts.snap +++ /dev/null @@ -1,2001 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AccountsStack us-east-1 Construct(AccountsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorGuardrails10AD44C7D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Accelerator GuardRails 1 -", - "key": "REPLACED-JSON-PATH.json", - "name": "AcceleratorGuardrails1", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorGuardrails26DF90F53": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Accelerator GuardRails 2 -", - "key": "REPLACED-JSON-PATH.json", - "name": "AcceleratorGuardrails2", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionA23D700D": { - "DependsOn": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyF69A72FD", - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleC97CFF94", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleC97CFF94", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup6DA63F2D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionA23D700D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleC97CFF94": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyF69A72FD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyF69A72FD", - "Roles": [ - { - "Ref": "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleC97CFF94", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent718EB907": { - "DependsOn": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy8B7FC4A6", - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole9D827C09", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-AccountsStack-111111111111-us-east-1/AccessAnalyzerServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionA23D700D", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole9D827C09", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole9D827C09": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy8B7FC4A6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionA23D700D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionA23D700D", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy8B7FC4A6", - "Roles": [ - { - "Ref": "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole9D827C09", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleResource7C0C5637": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup6DA63F2D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AccessAnalyzerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent718EB907", - "Arn", - ], - }, - "roleName": "AWSServiceRoleForAccessAnalyzer", - "serviceName": "access-analyzer.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "AllowList6FC5040C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "This SCP uses "allow-list" strategy. -", - "key": "REPLACED-JSON-PATH.json", - "name": "AllowList", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "strategy": "allow-list", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachAcceleratorGuardrails1Infrastructure84C16E51": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AcceleratorGuardrails10AD44C7D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "DataPerimeter", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "AcceleratorGuardrails10AD44C7D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-22222222", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachAcceleratorGuardrails2SharedServices7F9682DF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AcceleratorGuardrails26DF90F53", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails2", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "AcceleratorGuardrails26DF90F53", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "444444444444", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachAllowListSecureWorkloadsA2619BAE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AllowList6FC5040C", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AllowList", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "AllowList6FC5040C", - }, - "policyTagKey": "AWSAcceleratorManaged", - "strategy": "allow-list", - "targetId": "ou-asdf-33333333", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachDataPerimeterInfrastructure774B8264": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "DataPerimeter67F0B7E1", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "DataPerimeter", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "DataPerimeter67F0B7E1", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-22222222", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachQuarantineScpFunctionC9C93005": { - "DependsOn": [ - "AttachQuarantineScpFunctionServiceRoleDefaultPolicyE25B3D06", - "AttachQuarantineScpFunctionServiceRole7B57F1A9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to attach quarantine scp to new accounts", - "Environment": { - "Variables": { - "SCP_POLICY_NAME": "Quarantine", - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Role": { - "Fn::GetAtt": [ - "AttachQuarantineScpFunctionServiceRole7B57F1A9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "AttachQuarantineScpFunctionLogGroup5CA6B914": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AttachQuarantineScpFunctionC9C93005", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AttachQuarantineScpFunctionServiceRole7B57F1A9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AttachQuarantineScpFunctionServiceRoleDefaultPolicyE25B3D06": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListPolicies", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrgRead", - }, - { - "Action": "organizations:AttachPolicy", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::111111111111:policy/o-*/service_control_policy/", - { - "Ref": "Quarantine23FF09FE", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::111111111111:account/o-*/*", - ], - ], - }, - ], - "Sid": "OrgWrite", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AttachQuarantineScpFunctionServiceRoleDefaultPolicyE25B3D06", - "Roles": [ - { - "Ref": "AttachQuarantineScpFunctionServiceRole7B57F1A9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateAccountRule97EFD4F3": { - "Properties": { - "Description": "Rule to notify when a new account is created.", - "EventPattern": { - "detail": { - "eventName": [ - "CreateAccount", - ], - "eventSource": [ - "organizations.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.organizations", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "AttachQuarantineScpFunctionC9C93005", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "CreateAccountRuleAllowEventRuleAWSAcceleratorAccountsStack111111111111useast1AttachQuarantineScpFunction9505F8CAFDB46AF6": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AttachQuarantineScpFunctionC9C93005", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "CreateAccountRule97EFD4F3", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "CreateGovCloudAccountRule48208E15": { - "Properties": { - "Description": "Rule to notify when a new account is created using the create govcloud account api.", - "EventPattern": { - "detail": { - "eventName": [ - "CreateGovCloudAccount", - ], - "eventSource": [ - "organizations.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.organizations", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "AttachQuarantineScpFunctionC9C93005", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "CreateGovCloudAccountRuleAllowEventRuleAWSAcceleratorAccountsStack111111111111useast1AttachQuarantineScpFunction9505F8CAA07EFEB1": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AttachQuarantineScpFunctionC9C93005", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "CreateGovCloudAccountRule48208E15", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1": { - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:DisablePolicyType", - "organizations:EnablePolicyType", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribeEffectivePolicy", - "organizations:DescribePolicy", - "organizations:DisableAWSServiceAccess", - "organizations:DetachPolicy", - "organizations:DeletePolicy", - "organizations:DescribeAccount", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:EnableAWSServiceAccess", - "organizations:ListCreateAccountStatus", - "organizations:UpdatePolicy", - "organizations:DescribeOrganizationalUnit", - "organizations:AttachPolicy", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - "organizations:CreatePolicy", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202": { - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:ListPoliciesForTarget", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619": { - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Organizations create policy", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreatePolicy", - "organizations:DeletePolicy", - "organizations:DetachPolicy", - "organizations:ListPolicies", - "organizations:ListTargetsForPolicy", - "organizations:UpdatePolicy", - "organizations:TagResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-hnb659fds-assets-111111111111-us-east-1/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DataPerimeter67F0B7E1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Data Perimeter SCP -", - "key": "REPLACED-JSON-PATH.json", - "name": "DataPerimeter", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunction151BCBCB": { - "DependsOn": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy17F37920", - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole2A9ECD02", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole2A9ECD02", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup362E3CCE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunction151BCBCB", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole2A9ECD02": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy17F37920": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy17F37920", - "Roles": [ - { - "Ref": "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole2A9ECD02", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventFCA8A49C": { - "DependsOn": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyCEB270B2", - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF626C82F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-AccountsStack-111111111111-us-east-1/GuardDutyServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunction151BCBCB", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF626C82F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyCEB270B2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunction151BCBCB", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunction151BCBCB", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyCEB270B2", - "Roles": [ - { - "Ref": "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF626C82F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF626C82F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleResourceD5FE1FBD": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup362E3CCE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "GuardDutyServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventFCA8A49C", - "Arn", - ], - }, - "description": "A service-linked role required for Amazon GuardDuty to access your resources. ", - "roleName": "AWSServiceRoleForAmazonGuardDuty", - "serviceName": "guardduty.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunction64213ACA": { - "DependsOn": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyB782371E", - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole1E0AE3FE", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole1E0AE3FE", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup7DF41D83": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "MacieServiceLinkedRoleCreateServiceLinkedRoleFunction64213ACA", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole1E0AE3FE": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyB782371E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyB782371E", - "Roles": [ - { - "Ref": "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole1E0AE3FE", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventA4030044": { - "DependsOn": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy0779FE88", - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6ACF14A7", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-AccountsStack-111111111111-us-east-1/MacieServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunction64213ACA", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6ACF14A7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6ACF14A7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy0779FE88": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunction64213ACA", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunction64213ACA", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy0779FE88", - "Roles": [ - { - "Ref": "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6ACF14A7", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "MacieServiceLinkedRoleCreateServiceLinkedRoleResourceD2E64172": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup7DF41D83", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "MacieServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventA4030044", - "Arn", - ], - }, - "roleName": "AWSServiceRoleForAmazonMacie", - "serviceName": "macie.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "Quarantine23FF09FE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "This SCP is used to prevent changes to new accounts until the Accelerator has been executed successfully. This policy will be applied upon account creation if enabled. -", - "key": "REPLACED-JSON-PATH.json", - "name": "Quarantine", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunction4EC1E85D": { - "DependsOn": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy18D14A5E", - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleA20B559F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleA20B559F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupD9501DFE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunction4EC1E85D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleA20B559F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy18D14A5E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy18D14A5E", - "Roles": [ - { - "Ref": "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleA20B559F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent26F8E1F4": { - "DependsOn": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy79C6E2DD", - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole57AAF85D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-AccountsStack-111111111111-us-east-1/SecurityHubServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunction4EC1E85D", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole57AAF85D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole57AAF85D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy79C6E2DD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunction4EC1E85D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunction4EC1E85D", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy79C6E2DD", - "Roles": [ - { - "Ref": "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole57AAF85D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleResource4CC7EFAA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupD9501DFE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SecurityHubServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent26F8E1F4", - "Arn", - ], - }, - "description": "A service-linked role required for AWS Security Hub to access your resources.", - "roleName": "AWSServiceRoleForSecurityHub", - "serviceName": "securityhub.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-AccountsStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamQuarantineScpPolicyId88188E20": { - "Properties": { - "Name": "/accelerator/organizations/scp/Quarantine/id", - "Type": "String", - "Value": { - "Ref": "Quarantine23FF09FE", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-AccountsStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "enablePolicyTypeScpB4BC96BE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "SERVICE_CONTROL_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`AccountsStack us-west-2 Construct(AccountsStackUsWest2): Snapshot Test 1`] = ` -{ - "Resources": { - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-AccountsStack-111111111111-us-west-2/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-AccountsStack-111111111111-us-west-2/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/applications-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/applications-stack.test.ts.snap deleted file mode 100644 index 970fbe3..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/applications-stack.test.ts.snap +++ /dev/null @@ -1,459 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ApplicationsStack Construct(ApplicationsStack): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3": { - "DependsOn": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB", - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-444444444444-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "iam:ListRoles", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "stomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB", - "Roles": [ - { - "Ref": "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventF202C45D": { - "DependsOn": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F", - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-444444444444-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-CustomizationsStack-444444444444-us-east-1/Custom::PortfolioAssociationsRoleArnProvider/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F", - "Roles": [ - { - "Ref": "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38": { - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-444444444444-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Portfolio2EC2DefaultSSMADRoleRole": { - "Properties": { - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/EC2-Default-SSM-AD-Role", - ], - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "Portfolio2PermissionSet1444444444444getPermissionSetRoleArnC8C4393D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventF202C45D", - "Arn", - ], - }, - "permissionSetName": "PermissionSet1", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "Portfolio2PermissionSet1PermissionSet": { - "Properties": { - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "PrincipalARN": { - "Fn::GetAtt": [ - "Portfolio2PermissionSet1444444444444getPermissionSetRoleArnC8C4393D", - "roleArn", - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "Portfolio2PortfolioBCD692DB": { - "Properties": { - "DisplayName": "Portfolio-2", - "ProviderName": "LZA", - }, - "Type": "AWS::ServiceCatalog::Portfolio", - }, - "Portfolio2PortfolioPortfolioProductAssociation302916d8118a0471A6BA": { - "Properties": { - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "ProductId": { - "Ref": "Portfolio2PortfolioProduct2Product388CC26D", - }, - }, - "Type": "AWS::ServiceCatalog::PortfolioProductAssociation", - }, - "Portfolio2PortfolioPortfolioShare0cd68f1185a6B0D46AF1": { - "Properties": { - "AccountId": "222222222222", - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "ShareTagOptions": false, - }, - "Type": "AWS::ServiceCatalog::PortfolioShare", - }, - "Portfolio2PortfolioProduct2Product388CC26D": { - "Properties": { - "Description": "Sample product by lZA.", - "Distributor": "LZA", - "Name": "Product2", - "Owner": "LZA", - "ProvisioningArtifactParameters": [ - { - "Description": "product version is v1", - "DisableTemplateValidation": false, - "Info": { - "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-assets-444444444444-us-east-1/1f420304268aa1de8b41800e1b887efb6dcd863358d324c76ea4fe6cbd8e3765.yaml", - }, - }, - "Name": "v1", - }, - ], - "SupportDescription": "Please include account ID and provisioned product ID.", - "SupportEmail": "support@example.com", - "SupportUrl": "https://support.amazon.com", - }, - "Type": "AWS::ServiceCatalog::CloudFormationProduct", - }, - "Portfolio2PropagationPropagateAssociationsBF7C9A38": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D", - "Portfolio2PortfolioPortfolioProductAssociation302916d8118a0471A6BA", - "Portfolio2PortfolioPortfolioShare0cd68f1185a6B0D46AF1", - "Portfolio2PortfolioBCD692DB", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - "Arn", - ], - }, - "crossAccountRole": "AWSAccelerator-CrossAccount-ServiceCatalog-Role", - "partition": { - "Ref": "AWS::Partition", - }, - "portfolioDefinition": "{"name":"Portfolio-2","provider":"LZA","account":"SharedServices","regions":["us-east-1"],"shareTargets":{"accounts":["Audit"]},"portfolioAssociations":[{"type":"Role","name":"EC2-Default-SSM-AD-Role","propagateAssociation":true},{"type":"PermissionSet","name":"PermissionSet1"}],"products":[{"name":"Product2","description":"Sample product by lZA.","owner":"LZA","distributor":"LZA","versions":[{"name":"v1","description":"product version is v1","template":"cloudformation-templates/custom-s3-bucket.yaml","deprecated":false}],"support":{"description":"Please include account ID and provisioned product ID.","email":"support@example.com","url":"https://support.amazon.com"}}]}", - "portfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "shareAccountIds": "222222222222", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::PropagatePortfolioAssociations", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-CustomizationsStack-444444444444-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-CustomizationsStack-444444444444-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/bootstrap-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/bootstrap-stack.test.ts.snap deleted file mode 100644 index 68f158e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/bootstrap-stack.test.ts.snap +++ /dev/null @@ -1,1224 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BootstrapStack Construct(BootstrapStack): Snapshot Test 1`] = ` -{ - "Outputs": { - "BootstrapVersionOutput": { - "Description": "The version of the bootstrap resources that are currently mastered in this stack", - "Value": "18", - }, - "BucketDomainNameOutput": { - "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", - "Value": "cdk-accel-assets-111111111111-us-east-1.s3.us-east-1.amazonaws.com", - }, - "BucketNameOutput": { - "Description": "The name of the S3 bucket owned by the CDK toolkit stack", - "Value": "cdk-accel-assets-111111111111-us-east-1", - }, - "FileAssetKeyArnOutput": { - "Description": "The ARN of the KMS key used to encrypt the asset bucket ", - "Export": { - "Name": "CdkBootstrap-accel-FileAssetKeyArn", - }, - "Value": { - "Fn::GetAtt": [ - "AssetEncryptionKey49BA7B12", - "Arn", - ], - }, - }, - "ImageRepositoryNameOutput": { - "Description": "The name of the ECR repository which hosts docker image assets ", - "Value": "cdk-accel-container-assets-111111111111-us-east-1", - }, - }, - "Parameters": { - "CloudFormationExecutionPolicies": { - "Type": "String", - }, - "ContainerAssetsRepositoryName": { - "Default": "", - "Type": "String", - }, - "FileAssetsBucketKmsKeyId": { - "Default": "", - "Type": "String", - }, - "FileAssetsBucketName": { - "Default": "", - "Type": "String", - }, - "PublicAccessBlockConfiguration": { - "Type": "String", - }, - "Qualifier": { - "Type": "String", - }, - "TrustedAccounts": { - "Type": "CommaDelimitedList", - }, - "TrustedAccountsForLookup": { - "Type": "String", - }, - }, - "Resources": { - "AssetEncryptionKey49BA7B12": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "Key used to encrypt centralized CDK assets", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion", - "kms:GenerateDataKey", - "kms:TagResource", - "kms:UntagResource", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Management Actions", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey", - "kms:Describe*", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow S3 to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/cdk-accel*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSControlTowerExecution", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSA-Deployment", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow org to perform encryption", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "Service": "cloudformation.amazonaws.com", - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AssetEncryptionKeyAliasF99A8AC7": { - "Properties": { - "AliasName": "alias/accelerator/kms/cdk/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AssetEncryptionKey49BA7B12", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "CdkBootstrapVersion": { - "Properties": { - "Name": "/cdk-bootstrap/accel/version", - "Type": "String", - "Value": "18", - }, - "Type": "AWS::SSM::Parameter", - }, - "CloudFormationExecutionRole": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "cloudformation.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - "RoleName": "cdk-accel-cfn-exec-role-111111111111-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "ContainerAssetsRepository": { - "DeletionPolicy": "Retain", - "Properties": { - "ImageTagMutability": "IMMUTABLE", - "RepositoryName": "cdk-accel-container-assets-111111111111-us-east-1", - "RepositoryPolicyText": { - "Statement": [ - { - "Action": [ - "ecr:BatchGetImage", - "ecr:GetDownloadUrlForLayer", - ], - "Condition": { - "StringLike": { - "aws:sourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:us-east-1:111111111111:function:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Sid": "LambdaECRImageRetrievalPolicy-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::ECR::Repository", - "UpdateReplacePolicy": "Retain", - }, - "CustomDeploymentRole": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "cloudformation.amazonaws.com", - }, - }, - { - "Action": "sts:AssumeRole", - "Condition": { - "StringEquals": { - "AWS:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/AWSA-Deployment", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - "RoleName": "AWSA-Deployment", - }, - "Type": "AWS::IAM::Role", - }, - "DeploymentActionRole": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "cloudformation:CreateChangeSet", - "cloudformation:DeleteChangeSet", - "cloudformation:DescribeChangeSet", - "cloudformation:DescribeStacks", - "cloudformation:ExecuteChangeSet", - "cloudformation:CreateStack", - "cloudformation:UpdateStack", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CloudFormationPermissions", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:Abort*", - "s3:DeleteObject*", - "s3:PutObject*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1/*", - ], - ], - }, - ], - "Sid": "PipelineCrossAccountArtifactsBucket", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CloudFormationExecutionRole", - "Arn", - ], - }, - "Sid": "CliPermissions", - }, - { - "Action": [ - "cloudformation:DescribeStackEvents", - "cloudformation:GetTemplate", - "cloudformation:DeleteStack", - "cloudformation:UpdateTerminationProtection", - "sts:GetCallerIdentity", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CfnPermissions", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1/*", - ], - ], - }, - ], - "Sid": "CliStagingBucket", - }, - { - "Action": "ssm:GetParameter", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:111111111111:parameter", - { - "Ref": "CdkBootstrapVersion", - }, - ], - ], - }, - "Sid": "ReadVersion", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "cdk-accel-deploy-role-111111111111-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "FilePublishingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "RoleName": "cdk-accel-file-publishing-role-111111111111-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "FilePublishingRoleDefaultPolicy": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:GetEncryptionConfiguration", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject*", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "cdk-accel-file-publishing-role-default-policy-111111111111-us-east-1", - "Roles": [ - { - "Ref": "FilePublishingRole", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImagePublishingRole": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "RoleName": "cdk-accel-image-publishing-role-111111111111-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "ImagePublishingRoleDefaultPolicy": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:PutImage", - "ecr:InitiateLayerUpload", - "ecr:UploadLayerPart", - "ecr:CompleteLayerUpload", - "ecr:BatchCheckLayerAvailability", - "ecr:DescribeRepositories", - "ecr:DescribeImages", - "ecr:BatchGetImage", - "ecr:GetDownloadUrlForLayer", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ecr:us-east-1:111111111111:repository/cdk-accel-container-assets-111111111111-us-east-1", - ], - ], - }, - }, - { - "Action": "ecr:GetAuthorizationToken", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "cdk-accel-image-publishing-role-default-policy-111111111111-us-east-1", - "Roles": [ - { - "Ref": "ImagePublishingRole", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "LookupRole": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/ReadOnlyAccess", - ], - ], - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "kms:Decrypt", - "Effect": "Deny", - "Resource": "*", - "Sid": "DontReadSecrets", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "LookupRolePolicy", - }, - ], - "RoleName": "cdk-accel-lookup-role-111111111111-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "PipelineCrossAccountArtifactsKey6069D325": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Split": [ - "|", - { - "Fn::Sub": [ - "arn:\${AWS::Partition}:kms:*:\${JoinedAccounts}:*", - { - "JoinedAccounts": { - "Fn::Join": [ - ":*|arn:\${AWS::Partition}:kms:*:", - { - "Ref": "TrustedAccounts", - }, - ], - }, - }, - ], - }, - ], - }, - "Sid": "PipelineCrossAccountArtifactsKey", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineCrossAccountArtifactsKey6069D325", - "Roles": [ - { - "Ref": "DeploymentActionRole", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-BootstrapStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-BootstrapStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "StagingBucket": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "StagingBucket has server access logs disabled until the task for access logging completed.", - }, - { - "id": "AwsSolutions-S10", - "reason": "StagingBucket denies insecure requests via the bucket policy.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "Private", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "AssetEncryptionKey49BA7B12", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": "cdk-accel-assets-111111111111-us-east-1", - "LifecycleConfiguration": { - "Rules": [ - { - "Id": "CleanupOldVersions", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 365, - }, - "Status": "Enabled", - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "StagingBucketPolicy83BEDEEE": { - "Properties": { - "Bucket": { - "Ref": "StagingBucket", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "Service": "cloudformation.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": "s3:*", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/cdk-accel*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSControlTowerExecution", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSA-Deployment", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - "/*", - ], - ], - }, - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - ], - "Sid": "cdk-read-write-access", - }, - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "StagingBucket", - "Arn", - ], - }, - "/*", - ], - ], - }, - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/customizations-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/customizations-stack.test.ts.snap deleted file mode 100644 index 98ec4ab..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/customizations-stack.test.ts.snap +++ /dev/null @@ -1,1133 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CustomizationsStack Construct(CustomizationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AwsAcceleratorCustomCustomS3Stackset": { - "Properties": { - "Capabilities": [ - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM", - "CAPABILITY_AUTO_EXPAND", - ], - "Description": "Sample stackset description", - "OperationPreferences": { - "FailureTolerancePercentage": 25, - "MaxConcurrentPercentage": 35, - "RegionConcurrencyType": "PARALLEL", - }, - "PermissionModel": "SELF_MANAGED", - "StackInstancesGroup": [ - { - "DeploymentTargets": { - "Accounts": [ - "444444444444", - "555555555555", - "111111111111", - ], - }, - "Regions": [ - "us-east-1", - ], - }, - ], - "StackSetName": "Custom-S3-Stackset", - "TemplateURL": { - "Fn::Sub": "https://s3.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-assets-111111111111-us-east-1/1f420304268aa1de8b41800e1b887efb6dcd863358d324c76ea4fe6cbd8e3765.yaml", - }, - }, - "Type": "AWS::CloudFormation::StackSet", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3": { - "DependsOn": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB", - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "iam:ListRoles", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "stomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB", - "Roles": [ - { - "Ref": "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventF202C45D": { - "DependsOn": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F", - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-CustomizationsStack-111111111111-us-east-1/Custom::PortfolioAssociationsRoleArnProvider/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F", - "Roles": [ - { - "Ref": "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38": { - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D": { - "DependsOn": [ - "CustomSharePortfolioWithOrgCustomResourceProviderRoleD968FEC7", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSharePortfolioWithOrgCustomResourceProviderRoleD968FEC7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSharePortfolioWithOrgCustomResourceProviderLogGroup3D78DA1D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSharePortfolioWithOrgCustomResourceProviderRoleD968FEC7": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "servicecatalog:CreatePortfolioShare", - "servicecatalog:UpdatePortfolioShare", - "servicecatalog:DeletePortfolioShare", - "servicecatalog:DescribePortfolioShareStatus", - "organizations:DescribeOrganization", - "organizations:ListParents", - "organizations:ListChildren", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "MyPortfolioAdministratorsGroup": { - "Properties": { - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:group/Administrators", - ], - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "MyPortfolioEC2DefaultSSMADRoleRole": { - "Properties": { - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/EC2-Default-SSM-AD-Role", - ], - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "MyPortfolioMyProductNotificationConstraint": { - "Properties": { - "Description": "Notify topics for My-Product", - "NotificationArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:AWSAccelerator-ControlTowerNotification", - ], - ], - }, - ], - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "ProductId": { - "Ref": "MyPortfolioPortfolioMyProductProduct084EC200", - }, - }, - "Type": "AWS::ServiceCatalog::LaunchNotificationConstraint", - }, - "MyPortfolioPortfolio94625F45": { - "Properties": { - "DisplayName": "My-Portfolio", - "ProviderName": "LZA", - }, - "Type": "AWS::ServiceCatalog::Portfolio", - }, - "MyPortfolioPortfolioMyProductProduct084EC200": { - "Properties": { - "Description": "Sample product by lZA.", - "Distributor": "LZA", - "Name": "My-Product", - "Owner": "LZA", - "ProvisioningArtifactParameters": [ - { - "Description": "product version is v1", - "DisableTemplateValidation": false, - "Info": { - "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-assets-111111111111-us-east-1/1f420304268aa1de8b41800e1b887efb6dcd863358d324c76ea4fe6cbd8e3765.yaml", - }, - }, - "Name": "v1", - }, - ], - "SupportDescription": "Please include account ID and provisioned product ID.", - "SupportEmail": "support@example.com", - "SupportUrl": "https://support.amazon.com", - }, - "Type": "AWS::ServiceCatalog::CloudFormationProduct", - }, - "MyPortfolioPortfolioMyProductProductLaunchConstraint": { - "Properties": { - "LocalRoleName": "testRole", - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "ProductId": { - "Ref": "MyPortfolioPortfolioMyProductProduct084EC200", - }, - }, - "Type": "AWS::ServiceCatalog::LaunchRoleConstraint", - }, - "MyPortfolioPortfolioPortfolioProductAssociation72040103f9e35F6E6349": { - "Properties": { - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "ProductId": { - "Ref": "MyPortfolioPortfolioMyProductProduct084EC200", - }, - }, - "Type": "AWS::ServiceCatalog::PortfolioProductAssociation", - }, - "MyPortfolioPortfolioResourceUpdateConstraint72040103f9e34CF340D2": { - "DependsOn": [ - "MyPortfolioPortfolioPortfolioProductAssociation72040103f9e35F6E6349", - ], - "Properties": { - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "ProductId": { - "Ref": "MyPortfolioPortfolioMyProductProduct084EC200", - }, - "TagUpdateOnProvisionedProduct": "NOT_ALLOWED", - }, - "Type": "AWS::ServiceCatalog::ResourceUpdateConstraint", - }, - "MyPortfolioPropagationPropagateAssociationsDC7707C1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D", - "MyPortfolioPortfolioPortfolioProductAssociation72040103f9e35F6E6349", - "MyPortfolioPortfolio94625F45", - "MyPortfolioPortfolioResourceUpdateConstraint72040103f9e34CF340D2", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - "Arn", - ], - }, - "crossAccountRole": "AWSAccelerator-CrossAccount-ServiceCatalog-Role", - "partition": { - "Ref": "AWS::Partition", - }, - "portfolioDefinition": "{"name":"My-Portfolio","provider":"LZA","account":"Management","regions":["us-east-1","us-west-2"],"shareTargets":{"organizationalUnits":["Security"]},"shareTagOptions":true,"portfolioAssociations":[{"type":"Group","name":"Administrators","propagateAssociation":true},{"type":"Role","name":"EC2-Default-SSM-AD-Role"},{"type":"User","name":"breakGlassUser01"}],"products":[{"name":"My-Product","description":"Sample product by lZA.","owner":"LZA","distributor":"LZA","versions":[{"name":"v1","description":"product version is v1","template":"cloudformation-templates/custom-s3-bucket.yaml","deprecated":false}],"constraints":{"launch":{"type":"LocalRole","role":"testRole"},"tagUpdate":false,"notifications":["AWSAccelerator-ControlTowerNotification"]},"support":{"description":"Please include account ID and provisioned product ID.","email":"support@example.com","url":"https://support.amazon.com"}}]}", - "portfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "shareAccountIds": "333333333333,222222222222", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::PropagatePortfolioAssociations", - "UpdateReplacePolicy": "Delete", - }, - "MyPortfolioSharePortfolioShareouasdf11111111F53E345D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSharePortfolioWithOrgCustomResourceProviderLogGroup3D78DA1D", - "MyPortfolioPortfolioPortfolioProductAssociation72040103f9e35F6E6349", - "MyPortfolioPortfolio94625F45", - "MyPortfolioPortfolioResourceUpdateConstraint72040103f9e34CF340D2", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D", - "Arn", - ], - }, - "organizationId": "", - "organizationalUnitId": "ou-asdf-11111111", - "portfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "tagShareOptions": true, - }, - "Type": "Custom::SharePortfolioWithOrg", - "UpdateReplacePolicy": "Delete", - }, - "MyPortfoliobreakGlassUser01User": { - "Properties": { - "PortfolioId": { - "Ref": "MyPortfolioPortfolio94625F45", - }, - "PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:user/breakGlassUser01", - ], - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-CustomizationsStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-CustomizationsStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; - -exports[`CustomizationsStack Construct(CustomizationsStack): Snapshot Test 2`] = ` -{ - "Resources": { - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3": { - "DependsOn": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB", - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-444444444444-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "iam:ListRoles", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "stomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleDefaultPolicy5A2D45EB", - "Roles": [ - { - "Ref": "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambdaServiceRoleB97F1DC3", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventF202C45D": { - "DependsOn": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F", - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-444444444444-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-CustomizationsStack-444444444444-us-east-1/Custom::PortfolioAssociationsRoleArnProvider/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderCustomPortfolioAssociationsRoleArnProviderProviderLambda303083B3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleDefaultPolicyCFA4199F", - "Roles": [ - { - "Ref": "CustomPortfolioAssociationsRoleArnProviderframeworkonEventServiceRoleBEFB90CD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38": { - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-444444444444-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Portfolio2EC2DefaultSSMADRoleRole": { - "Properties": { - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/EC2-Default-SSM-AD-Role", - ], - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "Portfolio2PermissionSet1444444444444getPermissionSetRoleArnC8C4393D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPortfolioAssociationsRoleArnProviderframeworkonEventF202C45D", - "Arn", - ], - }, - "permissionSetName": "PermissionSet1", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "Portfolio2PermissionSet1PermissionSet": { - "Properties": { - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "PrincipalARN": { - "Fn::GetAtt": [ - "Portfolio2PermissionSet1444444444444getPermissionSetRoleArnC8C4393D", - "roleArn", - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "Portfolio2PortfolioBCD692DB": { - "Properties": { - "DisplayName": "Portfolio-2", - "ProviderName": "LZA", - }, - "Type": "AWS::ServiceCatalog::Portfolio", - }, - "Portfolio2PortfolioPortfolioProductAssociation302916d8118a0471A6BA": { - "Properties": { - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "ProductId": { - "Ref": "Portfolio2PortfolioProduct2Product388CC26D", - }, - }, - "Type": "AWS::ServiceCatalog::PortfolioProductAssociation", - }, - "Portfolio2PortfolioPortfolioShare0cd68f1185a6B0D46AF1": { - "Properties": { - "AccountId": "222222222222", - "PortfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "ShareTagOptions": false, - }, - "Type": "AWS::ServiceCatalog::PortfolioShare", - }, - "Portfolio2PortfolioProduct2Product388CC26D": { - "Properties": { - "Description": "Sample product by lZA.", - "Distributor": "LZA", - "Name": "Product2", - "Owner": "LZA", - "ProvisioningArtifactParameters": [ - { - "Description": "product version is v1", - "DisableTemplateValidation": false, - "Info": { - "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-assets-444444444444-us-east-1/1f420304268aa1de8b41800e1b887efb6dcd863358d324c76ea4fe6cbd8e3765.yaml", - }, - }, - "Name": "v1", - }, - ], - "SupportDescription": "Please include account ID and provisioned product ID.", - "SupportEmail": "support@example.com", - "SupportUrl": "https://support.amazon.com", - }, - "Type": "AWS::ServiceCatalog::CloudFormationProduct", - }, - "Portfolio2PropagationPropagateAssociationsBF7C9A38": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D", - "Portfolio2PortfolioPortfolioProductAssociation302916d8118a0471A6BA", - "Portfolio2PortfolioPortfolioShare0cd68f1185a6B0D46AF1", - "Portfolio2PortfolioBCD692DB", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - "Arn", - ], - }, - "crossAccountRole": "AWSAccelerator-CrossAccount-ServiceCatalog-Role", - "partition": { - "Ref": "AWS::Partition", - }, - "portfolioDefinition": "{"name":"Portfolio-2","provider":"LZA","account":"SharedServices","regions":["us-east-1"],"shareTargets":{"accounts":["Audit"]},"portfolioAssociations":[{"type":"Role","name":"EC2-Default-SSM-AD-Role","propagateAssociation":true},{"type":"PermissionSet","name":"PermissionSet1"}],"products":[{"name":"Product2","description":"Sample product by lZA.","owner":"LZA","distributor":"LZA","versions":[{"name":"v1","description":"product version is v1","template":"cloudformation-templates/custom-s3-bucket.yaml","deprecated":false}],"support":{"description":"Please include account ID and provisioned product ID.","email":"support@example.com","url":"https://support.amazon.com"}}]}", - "portfolioId": { - "Ref": "Portfolio2PortfolioBCD692DB", - }, - "shareAccountIds": "222222222222", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::PropagatePortfolioAssociations", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-CustomizationsStack-444444444444-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-CustomizationsStack-444444444444-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/dependencies-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/dependencies-stack.test.ts.snap deleted file mode 100644 index df1561a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/dependencies-stack.test.ts.snap +++ /dev/null @@ -1,220 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DependenciesStack Construct(DependenciesStack): Snapshot Test 1`] = ` -{ - "Resources": { - "LzaManagedPolicy01FDAB5303": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policies definition are derived from accelerator iam-config boundary-policy file", - }, - ], - }, - }, - "Properties": { - "Description": "", - "ManagedPolicyName": "lzaManagedPolicy01", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:CreateBucket", - "s3:PutBucketPolicy", - ], - "Effect": "Allow", - "Resource": "arn:aws:s3:::rcc-bucket-trustedservice-do-not-delete-*", - }, - { - "Action": "s3:ListAllMyBuckets", - "Effect": "Allow", - "Resource": "arn:aws:s3:::*", - }, - { - "Action": [ - "config:DescribeConfigurationRecorders", - "config:DescribeConfigurationRecorderStatus", - "config:PutConfigurationRecorder", - "config:StartConfigurationRecorder", - "config:DescribeDeliveryChannels", - "config:DescribeDeliveryChannelStatus", - "config:PutDeliveryChannel", - "config:DescribeAggregationAuthorizations", - "config:PutAggregationAuthorization", - "config:ListDiscoveredResources", - "config:BatchGetResourceConfig", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:GetRole", - "iam:PassRole", - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - "iam:GetServiceLinkedRoleDeletionStatus", - ], - "Effect": "Allow", - "Resource": "arn:aws:iam::*:role/aws-service-role/config.amazonaws.com/*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "LzaManagedPolicy027214A961": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policies definition are derived from accelerator iam-config boundary-policy file", - }, - ], - }, - }, - "Properties": { - "Description": "", - "ManagedPolicyName": "lzaManagedPolicy02", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:CreateBucket", - "s3:PutBucketPolicy", - ], - "Effect": "Allow", - "Resource": "arn:aws:s3:::rcc-bucket-trustedservice-do-not-delete-*", - }, - { - "Action": "s3:ListAllMyBuckets", - "Effect": "Allow", - "Resource": "arn:aws:s3:::*", - }, - { - "Action": [ - "config:DescribeConfigurationRecorders", - "config:DescribeConfigurationRecorderStatus", - "config:PutConfigurationRecorder", - "config:StartConfigurationRecorder", - "config:DescribeDeliveryChannels", - "config:DescribeDeliveryChannelStatus", - "config:PutDeliveryChannel", - "config:DescribeAggregationAuthorizations", - "config:PutAggregationAuthorization", - "config:ListDiscoveredResources", - "config:BatchGetResourceConfig", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:GetRole", - "iam:PassRole", - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - "iam:GetServiceLinkedRoleDeletionStatus", - ], - "Effect": "Allow", - "Resource": "arn:aws:iam::*:role/aws-service-role/config.amazonaws.com/*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "PutSsmParameterRoleEF99BE78": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "This role is required to give permissions to put/delete SSM parameters across accounts and regions", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:PutParameter", - "ssm:DeleteParameter", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "arn:aws:ssm:*:*:parameter/accelerator*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-CrossAccountSsmParameterShare", - }, - "Type": "AWS::IAM::Role", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-DependenciesStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-DependenciesStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/diagnostics-pack-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/diagnostics-pack-stack.test.ts.snap deleted file mode 100644 index acbbff0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/diagnostics-pack-stack.test.ts.snap +++ /dev/null @@ -1,511 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DiagnosticsPackStack Construct(DiagnosticsPackStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/aws-accelerator/installer/kms/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "DiagnosticsFunctionF6482E72": { - "DependsOn": [ - "DiagnosticsPackLambdaRoleDefaultPolicy93B3F548", - "DiagnosticsPackLambdaRole5CC68755", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-000000000000-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator diagnostics report lambda function.", - "Environment": { - "Variables": { - "CONFIG_REPO_NAME": "aws-accelerator-config", - "DAYS_PIPELINE_IN_FAILED_STATUS": "1", - "HOME_REGION": "us-east-1", - "INSTALLER_STACK_NAME": "AWSAccelerator-InstallerStack", - "MANAGEMENT_ACCOUNT_ROLE_NAME": "AWSAccelerator-DiagnosticsPackAccessRole", - "PARTITION": { - "Ref": "AWS::Partition", - }, - "PIPELINE_ACCOUNT_ID": "000000000000", - "REPORT_BUCKET_NAME": "aws-accelerator-installer-000000000000-us-east-1", - }, - }, - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "DiagnosticsPackLambdaRole5CC68755", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "DiagnosticsFunctionLogGroup65B6B182": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "DiagnosticsFunctionF6482E72", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "DiagnosticsPackLambdaRole5CC68755": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "lambda.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - "RoleName": "aws-accelerator-DiagnosticsPackLambdaRole", - }, - "Type": "AWS::IAM::Role", - }, - "DiagnosticsPackLambdaRoleDefaultPolicy93B3F548": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-DiagnosticsPackAccessRole", - ], - ], - }, - }, - { - "Action": "sts:GetCallerIdentity", - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowStsCallerIdentityActions", - }, - { - "Action": [ - "cloudformation:DescribeStackEvents", - "cloudformation:DescribeStacks", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cloudformation:us-east-1:000000000000:stack/aws-accelerator*", - ], - ], - }, - "Sid": "CloudformationAccess", - }, - { - "Action": "codecommit:GetFile", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:us-east-1:000000000000:aws-accelerator-config", - ], - ], - }, - "Sid": "CodecommitAccess", - }, - { - "Action": "codepipeline:GetPipelineState", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:us-east-1:000000000000:aws-accelerator*", - ], - ], - }, - "Sid": "CodepipelineAccess", - }, - { - "Action": "logs:FilterLogEvents", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/aws-accelerator*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/lambda/aws-accelerator*", - ], - ], - }, - ], - "Sid": "CloudwatchLogsAccess", - }, - { - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:AbortMultipartUpload", - "s3:ListBucket", - "s3:DeleteObject", - "s3:GetObjectVersion", - "s3:ListMultipartUploadParts", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-installer-000000000000-us-east-1", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-installer-000000000000-us-east-1/*", - ], - ], - }, - ], - "Sid": "DiagnosticsBucketWriteAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "DiagnosticsPackLambdaRoleDefaultPolicy93B3F548", - "Roles": [ - { - "Ref": "DiagnosticsPackLambdaRole5CC68755", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "DiagnosticsProject5D286B2E": { - "Properties": { - "Artifacts": { - "Type": "NO_ARTIFACTS", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "Description": "Accelerator diagnostic project. You can execute this project to generate an error report and store it into the aws-accelerator-installer-000000000000-us-east-1 bucket.", - "EncryptionKey": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_SMALL", - "EnvironmentVariables": [ - { - "Name": "DAYS_PIPELINE_IN_FAILED_STATUS", - "Type": "PLAINTEXT", - "Value": "1", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": "aws-accelerator-DiagnosticProject", - "ServiceRole": { - "Fn::GetAtt": [ - "DiagnosticsProjectRole29C23DD8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - build: - commands: - - set -e - - aws lambda update-function-configuration --function-name ", - { - "Fn::GetAtt": [ - "DiagnosticsFunctionF6482E72", - "Arn", - ], - }, - " --region us-east-1 --environment Variables="{INSTALLER_STACK_NAME=AWSAccelerator-InstallerStack,HOME_REGION=us-east-1,PIPELINE_ACCOUNT_ID=000000000000,PARTITION=", - { - "Ref": "AWS::Partition", - }, - ",DAYS_PIPELINE_IN_FAILED_STATUS=$DAYS_PIPELINE_IN_FAILED_STATUS,REPORT_BUCKET_NAME=aws-accelerator-installer-000000000000-us-east-1,CONFIG_REPO_NAME=aws-accelerator-config,MANAGEMENT_ACCOUNT_ROLE_NAME=AWSAccelerator-DiagnosticsPackAccessRole}" --output text - - aws lambda wait function-updated --function-name ", - { - "Fn::GetAtt": [ - "DiagnosticsFunctionF6482E72", - "Arn", - ], - }, - " --region us-east-1 - - aws lambda invoke --function-name ", - { - "Fn::GetAtt": [ - "DiagnosticsFunctionF6482E72", - "Arn", - ], - }, - "REPLACED-JSON-PATH.json", - ], - ], - }, - "Type": "NO_SOURCE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "DiagnosticsProjectRole29C23DD8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "DiagnosticsProjectRoleDefaultPolicyEA9CD8D1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Diagnostic CodeBuild project role.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/", - { - "Ref": "DiagnosticsProject5D286B2E", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/", - { - "Ref": "DiagnosticsProject5D286B2E", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:us-east-1:000000000000:report-group/", - { - "Ref": "DiagnosticsProject5D286B2E", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:UpdateFunctionConfiguration", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "DiagnosticsFunctionF6482E72", - "Arn", - ], - }, - "Sid": "AllowLambdaAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "DiagnosticsProjectRoleDefaultPolicyEA9CD8D1", - "Roles": [ - { - "Ref": "DiagnosticsProjectRole29C23DD8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/finalize-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/finalize-stack.test.ts.snap deleted file mode 100644 index 58fa96a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/finalize-stack.test.ts.snap +++ /dev/null @@ -1,990 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FinalizeStack Construct(FinalizeStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratororganizationsscpQuarantineidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/organizations/scp/Quarantine/id", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorCommitIdParameterEF286FB9": { - "Properties": { - "Description": "The commit hash of the latest aws-accelerator-config commit to deploy successfully", - "Name": "/accelerator/configuration/configCommitId", - "Type": "String", - "Value": "e3cdaecaa6073ad9e4721344cd109eb6de351cfb", - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorGuardrails10AD44C7D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Accelerator GuardRails 1 -", - "key": "REPLACED-JSON-PATH.json", - "name": "AcceleratorGuardrails1", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorGuardrails26DF90F53": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Accelerator GuardRails 2 -", - "key": "REPLACED-JSON-PATH.json", - "name": "AcceleratorGuardrails2", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "AllowList6FC5040C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "This SCP uses "allow-list" strategy. -", - "key": "REPLACED-JSON-PATH.json", - "name": "AllowList", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "strategy": "allow-list", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachAcceleratorGuardrails1Infrastructure84C16E51": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AcceleratorGuardrails10AD44C7D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "DataPerimeter", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "AcceleratorGuardrails10AD44C7D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-22222222", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachAcceleratorGuardrails2SharedServices7F9682DF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AcceleratorGuardrails26DF90F53", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails2", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "AcceleratorGuardrails26DF90F53", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "444444444444", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachAllowListSecureWorkloadsA2619BAE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AllowList6FC5040C", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AllowList", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "AllowList6FC5040C", - }, - "policyTagKey": "AWSAcceleratorManaged", - "strategy": "allow-list", - "targetId": "ou-asdf-33333333", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachDataPerimeterInfrastructure774B8264": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "DataPerimeter67F0B7E1", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "DataPerimeter", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "DataPerimeter67F0B7E1", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-22222222", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "CustomDetachQuarantineScpCustomResourceProviderHandlerA1F1C263": { - "DependsOn": [ - "CustomDetachQuarantineScpCustomResourceProviderRoleE5C433C1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetachQuarantineScpCustomResourceProviderRoleE5C433C1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetachQuarantineScpCustomResourceProviderLogGroupD9E0EA42": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetachQuarantineScpCustomResourceProviderHandlerA1F1C263", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDetachQuarantineScpCustomResourceProviderRoleE5C433C1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "organizations", - }, - { - "Action": [ - "organizations:DetachPolicy", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:organizations::111111111111:policy/o-*/service_control_policy/", - { - "Ref": "SsmParameterValueacceleratororganizationsscpQuarantineidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "arn:aws:organizations::111111111111:account/o-*/*", - ], - "Sid": "detach", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1": { - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:DisablePolicyType", - "organizations:EnablePolicyType", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribeEffectivePolicy", - "organizations:DescribePolicy", - "organizations:DisableAWSServiceAccess", - "organizations:DetachPolicy", - "organizations:DeletePolicy", - "organizations:DescribeAccount", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:EnableAWSServiceAccess", - "organizations:ListCreateAccountStatus", - "organizations:UpdatePolicy", - "organizations:DescribeOrganizationalUnit", - "organizations:AttachPolicy", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - "organizations:CreatePolicy", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202": { - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:ListPoliciesForTarget", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619": { - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Organizations create policy", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreatePolicy", - "organizations:DeletePolicy", - "organizations:DetachPolicy", - "organizations:ListPolicies", - "organizations:ListTargetsForPolicy", - "organizations:UpdatePolicy", - "organizations:TagResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-hnb659fds-assets-111111111111-us-east-1/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DataPerimeter67F0B7E1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Data Perimeter SCP -", - "key": "REPLACED-JSON-PATH.json", - "name": "DataPerimeter", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "DetachQuarantineScpD09A8004": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetachQuarantineScpCustomResourceProviderLogGroupD9E0EA42", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetachQuarantineScpCustomResourceProviderHandlerA1F1C263", - "Arn", - ], - }, - "scpPolicyId": { - "Ref": "SsmParameterValueacceleratororganizationsscpQuarantineidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::DetachQuarantineScp", - "UpdateReplacePolicy": "Delete", - }, - "Quarantine23FF09FE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeScpB4BC96BE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "This SCP is used to prevent changes to new accounts until the Accelerator has been executed successfully. This policy will be applied upon account creation if enabled. -", - "key": "REPLACED-JSON-PATH.json", - "name": "Quarantine", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "RevertScpChangesModifyScpRule4ECCD6B5": { - "Properties": { - "Description": "Rule to notify when an LZA-managed SCP is modified or detached.", - "EventPattern": { - "detail": { - "eventName": [ - "AttachPolicy", - "DetachPolicy", - "UpdatePolicy", - ], - "eventSource": [ - "organizations.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.organizations", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "RevertScpChangesRevertScpChangesFunction5EF82185", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "RevertScpChangesModifyScpRuleAllowEventRuleAWSAcceleratorFinalizeStack111111111111useast1RevertScpChangesRevertScpChangesFunctionB38BE768D9301B84": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "RevertScpChangesRevertScpChangesFunction5EF82185", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "RevertScpChangesModifyScpRule4ECCD6B5", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "RevertScpChangesRevertScpChangesFunction5EF82185": { - "DependsOn": [ - "RevertScpChangesRevertScpChangesFunctionServiceRoleDefaultPolicyF3C3ECD9", - "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to revert changes made to LZA-controlled service control policies", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-east-1", - "ORGANIZATIONS_ENABLED": "true", - "SINGLE_ACCOUNT_MODE": "false", - "SNS_TOPIC_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Role": { - "Fn::GetAtt": [ - "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "RevertScpChangesRevertScpChangesFunctionLogGroup92A9E8AA": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "RevertScpChangesRevertScpChangesFunction5EF82185", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RevertScpChangesRevertScpChangesFunctionServiceRoleDefaultPolicyF3C3ECD9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Encrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "kmsEncryptMessage", - }, - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:DescribePolicy", - "organizations:ListAccounts", - "organizations:ListRoots", - "organizations:ListOrganizationalUnitsForParent", - "organizations:UpdatePolicy", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrgPolicyUpdate", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - "Sid": "snsPublishMessage", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "RevertScpChangesRevertScpChangesFunctionServiceRoleDefaultPolicyF3C3ECD9", - "Roles": [ - { - "Ref": "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-FinalizeStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-FinalizeStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "enablePolicyTypeScpB4BC96BE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "SERVICE_CONTROL_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/identity-center-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/identity-center-stack.test.ts.snap deleted file mode 100644 index e9e847c..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/identity-center-stack.test.ts.snap +++ /dev/null @@ -1,714 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IdentityCenterStack Construct(IdentityCenterStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850": { - "DependsOn": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderRoleAEF569D6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderRoleAEF569D6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIdentityCenterAssignmentsCustomResourceProviderLogGroup0BC21EA5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIdentityCenterAssignmentsCustomResourceProviderRoleAEF569D6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:ListRoles", - "iam:ListPolicies", - "identitystore:ListGroups", - "identitystore:ListUsers", - "sso:CreateAccountAssignment", - "sso:DeleteAccountAssignment", - "sso:ListAccountAssignments", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:GetSAMLProvider", - "iam:UpdateSAMLProvider", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:saml-provider/AWSSSO_*_DO_NOT_DELETE", - ], - ], - }, - }, - { - "Action": [ - "iam:AttachRolePolicy", - "iam:CreateRole", - "iam:DeleteRole", - "iam:DeleteRolePolicy", - "iam:DetachRolePolicy", - "iam:GetRole", - "iam:ListAttachedRolePolicies", - "iam:ListRolePolicies", - "iam:PutRolePolicy", - "iam:UpdateRole", - "iam:UpdateRoleDescription", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/aws-reserved/sso.amazonaws.com/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAssignmentAssignment1DDEF509E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderLogGroup0BC21EA5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850", - "Arn", - ], - }, - "accountIds": [ - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "identityStoreId": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "identityStoreId", - ], - }, - "instanceArn": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "instanceArn", - ], - }, - "permissionSetArn": { - "Fn::GetAtt": [ - "PermissionSet1IdentityCenterPermissionSet", - "PermissionSetArn", - ], - }, - "principalId": "REPLACED-UUID", - "principalType": "USER", - "principals": [ - { - "name": "lza-accelerator-user-01", - "type": "USER", - }, - { - "name": "lza-accelerator-group-01", - "type": "GROUP", - }, - { - "name": "lza-accelerator-user-02", - "type": "USER", - }, - { - "name": "lza-accelerator-group-02", - "type": "GROUP", - }, - ], - }, - "Type": "Custom::IdentityCenterAssignments", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterAssignmentAssignment209F9E262": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderLogGroup0BC21EA5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850", - "Arn", - ], - }, - "accountIds": [ - "333333333333", - "222222222222", - ], - "identityStoreId": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "identityStoreId", - ], - }, - "instanceArn": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "instanceArn", - ], - }, - "permissionSetArn": { - "Fn::GetAtt": [ - "PermissionSet1IdentityCenterPermissionSet", - "PermissionSetArn", - ], - }, - "principalId": "REPLACED-UUID", - "principalType": "GROUP", - }, - "Type": "Custom::IdentityCenterAssignments", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterIdentityStoreIdSsmParameter04D047E5": { - "Properties": { - "Name": "/accelerator/organization/security/identity-center/identity-store-id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "identityStoreId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "IdentityCenterInstanceArnSsmParameterE98084E0": { - "Properties": { - "Name": "/accelerator/organization/security/identity-center/instance-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "instanceArn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54": { - "DependsOn": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleDefaultPolicyF2FD593D", - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed IdentityCenterGetInstanceId custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionResourceLogGroup043D5A24": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleDefaultPolicyF2FD593D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sso:ListInstances", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleDefaultPolicyF2FD593D", - "Roles": [ - { - "Ref": "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionResourceLogGroup043D5A24", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEvent5DB81BA0", - "Arn", - ], - }, - "debug": false, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEvent5DB81BA0": { - "DependsOn": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRoleDefaultPolicyA09146FD", - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-IdentityCenterStack-111111111111-us-east-1/IdentityCenterInstance/IdentityCenterGetInstanceId/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRoleDefaultPolicyA09146FD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRoleDefaultPolicyA09146FD", - "Roles": [ - { - "Ref": "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PermissionSet1IdentityCenterPermissionSet": { - "Properties": { - "CustomerManagedPolicyReferences": [ - { - "Name": "ResourceConfigurationCollectorPolicy", - }, - { - "Name": "lzaManagedPolicy01", - }, - { - "Name": "lzaManagedPolicy02", - }, - ], - "InlinePolicy": { - "Statement": [ - { - "Action": [ - "s3:ListBucket", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "Statement1", - }, - ], - "Version": "2012-10-17", - }, - "InstanceArn": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "instanceArn", - ], - }, - "ManagedPolicies": [ - "arn:aws:iam::aws:policy/AdministratorAccess", - "arn:aws:iam::aws:policy/CloudFrontFullAccess", - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/PowerUserAccess", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/ReadOnlyAccess", - ], - ], - }, - ], - "Name": "PermissionSet1", - "PermissionsBoundary": { - "ManagedPolicyArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/PowerUserAccess", - ], - ], - }, - }, - }, - "Type": "AWS::SSO::PermissionSet", - }, - "PermissionSet2IdentityCenterPermissionSet": { - "DependsOn": [ - "PermissionSet1IdentityCenterPermissionSet", - ], - "Properties": { - "CustomerManagedPolicyReferences": [ - { - "Name": "ResourceConfigurationCollectorPolicy", - }, - { - "Name": "lzaManagedPolicy01", - }, - ], - "InlinePolicy": { - "Statement": [ - { - "Action": [ - "s3:ListBucket", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "Statement1", - }, - ], - "Version": "2012-10-17", - }, - "InstanceArn": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B", - "instanceArn", - ], - }, - "ManagedPolicies": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/PowerUserAccess", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/ReadOnlyAccess", - ], - ], - }, - ], - "Name": "PermissionSet2", - "PermissionsBoundary": { - "CustomerManagedPolicyReference": { - "Name": "lzaManagedPolicy01", - "Path": "/", - }, - }, - "SessionDuration": "PT1H0M", - }, - "Type": "AWS::SSO::PermissionSet", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-IdentityCenterStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-IdentityCenterStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/key-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/key-stack.test.ts.snap deleted file mode 100644 index ac7d3ee..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/key-stack.test.ts.snap +++ /dev/null @@ -1,421 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`KeyStack Construct(KeyStack): Snapshot Test 1`] = ` -{ - "Resources": { - "AcceleratorKeyAlias692B87F4": { - "Properties": { - "AliasName": "alias/accelerator/kms/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorKeyF6E5723B", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorKeyF6E5723B": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Decrypt*", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:*:log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch logs to use the encryption key", - }, - { - "Action": "kms:CreateGrant", - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - "StringLike": { - "aws:PrincipalOrgID": "o-asdf123456", - "kms:ViaService": "auditmanager.*.amazonaws.com", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Audit Manager service to provision encryption key grants", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Sns service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Lambda service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Cloudwatch service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sqs.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Sqs service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Macie service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "guardduty.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Guardduty service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "auditmanager.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow AuditManager service to use the encryption key", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorKmsArnParameter5898E452": { - "Properties": { - "Name": "/accelerator/kms/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorKeyF6E5723B", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CrossAccountAcceleratorSsmParamAccessRoleEC51D0AC": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "This policy is required to give access to ssm parameters in every region where accelerator deployed. Various accelerator roles need permission to describe SSM parameters.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:*:222222222222:parameter/accelerator/kms/key-arn", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:*:222222222222:parameter/accelerator/kms/s3/key-arn", - ], - ], - }, - ], - }, - { - "Action": "ssm:DescribeParameters", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-CrossAccount-SsmParameter-Role", - }, - "Type": "AWS::IAM::Role", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-KeyStack-222222222222-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-KeyStack-222222222222-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/logging-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/logging-stack.test.ts.snap deleted file mode 100644 index 81d278a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/logging-stack.test.ts.snap +++ /dev/null @@ -1,13010 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LoggingStack Construct(LoggingStack): Snapshot Test 1`] = ` -{ - "Resources": { - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922": { - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionLogGroup3B94FCDE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70", - "Roles": [ - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventB3756D27": { - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C", - "Roles": [ - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionLogGroup3B94FCDE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventB3756D27", - "Arn", - ], - }, - "description": "Service linked role for AWS Cloud9", - "roleName": "AWSServiceRoleForAWSCloud9", - "serviceName": "cloud9.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "Accelerator3KeyAliasA5E4BDCB": { - "Properties": { - "AliasName": "alias/accelerator/kms/s3/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "Accelerator3KeyBF43FCD9", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "Accelerator3KeyBF43FCD9": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator S3 Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey", - "kms:Describe*", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow S3 to use the encryption key", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - ], - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow AWS Services to encrypt and describe logs", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorCentralSnsKmsArnParameter4E5BD663": { - "Properties": { - "Name": "/accelerator/kms/snstopic/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorCloudWatchDataProtectionCloudWatchDataProtectionResource62439E9B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AcceleratorCloudWatchDataProtectionFunctionResourceLogGroupBF7014C4", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchDataProtectionframeworkonEvent41D3EE34", - "Arn", - ], - }, - "centralLogBucketName": "existing-central-log-bucket", - "identifierNames": [ - "AwsSecretKey", - "OpenSshPrivateKey", - "PgpPrivateKey", - "PkcsPrivateKey", - "PuttyPrivateKey", - ], - "overrideExisting": false, - "partition": { - "Ref": "AWS::Partition", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorCloudWatchDataProtectionFunctionAB6284FB": { - "DependsOn": [ - "AcceleratorCloudWatchDataProtectionFunctionServiceRoleDefaultPolicy142CDDF6", - "AcceleratorCloudWatchDataProtectionFunctionServiceRole84B1A0EF", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed CloudWatchDataProtection custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchDataProtectionFunctionServiceRole84B1A0EF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorCloudWatchDataProtectionFunctionResourceLogGroupBF7014C4": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorCloudWatchDataProtectionFunctionAB6284FB", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorCloudWatchDataProtectionFunctionServiceRole84B1A0EF": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorCloudWatchDataProtectionFunctionServiceRoleDefaultPolicy142CDDF6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:DeleteAccountPolicy", - "logs:DescribeAccountPolicies", - "logs:PutAccountPolicy", - "logs:PutDataProtectionPolicy", - "logs:DeleteDataProtectionPolicy", - "logs:CreateLogDelivery", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CloudWatchAccess", - }, - { - "Action": [ - "s3:GetBucketPolicy", - "s3:PutBucketPolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "Sid": "S3Access", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorCloudWatchDataProtectionFunctionServiceRoleDefaultPolicy142CDDF6", - "Roles": [ - { - "Ref": "AcceleratorCloudWatchDataProtectionFunctionServiceRole84B1A0EF", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorCloudWatchDataProtectionframeworkonEvent41D3EE34": { - "DependsOn": [ - "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRoleDefaultPolicyA23F360B", - "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRole37B79FF9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/AcceleratorCloudWatchDataProtection/CloudWatchDataProtection/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchDataProtectionFunctionAB6284FB", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRole37B79FF9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRole37B79FF9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRoleDefaultPolicyA23F360B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorCloudWatchDataProtectionFunctionAB6284FB", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorCloudWatchDataProtectionFunctionAB6284FB", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRoleDefaultPolicyA23F360B", - "Roles": [ - { - "Ref": "AcceleratorCloudWatchDataProtectionframeworkonEventServiceRole37B79FF9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorCloudWatchKeyAlias6842582C": { - "Properties": { - "AliasName": "alias/accelerator/kms/cloudwatch/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorCloudWatchKeyF93B6E17": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator CloudWatch Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Decrypt*", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": "arn:aws:logs:us-east-1:*:log-group:*", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch logs to use the encryption key", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEqualsIfExists": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow EventBridge to send to encrypted CloudWatch log groups", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorCloudWatchKmsArnParameter9BAD6EA0": { - "Properties": { - "Name": "/accelerator/kms/cloudwatch/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-CrossAccountSsmParameterShare", - ], - ], - }, - "invokingAccountID": "333333333333", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorKmsArnParameterAppEbsKey9D12461F": { - "Properties": { - "Name": "/accelerator/kms/appEbsKey/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyAppEbsKey332A95A6", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsArnParameterKey13EEB1676": { - "Properties": { - "Name": "/accelerator/kms/key1/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyKey1660964AC", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsKeyAppEbsKey332A95A6": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "CMK policy defined by customer provided policy definition file.", - }, - ], - }, - }, - "Properties": { - "Description": "Test KMS Key 2", - "EnableKeyRotation": true, - "Enabled": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorKmsKeyAppEbsKeyAlias009947BE": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Properties": { - "AliasName": "alias/accelerator/test-key/app-ebs-key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyAppEbsKey332A95A6", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorKmsKeyKey1660964AC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "CMK policy defined by customer provided policy definition file.", - }, - ], - }, - }, - "Properties": { - "Description": "Test KMS Key", - "EnableKeyRotation": true, - "Enabled": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorKmsKeyKey1Alias6B1D2C45": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Properties": { - "AliasName": "alias/accelerator/test-key/key1", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyKey1660964AC", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorKmsMetadataKey9CF7DF88": { - "DependsOn": [ - "AcceleratorKmsArnParameterAppEbsKey9D12461F", - ], - "Properties": { - "Name": "/accelerator/kms/metadata/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucketCmkC5190F6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorLambdaKeyAlias4E15225B": { - "Properties": { - "AliasName": "alias/accelerator/kms/lambda/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorLambdaKeyD279839E": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Lambda Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorLambdaKmsArnParameterFECEE80C": { - "Properties": { - "Name": "/accelerator/kms/lambda/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorMetadataBucket1A53514B": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucketCmkC5190F6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": "aws-accelerator-metadata-333333333333-us-east-1", - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": "LifecycleRuleaws-accelerator-metadata-333333333333-us-east-1", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": "existing-access-logs-bucket-333333333333-us-east-1", - "LogFilePrefix": "aws-accelerator-metadata-333333333333-us-east-1/", - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorMetadataBucketCmkAlias81144DE6": { - "Properties": { - "AliasName": "alias/accelerator/kms/metadata/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucketCmkC5190F6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorMetadataBucketCmkC5190F6D": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "The s3 bucket key for accelerator metadata collection", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow org to perform encryption", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:role/test-access-role", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorMetadataBucketPolicyAE23D97C": { - "Properties": { - "Bucket": { - "Ref": "AcceleratorMetadataBucket1A53514B", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:Get*", - "s3:List*", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetObject", - "s3:ListBucket", - ], - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:role/test-access-role", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "AcceleratorS3KmsArnParameter82C4C525": { - "Properties": { - "Name": "/accelerator/kms/s3/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "Accelerator3KeyBF43FCD9", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorS3MetadataBucket3CD9B291": { - "DependsOn": [ - "AcceleratorKmsArnParameterAppEbsKey9D12461F", - ], - "Properties": { - "Name": "/accelerator/metadata/bucket/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorSnsTopicKey4E909F1D": { - "DeletionPolicy": "Delete", - "Properties": { - "Description": "AWS Accelerator SNS Topic Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "sns", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": "*", - "Sid": "cloudwatch", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEqualsIfExists": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": "*", - "Sid": "events", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "crossaccount", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorSnsTopicKeyAliasCCEBAF56": { - "Properties": { - "AliasName": "alias/accelerator/kms/snstopic/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055", - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupAF98D208": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055", - "Roles": [ - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent05930586": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2", - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2", - "Roles": [ - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupAF98D208", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent05930586", - "Arn", - ], - }, - "description": "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling", - "roleName": "AWSServiceRoleForAutoScaling", - "serviceName": "autoscaling.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountCentralSnsTopicKMSArnSsmParamAccessRoleFA0EB249": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific role arns.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:*:*:parameter/accelerator/kms/snstopic/key-arn", - ], - ], - }, - }, - { - "Action": "ssm:DescribeParameters", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-SnsTopic-KeyArnParam-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B": { - "DependsOn": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderLogGroup354123A8": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:PutAccountPublicAccessBlock", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608": { - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - ], - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FirehoseToS3SetupFirehoseKinesisStreamServiceRoleDCF2BF9E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A": { - "DependsOn": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193", - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "REPLACED-JSON-PATH.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - }, - "FunctionName": "AWSAccelerator-FirehoseRecordsProcessor", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193", - "Roles": [ - { - "Ref": "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirehoseToS3SetupFirehoseServiceRole23B67E37": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Bucket permissions are wildcards to abort downloads and clean up objects. KMS permissions are wildcards to re-encrypt entities.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehoseStream649EE2A0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - }, - "LogStreamName": { - "Ref": "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseServiceRole23B67E37", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseKinesisStreamServiceRoleDCF2BF9E", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "ImportedaccessLogsBucketPolicyBucketPolicyResource5390F048": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedaccessLogsBucketPolicyFunctionResourceLogGroup665D22B3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyframeworkonEventB1CEE6E8", - "Arn", - ], - }, - "applyAcceleratorManagedPolicy": false, - "bucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-access-logs-bucket-333333333333-us-east-1", - ], - ], - }, - "bucketName": "existing-access-logs-bucket-333333333333-us-east-1", - "bucketPolicyFilePaths": [ - "REPLACED-JSON-PATH.json", - ], - "bucketType": "access-logs", - "sourceAccount": "333333333333", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedaccessLogsBucketPolicyFunctionE108A801": { - "DependsOn": [ - "ImportedaccessLogsBucketPolicyFunctionServiceRoleDefaultPolicyCDBB1EF9", - "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketPolicy custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedaccessLogsBucketPolicyFunctionResourceLogGroup665D22B3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedaccessLogsBucketPolicyFunctionE108A801", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedaccessLogsBucketPolicyFunctionServiceRoleDefaultPolicyCDBB1EF9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutBucketPolicy", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-access-logs-bucket-333333333333-us-east-1", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedaccessLogsBucketPolicyFunctionServiceRoleDefaultPolicyCDBB1EF9", - "Roles": [ - { - "Ref": "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedaccessLogsBucketPolicyframeworkonEventB1CEE6E8": { - "DependsOn": [ - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyD3665E5C", - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/ImportedaccessLogsBucketPolicy/BucketPolicy/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionE108A801", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyD3665E5C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionE108A801", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionE108A801", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedaccessLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyD3665E5C", - "Roles": [ - { - "Ref": "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedelbLogsBucketPolicyBucketPolicyResource2C3B688F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedelbLogsBucketPolicyFunctionResourceLogGroupDF83CD38", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyframeworkonEventEBECA79F", - "Arn", - ], - }, - "applyAcceleratorManagedPolicy": true, - "bucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-elb-logs-bucket-333333333333-us-east-1", - ], - ], - }, - "bucketName": "existing-elb-logs-bucket-333333333333-us-east-1", - "bucketPolicyFilePaths": [ - "REPLACED-JSON-PATH.json", - ], - "bucketType": "elb-logs", - "elbAccountId": "127311923021", - "organizationId": "o-asdf123456", - "principalOrgIdCondition": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - "sourceAccount": "333333333333", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedelbLogsBucketPolicyFunction0F7A82C4": { - "DependsOn": [ - "ImportedelbLogsBucketPolicyFunctionServiceRoleDefaultPolicy68485230", - "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketPolicy custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedelbLogsBucketPolicyFunctionResourceLogGroupDF83CD38": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedelbLogsBucketPolicyFunction0F7A82C4", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ImportedelbLogsBucketPolicyFunctionServiceRoleDefaultPolicy68485230": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutBucketPolicy", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-elb-logs-bucket-333333333333-us-east-1", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedelbLogsBucketPolicyFunctionServiceRoleDefaultPolicy68485230", - "Roles": [ - { - "Ref": "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedelbLogsBucketPolicyframeworkonEventEBECA79F": { - "DependsOn": [ - "ImportedelbLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicy9EBBBDE0", - "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/ImportedelbLogsBucketPolicy/BucketPolicy/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunction0F7A82C4", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedelbLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicy9EBBBDE0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunction0F7A82C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunction0F7A82C4", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedelbLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicy9EBBBDE0", - "Roles": [ - { - "Ref": "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "LogsDestinationSetup408DBC7D": { - "Properties": { - "DestinationName": "AWSAcceleratorCloudWatchToS3", - "DestinationPolicy": { - "Fn::Join": [ - "", - [ - "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":"logs:PutSubscriptionFilter","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:destination:AWSAcceleratorCloudWatchToS3","Condition":{"StringEquals":{"aws:PrincipalOrgID":"o-asdf123456"}}}]}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "LogsDestinationSetupLogsKinesisRoleBBFE4D49", - "Arn", - ], - }, - "TargetArn": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - "Type": "AWS::Logs::Destination", - }, - "LogsDestinationSetupLogsKinesisRoleBBFE4D49": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:ListShards", - "kinesis:PutRecord", - "kinesis:PutRecords", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KinesisAccess", - }, - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KmsAccess", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "LogsKinesisStreamCfn": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDS3", - "reason": "Customer managed key is being used to encrypt Kinesis Data Stream", - }, - ], - }, - }, - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "EncryptionType": "KMS", - "KeyId": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "LogsReplicationKeyAE749486": { - "DeletionPolicy": "Delete", - "Properties": { - "Description": "AWS Accelerator CloudWatch Logs Replication Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "LogsReplicationKeyAlias6226562C": { - "Properties": { - "AliasName": "alias/accelerator/kms/replication/cloudwatch/logs/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "LogsSubscriptionFilter58C57C6B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B", - "LogsDestinationSetupLogsKinesisRoleBBFE4D49", - "LogsDestinationSetup408DBC7D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - "Arn", - ], - }, - "acceleratorCreatedLogDestinationArn": "arn:aws:logs:us-east-1:333333333333:destination:AWSAcceleratorCloudWatchToS3", - "acceleratorLogKmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "acceleratorLogRetentionInDays": "3653", - "acceleratorLogSubscriptionRoleArn": { - "Fn::GetAtt": [ - "SubscriptionFilterRoleB0B89330", - "Arn", - ], - }, - "logExclusionOption": "{"account":"333333333333","region":"us-east-1","logGroupNames":["test1/*","/aws/lambda/AWSAccelerator-FirehoseRecordsProcessor"]}", - "replaceLogDestinationArn": "arn:aws:logs:us-east-1:111111111111:destination/log-destination", - }, - "Type": "Custom::UpdateSubscriptionFilter", - "UpdateReplacePolicy": "Delete", - }, - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleA43A0A6D": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Properties": { - "EventPattern": { - "detail": { - "eventName": [ - "CreateLogGroup", - ], - "eventSource": [ - "logs.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.logs", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 5, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleAllowEventRuleAWSAcceleratorLoggingStack333333333333useast1NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction8CBF4C192DFF85CD": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleA43A0A6D", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C", - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "LogDestination": "arn:aws:logs:us-east-1:333333333333:destination:AWSAcceleratorCloudWatchToS3", - "LogExclusion": "{"account":"333333333333","region":"us-east-1","logGroupNames":["test1/*","/aws/lambda/AWSAccelerator-FirehoseRecordsProcessor"]}", - "LogKmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogRetention": "3653", - "LogSubscriptionRole": { - "Fn::GetAtt": [ - "SubscriptionFilterRoleB0B89330", - "Arn", - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C", - "Roles": [ - { - "Ref": "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "S3PublicAccessBlock344F906B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderLogGroup354123A8", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B", - "Arn", - ], - }, - "accountId": "333333333333", - "blockPublicAcls": true, - "blockPublicPolicy": true, - "ignorePublicAcls": true, - "restrictPublicBuckets": true, - }, - "Type": "Custom::PutPublicAccessBlock", - "UpdateReplacePolicy": "Delete", - }, - "SecuritySNSTopicE3C1354E": { - "Properties": { - "DisplayName": "aws-accelerator-Security", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - "TopicName": "aws-accelerator-Security", - }, - "Type": "AWS::SNS::Topic", - }, - "SecuritySNSTopicPolicyFA391E1F": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "0", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "1", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "2", - }, - { - "Action": "sns:Publish", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "3", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "SecuritySNSTopicE3C1354E", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "SecuritySNSTopicnotifysecurityexamplecom26985403": { - "Properties": { - "Endpoint": "notify-security@example.com", - "Protocol": "email", - "TopicArn": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-LoggingStack-333333333333-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-LoggingStack-333333333333-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SubscriptionFilterRoleB0B89330": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Access is needed to ready all log events across all log groups for replication to S3.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Subscription Filter to allow access to CloudWatch Destination", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "accessLogEvents", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE": { - "DependsOn": [ - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyD5BFE6E0", - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunctionResourceLogGroupDA884B01": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyD5BFE6E0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-access-logs-bucket-333333333333-us-east-1", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyD5BFE6E0", - "Roles": [ - { - "Ref": "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedaccessLogsBucketValidateBucketValidateBucketResourceC9A0ADD2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateImportedaccessLogsBucketValidateBucketFunctionResourceLogGroupDA884B01", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEvent96ADDA6B", - "Arn", - ], - }, - "bucketName": "existing-access-logs-bucket-333333333333-us-east-1", - "encryptionType": "s3", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEvent96ADDA6B": { - "DependsOn": [ - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy9A87B05A", - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/ValidateImportedaccessLogsBucket/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy9A87B05A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy9A87B05A", - "Roles": [ - { - "Ref": "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99": { - "DependsOn": [ - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyFB76DBF7", - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedelbLogsBucketValidateBucketFunctionResourceLogGroup700806F5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyFB76DBF7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-elb-logs-bucket-333333333333-us-east-1", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyFB76DBF7", - "Roles": [ - { - "Ref": "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedelbLogsBucketValidateBucketValidateBucketResourceD5022336": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateImportedelbLogsBucketValidateBucketFunctionResourceLogGroup700806F5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketframeworkonEvent4E2573AA", - "Arn", - ], - }, - "bucketName": "existing-elb-logs-bucket-333333333333-us-east-1", - "encryptionType": "s3", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedelbLogsBucketValidateBucketframeworkonEvent4E2573AA": { - "DependsOn": [ - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy3DD701AA", - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/ValidateImportedelbLogsBucket/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy3DD701AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy3DD701AA", - "Roles": [ - { - "Ref": "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`LoggingStack Construct(LoggingStack): Snapshot Test 2`] = ` -{ - "Resources": { - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922": { - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionLogGroup3B94FCDE": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70", - "Roles": [ - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventB3756D27": { - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C", - "Roles": [ - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionLogGroup3B94FCDE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventB3756D27", - "Arn", - ], - }, - "description": "Service linked role for AWS Cloud9", - "roleName": "AWSServiceRoleForAWSCloud9", - "serviceName": "cloud9.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorCentralSnsKmsArnParameter4E5BD663": { - "Properties": { - "Name": "/accelerator/kms/snstopic/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsArnParameterAppEbsKey9D12461F": { - "Properties": { - "Name": "/accelerator/kms/appEbsKey/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyAppEbsKey332A95A6", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsArnParameterKey13EEB1676": { - "Properties": { - "Name": "/accelerator/kms/key1/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyKey1660964AC", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsKeyAppEbsKey332A95A6": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "CMK policy defined by customer provided policy definition file.", - }, - ], - }, - }, - "Properties": { - "Description": "Test KMS Key 2", - "EnableKeyRotation": true, - "Enabled": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorKmsKeyAppEbsKeyAlias009947BE": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Properties": { - "AliasName": "alias/accelerator/test-key/app-ebs-key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyAppEbsKey332A95A6", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorKmsKeyKey1660964AC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "CMK policy defined by customer provided policy definition file.", - }, - ], - }, - }, - "Properties": { - "Description": "Test KMS Key", - "EnableKeyRotation": true, - "Enabled": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorKmsKeyKey1Alias6B1D2C45": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Properties": { - "AliasName": "alias/accelerator/test-key/key1", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyKey1660964AC", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorSnsTopicKey4E909F1D": { - "DeletionPolicy": "Delete", - "Properties": { - "Description": "AWS Accelerator SNS Topic Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "sns", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": "*", - "Sid": "cloudwatch", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEqualsIfExists": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": "*", - "Sid": "events", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "crossaccount", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorSnsTopicKeyAliasCCEBAF56": { - "Properties": { - "AliasName": "alias/accelerator/kms/snstopic/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055", - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupAF98D208": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055", - "Roles": [ - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent05930586": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2", - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2", - "Roles": [ - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupAF98D208", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent05930586", - "Arn", - ], - }, - "description": "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling", - "roleName": "AWSServiceRoleForAutoScaling", - "serviceName": "autoscaling.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F": { - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to put cross-account ssm parameter value", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DeleteParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmPutParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608": { - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:log-group:*", - ], - ], - }, - ], - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FirehoseToS3SetupFirehoseKinesisStreamServiceRoleDCF2BF9E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A": { - "DependsOn": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193", - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "REPLACED-JSON-PATH.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - }, - "FunctionName": "AWSAccelerator-FirehoseRecordsProcessor", - "Handler": "index.handler", - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193", - "Roles": [ - { - "Ref": "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirehoseToS3SetupFirehoseServiceRole23B67E37": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Bucket permissions are wildcards to abort downloads and clean up objects. KMS permissions are wildcards to re-encrypt entities.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketKey7AB36134", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehoseStream649EE2A0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - }, - "LogStreamName": { - "Ref": "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketKey7AB36134", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseServiceRole23B67E37", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseKinesisStreamServiceRoleDCF2BF9E", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixFunction72196444": { - "DependsOn": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleDefaultPolicy5FD96CEA", - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleED050E0B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed S3CreateBucketPrefix custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleED050E0B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionResourceLogGroupDC741551": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedLogBucketPrefixS3CreateBucketPrefixFunction72196444", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleDefaultPolicy5FD96CEA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:ListBucket", - "s3:GetObject", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleDefaultPolicy5FD96CEA", - "Roles": [ - { - "Ref": "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleED050E0B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionServiceRoleED050E0B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixS3CreateBucketPrefixResource2A2DF87D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixFunctionResourceLogGroupDC741551", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEvent42D5FBB9", - "Arn", - ], - }, - "bucketPrefixes": [ - "guardduty", - ], - "sourceBucketName": "existing-central-log-bucket", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEvent42D5FBB9": { - "DependsOn": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleDefaultPolicy223B46DE", - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRole1C1704DF", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ImportedLogBucketPrefix/S3CreateBucketPrefix/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixFunction72196444", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRole1C1704DF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRole1C1704DF": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleDefaultPolicy223B46DE": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixFunction72196444", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedLogBucketPrefixS3CreateBucketPrefixFunction72196444", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleDefaultPolicy223B46DE", - "Roles": [ - { - "Ref": "ImportedLogBucketPrefixS3CreateBucketPrefixframeworkonEventServiceRole1C1704DF", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedaccessLogsBucketPolicyBucketPolicyResource5390F048": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedaccessLogsBucketPolicyFunctionResourceLogGroup665D22B3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyframeworkonEventB1CEE6E8", - "Arn", - ], - }, - "applyAcceleratorManagedPolicy": false, - "bucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-access-logs-bucket-333333333333-us-west-2", - ], - ], - }, - "bucketName": "existing-access-logs-bucket-333333333333-us-west-2", - "bucketPolicyFilePaths": [ - "REPLACED-JSON-PATH.json", - ], - "bucketType": "access-logs", - "sourceAccount": "333333333333", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedaccessLogsBucketPolicyFunctionE108A801": { - "DependsOn": [ - "ImportedaccessLogsBucketPolicyFunctionServiceRoleDefaultPolicyCDBB1EF9", - "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketPolicy custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedaccessLogsBucketPolicyFunctionResourceLogGroup665D22B3": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedaccessLogsBucketPolicyFunctionE108A801", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedaccessLogsBucketPolicyFunctionServiceRoleDefaultPolicyCDBB1EF9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutBucketPolicy", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-access-logs-bucket-333333333333-us-west-2", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedaccessLogsBucketPolicyFunctionServiceRoleDefaultPolicyCDBB1EF9", - "Roles": [ - { - "Ref": "ImportedaccessLogsBucketPolicyFunctionServiceRole37C32A68", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedaccessLogsBucketPolicyframeworkonEventB1CEE6E8": { - "DependsOn": [ - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyD3665E5C", - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ImportedaccessLogsBucketPolicy/BucketPolicy/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionE108A801", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedaccessLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyD3665E5C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionE108A801", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedaccessLogsBucketPolicyFunctionE108A801", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedaccessLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyD3665E5C", - "Roles": [ - { - "Ref": "ImportedaccessLogsBucketPolicyframeworkonEventServiceRole7593A466", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedcentralLogsBucketEncryptionBucketEncryptionResource27DEBA46": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedcentralLogsBucketEncryptionFunctionResourceLogGroupE1CF6C47", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketEncryptionframeworkonEventE90F5DD4", - "Arn", - ], - }, - "bucketName": "existing-central-log-bucket", - "kmsKeyArn": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketKey7AB36134", - "Arn", - ], - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedcentralLogsBucketEncryptionFunction1213293C": { - "DependsOn": [ - "ImportedcentralLogsBucketEncryptionFunctionServiceRoleDefaultPolicy7ED44A90", - "ImportedcentralLogsBucketEncryptionFunctionServiceRole9CF8A4EB", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketEncryption custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketEncryptionFunctionServiceRole9CF8A4EB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedcentralLogsBucketEncryptionFunctionResourceLogGroupE1CF6C47": { - "DeletionPolicy": "Retain", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedcentralLogsBucketEncryptionFunction1213293C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "ImportedcentralLogsBucketEncryptionFunctionServiceRole9CF8A4EB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedcentralLogsBucketEncryptionFunctionServiceRoleDefaultPolicy7ED44A90": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedcentralLogsBucketEncryptionFunctionServiceRoleDefaultPolicy7ED44A90", - "Roles": [ - { - "Ref": "ImportedcentralLogsBucketEncryptionFunctionServiceRole9CF8A4EB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedcentralLogsBucketEncryptionframeworkonEventE90F5DD4": { - "DependsOn": [ - "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleDefaultPolicyCA7F7565", - "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleBBC0F3CB", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ImportedcentralLogsBucketEncryption/BucketEncryption/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketEncryptionFunction1213293C", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleBBC0F3CB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleBBC0F3CB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleDefaultPolicyCA7F7565": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketEncryptionFunction1213293C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketEncryptionFunction1213293C", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleDefaultPolicyCA7F7565", - "Roles": [ - { - "Ref": "ImportedcentralLogsBucketEncryptionframeworkonEventServiceRoleBBC0F3CB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedcentralLogsBucketKey7AB36134": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Imported Central Logs Bucket CMK", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::333333333333:root", - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::457936900343:root", - }, - "Resource": "*", - "Sid": "Enable IAM User Permissions", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateRandom", - "kms:GetKeyPolicy", - "kms:GetKeyRotationStatus", - "kms:ListAliases", - "kms:ListGrants", - "kms:ListKeyPolicies", - "kms:ListKeys", - "kms:ListResourceTags", - "kms:ListRetirableGrants", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - ], - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow S3 use of the key - From file", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com", - "config.amazonaws.com", - "delivery.logs.amazonaws.com", - "ssm.amazonaws.com", - ], - }, - "Resource": "*", - "Sid": "Allow AWS Services to encrypt and describe logs - From file", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - "kms:ListAliases", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Organization use of the key - From file", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Macie service to use the encryption key - From file", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "guardduty.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow Guardduty service to use the encryption key - From file", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com", - }, - "Resource": "*", - "Sid": "Policy statement 1 from file", - }, - { - "Action": "kms:ListAliases", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Policy statement 2 from file", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ImportedcentralLogsBucketKeyAlias028F6E74": { - "Properties": { - "AliasName": "alias/accelerator/imported-bucket/central-logs/s3", - "TargetKeyId": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketKey7AB36134", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "ImportedcentralLogsBucketPolicyBucketPolicyResourceB8A42F0A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedcentralLogsBucketPolicyFunctionResourceLogGroup2E16ACD3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketPolicyframeworkonEvent446C6D3E", - "Arn", - ], - }, - "applyAcceleratorManagedPolicy": false, - "awsPrincipalAccesses": [ - { - "accessType": "readwrite", - "name": "Macie", - "principal": "macie.amazonaws.com", - }, - { - "accessType": "readwrite", - "name": "Guardduty", - "principal": "guardduty.amazonaws.com", - }, - { - "accessType": "readwrite", - "name": "AuditManager", - "principal": "auditmanager.amazonaws.com", - }, - { - "accessType": "no_access", - "name": "SessionManager", - "principal": "session-manager.amazonaws.com", - }, - ], - "bucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "bucketName": "existing-central-log-bucket", - "bucketPolicyFilePaths": [ - "REPLACED-JSON-PATH.json", - ], - "bucketType": "central-logs", - "organizationId": "o-asdf123456", - "principalOrgIdCondition": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - "sourceAccount": "333333333333", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedcentralLogsBucketPolicyFunction52BB88BE": { - "DependsOn": [ - "ImportedcentralLogsBucketPolicyFunctionServiceRoleDefaultPolicy215687F6", - "ImportedcentralLogsBucketPolicyFunctionServiceRoleE52C0EF9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketPolicy custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketPolicyFunctionServiceRoleE52C0EF9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedcentralLogsBucketPolicyFunctionResourceLogGroup2E16ACD3": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedcentralLogsBucketPolicyFunction52BB88BE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ImportedcentralLogsBucketPolicyFunctionServiceRoleDefaultPolicy215687F6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutBucketPolicy", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedcentralLogsBucketPolicyFunctionServiceRoleDefaultPolicy215687F6", - "Roles": [ - { - "Ref": "ImportedcentralLogsBucketPolicyFunctionServiceRoleE52C0EF9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedcentralLogsBucketPolicyFunctionServiceRoleE52C0EF9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedcentralLogsBucketPolicyframeworkonEvent446C6D3E": { - "DependsOn": [ - "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyF97E86E4", - "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleA85CAD5F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ImportedcentralLogsBucketPolicy/BucketPolicy/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketPolicyFunction52BB88BE", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleA85CAD5F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleA85CAD5F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyF97E86E4": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketPolicyFunction52BB88BE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketPolicyFunction52BB88BE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicyF97E86E4", - "Roles": [ - { - "Ref": "ImportedcentralLogsBucketPolicyframeworkonEventServiceRoleA85CAD5F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedelbLogsBucketPolicyBucketPolicyResource2C3B688F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportedelbLogsBucketPolicyFunctionResourceLogGroupDF83CD38", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyframeworkonEventEBECA79F", - "Arn", - ], - }, - "applyAcceleratorManagedPolicy": true, - "bucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-elb-logs-bucket-333333333333-us-west-2", - ], - ], - }, - "bucketName": "existing-elb-logs-bucket-333333333333-us-west-2", - "bucketPolicyFilePaths": [ - "REPLACED-JSON-PATH.json", - ], - "bucketType": "elb-logs", - "elbAccountId": "797873946194", - "organizationId": "o-asdf123456", - "principalOrgIdCondition": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - "sourceAccount": "333333333333", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ImportedelbLogsBucketPolicyFunction0F7A82C4": { - "DependsOn": [ - "ImportedelbLogsBucketPolicyFunctionServiceRoleDefaultPolicy68485230", - "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketPolicy custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedelbLogsBucketPolicyFunctionResourceLogGroupDF83CD38": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportedelbLogsBucketPolicyFunction0F7A82C4", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ImportedelbLogsBucketPolicyFunctionServiceRoleDefaultPolicy68485230": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutBucketPolicy", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-elb-logs-bucket-333333333333-us-west-2", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedelbLogsBucketPolicyFunctionServiceRoleDefaultPolicy68485230", - "Roles": [ - { - "Ref": "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportedelbLogsBucketPolicyFunctionServiceRoleEE26D2A8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedelbLogsBucketPolicyframeworkonEventEBECA79F": { - "DependsOn": [ - "ImportedelbLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicy9EBBBDE0", - "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ImportedelbLogsBucketPolicy/BucketPolicy/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunction0F7A82C4", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedelbLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicy9EBBBDE0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunction0F7A82C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportedelbLogsBucketPolicyFunction0F7A82C4", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportedelbLogsBucketPolicyframeworkonEventServiceRoleDefaultPolicy9EBBBDE0", - "Roles": [ - { - "Ref": "ImportedelbLogsBucketPolicyframeworkonEventServiceRole18BD877A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "LogsDestinationSetup408DBC7D": { - "Properties": { - "DestinationName": "AWSAcceleratorCloudWatchToS3", - "DestinationPolicy": { - "Fn::Join": [ - "", - [ - "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":"logs:PutSubscriptionFilter","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:destination:AWSAcceleratorCloudWatchToS3","Condition":{"StringEquals":{"aws:PrincipalOrgID":"o-asdf123456"}}}]}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "LogsDestinationSetupLogsKinesisRoleBBFE4D49", - "Arn", - ], - }, - "TargetArn": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - "Type": "AWS::Logs::Destination", - }, - "LogsDestinationSetupLogsKinesisRoleBBFE4D49": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-west-2.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:ListShards", - "kinesis:PutRecord", - "kinesis:PutRecords", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KinesisAccess", - }, - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KmsAccess", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "LogsKinesisStreamCfn": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDS3", - "reason": "Customer managed key is being used to encrypt Kinesis Data Stream", - }, - ], - }, - }, - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "EncryptionType": "KMS", - "KeyId": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "LogsReplicationKeyAE749486": { - "DeletionPolicy": "Delete", - "Properties": { - "Description": "AWS Accelerator CloudWatch Logs Replication Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "LogsReplicationKeyAlias6226562C": { - "Properties": { - "AliasName": "alias/accelerator/kms/replication/cloudwatch/logs/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "LogsSubscriptionFilter58C57C6B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B", - "LogsDestinationSetupLogsKinesisRoleBBFE4D49", - "LogsDestinationSetup408DBC7D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - "Arn", - ], - }, - "acceleratorCreatedLogDestinationArn": "arn:aws:logs:us-west-2:333333333333:destination:AWSAcceleratorCloudWatchToS3", - "acceleratorLogRetentionInDays": "3653", - "acceleratorLogSubscriptionRoleArn": { - "Fn::GetAtt": [ - "SubscriptionFilterRoleB0B89330", - "Arn", - ], - }, - "logExclusionOption": "{"account":"333333333333","region":"us-west-2","logGroupNames":["test1/*","/aws/lambda/AWSAccelerator-FirehoseRecordsProcessor"]}", - "replaceLogDestinationArn": "arn:aws:logs:us-east-1:111111111111:destination/log-destination", - }, - "Type": "Custom::UpdateSubscriptionFilter", - "UpdateReplacePolicy": "Delete", - }, - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleA43A0A6D": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Properties": { - "EventPattern": { - "detail": { - "eventName": [ - "CreateLogGroup", - ], - "eventSource": [ - "logs.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.logs", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 5, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleAllowEventRuleAWSAcceleratorLoggingStack333333333333uswest2NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1ECC2C7A85E69C4C": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleA43A0A6D", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C", - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "LogDestination": "arn:aws:logs:us-west-2:333333333333:destination:AWSAcceleratorCloudWatchToS3", - "LogExclusion": "{"account":"333333333333","region":"us-west-2","logGroupNames":["test1/*","/aws/lambda/AWSAccelerator-FirehoseRecordsProcessor"]}", - "LogRetention": "3653", - "LogSubscriptionRole": { - "Fn::GetAtt": [ - "SubscriptionFilterRoleB0B89330", - "Arn", - ], - }, - }, - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:log-group:*", - ], - ], - }, - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-west-2:333333333333:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C", - "Roles": [ - { - "Ref": "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "PutImportedcentralLogsBucketKmsArnParameter11541199": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "333333333333", - "parameterAccountIds": [ - "333333333333", - ], - "parameters": [ - { - "name": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "value": { - "Fn::GetAtt": [ - "ImportedcentralLogsBucketKey7AB36134", - "Arn", - ], - }, - }, - ], - "region": "us-west-2", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SecuritySNSTopicE3C1354E": { - "Properties": { - "DisplayName": "aws-accelerator-Security", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - "TopicName": "aws-accelerator-Security", - }, - "Type": "AWS::SNS::Topic", - }, - "SecuritySNSTopicPolicyFA391E1F": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "0", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "1", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "2", - }, - { - "Action": "sns:Publish", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "3", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "SecuritySNSTopicE3C1354E", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "SecuritySNSTopicnotifysecurityexamplecom26985403": { - "Properties": { - "Endpoint": "notify-security@example.com", - "Protocol": "email", - "TopicArn": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-LoggingStack-333333333333-us-west-2/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-LoggingStack-333333333333-us-west-2/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SubscriptionFilterRoleB0B89330": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Access is needed to ready all log events across all log groups for replication to S3.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-west-2.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Subscription Filter to allow access to CloudWatch Destination", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "accessLogEvents", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE": { - "DependsOn": [ - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyD5BFE6E0", - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunctionResourceLogGroupDA884B01": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyD5BFE6E0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-access-logs-bucket-333333333333-us-west-2", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyD5BFE6E0", - "Roles": [ - { - "Ref": "ValidateImportedaccessLogsBucketValidateBucketFunctionServiceRole622EF956", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedaccessLogsBucketValidateBucketValidateBucketResourceC9A0ADD2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateImportedaccessLogsBucketValidateBucketFunctionResourceLogGroupDA884B01", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEvent96ADDA6B", - "Arn", - ], - }, - "bucketName": "existing-access-logs-bucket-333333333333-us-west-2", - "encryptionType": "s3", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEvent96ADDA6B": { - "DependsOn": [ - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy9A87B05A", - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ValidateImportedaccessLogsBucket/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy9A87B05A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateImportedaccessLogsBucketValidateBucketFunction67AF64AE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy9A87B05A", - "Roles": [ - { - "Ref": "ValidateImportedaccessLogsBucketValidateBucketframeworkonEventServiceRole90079A8B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedcentralLogsBucketValidateBucketFunctionA46C1293": { - "DependsOn": [ - "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRoleDefaultPolicy04F8BB58", - "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRole66C672FA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRole66C672FA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedcentralLogsBucketValidateBucketFunctionResourceLogGroupC510852F": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateImportedcentralLogsBucketValidateBucketFunctionA46C1293", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRole66C672FA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRoleDefaultPolicy04F8BB58": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRoleDefaultPolicy04F8BB58", - "Roles": [ - { - "Ref": "ValidateImportedcentralLogsBucketValidateBucketFunctionServiceRole66C672FA", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedcentralLogsBucketValidateBucketValidateBucketResource409E29BE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateImportedcentralLogsBucketValidateBucketFunctionResourceLogGroupC510852F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEvent8DC338CF", - "Arn", - ], - }, - "bucketName": "existing-central-log-bucket", - "encryptionType": "kms", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEvent8DC338CF": { - "DependsOn": [ - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy080B8C9B", - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDD88BC27", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ValidateImportedcentralLogsBucket/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateImportedcentralLogsBucketValidateBucketFunctionA46C1293", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDD88BC27", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDD88BC27": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy080B8C9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateImportedcentralLogsBucketValidateBucketFunctionA46C1293", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateImportedcentralLogsBucketValidateBucketFunctionA46C1293", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy080B8C9B", - "Roles": [ - { - "Ref": "ValidateImportedcentralLogsBucketValidateBucketframeworkonEventServiceRoleDD88BC27", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99": { - "DependsOn": [ - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyFB76DBF7", - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedelbLogsBucketValidateBucketFunctionResourceLogGroup700806F5": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyFB76DBF7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-elb-logs-bucket-333333333333-us-west-2", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRoleDefaultPolicyFB76DBF7", - "Roles": [ - { - "Ref": "ValidateImportedelbLogsBucketValidateBucketFunctionServiceRole77EE6C35", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateImportedelbLogsBucketValidateBucketValidateBucketResourceD5022336": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateImportedelbLogsBucketValidateBucketFunctionResourceLogGroup700806F5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketframeworkonEvent4E2573AA", - "Arn", - ], - }, - "bucketName": "existing-elb-logs-bucket-333333333333-us-west-2", - "encryptionType": "s3", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateImportedelbLogsBucketValidateBucketframeworkonEvent4E2573AA": { - "DependsOn": [ - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy3DD701AA", - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-2", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-west-2/ValidateImportedelbLogsBucket/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy3DD701AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateImportedelbLogsBucketValidateBucketFunction9D3CDC99", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRoleDefaultPolicy3DD701AA", - "Roles": [ - { - "Ref": "ValidateImportedelbLogsBucketValidateBucketframeworkonEventServiceRole3CAD21DD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`LoggingStackOuTargets Construct(LoggingStackOuTargets): Snapshot Test 1`] = ` -{ - "Resources": { - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922": { - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionLogGroup3B94FCDE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA960DF70", - "Roles": [ - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionServiceRoleD8D1BF16", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventB3756D27": { - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/AWSServiceRoleForAWSCloud9/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunction78452922", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy3D6F853C", - "Roles": [ - { - "Ref": "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventServiceRole8AB21249", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleFunctionLogGroup3B94FCDE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleProviderframeworkonEventB3756D27", - "Arn", - ], - }, - "description": "Service linked role for AWS Cloud9", - "roleName": "AWSServiceRoleForAWSCloud9", - "serviceName": "cloud9.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "Accelerator3KeyAliasA5E4BDCB": { - "Properties": { - "AliasName": "alias/accelerator/kms/s3/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "Accelerator3KeyBF43FCD9", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "Accelerator3KeyBF43FCD9": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator S3 Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey", - "kms:Describe*", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow S3 to use the encryption key", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - ], - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow AWS Services to encrypt and describe logs", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorCentralLogBucketKeyLookup26F8C418": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-us-west-2-CentralBucket-KeyArnParam-Role", - ], - ], - }, - "invokingAccountID": "333333333333", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorCentralSnsKmsArnParameter4E5BD663": { - "Properties": { - "Name": "/accelerator/kms/snstopic/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorCloudWatchKeyAlias6842582C": { - "Properties": { - "AliasName": "alias/accelerator/kms/cloudwatch/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorCloudWatchKeyF93B6E17": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator CloudWatch Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Decrypt*", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": "arn:aws:logs:us-east-1:*:log-group:*", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch logs to use the encryption key", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEqualsIfExists": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow EventBridge to send to encrypted CloudWatch log groups", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorCloudWatchKmsArnParameter9BAD6EA0": { - "Properties": { - "Name": "/accelerator/kms/cloudwatch/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsArnParameterKey13EEB1676": { - "Properties": { - "Name": "/accelerator/kms/key1/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyKey1660964AC", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorKmsKeyKey1660964AC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "CMK policy defined by customer provided policy definition file.", - }, - ], - }, - }, - "Properties": { - "Description": "Test KMS Key", - "EnableKeyRotation": true, - "Enabled": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorKmsKeyKey1Alias6B1D2C45": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878", - "AWSServiceRoleForAWSCloud9CreateServiceLinkedRoleResource5A765F9A", - ], - "Properties": { - "AliasName": "alias/accelerator/test-key/key1", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorKmsKeyKey1660964AC", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorKmsMetadataKey9CF7DF88": { - "DependsOn": [ - "AcceleratorS3MetadataBucket3CD9B291", - ], - "Properties": { - "Name": "/accelerator/kms/metadata/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucketCmkC5190F6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorLambdaKeyAlias4E15225B": { - "Properties": { - "AliasName": "alias/accelerator/kms/lambda/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorLambdaKeyD279839E": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Lambda Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorLambdaKmsArnParameterFECEE80C": { - "Properties": { - "Name": "/accelerator/kms/lambda/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorMetadataBucket1A53514B": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucketCmkC5190F6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": "aws-accelerator-metadata-333333333333-us-east-1", - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": "LifecycleRuleaws-accelerator-metadata-333333333333-us-east-1", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "AccessLogsBucketFA218D2A", - }, - "LogFilePrefix": "aws-accelerator-metadata-333333333333-us-east-1/", - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorMetadataBucketCmkAlias81144DE6": { - "Properties": { - "AliasName": "alias/accelerator/kms/metadata/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucketCmkC5190F6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorMetadataBucketCmkC5190F6D": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "The s3 bucket key for accelerator metadata collection", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow org to perform encryption", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:role/test-access-role", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorMetadataBucketPolicyAE23D97C": { - "Properties": { - "Bucket": { - "Ref": "AcceleratorMetadataBucket1A53514B", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:Get*", - "s3:List*", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetObject", - "s3:ListBucket", - ], - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:role/test-access-role", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "AcceleratorS3KmsArnParameter82C4C525": { - "Properties": { - "Name": "/accelerator/kms/s3/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "Accelerator3KeyBF43FCD9", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorS3MetadataBucket3CD9B291": { - "Properties": { - "Name": "/accelerator/metadata/bucket/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorMetadataBucket1A53514B", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorSnsTopicKey4E909F1D": { - "DeletionPolicy": "Delete", - "Properties": { - "Description": "AWS Accelerator SNS Topic Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "sns", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": "*", - "Sid": "cloudwatch", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEqualsIfExists": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": "*", - "Sid": "events", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "crossaccount", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorSnsTopicKeyAliasCCEBAF56": { - "Properties": { - "AliasName": "alias/accelerator/kms/snstopic/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AccessLogsBucketFA218D2A": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "aws-accelerator-s3-access-logs-333333333333-us-east-1", - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "AccessLogsBucketPolicy00F12803": { - "Properties": { - "Bucket": { - "Ref": "AccessLogsBucketFA218D2A", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AccessLogsBucketFA218D2A", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccessLogsBucketFA218D2A", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "aws:SourceAccount": "333333333333", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "logging.s3.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccessLogsBucketFA218D2A", - "Arn", - ], - }, - "/*", - ], - ], - }, - "Sid": "Allow write access for logging service principal", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055", - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupAF98D208": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy4BF99055", - "Roles": [ - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleF168CC0D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent05930586": { - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2", - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-LoggingStack-333333333333-us-east-1/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction56E83FD3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyC1F62AB2", - "Roles": [ - { - "Ref": "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleF8C91A0A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceE203F878": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupAF98D208", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent05930586", - "Arn", - ], - }, - "description": "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling", - "roleName": "AWSServiceRoleForAutoScaling", - "serviceName": "autoscaling.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountCentralSnsTopicKMSArnSsmParamAccessRoleFA0EB249": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific role arns.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:*:*:parameter/accelerator/kms/snstopic/key-arn", - ], - ], - }, - }, - { - "Action": "ssm:DescribeParameters", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-SnsTopic-KeyArnParam-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B": { - "DependsOn": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderLogGroup354123A8": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:PutAccountPublicAccessBlock", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608": { - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - ], - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ElbAccessLogsBucketD6CD6A5D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "ElbAccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - }, - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "aws-accelerator-elb-access-logs-333333333333-us-east-1", - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "ElbAccessLogsBucketElbAccessLogsBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole704C2B75": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "ElbAccessLogsBucketElbAccessLogsBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy13236CE3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "lbAccessLogsBucketElbAccessLogsBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy13236CE3", - "Roles": [ - { - "Ref": "ElbAccessLogsBucketElbAccessLogsBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole704C2B75", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ElbAccessLogsBucketElbAccessLogsBucketReplicationC8860256": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": "333333333333", - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - "destinationBucketKeyArn": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "ElbAccessLogsBucketElbAccessLogsBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole704C2B75", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "ElbAccessLogsBucketD6CD6A5D", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "ElbAccessLogsBucketPolicyC6AEEA2E": { - "Properties": { - "Bucket": { - "Ref": "ElbAccessLogsBucketD6CD6A5D", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": "s3:GetBucketAcl", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "Sid": "Allow get acl access for SSM principal", - }, - { - "Action": "s3:PutObject", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::127311923021:root", - ], - ], - }, - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "/*", - ], - ], - }, - "Sid": "Allow write access for ELB Account principal", - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "/*", - ], - ], - }, - "Sid": "Allow write access for delivery logging service principal", - }, - { - "Action": "s3:GetBucketAcl", - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "Sid": "Allow read bucket ACL access for delivery logging service principal", - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:PutObject", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ElbAccessLogsBucketD6CD6A5D", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "Allow Organization principals to use of the bucket", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FirehoseToS3SetupFirehoseKinesisStreamServiceRoleDCF2BF9E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A": { - "DependsOn": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193", - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "REPLACED-JSON-PATH.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - }, - "FunctionName": "AWSAccelerator-FirehoseRecordsProcessor", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy56739193", - "Roles": [ - { - "Ref": "FirehoseToS3SetupFirehosePrefixProcessingLambdaServiceRole55C795C1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirehoseToS3SetupFirehoseServiceRole23B67E37": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Bucket permissions are wildcards to abort downloads and clean up objects. KMS permissions are wildcards to re-encrypt entities.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.us-east-1.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirehoseToS3SetupFirehoseStream649EE2A0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "FirehoseToS3SetupFirehoseCloudwatchLogs4286DAE2", - }, - "LogStreamName": { - "Ref": "FirehoseToS3SetupFirehoseCloudWatchLogStreamC16DD5F5", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehosePrefixProcessingLambdaCFF0C71A", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseServiceRole23B67E37", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "FirehoseToS3SetupFirehoseKinesisStreamServiceRoleDCF2BF9E", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "LogsDestinationSetup408DBC7D": { - "Properties": { - "DestinationName": "AWSAcceleratorCloudWatchToS3", - "DestinationPolicy": { - "Fn::Join": [ - "", - [ - "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":"logs:PutSubscriptionFilter","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:destination:AWSAcceleratorCloudWatchToS3","Condition":{"StringEquals":{"aws:PrincipalOrgID":"o-asdf123456"}}}]}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "LogsDestinationSetupLogsKinesisRoleBBFE4D49", - "Arn", - ], - }, - "TargetArn": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - "Type": "AWS::Logs::Destination", - }, - "LogsDestinationSetupLogsKinesisRoleBBFE4D49": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:ListShards", - "kinesis:PutRecord", - "kinesis:PutRecords", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsKinesisStreamCfn", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KinesisAccess", - }, - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KmsAccess", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "LogsKinesisStreamCfn": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDS3", - "reason": "Customer managed key is being used to encrypt Kinesis Data Stream", - }, - ], - }, - }, - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "EncryptionType": "KMS", - "KeyId": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "LogsReplicationKeyAE749486": { - "DeletionPolicy": "Delete", - "Properties": { - "Description": "AWS Accelerator CloudWatch Logs Replication Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Delete", - }, - "LogsReplicationKeyAlias6226562C": { - "Properties": { - "AliasName": "alias/accelerator/kms/replication/cloudwatch/logs/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "LogsReplicationKeyAE749486", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "LogsSubscriptionFilter58C57C6B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B", - "LogsDestinationSetupLogsKinesisRoleBBFE4D49", - "LogsDestinationSetup408DBC7D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - "Arn", - ], - }, - "acceleratorCreatedLogDestinationArn": "arn:aws:logs:us-east-1:333333333333:destination:AWSAcceleratorCloudWatchToS3", - "acceleratorLogKmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "acceleratorLogRetentionInDays": "3653", - "acceleratorLogSubscriptionRoleArn": { - "Fn::GetAtt": [ - "SubscriptionFilterRoleB0B89330", - "Arn", - ], - }, - "logExclusionOption": "{"account":"333333333333","region":"us-east-1","logGroupNames":["/aws/lambda/AWSAccelerator-FirehoseRecordsProcessor"]}", - }, - "Type": "Custom::UpdateSubscriptionFilter", - "UpdateReplacePolicy": "Delete", - }, - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleA43A0A6D": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Properties": { - "EventPattern": { - "detail": { - "eventName": [ - "CreateLogGroup", - ], - "eventSource": [ - "logs.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.logs", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 5, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleAllowEventRuleAWSAcceleratorLoggingStack333333333333useast1NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction8CBF4C192DFF85CD": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventNewLogGroupCreatedRuleA43A0A6D", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunction1A3BCE58": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C", - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "LogDestination": "arn:aws:logs:us-east-1:333333333333:destination:AWSAcceleratorCloudWatchToS3", - "LogExclusion": "{"account":"333333333333","region":"us-east-1","logGroupNames":["/aws/lambda/AWSAccelerator-FirehoseRecordsProcessor"]}", - "LogKmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorCloudWatchKeyF93B6E17", - "Arn", - ], - }, - "LogRetention": "3653", - "LogSubscriptionRole": { - "Fn::GetAtt": [ - "SubscriptionFilterRoleB0B89330", - "Arn", - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorLambdaKeyD279839E", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:333333333333:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy5DE2EB5C", - "Roles": [ - { - "Ref": "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NewCloudWatchLogsCreateEventSetLogRetentionSubscriptionFunctionServiceRoleF408E8D2": { - "DependsOn": [ - "LogsSubscriptionFilter58C57C6B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "This role needs permissions to change retention and subscription filter for any new log group that is created to enable log replication.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "S3PublicAccessBlock344F906B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderLogGroup354123A8", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B", - "Arn", - ], - }, - "accountId": "333333333333", - "blockPublicAcls": true, - "blockPublicPolicy": true, - "ignorePublicAcls": true, - "restrictPublicBuckets": true, - }, - "Type": "Custom::PutPublicAccessBlock", - "UpdateReplacePolicy": "Delete", - }, - "SecuritySNSTopicE3C1354E": { - "Properties": { - "DisplayName": "aws-accelerator-Security", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsTopicKey4E909F1D", - "Arn", - ], - }, - "TopicName": "aws-accelerator-Security", - }, - "Type": "AWS::SNS::Topic", - }, - "SecuritySNSTopicPolicyFA391E1F": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "0", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "1", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "2", - }, - { - "Action": "sns:Publish", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - "Sid": "3", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "SecuritySNSTopicE3C1354E", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "SecuritySNSTopicnotifysecurityexamplecom26985403": { - "Properties": { - "Endpoint": "notify-security@example.com", - "Protocol": "email", - "TopicArn": { - "Ref": "SecuritySNSTopicE3C1354E", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-LoggingStack-333333333333-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-LoggingStack-333333333333-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SubscriptionFilterRoleB0B89330": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Access is needed to ready all log events across all log groups for replication to S3.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Subscription Filter to allow access to CloudWatch Destination", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "accessLogEvents", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-gwlb-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-gwlb-stack.test.ts.snap deleted file mode 100644 index 7fd872e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-gwlb-stack.test.ts.snap +++ /dev/null @@ -1,2940 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkAssociationsGwlbStack Construct(NetworkAssociationsGwlbStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBNonDelegatedendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/gwlb/Accelerator-GWLB-NonDelegated/endpointService/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/gwlb/Accelerator-GWLB/arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/gwlb/Accelerator-GWLB/endpointService/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBsecondaryarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/gwlb/Accelerator-GWLB-secondary/arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBsecondaryendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/gwlb/Accelerator-GWLB-secondary/endpointService/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/prefixList/accelerator-prefix-list/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Core/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Shared/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainStandaloneidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Standalone/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssecurityGroupManagementidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/securityGroup/Management/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssecurityGroupNetworkEndpointsCustomEndpointSgidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/securityGroup/Network-Endpoints-CustomEndpointSg/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsTgwAttachAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsTgwAttachBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Gateway/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/securityGroup/Data/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionTgwAttachAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionTgwAttachBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionvirtualPrivateGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/virtualPrivateGateway/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryARtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-A-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryBRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-B-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryCRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-C-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryDRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-D-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryCidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-C/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryDidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-D/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryDualStackidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Dual-Stack/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryIpv6OnlyidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Ipv6-Only/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryOutposts1idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-1/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryOutpostsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcSharedServicesMainidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/SharedServices-Main/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcSharedServicesMainsecurityGroupSharedServicesMainRsyslogsgidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/SharedServices-Main/securityGroup/SharedServices-Main-Rsyslog-sg/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcSharedServicesMainsubnetSharedServicesAppCidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/SharedServices-Main/subnet/SharedServices-App-C/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplateidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadARtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-A-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadBRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-B-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplatesubnetWorkloadAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/subnet/Workload-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplatesubnetWorkloadBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/subnet/Workload-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/security-stack/ebsDefaultVolumeEncryptionKeyArn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801": { - "DependsOn": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9", - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Cross account EC2 route OnEvent handler", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 15, - }, - "Type": "AWS::Lambda::Function", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionLogGroupCFE2C9BD": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2RouteCreateDelete", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9", - "Roles": [ - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC": { - "DependsOn": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E", - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsGwlbStack-555555555555-us-east-1/CrossAccountRouteFramework/CrossAccountRouteProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E", - "Roles": [ - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5": { - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeSubnets", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3": { - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTransitGatewayPrefixListReference", - "ec2:ModifyTransitGatewayPrefixListReference", - "ec2:DeleteTransitGatewayPrefixListReference", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyTgwReferences", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkInspectionAcceleratorGwlbListener": { - "Properties": { - "DefaultActions": [ - { - "TargetGroupArn": { - "Ref": "NetworkInspectioninstancetargetFirewallTargetGroupBF1E619D", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "NetworkInspectionNetworkInspectionTgwAttachAIpamSubnetLookup0C1296D2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-A/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionNetworkInspectionTgwAttachBIpamSubnetLookup4FB5ACC0": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-B/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionVpcEndpointAGwlbEpB5238F8C": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.us-east-1.", - { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkInspectionVpcEndpointAGwlbEpNetworkInspectionVpcNetworkInspectionGatewayRouteTableGwlbRouteDynamicA422EA421": { - "Properties": { - "DestinationCidrBlock": { - "Fn::GetAtt": [ - "NetworkInspectionNetworkInspectionTgwAttachAIpamSubnetLookup0C1296D2", - "ipv4CidrBlock", - ], - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionVpcEndpointAGwlbEpB5238F8C", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcEndpointAGwlbEpNetworkInspectionVpcNetworkInspectionTgwARouteTableGwlbRoute2D6CF39B": { - "Properties": { - "DestinationCidrBlock": "10.0.0.0/8", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionVpcEndpointAGwlbEpB5238F8C", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcEndpointBGwlbEpEF9CAE4C": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.us-east-1.", - { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkInspectionVpcEndpointBGwlbEpNetworkInspectionVpcNetworkInspectionGatewayRouteTableGwlbRouteDynamicB140E00A1": { - "Properties": { - "DestinationCidrBlock": { - "Fn::GetAtt": [ - "NetworkInspectionNetworkInspectionTgwAttachBIpamSubnetLookup4FB5ACC0", - "ipv4CidrBlock", - ], - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionVpcEndpointBGwlbEpEF9CAE4C", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcEndpointBGwlbEpNetworkInspectionVpcNetworkInspectionTgwBRouteTableGwlbRoute2B7C4C2E": { - "Properties": { - "DestinationCidrBlock": "10.0.0.0/8", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionVpcEndpointBGwlbEpEF9CAE4C", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcNetworkInspectionTgwARouteTableNetworkInterfaceDirectRoute": { - "Properties": { - "DestinationCidrBlock": "10.0.0.0/28", - "NetworkInterfaceId": "eni-0123456789abcdeff", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcNetworkInspectionTgwARouteTableNetworkInterfaceDirectRoutev6": { - "Properties": { - "DestinationIpv6CidrBlock": "fd01::/8", - "NetworkInterfaceId": "eni-0123456789abcdeff", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcNetworkInspectionTgwARouteTableNetworkInterfaceRoute": { - "Properties": { - "DestinationCidrBlock": "10.0.0.0/26", - "NetworkInterfaceId": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallNetworkInterface13C23C566", - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcNetworkInspectionTgwARouteTableNetworkInterfaceRoutev6": { - "Properties": { - "DestinationIpv6CidrBlock": "fd00::/8", - "NetworkInterfaceId": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallNetworkInterface13C23C566", - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionA38B05D1": { - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Firewall configuration replacement custom resource", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-FirewallConfigAccessRole", - ], - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 120, - }, - "Type": "AWS::Lambda::Function", - }, - "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionResourceLogGroup8D00AAAF": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionA38B05D1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionacceleratorFirewallConfigReplacementsResourceResourceD705B19B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionResourceLogGroup8D00AAAF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEvent426B7DCC", - "Arn", - ], - }, - "assetBucketName": "aws-accelerator-assets-111111111111-us-east-1", - "configBucketName": "aws-accelerator-firewall-config-555555555555-us-east-1", - "configFile": "instance-config.txt", - "firewallName": "accelerator-firewall", - "instanceId": { - "Ref": "NetworkInspectionacceleratorFirewallFirewall23AAB613", - }, - "licenseFile": "license.lic", - "managementAccountId": "111111111111", - "roleName": "AWSAccelerator-CrossAccount-SiteToSiteVpn-Role", - "staticReplacements": [ - { - "key": "CORP_CIDR_1", - "value": "10.0.0.0/16", - }, - { - "key": "CORP_CIDR_2", - "value": "192.168.0.0/16", - }, - ], - "vpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "vpnConnections": [ - { - "awsBgpAsn": 65521, - "cgwBgpAsn": 65500, - "cgwOutsideIp": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallElasticIp1628ED846", - }, - "id": { - "Ref": "SameAccountTgwVpnVpnConnection83EBF560", - }, - "name": "same-account-tgw-vpn", - }, - { - "awsBgpAsn": 65000, - "cgwBgpAsn": 65500, - "cgwOutsideIp": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallElasticIp1628ED846", - }, - "id": { - "Ref": "SameAccountVgwVpnVpnConnectionD0DEB61E", - }, - "name": "same-account-vgw-vpn", - }, - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEvent426B7DCC": { - "DependsOn": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleDefaultPolicyE188DFA4", - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleB802B0A9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsGwlbStack-555555555555-us-east-1/NetworkInspectionacceleratorFirewallConfigReplacements/Resource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionA38B05D1", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleB802B0A9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleB802B0A9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleDefaultPolicyE188DFA4": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionA38B05D1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallConfigReplacementsFunctionA38B05D1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleDefaultPolicyE188DFA4", - "Roles": [ - { - "Ref": "NetworkInspectionacceleratorFirewallConfigReplacementsframeworkonEventServiceRoleB802B0A9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkInspectionacceleratorFirewallFirewall23AAB613": { - "DependsOn": [ - "SameAccountTgwVpnVpnConnection83EBF560", - "SameAccountVgwVpnVpnConnectionD0DEB61E", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC28", - "reason": "Detailed monitoring not enabled by configuration.", - }, - { - "id": "AwsSolutions-EC29", - "reason": "Termination protection not enabled by configuration.", - }, - ], - }, - }, - "Properties": { - "LaunchTemplate": { - "LaunchTemplateId": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallLaunchTemplate74BB830F", - }, - "Version": { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallFirewallLaunchTemplate74BB830F", - "LatestVersionNumber", - ], - }, - }, - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-firewall", - }, - ], - }, - "Type": "AWS::EC2::Instance", - }, - "NetworkInspectionacceleratorFirewallFirewallEipAssociation18F25714C": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorFirewallFirewallElasticIp1628ED846", - "AllocationId", - ], - }, - "NetworkInterfaceId": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallNetworkInterface13C23C566", - }, - }, - "Type": "AWS::EC2::EIPAssociation", - }, - "NetworkInspectionacceleratorFirewallFirewallElasticIp1628ED846": { - "Properties": { - "Domain": "vpc", - }, - "Type": "AWS::EC2::EIP", - }, - "NetworkInspectionacceleratorFirewallFirewallLaunchTemplate74BB830F": { - "DependsOn": [ - "NetworkInspectionacceleratorFirewallFirewallEipAssociation18F25714C", - ], - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "/dev/xvda", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 20, - }, - }, - { - "DeviceName": "/dev/xvdb", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 10, - }, - }, - ], - "IamInstanceProfile": { - "Name": "EC2-Default-SSM-AD-Role", - }, - "ImageId": { - "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "InstanceType": "t3.large", - "MetadataOptions": { - "HttpTokens": "required", - }, - "NetworkInterfaces": [ - { - "Description": "Primary interface", - "DeviceIndex": 0, - "Groups": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "DeviceIndex": 1, - "NetworkInterfaceId": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallNetworkInterface13C23C566", - }, - }, - ], - "SecurityGroupIds": [], - }, - "LaunchTemplateName": "firewall-lt", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - "NetworkInspectionacceleratorFirewallFirewallNetworkInterface13C23C566": { - "Properties": { - "Description": "Secondary", - "GroupSet": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::NetworkInterface", - }, - "NetworkInspectionacceleratorManagerFirewallB809D98F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC28", - "reason": "Detailed monitoring not enabled by configuration.", - }, - { - "id": "AwsSolutions-EC29", - "reason": "Termination protection not enabled by configuration.", - }, - ], - }, - }, - "Properties": { - "LaunchTemplate": { - "LaunchTemplateId": { - "Ref": "NetworkInspectionacceleratorManagerFirewallLaunchTemplate2623D086", - }, - "Version": { - "Fn::GetAtt": [ - "NetworkInspectionacceleratorManagerFirewallLaunchTemplate2623D086", - "LatestVersionNumber", - ], - }, - }, - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-manager", - }, - ], - }, - "Type": "AWS::EC2::Instance", - }, - "NetworkInspectionacceleratorManagerFirewallLaunchTemplate2623D086": { - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "/dev/xvda", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 20, - }, - }, - { - "DeviceName": "/dev/xvdb", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 10, - }, - }, - ], - "IamInstanceProfile": {}, - "ImageId": { - "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "InstanceType": "t3.large", - "MetadataOptions": { - "HttpTokens": "required", - }, - "NetworkInterfaces": [ - { - "Description": "Primary interface", - "DeviceIndex": 0, - "Groups": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Description": "Secondary", - "DeviceIndex": 1, - "Groups": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "SecurityGroupIds": [], - }, - "LaunchTemplateName": "manager-lt", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - "NetworkInspectionasgtargetFirewallTargetGroupC774988D": { - "Properties": { - "HealthCheckEnabled": true, - "Matcher": {}, - "Name": "asg-target", - "Port": 6081, - "Protocol": "GENEVE", - "Tags": [ - { - "Key": "Name", - "Value": "asg-target", - }, - ], - "TargetType": "instance", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkInspectioninstancetargetFirewallTargetGroupBF1E619D": { - "Properties": { - "HealthCheckEnabled": true, - "Matcher": {}, - "Name": "instance-target", - "Port": 6081, - "Protocol": "GENEVE", - "Tags": [ - { - "Key": "Name", - "Value": "instance-target", - }, - ], - "TargetGroupAttributes": [ - { - "Key": "target_failover.on_deregistration", - "Value": "rebalance", - }, - { - "Key": "target_failover.on_unhealthy", - "Value": "rebalance", - }, - ], - "TargetType": "instance", - "Targets": [ - { - "Id": { - "Ref": "NetworkInspectionacceleratorFirewallFirewall23AAB613", - }, - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkInspectiontestAsgConfigReplacementsFunction8E5695D3": { - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Firewall configuration replacement custom resource", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-FirewallConfigAccessRole", - ], - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 120, - }, - "Type": "AWS::Lambda::Function", - }, - "NetworkInspectiontestAsgConfigReplacementsFunctionResourceLogGroup72BE7636": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "NetworkInspectiontestAsgConfigReplacementsFunction8E5695D3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectiontestAsgConfigReplacementsResourceResourceB393099D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "NetworkInspectiontestAsgConfigReplacementsFunctionResourceLogGroup72BE7636", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgConfigReplacementsframeworkonEvent31971E16", - "Arn", - ], - }, - "assetBucketName": "aws-accelerator-assets-111111111111-us-east-1", - "configBucketName": "aws-accelerator-firewall-config-555555555555-us-east-1", - "configFile": "asg-config.txt", - "licenseFile": "license.lic", - "managementAccountId": "111111111111", - "vpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectiontestAsgConfigReplacementsframeworkonEvent31971E16": { - "DependsOn": [ - "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRoleDefaultPolicyBB92E714", - "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRole21BD3C55", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsGwlbStack-555555555555-us-east-1/NetworkInspectiontestAsgConfigReplacements/Resource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgConfigReplacementsFunction8E5695D3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRole21BD3C55", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRole21BD3C55": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRoleDefaultPolicyBB92E714": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgConfigReplacementsFunction8E5695D3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgConfigReplacementsFunction8E5695D3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRoleDefaultPolicyBB92E714", - "Roles": [ - { - "Ref": "NetworkInspectiontestAsgConfigReplacementsframeworkonEventServiceRole21BD3C55", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkInspectiontestAsgFirewallAsg1343090A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-AS3", - "reason": "Scaling policies are not offered as a part of this solution.", - }, - ], - }, - }, - "Properties": { - "DesiredCapacity": "2", - "HealthCheckGracePeriod": 300, - "HealthCheckType": "EC2", - "LaunchTemplate": { - "LaunchTemplateId": { - "Ref": "NetworkInspectiontestAsgFirewallAsgLaunchTemplate333DF543", - }, - "Version": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgLaunchTemplate333DF543", - "LatestVersionNumber", - ], - }, - }, - "MaxInstanceLifetime": 86400, - "MaxSize": "4", - "MinSize": "1", - "Tags": [ - { - "Key": "Name", - "PropagateAtLaunch": true, - "Value": "test-asg", - }, - ], - "TargetGroupARNs": [ - { - "Ref": "NetworkInspectionasgtargetFirewallTargetGroupC774988D", - }, - ], - "VPCZoneIdentifier": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - "Type": "AWS::AutoScaling::AutoScalingGroup", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionBC1FC120": { - "DependsOn": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA0EEDA8C", - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole16F03D96", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole16F03D96", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupF88900CB": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionBC1FC120", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole16F03D96": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA0EEDA8C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyA0EEDA8C", - "Roles": [ - { - "Ref": "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole16F03D96", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent9D95F8E6": { - "DependsOn": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy4EF0DABE", - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole596D2991", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsGwlbStack-555555555555-us-east-1/NetworkInspectiontestAsgFirewallAsg/Resource/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionBC1FC120", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole596D2991", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole596D2991": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy4EF0DABE": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionBC1FC120", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionBC1FC120", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "iontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy4EF0DABE", - "Roles": [ - { - "Ref": "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole596D2991", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleResource284D5F40": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupF88900CB", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "NetworkInspectiontestAsgFirewallAsgAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent9D95F8E6", - "Arn", - ], - }, - "description": "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling", - "roleName": "AWSServiceRoleForAutoScaling", - "serviceName": "autoscaling.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectiontestAsgFirewallAsgLaunchTemplate333DF543": { - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "/dev/xvda", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 20, - }, - }, - { - "DeviceName": "/dev/xvdb", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 10, - }, - }, - ], - "IamInstanceProfile": { - "Name": "EC2-Default-SSM-AD-Role", - }, - "ImageId": { - "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "InstanceType": "t3.large", - "MetadataOptions": { - "HttpTokens": "required", - }, - "NetworkInterfaces": [ - { - "Description": "Primary interface", - "DeviceIndex": 0, - "Groups": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - { - "Description": "Secondary", - "DeviceIndex": 1, - "Groups": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsecurityGroupDataidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - "SecurityGroupIds": [], - }, - "LaunchTemplateName": "asg", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - "NetworkMainCore10400016sameAccountTgwVpnStaticRoute68BFCB50": { - "Properties": { - "DestinationCidrBlock": "10.40.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore10500016sameAccountTgwVpnStaticRouteE9B062C3": { - "Properties": { - "DestinationCidrBlock": "10.50.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainStandaloneacceleratorPrefixListsameAccountTgwVpnFCA3D5F2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - "Arn", - ], - }, - "prefixListReference": { - "PrefixListId": { - "Ref": "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayAttachmentId": { - "Ref": "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainStandaloneidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - }, - "Type": "Custom::TransitGatewayPrefixListReference", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcSecondaryEndpointAGwlbEpB92A94A3": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.us-east-1.", - { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBsecondaryendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkSecondaryVpcSecondaryEndpointBGwlbEpA752124D": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.us-east-1.", - { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBsecondaryendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkSecondaryVpcSharedSecondaryEndpointAGwlbEpBCA22033": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.us-east-1.", - { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBNonDelegatedendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkSecondaryVpcSharedSecondaryEndpointAGwlbEpNetworkSecondaryVpcNetworkSecondaryARtRouteTableGwlbRoute8BFB7480": { - "Properties": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryARtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkSecondaryVpcSharedSecondaryEndpointAGwlbEpBCA22033", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryVpcSharedSecondaryEndpointAGwlbEpNetworkSecondaryVpcNetworkSecondaryARtRouteTableGwlbRoutev6341332ED": { - "Properties": { - "DestinationIpv6CidrBlock": "::/0", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryARtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkSecondaryVpcSharedSecondaryEndpointAGwlbEpBCA22033", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryVpcSharedSecondaryEndpointDGwlbEp3C49791E": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.us-east-1.", - { - "Ref": "SsmParameterValueacceleratornetworkgwlbAcceleratorGWLBNonDelegatedendpointServiceidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - ], - }, - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryDidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkSecondaryVpcSharedSecondaryEndpointDGwlbEpNetworkSecondaryVpcNetworkSecondaryDRtRouteTableGwlbRouteEEDE358E": { - "Properties": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryDRtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkSecondaryVpcSharedSecondaryEndpointDGwlbEp3C49791E", - }, - }, - "Type": "AWS::EC2::Route", - }, - "SameAccountFirewallCgwCustomerGatewayC0237E66": { - "Properties": { - "BgpAsn": 65500, - "IpAddress": { - "Ref": "NetworkInspectionacceleratorFirewallFirewallElasticIp1628ED846", - }, - "Tags": [ - { - "Key": "Name", - "Value": "same-account-firewall-cgw", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::CustomerGateway", - }, - "SameAccountTgwVpnNetworkMainCorePropagation9D8E7774": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SameAccountTgwVpnNetworkMainSharedAssociationEED2CCFB": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "SameAccountTgwVpnNetworkMainSharedPropagation4D265167": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SameAccountTgwVpnVpnConnection83EBF560": { - "Properties": { - "CustomerGatewayId": { - "Ref": "SameAccountFirewallCgwCustomerGatewayC0237E66", - }, - "Tags": [ - { - "Key": "Name", - "Value": "same-account-tgw-vpn", - }, - ], - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNConnection", - }, - "SameAccountTgwVpnVpnTransitGatewayAttachment33D92C61": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - "SameAccountTgwVpnVpnConnection83EBF560", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "crossAccountVpnOptions": {}, - "name": "same-account-tgw-vpn", - "region": "us-east-1", - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "vpn", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "SameAccountVgwVpnVpnConnectionD0DEB61E": { - "Properties": { - "CustomerGatewayId": { - "Ref": "SameAccountFirewallCgwCustomerGatewayC0237E66", - }, - "Tags": [ - { - "Key": "Name", - "Value": "same-account-vgw-vpn", - }, - ], - "Type": "ipsec.1", - "VpnGatewayId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionvirtualPrivateGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPNConnection", - }, - "SharedServicesMainVpcSharedServicesAppARouteTableCrossAcctNetworkInterfaceRoute88DB3779": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/28", - "NetworkInterfaceId": { - "Ref": "SharedServicesMainfirewallWithSharedSubnetFirewallNetworkInterface0C2930B84", - }, - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesAppA27ABCD73", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainVpcSharedServicesAppARouteTableCrossAcctNetworkInterfaceRoutev6B085C72A": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "routeDefinition": { - "DestinationIpv6CidrBlock": "ff06::/64", - "NetworkInterfaceId": { - "Ref": "SharedServicesMainfirewallWithSharedSubnetFirewallNetworkInterface0C2930B84", - }, - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesAppA27ABCD73", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainfirewallWithSharedSubnetFirewallB18C6438": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC28", - "reason": "Detailed monitoring not enabled by configuration.", - }, - { - "id": "AwsSolutions-EC29", - "reason": "Termination protection not enabled by configuration.", - }, - ], - }, - }, - "Properties": { - "LaunchTemplate": { - "LaunchTemplateId": { - "Ref": "SharedServicesMainfirewallWithSharedSubnetFirewallLaunchTemplate62AEA644", - }, - "Version": { - "Fn::GetAtt": [ - "SharedServicesMainfirewallWithSharedSubnetFirewallLaunchTemplate62AEA644", - "LatestVersionNumber", - ], - }, - }, - "Tags": [ - { - "Key": "Name", - "Value": "firewall-with-shared-subnet", - }, - ], - }, - "Type": "AWS::EC2::Instance", - }, - "SharedServicesMainfirewallWithSharedSubnetFirewallEipAssociation0360D7D9A": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "SharedServicesMainfirewallWithSharedSubnetFirewallElasticIp0C1D2D48E", - "AllocationId", - ], - }, - "NetworkInterfaceId": { - "Ref": "SharedServicesMainfirewallWithSharedSubnetFirewallNetworkInterface0C2930B84", - }, - }, - "Type": "AWS::EC2::EIPAssociation", - }, - "SharedServicesMainfirewallWithSharedSubnetFirewallElasticIp0C1D2D48E": { - "Properties": { - "Domain": "vpc", - }, - "Type": "AWS::EC2::EIP", - }, - "SharedServicesMainfirewallWithSharedSubnetFirewallLaunchTemplate62AEA644": { - "DependsOn": [ - "SharedServicesMainfirewallWithSharedSubnetFirewallEipAssociation0360D7D9A", - ], - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "/dev/xvda", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 20, - }, - }, - { - "DeviceName": "/dev/xvdb", - "Ebs": { - "Encrypted": true, - "KmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorsecuritystackebsDefaultVolumeEncryptionKeyArnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - "VolumeSize": 10, - }, - }, - ], - "IamInstanceProfile": { - "Name": "EC2-Default-SSM-AD-Role", - }, - "ImageId": { - "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "InstanceType": "t3.large", - "MetadataOptions": { - "HttpTokens": "required", - }, - "NetworkInterfaces": [ - { - "DeviceIndex": 0, - "NetworkInterfaceId": { - "Ref": "SharedServicesMainfirewallWithSharedSubnetFirewallNetworkInterface0C2930B84", - }, - }, - ], - "SecurityGroupIds": [], - }, - "LaunchTemplateName": "firewall-lt", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - "SharedServicesMainfirewallWithSharedSubnetFirewallNetworkInterface0C2930B84": { - "Properties": { - "Description": "Primary interface", - "GroupSet": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcSharedServicesMainsecurityGroupSharedServicesMainRsyslogsgidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcSharedServicesMainsubnetSharedServicesAppCidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::NetworkInterface", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkAssociationsGwlbStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamLookupNetworkInspectionTgwA7808FEAE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "555555555555", - "parameterName": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-A/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamLookupSharedServicesAppA27ABCD73": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/routeTable/SharedServices-App-A/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkAssociationsGwlbStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-stack.test.ts.snap deleted file mode 100644 index 937c037..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-associations-stack.test.ts.snap +++ /dev/null @@ -1,5430 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkAssociationsStack Construct(NetworkAssociationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkdirectConnectGatewaysNetworkDXGWidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/directConnectGateways/Network-DXGW/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/prefixList/accelerator-prefix-list/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistipv6idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/prefixList/accelerator-prefix-list-ipv6/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverfirewallruleGroupsacceleratorblockgroupidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/firewall/ruleGroups/accelerator-block-group/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogscwlidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/queryLogConfigs/accelerator-query-logs-cwl/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogss3idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/queryLogConfigs/accelerator-query-logs-s3/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsnetworklocalendpointquerylogscwlidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/queryLogConfigs/network-local-endpoint-query-logs-cwl/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsnetworklocalendpointquerylogss3idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/queryLogConfigs/network-local-endpoint-query-logs-s3/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverrulesexampleruleidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/rules/example-rule/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main-2/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2CoreidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Core/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2SegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Segregated/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2SharedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Shared/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2StandaloneidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Standalone/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Core/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Segregated/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Shared/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainStandaloneidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Standalone/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsalbappAalb01idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/alb/appA-alb-01/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointscidripv4C96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/cidr/ipv4", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnetworkAclTestNACLidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/networkAcl/TestNACL/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnlbappAnlb01idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/nlb/appA-nlb-01/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneaccessanalyzeridC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/access-analyzer/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonecodeartifactapiidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/codeartifact.api/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonecodeartifactrepositoriesidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/codeartifact.repositories/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ec2/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2messagesidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ec2messages/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneecrdkridC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ecr.dkr/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneeksidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/eks/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonekmsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/kms/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonelogsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/logs/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZones3globalaccesspointidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/s3-global.accesspoint/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZones3idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/s3/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonesecretsmanageridC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/secretsmanager/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ssm/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmmessagesidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ssmmessages/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/transitGatewayAttachment/Network-Endpoints-2/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/transitGatewayAttachment/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Gateway/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/transitGatewayAttachment/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarynetworkAclTestNaclidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/networkAcl/TestNacl/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryARtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-A-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryBRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-B-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryCRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-C-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryDRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-D-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcSharedServicesMainidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/SharedServices-Main/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplatecidripv4C96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/cidr/ipv4", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplateidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadARtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-A-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadBRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-B-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorVpnNetworkMainCoreAssociationEF397C51": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "AcceleratorVpnNetworkMainCorePropagation585EB5AE": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "accelerator-vpn", - "region": "us-east-1", - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "vpn", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "AssociateHostedZonesF0E2F0DA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderLogGroupDEA7760D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71", - "Arn", - ], - }, - "accountIds": [ - "555555555555", - "444444444444", - ], - "hostedZoneAccountId": "555555555555", - "hostedZoneIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2messagesidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmmessagesidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonekmsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonelogsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZones3idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneecrdkridC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZones3globalaccesspointidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonecodeartifactrepositoriesidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonecodeartifactapiidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneeksidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonesecretsmanageridC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneaccessanalyzeridC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - "roleName": "AWSAccelerator-EnableCentralEndpointsRole-us-east-1", - "tagFilters": [ - { - "key": "accelerator:use-central-endpoints", - "value": "true", - }, - { - "key": "accelerator:central-endpoints-account-id", - "value": "555555555555", - }, - ], - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::Route53AssociateHostedZones", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801": { - "DependsOn": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9", - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Cross account EC2 route OnEvent handler", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 15, - }, - "Type": "AWS::Lambda::Function", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionLogGroupCFE2C9BD": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource provider requires managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource provider requires access to assume cross-account role", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2RouteCreateDelete", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9", - "Roles": [ - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC": { - "DependsOn": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E", - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/CrossAccountRouteFramework/CrossAccountRouteProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource provider requires access to assume cross-account role", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E", - "Roles": [ - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource provider requires managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountVpcPeering9E0C69A2": { - "Properties": { - "PeerOwnerId": "444444444444", - "PeerRegion": "us-east-1", - "PeerRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "PeerVpcId": { - "Ref": "SsmParamLookupCrossAccountC2B66C85", - }, - "Tags": [ - { - "Key": "Name", - "Value": "CrossAccount", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCPeeringConnection", - }, - "CrossAccountVpcPeeringNetworkEndpointsVpcNetworkEndpointsARouteTableVpcPeer338649DC": { - "Properties": { - "DestinationCidrBlock": "10.4.0.0/16", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "CrossAccountVpcPeeringNetworkEndpointsVpcNetworkEndpointsARouteTableVpcPeerv6521A0A3D": { - "Properties": { - "DestinationIpv6CidrBlock": "fd00::/8", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "CrossAccountVpcPeeringNetworkEndpointsVpcNetworkEndpointsBRouteTableVpcPeer7B5320E6": { - "Properties": { - "DestinationCidrBlock": "10.4.0.0/16", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "CrossAccountVpcPeeringNetworkEndpointsVpcNetworkEndpointsBRouteTableVpcPeerv69D50B758": { - "Properties": { - "DestinationIpv6CidrBlock": "fd00::/8", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppARouteTableVpcPeer94525782": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppA08A11B17", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppARouteTableVpcPeerv6B4238292": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationIpv6CidrBlock": "fd00::/8", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppA08A11B17", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppBRouteTableVpcPeer02D33BA6": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppB580D7687", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppBRouteTableVpcPeerv66590CCF1": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationIpv6CidrBlock": "fd00::/8", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppB580D7687", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppCRouteTableVpcPeerAC82CB89": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppCCD1A1D60", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAcctSsmParamCrossAccountVpcPeeringEB560383": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/vpcPeering/CrossAccount/id", - "value": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92": { - "DependsOn": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderLogGroup966224A3": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreateDirectConnectGatewayAssociation", - "directconnect:DeleteDirectConnectGatewayAssociation", - "directconnect:DescribeDirectConnectGatewayAssociations", - "directconnect:UpdateDirectConnectGatewayAssociation", - "ec2:DescribeTransitGatewayAttachments", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DirectConnectGatewayCRUD", - }, - { - "Action": [ - "lambda:InvokeFunction", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:AWSAccelerator-NetworkAss-CustomDirectConnect*", - ], - ], - }, - "Sid": "InvokeSelf", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5": { - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeSubnets", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetResourceShareCustomResourceProviderHandler5DB0EF7A": { - "DependsOn": [ - "CustomGetResourceShareCustomResourceProviderRoleBC3FCEEF", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetResourceShareCustomResourceProviderRoleBC3FCEEF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetResourceShareCustomResourceProviderRoleBC3FCEEF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ram:AcceptResourceShareInvitation", - "ram:GetResourceShareInvitations", - "ram:GetResourceShares", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetResourceShareItemCustomResourceProviderHandlerE72D791B": { - "DependsOn": [ - "CustomGetResourceShareItemCustomResourceProviderRoleA7181EBA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetResourceShareItemCustomResourceProviderRoleA7181EBA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetResourceShareItemCustomResourceProviderLogGroup6DB08FC0": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetResourceShareItemCustomResourceProviderHandlerE72D791B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetResourceShareItemCustomResourceProviderRoleA7181EBA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ram:ListResources", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71": { - "DependsOn": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderLogGroupDEA7760D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeVpcs", - "route53:AssociateVPCWithHostedZone", - "route53:CreateVPCAssociationAuthorization", - "route53:DeleteVPCAssociationAuthorization", - "route53:GetHostedZone", - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Route53AssociateHostedZonesActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomShareSubnetTagsCustomResourceProviderHandler4A04C5EC": { - "DependsOn": [ - "CustomShareSubnetTagsCustomResourceProviderRole2582495C", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomShareSubnetTagsCustomResourceProviderRole2582495C", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomShareSubnetTagsCustomResourceProviderLogGroupA1F3F80A": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomShareSubnetTagsCustomResourceProviderHandler4A04C5EC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomShareSubnetTagsCustomResourceProviderRole2582495C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DeleteTags", - "ec2:CreateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":*:subnet/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":*:vpc/*", - ], - ], - }, - ], - }, - { - "Action": [ - "ec2:DescribeTags", - "ec2:DescribeVpcs", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F": { - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to put cross-account ssm parameter value", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DeleteParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmPutParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3": { - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTransitGatewayPrefixListReference", - "ec2:ModifyTransitGatewayPrefixListReference", - "ec2:DeleteTransitGatewayPrefixListReference", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyTgwReferences", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "EnhancedVpnNetworkMainCoreAssociation2D5BE78A": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "EnhancedVpnVpnTransitGatewayAttachment579ECAAD", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "EnhancedVpnNetworkMainCorePropagation47AEEE67": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "EnhancedVpnVpnTransitGatewayAttachment579ECAAD", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "EnhancedVpnVpnTransitGatewayAttachment579ECAAD": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "enhanced-vpn", - "region": "us-east-1", - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "vpn", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "ListenerNetworkEndpointsappAAlb01appAListener2": { - "Properties": { - "Certificates": [ - {}, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAalbtg176E11B7D", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAalbtg176E11B7D", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsalbappAalb01idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Port": 80, - "Protocol": "HTTP", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "ListenerNetworkEndpointsappANlb01appAListener1": { - "Properties": { - "AlpnPolicy": [], - "Certificates": [ - {}, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAnlbtg19C910FB9", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAnlbtg19C910FB9", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnlbappAnlb01idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Port": 80, - "Protocol": "TCP", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "ListenerNetworkEndpointsappANlb01appAListener2": { - "Properties": { - "AlpnPolicy": [], - "Certificates": [ - {}, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAnlbtg2EE7F2886", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAnlbtg2EE7F2886", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnlbappAnlb01idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Port": 80, - "Protocol": "TCP", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "NetworkDxgwNetworkMainCoreAssociation1B0096A1": { - "Properties": { - "TransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A", - "TransitGatewayAttachmentId", - ], - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkDxgwNetworkMainCorePropagation959338BE": { - "Properties": { - "TransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A", - "TransitGatewayAttachmentId", - ], - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderLogGroup966224A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92", - "Arn", - ], - }, - "allowedPrefixes": [ - "10.0.0.0/8", - "192.168.0.0/16", - ], - "directConnectGatewayId": { - "Ref": "SsmParameterValueacceleratornetworkdirectConnectGatewaysNetworkDXGWidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "gatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "Custom::DirectConnectGatewayAssociation", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpoints2NetworkMain2CorePropagationD9285528": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2CoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpoints2NetworkMain2SegregatedPropagationD38531F3": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2SegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpoints2NetworkMain2SharedAssociation8D643CD4": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2SharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkEndpoints2NetworkMain2SharedPropagationE3803FF5": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2SharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsNetworkMainCorePropagationB71504FB": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsNetworkMainSegregatedPropagation126AD34C": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsNetworkMainSharedAssociation32A1E778": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkEndpointsNetworkMainSharedPropagationB52F1A2C": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsToSecondaryVpcPeering08A08BA2": { - "Properties": { - "PeerOwnerId": "555555555555", - "PeerRegion": "us-east-1", - "PeerVpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Tags": [ - { - "Key": "Name", - "Value": "NetworkEndpointsToSecondary", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCPeeringConnection", - }, - "NetworkEndpointsacceleratorBlockGroupRuleGroupAssociation1AF8FC15": { - "Properties": { - "FirewallRuleGroupId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverfirewallruleGroupsacceleratorblockgroupidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Priority": 101, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::FirewallRuleGroupAssociation", - }, - "NetworkEndpointsacceleratorQueryLogsCwlQueryLogAssociationEA44BB29": { - "Properties": { - "ResolverQueryLogConfigId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogscwlidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "ResourceId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation", - }, - "NetworkEndpointsacceleratorQueryLogsS3QueryLogAssociationC248D059": { - "Properties": { - "ResolverQueryLogConfigId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogss3idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "ResourceId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation", - }, - "NetworkEndpointsappAalbtg176E11B7D": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "HTTP", - "Matcher": {}, - "Name": "appA-alb-tg-1", - "Port": 80, - "Protocol": "HTTP", - "Tags": [ - { - "Key": "Name", - "Value": "appA-alb-tg-1", - }, - ], - "TargetType": "instance", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkEndpointsappAnlbtg19C910FB9": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "TCP", - "Matcher": {}, - "Name": "appA-nlb-tg-1", - "Port": 80, - "Protocol": "TCP", - "Tags": [ - { - "Key": "Name", - "Value": "appA-nlb-tg-1", - }, - ], - "TargetType": "instance", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkEndpointsappAnlbtg2EE7F2886": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "TCP", - "Matcher": {}, - "Name": "appA-nlb-tg-2", - "Port": 80, - "Protocol": "TCP", - "Tags": [ - { - "Key": "Name", - "Value": "appA-nlb-tg-2", - }, - ], - "TargetType": "ip", - "Targets": { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupB24081DE", - "ipAddresses", - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkEndpointsexampleRuleRuleAssociationB302A795": { - "Properties": { - "ResolverRuleId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverrulesexampleruleidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverRuleAssociation", - }, - "NetworkInspectionNetworkMainCorePropagation37D40A21": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkInspectionNetworkMainSegregatedPropagationA07EB35C": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkInspectionNetworkMainSharedAssociationC5D467EA": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkInspectionNetworkMainSharedPropagation9C8183A3": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkInspectionTransitGatewayConnectAttachmentNetworkInspectionTransitGatewayConnectAD31631B": { - "Properties": { - "Options": { - "Protocol": "gre", - }, - "Tags": [ - { - "Key": "Environment", - "Value": "CentralInspection", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - "TransportTransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayConnect", - }, - "NetworkInspectiondeployedtg2898BDF56": { - "Properties": { - "HealthCheckEnabled": true, - "Matcher": {}, - "Name": "deployed-tg-2", - "Port": 443, - "Protocol": "HTTPS", - "Tags": [ - { - "Key": "Name", - "Value": "deployed-tg-2", - }, - ], - "TargetType": "instance", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkInspectiontgwithouttargetip9ED227FD": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "HTTP", - "Matcher": {}, - "Name": "tg-without-target-ip", - "Port": 80, - "Protocol": "HTTP", - "ProtocolVersion": "HTTP1", - "Tags": [ - { - "Key": "Name", - "Value": "tg-without-target-ip", - }, - ], - "TargetType": "ip", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkInspectiontgwithtargetipB51306E1": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "HTTP", - "Matcher": {}, - "Name": "tg-with-target-ip", - "Port": 80, - "Protocol": "HTTP", - "ProtocolVersion": "HTTP1", - "Tags": [ - { - "Key": "Name", - "Value": "tg-with-target-ip", - }, - ], - "TargetType": "ip", - "Targets": [ - { - "Id": "10.3.100.20", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkMain2Core101000016NetworkEndpointsNetworkStaticRouteE97CED2D": { - "Properties": { - "DestinationCidrBlock": "10.100.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2CoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMain2Core111132blackholeStaticRoute0ADA733D": { - "Properties": { - "Blackhole": true, - "DestinationCidrBlock": "1.1.1.1/32", - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2CoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMain2CoreacceleratorPrefixListNetworkEndpointsNetworkBB01BDA8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - "Arn", - ], - }, - "prefixListReference": { - "PrefixListId": { - "Ref": "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpoints2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2routeTablesNetworkMain2CoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - }, - "Type": "Custom::TransitGatewayPrefixListReference", - "UpdateReplacePolicy": "Delete", - }, - "NetworkMainCore0NetworkEndpointsNetworkStaticRoute554C10A2": { - "Properties": { - "DestinationCidrBlock": "::/0", - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore101000016NetworkEndpointsNetworkStaticRouteEC325B8C": { - "Properties": { - "DestinationCidrBlock": "10.100.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore102000016NetworkDXGWStaticRoute7BFECA73": { - "Properties": { - "DestinationCidrBlock": "10.200.0.0/16", - "TransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A", - "TransitGatewayAttachmentId", - ], - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore10300016acceleratorvpnStaticRoute1A702C47": { - "Properties": { - "DestinationCidrBlock": "10.30.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore111132blackholeStaticRouteEF8CDB96": { - "Properties": { - "Blackhole": true, - "DestinationCidrBlock": "1.1.1.1/32", - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCoreacceleratorPrefixListNetworkEndpointsNetwork3EFFBC42": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - "Arn", - ], - }, - "prefixListReference": { - "PrefixListId": { - "Ref": "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - }, - "Type": "Custom::TransitGatewayPrefixListReference", - "UpdateReplacePolicy": "Delete", - }, - "NetworkMainCorefd008blackholeStaticRoute2581AB5F": { - "Properties": { - "Blackhole": true, - "DestinationCidrBlock": "fd00::/8", - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainShared102000016NetworkMainAndSharedServicesMainPeeringStaticRoute6433AEC2": { - "Properties": { - "DestinationCidrBlock": "10.200.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "NetworkNetworkMainAndSharedServicesMainPeeringTransitGatewayPeeringAttachment6A5D4528", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkNetworkMainAndSharedServicesMainPeeringTransitGatewayPeeringAttachment6A5D4528": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "Network-Main-And-SharedServices-Main-Peering", - "region": "us-east-1", - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "peering", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryTestNacl105EgressNaclRuleA32FBE84": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/SharedServices-Ipam-East/subnet/SharedServices-A/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryTestNacl105IngressNaclRule02EE83A1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/SharedServices-Ipam-East/subnet/SharedServices-A/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryTestNacl106EgressNaclRule04845A6D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-west-2", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/SharedServices-Ipam-West/subnet/SharedServices-A/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryTestNacl106IngressNaclRuleCBC09801": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-west-2", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/SharedServices-Ipam-West/subnet/SharedServices-A/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryTestNaclinbound4": { - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkSecondaryTestNacl105IngressNaclRule02EE83A1", - "ipv4CidrBlock", - ], - }, - "Egress": false, - "NetworkAclId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarynetworkAclTestNaclidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 105, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryTestNaclinbound5": { - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkSecondaryTestNacl106IngressNaclRuleCBC09801", - "ipv4CidrBlock", - ], - }, - "Egress": false, - "NetworkAclId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarynetworkAclTestNaclidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 106, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryTestNacloutbound4": { - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkSecondaryTestNacl105EgressNaclRuleA32FBE84", - "ipv4CidrBlock", - ], - }, - "Egress": true, - "NetworkAclId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarynetworkAclTestNaclidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 105, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryTestNacloutbound5": { - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkSecondaryTestNacl106EgressNaclRule04845A6D", - "ipv4CidrBlock", - ], - }, - "Egress": true, - "NetworkAclId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarynetworkAclTestNaclidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 106, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondarynetworkLocalEndpointQueryLogsCwlQueryLogAssociation93B2790B": { - "Properties": { - "ResolverQueryLogConfigId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsnetworklocalendpointquerylogscwlidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "ResourceId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation", - }, - "NetworkSecondarynetworkLocalEndpointQueryLogsS3QueryLogAssociation35881457": { - "Properties": { - "ResolverQueryLogConfigId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsnetworklocalendpointquerylogss3idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "ResourceId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation", - }, - "ShareSubnetTagsSharedServicesSharedServicesAppC062D4D03": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomShareSubnetTagsCustomResourceProviderLogGroupA1F3F80A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomShareSubnetTagsCustomResourceProviderHandler4A04C5EC", - "Arn", - ], - }, - "acceleratorSsmParamPrefix": "/accelerator", - "sharedSubnetId": { - "Ref": "SharedServicesMainSharedServicesAppCSubnetE0D5A73C", - }, - "sharedSubnetName": "SharedServices-App-C", - "subnetTags": [ - { - "key": "Name", - "value": "SharedServices-App-C", - }, - ], - "vpcName": "SharedServices-Main", - "vpcTags": [ - { - "key": "Name", - "value": "SharedServices-Main", - }, - ], - }, - "Type": "Custom::ShareSubnetTags", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainNetworkMainCorePropagationB8A9444B": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SharedServicesMainNetworkMainSegregatedPropagation1734E165": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SharedServicesMainNetworkMainSharedAssociationFE2C7420": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "SharedServicesMainNetworkMainSharedPropagationA83F55E7": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SharedServicesMainSharedServicesAppCSubnetE0D5A73C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetResourceShareItemCustomResourceProviderLogGroup6DB08FC0", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetResourceShareItemCustomResourceProviderHandlerE72D791B", - "Arn", - ], - }, - "resourceOwner": "OTHER-ACCOUNTS", - "resourceShareArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ram:us-east-1:444444444444:resource-share/", - { - "Ref": "SharedServicesMainSharedServicesAppCSubnetShare33CA4D7C", - }, - ], - ], - }, - "resourceType": "ec2:Subnet", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetResourceShareItem", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainSharedServicesAppCSubnetShare33CA4D7C": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetResourceShareCustomResourceProviderHandler5DB0EF7A", - "Arn", - ], - }, - "name": "SharedServices-App-C_SubnetShare", - "owningAccountId": "444444444444", - "resourceOwner": "OTHER-ACCOUNTS", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetResourceShare", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "SharedServices-Main", - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-DescribeTgwAttachRole-us-east-1", - ], - ], - }, - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "vpc", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainVpcSharedServicesMainRsyslogSgSg82BD8A5A": { - "Properties": { - "GroupDescription": "Security Group for AWS Accelerator rsyslog", - "GroupName": "SharedServices-Main-Rsyslog-sg", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "All Outbound", - "IpProtocol": "-1", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "10.0.0.0/8", - "Description": "allow inbound traffic", - "FromPort": 514, - "IpProtocol": "tcp", - "ToPort": 514, - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "allow inbound traffic", - "FromPort": 514, - "IpProtocol": "udp", - "ToPort": 514, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "SharedServices-Main-Rsyslog-sg", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcSharedServicesMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamCrossAccountVpcPeering03403783": { - "Properties": { - "Name": "/accelerator/network/vpcPeering/CrossAccount/id", - "Type": "String", - "Value": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamLookupCrossAccountC2B66C85": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamLookupSharedServicesMain444444444444SharedServicesAppA08A11B17": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/routeTable/SharedServices-App-A/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamLookupSharedServicesMain444444444444SharedServicesAppB580D7687": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/routeTable/SharedServices-App-B/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamLookupSharedServicesMain444444444444SharedServicesAppCCD1A1D60": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/routeTable/SharedServices-App-C/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamNetworkEndpointsToSecondaryVpcPeeringED109890": { - "Properties": { - "Name": "/accelerator/network/vpcPeering/NetworkEndpointsToSecondary/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsToSecondaryVpcPeering08A08BA2", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamSharedServicesMainSharedServicesMainRsyslogSgSecurityGroupFCDC7FBA": { - "Properties": { - "Name": "/accelerator/network/vpc/SharedServices-Main/securityGroup/SharedServices-Main-Rsyslog-sg/id", - "Type": "String", - "Value": { - "Ref": "SharedServicesMainVpcSharedServicesMainRsyslogSgSg82BD8A5A", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamWorkloadTemplateToNetworkEndpointsVpcPeeringA6E15753": { - "Properties": { - "Name": "/accelerator/network/vpcPeering/WorkloadTemplateToNetworkEndpoints/id", - "Type": "String", - "Value": { - "Ref": "WorkloadTemplateToNetworkEndpointsVpcPeering1FE95B9F", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "WorkloadTemplateToNetworkEndpointsVpcPeering1FE95B9F": { - "Properties": { - "PeerOwnerId": "555555555555", - "PeerRegion": "us-east-1", - "PeerVpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Tags": [ - { - "Key": "Name", - "Value": "WorkloadTemplateToNetworkEndpoints", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcWorkloadTemplateidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCPeeringConnection", - }, - "WorkloadTemplateToNetworkEndpointsVpcPeeringNetworkEndpointsVpcNetworkEndpointsARouteTablePeeringRouteToWorkloadTemplate18B85C31": { - "Properties": { - "DestinationCidrBlock": { - "Ref": "SsmParameterValueacceleratornetworkvpcWorkloadTemplatecidripv4C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "WorkloadTemplateToNetworkEndpointsVpcPeering1FE95B9F", - }, - }, - "Type": "AWS::EC2::Route", - }, - "WorkloadTemplateToNetworkEndpointsVpcPeeringNetworkEndpointsVpcNetworkEndpointsBRouteTablePeeringRouteToWorkloadTemplate2A3384CF": { - "Properties": { - "DestinationCidrBlock": { - "Ref": "SsmParameterValueacceleratornetworkvpcWorkloadTemplatecidripv4C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "WorkloadTemplateToNetworkEndpointsVpcPeering1FE95B9F", - }, - }, - "Type": "AWS::EC2::Route", - }, - "WorkloadTemplateToNetworkEndpointsVpcPeeringWorkloadTemplateVpcWorkloadARtRouteTablePeeringRouteToNetworkEndpointsA455931E": { - "Properties": { - "DestinationCidrBlock": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointscidripv4C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadARtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "WorkloadTemplateToNetworkEndpointsVpcPeering1FE95B9F", - }, - }, - "Type": "AWS::EC2::Route", - }, - "WorkloadTemplateToNetworkEndpointsVpcPeeringWorkloadTemplateVpcWorkloadBRtRouteTablePeeringRouteToNetworkEndpoints3AE45A7C": { - "Properties": { - "DestinationCidrBlock": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointscidripv4C96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadBRtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "WorkloadTemplateToNetworkEndpointsVpcPeering1FE95B9F", - }, - }, - "Type": "AWS::EC2::Route", - }, - "appAnlbtg2ipLookupB24081DE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaLogGroup713C77F0", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEvent694CD713", - "Arn", - ], - }, - "assumeRoleName": "AWSAccelerator-GetNLBIPAddressLookup", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - "targets": [ - { - "account": "555555555555", - "nlbName": "appA-nlb-01", - "region": "us-east-1", - }, - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaEF941FA2": { - "DependsOn": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRoleDefaultPolicyD488029A", - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRole92332B48", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRole92332B48", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 15, - }, - "Type": "AWS::Lambda::Function", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaLogGroup713C77F0": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaEF941FA2", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRole92332B48": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRoleDefaultPolicyD488029A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-GetNLBIPAddressLookup", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRoleDefaultPolicyD488029A", - "Roles": [ - { - "Ref": "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaServiceRole92332B48", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEvent694CD713": { - "DependsOn": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleDefaultPolicy60AF00B7", - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleA31E553B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/appA-nlb-tg-2-ipLookup/appA-nlb-tg-2-ipLookupProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaEF941FA2", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleA31E553B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleA31E553B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleDefaultPolicy60AF00B7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaEF941FA2", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderLambdaEF941FA2", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleDefaultPolicy60AF00B7", - "Roles": [ - { - "Ref": "appAnlbtg2ipLookupappAnlbtg2ipLookupProviderframeworkonEventServiceRoleA31E553B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`NoVpcFlowLogStack Construct(NetworkAssociationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkdirectConnectGatewaysNetworkDXGWidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/directConnectGateways/Network-DXGW/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/prefixList/accelerator-prefix-list/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverfirewallruleGroupsacceleratorblockgroupidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/firewall/ruleGroups/accelerator-block-group/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogscwlidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/queryLogConfigs/accelerator-query-logs-cwl/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogss3idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/queryLogConfigs/accelerator-query-logs-s3/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverrulesexampleruleidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/rules/example-rule/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Core/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Segregated/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Shared/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainStandaloneidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Standalone/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsalbappAalb01idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/alb/appA-alb-01/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnetworkAclTestNACLidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/networkAcl/TestNACL/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnlbappAnlb01idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/nlb/appA-nlb-01/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ec2/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2messagesidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ec2messages/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonekmsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/kms/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonelogsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/logs/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ssm/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmmessagesidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ssmmessages/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/transitGatewayAttachment/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Gateway/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/transitGatewayAttachment/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorVpnNetworkMainCoreAssociationEF397C51": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "AcceleratorVpnNetworkMainCorePropagation585EB5AE": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "accelerator-vpn", - "region": "us-east-1", - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "vpn", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "AssociateHostedZonesF0E2F0DA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderLogGroupDEA7760D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71", - "Arn", - ], - }, - "accountIds": [ - "555555555555", - "444444444444", - ], - "hostedZoneAccountId": "555555555555", - "hostedZoneIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZoneec2messagesidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonessmmessagesidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonekmsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsroute53hostedZonelogsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - "roleName": "AWSAccelerator-EnableCentralEndpointsRole-us-east-1", - "tagFilters": [ - { - "key": "accelerator:use-central-endpoints", - "value": "true", - }, - { - "key": "accelerator:central-endpoints-account-id", - "value": "555555555555", - }, - ], - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::Route53AssociateHostedZones", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801": { - "DependsOn": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9", - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Cross account EC2 route OnEvent handler", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 15, - }, - "Type": "AWS::Lambda::Function", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionLogGroupCFE2C9BD": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource provider requires managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource provider requires access to assume cross-account role", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2RouteCreateDelete", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicy2CB6E8A9", - "Roles": [ - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteFunctionServiceRole0BC7B615", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC": { - "DependsOn": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E", - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/CrossAccountRouteFramework/CrossAccountRouteProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource provider requires access to assume cross-account role", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteFunctionAFDC4801", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyDBD57C1E", - "Roles": [ - { - "Ref": "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventServiceRoleED507AC0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource provider requires managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountVpcPeering9E0C69A2": { - "Properties": { - "PeerOwnerId": "444444444444", - "PeerRegion": "us-east-1", - "PeerRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "PeerVpcId": { - "Ref": "SsmParamLookupCrossAccountC2B66C85", - }, - "Tags": [ - { - "Key": "Name", - "Value": "CrossAccount", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCPeeringConnection", - }, - "CrossAccountVpcPeeringNetworkEndpointsVpcNetworkEndpointsARouteTableVpcPeer338649DC": { - "Properties": { - "DestinationCidrBlock": "10.4.0.0/16", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "CrossAccountVpcPeeringNetworkEndpointsVpcNetworkEndpointsBRouteTableVpcPeer7B5320E6": { - "Properties": { - "DestinationCidrBlock": "10.4.0.0/16", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppARouteTableVpcPeer94525782": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppA08A11B17", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountVpcPeeringSharedServicesMainVpcSharedServicesAppBRouteTableVpcPeer02D33BA6": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CrossAccountRouteFrameworkCrossAccountRouteProviderframeworkonEventE7CF8ACC", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/24", - "RouteTableId": { - "Ref": "SsmParamLookupSharedServicesMain444444444444SharedServicesAppB580D7687", - }, - "VpcPeeringConnectionId": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "CrossAcctSsmParamCrossAccountVpcPeeringEB560383": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/vpcPeering/CrossAccount/id", - "value": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92": { - "DependsOn": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderLogGroup966224A3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreateDirectConnectGatewayAssociation", - "directconnect:DeleteDirectConnectGatewayAssociation", - "directconnect:DescribeDirectConnectGatewayAssociations", - "directconnect:UpdateDirectConnectGatewayAssociation", - "ec2:DescribeTransitGatewayAttachments", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DirectConnectGatewayCRUD", - }, - { - "Action": [ - "lambda:InvokeFunction", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:AWSAccelerator-NetworkAss-CustomDirectConnect*", - ], - ], - }, - "Sid": "InvokeSelf", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71": { - "DependsOn": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderLogGroupDEA7760D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeVpcs", - "route53:AssociateVPCWithHostedZone", - "route53:CreateVPCAssociationAuthorization", - "route53:DeleteVPCAssociationAuthorization", - "route53:GetHostedZone", - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Route53AssociateHostedZonesActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F": { - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to put cross-account ssm parameter value", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DeleteParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmPutParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3": { - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTransitGatewayPrefixListReference", - "ec2:ModifyTransitGatewayPrefixListReference", - "ec2:DeleteTransitGatewayPrefixListReference", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyTgwReferences", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ListenerNetworkEndpointsappAAlb01appAListener2": { - "Properties": { - "Certificates": [ - {}, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAalbtg176E11B7D", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAalbtg176E11B7D", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsalbappAalb01idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Port": 80, - "Protocol": "HTTP", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "ListenerNetworkEndpointsappANlb01appAListener1": { - "Properties": { - "AlpnPolicy": [], - "Certificates": [ - {}, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAnlbtg19C910FB9", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "NetworkEndpointsappAnlbtg19C910FB9", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsnlbappAnlb01idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Port": 80, - "Protocol": "TCP", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "NetworkDxgwNetworkMainCoreAssociation1B0096A1": { - "Properties": { - "TransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A", - "TransitGatewayAttachmentId", - ], - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkDxgwNetworkMainCorePropagation959338BE": { - "Properties": { - "TransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A", - "TransitGatewayAttachmentId", - ], - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderLogGroup966224A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92", - "Arn", - ], - }, - "allowedPrefixes": [ - "10.0.0.0/8", - "192.168.0.0/16", - ], - "directConnectGatewayId": { - "Ref": "SsmParameterValueacceleratornetworkdirectConnectGatewaysNetworkDXGWidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "gatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "Custom::DirectConnectGatewayAssociation", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsNetworkMainCorePropagationB71504FB": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsNetworkMainSegregatedPropagation126AD34C": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsNetworkMainSharedAssociation32A1E778": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkEndpointsNetworkMainSharedPropagationB52F1A2C": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkEndpointsToInspectionVpcPeeringE81AF602": { - "Properties": { - "PeerOwnerId": "555555555555", - "PeerRegion": "us-east-1", - "PeerVpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Tags": [ - { - "Key": "Name", - "Value": "NetworkEndpointsToInspection", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCPeeringConnection", - }, - "NetworkEndpointsacceleratorBlockGroupRuleGroupAssociation1AF8FC15": { - "Properties": { - "FirewallRuleGroupId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverfirewallruleGroupsacceleratorblockgroupidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Priority": 101, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::FirewallRuleGroupAssociation", - }, - "NetworkEndpointsacceleratorQueryLogsCwlQueryLogAssociationEA44BB29": { - "Properties": { - "ResolverQueryLogConfigId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogscwlidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "ResourceId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation", - }, - "NetworkEndpointsacceleratorQueryLogsS3QueryLogAssociationC248D059": { - "Properties": { - "ResolverQueryLogConfigId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverqueryLogConfigsacceleratorquerylogss3idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "ResourceId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation", - }, - "NetworkEndpointsappAalbtg176E11B7D": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "HTTP", - "Matcher": {}, - "Name": "appA-alb-tg-1", - "Port": 80, - "Protocol": "HTTP", - "Tags": [ - { - "Key": "Name", - "Value": "appA-alb-tg-1", - }, - ], - "TargetType": "instance", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkEndpointsappAnlbtg19C910FB9": { - "Properties": { - "HealthCheckEnabled": true, - "HealthCheckPort": "80", - "HealthCheckProtocol": "TCP", - "Matcher": {}, - "Name": "appA-nlb-tg-1", - "Port": 80, - "Protocol": "TCP", - "Tags": [ - { - "Key": "Name", - "Value": "appA-nlb-tg-1", - }, - ], - "TargetType": "instance", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - "NetworkEndpointsexampleRuleRuleAssociationB302A795": { - "Properties": { - "ResolverRuleId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverrulesexampleruleidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::Route53Resolver::ResolverRuleAssociation", - }, - "NetworkInspectionNetworkMainCorePropagation37D40A21": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkInspectionNetworkMainSegregatedPropagationA07EB35C": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkInspectionNetworkMainSharedAssociationC5D467EA": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "NetworkInspectionNetworkMainSharedPropagation9C8183A3": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectiontransitGatewayAttachmentNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "NetworkMainCore101000016NetworkEndpointsNetworkStaticRouteEC325B8C": { - "Properties": { - "DestinationCidrBlock": "10.100.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore102000016NetworkDXGWStaticRoute7BFECA73": { - "Properties": { - "DestinationCidrBlock": "10.200.0.0/16", - "TransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "NetworkDxgwNetworkMainDxGatewayAssociationF3E8BC9A", - "TransitGatewayAttachmentId", - ], - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore103000016acceleratorvpnStaticRoute674FF3C8": { - "Properties": { - "DestinationCidrBlock": "10.300.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "AcceleratorVpnVpnTransitGatewayAttachmentFBF8EE77", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCore111132blackholeStaticRouteEF8CDB96": { - "Properties": { - "Blackhole": true, - "DestinationCidrBlock": "1.1.1.1/32", - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkMainCoreacceleratorPrefixListNetworkEndpointsNetwork3EFFBC42": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - "Arn", - ], - }, - "prefixListReference": { - "PrefixListId": { - "Ref": "SsmParameterValueacceleratornetworkprefixListacceleratorprefixlistidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayAttachmentId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointstransitGatewayAttachmentNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - }, - "Type": "Custom::TransitGatewayPrefixListReference", - "UpdateReplacePolicy": "Delete", - }, - "NetworkMainShared102000016NetworkMainAndSharedServicesMainPeeringStaticRoute6433AEC2": { - "Properties": { - "DestinationCidrBlock": "10.200.0.0/16", - "TransitGatewayAttachmentId": { - "Ref": "NetworkNetworkMainAndSharedServicesMainPeeringTransitGatewayPeeringAttachment6A5D4528", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "NetworkNetworkMainAndSharedServicesMainPeeringTransitGatewayPeeringAttachment6A5D4528": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "Network-Main-And-SharedServices-Main-Peering", - "region": "us-east-1", - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "peering", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "SharedServicesMainNetworkMainCorePropagationB8A9444B": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainCoreidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SharedServicesMainNetworkMainSegregatedPropagation1734E165": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSegregatedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SharedServicesMainNetworkMainSharedAssociationFE2C7420": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "SharedServicesMainNetworkMainSharedPropagationA83F55E7": { - "Properties": { - "TransitGatewayAttachmentId": { - "Ref": "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8", - }, - "TransitGatewayRouteTableId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainrouteTablesNetworkMainSharedidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "SharedServicesMainSharedServicesVpcTransitGatewayAttachmentE1E0A8A8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "SharedServices-Main", - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-DescribeTgwAttachRole-us-east-1", - ], - ], - }, - "transitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "type": "vpc", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamCrossAccountVpcPeering03403783": { - "Properties": { - "Name": "/accelerator/network/vpcPeering/CrossAccount/id", - "Type": "String", - "Value": { - "Ref": "CrossAccountVpcPeering9E0C69A2", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamLookupCrossAccountC2B66C85": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamLookupSharedServicesMain444444444444SharedServicesAppA08A11B17": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/routeTable/SharedServices-App-A/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamLookupSharedServicesMain444444444444SharedServicesAppB580D7687": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-VpcPeeringRole-us-east-1", - ], - ], - }, - "invokingAccountID": "555555555555", - "invokingRegion": "us-east-1", - "parameterAccountID": "444444444444", - "parameterName": "/accelerator/network/vpc/SharedServices-Main/routeTable/SharedServices-App-B/id", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamNetworkEndpointsToInspectionVpcPeering66A4E12A": { - "Properties": { - "Name": "/accelerator/network/vpcPeering/NetworkEndpointsToInspection/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsToInspectionVpcPeeringE81AF602", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkAssociationsStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-prep-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-prep-stack.test.ts.snap deleted file mode 100644 index 1a5c35d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-prep-stack.test.ts.snap +++ /dev/null @@ -1,2983 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkPrepStack Construct(NetworkPrepStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorBlockGroupResolverFirewallRuleGroupShareResourceShare3C3E6D12": { - "Properties": { - "Name": "accelerator-block-group_ResolverFirewallRuleGroupShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "AcceleratorBlockGroupRuleGroup1083FDE0", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "AcceleratorBlockGroupRuleGroup1083FDE0": { - "Properties": { - "FirewallRules": [ - { - "Action": "BLOCK", - "BlockResponse": "NXDOMAIN", - "FirewallDomainListId": { - "Fn::GetAtt": [ - "DomainList1DomainListBF84D823", - "Id", - ], - }, - "Priority": 100, - }, - { - "Action": "BLOCK", - "BlockResponse": "NODATA", - "FirewallDomainListId": { - "Ref": "AwsManagedDomainsBotnetCommandandControlDomainList1AA90CC1", - }, - "Priority": 300, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-block-group", - }, - ], - }, - "Type": "AWS::Route53Resolver::FirewallRuleGroup", - }, - "AcceleratorCgwCustomerGateway56465E97": { - "Properties": { - "BgpAsn": 65500, - "IpAddress": "1.1.1.1", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-cgw", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::CustomerGateway", - }, - "AcceleratorIpamIpamB72C793C": { - "Properties": { - "Description": "Accelerator IPAM", - "OperatingRegions": [ - { - "RegionName": "us-east-1", - }, - { - "RegionName": "us-west-2", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-ipam", - }, - ], - }, - "Type": "AWS::EC2::IPAM", - }, - "AcceleratorPolicyNetworkFirewallPolicyC840A0C2": { - "Properties": { - "FirewallPolicy": { - "StatefulRuleGroupReferences": [ - { - "ResourceArn": { - "Ref": "AcceleratorRuleGroupNetworkFirewallRuleGroup3B409F78", - }, - }, - { - "ResourceArn": { - "Ref": "DomainListGroupNetworkFirewallRuleGroup8FEBF91F", - }, - }, - ], - "StatelessDefaultActions": [ - "aws:forward_to_sfe", - ], - "StatelessFragmentDefaultActions": [ - "aws:forward_to_sfe", - ], - "StatelessRuleGroupReferences": [], - }, - "FirewallPolicyName": "accelerator-policy", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-policy", - }, - ], - }, - "Type": "AWS::NetworkFirewall::FirewallPolicy", - }, - "AcceleratorPolicyNetworkFirewallPolicyShareResourceShareA8374828": { - "Properties": { - "Name": "accelerator-policy_NetworkFirewallPolicyShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Ref": "AcceleratorPolicyNetworkFirewallPolicyC840A0C2", - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "AcceleratorQueryLogsCwlQueryLogConfigE0FE97C8": { - "Properties": { - "DestinationArn": { - "Fn::GetAtt": [ - "QueryLogsLogGroup9D69754D", - "Arn", - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfig", - }, - "AcceleratorQueryLogsCwlQueryLogConfigShareResourceShare584C5889": { - "Properties": { - "Name": "accelerator-query-logs-cwl_QueryLogConfigShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "AcceleratorQueryLogsCwlQueryLogConfigE0FE97C8", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "AcceleratorQueryLogsS3QueryLogConfig31CD159A": { - "Properties": { - "DestinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfig", - }, - "AcceleratorQueryLogsS3QueryLogConfigShareResourceShareEDB81CFA": { - "Properties": { - "Name": "accelerator-query-logs-s3_QueryLogConfigShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "AcceleratorQueryLogsS3QueryLogConfig31CD159A", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "AcceleratorRuleGroupNetworkFirewallRuleGroup3B409F78": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RulesSource": { - "StatefulRules": [ - { - "Action": "PASS", - "Header": { - "Destination": "10.0.0.0/16", - "DestinationPort": "ANY", - "Direction": "FORWARD", - "Protocol": "IP", - "Source": "10.1.0.0/16", - "SourcePort": "ANY", - }, - "RuleOptions": [ - { - "Keyword": "sid", - "Settings": [ - "100", - ], - }, - ], - }, - { - "Action": "DROP", - "Header": { - "Destination": "ANY", - "DestinationPort": "ANY", - "Direction": "ANY", - "Protocol": "IP", - "Source": "ANY", - "SourcePort": "ANY", - }, - "RuleOptions": [ - { - "Keyword": "sid", - "Settings": [ - "101", - ], - }, - ], - }, - { - "Action": "ALERT", - "Header": { - "Destination": "1.1.1.1/32", - "DestinationPort": "80", - "Direction": "FORWARD", - "Protocol": "TCP", - "Source": "ANY", - "SourcePort": "ANY", - }, - "RuleOptions": [ - { - "Keyword": "msg", - "Settings": [ - ""example message"", - ], - }, - { - "Keyword": "sid", - "Settings": [ - "102", - ], - }, - ], - }, - ], - }, - }, - "RuleGroupName": "accelerator-rule-group", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-rule-group", - }, - ], - "Type": "STATEFUL", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - "AcceleratorStrictPolicyNetworkFirewallPolicyA16587CE": { - "Properties": { - "FirewallPolicy": { - "StatefulEngineOptions": { - "RuleOrder": "STRICT_ORDER", - }, - "StatefulRuleGroupReferences": [ - { - "Priority": 100, - "ResourceArn": { - "Ref": "AcceleratorStrictRuleGroupNetworkFirewallRuleGroup015E7B87", - }, - }, - ], - "StatelessDefaultActions": [ - "aws:forward_to_sfe", - ], - "StatelessFragmentDefaultActions": [ - "aws:forward_to_sfe", - ], - "StatelessRuleGroupReferences": [], - }, - "FirewallPolicyName": "accelerator-strict-policy", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-strict-policy", - }, - ], - }, - "Type": "AWS::NetworkFirewall::FirewallPolicy", - }, - "AcceleratorStrictPolicyNetworkFirewallPolicyShareResourceShare63900407": { - "Properties": { - "Name": "accelerator-strict-policy_NetworkFirewallPolicyShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Ref": "AcceleratorStrictPolicyNetworkFirewallPolicyA16587CE", - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "AcceleratorStrictRuleGroupNetworkFirewallRuleGroup015E7B87": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RuleVariables": { - "IPSets": { - "HOME_NET": { - "Definition": [ - "10.0.0.0/16", - "10.1.0.0/16", - ], - }, - }, - "PortSets": { - "HOME_NET": { - "Definition": [ - "80", - "443", - ], - }, - }, - }, - "RulesSource": { - "RulesString": "pass ip 10.1.0.0/16 any -> 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; flow:to_server, established; sid:104; rev:1;)", - }, - "StatefulRuleOptions": { - "RuleOrder": "STRICT_ORDER", - }, - }, - "RuleGroupName": "accelerator-strict-rule-group", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-strict-rule-group", - }, - ], - "Type": "STATEFUL", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - "AcceleratorSuricataRuleGroupNetworkFirewallRuleGroup7657B52C": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RuleVariables": { - "IPSets": { - "HOME_NET": { - "Definition": [ - "10.0.0.0/16", - "10.1.0.0/16", - ], - }, - }, - "PortSets": { - "HOME_NET": { - "Definition": [ - "80", - "443", - ], - }, - }, - }, - "RulesSource": { - "RulesString": "pass ip 10.1.0.0/16 any -> 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; flow:to_server, established; sid:104; rev:1;)", - }, - }, - "RuleGroupName": "accelerator-suricata-rule-group", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-suricata-rule-group", - }, - ], - "Type": "STATEFUL", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - "AcceleratorVpnVpnConnection4C6D9196": { - "Properties": { - "CustomerGatewayId": { - "Ref": "AcceleratorCgwCustomerGateway56465E97", - }, - "StaticRoutesOnly": false, - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-vpn", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - "Type": "ipsec.1", - "VpnTunnelOptionsSpecifications": [ - { - "TunnelInsideCidr": "169.254.200.0/30", - }, - { - "TunnelInsideCidr": "169.254.200.100/30", - }, - ], - }, - "Type": "AWS::EC2::VPNConnection", - }, - "AwsManagedDomainsBotnetCommandandControlDomainList1AA90CC1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomResolverManagedDomainListCustomResourceProviderLogGroupAA48B1A0", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomResolverManagedDomainListCustomResourceProviderHandler9F7C9581", - "Arn", - ], - }, - "listName": "AWSManagedDomainsBotnetCommandandControl", - "region": "us-east-1", - }, - "Type": "Custom::ResolverManagedDomainList", - "UpdateReplacePolicy": "Delete", - }, - "BasePoolPool0FC01DEF": { - "Properties": { - "AddressFamily": "ipv4", - "Description": "accelerator-base", - "IpamScopeId": { - "Fn::GetAtt": [ - "AcceleratorIpamIpamB72C793C", - "PrivateDefaultScopeId", - ], - }, - "ProvisionedCidrs": [ - { - "Cidr": "10.0.0.0/8", - }, - { - "Cidr": "172.16.0.0/12", - }, - { - "Cidr": "192.168.0.0/16", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "base-pool", - }, - ], - }, - "Type": "AWS::EC2::IPAMPool", - }, - "CrossAccountCgwPolicy9EED0A31": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Cross account policy allows access for CGW CRUD operations", - }, - ], - }, - }, - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateCustomerGateway", - "ec2:CreateTags", - "ec2:DeleteCustomerGateway", - "ec2:DeleteTags", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CGWCRUD", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "CrossAccountCgwRole33146FF0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role for lambda needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Ref": "CrossAccountCgwPolicy9EED0A31", - }, - ], - "RoleName": "AWSAccelerator-CrossAccount-CustomerGateway-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountCgwTgwVpnSharedParameters0A7B809A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/transitGateways/Network-Main/id", - "value": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - }, - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/transitGateways/Network-Main/routeTables/Network-Main-Core/id", - "value": { - "Ref": "NetworkMainCoreTransitGatewayRouteTableE04FCA1B", - }, - }, - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/transitGateways/Network-Main/routeTables/Network-Main-Segregated/id", - "value": { - "Ref": "NetworkMainSegregatedTransitGatewayRouteTableFD7A15B0", - }, - }, - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/transitGateways/Network-Main/routeTables/Network-Main-Shared/id", - "value": { - "Ref": "NetworkMainSharedTransitGatewayRouteTable7A039C6C", - }, - }, - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/transitGateways/Network-Main/routeTables/Network-Main-Standalone/id", - "value": { - "Ref": "NetworkMainStandaloneTransitGatewayRouteTable663AEF73", - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountLogsPolicyA5C7DC3B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Cross account policy allows access for Logs CRUD operations", - }, - ], - }, - }, - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:DescribeKey", - "kms:ListKeys", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "LogsList", - }, - { - "Action": [ - "logs:AssociateKmsKey", - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:*:555555555555:log-group:AWSAccelerator*", - ], - ], - }, - "Sid": "LogsCRUD", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "CrossAccountLogsRole90E58DD7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role for lambda needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Ref": "CrossAccountLogsPolicyA5C7DC3B", - }, - ], - "RoleName": "AWSAccelerator-CrossAccount-PutLogs-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountTgwRoutesPolicyDCC39B57": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Cross account policy allows access for TGW route CRUD operations", - }, - ], - }, - }, - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:AssociateTransitGatewayRouteTable", - "ec2:CreateTransitGatewayRoute", - "ec2:CreateTransitGatewayPrefixListReference", - "ec2:DeleteTransitGatewayPrefixListReference", - "ec2:DeleteTransitGatewayRoute", - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - "ec2:DisableTransitGatewayRouteTablePropagation", - "ec2:DisassociateTransitGatewayRouteTable", - "ec2:EnableTransitGatewayRouteTablePropagation", - "ec2:ModifyTransitGatewayPrefixListReference", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "TGWRouteCRUD", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "CrossAccountTgwRoutesRole6DAD417B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role for lambda needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Ref": "CrossAccountTgwRoutesPolicyDCC39B57", - }, - ], - "RoleName": "AWSAccelerator-CrossAccount-TgwRoutes-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CrossAccountVpnPolicyB19C2485": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Cross account policy allows access for VPN CRUD operations", - }, - ], - }, - }, - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTags", - "ec2:CreateVpnConnection", - "ec2:DeleteTags", - "ec2:DeleteVpnConnection", - "ec2:DescribeVpnConnections", - "ec2:ModifyVpnConnectionOptions", - "ec2:ModifyVpnTunnelOptions", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S2SVPNCRUD", - }, - { - "Action": [ - "logs:CreateLogDelivery", - "logs:GetLogDelivery", - "logs:UpdateLogDelivery", - "logs:DeleteLogDelivery", - "logs:ListLogDeliveries", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "LogDeliveryCRUD", - }, - { - "Action": [ - "logs:PutResourcePolicy", - "logs:DescribeResourcePolicies", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S2SVPNLoggingCWL", - }, - { - "Action": [ - "secretsmanager:GetSecretValue", - "kms:Decrypt", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecretsManagerReadOnly", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "CrossAccountVpnRole2816F7C4": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role for lambda needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Ref": "CrossAccountVpnPolicyB19C2485", - }, - ], - "RoleName": "AWSAccelerator-CrossAccount-SiteToSiteVpn-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CustomDirectConnectGatewayCustomResourceProviderHandler82F9382C": { - "DependsOn": [ - "CustomDirectConnectGatewayCustomResourceProviderRole60C5A7B2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayCustomResourceProviderRole60C5A7B2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectGatewayCustomResourceProviderLogGroup242F4324": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectGatewayCustomResourceProviderHandler82F9382C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayCustomResourceProviderRole60C5A7B2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreateDirectConnectGateway", - "directconnect:DeleteDirectConnectGateway", - "directconnect:UpdateDirectConnectGateway", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DirectConnectGatewayCRUD", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDirectConnectVirtualInterfaceCustomResourceProviderHandler147DBFD9": { - "DependsOn": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderRoleD8C9D582", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderRoleD8C9D582", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectVirtualInterfaceCustomResourceProviderLogGroup51E4607A": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectVirtualInterfaceCustomResourceProviderHandler147DBFD9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectVirtualInterfaceCustomResourceProviderRoleD8C9D582": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreatePrivateVirtualInterface", - "directconnect:CreateTransitVirtualInterface", - "directconnect:DeleteVirtualInterface", - "directconnect:DescribeVirtualInterfaces", - "directconnect:TagResource", - "directconnect:UntagResource", - "directconnect:UpdateVirtualInterfaceAttributes", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DxVirtualInterfaceCRUD", - }, - { - "Action": [ - "lambda:InvokeFunction", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:AWSAccelerator-NetworkPre-CustomDirectConnect*", - ], - ], - }, - "Sid": "InvokeSelf", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomResolverManagedDomainListCustomResourceProviderHandler9F7C9581": { - "DependsOn": [ - "CustomResolverManagedDomainListCustomResourceProviderRole33DECC65", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomResolverManagedDomainListCustomResourceProviderRole33DECC65", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomResolverManagedDomainListCustomResourceProviderLogGroupAA48B1A0": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomResolverManagedDomainListCustomResourceProviderHandler9F7C9581", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomResolverManagedDomainListCustomResourceProviderRole33DECC65": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:ListFirewallDomainLists", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F": { - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to put cross-account ssm parameter value", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DeleteParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmPutParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DomainList1DomainListBF84D823": { - "Properties": { - "DomainFileUrl": "s3://cdk-hnb659fds-assets-555555555555-us-east-1/82f4f8c337dd7186cc85614bbe281dceae50920818a45eea246ef414ba6f9bf1.txt", - "Tags": [ - { - "Key": "Name", - "Value": "domain-list-1", - }, - ], - }, - "Type": "AWS::Route53Resolver::FirewallDomainList", - }, - "DomainListGroupNetworkFirewallRuleGroup8FEBF91F": { - "Properties": { - "Capacity": 10, - "RuleGroup": { - "RuleVariables": { - "IPSets": { - "HOME_NET": { - "Definition": [ - "10.0.0.0/16", - "10.1.0.0/16", - ], - }, - }, - "PortSets": { - "HOME_NET": { - "Definition": [ - "80", - "443", - ], - }, - }, - }, - "RulesSource": { - "RulesSourceList": { - "GeneratedRulesType": "DENYLIST", - "TargetTypes": [ - "TLS_SNI", - "HTTP_HOST", - ], - "Targets": [ - ".example.com", - ], - }, - }, - }, - "RuleGroupName": "domain-list-group", - "Tags": [ - { - "Key": "Name", - "Value": "domain-list-group", - }, - ], - "Type": "STATEFUL", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - "EnhancedVpnTunnel0LogGroup9B6D89BD": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "EnhancedVpnTunnel1LogGroup3967C793": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "EnhancedVpnVpnConnectionCustomResourceCustomResourceResourceB85A038C": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "EnhancedVpnVpnConnectionCustomResourceframeworkonEvent6111F5FA", - "Arn", - ], - }, - "amazonIpv4NetworkCidr": "10.0.0.0/16", - "customerGatewayId": { - "Ref": "AcceleratorCgwCustomerGateway56465E97", - }, - "customerIpv4NetworkCidr": "192.168.0.0/16", - "enableVpnAcceleration": true, - "roleName": "AWSAccelerator-CrossAccount-SiteToSiteVpn-Role", - "staticRoutesOnly": false, - "tags": [ - { - "Key": "Name", - "Value": "enhanced-vpn", - }, - ], - "transitGatewayId": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - "vpnTunnelOptions": [ - { - "dpdTimeoutAction": "restart", - "dpdTimeoutSeconds": 60, - "ikeVersions": [ - 2, - ], - "logging": { - "enable": true, - "logGroupArn": { - "Fn::GetAtt": [ - "EnhancedVpnTunnel0LogGroup9B6D89BD", - "Arn", - ], - }, - }, - "phase1": { - "dhGroups": [ - 2, - 14, - 20, - 21, - 22, - ], - "encryptionAlgorithms": [ - "AES256", - "AES256-GCM-16", - ], - "integrityAlgorithms": [ - "SHA2-256", - "SHA2-512", - ], - "lifetimeSeconds": 3600, - }, - "phase2": { - "dhGroups": [ - 5, - 20, - ], - "encryptionAlgorithms": [ - "AES128", - ], - "integrityAlgorithms": [ - "SHA2-256", - ], - "lifetimeSeconds": 900, - }, - "rekeyFuzzPercentage": 80, - "rekeyMarginTimeSeconds": 350, - "replayWindowSize": 64, - "startupAction": "start", - "tunnelInsideCidr": "169.254.200.0/30", - "tunnelLifecycleControl": false, - }, - { - "dpdTimeoutAction": "clear", - "dpdTimeoutSeconds": 120, - "ikeVersions": [ - 2, - ], - "logging": { - "enable": true, - "logGroupArn": { - "Fn::GetAtt": [ - "EnhancedVpnTunnel1LogGroup3967C793", - "Arn", - ], - }, - }, - "phase1": { - "dhGroups": [ - 2, - 14, - 20, - 21, - 22, - ], - "encryptionAlgorithms": [ - "AES256", - "AES256-GCM-16", - ], - "integrityAlgorithms": [ - "SHA2-256", - "SHA2-512", - ], - "lifetimeSeconds": 3600, - }, - "phase2": { - "dhGroups": [ - 5, - 20, - ], - "encryptionAlgorithms": [ - "AES128", - ], - "integrityAlgorithms": [ - "SHA2-256", - ], - "lifetimeSeconds": 900, - }, - "rekeyFuzzPercentage": 65, - "rekeyMarginTimeSeconds": 120, - "replayWindowSize": 64, - "tunnelInsideCidr": "169.254.200.100/30", - }, - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "EnhancedVpnVpnConnectionCustomResourceframeworkonEvent6111F5FA": { - "DependsOn": [ - "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRoleDefaultPolicyF7C1270A", - "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRole8400FDCC", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-NetworkPrepStack-555555555555-us-east-1/EnhancedVpnVpnConnection/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "VpnOnEventHandler05011160", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRole8400FDCC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRole8400FDCC": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRoleDefaultPolicyF7C1270A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "VpnOnEventHandler05011160", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "VpnOnEventHandler05011160", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRoleDefaultPolicyF7C1270A", - "Roles": [ - { - "Ref": "EnhancedVpnVpnConnectionCustomResourceframeworkonEventServiceRole8400FDCC", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "GetIpamSsmParamRoleB9606C34": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allow cross-account resources to get SSM parameters under this path.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameter", - "ssm:GetParameters", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:*:", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/network/ipam/pools/*/id", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-Ipam-GetSsmParamRole", - }, - "Type": "AWS::IAM::Role", - }, - "HomeRegionPoolIpamPoolShareResourceShare6A09CD50": { - "Properties": { - "Name": "home-region-pool_IpamPoolShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "HomeRegionPoolPool16097CF6", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "HomeRegionPoolPool16097CF6": { - "Properties": { - "AddressFamily": "ipv4", - "Description": "Pool for us-east-1", - "IpamScopeId": { - "Fn::GetAtt": [ - "AcceleratorIpamIpamB72C793C", - "PrivateDefaultScopeId", - ], - }, - "Locale": "us-east-1", - "ProvisionedCidrs": [ - { - "Cidr": "10.0.0.0/16", - }, - { - "Cidr": "10.2.0.0/16", - }, - ], - "SourceIpamPoolId": { - "Ref": "BasePoolPool0FC01DEF", - }, - "Tags": [ - { - "Key": "Name", - "Value": "home-region-pool", - }, - ], - }, - "Type": "AWS::EC2::IPAMPool", - }, - "HomeRegionProdPoolIpamPoolShareResourceShare00E531E8": { - "Properties": { - "Name": "home-region-prod-pool_IpamPoolShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "HomeRegionProdPoolPool04473C66", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "HomeRegionProdPoolPool04473C66": { - "Properties": { - "AddressFamily": "ipv4", - "AllocationResourceTags": [ - { - "Key": "env", - "Value": "prod", - }, - ], - "Description": "Pool for prod environment", - "IpamScopeId": { - "Fn::GetAtt": [ - "AcceleratorIpamIpamB72C793C", - "PrivateDefaultScopeId", - ], - }, - "Locale": "us-east-1", - "ProvisionedCidrs": [ - { - "Cidr": "10.0.0.0/24", - }, - { - "Cidr": "10.2.0.0/24", - }, - ], - "SourceIpamPoolId": { - "Ref": "HomeRegionPoolPool16097CF6", - }, - "Tags": [ - { - "Key": "Name", - "Value": "home-region-prod-pool", - }, - ], - }, - "Type": "AWS::EC2::IPAMPool", - }, - "MadShareAcceptRole0F055D0C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "MAD share accept role needs access to directory for acceptance ", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "ds:AcceptSharedDirectory", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-MadAccept-Role", - }, - "Type": "AWS::IAM::Role", - }, - "NetworkDxgwAccelratorVifVirtualInterface0F06099A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderLogGroup51E4607A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderHandler147DBFD9", - "Arn", - ], - }, - "addressFamily": "ipv4", - "connectionId": "dxcon-test1234", - "customerAsn": 65002, - "directConnectGatewayId": { - "Ref": "NetworkDxgwDxGatewayB600DBBE", - }, - "enableSiteLink": true, - "interfaceName": "Accelrator-VIF", - "jumboFrames": true, - "region": "us-east-1", - "type": "transit", - "vlan": 575, - }, - "Type": "Custom::DirectConnectVirtualInterface", - "UpdateReplacePolicy": "Delete", - }, - "NetworkDxgwDxGatewayB600DBBE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectGatewayCustomResourceProviderLogGroup242F4324", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayCustomResourceProviderHandler82F9382C", - "Arn", - ], - }, - "asn": 65000, - "gatewayName": "Network-DXGW", - }, - "Type": "Custom::DirectConnectGateway", - "UpdateReplacePolicy": "Delete", - }, - "NetworkLocalEndpointQueryLogsCwlQueryLogConfig48CF2515": { - "Properties": { - "DestinationArn": { - "Fn::GetAtt": [ - "NetworkLocalEndpointQueryLogsQueryLogsLogGroup931180D2", - "Arn", - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfig", - }, - "NetworkLocalEndpointQueryLogsQueryLogsLogGroup931180D2": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "NetworkLocalEndpointQueryLogsQueryLogsLogGroupPolicyResourcePolicyA532C052": { - "Properties": { - "PolicyDocument": { - "Fn::Join": [ - "", - [ - "{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Condition":{"StringEquals":{"aws:PrincipalOrgId":"o-asdf123456"}},"Effect":"Allow","Principal":{"Service":"delivery.logs.amazonaws.com"},"Resource":"", - { - "Fn::GetAtt": [ - "NetworkLocalEndpointQueryLogsQueryLogsLogGroup931180D2", - "Arn", - ], - }, - ":log-stream:*","Sid":"Allow log delivery access"}],"Version":"2012-10-17"}", - ], - ], - }, - "PolicyName": "AWSAcceleratorNetworkPrepStack555555555555useast1NetworkLocalEndpointQueryLogsQueryLogsLogGroupPolicy16658257", - }, - "Type": "AWS::Logs::ResourcePolicy", - }, - "NetworkLocalEndpointQueryLogsS3QueryLogConfigB308F9D0": { - "Properties": { - "DestinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfig", - }, - "NetworkMain2CoreTransitGatewayRouteTableA0D16E8B": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-2-Core", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMain2TransitGateway9D65305C", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMain2SegregatedTransitGatewayRouteTable189150BD": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-2-Segregated", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMain2TransitGateway9D65305C", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMain2SharedTransitGatewayRouteTable99DD8F5E": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-2-Shared", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMain2TransitGateway9D65305C", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMain2StandaloneTransitGatewayRouteTableA7C72B59": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-2-Standalone", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMain2TransitGateway9D65305C", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMain2TransitGateway9D65305C": { - "Properties": { - "AmazonSideAsn": 65521, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "disable", - "DefaultRouteTablePropagation": "disable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-2", - }, - ], - "TransitGatewayCidrBlocks": [ - "10.50.0.0/20", - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "NetworkMain2TransitGatewayShareResourceShareAA3E1526": { - "Properties": { - "Name": "Network-Main-2_TransitGatewayShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:555555555555:transit-gateway/", - { - "Ref": "NetworkMain2TransitGateway9D65305C", - }, - ], - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "NetworkMainCoreTransitGatewayRouteTableE04FCA1B": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-Core", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMainSegregatedTransitGatewayRouteTableFD7A15B0": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-Segregated", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMainSharedTransitGatewayRouteTable7A039C6C": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-Shared", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMainStandaloneTransitGatewayRouteTable663AEF73": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main-Standalone", - }, - ], - "TransitGatewayId": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - "NetworkMainTransitGatewayF6473E53": { - "Properties": { - "AmazonSideAsn": 65521, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "disable", - "DefaultRouteTablePropagation": "disable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Main", - }, - ], - "TransitGatewayCidrBlocks": [ - "10.0.0.0/20", - "10.5.0.0/20", - "2001:db8::/64", - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "NetworkMainTransitGatewayShareResourceShare0D259A78": { - "Properties": { - "Name": "Network-Main_TransitGatewayShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:555555555555:transit-gateway/", - { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - ], - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "QueryLogsLogGroup9D69754D": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "QueryLogsLogGroupPolicyResourcePolicyA5124028": { - "Properties": { - "PolicyDocument": { - "Fn::Join": [ - "", - [ - "{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Condition":{"StringEquals":{"aws:PrincipalOrgId":"o-asdf123456"}},"Effect":"Allow","Principal":{"Service":"delivery.logs.amazonaws.com"},"Resource":"", - { - "Fn::GetAtt": [ - "QueryLogsLogGroup9D69754D", - "Arn", - ], - }, - ":log-stream:*","Sid":"Allow log delivery access"}],"Version":"2012-10-17"}", - ], - ], - }, - "PolicyName": "AWSAcceleratorNetworkPrepStack555555555555useast1QueryLogsLogGroupPolicy6100363D", - }, - "Type": "AWS::Logs::ResourcePolicy", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkPrepStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkDxgwAccelratorVifVirtualInterface811176E7": { - "Properties": { - "Name": "/accelerator/network/directConnectGateways/Network-DXGW/virtualInterfaces/Accelrator-VIF/id", - "Type": "String", - "Value": { - "Ref": "NetworkDxgwAccelratorVifVirtualInterface0F06099A", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkDxgwDirectConnectGatewayC782B366": { - "DependsOn": [ - "SsmParamNetworkMain2NetworkMain2StandaloneTransitGatewayRouteTableIdF641FDF1", - ], - "Properties": { - "Name": "/accelerator/network/directConnectGateways/Network-DXGW/id", - "Type": "String", - "Value": { - "Ref": "NetworkDxgwDxGatewayB600DBBE", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMain2NetworkMain2CoreTransitGatewayRouteTableIdC5E730F7": { - "DependsOn": [ - "SsmParamNetworkMainNetworkMainStandaloneTransitGatewayRouteTableId81A119C5", - ], - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Core/id", - "Type": "String", - "Value": { - "Ref": "NetworkMain2CoreTransitGatewayRouteTableA0D16E8B", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMain2NetworkMain2SegregatedTransitGatewayRouteTableId8571D494": { - "DependsOn": [ - "SsmParamNetworkMainNetworkMainStandaloneTransitGatewayRouteTableId81A119C5", - ], - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Segregated/id", - "Type": "String", - "Value": { - "Ref": "NetworkMain2SegregatedTransitGatewayRouteTable189150BD", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMain2NetworkMain2SharedTransitGatewayRouteTableIdE9E46782": { - "DependsOn": [ - "SsmParamNetworkMainNetworkMainStandaloneTransitGatewayRouteTableId81A119C5", - ], - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Shared/id", - "Type": "String", - "Value": { - "Ref": "NetworkMain2SharedTransitGatewayRouteTable99DD8F5E", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMain2NetworkMain2StandaloneTransitGatewayRouteTableIdF641FDF1": { - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main-2/routeTables/Network-Main-2-Standalone/id", - "Type": "String", - "Value": { - "Ref": "NetworkMain2StandaloneTransitGatewayRouteTableA7C72B59", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMain2TransitGatewayId3D6B0437": { - "DependsOn": [ - "SsmParamNetworkMainNetworkMainStandaloneTransitGatewayRouteTableId81A119C5", - ], - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main-2/id", - "Type": "String", - "Value": { - "Ref": "NetworkMain2TransitGateway9D65305C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMainNetworkMainCoreTransitGatewayRouteTableId45D6D502": { - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Core/id", - "Type": "String", - "Value": { - "Ref": "NetworkMainCoreTransitGatewayRouteTableE04FCA1B", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMainNetworkMainSegregatedTransitGatewayRouteTableIdF2215D71": { - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Segregated/id", - "Type": "String", - "Value": { - "Ref": "NetworkMainSegregatedTransitGatewayRouteTableFD7A15B0", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMainNetworkMainSharedTransitGatewayRouteTableId0C5DAACF": { - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Shared/id", - "Type": "String", - "Value": { - "Ref": "NetworkMainSharedTransitGatewayRouteTable7A039C6C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMainNetworkMainStandaloneTransitGatewayRouteTableId81A119C5": { - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main/routeTables/Network-Main-Standalone/id", - "Type": "String", - "Value": { - "Ref": "NetworkMainStandaloneTransitGatewayRouteTable663AEF73", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkMainTransitGatewayId388A193C": { - "Properties": { - "Name": "/accelerator/network/transitGateways/Network-Main/id", - "Type": "String", - "Value": { - "Ref": "NetworkMainTransitGatewayF6473E53", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkPrepStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorBlockGroupRuleGroupEA562A3B": { - "DependsOn": [ - "SsmParamwestRegionPoolPoolId673ACD76", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/firewall/ruleGroups/accelerator-block-group/id", - "Type": "String", - "Value": { - "Ref": "AcceleratorBlockGroupRuleGroup1083FDE0", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorCgwCustomerGateway984E0261": { - "DependsOn": [ - "SsmParamNetworkMain2NetworkMain2StandaloneTransitGatewayRouteTableIdF641FDF1", - ], - "Properties": { - "Name": "/accelerator/network/customerGateways/accelerator-cgw/id", - "Type": "String", - "Value": { - "Ref": "AcceleratorCgwCustomerGateway56465E97", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorIpamIpamId271C0A36": { - "DependsOn": [ - "SsmParamNetworkDxgwAccelratorVifVirtualInterface811176E7", - ], - "Properties": { - "Name": "/accelerator/network/ipam/accelerator-ipam/id", - "Type": "String", - "Value": { - "Ref": "AcceleratorIpamIpamB72C793C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorPolicyNetworkFirewallPolicy68E9C1A5": { - "Properties": { - "Name": "/accelerator/network/networkFirewall/policies/accelerator-policy/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorPolicyNetworkFirewallPolicyC840A0C2", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorQueryLogsCwlQueryLogConfig0E6CC558": { - "DependsOn": [ - "SsmParamwestRegionPoolPoolId673ACD76", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/queryLogConfigs/accelerator-query-logs-cwl/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorQueryLogsCwlQueryLogConfigE0FE97C8", - "Id", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorQueryLogsS3QueryLogConfig0B1FF32F": { - "DependsOn": [ - "SsmParamwestRegionPoolPoolId673ACD76", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/queryLogConfigs/accelerator-query-logs-s3/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorQueryLogsS3QueryLogConfig31CD159A", - "Id", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorRuleGroupNetworkFirewallRuleGroup28931513": { - "DependsOn": [ - "SsmParamnetworkLocalEndpointQueryLogsCwlQueryLogConfig40CFAA3E", - ], - "Properties": { - "Name": "/accelerator/network/networkFirewall/ruleGroups/accelerator-rule-group/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorRuleGroupNetworkFirewallRuleGroup3B409F78", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorStrictPolicyNetworkFirewallPolicy2C70E8B6": { - "DependsOn": [ - "SsmParamacceleratorPolicyNetworkFirewallPolicy68E9C1A5", - ], - "Properties": { - "Name": "/accelerator/network/networkFirewall/policies/accelerator-strict-policy/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorStrictPolicyNetworkFirewallPolicyA16587CE", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorStrictRuleGroupNetworkFirewallRuleGroup315F5768": { - "DependsOn": [ - "SsmParamnetworkLocalEndpointQueryLogsCwlQueryLogConfig40CFAA3E", - ], - "Properties": { - "Name": "/accelerator/network/networkFirewall/ruleGroups/accelerator-strict-rule-group/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorStrictRuleGroupNetworkFirewallRuleGroup015E7B87", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorSuricataRuleGroupNetworkFirewallRuleGroup5707985D": { - "DependsOn": [ - "SsmParamnetworkLocalEndpointQueryLogsCwlQueryLogConfig40CFAA3E", - ], - "Properties": { - "Name": "/accelerator/network/networkFirewall/ruleGroups/accelerator-suricata-rule-group/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorSuricataRuleGroupNetworkFirewallRuleGroup7657B52C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorVpnVpnConnection261CAE04": { - "DependsOn": [ - "SsmParamNetworkMain2NetworkMain2StandaloneTransitGatewayRouteTableIdF641FDF1", - ], - "Properties": { - "Name": "/accelerator/network/vpnConnection/accelerator-vpn/id", - "Type": "String", - "Value": { - "Ref": "AcceleratorVpnVpnConnection4C6D9196", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParambasePoolPoolId86A35FFC": { - "DependsOn": [ - "SsmParamNetworkDxgwAccelratorVifVirtualInterface811176E7", - ], - "Properties": { - "Name": "/accelerator/network/ipam/pools/base-pool/id", - "Type": "String", - "Value": { - "Ref": "BasePoolPool0FC01DEF", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamdomainListGroupNetworkFirewallRuleGroupC2BC037A": { - "DependsOn": [ - "SsmParamnetworkLocalEndpointQueryLogsCwlQueryLogConfig40CFAA3E", - ], - "Properties": { - "Name": "/accelerator/network/networkFirewall/ruleGroups/domain-list-group/arn", - "Type": "String", - "Value": { - "Ref": "DomainListGroupNetworkFirewallRuleGroup8FEBF91F", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamenhancedVpnVpnConnection0FB23967": { - "DependsOn": [ - "SsmParamNetworkMain2NetworkMain2StandaloneTransitGatewayRouteTableIdF641FDF1", - ], - "Properties": { - "Name": "/accelerator/network/vpnConnection/enhanced-vpn/id", - "Type": "String", - "Value": { - "Ref": "EnhancedVpnVpnConnectionCustomResourceCustomResourceResourceB85A038C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamhomeRegionPoolPoolId475D7271": { - "DependsOn": [ - "SsmParamNetworkDxgwAccelratorVifVirtualInterface811176E7", - ], - "Properties": { - "Name": "/accelerator/network/ipam/pools/home-region-pool/id", - "Type": "String", - "Value": { - "Ref": "HomeRegionPoolPool16097CF6", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamhomeRegionProdPoolPoolId5672D6D3": { - "DependsOn": [ - "SsmParamNetworkDxgwAccelratorVifVirtualInterface811176E7", - ], - "Properties": { - "Name": "/accelerator/network/ipam/pools/home-region-prod-pool/id", - "Type": "String", - "Value": { - "Ref": "HomeRegionProdPoolPool04473C66", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamnetworkLocalEndpointQueryLogsCwlQueryLogConfig40CFAA3E": { - "Properties": { - "Name": "/accelerator/network/route53Resolver/queryLogConfigs/network-local-endpoint-query-logs-cwl/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "NetworkLocalEndpointQueryLogsCwlQueryLogConfig48CF2515", - "Id", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamnetworkLocalEndpointQueryLogsS3QueryLogConfig14EBC8D4": { - "DependsOn": [ - "SsmParamwestRegionPoolPoolId673ACD76", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/queryLogConfigs/network-local-endpoint-query-logs-s3/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "NetworkLocalEndpointQueryLogsS3QueryLogConfigB308F9D0", - "Id", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamwestRegionPoolPoolId673ACD76": { - "Properties": { - "Name": "/accelerator/network/ipam/pools/west-region-pool/id", - "Type": "String", - "Value": { - "Ref": "WestRegionPoolPoolB2DED4AA", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "TgwPeeringRole3CC1F9AB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "TgwPeeringRole needs access to create peering connections for TGWs in the account ", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/network/transitGateways/*", - ], - ], - }, - }, - { - "Action": [ - "ec2:DescribeTransitGatewayPeeringAttachments", - "ec2:AcceptTransitGatewayPeeringAttachment", - "ec2:AssociateTransitGatewayRouteTable", - "ec2:DisassociateTransitGatewayRouteTable", - "ec2:DescribeTransitGatewayAttachments", - "ec2:CreateTags", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-TgwPeering-Role", - }, - "Type": "AWS::IAM::Role", - }, - "VpnOnEventHandler05011160": { - "DependsOn": [ - "VpnRoleD22FDF01", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource onEvent handler for site-to-site VPN", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "VpnRoleD22FDF01", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "VpnOnEventHandlerPolicyADCD3179": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Managed policy allows access for VPN CRUD operations", - }, - ], - }, - }, - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTags", - "ec2:CreateVpnConnection", - "ec2:DeleteTags", - "ec2:DeleteVpnConnection", - "ec2:DescribeVpnConnections", - "ec2:ModifyVpnConnectionOptions", - "ec2:ModifyVpnTunnelOptions", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S2SVPNCRUD", - }, - { - "Action": [ - "logs:CreateLogDelivery", - "logs:GetLogDelivery", - "logs:UpdateLogDelivery", - "logs:DeleteLogDelivery", - "logs:ListLogDeliveries", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "LogDeliveryCRUD", - }, - { - "Action": [ - "logs:PutResourcePolicy", - "logs:DescribeResourcePolicies", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S2SVPNLoggingCWL", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-CrossAccount-SiteToSiteVpn-Role", - ], - ], - }, - "Sid": "S2SVPNAssumeRole", - }, - { - "Action": [ - "secretsmanager:GetSecretValue", - "kms:Decrypt", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecretsManagerReadOnly", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "VpnOnEventHandlerResourceLogGroupBD9BBFBB": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "VpnOnEventHandler05011160", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "VpnRoleD22FDF01": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role for lambda needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "lambda.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Landing Zone Accelerator site-to-site VPN custom resource access role", - "ManagedPolicyArns": [ - { - "Ref": "VpnOnEventHandlerPolicyADCD3179", - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "WestRegionPoolIpamPoolShareResourceShareCD06FDCF": { - "Properties": { - "Name": "west-region-pool_IpamPoolShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "WestRegionPoolPoolB2DED4AA", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "WestRegionPoolPoolB2DED4AA": { - "Properties": { - "AddressFamily": "ipv4", - "Description": "Pool for us-west-2", - "IpamScopeId": { - "Fn::GetAtt": [ - "AcceleratorIpamIpamB72C793C", - "PrivateDefaultScopeId", - ], - }, - "Locale": "us-west-2", - "ProvisionedCidrs": [ - { - "Cidr": "10.1.0.0/16", - }, - ], - "SourceIpamPoolId": { - "Ref": "BasePoolPool0FC01DEF", - }, - "Tags": [ - { - "Key": "Name", - "Value": "west-region-pool", - }, - ], - }, - "Type": "AWS::EC2::IPAMPool", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-dns-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-dns-stack.test.ts.snap deleted file mode 100644 index 46e16e0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-dns-stack.test.ts.snap +++ /dev/null @@ -1,1339 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkVpcDnsStack Construct(NetworkVpcDnsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverendpointsacceleratorinboundidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/endpoints/accelerator-inbound/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkroute53ResolverendpointsacceleratoroutboundidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/route53Resolver/endpoints/accelerator-outbound/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsaccessanalyzerdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/access-analyzer/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsaccessanalyzerhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/access-analyzer/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactapidnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.api/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactapihostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.api/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactrepositoriesdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.repositories/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactrepositorieshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.repositories/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2dnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2messagesdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2messages/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2messageshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2messages/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsecrdkrdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ecr.dkr/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsecrdkrhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ecr.dkr/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointseksdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/eks/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsekshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/eks/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointskmsdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/kms/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointskmshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/kms/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointslogsdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/logs/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointslogshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/logs/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3globalaccesspointdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3-global.accesspoint/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3globalaccesspointhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3-global.accesspoint/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointssecretsmanagerdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/secretsmanager/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointssecretsmanagerhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/secretsmanager/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssm/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssm/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmmessagesdnsC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssmmessages/dns", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmmessageshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssmmessages/hostedZoneId", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplateidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/id", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorOutboundResolverRuleExampleRule8B329125": { - "Properties": { - "DomainName": "example.com", - "Name": "example-rule", - "ResolverEndpointId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverendpointsacceleratoroutboundidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "RuleType": "FORWARD", - "Tags": [ - { - "Key": "Name", - "Value": "example-rule", - }, - ], - "TargetIps": [ - { - "Ip": "1.1.1.1", - "Port": "5353", - }, - { - "Ip": "2.2.2.2", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverRule", - }, - "AcceleratorOutboundResolverRuleInboundTargetRule35E9CFD7": { - "Properties": { - "DomainName": "aws.internal.domain", - "Name": "inbound-target-rule", - "ResolverEndpointId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverendpointsacceleratoroutboundidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "RuleType": "FORWARD", - "Tags": [ - { - "Key": "Name", - "Value": "inbound-target-rule", - }, - ], - "TargetIps": { - "Fn::GetAtt": [ - "AcceleratorOutboundResolverRuleInboundTargetRuleLookupInbound44F7FDD0", - "ipAddresses", - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverRule", - }, - "AcceleratorOutboundResolverRuleInboundTargetRuleLookupInbound44F7FDD0": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomResolverEndpointAddressesCustomResourceProviderLogGroup70A41B6B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomResolverEndpointAddressesCustomResourceProviderHandler09D4123E", - "Arn", - ], - }, - "endpointId": { - "Ref": "SsmParameterValueacceleratornetworkroute53ResolverendpointsacceleratorinboundidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "region": "us-east-1", - }, - "Type": "Custom::ResolverEndpointAddresses", - "UpdateReplacePolicy": "Delete", - }, - "AwsAcceleratorMadExampleRuleAA6ABF92": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Role needs access to list resolver rules and update", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "route53resolver:ListResolverRules", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "route53resolver:UpdateResolverRule", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "AcceleratorOutboundResolverRuleExampleRule8B329125", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-MAD-example-rule", - }, - "Type": "AWS::IAM::Role", - }, - "CustomResolverEndpointAddressesCustomResourceProviderHandler09D4123E": { - "DependsOn": [ - "CustomResolverEndpointAddressesCustomResourceProviderRoleA94B4F27", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomResolverEndpointAddressesCustomResourceProviderRoleA94B4F27", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomResolverEndpointAddressesCustomResourceProviderLogGroup70A41B6B": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomResolverEndpointAddressesCustomResourceProviderHandler09D4123E", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomResolverEndpointAddressesCustomResourceProviderRoleA94B4F27": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:ListResolverEndpointIpAddresses", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ExampleRuleResolverRuleResourceShare18E76E24": { - "Properties": { - "Name": "example-rule_ResolverRule", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - ], - "ResourceArns": [ - { - "Fn::GetAtt": [ - "AcceleratorOutboundResolverRuleExampleRule8B329125", - "Arn", - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "NetworkEndpointsVpcAccessAnalyzerEpHostedZone51F8537E": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "access-analyzer.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcAccessAnalyzerEpRecordSetCE96E911": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsaccessanalyzerdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsaccessanalyzerhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcAccessAnalyzerEpHostedZone51F8537E", - }, - "Name": "access-analyzer.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcCodeartifactApiEpHostedZoneF2395ADF": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "codeartifact.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcCodeartifactApiEpRecordSetC1C8C062": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactapidnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactapihostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcCodeartifactApiEpHostedZoneF2395ADF", - }, - "Name": "codeartifact.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcCodeartifactRepositoriesEpHostedZoneA4A53667": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "d.codeartifact.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcCodeartifactRepositoriesEpRecordSet4E3BFCE2": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactrepositoriesdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactrepositorieshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcCodeartifactRepositoriesEpHostedZoneA4A53667", - }, - "Name": "*.d.codeartifact.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcCodeartifactRepositoriesEpRecordSetNonWildcard8523F774": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactrepositoriesdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointscodeartifactrepositorieshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcCodeartifactRepositoriesEpHostedZoneA4A53667", - }, - "Name": "d.codeartifact.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcEc2EpHostedZone3457AFDE": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "ec2.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcEc2EpRecordSetB8C5464A": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcEc2EpHostedZone3457AFDE", - }, - "Name": "ec2.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcEc2messagesEpHostedZoneD74E6E18": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "ec2messages.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcEc2messagesEpRecordSet605BEA6A": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2messagesdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsec2messageshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcEc2messagesEpHostedZoneD74E6E18", - }, - "Name": "ec2messages.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcEcrDkrEpHostedZoneE4E1EA3F": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "dkr.ecr.us-east-1.amazonaws.com.", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcEcrDkrEpRecordSet2DE2B6CB": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsecrdkrdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsecrdkrhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcEcrDkrEpHostedZoneE4E1EA3F", - }, - "Name": "*.dkr.ecr.us-east-1.amazonaws.com.", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcEcrDkrEpRecordSetNonWildcard056D21B7": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsecrdkrdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsecrdkrhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcEcrDkrEpHostedZoneE4E1EA3F", - }, - "Name": "dkr.ecr.us-east-1.amazonaws.com.", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcEksEpHostedZoneB3A66C5D": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "eks.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcEksEpRecordSet48754069": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointseksdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsekshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcEksEpHostedZoneB3A66C5D", - }, - "Name": "eks.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcKmsEpHostedZoneC9CFEF9D": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "kms.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcKmsEpRecordSet5C869506": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointskmsdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointskmshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcKmsEpHostedZoneC9CFEF9D", - }, - "Name": "kms.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcLogsEpHostedZoneDBA51FD8": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "logs.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcLogsEpRecordSet0B536BC1": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointslogsdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointslogshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcLogsEpHostedZoneDBA51FD8", - }, - "Name": "logs.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3AccesspointEpHostedZone075BACF0": { - "Properties": { - "Name": "s3-accesspoint.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcS3AccesspointEpRecordSetNonWildcard3AAEFEE8": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3AccesspointEpHostedZone075BACF0", - }, - "Name": "s3-accesspoint.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3AccesspointEpRecordWildcard0BBAEB36": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3AccesspointEpHostedZone075BACF0", - }, - "Name": "*.s3-accesspoint.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3ControlEpHostedZoneCF8C699D": { - "Properties": { - "Name": "s3-control.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcS3ControlEpRecordSetNonWildcard7D6FFE39": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3ControlEpHostedZoneCF8C699D", - }, - "Name": "s3-control.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3ControlEpRecordWildcard5A5F40A0": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3ControlEpHostedZoneCF8C699D", - }, - "Name": "*.s3-control.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3EpHostedZoneD27476BE": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "s3.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcS3EpRecordSet90C07F24": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3EpHostedZoneD27476BE", - }, - "Name": "*.s3.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3EpRecordSetNonWildcard7EEE9166": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3dnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3hostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3EpHostedZoneD27476BE", - }, - "Name": "s3.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3GlobalAccesspointEpHostedZone763F0212": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "s3-global.accesspoint.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcS3GlobalAccesspointEpRecordSet12FE0B73": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3globalaccesspointdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3globalaccesspointhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3GlobalAccesspointEpHostedZone763F0212", - }, - "Name": "*.s3-global.accesspoint.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcS3GlobalAccesspointEpRecordSetNonWildcardE857AB8F": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3globalaccesspointdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointss3globalaccesspointhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcS3GlobalAccesspointEpHostedZone763F0212", - }, - "Name": "s3-global.accesspoint.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcSecretsmanagerEpHostedZoneE5742A7A": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "secretsmanager.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcSecretsmanagerEpRecordSet4FD8850B": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointssecretsmanagerdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointssecretsmanagerhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcSecretsmanagerEpHostedZoneE5742A7A", - }, - "Name": "secretsmanager.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcSsmEpHostedZoneE400276D": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "ssm.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcSsmEpRecordSetE72544F4": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmhostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcSsmEpHostedZoneE400276D", - }, - "Name": "ssm.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "NetworkEndpointsVpcSsmmessagesEpHostedZone19BD15F2": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "Environment", - "Value": "CentralVpc", - }, - ], - "Name": "ssmmessages.us-east-1.amazonaws.com", - "VPCs": [ - { - "VPCId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VPCRegion": "us-east-1", - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "NetworkEndpointsVpcSsmmessagesEpRecordSet491A807C": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmmessagesdnsC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "HostedZoneId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsendpointsssmmessageshostedZoneIdC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "HostedZoneId": { - "Ref": "NetworkEndpointsVpcSsmmessagesEpHostedZone19BD15F2", - }, - "Name": "ssmmessages.us-east-1.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcDnsStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcAccessAnalyzerEpHostedZone6D516002": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcSecretsmanagerEpHostedZone8D952494", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/access-analyzer/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcAccessAnalyzerEpHostedZone51F8537E", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcCodeartifactApiEpHostedZone27175AD5": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcEcrDkrEpHostedZone77F74872", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/codeartifact.api/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcCodeartifactApiEpHostedZoneF2395ADF", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcCodeartifactRepositoriesEpHostedZone53376C20": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcEcrDkrEpHostedZone77F74872", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/codeartifact.repositories/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcCodeartifactRepositoriesEpHostedZoneA4A53667", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcEc2EpHostedZone303E2193": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ec2/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcEc2EpHostedZone3457AFDE", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcEc2messagesEpHostedZoneFD9B43A2": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ec2messages/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcEc2messagesEpHostedZoneD74E6E18", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcEcrDkrEpHostedZone77F74872": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ecr.dkr/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcEcrDkrEpHostedZoneE4E1EA3F", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcEksEpHostedZone07095F43": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcEcrDkrEpHostedZone77F74872", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/eks/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcEksEpHostedZoneB3A66C5D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcKmsEpHostedZoneFA42C849": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/kms/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcKmsEpHostedZoneC9CFEF9D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcLogsEpHostedZoneCE810EE0": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcKmsEpHostedZoneFA42C849", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/logs/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcLogsEpHostedZoneDBA51FD8", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcS3AccesspointEpHostedZone63098ADA": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcKmsEpHostedZoneFA42C849", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/s3-accesspoint/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcS3AccesspointEpHostedZone075BACF0", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcS3ControlEpHostedZone3671707B": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcKmsEpHostedZoneFA42C849", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/s3-control/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcS3ControlEpHostedZoneCF8C699D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcS3EpHostedZone2453DC2C": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcKmsEpHostedZoneFA42C849", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/s3/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcS3EpHostedZoneD27476BE", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcS3GlobalAccesspointEpHostedZone9DDAEF48": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcEcrDkrEpHostedZone77F74872", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/s3-global.accesspoint/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcS3GlobalAccesspointEpHostedZone763F0212", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcSecretsmanagerEpHostedZone8D952494": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/secretsmanager/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcSecretsmanagerEpHostedZoneE5742A7A", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcSsmEpHostedZone52E166EC": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ssm/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcSsmEpHostedZoneE400276D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcSsmmessagesEpHostedZoneE3AF7882": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/route53/hostedZone/ssmmessages/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcSsmmessagesEpHostedZone19BD15F2", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcDnsStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamexampleRuleResolverRuleE76DD02F": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcSecretsmanagerEpHostedZone8D952494", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/rules/example-rule/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorOutboundResolverRuleExampleRule8B329125", - "ResolverRuleId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParaminboundTargetRuleResolverRule19318804": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpcSecretsmanagerEpHostedZone8D952494", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/rules/inbound-target-rule/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorOutboundResolverRuleInboundTargetRule35E9CFD7", - "ResolverRuleId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-endpoints-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-endpoints-stack.test.ts.snap deleted file mode 100644 index 7f3bd95..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-endpoints-stack.test.ts.snap +++ /dev/null @@ -1,3076 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkVpcEndpointsStack Construct(NetworkVpcEndpointsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworknetworkFirewallpoliciesacceleratorpolicyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/networkFirewall/policies/accelerator-policy/arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssecurityGroupNetworkEndpointsCustomEndpointSgidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/securityGroup/Network-Endpoints-CustomEndpointSg/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsTgwAttachAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsTgwAttachBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Gateway/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionTgwAttachAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionTgwAttachBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryARtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-A-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryBRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-B-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryCRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-C-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryDRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-D-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryCidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-C/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryDidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-D/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryDualStackidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Dual-Stack/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryIpv6OnlyidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Ipv6-Only/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryOutposts1idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-1/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryOutpostsAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplateidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadARtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-A-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplaterouteTableWorkloadBRtidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-B-Rt/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplatesubnetWorkloadAidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/subnet/Workload-A/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkvpcWorkloadTemplatesubnetWorkloadBidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/vpc/Workload-Template/subnet/Workload-B/id", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorFirewallFlowLogGroupC8720B1F": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorInboundEpSecurityGroup562F5EAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC23", - "reason": "Allowed access for interface endpoints", - }, - ], - }, - }, - "Properties": { - "GroupDescription": "AWS Route 53 Resolver endpoint - accelerator-inbound", - "GroupName": "ep_accelerator-inbound_sg", - "SecurityGroupEgress": [ - { - "CidrIp": "127.0.0.1/32", - "IpProtocol": "-1", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 53, - "IpProtocol": "tcp", - "ToPort": 53, - }, - { - "CidrIp": "0.0.0.0/0", - "FromPort": 53, - "IpProtocol": "udp", - "ToPort": 53, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "ep_accelerator-inbound_sg", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "AcceleratorInboundResolverEndpoint45B4F44C": { - "Properties": { - "Direction": "INBOUND", - "IpAddresses": [ - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Name": "accelerator-inbound", - "SecurityGroupIds": [ - { - "Ref": "AcceleratorInboundEpSecurityGroup562F5EAB", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-inbound", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverEndpoint", - }, - "AcceleratorOutboundEpSecurityGroup37D346D3": { - "Properties": { - "GroupDescription": "AWS Route 53 Resolver endpoint - accelerator-outbound", - "GroupName": "ep_accelerator-outbound_sg", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 53, - "IpProtocol": "tcp", - "ToPort": 53, - }, - { - "CidrIp": "0.0.0.0/0", - "FromPort": 53, - "IpProtocol": "udp", - "ToPort": 53, - }, - ], - "SecurityGroupIngress": [], - "Tags": [ - { - "Key": "Name", - "Value": "ep_accelerator-outbound_sg", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "AcceleratorOutboundResolverEndpoint67D7E14C": { - "Properties": { - "Direction": "OUTBOUND", - "IpAddresses": [ - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Name": "accelerator-outbound", - "SecurityGroupIds": [ - { - "Ref": "AcceleratorOutboundEpSecurityGroup37D346D3", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-outbound", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverEndpoint", - }, - "AzIdFirewallFlowLogGroup1F60EDC3": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5": { - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeSubnets", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1": { - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeAvailabilityZones", - "network-firewall:DescribeFirewall", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InboundEndpointLocalEpSecurityGroup4A3F8547": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC23", - "reason": "Allowed access for interface endpoints", - }, - ], - }, - }, - "Properties": { - "GroupDescription": "AWS Route 53 Resolver endpoint - inbound-endpoint-local", - "GroupName": "ep_inbound-endpoint-local_sg", - "SecurityGroupEgress": [ - { - "CidrIp": "127.0.0.1/32", - "IpProtocol": "-1", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 53, - "IpProtocol": "tcp", - "ToPort": 53, - }, - { - "CidrIp": "0.0.0.0/0", - "FromPort": 53, - "IpProtocol": "udp", - "ToPort": 53, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "ep_inbound-endpoint-local_sg", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "InboundEndpointLocalResolverEndpointB3CA66C8": { - "Properties": { - "Direction": "INBOUND", - "IpAddresses": [ - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Name": "inbound-endpoint-local", - "SecurityGroupIds": [ - { - "Ref": "InboundEndpointLocalEpSecurityGroup4A3F8547", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "inbound-endpoint-local", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverEndpoint", - }, - "NetworkEndpointsVpcAccessAnalyzerEp6343FDB8": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssecurityGroupNetworkEndpointsCustomEndpointSgidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.access-analyzer", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcCodeartifactApiEp2E4B51E8": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.codeartifact.api", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcCodeartifactRepositoriesEp7F59D69F": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.codeartifact.repositories", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcDynamodbA4771DEC": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".dynamodb", - ], - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcEc2Ep783F3682": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.ec2", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcEc2messagesEpA54B71D8": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.ec2messages", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcEcrDkrEp3906D7BF": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.ecr.dkr", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcEksEp8365ED80": { - "Properties": { - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.eks", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcKmsEp2C6C39AF": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.kms", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcLogsEp2E2972B3": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.logs", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcS343643D83": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsrouteTableNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".s3", - ], - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcS3EpE4A4AFCA": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.s3", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcS3GlobalAccesspointEp121D6E6D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.s3-global.accesspoint", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcSecretsmanagerEp1DDBFEFC": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssecurityGroupNetworkEndpointsCustomEndpointSgidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.secretsmanager", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcSsmEpAE333CD4": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.ssm", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpcSsmmessagesEp9F9567DE": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD", - }, - ], - "ServiceName": "com.amazonaws.us-east-1.ssmmessages", - "SubnetIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointssubnetNetworkEndpointsBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "VpcEndpointType": "Interface", - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkEndpointsVpchttpsEpSecurityGroup90D555DD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC23", - "reason": "Allowed access for interface endpoints", - }, - ], - }, - }, - "Properties": { - "GroupDescription": "Security group for interface endpoints -- https traffic", - "GroupName": "interface_ep_https_sg", - "SecurityGroupEgress": [ - { - "CidrIp": "127.0.0.1/32", - "IpProtocol": "-1", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 443, - "IpProtocol": "tcp", - "ToPort": 443, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "interface_ep_https_sg", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkEndpointsidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "NetworkInspectionNetworkInspectionAIpamSubnetLookupAB49CB8A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-A/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionNetworkInspectionBIpamSubnetLookup8E0B0779": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:role/AWSAccelerator-GetIpamCidrRole", - ], - ], - }, - "ssmSubnetIdPath": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-B/id", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionVpcDynamodbB337ABEC": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".dynamodb", - ], - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkInspectionVpcS30E917C85": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [ - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".s3", - ], - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427": { - "Properties": { - "FirewallName": "accelerator-firewall", - "FirewallPolicyArn": { - "Ref": "SsmParameterValueacceleratornetworknetworkFirewallpoliciesacceleratorpolicyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "SubnetMappings": [ - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionsubnetNetworkInspectionBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-firewall", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::NetworkFirewall::Firewall", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallLoggingConfigFCD92D36": { - "Properties": { - "FirewallArn": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427", - }, - "LoggingConfiguration": { - "LogDestinationConfigs": [ - { - "LogDestination": { - "bucketName": "existing-central-log-bucket", - "prefix": "firewall", - }, - "LogDestinationType": "S3", - "LogType": "ALERT", - }, - { - "LogDestination": { - "logGroup": { - "Ref": "AcceleratorFirewallFlowLogGroupC8720B1F", - }, - }, - "LogDestinationType": "CloudWatchLogs", - "LogType": "FLOW", - }, - ], - }, - }, - "Type": "AWS::NetworkFirewall::LoggingConfiguration", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionGatewayRouteTableNfwRouteDynamicA7F44B00F": { - "Properties": { - "DestinationCidrBlock": { - "Fn::GetAtt": [ - "NetworkInspectionNetworkInspectionAIpamSubnetLookupAB49CB8A", - "ipv4CidrBlock", - ], - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionGatewayRouteTableNfwRouteDynamicAEndpointCEB2BF68", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionGatewayRouteTableNfwRouteDynamicAEndpointCEB2BF68": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "us-east-1a", - "firewallArn": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionGatewayRouteTableNfwRouteDynamicBA3840700": { - "Properties": { - "DestinationCidrBlock": { - "Fn::GetAtt": [ - "NetworkInspectionNetworkInspectionBIpamSubnetLookup8E0B0779", - "ipv4CidrBlock", - ], - }, - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionGatewayidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionGatewayRouteTableNfwRouteDynamicBEndpoint06B0174C", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionGatewayRouteTableNfwRouteDynamicBEndpoint06B0174C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "us-east-1b", - "firewallArn": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionTgwARouteTableNfwRoute6E086E4C": { - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwAidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionTgwARouteTableNfwRouteEndpointEAD15969", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionTgwARouteTableNfwRouteEndpointEAD15969": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "us-east-1a", - "firewallArn": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionTgwBRouteTableNfwRouteEndpoint581805D2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "us-east-1b", - "firewallArn": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionTgwBRouteTableNfwRouteFB931FD1": { - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkInspectionrouteTableNetworkInspectionTgwBidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallNetworkInspectionVpcNetworkInspectionTgwBRouteTableNfwRouteEndpoint581805D2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryVpcDynamodbD30AC83F": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".dynamodb", - ], - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkSecondaryVpcS395D8AC66": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByOrgsIdentities", - }, - { - "Action": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AllowRequestsByAWSServicePrincipals", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".s3", - ], - ], - }, - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "NetworkSecondaryazIdFirewallNetworkFirewall7A5509D1": { - "Properties": { - "FirewallName": "az-id-firewall", - "FirewallPolicyArn": { - "Ref": "SsmParameterValueacceleratornetworknetworkFirewallpoliciesacceleratorpolicyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "SubnetMappings": [ - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryCidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "SubnetId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondarysubnetNetworkSecondaryDidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "az-id-firewall", - }, - ], - "VpcId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::NetworkFirewall::Firewall", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallLoggingConfig8FD77CF7": { - "Properties": { - "FirewallArn": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewall7A5509D1", - }, - "LoggingConfiguration": { - "LogDestinationConfigs": [ - { - "LogDestination": { - "bucketName": "existing-central-log-bucket", - "prefix": "firewall", - }, - "LogDestinationType": "S3", - "LogType": "ALERT", - }, - { - "LogDestination": { - "logGroup": { - "Ref": "AzIdFirewallFlowLogGroup1F60EDC3", - }, - }, - "LogDestinationType": "CloudWatchLogs", - "LogType": "FLOW", - }, - ], - }, - }, - "Type": "AWS::NetworkFirewall::LoggingConfiguration", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryCRtRouteTableNfwRouteC17C0EA2": { - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryCRtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryCRtRouteTableNfwRouteEndpointA0D74808", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryCRtRouteTableNfwRouteEndpointA0D74808": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "use1-az1", - "firewallArn": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewall7A5509D1", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryCRtRouteTableNfwRoutev6704FCC8B": { - "Properties": { - "DestinationIpv6CidrBlock": "::/0", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryCRtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryCRtRouteTableNfwRoutev6EndpointF406EA26", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryCRtRouteTableNfwRoutev6EndpointF406EA26": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "use1-az1", - "firewallArn": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewall7A5509D1", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryDRtRouteTableNfwRoute8CC7C03B": { - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "SsmParameterValueacceleratornetworkvpcNetworkSecondaryrouteTableNetworkSecondaryDRtidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcEndpointId": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryDRtRouteTableNfwRouteEndpoint7037E5C2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryazIdFirewallNetworkFirewallNetworkSecondaryVpcNetworkSecondaryDRtRouteTableNfwRouteEndpoint7037E5C2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "use1-az2", - "firewallArn": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewall7A5509D1", - }, - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcEndpointsStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsaccessAnalyzerDns80847016": { - "DependsOn": [ - "SsmParamNetworkEndpointseksPhz632B4665", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/access-analyzer/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcAccessAnalyzerEp6343FDB8", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsaccessAnalyzerPhzF41FB5A9": { - "DependsOn": [ - "SsmParamNetworkEndpointseksPhz632B4665", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/access-analyzer/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcAccessAnalyzerEp6343FDB8", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointscodeartifactApiDnsD1D4C462": { - "DependsOn": [ - "SsmParamNetworkEndpointscodeartifactRepositoriesDnsD49322BB", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.api/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcCodeartifactApiEp2E4B51E8", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointscodeartifactApiPhzEDB90881": { - "DependsOn": [ - "SsmParamNetworkEndpointscodeartifactRepositoriesDnsD49322BB", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.api/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcCodeartifactApiEp2E4B51E8", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointscodeartifactRepositoriesDnsD49322BB": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.repositories/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcCodeartifactRepositoriesEp7F59D69F", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointscodeartifactRepositoriesPhzDC1F51C9": { - "DependsOn": [ - "SsmParamNetworkEndpointscodeartifactRepositoriesDnsD49322BB", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/codeartifact.repositories/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcCodeartifactRepositoriesEp7F59D69F", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsec2DnsA60AF951": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEc2Ep783F3682", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsec2Phz5CD5C191": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEc2Ep783F3682", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsec2messagesDnsFA1C583B": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2messages/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEc2messagesEpA54B71D8", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsec2messagesPhz6357A488": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ec2messages/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEc2messagesEpA54B71D8", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsecrDkrDns31441DBC": { - "DependsOn": [ - "SsmParamNetworkEndpointss3PhzE2EECCBF", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ecr.dkr/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEcrDkrEp3906D7BF", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsecrDkrPhz761BB2A9": { - "DependsOn": [ - "SsmParamNetworkEndpointss3PhzE2EECCBF", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ecr.dkr/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEcrDkrEp3906D7BF", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointseksDns35E4FACC": { - "DependsOn": [ - "SsmParamNetworkEndpointscodeartifactRepositoriesDnsD49322BB", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/eks/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEksEp8365ED80", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointseksPhz632B4665": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/eks/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcEksEp8365ED80", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointskmsDns52AB3E63": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/kms/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcKmsEp2C6C39AF", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointskmsPhz4C558915": { - "DependsOn": [ - "SsmParamNetworkEndpointskmsDns52AB3E63", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/kms/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcKmsEp2C6C39AF", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointslogsDns987F0AF9": { - "DependsOn": [ - "SsmParamNetworkEndpointskmsDns52AB3E63", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/logs/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcLogsEp2E2972B3", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointslogsPhzDC71A382": { - "DependsOn": [ - "SsmParamNetworkEndpointskmsDns52AB3E63", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/logs/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcLogsEp2E2972B3", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointss3DnsA83EEA61": { - "DependsOn": [ - "SsmParamNetworkEndpointskmsDns52AB3E63", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcS3EpE4A4AFCA", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointss3GlobalAccesspointDns50F35D30": { - "DependsOn": [ - "SsmParamNetworkEndpointss3PhzE2EECCBF", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3-global.accesspoint/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcS3GlobalAccesspointEp121D6E6D", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointss3GlobalAccesspointPhz46A7E80B": { - "DependsOn": [ - "SsmParamNetworkEndpointss3PhzE2EECCBF", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3-global.accesspoint/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcS3GlobalAccesspointEp121D6E6D", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointss3PhzE2EECCBF": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/s3/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcS3EpE4A4AFCA", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointssecretsmanagerDns073EEE73": { - "DependsOn": [ - "SsmParamNetworkEndpointseksPhz632B4665", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/secretsmanager/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcSecretsmanagerEp1DDBFEFC", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointssecretsmanagerPhzF52D763D": { - "DependsOn": [ - "SsmParamNetworkEndpointseksPhz632B4665", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/secretsmanager/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcSecretsmanagerEp1DDBFEFC", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsssmDnsADE5D35A": { - "DependsOn": [ - "SsmParamNetworkEndpointsec2messagesPhz6357A488", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssm/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcSsmEpAE333CD4", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsssmPhzF2C37E80": { - "DependsOn": [ - "SsmParamNetworkEndpointsec2messagesPhz6357A488", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssm/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcSsmEpAE333CD4", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsssmmessagesDns075A6D34": { - "DependsOn": [ - "SsmParamNetworkEndpointsec2messagesPhz6357A488", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssmmessages/dns", - "Type": "String", - "Value": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcSsmmessagesEp9F9567DE", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsssmmessagesPhzE73CA844": { - "DependsOn": [ - "SsmParamNetworkEndpointsec2messagesPhz6357A488", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/endpoints/ssmmessages/hostedZoneId", - "Type": "String", - "Value": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "NetworkEndpointsVpcSsmmessagesEp9F9567DE", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionAcceleratorFirewallFirewallArn40D3C53C": { - "DependsOn": [ - "SsmParamacceleratorInboundResolverEndpoint80E88844", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/networkFirewall/accelerator-firewall/arn", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionacceleratorFirewallNetworkFirewallF1BD6427", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryAzIdFirewallFirewallArn3FA086E9": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/networkFirewall/az-id-firewall/arn", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryazIdFirewallNetworkFirewall7A5509D1", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcEndpointsStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorInboundResolverEndpoint80E88844": { - "Properties": { - "Name": "/accelerator/network/route53Resolver/endpoints/accelerator-inbound/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorInboundResolverEndpoint45B4F44C", - "ResolverEndpointId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamacceleratorOutboundResolverEndpoint9F3A81CE": { - "DependsOn": [ - "SsmParamacceleratorInboundResolverEndpoint80E88844", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/endpoints/accelerator-outbound/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorOutboundResolverEndpoint67D7E14C", - "ResolverEndpointId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParaminboundEndpointLocalResolverEndpoint79EEABA5": { - "DependsOn": [ - "SsmParamacceleratorInboundResolverEndpoint80E88844", - ], - "Properties": { - "Name": "/accelerator/network/route53Resolver/endpoints/inbound-endpoint-local/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InboundEndpointLocalResolverEndpointB3CA66C8", - "ResolverEndpointId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-stack.test.ts.snap deleted file mode 100644 index a7d2e07..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/network-vpc-stack.test.ts.snap +++ /dev/null @@ -1,5797 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkVpcStack Construct(NetworkVpcStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkcustomerGatewaysacceleratorcgwidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/customerGateways/accelerator-cgw/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkipampoolshomeregionpoolidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/ipam/pools/home-region-pool/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworkipampoolshomeregionprodpoolidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/ipam/pools/home-region-prod-pool/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2idC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main-2/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/network/transitGateways/Network-Main/id", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorvpcflowlogsdestinationbucketarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/vpc/flow-logs/destination/bucket/arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorDhcpOptsDhcpOpts09C66A89": { - "Properties": { - "DomainName": "example.com", - "DomainNameServers": [ - "1.1.1.1", - "2.2.2.2", - ], - "NetbiosNameServers": [ - "1.1.1.1", - "2.2.2.2", - ], - "NetbiosNodeType": 2, - "NtpServers": [ - "1.1.1.1", - "2.2.2.2", - ], - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-dhcp-opts", - }, - ], - }, - "Type": "AWS::EC2::DHCPOptions", - }, - "AcceleratorGwlbGatewayLoadBalancerEBD97F3E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-ELB2", - "reason": "Gateway Load Balancers do not support access logging.", - }, - ], - }, - }, - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "deletion_protection.enabled", - "Value": "true", - }, - { - "Key": "load_balancing.cross_zone.enabled", - "Value": "true", - }, - ], - "Subnets": [ - { - "Ref": "NetworkInspectionVpcNetworkInspectionASubnet01E2BD4D", - }, - { - "Ref": "NetworkInspectionVpcNetworkInspectionBSubnet064602BF", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Accelerator-GWLB", - }, - ], - "Type": "gateway", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "AcceleratorGwlbGatewayLoadBalancerEndpointServiceC984744B": { - "Properties": { - "AcceptanceRequired": false, - "GatewayLoadBalancerArns": [ - { - "Ref": "AcceleratorGwlbGatewayLoadBalancerEBD97F3E", - }, - ], - }, - "Type": "AWS::EC2::VPCEndpointService", - }, - "AcceleratorGwlbGatewayLoadBalancerEndpointServicePermissions6F55756F": { - "Properties": { - "AllowedPrincipals": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:root", - ], - ], - }, - ], - "ServiceId": { - "Ref": "AcceleratorGwlbGatewayLoadBalancerEndpointServiceC984744B", - }, - }, - "Type": "AWS::EC2::VPCEndpointServicePermissions", - }, - "AcceleratorGwlbSecondaryGatewayLoadBalancerD11F9F31": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-ELB2", - "reason": "Gateway Load Balancers do not support access logging.", - }, - ], - }, - }, - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "deletion_protection.enabled", - "Value": "true", - }, - { - "Key": "load_balancing.cross_zone.enabled", - "Value": "true", - }, - ], - "Subnets": [ - { - "Ref": "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - }, - { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Accelerator-GWLB-secondary", - }, - ], - "Type": "gateway", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "AcceleratorGwlbSecondaryGatewayLoadBalancerEndpointService321C8D34": { - "Properties": { - "AcceptanceRequired": false, - "GatewayLoadBalancerArns": [ - { - "Ref": "AcceleratorGwlbSecondaryGatewayLoadBalancerD11F9F31", - }, - ], - }, - "Type": "AWS::EC2::VPCEndpointService", - }, - "AcceleratorGwlbSecondaryGatewayLoadBalancerEndpointServicePermissions745739E2": { - "Properties": { - "AllowedPrincipals": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:root", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:root", - ], - ], - }, - ], - "ServiceId": { - "Ref": "AcceleratorGwlbSecondaryGatewayLoadBalancerEndpointService321C8D34", - }, - }, - "Type": "AWS::EC2::VPCEndpointServicePermissions", - }, - "AcceleratorGwlbSecondaryNetworkSecondarySharedSsmParametersDB9904B7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/gwlb/Accelerator-GWLB-secondary/endpointService/id", - "value": { - "Ref": "AcceleratorGwlbSecondaryGatewayLoadBalancerEndpointService321C8D34", - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorPrefixListIpv6PrefixListE74978FB": { - "Properties": { - "AddressFamily": "IPv6", - "Entries": [ - { - "Cidr": "2400:cb00::/32", - }, - { - "Cidr": "2606:4700::/32", - }, - { - "Cidr": "2803:f800::/32", - }, - { - "Cidr": "2405:b500::/32", - }, - { - "Cidr": "2405:8100::/32", - }, - { - "Cidr": "2a06:98c0::/29", - }, - { - "Cidr": "2c0f:f248::/32", - }, - ], - "MaxEntries": 10, - "PrefixListName": "accelerator-prefix-list-ipv6", - }, - "Type": "AWS::EC2::PrefixList", - }, - "AcceleratorPrefixListPrefixList8ACB36CF": { - "Properties": { - "AddressFamily": "IPv4", - "Entries": [ - { - "Cidr": "10.1.0.1/32", - }, - ], - "MaxEntries": 1, - "PrefixListName": "accelerator-prefix-list", - }, - "Type": "AWS::EC2::PrefixList", - }, - "CrossAccountCgwPrefixListSharedParameters45B03549": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/prefixList/accelerator-prefix-list/id", - "value": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "CrossAccountCgwVgwVpnSharedParameters71C8F415": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/customerGateways/cross-account-cgw/virtualPrivateGateway/Network-Inspection/id", - "value": { - "Ref": "NetworkInspectionVpcVirtualPrivateGatewayB665C776", - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F": { - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeSecurityGroups", - "ec2:RevokeSecurityGroupIngress", - "ec2:RevokeSecurityGroupEgress", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35": { - "DependsOn": [ - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultVpcCustomResourceProviderLogGroup4113DA48": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DeleteInternetGateway", - "ec2:DetachInternetGateway", - "ec2:DeleteNetworkAcl", - "ec2:DeleteRoute", - "ec2:DeleteSecurityGroup", - "ec2:DeleteSubnet", - "ec2:DeleteVpc", - "ec2:DescribeInternetGateways", - "ec2:DescribeNetworkAcls", - "ec2:DescribeRouteTables", - "ec2:DescribeSecurityGroups", - "ec2:DescribeSubnets", - "ec2:DescribeVpcs", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A": { - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTags", - "ec2:DeleteSubnet", - "ec2:DeleteTags", - "ec2:ModifySubnetAttribute", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":subnet/*", - ], - ], - }, - }, - { - "Action": [ - "ec2:CreateSubnet", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":subnet/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":vpc/*", - ], - ], - }, - ], - }, - { - "Action": [ - "ec2:DescribeVpcs", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077": { - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyRoutes", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F": { - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-555555555555-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to put cross-account ssm parameter value", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DeleteParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmPutParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DeleteDefaultVpc4DBAE36C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultVpcCustomResourceProviderLogGroup4113DA48", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35", - "Arn", - ], - }, - }, - "Type": "Custom::DeleteDefaultVpc", - "UpdateReplacePolicy": "Delete", - }, - "DeleteSecurityGroupRulesNetworkInspectionAC48E56C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - "vpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "Custom::DeleteDefaultSecurityGroupRules", - "UpdateReplacePolicy": "Delete", - }, - "GetIpamCidrRole2DCCA945": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allow read role to get CIDRs from dynamic IPAM resources.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeSubnets", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-GetIpamCidrRole", - }, - "Type": "AWS::IAM::Role", - }, - "GetNLBIPAddressLookupCB983550": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific role arns.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "ec2:DescribeNetworkInterfaces", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-GetNLBIPAddressLookup", - }, - "Type": "AWS::IAM::Role", - }, - "NetworkEndpoints2VpcTransitGatewayAttachment9892C5DB": { - "Properties": { - "Options": { - "ApplianceModeSupport": "disable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - }, - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints-2", - }, - ], - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMain2idC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "NetworkEndpointsASubnetShareResourceShare2DF08804": { - "Properties": { - "Name": "Network-Endpoints-A_SubnetShare", - "Principals": [ - "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222", - "444444444444", - ], - "ResourceArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:555555555555:subnet/", - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - }, - ], - ], - }, - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - "NetworkEndpointsVpc619A1F01": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Ipv4IpamPoolId": { - "Ref": "SsmParameterValueacceleratornetworkipampoolshomeregionprodpoolidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Ipv4NetmaskLength": 25, - "Tags": [ - { - "Key": "env", - "Value": "prod", - }, - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "NetworkEndpointsVpcCloudWatchFlowLogEB41E61E": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcFlowLogsRole2614AF13", - "Arn", - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcFlowLogsGroupB21A3331", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "NetworkEndpointsVpcDhcpOptionsAssociationC9A5CF5F": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "DhcpOptionsId": { - "Ref": "AcceleratorDhcpOptsDhcpOpts09C66A89", - }, - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::VPCDHCPOptionsAssociation", - }, - "NetworkEndpointsVpcFlowLogsGroupB21A3331": { - "DeletionPolicy": "Retain", - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "RetentionInDays": 3653, - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "NetworkEndpointsVpcFlowLogsRole2614AF13": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "vpc-flow-logs.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkEndpointsVpcFlowLogsRoleDefaultPolicyA9F3924A": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogDelivery", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DeleteLogDelivery", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcFlowLogsGroupB21A3331", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NetworkEndpointsVpcFlowLogsRoleDefaultPolicyA9F3924A", - "Roles": [ - { - "Ref": "NetworkEndpointsVpcFlowLogsRole2614AF13", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkEndpointsVpcInternetGateway6505072B": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - }, - "Type": "AWS::EC2::InternetGateway", - }, - "NetworkEndpointsVpcInternetGatewayAttachment8B5DCF4D": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "InternetGatewayId": { - "Ref": "NetworkEndpointsVpcInternetGateway6505072B", - }, - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "NetworkEndpointsVpcManagementSg2B8F9418": { - "Properties": { - "GroupDescription": "Management Security Group", - "GroupName": "Management", - "SecurityGroupEgress": [ - { - "CidrIp": "10.0.0.0/8", - "Description": "All Outbound", - "IpProtocol": "-1", - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "All Outbound", - "IpProtocol": "-1", - }, - { - "CidrIp": "100.96.250.0/23", - "Description": "All Outbound", - "IpProtocol": "-1", - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "ipv4CidrBlock", - ], - }, - "Description": "All Outbound", - "IpProtocol": "-1", - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "ipv4CidrBlock", - ], - }, - "Description": "All Outbound", - "IpProtocol": "-1", - }, - { - "Description": "All Outbound", - "DestinationPrefixListId": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - "IpProtocol": "-1", - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "Limited Outbound", - "FromPort": 22, - "IpProtocol": "tcp", - "ToPort": 22, - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "Limited Outbound", - "FromPort": 22, - "IpProtocol": "udp", - "ToPort": 22, - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "IP Protocol Rule", - "IpProtocol": "50", - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "IP Protocol Rule", - "IpProtocol": "45", - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "IP Protocol Rule", - "IpProtocol": "5", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "10.0.0.0/8", - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "ToPort": 3389, - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "ToPort": 3389, - }, - { - "CidrIp": "100.96.250.0/23", - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "ToPort": 3389, - }, - { - "CidrIpv6": "fd00::/8", - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "ToPort": 3389, - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "ipv4CidrBlock", - ], - }, - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "ToPort": 3389, - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "ipv4CidrBlock", - ], - }, - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "ToPort": 3389, - }, - { - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "IpProtocol": "tcp", - "SourcePrefixListId": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - "ToPort": 3389, - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "tcp", - "ToPort": 22, - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "tcp", - "ToPort": 22, - }, - { - "CidrIp": "100.96.250.0/23", - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "tcp", - "ToPort": 22, - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "ipv4CidrBlock", - ], - }, - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "tcp", - "ToPort": 22, - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "ipv4CidrBlock", - ], - }, - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "tcp", - "ToPort": 22, - }, - { - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "tcp", - "SourcePrefixListId": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - "ToPort": 22, - }, - { - "CidrIp": "10.0.0.0/8", - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "udp", - "ToPort": 22, - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "udp", - "ToPort": 22, - }, - { - "CidrIp": "100.96.250.0/23", - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "udp", - "ToPort": 22, - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "ipv4CidrBlock", - ], - }, - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "udp", - "ToPort": 22, - }, - { - "CidrIp": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "ipv4CidrBlock", - ], - }, - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "udp", - "ToPort": 22, - }, - { - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "IpProtocol": "udp", - "SourcePrefixListId": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - "ToPort": 22, - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "IP Protocol Rule", - "IpProtocol": "50", - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "IP Protocol Rule", - "IpProtocol": "45", - }, - { - "CidrIp": "100.96.252.0/23", - "Description": "IP Protocol Rule", - "IpProtocol": "5", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Management", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "NetworkEndpointsVpcManagementSgManagementEgress00D58058BF": { - "Properties": { - "Description": "All Outbound", - "DestinationSecurityGroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "GroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "IpProtocol": "-1", - }, - "Type": "AWS::EC2::SecurityGroupEgress", - }, - "NetworkEndpointsVpcManagementSgManagementIngress008FD972B5": { - "Properties": { - "Description": "Management RDP Traffic Inbound", - "FromPort": 3389, - "GroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "IpProtocol": "tcp", - "SourceSecurityGroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "ToPort": 3389, - }, - "Type": "AWS::EC2::SecurityGroupIngress", - }, - "NetworkEndpointsVpcManagementSgManagementIngress105119E927": { - "Properties": { - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "GroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "IpProtocol": "tcp", - "SourceSecurityGroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "ToPort": 22, - }, - "Type": "AWS::EC2::SecurityGroupIngress", - }, - "NetworkEndpointsVpcManagementSgManagementIngress1135D6C2DD": { - "Properties": { - "Description": "Management SSH Traffic Inbound", - "FromPort": 22, - "GroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "IpProtocol": "udp", - "SourceSecurityGroupId": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - "ToPort": 22, - }, - "Type": "AWS::EC2::SecurityGroupIngress", - }, - "NetworkEndpointsVpcNetworkEndpointsARouteTable5B9BF71B": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints-A", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkEndpointsVpcNetworkEndpointsARouteTableNetworkEndpointsVpcNetworkEndpointsARouteTablePlRoute6AA1061C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - "NetworkEndpointsVpcTransitGatewayAttachmentF207787E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsARouteTable5B9BF71B", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcNetworkEndpointsARouteTableNetworkEndpointsVpcNetworkEndpointsARouteTableTgwRouteBAC98A91": { - "DependsOn": [ - "NetworkEndpointsVpcTransitGatewayAttachmentF207787E", - ], - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsARouteTable5B9BF71B", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkEndpointsVpcNetworkEndpointsARouteTableNetworkEndpointsVpcNetworkEndpointsARouteTableTgwRoutev65D772C9E": { - "DependsOn": [ - "NetworkEndpointsVpcTransitGatewayAttachmentF207787E", - ], - "Properties": { - "DestinationIpv6CidrBlock": "::/0", - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsARouteTable5B9BF71B", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1a", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 27, - }, - "name": "Network-Endpoints-A", - "tags": [ - { - "Key": "exampleKey", - "Value": "exampleValue", - }, - ], - "vpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcNetworkEndpointsASubnetRouteTableAssociation9EE74497": { - "DependsOn": [ - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsARouteTable5B9BF71B", - }, - "SubnetId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkEndpointsVpcNetworkEndpointsBRouteTable639AAADB": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints-B", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkEndpointsVpcNetworkEndpointsBRouteTableNetworkEndpointsVpcNetworkEndpointsBRouteTablePlRouteAE865035": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - "NetworkEndpointsVpcTransitGatewayAttachmentF207787E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsBRouteTable639AAADB", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcNetworkEndpointsBRouteTableNetworkEndpointsVpcNetworkEndpointsBRouteTableTgwRoute7A9706EC": { - "DependsOn": [ - "NetworkEndpointsVpcTransitGatewayAttachmentF207787E", - ], - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsBRouteTable639AAADB", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "NetworkEndpointsVpcNetworkEndpointsASubnetRouteTableAssociation9EE74497", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1b", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 27, - }, - "name": "Network-Endpoints-B", - "tags": [], - "vpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcNetworkEndpointsBSubnetRouteTableAssociation68383153": { - "DependsOn": [ - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "NetworkEndpointsVpcNetworkEndpointsASubnetRouteTableAssociation9EE74497", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsBRouteTable639AAADB", - }, - "SubnetId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkEndpointsVpcNetworkEndpointsCustomEndpointSgSg4BB9279D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-EC23", - "reason": "User defined an all ingress rule in configuration.", - }, - ], - }, - }, - "Properties": { - "GroupDescription": "Accelerator security group for custom endpoint", - "GroupName": "Network-Endpoints-CustomEndpointSg", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound", - "IpProtocol": "-1", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow access to 443", - "FromPort": 443, - "IpProtocol": "tcp", - "ToPort": 443, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints-CustomEndpointSg", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "NetworkEndpointsVpcNetworkEndpointsTgwARouteTable9117846C": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints-Tgw-A", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "NetworkEndpointsVpcNetworkEndpointsBSubnetRouteTableAssociation68383153", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1a", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-EndpointsTgwAttach-A", - "tags": [], - "vpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnetRouteTableAssociation4D925D52": { - "DependsOn": [ - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "NetworkEndpointsVpcNetworkEndpointsBSubnetRouteTableAssociation68383153", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwARouteTable9117846C", - }, - "SubnetId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnetRouteTableAssociation4D925D52", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1b", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-EndpointsTgwAttach-B", - "tags": [], - "vpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnetRouteTableAssociation958ED1AB": { - "DependsOn": [ - "NetworkEndpointsVpcVpcCidrBlockC483B99B", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnetRouteTableAssociation4D925D52", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwBRouteTable7A11C65B", - }, - "SubnetId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkEndpointsVpcNetworkEndpointsTgwBRouteTable7A11C65B": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints-Tgw-B", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkEndpointsVpcS3FlowLog1207DE15": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "LogDestination": { - "Fn::Join": [ - "", - [ - { - "Ref": "SsmParameterValueacceleratorvpcflowlogsdestinationbucketarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "/vpc-flow-logs/", - ], - ], - }, - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "NetworkEndpointsVpcSharedParameters07793E5F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "444444444444", - ], - "parameters": [ - { - "name": "/accelerator/network/vpc/Network-Endpoints/id", - "value": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-A/id", - "value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - }, - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-A/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "ipv4CidrBlock", - ], - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "NetworkEndpointsVpcTestNaclNaclD97515C3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "TestNACL", - }, - ], - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::NetworkAcl", - }, - "NetworkEndpointsVpcTestNaclNaclNetworkEndpointsVpcTestNaclInbound10285077E8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": "10.0.0.0/8", - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkEndpointsVpcTestNaclNaclD97515C3", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 10, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkEndpointsVpcTestNaclNaclNetworkEndpointsVpcTestNaclInbound204B9A2831": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "ipv4CidrBlock", - ], - }, - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkEndpointsVpcTestNaclNaclD97515C3", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 20, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkEndpointsVpcTestNaclNaclNetworkEndpointsVpcTestNaclNaclAssociateNetworkEndpointsA4115E6F7": { - "Properties": { - "NetworkAclId": { - "Ref": "NetworkEndpointsVpcTestNaclNaclD97515C3", - }, - "SubnetId": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - }, - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "NetworkEndpointsVpcTestNaclNaclNetworkEndpointsVpcTestNaclOutbound1089A97466": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": "0.0.0.0/0", - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkEndpointsVpcTestNaclNaclD97515C3", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 10, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkEndpointsVpcTestNaclNaclNetworkEndpointsVpcTestNaclOutbound20634A981E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "ipv4CidrBlock", - ], - }, - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkEndpointsVpcTestNaclNaclD97515C3", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 20, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkEndpointsVpcTransitGatewayAttachmentF207787E": { - "Properties": { - "Options": { - "ApplianceModeSupport": "disable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - }, - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "NetworkEndpointsVpcVirtualPrivateGateway2CE2FFC3": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AmazonSideAsn": 65200, - "Tags": [ - { - "Key": "Name", - "Value": "Network-Endpoints", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNGateway", - }, - "NetworkEndpointsVpcVirtualPrivateGatewayAttachment01185FBA": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - "VpnGatewayId": { - "Ref": "NetworkEndpointsVpcVirtualPrivateGateway2CE2FFC3", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "NetworkEndpointsVpcVpcCidrBlockC483B99B": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "Ipv4IpamPoolId": { - "Ref": "SsmParameterValueacceleratornetworkipampoolshomeregionprodpoolidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Ipv4NetmaskLength": 25, - "VpcId": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "NetworkInspectionVgwVpnConnectionCC9DB36D": { - "Properties": { - "CustomerGatewayId": { - "Ref": "SsmParameterValueacceleratornetworkcustomerGatewaysacceleratorcgwidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "StaticRoutesOnly": false, - "Tags": [ - { - "Key": "Name", - "Value": "VpcInspectionVpnConnection", - }, - ], - "Type": "ipsec.1", - "VpnGatewayId": { - "Ref": "NetworkInspectionVpcVirtualPrivateGatewayB665C776", - }, - "VpnTunnelOptionsSpecifications": [ - { - "TunnelInsideCidr": "169.254.100.0/30", - }, - { - "TunnelInsideCidr": "169.254.100.100/30", - }, - ], - }, - "Type": "AWS::EC2::VPNConnection", - }, - "NetworkInspectionVpc302E00C0": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "CidrBlock": "10.2.0.0/22", - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "NetworkInspectionVpcAcceleratorNatGwANatGateway512AA371": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "NetworkInspectionVpcAcceleratorNatGwANatGatewayEipDF395D54", - "AllocationId", - ], - }, - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionASubnet01E2BD4D", - }, - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-nat-gw-a", - }, - ], - }, - "Type": "AWS::EC2::NatGateway", - }, - "NetworkInspectionVpcAcceleratorNatGwANatGatewayEipDF395D54": { - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-nat-gw-a", - }, - ], - }, - "Type": "AWS::EC2::EIP", - }, - "NetworkInspectionVpcAcceleratorNatGwBNatGateway2164E612": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "NetworkInspectionVpcAcceleratorNatGwBNatGatewayEip01CCA1DC", - "AllocationId", - ], - }, - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionBSubnet064602BF", - }, - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-nat-gw-b", - }, - ], - }, - "Type": "AWS::EC2::NatGateway", - }, - "NetworkInspectionVpcAcceleratorNatGwBNatGatewayEip01CCA1DC": { - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-nat-gw-b", - }, - ], - }, - "Type": "AWS::EC2::EIP", - }, - "NetworkInspectionVpcAcceleratorNatGwCNatGateway484831C4": { - "Properties": { - "ConnectivityType": "private", - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionBSubnet064602BF", - }, - "Tags": [ - { - "Key": "Name", - "Value": "accelerator-nat-gw-c", - }, - ], - }, - "Type": "AWS::EC2::NatGateway", - }, - "NetworkInspectionVpcCloudWatchFlowLog2C44FB5C": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::GetAtt": [ - "NetworkInspectionVpcFlowLogsRole07B5123D", - "Arn", - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "NetworkInspectionVpcFlowLogsGroupAF8DE105", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "NetworkInspectionVpcDataSg936B8E8C": { - "Properties": { - "GroupDescription": "Firewall data", - "GroupName": "Data", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "All outbound", - "IpProtocol": "-1", - }, - ], - "SecurityGroupIngress": [ - { - "CidrIp": "10.2.0.0/22", - "Description": "GENEVE", - "FromPort": 6081, - "IpProtocol": "udp", - "ToPort": 6081, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Data", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "NetworkInspectionVpcFlowLogsGroupAF8DE105": { - "DeletionPolicy": "Retain", - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "RetentionInDays": 3653, - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "NetworkInspectionVpcFlowLogsRole07B5123D": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "vpc-flow-logs.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkInspectionVpcFlowLogsRoleDefaultPolicyCCB53259": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogDelivery", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DeleteLogDelivery", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NetworkInspectionVpcFlowLogsGroupAF8DE105", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NetworkInspectionVpcFlowLogsRoleDefaultPolicyCCB53259", - "Roles": [ - { - "Ref": "NetworkInspectionVpcFlowLogsRole07B5123D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkInspectionVpcInternetGateway6D8C30C4": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - }, - "Type": "AWS::EC2::InternetGateway", - }, - "NetworkInspectionVpcInternetGatewayAttachment6A85212A": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "InternetGatewayId": { - "Ref": "NetworkInspectionVpcInternetGateway6D8C30C4", - }, - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "NetworkInspectionVpcNetworkInspectionARouteTableED0E5084": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-A", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkInspectionVpcNetworkInspectionARouteTableNetworkInspectionVpcNetworkInspectionARouteTableTgwRouteA8C2E061": { - "DependsOn": [ - "NetworkInspectionVpcTransitGatewayAttachment2E4BA675", - ], - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionARouteTableED0E5084", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcNetworkInspectionASubnet01E2BD4D": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "AvailabilityZone": "us-east-1a", - "CidrBlock": "10.2.0.0/24", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-A", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "NetworkInspectionVpcNetworkInspectionASubnetRouteTableAssociation36C3A49A": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionARouteTableED0E5084", - }, - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionASubnet01E2BD4D", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkInspectionVpcNetworkInspectionBRouteTable1410DC9F": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-B", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkInspectionVpcNetworkInspectionBRouteTableNetworkInspectionVpcNetworkInspectionBRouteTableTgwRoute9373B412": { - "DependsOn": [ - "NetworkInspectionVpcTransitGatewayAttachment2E4BA675", - ], - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionBRouteTable1410DC9F", - }, - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkInspectionVpcNetworkInspectionBSubnet064602BF": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "AvailabilityZone": "us-east-1b", - "CidrBlock": "10.2.1.0/24", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-B", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "NetworkInspectionVpcNetworkInspectionBSubnetRouteTableAssociationBF1B26ED": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionBRouteTable1410DC9F", - }, - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionBSubnet064602BF", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkInspectionVpcNetworkInspectionGatewayRouteTable22A0DD2C": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-Gateway", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkInspectionVpcNetworkInspectionGatewayRouteTableGatewayAssociation826FEA45": { - "DependsOn": [ - "NetworkInspectionVpcInternetGatewayAttachment6A85212A", - ], - "Properties": { - "GatewayId": { - "Ref": "NetworkInspectionVpcInternetGateway6D8C30C4", - }, - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionGatewayRouteTable22A0DD2C", - }, - }, - "Type": "AWS::EC2::GatewayRouteTableAssociation", - }, - "NetworkInspectionVpcNetworkInspectionTgwARouteTable1753FE94": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-Tgw-A", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkInspectionVpcNetworkInspectionTgwAttachASubnetAB4DCECD": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "AvailabilityZone": "us-east-1a", - "CidrBlock": "10.2.3.208/28", - "Tags": [ - { - "Key": "Name", - "Value": "Network-InspectionTgwAttach-A", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "NetworkInspectionVpcNetworkInspectionTgwAttachASubnetRouteTableAssociation7B585652": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwARouteTable1753FE94", - }, - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwAttachASubnetAB4DCECD", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkInspectionVpcNetworkInspectionTgwAttachBSubnet58B77365": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "AvailabilityZone": "us-east-1b", - "CidrBlock": "10.2.3.224/28", - "Tags": [ - { - "Key": "Name", - "Value": "Network-InspectionTgwAttach-B", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "NetworkInspectionVpcNetworkInspectionTgwAttachBSubnetRouteTableAssociation30CB945F": { - "DependsOn": [ - "NetworkInspectionVpcVpcCidrBlock343B2A17", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwBRouteTable4D355F69", - }, - "SubnetId": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwAttachBSubnet58B77365", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkInspectionVpcNetworkInspectionTgwBRouteTable4D355F69": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection-Tgw-B", - }, - ], - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkInspectionVpcS3FlowLogC404C8FC": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "LogDestination": { - "Fn::Join": [ - "", - [ - { - "Ref": "SsmParameterValueacceleratorvpcflowlogsdestinationbucketarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "/vpc-flow-logs/", - ], - ], - }, - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "NetworkInspectionVpcTransitGatewayAttachment2E4BA675": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwAttachASubnetAB4DCECD", - }, - { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwAttachBSubnet58B77365", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - "TransitGatewayId": { - "Ref": "SsmParameterValueacceleratornetworktransitGatewaysNetworkMainidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "NetworkInspectionVpcVirtualPrivateGatewayAttachment8507FAB6": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - "VpnGatewayId": { - "Ref": "NetworkInspectionVpcVirtualPrivateGatewayB665C776", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "NetworkInspectionVpcVirtualPrivateGatewayB665C776": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AmazonSideAsn": 65000, - "Tags": [ - { - "Key": "accelerator:central-endpoints-account-id", - "Value": "555555555555", - }, - { - "Key": "accelerator:use-central-endpoints", - "Value": "true", - }, - { - "Key": "Name", - "Value": "Network-Inspection", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNGateway", - }, - "NetworkInspectionVpcVpcCidrBlock343B2A17": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "CidrBlock": "10.3.0.0/16", - "VpcId": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "NetworkSecondaryVpc37FB1791": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Ipv4IpamPoolId": { - "Ref": "SsmParameterValueacceleratornetworkipampoolshomeregionprodpoolidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Ipv4NetmaskLength": 25, - "Tags": [ - { - "Key": "env", - "Value": "prod", - }, - { - "Key": "Name", - "Value": "Network-Secondary", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "NetworkSecondaryVpcCloudWatchFlowLogFA128544": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcFlowLogsRole8D6C92BF", - "Arn", - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcFlowLogsGroup3069B74B", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "NetworkSecondaryVpcEgressOnlyIgwF55C954F": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::EgressOnlyInternetGateway", - }, - "NetworkSecondaryVpcFlowLogsGroup3069B74B": { - "DeletionPolicy": "Retain", - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "RetentionInDays": 3653, - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary", - }, - ], - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "NetworkSecondaryVpcFlowLogsRole8D6C92BF": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "vpc-flow-logs.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NetworkSecondaryVpcFlowLogsRoleDefaultPolicy8B098897": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogDelivery", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DeleteLogDelivery", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcFlowLogsGroup3069B74B", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NetworkSecondaryVpcFlowLogsRoleDefaultPolicy8B098897", - "Roles": [ - { - "Ref": "NetworkSecondaryVpcFlowLogsRole8D6C92BF", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AmazonProvidedIpv6CidrBlock": true, - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "NetworkSecondaryVpcNetworkSecondaryARtRouteTable649EACD5": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary-A-Rt", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1a", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-Secondary-A", - "tags": [], - "vpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcNetworkSecondaryASubnetRouteTableAssociationBBEA4449": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryARtRouteTable649EACD5", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary-B-Rt", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkSecondaryVpcNetworkSecondaryBRtRouteTableNetworkSecondaryVpcNetworkSecondaryBRtRouteTableEigwRoute60FE5861": { - "Properties": { - "DestinationIpv6CidrBlock": "::/0", - "EgressOnlyInternetGatewayId": { - "Ref": "NetworkSecondaryVpcEgressOnlyIgwF55C954F", - }, - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryVpcNetworkSecondaryBRtRouteTableNetworkSecondaryVpcNetworkSecondaryBRtRouteTableLgwRouteAE021913": { - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "LocalGatewayId": "lgw-abcxyz", - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF", - }, - }, - "Type": "AWS::EC2::Route", - }, - "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - "NetworkSecondaryVpcNetworkSecondaryASubnetRouteTableAssociationBBEA4449", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1b", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-Secondary-B", - "tags": [], - "vpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcNetworkSecondaryBSubnetRouteTableAssociation6EB02D15": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - "NetworkSecondaryVpcNetworkSecondaryASubnetRouteTableAssociationBBEA4449", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryCRtRouteTableF66B2FAA": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary-C-Rt", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - "NetworkSecondaryVpcNetworkSecondaryBSubnetRouteTableAssociation6EB02D15", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZoneId": "use1-az1", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-Secondary-C", - "tags": [], - "vpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcNetworkSecondaryCSubnetRouteTableAssociation0ADC5EBF": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - "NetworkSecondaryVpcNetworkSecondaryBSubnetRouteTableAssociation6EB02D15", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryCRtRouteTableF66B2FAA", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryDRtRouteTable55050289": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary-D-Rt", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE", - "NetworkSecondaryVpcNetworkSecondaryCSubnetRouteTableAssociation0ADC5EBF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZoneId": "use1-az2", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-Secondary-D", - "tags": [], - "vpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcNetworkSecondaryDSubnetRouteTableAssociation7127663A": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE", - "NetworkSecondaryVpcNetworkSecondaryCSubnetRouteTableAssociation0ADC5EBF", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDRtRouteTable55050289", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryDualStackSubnetACB095CF": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - ], - "Properties": { - "AvailabilityZone": "us-east-1a", - "CidrBlock": "10.0.0.192/28", - "Ipv6CidrBlock": "fd00::/64", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary-Dual-Stack", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "NetworkSecondaryVpcNetworkSecondaryDualStackSubnetRouteTableAssociation5BF657BA": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryARtRouteTable649EACD5", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDualStackSubnetACB095CF", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryIpv6OnlySubnet1C319120": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - ], - "Properties": { - "AvailabilityZone": "us-east-1b", - "Ipv6CidrBlock": "fd01::/64", - "Ipv6Native": true, - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary-Ipv6-Only", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "NetworkSecondaryVpcNetworkSecondaryIpv6OnlySubnetRouteTableAssociation05E9EE6B": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryIpv6OnlySubnet1C319120", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryOutposts1Subnet92FEAC1C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnet94859C6D", - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnetRouteTableAssociation9ACEC596", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZoneId": "use1-az1", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-Secondary-Outposts-1", - "outpostArn": "test-arn-2", - "tags": [], - "vpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcNetworkSecondaryOutposts1SubnetRouteTableAssociation9EE45549": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnet94859C6D", - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnetRouteTableAssociation9ACEC596", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryOutposts1Subnet92FEAC1C", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnet94859C6D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74", - "NetworkSecondaryVpcNetworkSecondaryDSubnetRouteTableAssociation7127663A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1a", - "basePool": [ - "10.0.0.0/24", - "10.2.0.0/24", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-prod-pool", - "netmaskLength": 28, - }, - "name": "Network-Secondary-Outposts-A", - "outpostArn": "test-arn", - "tags": [], - "vpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnetRouteTableAssociation9ACEC596": { - "DependsOn": [ - "NetworkSecondaryVpcIpv6CidrBlock093FFC4F7", - "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74", - "NetworkSecondaryVpcNetworkSecondaryDSubnetRouteTableAssociation7127663A", - ], - "Properties": { - "RouteTableId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryARtRouteTable649EACD5", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnet94859C6D", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "NetworkSecondaryVpcS3FlowLogA736115E": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "LogDestination": { - "Fn::Join": [ - "", - [ - { - "Ref": "SsmParameterValueacceleratorvpcflowlogsdestinationbucketarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "/vpc-flow-logs/", - ], - ], - }, - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Network-Secondary", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "NetworkSecondaryVpcTestNaclNacl63646F9E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "TestNacl", - }, - ], - "VpcId": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::EC2::NetworkAcl", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclInbound1016BE52CB8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": "10.0.0.0/24", - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": 22, - "To": 22, - }, - "Protocol": 22, - "RuleAction": "allow", - "RuleNumber": 101, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclInbound10299C8DDCC": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "ipv4CidrBlock", - ], - }, - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": 443, - "To": 443, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 102, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclInbound10371F28952": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "ipv4CidrBlock", - ], - }, - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": 80, - "To": 80, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 103, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclInbound104FB92CA9E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "ipv4CidrBlock", - ], - }, - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 104, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclInbound107296D77E8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": "10.4.0.0/24", - "Egress": false, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 107, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclInbound10869F993D1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "Egress": false, - "Ipv6CidrBlock": "::/0", - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 108, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclNaclAssociateNetworkSecondaryA052E10A4": { - "Properties": { - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - }, - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclNaclAssociateNetworkSecondaryBD9BA3321": { - "Properties": { - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - }, - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclNaclAssociateNetworkSecondaryC73E9D9B0": { - "Properties": { - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE", - }, - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclNaclAssociateNetworkSecondaryD02015E20": { - "Properties": { - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "SubnetId": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74", - }, - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclOutbound10101A31DA2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": "172.16.0.0/12", - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": 80, - "To": 80, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 101, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclOutbound10295F76BCA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "ipv4CidrBlock", - ], - }, - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": 443, - "To": 443, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 102, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclOutbound1038303E3E9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "ipv4CidrBlock", - ], - }, - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": 80, - "To": 80, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 103, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclOutbound104CA731485": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "ipv4CidrBlock", - ], - }, - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "allow", - "RuleNumber": 104, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclOutbound107783EBEA7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "CidrBlock": "10.4.0.0/24", - "Egress": true, - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 107, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "NetworkSecondaryVpcTestNaclNaclNetworkSecondaryVpcTestNaclOutbound1081A01D512": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-VPC3", - "reason": "NACL added to VPC", - }, - ], - }, - }, - "Properties": { - "Egress": true, - "Ipv6CidrBlock": "::/0", - "NetworkAclId": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - "PortRange": { - "From": -1, - "To": -1, - }, - "Protocol": -1, - "RuleAction": "deny", - "RuleNumber": 108, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "PutNetworkVpcStackResourceParameters94AB00D4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "555555555555", - "parameterAccountIds": [ - "555555555555", - ], - "parameters": [ - { - "name": "/accelerator/network/vpc/Network-Secondary/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpc37FB1791", - "CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkEndpointsVpc619A1F01", - "CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Inspection/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkInspectionVpc302E00C0", - "CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Workload-Template/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "WorkloadTemplateVpc46A4D4C0", - "CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-A/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-B/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-C/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-D/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-A/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnet94859C6D", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-1/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkSecondaryVpcNetworkSecondaryOutposts1Subnet92FEAC1C", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Dual-Stack/cidr/ipv4", - "value": "10.0.0.192/28", - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-A/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-B/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-A/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-B/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-A/cidr/ipv4", - "value": "10.2.0.0/24", - }, - { - "name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-B/cidr/ipv4", - "value": "10.2.1.0/24", - }, - { - "name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-A/cidr/ipv4", - "value": "10.2.3.208/28", - }, - { - "name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-B/cidr/ipv4", - "value": "10.2.3.224/28", - }, - { - "name": "/accelerator/network/vpc/Workload-Template/subnet/Workload-A/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "WorkloadTemplateVpcWorkloadASubnetA3CC7B46", - "ipv4CidrBlock", - ], - }, - }, - { - "name": "/accelerator/network/vpc/Workload-Template/subnet/Workload-B/cidr/ipv4", - "value": { - "Fn::GetAtt": [ - "WorkloadTemplateVpcWorkloadBSubnet1E445280", - "ipv4CidrBlock", - ], - }, - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-CrossAccountSsmParameterShare", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorGwlbGwlbArn7CF5F889": { - "DependsOn": [ - "SsmParamAcceleratorGwlbGwlbServiceIdF93A23BF", - ], - "Properties": { - "Name": "/accelerator/network/gwlb/Accelerator-GWLB/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorGwlbGatewayLoadBalancerEBD97F3E", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorGwlbGwlbServiceIdF93A23BF": { - "Properties": { - "Name": "/accelerator/network/gwlb/Accelerator-GWLB/endpointService/id", - "Type": "String", - "Value": { - "Ref": "AcceleratorGwlbGatewayLoadBalancerEndpointServiceC984744B", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorGwlbSecondaryGwlbArn364EE96B": { - "DependsOn": [ - "SsmParamNetworkInspectionDataSecurityGroup1FD45226", - ], - "Properties": { - "Name": "/accelerator/network/gwlb/Accelerator-GWLB-secondary/arn", - "Type": "String", - "Value": { - "Ref": "AcceleratorGwlbSecondaryGatewayLoadBalancerD11F9F31", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorGwlbSecondaryGwlbServiceId3F04FE9A": { - "DependsOn": [ - "SsmParamNetworkInspectionDataSecurityGroup1FD45226", - ], - "Properties": { - "Name": "/accelerator/network/gwlb/Accelerator-GWLB-secondary/endpointService/id", - "Type": "String", - "Value": { - "Ref": "AcceleratorGwlbSecondaryGatewayLoadBalancerEndpointService321C8D34", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorPrefixListIpv6PrefixList8A033D61": { - "Properties": { - "Name": "/accelerator/network/prefixList/accelerator-prefix-list-ipv6/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorPrefixListIpv6PrefixListE74978FB", - "PrefixListId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorPrefixListPrefixListEBB07221": { - "Properties": { - "Name": "/accelerator/network/prefixList/accelerator-prefix-list/id", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorPrefixListPrefixList8ACB36CF", - "PrefixListId", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcStack-555555555555-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsManagementSecurityGroup157CDC67": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTransitGatewayAttachmentId2EBCB846", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/securityGroup/Management/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpoints2TransitGatewayAttachmentIdD5FF19F5": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTransitGatewayAttachmentId2EBCB846", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/transitGatewayAttachment/Network-Endpoints-2/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpoints2VpcTransitGatewayAttachment9892C5DB", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsARouteTableIdC55DBCD4": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsARouteTable5B9BF71B", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsASubnetIdF53EDCC9": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryDualStackSubnetId7D04B583", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsASubnet758434F8", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsBRouteTableId03F05790": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsARouteTableIdC55DBCD4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsBRouteTable639AAADB", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsBSubnetId62D810E8": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryDualStackSubnetId7D04B583", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-Endpoints-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsBSubnet1CF39812", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsCustomEndpointSgSecurityGroupE01E3A88": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTransitGatewayAttachmentId2EBCB846", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/securityGroup/Network-Endpoints-CustomEndpointSg/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsCustomEndpointSgSg4BB9279D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsTgwARouteTableId2037B6B4": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBRtRouteTableIdFA654D75", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwARouteTable9117846C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsTgwAttachASubnetId72C0AC02": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryDualStackSubnetId7D04B583", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsTgwAttachBSubnetId0B7C06C7": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/subnet/Network-EndpointsTgwAttach-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsTgwBRouteTableIdC8AB07B7": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBRtRouteTableIdFA654D75", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/routeTable/Network-Endpoints-Tgw-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwBRouteTable7A11C65B", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsNetworkEndpointsTransitGatewayAttachmentId2EBCB846": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/transitGatewayAttachment/Network-Endpoints/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcTransitGatewayAttachmentF207787E", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsTestNaclNaclED9584AC": { - "DependsOn": [ - "SsmParamNetworkInspectionDataSecurityGroup1FD45226", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/networkAcl/TestNACL/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcTestNaclNaclD97515C3", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpcIdD8AF7DB9": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpc619A1F01", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkEndpointsVpnGatewayId9424DAB4": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/virtualPrivateGateway/id", - "Type": "String", - "Value": { - "Ref": "NetworkEndpointsVpcVirtualPrivateGateway2CE2FFC3", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionAcceleratorNatGwANatGatewayId28332EEC": { - "DependsOn": [ - "SsmParamWorkloadTemplateWorkloadASubnetIdC0F8B4F7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/natGateway/accelerator-nat-gw-a/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcAcceleratorNatGwANatGateway512AA371", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionAcceleratorNatGwBNatGatewayId74BA7991": { - "DependsOn": [ - "SsmParamWorkloadTemplateWorkloadASubnetIdC0F8B4F7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/natGateway/accelerator-nat-gw-b/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcAcceleratorNatGwBNatGateway2164E612", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionAcceleratorNatGwCNatGatewayId498E58EE": { - "DependsOn": [ - "SsmParamWorkloadTemplateWorkloadASubnetIdC0F8B4F7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/natGateway/accelerator-nat-gw-c/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcAcceleratorNatGwCNatGateway484831C4", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionDataSecurityGroup1FD45226": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/securityGroup/Data/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcDataSg936B8E8C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionARouteTableIdAA9E21C5": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsARouteTableIdC55DBCD4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionARouteTableED0E5084", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionASubnetId84BB7982": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTgwAttachBSubnetId0B7C06C7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionASubnet01E2BD4D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionBRouteTableIdAC1C6F68": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsARouteTableIdC55DBCD4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionBRouteTable1410DC9F", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionBSubnetId08F48703": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTgwAttachBSubnetId0B7C06C7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-Inspection-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionBSubnet064602BF", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionGatewayRouteTableId9C85DF52": { - "DependsOn": [ - "SsmParamNetworkInspectionNetworkInspectionTgwBRouteTableId7113F1A4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Gateway/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionGatewayRouteTable22A0DD2C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionTgwARouteTableId838420AE": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsARouteTableIdC55DBCD4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwARouteTable1753FE94", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionTgwAttachASubnetId90B86E86": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTgwAttachBSubnetId0B7C06C7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwAttachASubnetAB4DCECD", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionTgwAttachBSubnetId470AEEA9": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTgwAttachBSubnetId0B7C06C7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/subnet/Network-InspectionTgwAttach-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwAttachBSubnet58B77365", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionTgwBRouteTableId7113F1A4": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/routeTable/Network-Inspection-Tgw-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcNetworkInspectionTgwBRouteTable4D355F69", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionNetworkInspectionTransitGatewayAttachmentId80B7A8EF": { - "DependsOn": [ - "SsmParamNetworkEndpointsNetworkEndpointsTransitGatewayAttachmentId2EBCB846", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/transitGatewayAttachment/Network-Inspection/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcTransitGatewayAttachment2E4BA675", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionVpcIdFD2142E1": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpnGatewayId9424DAB4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpc302E00C0", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkInspectionVpnGatewayId5E4193A7": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpnGatewayId9424DAB4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Inspection/virtualPrivateGateway/id", - "Type": "String", - "Value": { - "Ref": "NetworkInspectionVpcVirtualPrivateGatewayB665C776", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryARtRouteTableIdD8813C63": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpnGatewayId9424DAB4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-A-Rt/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryARtRouteTable649EACD5", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryASubnetId121C5F23": { - "DependsOn": [ - "SsmParamNetworkInspectionNetworkInspectionTgwBRouteTableId7113F1A4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryASubnet791F4607", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryBRtRouteTableIdFA654D75": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-B-Rt/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBRtRouteTable851997AF", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryBSubnetIdDF008255": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-B/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryBSubnetDD64E26D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryCRtRouteTableId08D87312": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBRtRouteTableIdFA654D75", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-C-Rt/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryCRtRouteTableF66B2FAA", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryCSubnetId6A8F9E78": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBSubnetIdDF008255", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-C/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryCSubnet7C04BEAE", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryDRtRouteTableIdD54D03D4": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBRtRouteTableIdFA654D75", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/routeTable/Network-Secondary-D-Rt/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDRtRouteTable55050289", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryDSubnetIdEFF1A015": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBSubnetIdDF008255", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-D/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDSubnet339A8E74", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryDualStackSubnetId7D04B583": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Dual-Stack/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryDualStackSubnetACB095CF", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryIpv6OnlySubnetId8392EF4E": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryDualStackSubnetId7D04B583", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Ipv6-Only/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryIpv6OnlySubnet1C319120", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryOutposts1SubnetIdBFE852EB": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBSubnetIdDF008255", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-1/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryOutposts1Subnet92FEAC1C", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryNetworkSecondaryOutpostsASubnetId8238BEAB": { - "DependsOn": [ - "SsmParamNetworkSecondaryNetworkSecondaryBSubnetIdDF008255", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/subnet/Network-Secondary-Outposts-A/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcNetworkSecondaryOutpostsASubnet94859C6D", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryTestNaclNacl0FC3F602": { - "DependsOn": [ - "SsmParamNetworkInspectionDataSecurityGroup1FD45226", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/networkAcl/TestNacl/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpcTestNaclNacl63646F9E", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecondaryVpcIdB6CE5CFB": { - "Properties": { - "Name": "/accelerator/network/vpc/Network-Secondary/id", - "Type": "String", - "Value": { - "Ref": "NetworkSecondaryVpc37FB1791", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcStack-555555555555-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamWorkloadTemplateVpcId8722DBFE": { - "DependsOn": [ - "SsmParamNetworkEndpointsVpnGatewayId9424DAB4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Workload-Template/id", - "Type": "String", - "Value": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamWorkloadTemplateWorkloadARtRouteTableId8F1E5325": { - "DependsOn": [ - "SsmParamNetworkInspectionNetworkInspectionTgwBRouteTableId7113F1A4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-A-Rt/id", - "Type": "String", - "Value": { - "Ref": "WorkloadTemplateVpcWorkloadARtRouteTable42FD1E27", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamWorkloadTemplateWorkloadASubnetIdC0F8B4F7": { - "Properties": { - "Name": "/accelerator/network/vpc/Workload-Template/subnet/Workload-A/id", - "Type": "String", - "Value": { - "Ref": "WorkloadTemplateVpcWorkloadASubnetA3CC7B46", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamWorkloadTemplateWorkloadBRtRouteTableId3D4D6359": { - "DependsOn": [ - "SsmParamNetworkInspectionNetworkInspectionTgwBRouteTableId7113F1A4", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Workload-Template/routeTable/Workload-B-Rt/id", - "Type": "String", - "Value": { - "Ref": "WorkloadTemplateVpcWorkloadBRtRouteTable414704DF", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamWorkloadTemplateWorkloadBSubnetIdC690B217": { - "DependsOn": [ - "SsmParamWorkloadTemplateWorkloadASubnetIdC0F8B4F7", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Workload-Template/subnet/Workload-B/id", - "Type": "String", - "Value": { - "Ref": "WorkloadTemplateVpcWorkloadBSubnet1E445280", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "TestIpv6DhcpOpts2920A043": { - "Properties": { - "DomainName": "example.com", - "DomainNameServers": [ - "192.168.1.2", - "192.168.2.2", - "10.0.0.2", - "10.0.1.2", - "::1", - "::2", - "::3", - "::4", - ], - "NetbiosNameServers": [ - "192.168.1.2", - "192.168.2.2", - "10.0.0.2", - "10.0.1.2", - ], - "NtpServers": [ - "192.168.1.2", - "192.168.2.2", - "10.0.0.2", - "10.0.1.2", - "::1", - "::2", - "::3", - "::4", - ], - "Tags": [ - { - "Key": "Name", - "Value": "test-ipv6", - }, - ], - }, - "Type": "AWS::EC2::DHCPOptions", - }, - "VpcPeeringRoleCBAE4CA9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "VpcPeeringRole needs access to create routes for VPCs in the account", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::444444444444:root", - ], - ], - }, - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":route-table/*", - ], - ], - }, - }, - { - "Action": "ssm:GetParameter", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/network/*", - ], - ], - }, - }, - { - "Action": [ - "ec2:AcceptVpcPeeringConnection", - "ec2:CreateVpcPeeringConnection", - "ec2:DeleteVpcPeeringConnection", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":vpc/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":vpc-peering-connection/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-VpcPeeringRole-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "WorkloadTemplateVpc46A4D4C0": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "EnableDnsHostnames": true, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Ipv4IpamPoolId": { - "Ref": "SsmParameterValueacceleratornetworkipampoolshomeregionpoolidC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Ipv4NetmaskLength": 25, - "Tags": [ - { - "Key": "Name", - "Value": "Workload-Template", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "WorkloadTemplateVpcCloudWatchFlowLog39EE1B63": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::GetAtt": [ - "WorkloadTemplateVpcFlowLogsRole753D55F4", - "Arn", - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "WorkloadTemplateVpcFlowLogsGroup24286945", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Workload-Template", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "WorkloadTemplateVpcFlowLogsGroup24286945": { - "DeletionPolicy": "Retain", - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "RetentionInDays": 3653, - "Tags": [ - { - "Key": "Name", - "Value": "Workload-Template", - }, - ], - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "WorkloadTemplateVpcFlowLogsRole753D55F4": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "vpc-flow-logs.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Tags": [ - { - "Key": "Name", - "Value": "Workload-Template", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "WorkloadTemplateVpcFlowLogsRoleDefaultPolicyEE72B403": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogDelivery", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DeleteLogDelivery", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "WorkloadTemplateVpcFlowLogsGroup24286945", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "WorkloadTemplateVpcFlowLogsRoleDefaultPolicyEE72B403", - "Roles": [ - { - "Ref": "WorkloadTemplateVpcFlowLogsRole753D55F4", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "WorkloadTemplateVpcS3FlowLog07F0B649": { - "DependsOn": [ - "DeleteDefaultVpc4DBAE36C", - ], - "Properties": { - "LogDestination": { - "Fn::Join": [ - "", - [ - { - "Ref": "SsmParameterValueacceleratorvpcflowlogsdestinationbucketarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "/vpc-flow-logs/", - ], - ], - }, - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Workload-Template", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "WorkloadTemplateVpcWorkloadARtRouteTable42FD1E27": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Workload-A-Rt", - }, - ], - "VpcId": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "WorkloadTemplateVpcWorkloadASubnetA3CC7B46": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1a", - "basePool": [ - "10.0.0.0/16", - "10.2.0.0/16", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-pool", - "netmaskLength": 28, - }, - "name": "Workload-A", - "tags": [], - "vpcId": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "WorkloadTemplateVpcWorkloadASubnetRouteTableAssociation153D1D56": { - "Properties": { - "RouteTableId": { - "Ref": "WorkloadTemplateVpcWorkloadARtRouteTable42FD1E27", - }, - "SubnetId": { - "Ref": "WorkloadTemplateVpcWorkloadASubnetA3CC7B46", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "WorkloadTemplateVpcWorkloadBRtRouteTable414704DF": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Workload-B-Rt", - }, - ], - "VpcId": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "WorkloadTemplateVpcWorkloadBSubnet1E445280": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - "WorkloadTemplateVpcWorkloadASubnetA3CC7B46", - "WorkloadTemplateVpcWorkloadASubnetRouteTableAssociation153D1D56", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1b", - "basePool": [ - "10.0.0.0/16", - "10.2.0.0/16", - ], - "ipamAllocation": { - "ipamPoolName": "home-region-pool", - "netmaskLength": 28, - }, - "name": "Workload-B", - "tags": [], - "vpcId": { - "Ref": "WorkloadTemplateVpc46A4D4C0", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "WorkloadTemplateVpcWorkloadBSubnetRouteTableAssociation157AF42C": { - "DependsOn": [ - "WorkloadTemplateVpcWorkloadASubnetA3CC7B46", - "WorkloadTemplateVpcWorkloadASubnetRouteTableAssociation153D1D56", - ], - "Properties": { - "RouteTableId": { - "Ref": "WorkloadTemplateVpcWorkloadBRtRouteTable414704DF", - }, - "SubnetId": { - "Ref": "WorkloadTemplateVpcWorkloadBSubnet1E445280", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "appAalb01NetworkEndpointsD769A400": { - "DependsOn": [ - "NetworkEndpointsVpcManagementSgManagementEgress00D58058BF", - "NetworkEndpointsVpcManagementSgManagementIngress008FD972B5", - "NetworkEndpointsVpcManagementSgManagementIngress105119E927", - "NetworkEndpointsVpcManagementSgManagementIngress1135D6C2DD", - "NetworkEndpointsVpcManagementSg2B8F9418", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnetRouteTableAssociation4D925D52", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnetRouteTableAssociation958ED1AB", - ], - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "access_logs.s3.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.bucket", - "Value": "aws-accelerator-elb-access-logs-333333333333-us-east-1", - }, - { - "Key": "access_logs.s3.prefix", - "Value": "555555555555/us-east-1/appA-alb-01", - }, - ], - "Name": "appA-alb-01", - "Scheme": "internet-facing", - "SecurityGroups": [ - { - "Ref": "NetworkEndpointsVpcManagementSg2B8F9418", - }, - ], - "Subnets": [ - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - }, - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "appA-alb-01", - }, - ], - "Type": "application", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "appAalb01NetworkEndpointsssmF399A7CC": { - "DependsOn": [ - "SsmParamAcceleratorGwlbGwlbServiceIdF93A23BF", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/alb/appA-alb-01/id", - "Type": "String", - "Value": { - "Ref": "appAalb01NetworkEndpointsD769A400", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "appAnlb01NetworkEndpoints0281B174": { - "DependsOn": [ - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnetRouteTableAssociation4D925D52", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnetRouteTableAssociation958ED1AB", - ], - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "deletion_protection.enabled", - "Value": "false", - }, - { - "Key": "load_balancing.cross_zone.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.bucket", - "Value": "aws-accelerator-elb-access-logs-333333333333-us-east-1", - }, - { - "Key": "access_logs.s3.prefix", - "Value": "555555555555/us-east-1/appA-nlb-01", - }, - ], - "Name": "appA-nlb-01", - "Scheme": "internet-facing", - "Subnets": [ - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachASubnet3580E1C3", - }, - { - "Ref": "NetworkEndpointsVpcNetworkEndpointsTgwAttachBSubnet7F2CD5B5", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "appA-nlb-01", - }, - ], - "Type": "network", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "appAnlb01NetworkEndpointsssm0DA0AE1E": { - "DependsOn": [ - "SsmParamAcceleratorGwlbGwlbServiceIdF93A23BF", - ], - "Properties": { - "Name": "/accelerator/network/vpc/Network-Endpoints/nlb/appA-nlb-01/id", - "Type": "String", - "Value": { - "Ref": "appAnlb01NetworkEndpoints0281B174", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; - -exports[`NoVpcFlowLogStack Construct(NetworkVpcStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35": { - "DependsOn": [ - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultVpcCustomResourceProviderLogGroup4113DA48": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DeleteInternetGateway", - "ec2:DetachInternetGateway", - "ec2:DeleteNetworkAcl", - "ec2:DeleteRoute", - "ec2:DeleteSecurityGroup", - "ec2:DeleteSubnet", - "ec2:DeleteVpc", - "ec2:DescribeInternetGateways", - "ec2:DescribeNetworkAcls", - "ec2:DescribeRouteTables", - "ec2:DescribeSecurityGroups", - "ec2:DescribeSubnets", - "ec2:DescribeVpcs", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DeleteDefaultVpc4DBAE36C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultVpcCustomResourceProviderLogGroup4113DA48", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35", - "Arn", - ], - }, - }, - "Type": "Custom::DeleteDefaultVpc", - "UpdateReplacePolicy": "Delete", - }, - "EnableCentralEndpointsRoleB69A8E04": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "EnableCentralEndpointsRole needs access to every describe every VPC in the account ", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::555555555555:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeVpcs", - "route53:AssociateVPCWithHostedZone", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-EnableCentralEndpointsRole-us-east-1", - }, - "Type": "AWS::IAM::Role", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-NetworkVpcStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/operations-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/operations-stack.test.ts.snap deleted file mode 100644 index a59c024..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/operations-stack.test.ts.snap +++ /dev/null @@ -1,1748 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OperationsStack Construct(OperationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorassetskmskeyC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/assets/kms/key", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-CrossAccountSsmParameterShare", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorSsmInventoryGatherInventoryFA38251A": { - "Properties": { - "AssociationName": "aws-accelerator111111111111-InventoryCollection", - "Name": "AWS-GatherSoftwareInventory", - "ScheduleExpression": "rate(12 hours)", - "Targets": [ - { - "Key": "InstanceIds", - "Values": [ - "*", - ], - }, - ], - }, - "Type": "AWS::SSM::Association", - }, - "AcceleratorSsmInventoryResourceDataSyncA32D4B64": { - "Properties": { - "BucketName": "existing-central-log-bucket", - "BucketPrefix": "ssm-inventory", - "BucketRegion": "us-west-2", - "SyncFormat": "JsonSerDe", - "SyncName": "aws-accelerator111111111111-Inventory", - "SyncType": "SyncToDestination", - }, - "Type": "AWS::SSM::ResourceDataSync", - }, - "AdministratorsA37EF73A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Groups created as per accelerator iam-config needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "GroupName": "Administrators", - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Group", - }, - "AssetAccessRole1111111111114C0C1911": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role for lambda needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "AWS Accelerator assets access role in workload accounts deploy ACM imported certificates.", - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - "RoleName": "AWSAccelerator-AssetsAccessRole", - }, - "Type": "AWS::IAM::Role", - }, - "AssetAccessRole111111111111DefaultPolicy219CE4D7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policy permissions are part of managed role and rest is to get access from s3 bucket", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:ListBucket", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::aws-accelerator-assets-111111111111-us-east-1", - "arn:aws:s3:::aws-accelerator-assets-111111111111-us-east-1/*", - ], - }, - { - "Action": "acm:ImportCertificate", - "Effect": "Allow", - "Resource": "arn:aws:acm:*:111111111111:certificate/*", - }, - { - "Action": [ - "acm:RequestCertificate", - "acm:DeleteCertificate", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ssm:PutParameter", - "ssm:DeleteParameter", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "arn:aws:ssm:*:111111111111:parameter/*", - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorassetskmskeyC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AssetAccessRole111111111111DefaultPolicy219CE4D7", - "Roles": [ - { - "Ref": "AssetAccessRole1111111111114C0C1911", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BackupKey60B97760": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Backup Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "BackupKeyAliasA9FE2B6D": { - "Properties": { - "AliasName": "alias/accelerator/kms/backup/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "BackupKey60B97760", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "BackupRoleF43CFD90": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role created as per accelerator iam-config needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "backup.amazonaws.com", - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::123456789012:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores", - ], - ], - }, - ], - "RoleName": "Backup-Role", - }, - "Type": "AWS::IAM::Role", - }, - "BackupVaultBackupVaultACBCC720": { - "DeletionPolicy": "Retain", - "Properties": { - "BackupVaultName": "BackupVault", - "EncryptionKeyArn": { - "Fn::GetAtt": [ - "BackupKey60B97760", - "Arn", - ], - }, - }, - "Type": "AWS::Backup::BackupVault", - "UpdateReplacePolicy": "Retain", - }, - "BackupVaultInfrastructureVault94AE1FE1": { - "DeletionPolicy": "Retain", - "Properties": { - "AccessPolicy": { - "Statement": [ - { - "Action": "backup:DeleteRecoveryPoint", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false", - }, - }, - "Effect": "Deny", - "Principal": "*", - "Resource": "*", - "Sid": "DenyDeleteRecoveryPoint", - }, - ], - "Version": "2012-10-17", - }, - "BackupVaultName": "InfrastructureVault", - "EncryptionKeyArn": { - "Fn::GetAtt": [ - "BackupKey60B97760", - "Arn", - ], - }, - }, - "Type": "AWS::Backup::BackupVault", - "UpdateReplacePolicy": "Retain", - }, - "BreakGlassUser01AA051328": { - "Properties": { - "Groups": [ - { - "Ref": "AdministratorsA37EF73A", - }, - ], - "LoginProfile": { - "Password": { - "Fn::Join": [ - "", - [ - "{{resolve:secretsmanager:", - { - "Ref": "BreakGlassUser01Secret8A54324D", - }, - ":SecretString:password::}}", - ], - ], - }, - "PasswordResetRequired": true, - }, - "PermissionsBoundary": { - "Ref": "DefaultBoundaryPolicy489A8D26", - }, - "UserName": "breakGlassUser01", - }, - "Type": "AWS::IAM::User", - }, - "BreakGlassUser01Secret8A54324D": { - "DeletionPolicy": "Delete", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-SMG4", - "reason": "Accelerator users created as per iam-config file, MFA usage is enforced with boundary policy", - }, - ], - }, - }, - "Properties": { - "GenerateSecretString": { - "GenerateStringKey": "password", - "SecretStringTemplate": "{"username":"breakGlassUser01"}", - }, - "Name": "/accelerator/breakGlassUser01", - }, - "Type": "AWS::SecretsManager::Secret", - "UpdateReplacePolicy": "Delete", - }, - "BreakGlassUser02DFF444C8": { - "Properties": { - "Groups": [ - { - "Ref": "AdministratorsA37EF73A", - }, - ], - "LoginProfile": { - "Password": { - "Fn::Join": [ - "", - [ - "{{resolve:secretsmanager:", - { - "Ref": "BreakGlassUser02Secret4D200D8D", - }, - ":SecretString:password::}}", - ], - ], - }, - "PasswordResetRequired": true, - }, - "PermissionsBoundary": { - "Ref": "DefaultBoundaryPolicy489A8D26", - }, - "UserName": "breakGlassUser02", - }, - "Type": "AWS::IAM::User", - }, - "BreakGlassUser02Secret4D200D8D": { - "DeletionPolicy": "Delete", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-SMG4", - "reason": "Accelerator users created as per iam-config file, MFA usage is enforced with boundary policy", - }, - ], - }, - }, - "Properties": { - "GenerateSecretString": { - "GenerateStringKey": "password", - "SecretStringTemplate": "{"username":"breakGlassUser02"}", - }, - "Name": "/accelerator/breakGlassUser02", - }, - "Type": "AWS::SecretsManager::Secret", - "UpdateReplacePolicy": "Delete", - }, - "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF": { - "DependsOn": [ - "CustomServiceQuotaLimitsCustomResourceProviderRole024C3C88", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomServiceQuotaLimitsCustomResourceProviderRole024C3C88", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomServiceQuotaLimitsCustomResourceProviderLogGroupBC81BDCC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomServiceQuotaLimitsCustomResourceProviderRole024C3C88": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:ListAWSServiceAccessForOrganization", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrganizationListActions", - }, - { - "Action": [ - "autoscaling:DescribeAccountLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AutoScalingLimitsAction", - }, - { - "Action": [ - "dynamodb:DescribeLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DynamoDBLimitsAction", - }, - { - "Action": [ - "kinesis:DescribeLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "KinesisLimitsAction", - }, - { - "Action": [ - "iam:GetAccountSummary", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "IAMAccountSummaryAction", - }, - { - "Action": [ - "cloudformation:DescribeAccountLimits", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "CloudFormationAccountLimitsAction", - }, - { - "Action": [ - "cloudformation:DescribeAccountLimits", - "cloudwatch:DescribeAlarmsForMetric", - "cloudwatch:DescribeAlarms", - "cloudwatch:GetMetricData", - "cloudwatch:GetMetricStatistics", - "cloudwatch:PutMetricAlarm", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CloudWatchLimitsActions", - }, - { - "Action": [ - "elasticloadbalancing:DescribeAccountLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ElasticLoadBalancingLimitsAction", - }, - { - "Action": [ - "route53:GetAccountLimit", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Route53LimitsAction", - }, - { - "Action": [ - "rds:DescribeAccountAttributes", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "RDSLimitsAction", - }, - { - "Action": [ - "servicequotas:*", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceQuotaLimitsAction", - }, - { - "Action": [ - "tag:GetTagKeys", - "tag:GetTagValues", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "TaggingLimitsActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CreateServiceLinkedRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DefaultBoundaryPolicy489A8D26": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policies definition are derived from accelerator iam-config boundary-policy file", - }, - ], - }, - }, - "Properties": { - "Description": "", - "ManagedPolicyName": "Default-Boundary-Policy", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Effect": "Allow", - "Resource": "*", - }, - { - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false", - "aws:ViaAWSService": "false", - }, - }, - "Effect": "Deny", - "NotAction": [ - "iam:CreateVirtualMFADevice", - "iam:DeleteVirtualMFADevice", - "iam:ListVirtualMFADevices", - "iam:EnableMFADevice", - "iam:ResyncMFADevice", - "iam:ListAccountAliases", - "iam:ListUsers", - "iam:ListSSHPublicKeys", - "iam:ListAccessKeys", - "iam:ListServiceSpecificCredentials", - "iam:ListMFADevices", - "iam:GetAccountSummary", - "sts:GetSessionToken", - ], - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "Ec2DefaultSsmAdRoleADFFA4C6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role created as per accelerator iam-config needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ec2.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AmazonSSMManagedInstanceCore", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AmazonSSMDirectoryServiceAccess", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/CloudWatchAgentServerPolicy", - ], - ], - }, - ], - "PermissionsBoundary": { - "Ref": "DefaultBoundaryPolicy489A8D26", - }, - "RoleName": "EC2-Default-SSM-AD-Role", - }, - "Type": "AWS::IAM::Role", - }, - "Ec2DefaultSsmAdRoleInstanceProfile": { - "Properties": { - "InstanceProfileName": { - "Ref": "Ec2DefaultSsmAdRoleADFFA4C6", - }, - "Roles": [ - { - "Ref": "Ec2DefaultSsmAdRoleADFFA4C6", - }, - ], - }, - "Type": "AWS::IAM::InstanceProfile", - }, - "NetworkSecurityRoleC82A0DA7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role created as per accelerator iam-config needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "StringEquals": { - "sts:ExternalId": "111122223333", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::444455556666:role/test-access-role", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AmazonSSMManagedInstanceCore", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AmazonCognitoReadOnly", - ], - ], - }, - ], - "PermissionsBoundary": { - "Ref": "DefaultBoundaryPolicy489A8D26", - }, - "RoleName": "Network-Security-Role", - }, - "Type": "AWS::IAM::Role", - }, - "NetworkSecurityRoleInstanceProfile": { - "Properties": { - "InstanceProfileName": { - "Ref": "NetworkSecurityRoleC82A0DA7", - }, - "Roles": [ - { - "Ref": "NetworkSecurityRoleC82A0DA7", - }, - ], - }, - "Type": "AWS::IAM::InstanceProfile", - }, - "ServiceCatalogPropagationRole17EC5FCF": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policy must have access to all Service Catalog Portfolios and IAM Roles", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:GetGroup", - "iam:GetRole", - "iam:GetUser", - "iam:ListRoles", - "servicecatalog:AcceptPortfolioShare", - "servicecatalog:AssociatePrincipalWithPortfolio", - "servicecatalog:DisassociatePrincipalFromPortfolio", - "servicecatalog:ListAcceptedPortfolioShares", - "servicecatalog:ListPrincipalsForPortfolio", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-CrossAccount-ServiceCatalog-Role", - }, - "Type": "AWS::IAM::Role", - }, - "ServiceQuotaUpdatesL4019AD8B15624D57B3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomServiceQuotaLimitsCustomResourceProviderLogGroupBC81BDCC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF", - "Arn", - ], - }, - "desiredValue": 15, - "quotaCode": "L-4019AD8B", - "serviceCode": "iam", - }, - "Type": "Custom::ServiceQuotaLimits", - "UpdateReplacePolicy": "Delete", - }, - "ServiceQuotaUpdatesLB99A93841000457ABA73": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomServiceQuotaLimitsCustomResourceProviderLogGroupBC81BDCC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF", - "Arn", - ], - }, - "desiredValue": 1000, - "quotaCode": "L-B99A9384", - "serviceCode": "lambda", - }, - "Type": "Custom::ServiceQuotaLimits", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OperationsStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAdministratorsGroupArn1018D83B": { - "Properties": { - "Name": "/accelerator/iam/group/Administrators/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AdministratorsA37EF73A", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamBackupRoleRoleArn575A5734": { - "Properties": { - "Name": "/accelerator/iam/role/Backup-Role/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "BackupRoleF43CFD90", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamBreakGlassUser01UserArn935D8C01": { - "DependsOn": [ - "SsmParamAdministratorsGroupArn1018D83B", - ], - "Properties": { - "Name": "/accelerator/iam/user/breakGlassUser01/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "BreakGlassUser01AA051328", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamBreakGlassUser02UserArnB4B8C9E3": { - "DependsOn": [ - "SsmParamAdministratorsGroupArn1018D83B", - ], - "Properties": { - "Name": "/accelerator/iam/user/breakGlassUser02/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "BreakGlassUser02DFF444C8", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamDefaultBoundaryPolicyPolicyArnD6B0A5E8": { - "Properties": { - "Name": "/accelerator/iam/policy/Default-Boundary-Policy/arn", - "Type": "String", - "Value": { - "Ref": "DefaultBoundaryPolicy489A8D26", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamEc2DefaultSsmAdRoleRoleArnEA559EEC": { - "Properties": { - "Name": "/accelerator/iam/role/EC2-Default-SSM-AD-Role/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "Ec2DefaultSsmAdRoleADFFA4C6", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamNetworkSecurityRoleRoleArn068849A7": { - "Properties": { - "Name": "/accelerator/iam/role/Network-Security-Role/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "NetworkSecurityRoleC82A0DA7", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OperationsStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParameterParameterTest8FE0D600": { - "Properties": { - "Name": "/my/parameter/structure", - "Type": "String", - "Value": "parameterTestValue", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParameterSomeOtherName594B7F4C": { - "Properties": { - "Name": "/my/account/structure", - "Type": "String", - "Value": "someOtherValue", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmSessionManagerSettingsSessionManagerEC2InstanceProfile36B87210": { - "Properties": { - "InstanceProfileName": "AWSAccelerator-SessionManagerEc2Role", - "Roles": [ - { - "Ref": "SsmSessionManagerSettingsSessionManagerEC2Role83702F06", - }, - ], - }, - "Type": "AWS::IAM::InstanceProfile", - }, - "SsmSessionManagerSettingsSessionManagerEC2Policy8ED295CA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policy needed access to all S3 objects for the account to put objects into the access log bucket", - }, - ], - }, - }, - "Properties": { - "Description": "", - "ManagedPolicyName": "AWSAccelerator-SessionManagerLogging", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssmmessages:CreateControlChannel", - "ssmmessages:CreateDataChannel", - "ssmmessages:OpenControlChannel", - "ssmmessages:OpenDataChannel", - "ssm:UpdateInstanceInformation", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/session/111111111111/us-east-1/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/session/111111111111/us-west-2/*", - ], - ], - }, - ], - "Sid": "S3CentralLogs", - }, - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "Sid": "S3CentralLogsEncryptionConfig", - }, - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - "Sid": "S3CentralLogsEncryption", - }, - { - "Action": "kms:Decrypt", - "Condition": { - "ForAllValues:StringLike": { - "kms:ResourceAliases": [ - "alias/accelerator/sessionmanager-logs/session", - ], - }, - "Null": { - "kms:ResourceAliases": "false", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:us-east-1:111111111111:key/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "SsmSessionManagerSettingsSessionManagerEC2Role83702F06": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Create an IAM managed Policy for users to be able to use Session Manager with KMS encryption", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "IAM Role for an EC2 configured for Session Manager Logging", - "ManagedPolicyArns": [ - { - "Ref": "SsmSessionManagerSettingsSessionManagerEC2Policy8ED295CA", - }, - ], - "RoleName": "AWSAccelerator-SessionManagerEC2Role", - }, - "Type": "AWS::IAM::Role", - }, - "SsmSessionManagerSettingsSessionManagerPolicy2E1F5C10": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific log group", - }, - ], - }, - }, - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssmmessages:CreateControlChannel", - "ssmmessages:CreateDataChannel", - "ssmmessages:OpenControlChannel", - "ssmmessages:OpenDataChannel", - "ssm:UpdateInstanceInformation", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/session/111111111111/us-east-1/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/session/111111111111/us-west-2/*", - ], - ], - }, - ], - "Sid": "S3CentralLogs", - }, - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "Sid": "S3CentralLogsEncryptionConfig", - }, - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - "Sid": "S3CentralLogsEncryption", - }, - { - "Action": "kms:Decrypt", - "Condition": { - "ForAllValues:StringLike": { - "kms:ResourceAliases": [ - "alias/accelerator/sessionmanager-logs/session", - ], - }, - "Null": { - "kms:ResourceAliases": "false", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:us-east-1:111111111111:key/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "StackSetAdminRole10A324D2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policies definition are derived from accelerator iam-config boundary-policy file", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "cloudformation.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Assumes AWSCloudFormationStackSetExecutionRole in workload accounts to deploy StackSets", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": "arn:*:iam::*:role/AWSCloudFormationStackSetExecutionRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AssumeRole", - }, - ], - "RoleName": "AWSCloudFormationStackSetAdministrationRole", - }, - "Type": "AWS::IAM::Role", - }, - "StackSetExecutionRole02DBF677": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "IAM Role created as per accelerator iam-config needs AWS managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Used to deploy StackSets", - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - "RoleName": "AWSCloudFormationStackSetExecutionRole", - }, - "Type": "AWS::IAM::Role", - }, - "accelbudgetBudgetDefinitionaccelbudget73707CCE": { - "Properties": { - "Budget": { - "BudgetLimit": { - "Amount": 2000, - "Unit": "USD", - }, - "BudgetName": "accel-budget", - "BudgetType": "COST", - "CostTypes": { - "IncludeCredit": false, - "IncludeDiscount": true, - "IncludeOtherSubscription": true, - "IncludeRecurring": true, - "IncludeRefund": false, - "IncludeSubscription": true, - "IncludeSupport": true, - "IncludeTax": true, - "IncludeUpfront": true, - "UseAmortized": false, - "UseBlended": false, - }, - "TimeUnit": "MONTHLY", - }, - "NotificationsWithSubscribers": [ - { - "Notification": { - "ComparisonOperator": "GREATER_THAN", - "NotificationType": "ACTUAL", - "Threshold": 100, - "ThresholdType": "PERCENTAGE", - }, - "Subscribers": [ - { - "Address": "myemail+pa-budg@example.com", - "SubscriptionType": "EMAIL", - }, - ], - }, - { - "Notification": { - "ComparisonOperator": "GREATER_THAN", - "NotificationType": "ACTUAL", - "Threshold": 90, - "ThresholdType": "PERCENTAGE", - }, - "Subscribers": [ - { - "Address": "myemail+pa-budg@example.com", - "SubscriptionType": "EMAIL", - }, - ], - }, - { - "Notification": { - "ComparisonOperator": "GREATER_THAN", - "NotificationType": "ACTUAL", - "Threshold": 80, - "ThresholdType": "PERCENTAGE", - }, - "Subscribers": [ - { - "Address": "arn:aws:sns:us-east-1:111111111111:sample-budget", - "SubscriptionType": "SNS", - }, - ], - }, - { - "Notification": { - "ComparisonOperator": "GREATER_THAN", - "NotificationType": "ACTUAL", - "Threshold": 75, - "ThresholdType": "PERCENTAGE", - }, - "Subscribers": [ - { - "Address": "arn:aws:sns:us-east-1:111111111111:sample-budget", - "SubscriptionType": "SNS", - }, - ], - }, - { - "Notification": { - "ComparisonOperator": "GREATER_THAN", - "NotificationType": "ACTUAL", - "Threshold": 50, - "ThresholdType": "PERCENTAGE", - }, - "Subscribers": [ - { - "Address": "myemail+pa-budg@example.com", - "SubscriptionType": "EMAIL", - }, - { - "Address": "myemail+pa1-budg@example.com", - "SubscriptionType": "EMAIL", - }, - ], - }, - ], - }, - "Type": "AWS::Budgets::Budget", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/organizations-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/organizations-stack.test.ts.snap deleted file mode 100644 index 01098db..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/organizations-stack.test.ts.snap +++ /dev/null @@ -1,9930 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MultiOuOrganizationsStack Construct(OrganizationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorCentralLogBucketKeyLookup26F8C418": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-us-west-2-CentralBucket-KeyArnParam-Role", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AttachBackupPolicyInfrastructure61051A6A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "BackupPolicy8656A81D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "AcceleratorGuardrails2", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "BackupPolicy8656A81D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-22222222", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachBackupPolicySecurity64CA87BA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "BackupPolicy8656A81D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "BackupPolicy8656A81D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-11111111", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachTagPolicyInfrastructure2BD308FA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "TagPolicy00F603BF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "AcceleratorGuardrails2", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "TagPolicy00F603BF", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-22222222", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachTagPolicySecurity054477B7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "TagPolicy00F603BF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "TagPolicy00F603BF", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "ou-asdf-11111111", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerEnableOrganizationAdminAccount9070BCC2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "kmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "region": "us-east-1", - }, - "Type": "Custom::AuditManagerEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "BackupPolicy8656A81D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeBackupBC7A53AE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Backup Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "BackupPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9": { - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAccounts": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "auditmanager.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "auditmanager:RegisterAccount", - "auditmanager:DeregisterAccount", - "auditmanager:RegisterOrganizationAdminAccount", - "auditmanager:DeregisterOrganizationAdminAccount", - "auditmanager:getOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": "kms:CreateGrant", - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - "StringLike": { - "kms:ViaService": "auditmanager.*.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Sid": "AuditManagerEnableKmsKeyGrants", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1": { - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccounts", - "detective:DisableOrganizationAdminAccount", - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25": { - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableIpamOrganizationAdminAccount", - "ec2:EnableIpamOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:DisableAwsServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ServicePrincipal": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:AWSServiceName": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1": { - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:DisablePolicyType", - "organizations:EnablePolicyType", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribeEffectivePolicy", - "organizations:DescribePolicy", - "organizations:DisableAWSServiceAccess", - "organizations:DetachPolicy", - "organizations:DeletePolicy", - "organizations:DescribeAccount", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:EnableAWSServiceAccess", - "organizations:ListCreateAccountStatus", - "organizations:UpdatePolicy", - "organizations:DescribeOrganizationalUnit", - "organizations:AttachPolicy", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - "organizations:CreatePolicy", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398": { - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ram:EnableSharingWithAwsOrganization", - "iam:CreateServiceLinkedRole", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:DescribeOrganization", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026": { - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "guardduty.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "guardduty.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "GuardDuty:EnableOrganizationAdminAccount", - "GuardDuty:ListOrganizationAdminAccounts", - "guardduty:DisableOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A": { - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "macie.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "macie.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "macie.amazonaws.com", - ], - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "macie.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "macie.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "macie2:DisableOrganizationAdminAccount", - "macie2:EnableMacie", - "macie2:EnableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:ListOrganizationAdminAccounts", - "macie2:DisableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:EnableMacie", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskMacieActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:CreateServiceLinkedRole": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableMacieTaskIamAction", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202": { - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:ListPoliciesForTarget", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619": { - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Organizations create policy", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreatePolicy", - "organizations:DeletePolicy", - "organizations:DetachPolicy", - "organizations:ListPolicies", - "organizations:ListTargetsForPolicy", - "organizations:UpdatePolicy", - "organizations:TagResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-hnb659fds-assets-111111111111-us-east-1/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71": { - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C": { - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9": { - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": "organizations:EnableAWSServiceAccess", - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - ], - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::*:account/o-*/*", - ], - ], - }, - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskIamAction", - }, - { - "Action": [ - "securityhub:DisableOrganizationAdminAccount", - "securityhub:EnableOrganizationAdminAccount", - "securityhub:EnableSecurityHub", - "securityhub:ListOrganizationAdminAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DetectiveOrganizationAdminAccountD12FBDDC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "region": "us-east-1", - }, - "Type": "Custom::DetectiveEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "EnableAccessAnalyzerAFBAAEC3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "access-analyzer.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - "EnableOrganizationsServiceCatalog4D66D976": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "servicecatalog.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - "EnableSharingWithAwsOrganization81D5714F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - "Arn", - ], - }, - }, - "Type": "Custom::EnableSharingWithAwsOrganization", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "fms:AssociateAdminAccount", - "fms:DisassociateAdminAccount", - "fms:GetAdminAccount", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:DeregisterDelegatedAdministrator", - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListChildren", - "organizations:ListDelegatedAdministrators", - "organizations:ListDelegatedServicesForAccount", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:role/AWSControlTowerExecution", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FMSOrganizationAdminAccountfmsAdmin222222222222B4E8A399": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEvent17783F2B", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "assumeRoleName": "AWSControlTowerExecution", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountframeworkonEvent17783F2B": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/FMSOrganizationAdminAccount/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "Roles": [ - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "Roles": [ - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "Arn", - ], - }, - "roleName": "AWSServiceRoleForFMS", - "serviceName": "fms.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyEnableOrganizationAdminAccount90D7393E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "region": "us-east-1", - }, - "Type": "Custom::GuardDutyEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029": { - "DependsOn": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F", - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 160, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - "organizations:EnableAwsServiceAccess", - "organizations:DisableAWSServiceAccess", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "sso:ListInstances", - "sso:ListPermissionSets", - "sso:ListAccountAssignments", - "sso:DescribePermissionSet", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F", - "Roles": [ - { - "Ref": "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterAdminframeworkonEvent80E08E77": { - "DependsOn": [ - "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA", - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/IdentityCenterAdmin/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA", - "Roles": [ - { - "Ref": "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterAdminidentityCenterAdminEB714AB1": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterAdminframeworkonEvent80E08E77", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "lzaManagedAssignments": [], - "lzaManagedPermissionSets": [], - "partition": { - "Ref": "AWS::Partition", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "IpamAdminAccountB45C9E06": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - "Arn", - ], - }, - "accountId": "555555555555", - "region": "us-east-1", - }, - "Type": "Custom::EnableIpamOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "MacieOrganizationAdminAccount2C23317B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "region": "us-east-1", - }, - "Type": "Custom::MacieEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "RegisterDelegatedAdministratorAccessAnalyzerE0CB7BBC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766", - "EnableAccessAnalyzerAFBAAEC3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - "Arn", - ], - }, - "accountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "access-analyzer.amazonaws.com", - }, - "Type": "Custom::OrganizationsRegisterDelegatedAdministrator", - "UpdateReplacePolicy": "Delete", - }, - "ReportBucket93428221": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "aws-accelerator-cur-111111111111-us-east-1", - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "ReportBucketPolicyC1DDDC0D": { - "Properties": { - "Bucket": { - "Ref": "ReportBucket93428221", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetBucketAcl", - "s3:GetBucketPolicy", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "ReportBucketReportBucketReplication297F0904": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": "333333333333", - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - "destinationBucketKeyArn": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole125103F9", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "ReportBucket93428221", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole125103F9": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy390C828B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy390C828B", - "Roles": [ - { - "Ref": "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole125103F9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ReportDefinitionB2CAC9F7": { - "DependsOn": [ - "ReportBucketPolicyC1DDDC0D", - ], - "Properties": { - "Compression": "Parquet", - "Format": "Parquet", - "RefreshClosedReports": true, - "ReportName": "accelerator-cur", - "ReportVersioning": "CREATE_NEW_REPORT", - "S3Bucket": { - "Ref": "ReportBucket93428221", - }, - "S3Prefix": "cur/111111111111/", - "S3Region": "us-east-1", - "TimeUnit": "DAILY", - }, - "Type": "AWS::CUR::ReportDefinition", - }, - "SecurityHubOrganizationAdminAccount71D5E029": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::SecurityHubEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OrganizationsStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OrganizationsStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "TagPolicy00F603BF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeTagB9F93488", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Multi Target Tagging Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "TagPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "enablePolicyTypeBackupBC7A53AE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "BACKUP_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - "enablePolicyTypeTagB9F93488": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "TAG_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`OrganizationsStack Construct(OrganizationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-CrossAccountSsmParameterShare", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AttachBackupPolicy01RootC121ABD5": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "BackupPolicy01D701AF7D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "BackupPolicy01D701AF7D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "r-asdf", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachBackupPolicyRootB4A999CE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "BackupPolicy8656A81D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "BackupPolicy8656A81D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "r-asdf", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachTagPolicy01RootD3F4B8AF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "TagPolicy01236267C8", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "TagPolicy01236267C8", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "r-asdf", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachTagPolicyRoot6C946AE8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "TagPolicy00F603BF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "TagPolicy00F603BF", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "r-asdf", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerEnableOrganizationAdminAccount9070BCC2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "kmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "region": "us-east-1", - }, - "Type": "Custom::AuditManagerEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "AwsGrEbsOptimizedInstanceSecureWorkloads": { - "Properties": { - "ControlIdentifier": "arn:aws:controltower:us-east-1::control/AWS-GR_EBS_OPTIMIZED_INSTANCE", - "TargetIdentifier": "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-33333333", - }, - "Type": "AWS::ControlTower::EnabledControl", - }, - "AwsGrRestrictRootUserAccessKeysSecureWorkloads": { - "Properties": { - "ControlIdentifier": "arn:aws:controltower:us-east-1::control/AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS", - "TargetIdentifier": "arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-33333333", - }, - "Type": "AWS::ControlTower::EnabledControl", - }, - "BackupPolicy01D701AF7D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeBackupBC7A53AE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Backup Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "BackupPolicy01", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "BackupPolicy8656A81D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeBackupBC7A53AE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Backup Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "BackupPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9": { - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAccounts": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "auditmanager.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "auditmanager:RegisterAccount", - "auditmanager:DeregisterAccount", - "auditmanager:RegisterOrganizationAdminAccount", - "auditmanager:DeregisterOrganizationAdminAccount", - "auditmanager:getOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": "kms:CreateGrant", - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - "StringLike": { - "kms:ViaService": "auditmanager.*.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Sid": "AuditManagerEnableKmsKeyGrants", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1": { - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccounts", - "detective:DisableOrganizationAdminAccount", - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25": { - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableIpamOrganizationAdminAccount", - "ec2:EnableIpamOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:DisableAwsServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ServicePrincipal": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:AWSServiceName": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1": { - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:DisablePolicyType", - "organizations:EnablePolicyType", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribeEffectivePolicy", - "organizations:DescribePolicy", - "organizations:DisableAWSServiceAccess", - "organizations:DetachPolicy", - "organizations:DeletePolicy", - "organizations:DescribeAccount", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:EnableAWSServiceAccess", - "organizations:ListCreateAccountStatus", - "organizations:UpdatePolicy", - "organizations:DescribeOrganizationalUnit", - "organizations:AttachPolicy", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - "organizations:CreatePolicy", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398": { - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ram:EnableSharingWithAwsOrganization", - "iam:CreateServiceLinkedRole", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:DescribeOrganization", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026": { - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "guardduty.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "guardduty.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "GuardDuty:EnableOrganizationAdminAccount", - "GuardDuty:ListOrganizationAdminAccounts", - "guardduty:DisableOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A": { - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "macie.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "macie.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "macie.amazonaws.com", - ], - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "macie.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "macie.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "macie2:DisableOrganizationAdminAccount", - "macie2:EnableMacie", - "macie2:EnableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:ListOrganizationAdminAccounts", - "macie2:DisableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:EnableMacie", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskMacieActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:CreateServiceLinkedRole": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableMacieTaskIamAction", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202": { - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:ListPoliciesForTarget", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619": { - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Organizations create policy", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreatePolicy", - "organizations:DeletePolicy", - "organizations:DetachPolicy", - "organizations:ListPolicies", - "organizations:ListTargetsForPolicy", - "organizations:UpdatePolicy", - "organizations:TagResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-hnb659fds-assets-111111111111-us-east-1/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71": { - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C": { - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9": { - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": "organizations:EnableAWSServiceAccess", - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - ], - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::*:account/o-*/*", - ], - ], - }, - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskIamAction", - }, - { - "Action": [ - "securityhub:DisableOrganizationAdminAccount", - "securityhub:EnableOrganizationAdminAccount", - "securityhub:EnableSecurityHub", - "securityhub:ListOrganizationAdminAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DetectiveOrganizationAdminAccountD12FBDDC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "region": "us-east-1", - }, - "Type": "Custom::DetectiveEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "EnableAccessAnalyzerAFBAAEC3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "access-analyzer.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - "EnableOrganizationsServiceCatalog4D66D976": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "servicecatalog.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - "EnableSharingWithAwsOrganization81D5714F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - "Arn", - ], - }, - }, - "Type": "Custom::EnableSharingWithAwsOrganization", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "fms:AssociateAdminAccount", - "fms:DisassociateAdminAccount", - "fms:GetAdminAccount", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:DeregisterDelegatedAdministrator", - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListChildren", - "organizations:ListDelegatedAdministrators", - "organizations:ListDelegatedServicesForAccount", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:role/AWSControlTowerExecution", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FMSOrganizationAdminAccountfmsAdmin222222222222B4E8A399": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEvent17783F2B", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "assumeRoleName": "AWSControlTowerExecution", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountframeworkonEvent17783F2B": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/FMSOrganizationAdminAccount/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "Roles": [ - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "Roles": [ - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "Arn", - ], - }, - "roleName": "AWSServiceRoleForFMS", - "serviceName": "fms.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyEnableOrganizationAdminAccount90D7393E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "region": "us-east-1", - }, - "Type": "Custom::GuardDutyEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029": { - "DependsOn": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F", - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 160, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - "organizations:EnableAwsServiceAccess", - "organizations:DisableAWSServiceAccess", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "sso:ListInstances", - "sso:ListPermissionSets", - "sso:ListAccountAssignments", - "sso:DescribePermissionSet", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F", - "Roles": [ - { - "Ref": "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterAdminframeworkonEvent80E08E77": { - "DependsOn": [ - "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA", - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/IdentityCenterAdmin/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA", - "Roles": [ - { - "Ref": "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterAdminidentityCenterAdminEB714AB1": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterAdminframeworkonEvent80E08E77", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "lzaManagedAssignments": [ - { - "PermissionSet1": [ - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - }, - { - "PermissionSet1": [ - "333333333333", - "222222222222", - ], - }, - ], - "lzaManagedPermissionSets": [ - { - "name": "PermissionSet1", - "policies": { - "acceleratorManaged": [ - "lzaManagedPolicy01", - "lzaManagedPolicy02", - ], - "awsManaged": [ - "arn:aws:iam::aws:policy/AdministratorAccess", - "arn:aws:iam::aws:policy/CloudFrontFullAccess", - "PowerUserAccess", - "ReadOnlyAccess", - ], - "customerManaged": [ - "ResourceConfigurationCollectorPolicy", - ], - "inlinePolicy": "REPLACED-JSON-PATH.json", - "permissionsBoundary": { - "awsManagedPolicyName": "PowerUserAccess", - }, - }, - }, - { - "name": "PermissionSet2", - "policies": { - "acceleratorManaged": [ - "lzaManagedPolicy01", - ], - "awsManaged": [ - "PowerUserAccess", - "ReadOnlyAccess", - ], - "customerManaged": [ - "ResourceConfigurationCollectorPolicy", - ], - "inlinePolicy": "REPLACED-JSON-PATH.json", - "permissionsBoundary": { - "customerManagedPolicy": { - "name": "lzaManagedPolicy01", - "path": "/", - }, - }, - }, - "sessionDuration": 60, - }, - ], - "partition": { - "Ref": "AWS::Partition", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "IpamAdminAccountB45C9E06": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - "Arn", - ], - }, - "accountId": "555555555555", - "region": "us-east-1", - }, - "Type": "Custom::EnableIpamOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "MacieOrganizationAdminAccount2C23317B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "region": "us-east-1", - }, - "Type": "Custom::MacieEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "RegisterDelegatedAdministratorAccessAnalyzerE0CB7BBC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766", - "EnableAccessAnalyzerAFBAAEC3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - "Arn", - ], - }, - "accountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "access-analyzer.amazonaws.com", - }, - "Type": "Custom::OrganizationsRegisterDelegatedAdministrator", - "UpdateReplacePolicy": "Delete", - }, - "ReportBucket93428221": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "aws-accelerator-cur-111111111111-us-east-1", - "LoggingConfiguration": { - "DestinationBucketName": "existing-access-logs-bucket-111111111111-us-east-1", - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "ReportBucketPolicyC1DDDC0D": { - "Properties": { - "Bucket": { - "Ref": "ReportBucket93428221", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetBucketAcl", - "s3:GetBucketPolicy", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "ReportBucketReportBucketReplication297F0904": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": "333333333333", - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "ReportBucketReportBucketReplicationExistingCentralLogBucketReplicationRole5145BB67", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "ReportBucket93428221", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "ReportBucketReportBucketReplicationExistingCentralLogBucketReplicationRole5145BB67": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "ReportBucketReportBucketReplicationExistingCentralLogBucketReplicationRoleDefaultPolicy71E2DE5E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ReportBucketReportBucketReplicationExistingCentralLogBucketReplicationRoleDefaultPolicy71E2DE5E", - "Roles": [ - { - "Ref": "ReportBucketReportBucketReplicationExistingCentralLogBucketReplicationRole5145BB67", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ReportDefinitionB2CAC9F7": { - "DependsOn": [ - "ReportBucketPolicyC1DDDC0D", - ], - "Properties": { - "AdditionalArtifacts": [ - "ATHENA", - "QUICKSIGHT", - ], - "Compression": "Parquet", - "Format": "Parquet", - "RefreshClosedReports": true, - "ReportName": "accelerator-cur", - "ReportVersioning": "CREATE_NEW_REPORT", - "S3Bucket": { - "Ref": "ReportBucket93428221", - }, - "S3Prefix": "cur/111111111111/", - "S3Region": "us-east-1", - "TimeUnit": "DAILY", - }, - "Type": "AWS::CUR::ReportDefinition", - }, - "SecurityHubOrganizationAdminAccount71D5E029": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::SecurityHubEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OrganizationsStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OrganizationsStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "TagPolicy00F603BF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeTagB9F93488", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Tagging Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "TagPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "TagPolicy01236267C8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeTagB9F93488", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Tagging Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "TagPolicy01", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "enablePolicyTypeBackupBC7A53AE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "BACKUP_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - "enablePolicyTypeTagB9F93488": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "TAG_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`delegatedAdminStack Construct(OrganizationsStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorCentralLogBucketKeyLookup26F8C418": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-us-west-2-CentralBucket-KeyArnParam-Role", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AttachBackupPolicyRootB4A999CE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "BackupPolicy8656A81D", - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "BackupPolicy8656A81D", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "r-asdf", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AttachTagPolicyRoot6C946AE8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - "TagPolicy00F603BF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": { - "Ref": "TagPolicy00F603BF", - }, - "policyTagKey": "AWSAcceleratorManaged", - "targetId": "r-asdf", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerEnableOrganizationAdminAccount9070BCC2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - "Arn", - ], - }, - "adminAccountId": "666666666666", - "kmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "region": "us-east-1", - }, - "Type": "Custom::AuditManagerEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "BackupPolicy8656A81D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeBackupBC7A53AE", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Backup Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "BackupPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "BACKUP_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9": { - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAccounts": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "auditmanager.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "auditmanager:RegisterAccount", - "auditmanager:DeregisterAccount", - "auditmanager:RegisterOrganizationAdminAccount", - "auditmanager:DeregisterOrganizationAdminAccount", - "auditmanager:getOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": "kms:CreateGrant", - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - "StringLike": { - "kms:ViaService": "auditmanager.*.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Sid": "AuditManagerEnableKmsKeyGrants", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1": { - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccounts", - "detective:DisableOrganizationAdminAccount", - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25": { - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableIpamOrganizationAdminAccount", - "ec2:EnableIpamOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:DisableAwsServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ServicePrincipal": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:AWSServiceName": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1": { - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:DisablePolicyType", - "organizations:EnablePolicyType", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribeEffectivePolicy", - "organizations:DescribePolicy", - "organizations:DisableAWSServiceAccess", - "organizations:DetachPolicy", - "organizations:DeletePolicy", - "organizations:DescribeAccount", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:EnableAWSServiceAccess", - "organizations:ListCreateAccountStatus", - "organizations:UpdatePolicy", - "organizations:DescribeOrganizationalUnit", - "organizations:AttachPolicy", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - "organizations:CreatePolicy", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398": { - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ram:EnableSharingWithAwsOrganization", - "iam:CreateServiceLinkedRole", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:DescribeOrganization", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026": { - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "guardduty.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "guardduty.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "GuardDuty:EnableOrganizationAdminAccount", - "GuardDuty:ListOrganizationAdminAccounts", - "guardduty:DisableOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A": { - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "macie.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "macie.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "macie.amazonaws.com", - ], - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "macie.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "macie.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "macie2:DisableOrganizationAdminAccount", - "macie2:EnableMacie", - "macie2:EnableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:ListOrganizationAdminAccounts", - "macie2:DisableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:EnableMacie", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskMacieActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:CreateServiceLinkedRole": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableMacieTaskIamAction", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202": { - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:ListPoliciesForTarget", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619": { - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Organizations create policy", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreatePolicy", - "organizations:DeletePolicy", - "organizations:DetachPolicy", - "organizations:ListPolicies", - "organizations:ListTargetsForPolicy", - "organizations:UpdatePolicy", - "organizations:TagResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-hnb659fds-assets-111111111111-us-east-1/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71": { - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C": { - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9": { - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": "organizations:EnableAWSServiceAccess", - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - ], - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::*:account/o-*/*", - ], - ], - }, - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskIamAction", - }, - { - "Action": [ - "securityhub:DisableOrganizationAdminAccount", - "securityhub:EnableOrganizationAdminAccount", - "securityhub:EnableSecurityHub", - "securityhub:ListOrganizationAdminAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DetectiveOrganizationAdminAccountD12FBDDC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - "Arn", - ], - }, - "adminAccountId": "666666666666", - "region": "us-east-1", - }, - "Type": "Custom::DetectiveEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "EnableAccessAnalyzerAFBAAEC3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "access-analyzer.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - "EnableOrganizationsServiceCatalog4D66D976": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "servicecatalog.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - "EnableSharingWithAwsOrganization81D5714F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - "Arn", - ], - }, - }, - "Type": "Custom::EnableSharingWithAwsOrganization", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "fms:AssociateAdminAccount", - "fms:DisassociateAdminAccount", - "fms:GetAdminAccount", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:DeregisterDelegatedAdministrator", - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListChildren", - "organizations:ListDelegatedAdministrators", - "organizations:ListDelegatedServicesForAccount", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:role/AWSControlTowerExecution", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FMSOrganizationAdminAccountfmsAdmin222222222222B4E8A399": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEvent17783F2B", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "assumeRoleName": "AWSControlTowerExecution", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountframeworkonEvent17783F2B": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/FMSOrganizationAdminAccount/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicyBF2D7C58", - "Roles": [ - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole4B6F4F45", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B": { - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/FirewallManagerServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Custom resource Lambda role policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionA541DABF", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy65EE4B90", - "Roles": [ - { - "Ref": "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole41A12BB9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleResourceE252DEA3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup162E628B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FirewallManagerServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventEE1DB34B", - "Arn", - ], - }, - "roleName": "AWSServiceRoleForFMS", - "serviceName": "fms.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyEnableOrganizationAdminAccount90D7393E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - "Arn", - ], - }, - "adminAccountId": "666666666666", - "region": "us-east-1", - }, - "Type": "Custom::GuardDutyEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029": { - "DependsOn": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F", - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 160, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - "organizations:EnableAwsServiceAccess", - "organizations:DisableAWSServiceAccess", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "sso:ListInstances", - "sso:ListPermissionSets", - "sso:ListAccountAssignments", - "sso:DescribePermissionSet", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRoleDefaultPolicyBBBDA17F", - "Roles": [ - { - "Ref": "IdentityCenterAdminIdentityCenterAdminProviderLambdaServiceRole6939696D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterAdminframeworkonEvent80E08E77": { - "DependsOn": [ - "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA", - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-OrganizationsStack-111111111111-us-east-1/IdentityCenterAdmin/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterAdminIdentityCenterAdminProviderLambdaE457A029", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterAdminframeworkonEventServiceRoleDefaultPolicy0A2A63AA", - "Roles": [ - { - "Ref": "IdentityCenterAdminframeworkonEventServiceRole2ED5ECC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterAdminidentityCenterAdminEB714AB1": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterAdminframeworkonEvent80E08E77", - "Arn", - ], - }, - "adminAccountId": "666666666666", - "lzaManagedAssignments": [ - { - "PermissionSet1": [ - "333333333333", - ], - }, - { - "PermissionSet1": [ - "333333333333", - "222222222222", - "666666666666", - ], - }, - ], - "lzaManagedPermissionSets": [ - { - "name": "PermissionSet1", - "policies": { - "awsManaged": [ - "arn:aws:iam::aws:policy/AdministratorAccess", - ], - "customerManaged": [ - "ResourceConfigurationCollectorPolicy", - ], - }, - "sessionDuration": 60, - }, - ], - "partition": { - "Ref": "AWS::Partition", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "IpamAdminAccountB45C9E06": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - "Arn", - ], - }, - "accountId": "555555555555", - "region": "us-east-1", - }, - "Type": "Custom::EnableIpamOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "MacieOrganizationAdminAccount2C23317B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - "Arn", - ], - }, - "adminAccountId": "666666666666", - "region": "us-east-1", - }, - "Type": "Custom::MacieEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "RegisterDelegatedAdministratorAccessAnalyzerE0CB7BBC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766", - "EnableAccessAnalyzerAFBAAEC3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - "Arn", - ], - }, - "accountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "access-analyzer.amazonaws.com", - }, - "Type": "Custom::OrganizationsRegisterDelegatedAdministrator", - "UpdateReplacePolicy": "Delete", - }, - "ReportBucket93428221": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "aws-accelerator-cur-111111111111-us-east-1", - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "ReportBucketPolicyC1DDDC0D": { - "Properties": { - "Bucket": { - "Ref": "ReportBucket93428221", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetBucketAcl", - "s3:GetBucketPolicy", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "ReportBucketReportBucketReplication297F0904": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": "333333333333", - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - "destinationBucketKeyArn": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole125103F9", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "ReportBucket93428221", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole125103F9": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy390C828B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ReportBucket93428221", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy390C828B", - "Roles": [ - { - "Ref": "ReportBucketReportBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRole125103F9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ReportDefinitionB2CAC9F7": { - "DependsOn": [ - "ReportBucketPolicyC1DDDC0D", - ], - "Properties": { - "Compression": "Parquet", - "Format": "Parquet", - "RefreshClosedReports": true, - "ReportName": "accelerator-cur", - "ReportVersioning": "CREATE_NEW_REPORT", - "S3Bucket": { - "Ref": "ReportBucket93428221", - }, - "S3Prefix": "cur/111111111111/", - "S3Region": "us-east-1", - "TimeUnit": "DAILY", - }, - "Type": "AWS::CUR::ReportDefinition", - }, - "SecurityHubOrganizationAdminAccount71D5E029": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - "Arn", - ], - }, - "adminAccountId": "666666666666", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::SecurityHubEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OrganizationsStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-OrganizationsStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "TagPolicy00F603BF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - "enablePolicyTypeTagB9F93488", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "description": "Organization Tagging Policy", - "key": "REPLACED-JSON-PATH.json", - "name": "TagPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "type": "TAG_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - "enablePolicyTypeBackupBC7A53AE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "BACKUP_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - "enablePolicyTypeTagB9F93488": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "TAG_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/pipeline-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/pipeline-stack.test.ts.snap deleted file mode 100644 index 329971e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/pipeline-stack.test.ts.snap +++ /dev/null @@ -1,2598 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PipelineStack Construct(PipelineStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorawsacceleratorinstalleraccesslogsbucketnameC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/aws-accelerator/installer-access-logs-bucket-name", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/aws-accelerator/installer/kms/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AdminCdkToolkitRole292E163A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - "MaxSessionDuration": 14400, - }, - "Type": "AWS::IAM::Role", - }, - "AdminCdkToolkitRoleDefaultPolicy2A6A4DB7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/", - { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/", - { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:us-east-1:000000000000:report-group/", - { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AdminCdkToolkitRoleDefaultPolicy2A6A4DB7", - "Roles": [ - { - "Ref": "AdminCdkToolkitRole292E163A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "Pipeline8E4BFAC9": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - "PipelinePipelineRoleDefaultPolicy7D262A22", - "PipelinePipelineRole6D983AD5", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Type": "KMS", - }, - "Location": { - "Ref": "PipelineSecureBucketB3EEB324", - }, - "Type": "S3", - }, - "Name": "aws-accelerator-pipeline", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": "main", - "PollForSourceChanges": false, - "RepositoryName": "accelerator-source", - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "PipelineSourceCodePipelineActionRoleBBC58FD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": "main", - "PollForSourceChanges": false, - "RepositoryName": { - "Fn::GetAtt": [ - "PipelineConfigRepositoryE5225086", - "Name", - ], - }, - }, - "Name": "Configuration", - "Namespace": "Config-Vars", - "OutputArtifacts": [ - { - "Name": "Config", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "PipelineSourceConfigurationCodePipelineActionRoleA2807B19", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "PrimarySource": "Source", - "ProjectName": { - "Ref": "PipelineBuildProject9D447FA8", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - { - "Name": "Config", - }, - ], - "Name": "Build", - "OutputArtifacts": [ - { - "Name": "Build", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Build", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage prepare"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"prepare"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Prepare", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Prepare", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage accounts"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"accounts"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Accounts", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Accounts", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"bootstrap"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Bootstrap", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Bootstrap", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"diff"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Diff", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Approval", - "Owner": "AWS", - "Provider": "Manual", - "Version": "1", - }, - "Configuration": { - "CustomData": "See previous stage (Diff) for changes.", - "NotificationArn": { - "Ref": "PipelineManualApprovalActionTopicB55612D2", - }, - }, - "Name": "Approve", - "RoleArn": { - "Fn::GetAtt": [ - "PipelineReviewApproveCodePipelineActionRole3122ED42", - "Arn", - ], - }, - "RunOrder": 2, - }, - ], - "Name": "Review", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage key"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"key"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Key", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage logging"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"logging"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Logging", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 2, - }, - ], - "Name": "Logging", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage organizations"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"organizations"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Organizations", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Organization", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage security-audit"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"security-audit"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "SecurityAudit", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "SecurityAudit", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage network-prep"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"network-prep"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Network_Prepare", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage security"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"security"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Security", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage operations"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"operations"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Operations", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage network-vpc"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"network-vpc"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Network_VPCs", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 2, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage security-resources"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"security-resources"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Security_Resources", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 2, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage identity-center"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"identity-center"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Identity_Center", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 2, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage network-associations"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"network-associations"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Network_Associations", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 3, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage customizations"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"customizations"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Customizations", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 4, - }, - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "EnvironmentVariables": "[{"name":"CDK_OPTIONS","type":"PLAINTEXT","value":"deploy --stage finalize"},{"name":"CONFIG_COMMIT_ID","type":"PLAINTEXT","value":"#{Config-Vars.CommitId}"},{"name":"ACCELERATOR_STAGE","type":"PLAINTEXT","value":"finalize"}]", - "PrimarySource": "Build", - "ProjectName": { - "Ref": "PipelineToolkitProjectBCBD6910", - }, - }, - "InputArtifacts": [ - { - "Name": "Build", - }, - { - "Name": "Config", - }, - ], - "Name": "Finalize", - "RoleArn": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - "RunOrder": 5, - }, - ], - "Name": "Deploy", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-000000000000-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CodeStar Notification SLR needs managed policies.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "Roles": [ - { - "Ref": "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-000000000000-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (PipelineStack/Pipeline/AWSServiceRoleForCodeStarNotifications/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CodeStar Notification SLR needs managed policies.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "Roles": [ - { - "Ref": "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "Arn", - ], - }, - "description": "Allows AWS CodeStar Notifications to access Amazon CloudWatch Events on your behalf", - "roleName": "AWSServiceRoleForCodeStarNotifications", - "serviceName": "codestar-notifications.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "PipelineAcceleratorFailedStatusTopic614002B3": { - "Properties": { - "DisplayName": "aws-accelerator-pipeline-failed-status-topic", - "KmsMasterKeyId": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TopicName": "aws-accelerator-pipeline-failed-status-topic", - }, - "Type": "AWS::SNS::Topic", - }, - "PipelineAcceleratorFailedStatusTopicPolicy903B57F7": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": { - "Ref": "PipelineAcceleratorFailedStatusTopic614002B3", - }, - "Sid": "0", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "PipelineAcceleratorFailedStatusTopic614002B3", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "PipelineAcceleratorPipelineFailureAlarm479BDCEA": { - "Properties": { - "AlarmDescription": "AWS Accelerator pipeline failure alarm, created by accelerator", - "AlarmName": "aws-accelerator-pipeline-failed-alarm", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "DatapointsToAlarm": 1, - "Dimensions": [ - { - "Name": "TopicName", - "Value": { - "Fn::GetAtt": [ - "PipelineAcceleratorFailedStatusTopic614002B3", - "TopicName", - ], - }, - }, - ], - "EvaluationPeriods": 1, - "MetricName": "NumberOfMessagesPublished", - "Namespace": "AWS/SNS", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "PipelineAcceleratorPipelineFailureNotification5BE0EB21": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Properties": { - "DetailType": "FULL", - "EventTypeIds": [ - "codepipeline-pipeline-pipeline-execution-failed", - ], - "Name": "elineStackPipelineAcceleratorPipelineFailureNotificationDB4C2A96", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:us-east-1:000000000000:", - { - "Ref": "Pipeline8E4BFAC9", - }, - ], - ], - }, - "Targets": [ - { - "TargetAddress": { - "Ref": "PipelineAcceleratorFailedStatusTopic614002B3", - }, - "TargetType": "SNS", - }, - ], - }, - "Type": "AWS::CodeStarNotifications::NotificationRule", - }, - "PipelineAcceleratorPipelineStatusNotification0FCF4387": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Properties": { - "DetailType": "FULL", - "EventTypeIds": [ - "codepipeline-pipeline-manual-approval-failed", - "codepipeline-pipeline-manual-approval-needed", - "codepipeline-pipeline-manual-approval-succeeded", - "codepipeline-pipeline-pipeline-execution-canceled", - "codepipeline-pipeline-pipeline-execution-failed", - "codepipeline-pipeline-pipeline-execution-resumed", - "codepipeline-pipeline-pipeline-execution-started", - "codepipeline-pipeline-pipeline-execution-succeeded", - "codepipeline-pipeline-pipeline-execution-superseded", - ], - "Name": "pelineStackPipelineAcceleratorPipelineStatusNotification753A71B4", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:us-east-1:000000000000:", - { - "Ref": "Pipeline8E4BFAC9", - }, - ], - ], - }, - "Targets": [ - { - "TargetAddress": { - "Ref": "PipelineAcceleratorStatusTopic2BD5793F", - }, - "TargetType": "SNS", - }, - ], - }, - "Type": "AWS::CodeStarNotifications::NotificationRule", - }, - "PipelineAcceleratorStatusTopic2BD5793F": { - "Properties": { - "DisplayName": "aws-accelerator-pipeline-status-topic", - "KmsMasterKeyId": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TopicName": "aws-accelerator-pipeline-status-topic", - }, - "Type": "AWS::SNS::Topic", - }, - "PipelineAcceleratorStatusTopicPolicy30BCED97": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": { - "Ref": "PipelineAcceleratorStatusTopic2BD5793F", - }, - "Sid": "0", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "PipelineAcceleratorStatusTopic2BD5793F", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "PipelineBuildProject9D447FA8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Modes": [ - "LOCAL_SOURCE_CACHE", - ], - "Type": "LOCAL", - }, - "EncryptionKey": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=8192", - }, - { - "Name": "PARTITION", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::Partition", - }, - }, - { - "Name": "ACCELERATOR_PIPELINE_VERSION", - "Type": "PARAMETER_STORE", - "Value": "/accelerator/PipelineStack/version", - }, - { - "Name": "ACCELERATOR_CHECK_VERSION", - "Type": "PLAINTEXT", - "Value": "yes", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": "aws-accelerator-build-project", - "ServiceRole": { - "Fn::GetAtt": [ - "PipelineBuildRoleDC686070", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "REPLACED-JSON-PATH.json", - { - "Ref": "AWS::Partition", - }, - "\\" = \\"aws-cn\\" ]; then\\n sed -i \\"s#registry.yarnpkg.com#registry.npmmirror.com#g\\" yarn.lock;\\n yarn config set registry https://registry.npmmirror.com\\n fi", - "yarn install", - "yarn build", - "yarn validate-config $CODEBUILD_SRC_DIR_Config" - ] - } - }, - "artifacts": { - "files": [ - "**/*" - ], - "enable-symlinks": "yes" - } -}", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "PipelineBuildRoleDC686070": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for External Pipeline Deployment Lookups attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Ref": "PipelineValidateConfigPolicyDocument5A45073D", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "PipelineBuildRoleDefaultPolicy3DAB973E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ssm:GetParameters", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:000000000000:parameter/accelerator/PipelineStack/version", - ], - ], - }, - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/", - { - "Ref": "PipelineBuildProject9D447FA8", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:000000000000:log-group:/aws/codebuild/", - { - "Ref": "PipelineBuildProject9D447FA8", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:us-east-1:000000000000:report-group/", - { - "Ref": "PipelineBuildProject9D447FA8", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineBuildRoleDefaultPolicy3DAB973E", - "Roles": [ - { - "Ref": "PipelineBuildRoleDC686070", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineConfigRepositoryE5225086": { - "DeletionPolicy": "Retain", - "Properties": { - "Code": { - "BranchName": "main", - "S3": { - "Bucket": "cdk-hnb659fds-assets-000000000000-us-east-1", - "Key": "REPLACED-GENERATED-NAME.zip", - }, - }, - "RepositoryName": "aws-accelerator-config", - }, - "Type": "AWS::CodeCommit::Repository", - "UpdateReplacePolicy": "Retain", - }, - "PipelineManualApprovalActionTopicB55612D2": { - "Properties": { - "DisplayName": "aws-accelerator-pipeline-review-topic", - "KmsMasterKeyId": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "TopicName": "aws-accelerator-pipeline-review-topic", - }, - "Type": "AWS::SNS::Topic", - }, - "PipelinePipelineRole6D983AD5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "PipelinePipelineRoleDefaultPolicy7D262A22": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelineSourceCodePipelineActionRoleBBC58FD5", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelineSourceConfigurationCodePipelineActionRoleA2807B19", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelinePipelineRole6D983AD5", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelineBuildProject9D447FA8", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelineToolkitProjectBCBD6910", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelineReviewApproveCodePipelineActionRole3122ED42", - "Arn", - ], - }, - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Ref": "PipelineAcceleratorStatusTopic2BD5793F", - }, - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Ref": "PipelineAcceleratorFailedStatusTopic614002B3", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelinePipelineRoleDefaultPolicy7D262A22", - "Roles": [ - { - "Ref": "PipelinePipelineRole6D983AD5", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineReviewApproveCodePipelineActionRole3122ED42": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::000000000000:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "PipelineReviewApproveCodePipelineActionRoleDefaultPolicyD445B059": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Ref": "PipelineManualApprovalActionTopicB55612D2", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineReviewApproveCodePipelineActionRoleDefaultPolicyD445B059", - "Roles": [ - { - "Ref": "PipelineReviewApproveCodePipelineActionRole3122ED42", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineSecureBucketB3EEB324": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": "aws-accelerator-pipeline-000000000000-us-east-1", - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": "LifecycleRuleaws-accelerator-pipeline-000000000000-us-east-1", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstalleraccesslogsbucketnameC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "PipelineSecureBucketPolicy1BD98DDB": { - "Properties": { - "Bucket": { - "Ref": "PipelineSecureBucketB3EEB324", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "PipelineSourceCodePipelineActionRoleBBC58FD5": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::000000000000:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "PipelineSourceCodePipelineActionRoleDefaultPolicy5FD830BF": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:us-east-1:000000000000:accelerator-source", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineSourceCodePipelineActionRoleDefaultPolicy5FD830BF", - "Roles": [ - { - "Ref": "PipelineSourceCodePipelineActionRoleBBC58FD5", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineSourceConfigurationCodePipelineActionRoleA2807B19": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::000000000000:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "PipelineSourceConfigurationCodePipelineActionRoleDefaultPolicy5FE1A228": { - "DependsOn": [ - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionB3FFD974", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy28F66406", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionServiceRoleD177D2D7", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleFunctionLogGroup28940852", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEvent8BAA45B0", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyD8319C53", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleProviderframeworkonEventServiceRole4961EAEB", - "PipelineAWSServiceRoleForCodeStarNotificationsCreateServiceLinkedRoleResource12D935F1", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "PipelineSecureBucketB3EEB324", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "PipelineConfigRepositoryE5225086", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "PipelineSourceConfigurationCodePipelineActionRoleDefaultPolicy5FE1A228", - "Roles": [ - { - "Ref": "PipelineSourceConfigurationCodePipelineActionRoleA2807B19", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "PipelineToolkitProjectBCBD6910": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Modes": [ - "LOCAL_SOURCE_CACHE", - ], - "Type": "LOCAL", - }, - "EncryptionKey": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_LARGE", - "EnvironmentVariables": [ - { - "Name": "LOG_LEVEL", - "Type": "PLAINTEXT", - "Value": "error", - }, - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=12288", - }, - { - "Name": "CDK_METHOD", - "Type": "PLAINTEXT", - "Value": "direct", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_QUALIFIER", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator", - }, - { - "Name": "ACCELERATOR_REPO_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "ACCELERATOR_BUCKET_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "ACCELERATOR_KMS_ALIAS_PREFIX", - "Type": "PLAINTEXT", - "Value": "alias/accelerator", - }, - { - "Name": "ACCELERATOR_SSM_PARAM_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "/accelerator", - }, - { - "Name": "ACCELERATOR_SNS_TOPIC_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "accelerator", - }, - { - "Name": "ACCELERATOR_SECRET_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "/accelerator", - }, - { - "Name": "ACCELERATOR_TRAIL_LOG_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "ACCELERATOR_DATABASE_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": "000000000000", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": "Yes", - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "", - }, - { - "Name": "ACCELERATOR_PERMISSION_BOUNDARY", - "Type": "PLAINTEXT", - "Value": "", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": "aws-accelerator-toolkit-project", - "ServiceRole": { - "Fn::GetAtt": [ - "AdminCdkToolkitRole292E163A", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "{ - "version": "0.2", - "phases": { - "install": { - "runtime-versions": { - "nodejs": 18 - } - }, - "build": { - "commands": [ - "env", - "cd source", - "if [ \\"prepare\\" = \\"\${ACCELERATOR_STAGE}\\" ]; then set -e && export LOG_LEVEL=info && yarn run ts-node packages/@aws-accelerator/modules/bin/runner.ts --module control-tower --partition ", - { - "Ref": "AWS::Partition", - }, - " --use-existing-role No --config-dir $CODEBUILD_SRC_DIR_Config && if [ -z \\"\${ACCELERATOR_NO_ORG_MODULE}\\" ]; then yarn run ts-node packages/@aws-accelerator/modules/bin/runner.ts --module aws-organizations --partition ", - { - "Ref": "AWS::Partition", - }, - " --use-existing-role No --config-dir $CODEBUILD_SRC_DIR_Config; else echo \\"Module aws-organizations execution skipped by environment settings.\\"; fi && export LOG_LEVEL=error ; fi", - "if [ \\"prepare\\" = \\"\${ACCELERATOR_STAGE}\\" ]; then set -e && yarn run ts-node packages/@aws-accelerator/accelerator/lib/prerequisites.ts --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - " --minimal; fi", - "cd packages/@aws-accelerator/accelerator", - "export FULL_SYNTH=\\"true\\"", - "if [ $ASEA_MAPPING_BUCKET ]; then aws s3api head-object --bucket $ASEA_MAPPING_BUCKET --key $ASEA_MAPPING_FILE >/dev/null 2>&1 || export FULL_SYNTH=\\"false\\"; fi;", - "if [ -z \\"\${ACCELERATOR_STAGE}\\" ] && [ $CDK_OPTIONS = 'bootstrap' ] && [ $FULL_SYNTH = \\"true\\" ]; then for STAGE in \\"key\\" \\"logging\\" \\"organizations\\" \\"security-audit\\" \\"network-prep\\" \\"security\\" \\"operations\\" \\"identity-center\\" \\"network-vpc\\" \\"security-resources\\" \\"network-associations\\" \\"customizations\\" \\"finalize\\" \\"bootstrap\\"; do set -e && yarn run ts-node --transpile-only cdk.ts synth --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - " --stage $STAGE; done; fi", - "if [ -z \\"\${ACCELERATOR_STAGE}\\" ] && [ $CDK_OPTIONS = 'diff' ] && [ $FULL_SYNTH = \\"true\\" ]; then for STAGE in \\"key\\" \\"logging\\" \\"organizations\\" \\"security-audit\\" \\"network-prep\\" \\"security\\" \\"operations\\" \\"identity-center\\" \\"network-vpc\\" \\"security-resources\\" \\"network-associations\\" \\"customizations\\" \\"finalize\\" \\"bootstrap\\"; do set -e && yarn run ts-node --transpile-only cdk.ts synth --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - " --stage $STAGE; done; fi", - "if [ -z \\"\${ACCELERATOR_STAGE}\\" ] && [ $CDK_OPTIONS = 'bootstrap' ] && [ $FULL_SYNTH = \\"false\\" ]; then for STAGE in \\"bootstrap\\"; do set -e && yarn run ts-node --transpile-only cdk.ts synth --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - " --stage $STAGE; done; fi", - "if [ ! -z \\"\${ACCELERATOR_STAGE}\\" ]; then yarn run ts-node --transpile-only cdk.ts synth --stage $ACCELERATOR_STAGE --require-approval never --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - "; fi", - "if [ \\"diff\\" != \\"\${CDK_OPTIONS}\\" ]; then yarn run ts-node --transpile-only cdk.ts --require-approval never $CDK_OPTIONS --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - " --app cdk.out; fi", - "if [ \\"diff\\" = \\"\${CDK_OPTIONS}\\" ]; then for STAGE in \\"key\\" \\"logging\\" \\"organizations\\" \\"security-audit\\" \\"network-prep\\" \\"security\\" \\"operations\\" \\"identity-center\\" \\"network-vpc\\" \\"security-resources\\" \\"network-associations\\" \\"customizations\\" \\"finalize\\" \\"bootstrap\\"; do set -e && yarn run ts-node --transpile-only cdk.ts --require-approval never $CDK_OPTIONS --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - " --app cdk.out --stage $STAGE; done; find ./cdk.out -type f -name \\"*.diff\\" -exec cat \\"{}\\" \\\\;; fi", - "if [ \\"prepare\\" = \\"\${ACCELERATOR_STAGE}\\" ]; then cd ../../../ && set -e && yarn run ts-node packages/@aws-accelerator/accelerator/lib/prerequisites.ts --config-dir $CODEBUILD_SRC_DIR_Config --partition ", - { - "Ref": "AWS::Partition", - }, - "; fi" - ] - } - } -}", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - "TimeoutInMinutes": 480, - }, - "Type": "AWS::CodeBuild::Project", - }, - "PipelineValidateConfigPolicyDocument5A45073D": { - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/PipelineStack/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/PipelineStack/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/prepare-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/prepare-stack.test.ts.snap deleted file mode 100644 index 81eebf8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/prepare-stack.test.ts.snap +++ /dev/null @@ -1,4867 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PrepareStack Construct(PrepareStack): Snapshot Test 1`] = ` -{ - "Resources": { - "AcceleratorCloudWatchKmsArnParameter9BAD6EA0": { - "Properties": { - "Name": "/accelerator/kms/cloudwatch/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorConfigTable590367C4": { - "DeletionPolicy": "Delete", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-DDB3", - "reason": "AcceleratorConfigTable DynamoDB table do not need point in time recovery, data can be re-created", - }, - ], - }, - }, - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "dataType", - "AttributeType": "S", - }, - { - "AttributeName": "acceleratorKey", - "AttributeType": "S", - }, - { - "AttributeName": "awsKey", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "dataType", - "KeyType": "HASH", - }, - { - "AttributeName": "acceleratorKey", - "KeyType": "RANGE", - }, - ], - "LocalSecondaryIndexes": [ - { - "IndexName": "awsResourceKeys", - "KeySchema": [ - { - "AttributeName": "dataType", - "KeyType": "HASH", - }, - { - "AttributeName": "awsKey", - "KeyType": "RANGE", - }, - ], - "Projection": { - "ProjectionType": "KEYS_ONLY", - }, - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorControlTowerDriftMessageParameter153D37CE": { - "Properties": { - "Name": "/accelerator/controltower/lastDriftMessage", - "Type": "String", - "Value": "none", - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorControlTowerDriftParameter217B3055": { - "Properties": { - "AllowedPattern": "^(true|false)$", - "Name": "/accelerator/controltower/driftDetected", - "Type": "String", - "Value": "false", - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorLambdaKmsArnParameterFECEE80C": { - "Properties": { - "Name": "/accelerator/kms/lambda/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorManagementLambdaKeyEA4C7DBE", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorManagementCloudWatchKeyAliasF9F153C3": { - "Properties": { - "AliasName": "alias/accelerator/kms/cloudwatch/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorManagementCloudWatchKeyE630915F": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator CloudWatch Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Decrypt*", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch logs to use the encryption key", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": "/accelerator/management/kms/key-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "AcceleratorManagementLambdaKeyAliasE020802E": { - "Properties": { - "AliasName": "alias/accelerator/kms/lambda/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementLambdaKeyEA4C7DBE", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorManagementLambdaKeyEA4C7DBE": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Lambda Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AcceleratorMoveAccountRoleA64BDF6C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "CDK generated role", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/prepare-stack/configTable/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-MoveAccountConfigRule-Role", - }, - "Type": "AWS::IAM::Role", - }, - "ConfigTableArnParameter0395C8F4": { - "Properties": { - "Name": "/accelerator/prepare-stack/configTable/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AcceleratorConfigTable590367C4", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "ConfigTableNameParameter8F832044": { - "Properties": { - "Name": "/accelerator/prepare-stack/configTable/name", - "Type": "String", - "Value": { - "Ref": "AcceleratorConfigTable590367C4", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "ControlTowerNotification1FCAF33D": { - "Properties": { - "DisplayName": "ForwardedControlTowerNotifications", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "TopicName": "AWSAccelerator-ControlTowerNotification", - }, - "Type": "AWS::SNS::Topic", - }, - "ControlTowerNotificationPolicyC9D118BF": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:root", - ], - ], - }, - }, - "Resource": { - "Ref": "ControlTowerNotification1FCAF33D", - }, - "Sid": "auditAccount", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "ControlTowerNotification1FCAF33D", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "ControlTowerNotificationsFunctionAllowInvokeAWSAcceleratorPrepareStack111111111111useast1ControlTowerNotification2475DF555DE3539F": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "ControlTowerNotificationsFunctionF5514B7A", - "Arn", - ], - }, - "Principal": "sns.amazonaws.com", - "SourceArn": { - "Ref": "ControlTowerNotification1FCAF33D", - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "ControlTowerNotificationsFunctionControlTowerNotification26E3C9B0": { - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "ControlTowerNotificationsFunctionF5514B7A", - "Arn", - ], - }, - "Protocol": "lambda", - "TopicArn": { - "Ref": "ControlTowerNotification1FCAF33D", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "ControlTowerNotificationsFunctionF5514B7A": { - "DependsOn": [ - "ControlTowerNotificationsFunctionServiceRoleDefaultPolicy44497F9E", - "ControlTowerNotificationsFunctionServiceRoleB3D23260", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to process ControlTower notifications from audit account", - "Environment": { - "Variables": { - "DRIFT_MESSAGE_PARAMETER_NAME": { - "Ref": "AcceleratorControlTowerDriftMessageParameter153D37CE", - }, - "DRIFT_PARAMETER_NAME": { - "Ref": "AcceleratorControlTowerDriftParameter217B3055", - }, - }, - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ControlTowerNotificationsFunctionServiceRoleB3D23260", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ControlTowerNotificationsFunctionLogGroup35144E50": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ControlTowerNotificationsFunctionF5514B7A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ControlTowerNotificationsFunctionServiceRoleB3D23260": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Basic Lambda execution permissions.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ControlTowerNotificationsFunctionServiceRoleDefaultPolicy44497F9E": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ssm:PutParameter", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:111111111111:parameter", - { - "Ref": "AcceleratorControlTowerDriftParameter217B3055", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:111111111111:parameter", - { - "Ref": "AcceleratorControlTowerDriftMessageParameter153D37CE", - }, - ], - ], - }, - ], - "Sid": "ssm", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ControlTowerNotificationsFunctionServiceRoleDefaultPolicy44497F9E", - "Roles": [ - { - "Ref": "ControlTowerNotificationsFunctionServiceRoleB3D23260", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ControlTowerOuEventsFunctionFABED18B": { - "DependsOn": [ - "ControlTowerOuEventsFunctionServiceRoleDefaultPolicy8188B90A", - "ControlTowerOuEventsFunctionServiceRoleBC29C0C5", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to process ControlTower OU events from CloudTrail", - "Environment": { - "Variables": { - "CONFIG_TABLE_NAME": { - "Ref": "AcceleratorConfigTable590367C4", - }, - }, - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ControlTowerOuEventsFunctionServiceRoleBC29C0C5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ControlTowerOuEventsFunctionLogGroupD5BA6F1F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ControlTowerOuEventsFunctionFABED18B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ControlTowerOuEventsFunctionServiceRoleBC29C0C5": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Basic Lambda execution permissions.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ControlTowerOuEventsFunctionServiceRoleDefaultPolicy8188B90A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Requires access to all org units.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "dynamodb:UpdateItem", - "dynamodb:PutItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "AcceleratorConfigTable590367C4", - "Arn", - ], - }, - "Sid": "dynamodb", - }, - { - "Action": [ - "organizations:DescribeOrganizationalUnit", - "organizations:ListParents", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::111111111111:account/o-*/*", - ], - ], - }, - "Sid": "organizations", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ControlTowerOuEventsFunctionServiceRoleDefaultPolicy8188B90A", - "Roles": [ - { - "Ref": "ControlTowerOuEventsFunctionServiceRoleBC29C0C5", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ControlTowerOuEventsRule7E8DCB15": { - "Properties": { - "Description": "Rule to monitor for Control Tower OU registration and de-registration events", - "EventPattern": { - "detail": { - "eventName": [ - "RegisterOrganizationalUnit", - "DeregisterOrganizationalUnit", - ], - }, - "detail-type": [ - "AWS Service Event via CloudTrail", - ], - "source": [ - "aws.controltower", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "ControlTowerOuEventsFunctionFABED18B", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 3, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "ControlTowerOuEventsRuleAllowEventRuleAWSAcceleratorPrepareStack111111111111useast1ControlTowerOuEventsFunction7E5B6D582E959D5A": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "ControlTowerOuEventsFunctionFABED18B", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "ControlTowerOuEventsRule7E8DCB15", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "CreateCTAccounts3049A752": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAccountLogGroup7DD19087", - "CreateCTAccountsCreateControlTowerAccountStatusLogGroup18D2531C", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEvent8503E7D4", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateControlTowerAccounts", - "UpdateReplacePolicy": "Delete", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete97C67C8F": { - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleDefaultPolicy3785920A", - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleBB950C19", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - isComplete (AWSAccelerator-PrepareStack-111111111111-us-east-1/CreateCTAccounts/CreateControlTowerAcccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - }, - }, - "Handler": "framework.isComplete", - "Role": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleBB950C19", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleBB950C19": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleDefaultPolicy3785920A": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleDefaultPolicy3785920A", - "Roles": [ - { - "Ref": "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleBB950C19", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEvent8503E7D4": { - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleDefaultPolicyADBC2803", - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRole773985D8", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-PrepareStack-111111111111-us-east-1/CreateCTAccounts/CreateControlTowerAcccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - "WAITER_STATE_MACHINE_ARN": { - "Ref": "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineA6C8A44E", - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRole773985D8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRole773985D8": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleDefaultPolicyADBC2803": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "states:StartExecution", - "Effect": "Allow", - "Resource": { - "Ref": "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineA6C8A44E", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleDefaultPolicyADBC2803", - "Roles": [ - { - "Ref": "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRole773985D8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeout9340F558": { - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy40BA7D0E", - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8A0E95FA", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onTimeout (AWSAccelerator-PrepareStack-111111111111-us-east-1/CreateCTAccounts/CreateControlTowerAcccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onTimeout", - "Role": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8A0E95FA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8A0E95FA": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy40BA7D0E": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccount8636115B", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy40BA7D0E", - "Roles": [ - { - "Ref": "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8A0E95FA", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineA6C8A44E": { - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleDefaultPolicy987F4739", - "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleE4D3D067", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{"StartAt":"framework-isComplete-task","States":{"framework-isComplete-task":{"End":true,"Retry":[{"ErrorEquals":["States.ALL"],"IntervalSeconds":30,"MaxAttempts":480,"BackoffRate":1}],"Catch":[{"ErrorEquals":["States.ALL"],"Next":"framework-onTimeout-task"}],"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete97C67C8F", - "Arn", - ], - }, - ""},"framework-onTimeout-task":{"End":true,"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeout9340F558", - "Arn", - ], - }, - ""}}}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleE4D3D067", - "Arn", - ], - }, - }, - "Type": "AWS::StepFunctions::StateMachine", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleDefaultPolicy987F4739": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete97C67C8F", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete97C67C8F", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeout9340F558", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeout9340F558", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleDefaultPolicy987F4739", - "Roles": [ - { - "Ref": "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleE4D3D067", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleE4D3D067": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "states.us-east-1.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CreateCTAccountsCreateControlTowerAccount8636115B": { - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAccountServiceRoleAEFB3CA3", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Control Tower Account onEvent handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountServiceRoleAEFB3CA3", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateCTAccountsCreateControlTowerAccountLogGroup7DD19087": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CreateCTAccountsCreateControlTowerAccount8636115B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CreateCTAccountsCreateControlTowerAccountServiceRoleAEFB3CA3": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateCTAccountsCreateControlTowerAccountStatusE993FA04": { - "DependsOn": [ - "CreateCTAccountsCreateControlTowerAccountStatusServiceRoleDefaultPolicy9BE6F791", - "CreateCTAccountsCreateControlTowerAccountStatusServiceRole999B76C4", - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Control Tower Account isComplete handler", - "Environment": { - "Variables": { - "NewAccountsTableName": { - "Ref": "NewCTAccountsE326CD07", - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusServiceRole999B76C4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateCTAccountsCreateControlTowerAccountStatusLogGroup18D2531C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CreateCTAccountsCreateControlTowerAccountStatusE993FA04", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CreateCTAccountsCreateControlTowerAccountStatusServiceRole999B76C4": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AWSServiceCatalogEndUserFullAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateCTAccountsCreateControlTowerAccountStatusServiceRoleDefaultPolicy9BE6F791": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "dynamodb:Scan", - "dynamodb:GetItem", - "dynamodb:DeleteItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NewCTAccountsE326CD07", - "Arn", - ], - }, - "Sid": "DynamoDb", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "Sid": "KMS", - }, - { - "Action": [ - "controltower:CreateManagedAccount", - "controltower:SetupLandingZone", - "controltower:EnableGuardrail", - "controltower:Describe*", - "controltower:Get*", - "controltower:List*", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ControlTower", - }, - { - "Action": [ - "sso-directory:DescribeDirectory", - "sso-directory:CreateUser", - "sso-directory:SearchUsers", - "sso-directory:SearchGroups", - "sso:ListDirectoryAssociations", - "sso:DescribeRegisteredRegions", - "sso:ListProfileAssociations", - "sso:AssociateProfile", - "sso:GetProfile", - "sso:CreateProfile", - "sso:UpdateProfile", - "sso:GetTrust", - "sso:CreateTrust", - "sso:UpdateTrust", - "sso:GetApplicationInstance", - "sso:CreateApplicationInstance", - "sso:ListPermissionSets", - "sso:GetSSOStatus", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SSO", - }, - { - "Action": [ - "servicecatalog:SearchProvisionedProducts", - "servicecatalog:ProvisionProduct", - "servicecatalog:DescribeProduct", - "servicecatalog:ListProvisioningArtifacts", - "servicecatalog:DescribeProvisionedProduct", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateCTAccountsCreateControlTowerAccountStatusServiceRoleDefaultPolicy9BE6F791", - "Roles": [ - { - "Ref": "CreateCTAccountsCreateControlTowerAccountStatusServiceRole999B76C4", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateCTAccountsLambdaPrincipalAssociationFAD34BEB": { - "DependsOn": [ - "CreateOrganizationAccountsDDA8AFE1", - "CreateOrganizationAccountsServiceRole99CB3720", - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "CreateOrganizationAccounts49A5350C", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "PortfolioId": { - "Ref": "GetPortFolioId5CA7347E", - }, - "PrincipalARN": { - "Fn::GetAtt": [ - "CreateCTAccountsCreateControlTowerAccountStatusServiceRole999B76C4", - "Arn", - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - "CreateOrganizationAccounts49A5350C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateOrganizationAccounts", - "UpdateReplacePolicy": "Delete", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Organization Account isComplete handler", - "Environment": { - "Variables": { - "AccountRoleName": "AWSControlTowerExecution", - "GovCloudAccountMappingTableName": { - "Ref": "govCloudAccountMapping0E3D2AD8", - }, - "NewOrgAccountsTableName": { - "Ref": "NewOrgAccountsE5BA262F", - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "dynamodb:Scan", - "dynamodb:GetItem", - "dynamodb:DeleteItem", - "dynamodb:PutItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NewOrgAccountsE5BA262F", - "Arn", - ], - }, - "Sid": "DynamoDb", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "Sid": "KMS", - }, - { - "Action": [ - "organizations:CreateAccount", - "organizations:CreateGovCloudAccount", - "organizations:DescribeCreateAccountStatus", - "organizations:ListRoots", - "organizations:MoveAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Organizations", - }, - { - "Action": [ - "dynamodb:GetItem", - "dynamodb:PutItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "govCloudAccountMapping0E3D2AD8", - "Arn", - ], - }, - "Sid": "MappingDynamoDb", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "Sid": "MappingKMS", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CreateOrganizationAccountsDDA8AFE1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - isComplete (AWSAccelerator-PrepareStack-111111111111-us-east-1/CreateOrganizationAccounts/CreateOrganizationAccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - }, - }, - "Handler": "framework.isComplete", - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-PrepareStack-111111111111-us-east-1/CreateOrganizationAccounts/CreateOrganizationAccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - "WAITER_STATE_MACHINE_ARN": { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "states:StartExecution", - "Effect": "Allow", - "Resource": { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onTimeout (AWSAccelerator-PrepareStack-111111111111-us-east-1/CreateOrganizationAccounts/CreateOrganizationAccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onTimeout", - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{"StartAt":"framework-isComplete-task","States":{"framework-isComplete-task":{"End":true,"Retry":[{"ErrorEquals":["States.ALL"],"IntervalSeconds":15,"MaxAttempts":480,"BackoffRate":1}],"Catch":[{"ErrorEquals":["States.ALL"],"Next":"framework-onTimeout-task"}],"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "Arn", - ], - }, - ""},"framework-onTimeout-task":{"End":true,"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "Arn", - ], - }, - ""}}}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "Arn", - ], - }, - }, - "Type": "AWS::StepFunctions::StateMachine", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "states.us-east-1.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsDDA8AFE1": { - "DependsOn": [ - "CreateOrganizationAccountsServiceRole99CB3720", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Organization Accounts OnEvent handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsServiceRole99CB3720", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsServiceRole99CB3720": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationalUnits3A58A65B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderLogGroup5A9D7A53", - "LoadAcceleratorConfigTable8F9D29D6", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderHandler4596F0BC", - "Arn", - ], - }, - "commitId": "", - "configTableName": { - "Ref": "AcceleratorConfigTable590367C4", - }, - "controlTowerEnabled": true, - "organizationsEnabled": true, - "partition": { - "Ref": "AWS::Partition", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateOrganizationalUnits", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetPortfolioIdCustomResourceProviderHandlerB6C78D44": { - "DependsOn": [ - "CustomGetPortfolioIdCustomResourceProviderRole698D24D6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetPortfolioIdCustomResourceProviderRole698D24D6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetPortfolioIdCustomResourceProviderLogGroupA4D50C6A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetPortfolioIdCustomResourceProviderHandlerB6C78D44", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetPortfolioIdCustomResourceProviderRole698D24D6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "servicecatalog:ListPortfolios", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomInviteAccountsToOrganizationCustomResourceProviderHandlerC9A5BAC1": { - "DependsOn": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderRole88663193", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderRole88663193", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomInviteAccountsToOrganizationCustomResourceProviderLogGroup4B13FC0F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomInviteAccountsToOrganizationCustomResourceProviderHandlerC9A5BAC1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomInviteAccountsToOrganizationCustomResourceProviderRole88663193": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AcceptHandshake", - "organizations:ListAccounts", - "organizations:InviteAccountToOrganization", - "organizations:MoveAccount", - "organizations:ListRoots", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "dynamodb:Query", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorConfigTable590367C4", - "Arn", - ], - }, - ], - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSControlTowerExecution", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomLoadAcceleratorConfigTableCustomResourceProviderHandlerA3F7FD1F": { - "DependsOn": [ - "CustomLoadAcceleratorConfigTableCustomResourceProviderRole18CA864F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 1024, - "Role": { - "Fn::GetAtt": [ - "CustomLoadAcceleratorConfigTableCustomResourceProviderRole18CA864F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomLoadAcceleratorConfigTableCustomResourceProviderLogGroup3732F4CC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomLoadAcceleratorConfigTableCustomResourceProviderHandlerA3F7FD1F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomLoadAcceleratorConfigTableCustomResourceProviderRole18CA864F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - "organizations:ListRoots", - "organizations:ListOrganizationalUnitsForParent", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "organizations", - }, - { - "Action": [ - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "getReplacements", - }, - { - "Action": [ - "dynamodb:UpdateItem", - "dynamodb:PutItem", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorConfigTable590367C4", - "Arn", - ], - }, - ], - "Sid": "configTable", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - ], - "Sid": "kms", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::cdk-accel-assets-111111111111-us-east-1/*", - ], - ], - }, - ], - "Sid": "s3", - }, - { - "Action": [ - "cloudformation:DescribeStacks", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:cloudformation:us-east-1:111111111111:stack/AWSAccelerator-PrepareStack-111111111111-us-east-1*", - ], - "Sid": "cloudFormation", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderHandler4596F0BC": { - "DependsOn": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderRole4B8B81B0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderRole4B8B81B0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderLogGroup5A9D7A53": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderHandler4596F0BC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderRole4B8B81B0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreateOrganizationalUnit", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListRoots", - "organizations:UpdateOrganizationalUnit", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "organizations", - }, - { - "Action": [ - "dynamodb:UpdateItem", - "dynamodb:Query", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AcceleratorConfigTable590367C4", - "Arn", - ], - }, - ], - "Sid": "dynamodb", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "GetPortFolioId5CA7347E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetPortfolioIdCustomResourceProviderLogGroupA4D50C6A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetPortfolioIdCustomResourceProviderHandlerB6C78D44", - "Arn", - ], - }, - "displayName": "AWS Control Tower Account Factory Portfolio", - "providerName": "AWS Control Tower", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetPortfolioId", - "UpdateReplacePolicy": "Delete", - }, - "GovCloudAccountMappingTableNameParameter9B54E984": { - "DependsOn": [ - "NewCTAccountsTableNameParameter065A4927", - ], - "Properties": { - "Name": "/accelerator/prepare-stack/govCloudAccountMappingTableName", - "Type": "String", - "Value": { - "Ref": "govCloudAccountMapping0E3D2AD8", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InviteAccountsToOu482336A1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CreateOrganizationalUnits3A58A65B", - "CustomInviteAccountsToOrganizationCustomResourceProviderLogGroup4B13FC0F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderHandlerC9A5BAC1", - "Arn", - ], - }, - "assumeRoleName": "AWSControlTowerExecution", - "commitId": "", - "configTableName": { - "Ref": "AcceleratorConfigTable590367C4", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::InviteAccountsToOrganization", - "UpdateReplacePolicy": "Delete", - }, - "LoadAcceleratorConfigTable8F9D29D6": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomLoadAcceleratorConfigTableCustomResourceProviderLogGroup3732F4CC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomLoadAcceleratorConfigTableCustomResourceProviderHandlerA3F7FD1F", - "Arn", - ], - }, - "accountConfigS3Key": "7862daf1a54c347eedca1c3083aa8d03a12033fbb398666b50ba926cfb64ce14.yaml", - "auditAccountEmail": "all-enabled-audit-account@example.com", - "commitId": "", - "configRepositoryName": "aws-accelerator-config", - "configS3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "configTableName": { - "Ref": "AcceleratorConfigTable590367C4", - }, - "isOrgsEnabled": true, - "logArchiveAccountEmail": "all-enabled-logarchive-account@example.com", - "managementAccountEmail": "all-enabled-management-account@example.com", - "organizationsConfigS3Key": "fb76e9a61d7ab20c202744a5728b5278772fff1f6056a6f7b6d8a16bb7fce9f9.yaml", - "partition": "aws", - "replacementsConfigS3Key": "e075bcecb878a10a77f0f4650b85c3ab1692fabf90da3e07131fc7006192385f.yaml", - "stackName": "AWSAccelerator-PrepareStack-111111111111-us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::LoadAcceleratorConfigTable", - "UpdateReplacePolicy": "Delete", - }, - "ManagementKey0813A4D9": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role in this account to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Decrypt*", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:Describe*", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch logs to use the encryption key", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Encrypt", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "sns", - }, - { - "Action": [ - "kms:GenerateDataKey", - "kms:Encrypt", - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:root", - ], - ], - }, - }, - "Resource": "*", - "Sid": "auditAccount", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ManagementKeyAlias69A0A38F": { - "Properties": { - "AliasName": "alias/accelerator/management/kms/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "NewCTAccountsE326CD07": { - "DeletionPolicy": "Delete", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-DDB3", - "reason": "NewCTAccounts DynamoDB table do not need point in time recovery, data can be re-created", - }, - ], - }, - }, - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "accountEmail", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "accountEmail", - "KeyType": "HASH", - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Delete", - }, - "NewCTAccountsTableNameParameter065A4927": { - "Properties": { - "Name": "/accelerator/prepare-stack/NewCTAccountsTableName", - "Type": "String", - "Value": { - "Ref": "NewCTAccountsE326CD07", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "NewOrgAccountsE5BA262F": { - "DeletionPolicy": "Delete", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-DDB3", - "reason": "NewOrgAccounts DynamoDB table do not need point in time recovery, data can be re-created", - }, - ], - }, - }, - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "accountEmail", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "accountEmail", - "KeyType": "HASH", - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Delete", - }, - "NewOrgAccountsTableNameParameter91A89034": { - "DependsOn": [ - "NewCTAccountsTableNameParameter065A4927", - ], - "Properties": { - "Name": "/accelerator/prepare-stack/NewOrgAccountsTableName", - "Type": "String", - "Value": { - "Ref": "NewOrgAccountsE5BA262F", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "Parameter9E1B4FBA": { - "Properties": { - "Name": "/accelerator/prepare-stack/validate", - "Type": "String", - "Value": "value", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-PrepareStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-PrepareStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7": { - "DependsOn": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-PrepareStack-111111111111-us-east-1/ValidateEnvironmentConfig/Custom::ValidateEnvironmentConfiguration)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK created resource", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Policy permissions are part cdk provider framework", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRoleDefaultPolicyA669E020", - "Roles": [ - { - "Ref": "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEventServiceRole675155BD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77": { - "DependsOn": [ - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Validate Environment Configuration", - "Handler": "index.handler", - "MemorySize": 1024, - "Role": { - "Fn::GetAtt": [ - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "AcceleratorManagementCloudWatchKeyE630915F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateEnvironmentConfigValidateEnvironmentFunction13F94F77", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK created resource", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "CDK created resource", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - "servicecatalog:SearchProvisionedProducts", - "organizations:ListChildren", - "organizations:ListPoliciesForTarget", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListRoots", - "organizations:ListAccountsForParent", - "organizations:ListParents", - "organizations:ListPolicies", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrganizationsLookup", - }, - { - "Action": "dynamodb:PutItem", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "NewOrgAccountsE5BA262F", - "Arn", - ], - }, - { - "Fn::GetAtt": [ - "NewCTAccountsE326CD07", - "Arn", - ], - }, - ], - "Sid": "dynamodb", - }, - { - "Action": [ - "dynamodb:Query", - "dynamodb:UpdateItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "AcceleratorConfigTable590367C4", - "Arn", - ], - }, - "Sid": "dynamodbConfigTable", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "Sid": "kms", - }, - { - "Action": "cloudformation:DescribeStacks", - "Effect": "Allow", - "Resource": "arn:aws:cloudformation:us-east-1:111111111111:stack/AWSAccelerator-PrepareStack-111111111111-us-east-1*", - "Sid": "cloudformation", - }, - { - "Action": "ssm:GetParameter", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:111111111111:parameter", - { - "Ref": "AcceleratorControlTowerDriftParameter217B3055", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:111111111111:parameter", - { - "Ref": "AcceleratorControlTowerDriftMessageParameter153D37CE", - }, - ], - ], - }, - ], - "Sid": "sms", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRoleDefaultPolicy9ACA86A8", - "Roles": [ - { - "Ref": "ValidateEnvironmentConfigValidateEnvironmentFunctionServiceRole09865D4C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateEnvironmentConfigValidateEnvironmentResourceD10DC179": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateEnvironmentConfigValidateEnvironmentFunctionLogGroup632317CC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateEnvironmentConfigCustomValidateEnvironmentConfigurationframeworkonEvent4F574CF7", - "Arn", - ], - }, - "commitId": { - "Ref": "LoadAcceleratorConfigTable8F9D29D6", - }, - "configTableName": { - "Ref": "AcceleratorConfigTable590367C4", - }, - "controlTowerEnabled": true, - "driftDetectionMessageParameterName": { - "Ref": "AcceleratorControlTowerDriftMessageParameter153D37CE", - }, - "driftDetectionParameterName": { - "Ref": "AcceleratorControlTowerDriftParameter217B3055", - }, - "newCTAccountsTableName": { - "Ref": "NewCTAccountsE326CD07", - }, - "newOrgAccountsTableName": { - "Ref": "NewOrgAccountsE5BA262F", - }, - "organizationsEnabled": true, - "partition": "aws", - "serviceControlPolicies": [ - { - "name": "AcceleratorGuardrails1", - "strategy": "deny-list", - "targetType": "ou", - "targets": [ - { - "id": "ou-asdf-22222222", - "name": "Infrastructure", - }, - ], - }, - { - "name": "AcceleratorGuardrails2", - "strategy": "deny-list", - "targetType": "account", - "targets": [ - { - "id": "444444444444", - "name": "SharedServices", - }, - ], - }, - { - "name": "AllowList", - "strategy": "allow-list", - "targetType": "ou", - "targets": [ - { - "id": "ou-asdf-33333333", - "name": "SecureWorkloads", - }, - ], - }, - { - "name": "DataPerimeter", - "strategy": "deny-list", - "targetType": "ou", - "targets": [ - { - "id": "ou-asdf-22222222", - "name": "Infrastructure", - }, - ], - }, - ], - "skipScpValidation": "no", - "stackName": "AWSAccelerator-PrepareStack-111111111111-us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::ValidateEnvironmentConfiguration", - "UpdateReplacePolicy": "Delete", - }, - "govCloudAccountMapping0E3D2AD8": { - "DeletionPolicy": "Retain", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "commercialAccountId", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "commercialAccountId", - "KeyType": "HASH", - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/resource-policy-enforcement-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/resource-policy-enforcement-stack.test.ts.snap deleted file mode 100644 index 7f30b4d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/resource-policy-enforcement-stack.test.ts.snap +++ /dev/null @@ -1,893 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResourcePolicyEnforcementStack Construct(ResourcePolicyEnforcementStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AwsAcceleratorResourcePolicyComplianceCheck7EDAEEE0": { - "DependsOn": [ - "DetectResourcePolicyDetectResourcePolicyFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI1334F2B2", - "DetectResourcePolicyDetectResourcePolicyFunction3130D833", - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D", - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - ], - "Properties": { - "ConfigRuleName": "AWSAccelerator-Resource-Policy-Compliance-Check", - "Description": "Config rule to detect non-compliant resource based policy", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - "AWS::KMS::Key", - "AWS::IAM::Role", - "AWS::SecretsManager::Secret", - "AWS::ECR::Repository", - "AWS::OpenSearch::Domain", - "AWS::SNS::Topic", - "AWS::SQS::Queue", - "AWS::ApiGateway::RestApi", - "AWS::Lex::Bot", - "AWS::EFS::FileSystem", - "AWS::Events::EventBus", - "AWS::Backup::BackupVault", - "AWS::CodeArtifact::Repository", - "AWS::ACMPCA::CertificateAuthority", - "AWS::Lambda::Function", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "DetectResourcePolicyDetectResourcePolicyFunction3130D833", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AwsAcceleratorResourcePolicyComplianceCheckRemediationRoleD89CACA2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AwsAcceleratorResourcePolicyComplianceCheckRemediationRoleDefaultPolicy8ECF22F0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/AWSAccelerator-Attach-Resource-Based-Policy", - ], - ], - }, - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "RemediateResourcePolicyRemediateResourcePolicyFunction2736D3F4", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AwsAcceleratorResourcePolicyComplianceCheckRemediationRoleDefaultPolicy8ECF22F0", - "Roles": [ - { - "Ref": "AwsAcceleratorResourcePolicyComplianceCheckRemediationRoleD89CACA2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "DetectResRemediation": { - "DependsOn": [ - "AwsAcceleratorResourcePolicyComplianceCheck7EDAEEE0", - ], - "Properties": { - "Automatic": false, - "ConfigRuleName": "AWSAccelerator-Resource-Policy-Compliance-Check", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AwsAcceleratorResourcePolicyComplianceCheckRemediationRoleD89CACA2", - "Arn", - ], - }, - ], - }, - }, - "ConfigRuleName": { - "StaticValue": { - "Values": [ - "AWSAccelerator-Resource-Policy-Compliance-Check", - ], - }, - }, - "FunctionName": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "RemediateResourcePolicyRemediateResourcePolicyFunction2736D3F4", - "Arn", - ], - }, - ], - }, - }, - "ResourceId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/AWSAccelerator-Attach-Resource-Based-Policy", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "DetectResourcePolicyDetectResourcePolicyFunction3130D833": { - "DependsOn": [ - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D", - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to detect non-compliant resource policy", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "ACCOUNT_ID": "111111111111", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-east-1", - "ORG_ID": "o-asdf123456", - "SourceAccount": "{{ ALLOWED_EXTERNAL_ACCOUNTS }}", - }, - }, - "Handler": "detect-resource-policy.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Role": { - "Fn::GetAtt": [ - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI1334F2B2": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "DetectResourcePolicyDetectResourcePolicyFunction3130D833", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionLogGroup68C3F428": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "DetectResourcePolicyDetectResourcePolicyFunction3130D833", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "lex:DescribeResourcePolicy", - "apigateway:GET", - "lambda:GetPolicy", - "backup:GetBackupVaultAccessPolicy", - "codeartifact:GetRepositoryPermissionsPolicy", - "events:DescribeEventBus", - "acm-pca:GetPolicy", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lex:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":apigateway:us-east-1::*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":backup:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codeartifact:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":events:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":acm-pca:us-east-1:111111111111:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D", - "Roles": [ - { - "Ref": "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RemediateResourcePolicyRemediateResourcePolicyFunction2736D3F4": { - "DependsOn": [ - "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRoleDefaultPolicyBB93B16A", - "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRole83F5E0A5", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to remediate non-compliant resource based policy", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "ACCOUNT_ID": "111111111111", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-east-1", - "ORG_ID": "o-asdf123456", - "SourceAccount": "{{ ALLOWED_EXTERNAL_ACCOUNTS }}", - }, - }, - "Handler": "remediate-resource-policy.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Role": { - "Fn::GetAtt": [ - "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRole83F5E0A5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "RemediateResourcePolicyRemediateResourcePolicyFunctionLogGroupD8FEF036": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "RemediateResourcePolicyRemediateResourcePolicyFunction2736D3F4", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRole83F5E0A5": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRoleDefaultPolicyBB93B16A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:getRole", - "iam:updateAssumeRolePolicy", - "s3:GetBucketPolicy", - "s3:PutBucketPolicy", - "kms:GetKeyPolicy", - "kms:PutKeyPolicy", - "kms:DescribeKey", - "secretsmanager:GetResourcePolicy", - "secretsmanager:PutResourcePolicy", - "secretsmanager:ValidateResourcePolicy", - "lex:DescribeResourcePolicy", - "lex:UpdateResourcePolicy", - "lex:CreateResourcePolicy", - "apigateway:UpdateRestApiPolicy", - "apigateway:GET", - "apigateway:PATCH", - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "es:ESHttpGet", - "es:ESHttpPut", - "es:DescribeDomainConfig", - "es:UpdateDomainConfig", - "sns:GetTopicAttributes", - "sns:SetTopicAttributes", - "sqs:GetQueueAttributes", - "sqs:SetQueueAttributes", - "elasticfilesystem:DescribeFileSystemPolicy", - "elasticfilesystem:PutFileSystemPolicy", - "codeartifact:GetDomainPermissionsPolicy", - "codeartifact:GetRepositoryPermissionsPolicy", - "codeartifact:PutDomainPermissionsPolicy", - "codeartifact:PutRepositoryPermissionsPolicy", - "events:DescribeEventBus", - "events:PutPermission", - "backup:GetBackupVaultAccessPolicy", - "backup:PutBackupVaultAccessPolicy", - "acm-pca:PutPolicy", - "acm-pca:GetPolicy", - "lambda:GetPolicy", - "lambda:AddPermission", - "lambda:RemovePermission", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lex:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":apigateway:us-east-1::*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ecr:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":es:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sqs:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":elasticfilesystem:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codeartifact:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":backup:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":events:us-east-1:111111111111:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":acm-pca:us-east-1:111111111111:*", - ], - ], - }, - ], - }, - { - "Action": [ - "config:BatchGetResourceConfig", - "config:SelectResourceConfig", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRoleDefaultPolicyBB93B16A", - "Roles": [ - { - "Ref": "RemediateResourcePolicyRemediateResourcePolicyFunctionServiceRole83F5E0A5", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-ResourcePolicyEnforcementStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-ResourcePolicyEnforcementStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-audit-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-audit-stack.test.ts.snap deleted file mode 100644 index 7029486..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-audit-stack.test.ts.snap +++ /dev/null @@ -1,5101 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityAuditStack Construct(SecurityAuditStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/s3/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-CrossAccountSsmParameterShare", - ], - ], - }, - "invokingAccountID": "222222222222", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorSnsKeyAlias1909B90C": { - "Properties": { - "AliasName": "alias/accelerator/kms/sns/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsKeyDADF94B1", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "AcceleratorSnsKeyDADF94B1": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator SNS Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::222222222222:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "AccessAnalyzer": { - "Properties": { - "Type": "ORGANIZATION", - }, - "Type": "AWS::AccessAnalyzer::Analyzer", - }, - "AttachIamInstanceProfileC858EC06": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Associate AWS Iam Instance Profile to EC2 Instance", - "mainSteps": [ - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "associate_iam_instance_profile", - "IamInstanceProfile": { - "Name": "{{ IamInstanceProfile }}", - }, - "InstanceId": "{{ InstanceId }}", - "Service": "ec2", - }, - "name": "associateIamProfile", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "type": "String", - }, - "IamInstanceProfile": { - "type": "String", - }, - "InstanceId": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "Attach-IAM-Instance-Profile", - "TargetType": "/AWS::EC2::Instance", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "AttachIamInstanceProfileShareDocumentD1F87A43": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "AttachIamInstanceProfileC858EC06", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "AttachIamRolePolicy19A43F7E": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "IAM Role Policy", - "mainSteps": [ - { - "action": "aws:executeScript", - "inputs": { - "Handler": "script_handler", - "InputPayload": { - "AWSManagedPolicies": "{{ AWSManagedPolicies }}", - "CustomerManagedPolicies": "{{ CustomerManagedPolicies }}", - "ResourceId": "{{ ResourceId }}", - }, - "Runtime": "python3.7", - "Script": "import boto3 -partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] -iam = boto3.client("iam") -config = boto3.client("config") -def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - )", - }, - "name": "attachPolicy", - }, - ], - "parameters": { - "AWSManagedPolicies": { - "type": "StringList", - }, - "AutomationAssumeRole": { - "type": "String", - }, - "CustomerManagedPolicies": { - "default": [], - "minItems": 0, - "type": "StringList", - }, - "ResourceId": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "Attach-IAM-Role-Policy", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "AttachIamRolePolicyShareDocumentF5C9E6C7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "AttachIamRolePolicy19A43F7E", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerDefaultReportsDestinationAFD20D60": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderLogGroupF5AC3566", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433", - "Arn", - ], - }, - "bucket": { - "Fn::Join": [ - "", - [ - "s3://'", - { - "Ref": "AuditManagerPublishingDestinationBucket74974FCF", - }, - "/audit-manager/222222222222/", - ], - ], - }, - "defaultReportsDestinationType": "S3", - "kmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "region": "us-east-1", - }, - "Type": "Custom::AuditManagerCreateDefaultReportsDestination", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerPublishingDestinationBucket74974FCF": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-auditmgr-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": "existing-access-logs-bucket-222222222222-us-east-1", - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "Tags": [ - { - "Key": "aws-cdk:auto-audit-manager-access-bucket", - "Value": "true", - }, - { - "Key": "aws-cdk:auto-auditManager-access-bucket", - "Value": "true", - }, - ], - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationECF1B287": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": "333333333333", - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationExistingCentralLogBucketReplicationRoleBB4CF589", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "AuditManagerPublishingDestinationBucket74974FCF", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationExistingCentralLogBucketReplicationRoleBB4CF589": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - "Tags": [ - { - "Key": "aws-cdk:auto-audit-manager-access-bucket", - "Value": "true", - }, - { - "Key": "aws-cdk:auto-auditManager-access-bucket", - "Value": "true", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationExistingCentralLogBucketReplicationRoleDefaultPolicy923CA71F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "gDestinationBucketAuditManagerPublishingDestinationBucketReplicationExistingCentralLogBucketReplicationRoleDefaultPolicy923CA71F", - "Roles": [ - { - "Ref": "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationExistingCentralLogBucketReplicationRoleBB4CF589", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AuditManagerPublishingDestinationBucketPolicy3992854B": { - "Properties": { - "Bucket": { - "Ref": "AuditManagerPublishingDestinationBucket74974FCF", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "Service": "auditmanager.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:PutObject", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "Allow Organization principals to use of the bucket", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "ControlTowerNotificationsForwarderFunctionAllowInvokeAWSAcceleratorSecurityAuditStack222222222222useast1ControlTowerSNSTopic2C923A0CF50B8C0E": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "ControlTowerNotificationsForwarderFunctionFA79B131", - "Arn", - ], - }, - "Principal": "sns.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:222222222222:aws-controltower-AggregateSecurityNotifications", - ], - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "ControlTowerNotificationsForwarderFunctionControlTowerSNSTopicF1309D6C": { - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "ControlTowerNotificationsForwarderFunctionFA79B131", - "Arn", - ], - }, - "Protocol": "lambda", - "Region": "us-east-1", - "TopicArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:222222222222:aws-controltower-AggregateSecurityNotifications", - ], - ], - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "ControlTowerNotificationsForwarderFunctionFA79B131": { - "DependsOn": [ - "ControlTowerNotificationsForwarderFunctionServiceRoleDefaultPolicyEBACE9AF", - "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to forward ControlTower notifications to management account", - "Environment": { - "Variables": { - "SNS_TOPIC_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-ControlTowerNotification", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 120, - }, - "Type": "AWS::Lambda::Function", - }, - "ControlTowerNotificationsForwarderFunctionServiceRoleDefaultPolicyEBACE9AF": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Require access to all keys in management account", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-ControlTowerNotification", - ], - ], - }, - "Sid": "sns", - }, - { - "Action": [ - "kms:DescribeKey", - "kms:GenerateDataKey", - "kms:Decrypt", - "kms:Encrypt", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:us-east-1:111111111111:key/*", - ], - ], - }, - "Sid": "kms", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ControlTowerNotificationsForwarderFunctionServiceRoleDefaultPolicyEBACE9AF", - "Roles": [ - { - "Ref": "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider lambda role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433": { - "DependsOn": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderLogGroupF5AC3566": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "auditmanager:UpdateSettings", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerCreatePublishingDestinationCommandTaskAuditManagerActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D": { - "DependsOn": [ - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveCreateMembersCustomResourceProviderLogGroup8A5563BD": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "detective:ListOrganizationAdminAccounts", - "detective:UpdateOrganizationConfiguration", - "detective:CreateMembers", - "detective:DeleteMembers", - "detective:DisassociateMembership", - "detective:ListMembers", - "detective:ListGraphs", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveCreateMembersTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": [ - "organizations:ListAccounts", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "OrganisationsListDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1": { - "DependsOn": [ - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderLogGroupDF150426": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveConfigureOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:UpdateOrganizationConfiguration", - "detective:ListGraphs", - "detective:ListMembers", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveUpdateGraphTaskDetectiveActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673": { - "DependsOn": [ - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderLogGroupB5134860": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreateMembersTaskOrganizationAction", - }, - { - "Action": [ - "guardDuty:ListDetectors", - "guardDuty:ListOrganizationAdminAccounts", - "guardDuty:UpdateOrganizationConfiguration", - "guardduty:CreateMembers", - "guardduty:DeleteMembers", - "guardduty:DisassociateMembers", - "guardduty:ListDetectors", - "guardduty:ListMembers", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreateMembersTaskGuardDutyActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceLinkedRoleSecurityHub", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9": { - "DependsOn": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderLogGroup0E4B1900": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "guardduty:ListDetectors", - "guardduty:ListMembers", - "guardduty:UpdateDetector", - "guardduty:UpdateMemberDetectors", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyUpdateDetectorTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB": { - "DependsOn": [ - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieCreateMemberCustomResourceProviderLogGroupB2DBC335": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieCreateMemberTaskOrganizationAction", - }, - { - "Action": [ - "macie2:CreateMember", - "macie2:DeleteMember", - "macie2:DescribeOrganizationConfiguration", - "macie2:DisassociateMember", - "macie2:EnableMacie", - "macie2:GetMacieSession", - "macie2:ListMembers", - "macie2:UpdateOrganizationConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieCreateMemberTaskMacieActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87": { - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DescribeDocumentPermission", - "ssm:ModifyDocumentPermission", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/*", - ], - ], - }, - "Sid": "ShareDocumentActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3": { - "DependsOn": [ - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderLogGroup51212673": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskOrganizationAction", - }, - { - "Action": [ - "securityhub:CreateMembers", - "securityhub:DeleteMembers", - "securityhub:DisassociateMembers", - "securityhub:EnableSecurityHub", - "securityhub:ListMembers", - "securityhub:UpdateOrganizationConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A": { - "DependsOn": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderLogGroup241F2158": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "securityhub:CreateFindingAggregator", - "securityhub:UpdateFindingAggregator", - "securityhub:DeleteFindingAggregator", - "securityhub:ListFindingAggregators", - "securityhub:GetFindingAggregator", - "securityhub:DescribeHub", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubModifyRegionAggregation", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DetectiveGraphConfig248C4B9F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveUpdateGraphCustomResourceProviderLogGroupDF150426", - "DetectiveMembers42A16137", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1", - "Arn", - ], - }, - "region": "us-east-1", - }, - "Type": "Custom::DetectiveUpdateGraph", - "UpdateReplacePolicy": "Delete", - }, - "DetectiveMembers42A16137": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveCreateMembersCustomResourceProviderLogGroup8A5563BD", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::DetectiveCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyDetectorConfigDD64B103": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderLogGroup0E4B1900", - "GuardDutyMembersD34CA003", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9", - "Arn", - ], - }, - "enableEksProtection": true, - "enableS3Protection": true, - "exportFrequency": "FIFTEEN_MINUTES", - }, - "Type": "Custom::GuardDutyUpdateDetector", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyMembersD34CA003": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyCreateMembersCustomResourceProviderLogGroupB5134860", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673", - "Arn", - ], - }, - "autoEnableOrgMembers": false, - "enableEksProtection": true, - "enableS3Protection": true, - "guardDutyMemberAccountIds": [ - "111111111111", - "555555555555", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::GuardDutyCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - "HighSnsTopicF69104E5": { - "Properties": { - "DisplayName": "AWS Accelerator - High Notifications", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsKeyDADF94B1", - "Arn", - ], - }, - "TopicName": "aws-accelerator-HighNotifications", - }, - "Type": "AWS::SNS::Topic", - }, - "HighSnsTopicPolicy59BE4137": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": { - "Ref": "HighSnsTopicF69104E5", - }, - "Sid": "0", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": { - "Ref": "HighSnsTopicF69104E5", - }, - "Sid": "1", - }, - { - "Action": "sns:Publish", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "HighSnsTopicF69104E5", - }, - "Sid": "2", - }, - { - "Action": [ - "sns:ListSubscriptionsByTopic", - "sns:ListTagsForResource", - "sns:GetTopicAttributes", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "HighSnsTopicF69104E5", - }, - "Sid": "Allow Organization list topic", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "HighSnsTopicF69104E5", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "HighSnsTopicnotifyhighexamplecomD3D4C272": { - "Properties": { - "Endpoint": "notify-high@example.com", - "Protocol": "email", - "TopicArn": { - "Ref": "HighSnsTopicF69104E5", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "LowSnsTopic53AD0F18": { - "Properties": { - "DisplayName": "AWS Accelerator - Low Notifications", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsKeyDADF94B1", - "Arn", - ], - }, - "TopicName": "aws-accelerator-LowNotifications", - }, - "Type": "AWS::SNS::Topic", - }, - "LowSnsTopicPolicy0C1FEB12": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": { - "Ref": "LowSnsTopic53AD0F18", - }, - "Sid": "0", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": { - "Ref": "LowSnsTopic53AD0F18", - }, - "Sid": "1", - }, - { - "Action": "sns:Publish", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "LowSnsTopic53AD0F18", - }, - "Sid": "2", - }, - { - "Action": [ - "sns:ListSubscriptionsByTopic", - "sns:ListTagsForResource", - "sns:GetTopicAttributes", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "LowSnsTopic53AD0F18", - }, - "Sid": "Allow Organization list topic", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "LowSnsTopic53AD0F18", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "LowSnsTopicnotifylowexamplecom00FF09F7": { - "Properties": { - "Endpoint": "notify-low@example.com", - "Protocol": "email", - "TopicArn": { - "Ref": "LowSnsTopic53AD0F18", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "MacieMembers1B6840B4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieCreateMemberCustomResourceProviderLogGroupB2DBC335", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::MacieCreateMember", - "UpdateReplacePolicy": "Delete", - }, - "MediumSnsTopic267CAB5B": { - "Properties": { - "DisplayName": "AWS Accelerator - Medium Notifications", - "KmsMasterKeyId": { - "Fn::GetAtt": [ - "AcceleratorSnsKeyDADF94B1", - "Arn", - ], - }, - "TopicName": "aws-accelerator-MediumNotifications", - }, - "Type": "AWS::SNS::Topic", - }, - "MediumSnsTopicPolicy0B54F62B": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "cloudwatch.amazonaws.com", - }, - "Resource": { - "Ref": "MediumSnsTopic267CAB5B", - }, - "Sid": "0", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - "Resource": { - "Ref": "MediumSnsTopic267CAB5B", - }, - "Sid": "1", - }, - { - "Action": "sns:Publish", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "MediumSnsTopic267CAB5B", - }, - "Sid": "2", - }, - { - "Action": [ - "sns:ListSubscriptionsByTopic", - "sns:ListTagsForResource", - "sns:GetTopicAttributes", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": { - "Ref": "MediumSnsTopic267CAB5B", - }, - "Sid": "Allow Organization list topic", - }, - ], - "Version": "2012-10-17", - }, - "Topics": [ - { - "Ref": "MediumSnsTopic267CAB5B", - }, - ], - }, - "Type": "AWS::SNS::TopicPolicy", - }, - "MediumSnsTopicnotifymediumexamplecom8D67BD3D": { - "Properties": { - "Endpoint": "notify-medium@example.com", - "Protocol": "email", - "TopicArn": { - "Ref": "MediumSnsTopic267CAB5B", - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "PutS3Encryption383DF0CE": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Enables Encryption on S3 Bucket", - "mainSteps": [ - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "PutBucketEncryption", - "Bucket": "{{BucketName}}", - "ServerSideEncryptionConfiguration": { - "Rules": [ - { - "ApplyServerSideEncryptionByDefault": { - "KMSMasterKeyID": "{{KMSMasterKey}}", - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "Service": "s3", - }, - "isEnd": true, - "name": "PutBucketEncryption", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "default": "", - "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", - "type": "String", - }, - "BucketName": { - "description": "(Required) The name of the S3 Bucket whose content will be encrypted.", - "type": "String", - }, - "KMSMasterKey": { - "description": "(Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption.", - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "Put-S3-Encryption", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "PutS3EncryptionShareDocumentF7F3A94E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "PutS3Encryption383DF0CE", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "ResourcePolicyEnforcementRemediationDocumentAwsAcceleratorAttachResourceBasedPolicy51D68FB6": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Resource-based Policy", - "mainSteps": [ - { - "action": "aws:invokeLambdaFunction", - "inputs": { - "FunctionName": "{{ FunctionName }}", - "InputPayload": { - "ConfigRuleName": "{{ ConfigRuleName }}", - "ResourceId": "{{ ResourceId }}", - }, - "InvocationType": "RequestResponse", - }, - "name": "invokeLambdaFunction", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "type": "String", - }, - "ConfigRuleName": { - "type": "String", - }, - "FunctionName": { - "type": "String", - }, - "ResourceId": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "AWSAccelerator-Attach-Resource-Based-Policy", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "ResourcePolicyEnforcementRemediationDocumentAwsAcceleratorAttachResourceBasedPolicyShareDocument7708A01C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "222222222222", - "333333333333", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "ResourcePolicyEnforcementRemediationDocumentAwsAcceleratorAttachResourceBasedPolicy51D68FB6", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubMembers2A2B77C4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubCreateMembersCustomResourceProviderLogGroup51212673", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3", - "Arn", - ], - }, - "autoEnableOrgMembers": true, - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - "securityHubMemberAccountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - }, - "Type": "Custom::SecurityHubCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubRegionAggregation0CC69E1B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderLogGroup241F2158", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::SecurityHubRegionAggregation", - "UpdateReplacePolicy": "Delete", - }, - "SsmElbEnableLoggingDB91F105": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Enable logging on Elastic Load-Balancer", - "mainSteps": [ - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "get_caller_identity", - "Service": "sts", - }, - "name": "getAccount", - "outputs": [ - { - "Name": "Id", - "Selector": "$.Account", - "Type": "String", - }, - ], - }, - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "describe_load_balancers", - "LoadBalancerArns": [ - "{{ LoadBalancerArn }}", - ], - "Service": "elbv2", - }, - "name": "getLoadBalancer", - "outputs": [ - { - "Name": "Name", - "Selector": "$.LoadBalancers[0].LoadBalancerName", - "Type": "String", - }, - ], - }, - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "modify_load_balancer_attributes", - "Attributes": [ - { - "Key": "access_logs.s3.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.bucket", - "Value": "{{ LogDestination }}", - }, - { - "Key": "access_logs.s3.prefix", - "Value": "elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}", - }, - ], - "LoadBalancerArn": "{{ LoadBalancerArn }}", - "Service": "elbv2", - }, - "name": "enableLogging", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "type": "String", - }, - "LoadBalancerArn": { - "type": "String", - }, - "LogDestination": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "SSM-ELB-Enable-Logging", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "SsmElbEnableLoggingShareDocument16727B30": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "SsmElbEnableLoggingDB91F105", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityAuditStack-222222222222-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamOrganizationAuditManagerPublishingDestinationBucketArn488415E7": { - "Properties": { - "Name": "/accelerator/organization/security/auditManager/publishing-destination/bucket-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityAuditStack-222222222222-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "WafEnableLoggingB6AE5B82": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Adds logging to non-compliant WebACLs", - "mainSteps": [ - { - "action": "aws:executeScript", - "inputs": { - "Handler": "handler", - "InputPayload": { - "WebACLId": "{{ WebACLId }}", - }, - "Runtime": "python3.7", - "Script": "REPLACED-JSON-PATH.json", - }, - "name": "performRemediation", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "type": "String", - }, - "WebACLId": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "WAF-Enable-Logging", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "WafEnableLoggingShareDocument11FB34C8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "WafEnableLoggingB6AE5B82", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`delegatedAdminStack Construct(SecurityAuditStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/s3/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorCentralLogBucketKeyLookup26F8C418": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-us-west-2-CentralBucket-KeyArnParam-Role", - ], - ], - }, - "invokingAccountID": "222222222222", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AccessAnalyzer": { - "Properties": { - "Type": "ORGANIZATION", - }, - "Type": "AWS::AccessAnalyzer::Analyzer", - }, - "AttachIamInstanceProfileC858EC06": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Associate AWS Iam Instance Profile to EC2 Instance", - "mainSteps": [ - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "associate_iam_instance_profile", - "IamInstanceProfile": { - "Name": "{{ IamInstanceProfile }}", - }, - "InstanceId": "{{ InstanceId }}", - "Service": "ec2", - }, - "name": "associateIamProfile", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "type": "String", - }, - "IamInstanceProfile": { - "type": "String", - }, - "InstanceId": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "Attach-IAM-Instance-Profile", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "AttachIamInstanceProfileShareDocumentD1F87A43": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "AttachIamInstanceProfileC858EC06", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "AttachIamRolePolicy19A43F7E": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "IAM Role Policy", - "mainSteps": [ - { - "action": "aws:executeScript", - "inputs": { - "Handler": "script_handler", - "InputPayload": { - "AWSManagedPolicies": "{{ AWSManagedPolicies }}", - "CustomerManagedPolicies": "{{ CustomerManagedPolicies }}", - "ResourceId": "{{ ResourceId }}", - }, - "Runtime": "python3.7", - "Script": "import boto3 -partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] -iam = boto3.client("iam") -config = boto3.client("config") -def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - )", - }, - "name": "attachPolicy", - }, - ], - "parameters": { - "AWSManagedPolicies": { - "type": "StringList", - }, - "AutomationAssumeRole": { - "type": "String", - }, - "CustomerManagedPolicies": { - "default": [], - "minItems": 0, - "type": "StringList", - }, - "ResourceId": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "Attach-IAM-Role-Policy", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "AttachIamRolePolicyShareDocumentF5C9E6C7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "AttachIamRolePolicy19A43F7E", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerDefaultReportsDestinationAFD20D60": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderLogGroupF5AC3566", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433", - "Arn", - ], - }, - "bucket": { - "Fn::Join": [ - "", - [ - "s3://'", - { - "Ref": "AuditManagerPublishingDestinationBucket74974FCF", - }, - "/audit-manager/222222222222/", - ], - ], - }, - "defaultReportsDestinationType": "S3", - "kmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "region": "us-east-1", - }, - "Type": "Custom::AuditManagerCreateDefaultReportsDestination", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerPublishingDestinationBucket74974FCF": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-auditmgr-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "Tags": [ - { - "Key": "aws-cdk:auto-audit-manager-access-bucket", - "Value": "true", - }, - { - "Key": "aws-cdk:auto-auditManager-access-bucket", - "Value": "true", - }, - ], - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy6D87750E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ditManagerPublishingDestinationBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleDefaultPolicy6D87750E", - "Roles": [ - { - "Ref": "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleE0509DD9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleE0509DD9": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - "Tags": [ - { - "Key": "aws-cdk:auto-audit-manager-access-bucket", - "Value": "true", - }, - { - "Key": "aws-cdk:auto-auditManager-access-bucket", - "Value": "true", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationECF1B287": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": "333333333333", - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2", - ], - ], - }, - "destinationBucketKeyArn": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucketAuditManagerPublishingDestinationBucketReplicationAwsAcceleratorCentralLogs333333333333UsWest2ReplicationRoleE0509DD9", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "AuditManagerPublishingDestinationBucket74974FCF", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "AuditManagerPublishingDestinationBucketPolicy3992854B": { - "Properties": { - "Bucket": { - "Ref": "AuditManagerPublishingDestinationBucket74974FCF", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "Service": "auditmanager.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:PutObject", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "o-asdf123456", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "Allow Organization principals to use of the bucket", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "ControlTowerNotificationsForwarderFunctionAllowInvokeAWSAcceleratorSecurityAuditStack222222222222useast1ControlTowerSNSTopic2C923A0CF50B8C0E": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "ControlTowerNotificationsForwarderFunctionFA79B131", - "Arn", - ], - }, - "Principal": "sns.amazonaws.com", - "SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:222222222222:aws-controltower-AggregateSecurityNotifications", - ], - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "ControlTowerNotificationsForwarderFunctionControlTowerSNSTopicF1309D6C": { - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "ControlTowerNotificationsForwarderFunctionFA79B131", - "Arn", - ], - }, - "Protocol": "lambda", - "Region": "us-east-1", - "TopicArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:222222222222:aws-controltower-AggregateSecurityNotifications", - ], - ], - }, - }, - "Type": "AWS::SNS::Subscription", - }, - "ControlTowerNotificationsForwarderFunctionFA79B131": { - "DependsOn": [ - "ControlTowerNotificationsForwarderFunctionServiceRoleDefaultPolicyEBACE9AF", - "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to forward ControlTower notifications to management account", - "Environment": { - "Variables": { - "SNS_TOPIC_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-ControlTowerNotification", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 120, - }, - "Type": "AWS::Lambda::Function", - }, - "ControlTowerNotificationsForwarderFunctionServiceRoleDefaultPolicyEBACE9AF": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Require access to all keys in management account", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-ControlTowerNotification", - ], - ], - }, - "Sid": "sns", - }, - { - "Action": [ - "kms:DescribeKey", - "kms:GenerateDataKey", - "kms:Decrypt", - "kms:Encrypt", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:us-east-1:111111111111:key/*", - ], - ], - }, - "Sid": "kms", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ControlTowerNotificationsForwarderFunctionServiceRoleDefaultPolicyEBACE9AF", - "Roles": [ - { - "Ref": "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ControlTowerNotificationsForwarderFunctionServiceRoleE83D121D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider lambda role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433": { - "DependsOn": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderLogGroupF5AC3566": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "auditmanager:UpdateSettings", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerCreatePublishingDestinationCommandTaskAuditManagerActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D": { - "DependsOn": [ - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveCreateMembersCustomResourceProviderLogGroup8A5563BD": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "detective:ListOrganizationAdminAccounts", - "detective:UpdateOrganizationConfiguration", - "detective:CreateMembers", - "detective:DeleteMembers", - "detective:DisassociateMembership", - "detective:ListMembers", - "detective:ListGraphs", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveCreateMembersTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": [ - "organizations:ListAccounts", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "OrganisationsListDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1": { - "DependsOn": [ - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderLogGroupDF150426": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveConfigureOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:UpdateOrganizationConfiguration", - "detective:ListGraphs", - "detective:ListMembers", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveUpdateGraphTaskDetectiveActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673": { - "DependsOn": [ - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderLogGroupB5134860": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreateMembersTaskOrganizationAction", - }, - { - "Action": [ - "guardDuty:ListDetectors", - "guardDuty:ListOrganizationAdminAccounts", - "guardDuty:UpdateOrganizationConfiguration", - "guardduty:CreateMembers", - "guardduty:DeleteMembers", - "guardduty:DisassociateMembers", - "guardduty:ListDetectors", - "guardduty:ListMembers", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreateMembersTaskGuardDutyActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceLinkedRoleSecurityHub", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9": { - "DependsOn": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderLogGroup0E4B1900": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "guardduty:ListDetectors", - "guardduty:ListMembers", - "guardduty:UpdateDetector", - "guardduty:UpdateMemberDetectors", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyUpdateDetectorTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB": { - "DependsOn": [ - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieCreateMemberCustomResourceProviderLogGroupB2DBC335": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieCreateMemberTaskOrganizationAction", - }, - { - "Action": [ - "macie2:CreateMember", - "macie2:DeleteMember", - "macie2:DescribeOrganizationConfiguration", - "macie2:DisassociateMember", - "macie2:EnableMacie", - "macie2:GetMacieSession", - "macie2:ListMembers", - "macie2:UpdateOrganizationConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieCreateMemberTaskMacieActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87": { - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DescribeDocumentPermission", - "ssm:ModifyDocumentPermission", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/*", - ], - ], - }, - "Sid": "ShareDocumentActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3": { - "DependsOn": [ - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderLogGroup51212673": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskOrganizationAction", - }, - { - "Action": [ - "securityhub:CreateMembers", - "securityhub:DeleteMembers", - "securityhub:DisassociateMembers", - "securityhub:EnableSecurityHub", - "securityhub:ListMembers", - "securityhub:UpdateOrganizationConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A": { - "DependsOn": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderLogGroup241F2158": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "securityhub:CreateFindingAggregator", - "securityhub:UpdateFindingAggregator", - "securityhub:DeleteFindingAggregator", - "securityhub:ListFindingAggregators", - "securityhub:GetFindingAggregator", - "securityhub:DescribeHub", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubModifyRegionAggregation", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-222222222222-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DetectiveGraphConfig248C4B9F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveUpdateGraphCustomResourceProviderLogGroupDF150426", - "DetectiveMembers42A16137", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1", - "Arn", - ], - }, - "region": "us-east-1", - }, - "Type": "Custom::DetectiveUpdateGraph", - "UpdateReplacePolicy": "Delete", - }, - "DetectiveMembers42A16137": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveCreateMembersCustomResourceProviderLogGroup8A5563BD", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::DetectiveCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyDetectorConfigDD64B103": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderLogGroup0E4B1900", - "GuardDutyMembersD34CA003", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9", - "Arn", - ], - }, - "enableEksProtection": false, - "enableS3Protection": true, - "exportFrequency": "FIFTEEN_MINUTES", - }, - "Type": "Custom::GuardDutyUpdateDetector", - "UpdateReplacePolicy": "Delete", - }, - "GuardDutyMembersD34CA003": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyCreateMembersCustomResourceProviderLogGroupB5134860", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673", - "Arn", - ], - }, - "autoEnableOrgMembers": true, - "enableEksProtection": false, - "enableS3Protection": true, - "guardDutyMemberAccountIds": [], - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::GuardDutyCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - "MacieMembers1B6840B4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieCreateMemberCustomResourceProviderLogGroupB2DBC335", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB", - "Arn", - ], - }, - "adminAccountId": "222222222222", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::MacieCreateMember", - "UpdateReplacePolicy": "Delete", - }, - "PutS3Encryption383DF0CE": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Enables Encryption on S3 Bucket", - "mainSteps": [ - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "PutBucketEncryption", - "Bucket": "{{BucketName}}", - "ServerSideEncryptionConfiguration": { - "Rules": [ - { - "ApplyServerSideEncryptionByDefault": { - "KMSMasterKeyID": "{{KMSMasterKey}}", - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "Service": "s3", - }, - "isEnd": true, - "name": "PutBucketEncryption", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "default": "", - "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", - "type": "String", - }, - "BucketName": { - "description": "(Required) The name of the S3 Bucket whose content will be encrypted.", - "type": "String", - }, - "KMSMasterKey": { - "description": "(Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption.", - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "Put-S3-Encryption", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "PutS3EncryptionShareDocumentF7F3A94E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "PutS3Encryption383DF0CE", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubMembers2A2B77C4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubCreateMembersCustomResourceProviderLogGroup51212673", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3", - "Arn", - ], - }, - "autoEnableOrgMembers": true, - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - "securityHubMemberAccountIds": [], - }, - "Type": "Custom::SecurityHubCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubRegionAggregation0CC69E1B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderLogGroup241F2158", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - }, - "Type": "Custom::SecurityHubRegionAggregation", - "UpdateReplacePolicy": "Delete", - }, - "SsmElbEnableLoggingDB91F105": { - "Properties": { - "Content": { - "assumeRole": "{{ AutomationAssumeRole }}", - "description": "Enable logging on Elastic Load-Balancer", - "mainSteps": [ - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "get_caller_identity", - "Service": "sts", - }, - "name": "getAccount", - "outputs": [ - { - "Name": "Id", - "Selector": "$.Account", - "Type": "String", - }, - ], - }, - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "describe_load_balancers", - "LoadBalancerArns": [ - "{{ LoadBalancerArn }}", - ], - "Service": "elbv2", - }, - "name": "getLoadBalancer", - "outputs": [ - { - "Name": "Name", - "Selector": "$.LoadBalancers[0].LoadBalancerName", - "Type": "String", - }, - ], - }, - { - "action": "aws:executeAwsApi", - "inputs": { - "Api": "modify_load_balancer_attributes", - "Attributes": [ - { - "Key": "access_logs.s3.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.bucket", - "Value": "{{ LogDestination }}", - }, - { - "Key": "access_logs.s3.prefix", - "Value": "elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}", - }, - ], - "LoadBalancerArn": "{{ LoadBalancerArn }}", - "Service": "elbv2", - }, - "name": "enableLogging", - }, - ], - "parameters": { - "AutomationAssumeRole": { - "type": "String", - }, - "LoadBalancerArn": { - "type": "String", - }, - "LogDestination": { - "type": "String", - }, - }, - "schemaVersion": "0.3", - }, - "DocumentType": "Automation", - "Name": "SSM-ELB-Enable-Logging", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "SsmElbEnableLoggingShareDocument16727B30": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "333333333333", - "222222222222", - "444444444444", - "555555555555", - "666666666666", - ], - "name": { - "Ref": "SsmElbEnableLoggingDB91F105", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityAuditStack-222222222222-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamOrganizationAuditManagerPublishingDestinationBucketArn488415E7": { - "Properties": { - "Name": "/accelerator/organization/security/auditManager/publishing-destination/bucket-arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "AuditManagerPublishingDestinationBucket74974FCF", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityAuditStack-222222222222-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-resources-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-resources-stack.test.ts.snap deleted file mode 100644 index 1818129..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-resources-stack.test.ts.snap +++ /dev/null @@ -1,7982 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityResourcesStack Construct(SecurityResourcesStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/s3/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmssnstopickeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/snstopic/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorAlbHttpDropInvalidHeaderEnabled699B715A": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-alb-http-drop-invalid-header-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancingV2::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ALB_HTTP_DROP_INVALID_HEADER_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorApiGwCacheEnabledAndEncrypted090286F1": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-api-gw-cache-enabled-and-encrypted", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ApiGateway::Stage", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "API_GW_CACHE_ENABLED_AND_ENCRYPTED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorApiGwExecutionLoggingEnabledDA00B249": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-api-gw-execution-logging-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ApiGateway::Stage", - "AWS::ApiGatewayV2::Stage", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "API_GW_EXECUTION_LOGGING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorAttachEc2InstanceProfile1DAEB489": { - "DependsOn": [ - "AcceleratorAttachEc2InstanceProfileFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI5AF7FE49", - "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-attach-ec2-instance-profile", - "Description": "Custom rule for checking EC2 instance IAM profile attachment", - "InputParameters": {}, - "MaximumExecutionFrequency": "Six_Hours", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MaximumExecutionFrequency": "Six_Hours", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorAttachEc2InstanceProfileFunction2F25082F": { - "DependsOn": [ - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS Config custom rule function used for "accelerator-attach-ec2-instance-profile" rule", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 3, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorAttachEc2InstanceProfileFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI5AF7FE49": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Config custom rule needs managed readonly access policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorAttachEc2InstanceProfileLambdaRolePolicy9BC7815F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule custom lambda role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorAttachEc2InstanceProfileLambdaRolePolicy9BC7815F", - "Roles": [ - { - "Ref": "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorAttachEc2InstanceProfileLogGroup19DCB538": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorAttachEc2InstanceProfileRemediation": { - "DependsOn": [ - "AcceleratorAttachEc2InstanceProfile1DAEB489", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-attach-ec2-instance-profile", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileRemediationRole0804271B", - "Arn", - ], - }, - ], - }, - }, - "IamInstanceProfile": { - "StaticValue": { - "Values": [ - "EC2-Default-SSM-AD-Role", - ], - }, - }, - "InstanceId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Instance-Profile", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorAttachEc2InstanceProfileRemediationRole0804271B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorAttachEc2InstanceProfileRemediationRoleDefaultPolicy820C0688": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Instance-Profile", - ], - ], - }, - }, - { - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorAttachEc2InstanceProfileRemediationRoleDefaultPolicy820C0688", - "Roles": [ - { - "Ref": "AcceleratorAttachEc2InstanceProfileRemediationRole0804271B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailB1588991": { - "DependsOn": [ - "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleDefaultPolicyD0C1A29E", - "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleD0DD6F18", - ], - "Properties": { - "CloudWatchLogsLogGroupArn": { - "Fn::GetAtt": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - "Arn", - ], - }, - "CloudWatchLogsRoleArn": { - "Fn::GetAtt": [ - "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleD0DD6F18", - "Arn", - ], - }, - "EnableLogFileValidation": true, - "EventSelectors": [ - { - "IncludeManagementEvents": true, - "ReadWriteType": "All", - }, - { - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::", - ], - ], - }, - ], - }, - ], - }, - { - "DataResources": [ - { - "Type": "AWS::Lambda::Function", - "Values": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda", - ], - ], - }, - ], - }, - ], - }, - ], - "IncludeGlobalServiceEvents": true, - "InsightSelectors": [], - "IsLogging": true, - "IsMultiRegionTrail": true, - "IsOrganizationTrail": false, - "KMSKeyId": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - "S3BucketName": "existing-central-log-bucket", - "S3KeyPrefix": "cloudtrail-AWSAccelerator-Account-CloudTrail", - "TrailName": "AWSAccelerator-CloudTrail-AWSAccelerator-Account-CloudTrail", - }, - "Type": "AWS::CloudTrail::Trail", - }, - "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleD0DD6F18": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleDefaultPolicyD0C1A29E": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutLogEvents", - "logs:CreateLogStream", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleDefaultPolicyD0C1A29E", - "Roles": [ - { - "Ref": "AcceleratorCloudTrailAWSAcceleratorAccountCloudTrailLogsRoleD0DD6F18", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorCloudtrailEnabled08B9BEEA": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudtrail-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUD_TRAIL_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudtrailS3DataeventsEnabledF8E06B95": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudtrail-s3-dataevents-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDTRAIL_S3_DATAEVENTS_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudtrailSecurityTrailEnabledDBDDD05E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudtrail-security-trail-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDTRAIL_SECURITY_TRAIL_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudwatchAlarmActionCheckBBDDF3B7": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudwatch-alarm-action-check", - "InputParameters": { - "alarmActionRequired": "TRUE", - "insufficientDataActionRequired": "TRUE", - "okActionRequired": "FALSE", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::CloudWatch::Alarm", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDWATCH_ALARM_ACTION_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudwatchLogGroupEncrypted233263DC": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudwatch-log-group-encrypted", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDWATCH_LOG_GROUP_ENCRYPTED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCwLoggroupRetentionPeriodCheck7A9796B7": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cw-loggroup-retention-period-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CW_LOGGROUP_RETENTION_PERIOD_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorDuplicateS3RuleAFE3802F": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-duplicate-s3-rule", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorDuplicateS3RuleRemediation": { - "DependsOn": [ - "AcceleratorDuplicateS3RuleAFE3802F", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-duplicate-s3-rule", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorDuplicateS3RuleRemediationRole22D2D45F", - "Arn", - ], - }, - ], - }, - }, - "BucketName": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - "SSEAlgorithm": { - "StaticValue": { - "Values": [ - "AES256", - ], - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": "AWS-EnableS3BucketEncryption", - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorDuplicateS3RuleRemediationRole22D2D45F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorDuplicateS3RuleRemediationRoleDefaultPolicy6664FFB9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/AWS-EnableS3BucketEncryption", - ], - ], - }, - }, - { - "Action": "s3:PutBucketEncryption", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorDuplicateS3RuleRemediationRoleDefaultPolicy6664FFB9", - "Roles": [ - { - "Ref": "AcceleratorDuplicateS3RuleRemediationRole22D2D45F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorDynamodbTableEncryptedKms3EE6CD77": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-dynamodb-table-encrypted-kms", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::DynamoDB::Table", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "DYNAMODB_TABLE_ENCRYPTED_KMS", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceDetailedMonitoringEnabled9F032168": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instance-detailed-monitoring-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EC2_INSTANCE_DETAILED_MONITORING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceNoPublicIpB8B5F3AD": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instance-no-public-ip", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EC2_INSTANCE_NO_PUBLIC_IP", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceProfilePermission16FE7EFE": { - "DependsOn": [ - "AcceleratorEc2InstanceProfilePermissionFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI407CB1D2", - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instance-profile-permission", - "Description": "Custom role to remediate EC2 instance profile permission", - "InputParameters": { - "AWSManagedPolicies": "AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy", - "ResourceId": "RESOURCE_ID", - }, - "MaximumExecutionFrequency": "Six_Hours", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::Role", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MaximumExecutionFrequency": "Six_Hours", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331": { - "DependsOn": [ - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS Config custom rule function used for "accelerator-ec2-instance-profile-permission" rule", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 3, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorEc2InstanceProfilePermissionFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI407CB1D2": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Config custom rule needs managed readonly access policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorEc2InstanceProfilePermissionLambdaRolePolicy8942B625": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule custom lambda role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:Get*", - "iam:List*", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorEc2InstanceProfilePermissionLambdaRolePolicy8942B625", - "Roles": [ - { - "Ref": "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorEc2InstanceProfilePermissionLogGroupE31085F3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorEc2InstanceProfilePermissionRemediation": { - "DependsOn": [ - "AcceleratorEc2InstanceProfilePermission16FE7EFE", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-ec2-instance-profile-permission", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AWSManagedPolicies": { - "StaticValue": { - "Values": [ - "AmazonSSMManagedInstanceCore", - "AmazonSSMDirectoryServiceAccess", - "CloudWatchAgentServerPolicy", - ], - }, - }, - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionRemediationRoleDB841759", - "Arn", - ], - }, - ], - }, - }, - "ResourceId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Role-Policy", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorEc2InstanceProfilePermissionRemediationRoleDB841759": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorEc2InstanceProfilePermissionRemediationRoleDefaultPolicy9EC721D8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Role-Policy", - ], - ], - }, - }, - { - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorEc2InstanceProfilePermissionRemediationRoleDefaultPolicy9EC721D8", - "Roles": [ - { - "Ref": "AcceleratorEc2InstanceProfilePermissionRemediationRoleDB841759", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorEc2InstancesInVpcFCB9B1B9": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instances-in-vpc", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "INSTANCES_IN_VPC", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2VolumeInuseCheck934E830D": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-volume-inuse-check", - "InputParameters": { - "deleteOnTermination": "TRUE", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Volume", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EC2_VOLUME_INUSE_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElasticacheRedisClusterAutomaticBackupCheck7CFACFC6": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elasticache-redis-cluster-automatic-backup-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElasticsearchInVpcOnly1FFB209E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elasticsearch-in-vpc-only", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELASTICSEARCH_IN_VPC_ONLY", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElasticsearchNodeToNodeEncryptionCheck7F672889": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elasticsearch-node-to-node-encryption-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::Elasticsearch::Domain", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbAcmCertificateRequired7DFF09EF": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-acm-certificate-required", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_ACM_CERTIFICATE_REQUIRED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbCrossZoneLoadBalancingEnabled4933AD2A": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-cross-zone-load-balancing-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbDeletionProtectionEnabled6A44EA2D": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-deletion-protection-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancingV2::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_DELETION_PROTECTION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbLoggingEnabledD68765E9": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-logging-enabled", - "InputParameters": { - "s3BucketNames": "existing-elb-logs-bucket-111111111111-us-east-1", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - "AWS::ElasticLoadBalancingV2::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_LOGGING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbLoggingEnabledRemediation": { - "DependsOn": [ - "AcceleratorElbLoggingEnabledD68765E9", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-elb-logging-enabled", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorElbLoggingEnabledRemediationRole852D593A", - "Arn", - ], - }, - ], - }, - }, - "LoadBalancerArn": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - "LogDestination": { - "StaticValue": { - "Values": [ - "existing-elb-logs-bucket-111111111111-us-east-1", - ], - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/SSM-ELB-Enable-Logging", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorElbLoggingEnabledRemediationRole852D593A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorElbLoggingEnabledRemediationRoleDefaultPolicy31D14E3C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/SSM-ELB-Enable-Logging", - ], - ], - }, - }, - { - "Action": [ - "elasticloadbalancing:DescribeLoadBalancers", - "elasticloadbalancing:DescribeLoadBalancerAttributes", - "elasticloadbalancing:ModifyLoadBalancerAttributes", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorElbLoggingEnabledRemediationRoleDefaultPolicy31D14E3C", - "Roles": [ - { - "Ref": "AcceleratorElbLoggingEnabledRemediationRole852D593A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorElbTlsHttpsListenersOnly59A28904": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-tls-https-listeners-only", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_TLS_HTTPS_LISTENERS_ONLY", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEmrKerberosEnabled27B4E237": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-emr-kerberos-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EMR_KERBEROS_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorGuarddutyNonArchivedFindings32ED20C9": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-guardduty-non-archived-findings", - "InputParameters": { - "daysHighSev": "1", - "daysLowSev": "30", - "daysMediumSev": "7", - }, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "GUARDDUTY_NON_ARCHIVED_FINDINGS", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamGroupHasUsersCheck5E15FE54": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-group-has-users-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::Group", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_GROUP_HAS_USERS_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamNoInlinePolicyCheckAE16B56C": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-no-inline-policy-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::User", - "AWS::IAM::Role", - "AWS::IAM::Group", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_NO_INLINE_POLICY_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamUserGroupMembershipCheck5D2DBD69": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-user-group-membership-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::User", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_USER_GROUP_MEMBERSHIP_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamUserMfaEnabled119AE0DF": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-user-mfa-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_USER_MFA_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-CrossAccountSsmParameterShare", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorInternetGatewayAuthorizedVpcOnly75CCD662": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-internet-gateway-authorized-vpc-only", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::InternetGateway", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorKmsCmkNotScheduledForDeletionF3796D4E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-kms-cmk-not-scheduled-for-deletion", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::KMS::Key", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "KMS_CMK_NOT_SCHEDULED_FOR_DELETION", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorLambdaInsideVpc68BE0CC7": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-lambda-inside-vpc", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::Lambda::Function", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "LAMBDA_INSIDE_VPC", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorRdsLoggingEnabled02B0690C": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-rds-logging-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::RDS::DBInstance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "RDS_LOGGING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorRedshiftClusterConfigurationCheckC9D7559D": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-redshift-cluster-configuration-check", - "InputParameters": { - "clusterDbEncrypted": "TRUE", - "loggingEnabled": "TRUE", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::Redshift::Cluster", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "REDSHIFT_CLUSTER_CONFIGURATION_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketDefaultLockEnabled1E1E5D5A": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-default-lock-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_DEFAULT_LOCK_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketPolicyGranteeCheckA238FAF4": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-policy-grantee-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_POLICY_GRANTEE_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketReplicationEnabled54B924AB": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-replication-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_REPLICATION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledB22E11D0": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-server-side-encryption-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledRemediation": { - "DependsOn": [ - "AcceleratorS3BucketServerSideEncryptionEnabledB22E11D0", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-s3-bucket-server-side-encryption-enabled", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleC1D55E4D", - "Arn", - ], - }, - ], - }, - }, - "BucketName": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - "KMSMasterKey": { - "StaticValue": { - "Values": [ - { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Put-S3-Encryption", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleC1D55E4D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleDefaultPolicy8B79DC01": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Put-S3-Encryption", - ], - ], - }, - }, - { - "Action": [ - "s3:GetEncryptionConfiguration", - "s3:PutEncryptionConfiguration", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalArn": [ - "arn:aws:iam::*:role/AWSAccelerator-SecuritySt-*", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleDefaultPolicy8B79DC01", - "Roles": [ - { - "Ref": "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleC1D55E4D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorS3BucketVersioningEnabledA2D78DD3": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-versioning-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_VERSIONING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorSagemakerEndpointConfigurationKmsKeyConfigured29A26847": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-sagemaker-endpoint-configuration-kms-key-configured", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorSagemakerNotebookInstanceKmsKeyConfigured98505444": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-sagemaker-notebook-instance-kms-key-configured", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorSecurityhubEnabled25B1DE1B": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-securityhub-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "SECURITYHUB_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorTargetDocument01F99B0122": { - "DependsOn": [ - "AcceleratorTargetDocument01FunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI866B82A5", - "AcceleratorTargetDocument01Function89A6815D", - "AcceleratorTargetDocument01FunctionServiceRoleCDB753E0", - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-target-document-01", - "Description": "Custom rule for testing target document remediation", - "InputParameters": {}, - "MaximumExecutionFrequency": "Six_Hours", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MaximumExecutionFrequency": "Six_Hours", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "AcceleratorTargetDocument01Function89A6815D", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorTargetDocument01Function89A6815D": { - "DependsOn": [ - "AcceleratorTargetDocument01FunctionServiceRoleCDB753E0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS Config custom rule function used for "accelerator-target-document-01" rule", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorTargetDocument01FunctionServiceRoleCDB753E0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 3, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorTargetDocument01FunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI866B82A5": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorTargetDocument01Function89A6815D", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorTargetDocument01FunctionServiceRoleCDB753E0": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Config custom rule needs managed readonly access policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorTargetDocument01LambdaRolePolicy72C0AA10": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule custom lambda role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorTargetDocument01LambdaRolePolicy72C0AA10", - "Roles": [ - { - "Ref": "AcceleratorTargetDocument01FunctionServiceRoleCDB753E0", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorTargetDocument01LogGroup196D5782": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorTargetDocument01Function89A6815D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorTargetDocument01Remediation": { - "DependsOn": [ - "AcceleratorTargetDocument01F99B0122", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-target-document-01", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorTargetDocument01RemediationRoleB6FD3A5D", - "Arn", - ], - }, - ], - }, - }, - "IamInstanceProfile": { - "StaticValue": { - "Values": [ - "EC2-Default-SSM-AD-Role", - ], - }, - }, - "InstanceId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Instance-Profile", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorTargetDocument01RemediationFunction15F7D4F0": { - "DependsOn": [ - "AcceleratorTargetDocument01RemediationRoleDefaultPolicyD5AA3685", - "AcceleratorTargetDocument01RemediationRoleB6FD3A5D", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Function used in Attach-IAM-Instance-Profile SSM document for "accelerator-target-document-01" custom config rule to remediation", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorTargetDocument01RemediationRoleB6FD3A5D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorTargetDocument01RemediationLogGroupDCA10183": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorTargetDocument01RemediationFunction15F7D4F0", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorTargetDocument01RemediationRoleB6FD3A5D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorTargetDocument01RemediationRoleDefaultPolicyD5AA3685": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Instance-Profile", - ], - ], - }, - }, - { - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorTargetDocument01RemediationRoleDefaultPolicyD5AA3685", - "Roles": [ - { - "Ref": "AcceleratorTargetDocument01RemediationRoleB6FD3A5D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorTargetDocument01tagsFBAE2056": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomConfigServiceTagsCustomResourceProviderLogGroupF7260892", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomConfigServiceTagsCustomResourceProviderHandler159E7D96", - "Arn", - ], - }, - "resourceArn": { - "Fn::GetAtt": [ - "AcceleratorTargetDocument01F99B0122", - "Arn", - ], - }, - "tags": [ - { - "Key": "key", - "Value": "value", - }, - ], - }, - "Type": "Custom::ConfigServiceTags", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorVpcSgOpenOnlyToAuthorizedPorts9EF29973": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-vpc-sg-open-only-to-authorized-ports", - "InputParameters": { - "authorizedTcpPorts": "443", - "authorizedUdpPorts": "1020-1025", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::SecurityGroup", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorVpcVpn2TunnelsUpBED0249E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-vpc-vpn-2-tunnels-up", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::VPNConnection", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "VPC_VPN_2_TUNNELS_UP", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorWafLoggingEnabledC2F2415E": { - "DependsOn": [ - "AcceleratorWafLoggingEnabledFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI742BC256", - "AcceleratorWafLoggingEnabledFunctionE69C34AD", - "AcceleratorWafLoggingEnabledFunctionServiceRole3388E650", - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-waf-logging-enabled", - "Description": "Custom rule for checking WAF logging enabled", - "InputParameters": {}, - "MaximumExecutionFrequency": "Six_Hours", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::WAF::WebACL", - "AWS::WAFRegional::WebACL", - "AWS::WAFv2::WebACL", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MaximumExecutionFrequency": "Six_Hours", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "AcceleratorWafLoggingEnabledFunctionE69C34AD", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorWafLoggingEnabledFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI742BC256": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorWafLoggingEnabledFunctionE69C34AD", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorWafLoggingEnabledFunctionE69C34AD": { - "DependsOn": [ - "AcceleratorWafLoggingEnabledFunctionServiceRole3388E650", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS Config custom rule function used for "accelerator-waf-logging-enabled" rule", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorWafLoggingEnabledFunctionServiceRole3388E650", - "Arn", - ], - }, - "Runtime": "python3.12", - "Timeout": 3, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorWafLoggingEnabledFunctionServiceRole3388E650": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Config custom rule needs managed readonly access policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorWafLoggingEnabledLambdaRolePolicy8D9C985E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule custom lambda role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "waf:GetLoggingConfiguration", - "waf:GetWebACL", - "wafv2:GetLoggingConfiguration", - "wafv2:GetWebACL", - "waf-regional:GetLoggingConfiguration", - "waf-regional:GetWebACL", - ], - "Effect": "Allow", - "Resource": [ - "arn:*:waf::*:*", - "arn:*:wafv2:*:*:*/*/*", - "arn:*:waf-regional:*:*:*", - ], - "Sid": "wafconfig", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorWafLoggingEnabledLambdaRolePolicy8D9C985E", - "Roles": [ - { - "Ref": "AcceleratorWafLoggingEnabledFunctionServiceRole3388E650", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorWafLoggingEnabledLogGroupBE48D40E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorWafLoggingEnabledFunctionE69C34AD", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorWafLoggingEnabledRemediation": { - "DependsOn": [ - "AcceleratorWafLoggingEnabledC2F2415E", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-waf-logging-enabled", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorWafLoggingEnabledRemediationRole4997DFE0", - "Arn", - ], - }, - ], - }, - }, - "WebACLId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/WAF-Enable-Logging", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorWafLoggingEnabledRemediationRole4997DFE0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorWafLoggingEnabledRemediationRoleDefaultPolicyA51DB6A8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/WAF-Enable-Logging", - ], - ], - }, - }, - { - "Action": [ - "waf:PutLoggingConfiguration", - "waf:GetLoggingConfiguration", - "waf:GetWebACL", - "wafv2:PutLoggingConfiguration", - "wafv2:GetLoggingConfiguration", - "wafv2:GetWebACL", - "waf-regional:PutLoggingConfiguration", - "waf-regional:GetLoggingConfiguration", - "waf-regional:GetWebACL", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "wafconfig", - }, - { - "Action": "logs:*", - "Effect": "Allow", - "Resource": "*", - "Sid": "logs", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorWafLoggingEnabledRemediationRoleDefaultPolicyA51DB6A8", - "Roles": [ - { - "Ref": "AcceleratorWafLoggingEnabledRemediationRole4997DFE0", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AppTest2CustomCreateLogGroupCustomResourceProviderHandler03798759": { - "DependsOn": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderRole24FD6060", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderRole24FD6060", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AppTest2CustomCreateLogGroupCustomResourceProviderLogGroup7FB49646": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AppTest2CustomCreateLogGroupCustomResourceProviderHandler03798759", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AppTest2CustomCreateLogGroupCustomResourceProviderRole24FD6060": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/App/Test2:*", - ], - ], - }, - }, - { - "Action": [ - "kms:DescribeKey", - "kms:ListKeys", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AppTest2LogGroupCloudWatchLogsResource910CB0B1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderLogGroup7FB49646", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderHandler03798759", - "Arn", - ], - }, - "keyArn": "arn:aws:kms:us-east-1:111111111111:key/121ac3b6-8d53-4d8a-a05c-1234567789", - "logGroupName": "/App/Test2", - "retention": 180, - "terminationProtected": false, - }, - "Type": "Custom::CreateLogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AwsConfigChangesMetricFilter019CEB27": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "AWSConfigChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "Cis11RootAccountUsage27B8A444": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for usage of "root" account", - "AlarmName": "CIS-1.1-RootAccountUsage", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "RootAccountUsage", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis310SecurityGroupChanges08E807D3": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for security group changes", - "AlarmName": "CIS-3.10-SecurityGroupChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "SecurityGroupChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis311NetworkAclChanges9CF1F1AD": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for changes to Network Access Control Lists (NACL)", - "AlarmName": "CIS-3.11-NetworkACLChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "NetworkACLChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis312NetworkGatewayChanges1C63367E": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for changes to network gateways", - "AlarmName": "CIS-3.12-NetworkGatewayChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "NetworkGatewayChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis313RouteTableChanges2799DB8E": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for route table changes", - "AlarmName": "CIS-3.13-RouteTableChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "RouteTableChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis314VpcChanges4516C762": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for VPC changes", - "AlarmName": "CIS-3.14-VPCChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "VPCChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis31UnauthorizedApiCallsB850B3C7": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for unauthorized API calls", - "AlarmName": "CIS-3.1-UnauthorizedAPICalls", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "UnauthorizedAPICalls", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 5, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis32ConsoleSigninWithoutMfa8401FEDF": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for AWS Management Console sign-in without MFA", - "AlarmName": "CIS-3.2-ConsoleSigninWithoutMFA", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "ConsoleSigninWithoutMFA", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis33RootAccountUsage51A91DE1": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for usage of "root" account", - "AlarmName": "CIS-3.3-RootAccountUsage", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "RootAccountUsage", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis34IamPolicyChangesDD227791": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for IAM policy changes", - "AlarmName": "CIS-3.4-IAMPolicyChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "IAMPolicyChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis35CloudTrailChanges5766286B": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for CloudTrail configuration changes", - "AlarmName": "CIS-3.5-CloudTrailChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "CloudTrailChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis36ConsoleAuthenticationFailure01D5FCD6": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm exist for AWS Management Console authentication failures", - "AlarmName": "CIS-3.6-ConsoleAuthenticationFailure", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "ConsoleAuthenticationFailure", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis37DisableOrDeleteCmkA4096819": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for disabling or scheduled deletion of customer created CMKs", - "AlarmName": "CIS-3.7-DisableOrDeleteCMK", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "DisableOrDeleteCMK", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis38S3BucketPolicyChangesBCAEEBF2": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for S3 bucket policy changes", - "AlarmName": "CIS-3.8-S3BucketPolicyChanges.", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "S3BucketPolicyChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis39AwsConfigChangesD1108027": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for AWS Config configuration changes", - "AlarmName": "CIS-3.9-AWSConfigChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "AWSConfigChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "CloudTrailChangesMetricFilter25ABB504": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "CloudTrailChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "ConfigDeliveryChannel": { - "Properties": { - "ConfigSnapshotDeliveryProperties": { - "DeliveryFrequency": "One_Hour", - }, - "S3BucketName": "existing-central-log-bucket", - }, - "Type": "AWS::Config::DeliveryChannel", - }, - "ConfigRecorder": { - "Properties": { - "RecordingGroup": { - "AllSupported": true, - "IncludeGlobalResourceTypes": true, - }, - "RoleARN": { - "Fn::GetAtt": [ - "ConfigRecorderRoleC4E33AA3", - "Arn", - ], - }, - }, - "Type": "AWS::Config::ConfigurationRecorder", - }, - "ConfigRecorderRoleC4E33AA3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "ConfigRecorderRole needs managed policy service-role/AWS_ConfigRole to administer config rules", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "ConfigRecorderRole DefaultPolicy is built by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "config.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWS_ConfigRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ConfigRecorderRoleDefaultPolicyB8261303": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Single bucket", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - ], - "Condition": { - "StringLike": { - "s3:x-amz-acl": "bucket-owner-full-control", - }, - }, - "Effect": "Allow", - "Resource": "arn:aws:s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - "Sid": "S3WriteAccess", - }, - { - "Action": "s3:GetBucketAcl", - "Effect": "Allow", - "Resource": "arn:aws:s3:::aws-accelerator-central-logs-333333333333-us-west-2", - "Sid": "S3GetAclAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ConfigRecorderRoleDefaultPolicyB8261303", - "Roles": [ - { - "Ref": "ConfigRecorderRoleC4E33AA3", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ConsoleAuthenticationFailureMetricFilter0F833CC2": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "ConsoleAuthenticationFailure", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "ConsoleSigninWithoutMfaMetricFilter85B015F7": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "ConsoleSigninWithoutMFA", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "CustomConfigServiceTagsCustomResourceProviderHandler159E7D96": { - "DependsOn": [ - "CustomConfigServiceTagsCustomResourceProviderRoleDC1C2A56", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomConfigServiceTagsCustomResourceProviderRoleDC1C2A56", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomConfigServiceTagsCustomResourceProviderLogGroupF7260892": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomConfigServiceTagsCustomResourceProviderHandler159E7D96", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomConfigServiceTagsCustomResourceProviderRoleDC1C2A56": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "config:TagResource", - "config:UntagResource", - ], - "Effect": "Allow", - "Resource": "arn:aws:config:*:111111111111:config-rule/*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699": { - "DependsOn": [ - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSessionManagerLoggingCustomResourceProviderLogGroupF4E32979": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DescribeDocument", - "ssm:CreateDocument", - "ssm:UpdateDocument", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DisableOrDeleteCmkMetricFilter4217496A": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "DisableOrDeleteCMK", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricFilterGreaterThanThresholdF3747C30": { - "Properties": { - "FilterPattern": "{$.eventType !="AwsServiceEvent"}", - "LogGroupName": "/generic/log/group", - "MetricTransformations": [ - { - "MetricName": "GenericMetricForTestingGreaterThanThreshold", - "MetricNamespace": "LzaMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricFilterGreaterThanUpperThreshold6D5F12B4": { - "Properties": { - "FilterPattern": "{$.eventType !="AwsServiceEvent"}", - "LogGroupName": "/generic/log/group", - "MetricTransformations": [ - { - "MetricName": "GenericMetricForTestingGreaterThanUpperThreshold", - "MetricNamespace": "LzaMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricFilterLessThanLowerOrGreaterThanUpperThreshold959D3A93": { - "Properties": { - "FilterPattern": "{$.eventType !="AwsServiceEvent"}", - "LogGroupName": "/generic/log/group", - "MetricTransformations": [ - { - "MetricName": "GenericMetricForTestingLessThanLowerOrGreaterThanUpperThreshold", - "MetricNamespace": "LzaMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricFilterLessThanLowerThreshold9B1BB0AF": { - "Properties": { - "FilterPattern": "{$.eventType !="AwsServiceEvent"}", - "LogGroupName": "/generic/log/group", - "MetricTransformations": [ - { - "MetricName": "GenericMetricForTestingLessThanLowerThreshold", - "MetricNamespace": "LzaMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricFilterLessThanOrEqualToThreshold9E926CBD": { - "Properties": { - "FilterPattern": "{$.eventType !="AwsServiceEvent"}", - "LogGroupName": "/generic/log/group", - "MetricTransformations": [ - { - "MetricName": "GenericMetricForTestingLessThanOrEqualToThreshold", - "MetricNamespace": "LzaMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricFilterLessThanThreshold8E8F66A6": { - "Properties": { - "FilterPattern": "{$.eventType !="AwsServiceEvent"}", - "LogGroupName": "/generic/log/group", - "MetricTransformations": [ - { - "MetricName": "GenericMetricForTestingLessThanThreshold", - "MetricNamespace": "LzaMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "GenericMetricGreaterThanThresholdDCFEEABB": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm to test GenericMetric GreaterThanThreshold", - "AlarmName": "GenericMetricGreaterThanThreshold", - "ComparisonOperator": "GreaterThanThreshold", - "EvaluationPeriods": 1, - "MetricName": "GenericMetricForTestingGreaterThanThreshold", - "Namespace": "LzaMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "ignore", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "GenericMetricGreaterThanUpperThreshold72E3337B": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm to test GenericMetric GreaterThanUpperThreshold", - "AlarmName": "GenericMetricGreaterThanUpperThreshold", - "ComparisonOperator": "GreaterThanUpperThreshold", - "EvaluationPeriods": 1, - "MetricName": "GenericMetricForTestingGreaterThanUpperThreshold", - "Namespace": "LzaMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "GenericMetricLessThanLowerOrGreaterThanUpperThresholdAB2E321F": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm to test GenericMetric LessThanLowerOrGreaterThanUpperThreshold", - "AlarmName": "GenericMetricLessThanLowerOrGreaterThanUpperThreshold", - "ComparisonOperator": "LessThanLowerOrGreaterThanUpperThreshold", - "EvaluationPeriods": 1, - "MetricName": "GenericMetricForTestingLessThanLowerOrGreaterThanUpperThreshold", - "Namespace": "LzaMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "GenericMetricLessThanLowerThresholdC7D23608": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm to test GenericMetric LessThanLowerThreshold", - "AlarmName": "GenericMetricLessThanLowerThreshold", - "ComparisonOperator": "LessThanLowerThreshold", - "EvaluationPeriods": 1, - "MetricName": "GenericMetricForTestingLessThanLowerThreshold", - "Namespace": "LzaMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "GenericMetricLessThanOrEqualToThreshold76C1BA2A": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm to test GenericMetric LessThanOrEqualToThreshold", - "AlarmName": "GenericMetricLessThanOrEqualToThreshold", - "ComparisonOperator": "LessThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "GenericMetricForTestingLessThanOrEqualToThreshold", - "Namespace": "LzaMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "GenericMetricLessThanThreshold69CF30D8": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm to test GenericMetric LessThanThreshold", - "AlarmName": "GenericMetricLessThanThreshold", - "ComparisonOperator": "LessThanThreshold", - "EvaluationPeriods": 1, - "MetricName": "GenericMetricForTestingLessThanThreshold", - "Namespace": "LzaMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "missing", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "IamPolicyChangesMetricFilter29A943B3": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "IAMPolicyChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "MetricFilter1B93B6E5": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "RootAccountUsage", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "NetworkAclChangesMetricFilter87734AD0": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "NetworkACLChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "NetworkGatewayChangesMetricFilterB8EAA32B": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "NetworkGatewayChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "RootAccountMetricFilter2CA28475": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "RootAccount", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "RouteTableChangesMetricFilter5DC8694A": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "RouteTableChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "S3BucketPolicyChangesMetricFilter2247C8C8": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "S3BucketPolicyChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "SecurityGroupChangesMetricFilterE0B65A44": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "SecurityGroupChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Creates a CloudWatch Logs Group to store SecurityHub findings and updates CW Log Group resource policy", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionSecurityHubEventsFunctionResourceF6D56745": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9", - "Arn", - ], - }, - "logGroupArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/*:*", - ], - ], - }, - "logGroupName": "/AWSAccelerator-SecurityHub", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/AWSAccelerator-SecurityHub*", - ], - ], - }, - }, - { - "Action": [ - "logs:DescribeLogGroups", - "logs:PutResourcePolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "Roles": [ - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-SecurityResourcesStack-111111111111-us-east-1/SecurityHubEventsLog/SecurityHubEventsFunction/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "Roles": [ - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubEventsLogSecurityHubLogEventsRule9AC6959D": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - "SecurityHubEventsLogSecurityHubEventsFunctionSecurityHubEventsFunctionResourceF6D56745", - ], - "Properties": { - "Description": "Sends Security Hub Findings above threshold to CloudWatch Logs", - "EventPattern": { - "detail": { - "findings": { - "Severity": { - "Label": [ - "CRITICAL", - "HIGH", - "MEDIUM", - ], - }, - }, - }, - "detail-type": [ - "Security Hub Findings - Imported", - ], - "source": [ - "aws.securityhub", - ], - }, - "Targets": [ - { - "Arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/AWSAccelerator-SecurityHub:*", - ], - ], - }, - "Id": "CloudWatchLogTarget", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "SecurityHubEventsLogSecurityHubSnsEventsRule3900A9EA": { - "Properties": { - "Description": "Sends Security Hub Findings above threshold to SNS", - "EventPattern": { - "detail": { - "findings": { - "Severity": { - "Label": [ - "CRITICAL", - "HIGH", - ], - }, - }, - }, - "detail-type": [ - "Security Hub Findings - Imported", - ], - "source": [ - "aws.securityhub", - ], - }, - "Targets": [ - { - "Arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - "Id": "SnsTarget", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityResourcesStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityResourcesStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmSessionManagerSettings24721AC9": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSessionManagerLoggingCustomResourceProviderLogGroupF4E32979", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699", - "Arn", - ], - }, - "cloudWatchEncryptionEnabled": false, - "cloudWatchLogGroupName": "", - "kmsKeyId": { - "Ref": "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - }, - "s3BucketName": "existing-central-log-bucket", - "s3EncryptionEnabled": true, - "s3KeyPrefix": { - "Fn::Join": [ - "", - [ - "session/", - { - "Ref": "AWS::AccountId", - }, - "/us-east-1", - ], - ], - }, - }, - "Type": "Custom::SsmSessionManagerSettings", - "UpdateReplacePolicy": "Delete", - }, - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Session Manager Session Encryption", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SsmSessionManagerSettingsSessionManagerSessionKeyAlias59E0224E": { - "Properties": { - "AliasName": "alias/accelerator/sessionmanager-logs/session", - "TargetKeyId": { - "Fn::GetAtt": [ - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "SsmSessionManagerSettingsSessionManagerUserKMSPolicyFB96BB42": { - "Properties": { - "Description": "", - "ManagedPolicyName": "AWSAccelerator-SessionManagerUserKMS-us-east-1", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "UnauthorizedApiCallsMetricFilter95DF459D": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "UnauthorizedAPICalls", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "VpcChangesMetricFilter309EC3DF": { - "DependsOn": [ - "CloudTrailLogGroupAWSAcceleratorAccountCloudTrail9F5C56EA", - ], - "Properties": { - "FilterPattern": "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}", - "LogGroupName": "aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail", - "MetricTransformations": [ - { - "MetricName": "VPCChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - }, -} -`; - -exports[`delegatedAdminStack Construct(SecurityResourcesStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/lambda/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/s3/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmssnstopickeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/snstopic/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AcceleratorAlbHttpDropInvalidHeaderEnabled699B715A": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-alb-http-drop-invalid-header-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancingV2::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ALB_HTTP_DROP_INVALID_HEADER_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorApiGwCacheEnabledAndEncrypted090286F1": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-api-gw-cache-enabled-and-encrypted", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ApiGateway::Stage", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "API_GW_CACHE_ENABLED_AND_ENCRYPTED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorApiGwExecutionLoggingEnabledDA00B249": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-api-gw-execution-logging-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ApiGateway::Stage", - "AWS::ApiGatewayV2::Stage", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "API_GW_EXECUTION_LOGGING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorAttachEc2InstanceProfile1DAEB489": { - "DependsOn": [ - "AcceleratorAttachEc2InstanceProfileFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI5AF7FE49", - "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-attach-ec2-instance-profile", - "Description": "Custom rule for checking EC2 instance IAM profile attachment", - "InputParameters": {}, - "MaximumExecutionFrequency": "Six_Hours", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MaximumExecutionFrequency": "Six_Hours", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorAttachEc2InstanceProfileFunction2F25082F": { - "DependsOn": [ - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS Config custom rule function used for "accelerator-attach-ec2-instance-profile" rule", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 3, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorAttachEc2InstanceProfileFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI5AF7FE49": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Config custom rule needs managed readonly access policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorAttachEc2InstanceProfileLambdaRolePolicy9BC7815F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule custom lambda role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorAttachEc2InstanceProfileLambdaRolePolicy9BC7815F", - "Roles": [ - { - "Ref": "AcceleratorAttachEc2InstanceProfileFunctionServiceRoleF2BC24EC", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorAttachEc2InstanceProfileLogGroup19DCB538": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorAttachEc2InstanceProfileFunction2F25082F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorAttachEc2InstanceProfileRemediation": { - "DependsOn": [ - "AcceleratorAttachEc2InstanceProfile1DAEB489", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-attach-ec2-instance-profile", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorAttachEc2InstanceProfileRemediationRole0804271B", - "Arn", - ], - }, - ], - }, - }, - "IamInstanceProfile": { - "StaticValue": { - "Values": [ - "EC2-Default-SSM-AD-Role", - ], - }, - }, - "InstanceId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Instance-Profile", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorAttachEc2InstanceProfileRemediationRole0804271B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorAttachEc2InstanceProfileRemediationRoleDefaultPolicy820C0688": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Instance-Profile", - ], - ], - }, - }, - { - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorAttachEc2InstanceProfileRemediationRoleDefaultPolicy820C0688", - "Roles": [ - { - "Ref": "AcceleratorAttachEc2InstanceProfileRemediationRole0804271B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorCentralLogBucketKeyLookup26F8C418": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-us-west-2-CentralBucket-KeyArnParam-Role", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorCloudtrailEnabled08B9BEEA": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudtrail-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUD_TRAIL_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudtrailS3DataeventsEnabledF8E06B95": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudtrail-s3-dataevents-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDTRAIL_S3_DATAEVENTS_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudtrailSecurityTrailEnabledDBDDD05E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudtrail-security-trail-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDTRAIL_SECURITY_TRAIL_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudwatchAlarmActionCheckBBDDF3B7": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudwatch-alarm-action-check", - "InputParameters": { - "alarmActionRequired": "TRUE", - "insufficientDataActionRequired": "TRUE", - "okActionRequired": "FALSE", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::CloudWatch::Alarm", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDWATCH_ALARM_ACTION_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCloudwatchLogGroupEncrypted233263DC": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cloudwatch-log-group-encrypted", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CLOUDWATCH_LOG_GROUP_ENCRYPTED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorCwLoggroupRetentionPeriodCheck7A9796B7": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-cw-loggroup-retention-period-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "CW_LOGGROUP_RETENTION_PERIOD_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorDynamodbTableEncryptedKms3EE6CD77": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-dynamodb-table-encrypted-kms", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::DynamoDB::Table", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "DYNAMODB_TABLE_ENCRYPTED_KMS", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceDetailedMonitoringEnabled9F032168": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instance-detailed-monitoring-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EC2_INSTANCE_DETAILED_MONITORING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceNoPublicIpB8B5F3AD": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instance-no-public-ip", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EC2_INSTANCE_NO_PUBLIC_IP", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceProfilePermission16FE7EFE": { - "DependsOn": [ - "AcceleratorEc2InstanceProfilePermissionFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI407CB1D2", - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instance-profile-permission", - "Description": "Custom role to remediate EC2 instance profile permission", - "InputParameters": { - "AWSManagedPolicies": "AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy", - "ResourceId": "RESOURCE_ID", - }, - "MaximumExecutionFrequency": "Six_Hours", - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::Role", - ], - }, - "Source": { - "Owner": "CUSTOM_LAMBDA", - "SourceDetails": [ - { - "EventSource": "aws.config", - "MessageType": "ConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MessageType": "OversizedConfigurationItemChangeNotification", - }, - { - "EventSource": "aws.config", - "MaximumExecutionFrequency": "Six_Hours", - "MessageType": "ScheduledNotification", - }, - ], - "SourceIdentifier": { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331": { - "DependsOn": [ - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS Config custom rule function used for "accelerator-ec2-instance-profile-permission" rule", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 3, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorEc2InstanceProfilePermissionFunctionCustomRulePermissionBjrlLxHBU2q1eflzaT436qiq4Iu5pKu7iRO6toNI407CB1D2": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - "Arn", - ], - }, - "Principal": "config.amazonaws.com", - "SourceAccount": "111111111111", - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Config custom rule needs managed readonly access policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorEc2InstanceProfilePermissionLambdaRolePolicy8942B625": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule custom lambda role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:Get*", - "iam:List*", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorEc2InstanceProfilePermissionLambdaRolePolicy8942B625", - "Roles": [ - { - "Ref": "AcceleratorEc2InstanceProfilePermissionFunctionServiceRoleAA6A6427", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorEc2InstanceProfilePermissionLogGroupE31085F3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AcceleratorEc2InstanceProfilePermissionFunction65B8A331", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorEc2InstanceProfilePermissionRemediation": { - "DependsOn": [ - "AcceleratorEc2InstanceProfilePermission16FE7EFE", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-ec2-instance-profile-permission", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AWSManagedPolicies": { - "StaticValue": { - "Values": [ - "AmazonSSMManagedInstanceCore", - "AmazonSSMDirectoryServiceAccess", - "CloudWatchAgentServerPolicy", - ], - }, - }, - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorEc2InstanceProfilePermissionRemediationRoleDB841759", - "Arn", - ], - }, - ], - }, - }, - "ResourceId": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Role-Policy", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorEc2InstanceProfilePermissionRemediationRoleDB841759": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorEc2InstanceProfilePermissionRemediationRoleDefaultPolicy9EC721D8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Attach-IAM-Role-Policy", - ], - ], - }, - }, - { - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorEc2InstanceProfilePermissionRemediationRoleDefaultPolicy9EC721D8", - "Roles": [ - { - "Ref": "AcceleratorEc2InstanceProfilePermissionRemediationRoleDB841759", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorEc2InstancesInVpcFCB9B1B9": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-instances-in-vpc", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Instance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "INSTANCES_IN_VPC", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEc2VolumeInuseCheck934E830D": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-ec2-volume-inuse-check", - "InputParameters": { - "deleteOnTermination": "TRUE", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::Volume", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EC2_VOLUME_INUSE_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElasticacheRedisClusterAutomaticBackupCheck7CFACFC6": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elasticache-redis-cluster-automatic-backup-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElasticsearchInVpcOnly1FFB209E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elasticsearch-in-vpc-only", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELASTICSEARCH_IN_VPC_ONLY", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElasticsearchNodeToNodeEncryptionCheck7F672889": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elasticsearch-node-to-node-encryption-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::Elasticsearch::Domain", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbAcmCertificateRequired7DFF09EF": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-acm-certificate-required", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_ACM_CERTIFICATE_REQUIRED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbCrossZoneLoadBalancingEnabled4933AD2A": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-cross-zone-load-balancing-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbDeletionProtectionEnabled6A44EA2D": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-deletion-protection-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancingV2::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_DELETION_PROTECTION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbLoggingEnabledD68765E9": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-logging-enabled", - "InputParameters": { - "s3BucketNames": "aws-accelerator-elb-access-logs-333333333333-us-east-1", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - "AWS::ElasticLoadBalancingV2::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_LOGGING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorElbLoggingEnabledRemediation": { - "DependsOn": [ - "AcceleratorElbLoggingEnabledD68765E9", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-elb-logging-enabled", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorElbLoggingEnabledRemediationRole852D593A", - "Arn", - ], - }, - ], - }, - }, - "LoadBalancerArn": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - "LogDestination": { - "StaticValue": { - "Values": [ - "aws-accelerator-elb-access-logs-333333333333-us-east-1", - ], - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/SSM-ELB-Enable-Logging", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorElbLoggingEnabledRemediationRole852D593A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorElbLoggingEnabledRemediationRoleDefaultPolicy31D14E3C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/SSM-ELB-Enable-Logging", - ], - ], - }, - }, - { - "Action": [ - "elasticloadbalancing:DescribeLoadBalancers", - "elasticloadbalancing:DescribeLoadBalancerAttributes", - "elasticloadbalancing:ModifyLoadBalancerAttributes", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorElbLoggingEnabledRemediationRoleDefaultPolicy31D14E3C", - "Roles": [ - { - "Ref": "AcceleratorElbLoggingEnabledRemediationRole852D593A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorElbTlsHttpsListenersOnly59A28904": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-elb-tls-https-listeners-only", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::ElasticLoadBalancing::LoadBalancer", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "ELB_TLS_HTTPS_LISTENERS_ONLY", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorEmrKerberosEnabled27B4E237": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-emr-kerberos-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "EMR_KERBEROS_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorGuarddutyNonArchivedFindings32ED20C9": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-guardduty-non-archived-findings", - "InputParameters": { - "daysHighSev": "1", - "daysLowSev": "30", - "daysMediumSev": "7", - }, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "GUARDDUTY_NON_ARCHIVED_FINDINGS", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamGroupHasUsersCheck5E15FE54": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-group-has-users-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::Group", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_GROUP_HAS_USERS_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamNoInlinePolicyCheckAE16B56C": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-no-inline-policy-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::User", - "AWS::IAM::Role", - "AWS::IAM::Group", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_NO_INLINE_POLICY_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamUserGroupMembershipCheck5D2DBD69": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-user-group-membership-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::IAM::User", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_USER_GROUP_MEMBERSHIP_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorIamUserMfaEnabled119AE0DF": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-iam-user-mfa-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "IAM_USER_MFA_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorInternetGatewayAuthorizedVpcOnly75CCD662": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-internet-gateway-authorized-vpc-only", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::InternetGateway", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorKmsCmkNotScheduledForDeletionF3796D4E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-kms-cmk-not-scheduled-for-deletion", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::KMS::Key", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "KMS_CMK_NOT_SCHEDULED_FOR_DELETION", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorLambdaInsideVpc68BE0CC7": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-lambda-inside-vpc", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::Lambda::Function", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "LAMBDA_INSIDE_VPC", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorRdsLoggingEnabled02B0690C": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-rds-logging-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::RDS::DBInstance", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "RDS_LOGGING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorRedshiftClusterConfigurationCheckC9D7559D": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-redshift-cluster-configuration-check", - "InputParameters": { - "clusterDbEncrypted": "TRUE", - "loggingEnabled": "TRUE", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::Redshift::Cluster", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "REDSHIFT_CLUSTER_CONFIGURATION_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketDefaultLockEnabled1E1E5D5A": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-default-lock-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_DEFAULT_LOCK_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketPolicyGranteeCheckA238FAF4": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-policy-grantee-check", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_POLICY_GRANTEE_CHECK", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketReplicationEnabled54B924AB": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-replication-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_REPLICATION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledB22E11D0": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-server-side-encryption-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledRemediation": { - "DependsOn": [ - "AcceleratorS3BucketServerSideEncryptionEnabledB22E11D0", - ], - "Properties": { - "Automatic": true, - "ConfigRuleName": "accelerator-s3-bucket-server-side-encryption-enabled", - "MaximumAutomaticAttempts": 5, - "Parameters": { - "AutomationAssumeRole": { - "StaticValue": { - "Values": [ - { - "Fn::GetAtt": [ - "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleC1D55E4D", - "Arn", - ], - }, - ], - }, - }, - "BucketName": { - "ResourceValue": { - "Value": "RESOURCE_ID", - }, - }, - "KMSMasterKey": { - "StaticValue": { - "Values": [ - { - "Ref": "SsmParameterValueacceleratorkmss3keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - }, - }, - "RetryAttemptSeconds": 60, - "TargetId": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Put-S3-Encryption", - ], - ], - }, - "TargetType": "SSM_DOCUMENT", - }, - "Type": "AWS::Config::RemediationConfiguration", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleC1D55E4D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleDefaultPolicy8B79DC01": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Config rule remediation role, created by the permission provided in config repository", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetAutomationExecution", - "ssm:StartAutomationExecution", - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:222222222222:document/Put-S3-Encryption", - ], - ], - }, - }, - { - "Action": [ - "s3:GetEncryptionConfiguration", - "s3:PutEncryptionConfiguration", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalArn": [ - "arn:aws:iam::*:role/AWSAccelerator-SecuritySt-*", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleDefaultPolicy8B79DC01", - "Roles": [ - { - "Ref": "AcceleratorS3BucketServerSideEncryptionEnabledRemediationRoleC1D55E4D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AcceleratorS3BucketVersioningEnabledA2D78DD3": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-s3-bucket-versioning-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::S3::Bucket", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "S3_BUCKET_VERSIONING_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorSagemakerEndpointConfigurationKmsKeyConfigured29A26847": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-sagemaker-endpoint-configuration-kms-key-configured", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorSagemakerNotebookInstanceKmsKeyConfigured98505444": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-sagemaker-notebook-instance-kms-key-configured", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorSecurityhubEnabled25B1DE1B": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-securityhub-enabled", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "SECURITYHUB_ENABLED", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorVpcSgOpenOnlyToAuthorizedPorts9EF29973": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-vpc-sg-open-only-to-authorized-ports", - "InputParameters": { - "authorizedTcpPorts": "443", - "authorizedUdpPorts": "1020-1025", - }, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::SecurityGroup", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AcceleratorVpcVpn2TunnelsUpBED0249E": { - "DependsOn": [ - "ConfigRecorder", - ], - "Properties": { - "ConfigRuleName": "accelerator-vpc-vpn-2-tunnels-up", - "InputParameters": {}, - "Scope": { - "ComplianceResourceTypes": [ - "AWS::EC2::VPNConnection", - ], - }, - "Source": { - "Owner": "AWS", - "SourceIdentifier": "VPC_VPN_2_TUNNELS_UP", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "AwsConfigChangesMetricFilter019CEB27": { - "Properties": { - "FilterPattern": "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "AWSConfigChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "Cis11RootAccountUsage27B8A444": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for usage of "root" account", - "AlarmName": "CIS-1.1-RootAccountUsage", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "RootAccountUsage", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis310SecurityGroupChanges08E807D3": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for security group changes", - "AlarmName": "CIS-3.10-SecurityGroupChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "SecurityGroupChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis311NetworkAclChanges9CF1F1AD": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for changes to Network Access Control Lists (NACL)", - "AlarmName": "CIS-3.11-NetworkACLChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "NetworkACLChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis312NetworkGatewayChanges1C63367E": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for changes to network gateways", - "AlarmName": "CIS-3.12-NetworkGatewayChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "NetworkGatewayChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis313RouteTableChanges2799DB8E": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for route table changes", - "AlarmName": "CIS-3.13-RouteTableChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "RouteTableChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis314VpcChanges4516C762": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for VPC changes", - "AlarmName": "CIS-3.14-VPCChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "VPCChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis31UnauthorizedApiCallsB850B3C7": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for unauthorized API calls", - "AlarmName": "CIS-3.1-UnauthorizedAPICalls", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "UnauthorizedAPICalls", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 5, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis32ConsoleSigninWithoutMfa8401FEDF": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for AWS Management Console sign-in without MFA", - "AlarmName": "CIS-3.2-ConsoleSigninWithoutMFA", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "ConsoleSigninWithoutMFA", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis33RootAccountUsage51A91DE1": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for usage of "root" account", - "AlarmName": "CIS-3.3-RootAccountUsage", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "RootAccountUsage", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis34IamPolicyChangesDD227791": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for IAM policy changes", - "AlarmName": "CIS-3.4-IAMPolicyChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "IAMPolicyChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis35CloudTrailChanges5766286B": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for CloudTrail configuration changes", - "AlarmName": "CIS-3.5-CloudTrailChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "CloudTrailChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis36ConsoleAuthenticationFailure01D5FCD6": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm exist for AWS Management Console authentication failures", - "AlarmName": "CIS-3.6-ConsoleAuthenticationFailure", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "ConsoleAuthenticationFailure", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis37DisableOrDeleteCmkA4096819": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for disabling or scheduled deletion of customer created CMKs", - "AlarmName": "CIS-3.7-DisableOrDeleteCMK", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "DisableOrDeleteCMK", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis38S3BucketPolicyChangesBCAEEBF2": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for S3 bucket policy changes", - "AlarmName": "CIS-3.8-S3BucketPolicyChanges.", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "S3BucketPolicyChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "Cis39AwsConfigChangesD1108027": { - "Properties": { - "AlarmActions": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - ], - "AlarmDescription": "Alarm for AWS Config configuration changes", - "AlarmName": "CIS-3.9-AWSConfigChanges", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "AWSConfigChanges", - "Namespace": "LogMetrics", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching", - }, - "Type": "AWS::CloudWatch::Alarm", - }, - "CloudTrailChangesMetricFilter25ABB504": { - "Properties": { - "FilterPattern": "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "CloudTrailChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "ConfigDeliveryChannel": { - "Properties": { - "ConfigSnapshotDeliveryProperties": { - "DeliveryFrequency": "One_Hour", - }, - "S3BucketName": "aws-accelerator-central-logs-333333333333-us-west-2", - }, - "Type": "AWS::Config::DeliveryChannel", - }, - "ConfigRecorder": { - "Properties": { - "RecordingGroup": { - "AllSupported": true, - "IncludeGlobalResourceTypes": true, - }, - "RoleARN": { - "Fn::GetAtt": [ - "ConfigRecorderRoleC4E33AA3", - "Arn", - ], - }, - }, - "Type": "AWS::Config::ConfigurationRecorder", - }, - "ConfigRecorderRoleC4E33AA3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "ConfigRecorderRole needs managed policy service-role/AWS_ConfigRole to administer config rules", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "ConfigRecorderRole DefaultPolicy is built by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "config.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWS_ConfigRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ConfigRecorderRoleDefaultPolicyB8261303": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Single bucket", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - ], - "Condition": { - "StringLike": { - "s3:x-amz-acl": "bucket-owner-full-control", - }, - }, - "Effect": "Allow", - "Resource": "arn:aws:s3:::aws-accelerator-central-logs-333333333333-us-west-2/*", - "Sid": "S3WriteAccess", - }, - { - "Action": "s3:GetBucketAcl", - "Effect": "Allow", - "Resource": "arn:aws:s3:::aws-accelerator-central-logs-333333333333-us-west-2", - "Sid": "S3GetAclAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ConfigRecorderRoleDefaultPolicyB8261303", - "Roles": [ - { - "Ref": "ConfigRecorderRoleC4E33AA3", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ConsoleAuthenticationFailureMetricFilter0F833CC2": { - "Properties": { - "FilterPattern": "{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "ConsoleAuthenticationFailure", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "ConsoleSigninWithoutMfaMetricFilter85B015F7": { - "Properties": { - "FilterPattern": "{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "ConsoleSigninWithoutMFA", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699": { - "DependsOn": [ - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSessionManagerLoggingCustomResourceProviderLogGroupF4E32979": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DescribeDocument", - "ssm:CreateDocument", - "ssm:UpdateDocument", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DisableOrDeleteCmkMetricFilter4217496A": { - "Properties": { - "FilterPattern": "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "DisableOrDeleteCMK", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "IamPolicyChangesMetricFilter29A943B3": { - "Properties": { - "FilterPattern": "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "IAMPolicyChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "MetricFilter1B93B6E5": { - "Properties": { - "FilterPattern": "{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "RootAccountUsage", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "NetworkAclChangesMetricFilter87734AD0": { - "Properties": { - "FilterPattern": "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "NetworkACLChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "NetworkGatewayChangesMetricFilterB8EAA32B": { - "Properties": { - "FilterPattern": "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "NetworkGatewayChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "RootAccountMetricFilter2CA28475": { - "Properties": { - "FilterPattern": "{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "RootAccount", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "RouteTableChangesMetricFilter5DC8694A": { - "Properties": { - "FilterPattern": "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "RouteTableChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "S3BucketPolicyChangesMetricFilter2247C8C8": { - "Properties": { - "FilterPattern": "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "S3BucketPolicyChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "SecurityGroupChangesMetricFilterE0B65A44": { - "Properties": { - "FilterPattern": "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "SecurityGroupChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Creates a CloudWatch Logs Group to store SecurityHub findings and updates CW Log Group resource policy", - "Handler": "index.handler", - "KmsKeyArn": { - "Ref": "SsmParameterValueacceleratorkmslambdakeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionSecurityHubEventsFunctionResourceF6D56745": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9", - "Arn", - ], - }, - "logGroupArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/*:*", - ], - ], - }, - "logGroupName": "/AWSAccelerator-SecurityHub", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/AWSAccelerator-SecurityHub*", - ], - ], - }, - }, - { - "Action": [ - "logs:DescribeLogGroups", - "logs:PutResourcePolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "Roles": [ - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (AWSAccelerator-SecurityResourcesStack-111111111111-us-east-1/SecurityHubEventsLog/SecurityHubEventsFunction/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "Roles": [ - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubEventsLogSecurityHubLogEventsRule9AC6959D": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - "SecurityHubEventsLogSecurityHubEventsFunctionSecurityHubEventsFunctionResourceF6D56745", - ], - "Properties": { - "Description": "Sends Security Hub Findings above threshold to CloudWatch Logs", - "EventPattern": { - "detail-type": [ - "Security Hub Findings - Imported", - ], - "source": [ - "aws.securityhub", - ], - }, - "Targets": [ - { - "Arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:us-east-1:111111111111:log-group:/AWSAccelerator-SecurityHub:*", - ], - ], - }, - "Id": "CloudWatchLogTarget", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "SecurityHubEventsLogSecurityHubSnsEventsRule3900A9EA": { - "Properties": { - "Description": "Sends Security Hub Findings above threshold to SNS", - "EventPattern": { - "detail": { - "findings": { - "Severity": { - "Label": [ - "CRITICAL", - "HIGH", - ], - }, - }, - }, - "detail-type": [ - "Security Hub Findings - Imported", - ], - "source": [ - "aws.securityhub", - ], - }, - "Targets": [ - { - "Arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-east-1:111111111111:aws-accelerator-Security", - ], - ], - }, - "Id": "SnsTarget", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityResourcesStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityResourcesStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmSessionManagerSettings24721AC9": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSessionManagerLoggingCustomResourceProviderLogGroupF4E32979", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699", - "Arn", - ], - }, - "cloudWatchEncryptionEnabled": false, - "cloudWatchLogGroupName": "", - "kmsKeyId": { - "Ref": "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - }, - "s3BucketName": "aws-accelerator-central-logs-333333333333-us-west-2", - "s3EncryptionEnabled": true, - "s3KeyPrefix": { - "Fn::Join": [ - "", - [ - "session/", - { - "Ref": "AWS::AccountId", - }, - "/us-east-1", - ], - ], - }, - }, - "Type": "Custom::SsmSessionManagerSettings", - "UpdateReplacePolicy": "Delete", - }, - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Session Manager Session Encryption", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SsmSessionManagerSettingsSessionManagerSessionKeyAlias59E0224E": { - "Properties": { - "AliasName": "alias/accelerator/sessionmanager-logs/session", - "TargetKeyId": { - "Fn::GetAtt": [ - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "SsmSessionManagerSettingsSessionManagerUserKMSPolicyFB96BB42": { - "Properties": { - "Description": "", - "ManagedPolicyName": "AWSAccelerator-SessionManagerUserKMS-us-east-1", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "UnauthorizedApiCallsMetricFilter95DF459D": { - "Properties": { - "FilterPattern": "{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "UnauthorizedAPICalls", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - "VpcChangesMetricFilter309EC3DF": { - "Properties": { - "FilterPattern": "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}", - "LogGroupName": "aws-controltower/CloudTrailLogs", - "MetricTransformations": [ - { - "MetricName": "VPCChanges", - "MetricNamespace": "LogMetrics", - "MetricValue": "1", - }, - ], - }, - "Type": "AWS::Logs::MetricFilter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-stack.test.ts.snap deleted file mode 100644 index 49527c1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/security-stack.test.ts.snap +++ /dev/null @@ -1,2363 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityStack Construct(SecurityStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AWSAcceleratormetadatacollectionLogGroup76F89C67": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": "/aws/lambda/AWSAccelerator-metadata-collection", - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-CrossAccountSsmParameterShare", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/imported-resources/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6": { - "DependsOn": [ - "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6", - "AcceleratorMetadataMetadataLambdaC27FFAF6", - "AWSAcceleratormetadatacollectionLogGroup76F89C67", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "ACCELERATOR_VERSION_SSM_PATH": "/accelerator/AWSAccelerator-InstallerStack/version", - "CENTRAL_LOG_BUCKET": "existing-central-log-bucket", - "CONFIG_REPOSITORY_NAME": "aws-accelerator-config", - "CROSS_ACCOUNT_ROLE": "AWSControlTowerExecution", - "ELB_LOGGING_BUCKET": "existing-elb-logs-bucket-111111111111-us-east-1", - "GLOBAL_REGION": "us-east-1", - "LOG_ACCOUNT_ID": "333333333333", - "METADATA_BUCKET": "aws-accelerator-metadata-333333333333-us-east-1", - "ORGANIZATION_ID": "o-asdf123456", - "PARTITION": { - "Ref": "AWS::Partition", - }, - }, - }, - "FunctionName": "AWSAccelerator-metadata-collection", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorMetadataMetadataLambdaC27FFAF6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 600, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRule1FFE12D2": { - "Properties": { - "EventPattern": { - "account": [ - "111111111111", - ], - "detail": { - "stack-id": [ - { - "Ref": "AWS::StackId", - }, - ], - "status-details": { - "status": [ - "CREATE_COMPLETE", - "UPDATE_COMPLETE", - ], - }, - }, - "detail-type": [ - "CloudFormation Stack Status Change", - ], - "region": [ - "us-east-1", - ], - "resources": [ - { - "Ref": "AWS::StackId", - }, - ], - "source": [ - "aws.cloudformation", - ], - }, - "Name": "AWSAccelerator-metadata-collection-rule", - "ScheduleExpression": "rate(1 day)", - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6", - "Arn", - ], - }, - "Id": "Target0", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRuleAllowEventRuleAWSAcceleratorSecurityStack111111111111useast1AcceleratorMetadataAWSAcceleratormetadatacollection72A1EADD690CC386": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRule1FFE12D2", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorMetadataMetadataLambdaC27FFAF6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "RoleName": "AWSAccelerator-111111111111-us-east-1-metadata-lambda-role", - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "accelerator metadata collection custom resource", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipelineExecution", - "codepipeline:ListPipelineExecutions", - "codecommit:GetFolder", - "codecommit:GetFile", - "kms:DescribeKey", - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:ListAliases", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - "organizations:DescribeOrganizationalUnit", - "organizations:DescribeAccount", - "organizations:ListAccounts", - "organizations:ListChildren", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "ssm:GetParameter", - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObjectAcl", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-metadata-333333333333-us-east-1", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-metadata-333333333333-us-east-1/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6", - "Roles": [ - { - "Ref": "AcceleratorMetadataMetadataLambdaC27FFAF6", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AwsMacieUpdateExportConfigClassification832781E3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderLogGroup727354F4", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC", - "Arn", - ], - }, - "bucketName": "existing-central-log-bucket", - "keyPrefix": "macie/111111111111/", - "kmsKeyArn": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - "region": "us-east-1", - }, - "Type": "Custom::MaciePutClassificationExportConfiguration", - "UpdateReplacePolicy": "Delete", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B": { - "DependsOn": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderLogGroupB7212213": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableEbsEncryptionByDefault", - "ec2:EnableEbsEncryptionByDefault", - "ec2:ModifyEbsDefaultKmsKeyId", - "ec2:ResetEbsDefaultKmsKeyId", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2", - }, - { - "Action": [ - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "EbsEncryptionKey9149E0DA", - "Arn", - ], - }, - "Sid": "KMS", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8": { - "DependsOn": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderLogGroup118A06DB": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "guardDuty:CreateDetector", - "guardDuty:CreatePublishingDestination", - "guardDuty:DeletePublishingDestination", - "guardDuty:UpdatePublishingDestination", - "guardDuty:ListDetectors", - "guardDuty:ListPublishingDestinations", - "guardduty:DescribePublishingDestination", - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreatePublishingDestinationCommandTaskGuardDutyActions", - }, - { - "Action": [ - "s3:ListBucket", - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/guardduty", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/guardduty/*", - ], - ], - }, - ], - "Sid": "GuardDutyCreateBucketPrefix", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4": { - "DependsOn": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderLogGroup69C2279A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:UpdateAccountPasswordPolicy", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC": { - "DependsOn": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderLogGroup727354F4": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "macie2:EnableMacie", - "macie2:GetClassificationExportConfiguration", - "macie2:GetMacieSession", - "macie2:PutClassificationExportConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MaciePutClassificationExportConfigurationTaskMacieActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1": { - "DependsOn": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderLogGroup51D3B0EA": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "securityhub:BatchDisableStandards", - "securityhub:BatchEnableStandards", - "securityhub:DescribeStandards", - "securityhub:DescribeStandardsControls", - "securityhub:EnableSecurityHub", - "securityhub:GetEnabledStandards", - "securityhub:UpdateStandardsControl", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskSecurityHubActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubServiceLinkedRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "EbsDefaultVolumeEncryption55012F7A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderLogGroupB7212213", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B", - "Arn", - ], - }, - "kmsKeyId": { - "Ref": "EbsEncryptionKey9149E0DA", - }, - }, - "Type": "Custom::EbsDefaultVolumeEncryption", - "UpdateReplacePolicy": "Delete", - }, - "EbsDefaultVolumeEncryptionParameter61CF6625": { - "Properties": { - "Name": "/accelerator/security-stack/ebsDefaultVolumeEncryptionKeyArn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "EbsEncryptionKey9149E0DA", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "EbsEncryptionKey9149E0DA": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator default EBS Volume Encryption key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey*", - "kms:ReEncrypt*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow service-linked role use", - }, - { - "Action": "kms:CreateGrant", - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Autoscaling to create grant", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Account Access", - }, - { - "Action": "kms:*", - "Condition": { - "StringEquals": { - "kms:CallerAccount": "111111111111", - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "ec2.us-east-1.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "ec2", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey*", - "kms:ReEncrypt*", - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/aws-service-role/cloud9.amazonaws.com/AWSServiceRoleForAWSCloud9", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow cloud9 service-linked role use", - }, - { - "Action": [ - "kms:CreateGrant", - "kms:ListGrants", - "kms:RevokeGrant", - ], - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/aws-service-role/cloud9.amazonaws.com/AWSServiceRoleForAWSCloud9", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow cloud9 attachment of persistent resources", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "EbsEncryptionKeyAlias4A392FEE": { - "Properties": { - "AliasName": "alias/accelerator/ebs/default-encryption/key", - "TargetKeyId": { - "Fn::GetAtt": [ - "EbsEncryptionKey9149E0DA", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "GuardDutyPublishingDestination52AE4412": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderLogGroup118A06DB", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8", - "Arn", - ], - }, - "destinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::existing-central-log-bucket/guardduty", - ], - ], - }, - "exportDestinationOverride": true, - "exportDestinationType": "S3", - "kmsKeyArn": { - "Ref": "AcceleratorImportedCentralLogBucketKeyLookupCDD7B719", - }, - }, - "Type": "Custom::GuardDutyCreatePublishingDestinationCommand", - "UpdateReplacePolicy": "Delete", - }, - "IamPasswordPolicy7117FCDB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderLogGroup69C2279A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4", - "Arn", - ], - }, - "allowUsersToChangePassword": true, - "hardExpiry": false, - "maxPasswordAge": 90, - "minimumPasswordLength": 14, - "passwordReusePrevention": 24, - "requireLowercaseCharacters": true, - "requireNumbers": true, - "requireSymbols": true, - "requireUppercaseCharacters": true, - }, - "Type": "Custom::IamUpdateAccountPasswordPolicy", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubStandards294083BB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderLogGroup51D3B0EA", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1", - "Arn", - ], - }, - "region": "us-east-1", - "standards": [ - { - "controlsToDisable": [ - "IAM.1", - "EC2.10", - "Lambda.4", - ], - "enable": true, - "name": "AWS Foundational Security Best Practices v1.0.0", - }, - { - "controlsToDisable": [ - "PCI.IAM.3", - "PCI.S3.3", - "PCI.EC2.3", - "PCI.Lambda.2", - ], - "enable": true, - "name": "PCI DSS v3.2.1", - }, - { - "controlsToDisable": [ - "CIS.1.20", - "CIS.1.22", - "CIS.2.6", - ], - "enable": true, - "name": "CIS AWS Foundations Benchmark v1.2.0", - }, - { - "controlsToDisable": [ - "1.17", - "1.16", - ], - "enable": true, - "name": "CIS AWS Foundations Benchmark v1.4.0", - }, - { - "enable": true, - "name": "CIS AWS Foundations Benchmark v3.0.0", - }, - ], - }, - "Type": "Custom::SecurityHubBatchEnableStandards", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; - -exports[`delegatedAdminStack Construct(SecurityStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/cloudwatch/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorkmskey1keyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/kms/key1/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "AWSAcceleratormetadatacollectionLogGroup76F89C67": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": "/aws/lambda/AWSAccelerator-metadata-collection", - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorCentralLogBucketKeyLookup26F8C418": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:role/AWSAccelerator-us-west-2-CentralBucket-KeyArnParam-Role", - ], - ], - }, - "invokingAccountID": "111111111111", - "invokingRegion": "us-east-1", - "parameterAccountID": "333333333333", - "parameterName": "/accelerator/logging/central-bucket/kms/arn", - "parameterRegion": "us-west-2", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6": { - "DependsOn": [ - "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6", - "AcceleratorMetadataMetadataLambdaC27FFAF6", - "AWSAcceleratormetadatacollectionLogGroup76F89C67", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "ACCELERATOR_VERSION_SSM_PATH": "/accelerator/AWSAccelerator-InstallerStack/version", - "CENTRAL_LOG_BUCKET": "aws-accelerator-central-logs-333333333333-us-west-2", - "CONFIG_REPOSITORY_NAME": "aws-accelerator-config", - "CROSS_ACCOUNT_ROLE": "AWSControlTowerExecution", - "ELB_LOGGING_BUCKET": "aws-accelerator-elb-access-logs-333333333333-us-east-1", - "GLOBAL_REGION": "us-east-1", - "LOG_ACCOUNT_ID": "333333333333", - "METADATA_BUCKET": "aws-accelerator-metadata-333333333333-us-east-1", - "ORGANIZATION_ID": "o-asdf123456", - "PARTITION": { - "Ref": "AWS::Partition", - }, - }, - }, - "FunctionName": "AWSAccelerator-metadata-collection", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorMetadataMetadataLambdaC27FFAF6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 600, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRule1FFE12D2": { - "Properties": { - "EventPattern": { - "account": [ - "111111111111", - ], - "detail": { - "stack-id": [ - { - "Ref": "AWS::StackId", - }, - ], - "status-details": { - "status": [ - "CREATE_COMPLETE", - "UPDATE_COMPLETE", - ], - }, - }, - "detail-type": [ - "CloudFormation Stack Status Change", - ], - "region": [ - "us-east-1", - ], - "resources": [ - { - "Ref": "AWS::StackId", - }, - ], - "source": [ - "aws.cloudformation", - ], - }, - "Name": "AWSAccelerator-metadata-collection-rule", - "ScheduleExpression": "rate(1 day)", - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6", - "Arn", - ], - }, - "Id": "Target0", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRuleAllowEventRuleAWSAcceleratorSecurityStack111111111111useast1AcceleratorMetadataAWSAcceleratormetadatacollection72A1EADD690CC386": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRule1FFE12D2", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorMetadataMetadataLambdaC27FFAF6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "RoleName": "AWSAccelerator-111111111111-us-east-1-metadata-lambda-role", - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "accelerator metadata collection custom resource", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipelineExecution", - "codepipeline:ListPipelineExecutions", - "codecommit:GetFolder", - "codecommit:GetFile", - "kms:DescribeKey", - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:ListAliases", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - "organizations:DescribeOrganizationalUnit", - "organizations:DescribeAccount", - "organizations:ListAccounts", - "organizations:ListChildren", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "ssm:GetParameter", - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObjectAcl", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-metadata-333333333333-us-east-1", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-metadata-333333333333-us-east-1/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6", - "Roles": [ - { - "Ref": "AcceleratorMetadataMetadataLambdaC27FFAF6", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AwsMacieUpdateExportConfigClassification832781E3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderLogGroup727354F4", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC", - "Arn", - ], - }, - "bucketName": "aws-accelerator-central-logs-333333333333-us-west-2", - "keyPrefix": "macie/111111111111/", - "kmsKeyArn": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - "region": "us-east-1", - }, - "Type": "Custom::MaciePutClassificationExportConfiguration", - "UpdateReplacePolicy": "Delete", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B": { - "DependsOn": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderLogGroupB7212213": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableEbsEncryptionByDefault", - "ec2:EnableEbsEncryptionByDefault", - "ec2:ModifyEbsDefaultKmsKeyId", - "ec2:ResetEbsDefaultKmsKeyId", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2", - }, - { - "Action": [ - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorkmskey1keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Sid": "KMS", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8": { - "DependsOn": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderLogGroup118A06DB": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "guardDuty:CreateDetector", - "guardDuty:CreatePublishingDestination", - "guardDuty:DeletePublishingDestination", - "guardDuty:UpdatePublishingDestination", - "guardDuty:ListDetectors", - "guardDuty:ListPublishingDestinations", - "guardduty:DescribePublishingDestination", - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreatePublishingDestinationCommandTaskGuardDutyActions", - }, - { - "Action": [ - "s3:ListBucket", - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/guardduty", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/guardduty/*", - ], - ], - }, - ], - "Sid": "GuardDutyCreateBucketPrefix", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4": { - "DependsOn": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderLogGroup69C2279A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:UpdateAccountPasswordPolicy", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC": { - "DependsOn": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderLogGroup727354F4": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "macie2:EnableMacie", - "macie2:GetClassificationExportConfiguration", - "macie2:GetMacieSession", - "macie2:PutClassificationExportConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MaciePutClassificationExportConfigurationTaskMacieActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1": { - "DependsOn": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderLogGroup51D3B0EA": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "securityhub:BatchDisableStandards", - "securityhub:BatchEnableStandards", - "securityhub:DescribeStandards", - "securityhub:DescribeStandardsControls", - "securityhub:EnableSecurityHub", - "securityhub:GetEnabledStandards", - "securityhub:UpdateStandardsControl", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskSecurityHubActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubServiceLinkedRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Ref": "SsmParameterValueacceleratorkmscloudwatchkeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "EbsDefaultVolumeEncryption55012F7A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderLogGroupB7212213", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B", - "Arn", - ], - }, - "kmsKeyId": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "/", - { - "Fn::Select": [ - 5, - { - "Fn::Split": [ - ":", - { - "Ref": "SsmParameterValueacceleratorkmskey1keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - "Type": "Custom::EbsDefaultVolumeEncryption", - "UpdateReplacePolicy": "Delete", - }, - "EbsDefaultVolumeEncryptionParameter61CF6625": { - "Properties": { - "Name": "/accelerator/security-stack/ebsDefaultVolumeEncryptionKeyArn", - "Type": "String", - "Value": { - "Ref": "SsmParameterValueacceleratorkmskey1keyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "GuardDutyPublishingDestination52AE4412": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderLogGroup118A06DB", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8", - "Arn", - ], - }, - "destinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-333333333333-us-west-2/guardduty", - ], - ], - }, - "exportDestinationOverride": true, - "exportDestinationType": "S3", - "kmsKeyArn": { - "Ref": "AcceleratorCentralLogBucketKeyLookup26F8C418", - }, - }, - "Type": "Custom::GuardDutyCreatePublishingDestinationCommand", - "UpdateReplacePolicy": "Delete", - }, - "IamPasswordPolicy7117FCDB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderLogGroup69C2279A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4", - "Arn", - ], - }, - "allowUsersToChangePassword": true, - "hardExpiry": false, - "maxPasswordAge": 90, - "minimumPasswordLength": 14, - "passwordReusePrevention": 24, - "requireLowercaseCharacters": true, - "requireNumbers": true, - "requireSymbols": true, - "requireUppercaseCharacters": true, - }, - "Type": "Custom::IamUpdateAccountPasswordPolicy", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubStandards294083BB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderLogGroup51D3B0EA", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1", - "Arn", - ], - }, - "region": "us-east-1", - "standards": [ - { - "controlsToDisable": [ - "IAM.1", - "EC2.10", - "Lambda.4", - ], - "enable": true, - "name": "AWS Foundational Security Best Practices v1.0.0", - }, - { - "controlsToDisable": [ - "PCI.IAM.3", - "PCI.S3.3", - "PCI.EC2.3", - "PCI.Lambda.2", - ], - "enable": true, - "name": "PCI DSS v3.2.1", - }, - { - "controlsToDisable": [ - "CIS.1.20", - "CIS.1.22", - "CIS.2.6", - ], - "enable": true, - "name": "CIS AWS Foundations Benchmark v1.2.0", - }, - ], - }, - "Type": "Custom::SecurityHubBatchEnableStandards", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityStack-111111111111-us-east-1/version", - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": "/accelerator/AWSAccelerator-SecurityStack-111111111111-us-east-1/stack-id", - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/tester-pipeline-stack.test.ts.snap b/source/packages/@aws-accelerator/accelerator/test/__snapshots__/tester-pipeline-stack.test.ts.snap deleted file mode 100644 index 408965e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/__snapshots__/tester-pipeline-stack.test.ts.snap +++ /dev/null @@ -1,1180 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TesterPipelineStack Construct(TesterPipelineStack): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorawsacceleratorinstalleraccesslogsbucketnameC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/aws-accelerator/installer-access-logs-bucket-name", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/aws-accelerator/installer/kms/key-arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "TesterPipeline69BAAE53": { - "DependsOn": [ - "TesterPipelinePipelineRoleDefaultPolicyFC1B0BBB", - "TesterPipelinePipelineRoleBF82DB14", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Type": "KMS", - }, - "Location": { - "Ref": "TesterPipelineSecureBucket8740FCE8", - }, - "Type": "S3", - }, - "Name": "aws-accelerator-tester-pipeline", - "RoleArn": { - "Fn::GetAtt": [ - "TesterPipelinePipelineRoleBF82DB14", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": "main", - "PollForSourceChanges": false, - "RepositoryName": "accelerator-source", - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "TesterPipelineSourceCodePipelineActionRole1C0E642C", - "Arn", - ], - }, - "RunOrder": 1, - }, - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": "main", - "PollForSourceChanges": false, - "RepositoryName": { - "Fn::GetAtt": [ - "TesterPipelineConfigRepositoryC9B47F16", - "Name", - ], - }, - }, - "Name": "Configuration", - "OutputArtifacts": [ - { - "Name": "Config", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "TesterPipelineSourceConfigurationCodePipelineActionRole6DD3F86D", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "PrimarySource": "Source", - "ProjectName": { - "Ref": "TesterPipelineTesterProject3BEC9F5A", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - { - "Name": "Config", - }, - ], - "Name": "Deploy", - "OutputArtifacts": [ - { - "Name": "DeployOutput", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "TesterPipelinePipelineRoleBF82DB14", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Deploy", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "TesterPipelineConfigRepositoryC9B47F16": { - "DeletionPolicy": "Retain", - "Properties": { - "Code": { - "BranchName": "main", - "S3": { - "Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "Key": "REPLACED-GENERATED-NAME.zip", - }, - }, - "RepositoryDescription": "AWS Accelerator functional test configuration repository", - "RepositoryName": "aws-accelerator-test-config", - }, - "Type": "AWS::CodeCommit::Repository", - "UpdateReplacePolicy": "Retain", - }, - "TesterPipelineConfigRepositoryTesterPipelineStackTesterPipeline463CB8B6mainEventRuleB1B7F3DD": { - "Properties": { - "EventPattern": { - "detail": { - "event": [ - "referenceCreated", - "referenceUpdated", - ], - "referenceName": [ - "main", - ], - }, - "detail-type": [ - "CodeCommit Repository State Change", - ], - "resources": [ - { - "Fn::GetAtt": [ - "TesterPipelineConfigRepositoryC9B47F16", - "Arn", - ], - }, - ], - "source": [ - "aws.codecommit", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "TesterPipeline69BAAE53", - }, - ], - ], - }, - "Id": "Target0", - "RoleArn": { - "Fn::GetAtt": [ - "TesterPipelineEventsRoleC96AADF0", - "Arn", - ], - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "TesterPipelineDeployAdminRole3DA8CFF7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TesterPipelineDeployAdminRoleDefaultPolicy5D9BE98D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "TesterPipelineTesterProject3BEC9F5A", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "TesterPipelineTesterProject3BEC9F5A", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "TesterPipelineTesterProject3BEC9F5A", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TesterPipelineDeployAdminRoleDefaultPolicy5D9BE98D", - "Roles": [ - { - "Ref": "TesterPipelineDeployAdminRole3DA8CFF7", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TesterPipelineEventsRoleC96AADF0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "events.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "TesterPipelineEventsRoleDefaultPolicy61DCBDBE": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "codepipeline:StartPipelineExecution", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "TesterPipeline69BAAE53", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TesterPipelineEventsRoleDefaultPolicy61DCBDBE", - "Roles": [ - { - "Ref": "TesterPipelineEventsRoleC96AADF0", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TesterPipelinePipelineRoleBF82DB14": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "TesterPipelinePipelineRoleDefaultPolicyFC1B0BBB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TesterPipelineSourceCodePipelineActionRole1C0E642C", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TesterPipelineSourceConfigurationCodePipelineActionRole6DD3F86D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TesterPipelinePipelineRoleBF82DB14", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TesterPipelineTesterProject3BEC9F5A", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TesterPipelinePipelineRoleDefaultPolicyFC1B0BBB", - "Roles": [ - { - "Ref": "TesterPipelinePipelineRoleBF82DB14", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TesterPipelineSecureBucket8740FCE8": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-tester-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-tester-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstalleraccesslogsbucketnameC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "TesterPipelineSecureBucketPolicyD3292C3A": { - "Properties": { - "Bucket": { - "Ref": "TesterPipelineSecureBucket8740FCE8", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "TesterPipelineSourceCodePipelineActionRole1C0E642C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "TesterPipelineSourceCodePipelineActionRoleDefaultPolicy9AAA0DC1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":accelerator-source", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TesterPipelineSourceCodePipelineActionRoleDefaultPolicy9AAA0DC1", - "Roles": [ - { - "Ref": "TesterPipelineSourceCodePipelineActionRole1C0E642C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TesterPipelineSourceConfigurationCodePipelineActionRole6DD3F86D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "TesterPipelineSourceConfigurationCodePipelineActionRoleDefaultPolicyCD0DC6AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TesterPipelineSecureBucket8740FCE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TesterPipelineConfigRepositoryC9B47F16", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TesterPipelineSourceConfigurationCodePipelineActionRoleDefaultPolicyCD0DC6AA", - "Roles": [ - { - "Ref": "TesterPipelineSourceConfigurationCodePipelineActionRole6DD3F86D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TesterPipelineTesterProject3BEC9F5A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Pipeline tester project allow access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Modes": [ - "LOCAL_SOURCE_CACHE", - ], - "Type": "LOCAL", - }, - "EncryptionKey": { - "Ref": "SsmParameterValueacceleratorawsacceleratorinstallerkmskeyarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": "accelerator-source", - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": "main", - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator", - }, - { - "Name": "ACCELERATOR_REPO_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "ACCELERATOR_BUCKET_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "aws-accelerator", - }, - { - "Name": "ACCELERATOR_KMS_ALIAS_PREFIX", - "Type": "PLAINTEXT", - "Value": "alias/accelerator", - }, - { - "Name": "ACCELERATOR_SSM_PARAM_NAME_PREFIX", - "Type": "PLAINTEXT", - "Value": "/accelerator", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": true, - "Type": "LINUX_CONTAINER", - }, - "Name": "aws-accelerator-tester-project", - "ServiceRole": { - "Fn::GetAtt": [ - "TesterPipelineDeployAdminRole3DA8CFF7", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - build: - commands: - - cd source - - yarn install - - yarn build - - cd packages/@aws-accelerator/tester - - env - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then yarn run cdk deploy --require-approval never --context acceleratorPrefix=AWSAccelerator --context account=", - { - "Ref": "AWS::AccountId", - }, - " --context region=", - { - "Ref": "AWS::Region", - }, - " --context management-cross-account-role-name=AWSControlTowerExecution --context qualifier=aws-accelerator --context config-dir=$CODEBUILD_SRC_DIR_Config --context management-account-id=undefined --context management-account-role-name=AcceleratorAccountAccessRole; else yarn run cdk deploy --require-approval never --context acceleratorPrefix=AWSAccelerator --context account=", - { - "Ref": "AWS::AccountId", - }, - " --context region=", - { - "Ref": "AWS::Region", - }, - " --context management-cross-account-role-name=AWSControlTowerExecution --context config-dir=$CODEBUILD_SRC_DIR_Config; fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/accelerator/test/accelerator-synth-stacks.ts b/source/packages/@aws-accelerator/accelerator/test/accelerator-synth-stacks.ts deleted file mode 100644 index c0b9e9d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/accelerator-synth-stacks.ts +++ /dev/null @@ -1,816 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as fs from 'fs'; -import * as path from 'path'; - -import { - AccountConfig, - AccountsConfig, - AppConfigItem, - CustomizationsConfig, - GlobalConfig, - IamConfig, - NetworkConfig, - OrganizationConfig, - ReplacementsConfig, - SecurityConfig, -} from '@aws-accelerator/config'; - -import { Stack } from 'aws-cdk-lib'; -import { AcceleratorStackNames } from '../lib/accelerator'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorStack, AcceleratorStackProps } from '../lib/stacks/accelerator-stack'; -import { AccountsStack } from '../lib/stacks/accounts-stack'; -import { ApplicationsStack } from '../lib/stacks/applications-stack'; -import { BootstrapStack } from '../lib/stacks/bootstrap-stack'; -import { CustomStack, generateCustomStackMappings, isIncluded } from '../lib/stacks/custom-stack'; -import { CustomizationsStack } from '../lib/stacks/customizations-stack'; -import { DependenciesStack } from '../lib/stacks/dependencies-stack/dependencies-stack'; -import { FinalizeStack } from '../lib/stacks/finalize-stack'; -import { IdentityCenterStack } from '../lib/stacks/identity-center-stack'; -import { KeyStack } from '../lib/stacks/key-stack'; -import { LoggingStack } from '../lib/stacks/logging-stack'; -import { NetworkAssociationsGwlbStack } from '../lib/stacks/network-stacks/network-associations-gwlb-stack/network-associations-gwlb-stack'; -import { NetworkAssociationsStack } from '../lib/stacks/network-stacks/network-associations-stack/network-associations-stack'; -import { NetworkPrepStack } from '../lib/stacks/network-stacks/network-prep-stack/network-prep-stack'; -import { NetworkVpcDnsStack } from '../lib/stacks/network-stacks/network-vpc-dns-stack/network-vpc-dns-stack'; -import { NetworkVpcEndpointsStack } from '../lib/stacks/network-stacks/network-vpc-endpoints-stack/network-vpc-endpoints-stack'; -import { NetworkVpcStack } from '../lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack'; -import { OperationsStack } from '../lib/stacks/operations-stack'; -import { OrganizationsStack } from '../lib/stacks/organizations-stack'; -import { PrepareStack } from '../lib/stacks/prepare-stack'; -import { SecurityAuditStack } from '../lib/stacks/security-audit-stack'; -import { SecurityResourcesStack } from '../lib/stacks/security-resources-stack'; -import { SecurityStack } from '../lib/stacks/security-stack'; -import { ResourcePolicyEnforcementStack } from '../lib/stacks/resource-policy-enforcement-stack'; - -export class AcceleratorSynthStacks { - private readonly configFolderName: string; - private readonly partition: string; - private readonly configDirPath: string; - private readonly props: AcceleratorStackProps; - private readonly app: cdk.App; - private readonly homeRegion: string; - private readonly managementAccountId: string; - private readonly managementAccount: AccountConfig; - private readonly auditAccountId: string; - private readonly auditAccount: AccountConfig; - private readonly stageName: string; - private readonly globalRegion: string; - - public readonly stacks = new Map(); - constructor(stageName: string, partition: string, globalRegion: string, configFolderName?: string) { - this.configFolderName = configFolderName ?? 'snapshot-only'; - this.partition = partition; - this.stageName = stageName; - this.globalRegion = globalRegion; - - /** - * Test stack CDK app - */ - this.app = new cdk.App({ - context: { 'config-dir': path.join(__dirname, `configs/${this.configFolderName}`) }, - }); - this.configDirPath = this.app.node.tryGetContext('config-dir'); - - const globalConfig = GlobalConfig.load(this.configDirPath); - - let customizationsConfig: CustomizationsConfig; - // Create empty customizationsConfig if optional configuration file does not exist - if (fs.existsSync(path.join(this.configDirPath, 'customizations-config.yaml'))) { - customizationsConfig = CustomizationsConfig.load(this.configDirPath); - } else { - customizationsConfig = new CustomizationsConfig(); - } - - const accountsConfig = AccountsConfig.load(this.configDirPath); - const orgConfig = OrganizationConfig.load(this.configDirPath); - const replacementsConfig = ReplacementsConfig.load(this.configDirPath, accountsConfig); - replacementsConfig.loadReplacementValues({}, orgConfig.enable); - this.props = { - configDirPath: this.configDirPath, - accountsConfig: accountsConfig, - customizationsConfig, - globalConfig, - iamConfig: IamConfig.load(this.configDirPath), - networkConfig: NetworkConfig.load(this.configDirPath), - organizationConfig: OrganizationConfig.load(this.configDirPath), - securityConfig: SecurityConfig.load(this.configDirPath), - replacementsConfig: replacementsConfig, - partition: this.partition, - globalRegion: this.globalRegion, - centralizedLoggingRegion: globalConfig.logging.centralizedLoggingRegion ?? globalConfig.homeRegion, - configRepositoryName: 'aws-accelerator-config', - prefixes: { - accelerator: 'AWSAccelerator', - kmsAlias: 'alias/accelerator', - bucketName: 'aws-accelerator', - ssmParamName: '/accelerator', - importResourcesSsmParamName: '/accelerator/imported-resources', - snsTopicName: 'aws-accelerator', - repoName: 'aws-accelerator', - secretName: '/accelerator', - trailLogName: 'aws-accelerator', - databaseName: 'aws-accelerator', - ssmLogName: 'aws-accelerator', - }, - enableSingleAccountMode: false, - useExistingRoles: false, - centralLogsBucketKmsKeyArn: 'arn:aws:kms:us-east-1:111111111111:key/00000000-0000-0000-0000-000000000000', - isDiagnosticsPackEnabled: 'Yes', - pipelineAccountId: '111111111111', - }; - - this.homeRegion = this.props.globalConfig.homeRegion; - - this.managementAccount = this.props.accountsConfig.getManagementAccount(); - this.managementAccountId = this.props.accountsConfig.getManagementAccountId(); - - this.auditAccount = this.props.accountsConfig.getAuditAccount(); - this.auditAccountId = this.props.accountsConfig.getAuditAccountId(); - - /** - * synth all test stacks - */ - this.synthAllTestStacks(); - } - - private synthAllTestStacks() { - switch (this.stageName) { - case AcceleratorStage.FINALIZE: - this.synthFinalizeStacks(); - break; - case AcceleratorStage.SECURITY_AUDIT: - this.synthSecurityAuditStacks(); - break; - case AcceleratorStage.LOGGING: - this.synthLoggingStacks(); - break; - case AcceleratorStage.CUSTOMIZATIONS: - this.synthCustomizationsStacks(); - this.synthApplicationsStacks(); - break; - case AcceleratorStage.NETWORK_ASSOCIATIONS: - this.synthNetworkAssociationStacks(); - break; - case AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB: - this.synthNetworkAssociationGwlbStacks(); - break; - case AcceleratorStage.NETWORK_PREP: - this.synthNetworkPrepStacks(); - break; - case AcceleratorStage.NETWORK_VPC_DNS: - this.synthNetworkVpcDnsStacks(); - break; - case AcceleratorStage.NETWORK_VPC_ENDPOINTS: - this.synthNetworkVpcEndPointsStacks(); - break; - case AcceleratorStage.NETWORK_VPC: - this.synthNetworkVpcStacks(); - break; - case AcceleratorStage.OPERATIONS: - this.synthOperationsStacks(); - break; - case AcceleratorStage.IDENTITY_CENTER: - this.synthIdentityCenterStacks(); - break; - case AcceleratorStage.ORGANIZATIONS: - this.synthOrganizationsStacks(); - break; - case AcceleratorStage.PREPARE: - this.synthPrepareStacks(); - break; - case AcceleratorStage.KEY: - this.synthKeyStacks(); - break; - case AcceleratorStage.SECURITY_RESOURCES: - this.synthSecurityResourcesStacks(); - break; - case AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT: - this.synthResourcePolicyEnforcementStacks(); - break; - case AcceleratorStage.SECURITY: - this.synthSecurityStacks(); - break; - case AcceleratorStage.ACCOUNTS: - this.synthAccountStacks(); - break; - case AcceleratorStage.BOOTSTRAP: - this.synthBootstrapStacks(); - break; - case AcceleratorStage.DEPENDENCIES: - this.synthDependenciesStacks(); - break; - } - } - - /** - * Function to synth Finalize stack - */ - private synthFinalizeStacks() { - this.stacks.set( - `${this.managementAccount.name}-${this.homeRegion}`, - new FinalizeStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.FINALIZE]}-${this.managementAccountId}-${this.homeRegion}`, - { - env: { - account: this.managementAccountId, - region: this.homeRegion, - }, - ...this.props, - }, - ), - ); - } - /** - * synth Security Audit stacks - */ - private synthSecurityAuditStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - this.stacks.set( - `${this.auditAccount.name}-${region}`, - new SecurityAuditStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.SECURITY_AUDIT]}-${this.auditAccountId}-${region}`, - { - env: { - account: this.auditAccountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - - /** - * synth Logging stacks - */ - private synthLoggingStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new LoggingStack(this.app, `${AcceleratorStackNames[AcceleratorStage.LOGGING]}-${accountId}-${region}`, { - env: { - account: accountId, - region: region, - }, - ...this.props, - }), - ); - } - } - } - /** - * synth Customizations stacks - */ - private synthCustomizationsStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new CustomizationsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.CUSTOMIZATIONS]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - this.synthCustomStacks(); - } - /** - * synth Custom stacks - */ - private synthCustomStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - const customStackList = generateCustomStackMappings( - this.props.accountsConfig, - this.props.organizationConfig, - this.props.customizationsConfig, - accountId, - region, - ); - - for (const stack of customStackList ?? []) { - stack.stackObj = new CustomStack(this.app, `${stack.stackConfig.name}-${accountId}-${region}`, { - env: { - account: accountId, - region: region, - }, - description: stack.stackConfig.description, - runOrder: stack.stackConfig.runOrder, - stackName: stack.stackConfig.name, - templateFile: stack.stackConfig.template, - terminationProtection: stack.stackConfig.terminationProtection, - ...this.props, - parameters: stack.stackConfig.parameters, - ssmParamNamePrefix: '/accelerator', - }); - - if (stack.dependsOn) { - for (const stackName of stack.dependsOn) { - const previousStack = customStackList.find(a => a.stackConfig.name == stackName)?.stackObj; - if (previousStack) { - stack.stackObj.addDependency(previousStack); - } - } - } - this.stacks.set(`${stack.stackConfig.name}-${accountId}-${region}`, stack.stackObj); - } - } - } - } - /** - * synth Applications stacks - */ - private synthApplicationsStacks() { - for (const application of this.props.customizationsConfig.applications ?? []) { - this.synthProcessEachApplicationStack(application); - } - } - - private synthProcessEachApplicationStack(application: AppConfigItem) { - if ( - isIncluded( - application.deploymentTargets, - 'us-east-1', - '444444444444', - this.props.accountsConfig, - this.props.organizationConfig, - ) - ) { - const applicationStackName = `AWSAccelerator-App-${application.name}-444444444444-us-east-1`; - const env = { - account: '444444444444', - region: 'us-east-1', - }; - - new ApplicationsStack(this.app, applicationStackName, { - env, - ...this.props, - appConfigItem: application, - }); - } - } - /** - * synth Network Association stacks - */ - private synthNetworkAssociationStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new NetworkAssociationsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_ASSOCIATIONS]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth Network Association stacks - */ - private synthNetworkAssociationGwlbStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new NetworkAssociationsGwlbStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth Network Prep stacks - */ - private synthNetworkPrepStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new NetworkPrepStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_PREP]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth Network VPC DNS stacks - */ - private synthNetworkVpcDnsStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new NetworkVpcDnsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC_DNS]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth Network VPC Endpoints stacks - */ - private synthNetworkVpcEndPointsStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new NetworkVpcEndpointsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC_ENDPOINTS]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth Network VPC stacks - */ - private synthNetworkVpcStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new NetworkVpcStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - - /** - * synth Operations stacks - */ - private synthOperationsStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new OperationsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.OPERATIONS]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - accountWarming: account.warm ?? false, - }, - ), - ); - } - } - } - /** - * synth IdentityCenter stacks - */ - private synthIdentityCenterStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new IdentityCenterStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.IDENTITY_CENTER]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - - /** - * synth Organizations stacks - */ - private synthOrganizationsStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - this.stacks.set( - `${this.managementAccount.name}-${region}`, - new OrganizationsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.ORGANIZATIONS]}-${this.managementAccountId}-${region}`, - { - env: { - account: this.managementAccountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - /** - * synth Prepare stacks - */ - private synthPrepareStacks() { - this.stacks.set( - `${this.managementAccount.name}-${this.homeRegion}`, - new PrepareStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.PREPARE]}-${this.managementAccountId}-${this.homeRegion}`, - { - env: { - account: this.managementAccountId, - region: this.homeRegion, - }, - ...this.props, - }, - ), - ); - } - /** - * synth Key stacks - */ - private synthKeyStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - this.stacks.set( - `${this.auditAccount.name}-${region}`, - new KeyStack(this.app, `${AcceleratorStackNames[AcceleratorStage.KEY]}-${this.auditAccountId}-${region}`, { - env: { - account: this.auditAccountId, - region: region, - }, - ...this.props, - }), - ); - } - } - /** - * synth SecurityResources stacks - */ - private synthSecurityResourcesStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new SecurityResourcesStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.SECURITY_RESOURCES]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth ResourcePolicyEnforcementStack - */ - private synthResourcePolicyEnforcementStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new ResourcePolicyEnforcementStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } - /** - * synth Security stacks - */ - private synthSecurityStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new SecurityStack(this.app, `${AcceleratorStackNames[AcceleratorStage.SECURITY]}-${accountId}-${region}`, { - env: { - account: accountId, - region: region, - }, - ...this.props, - }), - ); - } - } - } - /** - * synth Bootstrap stacks - */ - private synthBootstrapStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new BootstrapStack(this.app, `${AcceleratorStackNames[AcceleratorStage.BOOTSTRAP]}-${accountId}-${region}`, { - env: { - account: accountId, - region: region, - }, - ...this.props, - }), - ); - } - } - } - /** - * synth Account stacks - */ - - private synthAccountStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - this.stacks.set( - `${this.managementAccount.name}-${region}`, - new AccountsStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.ACCOUNTS]}-${this.managementAccountId}-${region}`, - { - env: { - account: this.managementAccountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - - /** - * Synth Dependencies stacks - */ - private synthDependenciesStacks() { - for (const region of this.props.globalConfig.enabledRegions) { - for (const account of [ - ...this.props.accountsConfig.mandatoryAccounts, - ...this.props.accountsConfig.workloadAccounts, - ]) { - const accountId = this.props.accountsConfig.getAccountId(account.name); - this.stacks.set( - `${account.name}-${region}`, - new DependenciesStack( - this.app, - `${AcceleratorStackNames[AcceleratorStage.DEPENDENCIES]}-${accountId}-${region}`, - { - env: { - account: accountId, - region: region, - }, - ...this.props, - }, - ), - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/accelerator.test.ts b/source/packages/@aws-accelerator/accelerator/test/accelerator.test.ts deleted file mode 100644 index cf091f9..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/accelerator.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { describe, beforeEach, afterEach, expect, test, jest } from '@jest/globals'; -import { getCentralLogBucketKmsKeyArn } from '../lib/accelerator'; -import { STSClient, AssumeRoleCommand, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; -import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; - -jest.mock('uuid', () => ({ v4: () => '123456789' })); -let stsMock: AwsClientStub; -let ssmMock: AwsClientStub; - -describe('getCentralLogBucketKmsKeyArn', () => { - beforeEach(() => { - stsMock = mockClient(STSClient); - ssmMock = mockClient(SSMClient); - }); - afterEach(() => { - stsMock.reset(); - ssmMock.reset(); - }); - test('should return the correct KMS key ARN cross account', async () => { - // Given - logArchive account is 333333333333 - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - // Assume role in logArchive account - stsMock.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'fake-access-key', - SecretAccessKey: 'fake-secret-key', - SessionToken: 'fake-session-token', - Expiration: new Date(Date.now() + 3600 * 1000), - }, - }); - ssmMock.on(GetParameterCommand).resolves({ Parameter: { Value: 'fake-arn' } }); - // When - const result = await getCentralLogBucketKmsKeyArn( - 'us-east-1', - 'aws', - '333333333333', - 'managementAccountAccessRole', - 'parameterName', - true, - ); - // Then - expect(result).toEqual('fake-arn'); - }); - test('orgs disabled', async () => { - // Given - logArchive account is 333333333333 - // When - const result = await getCentralLogBucketKmsKeyArn( - 'us-east-1', - 'aws', - '333333333333', - 'managementAccountAccessRole', - 'parameterName', - false, - ); - // Then - expect(result).toEqual('123456789'); - }); - test('should return the correct KMS key ARN same account', async () => { - // Given - logArchive account is 333333333333 - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '333333333333', - }); - ssmMock.on(GetParameterCommand).resolves({ Parameter: { Value: 'fake-arn' } }); - // When - const result = await getCentralLogBucketKmsKeyArn( - 'us-east-1', - 'aws', - '333333333333', - 'managementAccountAccessRole', - 'parameterName', - true, - ); - // Then - expect(result).toEqual('fake-arn'); - }); - test('should return the UUID on error', async () => { - // Given - logArchive account is 333333333333 - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '333333333333', - }); - ssmMock.on(GetParameterCommand).rejects({}); - // When - const result = await getCentralLogBucketKmsKeyArn( - 'us-east-1', - 'aws', - '333333333333', - 'managementAccountAccessRole', - 'parameterName', - true, - ); - // Then - expect(result).toEqual('123456789'); - }); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/accounts-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/accounts-stack.test.ts deleted file mode 100644 index d39b02e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/accounts-stack.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(AccountsStack): '; - -describe('AccountsStack us-east-1', () => { - const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.ACCOUNTS, 'aws', 'us-east-1'); - const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - snapShotTest(testNamePrefix, stack); -}); - -describe('AccountsStack us-west-2', () => { - const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.ACCOUNTS, 'aws', 'us-east-1'); - - const stack = acceleratorTestStacks.stacks.get(`Management-us-west-2`)!; - snapShotTest('Construct(AccountsStackUsWest2): ', stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/app-utils.test.ts b/source/packages/@aws-accelerator/accelerator/test/app-utils.test.ts deleted file mode 100644 index 1e69c24..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/app-utils.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AccountsConfig, OrganizationConfig } from '@aws-accelerator/config'; -import AWS from 'aws-sdk'; -import { - AcceleratorResourcePrefixes, - getContext, - setResourcePrefixes, - isBeforeBootstrapStage, - setAcceleratorEnvironment, -} from '../utils/app-utils'; -import { describe, expect, test, jest } from '@jest/globals'; -import * as cdk from 'aws-cdk-lib'; -import * as path from 'path'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorStackProps } from '../lib/stacks/accelerator-stack'; - -function testAppUtils() { - const app = new cdk.App({ - context: { 'config-dir': path.join(__dirname, `configs/snapshot-only`), partition: 'aws' }, - }); - // Read in context inputs - const context = getContext(app); - - // Set various resource name prefixes used in code base - const resourcePrefixes = setResourcePrefixes(process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'); - - // Set accelerator environment variables - const acceleratorEnv = setAcceleratorEnvironment(process.env, resourcePrefixes, context.stage); - return { context, resourcePrefixes, acceleratorEnv }; -} - -test('AppUtilTest', () => { - const { acceleratorEnv: testAcceleratorEnv } = testAppUtils(); - expect(testAcceleratorEnv).toHaveProperty('auditAccountEmail'); -}); - -const prefixes: AcceleratorResourcePrefixes = { - accelerator: 'AWSAccelerator', - bucketName: 'accelerator-bucket', - databaseName: 'accelerator-db', - kmsAlias: 'alias/accelerator-kms', - repoName: 'accelerator-repo', - secretName: 'accelerator-secret', - snsTopicName: 'accelerator', - ssmParamName: 'aws-accelerator-ssm', - importResourcesSsmParamName: 'aws-accelerator-import-resources-ssm', - trailLogName: 'accelerator-trail-log', - ssmLogName: 'aws-accelerator', -}; - -describe('getContext ideally', () => { - test('getContext default', () => { - const app = new cdk.App({ - context: { - partition: 'aws', - 'config-dir': '/path/to/config/dir', - stage: 'logging', - account: '123456789012', - region: 'us-east-1', - }, - }); - const context = getContext(app); - expect(context).toBeDefined(); - }); - test('getContext no partition', () => { - const app = new cdk.App({ - context: { - 'config-dir': '/path/to/config/dir', - stage: 'logging', - account: '123456789012', - region: 'us-east-1', - }, - }); - function getContextNoPartition() { - getContext(app); - } - expect(getContextNoPartition).toThrowError(new Error('Partition value must be specified in app context')); - }); -}); - -describe('test setResourcePrefixes', () => { - test('setResourcePrefixes default', () => { - const prefixReturn = setResourcePrefixes(process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'); - expect(prefixReturn.accelerator).toBe('AWSAccelerator'); - }); - test('setResourcePrefixes custom prefix', () => { - const prefixReturn = setResourcePrefixes('CustomPrefix'); - expect(prefixReturn.accelerator).toBe('CustomPrefix'); - }); -}); - -describe('test isBeforeBootstrapStage', () => { - test('isBeforeBootstrapStage default', () => { - const isBeforeBootstrapStageReturn = isBeforeBootstrapStage('synth', 'prepare'); - expect(isBeforeBootstrapStageReturn).toBe(true); - }); - test('isBeforeBootstrapStage post bootstrap', () => { - const isBeforeBootstrapStageReturn = isBeforeBootstrapStage('synth', 'logging'); - expect(isBeforeBootstrapStageReturn).toBe(false); - }); - test('isBeforeBootstrapStage bootstrap', () => { - const isBeforeBootstrapStageReturn = isBeforeBootstrapStage('bootstrap'); - expect(isBeforeBootstrapStageReturn).toBe(true); - }); - test('isBeforeBootstrapStage no stage', () => { - const isBeforeBootstrapStageReturn = isBeforeBootstrapStage('deploy'); - expect(isBeforeBootstrapStageReturn).toBe(false); - }); -}); - -describe('test setAcceleratorEnvironment', () => { - test('setAcceleratorEnvironment default', () => { - const setAcceleratorEnvironmentReturn = setAcceleratorEnvironment( - { USE_EXISTING_CONFIG_REPO: 'No', ACCELERATOR_QUALIFIER: 'AWSAccelerator' }, - prefixes, - 'prepare', - ); - expect(setAcceleratorEnvironmentReturn.qualifier).toBeDefined; - }); - test('setAcceleratorEnvironment config repo error', () => { - function setConfigRepoNameError() { - setAcceleratorEnvironment( - { USE_EXISTING_CONFIG_REPO: 'Yes', ACCELERATOR_QUALIFIER: 'AWSAccelerator' }, - prefixes, - 'prepare', - ); - } - const errMsg = - 'Attempting to deploy pipeline stage(s) and environment variables are not set [EXISTING_CONFIG_REPOSITORY_NAME, EXISTING_CONFIG_REPOSITORY_BRANCH_NAME], when USE_EXISTING_CONFIG_REPO environment is set to Yes'; - expect(setConfigRepoNameError).toThrowError(new Error(errMsg)); - }); - test('setAcceleratorEnvironment existing repo', () => { - const setAcceleratorEnvironmentReturn = setAcceleratorEnvironment( - { - USE_EXISTING_CONFIG_REPO: 'Yes', - ACCELERATOR_QUALIFIER: 'AWSAccelerator', - EXISTING_CONFIG_REPOSITORY_NAME: 'test-config', - EXISTING_CONFIG_REPOSITORY_BRANCH_NAME: 'test', - }, - prefixes, - 'prepare', - ); - expect(setAcceleratorEnvironmentReturn.configRepositoryName).toBe('test-config'); - }); - test('setAcceleratorEnvironment checkMandatoryEnvVariables error', () => { - function checkMandatoryEnvVariablesError() { - setAcceleratorEnvironment( - { - USE_EXISTING_CONFIG_REPO: 'No', - ACCELERATOR_QUALIFIER: 'AWSAccelerator', - MANAGEMENT_ACCOUNT_EMAIL: 'management@example.com', - LOG_ARCHIVE_ACCOUNT_EMAIL: 'log@example.com', - }, - prefixes, - 'pipeline', - ); - } - - expect(checkMandatoryEnvVariablesError).toThrowError( - new Error( - 'Missing mandatory environment variables: AUDIT_ACCOUNT_EMAIL, CONTROL_TOWER_ENABLED, ACCELERATOR_REPOSITORY_BRANCH_NAME', - ), - ); - }); -}); - -describe('test setAcceleratorStackProps', () => { - function initializeMock() { - AccountsConfig.prototype.loadAccountIds = jest - .fn< - ( - partition: string, - enableSingleAccountMode: boolean, - isOrgsEnabled: boolean, - accountConfig: AccountsConfig, - ) => Promise - >() - .mockResolvedValue(); - OrganizationConfig.prototype.loadOrganizationalUnitIds = jest - .fn<(partition: string) => Promise>() - .mockResolvedValue(); - - // mock STS AssumeRole - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const mockAssumeRole = jest.fn<() => { promise: any }>(); - (AWS.STS.prototype.assumeRole as jest.Mock) = mockAssumeRole.mockReturnValue({ - promise: jest - .fn<() => Promise<{ Credentials: { AccessKeyId: string; SecretAccessKey: string; SessionToken: string } }>>() - .mockResolvedValue({ - Credentials: { - AccessKeyId: 'fake-cred', - SecretAccessKey: 'fake-cred', - SessionToken: 'fake-cred', - }, - }), - }); - // Mock EC2 describeVpcs method - const mockDescribeVpcs = jest.fn<() => { promise: unknown }>(); - (AWS.EC2.prototype.describeVpcs as jest.Mock) = mockDescribeVpcs.mockReturnValue({ - promise: jest - .fn<() => Promise<{ Vpcs: { VpcId: string }[] }>>() - .mockResolvedValueOnce({ - Vpcs: [{ VpcId: 'fake-vpc-id-1' }], - }) - .mockResolvedValueOnce({ - Vpcs: [{ VpcId: 'fake-vpc-id-2' }], - }) - .mockResolvedValueOnce({ - Vpcs: [{ VpcId: 'fake-vpc-id-3' }], - }) - .mockResolvedValueOnce({ - Vpcs: [{ VpcId: 'fake-vpc-id-4' }], - }), - }); - - // Mock EC2 describeVpcEndpoints method - const mockDescribeVpcEndpoints = jest.fn<() => { promise: unknown }>(); - (AWS.EC2.prototype.describeVpcEndpoints as jest.Mock) = mockDescribeVpcEndpoints.mockReturnValue({ - promise: jest - .fn<() => Promise<{ VpcEndpoints: { VpcEndpointId: string }[] }>>() - .mockResolvedValueOnce({ - VpcEndpoints: [{ VpcEndpointId: 'fake-vpce-id-1' }], - }) - .mockResolvedValueOnce({ VpcEndpoints: [{ VpcEndpointId: 'fake-vpce-id-2' }] }), - }); - - const accelerator = require('../lib/accelerator.ts'); - accelerator.getCentralLogBucketKmsKeyArn = jest.fn().mockReturnValue(Promise.resolve('fake-kms-arn')); - } - - test('should load VPC IDs and VPCE IDs in network config for Finalize Stage', async () => { - initializeMock(); - const { context, resourcePrefixes, acceleratorEnv } = testAppUtils(); - const { setAcceleratorStackProps } = require('../utils/app-utils'); - - context.stage = AcceleratorStage.FINALIZE; - const { networkConfig } = (await setAcceleratorStackProps( - context, - acceleratorEnv, - resourcePrefixes, - 'us-east-1', - )) as AcceleratorStackProps; - - // ${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure} and ${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network} are used in - // snapshot-only/service-control-policies/data-perimeter.json - // Here are the expected VPC IDs from account under OU Infrastructure - Network and ShareService account - const expectedAccountVpcIds = { - '444444444444': ['fake-vpc-id-1', 'fake-vpc-id-2'], - '555555555555': ['fake-vpc-id-3', 'fake-vpc-id-4'], - }; - const expectedAccountVpceIds = { '555555555555': ['fake-vpce-id-1', 'fake-vpce-id-2'] }; // expected VPCE ID from network account - expect(networkConfig.accountVpcIds).toEqual(expectedAccountVpcIds); - expect(networkConfig.accountVpcEndpointIds).toEqual(expectedAccountVpceIds); - }); - - test('should not load VPC IDs and VPCE IDs in network config for other Stages except for finalize and account stage', async () => { - initializeMock(); - const { setAcceleratorStackProps } = require('../utils/app-utils'); - - const { context, resourcePrefixes, acceleratorEnv } = testAppUtils(); - - context.stage = AcceleratorStage.OPERATIONS; - const { networkConfig } = (await setAcceleratorStackProps( - context, - acceleratorEnv, - resourcePrefixes, - 'us-east-1', - )) as AcceleratorStackProps; - - expect(networkConfig.accountVpcIds).toEqual(undefined); - expect(networkConfig.accountVpcEndpointIds).toEqual(undefined); - }); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/applications-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/applications-stack.test.ts deleted file mode 100644 index dc99ab8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/applications-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(ApplicationsStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.CUSTOMIZATIONS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`SharedServices-us-east-1`)!; - -describe('ApplicationsStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/bootstrap-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/bootstrap-stack.test.ts deleted file mode 100644 index 87c0422..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/bootstrap-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(BootstrapStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.BOOTSTRAP, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('BootstrapStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/config-repository.test.ts b/source/packages/@aws-accelerator/accelerator/test/config-repository.test.ts deleted file mode 100644 index ad2fb4f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/config-repository.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { ConfigRepository } from '../lib/config-repository'; -import * as cdk from 'aws-cdk-lib'; -import { describe, it, expect } from '@jest/globals'; -import { Repository } from 'aws-cdk-lib/aws-codecommit'; - -describe('accounts-config', () => { - const stack = new cdk.Stack(); - const configRepository = new ConfigRepository(stack, 'ConfigRepository', { - repositoryName: 'aws-accelerator-config', - repositoryBranchName: 'main', - description: 'LZA config repo', - managementAccountEmail: 'example1@example.com', - logArchiveAccountEmail: 'example2@example.com', - auditAccountEmail: 'example3@example.com', - controlTowerEnabled: 'yes', - enableSingleAccountMode: false, - }); - - const configRepository2 = new ConfigRepository(stack, 'ConfigRepository2', { - repositoryName: 'aws-accelerator-config', - repositoryBranchName: 'main', - description: 'LZA config repo', - managementAccountEmail: 'example1@example.com', - logArchiveAccountEmail: 'example2@example.com', - auditAccountEmail: 'example3@example.com', - controlTowerEnabled: 'no', - enableSingleAccountMode: false, - }); - - describe('AccountIdConfig', () => { - it('is tested', () => { - expect(configRepository.getRepository()).toBeInstanceOf(Repository); - expect(configRepository2.getRepository()).toBeInstanceOf(Repository); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/accounts-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/accounts-config.yaml deleted file mode 100644 index 17de322..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/accounts-config.yaml +++ /dev/null @@ -1,39 +0,0 @@ -mandatoryAccounts: - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: management-account@example.com - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: logarchive-account@example.com - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: audit-account@example.com - organizationalUnit: Security -workloadAccounts: - - name: SharedServices - description: The SharedServices account - email: shared-services@example.com - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: network@example.com - organizationalUnit: Infrastructure - - name: CentralSecurity - description: Central Security account - email: central-security@example.com - organizationalUnit: Security -accountIds: - - email: management-account@example.com - accountId: '111111111111' - - email: audit-account@example.com - accountId: '222222222222' - - email: logarchive-account@example.com - accountId: '333333333333' - - email: shared-services@example.com - accountId: '444444444444' - - email: network@example.com - accountId: '555555555555' - - email: central-security@example.com - accountId: '666666666666' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-connector-permissions-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-connector-permissions-setup.ps1 deleted file mode 100644 index ee25a83..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-connector-permissions-setup.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -Start-Process powershell.exe -Credential $credential -ArgumentList "-file c:\cfn\scripts\AD-group-grant-permissions-setup.ps1", "$GroupName" \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-grant-permissions-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-grant-permissions-setup.ps1 deleted file mode 100644 index efab9d6..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-grant-permissions-setup.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -#Delegate Control -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;computer" -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;user" \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-setup.ps1 deleted file mode 100644 index f27268f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-group-setup.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - $groupName = $groupsArray[$i] - $groupExists = Get-ADGroup -Filter {Name -eq $groupName} -Credential $credential - if($null -eq $groupExists) { - #Create Group - New-ADGroup -Name $groupName -GroupCategory Security -GroupScope Global -Credential $credential - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-group-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-group-setup.ps1 deleted file mode 100644 index 848d3c0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-group-setup.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $UserName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - #Add User to Group - Add-ADGroupMember -Identity $groupsArray[$i] -Members $UserName -Credential $credential -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-setup.ps1 deleted file mode 100644 index dd154bf..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AD-user-setup.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -[CmdletBinding()] -param( - [string] - $UserName, - - [string] - $Password, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [string] - $PasswordNeverExpires, - - [Parameter(Mandatory=$false)] - [AllowEmptyString()] - [string]$UserEmailAddress = '' -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$pass = ConvertTo-SecureString $Password -AsPlainText -Force -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword -$userExists = Get-ADUser -Credential $credential -Filter "Name -eq '$UserName'" - -If ($null -eq $userExists -and $UserEmailAddress) { - #Create User - New-ADUser -Name $UserName -EmailAddress $UserEmailAddress -AccountPassword $pass -Enabled 1 -Credential $credential -SamAccountName $UserName -} - -#Set the admin & connector user's password never expires flag -If (-NOT ($PasswordNeverExpires -eq 'No')) { - Set-ADUser -Identity $UserName -PasswordNeverExpires $true -Credential $credential -} Else { - Set-ADUser -Identity $UserName -PasswordNeverExpires $false -Credential $credential -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AWSQuickStart.psm1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AWSQuickStart.psm1 deleted file mode 100644 index fc1b30f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/AWSQuickStart.psm1 +++ /dev/null @@ -1,345 +0,0 @@ -function New-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [string] - $Handle, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\', - - [Parameter(Mandatory=$false)] - [switch] - $Base64Handle - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - if ($Base64Handle) { - Write-Verbose "Trying to decode handle Base64 string as UTF8 string" - $decodedHandle = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Handle)) - if ($decodedHandle -notlike "http*") { - Write-Verbose "Now trying to decode handle Base64 string as Unicode string" - $decodedHandle = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($Handle)) - } - Write-Verbose "Decoded handle string: $decodedHandle" - $Handle = $decodedHandle - } - - Write-Verbose "Creating Handle Registry Key" - New-ItemProperty -Path $Path -Name Handle -Value $Handle -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function New-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [string] - $Stack, - - [Parameter(Mandatory=$true)] - [string] - $Resource, - - [Parameter(Mandatory=$true)] - [string] - $Region, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - Write-Verbose "Creating Stack Registry Key" - New-ItemProperty -Path $Path -Name Stack -Value $Stack -Force - - Write-Verbose "Creating Resource Registry Key" - New-ItemProperty -Path $Path -Name Resource -Value $Resource -Force - - Write-Verbose "Creating Region Registry Key" - New-ItemProperty -Path $Path -Name Region -Value $Region -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - - -function Get-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - Write-Verbose "Getting ErrorCount Registry Key" - Get-ItemProperty -Path $Path -Name ErrorCount -ErrorAction Stop | Select-Object -ExpandProperty ErrorCount - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Set-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [int32] - $Count, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $currentCount = Get-AWSQuickStartErrorCount - $currentCount += $Count - - Write-Verbose "Creating ErrorCount Registry Key" - Set-ItemProperty -Path $Path -Name ErrorCount -Value $currentCount -ErrorAction Stop - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty $Path - - return $key.Handle - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty $Path - $resourceSignal = @{ - Stack = $key.Stack - Resource = $key.Resource - Region = $key.Region - } - $toReturn = New-Object -TypeName PSObject -Property $resourceSignal - - if ($toReturn.Stack -and $toReturn.Resource -and $toReturn.Region) { - return $toReturn - } else { - return $null - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Remove-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty -Path $Path -Name Handle -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing Handle key value from $Path" - Remove-ItemProperty -Path $Path -Name Handle - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Remove-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - foreach ($keyName in @('Stack','Resource','Region')) { - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty -Path $Path -Name $keyName -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing $keyName key value from $Path" - Remove-ItemProperty -Path $Path -Name $keyName - } - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Write-AWSQuickStartEvent { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipelineByPropertyName=$true)] - [string] - $Message, - - [Parameter(Mandatory=$false)] - [string] - $EntryType = 'Error' - ) - - process { - Write-Verbose "Checking for AWSQuickStart Eventlog Source" - if(![System.Diagnostics.EventLog]::SourceExists('AWSQuickStart')) { - New-EventLog -LogName Application -Source AWSQuickStart -ErrorAction SilentlyContinue - } - else { - Write-Verbose "AWSQuickStart Eventlog Source exists" - } - - Write-Verbose "Writing message to application log" - - try { - Write-EventLog -LogName Application -Source AWSQuickStart -EntryType $EntryType -EventId 1001 -Message $Message - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Write-AWSQuickStartException { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - process { - try { - Write-Verbose "Incrementing error count" - Set-AWSQuickStartErrorCount -Count 1 - - Write-Verbose "Getting total error count" - $errorTotal = Get-AWSQuickStartErrorCount - - $errorMessage = "Command failure in {0} {1} on line {2} `nException: {3}" -f $ErrorRecord.InvocationInfo.MyCommand.name, - $ErrorRecord.InvocationInfo.ScriptName, $ErrorRecord.InvocationInfo.ScriptLineNumber, $ErrorRecord.Exception.ToString() - - $CmdSafeErrorMessage = $errorMessage -replace '[^a-zA-Z0-9\s\.\[\]\-,:_\\\/\(\)]', '' - if ($CmdSafeErrorMessage.length -gt 255) { - $CmdSafeErrorMessage = $CmdSafeErrorMessage.substring(0,252) + '...' - } - - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 1 --reason='$CmdSafeErrorMessage' '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 1 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - finally { - Write-AWSQuickStartEvent -Message $errorMessage - # throwing an exception to force cfn-init execution to stop - throw $CmdSafeErrorMessage - } - } -} - -function Write-AWSQuickStartStatus { - [CmdletBinding()] - Param() - - process { - try { - Write-Verbose "Checking error count" - if((Get-AWSQuickStartErrorCount) -eq 0) { - Write-Verbose "Getting Handle" - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 0 '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 0 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Configure-password-policy.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Configure-password-policy.ps1 deleted file mode 100644 index fde4721..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Configure-password-policy.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [Boolean] - $ComplexityEnabled, - - [string] - $LockoutDuration, - - [string] - $LockoutObservationWindow, - - [string] - $LockoutThreshold, - - [string] - $MaxPasswordAge, - - [string] - $MinPasswordAge, - - [string] - $MinPasswordLength, - - [string] - $PasswordHistoryCount, - - [Boolean] - $ReversibleEncryptionEnabled -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -#Configure passsord policy for all users -Set-ADFineGrainedPasswordPolicy -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -ComplexityEnabled:$ComplexityEnabled -MaxPasswordAge:$MaxPasswordAge -LockoutDuration:$LockoutDuration -LockoutObservationWindow:$LockoutObservationWindow -LockoutThreshold:$LockoutThreshold -MinPasswordAge:$MinPasswordAge -MinPasswordLength:$MinPasswordLength -PasswordHistoryCount:$PasswordHistoryCount -ReversibleEncryptionEnabled:$ReversibleEncryptionEnabled -Server:$fdn -Credential $credential - -#Create password policy subject -Add-ADFineGrainedPasswordPolicySubject -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -Server:$fdn -Subjects:"CN=Domain Users,CN=Users,DC=$dom,DC=$ext" -Credential $credential diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Join-Domain.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Join-Domain.ps1 deleted file mode 100644 index acdf6d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ad-config-scripts/Join-Domain.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainName, - - [string] - $UserName, - - [string] - $Password -) - -try { - $ErrorActionPreference = "Stop" - - $pass = ConvertTo-SecureString $Password -AsPlainText -Force - $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$pass - - Add-Computer -DomainName $DomainName -Credential $cred -ErrorAction Stop - - # Execute restart after script exit and allow time for external services - $shutdown = Start-Process -FilePath "shutdown.exe" -ArgumentList @("/r", "/t 10") -Wait -NoNewWindow -PassThru - if ($shutdown.ExitCode -ne 0) { - throw "[ERROR] shutdown.exe exit code was not 0. It was actually $($shutdown.ExitCode)." - } -} -catch { - $_ | Write-AWSQuickStartException -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/appConfigs/appA/launchTemplate/userData.sh b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/appConfigs/appA/launchTemplate/userData.sh deleted file mode 100644 index 01e78a0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/appConfigs/appA/launchTemplate/userData.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -yum update -y -yum install -y httpd -systemctl start httpd -systemctl enable httpd -usermod -a -G apache ec2-user -chown -R ec2-user:apache /var/www -chmod 2775 /var/www -find /var/www -type d -exec chmod 2775 {} \; -find /var/www -type f -exec chmod 0664 {} \; \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/backup-policies/org-backup-policies.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/backup-policies/org-backup-policies.json deleted file mode 100644 index aadece2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/backup-policies/org-backup-policies.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "plans": { - "Organization_Backup_Plan": { - "rules": { - "Monthly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Weekly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 ? * MON *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Daily_Rule": { - "schedule_expression": { - "@@assign": "cron(15 10 * * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "NinetyDay_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */3 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Yearly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */12 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "1825" - }, - "delete_after_days": { - "@@assign": "365" - } - } - } - }, - "regions": { - "@@append": [ - "us-east-1" - ] - }, - "selections": { - "tags": { - "Monthly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupMonthly" - }, - "tag_value": { - "@@assign": [ - "Monthly" - ] - } - }, - "Weekly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupWeekly" - }, - "tag_value": { - "@@assign": [ - "Weekly" - ] - } - }, - "Daily_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupDaily" - }, - "tag_value": { - "@@assign": [ - "Daily" - ] - } - }, - "NinetyDay_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupNinetyDay" - }, - "tag_value": { - "@@assign": [ - "NinetyDay" - ] - } - }, - "Yearly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupYearly" - }, - "tag_value": { - "@@assign": [ - "Yearly" - ] - } - } - } - }, - "backup_plan_tags": { - "stage": { - "tag_key": { - "@@assign": "Stage" - }, - "tag_value": { - "@@assign": "Released" - } - } - } - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/cloudformation-templates/custom-s3-bucket.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/cloudformation-templates/custom-s3-bucket.yaml deleted file mode 100644 index e3b58d1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/cloudformation-templates/custom-s3-bucket.yaml +++ /dev/null @@ -1,37 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Resources: - LZACustomizationsBucket: - Type: 'AWS::S3::Bucket' - Properties: - BucketEncryption: - ServerSideEncryptionConfiguration: - - BucketKeyEnabled: false - ServerSideEncryptionByDefault: - SSEAlgorithm: 'aws:kms' - KMSMasterKeyID: 'aws/s3' - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - VersioningConfiguration: - Status: Enabled - - LZACustomizationsSampleBucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref LZACustomizationsBucket - PolicyDocument: - Version: '2012-10-17' - Statement: - - Principal: - AWS: '*' - Action: - - 's3:*' - Resource: - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}/*' - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}' - Effect: 'Deny' - Condition: - Bool: - aws:SecureTransport: 'false' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-detection-role.json deleted file mode 100644 index db821df..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-detection-role.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-remediation-role.json deleted file mode 100644 index 52701c4..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile-remediation-role.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/attach-ec2-instance-profile.zip deleted file mode 100644 index 1de00bf06d02573ed8890d48da07d3c50ed785dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmWIWW@Zs#-~hs}B^f~sQ1F+70VtxtkeQc~TA`O!92&yQz<&LuYr;(+2GOMz+zgB? zq7NO*Sk6>47j6wZm?vc@u=lg5@XAw7F6KdfA(jD$8nqfZR#bH?dQic>VN-T~u0D(F zpX%e;(^4A+LblF1V4R}~FRwR-!${OMK~i1d4x^c&R)1FH?mAy5u>2*L=IrOQWFLCFF_mpk zTpd;MxlyY1^FEb*3(hJmpQ(Fzmang~No3CPSE8xhdmMJzWIeO9ULj`l%zZ=Vir-Ug zG-sSLIUaY#O6B#VyftTqBh;U2ZrLn3%Yq{>7<#t{oL2L{{QrZl|Oi`P~s=;Bwwr0?Z0pQ5l}FFIxo7y-e_C) ztOIv8-sta67UzE}-@lo|_istR^wqgHU!DDKCHIB>@pGfVfPggXy)%2BbKHt`o9Lv> z%&p`5STusw)r&cc>)P6n)4yMCU8`<))G9vtz>S1+-*^PwLjuzNxCWlNt$QrPkn5k~ z-2)fbs<1y~i85lI=$gq(BwSQb`SYegdz0$Xv-@{` zIluId1^@*MvS$DQ diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/bucket-sse-enabled-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/bucket-sse-enabled-remediation-role.json deleted file mode 100644 index 3c37861..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/bucket-sse-enabled-remediation-role.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["s3:GetEncryptionConfiguration", "s3:PutEncryptionConfiguration"], - "Resource": "*", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": ["arn:aws:iam::*:role/AWSAccelerator-SecuritySt-*"] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-detection-role.json deleted file mode 100644 index e612dc8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-detection-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "iam:Get*", - "iam:List*" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json deleted file mode 100644 index 83c5a8d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/custom-config-rules/ec2-instance-profile-permissions.zip deleted file mode 100644 index 9d64fe687967e4fa3cf01897db666d68356cb1af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1292 zcmWIWW@Zs#-~hr?Ejd99P~gSS02EPR$jnPgtcVx~UR-qK+7Txoqf#cY`$ghLj~zm}!V^8GE_ovn z`S&}Yr>*YMNS2oi_uiay{QKF%#*aM;mL~qWF0{_!(tcyLmG37u|B5`?A6H=M;3>hm zFKxDg;B4NhE86c*^?30#K_D_)-JNkc`(v#+H`-OFR*MDNuMj!Def#yzCx3i@{qpbK zlAS+3=NXyqm~U$$)tmNs_Qe-`veoeyn(UT3Wr^l-_6u`+UzzB>;atU%nGYUZwtZTa zaly3q5~B_Of+cA?e(_KC$=ukyTW8@0F`-4ey>gQG53KcYIkJLjk?Fzf5?dHr*gLmP z(5yL8(X+rf#ka!cVOW=`^Zvvi4G*^z{)^Ciw(ydW?dGo=mmJ-!B6(*$FlJ zJdG;@4|c3c^;)v%oK}uzh{HJpCABwK4VPxz4B{yd(cHAqE$p@j&+lm-n}TaSrcTMy z60P~ocCYwdq{lY4|LwP|jy$>}(DyI-jP&Np-yaxfN$Fh-nYUu+B!(AjMc8l41Z%c_ z*{gXu`pi4m>z`Q@=IX_LC}(uJw8=YJ-{E=X!S5!H;o%Q-eN!i<*oe$yj~GN&RZ^+BwtZ_wV4Z3=r|`k_|3f z@hsx_9EFew%~`ypDUAoI!}zSW@=FD zx|Oki@h)$bDVpXfr_%no2+vuv@q6FHDz!Ue76!(X`CJ^|JP5FNo2t{v`1XM|YYu~$ z@Zlwr_ZDb8)joK0=f5sKv3qZiB&o(3FTHP_vAOH4B7@J(9e0e>{@jQ>ac_24#-keN z8Hr9m-STrE*8g08H0G0T*;V0V&B4SvPJtDi!u)yMj>Ar4xp(^M2X~c(Ze?O!&FVi-my!R0;)nGct)VBT6V_IZz1& d14|k~EMm*60B=?{ka|WS3 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; priority:1; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; priority:1; flow:to_server, established; sid:104; rev:1;) \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/global-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/global-config.yaml deleted file mode 100644 index 845ad39..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/global-config.yaml +++ /dev/null @@ -1,120 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -enabledRegions: - - *HOME_REGION - - us-west-2 -managementAccountAccessRole: AWSControlTowerExecution -cloudwatchLogRetentionInDays: 3653 -acceleratorMetadata: - enable: true - account: LogArchive - readOnlyAccessRoleArns: - - arn:aws:iam::111111111111:role/test-access-role -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - notify-security@example.com -terminationProtection: true -controlTower: - enable: true -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true -logging: - account: LogArchive - centralizedLoggingRegion: us-west-2 - cloudtrail: - enable: false - organizationTrail: false - sessionManager: - sendToCloudWatchLogs: false - sendToS3: true - cloudwatchLogs: - dynamicPartitioning: dynamic-partitioning/log-filters.json - exclusions: - - organizationalUnits: - - Infrastructure - excludeAll: true - - accounts: - - Management - regions: - - us-east-1 - logGroupNames: - - test/* - -limits: - - serviceCode: lambda - quotaCode: L-B99A9384 - desiredValue: 1000 - deploymentTargets: - organizationalUnits: - - Root -reports: - costAndUsageReport: - compression: Parquet - format: Parquet - reportName: accelerator-cur - s3Prefix: cur - timeUnit: DAILY - refreshClosedReports: true - reportVersioning: CREATE_NEW_REPORT - budgets: - - deploymentTargets: - accounts: - - Management - name: accel-budget - timeUnit: MONTHLY - type: COST - amount: 2000 - includeUpfront: true - includeTax: true - includeSupport: true - includeSubscription: true - includeRecurring: true - includeOtherSubscription: true - includeDiscount: true - includeCredit: false - includeRefund: false - useBlended: false - useAmortized: false - unit: USD - notifications: - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 100 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 90 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 80 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 75 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 50 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com -backup: - vaults: - - name: BackupVault - deploymentTargets: - organizationalUnits: - - Root diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-config.yaml deleted file mode 100644 index 24d1dff..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-config.yaml +++ /dev/null @@ -1,194 +0,0 @@ -policySets: - - deploymentTargets: - organizationalUnits: - - Root - policies: - - name: Default-Boundary-Policy - policy: iam-policies/boundary-policy.json -roleSets: - - deploymentTargets: - organizationalUnits: - - Root - roles: - - name: EC2-Default-SSM-AD-Role - instanceProfile: true - assumedBy: - - type: service - principal: ec2.amazonaws.com - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonSSMDirectoryServiceAccess - - CloudWatchAgentServerPolicy - boundaryPolicy: Default-Boundary-Policy - - name: Backup-Role - assumedBy: - - type: service - principal: backup.amazonaws.com - policies: - awsManaged: - - service-role/AWSBackupServiceRolePolicyForBackup - - service-role/AWSBackupServiceRolePolicyForRestores -groupSets: - - deploymentTargets: - organizationalUnits: - - Root - groups: - - name: Administrators - policies: - awsManaged: - - AdministratorAccess -identityCenter: - name: identityCenter1 - identityCenterPermissionSets: - - name: PermissionSet1 - policies: - awsManaged: - - arn:aws:iam::aws:policy/AdministratorAccess - customerManaged: - - ResourceConfigurationCollectorPolicy - sessionDuration: 60 - identityCenterAssignments: - - name: Assignment1 - permissionSetName: PermissionSet1 - principalId: 'abcd1234-1001-70f0-9c12-56a6aa967ca4' - principalType: USER - deploymentTargets: - accounts: - - LogArchive - - name: Assignment2 - permissionSetName: PermissionSet1 - principalId: '1234abcd-1001-70f0-9c12-56a6aa967ca4' - principalType: GROUP - deploymentTargets: - organizationalUnits: - - Security -userSets: - - deploymentTargets: - accounts: - - Management - users: - - username: breakGlassUser01 - group: Administrators - boundaryPolicy: Default-Boundary-Policy - - username: breakGlassUser02 - group: Administrators - boundaryPolicy: Default-Boundary-Policy -secretSets: - - deploymentTargets: - accounts: - - SharedServices - secrets: - - name: managedActiveDirectoryAdminUserSecret - description: Managed AD admin user secret - # kmsKeyName: Key1 - userName: Admin - passwordPolicy: - excludeUppercase: false - excludeLowercase: false - excludeNumbers: false - requireEachIncludedType: true - includeSpace: false - passwordLength: 32 - excludePunctuation: false - excludeCharacters: '@#' -managedActiveDirectories: - - name: AcceleratorManagedActiveDirectory - type: AWS Managed Microsoft AD - account: LogArchive - region: us-east-1 - dnsName: example.com - netBiosDomainName: example - description: Example managed AD - edition: Standard - vpcSettings: - vpcName: SharedServices-Main - subnets: - - SharedServices-App-A - - SharedServices-App-B - resolverRuleName: example-com-rule - secretConfig: - account: Audit - region: us-east-1 - adminSecretName: my-admin-002 - sharedOrganizationalUnits: - organizationalUnits: - - Root - excludedAccounts: - - Audit - # sharedAccounts: - # - Network - # - Management - logs: - groupName: /aws/directoryservice/AcceleratorManagedActiveDirectory - retentionInDays: 30 - activeDirectoryConfigurationInstance: - instanceType: t3.large - vpcName: SharedServices-Main - subnetName: SharedServices-App-A - imagePath: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base - securityGroupInboundSources: - - 10.4.0.0/16 - # securityGroupInboundConfigs: - # - description: Allow RDP and HTTPS Traffic Inbound - # types: - # - RDP - # - HTTPS - # sources: - # - 10.4.0.0/16 - # securityGroupName: ActiveDirectoryConfigInstanceSG - instanceRole: EC2-Default-SSM-AD-Role - userDataScripts: - - scriptName: JoinDomain - scriptFilePath: ad-config-scripts/Join-Domain.ps1 - - scriptName: AWSQuickStart - scriptFilePath: ad-config-scripts/AWSQuickStart.psm1 - - scriptName: ADGroupSetup - scriptFilePath: ad-config-scripts/AD-group-setup.ps1 - - scriptName: ADUserSetup - scriptFilePath: ad-config-scripts/AD-user-setup.ps1 - - scriptName: ADUserGroupSetup - scriptFilePath: ad-config-scripts/AD-user-group-setup.ps1 - - scriptName: ADGroupGrantPermissionsSetup - scriptFilePath: ad-config-scripts/AD-group-grant-permissions-setup.ps1 - - scriptName: ADConnectorPermissionsSetup - scriptFilePath: ad-config-scripts/AD-connector-permissions-setup.ps1 - - scriptName: ConfigurePasswordPolicy - scriptFilePath: ad-config-scripts/Configure-password-policy.ps1 - adGroups: - - aws-Provisioning - - aws-Billing - adPerAccountGroups: - - "*-Admin" - - "*-PowerUser" - - "*-View" - adConnectorGroup: ADConnector-grp - adPasswordPolicy: - history: 24 - maximumAge: 90 - minimumAge: 1 - minimumLength: 14 - complexity: true - reversible: false - failedAttempts: 6 - lockoutDuration: 30 - lockoutAttemptsReset: 30 - adUsers: - - name: adconnector-user-01 - email: example-adconnector-user@example.com - groups: - - ADConnector-grp - - name: user1-01 - email: example-user1@example.com - groups: - - aws-Provisioning - - "*-View" - - "*-Admin" - - "*-PowerUser" - - AWS Delegated Administrators - - name: user2-002 - email: example-user2@example.com - groups: - - aws-Provisioning - - "*-View" - diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-policies/boundary-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-policies/boundary-policy.json deleted file mode 100644 index 18de2ad..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/iam-policies/boundary-policy.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - }, - { - "Effect": "Deny", - "NotAction": [ - "iam:CreateVirtualMFADevice", - "iam:DeleteVirtualMFADevice", - "iam:ListVirtualMFADevices", - "iam:EnableMFADevice", - "iam:ResyncMFADevice", - "iam:ListAccountAliases", - "iam:ListUsers", - "iam:ListSSHPublicKeys", - "iam:ListAccessKeys", - "iam:ListServiceSpecificCredentials", - "iam:ListMFADevices", - "iam:GetAccountSummary", - "sts:GetSessionToken" - ], - "Resource": "*", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false", - "aws:ViaAWSService": "false" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/kms/kms-policy-01.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/kms/kms-policy-01.json deleted file mode 100644 index ef8e3bc..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/kms/kms-policy-01.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root" - }, - "Action": "kms:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/network-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/network-config.yaml deleted file mode 100644 index 190afea..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/network-config.yaml +++ /dev/null @@ -1,1088 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -defaultVpc: - delete: true - excludeAccounts: [] -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Core - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 10.200.0.0/16 - attachment: - directConnectGatewayName: Network-DXGW - - destinationCidrBlock: 10.300.0.0/16 - attachment: - vpnConnectionName: accelerator-vpn - - destinationCidrBlock: 1.1.1.1/32 - blackhole: true - - destinationPrefixList: accelerator-prefix-list - attachment: - vpcName: Network-Endpoints - account: Network - - name: Network-Main-Segregated - routes: [] - - name: Network-Main-Shared - routes: - - destinationCidrBlock: 10.200.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - name: Network-Main-Standalone - routes: [] - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 64512 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: SharedServices-Main-Core - routes: [] - - name: SharedServices-Main-Segregated - routes: [] - - name: SharedServices-Main-Shared - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - name: SharedServices-Main-Standalone - routes: [] -transitGatewayPeering: - - name: Network-Main-And-SharedServices-Main-Peering - requester: - transitGatewayName: SharedServices-Main - account: SharedServices - region: *HOME_REGION - routeTableAssociations: SharedServices-Main-Shared - tags: - - key: Name - value: Network-Main-And-SharedServices-Main-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - - key: Organization - value: WWPS - - key: Team - value: Development - accepter: - transitGatewayName: Network-Main - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-Shared - # autoAccept: true - # applyTags: false - -dhcpOptions: - - name: accelerator-dhcp-opts - accounts: - - Network - regions: - - *HOME_REGION - domainName: example.com - domainNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNodeType: 2 - ntpServers: - - 1.1.1.1 - - 2.2.2.2 -centralNetworkServices: - delegatedAdminAccount: Network - ipams: - - name: accelerator-ipam - region: *HOME_REGION - description: Accelerator IPAM - operatingRegions: - - *HOME_REGION - - us-west-2 - pools: - - name: &BASE_POOL base-pool - description: accelerator-base - provisionedCidrs: - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - name: home-region-pool - description: Pool for us-east-1 - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/16 - sourceIpamPool: *BASE_POOL - - name: home-region-prod-pool - description: Pool for prod environment - allocationResourceTags: - - key: env - value: prod - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - sourceIpamPool: home-region-pool - - name: west-region-pool - description: Pool for us-west-2 - locale: us-west-2 - provisionedCidrs: - - 10.1.0.0/16 - sourceIpamPool: *BASE_POOL - gatewayLoadBalancers: - - name: Accelerator-GWLB - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - deletionProtection: true - targetGroup: instance-target - endpoints: - - name: Endpoint-A - account: Network - subnet: Network-Inspection-A - vpc: Network-Inspection - - name: Endpoint-B - account: Network - subnet: Network-Inspection-B - vpc: Network-Inspection - networkFirewall: - firewalls: - - name: accelerator-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - policies: - - name: accelerator-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulRuleGroups: - - name: accelerator-rule-group - - name: domain-list-group - shareTargets: - organizationalUnits: - - Infrastructure - - name: accelerator-strict-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulEngineOptions: STRICT_ORDER - statefulRuleGroups: - - name: accelerator-strict-rule-group - priority: 100 - shareTargets: - organizationalUnits: - - Infrastructure - rules: - - name: accelerator-suricata-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-strict-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - statefulRuleOptions: STRICT_ORDER - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - statefulRules: - - action: PASS - header: - destination: 10.0.0.0/16 - destinationPort: ANY - direction: FORWARD - protocol: IP - source: 10.1.0.0/16 - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['100'] - - action: DROP - header: - destination: ANY - destinationPort: ANY - direction: ANY - protocol: IP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['101'] - - action: ALERT - header: - destination: 1.1.1.1/32 - destinationPort: '80' - direction: FORWARD - protocol: TCP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: msg - settings: ['"example message"'] - - keyword: sid - settings: ['102'] - - name: domain-list-group - regions: - - *HOME_REGION - capacity: 10 - type: STATEFUL - ruleGroup: - rulesSource: - rulesSourceList: - generatedRulesType: DENYLIST - targets: ['.example.com'] - targetTypes: ['TLS_SNI', 'HTTP_HOST'] - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - route53Resolver: - endpoints: - - name: accelerator-inbound - type: INBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - - name: accelerator-outbound - type: OUTBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - rules: - - name: example-rule - domainName: example.com - targetIps: - - ip: 1.1.1.1 - port: '5353' # only include if targeting a non-standard DNS port - - ip: 2.2.2.2 - shareTargets: - organizationalUnits: - - Infrastructure - - name: inbound-target-rule - domainName: aws.internal.domain - inboundEndpointTarget: accelerator-inbound # This endpoint must be listed in the configuration before the outbound endpoint - queryLogs: - name: accelerator-query-logs - destinations: - - s3 - - cloud-watch-logs - shareTargets: - organizationalUnits: - - Infrastructure - firewallRuleGroups: - - name: accelerator-block-group - regions: - - *HOME_REGION - rules: - - name: nxdomain-block-rule - action: BLOCK - customDomainList: dns-firewall-domain-lists/domain-list-1.txt - priority: 100 - blockResponse: NXDOMAIN - - name: override-block-rule - action: BLOCK - customDomainList: dns-firewall-domain-lists/domain-list-1.txt - priority: 200 - blockResponse: OVERRIDE - blockOverrideDomain: amazon.com - blockOverrideTtl: 3600 - - name: managed-rule - action: BLOCK - managedDomainList: AWSManagedDomainsBotnetCommandandControl - priority: 300 - blockResponse: NODATA - shareTargets: - organizationalUnits: - - Infrastructure -prefixLists: - - name: accelerator-prefix-list - accounts: - - Network - regions: - - *HOME_REGION - addressFamily: 'IPv4' - maxEntries: 1 - entries: - - 10.1.0.1/32 - -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json - -vpcs: - - name: Network-Endpoints - account: Network - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - internetGateway: false - dhcpOptions: accelerator-dhcp-opts - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - dnsFirewallRuleGroups: - - name: accelerator-block-group - priority: 101 - queryLogs: - - accelerator-query-logs - resolverRules: - - example-rule - routeTables: - - name: Network-Endpoints-Tgw-A - routes: [] - - name: Network-Endpoints-Tgw-B - routes: [] - - name: Network-Endpoints-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - - name: Network-Endpoints-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - subnets: - - name: Network-Endpoints-A - availabilityZone: a - routeTable: Network-Endpoints-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - shareTargets: - organizationalUnits: - - Infrastructure - accounts: - - Network - - name: Network-Endpoints-B - availabilityZone: b - routeTable: Network-Endpoints-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - - name: Network-EndpointsTgwAttach-A - availabilityZone: a - routeTable: Network-Endpoints-Tgw-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-EndpointsTgwAttach-B - availabilityZone: b - routeTable: Network-Endpoints-Tgw-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - transitGatewayAttachments: - - name: Network-Endpoints - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - virtualPrivateGateway: - asn: 65200 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - interfaceEndpoints: - central: true - defaultPolicy: Default - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: kms - - service: logs - # - service: secretsmanager - # - service: cloudformation - # - service: access-analyzer - # - service: application-autoscaling - # - service: appmesh-envoy-management - # - service: athena - # - service: autoscaling - # - service: autoscaling-plans - # - service: clouddirectory - # - service: cloudtrail - # - service: codebuild - # - service: codecommit - # - service: codepipeline - # - service: config - # - service: datasync - # - service: ecr.dkr - # - service: ecs - # - service: ecs-agent - # - service: ecs-telemetry - # - service: elasticfilesystem - # - service: elasticloadbalancing - # - service: elasticmapreduce - # - service: events - # - service: execute-api - # - service: git-codecommit - # - service: glue - # - service: kinesis-streams - # - service: kms - # - service: logs - # - service: monitoring - # - service: sagemaker.api - # - service: sagemaker.runtime - # - service: servicecatalog - # - service: sms - # - service: sns - # - service: sqs - # - service: storagegateway - # - service: sts - # - service: transfer - # - service: workspaces - # - service: awsconnector - # - service: ecr.api - # - service: kinesis-firehose - # - service: states - # - service: acm-pca - # - service: cassandra - # - service: ebs - # - service: elasticbeanstalk - # - service: elasticbeanstalk-health - # - service: email-smtp - # - service: license-manager - # - service: macie2 - # - service: notebook - # - service: synthetics - # - service: transfer.server - securityGroups: - - name: 'Management' - description: 'Management Security Group' - inboundRules: - - description: 'Management RDP Traffic Inbound' - types: - - RDP - sources: - - '10.0.0.0/8' - - '100.96.252.0/23' - - '100.96.250.0/23' - - account: 'Network' - vpc: 'Network-Endpoints' - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'Management SSH Traffic Inbound' - types: [] - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - outboundRules: - - description: 'All Outbound' - types: - - ALL - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'All Outbound' - types: [] - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - networkAcls: - - name: TestNACL - subnetAssociations: - - Network-Endpoints-A - inboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - source: 10.0.0.0/8 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - outboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: 0.0.0.0/0 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - - - name: Network-Inspection - account: Network - region: *HOME_REGION - cidrs: - - 10.2.0.0/22 - - 192.168.0.0/16 - internetGateway: true - routeTables: - - name: Network-Inspection-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-Tgw-A - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: GwlbRoute - destination: 0.0.0.0/0 - type: gatewayLoadBalancerEndpoint - target: Endpoint-A - - name: Network-Inspection-Tgw-B - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: GwlbRoute - destination: 0.0.0.0/0 - type: gatewayLoadBalancerEndpoint - target: Endpoint-B - - name: Network-Inspection-Gateway - gatewayAssociation: internetGateway - routes: [] - subnets: - - name: Network-Inspection-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.0.0/24 - - name: Network-Inspection-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.1.0/24 - - name: Network-InspectionTgwAttach-A - availabilityZone: a - routeTable: Network-Inspection-Tgw-A - ipv4CidrBlock: 10.2.3.208/28 - - name: Network-InspectionTgwAttach-B - availabilityZone: b - routeTable: Network-Inspection-Tgw-B - ipv4CidrBlock: 10.2.3.224/28 - securityGroups: - - name: Data - description: Firewall data - inboundRules: - - description: GENEVE - sources: - - 10.2.0.0/22 - udpPorts: - - 6081 - outboundRules: - - description: All outbound - types: - - ALL - sources: - - 0.0.0.0/0 - transitGatewayAttachments: - - name: Network-Inspection - transitGateway: - name: Network-Main - account: Network - options: - applianceModeSupport: enable - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-InspectionTgwAttach-A - - Network-InspectionTgwAttach-B - virtualPrivateGateway: - asn: 65000 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.4.0.0/16 - routeTables: - - name: SharedServices-Tgw-A - routes: [] - - name: SharedServices-Tgw-B - routes: [] - - name: SharedServices-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - - name: SharedServices-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - subnets: - - name: SharedServices-App-A - availabilityZone: a - routeTable: SharedServices-App-A - ipv4CidrBlock: 10.4.0.0/24 - - name: SharedServices-App-B - availabilityZone: b - routeTable: SharedServices-App-B - ipv4CidrBlock: 10.4.1.0/24 - - name: SharedServices-MainTgwAttach-A - availabilityZone: a - routeTable: SharedServices-Tgw-A - ipv4CidrBlock: 10.4.255.208/28 - - name: SharedServices-MainTgwAttach-B - availabilityZone: b - routeTable: SharedServices-Tgw-B - ipv4CidrBlock: 10.4.255.224/28 - transitGatewayAttachments: - - name: SharedServices-Main - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - SharedServices-MainTgwAttach-A - - SharedServices-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - virtualPrivateGateway: - asn: 65002 - useCentralEndpoints: true - securityGroups: - - name: SharedServices-Main-Rsyslog-sg - description: Security Group for AWS Accelerator rsyslog - inboundRules: - - description: allow inbound traffic - udpPorts: - - 514 - tcpPorts: - - 514 - sources: - - 10.0.0.0/8 - outboundRules: - - description: All Outbound - type: - - ALL - sources: - - 0.0.0.0/0 - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -directConnectGateways: - - name: Network-DXGW - account: Network - asn: 65000 - gatewayName: Network-DXGW - virtualInterfaces: - - name: Accelrator-VIF - connectionId: dxcon-test1234 - customerAsn: 65002 - interfaceName: Accelrator-VIF - ownerAccount: Network - region: us-east-1 - type: transit - vlan: 575 - enableSiteLink: true - jumboFrames: true - transitGatewayAssociations: - - name: Network-Main - account: Network - allowedPrefixes: - - 10.0.0.0/8 - - 192.168.0.0/16 - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - -vpcPeering: - - name: NetworkEndpointsToInspection - vpcs: - - Network-Endpoints - - Network-Inspection - - name: CrossAccount - vpcs: - - Network-Endpoints - - SharedServices-Main - -customerGateways: - - name: accelerator-cgw - account: Network - region: *HOME_REGION - ipAddress: 1.1.1.1 - asn: 65500 - vpnConnections: - - name: accelerator-vpn - transitGateway: Network-Main - staticRoutesOnly: false - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - tunnelSpecifications: - - tunnelInsideCidr: 169.254.200.0/30 - - tunnelInsideCidr: 169.254.200.100/30 - - - name: VpcInspectionVpnConnection - vpc: Network-Inspection - staticRoutesOnly: false - tunnelSpecifications: - - tunnelInsideCidr: 169.254.100.0/30 - - tunnelInsideCidr: 169.254.100.100/30 - -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -certificates: - - name: cert1 - type: import - privKey: cert1/privKey.key - cert: cert1/cert.crt - deploymentTargets: - accounts: - - Management - - Audit - - name: cert2 - type: request - validation: DNS - domain: example.com - san: - - www.example.com - - www.example.net - - e.co - deploymentTargets: - OU: - - Infrastructure -firewallManagerService: - delegatedAdminAccount: Audit diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/organization-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/organization-config.yaml deleted file mode 100644 index 0c21002..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/organization-config.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] -taggingPolicies: - - name: TagPolicy - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/org-backup-policies.json - deploymentTargets: - organizationalUnits: - - Root -organizationalUnitIds: - - name: Root - id: r-asdf - arn: arn:aws:organizations::111111111111:root/o-asdf123456/r-asdf - - name: Security - id: ou-asdf-11111111 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-11111111 - - name: Infrastructure - id: ou-asdf-22222222 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222 diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/security-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/security-config.yaml deleted file mode 100644 index 2b511e7..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/security-config.yaml +++ /dev/null @@ -1,678 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -keyManagementService: - keySets: - - name: key1 - alias: accelerator/test-key/key1 - policy: kms/kms-policy-01.json - description: Test KMS Key - enableKeyRotation: true - enabled: true - removalPolicy: destroy - deploymentTargets: - organizationalUnits: - - Root - - Infrastructure -centralSecurityServices: - delegatedAdminAccount: CentralSecurity - ebsDefaultVolumeEncryption: - enable: true - kmsKey: key1 - excludeRegions: [] - s3PublicAccessBlock: - enable: true - excludeAccounts: [] - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: true - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: true - excludeRegions: [] - s3Protection: - enable: true - excludeRegions: [] - exportConfiguration: - enable: true - overrideExisting: true - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - auditManager: - enable: true - excludeRegions: [] - defaultReportsConfiguration: - enable: true - destinationType: S3 - detective: - enable: true - excludeRegions: [] - securityHub: - enable: true - regionAggregation: true - snsTopicName: Security - notificationLevel: HIGH - excludeRegions: [] - standards: - - name: AWS Foundational Security Best Practices v1.0.0 - enable: true - controlsToDisable: - - IAM.1 - - EC2.10 - - Lambda.4 - - name: PCI DSS v3.2.1 - enable: true - controlsToDisable: - - PCI.IAM.3 - - PCI.S3.3 - - PCI.EC2.3 - - PCI.Lambda.2 - - name: CIS AWS Foundations Benchmark v1.2.0 - enable: true - controlsToDisable: - - CIS.1.20 - - CIS.1.22 - - CIS.2.6 - ssmAutomation: - documentSets: - - shareTargets: - organizationalUnits: - - Root - documents: - # Calls the AWS CLI to enable access logs on a specified ELB - - name: SSM-ELB-Enable-Logging - template: ssm-documents/ssm-elb-enable-logging.yaml - # Enables S3 encryption using KMS - - name: Put-S3-Encryption - template: ssm-documents/s3-encryption.yaml - # Attaches instance profiles to an EC2 instance - - name: Attach-IAM-Instance-Profile - template: ssm-documents/attach-iam-instance-profile.yaml - # Attaches Aws IAM Managed Policy to IAM Role - - name: Attach-IAM-Role-Policy - template: ssm-documents/attach-iam-role-policy.yaml -accessAnalyzer: - enable: true -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: - - deploymentTargets: - organizationalUnits: - - Root - rules: - - name: accelerator-attach-ec2-instance-profile - type: Custom - description: Custom rule for checking EC2 instance IAM profile attachment - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::EC2::Instance - remediation: - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - automatic: true - targetId: Attach-IAM-Instance-Profile - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: InstanceId - value: RESOURCE_ID - type: String - - name: IamInstanceProfile - value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} - type: StringList - - name: accelerator-ec2-instance-profile-permission - type: Custom - description: Custom role to remediate EC2 instance profile permission - inputParameters: - AWSManagedPolicies: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - # CustomerManagedPolicies: ${ACCEL_LOOKUP::CustomerManagedPolicy:},${ACCEL_LOOKUP::CustomerManagedPolicy:} - ResourceId: RESOURCE_ID - customRule: - lambda: - sourceFilePath: custom-config-rules/ec2-instance-profile-permissions.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::IAM::Role - remediation: - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-remediation-role.json - automatic: true - targetId: Attach-IAM-Role-Policy - targetAccountName: Audit - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: ResourceId - value: RESOURCE_ID - type: String - - name: AWSManagedPolicies - value: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - type: StringList - # - name: CustomerManagedPolicies - # value: ${ACCEL_LOOKUP::CustomerManagedPolicy:policy-00},${ACCEL_LOOKUP::CustomerManagedPolicy:policy-01} - # type: StringList - - name: accelerator-s3-bucket-server-side-encryption-enabled - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - complianceResourceTypes: - - AWS::S3::Bucket - remediation: - rolePolicyFile: custom-config-rules/bucket-sse-enabled-remediation-role.json - automatic: true - targetId: Put-S3-Encryption - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: KMSMasterKey - value: ${ACCEL_LOOKUP::KMS} - type: StringList - - name: accelerator-elb-logging-enabled - identifier: ELB_LOGGING_ENABLED - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - - AWS::ElasticLoadBalancingV2::LoadBalancer - inputParameters: - s3BucketNames: ${ACCEL_LOOKUP::Bucket:elbLogs} - remediation: - rolePolicyFile: custom-config-rules/elb-logging-enabled-remediation-role.json - automatic: true - targetId: SSM-ELB-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: LoadBalancerArn - value: RESOURCE_ID - type: String - - name: LogDestination - value: ${ACCEL_LOOKUP::Bucket:elbLogs} - type: StringList - - name: accelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: accelerator-securityhub-enabled - identifier: SECURITYHUB_ENABLED - - name: accelerator-cloudtrail-enabled - identifier: CLOUD_TRAIL_ENABLED - - name: accelerator-rds-logging-enabled - complianceResourceTypes: - - AWS::RDS::DBInstance - identifier: RDS_LOGGING_ENABLED - - name: accelerator-cloudwatch-alarm-action-check - complianceResourceTypes: - - AWS::CloudWatch::Alarm - inputParameters: - alarmActionRequired: "TRUE" - insufficientDataActionRequired: "TRUE" - okActionRequired: "FALSE" - identifier: CLOUDWATCH_ALARM_ACTION_CHECK - - name: accelerator-redshift-cluster-configuration-check - inputParameters: - clusterDbEncrypted: "TRUE" - loggingEnabled: "TRUE" - complianceResourceTypes: - - AWS::Redshift::Cluster - identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK - - name: accelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: accelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: accelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: accelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - - name: accelerator-lambda-inside-vpc - complianceResourceTypes: - - AWS::Lambda::Function - identifier: LAMBDA_INSIDE_VPC - - name: accelerator-ec2-instances-in-vpc - complianceResourceTypes: - - AWS::EC2::Instance - identifier: INSTANCES_IN_VPC - - name: accelerator-vpc-sg-open-only-to-authorized-ports - inputParameters: - authorizedTcpPorts: "443" - authorizedUdpPorts: "1020-1025" - complianceResourceTypes: - - AWS::EC2::SecurityGroup - identifier: VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS - - name: accelerator-ec2-instance-no-public-ip - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_NO_PUBLIC_IP - - name: accelerator-elasticsearch-in-vpc-only - identifier: ELASTICSEARCH_IN_VPC_ONLY - - name: accelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: accelerator-iam-no-inline-policy-check - complianceResourceTypes: - - AWS::IAM::User - - AWS::IAM::Role - - AWS::IAM::Group - identifier: IAM_NO_INLINE_POLICY_CHECK - - name: accelerator-elb-acm-certificate-required - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_ACM_CERTIFICATE_REQUIRED - - name: accelerator-alb-http-drop-invalid-header-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ALB_HTTP_DROP_INVALID_HEADER_ENABLED - - name: accelerator-elb-tls-https-listeners-only - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_TLS_HTTPS_LISTENERS_ONLY - - name: accelerator-api-gw-execution-logging-enabled - complianceResourceTypes: - - AWS::ApiGateway::Stage - - AWS::ApiGatewayV2::Stage - identifier: API_GW_EXECUTION_LOGGING_ENABLED - - name: accelerator-cloudwatch-log-group-encrypted - identifier: CLOUDWATCH_LOG_GROUP_ENCRYPTED - - name: accelerator-s3-bucket-replication-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_REPLICATION_ENABLED - - name: accelerator-cw-loggroup-retention-period-check - identifier: CW_LOGGROUP_RETENTION_PERIOD_CHECK - - name: accelerator-ec2-instance-detailed-monitoring-enabled - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: accelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: "TRUE" - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: accelerator-elb-deletion-protection-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ELB_DELETION_PROTECTION_ENABLED - - name: accelerator-cloudtrail-security-trail-enabled - identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: accelerator-elasticache-redis-cluster-automatic-backup-check - identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - - name: accelerator-s3-bucket-versioning-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_VERSIONING_ENABLED - - name: accelerator-vpc-vpn-2-tunnels-up - complianceResourceTypes: - - AWS::EC2::VPNConnection - identifier: VPC_VPN_2_TUNNELS_UP - - name: accelerator-elb-cross-zone-load-balancing-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED - - name: accelerator-iam-user-mfa-enabled - identifier: IAM_USER_MFA_ENABLED - - name: accelerator-guardduty-non-archived-findings - inputParameters: - daysHighSev: "1" - daysLowSev: "30" - daysMediumSev: "7" - identifier: GUARDDUTY_NON_ARCHIVED_FINDINGS - - name: accelerator-elasticsearch-node-to-node-encryption-check - complianceResourceTypes: - - AWS::Elasticsearch::Domain - identifier: ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK - - name: accelerator-kms-cmk-not-scheduled-for-deletion - complianceResourceTypes: - - AWS::KMS::Key - identifier: KMS_CMK_NOT_SCHEDULED_FOR_DELETION - - name: accelerator-api-gw-cache-enabled-and-encrypted - complianceResourceTypes: - - AWS::ApiGateway::Stage - identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED - - name: accelerator-sagemaker-endpoint-configuration-kms-key-configured - identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED - - name: accelerator-sagemaker-notebook-instance-kms-key-configured - identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED - - name: accelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - name: accelerator-s3-bucket-default-lock-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_DEFAULT_LOCK_ENABLED -cloudWatch: - metricSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - metrics: - # CIS 1.1 – Avoid the use of the "root" account - - filterName: RootAccountMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccount - metricValue: "1" - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - filterName: UnauthorizedAPICallsMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}' - metricNamespace: LogMetrics - metricName: UnauthorizedAPICalls - metricValue: "1" - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - filterName: ConsoleSigninWithoutMFAMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}' - metricNamespace: LogMetrics - metricName: ConsoleSigninWithoutMFA - metricValue: "1" - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - filterName: MetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccountUsage - metricValue: "1" - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}" - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: "1" - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}" - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: "1" - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: "1" - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}" - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: "1" - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}" - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: "1" - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}" - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: "1" - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}" - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: "1" - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}" - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: "1" - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}" - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: "1" - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}" - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: "1" - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}" - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: "1" - alarmSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - alarms: - # CIS 1.1 – Avoid the use of the "root" account - - alarmName: CIS-1.1-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - alarmName: CIS-3.1-UnauthorizedAPICalls - alarmDescription: Alarm for unauthorized API calls - snsTopicName: Security - metricName: UnauthorizedAPICalls - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 5 - treatMissingData: notBreaching - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - alarmName: CIS-3.2-ConsoleSigninWithoutMFA - alarmDescription: Alarm for AWS Management Console sign-in without MFA - snsTopicName: Security - metricName: ConsoleSigninWithoutMFA - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - alarmName: CIS-3.3-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-3.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-3.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-3.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-3.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-3.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-3.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-3.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-3.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-3.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-3.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-1.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-1.json deleted file mode 100644 index 703e007..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ConfigRulesStatement", - "Effect": "Deny", - "Action": [ - "config:PutConfigRule", - "config:DeleteConfigRule", - "config:DeleteEvaluationResults", - "config:DeleteConfigurationAggregator", - "config:PutConfigurationAggregator" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "LambdaStatement", - "Effect": "Deny", - "Action": [ - "lambda:AddPermission", - "lambda:CreateEventSourceMapping", - "lambda:CreateFunction", - "lambda:DeleteEventSourceMapping", - "lambda:DeleteFunction", - "lambda:DeleteFunctionConcurrency", - "lambda:PutFunctionConcurrency", - "lambda:RemovePermission", - "lambda:UpdateEventSourceMapping", - "lambda:UpdateFunctionCode", - "lambda:UpdateFunctionConfiguration" - ], - "Resource": "arn:${PARTITION}:lambda:*:*:function:${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "SnsStatement", - "Effect": "Deny", - "Action": [ - "sns:AddPermission", - "sns:CreateTopic", - "sns:DeleteTopic", - "sns:RemovePermission", - "sns:SetTopicAttributes", - "sns:Subscribe", - "sns:Unsubscribe" - ], - "Resource": "arn:${PARTITION}:sns:*:*:aws-accelerator-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "EbsEncryptionStatement", - "Effect": "Deny", - "Action": ["ec2:DisableEbsEncryptionByDefault"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-2.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-2.json deleted file mode 100644 index 5d3a5a8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/guardrails-2.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "IamSettingsStatement", - "Effect": "Deny", - "Action": [ - "iam:DeleteAccountPasswordPolicy", - "iam:UpdateAccountPasswordPolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "IamRolesStatement", - "Effect": "Deny", - "Action": [ - "iam:AttachRolePolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias", - "iam:CreateUser", - "iam:DeleteUser", - "iam:CreateRole", - "iam:DeleteRole", - "iam:CreatePolicy", - "iam:DeletePolicy", - "iam:DeleteRolePermissionsBoundary", - "iam:DeleteRolePolicy", - "iam:DetachRolePolicy", - "iam:PutRolePermissionsBoundary", - "iam:PutRolePolicy", - "iam:UpdateAssumeRolePolicy", - "iam:UpdateRole", - "iam:UpdateRoleDescription" - ], - "Resource": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "GDSecHubServicesStatement", - "Effect": "Deny", - "Action": [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:UpdateDetector", - "guardduty:StopMonitoringMembers", - "guardduty:Disassociate*", - "securityhub:BatchDisableStandards", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:Disassociate*" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "MacieServiceStatement", - "Effect": "Deny", - "Action": [ - "macie:AcceptInvitation", - "macie:CreateInvitations", - "macie:CreateMember", - "macie:DeclineInvitations", - "macie:DeleteInvitations", - "macie:DeleteMember", - "macie:DisableMacie", - "macie:DisableOrganizationAdminAccount", - "macie:Disassociate*", - "macie:Enable*", - "macie:UpdateMacieSession", - "macie:UpdateMemberSession", - "macie:UpdateOrganizationConfiguration" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "CloudFormationStatement", - "Effect": "Deny", - "Action": ["cloudformation:Delete*"], - "Resource": "arn:${PARTITION}:cloudformation:*:*:stack/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventSSMModification", - "Effect": "Deny", - "Action": ["ssm:DeleteParameters"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/quarantine.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-instance-profile.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-instance-profile.yaml deleted file mode 100644 index 543ba02..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-instance-profile.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: Associate AWS Iam Instance Profile to EC2 Instance -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - IamInstanceProfile: - type: String - InstanceId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: associateIamProfile - action: 'aws:executeAwsApi' - inputs: - Service: ec2 - Api: associate_iam_instance_profile - IamInstanceProfile: - Name: '{{ IamInstanceProfile }}' - InstanceId: '{{ InstanceId }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-role-policy.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-role-policy.yaml deleted file mode 100644 index 58cc557..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/attach-iam-role-policy.yaml +++ /dev/null @@ -1,50 +0,0 @@ -description: IAM Role Policy -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - ResourceId: - type: String - AWSManagedPolicies: - type: StringList - CustomerManagedPolicies: - type: StringList - minItems: 0 - default: [] - AutomationAssumeRole: - type: String -mainSteps: - - name: attachPolicy - action: 'aws:executeScript' - inputs: - Runtime: python3.7 - Handler: script_handler - Script: |- - import boto3 - partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] - iam = boto3.client("iam") - config = boto3.client("config") - def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - ) - InputPayload: - ResourceId: '{{ ResourceId }}' - AWSManagedPolicies: '{{ AWSManagedPolicies }}' - CustomerManagedPolicies: '{{ CustomerManagedPolicies }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/s3-encryption.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/s3-encryption.yaml deleted file mode 100644 index 79bc510..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/s3-encryption.yaml +++ /dev/null @@ -1,28 +0,0 @@ -description: Enables Encryption on S3 Bucket -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - BucketName: - type: String - description: (Required) The name of the S3 Bucket whose content will be encrypted. - KMSMasterKey: - type: String - description: (Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption. - AutomationAssumeRole: - type: String - description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. - default: "" -mainSteps: -- name: PutBucketEncryption - action: aws:executeAwsApi - inputs: - Service: s3 - Api: PutBucketEncryption - Bucket: "{{BucketName}}" - ServerSideEncryptionConfiguration: - Rules: - - - ApplyServerSideEncryptionByDefault: - SSEAlgorithm: "aws:kms" - KMSMasterKeyID: "{{KMSMasterKey}}" - isEnd: true \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/ssm-elb-enable-logging.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/ssm-elb-enable-logging.yaml deleted file mode 100644 index 00af3e2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/ssm-documents/ssm-elb-enable-logging.yaml +++ /dev/null @@ -1,44 +0,0 @@ -description: Enable logging on Elastic Load-Balancer -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - LogDestination: - type: String - LoadBalancerArn: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: getAccount - action: 'aws:executeAwsApi' - inputs: - Service: sts - Api: get_caller_identity - outputs: - - Name: Id - Selector: $.Account - Type: String - - name: getLoadBalancer - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: describe_load_balancers - LoadBalancerArns: - - '{{ LoadBalancerArn }}' - outputs: - - Name: Name - Selector: $.LoadBalancers[0].LoadBalancerName - Type: String - - name: enableLogging - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: modify_load_balancer_attributes - LoadBalancerArn: '{{ LoadBalancerArn }}' - Attributes: - - Key: access_logs.s3.enabled - Value: 'true' - - Key: access_logs.s3.bucket - Value: '{{ LogDestination }}' - - Key: access_logs.s3.prefix - Value: 'elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/tagging-policies/org-tag-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/tagging-policies/org-tag-policy.json deleted file mode 100644 index e49fbb0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/tagging-policies/org-tag-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tags": { - "costcenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/test-configuration/config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/test-configuration/config.yaml deleted file mode 100644 index 482e914..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/test-configuration/config.yaml +++ /dev/null @@ -1,22 +0,0 @@ -tests: - - name: validate main transit gateway - description: Validate Main Transit Gateway - suite: network - testTarget: validateTransitGateway - expect: PASS - parameters: - name: Main - accountId: '' - region: us-east-1 - amazonSideAsn: '65521' - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTableNames: - - core - - segregated - - shared - - standalone - shareTargetAccountIds: [] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/default.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/ec2.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-delegated-admin/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/accounts-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/accounts-config.yaml deleted file mode 100644 index 87678e2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/accounts-config.yaml +++ /dev/null @@ -1,33 +0,0 @@ -mandatoryAccounts: - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: management-account@example.com - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: logarchive-account@example.com - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: audit-account@example.com - organizationalUnit: Security -workloadAccounts: - - name: SharedServices - description: The SharedServices account - email: shared-services@example.com - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: network@example.com - organizationalUnit: Infrastructure -accountIds: - - email: management-account@example.com - accountId: '111111111111' - - email: audit-account@example.com - accountId: '222222222222' - - email: logarchive-account@example.com - accountId: '333333333333' - - email: shared-services@example.com - accountId: '444444444444' - - email: network@example.com - accountId: '555555555555' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/appConfigs/appA/launchTemplate/userData.sh b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/appConfigs/appA/launchTemplate/userData.sh deleted file mode 100644 index 01e78a0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/appConfigs/appA/launchTemplate/userData.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -yum update -y -yum install -y httpd -systemctl start httpd -systemctl enable httpd -usermod -a -G apache ec2-user -chown -R ec2-user:apache /var/www -chmod 2775 /var/www -find /var/www -type d -exec chmod 2775 {} \; -find /var/www -type f -exec chmod 0664 {} \; \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/backup-policies/org-backup-policies.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/backup-policies/org-backup-policies.json deleted file mode 100644 index aadece2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/backup-policies/org-backup-policies.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "plans": { - "Organization_Backup_Plan": { - "rules": { - "Monthly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Weekly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 ? * MON *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Daily_Rule": { - "schedule_expression": { - "@@assign": "cron(15 10 * * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "NinetyDay_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */3 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Yearly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */12 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "1825" - }, - "delete_after_days": { - "@@assign": "365" - } - } - } - }, - "regions": { - "@@append": [ - "us-east-1" - ] - }, - "selections": { - "tags": { - "Monthly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupMonthly" - }, - "tag_value": { - "@@assign": [ - "Monthly" - ] - } - }, - "Weekly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupWeekly" - }, - "tag_value": { - "@@assign": [ - "Weekly" - ] - } - }, - "Daily_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupDaily" - }, - "tag_value": { - "@@assign": [ - "Daily" - ] - } - }, - "NinetyDay_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupNinetyDay" - }, - "tag_value": { - "@@assign": [ - "NinetyDay" - ] - } - }, - "Yearly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupYearly" - }, - "tag_value": { - "@@assign": [ - "Yearly" - ] - } - } - } - }, - "backup_plan_tags": { - "stage": { - "tag_key": { - "@@assign": "Stage" - }, - "tag_value": { - "@@assign": "Released" - } - } - } - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/cloudformation-templates/custom-s3-bucket.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/cloudformation-templates/custom-s3-bucket.yaml deleted file mode 100644 index e3b58d1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/cloudformation-templates/custom-s3-bucket.yaml +++ /dev/null @@ -1,37 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Resources: - LZACustomizationsBucket: - Type: 'AWS::S3::Bucket' - Properties: - BucketEncryption: - ServerSideEncryptionConfiguration: - - BucketKeyEnabled: false - ServerSideEncryptionByDefault: - SSEAlgorithm: 'aws:kms' - KMSMasterKeyID: 'aws/s3' - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - VersioningConfiguration: - Status: Enabled - - LZACustomizationsSampleBucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref LZACustomizationsBucket - PolicyDocument: - Version: '2012-10-17' - Statement: - - Principal: - AWS: '*' - Action: - - 's3:*' - Resource: - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}/*' - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}' - Effect: 'Deny' - Condition: - Bool: - aws:SecureTransport: 'false' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-detection-role.json deleted file mode 100644 index db821df..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-detection-role.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-remediation-role.json deleted file mode 100644 index 52701c4..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile-remediation-role.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/attach-ec2-instance-profile.zip deleted file mode 100644 index 1de00bf06d02573ed8890d48da07d3c50ed785dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmWIWW@Zs#-~hs}B^f~sQ1F+70VtxtkeQc~TA`O!92&yQz<&LuYr;(+2GOMz+zgB? zq7NO*Sk6>47j6wZm?vc@u=lg5@XAw7F6KdfA(jD$8nqfZR#bH?dQic>VN-T~u0D(F zpX%e;(^4A+LblF1V4R}~FRwR-!${OMK~i1d4x^c&R)1FH?mAy5u>2*L=IrOQWFLCFF_mpk zTpd;MxlyY1^FEb*3(hJmpQ(Fzmang~No3CPSE8xhdmMJzWIeO9ULj`l%zZ=Vir-Ug zG-sSLIUaY#O6B#VyftTqBh;U2ZrLn3%Yq{>7<#t{oL2L{{QrZl|Oi`P~s=;Bwwr0?Z0pQ5l}FFIxo7y-e_C) ztOIv8-sta67UzE}-@lo|_istR^wqgHU!DDKCHIB>@pGfVfPggXy)%2BbKHt`o9Lv> z%&p`5STusw)r&cc>)P6n)4yMCU8`<))G9vtz>S1+-*^PwLjuzNxCWlNt$QrPkn5k~ z-2)fbs<1y~i85lI=$gq(BwSQb`SYegdz0$Xv-@{` zIluId1^@*MvS$DQ diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/bucket-sse-enabled-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/bucket-sse-enabled-remediation-role.json deleted file mode 100644 index 3c37861..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/bucket-sse-enabled-remediation-role.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["s3:GetEncryptionConfiguration", "s3:PutEncryptionConfiguration"], - "Resource": "*", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": ["arn:aws:iam::*:role/AWSAccelerator-SecuritySt-*"] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-detection-role.json deleted file mode 100644 index e612dc8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-detection-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "iam:Get*", - "iam:List*" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json deleted file mode 100644 index 83c5a8d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/custom-config-rules/ec2-instance-profile-permissions.zip deleted file mode 100644 index 9d64fe687967e4fa3cf01897db666d68356cb1af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1292 zcmWIWW@Zs#-~hr?Ejd99P~gSS02EPR$jnPgtcVx~UR-qK+7Txoqf#cY`$ghLj~zm}!V^8GE_ovn z`S&}Yr>*YMNS2oi_uiay{QKF%#*aM;mL~qWF0{_!(tcyLmG37u|B5`?A6H=M;3>hm zFKxDg;B4NhE86c*^?30#K_D_)-JNkc`(v#+H`-OFR*MDNuMj!Def#yzCx3i@{qpbK zlAS+3=NXyqm~U$$)tmNs_Qe-`veoeyn(UT3Wr^l-_6u`+UzzB>;atU%nGYUZwtZTa zaly3q5~B_Of+cA?e(_KC$=ukyTW8@0F`-4ey>gQG53KcYIkJLjk?Fzf5?dHr*gLmP z(5yL8(X+rf#ka!cVOW=`^Zvvi4G*^z{)^Ciw(ydW?dGo=mmJ-!B6(*$FlJ zJdG;@4|c3c^;)v%oK}uzh{HJpCABwK4VPxz4B{yd(cHAqE$p@j&+lm-n}TaSrcTMy z60P~ocCYwdq{lY4|LwP|jy$>}(DyI-jP&Np-yaxfN$Fh-nYUu+B!(AjMc8l41Z%c_ z*{gXu`pi4m>z`Q@=IX_LC}(uJw8=YJ-{E=X!S5!H;o%Q-eN!i<*oe$yj~GN&RZ^+BwtZ_wV4Z3=r|`k_|3f z@hsx_9EFew%~`ypDUAoI!}zSW@=FD zx|Oki@h)$bDVpXfr_%no2+vuv@q6FHDz!Ue76!(X`CJ^|JP5FNo2t{v`1XM|YYu~$ z@Zlwr_ZDb8)joK0=f5sKv3qZiB&o(3FTHP_vAOH4B7@J(9e0e>{@jQ>ac_24#-keN z8Hr9m-STrE*8g08H0G0T*;V0V&B4SvPJtDi!u)yMj>Ar4xp(^M2X~c(Ze?O!&FVi-my!R0;)nGct)VBT6V_IZz1& d14|k~EMm*60B=?{ka|WS3 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; priority:1; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; priority:1; flow:to_server, established; sid:104; rev:1;) \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/global-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/global-config.yaml deleted file mode 100644 index 2a1515e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/global-config.yaml +++ /dev/null @@ -1,113 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -enabledRegions: - - *HOME_REGION - - us-west-2 -managementAccountAccessRole: AWSControlTowerExecution -cloudwatchLogRetentionInDays: 3653 -acceleratorMetadata: - enable: true - account: LogArchive - readOnlyAccessRoleArns: - - arn:aws:iam::111111111111:role/test-access-role -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - notify-security@example.com -terminationProtection: true -controlTower: - enable: true -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true -logging: - account: LogArchive - centralizedLoggingRegion: us-west-2 - cloudtrail: - enable: false - organizationTrail: false - sessionManager: - sendToCloudWatchLogs: false - sendToS3: true - cloudwatchLogs: - dynamicPartitioning: dynamic-partitioning/log-filters.json - exclusions: - - organizationalUnits: - - Infrastructure - excludeAll: true - - accounts: - - Management - regions: - - us-east-1 - logGroupNames: - - test/* - -reports: - costAndUsageReport: - compression: Parquet - format: Parquet - reportName: accelerator-cur - s3Prefix: cur - timeUnit: DAILY - refreshClosedReports: true - reportVersioning: CREATE_NEW_REPORT - budgets: - - deploymentTargets: - accounts: - - Management - name: accel-budget - timeUnit: MONTHLY - type: COST - amount: 2000 - includeUpfront: true - includeTax: true - includeSupport: true - includeSubscription: true - includeRecurring: true - includeOtherSubscription: true - includeDiscount: true - includeCredit: false - includeRefund: false - useBlended: false - useAmortized: false - unit: USD - notifications: - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 100 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 90 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 80 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 75 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 50 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com -backup: - vaults: - - name: BackupVault - deploymentTargets: - organizationalUnits: - - Root diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-config.yaml deleted file mode 100644 index bc57ab3..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-config.yaml +++ /dev/null @@ -1,51 +0,0 @@ -policySets: - - deploymentTargets: - organizationalUnits: - - Root - policies: - - name: Default-Boundary-Policy - policy: iam-policies/boundary-policy.json -roleSets: - - deploymentTargets: - organizationalUnits: - - Root - roles: - - name: EC2-Default-SSM-AD-Role - instanceProfile: true - assumedBy: - - type: service - principal: ec2.amazonaws.com - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonSSMDirectoryServiceAccess - - CloudWatchAgentServerPolicy - boundaryPolicy: Default-Boundary-Policy - - name: Backup-Role - assumedBy: - - type: service - principal: backup.amazonaws.com - policies: - awsManaged: - - service-role/AWSBackupServiceRolePolicyForBackup - - service-role/AWSBackupServiceRolePolicyForRestores -groupSets: - - deploymentTargets: - organizationalUnits: - - Root - groups: - - name: Administrators - policies: - awsManaged: - - AdministratorAccess -userSets: - - deploymentTargets: - accounts: - - Management - users: - - username: breakGlassUser01 - group: Administrators - boundaryPolicy: Default-Boundary-Policy - - username: breakGlassUser02 - group: Administrators - boundaryPolicy: Default-Boundary-Policy diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-policies/boundary-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-policies/boundary-policy.json deleted file mode 100644 index 18de2ad..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/iam-policies/boundary-policy.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - }, - { - "Effect": "Deny", - "NotAction": [ - "iam:CreateVirtualMFADevice", - "iam:DeleteVirtualMFADevice", - "iam:ListVirtualMFADevices", - "iam:EnableMFADevice", - "iam:ResyncMFADevice", - "iam:ListAccountAliases", - "iam:ListUsers", - "iam:ListSSHPublicKeys", - "iam:ListAccessKeys", - "iam:ListServiceSpecificCredentials", - "iam:ListMFADevices", - "iam:GetAccountSummary", - "sts:GetSessionToken" - ], - "Resource": "*", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false", - "aws:ViaAWSService": "false" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/kms/kms-policy-01.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/kms/kms-policy-01.json deleted file mode 100644 index ef8e3bc..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/kms/kms-policy-01.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root" - }, - "Action": "kms:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/network-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/network-config.yaml deleted file mode 100644 index 520326f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/network-config.yaml +++ /dev/null @@ -1,1075 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -defaultVpc: - delete: true - excludeAccounts: [] -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Core - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 10.200.0.0/16 - attachment: - directConnectGatewayName: Network-DXGW - - destinationCidrBlock: 10.300.0.0/16 - attachment: - vpnConnectionName: accelerator-vpn - - destinationCidrBlock: 1.1.1.1/32 - blackhole: true - - destinationPrefixList: accelerator-prefix-list - attachment: - vpcName: Network-Endpoints - account: Network - - name: Network-Main-Segregated - routes: [] - - name: Network-Main-Shared - routes: - - destinationCidrBlock: 10.200.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - name: Network-Main-Standalone - routes: [] - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 64512 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: SharedServices-Main-Core - routes: [] - - name: SharedServices-Main-Segregated - routes: [] - - name: SharedServices-Main-Shared - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - name: SharedServices-Main-Standalone - routes: [] -transitGatewayPeering: - - name: Network-Main-And-SharedServices-Main-Peering - requester: - transitGatewayName: SharedServices-Main - account: SharedServices - region: *HOME_REGION - routeTableAssociations: SharedServices-Main-Shared - tags: - - key: Name - value: Network-Main-And-SharedServices-Main-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - - key: Organization - value: WWPS - - key: Team - value: Development - accepter: - transitGatewayName: Network-Main - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-Shared - # autoAccept: true - # applyTags: false - -dhcpOptions: - - name: accelerator-dhcp-opts - accounts: - - Network - regions: - - *HOME_REGION - domainName: example.com - domainNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNodeType: 2 - ntpServers: - - 1.1.1.1 - - 2.2.2.2 -centralNetworkServices: - delegatedAdminAccount: Network - ipams: - - name: accelerator-ipam - region: *HOME_REGION - description: Accelerator IPAM - operatingRegions: - - *HOME_REGION - - us-west-2 - pools: - - name: &BASE_POOL base-pool - description: accelerator-base - provisionedCidrs: - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - name: home-region-pool - description: Pool for us-east-1 - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/16 - sourceIpamPool: *BASE_POOL - - name: home-region-prod-pool - description: Pool for prod environment - allocationResourceTags: - - key: env - value: prod - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - sourceIpamPool: home-region-pool - - name: west-region-pool - description: Pool for us-west-2 - locale: us-west-2 - provisionedCidrs: - - 10.1.0.0/16 - sourceIpamPool: *BASE_POOL - gatewayLoadBalancers: - - name: Accelerator-GWLB - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - deletionProtection: true - targetGroup: instance-target - endpoints: - - name: Endpoint-A - account: Network - subnet: Network-Inspection-A - vpc: Network-Inspection - - name: Endpoint-B - account: Network - subnet: Network-Inspection-B - vpc: Network-Inspection - networkFirewall: - firewalls: - - name: accelerator-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - policies: - - name: accelerator-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulRuleGroups: - - name: accelerator-rule-group - - name: domain-list-group - shareTargets: - organizationalUnits: - - Infrastructure - - name: accelerator-strict-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulEngineOptions: STRICT_ORDER - statefulRuleGroups: - - name: accelerator-strict-rule-group - priority: 100 - shareTargets: - organizationalUnits: - - Infrastructure - rules: - - name: accelerator-suricata-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-strict-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - statefulRuleOptions: STRICT_ORDER - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - statefulRules: - - action: PASS - header: - destination: 10.0.0.0/16 - destinationPort: ANY - direction: FORWARD - protocol: IP - source: 10.1.0.0/16 - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['100'] - - action: DROP - header: - destination: ANY - destinationPort: ANY - direction: ANY - protocol: IP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['101'] - - action: ALERT - header: - destination: 1.1.1.1/32 - destinationPort: '80' - direction: FORWARD - protocol: TCP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: msg - settings: ['"example message"'] - - keyword: sid - settings: ['102'] - - name: domain-list-group - regions: - - *HOME_REGION - capacity: 10 - type: STATEFUL - ruleGroup: - rulesSource: - rulesSourceList: - generatedRulesType: DENYLIST - targets: ['.example.com'] - targetTypes: ['TLS_SNI', 'HTTP_HOST'] - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - route53Resolver: - endpoints: - - name: accelerator-inbound - type: INBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - - name: accelerator-outbound - type: OUTBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - rules: - - name: example-rule - domainName: example.com - targetIps: - - ip: 1.1.1.1 - port: '5353' # only include if targeting a non-standard DNS port - - ip: 2.2.2.2 - shareTargets: - organizationalUnits: - - Infrastructure - - name: inbound-target-rule - domainName: aws.internal.domain - inboundEndpointTarget: accelerator-inbound # This endpoint must be listed in the configuration before the outbound endpoint - queryLogs: - name: accelerator-query-logs - destinations: - - s3 - - cloud-watch-logs - shareTargets: - organizationalUnits: - - Infrastructure - firewallRuleGroups: - - name: accelerator-block-group - regions: - - *HOME_REGION - rules: - - name: nxdomain-block-rule - action: BLOCK - customDomainList: dns-firewall-domain-lists/domain-list-1.txt - priority: 100 - blockResponse: NXDOMAIN - - name: override-block-rule - action: BLOCK - customDomainList: dns-firewall-domain-lists/domain-list-1.txt - priority: 200 - blockResponse: OVERRIDE - blockOverrideDomain: amazon.com - blockOverrideTtl: 3600 - - name: managed-rule - action: BLOCK - managedDomainList: AWSManagedDomainsBotnetCommandandControl - priority: 300 - blockResponse: NODATA - shareTargets: - organizationalUnits: - - Infrastructure -prefixLists: - - name: accelerator-prefix-list - accounts: - - SharedServices - - Network - regions: - - *HOME_REGION - addressFamily: 'IPv4' - maxEntries: 1 - entries: - - 10.1.0.1/32 - -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json - -vpcs: - - name: Network-Endpoints - account: Network - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - internetGateway: false - dhcpOptions: accelerator-dhcp-opts - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - dnsFirewallRuleGroups: - - name: accelerator-block-group - priority: 101 - queryLogs: - - accelerator-query-logs - resolverRules: - - example-rule - routeTables: - - name: Network-Endpoints-Tgw-A - routes: [] - - name: Network-Endpoints-Tgw-B - routes: [] - - name: Network-Endpoints-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - - name: VgwRoute - destination: 52.94.124.199/32 - type: virtualPrivateGateway - target: Network-Main - - name: Network-Endpoints-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - subnets: - - name: Network-Endpoints-A - availabilityZone: a - routeTable: Network-Endpoints-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - shareTargets: - organizationalUnits: - - Infrastructure - accounts: - - Network - - name: Network-Endpoints-B - availabilityZone: b - routeTable: Network-Endpoints-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - - name: Network-EndpointsTgwAttach-A - availabilityZone: a - routeTable: Network-Endpoints-Tgw-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-EndpointsTgwAttach-B - availabilityZone: b - routeTable: Network-Endpoints-Tgw-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - transitGatewayAttachments: - - name: Network-Endpoints - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - virtualPrivateGateway: - asn: 65200 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - interfaceEndpoints: - central: true - defaultPolicy: Default - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: kms - - service: logs - # - service: secretsmanager - # - service: cloudformation - # - service: access-analyzer - # - service: application-autoscaling - # - service: appmesh-envoy-management - # - service: athena - # - service: autoscaling - # - service: autoscaling-plans - # - service: clouddirectory - # - service: cloudtrail - # - service: codebuild - # - service: codecommit - # - service: codepipeline - # - service: config - # - service: datasync - # - service: ecr.dkr - # - service: ecs - # - service: ecs-agent - # - service: ecs-telemetry - # - service: elasticfilesystem - # - service: elasticloadbalancing - # - service: elasticmapreduce - # - service: events - # - service: execute-api - # - service: git-codecommit - # - service: glue - # - service: kinesis-streams - # - service: kms - # - service: logs - # - service: monitoring - # - service: sagemaker.api - # - service: sagemaker.runtime - # - service: servicecatalog - # - service: sms - # - service: sns - # - service: sqs - # - service: storagegateway - # - service: sts - # - service: transfer - # - service: workspaces - # - service: awsconnector - # - service: ecr.api - # - service: kinesis-firehose - # - service: states - # - service: acm-pca - # - service: cassandra - # - service: ebs - # - service: elasticbeanstalk - # - service: elasticbeanstalk-health - # - service: email-smtp - # - service: license-manager - # - service: macie2 - # - service: notebook - # - service: synthetics - # - service: transfer.server - securityGroups: - - name: 'Management' - description: 'Management Security Group' - inboundRules: - - description: 'Management RDP Traffic Inbound' - types: - - RDP - sources: - - '10.0.0.0/8' - - '100.96.252.0/23' - - '100.96.250.0/23' - - account: 'Network' - vpc: 'Network-Endpoints' - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'Management SSH Traffic Inbound' - types: [] - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - outboundRules: - - description: 'All Outbound' - types: - - ALL - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'All Outbound' - types: [] - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - networkAcls: - - name: TestNACL - subnetAssociations: - - Network-Endpoints-A - inboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - source: 10.0.0.0/8 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - outboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: 0.0.0.0/0 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - loadBalancers: - applicationLoadBalancers: - - name: appA-alb-01 - scheme: internet-facing - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - securityGroups: - - Management - listeners: - - name: appA-listener-2 - port: 80 - protocol: HTTP - targetGroup: appA-alb-tg-1 - type: forward - - name: appA-alb-02 - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - securityGroups: - - Management - listeners: - - name: appA-listener-alb-2 - port: 80 - protocol: HTTP - targetGroup: appA-alb-tg-2 - type: forward - shareTargets: - accounts: - - SharedServices - networkLoadBalancers: - - name: appA-nlb-01 - scheme: internet-facing - deletionProtection: false - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - listeners: - - name: appA-listener-1 - port: 80 - protocol: TCP - targetGroup: appA-nlb-tg-1 - targetGroups: - - name: appA-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - - name: appA-alb-tg-1 - port: 80 - protocol: HTTP - type: instance - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: HTTP - - name: appA-alb-tg-2 - port: 80 - protocol: HTTP - type: instance - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - shareTargets: - accounts: - - SharedServices - - name: Network-Inspection - account: Network - region: *HOME_REGION - cidrs: - - 10.2.0.0/22 - - 192.168.0.0/16 - internetGateway: true - routeTables: - - name: Network-Inspection-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-Tgw-A - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: GwlbRoute - destination: 0.0.0.0/0 - type: gatewayLoadBalancerEndpoint - target: Endpoint-A - - name: Network-Inspection-Tgw-B - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: GwlbRoute - destination: 0.0.0.0/0 - type: gatewayLoadBalancerEndpoint - target: Endpoint-B - - name: Network-Inspection-Gateway - gatewayAssociation: internetGateway - routes: [] - subnets: - - name: Network-Inspection-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.0.0/24 - - name: Network-Inspection-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.1.0/24 - - name: Network-InspectionTgwAttach-A - availabilityZone: a - routeTable: Network-Inspection-Tgw-A - ipv4CidrBlock: 10.2.3.208/28 - - name: Network-InspectionTgwAttach-B - availabilityZone: b - routeTable: Network-Inspection-Tgw-B - ipv4CidrBlock: 10.2.3.224/28 - transitGatewayAttachments: - - name: Network-Inspection - transitGateway: - name: Network-Main - account: Network - options: - applianceModeSupport: enable - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-InspectionTgwAttach-A - - Network-InspectionTgwAttach-B - virtualPrivateGateway: - asn: 65000 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.4.0.0/16 - routeTables: - - name: SharedServices-Tgw-A - routes: [] - - name: SharedServices-Tgw-B - routes: [] - - name: SharedServices-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - - name: SharedServices-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - subnets: - - name: SharedServices-App-A - availabilityZone: a - routeTable: SharedServices-App-A - ipv4CidrBlock: 10.4.0.0/24 - - name: SharedServices-App-B - availabilityZone: b - routeTable: SharedServices-App-B - ipv4CidrBlock: 10.4.1.0/24 - - name: SharedServices-MainTgwAttach-A - availabilityZone: a - routeTable: SharedServices-Tgw-A - ipv4CidrBlock: 10.4.255.208/28 - - name: SharedServices-MainTgwAttach-B - availabilityZone: b - routeTable: SharedServices-Tgw-B - ipv4CidrBlock: 10.4.255.224/28 - transitGatewayAttachments: - - name: SharedServices-Main - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - SharedServices-MainTgwAttach-A - - SharedServices-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - virtualPrivateGateway: - asn: 65002 - useCentralEndpoints: true - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - overrideS3LogPath: 'somePath' - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -directConnectGateways: - - name: Network-DXGW - account: Network - asn: 65000 - gatewayName: Network-DXGW - virtualInterfaces: - - name: Accelrator-VIF - connectionId: dxcon-test1234 - customerAsn: 65002 - interfaceName: Accelrator-VIF - ownerAccount: Network - region: us-east-1 - type: transit - vlan: 575 - enableSiteLink: true - jumboFrames: true - transitGatewayAssociations: - - name: Network-Main - account: Network - allowedPrefixes: - - 10.0.0.0/8 - - 192.168.0.0/16 - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - -vpcPeering: - - name: NetworkEndpointsToInspection - vpcs: - - Network-Endpoints - - Network-Inspection - - name: CrossAccount - vpcs: - - Network-Endpoints - - SharedServices-Main - -customerGateways: - - name: accelerator-cgw - account: Network - region: *HOME_REGION - ipAddress: 1.1.1.1 - asn: 65500 - vpnConnections: - - name: accelerator-vpn - transitGateway: Network-Main - staticRoutesOnly: false - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - tunnelSpecifications: - - tunnelInsideCidr: 169.254.200.0/30 - - tunnelInsideCidr: 169.254.200.100/30 - - - name: VpcInspectionVpnConnection - vpc: Network-Inspection - staticRoutesOnly: false - tunnelSpecifications: - - tunnelInsideCidr: 169.254.100.0/30 - - tunnelInsideCidr: 169.254.100.100/30 - -firewallManagerService: - delegatedAdminAccount: Audit diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/organization-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/organization-config.yaml deleted file mode 100644 index ac400cf..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/organization-config.yaml +++ /dev/null @@ -1,63 +0,0 @@ -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] -taggingPolicies: - - name: TagPolicy - description: Multi Target Tagging Policy - policy: /tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Security - - Infrastructure -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/org-backup-policies.json - deploymentTargets: - organizationalUnits: - - Security - - Infrastructure -organizationalUnitIds: - - name: Root - id: r-asdf - arn: arn:aws:organizations::111111111111:root/o-asdf123456/r-asdf - - name: Security - id: ou-asdf-11111111 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-11111111 - - name: Infrastructure - id: ou-asdf-22222222 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222 diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/security-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/security-config.yaml deleted file mode 100644 index 63b6d67..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/security-config.yaml +++ /dev/null @@ -1,678 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -keyManagementService: - keySets: - - name: key1 - alias: accelerator/test-key/key1 - policy: kms/kms-policy-01.json - description: Test KMS Key - enableKeyRotation: true - enabled: true - removalPolicy: destroy - deploymentTargets: - organizationalUnits: - - Root - - Infrastructure -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: true - kmsKey: key1 - excludeRegions: [] - s3PublicAccessBlock: - enable: true - excludeAccounts: [] - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: true - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: true - excludeRegions: [] - s3Protection: - enable: true - excludeRegions: [] - exportConfiguration: - enable: true - overrideExisting: true - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - auditManager: - enable: true - excludeRegions: [] - defaultReportsConfiguration: - enable: true - destinationType: S3 - detective: - enable: true - excludeRegions: [] - securityHub: - enable: true - regionAggregation: true - snsTopicName: Security - notificationLevel: HIGH - excludeRegions: [] - standards: - - name: AWS Foundational Security Best Practices v1.0.0 - enable: true - controlsToDisable: - - IAM.1 - - EC2.10 - - Lambda.4 - - name: PCI DSS v3.2.1 - enable: true - controlsToDisable: - - PCI.IAM.3 - - PCI.S3.3 - - PCI.EC2.3 - - PCI.Lambda.2 - - name: CIS AWS Foundations Benchmark v1.2.0 - enable: true - controlsToDisable: - - CIS.1.20 - - CIS.1.22 - - CIS.2.6 - ssmAutomation: - documentSets: - - shareTargets: - organizationalUnits: - - Root - documents: - # Calls the AWS CLI to enable access logs on a specified ELB - - name: SSM-ELB-Enable-Logging - template: ssm-documents/ssm-elb-enable-logging.yaml - # Enables S3 encryption using KMS - - name: Put-S3-Encryption - template: ssm-documents/s3-encryption.yaml - # Attaches instance profiles to an EC2 instance - - name: Attach-IAM-Instance-Profile - template: ssm-documents/attach-iam-instance-profile.yaml - # Attaches Aws IAM Managed Policy to IAM Role - - name: Attach-IAM-Role-Policy - template: ssm-documents/attach-iam-role-policy.yaml -accessAnalyzer: - enable: true -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: - - deploymentTargets: - organizationalUnits: - - Root - rules: - - name: accelerator-attach-ec2-instance-profile - type: Custom - description: Custom rule for checking EC2 instance IAM profile attachment - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::EC2::Instance - remediation: - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - automatic: true - targetId: Attach-IAM-Instance-Profile - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: InstanceId - value: RESOURCE_ID - type: String - - name: IamInstanceProfile - value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} - type: StringList - - name: accelerator-ec2-instance-profile-permission - type: Custom - description: Custom role to remediate EC2 instance profile permission - inputParameters: - AWSManagedPolicies: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - # CustomerManagedPolicies: ${ACCEL_LOOKUP::CustomerManagedPolicy:},${ACCEL_LOOKUP::CustomerManagedPolicy:} - ResourceId: RESOURCE_ID - customRule: - lambda: - sourceFilePath: custom-config-rules/ec2-instance-profile-permissions.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::IAM::Role - remediation: - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-remediation-role.json - automatic: true - targetId: Attach-IAM-Role-Policy - targetAccountName: Audit - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: ResourceId - value: RESOURCE_ID - type: String - - name: AWSManagedPolicies - value: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - type: StringList - # - name: CustomerManagedPolicies - # value: ${ACCEL_LOOKUP::CustomerManagedPolicy:policy-00},${ACCEL_LOOKUP::CustomerManagedPolicy:policy-01} - # type: StringList - - name: accelerator-s3-bucket-server-side-encryption-enabled - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - complianceResourceTypes: - - AWS::S3::Bucket - remediation: - rolePolicyFile: custom-config-rules/bucket-sse-enabled-remediation-role.json - automatic: true - targetId: Put-S3-Encryption - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: KMSMasterKey - value: ${ACCEL_LOOKUP::KMS} - type: StringList - - name: accelerator-elb-logging-enabled - identifier: ELB_LOGGING_ENABLED - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - - AWS::ElasticLoadBalancingV2::LoadBalancer - inputParameters: - s3BucketNames: ${ACCEL_LOOKUP::Bucket:elbLogs} - remediation: - rolePolicyFile: custom-config-rules/elb-logging-enabled-remediation-role.json - automatic: true - targetId: SSM-ELB-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: LoadBalancerArn - value: RESOURCE_ID - type: String - - name: LogDestination - value: ${ACCEL_LOOKUP::Bucket:elbLogs} - type: StringList - - name: accelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: accelerator-securityhub-enabled - identifier: SECURITYHUB_ENABLED - - name: accelerator-cloudtrail-enabled - identifier: CLOUD_TRAIL_ENABLED - - name: accelerator-rds-logging-enabled - complianceResourceTypes: - - AWS::RDS::DBInstance - identifier: RDS_LOGGING_ENABLED - - name: accelerator-cloudwatch-alarm-action-check - complianceResourceTypes: - - AWS::CloudWatch::Alarm - inputParameters: - alarmActionRequired: "TRUE" - insufficientDataActionRequired: "TRUE" - okActionRequired: "FALSE" - identifier: CLOUDWATCH_ALARM_ACTION_CHECK - - name: accelerator-redshift-cluster-configuration-check - inputParameters: - clusterDbEncrypted: "TRUE" - loggingEnabled: "TRUE" - complianceResourceTypes: - - AWS::Redshift::Cluster - identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK - - name: accelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: accelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: accelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: accelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - - name: accelerator-lambda-inside-vpc - complianceResourceTypes: - - AWS::Lambda::Function - identifier: LAMBDA_INSIDE_VPC - - name: accelerator-ec2-instances-in-vpc - complianceResourceTypes: - - AWS::EC2::Instance - identifier: INSTANCES_IN_VPC - - name: accelerator-vpc-sg-open-only-to-authorized-ports - inputParameters: - authorizedTcpPorts: "443" - authorizedUdpPorts: "1020-1025" - complianceResourceTypes: - - AWS::EC2::SecurityGroup - identifier: VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS - - name: accelerator-ec2-instance-no-public-ip - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_NO_PUBLIC_IP - - name: accelerator-elasticsearch-in-vpc-only - identifier: ELASTICSEARCH_IN_VPC_ONLY - - name: accelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: accelerator-iam-no-inline-policy-check - complianceResourceTypes: - - AWS::IAM::User - - AWS::IAM::Role - - AWS::IAM::Group - identifier: IAM_NO_INLINE_POLICY_CHECK - - name: accelerator-elb-acm-certificate-required - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_ACM_CERTIFICATE_REQUIRED - - name: accelerator-alb-http-drop-invalid-header-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ALB_HTTP_DROP_INVALID_HEADER_ENABLED - - name: accelerator-elb-tls-https-listeners-only - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_TLS_HTTPS_LISTENERS_ONLY - - name: accelerator-api-gw-execution-logging-enabled - complianceResourceTypes: - - AWS::ApiGateway::Stage - - AWS::ApiGatewayV2::Stage - identifier: API_GW_EXECUTION_LOGGING_ENABLED - - name: accelerator-cloudwatch-log-group-encrypted - identifier: CLOUDWATCH_LOG_GROUP_ENCRYPTED - - name: accelerator-s3-bucket-replication-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_REPLICATION_ENABLED - - name: accelerator-cw-loggroup-retention-period-check - identifier: CW_LOGGROUP_RETENTION_PERIOD_CHECK - - name: accelerator-ec2-instance-detailed-monitoring-enabled - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: accelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: "TRUE" - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: accelerator-elb-deletion-protection-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ELB_DELETION_PROTECTION_ENABLED - - name: accelerator-cloudtrail-security-trail-enabled - identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: accelerator-elasticache-redis-cluster-automatic-backup-check - identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - - name: accelerator-s3-bucket-versioning-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_VERSIONING_ENABLED - - name: accelerator-vpc-vpn-2-tunnels-up - complianceResourceTypes: - - AWS::EC2::VPNConnection - identifier: VPC_VPN_2_TUNNELS_UP - - name: accelerator-elb-cross-zone-load-balancing-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED - - name: accelerator-iam-user-mfa-enabled - identifier: IAM_USER_MFA_ENABLED - - name: accelerator-guardduty-non-archived-findings - inputParameters: - daysHighSev: "1" - daysLowSev: "30" - daysMediumSev: "7" - identifier: GUARDDUTY_NON_ARCHIVED_FINDINGS - - name: accelerator-elasticsearch-node-to-node-encryption-check - complianceResourceTypes: - - AWS::Elasticsearch::Domain - identifier: ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK - - name: accelerator-kms-cmk-not-scheduled-for-deletion - complianceResourceTypes: - - AWS::KMS::Key - identifier: KMS_CMK_NOT_SCHEDULED_FOR_DELETION - - name: accelerator-api-gw-cache-enabled-and-encrypted - complianceResourceTypes: - - AWS::ApiGateway::Stage - identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED - - name: accelerator-sagemaker-endpoint-configuration-kms-key-configured - identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED - - name: accelerator-sagemaker-notebook-instance-kms-key-configured - identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED - - name: accelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - name: accelerator-s3-bucket-default-lock-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_DEFAULT_LOCK_ENABLED -cloudWatch: - metricSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - metrics: - # CIS 1.1 – Avoid the use of the "root" account - - filterName: RootAccountMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccount - metricValue: "1" - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - filterName: UnauthorizedAPICallsMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}' - metricNamespace: LogMetrics - metricName: UnauthorizedAPICalls - metricValue: "1" - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - filterName: ConsoleSigninWithoutMFAMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}' - metricNamespace: LogMetrics - metricName: ConsoleSigninWithoutMFA - metricValue: "1" - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - filterName: MetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccountUsage - metricValue: "1" - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}" - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: "1" - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}" - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: "1" - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: "1" - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}" - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: "1" - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}" - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: "1" - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}" - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: "1" - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}" - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: "1" - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}" - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: "1" - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}" - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: "1" - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}" - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: "1" - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-controltower/CloudTrailLogs - filterPattern: "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}" - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: "1" - alarmSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - alarms: - # CIS 1.1 – Avoid the use of the "root" account - - alarmName: CIS-1.1-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - alarmName: CIS-3.1-UnauthorizedAPICalls - alarmDescription: Alarm for unauthorized API calls - snsTopicName: Security - metricName: UnauthorizedAPICalls - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 5 - treatMissingData: notBreaching - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - alarmName: CIS-3.2-ConsoleSigninWithoutMFA - alarmDescription: Alarm for AWS Management Console sign-in without MFA - snsTopicName: Security - metricName: ConsoleSigninWithoutMFA - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - alarmName: CIS-3.3-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-3.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-3.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-3.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-3.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-3.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-3.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-3.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-3.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-3.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-3.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-1.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-1.json deleted file mode 100644 index 703e007..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ConfigRulesStatement", - "Effect": "Deny", - "Action": [ - "config:PutConfigRule", - "config:DeleteConfigRule", - "config:DeleteEvaluationResults", - "config:DeleteConfigurationAggregator", - "config:PutConfigurationAggregator" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "LambdaStatement", - "Effect": "Deny", - "Action": [ - "lambda:AddPermission", - "lambda:CreateEventSourceMapping", - "lambda:CreateFunction", - "lambda:DeleteEventSourceMapping", - "lambda:DeleteFunction", - "lambda:DeleteFunctionConcurrency", - "lambda:PutFunctionConcurrency", - "lambda:RemovePermission", - "lambda:UpdateEventSourceMapping", - "lambda:UpdateFunctionCode", - "lambda:UpdateFunctionConfiguration" - ], - "Resource": "arn:${PARTITION}:lambda:*:*:function:${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "SnsStatement", - "Effect": "Deny", - "Action": [ - "sns:AddPermission", - "sns:CreateTopic", - "sns:DeleteTopic", - "sns:RemovePermission", - "sns:SetTopicAttributes", - "sns:Subscribe", - "sns:Unsubscribe" - ], - "Resource": "arn:${PARTITION}:sns:*:*:aws-accelerator-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "EbsEncryptionStatement", - "Effect": "Deny", - "Action": ["ec2:DisableEbsEncryptionByDefault"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-2.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-2.json deleted file mode 100644 index 5d3a5a8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/guardrails-2.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "IamSettingsStatement", - "Effect": "Deny", - "Action": [ - "iam:DeleteAccountPasswordPolicy", - "iam:UpdateAccountPasswordPolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "IamRolesStatement", - "Effect": "Deny", - "Action": [ - "iam:AttachRolePolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias", - "iam:CreateUser", - "iam:DeleteUser", - "iam:CreateRole", - "iam:DeleteRole", - "iam:CreatePolicy", - "iam:DeletePolicy", - "iam:DeleteRolePermissionsBoundary", - "iam:DeleteRolePolicy", - "iam:DetachRolePolicy", - "iam:PutRolePermissionsBoundary", - "iam:PutRolePolicy", - "iam:UpdateAssumeRolePolicy", - "iam:UpdateRole", - "iam:UpdateRoleDescription" - ], - "Resource": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "GDSecHubServicesStatement", - "Effect": "Deny", - "Action": [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:UpdateDetector", - "guardduty:StopMonitoringMembers", - "guardduty:Disassociate*", - "securityhub:BatchDisableStandards", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:Disassociate*" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "MacieServiceStatement", - "Effect": "Deny", - "Action": [ - "macie:AcceptInvitation", - "macie:CreateInvitations", - "macie:CreateMember", - "macie:DeclineInvitations", - "macie:DeleteInvitations", - "macie:DeleteMember", - "macie:DisableMacie", - "macie:DisableOrganizationAdminAccount", - "macie:Disassociate*", - "macie:Enable*", - "macie:UpdateMacieSession", - "macie:UpdateMemberSession", - "macie:UpdateOrganizationConfiguration" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "CloudFormationStatement", - "Effect": "Deny", - "Action": ["cloudformation:Delete*"], - "Resource": "arn:${PARTITION}:cloudformation:*:*:stack/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventSSMModification", - "Effect": "Deny", - "Action": ["ssm:DeleteParameters"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/quarantine.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-instance-profile.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-instance-profile.yaml deleted file mode 100644 index 543ba02..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-instance-profile.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: Associate AWS Iam Instance Profile to EC2 Instance -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - IamInstanceProfile: - type: String - InstanceId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: associateIamProfile - action: 'aws:executeAwsApi' - inputs: - Service: ec2 - Api: associate_iam_instance_profile - IamInstanceProfile: - Name: '{{ IamInstanceProfile }}' - InstanceId: '{{ InstanceId }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-role-policy.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-role-policy.yaml deleted file mode 100644 index 58cc557..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/attach-iam-role-policy.yaml +++ /dev/null @@ -1,50 +0,0 @@ -description: IAM Role Policy -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - ResourceId: - type: String - AWSManagedPolicies: - type: StringList - CustomerManagedPolicies: - type: StringList - minItems: 0 - default: [] - AutomationAssumeRole: - type: String -mainSteps: - - name: attachPolicy - action: 'aws:executeScript' - inputs: - Runtime: python3.7 - Handler: script_handler - Script: |- - import boto3 - partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] - iam = boto3.client("iam") - config = boto3.client("config") - def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - ) - InputPayload: - ResourceId: '{{ ResourceId }}' - AWSManagedPolicies: '{{ AWSManagedPolicies }}' - CustomerManagedPolicies: '{{ CustomerManagedPolicies }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/s3-encryption.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/s3-encryption.yaml deleted file mode 100644 index 79bc510..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/s3-encryption.yaml +++ /dev/null @@ -1,28 +0,0 @@ -description: Enables Encryption on S3 Bucket -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - BucketName: - type: String - description: (Required) The name of the S3 Bucket whose content will be encrypted. - KMSMasterKey: - type: String - description: (Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption. - AutomationAssumeRole: - type: String - description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. - default: "" -mainSteps: -- name: PutBucketEncryption - action: aws:executeAwsApi - inputs: - Service: s3 - Api: PutBucketEncryption - Bucket: "{{BucketName}}" - ServerSideEncryptionConfiguration: - Rules: - - - ApplyServerSideEncryptionByDefault: - SSEAlgorithm: "aws:kms" - KMSMasterKeyID: "{{KMSMasterKey}}" - isEnd: true \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/ssm-elb-enable-logging.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/ssm-elb-enable-logging.yaml deleted file mode 100644 index 00af3e2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/ssm-documents/ssm-elb-enable-logging.yaml +++ /dev/null @@ -1,44 +0,0 @@ -description: Enable logging on Elastic Load-Balancer -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - LogDestination: - type: String - LoadBalancerArn: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: getAccount - action: 'aws:executeAwsApi' - inputs: - Service: sts - Api: get_caller_identity - outputs: - - Name: Id - Selector: $.Account - Type: String - - name: getLoadBalancer - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: describe_load_balancers - LoadBalancerArns: - - '{{ LoadBalancerArn }}' - outputs: - - Name: Name - Selector: $.LoadBalancers[0].LoadBalancerName - Type: String - - name: enableLogging - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: modify_load_balancer_attributes - LoadBalancerArn: '{{ LoadBalancerArn }}' - Attributes: - - Key: access_logs.s3.enabled - Value: 'true' - - Key: access_logs.s3.bucket - Value: '{{ LogDestination }}' - - Key: access_logs.s3.prefix - Value: 'elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/tagging-policies/org-tag-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/tagging-policies/org-tag-policy.json deleted file mode 100644 index e49fbb0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/tagging-policies/org-tag-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tags": { - "costcenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/test-configuration/config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/test-configuration/config.yaml deleted file mode 100644 index 482e914..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/test-configuration/config.yaml +++ /dev/null @@ -1,22 +0,0 @@ -tests: - - name: validate main transit gateway - description: Validate Main Transit Gateway - suite: network - testTarget: validateTransitGateway - expect: PASS - parameters: - name: Main - accountId: '' - region: us-east-1 - amazonSideAsn: '65521' - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTableNames: - - core - - segregated - - shared - - standalone - shareTargetAccountIds: [] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/default.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/ec2.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled-ou-targets/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/accounts-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/accounts-config.yaml deleted file mode 100644 index 46ceaed..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/accounts-config.yaml +++ /dev/null @@ -1,40 +0,0 @@ -mandatoryAccounts: - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: all-enabled-management-account@example.com - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: all-enabled-logarchive-account@example.com - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: all-enabled-audit-account@example.com - organizationalUnit: Security -workloadAccounts: - - name: SharedServices - description: The SharedServices account - email: all-enabled-shared-services@example.com - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: all-enabled-network@example.com - organizationalUnit: Infrastructure - - name: GovCloudWorkloadAccount01 - description: Sample govCloud workload account - email: all-enabled-govcloud-workload-account01@example.com - organizationalUnit: GovCloud - enableGovCloud: true -accountIds: - - email: all-enabled-management-account@example.com - accountId: '111111111111' - - email: all-enabled-audit-account@example.com - accountId: '222222222222' - - email: all-enabled-logarchive-account@example.com - accountId: '333333333333' - - email: all-enabled-shared-services@example.com - accountId: '444444444444' - - email: all-enabled-network@example.com - accountId: '555555555555' - - email: all-enabled-govcloud-workload-account01@example.com - accountId: '666666666666' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-connector-permissions-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-connector-permissions-setup.ps1 deleted file mode 100644 index ee25a83..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-connector-permissions-setup.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -Start-Process powershell.exe -Credential $credential -ArgumentList "-file c:\cfn\scripts\AD-group-grant-permissions-setup.ps1", "$GroupName" \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-grant-permissions-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-grant-permissions-setup.ps1 deleted file mode 100644 index efab9d6..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-grant-permissions-setup.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -#Delegate Control -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;computer" -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;user" \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-setup.ps1 deleted file mode 100644 index f27268f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-group-setup.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - $groupName = $groupsArray[$i] - $groupExists = Get-ADGroup -Filter {Name -eq $groupName} -Credential $credential - if($null -eq $groupExists) { - #Create Group - New-ADGroup -Name $groupName -GroupCategory Security -GroupScope Global -Credential $credential - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-group-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-group-setup.ps1 deleted file mode 100644 index 848d3c0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-group-setup.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $UserName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - #Add User to Group - Add-ADGroupMember -Identity $groupsArray[$i] -Members $UserName -Credential $credential -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-setup.ps1 deleted file mode 100644 index dd154bf..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AD-user-setup.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -[CmdletBinding()] -param( - [string] - $UserName, - - [string] - $Password, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [string] - $PasswordNeverExpires, - - [Parameter(Mandatory=$false)] - [AllowEmptyString()] - [string]$UserEmailAddress = '' -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$pass = ConvertTo-SecureString $Password -AsPlainText -Force -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword -$userExists = Get-ADUser -Credential $credential -Filter "Name -eq '$UserName'" - -If ($null -eq $userExists -and $UserEmailAddress) { - #Create User - New-ADUser -Name $UserName -EmailAddress $UserEmailAddress -AccountPassword $pass -Enabled 1 -Credential $credential -SamAccountName $UserName -} - -#Set the admin & connector user's password never expires flag -If (-NOT ($PasswordNeverExpires -eq 'No')) { - Set-ADUser -Identity $UserName -PasswordNeverExpires $true -Credential $credential -} Else { - Set-ADUser -Identity $UserName -PasswordNeverExpires $false -Credential $credential -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AWSQuickStart.psm1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AWSQuickStart.psm1 deleted file mode 100644 index fc1b30f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/AWSQuickStart.psm1 +++ /dev/null @@ -1,345 +0,0 @@ -function New-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [string] - $Handle, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\', - - [Parameter(Mandatory=$false)] - [switch] - $Base64Handle - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - if ($Base64Handle) { - Write-Verbose "Trying to decode handle Base64 string as UTF8 string" - $decodedHandle = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Handle)) - if ($decodedHandle -notlike "http*") { - Write-Verbose "Now trying to decode handle Base64 string as Unicode string" - $decodedHandle = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($Handle)) - } - Write-Verbose "Decoded handle string: $decodedHandle" - $Handle = $decodedHandle - } - - Write-Verbose "Creating Handle Registry Key" - New-ItemProperty -Path $Path -Name Handle -Value $Handle -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function New-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [string] - $Stack, - - [Parameter(Mandatory=$true)] - [string] - $Resource, - - [Parameter(Mandatory=$true)] - [string] - $Region, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - Write-Verbose "Creating Stack Registry Key" - New-ItemProperty -Path $Path -Name Stack -Value $Stack -Force - - Write-Verbose "Creating Resource Registry Key" - New-ItemProperty -Path $Path -Name Resource -Value $Resource -Force - - Write-Verbose "Creating Region Registry Key" - New-ItemProperty -Path $Path -Name Region -Value $Region -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - - -function Get-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - Write-Verbose "Getting ErrorCount Registry Key" - Get-ItemProperty -Path $Path -Name ErrorCount -ErrorAction Stop | Select-Object -ExpandProperty ErrorCount - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Set-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [int32] - $Count, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $currentCount = Get-AWSQuickStartErrorCount - $currentCount += $Count - - Write-Verbose "Creating ErrorCount Registry Key" - Set-ItemProperty -Path $Path -Name ErrorCount -Value $currentCount -ErrorAction Stop - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty $Path - - return $key.Handle - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty $Path - $resourceSignal = @{ - Stack = $key.Stack - Resource = $key.Resource - Region = $key.Region - } - $toReturn = New-Object -TypeName PSObject -Property $resourceSignal - - if ($toReturn.Stack -and $toReturn.Resource -and $toReturn.Region) { - return $toReturn - } else { - return $null - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Remove-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty -Path $Path -Name Handle -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing Handle key value from $Path" - Remove-ItemProperty -Path $Path -Name Handle - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Remove-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - foreach ($keyName in @('Stack','Resource','Region')) { - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty -Path $Path -Name $keyName -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing $keyName key value from $Path" - Remove-ItemProperty -Path $Path -Name $keyName - } - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Write-AWSQuickStartEvent { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipelineByPropertyName=$true)] - [string] - $Message, - - [Parameter(Mandatory=$false)] - [string] - $EntryType = 'Error' - ) - - process { - Write-Verbose "Checking for AWSQuickStart Eventlog Source" - if(![System.Diagnostics.EventLog]::SourceExists('AWSQuickStart')) { - New-EventLog -LogName Application -Source AWSQuickStart -ErrorAction SilentlyContinue - } - else { - Write-Verbose "AWSQuickStart Eventlog Source exists" - } - - Write-Verbose "Writing message to application log" - - try { - Write-EventLog -LogName Application -Source AWSQuickStart -EntryType $EntryType -EventId 1001 -Message $Message - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Write-AWSQuickStartException { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - process { - try { - Write-Verbose "Incrementing error count" - Set-AWSQuickStartErrorCount -Count 1 - - Write-Verbose "Getting total error count" - $errorTotal = Get-AWSQuickStartErrorCount - - $errorMessage = "Command failure in {0} {1} on line {2} `nException: {3}" -f $ErrorRecord.InvocationInfo.MyCommand.name, - $ErrorRecord.InvocationInfo.ScriptName, $ErrorRecord.InvocationInfo.ScriptLineNumber, $ErrorRecord.Exception.ToString() - - $CmdSafeErrorMessage = $errorMessage -replace '[^a-zA-Z0-9\s\.\[\]\-,:_\\\/\(\)]', '' - if ($CmdSafeErrorMessage.length -gt 255) { - $CmdSafeErrorMessage = $CmdSafeErrorMessage.substring(0,252) + '...' - } - - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 1 --reason='$CmdSafeErrorMessage' '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 1 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - finally { - Write-AWSQuickStartEvent -Message $errorMessage - # throwing an exception to force cfn-init execution to stop - throw $CmdSafeErrorMessage - } - } -} - -function Write-AWSQuickStartStatus { - [CmdletBinding()] - Param() - - process { - try { - Write-Verbose "Checking error count" - if((Get-AWSQuickStartErrorCount) -eq 0) { - Write-Verbose "Getting Handle" - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 0 '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 0 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Configure-password-policy.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Configure-password-policy.ps1 deleted file mode 100644 index fde4721..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Configure-password-policy.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [Boolean] - $ComplexityEnabled, - - [string] - $LockoutDuration, - - [string] - $LockoutObservationWindow, - - [string] - $LockoutThreshold, - - [string] - $MaxPasswordAge, - - [string] - $MinPasswordAge, - - [string] - $MinPasswordLength, - - [string] - $PasswordHistoryCount, - - [Boolean] - $ReversibleEncryptionEnabled -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -#Configure passsord policy for all users -Set-ADFineGrainedPasswordPolicy -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -ComplexityEnabled:$ComplexityEnabled -MaxPasswordAge:$MaxPasswordAge -LockoutDuration:$LockoutDuration -LockoutObservationWindow:$LockoutObservationWindow -LockoutThreshold:$LockoutThreshold -MinPasswordAge:$MinPasswordAge -MinPasswordLength:$MinPasswordLength -PasswordHistoryCount:$PasswordHistoryCount -ReversibleEncryptionEnabled:$ReversibleEncryptionEnabled -Server:$fdn -Credential $credential - -#Create password policy subject -Add-ADFineGrainedPasswordPolicySubject -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -Server:$fdn -Subjects:"CN=Domain Users,CN=Users,DC=$dom,DC=$ext" -Credential $credential diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Join-Domain.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Join-Domain.ps1 deleted file mode 100644 index acdf6d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ad-config-scripts/Join-Domain.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainName, - - [string] - $UserName, - - [string] - $Password -) - -try { - $ErrorActionPreference = "Stop" - - $pass = ConvertTo-SecureString $Password -AsPlainText -Force - $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$pass - - Add-Computer -DomainName $DomainName -Credential $cred -ErrorAction Stop - - # Execute restart after script exit and allow time for external services - $shutdown = Start-Process -FilePath "shutdown.exe" -ArgumentList @("/r", "/t 10") -Wait -NoNewWindow -PassThru - if ($shutdown.ExitCode -ne 0) { - throw "[ERROR] shutdown.exe exit code was not 0. It was actually $($shutdown.ExitCode)." - } -} -catch { - $_ | Write-AWSQuickStartException -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/appConfigs/appA/launchTemplate/userData.sh b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/appConfigs/appA/launchTemplate/userData.sh deleted file mode 100644 index 01e78a0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/appConfigs/appA/launchTemplate/userData.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -yum update -y -yum install -y httpd -systemctl start httpd -systemctl enable httpd -usermod -a -G apache ec2-user -chown -R ec2-user:apache /var/www -chmod 2775 /var/www -find /var/www -type d -exec chmod 2775 {} \; -find /var/www -type f -exec chmod 0664 {} \; \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-policies/org-backup-policies.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-policies/org-backup-policies.json deleted file mode 100644 index aadece2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-policies/org-backup-policies.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "plans": { - "Organization_Backup_Plan": { - "rules": { - "Monthly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Weekly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 ? * MON *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Daily_Rule": { - "schedule_expression": { - "@@assign": "cron(15 10 * * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "NinetyDay_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */3 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Yearly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */12 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "1825" - }, - "delete_after_days": { - "@@assign": "365" - } - } - } - }, - "regions": { - "@@append": [ - "us-east-1" - ] - }, - "selections": { - "tags": { - "Monthly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupMonthly" - }, - "tag_value": { - "@@assign": [ - "Monthly" - ] - } - }, - "Weekly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupWeekly" - }, - "tag_value": { - "@@assign": [ - "Weekly" - ] - } - }, - "Daily_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupDaily" - }, - "tag_value": { - "@@assign": [ - "Daily" - ] - } - }, - "NinetyDay_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupNinetyDay" - }, - "tag_value": { - "@@assign": [ - "NinetyDay" - ] - } - }, - "Yearly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupYearly" - }, - "tag_value": { - "@@assign": [ - "Yearly" - ] - } - } - } - }, - "backup_plan_tags": { - "stage": { - "tag_key": { - "@@assign": "Stage" - }, - "tag_value": { - "@@assign": "Released" - } - } - } - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-vault-policies/infrastructure-vault-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-vault-policies/infrastructure-vault-policy.json deleted file mode 100644 index ef8395f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/backup-vault-policies/infrastructure-vault-policy.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyDeleteRecoveryPoint", - "Effect": "Deny", - "Principal": "*", - "Action": "backup:DeleteRecoveryPoint", - "Resource": "*", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/access-logs-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/access-logs-bucket.json deleted file mode 100644 index fbff730..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/access-logs-bucket.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "statement from policy file", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "Bool": { - "aws:SecureTransport": "false" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/assets-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/assets-bucket.json deleted file mode 100644 index 417a53e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/assets-bucket.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": ["s3:List*"], - "Resource": [ - "arn:aws:s3:::aws-accelerator-imported-all-enabled", - "arn:aws:s3:::aws-accelerator-imported-all-enabled/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/central-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/central-log-bucket.json deleted file mode 100644 index ad51827..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/central-log-bucket.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "This is from policy file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}", - "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Organization principals to put session manager logs", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:PutObject", - "Resource": "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "ArnLike": { - "aws:PrincipalARN": "arn:${PARTITION}:iam::*:role/EC2-Default-SSM-AD-Role" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/elb-logs-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/elb-logs-bucket.json deleted file mode 100644 index 9cdfb74..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/elb-logs-bucket.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-access-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-access-log-bucket.json deleted file mode 100644 index 77aa336..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-access-log-bucket.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "statement from policy file", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "Bool": { - "aws:SecureTransport": "false" - } - } - }, - { - "Sid": "Allow write access for logging service principal - from file", - "Effect": "Allow", - "Principal": { - "Service": "logging.s3.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}/*", - "Condition": { - "StringEquals": { - "aws:SourceAccount": "${ACCOUNT_ID}" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-central-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-central-log-bucket.json deleted file mode 100644 index 7159bb3..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-central-log-bucket.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "deny-insecure-connections - From file", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "Bool": { - "aws:SecureTransport": "false" - } - } - }, - { - "Effect": "Allow", - "Principal": { - "Service": [ - "ssm.amazonaws.com", - "delivery.logs.amazonaws.com", - "config.amazonaws.com", - "cloudtrail.amazonaws.com" - ] - }, - "Action": "s3:PutObject", - "Resource": "arn:aws:s3:::existing-central-log-bucket/*", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - }, - { - "Effect": "Allow", - "Principal": { - "Service": [ - "delivery.logs.amazonaws.com", - "cloudtrail.amazonaws.com", - "config.amazonaws.com" - ] - }, - "Action": [ - "s3:GetBucketAcl", - "s3:ListBucket" - ], - "Resource": "arn:aws:s3:::existing-central-log-bucket" - }, - { - "Sid": "Allow Organization principals to use the bucket - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:GetBucketLocation", - "s3:GetBucketAcl", - "s3:PutObject", - "s3:GetObject", - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Organization use of the bucket for replication - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*", - "s3:GetBucketVersioning", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ObjectOwnerOverrideToBucketOwner" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow read write access for Macie service principal - From file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ] - }, - { - "Sid": "Allow read write access for Guardduty service principal - From file", - "Effect": "Allow", - "Principal": { - "Service": "guardduty.amazonaws.com" - }, - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ] - }, - { - "Sid": "Allow Organization principals to put objects - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:PutObjectAcl", - "s3:PutObject" - ], - "Resource": "arn:aws:s3:::existing-central-log-bucket/*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Organization principals to get encryption context and acl - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:GetEncryptionConfiguration", - "s3:GetBucketAcl" - ], - "Resource": "arn:aws:s3:::existing-central-log-bucket", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "This is from policy file - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:List*", - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-elb-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-elb-log-bucket.json deleted file mode 100644 index c6b680a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/bucket-policies/full-elb-log-bucket.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow get acl access for SSM principal - from file", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}" - }, - { - "Sid": "Allow write access for ELB Account principal - from file", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" - }, - "Action": "s3:PutObject", - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ] - }, - { - "Sid": "Allow write access for delivery logging service principal - from file", - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - }, - { - "Sid": "Allow read bucket ACL access for delivery logging service principal", - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}" - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/cloudformation-templates/custom-s3-bucket.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/cloudformation-templates/custom-s3-bucket.yaml deleted file mode 100644 index 47420cf..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/cloudformation-templates/custom-s3-bucket.yaml +++ /dev/null @@ -1,55 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Parameters: - pStringParameter: - Description: A parameter passed explicitly as a string - Type: String - Default: "" - pDynamicParameter: - Description: A parameter passed as a dynamic lookup from SSM - Type: String - Default: "" -Conditions: - hasStringParameter: !Not [ !Equals [ !Ref pStringParameter, "" ]] - hasDynamicParameter: !Not [ !Equals [ !Ref pDynamicParameter, "" ]] - hasParameters: !And [!Condition hasStringParameter, !Condition hasDynamicParameter] -Resources: - LZACustomizationsBucket: - Type: 'AWS::S3::Bucket' - Properties: - BucketEncryption: - ServerSideEncryptionConfiguration: - - BucketKeyEnabled: false - ServerSideEncryptionByDefault: - SSEAlgorithm: 'aws:kms' - KMSMasterKeyID: 'aws/s3' - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - VersioningConfiguration: - Status: Enabled - - LZACustomizationsSampleBucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref LZACustomizationsBucket - PolicyDocument: - Version: '2012-10-17' - Statement: - - Principal: - AWS: '*' - Action: - - 's3:*' - Resource: - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}/*' - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}' - Effect: 'Deny' - Condition: - Bool: - aws:SecureTransport: 'false' - MySNSTopic: - Condition: hasParameters - Type: AWS::SNS::Topic - Properties: - TopicName: !Sub ${pDynamicParameter}-${pStringParameter}-topic diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-detection-role.json deleted file mode 100644 index db821df..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-detection-role.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-remediation-role.json deleted file mode 100644 index 52701c4..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile-remediation-role.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/attach-ec2-instance-profile.zip deleted file mode 100644 index 1de00bf06d02573ed8890d48da07d3c50ed785dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmWIWW@Zs#-~hs}B^f~sQ1F+70VtxtkeQc~TA`O!92&yQz<&LuYr;(+2GOMz+zgB? zq7NO*Sk6>47j6wZm?vc@u=lg5@XAw7F6KdfA(jD$8nqfZR#bH?dQic>VN-T~u0D(F zpX%e;(^4A+LblF1V4R}~FRwR-!${OMK~i1d4x^c&R)1FH?mAy5u>2*L=IrOQWFLCFF_mpk zTpd;MxlyY1^FEb*3(hJmpQ(Fzmang~No3CPSE8xhdmMJzWIeO9ULj`l%zZ=Vir-Ug zG-sSLIUaY#O6B#VyftTqBh;U2ZrLn3%Yq{>7<#t{oL2L{{QrZl|Oi`P~s=;Bwwr0?Z0pQ5l}FFIxo7y-e_C) ztOIv8-sta67UzE}-@lo|_istR^wqgHU!DDKCHIB>@pGfVfPggXy)%2BbKHt`o9Lv> z%&p`5STusw)r&cc>)P6n)4yMCU8`<))G9vtz>S1+-*^PwLjuzNxCWlNt$QrPkn5k~ z-2)fbs<1y~i85lI=$gq(BwSQb`SYegdz0$Xv-@{` zIluId1^@*MvS$DQ diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/bucket-sse-enabled-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/bucket-sse-enabled-remediation-role.json deleted file mode 100644 index 3c37861..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/bucket-sse-enabled-remediation-role.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["s3:GetEncryptionConfiguration", "s3:PutEncryptionConfiguration"], - "Resource": "*", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": ["arn:aws:iam::*:role/AWSAccelerator-SecuritySt-*"] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-detection-role.json deleted file mode 100644 index e612dc8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-detection-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "iam:Get*", - "iam:List*" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json deleted file mode 100644 index 83c5a8d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/ec2-instance-profile-permissions.zip deleted file mode 100644 index 9d64fe687967e4fa3cf01897db666d68356cb1af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1292 zcmWIWW@Zs#-~hr?Ejd99P~gSS02EPR$jnPgtcVx~UR-qK+7Txoqf#cY`$ghLj~zm}!V^8GE_ovn z`S&}Yr>*YMNS2oi_uiay{QKF%#*aM;mL~qWF0{_!(tcyLmG37u|B5`?A6H=M;3>hm zFKxDg;B4NhE86c*^?30#K_D_)-JNkc`(v#+H`-OFR*MDNuMj!Def#yzCx3i@{qpbK zlAS+3=NXyqm~U$$)tmNs_Qe-`veoeyn(UT3Wr^l-_6u`+UzzB>;atU%nGYUZwtZTa zaly3q5~B_Of+cA?e(_KC$=ukyTW8@0F`-4ey>gQG53KcYIkJLjk?Fzf5?dHr*gLmP z(5yL8(X+rf#ka!cVOW=`^Zvvi4G*^z{)^Ciw(ydW?dGo=mmJ-!B6(*$FlJ zJdG;@4|c3c^;)v%oK}uzh{HJpCABwK4VPxz4B{yd(cHAqE$p@j&+lm-n}TaSrcTMy z60P~ocCYwdq{lY4|LwP|jy$>}(DyI-jP&Np-yaxfN$Fh-nYUu+B!(AjMc8l41Z%c_ z*{gXu`pi4m>z`Q@=IX_LC}(uJw8=YJ-{E=X!S5!H;o%Q-eN!i<*oe$yj~GN&RZ^+BwtZ_wV4Z3=r|`k_|3f z@hsx_9EFew%~`ypDUAoI!}zSW@=FD zx|Oki@h)$bDVpXfr_%no2+vuv@q6FHDz!Ue76!(X`CJ^|JP5FNo2t{v`1XM|YYu~$ z@Zlwr_ZDb8)joK0=f5sKv3qZiB&o(3FTHP_vAOH4B7@J(9e0e>{@jQ>ac_24#-keN z8Hr9m-STrE*8g08H0G0T*;V0V&B4SvPJtDi!u)yMj>Ar4xp(^M2X~c(Ze?O!&FVi-my!R0;)nGct)VBT6V_IZz1& d14|k~EMm*60B=?{ka|WS3^2bxl5K$)M3+`@GcdCJ zU}Rtb>SkczJ#>(>!GMSLLiO?=+g~l^o?_L?JwzRqWDOm9t` z@by_!pI_4JiEK7}8i%xcohC2+oE$6qpGVpB47+%AYpwiU>AUO!-s~LgyQeP?0GbVQ oOMo{clL#}yoyc+^cf!DuMi7fwR|j~rvVqhy0wKuj%Rn3k06Szw-v9sr diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-detection-role.json deleted file mode 100644 index 320452f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-detection-role.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "wafconfig", - "Effect": "Allow", - "Action": [ - "waf:GetLoggingConfiguration", - "waf:GetWebACL", - "wafv2:GetLoggingConfiguration", - "wafv2:GetWebACL", - "waf-regional:GetLoggingConfiguration", - "waf-regional:GetWebACL" - ], - "Resource": [ - "arn:*:waf::*:*", - "arn:*:wafv2:*:*:*/*/*", - "arn:*:waf-regional:*:*:*" - ] - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-remediation-role.json deleted file mode 100644 index da1d3ff..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled-remediation-role.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "wafconfig", - "Effect": "Allow", - "Action": [ - "waf:PutLoggingConfiguration", - "waf:GetLoggingConfiguration", - "waf:GetWebACL", - "wafv2:PutLoggingConfiguration", - "wafv2:GetLoggingConfiguration", - "wafv2:GetWebACL", - "waf-regional:PutLoggingConfiguration", - "waf-regional:GetLoggingConfiguration", - "waf-regional:GetWebACL" - ], - "Resource": [ - "*" - ] - }, - { - "Sid": "logs", - "Effect": "Allow", - "Action": [ - "logs:*" - ], - "Resource": [ - "*" - ] - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled.zip b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/custom-config-rules/waf-logging-enabled.zip deleted file mode 100644 index f699f66262bb0996ad0c38177268004eb62a82fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmWIWW@Zs#U|`^2D7Kmy#vyF z+wuClCimaNqNy+CRCcf4%VQ_oQha8mN8FpxDf^~Alxp#;h!j&Qb#-02UO`Z7d+Y&O zuiG!TGxR*&W@OMH{C85T`_i4+^*)QN^N-1$Jp5aYbzN>!&C6zmD5L)~Cmh=Tq*qtU zn=LTpxuDRroNseW8_!-`;kEpSZ@`SX(rndl7^X|^j6G%4kR2!SWjzPZL3{=zD9QP<;dk9YhH2REf6+nvhwe4$y8jyHOatDzsWA^$E#h; z?1t4PqMM3e%vHYIt2hOrEoj@5gOVyVtj#+HaCAfA`jAx3<@VgzNeT zr#zbGCAD4U;F{^m2c>>YGP$O6OP^(-2}eR{O;2!!*e<8*2iJ>uO9xH4Gb60QU8{P_ zJ@E(Ot-k4(Q!IAxoZIvGWz$bZiJVy`R~YIxc1((FwEFb=M(OJ0kKqY^D_dXhy4!Z~ zOP$Sp-+Z^rtapxoF8ot!wCnR^KC@F_0^YTRZMytrw|c+u?gL&`yYim++3u9AR4jeh z7rH*c#yHS-``(i$Y&)&Xz|tQICxlpO@LCsF(ja$+j?h>bag4g|R@2*duEZ}(N&{3;T2fV9O zQzu)gvl>rapIsutGcAtk!|s2Z9xmMQQ#1Pahd!h8lK1|WUnu-`al(^A&4k#yKiBY2 zGoGvXwsxODwAGWE=zZqzwI5EH?vhy2_{XrkzTRoxE`zLfT5p_e7s^~&_%+bQ;2&#% oHzSh>1MZvz%uEc73<@9$2w?dtz?+o~#AgITGa&5&%zF$B0L-G9761SM diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/customizations-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/customizations-config.yaml deleted file mode 100644 index 4c8bdad..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/customizations-config.yaml +++ /dev/null @@ -1,898 +0,0 @@ -customizations: - cloudFormationStacks: - - deploymentTargets: - accounts: - - Management - description: Sample stack description - name: Custom-S3-Stack - regions: - - us-east-1 - runOrder: 1 - template: cloudformation-templates/custom-s3-bucket.yaml - terminationProtection: false - parameters: - - name: pStringParameter - value: "test" - - name: pDynamicParameter - value: "{{resolve:ssm:/accelerator/lza-prefix}}" - - deploymentTargets: - organizationalUnits: - - Security - description: Second sample stack description - name: Custom-S3-Stack-2 - regions: - - us-east-1 - - us-west-2 - runOrder: 2 - template: cloudformation-templates/custom-s3-bucket.yaml - terminationProtection: true - - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Infrastructure - description: Third sample stack description - name: Custom-S3-Stack-3 - regions: - - us-east-1 - - us-west-2 - runOrder: 2 - template: cloudformation-templates/custom-s3-bucket.yaml - terminationProtection: true - cloudFormationStackSets: - - capabilities: [CAPABILITY_IAM, CAPABILITY_NAMED_IAM, CAPABILITY_AUTO_EXPAND] - deploymentTargets: - organizationalUnits: - - Infrastructure - accounts: - - Management - description: Sample stackset description - name: Custom-S3-Stackset - regions: - - us-east-1 - runOrder: 1 - template: cloudformation-templates/custom-s3-bucket.yaml - serviceCatalogPortfolios: - - name: My-Portfolio - provider: LZA - account: Management - regions: - - us-east-1 - - us-west-2 - shareTargets: - organizationalUnits: - - Security - shareTagOptions: true - portfolioAssociations: - - type: Group - name: Administrators - propagateAssociation: true - - type: Role - name: EC2-Default-SSM-AD-Role - - type: User - name: breakGlassUser01 - products: - - name: My-Product - description: Sample product by lZA. - owner: LZA - distributor: LZA - versions: - - name: v1 - description: product version is v1 - template: cloudformation-templates/custom-s3-bucket.yaml - deprecated: false - constraints: - launch: - type: LocalRole - role: testRole - tagUpdate: false - notifications: - - AWSAccelerator-ControlTowerNotification - support: - description: Please include account ID and provisioned product ID. - email: support@example.com - url: https://support.amazon.com - # - name: Portfolio-2 - # provider: LZA - # account: SharedServices - # regions: - # - us-east-1 - # shareTargets: - # accounts: - # - Audit - # portfolioAssociations: - # - type: Role - # name: EC2-Default-SSM-AD-Role - # propagateAssociation: true - # - type: PermissionSet - # name: PermissionSet1 - # products: - # - name: Product2 - # description: Sample product by lZA. - # owner: LZA - # distributor: LZA - # versions: - # - name: v1 - # description: product version is v1 - # template: cloudformation-templates/custom-s3-bucket.yaml - # deprecated: false - # support: - # description: Please include account ID and provisioned product ID. - # email: support@example.com - # url: https://support.amazon.com - -applications: - - name: appA - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - autoscaling: - name: appA-asg - maxSize: 2 - minSize: 0 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: appA-lt - healthCheckGracePeriod: 300 - # this is the only example with ELB so targetGroups are also specified - healthCheckType: ELB # EC2|ELB - # target group names should match the names from targetGroup section - targetGroups: - - appA-nlb-tg-1 - subnets: - - PrivateA - - PrivateB - maxInstanceLifetime: 86400 - - launchTemplate: - name: appA-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deleteOnTermination: true - description: secondary network interface - deviceIndex: 0 - groups: - # security group is from network-config.yaml under the same vpc - - appSecurityGroup - networkCardIndex: 0 - # subnet is from network-config.yaml under the same vpc - subnetId: PrivateA - # this path is relative to the config repository and the content should be in regular text. - # Its encoded in base64 before passing in to launch Template - userData: appConfigs/appA/launchTemplate/userData.sh - - targetGroups: - - name: appA-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - - name: appA-alb-tg-1 - port: 80 - protocol: HTTP - type: instance - attributes: - deregistrationDelay: 1200 - stickiness: true - # applies to application load balancer - stickinessType: app_cookie - algorithm: round_robin - slowStart: 120 - appCookieName: chocolate-chip - appCookieDuration: 4800 - lbCookieDuration: 4800 - healthCheck: - enabled: true - port: 80 - protocol: HTTP - networkLoadBalancer: - name: appA-nlb - scheme: internal - deletionProtection: false - subnets: - # subnets are from network-config.yaml under the same vpc - - PrivateA - - PrivateB - crossZoneLoadBalancing: true - listeners: - - name: appA-listener-1 - port: 80 - protocol: TCP - # target group names should match the names from targetGroup section - targetGroup: appA-nlb-tg-1 - - applicationLoadBalancer: - name: appA-alb-01 - scheme: internet-facing - subnets: - - PrivateA - - PrivateB - securityGroups: - - appSecurityGroup - listeners: - - name: appA-listener-2 - port: 80 - protocol: HTTP - targetGroup: appA-alb-tg-1 - type: forward - - name: appB - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - autoscaling: - name: testTgAsg-asg - maxSize: 2 - minSize: 0 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: testTgAsg-lt - healthCheckGracePeriod: 300 - healthCheckType: EC2 # EC2|ELB - subnets: - - PrivateA - - PrivateB - maxInstanceLifetime: 86400 - - launchTemplate: - name: testTgAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - - targetGroups: - - name: testTgAsg-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - protocolVersion: HTTP1 - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - networkLoadBalancer: - name: testTgAsg-nlb - scheme: internal - deletionProtection: false - subnets: - # subnets are from network-config.yaml under the same vpc - - PrivateA - - PrivateB - crossZoneLoadBalancing: true - listeners: - - name: testTgAsg-listener-1 - port: 80 - protocol: TCP - # target group names should match the names from targetGroup section - targetGroup: testTgAsg-nlb-tg-1 - # minimum config for application target group in vpc created - - name: appC - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - targetGroups: - - name: appC-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - # target group and launch template created - - name: appD - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - targetGroups: - - name: appD-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - launchTemplate: - name: appDAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - # target group, launch template and autoscaling created. Target group is not tied to AutoScaling - - name: appE - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - targetGroups: - - name: appE-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - launchTemplate: - name: appEAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - autoscaling: - name: appE-asg - maxSize: 2 - minSize: 0 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: testTgAsg-lt - healthCheckGracePeriod: 300 - healthCheckType: EC2 # EC2|ELB - subnets: - - PrivateA - - PrivateB - maxInstanceLifetime: 86400 - # target group, launch template and autoscaling added. Target group is tied to Autoscaling - - name: appF - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - targetGroups: - - name: appF-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - launchTemplate: - name: appFAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - - - name: appG - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - - ap-south-1 - - eu-central-1 - - us-west-1 - - eu-north-1 - - eu-west-3 - - eu-west-2 - - eu-west-1 - - ap-northeast-3 - - ap-northeast-2 - - ap-northeast-1 - - sa-east-1 - - ap-southeast-1 - - ap-southeast-2 - - us-east-2 - - ca-central-1 - targetGroups: - - name: appG-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - - name: appG-nlb-tg-2 - port: 443 - protocol: TLS - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 443 - protocol: TCP - - name: appG-alb-tg-1 - port: 80 - protocol: HTTP - type: instance - healthCheck: - enabled: true - port: 80 - protocol: HTTP - - name: appG-alb-tg-2 - port: 443 - protocol: HTTPS - type: instance - algorithm: round_robin - attributes: - slowStart: 120 - healthCheck: - enabled: true - port: 443 - protocol: HTTPS - networkLoadBalancer: - name: appG-nlb-01 - scheme: internal - deletionProtection: false - subnets: - - PrivateA - - PrivateB - listeners: - - name: appG-listener-1 - port: 80 - protocol: TCP - targetGroup: appG-nlb-tg-1 - - name: appG-listener-2 - port: 443 - protocol: TLS - targetGroup: appG-nlb-tg-2 - certificate: cert1 - alpnPolicy: HTTP2Optional - sslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 - applicationLoadBalancer: - name: appG-alb-01 - scheme: internet-facing - subnets: - - PrivateA - - PrivateB - securityGroups: - - appSecurityGroup - listeners: - - name: appG-listener-2 - port: 80 - protocol: HTTP - targetGroup: appG-alb-tg-1 - type: forward - - name: appG-alb-listener-2 - port: 443 - protocol: HTTPS - targetGroup: appG-alb-tg-2 - type: forward - certificate: cert1 - sslPolicy: ELBSecurityPolicy-2016-08 - # autoscaling group and launch template created using subnet shared from another account - - name: appH - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-Main - deploymentTargets: - accounts: - - Network - excludedRegions: - - us-west-2 - autoscaling: - name: appH-asg - maxSize: 1 - minSize: 1 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: appH-lt - healthCheckGracePeriod: 300 - # this is the only example with ELB so targetGroups are also specified - healthCheckType: EC2 # EC2|ELB - # target group names should match the names from targetGroup section - subnets: - - SharedServices-App-C - maxInstanceLifetime: 86400 - - launchTemplate: - name: appH-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - securityGroups: - - SharedServices-Main-Rsyslog-sg - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - # this path is relative to the config repository and the content should be in regular text. - # Its encoded in base64 before passing in to launch Template - userData: appConfigs/appA/launchTemplate/userData.sh - metadataOptions: - httpTokens: required - httpPutResponseHopLimit: 2 - httpEndpoint: enabled - httpProtocolIpv6: disabled - instanceMetadataTags: disabled - - -firewalls: - instances: - - name: accelerator-firewall - configFile: instance-config.txt - licenseFile: license.lic - staticReplacements: - - key: CORP_CIDR_1 - value: 10.0.0.0/16 - - key: CORP_CIDR_2 - value: 192.168.0.0/16 - launchTemplate: - name: firewall-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ami-0e1c5d8c23330dee3 - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - groups: - - Data - subnetId: Network-Inspection-A - - deviceIndex: 1 - associateElasticIp: true - description: Secondary - groups: - - Data - subnetId: Network-Inspection-A - securityGroups: [] - vpc: Network-Inspection - - name: cross-account-firewall - launchTemplate: - name: firewall-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ami-0e1c5d8c23330dee3 - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - associateElasticIp: true - groups: - - SharedServices-Main-Rsyslog-sg - subnetId: SharedServices-App-A - securityGroups: [] - vpc: SharedServices-Main - - name: firewall-with-shared-subnet - launchTemplate: - name: shared-subnet-firewall-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ami-0e1c5d8c23330dee3 - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - associateElasticIp: true - groups: - - SharedServices-Main-Rsyslog-sg - subnetId: SharedServices-App-C - securityGroups: [] - vpc: SharedServices-Main - account: Network - autoscalingGroups: - - name: test-asg - autoscaling: - name: firewall-asg - minSize: 1 - maxSize: 4 - desiredSize: 2 - launchTemplate: test-asg - healthCheckGracePeriod: 300 - healthCheckType: EC2 - targetGroups: - - asg-target - subnets: - - Network-Inspection-A - - Network-Inspection-B - maxInstanceLifetime: 86400 - configFile: asg-config.txt - licenseFile: license.lic - launchTemplate: - name: asg - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - groups: - - Data - - deviceIndex: 1 - description: Secondary - groups: - - Data - securityGroups: [] - vpc: Network-Inspection - managerInstances: - - name: accelerator-manager - launchTemplate: - name: manager-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - groups: - - Data - subnetId: Network-Inspection-A - - deviceIndex: 1 - description: Secondary - groups: - - Data - subnetId: Network-Inspection-A - securityGroups: [] - vpc: Network-Inspection - targetGroups: - - name: instance-target - port: 6081 - protocol: GENEVE - type: instance - targets: - - accelerator-firewall - attributes: - targetFailover: rebalance - - name: shared-services-target - port: 6081 - protocol: GENEVE - type: instance - targets: - - cross-account-firewall - attributes: - targetFailover: rebalance - - name: asg-target - port: 6081 - protocol: GENEVE - type: instance diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-1.txt b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-1.txt deleted file mode 100644 index 5bf762a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-1.txt +++ /dev/null @@ -1,2 +0,0 @@ -badactor.com -virus.net \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-2.txt b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-2.txt deleted file mode 100644 index 8152a84..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dns-firewall-domain-lists/domain-list-2.txt +++ /dev/null @@ -1,2 +0,0 @@ -badactor.net -virus.com \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dynamic-partitioning/log-filters.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dynamic-partitioning/log-filters.json deleted file mode 100644 index d8c241a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/dynamic-partitioning/log-filters.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "/AWSAccelerator-SecurityHub", "s3Prefix": "security-hub" } -] diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/firewall-rules/rules.txt b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/firewall-rules/rules.txt deleted file mode 100644 index 2921a6a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/firewall-rules/rules.txt +++ /dev/null @@ -1,16 +0,0 @@ -##################################################################################################################### -# List of Suricata compatible Intrusion prevention system (IPS) rules. # -# Suricata is an open source network IPS that includes a standard rule-based language for traffic inspection. # -# Please refer for https://suricata.readthedocs.io/en/suricata-6.0.2/rules/intro.html Suricata rule syntax. # -# Invalid rule syntax will cause LZA pipeline failure, please review rule syntax of each line before using. # -# This file can have one rule definition per line. -# A line starts with suricata supported action types (alert, pass, drop, reject, rejectsrc, rejectdst, rejectboth) # -# are considered to be rule entry. # -##################################################################################################################### - - -pass ip 10.1.0.0/16 any -> 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; flow:to_server, established; sid:104; rev:1;) \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/global-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/global-config.yaml deleted file mode 100644 index d4481c1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/global-config.yaml +++ /dev/null @@ -1,360 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -enabledRegions: - - *HOME_REGION - - us-west-2 - # - ap-south-1 - # - eu-central-1 - # - us-west-1 - # - eu-north-1 - # - eu-west-3 - # - eu-west-2 - # - eu-west-1 - # - ap-northeast-3 - # - ap-northeast-2 - # - ap-northeast-1 - # - sa-east-1 - # - ap-southeast-1 - # - ap-southeast-2 - # - us-east-2 - # - ca-central-1 - # Opt-in regions commented now, once test pipeline account configured for opt-in regions will use those - # - ap-south-2 - # - eu-south-1 - # - eu-south-2 - # - me-central-1 - # - il-central-1 - # - eu-central-2 - # - af-south-1 - # - me-south-1 - # - ap-east-1 - # - ap-southeast-3 - # - ca-west-1 - -managementAccountAccessRole: AWSControlTowerExecution -cloudwatchLogRetentionInDays: 3653 -acceleratorMetadata: - enable: true - account: LogArchive - readOnlyAccessRoleArns: - - arn:aws:iam::111111111111:role/test-access-role -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - notify-security@example.com -terminationProtection: true -lambda: - encryption: - useCMK: true - deploymentTargets: - accounts: - - Management - - Network - organizationalUnits: - - Security - excludedRegions: - - us-west-2 -controlTower: - enable: true - landingZone: - version: '3.3' - logging: - loggingBucketRetentionDays: 365 - accessLoggingBucketRetentionDays: 3650 - organizationTrail: true - security: - enableIdentityCenterAccess: true - controls: - - identifier: AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS - enable: true - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - identifier: AWS-GR_EBS_OPTIMIZED_INSTANCE - enable: true - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - identifier: AWS-GR_RESTRICTED_COMMON_PORTS - enable: false - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - identifier: AWS-GR_RDS_SNAPSHOTS_PUBLIC_PROHIBITED - enable: false - deploymentTargets: - organizationalUnits: - - SecureWorkloads -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true -s3: - encryption: - createCMK: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 -logging: - account: LogArchive - centralizedLoggingRegion: us-west-2 - cloudtrail: - enable: true - organizationTrail: false - accountTrails: - - name: AWSAccelerator-Account-CloudTrail - regions: - - us-east-1 - deploymentTargets: - organizationalUnits: - - Root - settings: - multiRegionTrail: true - globalServiceEvents: true - managementEvents: true - s3DataEvents: true - lambdaDataEvents: true - sendToCloudWatchLogs: true - apiErrorRateInsight: false - apiCallRateInsight: false - - centralLogBucket: - lifecycleRules: - - enabled: true - id: CentralLogsBucketLifecycleRule-01 - abortIncompleteMultipartUpload: 15 - expiration: 3563 - expiredObjectDeleteMarker: false - noncurrentVersionExpiration: 3653 - noncurrentVersionTransitions: - - storageClass: GLACIER - transitionAfter: 365 - transitions: - - storageClass: GLACIER - transitionAfter: 365 - - enabled: true - id: CentralLogsBucketLifecycleRule-02 - abortIncompleteMultipartUpload: 15 - expiration: 3563 - expiredObjectDeleteMarker: false - noncurrentVersionExpiration: 3653 - noncurrentVersionTransitions: - - storageClass: GLACIER - transitionAfter: 365 - transitions: - - storageClass: GLACIER - transitionAfter: 365 - prefix: 'guardduty' - - enabled: true - id: CentralLogsBucketLifecycleRule-03 - abortIncompleteMultipartUpload: 15 - expiredObjectDeleteMarker: true - noncurrentVersionExpiration: 3653 - noncurrentVersionTransitions: - - storageClass: GLACIER - transitionAfter: 365 - transitions: - - storageClass: GLACIER - transitionAfter: 365 - prefix: 'macie' - s3ResourcePolicyAttachments: - - policy: bucket-policies/central-log-bucket.json - - assetBucket: - importedBucket: - name: aws-accelerator-imported-all-enabled - applyAcceleratorManagedBucketPolicy: true - createAcceleratorManagedKey: true - s3ResourcePolicyAttachments: - - policy: bucket-policies/assets-bucket.json - - accessLogBucket: - enable: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 - importedBucket: - name: existing-access-logs-bucket-${ACCOUNT_ID}-${REGION} - customPolicyOverrides: - policy: bucket-policies/full-access-log-bucket.json - # s3ResourcePolicyAttachments: - # - policy: bucket-policies/access-logs-bucket.json - lifecycleRules: [] - elbLogBucket: - importedBucket: - name: existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION} - applyAcceleratorManagedBucketPolicy: true - # customPolicyOverrides: - # policy: bucket-policies/full-elb-log-bucket.json - s3ResourcePolicyAttachments: - - policy: bucket-policies/elb-logs-bucket.json - lifecycleRules: [] - - sessionManager: - sendToCloudWatchLogs: false - sendToS3: true - cloudwatchLogs: - encryption: - useCMK: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 - dataProtection: - overrideExisting: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - - Infrastructure - excludedRegions: - - us-west-2 - managedDataIdentifiers: - categories: - - Credentials - enable: true - dynamicPartitioning: dynamic-partitioning/log-filters.json - replaceLogDestinationArn: replaceLogDestinationArn - - exclusions: - - organizationalUnits: - - Infrastructure - excludeAll: true - - organizationalUnits: - - Root - logGroupNames: - - test1/* - - accounts: - - Management - regions: - - us-east-1 - logGroupNames: - - test/* -tags: - - key: Environment - value: Dev - - key: ResourceOwner - value: AcmeApp -limits: - - serviceCode: lambda - quotaCode: L-B99A9384 - desiredValue: 1000 - deploymentTargets: - organizationalUnits: - - Root - regions: - - us-west-2 - - serviceCode: iam - quotaCode: L-4019AD8B - desiredValue: 15 - deploymentTargets: - accounts: - - SharedServices -reports: - costAndUsageReport: - compression: Parquet - format: Parquet - reportName: accelerator-cur - s3Prefix: cur - timeUnit: DAILY - refreshClosedReports: true - reportVersioning: CREATE_NEW_REPORT - budgets: - - deploymentTargets: - accounts: - - Management - name: accel-budget - timeUnit: MONTHLY - type: COST - amount: 2000 - includeUpfront: true - includeTax: true - includeSupport: true - includeSubscription: true - includeRecurring: true - includeOtherSubscription: true - includeDiscount: true - includeCredit: false - includeRefund: false - useBlended: false - useAmortized: false - unit: USD - notifications: - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 100 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 90 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - recipients: - - myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 80 - comparisonOperator: GREATER_THAN - subscriptionType: SNS - address: arn:aws:sns:us-east-1:111111111111:aws-accelerator-Security - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 75 - comparisonOperator: GREATER_THAN - subscriptionType: SNS - recipients: - - arn:aws:sns:us-east-1:111111111111:aws-accelerator-Security - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 50 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - recipients: - - myemail+pa-budg@example.com - - myemail+pa1-budg@example.com -backup: - vaults: - - name: BackupVault - deploymentTargets: - organizationalUnits: - - Root - - name: InfrastructureVault - deploymentTargets: - accounts: - - Management - policy: backup-vault-policies/infrastructure-vault-policy.json -ssmParameters: - - deploymentTargets: - organizationalUnits: - - Infrastructure - accounts: - - Management - parameters: - - name: parameterTest - path: /my/parameter/structure - value: parameterTestValue - - name: someOtherName - path: /my/account/structure - value: someOtherValue - -ssmInventory: - enable: true - deploymentTargets: - organizationalUnits: - - Root diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-config.yaml deleted file mode 100644 index 4ce874c..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-config.yaml +++ /dev/null @@ -1,247 +0,0 @@ -policySets: - - deploymentTargets: - organizationalUnits: - - Root - policies: - - name: Default-Boundary-Policy - policy: iam-policies/boundary-policy.json - - deploymentTargets: - organizationalUnits: - - Root - # excludedAccounts: - # - Audit - # accounts: - # - Audit - # - LogArchive - identityCenterDependency: true - policies: - - name: lzaManagedPolicy01 - policy: iam-policies/ResourceConfigurationCollectorPolicy-policy.json - - name: lzaManagedPolicy02 - policy: iam-policies/ResourceConfigurationCollectorPolicy-policy.json -roleSets: - - deploymentTargets: - organizationalUnits: - - Root - roles: - - name: EC2-Default-SSM-AD-Role - instanceProfile: true - assumedBy: - - type: service - principal: ec2.amazonaws.com - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonSSMDirectoryServiceAccess - - CloudWatchAgentServerPolicy - boundaryPolicy: Default-Boundary-Policy - - name: Backup-Role - assumedBy: - - type: service - principal: backup.amazonaws.com - policies: - awsManaged: - - service-role/AWSBackupServiceRolePolicyForBackup - - service-role/AWSBackupServiceRolePolicyForRestores -groupSets: - - deploymentTargets: - organizationalUnits: - - Root - groups: - - name: Administrators - policies: - awsManaged: - - AdministratorAccess -identityCenter: - name: identityCenter1 - delegatedAdminAccount: Audit - identityCenterPermissionSets: - - name: PermissionSet1 - policies: - awsManaged: - - arn:aws:iam::aws:policy/AdministratorAccess - - arn:aws:iam::aws:policy/CloudFrontFullAccess - - PowerUserAccess - - ReadOnlyAccess - customerManaged: - - ResourceConfigurationCollectorPolicy - acceleratorManaged: - - lzaManagedPolicy01 - - lzaManagedPolicy02 - inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - permissionsBoundary: - # customerManagedPolicy: - # name: lzaManagedPolicy01 - # path: / - awsManagedPolicyName: PowerUserAccess - # sessionDuration: 100 - - name: PermissionSet2 - policies: - awsManaged: - - PowerUserAccess - - ReadOnlyAccess - customerManaged: - - ResourceConfigurationCollectorPolicy - acceleratorManaged: - - lzaManagedPolicy01 - # - lzaManagedPolicy02 - inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - permissionsBoundary: - customerManagedPolicy: - name: lzaManagedPolicy01 - path: / - # awsManagedPolicyName: PowerUserAccess - sessionDuration: 60 - identityCenterAssignments: - - name: Assignment1 - permissionSetName: PermissionSet1 - principalId: 'abcd1234-1001-70f0-9c12-56a6aa967ca4' - principalType: USER - principals: - - type: USER - name: lza-accelerator-user-01 - - type: GROUP - name: lza-accelerator-group-01 - - type: USER - name: lza-accelerator-user-02 - - type: GROUP - name: lza-accelerator-group-02 - deploymentTargets: - accounts: - - LogArchive - - name: Assignment2 - permissionSetName: PermissionSet1 - principalId: '1234abcd-1001-70f0-9c12-56a6aa967ca4' - principalType: GROUP - deploymentTargets: - organizationalUnits: - - Security -userSets: - - deploymentTargets: - accounts: - - Management - users: - - username: breakGlassUser01 - group: Administrators - boundaryPolicy: Default-Boundary-Policy - - username: breakGlassUser02 - group: Administrators - boundaryPolicy: Default-Boundary-Policy -secretSets: - - deploymentTargets: - accounts: - - SharedServices - secrets: - - name: managedActiveDirectoryAdminUserSecret - description: Managed AD admin user secret - # kmsKeyName: Key1 - userName: Admin - passwordPolicy: - excludeUppercase: false - excludeLowercase: false - excludeNumbers: false - requireEachIncludedType: true - includeSpace: false - passwordLength: 32 - excludePunctuation: false - excludeCharacters: '@#' -managedActiveDirectories: - - name: AcceleratorManagedActiveDirectory - type: AWS Managed Microsoft AD - account: SharedServices - region: us-east-1 - dnsName: example.com - netBiosDomainName: example - description: Example managed AD - edition: Standard - vpcSettings: - vpcName: SharedServices-Main - subnets: - - SharedServices-App-A - - SharedServices-App-B - resolverRuleName: example-rule - secretConfig: - account: Audit - region: us-east-1 - adminSecretName: my-admin-002 - sharedOrganizationalUnits: - organizationalUnits: - - Root - excludedAccounts: - - Audit - # sharedAccounts: - # - Network - # - Management - logs: - groupName: /aws/directoryservice/AcceleratorManagedActiveDirectory - retentionInDays: 30 - # activeDirectoryConfigurationInstance: - # instanceType: t3.large - # vpcName: SharedServices-Main - # subnetName: SharedServices-App-A - # imagePath: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base - # securityGroupInboundSources: - # - 10.4.0.0/16 - # # securityGroupInboundConfigs: - # # - description: Allow RDP and HTTPS Traffic Inbound - # # types: - # # - RDP - # # - HTTPS - # # sources: - # # - 10.4.0.0/16 - # # securityGroupName: ActiveDirectoryConfigInstanceSG - # instanceRole: EC2-Default-SSM-AD-Role - # enableTerminationProtection: false - # userDataScripts: - # - scriptName: JoinDomain - # scriptFilePath: ad-config-scripts/Join-Domain.ps1 - # - scriptName: AWSQuickStart - # scriptFilePath: ad-config-scripts/AWSQuickStart.psm1 - # - scriptName: ADGroupSetup - # scriptFilePath: ad-config-scripts/AD-group-setup.ps1 - # - scriptName: ADUserSetup - # scriptFilePath: ad-config-scripts/AD-user-setup.ps1 - # - scriptName: ADUserGroupSetup - # scriptFilePath: ad-config-scripts/AD-user-group-setup.ps1 - # - scriptName: ADGroupGrantPermissionsSetup - # scriptFilePath: ad-config-scripts/AD-group-grant-permissions-setup.ps1 - # - scriptName: ADConnectorPermissionsSetup - # scriptFilePath: ad-config-scripts/AD-connector-permissions-setup.ps1 - # - scriptName: ConfigurePasswordPolicy - # scriptFilePath: ad-config-scripts/Configure-password-policy.ps1 - # adGroups: - # - aws-Provisioning - # - aws-Billing - # adPerAccountGroups: - # - "*-Admin" - # - "*-PowerUser" - # - "*-View" - # adConnectorGroup: ADConnector-grp - # adPasswordPolicy: - # history: 24 - # maximumAge: 90 - # minimumAge: 1 - # minimumLength: 14 - # complexity: true - # reversible: false - # failedAttempts: 6 - # lockoutDuration: 30 - # lockoutAttemptsReset: 30 - # adUsers: - # - name: adconnector-user-01 - # email: example-adconnector-user@example.com - # groups: - # - ADConnector-grp - # - name: user1-01 - # email: example-user1@example.com - # groups: - # - aws-Provisioning - # - "*-View" - # - "*-Admin" - # - "*-PowerUser" - # - AWS Delegated Administrators - # - name: user2-002 - # email: example-user2@example.com - # groups: - # - aws-Provisioning - # - "*-View" diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/ResourceConfigurationCollectorPolicy-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/ResourceConfigurationCollectorPolicy-policy.json deleted file mode 100644 index 94280a6..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/ResourceConfigurationCollectorPolicy-policy.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:CreateBucket", - "s3:PutBucketPolicy" - ], - "Resource": "arn:aws:s3:::rcc-bucket-trustedservice-do-not-delete-*" - }, - { - "Effect": "Allow", - "Action": [ - "s3:ListAllMyBuckets" - ], - "Resource": "arn:aws:s3:::*" - }, - { - "Effect": "Allow", - "Action": [ - "config:DescribeConfigurationRecorders", - "config:DescribeConfigurationRecorderStatus", - "config:PutConfigurationRecorder", - "config:StartConfigurationRecorder", - "config:DescribeDeliveryChannels", - "config:DescribeDeliveryChannelStatus", - "config:PutDeliveryChannel", - "config:DescribeAggregationAuthorizations", - "config:PutAggregationAuthorization", - "config:ListDiscoveredResources", - "config:BatchGetResourceConfig" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetRole", - "iam:PassRole", - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - "iam:GetServiceLinkedRoleDeletionStatus" - ], - "Resource": "arn:aws:iam::*:role/aws-service-role/config.amazonaws.com/*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/boundary-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/boundary-policy.json deleted file mode 100644 index 18de2ad..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/boundary-policy.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - }, - { - "Effect": "Deny", - "NotAction": [ - "iam:CreateVirtualMFADevice", - "iam:DeleteVirtualMFADevice", - "iam:ListVirtualMFADevices", - "iam:EnableMFADevice", - "iam:ResyncMFADevice", - "iam:ListAccountAliases", - "iam:ListUsers", - "iam:ListSSHPublicKeys", - "iam:ListAccessKeys", - "iam:ListServiceSpecificCredentials", - "iam:ListMFADevices", - "iam:GetAccountSummary", - "sts:GetSessionToken" - ], - "Resource": "*", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false", - "aws:ViaAWSService": "false" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/sso-permissionSet1-inline-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/sso-permissionSet1-inline-policy.json deleted file mode 100644 index 7e38a10..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/iam-policies/sso-permissionSet1-inline-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Statement1", - "Effect": "Allow", - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "*" - ] - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/applicationEbs.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/applicationEbs.json deleted file mode 100644 index d0e6fcf..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/applicationEbs.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "Version": "2012-10-17", - "Id": "auto-ebs-2", - "Statement": [ - { - "Sid": "Allow access through EBS for all principals in the account that are authorized to use EBS", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:CreateGrant", - "kms:DescribeKey" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "kms:CallerAccount": {"Ref" :"AWS::AccountId"}, - "kms:ViaService": {"Fn::Sub":"ec2.${AWS::Region}.amazonaws.com"} - } - } - }, - { - "Sid": "Allow direct access to key metadata to the account", - "Effect": "Allow", - "Principal": { - "AWS": {"Fn::Sub":"arn:${AWS::Partition}:iam::${AWS::AccountId}:root"} - }, - "Action": [ - "kms:*" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/central-logs-bucket-key-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/central-logs-bucket-key-policy.json deleted file mode 100644 index afade72..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/central-logs-bucket-key-policy.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy statement 1 from file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/elb-logs-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/elb-logs-bucket.json deleted file mode 100644 index 9cdfb74..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/elb-logs-bucket.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/full-central-logs-bucket-key-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/full-central-logs-bucket-key-policy.json deleted file mode 100644 index 12a96e8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/full-central-logs-bucket-key-policy.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" - }, - "Action": "kms:*", - "Resource": "*" - }, - { - "Sid": "Enable IAM User Permissions", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::457936900343:root" - }, - "Action": "kms:*", - "Resource": "*" - }, - { - "Sid": "Allow S3 use of the key - From file", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateRandom", - "kms:GetKeyPolicy", - "kms:GetKeyRotationStatus", - "kms:ListAliases", - "kms:ListGrants", - "kms:ListKeyPolicies", - "kms:ListKeys", - "kms:ListResourceTags", - "kms:ListRetirableGrants", - "kms:ReEncryptFrom", - "kms:ReEncryptTo" - ], - "Resource": "*" - }, - { - "Sid": "Allow AWS Services to encrypt and describe logs - From file", - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com", - "config.amazonaws.com", - "delivery.logs.amazonaws.com", - "ssm.amazonaws.com" - ] - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo" - ], - "Resource": "*" - }, - { - "Sid": "Allow Organization use of the key - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - "kms:ListAliases" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Macie service to use the encryption key - From file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ], - "Resource": "*" - }, - { - "Sid": "Allow Guardduty service to use the encryption key - From file", - "Effect": "Allow", - "Principal": { - "Service": "guardduty.amazonaws.com" - }, - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ], - "Resource": "*" - }, - { - "Sid": "Policy statement 1 from file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Resource": "*" - }, - { - "Sid": "Policy statement 2 from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "kms:ListAliases", - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/kms-policy-01.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/kms-policy-01.json deleted file mode 100644 index ef8e3bc..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/kms/kms-policy-01.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root" - }, - "Action": "kms:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/network-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/network-config.yaml deleted file mode 100644 index 266eaf0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/network-config.yaml +++ /dev/null @@ -1,2270 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -defaultVpc: - delete: true - excludeAccounts: [] - excludeRegions: [] - -transitGatewayConnects: - - name: Network-Inspection - region: *HOME_REGION - transitGateway: - name: Network-Main - account: Network - vpc: - vpcName: Network-Endpoints - vpcAttachment: Network-Endpoints - options: - protocol: gre - tags: - - key: Environment - value: CentralInspection - -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - transitGatewayCidrBlocks: - - 10.0.0.0/20 - - 10.5.0.0/20 - transitGatewayIpv6CidrBlocks: - - 2001:db8::/64 - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Core - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: ::/0 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 10.200.0.0/16 - attachment: - directConnectGatewayName: Network-DXGW - - destinationCidrBlock: 10.30.0.0/16 - attachment: - vpnConnectionName: accelerator-vpn - - destinationCidrBlock: 1.1.1.1/32 - blackhole: true - - destinationCidrBlock: fd00::/8 - blackhole: true - - destinationPrefixList: accelerator-prefix-list - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 10.40.0.0/16 - attachment: - vpnConnectionName: same-account-tgw-vpn - - destinationCidrBlock: 10.50.0.0/16 - attachment: - vpnConnectionName: same-account-tgw-vpn - - name: Network-Main-Segregated - routes: [] - - name: Network-Main-Shared - routes: - - destinationCidrBlock: 10.200.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - destinationPrefixList: accelerator-prefix-list - attachment: - vpnConnectionName: cross-account-tgw-vpn - - name: Network-Main-Standalone - routes: - - destinationPrefixList: accelerator-prefix-list - attachment: - vpnConnectionName: same-account-tgw-vpn - - name: Network-Main-2 - account: Network - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-2-Core - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 1.1.1.1/32 - blackhole: true - - destinationPrefixList: accelerator-prefix-list - attachment: - vpcName: Network-Endpoints - account: Network - - name: Network-Main-2-Segregated - routes: [] - - name: Network-Main-2-Shared - routes: [] - - name: Network-Main-2-Standalone - routes: [] - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 64512 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: SharedServices-Main-Core - routes: [] - - name: SharedServices-Main-Segregated - routes: [] - - name: SharedServices-Main-Shared - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - name: SharedServices-Main-Standalone - routes: [] - - name: SharedServices-Main-Test01 - routes: - - destinationPrefixList: accelerator-prefix-list-001 - blackhole: true - - name: Network-Main-Us-West-2 - account: Network - region: us-west-2 - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Us-West-2-Core - routes: [] - - name: Network-Main-Us-West-2-Segregated - routes: [] - - name: Network-Main-Us-West-2-Shared - routes: - - destinationCidrBlock: 10.200.0.0/16 - # - destinationPrefixList: accelerator-pl - attachment: - transitGatewayPeeringName: Network-Main-And-Network-Main-Us-West-2-And-Peering - - name: Network-Main-Standalone - routes: [] -transitGatewayPeering: - - name: Network-Main-And-SharedServices-Main-Peering - requester: - transitGatewayName: SharedServices-Main - account: SharedServices - region: *HOME_REGION - routeTableAssociations: SharedServices-Main-Shared - tags: - - key: Name - value: Network-Main-And-SharedServices-Main-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - - key: Organization - value: WWPS - - key: Team - value: Development - accepter: - transitGatewayName: Network-Main - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-Shared - autoAccept: true - applyTags: true - - name: Network-Main-2-And-SharedServices-Main-Peering - autoAccept: false - requester: - transitGatewayName: SharedServices-Main - account: SharedServices - region: *HOME_REGION - routeTableAssociations: SharedServices-Main-Shared - tags: - - key: Name - value: Network-Main-2-And-SharedServices-Main-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - - key: Organization - value: WWPS - - key: Team - value: Development - accepter: - transitGatewayName: Network-Main-2 - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-2-Core - autoAccept: true - applyTags: true - - name: Network-Main-And-Network-Main-Us-West-2-And-Peering - requester: - transitGatewayName: Network-Main - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-Shared - tags: - - key: Name - value: Network-Main-And-Network-Main-Us-West-2-And-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - accepter: - transitGatewayName: Network-Main-Us-West-2 - account: Network - region: us-west-2 - routeTableAssociations: Network-Main-Us-West-2-Shared - autoAccept: true - applyTags: true -dhcpOptions: - - name: accelerator-dhcp-opts - accounts: - - Network - regions: - - *HOME_REGION - domainName: example.com - domainNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNodeType: 2 - ntpServers: - - 1.1.1.1 - - 2.2.2.2 - - name: test-ipv6 - accounts: - - Network - regions: - - us-east-1 - domainName: example.com - domainNameServers: - - 192.168.1.2 - - 192.168.2.2 - - 10.0.0.2 - - 10.0.1.2 - - ::1 - - ::2 - - ::3 - - ::4 - ntpServers: - - 192.168.1.2 - - 192.168.2.2 - - 10.0.0.2 - - 10.0.1.2 - - ::1 - - ::2 - - ::3 - - ::4 - netbiosNameServers: - - 192.168.1.2 - - 192.168.2.2 - - 10.0.0.2 - - 10.0.1.2 -centralNetworkServices: - delegatedAdminAccount: Network - ipams: - - name: accelerator-ipam - region: *HOME_REGION - description: Accelerator IPAM - operatingRegions: - - *HOME_REGION - - us-west-2 - pools: - - name: &BASE_POOL base-pool - description: accelerator-base - provisionedCidrs: - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - name: home-region-pool - description: Pool for us-east-1 - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/16 - - 10.2.0.0/16 - sourceIpamPool: *BASE_POOL - shareTargets: - organizationalUnits: - - Infrastructure - - name: home-region-prod-pool - description: Pool for prod environment - allocationResourceTags: - - key: env - value: prod - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/24 - - 10.2.0.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - sourceIpamPool: home-region-pool - - name: west-region-pool - description: Pool for us-west-2 - locale: us-west-2 - provisionedCidrs: - - 10.1.0.0/16 - sourceIpamPool: *BASE_POOL - shareTargets: - organizationalUnits: - - Infrastructure - gatewayLoadBalancers: - - name: Accelerator-GWLB - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - deletionProtection: true - targetGroup: instance-target - endpoints: - - name: Endpoint-A - account: Network - subnet: Network-Inspection-A - vpc: Network-Inspection - - name: Endpoint-B - account: Network - subnet: Network-Inspection-B - vpc: Network-Inspection - - name: Accelerator-GWLB-secondary - subnets: - - Network-Secondary-A - - Network-Secondary-B - vpc: Network-Secondary - deletionProtection: true - endpoints: - - name: Secondary-Endpoint-A - account: Network - subnet: Network-Secondary-A - vpc: Network-Secondary - - name: Secondary-Endpoint-B - account: Network - subnet: Network-Secondary-B - vpc: Network-Secondary - - name: SharedServices-Endpoint-B #Need to use us-east-1b in Shared Services - account: SharedServices - subnet: SharedServices-App-B - vpc: SharedServices-Main - - name: SharedServices-Endpoint-C #Need to use us-east-1c in Shared Services - account: SharedServices - subnet: SharedServices-App-C - vpc: SharedServices-Main - - name: Accelerator-GWLB-NonDelegated - subnets: - - SharedServices-App-A - - SharedServices-App-B - account: SharedServices - vpc: SharedServices-Main - deletionProtection: true - targetGroup: shared-services-target - endpoints: - - name: Shared-Endpoint-A - account: SharedServices - subnet: SharedServices-App-A - vpc: SharedServices-Main - - name: Shared-Endpoint-B - account: SharedServices - subnet: SharedServices-App-B - vpc: SharedServices-Main - - name: Shared-Secondary-Endpoint-D - account: Network - vpc: Network-Secondary - subnet: Network-Secondary-D - - name: Shared-Secondary-Endpoint-A - account: Network - vpc: Network-Secondary - subnet: Network-Secondary-A - networkFirewall: - firewalls: - - name: accelerator-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - - name: az-id-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Secondary-C - - Network-Secondary-D - vpc: Network-Secondary - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - policies: - - name: accelerator-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulRuleGroups: - - name: accelerator-rule-group - - name: domain-list-group - shareTargets: - organizationalUnits: - - Infrastructure - - name: accelerator-strict-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulEngineOptions: STRICT_ORDER - statefulRuleGroups: - - name: accelerator-strict-rule-group - priority: 100 - shareTargets: - organizationalUnits: - - Infrastructure - rules: - - name: accelerator-suricata-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-strict-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - statefulRuleOptions: STRICT_ORDER - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - statefulRules: - - action: PASS - header: - destination: 10.0.0.0/16 - destinationPort: ANY - direction: FORWARD - protocol: IP - source: 10.1.0.0/16 - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['100'] - - action: DROP - header: - destination: ANY - destinationPort: ANY - direction: ANY - protocol: IP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['101'] - - action: ALERT - header: - destination: 1.1.1.1/32 - destinationPort: '80' - direction: FORWARD - protocol: TCP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: msg - settings: ['"example message"'] - - keyword: sid - settings: ['102'] - - name: domain-list-group - regions: - - *HOME_REGION - capacity: 10 - type: STATEFUL - ruleGroup: - rulesSource: - rulesSourceList: - generatedRulesType: DENYLIST - targets: ['.example.com'] - targetTypes: ['TLS_SNI', 'HTTP_HOST'] - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - route53Resolver: - endpoints: - - name: accelerator-inbound - type: INBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - - name: accelerator-outbound - type: OUTBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - rules: - - name: example-rule - domainName: example.com - targetIps: - - ip: 1.1.1.1 - port: '5353' # only include if targeting a non-standard DNS port - - ip: 2.2.2.2 - shareTargets: - organizationalUnits: - - Infrastructure - - name: inbound-target-rule - domainName: aws.internal.domain - inboundEndpointTarget: accelerator-inbound # This endpoint must be listed in the configuration before the outbound endpoint - queryLogs: - name: accelerator-query-logs - destinations: - - s3 - - cloud-watch-logs - shareTargets: - organizationalUnits: - - Infrastructure - firewallRuleGroups: - - name: accelerator-block-group - regions: - - *HOME_REGION - rules: - - name: nxdomain-block-rule - action: BLOCK - customDomainList: dns-firewall-domain-lists/domain-list-1.txt - priority: 100 - blockResponse: NXDOMAIN - # - name: override-block-rule - # action: BLOCK - # customDomainList: dns-firewall-domain-lists/domain-list-2.txt - # priority: 200 - # blockResponse: OVERRIDE - # blockOverrideDomain: amazon.com - # blockOverrideTtl: 3600 - - name: managed-rule - action: BLOCK - managedDomainList: AWSManagedDomainsBotnetCommandandControl - priority: 300 - blockResponse: NODATA - shareTargets: - organizationalUnits: - - Infrastructure -prefixLists: - - name: accelerator-prefix-list - accounts: - - Network - - SharedServices - regions: - - *HOME_REGION - addressFamily: 'IPv4' - maxEntries: 1 - entries: - - 10.1.0.1/32 - - name: accelerator-prefix-list-001 - accounts: - - SharedServices - regions: - - *HOME_REGION - addressFamily: 'IPv4' - maxEntries: 1 - entries: - - 10.100.0.1/32 - - name: accelerator-prefix-list-ipv6 - deploymentTargets: - organizationalUnits: - - Infrastructure - addressFamily: IPv6 - maxEntries: 10 - entries: - - 2400:cb00::/32 - - 2606:4700::/32 - - 2803:f800::/32 - - 2405:b500::/32 - - 2405:8100::/32 - - 2a06:98c0::/29 - - 2c0f:f248::/32 - -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json - -vpcs: - - name: SharedServices-Ipam-East - account: SharedServices - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: SharedServices-A-Rt - routes: [] - - name: SharedServices-B-Rt - routes: [] - subnets: - - name: SharedServices-A - availabilityZone: a - routeTable: SharedServices-A-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - - name: SharedServices-B - availabilityZone: b - routeTable: SharedServices-B-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - - - name: SharedServices-Ipam-West - account: SharedServices - region: us-west-2 - ipamAllocations: - - ipamPoolName: west-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: SharedServices-A-Rt - routes: [] - - name: SharedServices-B-Rt - routes: [] - subnets: - - name: SharedServices-A - availabilityZone: a - routeTable: SharedServices-A-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - - name: SharedServices-B - availabilityZone: b - routeTable: SharedServices-B-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - - - name: Network-Ipam-West - account: Network - region: us-west-2 - ipamAllocations: - - ipamPoolName: west-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: Network-West-A-Rt - routes: [] - - name: Network-West-B-Rt - routes: [] - subnets: - - name: Network-West-A - availabilityZone: a - routeTable: Network-West-A-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - - name: Network-West-B - availabilityZone: b - routeTable: Network-West-B-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - - - name: Network-Secondary - account: Network - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - ipv6Cidrs: - - amazonProvided: true - egressOnlyIgw: true - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - vpcRoute53Resolver: - endpoints: - - name: inbound-endpoint-local - type: INBOUND - vpc: Network-Secondary - subnets: - - Network-Secondary-A - - Network-Secondary-B - queryLogs: - name: network-local-endpoint-query-logs - destinations: - - s3 - - cloud-watch-logs - routeTables: - - name: Network-Secondary-A-Rt - routes: - - name: GwlbRoute - destination: 10.0.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Secondary-Endpoint-A - - name: GwlbRoutev6 - ipv6Destination: ::/0 - type: gatewayLoadBalancerEndpoint - target: Shared-Secondary-Endpoint-A - - name: Network-Secondary-B-Rt - routes: - - name: eigwRoute - ipv6Destination: ::/0 - type: egressOnlyIgw - - name: Network-Secondary-C-Rt - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: az-id-firewall - targetAvailabilityZone: 1 - - name: NfwRoutev6 - ipv6Destination: ::/0 - type: networkFirewall - target: az-id-firewall - targetAvailabilityZone: 1 - - name: Network-Secondary-D-Rt - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: az-id-firewall - targetAvailabilityZone: 2 - - name: GwlbRoute - destination: 10.0.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Secondary-Endpoint-D - subnets: - - name: Network-Secondary-A - availabilityZone: a - routeTable: Network-Secondary-A-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-B - availabilityZone: b - routeTable: Network-Secondary-B-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-C - availabilityZone: 1 - routeTable: Network-Secondary-C-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-D - availabilityZone: 2 - routeTable: Network-Secondary-D-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-Dual-Stack - availabilityZone: a - routeTable: Network-Secondary-A-Rt - ipv4CidrBlock: 10.0.0.192/28 - ipv6CidrBlock: 2600:1f18:1305:b700::/64 - - name: Network-Secondary-Ipv6-Only - availabilityZone: b - routeTable: Network-Secondary-B-Rt - ipv6CidrBlock: 2600:1f18:1305:b701::/64 - networkAcls: - - name: TestNacl - subnetAssociations: - - Network-Secondary-A - - Network-Secondary-B - - Network-Secondary-C - - Network-Secondary-D - outboundRules: - - rule: 101 - action: allow - fromPort: 80 - toPort: 80 - protocol: -1 - destination: 172.16.0.0/12 - - rule: 102 - action: allow - fromPort: 443 - toPort: 443 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - region: us-east-1 - - rule: 103 - action: allow - fromPort: 80 - toPort: 80 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 104 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 105 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: SharedServices - vpc: SharedServices-Ipam-East - subnet: SharedServices-A - region: us-east-1 - - rule: 106 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: SharedServices - vpc: SharedServices-Ipam-West - subnet: SharedServices-A - region: us-west-2 - - rule: 107 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: SharedServices - vpc: SharedServices-Main - subnet: SharedServices-App-A - region: us-east-1 - - rule: 108 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - destination: ::/0 - inboundRules: - - rule: 101 - action: allow - fromPort: 22 - toPort: 22 - protocol: 22 - source: 10.0.0.0/24 - - rule: 102 - action: deny - fromPort: 443 - toPort: 443 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 103 - action: allow - fromPort: 80 - toPort: 80 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - region: us-east-1 - - rule: 104 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 105 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: SharedServices - vpc: SharedServices-Ipam-East - subnet: SharedServices-A - region: us-east-1 - - rule: 106 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: SharedServices - vpc: SharedServices-Ipam-West - subnet: SharedServices-A - region: us-west-2 - - rule: 107 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: SharedServices - vpc: SharedServices-Main - subnet: SharedServices-App-A - region: us-east-1 - - rule: 108 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - source: ::/0 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - tags: - - key: env - value: prod - - - name: Network-Endpoints - account: Network - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - internetGateway: true - dhcpOptions: accelerator-dhcp-opts - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - dnsFirewallRuleGroups: - - name: accelerator-block-group - priority: 101 - queryLogs: - - accelerator-query-logs - resolverRules: - - example-rule - routeTables: - - name: Network-Endpoints-Tgw-A - routes: [] - - name: Network-Endpoints-Tgw-B - routes: [] - - name: Network-Endpoints-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: TgwRoutev6 - ipv6Destination: ::/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - - name: PeeringRouteToWorkloadTemplate - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - - name: Network-Endpoints-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - - name: PeeringRouteToWorkloadTemplate - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - subnets: - - name: Network-Endpoints-A - availabilityZone: a - routeTable: Network-Endpoints-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - shareTargets: - organizationalUnits: - - Infrastructure - accounts: - - SharedServices - tags: - - key: exampleKey - value: exampleValue - - name: Network-Endpoints-B - availabilityZone: b - routeTable: Network-Endpoints-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - - name: Network-EndpointsTgwAttach-A - availabilityZone: a - routeTable: Network-Endpoints-Tgw-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-EndpointsTgwAttach-B - availabilityZone: b - routeTable: Network-Endpoints-Tgw-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - transitGatewayAttachments: - - name: Network-Endpoints - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - name: Network-Endpoints-2 - transitGateway: - name: Network-Main-2 - account: Network - routeTableAssociations: - - Network-Main-2-Shared - routeTablePropagations: - - Network-Main-2-Core - - Network-Main-2-Shared - - Network-Main-2-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - virtualPrivateGateway: - asn: 65200 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - interfaceEndpoints: - central: true - tags: - - key: Environment - value: CentralVpc - defaultPolicy: Default - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: kms - - service: logs - - service: s3 - - service: ecr.dkr - - service: s3-global.accesspoint - - service: codeartifact.repositories - - service: codeartifact.api - - service: eks - applyPolicy: false - - service: secretsmanager - securityGroup: Network-Endpoints-CustomEndpointSg - - service: access-analyzer - securityGroup: Network-Endpoints-CustomEndpointSg - # - service: cloudformation - # - service: application-autoscaling - # - service: appmesh-envoy-management - # - service: athena - # - service: autoscaling - # - service: autoscaling-plans - # - service: clouddirectory - # - service: cloudtrail - # - service: codebuild - # - service: codecommit - # - service: codepipeline - # - service: config - # - service: datasync - # - service: ecs - # - service: ecs-agent - # - service: ecs-telemetry - # - service: elasticfilesystem - # - service: elasticloadbalancing - # - service: elasticmapreduce - # - service: events - # - service: execute-api - # - service: git-codecommit - # - service: glue - # - service: kinesis-streams - # - service: kms - # - service: logs - # - service: monitoring - # - service: sagemaker.api - # - service: sagemaker.runtime - # - service: servicecatalog - # - service: sms - # - service: sns - # - service: sqs - # - service: storagegateway - # - service: sts - # - service: transfer - # - service: workspaces - # - service: awsconnector - # - service: kinesis-firehose - # - service: states - # - service: acm-pca - # - service: cassandra - # - service: ebs - # - service: elasticbeanstalk - # - service: elasticbeanstalk-health - # - service: email-smtp - # - service: license-manager - # - service: macie2 - # - service: notebook - # - service: synthetics - # - service: transfer.server - securityGroups: - - name: 'Management' - description: 'Management Security Group' - inboundRules: - - description: 'Management RDP Traffic Inbound' - types: - - RDP - sources: - - '10.0.0.0/8' - - '100.96.252.0/23' - - '100.96.250.0/23' - - fd00::/8 - - account: 'Network' - vpc: 'Network-Endpoints' - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'Management SSH Traffic Inbound' - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'IP Protocol Rule' - ipProtocols: - - ESP - - IDRP - - ST - sources: - - 100.96.252.0/23 - outboundRules: - - description: 'All Outbound' - types: - - ALL - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'Limited Outbound' - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - - description: 'IP Protocol Rule' - ipProtocols: - - ESP - - IDRP - - ST - sources: - - 10.0.0.0/8 - - name: Network-Endpoints-CustomEndpointSg - description: Accelerator security group for custom endpoint - inboundRules: - - description: Allow access to 443 - types: - - HTTPS - sources: - - 0.0.0.0/0 - - outboundRules: - - description: Allow all outbound - types: - - ALL - sources: - - 0.0.0.0/0 - networkAcls: - - name: TestNACL - subnetAssociations: - - Network-Endpoints-A - inboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - source: 10.0.0.0/8 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - outboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: 0.0.0.0/0 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - # loadBalancers: - # applicationLoadBalancers: - # - name: appA-alb-01 - # scheme: internet-facing - # subnets: - # - 'Network-EndpointsTgwAttach-A' - # - 'Network-EndpointsTgwAttach-B' - # securityGroups: - # - Management - # listeners: - # - name: appA-listener-2 - # port: 80 - # protocol: HTTP - # targetGroup: appA-alb-tg-1 - # type: forward - # networkLoadBalancers: - # - name: appA-nlb-01 - # scheme: internet-facing - # deletionProtection: false - # subnets: - # - 'Network-EndpointsTgwAttach-A' - # - 'Network-EndpointsTgwAttach-B' - # listeners: - # - name: appA-listener-1 - # port: 80 - # protocol: TCP - # targetGroup: appA-nlb-tg-1 - # - name: appA-listener-2 - # port: 80 - # protocol: TCP - # targetGroup: appA-nlb-tg-2 - # targetGroups: - # - name: appA-nlb-tg-1 - # port: 80 - # protocol: TCP - # type: instance - # connectionTermination: true - # preserveClientIp: true - # proxyProtocolV2: true - # healthCheck: - # enabled: true - # port: 80 - # protocol: TCP - # - name: appA-alb-tg-1 - # port: 80 - # protocol: HTTP - # type: instance - # connectionTermination: true - # preserveClientIp: true - # proxyProtocolV2: true - # healthCheck: - # enabled: true - # port: 80 - # protocol: HTTP - # - name: appA-nlb-tg-2 - # port: 80 - # protocol: TCP - # type: ip - # targets: - # - account: Network - # region: *HOME_REGION - # nlbName: appA-nlb-01 - # connectionTermination: true - # preserveClientIp: true - # proxyProtocolV2: true - # healthCheck: - # enabled: true - # port: 80 - # protocol: TCP - tags: - - key: env - value: prod - - - name: Network-Inspection - account: Network - region: *HOME_REGION - cidrs: - - 10.2.0.0/22 - - 10.3.0.0/16 - defaultSecurityGroupRulesDeletion: true - internetGateway: true - natGateways: - - name: accelerator-nat-gw-a - subnet: Network-Inspection-A - # allocationId: eipalloc-acbdefg123456 # This is a placeholder value and should not be used in a test config - - name: accelerator-nat-gw-b - subnet: Network-Inspection-B - - name: accelerator-nat-gw-c - subnet: Network-Inspection-B - private: true - routeTables: - - name: Network-Inspection-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-Tgw-A - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: GwlbRoute - destination: 10.0.0.0/8 - type: gatewayLoadBalancerEndpoint - target: Endpoint-A - - name: NetworkInterfaceRoute - destination: 10.0.0.0/26 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_1:accelerator-firewall} - - name: NetworkInterfaceRoutev6 - ipv6Destination: fd00::/8 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_1:accelerator-firewall} - - name: Network-Inspection-Tgw-B - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: GwlbRoute - destination: 10.0.0.0/8 - type: gatewayLoadBalancerEndpoint - target: Endpoint-B - - name: Network-Inspection-Gateway - gatewayAssociation: internetGateway - routes: - - name: NfwRouteDynamic-A - destination: Network-Inspection-A - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: GwlbRouteDynamic-A - destination: Network-InspectionTgwAttach-A - type: gatewayLoadBalancerEndpoint - target: Endpoint-A - - name: NfwRouteDynamic-B - destination: Network-Inspection-B - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: GwlbRouteDynamic-B - destination: Network-InspectionTgwAttach-B - type: gatewayLoadBalancerEndpoint - target: Endpoint-B - subnets: - - name: Network-Inspection-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.0.0/24 - - name: Network-Inspection-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.1.0/24 - - name: Network-InspectionTgwAttach-A - availabilityZone: a - routeTable: Network-Inspection-Tgw-A - ipv4CidrBlock: 10.2.3.208/28 - - name: Network-InspectionTgwAttach-B - availabilityZone: b - routeTable: Network-Inspection-Tgw-B - ipv4CidrBlock: 10.2.3.224/28 - securityGroups: - - name: Data - description: Firewall data - inboundRules: - - description: GENEVE - sources: - - 10.2.0.0/22 - udpPorts: - - 6081 - outboundRules: - - description: All outbound - types: - - ALL - sources: - - 0.0.0.0/0 - transitGatewayAttachments: - - name: Network-Inspection - transitGateway: - name: Network-Main - account: Network - options: - applianceModeSupport: enable - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-InspectionTgwAttach-A - - Network-InspectionTgwAttach-B - virtualPrivateGateway: - asn: 65000 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.4.0.0/16 - internetGateway: true - routeTables: - - name: SharedServices-Main-Default - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: SharedServices-Tgw-A - routes: [] - - name: SharedServices-Tgw-B - routes: [] - - name: SharedServices-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - - name: CrossAcctNetworkInterfaceRoute - destination: 10.0.0.0/28 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_0:firewall-with-shared-subnet} - - name: CrossAcctNetworkInterfaceRoutev6 - ipv6Destination: ff06::/64 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_0:firewall-with-shared-subnet} - - name: GwlbRoute - destination: 10.1.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Endpoint-A - - name: SharedServices-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - - name: GwlbRoute - destination: 10.1.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Endpoint-B - - name: SharedServices-App-C - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - subnets: - - name: SharedServices-App-A - availabilityZone: a - routeTable: SharedServices-Main-Default - ipv4CidrBlock: 10.4.0.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - tags: - - key: Name - value: SharedServices-App-A - - name: SharedServices-App-B - availabilityZone: b - routeTable: SharedServices-App-B - ipv4CidrBlock: 10.4.1.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - - name: SharedServices-App-C - availabilityZone: c - routeTable: SharedServices-App-C - ipv4CidrBlock: 10.4.2.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - - name: SharedServices-MainTgwAttach-A - availabilityZone: a - routeTable: SharedServices-Tgw-A - ipv4CidrBlock: 10.4.255.208/28 - - name: SharedServices-MainTgwAttach-B - availabilityZone: b - routeTable: SharedServices-Tgw-B - ipv4CidrBlock: 10.4.255.224/28 - loadBalancers: - applicationLoadBalancers: - - name: appA-alb-test-01 - scheme: internal - subnets: - - SharedServices-App-A - - SharedServices-App-B - securityGroups: - - InternalHTTP-SG - listeners: - - name: appA-listener-3 - port: 443 - protocol: HTTPS - targetGroup: deployed-tg-2 - order: 3 - type: forward - certificate: cert1 - sslPolicy: ELBSecurityPolicy-2016-08 - shareTargets: - organizationalUnits: - - Infrastructure - targetGroups: - - name: deployed-tg-2 - port: 443 - protocol: HTTPS - type: instance - shareTargets: - organizationalUnits: - - Infrastructure - - name: tg-with-target-ip - port: 80 - protocol: HTTP - protocolVersion: HTTP1 - type: ip - healthCheck: - enabled: true - port: 80 - protocol: HTTP - targets: - - '10.4.0.20' - - name: tg-without-target-ip - port: 80 - protocol: HTTP - protocolVersion: HTTP1 - type: ip - healthCheck: - enabled: true - port: 80 - protocol: HTTP - transitGatewayAttachments: - - name: SharedServices-Main - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - SharedServices-MainTgwAttach-A - - SharedServices-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - virtualPrivateGateway: - asn: 65002 - useCentralEndpoints: true - securityGroups: - - name: SharedServices-Main-Rsyslog-sg - description: Security Group for AWS Accelerator rsyslog - inboundRules: - - description: allow inbound traffic - udpPorts: - - 514 - tcpPorts: - - 514 - sources: - - 10.0.0.0/8 - outboundRules: - - description: All Outbound - types: - - ALL - sources: - - 0.0.0.0/0 - - name: InternalHTTP-SG - description: Accelerator Security Group - inboundRules: - - description: HTTP access security group rule - types: - - HTTP - sources: - - 10.0.0.0/16 - outboundRules: - - description: Allow all outbound - types: - - ALL - sources: - - 0.0.0.0/0 - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - - # vpc for application workloads - - name: SharedServices-appVpc - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.1.0.0/16 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: PrivRtA - routes: - - name: NatRoute - destination: 0.0.0.0/0 - type: natGateway - target: NatGwA - - name: PrivRtB - routes: - - name: NatRoute - destination: 0.0.0.0/0 - type: natGateway - target: NatGwB - - name: PublicA - routes: - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: PublicB - routes: - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - subnets: - - name: PublicA - availabilityZone: a - routeTable: PublicA - ipv4CidrBlock: 10.1.0.0/24 - - name: PublicB - availabilityZone: b - routeTable: PublicB - ipv4CidrBlock: 10.1.1.0/24 - - name: PrivateA - availabilityZone: a - routeTable: PrivRtA - ipv4CidrBlock: 10.1.2.0/24 - - name: PrivateB - availabilityZone: b - routeTable: PrivRtB - ipv4CidrBlock: 10.1.3.0/24 - natGateways: - - name: NatGwA - subnet: PublicA - - name: NatGwB - subnet: PublicB - securityGroups: - - name: appSecurityGroup - description: Accelerator security group - inboundRules: - - description: Self access security group - types: - - ALL - sources: - - securityGroups: - - appSecurityGroup - - outboundRules: - - description: Allow all outbound - types: - - ALL - sources: - - 0.0.0.0/0 - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - cloud-watch-logs - destinationsConfig: - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -vpcTemplates: - - name: Workload-Template - region: *HOME_REGION - deploymentTargets: - organizationalUnits: - - Infrastructure - ipamAllocations: - - ipamPoolName: home-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: Workload-A-Rt - routes: - - name: PeeringRouteToNetworkEndpoints - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - - name: Workload-B-Rt - routes: - - name: PeeringRouteToNetworkEndpoints - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - subnets: - - name: Workload-A - availabilityZone: a - routeTable: Workload-A-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - - name: Workload-B - availabilityZone: b - routeTable: Workload-B-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - -directConnectGateways: - - name: Network-DXGW - account: Network - asn: 65000 - gatewayName: Network-DXGW - # virtualInterfaces: - # - name: Accelrator-VIF - # connectionId: dxcon-test1234 - # customerAsn: 65002 - # interfaceName: Accelrator-VIF - # ownerAccount: Network - # region: us-east-1 - # type: transit - # vlan: 575 - # enableSiteLink: true - # jumboFrames: true - transitGatewayAssociations: - - name: Network-Main - account: Network - allowedPrefixes: - - 10.0.0.0/8 - - 192.168.0.0/16 - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - -vpcPeering: - - name: NetworkEndpointsToSecondary - vpcs: - - Network-Endpoints - - Network-Secondary - - name: CrossAccount - vpcs: - - Network-Endpoints - - SharedServices-Main - - name: WorkloadTemplateToNetworkEndpoints - vpcs: - - Workload-Template - - Network-Endpoints - -customerGateways: - - name: accelerator-cgw - account: Network - region: *HOME_REGION - ipAddress: 1.1.1.1 - asn: 65500 - vpnConnections: - - name: accelerator-vpn - transitGateway: Network-Main - staticRoutesOnly: false - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - tunnelSpecifications: - - tunnelInsideCidr: 169.254.200.0/30 - - tunnelInsideCidr: 169.254.200.100/30 - - - name: VpcInspectionVpnConnection - vpc: Network-Inspection - staticRoutesOnly: false - tunnelSpecifications: - - tunnelInsideCidr: 169.254.100.0/30 - - tunnelInsideCidr: 169.254.100.100/30 - - - name: enhanced-vpn - amazonIpv4NetworkCidr: 10.0.0.0/16 - customerIpv4NetworkCidr: 192.168.0.0/16 - enableVpnAcceleration: true - transitGateway: Network-Main - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - staticRoutesOnly: false - tunnelSpecifications: - - tunnelInsideCidr: 169.254.200.0/30 - dpdTimeoutAction: restart - dpdTimeoutSeconds: 60 - ikeVersions: [2] - phase1: - dhGroups: [2, 14, 20, 21, 22] - encryptionAlgorithms: [AES256, AES256-GCM-16] - integrityAlgorithms: [SHA2-256, SHA2-512] - lifetimeSeconds: 3600 - phase2: - dhGroups: [5, 20] - encryptionAlgorithms: [AES128] - integrityAlgorithms: [SHA2-256] - lifetimeSeconds: 900 - rekeyFuzzPercentage: 80 - rekeyMarginTimeSeconds: 350 - replayWindowSize: 64 - startupAction: start - tunnelLifecycleControl: false - - tunnelInsideCidr: 169.254.200.100/30 - dpdTimeoutAction: clear - dpdTimeoutSeconds: 120 - ikeVersions: [2] - phase1: - dhGroups: [2, 14, 20, 21, 22] - encryptionAlgorithms: [AES256, AES256-GCM-16] - integrityAlgorithms: [SHA2-256, SHA2-512] - lifetimeSeconds: 3600 - phase2: - dhGroups: [5, 20] - encryptionAlgorithms: [AES128] - integrityAlgorithms: [SHA2-256] - lifetimeSeconds: 900 - rekeyFuzzPercentage: 65 - rekeyMarginTimeSeconds: 120 - replayWindowSize: 64 - - name: cross-account-cgw - account: Network - region: *HOME_REGION - ipAddress: ${ACCEL_LOOKUP::EC2:ENI_0:cross-account-firewall} - asn: 65500 - vpnConnections: - - name: cross-account-tgw-vpn - transitGateway: Network-Main - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - name: cross-account-vgw-vpn - vpc: Network-Inspection - - name: same-account-firewall-cgw - account: Network - region: *HOME_REGION - ipAddress: ${ACCEL_LOOKUP::EC2:ENI_1:accelerator-firewall} - asn: 65500 - vpnConnections: - - name: same-account-tgw-vpn - transitGateway: Network-Main - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - name: same-account-vgw-vpn - vpc: Network-Inspection - -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -certificates: - - name: cert1 - type: import - privKey: cert1/privKey.key - cert: cert1/cert.crt - deploymentTargets: - organizationalUnits: - - Infrastructure - accounts: - - SharedServices - - name: cert2 - type: import - privKey: cert1/privKey.key - cert: cert1/cert.crt - deploymentTargets: - accounts: - - Management - - Audit - - name: cert3 - type: request - validation: DNS - domain: example.com - san: - - www.example.com - - www.example.net - - e.co - deploymentTargets: - organizationalUnits: - - Security - - name: cert4 - type: import - privKey: cert4/privKey.key - cert: cert4/cert.crt - deploymentTargets: - organizationalUnits: - - Infrastructure - -firewallManager: - delegatedAdminAccount: Audit diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/organization-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/organization-config.yaml deleted file mode 100644 index bcd29b9..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/organization-config.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure - - name: GovCloud - - name: SecureWorkloads - - name: Suspended - ignore: true - - name: Level1/Level2-02/Level3-01/Level4-01 - - name: Level1/Level2-02/Level3-01/Level4-01/Level5-01 - - name: Level1/Level2-02/Level3-01/Level4-01/Level5-02 - - name: Level1/Level2-02/Level3-01/Level4-01/Level5-03 - - name: Devops - - name: Level1/Level2-01 - - name: Level1 - - name: Level1/Level2-02 - - name: Level1/Level2-02/Level3-01 -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - Security - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - - name: AllowList - description: > - This SCP uses "allow-list" strategy. - strategy: allow-list - policy: service-control-policies/allow-ec2-only.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - SecureWorkloads -taggingPolicies: - - name: TagPolicy - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root - - name: TagPolicy01 - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/org-backup-policies.json - deploymentTargets: - organizationalUnits: - - Root - - name: BackupPolicy01 - description: Organization Backup Policy - policy: backup-policies/org-backup-policies.json - deploymentTargets: - organizationalUnits: - - Root -organizationalUnitIds: - - name: Root - id: r-asdf - arn: arn:aws:organizations::111111111111:root/o-asdf123456/r-asdf - - name: Security - id: ou-asdf-11111111 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-11111111 - - name: Infrastructure - id: ou-asdf-22222222 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222 - - name: SecureWorkloads - id: ou-asdf-33333333 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-33333333 diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/replacements-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/replacements-config.yaml deleted file mode 100644 index 58093e6..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/replacements-config.yaml +++ /dev/null @@ -1,16 +0,0 @@ -globalReplacements: - - key: ALLOWED_CORPORATE_CIDRS - type: StringList - value: - - 10.0.1.0/24 - - 10.0.2.0/24 - - key: ALLOWED_PRINCIPAL_ARNS - type: StringList - value: - - arn:aws:iam::*:role/cdk-accel-* - - arn:aws:iam::*:role/AWSA* - - arn:aws:iam::*:role/OrganizationAccountAccessRole - - key: ALLOWED_EXTERNAL_ACCOUNTS - type: StringList - value: - - '123456789012' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/apigateway.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/apigateway.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/apigateway.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/backup-vault.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/backup-vault.json deleted file mode 100644 index 62c3ead..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/backup-vault.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": [ - "backup:DescribeBackupVault", - "backup:DeleteBackupVault", - "backup:PutBackupVaultAccessPolicy", - "backup:DeleteBackupVaultAccessPolicy", - "backup:GetBackupVaultAccessPolicy", - "backup:StartBackupJob", - "backup:GetBackupVaultNotifications", - "backup:PutBackupVaultNotifications", - "backup:DeleteBackupVaultNotifications", - "backup:ListRecoveryPointsByBackupVault" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": [ - "backup:DescribeBackupVault", - "backup:DeleteBackupVault", - "backup:PutBackupVaultAccessPolicy", - "backup:DeleteBackupVaultAccessPolicy", - "backup:GetBackupVaultAccessPolicy", - "backup:StartBackupJob", - "backup:GetBackupVaultNotifications", - "backup:PutBackupVaultNotifications", - "backup:DeleteBackupVaultNotifications", - "backup:ListRecoveryPointsByBackupVault" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/codeartifact-repository.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/codeartifact-repository.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/codeartifact-repository.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/ecr.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/ecr.json deleted file mode 100644 index 171ae26..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/ecr.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/efs-file-system.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/efs-file-system.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/efs-file-system.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/eventbridge-eventbus.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/eventbridge-eventbus.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/eventbridge-eventbus.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/glue-catalog.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/glue-catalog.json deleted file mode 100644 index a0c2d77..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/glue-catalog.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "arn:aws:glue:us-east-1:${ACCOUNT_ID}:*", - "Action": "glue:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "arn:aws:glue:us-east-1:${ACCOUNT_ID}:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/iam.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/iam.json deleted file mode 100644 index 114767d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/iam.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Action": ["sts:AssumeRole", "sts:AssumeRoleWithWebIdentity"], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": [ - "sts:AssumeRole", - "sts:AssumeRoleWithSAML", - "sts:AssumeRoleWithWebIdentity" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/kms.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/kms.json deleted file mode 100644 index 7434841..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/kms.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Resource": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Resource": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/lex-bot.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/lex-bot.json deleted file mode 100644 index 43c77f7..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/lex-bot.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/opensearch.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/opensearch.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/opensearch.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/s3.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/s3.json deleted file mode 100644 index 76e840b..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/s3.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": "*", - "Effect": "Deny", - "Action": "s3:GetObject", - "Resource": ["${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}/*"], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": ["${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}/*"], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/secrets-manager.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/secrets-manager.json deleted file mode 100644 index c5d2c24..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/secrets-manager.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { "AWS": "*" }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { "aws:PrincipalIsAWSService": "false" } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { "AWS": "*" }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sns.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sns.json deleted file mode 100644 index 2cb0f78..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sns.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": [ - "SNS:Publish", - "SNS:RemovePermission", - "SNS:SetTopicAttributes", - "SNS:DeleteTopic", - "SNS:ListSubscriptionsByTopic", - "SNS:GetTopicAttributes", - "SNS:AddPermission", - "SNS:Subscribe" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": [ - "SNS:Publish", - "SNS:RemovePermission", - "SNS:SetTopicAttributes", - "SNS:DeleteTopic", - "SNS:ListSubscriptionsByTopic", - "SNS:GetTopicAttributes", - "SNS:AddPermission", - "SNS:Subscribe" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sqs.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sqs.json deleted file mode 100644 index 455f94b..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/resource-policies/sqs.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "SQS:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "SQS:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/security-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/security-config.yaml deleted file mode 100644 index 05c1dc4..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/security-config.yaml +++ /dev/null @@ -1,1047 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -snsSubscriptionConfig: - - level: High - email: foo@example.com -# keyManagementService: -# keySets: -# - name: key1 -# alias: accelerator/test-key/key1 -# policy: kms/kms-policy-01.json -# description: Test KMS Key -# enableKeyRotation: true -# enabled: true -# removalPolicy: destroy -# deploymentTargets: -# organizationalUnits: -# - Root -# - Infrastructure -keyManagementService: - keySets: - - name: appEbsKey - alias: accelerator/custom-key/appEbsKey - policy: kms/applicationEbs.json - description: KMS Key for EBS volume encryption of applications - enableKeyRotation: true - enabled: true - removalPolicy: destroy - deploymentTargets: - accounts: - - SharedServices - # only deploy in region where application workloads are present - excludedRegions: - - us-west-2 -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: true - #kmsKey: key1 - deploymentTargets: - organizationalUnits: - - Root - excludedRegions: - - us-west-2 - s3PublicAccessBlock: - enable: true - excludeAccounts: - - Management - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: true - excludeRegions: - - us-west-2 - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: true - deploymentTargets: - accounts: - - Management - - Network - excludedRegions: - - us-west-2 - autoEnableOrgMembers: false - s3Protection: - enable: true - excludeRegions: - - us-west-2 - eksProtection: - enable: true - exportConfiguration: - enable: true - overrideExisting: true - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - auditManager: - enable: true - excludeRegions: - - us-west-2 - defaultReportsConfiguration: - enable: true - destinationType: S3 - detective: - enable: true - excludeRegions: - - us-west-2 - securityHub: - enable: true - regionAggregation: true - snsTopicName: Security - notificationLevel: HIGH - logging: - cloudWatch: - enable: true - logLevel: MEDIUM - deploymentTargets: - organizationalUnits: - - Root - standards: - - name: AWS Foundational Security Best Practices v1.0.0 - enable: true - controlsToDisable: - - IAM.1 - - EC2.10 - - Lambda.4 - - name: PCI DSS v3.2.1 - enable: true - controlsToDisable: - - PCI.IAM.3 - - PCI.S3.3 - - PCI.EC2.3 - - PCI.Lambda.2 - - name: CIS AWS Foundations Benchmark v1.2.0 - enable: true - controlsToDisable: - - CIS.1.20 - - CIS.1.22 - - CIS.2.6 - - name: CIS AWS Foundations Benchmark v1.4.0 - enable: true - controlsToDisable: - - '1.17' - - '1.16' - - name: CIS AWS Foundations Benchmark v3.0.0 - enable: true - ssmAutomation: - documentSets: - - shareTargets: - organizationalUnits: - - Root - documents: - # Calls the AWS CLI to enable access logs on a specified ELB - - name: SSM-ELB-Enable-Logging - template: ssm-documents/ssm-elb-enable-logging.yaml - # Enables S3 encryption using KMS - - name: Put-S3-Encryption - template: ssm-documents/s3-encryption.yaml - # Attaches instance profiles to an EC2 instance - - name: Attach-IAM-Instance-Profile - template: ssm-documents/attach-iam-instance-profile.yaml - targetType: /AWS::EC2::Instance - # Attaches Aws IAM Managed Policy to IAM Role - - name: Attach-IAM-Role-Policy - template: ssm-documents/attach-iam-role-policy.yaml - # Turns on CloudWatch logging for WAF ACLs - - name: WAF-Enable-Logging - template: ssm-documents/waf-enable-logging.yaml - snsSubscriptions: - - level: High - email: notify-high@example.com - - level: Medium - email: notify-medium@example.com - - level: Low - email: notify-low@example.com - -accessAnalyzer: - enable: true -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - deploymentTargets: - organizationalUnits: - - Root - ruleSets: - - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 - rules: - - name: accelerator-s3-bucket-server-side-encryption-enabled - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - complianceResourceTypes: - - AWS::S3::Bucket - remediation: - rolePolicyFile: custom-config-rules/bucket-sse-enabled-remediation-role.json - automatic: true - targetId: Put-S3-Encryption - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: KMSMasterKey - value: ${ACCEL_LOOKUP::KMS} - type: StringList - - deploymentTargets: - organizationalUnits: - - Root - rules: - - name: accelerator-waf-logging-enabled - type: Custom - description: Custom rule for checking WAF logging enabled - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/waf-logging-enabled.zip - handler: index.handler - runtime: python3.12 - rolePolicyFile: custom-config-rules/waf-logging-enabled-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::WAF::WebACL - - AWS::WAFRegional::WebACL - - AWS::WAFv2::WebACL - remediation: - rolePolicyFile: custom-config-rules/waf-logging-enabled-remediation-role.json - automatic: true - targetId: WAF-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: WebACLId - value: RESOURCE_ID - type: String - - name: accelerator-target-document-01 - type: Custom - description: Custom rule for testing target document remediation - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::EC2::Instance - remediation: - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - automatic: true - targetId: Attach-IAM-Instance-Profile - targetDocumentLambda: - sourceFilePath: custom-config-rules/targetDocumentLambda.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: InstanceId - value: RESOURCE_ID - type: String - - name: IamInstanceProfile - value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} - type: StringList - tags: - - key: key - value: value - - name: accelerator-attach-ec2-instance-profile - type: Custom - description: Custom rule for checking EC2 instance IAM profile attachment - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::EC2::Instance - remediation: - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - automatic: true - targetId: Attach-IAM-Instance-Profile - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: InstanceId - value: RESOURCE_ID - type: String - - name: IamInstanceProfile - value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} - type: StringList - - name: accelerator-ec2-instance-profile-permission - type: Custom - description: Custom role to remediate EC2 instance profile permission - inputParameters: - AWSManagedPolicies: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - # CustomerManagedPolicies: ${ACCEL_LOOKUP::CustomerManagedPolicy:},${ACCEL_LOOKUP::CustomerManagedPolicy:} - ResourceId: RESOURCE_ID - customRule: - lambda: - sourceFilePath: custom-config-rules/ec2-instance-profile-permissions.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::IAM::Role - remediation: - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-remediation-role.json - automatic: true - targetId: Attach-IAM-Role-Policy - targetAccountName: Audit - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: ResourceId - value: RESOURCE_ID - type: String - - name: AWSManagedPolicies - value: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - type: StringList - # - name: CustomerManagedPolicies - # value: ${ACCEL_LOOKUP::CustomerManagedPolicy:policy-00},${ACCEL_LOOKUP::CustomerManagedPolicy:policy-01} - # type: StringList - - name: accelerator-elb-logging-enabled - identifier: ELB_LOGGING_ENABLED - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - - AWS::ElasticLoadBalancingV2::LoadBalancer - inputParameters: - s3BucketNames: ${ACCEL_LOOKUP::Bucket:elbLogs} - remediation: - rolePolicyFile: custom-config-rules/elb-logging-enabled-remediation-role.json - automatic: true - targetId: SSM-ELB-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: LoadBalancerArn - value: RESOURCE_ID - type: String - - name: LogDestination - value: ${ACCEL_LOOKUP::Bucket:elbLogs} - type: StringList - - name: accelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: accelerator-securityhub-enabled - identifier: SECURITYHUB_ENABLED - - name: accelerator-cloudtrail-enabled - identifier: CLOUD_TRAIL_ENABLED - - name: accelerator-rds-logging-enabled - complianceResourceTypes: - - AWS::RDS::DBInstance - identifier: RDS_LOGGING_ENABLED - - name: accelerator-cloudwatch-alarm-action-check - complianceResourceTypes: - - AWS::CloudWatch::Alarm - inputParameters: - alarmActionRequired: 'TRUE' - insufficientDataActionRequired: 'TRUE' - okActionRequired: 'FALSE' - identifier: CLOUDWATCH_ALARM_ACTION_CHECK - - name: accelerator-redshift-cluster-configuration-check - inputParameters: - clusterDbEncrypted: 'TRUE' - loggingEnabled: 'TRUE' - complianceResourceTypes: - - AWS::Redshift::Cluster - identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK - - name: accelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: accelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: accelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: accelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - - name: accelerator-lambda-inside-vpc - complianceResourceTypes: - - AWS::Lambda::Function - identifier: LAMBDA_INSIDE_VPC - - name: accelerator-ec2-instances-in-vpc - complianceResourceTypes: - - AWS::EC2::Instance - identifier: INSTANCES_IN_VPC - - name: accelerator-vpc-sg-open-only-to-authorized-ports - inputParameters: - authorizedTcpPorts: '443' - authorizedUdpPorts: '1020-1025' - complianceResourceTypes: - - AWS::EC2::SecurityGroup - identifier: VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS - - name: accelerator-ec2-instance-no-public-ip - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_NO_PUBLIC_IP - - name: accelerator-elasticsearch-in-vpc-only - identifier: ELASTICSEARCH_IN_VPC_ONLY - - name: accelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: accelerator-iam-no-inline-policy-check - complianceResourceTypes: - - AWS::IAM::User - - AWS::IAM::Role - - AWS::IAM::Group - identifier: IAM_NO_INLINE_POLICY_CHECK - - name: accelerator-elb-acm-certificate-required - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_ACM_CERTIFICATE_REQUIRED - - name: accelerator-alb-http-drop-invalid-header-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ALB_HTTP_DROP_INVALID_HEADER_ENABLED - - name: accelerator-elb-tls-https-listeners-only - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_TLS_HTTPS_LISTENERS_ONLY - - name: accelerator-api-gw-execution-logging-enabled - complianceResourceTypes: - - AWS::ApiGateway::Stage - - AWS::ApiGatewayV2::Stage - identifier: API_GW_EXECUTION_LOGGING_ENABLED - - name: accelerator-cloudwatch-log-group-encrypted - identifier: CLOUDWATCH_LOG_GROUP_ENCRYPTED - - name: accelerator-s3-bucket-replication-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_REPLICATION_ENABLED - - name: accelerator-cw-loggroup-retention-period-check - identifier: CW_LOGGROUP_RETENTION_PERIOD_CHECK - - name: accelerator-ec2-instance-detailed-monitoring-enabled - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: accelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: 'TRUE' - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: accelerator-elb-deletion-protection-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ELB_DELETION_PROTECTION_ENABLED - - name: accelerator-cloudtrail-security-trail-enabled - identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: accelerator-elasticache-redis-cluster-automatic-backup-check - identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - - name: accelerator-s3-bucket-versioning-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_VERSIONING_ENABLED - - name: accelerator-vpc-vpn-2-tunnels-up - complianceResourceTypes: - - AWS::EC2::VPNConnection - identifier: VPC_VPN_2_TUNNELS_UP - - name: accelerator-elb-cross-zone-load-balancing-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED - - name: accelerator-iam-user-mfa-enabled - identifier: IAM_USER_MFA_ENABLED - - name: accelerator-guardduty-non-archived-findings - inputParameters: - daysHighSev: '1' - daysLowSev: '30' - daysMediumSev: '7' - identifier: GUARDDUTY_NON_ARCHIVED_FINDINGS - - name: accelerator-elasticsearch-node-to-node-encryption-check - complianceResourceTypes: - - AWS::Elasticsearch::Domain - identifier: ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK - - name: accelerator-kms-cmk-not-scheduled-for-deletion - complianceResourceTypes: - - AWS::KMS::Key - identifier: KMS_CMK_NOT_SCHEDULED_FOR_DELETION - - name: accelerator-api-gw-cache-enabled-and-encrypted - complianceResourceTypes: - - AWS::ApiGateway::Stage - identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED - - name: accelerator-sagemaker-endpoint-configuration-kms-key-configured - identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED - - name: accelerator-sagemaker-notebook-instance-kms-key-configured - identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED - - name: accelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - name: accelerator-s3-bucket-default-lock-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_DEFAULT_LOCK_ENABLED - - name: accelerator-duplicate-s3-rule - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - remediation: - automatic: true - targetId: AWS-EnableS3BucketEncryption - rolePolicyFile: custom-config-rules/enable-s3-encryption.json - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: SSEAlgorithm - value: AES256 - type: String -cloudWatch: - logGroups: - - logGroupName: /App/Test1 - logRetentionInDays: 30 - terminationProtected: true - encryption: - useLzaManagedKey: true - deploymentTargets: - accounts: - - SharedServices - - logGroupName: /App/Test2 - logRetentionInDays: 180 - terminationProtected: false - encryption: - kmsKeyArn: 'arn:aws:kms:us-east-1:111111111111:key/121ac3b6-8d53-4d8a-a05c-1234567789' - deploymentTargets: - accounts: - - Management - excludedRegions: - - us-west-2 - - logGroupName: /App/Test3 - logRetentionInDays: 365 - terminationProtected: false - deploymentTargets: - organizationalUnits: - - Infrastructure - excludedRegions: - - us-east-1 - - logGroupName: /App/Test4 - logRetentionInDays: 14 - terminationProtected: true - # encryption: - # kmsKeyName: key1 - deploymentTargets: - organizationalUnits: - - Infrastructure - metricSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - metrics: - # CIS 1.1 – Avoid the use of the "root" account - - filterName: RootAccountMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccount - metricValue: '1' - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - filterName: UnauthorizedAPICallsMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}' - metricNamespace: LogMetrics - metricName: UnauthorizedAPICalls - metricValue: '1' - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - filterName: ConsoleSigninWithoutMFAMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}' - metricNamespace: LogMetrics - metricName: ConsoleSigninWithoutMFA - metricValue: '1' - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - filterName: MetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccountUsage - metricValue: '1' - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}' - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: '1' - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}' - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: '1' - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: '1' - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}' - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: '1' - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}' - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: '1' - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}' - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: '1' - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}' - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: '1' - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}' - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: '1' - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}' - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: '1' - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}' - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: '1' - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}' - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: '1' - # # greater than threshold test - # - filterName: GenericMetricFilterGreaterThanThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingGreaterThanThreshold - # metricValue: '1' - # # less than threshold test - # - filterName: GenericMetricFilterLessThanThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingLessThanThreshold - # metricValue: '1' - # # less than or equal to threshold test - # - filterName: GenericMetricFilterLessThanOrEqualToThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingLessThanOrEqualToThreshold - # metricValue: '1' - # # less than lower or greater than upper threshold test - # - filterName: GenericMetricFilterLessThanLowerOrGreaterThanUpperThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingLessThanLowerOrGreaterThanUpperThreshold - # metricValue: '1' - # # greater than upper threshold test - # - filterName: GenericMetricFilterGreaterThanUpperThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingGreaterThanUpperThreshold - # metricValue: '1' - # # less than lower threshold test - # - filterName: GenericMetricFilterLessThanLowerThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingLessThanLowerThreshold - # metricValue: '1' - # - regions: - # - *HOME_REGION - # deploymentTargets: - # account: - # - SharedServices - # metrics: - # - filterName: GenericMetricFilterGreaterThanOrEqualToThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingGreaterThanOrEqualToThreshold - # metricValue: '1' - # - filterName: GenericMetricFilterGreaterThanThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingGreaterThanThreshold - # metricValue: '1' - # - filterName: GenericMetricFilterLessThanThreshold - # logGroupName: /generic/log/group - # filterPattern: '{$.eventType !="AwsServiceEvent"}' - # metricNamespace: LzaMetrics - # metricName: GenericMetricForTestingLessThanThreshold - # metricValue: '1' - alarmSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - alarms: - # CIS 1.1 – Avoid the use of the "root" account - - alarmName: CIS-1.1-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - alarmName: CIS-3.1-UnauthorizedAPICalls - alarmDescription: Alarm for unauthorized API calls - snsTopicName: Security - metricName: UnauthorizedAPICalls - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 5 - treatMissingData: notBreaching - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - alarmName: CIS-3.2-ConsoleSigninWithoutMFA - alarmDescription: Alarm for AWS Management Console sign-in without MFA - snsTopicName: Security - metricName: ConsoleSigninWithoutMFA - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - alarmName: CIS-3.3-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-3.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-3.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-3.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-3.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-3.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-3.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-3.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-3.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-3.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-3.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # greater than threshold test - - alarmName: GenericMetricGreaterThanThreshold - alarmDescription: Alarm to test GenericMetric GreaterThanThreshold - snsTopicName: Security - metricName: GenericMetricForTestingGreaterThanThreshold - namespace: LzaMetrics - comparisonOperator: GreaterThanThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: ignore - # less than threshold testing - - alarmName: GenericMetricLessThanThreshold - alarmDescription: Alarm to test GenericMetric LessThanThreshold - snsTopicName: Security - metricName: GenericMetricForTestingLessThanThreshold - namespace: LzaMetrics - comparisonOperator: LessThanThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: missing - # less than or equal to threshold test - - alarmName: GenericMetricLessThanOrEqualToThreshold - alarmDescription: Alarm to test GenericMetric LessThanOrEqualToThreshold - snsTopicName: Security - metricName: GenericMetricForTestingLessThanOrEqualToThreshold - namespace: LzaMetrics - comparisonOperator: LessThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # less than lower or greater than upper threshold test - # - alarmName: GenericMetricLessThanLowerOrGreaterThanUpperThreshold - # alarmDescription: Alarm to test GenericMetric LessThanLowerOrGreaterThanUpperThreshold - # snsTopicName: Security - # metricName: GenericMetricForTestingLessThanLowerOrGreaterThanUpperThreshold - # namespace: LzaMetrics - # comparisonOperator: LessThanLowerOrGreaterThanUpperThreshold - # evaluationPeriods: 1 - # period: 300 - # statistic: Sum - # threshold: 1 - # treatMissingData: notBreaching - # greater than upper threshold test - # - alarmName: GenericMetricGreaterThanUpperThreshold - # alarmDescription: Alarm to test GenericMetric GreaterThanUpperThreshold - # snsTopicName: Security - # metricName: GenericMetricForTestingGreaterThanUpperThreshold - # namespace: LzaMetrics - # comparisonOperator: GreaterThanUpperThreshold - # evaluationPeriods: 1 - # period: 300 - # statistic: Sum - # threshold: 1 - # treatMissingData: notBreaching - # less than lower threshold test - # - alarmName: GenericMetricLessThanLowerThreshold - # alarmDescription: Alarm to test GenericMetric LessThanLowerThreshold - # snsTopicName: Security - # metricName: GenericMetricForTestingLessThanLowerThreshold - # namespace: LzaMetrics - # comparisonOperator: LessThanLowerThreshold - # evaluationPeriods: 1 - # period: 300 - # statistic: Sum - # threshold: 1 - # treatMissingData: notBreaching -resourcePolicyEnforcement: - enable: true - remediation: - automatic: false - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - policySets: - - resourcePolicies: - - resourceType: IAM_ROLE - document: resource-policies/iam.json - - resourceType: S3_BUCKET - document: resource-policies/s3.json - - resourceType: KMS_KEY - document: resource-policies/kms.json - - resourceType: SECRETS_MANAGER_SECRET - document: resource-policies/secrets-manager.json - - resourceType: ECR_REPOSITORY - document: resource-policies/ecr.json - - resourceType: OPENSEARCH_DOMAIN - document: resource-policies/opensearch.json - - resourceType: SNS_TOPIC - document: resource-policies/sns.json - - resourceType: SQS_QUEUE - document: resource-policies/sqs.json - - resourceType: APIGATEWAY_REST_API - document: resource-policies/apigateway.json - - resourceType: LEX_BOT - document: resource-policies/lex-bot.json - - resourceType: EFS_FILE_SYSTEM - document: resource-policies/efs-file-system.json - - resourceType: EVENTBRIDGE_EVENTBUS - document: resource-policies/eventbridge-eventbus.json - - resourceType: BACKUP_VAULT - document: resource-policies/backup-vault.json - - resourceType: CODEARTIFACT_REPOSITORY - document: resource-policies/codeartifact-repository.json - inputParameters: - SourceAccount: '{{ ALLOWED_EXTERNAL_ACCOUNTS }}' - deploymentTargets: - organizationalUnits: - - Root diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/allow-ec2-only.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/allow-ec2-only.json deleted file mode 100644 index 6e1f85f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/allow-ec2-only.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowEC2", - "Effect": "Allow", - "Action": "ec2:*", - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-1.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-1.json deleted file mode 100644 index 703e007..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ConfigRulesStatement", - "Effect": "Deny", - "Action": [ - "config:PutConfigRule", - "config:DeleteConfigRule", - "config:DeleteEvaluationResults", - "config:DeleteConfigurationAggregator", - "config:PutConfigurationAggregator" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "LambdaStatement", - "Effect": "Deny", - "Action": [ - "lambda:AddPermission", - "lambda:CreateEventSourceMapping", - "lambda:CreateFunction", - "lambda:DeleteEventSourceMapping", - "lambda:DeleteFunction", - "lambda:DeleteFunctionConcurrency", - "lambda:PutFunctionConcurrency", - "lambda:RemovePermission", - "lambda:UpdateEventSourceMapping", - "lambda:UpdateFunctionCode", - "lambda:UpdateFunctionConfiguration" - ], - "Resource": "arn:${PARTITION}:lambda:*:*:function:${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "SnsStatement", - "Effect": "Deny", - "Action": [ - "sns:AddPermission", - "sns:CreateTopic", - "sns:DeleteTopic", - "sns:RemovePermission", - "sns:SetTopicAttributes", - "sns:Subscribe", - "sns:Unsubscribe" - ], - "Resource": "arn:${PARTITION}:sns:*:*:aws-accelerator-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "EbsEncryptionStatement", - "Effect": "Deny", - "Action": ["ec2:DisableEbsEncryptionByDefault"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-2.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-2.json deleted file mode 100644 index 127f68e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/guardrails-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "IamSettingsStatement", - "Effect": "Deny", - "Action": [ - "iam:DeleteAccountPasswordPolicy", - "iam:UpdateAccountPasswordPolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "IamRolesStatement", - "Effect": "Deny", - "Action": ["iam:*"], - "Resource": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/AWSServiceRoleForConfig" - ] - } - } - }, - { - "Sid": "GDSecHubServicesStatement", - "Effect": "Deny", - "Action": [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:UpdateDetector", - "guardduty:StopMonitoringMembers", - "guardduty:Disassociate*", - "securityhub:BatchDisableStandards", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:Disassociate*" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "MacieServiceStatement", - "Effect": "Deny", - "Action": [ - "macie:AcceptInvitation", - "macie:CreateInvitations", - "macie:CreateMember", - "macie:DeclineInvitations", - "macie:DeleteInvitations", - "macie:DeleteMember", - "macie:DisableMacie", - "macie:DisableOrganizationAdminAccount", - "macie:Disassociate*", - "macie:Enable*", - "macie:UpdateMacieSession", - "macie:UpdateMemberSession", - "macie:UpdateOrganizationConfiguration" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "CloudFormationStatement", - "Effect": "Deny", - "Action": ["cloudformation:Delete*"], - "Resource": "arn:${PARTITION}:cloudformation:*:*:stack/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventSSMModification", - "Effect": "Deny", - "Action": ["ssm:DeleteParameter*", "ssm:PutParameter"], - "Resource": "arn:${PARTITION}:ssm:*:*:parameter${ACCELERATOR_SSM_PREFIX}*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/quarantine.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-instance-profile.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-instance-profile.yaml deleted file mode 100644 index 543ba02..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-instance-profile.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: Associate AWS Iam Instance Profile to EC2 Instance -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - IamInstanceProfile: - type: String - InstanceId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: associateIamProfile - action: 'aws:executeAwsApi' - inputs: - Service: ec2 - Api: associate_iam_instance_profile - IamInstanceProfile: - Name: '{{ IamInstanceProfile }}' - InstanceId: '{{ InstanceId }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-role-policy.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-role-policy.yaml deleted file mode 100644 index 58cc557..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/attach-iam-role-policy.yaml +++ /dev/null @@ -1,50 +0,0 @@ -description: IAM Role Policy -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - ResourceId: - type: String - AWSManagedPolicies: - type: StringList - CustomerManagedPolicies: - type: StringList - minItems: 0 - default: [] - AutomationAssumeRole: - type: String -mainSteps: - - name: attachPolicy - action: 'aws:executeScript' - inputs: - Runtime: python3.7 - Handler: script_handler - Script: |- - import boto3 - partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] - iam = boto3.client("iam") - config = boto3.client("config") - def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - ) - InputPayload: - ResourceId: '{{ ResourceId }}' - AWSManagedPolicies: '{{ AWSManagedPolicies }}' - CustomerManagedPolicies: '{{ CustomerManagedPolicies }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/s3-encryption.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/s3-encryption.yaml deleted file mode 100644 index 79bc510..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/s3-encryption.yaml +++ /dev/null @@ -1,28 +0,0 @@ -description: Enables Encryption on S3 Bucket -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - BucketName: - type: String - description: (Required) The name of the S3 Bucket whose content will be encrypted. - KMSMasterKey: - type: String - description: (Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption. - AutomationAssumeRole: - type: String - description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. - default: "" -mainSteps: -- name: PutBucketEncryption - action: aws:executeAwsApi - inputs: - Service: s3 - Api: PutBucketEncryption - Bucket: "{{BucketName}}" - ServerSideEncryptionConfiguration: - Rules: - - - ApplyServerSideEncryptionByDefault: - SSEAlgorithm: "aws:kms" - KMSMasterKeyID: "{{KMSMasterKey}}" - isEnd: true \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/ssm-elb-enable-logging.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/ssm-elb-enable-logging.yaml deleted file mode 100644 index 00af3e2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/ssm-elb-enable-logging.yaml +++ /dev/null @@ -1,44 +0,0 @@ -description: Enable logging on Elastic Load-Balancer -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - LogDestination: - type: String - LoadBalancerArn: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: getAccount - action: 'aws:executeAwsApi' - inputs: - Service: sts - Api: get_caller_identity - outputs: - - Name: Id - Selector: $.Account - Type: String - - name: getLoadBalancer - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: describe_load_balancers - LoadBalancerArns: - - '{{ LoadBalancerArn }}' - outputs: - - Name: Name - Selector: $.LoadBalancers[0].LoadBalancerName - Type: String - - name: enableLogging - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: modify_load_balancer_attributes - LoadBalancerArn: '{{ LoadBalancerArn }}' - Attributes: - - Key: access_logs.s3.enabled - Value: 'true' - - Key: access_logs.s3.bucket - Value: '{{ LogDestination }}' - - Key: access_logs.s3.prefix - Value: 'elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/waf-enable-logging.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/waf-enable-logging.yaml deleted file mode 100644 index be8fb95..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/ssm-documents/waf-enable-logging.yaml +++ /dev/null @@ -1,120 +0,0 @@ -description: Adds logging to non-compliant WebACLs -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - WebACLId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: performRemediation - action: "aws:executeScript" - inputs: - Runtime: python3.7 - Handler: handler - Script: |- - import boto3 - import json - import os - - # - # This Lambda function ensures that all WAF web ACLs have logging enabled. - # - # Trigger Type: SSM Automation - # Scope of Automation: AWS::WAF::WebACL & AWS::WAFRegional::WebACL - # - - CLOUDWATCH_LOG_GROUP = 'aws-waf-logs-AWSAccelerator' - - def evaluate_compliance(webAclName): - hasConfig = False - - #Setting up variables - client = '' - response = '' - wafArn = '' - cloudwatchArn = '' - - - #Check if this is a WAFv2. The ResourceId passed in is already the ARN - if webAclName.find(':wafv2:') >= 0: - wafArn = webAclName - client = boto3.client('wafv2') - else: - - isWebAcl = True - #Test if this is AWS::WAF::WebACL - try: - print('Testing for WAF::WebACL') - client = boto3.client('waf') - response = client.get_web_acl(WebACLId=webAclName) - except: - isWebAcl = False - pass - - if not isWebAcl: - #Test if this is AWS::WAFRegional::WebACL - try: - print('Testing for WAFRegional::WebACL') - client = boto3.client('waf-regional') - response = client.get_web_acl(WebACLId=webAclName) - except: - pass - - wafArn = response['WebACL']['WebACLArn'] - print('wafArn:' + wafArn) - - - try: - response = client.get_logging_configuration(ResourceArn=wafArn) - hasConfig = True - except: - print('Attempting to fix non-compliance') - print('WAF ARN: ' + wafArn) - - cwClient = boto3.client('logs') - - # check if CloudWatch log group exists - log_group_exists = False - response = cwClient.describe_log_groups(logGroupNamePrefix=CLOUDWATCH_LOG_GROUP) - log_groups = response['logGroups'] - print('LogGroups') - print(log_groups) - for log_group in log_groups: - if log_group['logGroupName'] == CLOUDWATCH_LOG_GROUP: - log_group_exists = True - cloudwatchArn = log_group['arn'] #record ARN - print('Log group already exists. Using:' + cloudwatchArn) - - #create log group if not exists - if log_group_exists == False: - print('Creating log group:' + CLOUDWATCH_LOG_GROUP) - response = cwClient.create_log_group( - logGroupName=CLOUDWATCH_LOG_GROUP - ) - print('Response from CreateLogGroup') - print(response) - #get newly created log group ARN - response = cwClient.describe_log_groups(logGroupNamePrefix=CLOUDWATCH_LOG_GROUP) - log_groups = response['logGroups'] - for log_group in log_groups: - if log_group['logGroupName'] == CLOUDWATCH_LOG_GROUP: - cloudwatchArn = log_group['arn'] #record ARN - - cloudwatchArn=cloudwatchArn.rstrip('*') - cloudwatchArn=cloudwatchArn.rstrip(':') - print('cloudwatchArn:' + cloudwatchArn) - print('Attempting to put ' + cloudwatchArn + 'as logging configuration for ' + wafArn) - response = client.put_logging_configuration(LoggingConfiguration={'ResourceArn': wafArn,'LogDestinationConfigs': [ cloudwatchArn ]}) - print ('## RESPONSE') - print(response) - - def handler(event, context): - print('## ENVIRONMENT VARIABLES') - print(os.environ) - print('## EVENT') - print(event) - aclName = event['WebACLId'] - evaluate_compliance(aclName) - InputPayload: - WebACLId: "{{ WebACLId }}" diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/tagging-policies/org-tag-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/tagging-policies/org-tag-policy.json deleted file mode 100644 index e49fbb0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/tagging-policies/org-tag-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tags": { - "costcenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/test-configuration/config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/test-configuration/config.yaml deleted file mode 100644 index 482e914..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/test-configuration/config.yaml +++ /dev/null @@ -1,22 +0,0 @@ -tests: - - name: validate main transit gateway - description: Validate Main Transit Gateway - suite: network - testTarget: validateTransitGateway - expect: PASS - parameters: - name: Main - accountId: '' - region: us-east-1 - amazonSideAsn: '65521' - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTableNames: - - core - - segregated - - shared - - standalone - shareTargetAccountIds: [] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/default.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/ec2.json b/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/all-enabled/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/accounts-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/accounts-config.yaml deleted file mode 100644 index b127654..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/accounts-config.yaml +++ /dev/null @@ -1,39 +0,0 @@ -mandatoryAccounts: - - name: Management - description: The management (primary) account - email: alias+no-org-root@example.com - organizationalUnit: Root - - name: LogArchive - description: The log archive account - email: alias+no-org-log@example.com - organizationalUnit: Root - - name: Audit - description: The security audit account (also referred to as the audit account) - email: alias+no-org-audit@example.com - organizationalUnit: Root -workloadAccounts: - - name: SharedServices - description: The shared services account - email: alias+no-org-shared@example.com - organizationalUnit: Root - - name: Network - description: The network account - email: alias+no-org-network@example.com - organizationalUnit: Root - - name: Tenant01 - description: Tenant 01 Account - email: alias+no-org-tenant01@example.com - organizationalUnit: Root -accountIds: - - email: alias+no-org-root@example.com - accountId: '111111111111' - - email: alias+no-org-log@example.com - accountId: '222222222222' - - email: alias+no-org-audit@example.com - accountId: '333333333333' - - email: alias+no-org-shared@example.com - accountId: '444444444444' - - email: alias+no-org-network@example.com - accountId: '555555555555' - - email: alias+no-org-tenant01@example.com - accountId: '666666666666' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/global-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/global-config.yaml deleted file mode 100644 index b66bff2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/global-config.yaml +++ /dev/null @@ -1,16 +0,0 @@ -homeRegion: &HOME_REGION us-gov-west-1 -enabledRegions: - - *HOME_REGION -managementAccountAccessRole: OrganizationAccountAccessRole -cloudwatchLogRetentionInDays: 365 -terminationProtection: false -controlTower: - enable: false -logging: - account: LogArchive - cloudtrail: - enable: false - organizationTrail: false - sessionManager: - sendToCloudWatchLogs: false - sendToS3: false \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/iam-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/iam-config.yaml deleted file mode 100644 index 579b516..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/iam-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -providers: [] -policySets: [] -roleSets: [] -groupSets: [] -userSets: [] diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/network-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/network-config.yaml deleted file mode 100644 index c19603e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/network-config.yaml +++ /dev/null @@ -1,45 +0,0 @@ -homeRegion: &HOME_REGION us-gov-west-1 -defaultVpc: - delete: false -transitGateways: [] -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json -vpcs: [] -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/organization-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/organization-config.yaml deleted file mode 100644 index 08976cd..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/organization-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -enable: false -organizationalUnits: [] -serviceControlPolicies: [] -taggingPolicies: [] -backupPolicies: [] diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/security-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/security-config.yaml deleted file mode 100644 index 4d6dce1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/security-config.yaml +++ /dev/null @@ -1,51 +0,0 @@ -homeRegion: &HOME_REGION us-gov-west-1 -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: false - excludeRegions: [] - s3PublicAccessBlock: - enable: false - excludeAccounts: [] - snsSubscriptions: [] - macie: - enable: false - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: false - excludeRegions: [] - s3Protection: - enable: false - excludeRegions: [] - exportConfiguration: - enable: false - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - securityHub: - enable: false - excludeRegions: [] - standards: [] - ssmAutomation: - excludeRegions: [] - documentSets: [] -accessAnalyzer: - enable: false -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: [] -cloudWatch: - metricSets: [] - alarmSets: [] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/default.json b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/ec2.json b/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/no-org-config/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/accounts-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/accounts-config.yaml deleted file mode 100644 index 46ceaed..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/accounts-config.yaml +++ /dev/null @@ -1,40 +0,0 @@ -mandatoryAccounts: - - name: Management - description: The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: all-enabled-management-account@example.com - organizationalUnit: Root - - name: LogArchive - description: The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: all-enabled-logarchive-account@example.com - organizationalUnit: Security - - name: Audit - description: The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name. - email: all-enabled-audit-account@example.com - organizationalUnit: Security -workloadAccounts: - - name: SharedServices - description: The SharedServices account - email: all-enabled-shared-services@example.com - organizationalUnit: Infrastructure - - name: Network - description: The Network account - email: all-enabled-network@example.com - organizationalUnit: Infrastructure - - name: GovCloudWorkloadAccount01 - description: Sample govCloud workload account - email: all-enabled-govcloud-workload-account01@example.com - organizationalUnit: GovCloud - enableGovCloud: true -accountIds: - - email: all-enabled-management-account@example.com - accountId: '111111111111' - - email: all-enabled-audit-account@example.com - accountId: '222222222222' - - email: all-enabled-logarchive-account@example.com - accountId: '333333333333' - - email: all-enabled-shared-services@example.com - accountId: '444444444444' - - email: all-enabled-network@example.com - accountId: '555555555555' - - email: all-enabled-govcloud-workload-account01@example.com - accountId: '666666666666' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-connector-permissions-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-connector-permissions-setup.ps1 deleted file mode 100644 index ee25a83..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-connector-permissions-setup.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -Start-Process powershell.exe -Credential $credential -ArgumentList "-file c:\cfn\scripts\AD-group-grant-permissions-setup.ps1", "$GroupName" \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-grant-permissions-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-grant-permissions-setup.ps1 deleted file mode 100644 index efab9d6..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-grant-permissions-setup.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupName -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -#Delegate Control -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;computer" -dsacls "CN=$GroupName,OU=Users,OU=$dom,DC=$dom,DC=$ext" /I:T /G "$dom\$GroupName`:CCDC;user" \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-setup.ps1 deleted file mode 100644 index f27268f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-setup.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - $groupName = $groupsArray[$i] - $groupExists = Get-ADGroup -Filter {Name -eq $groupName} -Credential $credential - if($null -eq $groupExists) { - #Create Group - New-ADGroup -Name $groupName -GroupCategory Security -GroupScope Global -Credential $credential - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-group-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-group-setup.ps1 deleted file mode 100644 index 848d3c0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-group-setup.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $UserName, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - #Add User to Group - Add-ADGroupMember -Identity $groupsArray[$i] -Members $UserName -Credential $credential -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-setup.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-setup.ps1 deleted file mode 100644 index dd154bf..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AD-user-setup.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -[CmdletBinding()] -param( - [string] - $UserName, - - [string] - $Password, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [string] - $PasswordNeverExpires, - - [Parameter(Mandatory=$false)] - [AllowEmptyString()] - [string]$UserEmailAddress = '' -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$pass = ConvertTo-SecureString $Password -AsPlainText -Force -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword -$userExists = Get-ADUser -Credential $credential -Filter "Name -eq '$UserName'" - -If ($null -eq $userExists -and $UserEmailAddress) { - #Create User - New-ADUser -Name $UserName -EmailAddress $UserEmailAddress -AccountPassword $pass -Enabled 1 -Credential $credential -SamAccountName $UserName -} - -#Set the admin & connector user's password never expires flag -If (-NOT ($PasswordNeverExpires -eq 'No')) { - Set-ADUser -Identity $UserName -PasswordNeverExpires $true -Credential $credential -} Else { - Set-ADUser -Identity $UserName -PasswordNeverExpires $false -Credential $credential -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AWSQuickStart.psm1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AWSQuickStart.psm1 deleted file mode 100644 index fc1b30f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/AWSQuickStart.psm1 +++ /dev/null @@ -1,345 +0,0 @@ -function New-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [string] - $Handle, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\', - - [Parameter(Mandatory=$false)] - [switch] - $Base64Handle - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - if ($Base64Handle) { - Write-Verbose "Trying to decode handle Base64 string as UTF8 string" - $decodedHandle = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Handle)) - if ($decodedHandle -notlike "http*") { - Write-Verbose "Now trying to decode handle Base64 string as Unicode string" - $decodedHandle = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($Handle)) - } - Write-Verbose "Decoded handle string: $decodedHandle" - $Handle = $decodedHandle - } - - Write-Verbose "Creating Handle Registry Key" - New-ItemProperty -Path $Path -Name Handle -Value $Handle -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function New-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [string] - $Stack, - - [Parameter(Mandatory=$true)] - [string] - $Resource, - - [Parameter(Mandatory=$true)] - [string] - $Region, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - Write-Verbose "Creating Stack Registry Key" - New-ItemProperty -Path $Path -Name Stack -Value $Stack -Force - - Write-Verbose "Creating Resource Registry Key" - New-ItemProperty -Path $Path -Name Resource -Value $Resource -Force - - Write-Verbose "Creating Region Registry Key" - New-ItemProperty -Path $Path -Name Region -Value $Region -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - - -function Get-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - Write-Verbose "Getting ErrorCount Registry Key" - Get-ItemProperty -Path $Path -Name ErrorCount -ErrorAction Stop | Select-Object -ExpandProperty ErrorCount - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Set-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [int32] - $Count, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $currentCount = Get-AWSQuickStartErrorCount - $currentCount += $Count - - Write-Verbose "Creating ErrorCount Registry Key" - Set-ItemProperty -Path $Path -Name ErrorCount -Value $currentCount -ErrorAction Stop - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty $Path - - return $key.Handle - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty $Path - $resourceSignal = @{ - Stack = $key.Stack - Resource = $key.Resource - Region = $key.Region - } - $toReturn = New-Object -TypeName PSObject -Property $resourceSignal - - if ($toReturn.Stack -and $toReturn.Resource -and $toReturn.Region) { - return $toReturn - } else { - return $null - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Remove-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty -Path $Path -Name Handle -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing Handle key value from $Path" - Remove-ItemProperty -Path $Path -Name Handle - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Remove-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\SOFTWARE\AWSQuickStart\' - ) - - try { - $ErrorActionPreference = "Stop" - - foreach ($keyName in @('Stack','Resource','Region')) { - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty -Path $Path -Name $keyName -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing $keyName key value from $Path" - Remove-ItemProperty -Path $Path -Name $keyName - } - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Write-AWSQuickStartEvent { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipelineByPropertyName=$true)] - [string] - $Message, - - [Parameter(Mandatory=$false)] - [string] - $EntryType = 'Error' - ) - - process { - Write-Verbose "Checking for AWSQuickStart Eventlog Source" - if(![System.Diagnostics.EventLog]::SourceExists('AWSQuickStart')) { - New-EventLog -LogName Application -Source AWSQuickStart -ErrorAction SilentlyContinue - } - else { - Write-Verbose "AWSQuickStart Eventlog Source exists" - } - - Write-Verbose "Writing message to application log" - - try { - Write-EventLog -LogName Application -Source AWSQuickStart -EntryType $EntryType -EventId 1001 -Message $Message - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Write-AWSQuickStartException { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - process { - try { - Write-Verbose "Incrementing error count" - Set-AWSQuickStartErrorCount -Count 1 - - Write-Verbose "Getting total error count" - $errorTotal = Get-AWSQuickStartErrorCount - - $errorMessage = "Command failure in {0} {1} on line {2} `nException: {3}" -f $ErrorRecord.InvocationInfo.MyCommand.name, - $ErrorRecord.InvocationInfo.ScriptName, $ErrorRecord.InvocationInfo.ScriptLineNumber, $ErrorRecord.Exception.ToString() - - $CmdSafeErrorMessage = $errorMessage -replace '[^a-zA-Z0-9\s\.\[\]\-,:_\\\/\(\)]', '' - if ($CmdSafeErrorMessage.length -gt 255) { - $CmdSafeErrorMessage = $CmdSafeErrorMessage.substring(0,252) + '...' - } - - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 1 --reason='$CmdSafeErrorMessage' '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 1 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - finally { - Write-AWSQuickStartEvent -Message $errorMessage - # throwing an exception to force cfn-init execution to stop - throw $CmdSafeErrorMessage - } - } -} - -function Write-AWSQuickStartStatus { - [CmdletBinding()] - Param() - - process { - try { - Write-Verbose "Checking error count" - if((Get-AWSQuickStartErrorCount) -eq 0) { - Write-Verbose "Getting Handle" - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 0 '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 0 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Configure-password-policy.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Configure-password-policy.ps1 deleted file mode 100644 index fde4721..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Configure-password-policy.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword, - - [Boolean] - $ComplexityEnabled, - - [string] - $LockoutDuration, - - [string] - $LockoutObservationWindow, - - [string] - $LockoutThreshold, - - [string] - $MaxPasswordAge, - - [string] - $MinPasswordAge, - - [string] - $MinPasswordLength, - - [string] - $PasswordHistoryCount, - - [Boolean] - $ReversibleEncryptionEnabled -) - -# Turned off logging; -# Start-Transcript -Path C:\cfn\log\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -#Configure passsord policy for all users -Set-ADFineGrainedPasswordPolicy -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -ComplexityEnabled:$ComplexityEnabled -MaxPasswordAge:$MaxPasswordAge -LockoutDuration:$LockoutDuration -LockoutObservationWindow:$LockoutObservationWindow -LockoutThreshold:$LockoutThreshold -MinPasswordAge:$MinPasswordAge -MinPasswordLength:$MinPasswordLength -PasswordHistoryCount:$PasswordHistoryCount -ReversibleEncryptionEnabled:$ReversibleEncryptionEnabled -Server:$fdn -Credential $credential - -#Create password policy subject -Add-ADFineGrainedPasswordPolicySubject -Identity:"CN=CustomerPSO-01,CN=Password Settings Container,CN=System,DC=$dom,DC=$ext" -Server:$fdn -Subjects:"CN=Domain Users,CN=Users,DC=$dom,DC=$ext" -Credential $credential diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Join-Domain.ps1 b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Join-Domain.ps1 deleted file mode 100644 index acdf6d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ad-config-scripts/Join-Domain.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -[CmdletBinding()] -param( - [string] - $DomainName, - - [string] - $UserName, - - [string] - $Password -) - -try { - $ErrorActionPreference = "Stop" - - $pass = ConvertTo-SecureString $Password -AsPlainText -Force - $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$pass - - Add-Computer -DomainName $DomainName -Credential $cred -ErrorAction Stop - - # Execute restart after script exit and allow time for external services - $shutdown = Start-Process -FilePath "shutdown.exe" -ArgumentList @("/r", "/t 10") -Wait -NoNewWindow -PassThru - if ($shutdown.ExitCode -ne 0) { - throw "[ERROR] shutdown.exe exit code was not 0. It was actually $($shutdown.ExitCode)." - } -} -catch { - $_ | Write-AWSQuickStartException -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/appConfigs/appA/launchTemplate/userData.sh b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/appConfigs/appA/launchTemplate/userData.sh deleted file mode 100644 index 01e78a0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/appConfigs/appA/launchTemplate/userData.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -yum update -y -yum install -y httpd -systemctl start httpd -systemctl enable httpd -usermod -a -G apache ec2-user -chown -R ec2-user:apache /var/www -chmod 2775 /var/www -find /var/www -type d -exec chmod 2775 {} \; -find /var/www -type f -exec chmod 0664 {} \; \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-policies/org-backup-policies.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-policies/org-backup-policies.json deleted file mode 100644 index aadece2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-policies/org-backup-policies.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "plans": { - "Organization_Backup_Plan": { - "rules": { - "Monthly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Weekly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 ? * MON *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Daily_Rule": { - "schedule_expression": { - "@@assign": "cron(15 10 * * ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "NinetyDay_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */3 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "365" - }, - "delete_after_days": { - "@@assign": "1825" - } - } - }, - "Yearly_Rule": { - "schedule_expression": { - "@@assign": "cron(0 8 1 */12 ? *)" - }, - "start_backup_window_minutes": { - "@@assign": "60" - }, - "complete_backup_window_minutes": { - "@@assign": "604800" - }, - "enable_continuous_backup": { - "@@assign": false - }, - "target_backup_vault_name": { - "@@assign": "BackupVault" - }, - "recovery_point_tags": { - "Owner": { - "tag_key": { - "@@assign": "Owner" - }, - "tag_value": { - "@@assign": "Backup" - } - } - }, - "lifecycle": { - "move_to_cold_storage_after_days": { - "@@assign": "1825" - }, - "delete_after_days": { - "@@assign": "365" - } - } - } - }, - "regions": { - "@@append": [ - "us-east-1" - ] - }, - "selections": { - "tags": { - "Monthly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupMonthly" - }, - "tag_value": { - "@@assign": [ - "Monthly" - ] - } - }, - "Weekly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupWeekly" - }, - "tag_value": { - "@@assign": [ - "Weekly" - ] - } - }, - "Daily_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupDaily" - }, - "tag_value": { - "@@assign": [ - "Daily" - ] - } - }, - "NinetyDay_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupNinetyDay" - }, - "tag_value": { - "@@assign": [ - "NinetyDay" - ] - } - }, - "Yearly_Backup_Assignment": { - "iam_role_arn": { - "@@assign": "arn:aws:iam::$account:role/Backup-Role" - }, - "tag_key": { - "@@assign": "orgBackupYearly" - }, - "tag_value": { - "@@assign": [ - "Yearly" - ] - } - } - } - }, - "backup_plan_tags": { - "stage": { - "tag_key": { - "@@assign": "Stage" - }, - "tag_value": { - "@@assign": "Released" - } - } - } - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-vault-policies/infrastructure-vault-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-vault-policies/infrastructure-vault-policy.json deleted file mode 100644 index ef8395f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/backup-vault-policies/infrastructure-vault-policy.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyDeleteRecoveryPoint", - "Effect": "Deny", - "Principal": "*", - "Action": "backup:DeleteRecoveryPoint", - "Resource": "*", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/access-logs-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/access-logs-bucket.json deleted file mode 100644 index fbff730..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/access-logs-bucket.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "statement from policy file", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "Bool": { - "aws:SecureTransport": "false" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/central-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/central-log-bucket.json deleted file mode 100644 index 173ec76..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/central-log-bucket.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "This is from policy file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*", - "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Organization principals to put session manager logs", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:PutObject", - "Resource": "arn:${PARTITION}:s3:::${ACCELERATOR_CENTRAL_LOGS_BUCKET_NAME}/*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "ArnLike": { - "aws:PrincipalARN": "arn:${PARTITION}:iam::*:role/EC2-Default-SSM-AD-Role" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/elb-logs-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/elb-logs-bucket.json deleted file mode 100644 index 9cdfb74..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/elb-logs-bucket.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-access-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-access-log-bucket.json deleted file mode 100644 index 77aa336..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-access-log-bucket.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "statement from policy file", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "Bool": { - "aws:SecureTransport": "false" - } - } - }, - { - "Sid": "Allow write access for logging service principal - from file", - "Effect": "Allow", - "Principal": { - "Service": "logging.s3.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": "arn:aws:s3:::existing-access-logs-bucket-${ACCOUNT_ID}-${REGION}/*", - "Condition": { - "StringEquals": { - "aws:SourceAccount": "${ACCOUNT_ID}" - } - } - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-central-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-central-log-bucket.json deleted file mode 100644 index 7159bb3..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-central-log-bucket.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "deny-insecure-connections - From file", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "Bool": { - "aws:SecureTransport": "false" - } - } - }, - { - "Effect": "Allow", - "Principal": { - "Service": [ - "ssm.amazonaws.com", - "delivery.logs.amazonaws.com", - "config.amazonaws.com", - "cloudtrail.amazonaws.com" - ] - }, - "Action": "s3:PutObject", - "Resource": "arn:aws:s3:::existing-central-log-bucket/*", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - }, - { - "Effect": "Allow", - "Principal": { - "Service": [ - "delivery.logs.amazonaws.com", - "cloudtrail.amazonaws.com", - "config.amazonaws.com" - ] - }, - "Action": [ - "s3:GetBucketAcl", - "s3:ListBucket" - ], - "Resource": "arn:aws:s3:::existing-central-log-bucket" - }, - { - "Sid": "Allow Organization principals to use the bucket - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:GetBucketLocation", - "s3:GetBucketAcl", - "s3:PutObject", - "s3:GetObject", - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Organization use of the bucket for replication - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*", - "s3:GetBucketVersioning", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ObjectOwnerOverrideToBucketOwner" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow read write access for Macie service principal - From file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ] - }, - { - "Sid": "Allow read write access for Guardduty service principal - From file", - "Effect": "Allow", - "Principal": { - "Service": "guardduty.amazonaws.com" - }, - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*" - ], - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ] - }, - { - "Sid": "Allow Organization principals to put objects - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:PutObjectAcl", - "s3:PutObject" - ], - "Resource": "arn:aws:s3:::existing-central-log-bucket/*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Organization principals to get encryption context and acl - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:GetEncryptionConfiguration", - "s3:GetBucketAcl" - ], - "Resource": "arn:aws:s3:::existing-central-log-bucket", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "This is from policy file - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:List*", - "Resource": [ - "arn:aws:s3:::existing-central-log-bucket", - "arn:aws:s3:::existing-central-log-bucket/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-elb-log-bucket.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-elb-log-bucket.json deleted file mode 100644 index c6b680a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/bucket-policies/full-elb-log-bucket.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:List*" - ], - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow get acl access for SSM principal - from file", - "Effect": "Allow", - "Principal": { - "Service": "ssm.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}" - }, - { - "Sid": "Allow write access for ELB Account principal - from file", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" - }, - "Action": "s3:PutObject", - "Resource": [ - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}", - "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*" - ] - }, - { - "Sid": "Allow write access for delivery logging service principal - from file", - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}/*", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - }, - { - "Sid": "Allow read bucket ACL access for delivery logging service principal", - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": "arn:aws:s3:::existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION}" - } - ] - } diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/certificates/certA/cert.crt b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/certificates/certA/cert.crt deleted file mode 100644 index e69de29..0000000 diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/certificates/certA/privKey.key b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/certificates/certA/privKey.key deleted file mode 100644 index e69de29..0000000 diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/cloudformation-templates/custom-s3-bucket.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/cloudformation-templates/custom-s3-bucket.yaml deleted file mode 100644 index 7dddeec..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/cloudformation-templates/custom-s3-bucket.yaml +++ /dev/null @@ -1,56 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Parameters: - pStringParameter: - Description: A parameter passed explicitly as a string - Type: String - Default: "" - pDynamicParameter: - Description: A parameter passed as a dynamic lookup from SSM - Type: String - Default: "" -Conditions: - hasStringParameter: !Not [ !Equals [ !Ref pStringParameter, "" ]] - hasDynamicParameter: !Not [ !Equals [ !Ref pDynamicParameter, "" ]] - hasParameters: !And [!Condition hasStringParameter, !Condition hasDynamicParameter] -Resources: - LZACustomizationsBucket: - Type: 'AWS::S3::Bucket' - Properties: - BucketEncryption: - ServerSideEncryptionConfiguration: - - BucketKeyEnabled: false - ServerSideEncryptionByDefault: - SSEAlgorithm: 'aws:kms' - KMSMasterKeyID: 'aws/s3' - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - VersioningConfiguration: - Status: Enabled - - LZACustomizationsSampleBucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref LZACustomizationsBucket - PolicyDocument: - Version: '2012-10-17' - Statement: - - Principal: - AWS: '*' - Action: - - 's3:*' - Resource: - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}/*' - - !Sub 'arn:aws:s3:::${LZACustomizationsBucket}' - Effect: 'Deny' - Condition: - Bool: - aws:SecureTransport: 'false' - - MySNSTopic: - Condition: hasParameters - Type: AWS::SNS::Topic - Properties: - TopicName: !Sub ${pDynamicParameter}-${pStringParameter}-topic diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-detection-role.json deleted file mode 100644 index db821df..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-detection-role.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:Describe*", - "ec2:Get*", - "ec2:ListSnapshotsInRecycleBin", - "ec2:SearchLocalGatewayRoutes", - "ec2:SearchTransitGatewayRoutes" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-remediation-role.json deleted file mode 100644 index 52701c4..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile-remediation-role.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:AssociateIamInstanceProfile", - "ec2:ReplaceIamInstanceProfileAssociation", - "iam:PassRole" - ], - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile.zip b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/attach-ec2-instance-profile.zip deleted file mode 100644 index 1de00bf06d02573ed8890d48da07d3c50ed785dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 987 zcmWIWW@Zs#-~hs}B^f~sQ1F+70VtxtkeQc~TA`O!92&yQz<&LuYr;(+2GOMz+zgB? zq7NO*Sk6>47j6wZm?vc@u=lg5@XAw7F6KdfA(jD$8nqfZR#bH?dQic>VN-T~u0D(F zpX%e;(^4A+LblF1V4R}~FRwR-!${OMK~i1d4x^c&R)1FH?mAy5u>2*L=IrOQWFLCFF_mpk zTpd;MxlyY1^FEb*3(hJmpQ(Fzmang~No3CPSE8xhdmMJzWIeO9ULj`l%zZ=Vir-Ug zG-sSLIUaY#O6B#VyftTqBh;U2ZrLn3%Yq{>7<#t{oL2L{{QrZl|Oi`P~s=;Bwwr0?Z0pQ5l}FFIxo7y-e_C) ztOIv8-sta67UzE}-@lo|_istR^wqgHU!DDKCHIB>@pGfVfPggXy)%2BbKHt`o9Lv> z%&p`5STusw)r&cc>)P6n)4yMCU8`<))G9vtz>S1+-*^PwLjuzNxCWlNt$QrPkn5k~ z-2)fbs<1y~i85lI=$gq(BwSQb`SYegdz0$Xv-@{` zIluId1^@*MvS$DQ diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/bucket-sse-enabled-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/bucket-sse-enabled-remediation-role.json deleted file mode 100644 index 3c37861..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/bucket-sse-enabled-remediation-role.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["s3:GetEncryptionConfiguration", "s3:PutEncryptionConfiguration"], - "Resource": "*", - "Condition": { - "ArnLike": { - "aws:PrincipalArn": ["arn:aws:iam::*:role/AWSAccelerator-SecuritySt-*"] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-detection-role.json deleted file mode 100644 index e612dc8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-detection-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "iam:Get*", - "iam:List*" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json deleted file mode 100644 index 83c5a8d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions-remediation-role.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "config:BatchGetResourceConfig", - "iam:AttachRolePolicy" - ], - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions.zip b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/ec2-instance-profile-permissions.zip deleted file mode 100644 index 9d64fe687967e4fa3cf01897db666d68356cb1af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1292 zcmWIWW@Zs#-~hr?Ejd99P~gSS02EPR$jnPgtcVx~UR-qK+7Txoqf#cY`$ghLj~zm}!V^8GE_ovn z`S&}Yr>*YMNS2oi_uiay{QKF%#*aM;mL~qWF0{_!(tcyLmG37u|B5`?A6H=M;3>hm zFKxDg;B4NhE86c*^?30#K_D_)-JNkc`(v#+H`-OFR*MDNuMj!Def#yzCx3i@{qpbK zlAS+3=NXyqm~U$$)tmNs_Qe-`veoeyn(UT3Wr^l-_6u`+UzzB>;atU%nGYUZwtZTa zaly3q5~B_Of+cA?e(_KC$=ukyTW8@0F`-4ey>gQG53KcYIkJLjk?Fzf5?dHr*gLmP z(5yL8(X+rf#ka!cVOW=`^Zvvi4G*^z{)^Ciw(ydW?dGo=mmJ-!B6(*$FlJ zJdG;@4|c3c^;)v%oK}uzh{HJpCABwK4VPxz4B{yd(cHAqE$p@j&+lm-n}TaSrcTMy z60P~ocCYwdq{lY4|LwP|jy$>}(DyI-jP&Np-yaxfN$Fh-nYUu+B!(AjMc8l41Z%c_ z*{gXu`pi4m>z`Q@=IX_LC}(uJw8=YJ-{E=X!S5!H;o%Q-eN!i<*oe$yj~GN&RZ^+BwtZ_wV4Z3=r|`k_|3f z@hsx_9EFew%~`ypDUAoI!}zSW@=FD zx|Oki@h)$bDVpXfr_%no2+vuv@q6FHDz!Ue76!(X`CJ^|JP5FNo2t{v`1XM|YYu~$ z@Zlwr_ZDb8)joK0=f5sKv3qZiB&o(3FTHP_vAOH4B7@J(9e0e>{@jQ>ac_24#-keN z8Hr9m-STrE*8g08H0G0T*;V0V&B4SvPJtDi!u)yMj>Ar4xp(^M2X~c(Ze?O!&FVi-my!R0;)nGct)VBT6V_IZz1& d14|k~EMm*60B=?{ka|WS3^2bxl5K$)M3+`@GcdCJ zU}Rtb>SkczJ#>(>!GMSLLiO?=+g~l^o?_L?JwzRqWDOm9t` z@by_!pI_4JiEK7}8i%xcohC2+oE$6qpGVpB47+%AYpwiU>AUO!-s~LgyQeP?0GbVQ oOMo{clL#}yoyc+^cf!DuMi7fwR|j~rvVqhy0wKuj%Rn3k06Szw-v9sr diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-detection-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-detection-role.json deleted file mode 100644 index 320452f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-detection-role.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "wafconfig", - "Effect": "Allow", - "Action": [ - "waf:GetLoggingConfiguration", - "waf:GetWebACL", - "wafv2:GetLoggingConfiguration", - "wafv2:GetWebACL", - "waf-regional:GetLoggingConfiguration", - "waf-regional:GetWebACL" - ], - "Resource": [ - "arn:*:waf::*:*", - "arn:*:wafv2:*:*:*/*/*", - "arn:*:waf-regional:*:*:*" - ] - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-remediation-role.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-remediation-role.json deleted file mode 100644 index da1d3ff..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled-remediation-role.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "wafconfig", - "Effect": "Allow", - "Action": [ - "waf:PutLoggingConfiguration", - "waf:GetLoggingConfiguration", - "waf:GetWebACL", - "wafv2:PutLoggingConfiguration", - "wafv2:GetLoggingConfiguration", - "wafv2:GetWebACL", - "waf-regional:PutLoggingConfiguration", - "waf-regional:GetLoggingConfiguration", - "waf-regional:GetWebACL" - ], - "Resource": [ - "*" - ] - }, - { - "Sid": "logs", - "Effect": "Allow", - "Action": [ - "logs:*" - ], - "Resource": [ - "*" - ] - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled.zip b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/custom-config-rules/waf-logging-enabled.zip deleted file mode 100644 index f699f66262bb0996ad0c38177268004eb62a82fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmWIWW@Zs#U|`^2D7Kmy#vyF z+wuClCimaNqNy+CRCcf4%VQ_oQha8mN8FpxDf^~Alxp#;h!j&Qb#-02UO`Z7d+Y&O zuiG!TGxR*&W@OMH{C85T`_i4+^*)QN^N-1$Jp5aYbzN>!&C6zmD5L)~Cmh=Tq*qtU zn=LTpxuDRroNseW8_!-`;kEpSZ@`SX(rndl7^X|^j6G%4kR2!SWjzPZL3{=zD9QP<;dk9YhH2REf6+nvhwe4$y8jyHOatDzsWA^$E#h; z?1t4PqMM3e%vHYIt2hOrEoj@5gOVyVtj#+HaCAfA`jAx3<@VgzNeT zr#zbGCAD4U;F{^m2c>>YGP$O6OP^(-2}eR{O;2!!*e<8*2iJ>uO9xH4Gb60QU8{P_ zJ@E(Ot-k4(Q!IAxoZIvGWz$bZiJVy`R~YIxc1((FwEFb=M(OJ0kKqY^D_dXhy4!Z~ zOP$Sp-+Z^rtapxoF8ot!wCnR^KC@F_0^YTRZMytrw|c+u?gL&`yYim++3u9AR4jeh z7rH*c#yHS-``(i$Y&)&Xz|tQICxlpO@LCsF(ja$+j?h>bag4g|R@2*duEZ}(N&{3;T2fV9O zQzu)gvl>rapIsutGcAtk!|s2Z9xmMQQ#1Pahd!h8lK1|WUnu-`al(^A&4k#yKiBY2 zGoGvXwsxODwAGWE=zZqzwI5EH?vhy2_{XrkzTRoxE`zLfT5p_e7s^~&_%+bQ;2&#% oHzSh>1MZvz%uEc73<@9$2w?dtz?+o~#AgITGa&5&%zF$B0L-G9761SM diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/customizations-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/customizations-config.yaml deleted file mode 100644 index 3d72383..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/customizations-config.yaml +++ /dev/null @@ -1,791 +0,0 @@ -customizations: - cloudFormationStacks: - - deploymentTargets: - accounts: - - Management - description: Sample stack description - name: Custom-S3-Stack - regions: - - us-east-1 - runOrder: 1 - template: cloudformation-templates/custom-s3-bucket.yaml - terminationProtection: false - parameters: - - name: pStringParameter - value: "test" - - name: pDynamicParameter - value: "{{resolve:ssm:/accelerator/lza-prefix}}" - - deploymentTargets: - organizationalUnits: - - Security - description: Second sample stack description - name: Custom-S3-Stack-2 - regions: - - us-east-1 - - us-west-2 - runOrder: 2 - template: cloudformation-templates/custom-s3-bucket.yaml - terminationProtection: true - - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Infrastructure - description: Third sample stack description - name: Custom-S3-Stack-3 - regions: - - us-east-1 - - us-west-2 - runOrder: 2 - template: cloudformation-templates/custom-s3-bucket.yaml - terminationProtection: true - cloudFormationStackSets: - - capabilities: [CAPABILITY_IAM, CAPABILITY_NAMED_IAM, CAPABILITY_AUTO_EXPAND] - deploymentTargets: - organizationalUnits: - - Infrastructure - accounts: - - Management - description: Sample stackset description - name: Custom-S3-Stackset - regions: - - us-east-1 - runOrder: 1 - template: cloudformation-templates/custom-s3-bucket.yaml - serviceCatalogPortfolios: - - name: My-Portfolio - provider: LZA - account: Management - regions: - - us-east-1 - - us-west-2 - shareTargets: - organizationalUnits: - - Security - shareTagOptions: true - portfolioAssociations: - - type: Group - name: Administrators - propagateAssociation: true - - type: Role - name: EC2-Default-SSM-AD-Role - - type: User - name: breakGlassUser01 - products: - - name: My-Product - description: Sample product by lZA. - owner: LZA - distributor: LZA - versions: - - name: v1 - description: product version is v1 - template: cloudformation-templates/custom-s3-bucket.yaml - deprecated: false - constraints: - launch: - type: LocalRole - role: testRole - tagUpdate: false - notifications: - - AWSAccelerator-ControlTowerNotification - support: - description: Please include account ID and provisioned product ID. - email: support@example.com - url: https://support.amazon.com - - name: Portfolio-2 - provider: LZA - account: SharedServices - regions: - - us-east-1 - shareTargets: - accounts: - - Audit - portfolioAssociations: - - type: Role - name: EC2-Default-SSM-AD-Role - propagateAssociation: true - - type: PermissionSet - name: PermissionSet1 - products: - - name: Product2 - description: Sample product by lZA. - owner: LZA - distributor: LZA - versions: - - name: v1 - description: product version is v1 - template: cloudformation-templates/custom-s3-bucket.yaml - deprecated: false - support: - description: Please include account ID and provisioned product ID. - email: support@example.com - url: https://support.amazon.com - -applications: - - name: appA - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - autoscaling: - name: appA-asg - maxSize: 2 - minSize: 0 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: appA-lt - healthCheckGracePeriod: 300 - # this is the only example with ELB so targetGroups are also specified - healthCheckType: ELB # EC2|ELB - # target group names should match the names from targetGroup section - targetGroups: - - appA-nlb-tg-1 - subnets: - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - maxInstanceLifetime: 86400 - - launchTemplate: - name: appA-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deleteOnTermination: true - description: secondary network interface - deviceIndex: 0 - groups: - # security group is from network-config.yaml under the same vpc - - SharedServices-appVpc-appSecurityGroup - networkCardIndex: 0 - # subnet is from network-config.yaml under the same vpc - subnetId: SharedServices-appVpc-PrivateSubnetA - # this path is relative to the config repository and the content should be in regular text. - # Its encoded in base64 before passing in to launch Template - userData: appConfigs/appA/launchTemplate/userData.sh - - targetGroups: - - name: appA-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - - name: appA-alb-tg-1 - port: 80 - protocol: HTTP - type: instance - attributes: - deregistrationDelay: 1200 - stickiness: true - # applies to application load balancer - stickinessType: app_cookie - algorithm: round_robin - slowStart: 120 - appCookieName: chocolate-chip - appCookieDuration: 4800 - lbCookieDuration: 4800 - healthCheck: - enabled: true - port: 80 - protocol: HTTP - networkLoadBalancer: - name: appA-nlb - scheme: internal - deletionProtection: false - subnets: - # subnets are from network-config.yaml under the same vpc - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - crossZoneLoadBalancing: true - listeners: - - name: appA-listener-1 - port: 80 - protocol: TCP - # target group names should match the names from targetGroup section - targetGroup: appA-nlb-tg-1 - - applicationLoadBalancer: - name: appA-alb-01 - scheme: internet-facing - subnets: - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - securityGroups: - - SharedServices-appVpc-appSecurityGroup - listeners: - - name: appA-listener-2 - port: 80 - protocol: HTTP - targetGroup: appA-alb-tg-1 - type: forward - - name: appB - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - autoscaling: - name: testTgAsg-asg - maxSize: 2 - minSize: 0 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: testTgAsg-lt - healthCheckGracePeriod: 300 - healthCheckType: EC2 # EC2|ELB - subnets: - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - maxInstanceLifetime: 86400 - - launchTemplate: - name: testTgAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - SharedServices-appVpc-appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - - targetGroups: - - name: testTgAsg-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - protocolVersion: HTTP1 - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - networkLoadBalancer: - name: testTgAsg-nlb - scheme: internal - deletionProtection: false - subnets: - # subnets are from network-config.yaml under the same vpc - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - crossZoneLoadBalancing: true - listeners: - - name: testTgAsg-listener-1 - port: 80 - protocol: TCP - # target group names should match the names from targetGroup section - targetGroup: testTgAsg-nlb-tg-1 - # minimum config for application target group in vpc created - - name: appC - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - targetGroups: - - name: appC-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - # target group and launch template created - - name: appD - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - targetGroups: - - name: appD-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - launchTemplate: - name: appDAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - SharedServices-appVpc-appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - # target group, launch template and autoscaling created. Target group is not tied to AutoScaling - - name: appE - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - targetGroups: - - name: appE-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - launchTemplate: - name: appEAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - SharedServices-appVpc-appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - autoscaling: - name: appE-asg - maxSize: 2 - minSize: 0 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: testTgAsg-lt - healthCheckGracePeriod: 300 - healthCheckType: EC2 # EC2|ELB - subnets: - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - maxInstanceLifetime: 86400 - # target group, launch template and autoscaling added. Target group is tied to Autoscaling - - name: appF - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - targetGroups: - - name: appF-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - launchTemplate: - name: appFAsg-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - # this kms key is in security-config.yaml under keyManagementService - kmsKeyId: appEbsKey - - deviceName: /dev/xvdg - ebs: - deleteOnTermination: true - encrypted: true - volumeSize: 20 - securityGroups: - # security group is from network-config.yaml under the same vpc - - SharedServices-appVpc-appSecurityGroup - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - - - name: appG - vpc: SharedServices-appVpc - deploymentTargets: - accounts: - - SharedServices - excludedRegions: - - us-west-2 - targetGroups: - - name: appG-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - - name: appG-nlb-tg-2 - port: 443 - protocol: TLS - type: instance - attributes: - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 443 - protocol: TCP - - name: appG-alb-tg-1 - port: 80 - protocol: HTTP - type: instance - healthCheck: - enabled: true - port: 80 - protocol: HTTP - - name: appG-alb-tg-2 - port: 443 - protocol: HTTPS - type: instance - algorithm: round_robin - attributes: - slowStart: 120 - healthCheck: - enabled: true - port: 443 - protocol: HTTPS - networkLoadBalancer: - name: appG-nlb-01 - scheme: internal - deletionProtection: false - subnets: - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - listeners: - - name: appG-listener-1 - port: 80 - protocol: TCP - targetGroup: appG-nlb-tg-1 - - name: appG-listener-2 - port: 443 - protocol: TLS - targetGroup: appG-nlb-tg-2 - certificate: cert1 - alpnPolicy: HTTP2Optional - sslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 - applicationLoadBalancer: - name: appG-alb-01 - scheme: internet-facing - subnets: - - SharedServices-appVpc-PrivateSubnetA - - SharedServices-appVpc-PrivateSubnetB - securityGroups: - - SharedServices-appVpc-appSecurityGroup - listeners: - - name: appG-listener-2 - port: 80 - protocol: HTTP - targetGroup: appG-alb-tg-1 - type: forward - - name: appG-alb-listener-2 - port: 443 - protocol: HTTPS - targetGroup: appG-alb-tg-2 - type: forward - certificate: cert1 - sslPolicy: ELBSecurityPolicy-2016-08 - # autoscaling group and launch template created using subnet shared from another account - - name: appH - # vpc name is taken from network-config.yaml under vpcs - vpc: SharedServices-Main - deploymentTargets: - accounts: - - Network - excludedRegions: - - us-west-2 - autoscaling: - name: appH-asg - maxSize: 1 - minSize: 1 - desiredSize: 1 - # Launch Template name should match name from launchTemplate section - launchTemplate: appH-lt - healthCheckGracePeriod: 300 - # this is the only example with ELB so targetGroups are also specified - healthCheckType: EC2 # EC2|ELB - # target group names should match the names from targetGroup section - subnets: - - SharedServices-App-C - maxInstanceLifetime: 86400 - - launchTemplate: - name: appH-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - deleteOnTermination: true - encrypted: true - securityGroups: - - SharedServices-Main-Rsyslog-sg - # this instance profile is in iam-config.yaml under roleSets - iamInstanceProfile: EC2-Default-SSM-AD-Role - # Local or public SSM parameter store lookup for Image ID - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - # this path is relative to the config repository and the content should be in regular text. - # Its encoded in base64 before passing in to launch Template - userData: appConfigs/appA/launchTemplate/userData.sh - metadataOptions: - httpTokens: required - httpPutResponseHopLimit: 2 - httpEndpoint: enabled - httpProtocolIpv6: disabled - instanceMetadataTags: disabled -firewalls: - instances: - - name: accelerator-firewall - configFile: instance-config.txt - licenseFile: license.lic - staticReplacements: - - key: CORP_CIDR_1 - value: 10.0.0.0/16 - - key: CORP_CIDR_2 - value: 192.168.0.0/16 - launchTemplate: - name: firewall-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - groups: - - Data - subnetId: Network-Inspection-A - - deviceIndex: 1 - associateElasticIp: true - description: Secondary - groups: - - Data - subnetId: Network-Inspection-A - securityGroups: [] - vpc: Network-Inspection - - name: cross-account-firewall - launchTemplate: - name: firewall-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - associateElasticIp: true - groups: - - SharedServices-Main-Rsyslog-sg - subnetId: SharedServices-App-A - securityGroups: [] - vpc: SharedServices-Main - - name: firewall-with-shared-subnet - launchTemplate: - name: firewall-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - associateElasticIp: true - groups: - - SharedServices-Main-Rsyslog-sg - subnetId: SharedServices-App-C - securityGroups: [] - vpc: SharedServices-Main - account: Network - autoscalingGroups: - - name: test-asg - autoscaling: - name: firewall-asg - minSize: 1 - maxSize: 4 - desiredSize: 2 - launchTemplate: test-asg - healthCheckGracePeriod: 300 - healthCheckType: EC2 - targetGroups: - - asg-target - subnets: - - Network-Inspection-A - - Network-Inspection-B - maxInstanceLifetime: 86400 - configFile: asg-config.txt - licenseFile: license.lic - launchTemplate: - name: asg - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - iamInstanceProfile: EC2-Default-SSM-AD-Role - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - groups: - - Data - - deviceIndex: 1 - description: Secondary - groups: - - Data - securityGroups: [] - vpc: Network-Inspection - managerInstances: - - name: accelerator-manager - launchTemplate: - name: manager-lt - blockDeviceMappings: - - deviceName: /dev/xvda - ebs: - encrypted: true - volumeSize: 20 - - deviceName: /dev/xvdb - ebs: - encrypted: true - volumeSize: 10 - imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - instanceType: t3.large - networkInterfaces: - - deviceIndex: 0 - description: Primary interface - groups: - - Data - subnetId: Network-Inspection-A - - deviceIndex: 1 - description: Secondary - groups: - - Data - subnetId: Network-Inspection-A - securityGroups: [] - vpc: Network-Inspection - targetGroups: - - name: instance-target - port: 6081 - protocol: GENEVE - type: instance - targets: - - accelerator-firewall - attributes: - targetFailover: rebalance - - name: shared-services-target - port: 6081 - protocol: GENEVE - type: instance - targets: - - cross-account-firewall - attributes: - targetFailover: rebalance - - name: asg-target - port: 6081 - protocol: GENEVE - type: instance diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-1.txt b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-1.txt deleted file mode 100644 index 5bf762a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-1.txt +++ /dev/null @@ -1,2 +0,0 @@ -badactor.com -virus.net \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-2.txt b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-2.txt deleted file mode 100644 index 8152a84..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dns-firewall-domain-lists/domain-list-2.txt +++ /dev/null @@ -1,2 +0,0 @@ -badactor.net -virus.com \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dynamic-partitioning/log-filters.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dynamic-partitioning/log-filters.json deleted file mode 100644 index d8c241a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/dynamic-partitioning/log-filters.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "/AWSAccelerator-SecurityHub", "s3Prefix": "security-hub" } -] diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/firewall-rules/rules.txt b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/firewall-rules/rules.txt deleted file mode 100644 index 2921a6a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/firewall-rules/rules.txt +++ /dev/null @@ -1,16 +0,0 @@ -##################################################################################################################### -# List of Suricata compatible Intrusion prevention system (IPS) rules. # -# Suricata is an open source network IPS that includes a standard rule-based language for traffic inspection. # -# Please refer for https://suricata.readthedocs.io/en/suricata-6.0.2/rules/intro.html Suricata rule syntax. # -# Invalid rule syntax will cause LZA pipeline failure, please review rule syntax of each line before using. # -# This file can have one rule definition per line. -# A line starts with suricata supported action types (alert, pass, drop, reject, rejectsrc, rejectdst, rejectboth) # -# are considered to be rule entry. # -##################################################################################################################### - - -pass ip 10.1.0.0/16 any -> 10.0.0.0/16 any (sid:100;) -drop ip any any <> any any (sid:101;) -alert tcp any any -> 1.1.1.1/32 80 (sid:102;msg:"example message";) -drop tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"example.com"; startswith; nocase; endswith; msg:"matching TLS denylisted FQDNs"; flow:to_server, established; sid:103; rev:1;) -drop http $HOME_NET any -> $EXTERNAL_NET any (http.host; content:"example.com"; startswith; endswith; msg:"matching HTTP denylisted FQDNs"; flow:to_server, established; sid:104; rev:1;) \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/global-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/global-config.yaml deleted file mode 100644 index 374659c..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/global-config.yaml +++ /dev/null @@ -1,341 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -enabledRegions: - - *HOME_REGION - - us-west-2 -managementAccountAccessRole: AWSControlTowerExecution -cloudwatchLogRetentionInDays: 3653 -acceleratorMetadata: - enable: true - account: LogArchive - readOnlyAccessRoleArns: - - arn:aws:iam::111111111111:role/test-access-role -snsTopics: - deploymentTargets: - organizationalUnits: - - Root - topics: - - name: Security - emailAddresses: - - notify-security@example.com -terminationProtection: true -lambda: - encryption: - useCMK: true - deploymentTargets: - accounts: - - Management - - Network - organizationalUnits: - - Security - excludedRegions: - - us-west-2 -controlTower: - enable: true - landingZone: - version: '3.3' - logging: - loggingBucketRetentionDays: 365 - accessLoggingBucketRetentionDays: 3650 - organizationTrail: true - security: - enableIdentityCenterAccess: true - controls: - - identifier: AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS - enable: true - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - identifier: AWS-GR_EBS_OPTIMIZED_INSTANCE - enable: true - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - identifier: AWS-GR_RESTRICTED_COMMON_PORTS - enable: false - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - identifier: AWS-GR_RDS_SNAPSHOTS_PUBLIC_PROHIBITED - enable: false - deploymentTargets: - organizationalUnits: - - SecureWorkloads -cdkOptions: - centralizeBuckets: true - useManagementAccessRole: true - customDeploymentRole: AWSA-Deployment - forceBootstrap: true -s3: - encryption: - createCMK: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 -logging: - account: LogArchive - centralizedLoggingRegion: us-west-2 - - centralLogBucket: - importedBucket: - name: existing-central-log-bucket - applyAcceleratorManagedBucketPolicy: false - createAcceleratorManagedKey: true - customPolicyOverrides: - s3Policy: bucket-policies/full-central-log-bucket.json - kmsPolicy: kms/full-central-logs-bucket-key-policy.json - # s3ResourcePolicyAttachments: - # - policy: bucket-policies/central-log-bucket.json - # kmsResourcePolicyAttachments: - # - policy: kms/central-logs-bucket-key-policy.json - lifecycleRules: - - enabled: true - id: CentralLogsBucketLifecycleRule-01 - abortIncompleteMultipartUpload: 15 - expiration: 3563 - expiredObjectDeleteMarker: false - noncurrentVersionExpiration: 3653 - noncurrentVersionTransitions: - - storageClass: GLACIER - transitionAfter: 365 - transitions: - - storageClass: GLACIER - transitionAfter: 365 - - enabled: true - id: CentralLogsBucketLifecycleRule-02 - abortIncompleteMultipartUpload: 15 - expiration: 3563 - expiredObjectDeleteMarker: false - noncurrentVersionExpiration: 3653 - noncurrentVersionTransitions: - - storageClass: GLACIER - transitionAfter: 365 - transitions: - - storageClass: GLACIER - transitionAfter: 365 - prefix: 'guardduty' - - enabled: true - id: CentralLogsBucketLifecycleRule-03 - abortIncompleteMultipartUpload: 15 - expiredObjectDeleteMarker: true - noncurrentVersionExpiration: 3653 - noncurrentVersionTransitions: - - storageClass: GLACIER - transitionAfter: 365 - transitions: - - storageClass: GLACIER - transitionAfter: 365 - prefix: 'macie' - accessLogBucket: - enable: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 - importedBucket: - name: existing-access-logs-bucket-${ACCOUNT_ID}-${REGION} - customPolicyOverrides: - policy: bucket-policies/full-access-log-bucket.json - # s3ResourcePolicyAttachments: - # - policy: bucket-policies/access-logs-bucket.json - lifecycleRules: [] - elbLogBucket: - importedBucket: - name: existing-elb-logs-bucket-${ACCOUNT_ID}-${REGION} - applyAcceleratorManagedBucketPolicy: true - # customPolicyOverrides: - # policy: bucket-policies/full-elb-log-bucket.json - s3ResourcePolicyAttachments: - - policy: bucket-policies/elb-logs-bucket.json - lifecycleRules: [] - cloudtrail: - enable: true - organizationTrail: false - accountTrails: - - name: AWSAccelerator-Account-CloudTrail - regions: - - us-east-1 - deploymentTargets: - organizationalUnits: - - Root - settings: - multiRegionTrail: true - globalServiceEvents: true - managementEvents: true - s3DataEvents: true - lambdaDataEvents: true - sendToCloudWatchLogs: true - apiErrorRateInsight: false - apiCallRateInsight: false - - sessionManager: - sendToCloudWatchLogs: false - sendToS3: true - cloudwatchLogs: - encryption: - useCMK: true - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - excludedRegions: - - us-west-2 - dataProtection: - deploymentTargets: - accounts: - - Management - organizationalUnits: - - Security - - Infrastructure - excludedRegions: - - us-west-2 - managedDataIdentifiers: - categories: - - Credentials - enable: true - replaceLogDestinationArn: arn:aws:logs:us-east-1:111111111111:destination/log-destination - dynamicPartitioning: dynamic-partitioning/log-filters.json - exclusions: - - organizationalUnits: - - Infrastructure - excludeAll: true - - organizationalUnits: - - Root - logGroupNames: - - test1/* - - accounts: - - Management - regions: - - us-east-1 - logGroupNames: - - test/* -tags: - - key: Environment - value: Dev - - key: ReplacementOne - value: "{{UNDEFINED_PLACEHOLDER}}" - - key: ReplacementTwo - value: "{{DEFINED_PLACEHOLDER}}" - - key: MgmtAccount - value: "{{account Management}}" - - key: DynamicLookup - value: "{{resolve:ssm:/accelerator/lza-prefix}}" -limits: - - serviceCode: lambda - quotaCode: L-B99A9384 - desiredValue: 1000 - deploymentTargets: - organizationalUnits: - - Root - regions: - - us-west-2 - - serviceCode: iam - quotaCode: L-4019AD8B - desiredValue: 15 - deploymentTargets: - accounts: - - Management -reports: - costAndUsageReport: - additionalArtifacts: - - ATHENA - - QUICKSIGHT - compression: Parquet - format: Parquet - reportName: accelerator-cur - s3Prefix: cur - timeUnit: DAILY - refreshClosedReports: true - reportVersioning: CREATE_NEW_REPORT - budgets: - - deploymentTargets: - accounts: - - Management - name: accel-budget - timeUnit: MONTHLY - type: COST - amount: 2000 - includeUpfront: true - includeTax: true - includeSupport: true - includeSubscription: true - includeRecurring: true - includeOtherSubscription: true - includeDiscount: true - includeCredit: false - includeRefund: false - useBlended: false - useAmortized: false - unit: USD - notifications: - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 100 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - address: myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 90 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - recipients: - - myemail+pa-budg@example.com - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 80 - comparisonOperator: GREATER_THAN - subscriptionType: SNS - address: arn:aws:sns:us-east-1:111111111111:sample-budget - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 75 - comparisonOperator: GREATER_THAN - subscriptionType: SNS - recipients: - - arn:aws:sns:us-east-1:111111111111:sample-budget - - type: ACTUAL - thresholdType: PERCENTAGE - threshold: 50 - comparisonOperator: GREATER_THAN - subscriptionType: EMAIL - recipients: - - myemail+pa-budg@example.com - - myemail+pa1-budg@example.com -backup: - vaults: - - name: BackupVault - deploymentTargets: - organizationalUnits: - - Root - - name: InfrastructureVault - deploymentTargets: - accounts: - - Management - policy: backup-vault-policies/infrastructure-vault-policy.json -ssmParameters: - - deploymentTargets: - organizationalUnits: - - Infrastructure - accounts: - - Management - parameters: - - name: parameterTest - path: /my/parameter/structure - value: parameterTestValue - - name: someOtherName - path: /my/account/structure - value: someOtherValue - -ssmInventory: - enable: true - deploymentTargets: - organizationalUnits: - - Root \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-config.yaml deleted file mode 100644 index 8aca6d3..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-config.yaml +++ /dev/null @@ -1,264 +0,0 @@ -policySets: - - deploymentTargets: - organizationalUnits: - - Root - policies: - - name: Default-Boundary-Policy - policy: iam-policies/boundary-policy.json - - deploymentTargets: - organizationalUnits: - - Root - identityCenterDependency: true - policies: - - name: lzaManagedPolicy01 - policy: iam-policies/ResourceConfigurationCollectorPolicy-policy.json - - name: lzaManagedPolicy02 - policy: iam-policies/ResourceConfigurationCollectorPolicy-policy.json -roleSets: - - deploymentTargets: - organizationalUnits: - - Root - roles: - - name: EC2-Default-SSM-AD-Role - instanceProfile: true - assumedBy: - - type: service - principal: ec2.amazonaws.com - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonSSMDirectoryServiceAccess - - CloudWatchAgentServerPolicy - boundaryPolicy: Default-Boundary-Policy - - name: Backup-Role - assumedBy: - - type: service - principal: backup.amazonaws.com - - type: account - principal: '123456789012' - policies: - awsManaged: - - service-role/AWSBackupServiceRolePolicyForBackup - - service-role/AWSBackupServiceRolePolicyForRestores - - deploymentTargets: - organizationalUnits: - - Root - # excludedAccounts: - # - Management - roles: - - name: Network-Security-Role - instanceProfile: true - assumedBy: - - type: principalArn - principal: 'arn:aws:iam::444455556666:role/test-access-role' - externalIds: - - '111122223333' - policies: - awsManaged: - - AmazonSSMManagedInstanceCore - - AmazonCognitoReadOnly - boundaryPolicy: Default-Boundary-Policy -groupSets: - - deploymentTargets: - organizationalUnits: - - Root - groups: - - name: Administrators - policies: - awsManaged: - - AdministratorAccess -identityCenter: - name: identityCenter1 - delegatedAdminAccount: Audit - identityCenterPermissionSets: - - name: PermissionSet1 - policies: - awsManaged: - - arn:aws:iam::aws:policy/AdministratorAccess - - arn:aws:iam::aws:policy/CloudFrontFullAccess - - PowerUserAccess - - ReadOnlyAccess - customerManaged: - - ResourceConfigurationCollectorPolicy - acceleratorManaged: - - lzaManagedPolicy01 - - lzaManagedPolicy02 - inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - permissionsBoundary: - # customerManagedPolicy: - # name: lzaManagedPolicy01 - # path: / - awsManagedPolicyName: PowerUserAccess - # sessionDuration: 100 - - name: PermissionSet2 - policies: - awsManaged: - - PowerUserAccess - - ReadOnlyAccess - customerManaged: - - ResourceConfigurationCollectorPolicy - acceleratorManaged: - - lzaManagedPolicy01 - # - lzaManagedPolicy02 - inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - permissionsBoundary: - customerManagedPolicy: - name: lzaManagedPolicy01 - path: / - # awsManagedPolicyName: PowerUserAccess - sessionDuration: 60 - identityCenterAssignments: - - name: Assignment1 - permissionSetName: PermissionSet1 - principalId: 'abcd1234-1001-70f0-9c12-56a6aa967ca4' - principalType: USER - principals: - - type: USER - name: lza-accelerator-user-01 - - type: GROUP - name: lza-accelerator-group-01 - - type: USER - name: lza-accelerator-user-02 - - type: GROUP - name: lza-accelerator-group-02 - deploymentTargets: - organizationalUnits: - - Root - excludedAccounts: - - Management - - name: Assignment2 - permissionSetName: PermissionSet1 - principalId: '1234abcd-1001-70f0-9c12-56a6aa967ca4' - principalType: GROUP - deploymentTargets: - organizationalUnits: - - Security -userSets: - - deploymentTargets: - accounts: - - Management - users: - - username: breakGlassUser01 - group: Administrators - boundaryPolicy: Default-Boundary-Policy - - username: breakGlassUser02 - group: Administrators - boundaryPolicy: Default-Boundary-Policy -secretSets: - - deploymentTargets: - accounts: - - SharedServices - secrets: - - name: managedActiveDirectoryAdminUserSecret - description: Managed AD admin user secret - # kmsKeyName: Key1 - userName: Admin - passwordPolicy: - excludeUppercase: false - excludeLowercase: false - excludeNumbers: false - requireEachIncludedType: true - includeSpace: false - passwordLength: 32 - excludePunctuation: false - excludeCharacters: '@#' -managedActiveDirectories: - - name: AcceleratorManagedActiveDirectory - type: AWS Managed Microsoft AD - account: SharedServices - region: us-east-1 - dnsName: example.com - netBiosDomainName: example - description: Example managed AD - edition: Standard - vpcSettings: - vpcName: SharedServices-Main - subnets: - - SharedServices-App-A - - SharedServices-App-B - resolverRuleName: example-rule - secretConfig: - account: Audit - region: us-east-1 - adminSecretName: my-admin-002 - sharedOrganizationalUnits: - organizationalUnits: - - Root - excludedAccounts: - - Audit - # sharedAccounts: - # - Network - # - Management - logs: - groupName: /aws/directoryservice/AcceleratorManagedActiveDirectory - retentionInDays: 30 - activeDirectoryConfigurationInstance: - instanceType: t3.large - vpcName: SharedServices-Main - subnetName: SharedServices-App-A - imagePath: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base - securityGroupInboundSources: - - 10.4.0.0/16 - # securityGroupInboundConfigs: - # - description: Allow RDP and HTTPS Traffic Inbound - # types: - # - RDP - # - HTTPS - # sources: - # - 10.4.0.0/16 - # securityGroupName: ActiveDirectoryConfigInstanceSG - instanceRole: EC2-Default-SSM-AD-Role - enableTerminationProtection: false - userDataScripts: - - scriptName: JoinDomain - scriptFilePath: ad-config-scripts/Join-Domain.ps1 - - scriptName: AWSQuickStart - scriptFilePath: ad-config-scripts/AWSQuickStart.psm1 - - scriptName: ADGroupSetup - scriptFilePath: ad-config-scripts/AD-group-setup.ps1 - - scriptName: ADUserSetup - scriptFilePath: ad-config-scripts/AD-user-setup.ps1 - - scriptName: ADUserGroupSetup - scriptFilePath: ad-config-scripts/AD-user-group-setup.ps1 - - scriptName: ADGroupGrantPermissionsSetup - scriptFilePath: ad-config-scripts/AD-group-grant-permissions-setup.ps1 - - scriptName: ADConnectorPermissionsSetup - scriptFilePath: ad-config-scripts/AD-connector-permissions-setup.ps1 - - scriptName: ConfigurePasswordPolicy - scriptFilePath: ad-config-scripts/Configure-password-policy.ps1 - adGroups: - - aws-Provisioning - - aws-Billing - adPerAccountGroups: - - '*-Admin' - - '*-PowerUser' - - '*-View' - adConnectorGroup: ADConnector-grp - adPasswordPolicy: - history: 24 - maximumAge: 90 - minimumAge: 1 - minimumLength: 14 - complexity: true - reversible: false - failedAttempts: 6 - lockoutDuration: 30 - lockoutAttemptsReset: 30 - adUsers: - - name: adconnector-user-01 - email: example-adconnector-user@example.com - groups: - - ADConnector-grp - - name: user1-01 - email: example-user1@example.com - groups: - - aws-Provisioning - - '*-View' - - '*-Admin' - - '*-PowerUser' - - AWS Delegated Administrators - - name: user2-002 - email: example-user2@example.com - groups: - - aws-Provisioning - - '*-View' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/ResourceConfigurationCollectorPolicy-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/ResourceConfigurationCollectorPolicy-policy.json deleted file mode 100644 index 94280a6..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/ResourceConfigurationCollectorPolicy-policy.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:CreateBucket", - "s3:PutBucketPolicy" - ], - "Resource": "arn:aws:s3:::rcc-bucket-trustedservice-do-not-delete-*" - }, - { - "Effect": "Allow", - "Action": [ - "s3:ListAllMyBuckets" - ], - "Resource": "arn:aws:s3:::*" - }, - { - "Effect": "Allow", - "Action": [ - "config:DescribeConfigurationRecorders", - "config:DescribeConfigurationRecorderStatus", - "config:PutConfigurationRecorder", - "config:StartConfigurationRecorder", - "config:DescribeDeliveryChannels", - "config:DescribeDeliveryChannelStatus", - "config:PutDeliveryChannel", - "config:DescribeAggregationAuthorizations", - "config:PutAggregationAuthorization", - "config:ListDiscoveredResources", - "config:BatchGetResourceConfig" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:GetRole", - "iam:PassRole", - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - "iam:GetServiceLinkedRoleDeletionStatus" - ], - "Resource": "arn:aws:iam::*:role/aws-service-role/config.amazonaws.com/*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/boundary-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/boundary-policy.json deleted file mode 100644 index 18de2ad..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/boundary-policy.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - }, - { - "Effect": "Deny", - "NotAction": [ - "iam:CreateVirtualMFADevice", - "iam:DeleteVirtualMFADevice", - "iam:ListVirtualMFADevices", - "iam:EnableMFADevice", - "iam:ResyncMFADevice", - "iam:ListAccountAliases", - "iam:ListUsers", - "iam:ListSSHPublicKeys", - "iam:ListAccessKeys", - "iam:ListServiceSpecificCredentials", - "iam:ListMFADevices", - "iam:GetAccountSummary", - "sts:GetSessionToken" - ], - "Resource": "*", - "Condition": { - "Bool": { - "aws:MultiFactorAuthPresent": "false", - "aws:ViaAWSService": "false" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/sso-permissionSet1-inline-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/sso-permissionSet1-inline-policy.json deleted file mode 100644 index 7e38a10..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/iam-policies/sso-permissionSet1-inline-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Statement1", - "Effect": "Allow", - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "*" - ] - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/central-logs-bucket-key-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/central-logs-bucket-key-policy.json deleted file mode 100644 index 8cab2ee..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/central-logs-bucket-key-policy.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Policy statement 1 from file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Resource": "*" - }, - { - "Sid": "Policy statement 2 from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "kms:ListAliases" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/full-central-logs-bucket-key-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/full-central-logs-bucket-key-policy.json deleted file mode 100644 index 12a96e8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/full-central-logs-bucket-key-policy.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" - }, - "Action": "kms:*", - "Resource": "*" - }, - { - "Sid": "Enable IAM User Permissions", - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::457936900343:root" - }, - "Action": "kms:*", - "Resource": "*" - }, - { - "Sid": "Allow S3 use of the key - From file", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateRandom", - "kms:GetKeyPolicy", - "kms:GetKeyRotationStatus", - "kms:ListAliases", - "kms:ListGrants", - "kms:ListKeyPolicies", - "kms:ListKeys", - "kms:ListResourceTags", - "kms:ListRetirableGrants", - "kms:ReEncryptFrom", - "kms:ReEncryptTo" - ], - "Resource": "*" - }, - { - "Sid": "Allow AWS Services to encrypt and describe logs - From file", - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com", - "config.amazonaws.com", - "delivery.logs.amazonaws.com", - "ssm.amazonaws.com" - ] - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo" - ], - "Resource": "*" - }, - { - "Sid": "Allow Organization use of the key - From file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - "kms:ListAliases" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "Allow Macie service to use the encryption key - From file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ], - "Resource": "*" - }, - { - "Sid": "Allow Guardduty service to use the encryption key - From file", - "Effect": "Allow", - "Principal": { - "Service": "guardduty.amazonaws.com" - }, - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ], - "Resource": "*" - }, - { - "Sid": "Policy statement 1 from file", - "Effect": "Allow", - "Principal": { - "Service": "macie.amazonaws.com" - }, - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*" - ], - "Resource": "*" - }, - { - "Sid": "Policy statement 2 from file", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "kms:ListAliases", - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/kms-policy-01.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/kms-policy-01.json deleted file mode 100644 index ef8e3bc..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/kms/kms-policy-01.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn:aws:iam::111111111111:root" - }, - "Action": "kms:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/network-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/network-config.yaml deleted file mode 100644 index 08a5135..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/network-config.yaml +++ /dev/null @@ -1,2274 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -defaultVpc: - delete: true - excludeAccounts: [] - excludeRegions: [] - -transitGatewayConnects: - - name: Network-Inspection - region: *HOME_REGION - transitGateway: - name: Network-Main - account: Network - vpc: - vpcName: Network-Endpoints - vpcAttachment: Network-Endpoints - options: - protocol: gre - tags: - - key: Environment - value: CentralInspection - -transitGateways: - - name: Network-Main - account: Network - region: *HOME_REGION - transitGatewayCidrBlocks: - - 10.0.0.0/20 - - 10.5.0.0/20 - transitGatewayIpv6CidrBlocks: - - 2001:db8::/64 - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-Core - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: ::/0 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 10.200.0.0/16 - attachment: - directConnectGatewayName: Network-DXGW - - destinationCidrBlock: 10.30.0.0/16 - attachment: - vpnConnectionName: accelerator-vpn - - destinationCidrBlock: 1.1.1.1/32 - blackhole: true - - destinationCidrBlock: fd00::/8 - blackhole: true - - destinationPrefixList: accelerator-prefix-list - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 10.40.0.0/16 - attachment: - vpnConnectionName: same-account-tgw-vpn - - destinationCidrBlock: 10.50.0.0/16 - attachment: - vpnConnectionName: same-account-tgw-vpn - - name: Network-Main-Segregated - routes: [] - - name: Network-Main-Shared - routes: - - destinationCidrBlock: 10.200.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - destinationPrefixList: accelerator-prefix-list - attachment: - vpnConnectionName: cross-account-tgw-vpn - - name: Network-Main-Standalone - routes: - - destinationPrefixList: accelerator-prefix-list - attachment: - vpnConnectionName: same-account-tgw-vpn - - name: Network-Main-2 - account: Network - region: *HOME_REGION - transitGatewayCidrBlocks: - - 10.50.0.0/20 - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65521 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: Network-Main-2-Core - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - vpcName: Network-Endpoints - account: Network - - destinationCidrBlock: 1.1.1.1/32 - blackhole: true - - destinationPrefixList: accelerator-prefix-list - attachment: - vpcName: Network-Endpoints - account: Network - - name: Network-Main-2-Segregated - routes: [] - - name: Network-Main-2-Shared - routes: [] - - name: Network-Main-2-Standalone - routes: [] - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - transitGatewayIpv6CidrBlocks: - - 5000:aaaa::/64 - shareTargets: - organizationalUnits: - - Infrastructure - asn: 64512 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTables: - - name: SharedServices-Main-Core - routes: [] - - name: SharedServices-Main-Segregated - routes: [] - - name: SharedServices-Main-Shared - routes: - - destinationCidrBlock: 10.100.0.0/16 - attachment: - transitGatewayPeeringName: Network-Main-And-SharedServices-Main-Peering - - name: SharedServices-Main-Standalone - routes: [] - - name: SharedServices-Main-Test01 - routes: - - destinationPrefixList: accelerator-prefix-list-001 - blackhole: true - - name: SharedServices-SecondaryTgw - account: SharedServices - region: *HOME_REGION - shareTargets: - organizationalUnits: - - Infrastructure - asn: 65524 - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: disable - routeTables: [] -transitGatewayPeering: - - name: Network-Main-And-SharedServices-Main-Peering - requester: - transitGatewayName: SharedServices-Main - account: SharedServices - region: *HOME_REGION - routeTableAssociations: SharedServices-Main-Shared - tags: - - key: Name - value: Network-Main-And-SharedServices-Main-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - - key: Organization - value: WWPS - - key: Team - value: Development - accepter: - transitGatewayName: Network-Main - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-Shared - autoAccept: true - applyTags: true - - name: Network-Main-2-And-SharedServices-Main-Peering - autoAccept: false - requester: - transitGatewayName: SharedServices-Main - account: SharedServices - region: *HOME_REGION - routeTableAssociations: SharedServices-Main-Shared - tags: - - key: Name - value: Network-Main-2-And-SharedServices-Main-Peering - - key: CostCenter - value: TSD-LZA - - key: Company - value: AWS - - key: Organization - value: WWPS - - key: Team - value: Development - accepter: - transitGatewayName: Network-Main-2 - account: Network - region: *HOME_REGION - routeTableAssociations: Network-Main-2-Core - autoAccept: true - applyTags: true - -dhcpOptions: - - name: accelerator-dhcp-opts - accounts: - - Network - regions: - - *HOME_REGION - domainName: example.com - domainNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNameServers: - - 1.1.1.1 - - 2.2.2.2 - netbiosNodeType: 2 - ntpServers: - - 1.1.1.1 - - 2.2.2.2 - - name: test-ipv6 - accounts: - - Network - regions: - - us-east-1 - domainName: example.com - domainNameServers: - - 192.168.1.2 - - 192.168.2.2 - - 10.0.0.2 - - 10.0.1.2 - - ::1 - - ::2 - - ::3 - - ::4 - ntpServers: - - 192.168.1.2 - - 192.168.2.2 - - 10.0.0.2 - - 10.0.1.2 - - ::1 - - ::2 - - ::3 - - ::4 - netbiosNameServers: - - 192.168.1.2 - - 192.168.2.2 - - 10.0.0.2 - - 10.0.1.2 -centralNetworkServices: - delegatedAdminAccount: Network - ipams: - - name: accelerator-ipam - region: *HOME_REGION - description: Accelerator IPAM - operatingRegions: - - *HOME_REGION - - us-west-2 - pools: - - name: &BASE_POOL base-pool - description: accelerator-base - provisionedCidrs: - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - - name: home-region-pool - description: Pool for us-east-1 - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/16 - - 10.2.0.0/16 - sourceIpamPool: *BASE_POOL - shareTargets: - organizationalUnits: - - Infrastructure - - name: home-region-prod-pool - description: Pool for prod environment - allocationResourceTags: - - key: env - value: prod - locale: *HOME_REGION - provisionedCidrs: - - 10.0.0.0/24 - - 10.2.0.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - sourceIpamPool: home-region-pool - - name: west-region-pool - description: Pool for us-west-2 - locale: us-west-2 - provisionedCidrs: - - 10.1.0.0/16 - sourceIpamPool: *BASE_POOL - shareTargets: - organizationalUnits: - - Infrastructure - gatewayLoadBalancers: - - name: Accelerator-GWLB - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - deletionProtection: true - targetGroup: instance-target - endpoints: - - name: Endpoint-A - account: Network - subnet: Network-Inspection-A - vpc: Network-Inspection - - name: Endpoint-B - account: Network - subnet: Network-Inspection-B - vpc: Network-Inspection - - name: Accelerator-GWLB-secondary - subnets: - - Network-Secondary-A - - Network-Secondary-B - vpc: Network-Secondary - deletionProtection: true - endpoints: - - name: Secondary-Endpoint-A - account: Network - subnet: Network-Secondary-A - vpc: Network-Secondary - - name: Secondary-Endpoint-B - account: Network - subnet: Network-Secondary-B - vpc: Network-Secondary - - name: SharedServices-Endpoint-B #Need to use us-east-1b in Shared Services - account: SharedServices - subnet: SharedServices-App-B - vpc: SharedServices-Main - - name: SharedServices-Endpoint-C #Need to use us-east-1c in Shared Services - account: SharedServices - subnet: SharedServices-App-C - vpc: SharedServices-Main - - name: Accelerator-GWLB-NonDelegated - subnets: - - SharedServices-App-A - - SharedServices-App-B - account: SharedServices - vpc: SharedServices-Main - deletionProtection: true - targetGroup: shared-services-target - endpoints: - - name: Shared-Endpoint-A - account: SharedServices - subnet: SharedServices-App-A - vpc: SharedServices-Main - - name: Shared-Endpoint-B - account: SharedServices - subnet: SharedServices-App-B - vpc: SharedServices-Main - - name: Shared-Secondary-Endpoint-D - account: Network - vpc: Network-Secondary - subnet: Network-Secondary-D - - name: Shared-Secondary-Endpoint-A - account: Network - vpc: Network-Secondary - subnet: Network-Secondary-A - networkFirewall: - firewalls: - - name: accelerator-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Inspection-A - - Network-Inspection-B - vpc: Network-Inspection - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - - name: az-id-firewall - firewallPolicy: accelerator-policy - subnets: - - Network-Secondary-C - - Network-Secondary-D - vpc: Network-Secondary - loggingConfiguration: - - destination: s3 - type: ALERT - - destination: cloud-watch-logs - type: FLOW - policies: - - name: accelerator-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulRuleGroups: - - name: accelerator-rule-group - - name: domain-list-group - shareTargets: - organizationalUnits: - - Infrastructure - - name: accelerator-strict-policy - regions: - - *HOME_REGION - firewallPolicy: - statelessDefaultActions: ['aws:forward_to_sfe'] - statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - statefulEngineOptions: STRICT_ORDER - statefulRuleGroups: - - name: accelerator-strict-rule-group - priority: 100 - shareTargets: - organizationalUnits: - - Infrastructure - rules: - - name: accelerator-suricata-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-strict-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - statefulRuleOptions: STRICT_ORDER - rulesSource: - rulesFile: firewall-rules/rules.txt - ruleVariables: - ipSets: - - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - - name: HOME_NET - definition: - - '80' - - '443' - - name: accelerator-rule-group - regions: - - *HOME_REGION - capacity: 100 - type: STATEFUL - ruleGroup: - rulesSource: - statefulRules: - - action: PASS - header: - destination: 10.0.0.0/16 - destinationPort: ANY - direction: FORWARD - protocol: IP - source: 10.1.0.0/16 - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['100'] - - action: DROP - header: - destination: ANY - destinationPort: ANY - direction: ANY - protocol: IP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: sid - settings: ['101'] - - action: ALERT - header: - destination: 1.1.1.1/32 - destinationPort: '80' - direction: FORWARD - protocol: TCP - source: ANY - sourcePort: ANY - ruleOptions: - - keyword: msg - settings: ['"example message"'] - - keyword: sid - settings: ['102'] - - name: domain-list-group - regions: - - *HOME_REGION - capacity: 10 - type: STATEFUL - ruleGroup: - rulesSource: - rulesSourceList: - generatedRulesType: DENYLIST - targets: ['.example.com'] - targetTypes: ['TLS_SNI', 'HTTP_HOST'] - ruleVariables: - ipSets: - name: HOME_NET - definition: - - 10.0.0.0/16 - - 10.1.0.0/16 - portSets: - name: HOME_NET - definition: - - '80' - - '443' - route53Resolver: - endpoints: - - name: accelerator-inbound - type: INBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - - name: accelerator-outbound - type: OUTBOUND - vpc: Network-Endpoints - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - rules: - - name: example-rule - domainName: example.com - targetIps: - - ip: 1.1.1.1 - port: '5353' # only include if targeting a non-standard DNS port - - ip: 2.2.2.2 - shareTargets: - organizationalUnits: - - Infrastructure - - name: inbound-target-rule - domainName: aws.internal.domain - inboundEndpointTarget: accelerator-inbound # This endpoint must be listed in the configuration before the outbound endpoint - queryLogs: - name: accelerator-query-logs - destinations: - - s3 - - cloud-watch-logs - shareTargets: - organizationalUnits: - - Infrastructure - firewallRuleGroups: - - name: accelerator-block-group - regions: - - *HOME_REGION - rules: - - name: nxdomain-block-rule - action: BLOCK - customDomainList: dns-firewall-domain-lists/domain-list-1.txt - priority: 100 - blockResponse: NXDOMAIN - # - name: override-block-rule - # action: BLOCK - # customDomainList: dns-firewall-domain-lists/domain-list-2.txt - # priority: 200 - # blockResponse: OVERRIDE - # blockOverrideDomain: amazon.com - # blockOverrideTtl: 3600 - - name: managed-rule - action: BLOCK - managedDomainList: AWSManagedDomainsBotnetCommandandControl - priority: 300 - blockResponse: NODATA - shareTargets: - organizationalUnits: - - Infrastructure -prefixLists: - - name: accelerator-prefix-list - accounts: - - Network - - SharedServices - regions: - - *HOME_REGION - addressFamily: 'IPv4' - maxEntries: 1 - entries: - - 10.1.0.1/32 - - name: accelerator-prefix-list-001 - accounts: - - SharedServices - regions: - - *HOME_REGION - addressFamily: 'IPv4' - maxEntries: 1 - entries: - - 10.100.0.1/32 - - name: accelerator-prefix-list-ipv6 - deploymentTargets: - organizationalUnits: - - Infrastructure - addressFamily: IPv6 - maxEntries: 10 - entries: - - 2400:cb00::/32 - - 2606:4700::/32 - - 2803:f800::/32 - - 2405:b500::/32 - - 2405:8100::/32 - - 2a06:98c0::/29 - - 2c0f:f248::/32 - -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json - -vpcs: - - name: SharedServices-Ipam-East - account: SharedServices - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: SharedServices-A-Rt - routes: [] - - name: SharedServices-B-Rt - routes: [] - subnets: - - name: SharedServices-A - availabilityZone: a - routeTable: SharedServices-A-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - - name: SharedServices-B - availabilityZone: b - routeTable: SharedServices-B-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - - - name: SharedServices-Ipam-West - account: SharedServices - region: us-west-2 - ipamAllocations: - - ipamPoolName: west-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: SharedServices-A-Rt - routes: [] - - name: SharedServices-B-Rt - routes: [] - subnets: - - name: SharedServices-A - availabilityZone: a - routeTable: SharedServices-A-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - - name: SharedServices-B - availabilityZone: b - routeTable: SharedServices-B-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - - - name: Network-Ipam-West - account: Network - region: us-west-2 - ipamAllocations: - - ipamPoolName: west-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: Network-West-A-Rt - routes: [] - - name: Network-West-B-Rt - routes: [] - subnets: - - name: Network-West-A - availabilityZone: a - routeTable: Network-West-A-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - - name: Network-West-B - availabilityZone: b - routeTable: Network-West-B-Rt - ipamAllocation: - ipamPoolName: west-region-pool - netmaskLength: 28 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - - - name: Network-Secondary - account: Network - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - ipv6Cidrs: - - amazonProvided: true - egressOnlyIgw: true - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - vpcRoute53Resolver: - endpoints: - - name: inbound-endpoint-local - type: INBOUND - vpc: Network-Secondary - subnets: - - Network-Secondary-A - - Network-Secondary-B - queryLogs: - name: network-local-endpoint-query-logs - destinations: - - s3 - - cloud-watch-logs - routeTables: - - name: Network-Secondary-A-Rt - routes: - - name: GwlbRoute - destination: 10.0.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Secondary-Endpoint-A - - name: GwlbRoutev6 - ipv6Destination: ::/0 - type: gatewayLoadBalancerEndpoint - target: Shared-Secondary-Endpoint-A - - name: Network-Secondary-B-Rt - routes: - - name: LgwRoute - destination: 0.0.0.0/0 - type: localGateway - target: accelerator-lgw-1 - - name: eigwRoute - ipv6Destination: ::/0 - type: egressOnlyIgw - - name: Network-Secondary-C-Rt - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: az-id-firewall - targetAvailabilityZone: 1 - - name: NfwRoutev6 - ipv6Destination: ::/0 - type: networkFirewall - target: az-id-firewall - targetAvailabilityZone: 1 - - name: Network-Secondary-D-Rt - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: az-id-firewall - targetAvailabilityZone: 2 - - name: GwlbRoute - destination: 10.0.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Secondary-Endpoint-D - subnets: - - name: Network-Secondary-A - availabilityZone: a - routeTable: Network-Secondary-A-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-B - availabilityZone: b - routeTable: Network-Secondary-B-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-C - availabilityZone: 1 - routeTable: Network-Secondary-C-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-D - availabilityZone: 2 - routeTable: Network-Secondary-D-Rt - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-Outposts-A - routeTable: Network-Secondary-A-Rt - outpost: accelerator-outpost-a - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-Outposts-1 - routeTable: Network-Secondary-B-Rt - outpost: accelerator-outpost-1 - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-Secondary-Dual-Stack - availabilityZone: a - routeTable: Network-Secondary-A-Rt - ipv4CidrBlock: 10.0.0.192/28 - ipv6CidrBlock: fd00::/64 - - name: Network-Secondary-Ipv6-Only - availabilityZone: b - routeTable: Network-Secondary-B-Rt - ipv6CidrBlock: fd01::/64 - outposts: - - name: accelerator-outpost-a - arn: test-arn - availabilityZone: a - localGateway: - name: accelerator-lgw-1 - id: lgw-abcxyz - routeTables: [] - - name: accelerator-outpost-1 - arn: test-arn-2 - availabilityZone: 1 - localGateway: - name: accelerator-lgw-2 - id: lgw-abcxyz - routeTables: [] - networkAcls: - - name: TestNacl - subnetAssociations: - - Network-Secondary-A - - Network-Secondary-B - - Network-Secondary-C - - Network-Secondary-D - outboundRules: - - rule: 101 - action: allow - fromPort: 80 - toPort: 80 - protocol: -1 - destination: 172.16.0.0/12 - - rule: 102 - action: allow - fromPort: 443 - toPort: 443 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - region: us-east-1 - - rule: 103 - action: allow - fromPort: 80 - toPort: 80 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 104 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 105 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: SharedServices - vpc: SharedServices-Ipam-East - subnet: SharedServices-A - region: us-east-1 - - rule: 106 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: SharedServices - vpc: SharedServices-Ipam-West - subnet: SharedServices-A - region: us-west-2 - - rule: 107 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: SharedServices - vpc: SharedServices-Main - subnet: SharedServices-App-A - region: us-east-1 - - rule: 108 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - destination: ::/0 - inboundRules: - - rule: 101 - action: allow - fromPort: 22 - toPort: 22 - protocol: 22 - source: 10.0.0.0/24 - - rule: 102 - action: deny - fromPort: 443 - toPort: 443 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 103 - action: allow - fromPort: 80 - toPort: 80 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - region: us-east-1 - - rule: 104 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-B - region: us-east-1 - - rule: 105 - action: allow - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: SharedServices - vpc: SharedServices-Ipam-East - subnet: SharedServices-A - region: us-east-1 - - rule: 106 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: SharedServices - vpc: SharedServices-Ipam-West - subnet: SharedServices-A - region: us-west-2 - - rule: 107 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: SharedServices - vpc: SharedServices-Main - subnet: SharedServices-App-A - region: us-east-1 - - rule: 108 - action: deny - fromPort: -1 - toPort: -1 - protocol: -1 - source: ::/0 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - tags: - - key: env - value: prod - - - name: Network-Endpoints - account: Network - region: *HOME_REGION - ipamAllocations: - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - - ipamPoolName: home-region-prod-pool - netmaskLength: 25 - internetGateway: true - dhcpOptions: accelerator-dhcp-opts - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - dnsFirewallRuleGroups: - - name: accelerator-block-group - priority: 101 - queryLogs: - - accelerator-query-logs - resolverRules: - - example-rule - routeTables: - - name: Network-Endpoints-Tgw-A - routes: [] - - name: Network-Endpoints-Tgw-B - routes: [] - - name: Network-Endpoints-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: TgwRoutev6 - ipv6Destination: ::/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - - name: PeeringRouteToWorkloadTemplate - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - - name: Network-Endpoints-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: PlRoute - destinationPrefixList: accelerator-prefix-list - type: transitGateway - target: Network-Main - - name: VpcPeer - destination: 10.4.0.0/16 - type: vpcPeering - target: CrossAccount - - name: PeeringRouteToWorkloadTemplate - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - subnets: - - name: Network-Endpoints-A - availabilityZone: a - routeTable: Network-Endpoints-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - shareTargets: - organizationalUnits: - - Infrastructure - accounts: - - SharedServices - tags: - - key: exampleKey - value: exampleValue - - name: Network-Endpoints-B - availabilityZone: b - routeTable: Network-Endpoints-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 27 - - name: Network-EndpointsTgwAttach-A - availabilityZone: a - routeTable: Network-Endpoints-Tgw-A - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - - name: Network-EndpointsTgwAttach-B - availabilityZone: b - routeTable: Network-Endpoints-Tgw-B - ipamAllocation: - ipamPoolName: home-region-prod-pool - netmaskLength: 28 - transitGatewayAttachments: - - name: Network-Endpoints - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - name: Network-Endpoints-2 - transitGateway: - name: Network-Main-2 - account: Network - routeTableAssociations: - - Network-Main-2-Shared - routeTablePropagations: - - Network-Main-2-Core - - Network-Main-2-Shared - - Network-Main-2-Segregated - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - virtualPrivateGateway: - asn: 65200 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - interfaceEndpoints: - central: true - tags: - - key: Environment - value: CentralVpc - defaultPolicy: Default - subnets: - - Network-Endpoints-A - - Network-Endpoints-B - endpoints: - - service: ec2 - - service: ec2messages - - service: ssm - - service: ssmmessages - - service: kms - - service: logs - - service: s3 - - service: ecr.dkr - - service: s3-global.accesspoint - - service: codeartifact.repositories - - service: codeartifact.api - - service: eks - applyPolicy: false - - service: secretsmanager - securityGroup: Network-Endpoints-CustomEndpointSg - - service: access-analyzer - securityGroup: Network-Endpoints-CustomEndpointSg - # - service: secretsmanager - # - service: cloudformation - # - service: application-autoscaling - # - service: appmesh-envoy-management - # - service: athena - # - service: autoscaling - # - service: autoscaling-plans - # - service: clouddirectory - # - service: cloudtrail - # - service: codebuild - # - service: codecommit - # - service: codepipeline - # - service: config - # - service: datasync - # - service: ecs - # - service: ecs-agent - # - service: ecs-telemetry - # - service: elasticfilesystem - # - service: elasticloadbalancing - # - service: elasticmapreduce - # - service: events - # - service: execute-api - # - service: git-codecommit - # - service: glue - # - service: kinesis-streams - # - service: kms - # - service: logs - # - service: monitoring - # - service: sagemaker.api - # - service: sagemaker.runtime - # - service: servicecatalog - # - service: sms - # - service: sns - # - service: sqs - # - service: storagegateway - # - service: sts - # - service: transfer - # - service: workspaces - # - service: awsconnector - # - service: kinesis-firehose - # - service: states - # - service: acm-pca - # - service: cassandra - # - service: ebs - # - service: elasticbeanstalk - # - service: elasticbeanstalk-health - # - service: email-smtp - # - service: license-manager - # - service: macie2 - # - service: notebook - # - service: synthetics - # - service: transfer.server - securityGroups: - - name: 'Management' - description: 'Management Security Group' - inboundRules: - - description: 'Management RDP Traffic Inbound' - types: - - RDP - sources: - - '10.0.0.0/8' - - '100.96.252.0/23' - - '100.96.250.0/23' - - fd00::/8 - - account: 'Network' - vpc: 'Network-Endpoints' - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'Management SSH Traffic Inbound' - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'IP Protocol Rule' - ipProtocols: - - ESP - - IDRP - - ST - sources: - - 100.96.252.0/23 - outboundRules: - - description: 'All Outbound' - types: - - ALL - sources: - - 10.0.0.0/8 - - 100.96.252.0/23 - - 100.96.250.0/23 - - account: Network - vpc: Network-Endpoints - subnets: - - Network-EndpointsTgwAttach-A - - Network-EndpointsTgwAttach-B - - securityGroups: - - Management - - prefixLists: - - accelerator-prefix-list - - description: 'Limited Outbound' - tcpPorts: - - 22 - udpPorts: - - 22 - sources: - - 10.0.0.0/8 - - description: 'IP Protocol Rule' - ipProtocols: - - ESP - - IDRP - - ST - sources: - - 10.0.0.0/8 - - name: Network-Endpoints-CustomEndpointSg - description: Accelerator security group for custom endpoint - inboundRules: - - description: Allow access to 443 - types: - - HTTPS - sources: - - 0.0.0.0/0 - - outboundRules: - - description: Allow all outbound - types: - - ALL - sources: - - 0.0.0.0/0 - networkAcls: - - name: TestNACL - subnetAssociations: - - Network-Endpoints-A - inboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - source: 10.0.0.0/8 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - source: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - outboundRules: - - action: allow - rule: 10 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: 0.0.0.0/0 - - action: allow - rule: 20 - fromPort: -1 - toPort: -1 - protocol: -1 - destination: - account: Network - vpc: Network-Endpoints - subnet: Network-Endpoints-A - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - loadBalancers: - applicationLoadBalancers: - - name: appA-alb-01 - scheme: internet-facing - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - securityGroups: - - Management - listeners: - - name: appA-listener-2 - port: 80 - protocol: HTTP - targetGroup: appA-alb-tg-1 - type: forward - networkLoadBalancers: - - name: appA-nlb-01 - scheme: internet-facing - deletionProtection: false - subnets: - - 'Network-EndpointsTgwAttach-A' - - 'Network-EndpointsTgwAttach-B' - listeners: - - name: appA-listener-1 - port: 80 - protocol: TCP - targetGroup: appA-nlb-tg-1 - - name: appA-listener-2 - port: 80 - protocol: TCP - targetGroup: appA-nlb-tg-2 - targetGroups: - - name: appA-nlb-tg-1 - port: 80 - protocol: TCP - type: instance - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - - name: appA-alb-tg-1 - port: 80 - protocol: HTTP - type: instance - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: HTTP - - name: deployed-tg-2 - port: 443 - protocol: HTTPS - type: instance - shareTargets: - organizationalUnits: - - Security - - name: appA-nlb-tg-2 - port: 80 - protocol: TCP - type: ip - targets: - - account: Network - region: *HOME_REGION - nlbName: appA-nlb-01 - connectionTermination: true - preserveClientIp: true - proxyProtocolV2: true - healthCheck: - enabled: true - port: 80 - protocol: TCP - tags: - - key: env - value: prod - - - name: Network-Inspection - account: Network - region: *HOME_REGION - cidrs: - - 10.2.0.0/22 - - 10.3.0.0/16 - defaultSecurityGroupRulesDeletion: true - internetGateway: true - natGateways: - - name: accelerator-nat-gw-a - subnet: Network-Inspection-A - # allocationId: eipalloc-acbdefg123456 # This is a placeholder value and should not be used in a test config - - name: accelerator-nat-gw-b - subnet: Network-Inspection-B - - name: accelerator-nat-gw-c - subnet: Network-Inspection-B - private: true - routeTables: - - name: Network-Inspection-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: Network-Inspection-Tgw-A - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: GwlbRoute - destination: 10.0.0.0/8 - type: gatewayLoadBalancerEndpoint - target: Endpoint-A - - name: NetworkInterfaceRoute - destination: 10.0.0.0/26 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_1:accelerator-firewall} - - name: NetworkInterfaceDirectRoute - destination: 10.0.0.0/28 - type: networkInterface - target: eni-0123456789abcdeff - - name: NetworkInterfaceRoutev6 - ipv6Destination: fd00::/8 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_1:accelerator-firewall} - - name: NetworkInterfaceDirectRoutev6 - ipv6Destination: fd01::/8 - type: networkInterface - target: eni-0123456789abcdeff - - name: Network-Inspection-Tgw-B - routes: - - name: NfwRoute - destination: 0.0.0.0/0 - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: GwlbRoute - destination: 10.0.0.0/8 - type: gatewayLoadBalancerEndpoint - target: Endpoint-B - - name: Network-Inspection-Gateway - gatewayAssociation: internetGateway - routes: - - name: NfwRouteDynamic-A - destination: Network-Inspection-A - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: a - - name: GwlbRouteDynamic-A - destination: Network-InspectionTgwAttach-A - type: gatewayLoadBalancerEndpoint - target: Endpoint-A - - name: NfwRouteDynamic-B - destination: Network-Inspection-B - type: networkFirewall - target: accelerator-firewall - targetAvailabilityZone: b - - name: GwlbRouteDynamic-B - destination: Network-InspectionTgwAttach-B - type: gatewayLoadBalancerEndpoint - target: Endpoint-B - subnets: - - name: Network-Inspection-A - availabilityZone: a - routeTable: Network-Inspection-A - ipv4CidrBlock: 10.2.0.0/24 - - name: Network-Inspection-B - availabilityZone: b - routeTable: Network-Inspection-B - ipv4CidrBlock: 10.2.1.0/24 - - name: Network-InspectionTgwAttach-A - availabilityZone: a - routeTable: Network-Inspection-Tgw-A - ipv4CidrBlock: 10.2.3.208/28 - - name: Network-InspectionTgwAttach-B - availabilityZone: b - routeTable: Network-Inspection-Tgw-B - ipv4CidrBlock: 10.2.3.224/28 - securityGroups: - - name: Data - description: Firewall data - inboundRules: - - description: GENEVE - sources: - - 10.2.0.0/22 - udpPorts: - - 6081 - outboundRules: - - description: All outbound - types: - - ALL - sources: - - 0.0.0.0/0 - targetGroups: - - name: deployed-tg-2 - port: 443 - protocol: HTTPS - type: instance - shareTargets: - organizationalUnits: - - Infrastructure - - name: tg-with-target-ip - port: 80 - protocol: HTTP - protocolVersion: HTTP1 - type: ip - healthCheck: - enabled: true - port: 80 - protocol: HTTP - targets: - - '10.3.100.20' - - name: tg-without-target-ip - port: 80 - protocol: HTTP - protocolVersion: HTTP1 - type: ip - healthCheck: - enabled: true - port: 80 - protocol: HTTP - transitGatewayAttachments: - - name: Network-Inspection - transitGateway: - name: Network-Main - account: Network - options: - applianceModeSupport: enable - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - Network-InspectionTgwAttach-A - - Network-InspectionTgwAttach-B - virtualPrivateGateway: - asn: 65000 - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - useCentralEndpoints: true - - name: SharedServices-Main - account: SharedServices - region: *HOME_REGION - cidrs: - - 10.4.0.0/16 - internetGateway: true - routeTables: - - name: SharedServices-Main-Default - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: SharedServices-Tgw-A - routes: [] - - name: SharedServices-Tgw-B - routes: [] - - name: SharedServices-App-A - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - - name: CrossAcctNetworkInterfaceRoute - destination: 10.0.0.0/28 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_0:firewall-with-shared-subnet} - - name: CrossAcctNetworkInterfaceRoutev6 - ipv6Destination: ff06::/64 - type: networkInterface - target: ${ACCEL_LOOKUP::EC2:ENI_0:firewall-with-shared-subnet} - - name: GwlbRoute - destination: 10.1.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Endpoint-A - - name: SharedServices-App-B - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - - name: VpcPeerv6 - ipv6Destination: fd00::/8 - type: vpcPeering - target: CrossAccount - - name: GwlbRoute - destination: 10.1.0.0/24 - type: gatewayLoadBalancerEndpoint - target: Shared-Endpoint-B - - name: SharedServices-App-C - routes: - - name: TgwRoute - destination: 0.0.0.0/0 - type: transitGateway - target: Network-Main - - name: S3Gateway - type: gatewayEndpoint - target: s3 - - name: DynamoDBGateway - type: gatewayEndpoint - target: dynamodb - - name: VpcPeer - destination: 10.0.0.0/24 - type: vpcPeering - target: CrossAccount - subnets: - - name: SharedServices-App-A - availabilityZone: a - routeTable: SharedServices-Main-Default - ipv4CidrBlock: 10.4.0.0/24 - shareTargets: - organizationalUnits: - - Security - tags: - - key: Name - value: SharedServices-App-A - - name: SharedServices-App-B - availabilityZone: b - routeTable: SharedServices-App-B - ipv4CidrBlock: 10.4.1.0/24 - - name: SharedServices-App-C - availabilityZone: c - routeTable: SharedServices-App-C - ipv4CidrBlock: 10.4.2.0/24 - shareTargets: - organizationalUnits: - - Infrastructure - - name: SharedServices-MainTgwAttach-A - availabilityZone: a - routeTable: SharedServices-Tgw-A - ipv4CidrBlock: 10.4.255.208/28 - - name: SharedServices-MainTgwAttach-B - availabilityZone: b - routeTable: SharedServices-Tgw-B - ipv4CidrBlock: 10.4.255.224/28 - transitGatewayAttachments: - - name: SharedServices-Main - transitGateway: - name: Network-Main - account: Network - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - Network-Main-Segregated - subnets: - - SharedServices-MainTgwAttach-A - - SharedServices-MainTgwAttach-B - gatewayEndpoints: - defaultPolicy: Default - endpoints: - - service: s3 - - service: dynamodb - virtualPrivateGateway: - asn: 65002 - useCentralEndpoints: true - securityGroups: - - name: SharedServices-Main-Rsyslog-sg - description: Security Group for AWS Accelerator rsyslog - inboundRules: - - description: allow inbound traffic - udpPorts: - - 514 - tcpPorts: - - 514 - sources: - - 10.0.0.0/8 - outboundRules: - - description: All Outbound - types: - - ALL - sources: - - 0.0.0.0/0 - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: [] - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - loadBalancers: - applicationLoadBalancers: - - name: appA-alb-test-01 - scheme: internal - subnets: - - SharedServices-App-A - - SharedServices-App-B - securityGroups: - - InternalHTTP-SG - listeners: - - name: appA-listener-3 - port: 443 - protocol: HTTPS - targetGroup: deployed-tg-2 - order: 3 - type: forward - certificate: cert1 - sslPolicy: ELBSecurityPolicy-2016-08 - shareTargets: - organizationalUnits: - - Security - - name: SharedServices-appVpc - account: SharedServices - region: us-east-1 - cidrs: - - 10.1.0.0/16 - internetGateway: true - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: SharedServices-appVpc-PrivRtA - routes: - - name: SharedServices-appVpc-NatRouteA - destination: 0.0.0.0/0 - type: natGateway - target: SharedServices-appVpc-NatGwA - - name: SharedServices-appVpc-PrivRtB - routes: - - name: SharedServices-appVpc-NatRouteB - destination: 0.0.0.0/0 - type: natGateway - target: SharedServices-appVpc-NatGwB - - name: SharedServices-appVpc-PublicRouteA - routes: - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - - name: SharedServices-appVpc-PublicRouteB - routes: - - name: IgwRoute - destination: 0.0.0.0/0 - type: internetGateway - target: IGW - subnets: - - name: SharedServices-appVpc-PublicSubnetA - availabilityZone: a - routeTable: SharedServices-appVpc-PublicRouteA - ipv4CidrBlock: 10.1.0.0/24 - - name: SharedServices-appVpc-PublicSubnetB - availabilityZone: b - routeTable: SharedServices-appVpc-PublicRouteB - ipv4CidrBlock: 10.1.1.0/24 - - name: SharedServices-appVpc-PrivateSubnetA - availabilityZone: a - routeTable: SharedServices-appVpc-PrivRtA - ipv4CidrBlock: 10.1.2.0/24 - - name: SharedServices-appVpc-PrivateSubnetB - availabilityZone: b - routeTable: SharedServices-appVpc-PrivRtB - ipv4CidrBlock: 10.1.3.0/24 - natGateways: - - name: SharedServices-appVpc-NatGwA - subnet: SharedServices-appVpc-PublicSubnetA - - name: SharedServices-appVpc-NatGwB - subnet: SharedServices-appVpc-PublicSubnetB - securityGroups: - - name: SharedServices-appVpc-appSecurityGroup - description: Accelerator security group - inboundRules: - - description: Remote access security group - types: - - ALL - sources: - - securityGroups: - - SharedServices-appVpc-appSecurityGroup - - outboundRules: - - description: Allow all outbound - types: - - ALL - sources: - - 0.0.0.0/0 - vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - # - s3 - - cloud-watch-logs - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -vpcTemplates: - - name: Workload-Template - region: *HOME_REGION - deploymentTargets: - organizationalUnits: - - Infrastructure - ipamAllocations: - - ipamPoolName: home-region-pool - netmaskLength: 25 - internetGateway: false - enableDnsHostnames: true - enableDnsSupport: true - instanceTenancy: default - routeTables: - - name: Workload-A-Rt - routes: - - name: PeeringRouteToNetworkEndpoints - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - - name: Workload-B-Rt - routes: - - name: PeeringRouteToNetworkEndpoints - type: vpcPeering - target: WorkloadTemplateToNetworkEndpoints - subnets: - - name: Workload-A - availabilityZone: a - routeTable: Workload-A-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 - - name: Workload-B - availabilityZone: b - routeTable: Workload-B-Rt - ipamAllocation: - ipamPoolName: home-region-pool - netmaskLength: 28 -directConnectGateways: - - name: Network-DXGW - account: Network - asn: 65000 - gatewayName: Network-DXGW - virtualInterfaces: - - name: Accelrator-VIF - connectionId: dxcon-test1234 - customerAsn: 65002 - interfaceName: Accelrator-VIF - ownerAccount: Network - region: us-east-1 - type: transit - vlan: 575 - enableSiteLink: true - jumboFrames: true - transitGatewayAssociations: - - name: Network-Main - account: Network - allowedPrefixes: - - 10.0.0.0/8 - - 192.168.0.0/16 - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - -vpcPeering: - - name: NetworkEndpointsToSecondary - vpcs: - - Network-Endpoints - - Network-Secondary - - name: CrossAccount - vpcs: - - Network-Endpoints - - SharedServices-Main - - name: WorkloadTemplateToNetworkEndpoints - vpcs: - - Workload-Template - - Network-Endpoints - -customerGateways: - - name: accelerator-cgw - account: Network - region: *HOME_REGION - ipAddress: 1.1.1.1 - asn: 65500 - vpnConnections: - - name: accelerator-vpn - transitGateway: Network-Main - staticRoutesOnly: false - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - tunnelSpecifications: - - tunnelInsideCidr: 169.254.200.0/30 - - tunnelInsideCidr: 169.254.200.100/30 - - - name: VpcInspectionVpnConnection - vpc: Network-Inspection - staticRoutesOnly: false - tunnelSpecifications: - - tunnelInsideCidr: 169.254.100.0/30 - - tunnelInsideCidr: 169.254.100.100/30 - - - name: enhanced-vpn - amazonIpv4NetworkCidr: 10.0.0.0/16 - customerIpv4NetworkCidr: 192.168.0.0/16 - enableVpnAcceleration: true - transitGateway: Network-Main - routeTableAssociations: - - Network-Main-Core - routeTablePropagations: - - Network-Main-Core - staticRoutesOnly: false - tunnelSpecifications: - - tunnelInsideCidr: 169.254.200.0/30 - dpdTimeoutAction: restart - dpdTimeoutSeconds: 60 - ikeVersions: [2] - logging: - enable: true - phase1: - dhGroups: [2, 14, 20, 21, 22] - encryptionAlgorithms: [AES256, AES256-GCM-16] - integrityAlgorithms: [SHA2-256, SHA2-512] - lifetimeSeconds: 3600 - phase2: - dhGroups: [5, 20] - encryptionAlgorithms: [AES128] - integrityAlgorithms: [SHA2-256] - lifetimeSeconds: 900 - rekeyFuzzPercentage: 80 - rekeyMarginTimeSeconds: 350 - replayWindowSize: 64 - startupAction: start - tunnelLifecycleControl: false - - tunnelInsideCidr: 169.254.200.100/30 - dpdTimeoutAction: clear - dpdTimeoutSeconds: 120 - ikeVersions: [2] - logging: - enable: true - phase1: - dhGroups: [2, 14, 20, 21, 22] - encryptionAlgorithms: [AES256, AES256-GCM-16] - integrityAlgorithms: [SHA2-256, SHA2-512] - lifetimeSeconds: 3600 - phase2: - dhGroups: [5, 20] - encryptionAlgorithms: [AES128] - integrityAlgorithms: [SHA2-256] - lifetimeSeconds: 900 - rekeyFuzzPercentage: 65 - rekeyMarginTimeSeconds: 120 - replayWindowSize: 64 - - name: cross-account-cgw - account: Network - region: *HOME_REGION - ipAddress: ${ACCEL_LOOKUP::EC2:ENI_0:cross-account-firewall} - asn: 65500 - vpnConnections: - - name: cross-account-tgw-vpn - transitGateway: Network-Main - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - name: cross-account-vgw-vpn - vpc: Network-Inspection - - name: same-account-firewall-cgw - account: Network - region: *HOME_REGION - ipAddress: ${ACCEL_LOOKUP::EC2:ENI_1:accelerator-firewall} - asn: 65500 - vpnConnections: - - name: same-account-tgw-vpn - transitGateway: Network-Main - routeTableAssociations: - - Network-Main-Shared - routeTablePropagations: - - Network-Main-Core - - Network-Main-Shared - - name: same-account-vgw-vpn - vpc: Network-Inspection - -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 60 - destinations: - - s3 - - cloud-watch-logs - destinationsConfig: - s3: - lifecycleRules: - - abortIncompleteMultipartUpload: 1 - enabled: true - expiration: 365 - noncurrentVersionExpiration: 30 - cloudWatchLogs: - retentionInDays: 3653 - defaultFormat: true - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path - -certificates: - - name: cert1 - type: import - privKey: cert1/privKey.key - cert: cert1/cert.crt - deploymentTargets: - accounts: - - Management - - Audit - - SharedServices - organizationalUnits: - - Security - - name: cert2 - type: import - privKey: cert1/privKey.key - cert: cert1/cert.crt - deploymentTargets: - accounts: - - Management - - Audit - - name: cert3 - type: request - validation: DNS - domain: example.com - san: - - www.example.com - - www.example.net - - e.co - deploymentTargets: - organizationalUnits: - - Security - -firewallManagerService: - delegatedAdminAccount: Audit - notificationChannels: - - snsTopic: Security - region: us-east-1 diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/organization-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/organization-config.yaml deleted file mode 100644 index 92af027..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/organization-config.yaml +++ /dev/null @@ -1,95 +0,0 @@ -# If using AWS Control Tower, ensure that all the specified Organizational Units (OU) -# have been created and enrolled as the accelerator will verify that the OU layout -# matches before continuing to execute the deployment pipeline. - -enable: true -organizationalUnits: - - name: Security - - name: Infrastructure - - name: GovCloud - - name: SecureWorkloads -quarantineNewAccounts: - enable: true - scpPolicyName: Quarantine -serviceControlPolicies: - - name: AcceleratorGuardrails1 - description: > - Accelerator GuardRails 1 - policy: service-control-policies/guardrails-1.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure - - name: AcceleratorGuardrails2 - description: > - Accelerator GuardRails 2 - policy: service-control-policies/guardrails-2.json - type: customerManaged - deploymentTargets: - accounts: - - SharedServices - - name: Quarantine - description: > - This SCP is used to prevent changes to new accounts until the Accelerator - has been executed successfully. - This policy will be applied upon account creation if enabled. - policy: service-control-policies/quarantine.json - type: customerManaged - deploymentTargets: - organizationalUnits: [] - - name: AllowList - description: > - This SCP uses "allow-list" strategy. - strategy: allow-list - policy: service-control-policies/allow-ec2-only.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - SecureWorkloads - - name: DataPerimeter - description: > - Data Perimeter SCP - policy: service-control-policies/data-perimeter.json - type: customerManaged - deploymentTargets: - organizationalUnits: - - Infrastructure -taggingPolicies: - - name: TagPolicy - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root - - name: TagPolicy01 - description: Organization Tagging Policy - policy: tagging-policies/org-tag-policy.json - deploymentTargets: - organizationalUnits: - - Root -backupPolicies: - - name: BackupPolicy - description: Organization Backup Policy - policy: backup-policies/org-backup-policies.json - deploymentTargets: - organizationalUnits: - - Root - - name: BackupPolicy01 - description: Organization Backup Policy - policy: backup-policies/org-backup-policies.json - deploymentTargets: - organizationalUnits: - - Root -organizationalUnitIds: - - name: Root - id: r-asdf - arn: arn:aws:organizations::111111111111:root/o-asdf123456/r-asdf - - name: Security - id: ou-asdf-11111111 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-11111111 - - name: Infrastructure - id: ou-asdf-22222222 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-22222222 - - name: SecureWorkloads - id: ou-asdf-33333333 - arn: arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-33333333 \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/replacements-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/replacements-config.yaml deleted file mode 100644 index 7aba644..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/replacements-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -globalReplacements: - - key: ALLOWED_CORPORATE_CIDRS - type: StringList - value: - - 10.0.1.0/24 - - 10.0.2.0/24 - - key: ALLOWED_PRINCIPAL_ARNS - type: StringList - value: - - arn:aws:iam::*:role/cdk-accel-* - - arn:aws:iam::*:role/AWSA* - - arn:aws:iam::*:role/OrganizationAccountAccessRole - - key: ALLOWED_EXTERNAL_ACCOUNTS - type: StringList - value: - - '123456789012' - - key: DEFINED_PLACEHOLDER - type: String - value: 'TagReplacementValue' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/apigateway.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/apigateway.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/apigateway.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/backup-vault.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/backup-vault.json deleted file mode 100644 index 62c3ead..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/backup-vault.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": [ - "backup:DescribeBackupVault", - "backup:DeleteBackupVault", - "backup:PutBackupVaultAccessPolicy", - "backup:DeleteBackupVaultAccessPolicy", - "backup:GetBackupVaultAccessPolicy", - "backup:StartBackupJob", - "backup:GetBackupVaultNotifications", - "backup:PutBackupVaultNotifications", - "backup:DeleteBackupVaultNotifications", - "backup:ListRecoveryPointsByBackupVault" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": [ - "backup:DescribeBackupVault", - "backup:DeleteBackupVault", - "backup:PutBackupVaultAccessPolicy", - "backup:DeleteBackupVaultAccessPolicy", - "backup:GetBackupVaultAccessPolicy", - "backup:StartBackupJob", - "backup:GetBackupVaultNotifications", - "backup:PutBackupVaultNotifications", - "backup:DeleteBackupVaultNotifications", - "backup:ListRecoveryPointsByBackupVault" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/codeartifact-repository.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/codeartifact-repository.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/codeartifact-repository.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/ecr.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/ecr.json deleted file mode 100644 index 171ae26..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/ecr.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/efs-file-system.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/efs-file-system.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/efs-file-system.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/eventbridge-eventbus.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/eventbridge-eventbus.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/eventbridge-eventbus.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/glue-catalog.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/glue-catalog.json deleted file mode 100644 index a0c2d77..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/glue-catalog.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "arn:aws:glue:us-east-1:${ACCOUNT_ID}:*", - "Action": "glue:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "arn:aws:glue:us-east-1:${ACCOUNT_ID}:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/iam.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/iam.json deleted file mode 100644 index 114767d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/iam.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Action": ["sts:AssumeRole", "sts:AssumeRoleWithWebIdentity"], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": [ - "sts:AssumeRole", - "sts:AssumeRoleWithSAML", - "sts:AssumeRoleWithWebIdentity" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/kms.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/kms.json deleted file mode 100644 index 7434841..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/kms.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Resource": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Resource": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/lex-bot.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/lex-bot.json deleted file mode 100644 index 43c77f7..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/lex-bot.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/opensearch.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/opensearch.json deleted file mode 100644 index 7374042..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/opensearch.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/s3.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/s3.json deleted file mode 100644 index 82b7aa2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/s3.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": "*", - "Effect": "Deny", - "Action": "*", - "Resource": [ - "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", - "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}/*" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": "*", - "Action": "*", - "Resource": [ - "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}", - "${ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN}/*" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/secrets-manager.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/secrets-manager.json deleted file mode 100644 index c5d2c24..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/secrets-manager.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { "AWS": "*" }, - "Effect": "Deny", - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { "aws:PrincipalIsAWSService": "false" } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { "AWS": "*" }, - "Resource": "*", - "Action": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sns.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sns.json deleted file mode 100644 index 2cb0f78..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sns.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": [ - "SNS:Publish", - "SNS:RemovePermission", - "SNS:SetTopicAttributes", - "SNS:DeleteTopic", - "SNS:ListSubscriptionsByTopic", - "SNS:GetTopicAttributes", - "SNS:AddPermission", - "SNS:Subscribe" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": [ - "SNS:Publish", - "SNS:RemovePermission", - "SNS:SetTopicAttributes", - "SNS:DeleteTopic", - "SNS:ListSubscriptionsByTopic", - "SNS:GetTopicAttributes", - "SNS:AddPermission", - "SNS:Subscribe" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sqs.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sqs.json deleted file mode 100644 index 455f94b..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/resource-policies/sqs.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Principal": { - "AWS": "*" - }, - "Effect": "Deny", - "Resource": "*", - "Action": "SQS:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "${ORG_ID}" - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - }, - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Action": "SQS:*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "NotIpAddressIfExists": { - "aws:SourceIp": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_CORPORATE_CIDRS} - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalARN": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_PRINCIPAL_ARNS} - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/security-config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/security-config.yaml deleted file mode 100644 index f4e1ed7..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/security-config.yaml +++ /dev/null @@ -1,1035 +0,0 @@ -homeRegion: &HOME_REGION us-east-1 -snsSubscriptionConfig: - - level: High - email: foo@example.com -keyManagementService: - keySets: - - name: key1 - alias: accelerator/test-key/key1 - policy: kms/kms-policy-01.json - description: Test KMS Key - enableKeyRotation: true - enabled: true - removalPolicy: destroy - deploymentTargets: - organizationalUnits: - - Root - - Infrastructure - - name: appEbsKey - alias: accelerator/test-key/app-ebs-key - policy: kms/kms-policy-01.json - description: Test KMS Key 2 - enableKeyRotation: true - enabled: true - removalPolicy: destroy - deploymentTargets: - organizationalUnits: - - Root - - Infrastructure -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: true - #kmsKey: key1 - deploymentTargets: - organizationalUnits: - - Root - excludedRegions: - - us-west-2 - s3PublicAccessBlock: - enable: true - excludeAccounts: - - Management - scpRevertChangesConfig: - enable: true - snsTopicName: Security - macie: - enable: true - excludeRegions: - - us-west-2 - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: true - deploymentTargets: - accounts: - - Management - - Network - excludedRegions: - - us-west-2 - autoEnableOrgMembers: false - s3Protection: - enable: true - excludeRegions: - - us-west-2 - eksProtection: - enable: true - exportConfiguration: - enable: true - overrideExisting: true - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - auditManager: - enable: true - excludeRegions: - - us-west-2 - defaultReportsConfiguration: - enable: true - destinationType: S3 - detective: - enable: true - excludeRegions: - - us-west-2 - securityHub: - enable: true - regionAggregation: true - snsTopicName: Security - notificationLevel: HIGH - logging: - cloudWatch: - enable: true - logLevel: MEDIUM - deploymentTargets: - organizationalUnits: - - Root - standards: - - name: AWS Foundational Security Best Practices v1.0.0 - enable: true - controlsToDisable: - - IAM.1 - - EC2.10 - - Lambda.4 - - name: PCI DSS v3.2.1 - enable: true - controlsToDisable: - - PCI.IAM.3 - - PCI.S3.3 - - PCI.EC2.3 - - PCI.Lambda.2 - - name: CIS AWS Foundations Benchmark v1.2.0 - enable: true - controlsToDisable: - - CIS.1.20 - - CIS.1.22 - - CIS.2.6 - - name: CIS AWS Foundations Benchmark v1.4.0 - enable: true - controlsToDisable: - - '1.17' - - '1.16' - - name: CIS AWS Foundations Benchmark v3.0.0 - enable: true - ssmAutomation: - documentSets: - - shareTargets: - organizationalUnits: - - Root - documents: - # Calls the AWS CLI to enable access logs on a specified ELB - - name: SSM-ELB-Enable-Logging - template: ssm-documents/ssm-elb-enable-logging.yaml - # Enables S3 encryption using KMS - - name: Put-S3-Encryption - template: ssm-documents/s3-encryption.yaml - # Attaches instance profiles to an EC2 instance - - name: Attach-IAM-Instance-Profile - template: ssm-documents/attach-iam-instance-profile.yaml - targetType: /AWS::EC2::Instance - # Attaches Aws IAM Managed Policy to IAM Role - - name: Attach-IAM-Role-Policy - template: ssm-documents/attach-iam-role-policy.yaml - # Turns on CloudWatch logging for WAF ACLs - - name: WAF-Enable-Logging - template: ssm-documents/waf-enable-logging.yaml - snsSubscriptions: - - level: High - email: notify-high@example.com - - level: Medium - email: notify-medium@example.com - - level: Low - email: notify-low@example.com - -accessAnalyzer: - enable: true -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - deploymentTargets: - organizationalUnits: - - Root - ruleSets: - - deploymentTargets: - organizationalUnits: - - Root - rules: - - name: accelerator-waf-logging-enabled - type: Custom - description: Custom rule for checking WAF logging enabled - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/waf-logging-enabled.zip - handler: index.handler - runtime: python3.12 - rolePolicyFile: custom-config-rules/waf-logging-enabled-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::WAF::WebACL - - AWS::WAFRegional::WebACL - - AWS::WAFv2::WebACL - remediation: - rolePolicyFile: custom-config-rules/waf-logging-enabled-remediation-role.json - automatic: true - targetId: WAF-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: WebACLId - value: RESOURCE_ID - type: String - - name: accelerator-target-document-01 - type: Custom - description: Custom rule for testing target document remediation - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::EC2::Instance - remediation: - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - automatic: true - targetId: Attach-IAM-Instance-Profile - targetDocumentLambda: - sourceFilePath: custom-config-rules/targetDocumentLambda.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: InstanceId - value: RESOURCE_ID - type: String - - name: IamInstanceProfile - value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} - type: StringList - tags: - - key: key - value: value - - name: accelerator-attach-ec2-instance-profile - type: Custom - description: Custom rule for checking EC2 instance IAM profile attachment - inputParameters: - customRule: - lambda: - sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::EC2::Instance - remediation: - rolePolicyFile: custom-config-rules/attach-ec2-instance-profile-remediation-role.json - automatic: true - targetId: Attach-IAM-Instance-Profile - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: InstanceId - value: RESOURCE_ID - type: String - - name: IamInstanceProfile - value: ${ACCEL_LOOKUP::InstanceProfile:EC2-Default-SSM-AD-Role} - type: StringList - - name: accelerator-ec2-instance-profile-permission - type: Custom - description: Custom role to remediate EC2 instance profile permission - inputParameters: - AWSManagedPolicies: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - # CustomerManagedPolicies: ${ACCEL_LOOKUP::CustomerManagedPolicy:},${ACCEL_LOOKUP::CustomerManagedPolicy:} - ResourceId: RESOURCE_ID - customRule: - lambda: - sourceFilePath: custom-config-rules/ec2-instance-profile-permissions.zip - handler: index.handler - runtime: nodejs18.x - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-detection-role.json - periodic: true - maximumExecutionFrequency: Six_Hours - configurationChanges: true - triggeringResources: - lookupType: ResourceTypes - lookupKey: ResourceTypes - lookupValue: - - AWS::IAM::Role - remediation: - rolePolicyFile: custom-config-rules/ec2-instance-profile-permissions-remediation-role.json - automatic: true - targetId: Attach-IAM-Role-Policy - targetAccountName: Audit - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: ResourceId - value: RESOURCE_ID - type: String - - name: AWSManagedPolicies - value: AmazonSSMManagedInstanceCore,AmazonSSMDirectoryServiceAccess,CloudWatchAgentServerPolicy - type: StringList - # - name: CustomerManagedPolicies - # value: ${ACCEL_LOOKUP::CustomerManagedPolicy:policy-00},${ACCEL_LOOKUP::CustomerManagedPolicy:policy-01} - # type: StringList - - name: accelerator-s3-bucket-server-side-encryption-enabled - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - complianceResourceTypes: - - AWS::S3::Bucket - remediation: - rolePolicyFile: custom-config-rules/bucket-sse-enabled-remediation-role.json - automatic: true - targetId: Put-S3-Encryption - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: KMSMasterKey - value: ${ACCEL_LOOKUP::KMS} - type: StringList - - name: accelerator-elb-logging-enabled - identifier: ELB_LOGGING_ENABLED - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - - AWS::ElasticLoadBalancingV2::LoadBalancer - inputParameters: - s3BucketNames: ${ACCEL_LOOKUP::Bucket:elbLogs} - remediation: - rolePolicyFile: custom-config-rules/elb-logging-enabled-remediation-role.json - automatic: true - targetId: SSM-ELB-Enable-Logging - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: LoadBalancerArn - value: RESOURCE_ID - type: String - - name: LogDestination - value: ${ACCEL_LOOKUP::Bucket:elbLogs} - type: StringList - - name: accelerator-iam-user-group-membership-check - complianceResourceTypes: - - AWS::IAM::User - identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - - name: accelerator-securityhub-enabled - identifier: SECURITYHUB_ENABLED - - name: accelerator-cloudtrail-enabled - identifier: CLOUD_TRAIL_ENABLED - - name: accelerator-rds-logging-enabled - complianceResourceTypes: - - AWS::RDS::DBInstance - identifier: RDS_LOGGING_ENABLED - - name: accelerator-cloudwatch-alarm-action-check - complianceResourceTypes: - - AWS::CloudWatch::Alarm - inputParameters: - alarmActionRequired: 'TRUE' - insufficientDataActionRequired: 'TRUE' - okActionRequired: 'FALSE' - identifier: CLOUDWATCH_ALARM_ACTION_CHECK - - name: accelerator-redshift-cluster-configuration-check - inputParameters: - clusterDbEncrypted: 'TRUE' - loggingEnabled: 'TRUE' - complianceResourceTypes: - - AWS::Redshift::Cluster - identifier: REDSHIFT_CLUSTER_CONFIGURATION_CHECK - - name: accelerator-cloudtrail-s3-dataevents-enabled - identifier: CLOUDTRAIL_S3_DATAEVENTS_ENABLED - - name: accelerator-emr-kerberos-enabled - identifier: EMR_KERBEROS_ENABLED - - name: accelerator-iam-group-has-users-check - complianceResourceTypes: - - AWS::IAM::Group - identifier: IAM_GROUP_HAS_USERS_CHECK - - name: accelerator-s3-bucket-policy-grantee-check - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_POLICY_GRANTEE_CHECK - - name: accelerator-lambda-inside-vpc - complianceResourceTypes: - - AWS::Lambda::Function - identifier: LAMBDA_INSIDE_VPC - - name: accelerator-ec2-instances-in-vpc - complianceResourceTypes: - - AWS::EC2::Instance - identifier: INSTANCES_IN_VPC - - name: accelerator-vpc-sg-open-only-to-authorized-ports - inputParameters: - authorizedTcpPorts: '443' - authorizedUdpPorts: '1020-1025' - complianceResourceTypes: - - AWS::EC2::SecurityGroup - identifier: VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS - - name: accelerator-ec2-instance-no-public-ip - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_NO_PUBLIC_IP - - name: accelerator-elasticsearch-in-vpc-only - identifier: ELASTICSEARCH_IN_VPC_ONLY - - name: accelerator-internet-gateway-authorized-vpc-only - complianceResourceTypes: - - AWS::EC2::InternetGateway - identifier: INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY - - name: accelerator-iam-no-inline-policy-check - complianceResourceTypes: - - AWS::IAM::User - - AWS::IAM::Role - - AWS::IAM::Group - identifier: IAM_NO_INLINE_POLICY_CHECK - - name: accelerator-elb-acm-certificate-required - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_ACM_CERTIFICATE_REQUIRED - - name: accelerator-alb-http-drop-invalid-header-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ALB_HTTP_DROP_INVALID_HEADER_ENABLED - - name: accelerator-elb-tls-https-listeners-only - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_TLS_HTTPS_LISTENERS_ONLY - - name: accelerator-api-gw-execution-logging-enabled - complianceResourceTypes: - - AWS::ApiGateway::Stage - - AWS::ApiGatewayV2::Stage - identifier: API_GW_EXECUTION_LOGGING_ENABLED - - name: accelerator-cloudwatch-log-group-encrypted - identifier: CLOUDWATCH_LOG_GROUP_ENCRYPTED - - name: accelerator-s3-bucket-replication-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_REPLICATION_ENABLED - - name: accelerator-cw-loggroup-retention-period-check - identifier: CW_LOGGROUP_RETENTION_PERIOD_CHECK - - name: accelerator-ec2-instance-detailed-monitoring-enabled - complianceResourceTypes: - - AWS::EC2::Instance - identifier: EC2_INSTANCE_DETAILED_MONITORING_ENABLED - - name: accelerator-ec2-volume-inuse-check - inputParameters: - deleteOnTermination: 'TRUE' - complianceResourceTypes: - - AWS::EC2::Volume - identifier: EC2_VOLUME_INUSE_CHECK - - name: accelerator-elb-deletion-protection-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancingV2::LoadBalancer - identifier: ELB_DELETION_PROTECTION_ENABLED - - name: accelerator-cloudtrail-security-trail-enabled - identifier: CLOUDTRAIL_SECURITY_TRAIL_ENABLED - - name: accelerator-elasticache-redis-cluster-automatic-backup-check - identifier: ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK - - name: accelerator-s3-bucket-versioning-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_VERSIONING_ENABLED - - name: accelerator-vpc-vpn-2-tunnels-up - complianceResourceTypes: - - AWS::EC2::VPNConnection - identifier: VPC_VPN_2_TUNNELS_UP - - name: accelerator-elb-cross-zone-load-balancing-enabled - complianceResourceTypes: - - AWS::ElasticLoadBalancing::LoadBalancer - identifier: ELB_CROSS_ZONE_LOAD_BALANCING_ENABLED - - name: accelerator-iam-user-mfa-enabled - identifier: IAM_USER_MFA_ENABLED - - name: accelerator-guardduty-non-archived-findings - inputParameters: - daysHighSev: '1' - daysLowSev: '30' - daysMediumSev: '7' - identifier: GUARDDUTY_NON_ARCHIVED_FINDINGS - - name: accelerator-elasticsearch-node-to-node-encryption-check - complianceResourceTypes: - - AWS::Elasticsearch::Domain - identifier: ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK - - name: accelerator-kms-cmk-not-scheduled-for-deletion - complianceResourceTypes: - - AWS::KMS::Key - identifier: KMS_CMK_NOT_SCHEDULED_FOR_DELETION - - name: accelerator-api-gw-cache-enabled-and-encrypted - complianceResourceTypes: - - AWS::ApiGateway::Stage - identifier: API_GW_CACHE_ENABLED_AND_ENCRYPTED - - name: accelerator-sagemaker-endpoint-configuration-kms-key-configured - identifier: SAGEMAKER_ENDPOINT_CONFIGURATION_KMS_KEY_CONFIGURED - - name: accelerator-sagemaker-notebook-instance-kms-key-configured - identifier: SAGEMAKER_NOTEBOOK_INSTANCE_KMS_KEY_CONFIGURED - - name: accelerator-dynamodb-table-encrypted-kms - complianceResourceTypes: - - AWS::DynamoDB::Table - identifier: DYNAMODB_TABLE_ENCRYPTED_KMS - - name: accelerator-s3-bucket-default-lock-enabled - complianceResourceTypes: - - AWS::S3::Bucket - identifier: S3_BUCKET_DEFAULT_LOCK_ENABLED - - name: accelerator-duplicate-s3-rule - identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - remediation: - automatic: true - targetId: AWS-EnableS3BucketEncryption - rolePolicyFile: custom-config-rules/enable-s3-encryption.json - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - parameters: - - name: BucketName - value: RESOURCE_ID - type: String - - name: SSEAlgorithm - value: AES256 - type: String -cloudWatch: - logGroups: - - logGroupName: /App/Test1 - logRetentionInDays: 30 - terminationProtected: true - encryption: - useLzaManagedKey: true - deploymentTargets: - accounts: - - SharedServices - - logGroupName: /App/Test2 - logRetentionInDays: 180 - terminationProtected: false - encryption: - kmsKeyArn: 'arn:aws:kms:us-east-1:111111111111:key/121ac3b6-8d53-4d8a-a05c-1234567789' - deploymentTargets: - accounts: - - Management - excludedRegions: - - us-west-2 - - logGroupName: /App/Test3 - logRetentionInDays: 365 - terminationProtected: false - deploymentTargets: - organizationalUnits: - - Infrastructure - excludedRegions: - - us-east-1 - - logGroupName: /App/Test4 - logRetentionInDays: 14 - terminationProtected: true - # encryption: - # kmsKeyName: key1 - deploymentTargets: - organizationalUnits: - - Infrastructure - metricSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - metrics: - # CIS 1.1 – Avoid the use of the "root" account - - filterName: RootAccountMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccount - metricValue: '1' - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - filterName: UnauthorizedAPICallsMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.errorCode="*UnauthorizedOperation") || ($.errorCode="AccessDenied*")}' - metricNamespace: LogMetrics - metricName: UnauthorizedAPICalls - metricValue: '1' - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - filterName: ConsoleSigninWithoutMFAMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") && ($.userIdentity.type = "IAMUser") && ($.responseElements.ConsoleLogin = "Success")}' - metricNamespace: LogMetrics - metricName: ConsoleSigninWithoutMFA - metricValue: '1' - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - filterName: MetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - metricNamespace: LogMetrics - metricName: RootAccountUsage - metricValue: '1' - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - filterName: IAMPolicyChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}' - metricNamespace: LogMetrics - metricName: IAMPolicyChanges - metricValue: '1' - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - filterName: CloudTrailChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}' - metricNamespace: LogMetrics - metricName: CloudTrailChanges - metricValue: '1' - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - filterName: ConsoleAuthenticationFailureMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=ConsoleLogin) && ($.errorMessage="Failed authentication")}' - metricNamespace: LogMetrics - metricName: ConsoleAuthenticationFailure - metricValue: '1' - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - filterName: DisableOrDeleteCMKMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}' - metricNamespace: LogMetrics - metricName: DisableOrDeleteCMK - metricValue: '1' - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - filterName: S3BucketPolicyChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}' - metricNamespace: LogMetrics - metricName: S3BucketPolicyChanges - metricValue: '1' - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - filterName: AWSConfigChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}' - metricNamespace: LogMetrics - metricName: AWSConfigChanges - metricValue: '1' - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - filterName: SecurityGroupChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}' - metricNamespace: LogMetrics - metricName: SecurityGroupChanges - metricValue: '1' - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - filterName: NetworkACLChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}' - metricNamespace: LogMetrics - metricName: NetworkACLChanges - metricValue: '1' - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - filterName: NetworkGatewayChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}' - metricNamespace: LogMetrics - metricName: NetworkGatewayChanges - metricValue: '1' - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - filterName: RouteTableChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}' - metricNamespace: LogMetrics - metricName: RouteTableChanges - metricValue: '1' - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - filterName: VPCChangesMetricFilter - logGroupName: aws-accelerator-cloudtrail-AWSAccelerator-Account-CloudTrail - filterPattern: '{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}' - metricNamespace: LogMetrics - metricName: VPCChanges - metricValue: '1' - # greater than threshold test - - filterName: GenericMetricFilterGreaterThanThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingGreaterThanThreshold - metricValue: '1' - # less than threshold test - - filterName: GenericMetricFilterLessThanThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingLessThanThreshold - metricValue: '1' - # less than or equal to threshold test - - filterName: GenericMetricFilterLessThanOrEqualToThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingLessThanOrEqualToThreshold - metricValue: '1' - # less than lower or greater than upper threshold test - - filterName: GenericMetricFilterLessThanLowerOrGreaterThanUpperThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingLessThanLowerOrGreaterThanUpperThreshold - metricValue: '1' - # greater than upper threshold test - - filterName: GenericMetricFilterGreaterThanUpperThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingGreaterThanUpperThreshold - metricValue: '1' - # less than lower threshold test - - filterName: GenericMetricFilterLessThanLowerThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingLessThanLowerThreshold - metricValue: '1' - - regions: - - *HOME_REGION - deploymentTargets: - account: - - SharedServices - metrics: - - filterName: GenericMetricFilterGreaterThanOrEqualToThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingGreaterThanOrEqualToThreshold - metricValue: '1' - - filterName: GenericMetricFilterGreaterThanThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingGreaterThanThreshold - metricValue: '1' - - filterName: GenericMetricFilterLessThanThreshold - logGroupName: /generic/log/group - filterPattern: '{$.eventType !="AwsServiceEvent"}' - metricNamespace: LzaMetrics - metricName: GenericMetricForTestingLessThanThreshold - metricValue: '1' - alarmSets: - - regions: - - *HOME_REGION - deploymentTargets: - organizationalUnits: - - Root - alarms: - # CIS 1.1 – Avoid the use of the "root" account - - alarmName: CIS-1.1-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.1 – Ensure a log metric filter and alarm exist for unauthorized API calls - - alarmName: CIS-3.1-UnauthorizedAPICalls - alarmDescription: Alarm for unauthorized API calls - snsTopicName: Security - metricName: UnauthorizedAPICalls - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 5 - treatMissingData: notBreaching - # CIS 3.2 – Ensure a log metric filter and alarm exist for AWS Management Console sign-in without MFA - - alarmName: CIS-3.2-ConsoleSigninWithoutMFA - alarmDescription: Alarm for AWS Management Console sign-in without MFA - snsTopicName: Security - metricName: ConsoleSigninWithoutMFA - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.3 – Ensure a log metric filter and alarm exist for usage of "root" account - - alarmName: CIS-3.3-RootAccountUsage - alarmDescription: Alarm for usage of "root" account - snsTopicName: Security - metricName: RootAccountUsage - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.4 – Ensure a log metric filter and alarm exist for IAM policy changes - - alarmName: CIS-3.4-IAMPolicyChanges - alarmDescription: Alarm for IAM policy changes - snsTopicName: Security - metricName: IAMPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.5 – Ensure a log metric filter and alarm exist for CloudTrail configuration changes - - alarmName: CIS-3.5-CloudTrailChanges - alarmDescription: Alarm for CloudTrail configuration changes - snsTopicName: Security - metricName: CloudTrailChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.6 – Ensure a log metric filter and alarm exist for AWS Management Console authentication failures - - alarmName: CIS-3.6-ConsoleAuthenticationFailure - alarmDescription: Alarm exist for AWS Management Console authentication failures - snsTopicName: Security - metricName: ConsoleAuthenticationFailure - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.7 – Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs - - alarmName: CIS-3.7-DisableOrDeleteCMK - alarmDescription: Alarm for disabling or scheduled deletion of customer created CMKs - snsTopicName: Security - metricName: DisableOrDeleteCMK - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.8 – Ensure a log metric filter and alarm exist for S3 bucket policy changes - - alarmName: CIS-3.8-S3BucketPolicyChanges. - alarmDescription: Alarm for S3 bucket policy changes - snsTopicName: Security - metricName: S3BucketPolicyChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.9 – Ensure a log metric filter and alarm exist for AWS Config configuration changes - - alarmName: CIS-3.9-AWSConfigChanges - alarmDescription: Alarm for AWS Config configuration changes - snsTopicName: Security - metricName: AWSConfigChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.10 – Ensure a log metric filter and alarm exist for security group changes - - alarmName: CIS-3.10-SecurityGroupChanges - alarmDescription: Alarm for security group changes - snsTopicName: Security - metricName: SecurityGroupChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.11 – Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) - - alarmName: CIS-3.11-NetworkACLChanges - alarmDescription: Alarm for changes to Network Access Control Lists (NACL) - snsTopicName: Security - metricName: NetworkACLChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.12 – Ensure a log metric filter and alarm exist for changes to network gateways - - alarmName: CIS-3.12-NetworkGatewayChanges - alarmDescription: Alarm for changes to network gateways - snsTopicName: Security - metricName: NetworkGatewayChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # CIS 3.13 – Ensure a log metric filter and alarm exist for route table changes - - alarmName: CIS-3.13-RouteTableChanges - alarmDescription: Alarm for route table changes - snsTopicName: Security - metricName: RouteTableChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Average - threshold: 1 - treatMissingData: notBreaching - # CIS 3.14 – Ensure a log metric filter and alarm exist for VPC changes - - alarmName: CIS-3.14-VPCChanges - alarmDescription: Alarm for VPC changes - snsTopicName: Security - metricName: VPCChanges - namespace: LogMetrics - comparisonOperator: GreaterThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # greater than threshold test - - alarmName: GenericMetricGreaterThanThreshold - alarmDescription: Alarm to test GenericMetric GreaterThanThreshold - snsTopicName: Security - metricName: GenericMetricForTestingGreaterThanThreshold - namespace: LzaMetrics - comparisonOperator: GreaterThanThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: ignore - # less than threshold testing - - alarmName: GenericMetricLessThanThreshold - alarmDescription: Alarm to test GenericMetric LessThanThreshold - snsTopicName: Security - metricName: GenericMetricForTestingLessThanThreshold - namespace: LzaMetrics - comparisonOperator: LessThanThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: missing - # less than or equal to threshold test - - alarmName: GenericMetricLessThanOrEqualToThreshold - alarmDescription: Alarm to test GenericMetric LessThanOrEqualToThreshold - snsTopicName: Security - metricName: GenericMetricForTestingLessThanOrEqualToThreshold - namespace: LzaMetrics - comparisonOperator: LessThanOrEqualToThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # less than lower or greater than upper threshold test - - alarmName: GenericMetricLessThanLowerOrGreaterThanUpperThreshold - alarmDescription: Alarm to test GenericMetric LessThanLowerOrGreaterThanUpperThreshold - snsTopicName: Security - metricName: GenericMetricForTestingLessThanLowerOrGreaterThanUpperThreshold - namespace: LzaMetrics - comparisonOperator: LessThanLowerOrGreaterThanUpperThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # greater than upper threshold test - - alarmName: GenericMetricGreaterThanUpperThreshold - alarmDescription: Alarm to test GenericMetric GreaterThanUpperThreshold - snsTopicName: Security - metricName: GenericMetricForTestingGreaterThanUpperThreshold - namespace: LzaMetrics - comparisonOperator: GreaterThanUpperThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching - # less than lower threshold test - - alarmName: GenericMetricLessThanLowerThreshold - alarmDescription: Alarm to test GenericMetric LessThanLowerThreshold - snsTopicName: Security - metricName: GenericMetricForTestingLessThanLowerThreshold - namespace: LzaMetrics - comparisonOperator: LessThanLowerThreshold - evaluationPeriods: 1 - period: 300 - statistic: Sum - threshold: 1 - treatMissingData: notBreaching -resourcePolicyEnforcement: - enable: true - remediation: - automatic: false - retryAttemptSeconds: 60 - maximumAutomaticAttempts: 5 - policySets: - - resourcePolicies: - - resourceType: IAM_ROLE - document: resource-policies/iam.json - - resourceType: S3_BUCKET - document: resource-policies/s3.json - - resourceType: KMS_KEY - document: resource-policies/kms.json - - resourceType: SECRETS_MANAGER_SECRET - document: resource-policies/secrets-manager.json - - resourceType: ECR_REPOSITORY - document: resource-policies/ecr.json - - resourceType: OPENSEARCH_DOMAIN - document: resource-policies/opensearch.json - - resourceType: SNS_TOPIC - document: resource-policies/sns.json - - resourceType: SQS_QUEUE - document: resource-policies/sqs.json - - resourceType: APIGATEWAY_REST_API - document: resource-policies/apigateway.json - - resourceType: LEX_BOT - document: resource-policies/lex-bot.json - - resourceType: EFS_FILE_SYSTEM - document: resource-policies/efs-file-system.json - - resourceType: EVENTBRIDGE_EVENTBUS - document: resource-policies/eventbridge-eventbus.json - - resourceType: BACKUP_VAULT - document: resource-policies/backup-vault.json - - resourceType: CODEARTIFACT_REPOSITORY - document: resource-policies/codeartifact-repository.json - inputParameters: - SourceAccount: '{{ ALLOWED_EXTERNAL_ACCOUNTS }}' - deploymentTargets: - organizationalUnits: - - Root diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/allow-ec2-only.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/allow-ec2-only.json deleted file mode 100644 index 6e1f85f..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/allow-ec2-only.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowEC2", - "Effect": "Allow", - "Action": "ec2:*", - "Resource": "*" - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/data-perimeter.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/data-perimeter.json deleted file mode 100644 index 502732c..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/data-perimeter.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceResourcePerimeterThirdPartyResources", - "Effect": "Deny", - "Action": ["s3:*", "kms:*", "iam:*"], - "Resource": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:ResourceOrgID": "${ORG_ID}", - "aws:ResourceAccount": ${ACCEL_LOOKUP::CUSTOM:ALLOWED_EXTERNAL_ACCOUNTS}, - "aws:SourceVpc": [${ACCEL_LOOKUP::VPC_ID:OU:Infrastructure}], - "aws:sourceVpce": [${ACCEL_LOOKUP::VPCE_ID:ACCOUNT:Network}] - }, - "ForAllValues:StringNotEquals": { - "aws:CalledVia": [ - "dataexchange.amazonaws.com", - "servicecatalog.amazonaws.com" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-1.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-1.json deleted file mode 100644 index 703e007..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "ConfigRulesStatement", - "Effect": "Deny", - "Action": [ - "config:PutConfigRule", - "config:DeleteConfigRule", - "config:DeleteEvaluationResults", - "config:DeleteConfigurationAggregator", - "config:PutConfigurationAggregator" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "LambdaStatement", - "Effect": "Deny", - "Action": [ - "lambda:AddPermission", - "lambda:CreateEventSourceMapping", - "lambda:CreateFunction", - "lambda:DeleteEventSourceMapping", - "lambda:DeleteFunction", - "lambda:DeleteFunctionConcurrency", - "lambda:PutFunctionConcurrency", - "lambda:RemovePermission", - "lambda:UpdateEventSourceMapping", - "lambda:UpdateFunctionCode", - "lambda:UpdateFunctionConfiguration" - ], - "Resource": "arn:${PARTITION}:lambda:*:*:function:${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "SnsStatement", - "Effect": "Deny", - "Action": [ - "sns:AddPermission", - "sns:CreateTopic", - "sns:DeleteTopic", - "sns:RemovePermission", - "sns:SetTopicAttributes", - "sns:Subscribe", - "sns:Unsubscribe" - ], - "Resource": "arn:${PARTITION}:sns:*:*:aws-accelerator-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "EbsEncryptionStatement", - "Effect": "Deny", - "Action": ["ec2:DisableEbsEncryptionByDefault"], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-2.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-2.json deleted file mode 100644 index 127f68e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/guardrails-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "IamSettingsStatement", - "Effect": "Deny", - "Action": [ - "iam:DeleteAccountPasswordPolicy", - "iam:UpdateAccountPasswordPolicy", - "iam:CreateAccountAlias", - "iam:DeleteAccountAlias" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "IamRolesStatement", - "Effect": "Deny", - "Action": ["iam:*"], - "Resource": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}" - ], - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/AWSServiceRoleForConfig" - ] - } - } - }, - { - "Sid": "GDSecHubServicesStatement", - "Effect": "Deny", - "Action": [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:UpdateDetector", - "guardduty:StopMonitoringMembers", - "guardduty:Disassociate*", - "securityhub:BatchDisableStandards", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:Disassociate*" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "MacieServiceStatement", - "Effect": "Deny", - "Action": [ - "macie:AcceptInvitation", - "macie:CreateInvitations", - "macie:CreateMember", - "macie:DeclineInvitations", - "macie:DeleteInvitations", - "macie:DeleteMember", - "macie:DisableMacie", - "macie:DisableOrganizationAdminAccount", - "macie:Disassociate*", - "macie:Enable*", - "macie:UpdateMacieSession", - "macie:UpdateMemberSession", - "macie:UpdateOrganizationConfiguration" - ], - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "CloudFormationStatement", - "Effect": "Deny", - "Action": ["cloudformation:Delete*"], - "Resource": "arn:${PARTITION}:cloudformation:*:*:stack/${ACCELERATOR_PREFIX}-*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/AWSControlTowerExecution", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - }, - { - "Sid": "PreventSSMModification", - "Effect": "Deny", - "Action": ["ssm:DeleteParameter*", "ssm:PutParameter"], - "Resource": "arn:${PARTITION}:ssm:*:*:parameter${ACCELERATOR_SSM_PREFIX}*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalARN": [ - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}-*", - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/quarantine.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/quarantine.json deleted file mode 100644 index b88eace..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/service-control-policies/quarantine.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "DenyAllAWSServicesExceptBreakglassRoles", - "Effect": "Deny", - "Action": "*", - "Resource": "*", - "Condition": { - "ArnNotLike": { - "aws:PrincipalArn": [ - "arn:${PARTITION}:iam::*:role/${MANAGEMENT_ACCOUNT_ACCESS_ROLE}", - "arn:${PARTITION}:iam::*:role/aws*", - "arn:${PARTITION}:iam::*:role/${ACCELERATOR_PREFIX}*", - "arn:${PARTITION}:iam::*:role/cdk-accel-*" - ] - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-instance-profile.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-instance-profile.yaml deleted file mode 100644 index 543ba02..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-instance-profile.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: Associate AWS Iam Instance Profile to EC2 Instance -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - IamInstanceProfile: - type: String - InstanceId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: associateIamProfile - action: 'aws:executeAwsApi' - inputs: - Service: ec2 - Api: associate_iam_instance_profile - IamInstanceProfile: - Name: '{{ IamInstanceProfile }}' - InstanceId: '{{ InstanceId }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-role-policy.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-role-policy.yaml deleted file mode 100644 index 58cc557..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/attach-iam-role-policy.yaml +++ /dev/null @@ -1,50 +0,0 @@ -description: IAM Role Policy -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - ResourceId: - type: String - AWSManagedPolicies: - type: StringList - CustomerManagedPolicies: - type: StringList - minItems: 0 - default: [] - AutomationAssumeRole: - type: String -mainSteps: - - name: attachPolicy - action: 'aws:executeScript' - inputs: - Runtime: python3.7 - Handler: script_handler - Script: |- - import boto3 - partition = boto3.client("sts").get_caller_identity()['Arn'].split(":")[1] - iam = boto3.client("iam") - config = boto3.client("config") - def script_handler(events, context): - resource_id = events["ResourceId"] - response = config.batch_get_resource_config( - resourceKeys=[{ - 'resourceType': 'AWS::IAM::Role', - 'resourceId': resource_id - }] - ) - role_name = response["baseConfigurationItems"][0]['resourceName'] - aws_policy_names = events["AWSManagedPolicies"] - customer_policy_names = events["CustomerManagedPolicies"] - for policy in aws_policy_names: - iam.attach_role_policy( - PolicyArn="arn:%s:iam::aws:policy/%s" %(partition, policy), - RoleName=role_name - ) - for policy in customer_policy_names: - iam.attach_role_policy( - PolicyArn=policy, - RoleName=role_name - ) - InputPayload: - ResourceId: '{{ ResourceId }}' - AWSManagedPolicies: '{{ AWSManagedPolicies }}' - CustomerManagedPolicies: '{{ CustomerManagedPolicies }}' \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/s3-encryption.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/s3-encryption.yaml deleted file mode 100644 index 79bc510..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/s3-encryption.yaml +++ /dev/null @@ -1,28 +0,0 @@ -description: Enables Encryption on S3 Bucket -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - BucketName: - type: String - description: (Required) The name of the S3 Bucket whose content will be encrypted. - KMSMasterKey: - type: String - description: (Optional) AWS Key Management Service (KMS) customer master key ARN to use for the default encryption. - AutomationAssumeRole: - type: String - description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. - default: "" -mainSteps: -- name: PutBucketEncryption - action: aws:executeAwsApi - inputs: - Service: s3 - Api: PutBucketEncryption - Bucket: "{{BucketName}}" - ServerSideEncryptionConfiguration: - Rules: - - - ApplyServerSideEncryptionByDefault: - SSEAlgorithm: "aws:kms" - KMSMasterKeyID: "{{KMSMasterKey}}" - isEnd: true \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/ssm-elb-enable-logging.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/ssm-elb-enable-logging.yaml deleted file mode 100644 index 00af3e2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/ssm-elb-enable-logging.yaml +++ /dev/null @@ -1,44 +0,0 @@ -description: Enable logging on Elastic Load-Balancer -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - LogDestination: - type: String - LoadBalancerArn: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: getAccount - action: 'aws:executeAwsApi' - inputs: - Service: sts - Api: get_caller_identity - outputs: - - Name: Id - Selector: $.Account - Type: String - - name: getLoadBalancer - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: describe_load_balancers - LoadBalancerArns: - - '{{ LoadBalancerArn }}' - outputs: - - Name: Name - Selector: $.LoadBalancers[0].LoadBalancerName - Type: String - - name: enableLogging - action: 'aws:executeAwsApi' - inputs: - Service: elbv2 - Api: modify_load_balancer_attributes - LoadBalancerArn: '{{ LoadBalancerArn }}' - Attributes: - - Key: access_logs.s3.enabled - Value: 'true' - - Key: access_logs.s3.bucket - Value: '{{ LogDestination }}' - - Key: access_logs.s3.prefix - Value: 'elb/elb-{{ getLoadBalancer.Name }}/{{ getAccount.Id }}' diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/waf-enable-logging.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/waf-enable-logging.yaml deleted file mode 100644 index be8fb95..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/ssm-documents/waf-enable-logging.yaml +++ /dev/null @@ -1,120 +0,0 @@ -description: Adds logging to non-compliant WebACLs -schemaVersion: "0.3" -assumeRole: "{{ AutomationAssumeRole }}" -parameters: - WebACLId: - type: String - AutomationAssumeRole: - type: String -mainSteps: - - name: performRemediation - action: "aws:executeScript" - inputs: - Runtime: python3.7 - Handler: handler - Script: |- - import boto3 - import json - import os - - # - # This Lambda function ensures that all WAF web ACLs have logging enabled. - # - # Trigger Type: SSM Automation - # Scope of Automation: AWS::WAF::WebACL & AWS::WAFRegional::WebACL - # - - CLOUDWATCH_LOG_GROUP = 'aws-waf-logs-AWSAccelerator' - - def evaluate_compliance(webAclName): - hasConfig = False - - #Setting up variables - client = '' - response = '' - wafArn = '' - cloudwatchArn = '' - - - #Check if this is a WAFv2. The ResourceId passed in is already the ARN - if webAclName.find(':wafv2:') >= 0: - wafArn = webAclName - client = boto3.client('wafv2') - else: - - isWebAcl = True - #Test if this is AWS::WAF::WebACL - try: - print('Testing for WAF::WebACL') - client = boto3.client('waf') - response = client.get_web_acl(WebACLId=webAclName) - except: - isWebAcl = False - pass - - if not isWebAcl: - #Test if this is AWS::WAFRegional::WebACL - try: - print('Testing for WAFRegional::WebACL') - client = boto3.client('waf-regional') - response = client.get_web_acl(WebACLId=webAclName) - except: - pass - - wafArn = response['WebACL']['WebACLArn'] - print('wafArn:' + wafArn) - - - try: - response = client.get_logging_configuration(ResourceArn=wafArn) - hasConfig = True - except: - print('Attempting to fix non-compliance') - print('WAF ARN: ' + wafArn) - - cwClient = boto3.client('logs') - - # check if CloudWatch log group exists - log_group_exists = False - response = cwClient.describe_log_groups(logGroupNamePrefix=CLOUDWATCH_LOG_GROUP) - log_groups = response['logGroups'] - print('LogGroups') - print(log_groups) - for log_group in log_groups: - if log_group['logGroupName'] == CLOUDWATCH_LOG_GROUP: - log_group_exists = True - cloudwatchArn = log_group['arn'] #record ARN - print('Log group already exists. Using:' + cloudwatchArn) - - #create log group if not exists - if log_group_exists == False: - print('Creating log group:' + CLOUDWATCH_LOG_GROUP) - response = cwClient.create_log_group( - logGroupName=CLOUDWATCH_LOG_GROUP - ) - print('Response from CreateLogGroup') - print(response) - #get newly created log group ARN - response = cwClient.describe_log_groups(logGroupNamePrefix=CLOUDWATCH_LOG_GROUP) - log_groups = response['logGroups'] - for log_group in log_groups: - if log_group['logGroupName'] == CLOUDWATCH_LOG_GROUP: - cloudwatchArn = log_group['arn'] #record ARN - - cloudwatchArn=cloudwatchArn.rstrip('*') - cloudwatchArn=cloudwatchArn.rstrip(':') - print('cloudwatchArn:' + cloudwatchArn) - print('Attempting to put ' + cloudwatchArn + 'as logging configuration for ' + wafArn) - response = client.put_logging_configuration(LoggingConfiguration={'ResourceArn': wafArn,'LogDestinationConfigs': [ cloudwatchArn ]}) - print ('## RESPONSE') - print(response) - - def handler(event, context): - print('## ENVIRONMENT VARIABLES') - print(os.environ) - print('## EVENT') - print(event) - aclName = event['WebACLId'] - evaluate_compliance(aclName) - InputPayload: - WebACLId: "{{ WebACLId }}" diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/tagging-policies/org-tag-policy.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/tagging-policies/org-tag-policy.json deleted file mode 100644 index e49fbb0..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/tagging-policies/org-tag-policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tags": { - "costcenter": { - "tag_key": { - "@@assign": "CostCenter" - }, - "tag_value": { - "@@assign": [ - "100", - "200" - ] - } - } - } -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/test-configuration/config.yaml b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/test-configuration/config.yaml deleted file mode 100644 index 482e914..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/test-configuration/config.yaml +++ /dev/null @@ -1,22 +0,0 @@ -tests: - - name: validate main transit gateway - description: Validate Main Transit Gateway - suite: network - testTarget: validateTransitGateway - expect: PASS - parameters: - name: Main - accountId: '' - region: us-east-1 - amazonSideAsn: '65521' - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTableNames: - - core - - segregated - - shared - - standalone - shareTargetAccountIds: [] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/default.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/default.json deleted file mode 100644 index f65610c..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "Statement": [ - { - "Sid": "AllowRequestsByOrgsIdentities", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "${ORG_ID}" - } - } - }, - { - "Sid": "AllowRequestsByAWSServicePrincipals", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "*", - "Resource": "*", - "Condition": { - "Bool": { - "aws:PrincipalIsAWSService": "true" - } - } - } - ] -} diff --git a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/ec2.json b/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/configs/snapshot-only/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/accelerator/test/customizations-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/customizations-stack.test.ts deleted file mode 100644 index aaac98e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/customizations-stack.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(CustomizationsStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.CUSTOMIZATIONS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; -const sharedServicesStack = acceleratorTestStacks.stacks.get(`SharedServices-us-east-1`)!; - -describe('CustomizationsStack', () => { - snapShotTest(testNamePrefix, stack); -}); -describe('CustomizationsStack', () => { - snapShotTest(testNamePrefix, sharedServicesStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/dependencies-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/dependencies-stack.test.ts deleted file mode 100644 index 4f9f120..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/dependencies-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(DependenciesStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.DEPENDENCIES, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('DependenciesStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/diagnostics-pack-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/diagnostics-pack-stack.test.ts deleted file mode 100644 index d408a74..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/diagnostics-pack-stack.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { DiagnosticsPackStack } from '../lib/stacks/diagnostics-pack-stack'; -import { snapShotTest } from './snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(DiagnosticsPackStack): '; - -/** - * DiagnosticsPack Stack - */ -const app = new cdk.App(); -const stack = new DiagnosticsPackStack(app, 'DiagnosticsPackStack', { - acceleratorPrefix: 'AWSAccelerator', - ssmParamPrefix: '/accelerator', - bucketNamePrefix: 'aws-accelerator', - installerStackName: 'AWSAccelerator-InstallerStack', - configRepositoryName: 'aws-accelerator-config', - qualifier: 'aws-accelerator', - env: { - account: '000000000000', - region: 'us-east-1', - }, -}); - -describe('DiagnosticsPackStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/finalize-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/finalize-stack.test.ts deleted file mode 100644 index e6484f2..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/finalize-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(FinalizeStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.FINALIZE, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('FinalizeStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/identity-center-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/identity-center-stack.test.ts deleted file mode 100644 index 54481c7..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/identity-center-stack.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(IdentityCenterStack): '; - -/** - * IdentityCenter Stack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.IDENTITY_CENTER, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('IdentityCenterStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/integ-test-aspects/integ.aspects.ts b/source/packages/@aws-accelerator/accelerator/test/integ-test-aspects/integ.aspects.ts deleted file mode 100644 index e93ca77..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/integ-test-aspects/integ.aspects.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/accelerator/test/integ-test-aspects --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct, IConstruct } from 'constructs'; -import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { AcceleratorAspects } from '../../lib/accelerator-aspects'; -import { version } from '../../../../../package.json'; -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - node.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); - } - } -} - -// CDK App for Integration Tests -const app = new cdk.App(); - -export class AspectIntegTestStack extends cdk.Stack { - public function128Name: string; - public function1024Name: string; - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - - const function128 = new cdk.aws_lambda.Function(this, 'Function128', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - code: cdk.aws_lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), - memorySize: 128, - }); - - const function1024 = new cdk.aws_lambda.Function(this, 'Function1024', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - code: cdk.aws_lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), - memorySize: 1024, - }); - - cdk.Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - new AcceleratorAspects(app, 'aws', false); - this.function128Name = function128.functionName; - this.function1024Name = function1024.functionName; - } -} - -// Stack under test -const stackUnderTest = new AspectIntegTestStack(app, 'AspectIntegTestStack', { - description: 'This stack includes the applications resources for integration testing.', -}); - -// Initialize Integ Test construct -const integ = new IntegTest(app, 'AspectIntegTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -// test that the aspect increases default memory from 128 to 512 -integ.assertions - .awsApiCall('Lambda', 'getFunction', { FunctionName: stackUnderTest.function128Name }) - .expect( - ExpectedResult.objectLike({ - Configuration: { MemorySize: 512 }, - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(5) }); - -// test that the aspect ignores functions with memorySize defined > 512 & AWS Solutions env variable is populated -integ.assertions - .awsApiCall('Lambda', 'getFunction', { FunctionName: stackUnderTest.function1024Name }) - .expect( - ExpectedResult.objectLike({ - Configuration: { - MemorySize: 1024, - Environment: { - Variables: { - SOLUTION_ID: `AwsSolution/SO0199/${version}`, - }, - }, - }, - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(5) }); diff --git a/source/packages/@aws-accelerator/accelerator/test/key-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/key-stack.test.ts deleted file mode 100644 index 66b1217..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/key-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(KeyStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.KEY, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Audit-us-east-1`)!; - -describe('KeyStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/logging-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/logging-stack.test.ts deleted file mode 100644 index 68401ae..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/logging-stack.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(LoggingStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.LOGGING, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`LogArchive-us-east-1`)!; - -describe('LoggingStack', () => { - snapShotTest(testNamePrefix, stack); -}); - -const acceleratorTestStacksOuTargets = new AcceleratorSynthStacks( - AcceleratorStage.LOGGING, - 'aws', - 'us-east-1', - 'all-enabled-ou-targets', -); -const stackOuTargets = acceleratorTestStacksOuTargets.stacks.get(`LogArchive-us-east-1`)!; - -describe('LoggingStackOuTargets', () => { - snapShotTest('Construct(LoggingStackOuTargets): ', stackOuTargets); -}); - -const centralizedRegionTestStacks = new AcceleratorSynthStacks(AcceleratorStage.LOGGING, 'aws', 'us-west-2'); -const centralizedRegionTestStack = centralizedRegionTestStacks.stacks.get(`LogArchive-us-west-2`)!; - -describe('LoggingStack', () => { - snapShotTest(testNamePrefix, centralizedRegionTestStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/network-associations-gwlb-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/network-associations-gwlb-stack.test.ts deleted file mode 100644 index 115f048..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/network-associations-gwlb-stack.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe } from '@jest/globals'; - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(NetworkAssociationsGwlbStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB, - 'aws', - 'us-east-1', -); -const stack = acceleratorTestStacks.stacks.get(`Network-us-east-1`)!; - -describe('NetworkAssociationsGwlbStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/network-associations-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/network-associations-stack.test.ts deleted file mode 100644 index a5fceb4..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/network-associations-stack.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe } from '@jest/globals'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(NetworkAssociationsStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.NETWORK_ASSOCIATIONS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Network-us-east-1`)!; - -describe('NetworkAssociationsStack', () => { - snapShotTest(testNamePrefix, stack); -}); -const noVpcFlowLogTestStack = new AcceleratorSynthStacks( - AcceleratorStage.NETWORK_ASSOCIATIONS, - 'aws', - 'us-east-1', - 'all-enabled-ou-targets', -); -const noVpcFlowLogStack = noVpcFlowLogTestStack.stacks.get(`Network-us-east-1`)!; - -describe('NoVpcFlowLogStack', () => { - snapShotTest(testNamePrefix, noVpcFlowLogStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/network-prep-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/network-prep-stack.test.ts deleted file mode 100644 index ca44a71..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/network-prep-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(NetworkPrepStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.NETWORK_PREP, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Network-us-east-1`)!; - -describe('NetworkPrepStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/network-vpc-dns-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/network-vpc-dns-stack.test.ts deleted file mode 100644 index c791dad..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/network-vpc-dns-stack.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(NetworkVpcDnsStack): '; - -/** - * NetworkVpcEndpointsStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.NETWORK_VPC_DNS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Network-us-east-1`)!; - -describe('NetworkVpcDnsStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/network-vpc-endpoints-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/network-vpc-endpoints-stack.test.ts deleted file mode 100644 index f31b0cc..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/network-vpc-endpoints-stack.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(NetworkVpcEndpointsStack): '; - -/** - * NetworkVpcEndpointsStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.NETWORK_VPC_ENDPOINTS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Network-us-east-1`)!; - -/** - * NetworkVpcStack construct test - */ -describe('NetworkVpcEndpointsStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/network-vpc-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/network-vpc-stack.test.ts deleted file mode 100644 index 7745d12..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/network-vpc-stack.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(NetworkVpcStack): '; - -/** - * NetworkVpcStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.NETWORK_VPC, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Network-us-east-1`)!; - -describe('NetworkVpcStack', () => { - snapShotTest(testNamePrefix, stack); -}); - -const noVpcFlowLogTestStack = new AcceleratorSynthStacks( - AcceleratorStage.NETWORK_VPC, - 'aws', - 'us-east-1', - 'all-enabled-ou-targets', -); -const noVpcFlowLogStack = noVpcFlowLogTestStack.stacks.get(`Management-us-east-1`)!; - -describe('NoVpcFlowLogStack', () => { - snapShotTest(testNamePrefix, noVpcFlowLogStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/operations-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/operations-stack.test.ts deleted file mode 100644 index f36556d..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/operations-stack.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(OperationsStack): '; - -/** - * OperationsStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.OPERATIONS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('OperationsStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/organizations-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/organizations-stack.test.ts deleted file mode 100644 index 71e0222..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/organizations-stack.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe, test } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; -import { Template } from 'aws-cdk-lib/assertions'; - -const testNamePrefix = 'Construct(OrganizationsStack): '; - -/** - * OrganizationsStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.ORGANIZATIONS, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('OrganizationsStack', () => { - snapShotTest(testNamePrefix, stack); -}); - -const multiOuTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.ORGANIZATIONS, - 'aws', - 'us-east-1', - 'all-enabled-ou-targets', -); -const multiOuStack = multiOuTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('MultiOuOrganizationsStack', () => { - snapShotTest(testNamePrefix, multiOuStack); -}); - -const delegatedAdminTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.ORGANIZATIONS, - 'aws', - 'us-east-1', - 'all-enabled-delegated-admin', -); -const delegatedAdminStack = delegatedAdminTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('delegatedAdminStack', () => { - snapShotTest(testNamePrefix, delegatedAdminStack); -}); - -describe('tagging policies', () => { - test("two OU's both get tagging policies", () => { - const template = Template.fromStack(multiOuStack); - - template.hasResourceProperties('Custom::CreatePolicy', { name: 'BackupPolicy', type: 'BACKUP_POLICY' }); - template.hasResourceProperties('Custom::AttachPolicy', { targetId: 'ou-asdf-11111111', type: 'TAG_POLICY' }); - template.hasResourceProperties('Custom::AttachPolicy', { targetId: 'ou-asdf-22222222', type: 'TAG_POLICY' }); - - // 2 policies for backup and tagging policies, 2 targets -> 4 attachments - template.resourceCountIs('Custom::CreatePolicy', 2); - template.resourceCountIs('Custom::AttachPolicy', 4); - }); - - test('Root OU gets tagging policies', () => { - const template = Template.fromStack(stack); - - template.hasResourceProperties('Custom::CreatePolicy', { name: 'TagPolicy01', type: 'TAG_POLICY' }); - template.hasResourceProperties('Custom::AttachPolicy', { targetId: 'r-asdf', type: 'TAG_POLICY' }); - - // 4 policies for backup and tagging policies, 1 target -> 2 attachments - template.resourceCountIs('Custom::CreatePolicy', 4); - template.resourceCountIs('Custom::AttachPolicy', 4); - }); -}); - -describe('backup policies', () => { - test("two OU's both get backup policies", () => { - const template = Template.fromStack(multiOuStack); - - template.hasResourceProperties('Custom::CreatePolicy', { name: 'BackupPolicy', type: 'BACKUP_POLICY' }); - template.hasResourceProperties('Custom::AttachPolicy', { targetId: 'ou-asdf-11111111', type: 'BACKUP_POLICY' }); - template.hasResourceProperties('Custom::AttachPolicy', { targetId: 'ou-asdf-22222222', type: 'BACKUP_POLICY' }); - - // 2 policies for backup and tagging policies, 2 targets -> 4 attachments - template.resourceCountIs('Custom::CreatePolicy', 2); - template.resourceCountIs('Custom::AttachPolicy', 4); - }); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/pipeline-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/pipeline-stack.test.ts deleted file mode 100644 index 527cb30..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/pipeline-stack.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { PipelineStack } from '../lib/stacks/pipeline-stack'; -import { snapShotTest } from './snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(PipelineStack): '; - -/** - * Pipeline Stack - */ -const app = new cdk.App(); -const stack = new PipelineStack(app, 'PipelineStack', { - sourceRepository: 'codecommit', - sourceRepositoryOwner: 'awslabs', - sourceRepositoryName: 'accelerator-source', - sourceBranchName: 'main', - enableApprovalStage: true, - qualifier: 'aws-accelerator', - managementAccountId: app.account, - managementAccountRoleName: 'AcceleratorAccountAccessRole', - managementAccountEmail: 'accelerator-root@example.com', - logArchiveAccountEmail: 'accelerator-log-archive@example.com', - auditAccountEmail: 'accelerator-audit@example.com', - controlTowerEnabled: 'Yes', - partition: 'aws', - env: { - account: '000000000000', - region: 'us-east-1', - }, - useExistingConfigRepo: false, - configRepositoryName: 'aws-accelerator-config', - configRepositoryBranchName: 'main', - prefixes: { - accelerator: 'AWSAccelerator', - kmsAlias: 'alias/accelerator', - bucketName: 'aws-accelerator', - ssmParamName: '/accelerator', - snsTopicName: 'accelerator', - repoName: 'aws-accelerator', - secretName: '/accelerator', - trailLogName: 'aws-accelerator', - databaseName: 'aws-accelerator', - }, - enableSingleAccountMode: false, - pipelineAccountId: '000000000000', - useExistingRoles: false, - // installerStackName: 'InstallerStack', -}); - -describe('PipelineStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/prepare-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/prepare-stack.test.ts deleted file mode 100644 index 645d525..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/prepare-stack.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(PrepareStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.PREPARE, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('PrepareStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/prerequisites.test.ts b/source/packages/@aws-accelerator/accelerator/test/prerequisites.test.ts deleted file mode 100644 index f181044..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/prerequisites.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { checkPrerequisiteParameters, main } from '../lib/prerequisites'; -import { describe, test, beforeEach, afterAll, jest, expect, afterEach } from '@jest/globals'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { STSClient, AssumeRoleCommand, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; -import { GetServiceQuotaCommand, ServiceQuotasClient } from '@aws-sdk/client-service-quotas'; - -import * as path from 'path'; - -let stsMock: AwsClientStub; -let serviceQuotasMock: AwsClientStub; - -describe('skip prerequisites', () => { - const OLD_ENV = process.env; - const OLD_ARGS = process.argv; - - beforeEach(() => { - jest.resetModules(); // Most important - it clears the cache - process.env = { ...OLD_ENV }; // Make a copy - process.argv = [...OLD_ARGS]; // Make a copy - }); - - afterAll(() => { - process.env = OLD_ENV; // Restore old env - process.argv = OLD_ARGS; // Restore old args - }); - test('nothing runs', async () => { - // Set the variables - process.env['ACCELERATOR_SKIP_PREREQUISITES'] = 'true'; - process.argv = [ - 'ts-node', - 'packages/@aws-accelerator/accelerator/lib/prerequisites.ts', - '--config-dir', - '/test/config/accel006_config', - '--partition', - 'aws', - '--account', - '111111111111', - '--region', - 'us-east-2', - ]; - }); -}); - -describe('test inputs to function', () => { - test('no account specified but region specified', async () => { - expect(async () => { - await main(undefined, 'region', true, '/test/config/accel006_config', 'aws'); - }).rejects.toThrow(); - }); - test('no minimal, no account specified but region specified', async () => { - expect(async () => { - await main(undefined, 'region', false, '/test/config/accel006_config', 'aws'); - }).rejects.toThrow(); - }); - test('minimal specified account and region not specified', async () => { - expect(async () => { - await main(undefined, undefined, true, '/test/config/accel006_config', 'aws'); - }).rejects.toThrow(); - }); - test('all valid inputs', () => { - const configDirPath = path.join(__dirname, 'configs/snapshot-only/'); - expect(checkPrerequisiteParameters(undefined, undefined, true, configDirPath, 'aws')).toBeTruthy(); - }); -}); - -describe('run prerequisites', () => { - beforeEach(() => { - stsMock = mockClient(STSClient); - serviceQuotasMock = mockClient(ServiceQuotasClient); - }); - afterEach(() => { - stsMock.reset(); - serviceQuotasMock.reset(); - }); - - test('account region specific run', async () => { - // Set the variables - const configDirPath = path.join(__dirname, 'configs/snapshot-only/'); - process.env['ACCELERATOR_SKIP_PREREQUISITES'] = 'false'; - process.argv = [ - 'ts-node', - 'packages/@aws-accelerator/accelerator/lib/prerequisites.ts', - '--config-dir', - configDirPath, - '--partition', - 'aws', - '--account', - '111111111111', - '--region', - 'us-east-2', - ]; - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).resolves({ - Quota: { Value: 10 }, - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 1000 }, - }); - const result = await main('111111111111', 'us-east-2', false, configDirPath, 'aws'); - expect(result).toBeUndefined(); - }); - test('minimal run', async () => { - // Set the variables - const configDirPath = path.join(__dirname, 'configs/snapshot-only/'); - process.env['ACCELERATOR_SKIP_PREREQUISITES'] = 'false'; - process.argv = [ - 'ts-node', - 'packages/@aws-accelerator/accelerator/lib/prerequisites.ts', - '--config-dir', - configDirPath, - '--partition', - 'aws', - '--minimal', - ]; - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).resolves({ - Quota: { Value: 10 }, - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 1000 }, - }); - const result = await main(undefined, undefined, true, configDirPath, 'aws'); - expect(result).toBeUndefined(); - }); - test('build run', async () => { - // Set the variables - const configDirPath = path.join(__dirname, 'configs/snapshot-only/'); - process.env['ACCELERATOR_SKIP_PREREQUISITES'] = 'false'; - process.argv = [ - 'ts-node', - 'packages/@aws-accelerator/accelerator/lib/prerequisites.ts', - '--config-dir', - configDirPath, - '--partition', - 'aws', - ]; - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - stsMock.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'fake-access-key', - SecretAccessKey: 'fake-secret-key', - SessionToken: 'fake-session-token', - Expiration: new Date(Date.now() + 3600 * 1000), - }, - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).resolves({ - Quota: { Value: 10 }, - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 1000 }, - }); - const result = await main(undefined, undefined, false, configDirPath, 'aws'); - expect(result).toBeUndefined(); - }); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/resource-policy-enforcement-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/resource-policy-enforcement-stack.test.ts deleted file mode 100644 index aff659a..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/resource-policy-enforcement-stack.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(ResourcePolicyEnforcementStack): '; - -/** - * ResourcePolicyEnforcementStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT, - 'aws', - 'us-east-1', -); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('ResourcePolicyEnforcementStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/security-audit-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/security-audit-stack.test.ts deleted file mode 100644 index aabc999..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/security-audit-stack.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(SecurityAuditStack): '; - -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.SECURITY_AUDIT, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Audit-us-east-1`)!; - -describe('SecurityAuditStack', () => { - snapShotTest(testNamePrefix, stack); -}); - -const delegatedAdminTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.SECURITY_AUDIT, - 'aws', - 'us-east-1', - 'all-enabled-delegated-admin', -); -const delegatedAdminStack = delegatedAdminTestStacks.stacks.get(`Audit-us-east-1`)!; - -describe('delegatedAdminStack', () => { - snapShotTest(testNamePrefix, delegatedAdminStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/security-resources-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/security-resources-stack.test.ts deleted file mode 100644 index e64598e..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/security-resources-stack.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(SecurityResourcesStack): '; - -/** - * SecurityResourcesStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.SECURITY_RESOURCES, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('SecurityResourcesStack', () => { - snapShotTest(testNamePrefix, stack); -}); - -const delegatedAdminTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.SECURITY_RESOURCES, - 'aws', - 'us-east-1', - 'all-enabled-delegated-admin', -); -const delegatedAdminStack = delegatedAdminTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('delegatedAdminStack', () => { - snapShotTest(testNamePrefix, delegatedAdminStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/security-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/security-stack.test.ts deleted file mode 100644 index 4d461fc..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/security-stack.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorSynthStacks } from './accelerator-synth-stacks'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(SecurityStack): '; - -/** - * SecurityStack - */ -const acceleratorTestStacks = new AcceleratorSynthStacks(AcceleratorStage.SECURITY, 'aws', 'us-east-1'); -const stack = acceleratorTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('SecurityStack', () => { - snapShotTest(testNamePrefix, stack); -}); - -const delegatedAdminTestStacks = new AcceleratorSynthStacks( - AcceleratorStage.SECURITY, - 'aws', - 'us-east-1', - 'all-enabled-delegated-admin', -); -const delegatedAdminStack = delegatedAdminTestStacks.stacks.get(`Management-us-east-1`)!; - -describe('delegatedAdminStack', () => { - snapShotTest(testNamePrefix, delegatedAdminStack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/test/snapshot-test.ts b/source/packages/@aws-accelerator/accelerator/test/snapshot-test.ts deleted file mode 100644 index 6750486..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/snapshot-test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { SynthUtils } from '@aws-cdk/assert'; -import { expect, test } from '@jest/globals'; - -export function snapShotTest(testNamePrefix: string, stack: cdk.Stack) { - test(`${testNamePrefix} Snapshot Test`, () => { - // greedy implementation: eg, because "/path/home/temp.json" matches on - // temp.json, replace the whole string to "replaced-json-path.json". - const greedyJsonRegex = /[a-z0-9]+.json/; - - // limited: only match length of generated zip file or UUID spec lengths. - const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/; - const zipRegex = /[0-9a-f]{64}\.zip/; - const md5Regex = /^[0-9a-f]{32}$/; // limited: only match length of md5 hash. - - // test each serialized object - if any part of string matches regex - // replace with value of print() - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(uuidRegex) != null, - print: () => '"REPLACED-UUID"', - }); - - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(zipRegex) != null, - print: () => '"REPLACED-GENERATED-NAME.zip"', - }); - - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(greedyJsonRegex) != null, - print: () => '"REPLACED-JSON-PATH.json"', - }); - - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(md5Regex) != null && !val.startsWith('REPLACED'), - print: () => '"REPLACED-MD5"', - }); - expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); - }); -} diff --git a/source/packages/@aws-accelerator/accelerator/test/tester-pipeline-stack.test.ts b/source/packages/@aws-accelerator/accelerator/test/tester-pipeline-stack.test.ts deleted file mode 100644 index b2ce33c..0000000 --- a/source/packages/@aws-accelerator/accelerator/test/tester-pipeline-stack.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TesterPipelineStack } from '../lib/stacks/tester-pipeline-stack'; -import { describe } from '@jest/globals'; -import { snapShotTest } from './snapshot-test'; - -const testNamePrefix = 'Construct(TesterPipelineStack): '; - -/** - * TesterPipelineStack - */ -const app = new cdk.App(); -const stack = new TesterPipelineStack(app, 'TesterPipelineStack', { - sourceRepositoryName: 'accelerator-source', - sourceBranchName: 'main', - qualifier: 'aws-accelerator', - managementCrossAccountRoleName: 'AWSControlTowerExecution', - managementAccountId: app.account, - managementAccountRoleName: 'AcceleratorAccountAccessRole', - prefixes: { - accelerator: 'AWSAccelerator', - repoName: 'aws-accelerator', - bucketName: 'aws-accelerator', - ssmParamName: '/accelerator', - kmsAlias: 'alias/accelerator', - }, -}); - -describe('TesterPipelineStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/accelerator/tsconfig.json b/source/packages/@aws-accelerator/accelerator/tsconfig.json deleted file mode 100644 index 067f5b6..0000000 --- a/source/packages/@aws-accelerator/accelerator/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "bin/**/*", "cdk.ts", "index.ts"], - "exclude": ["cdk.out/**/*", "test/**/*"] -} diff --git a/source/packages/@aws-accelerator/accelerator/utils/app-utils.ts b/source/packages/@aws-accelerator/accelerator/utils/app-utils.ts deleted file mode 100644 index c4cb219..0000000 --- a/source/packages/@aws-accelerator/accelerator/utils/app-utils.ts +++ /dev/null @@ -1,1104 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - ASEAMapping, - ASEAMappings, - AccountsConfig, - CfnResourceType, - CustomizationsConfig, - GlobalConfig, - IamConfig, - StackResources, - NetworkConfig, - OrganizationConfig, - ReplacementsConfig, - SecurityConfig, -} from '@aws-accelerator/config'; -import * as cdk from 'aws-cdk-lib'; -import fs from 'fs'; -import path from 'path'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorStackProps } from '../lib/stacks/accelerator-stack'; -import { getCentralLogBucketKmsKeyArn } from '../lib/accelerator'; -import { AcceleratorResourceNames } from '../lib/accelerator-resource-names'; -import { - POLICY_LOOKUP_TYPE, - POLICY_LOOKUP_SCOPE, - ACCEL_POLICY_LOOKUP_REGEX, -} from '@aws-accelerator/utils/lib/policy-replacements'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import AWS from 'aws-sdk'; -import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; -const logger = createLogger(['app-utils']); -export interface AcceleratorContext { - /** - * The AWS partition - */ - partition: string; - /** - * Use existing roles - */ - useExistingRoles: boolean; - /** - * The directory containing the accelerator configuration files - */ - configDirPath?: string; - /** - * The pipeline stage - * - * @see {@link AcceleratorStage} - */ - stage?: string; - /** - * The AWS account ID - */ - account?: string; - /** - * The AWS region name - */ - region?: string; -} - -export interface AcceleratorResourcePrefixes { - /** - * Accelerator prefix - */ - accelerator: string; - /** - * Accelerator bucket name prefix - */ - bucketName: string; - /** - * Accelerator database name prefix - */ - databaseName: string; - /** - * Accelerator KMS key alias prefix - */ - kmsAlias: string; - /** - * Accelerator config repository name prefix - */ - repoName: string; - /** - * Accelerator Secrets Manager secret name prefix - */ - secretName: string; - /** - * Accelerator SNS topic name prefix - */ - snsTopicName: string; - /** - * Accelerator SSM parameter name prefix for solution defined resources - */ - ssmParamName: string; - /** - * Accelerator SSM parameter name prefix for imported resources - */ - importResourcesSsmParamName: string; - /** - * Accelerator CloudTrail log name prefix - */ - trailLogName: string; - /** - * Accelerator SSM log name prefix - */ - ssmLogName: string; -} - -export interface AcceleratorEnvironment { - /** - * Accelerator installer stack name - */ - installerStackName: string; - /** - * Flag indicating diagnostic pack enabled - */ - isDiagnosticsPackEnabled: string; - /** - * Audit (Security-Tooling) account email address - */ - auditAccountEmail: string; - /** - * Accelerator configuration repository name - */ - configRepositoryName: string; - /** - * Accelerator configuration repository branch name - * - * @default 'main' - */ - configRepositoryBranchName: string; - /** - * Whether or not Control Tower is enabled in the accelerator environment - */ - controlTowerEnabled: string; - /** - * Whether or not to enable the manual approval pipeline stage - * - * @default true - */ - enableApprovalStage: boolean; - /** - * Whether or not to enable single account mode - */ - enableSingleAccountMode: boolean; - /** - * Log Archive account email address - */ - logArchiveAccountEmail: string; - /** - * Management account email address - */ - managementAccountEmail: string; - /** - * Source code repository branch name - */ - sourceBranchName: string; - /** - * Source code repository location - * - * @default 'github' - */ - sourceRepository: string; - /** - * Source code repository name - * - * @default 'landing-zone-accelerator-on-aws' - */ - sourceRepositoryName: string; - /** - * Source code repository owner - * - * @default 'awslabs' - */ - sourceRepositoryOwner: string; - /** - * Use a configuration repository that already exists - */ - useExistingConfigRepo: boolean; - /** - * Notification email list for manual approval stage - */ - approvalStageNotifyEmailList?: string; - /** - * Configuration git commit ID - */ - configCommitId?: string; - /** - * AWS account ID for management account - */ - managementAccountId?: string; - /** - * Management account assume role name - */ - managementAccountRoleName?: string; - /** - * Cross-account assume role name - */ - managementCrossAccountRoleName?: string; - /** - * Accelerator qualifier - */ - qualifier?: string; - /** - * Accelerator pipeline account id, for external deployment it will be pipeline account otherwise management account - */ - pipelineAccountId: string; - /** - * Accelerator permission boundary as SSM path - * If permission boundary is already is applied then it will not overwrite it - * Only applies if that SSM is present in the account - */ - acceleratorPermissionBoundary?: string; -} - -/** - * Get accelerator app context from CLI input - * @param app - * @returns - */ -export function getContext(app: cdk.App): AcceleratorContext { - const partition = app.node.tryGetContext('partition'); - const useExistingRoles = app.node.tryGetContext('useExistingRoles') === 'true'; - - if (!partition) { - throw new Error('Partition value must be specified in app context'); - } - - return { - partition, - configDirPath: app.node.tryGetContext('config-dir'), - stage: app.node.tryGetContext('stage'), - account: app.node.tryGetContext('account'), - region: app.node.tryGetContext('region'), - useExistingRoles, - }; -} - -/** - * Set accelerator resource prefixes based on provided input - * from installer stack parameters - * @param prefix - * @returns - */ -export function setResourcePrefixes(prefix: string): AcceleratorResourcePrefixes { - return prefix === 'AWSAccelerator' - ? { - accelerator: prefix, - bucketName: 'aws-accelerator', - databaseName: 'aws-accelerator', - kmsAlias: 'alias/accelerator', - repoName: 'aws-accelerator', - secretName: '/accelerator', - snsTopicName: 'aws-accelerator', - ssmParamName: '/accelerator', - importResourcesSsmParamName: '/accelerator/imported-resources', - trailLogName: 'aws-accelerator', - ssmLogName: 'aws-accelerator', - } - : { - accelerator: prefix, - bucketName: prefix.toLocaleLowerCase(), - databaseName: prefix.toLocaleLowerCase(), - kmsAlias: `alias/${prefix}`, - repoName: prefix, - secretName: prefix, - snsTopicName: prefix, - ssmParamName: `/${prefix}`, - importResourcesSsmParamName: `/${prefix}/imported-resources`, - trailLogName: prefix, - ssmLogName: prefix, - }; -} - -/** - * Set config repository name based on provided input - * from installer stack parameters - * @param repoNamePrefix - * @param useExisting - * @param existingRepoName - * @param existingBranchName - * @param qualifier - * @returns - */ -function setConfigRepoName( - repoNamePrefix: string, - useExistingConfigRepo?: string, - existingRepoName?: string, - existingBranchName?: string, - qualifier?: string, -): string { - if (useExistingConfigRepo === 'Yes' && (!existingRepoName || !existingBranchName)) { - throw new Error( - 'Attempting to deploy pipeline stage(s) and environment variables are not set [EXISTING_CONFIG_REPOSITORY_NAME, EXISTING_CONFIG_REPOSITORY_BRANCH_NAME], when USE_EXISTING_CONFIG_REPO environment is set to Yes', - ); - } - - let configRepositoryName = `${repoNamePrefix}-config`; - if (useExistingConfigRepo === 'Yes') { - configRepositoryName = existingRepoName!; - } else { - if (qualifier) { - configRepositoryName = `${qualifier}-config`; - } - } - return configRepositoryName; -} - -/** - * Set accelerator environment variables - * @param env - * @param resourcePrefixes - * @param stage - * @returns - */ -export function setAcceleratorEnvironment( - env: NodeJS.ProcessEnv, - resourcePrefixes: AcceleratorResourcePrefixes, - stage?: string, -): AcceleratorEnvironment { - // Check for mandatory environment variables in PIPELINE stage - checkMandatoryEnvVariables(env, stage); - - // Set config repo name - const configRepositoryName = setConfigRepoName( - resourcePrefixes.repoName, - env['USE_EXISTING_CONFIG_REPO'], - env['EXISTING_CONFIG_REPOSITORY_NAME'], - env['EXISTING_CONFIG_REPOSITORY_BRANCH_NAME'], - env['ACCELERATOR_QUALIFIER'], - ); - - return { - installerStackName: env['INSTALLER_STACK_NAME'] ?? '', - pipelineAccountId: env['PIPELINE_ACCOUNT_ID'] ?? '', - isDiagnosticsPackEnabled: env['ENABLE_DIAGNOSTICS_PACK'] ?? 'Yes', - auditAccountEmail: env['AUDIT_ACCOUNT_EMAIL'] ?? '', - configRepositoryName, - configRepositoryBranchName: env['EXISTING_CONFIG_REPOSITORY_BRANCH_NAME'] ?? 'main', - controlTowerEnabled: env['CONTROL_TOWER_ENABLED'] ?? '', - enableApprovalStage: env['ACCELERATOR_ENABLE_APPROVAL_STAGE'] - ? env['ACCELERATOR_ENABLE_APPROVAL_STAGE'] === 'Yes' - : true, - enableSingleAccountMode: env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] === 'true', - logArchiveAccountEmail: env['LOG_ARCHIVE_ACCOUNT_EMAIL'] ?? '', - managementAccountEmail: env['MANAGEMENT_ACCOUNT_EMAIL'] ?? '', - sourceBranchName: env['ACCELERATOR_REPOSITORY_BRANCH_NAME'] ?? '', - sourceRepository: env['ACCELERATOR_REPOSITORY_SOURCE'] ?? 'github', - sourceRepositoryName: env['ACCELERATOR_REPOSITORY_NAME'] ?? 'landing-zone-accelerator-on-aws', - sourceRepositoryOwner: env['ACCELERATOR_REPOSITORY_OWNER'] ?? 'awslabs', - useExistingConfigRepo: env['USE_EXISTING_CONFIG_REPO'] === 'Yes', - approvalStageNotifyEmailList: env['APPROVAL_STAGE_NOTIFY_EMAIL_LIST'], - configCommitId: env['CONFIG_COMMIT_ID'], - managementAccountId: env['MANAGEMENT_ACCOUNT_ID'], - managementAccountRoleName: env['MANAGEMENT_ACCOUNT_ROLE_NAME'], - managementCrossAccountRoleName: env['MANAGEMENT_CROSS_ACCOUNT_ROLE_NAME'], - qualifier: env['ACCELERATOR_QUALIFIER'], - acceleratorPermissionBoundary: env['ACCELERATOR_PERMISSION_BOUNDARY'], - }; -} - -/** - * Checks for mandatory environment variables based on accelerator stage and throws - * an error if any are missing - * @param env - * @param stage - */ -function checkMandatoryEnvVariables(env: NodeJS.ProcessEnv, stage?: string) { - const missingVariables: string[] = []; - - if (stage === AcceleratorStage.PIPELINE) { - const mandatoryVariables = [ - 'AUDIT_ACCOUNT_EMAIL', - 'CONTROL_TOWER_ENABLED', - 'LOG_ARCHIVE_ACCOUNT_EMAIL', - 'MANAGEMENT_ACCOUNT_EMAIL', - 'ACCELERATOR_REPOSITORY_BRANCH_NAME', - ]; - - for (const variable of mandatoryVariables) { - if (!env[variable]) { - missingVariables.push(variable); - } - } - } - // Throw error if any mandatory variables are missing - if (missingVariables.length > 0) { - throw new Error(`Missing mandatory environment variables: ${missingVariables.join(', ')}`); - } -} - -/** - * Set stack properties for accelerator stacks - * @param context - * @param acceleratorEnv - * @param prefixes - * @param globalRegion - * @returns - */ -export async function setAcceleratorStackProps( - context: AcceleratorContext, - acceleratorEnv: AcceleratorEnvironment, - prefixes: AcceleratorResourcePrefixes, - globalRegion: string, -): Promise { - if (!context.configDirPath) { - return; - } - const homeRegion = GlobalConfig.loadRawGlobalConfig(context.configDirPath).homeRegion; - const orgsEnabled = OrganizationConfig.loadRawOrganizationsConfig(context.configDirPath).enable; - - const accountsConfig = AccountsConfig.load(context.configDirPath); - await accountsConfig.loadAccountIds( - context.partition, - acceleratorEnv.enableSingleAccountMode, - orgsEnabled, - accountsConfig, - ); - - const replacementsConfig = getReplacementsConfig(context.configDirPath, accountsConfig); - await replacementsConfig.loadReplacementValues({ region: homeRegion }, orgsEnabled); - const globalConfig = GlobalConfig.load(context.configDirPath, replacementsConfig); - const organizationConfig = OrganizationConfig.load(context.configDirPath, replacementsConfig); - await organizationConfig.loadOrganizationalUnitIds(context.partition); - - if (globalConfig.externalLandingZoneResources?.importExternalLandingZoneResources) { - await globalConfig.loadExternalMapping(); - await globalConfig.loadLzaResources(context.partition, prefixes.ssmParamName); - } - const centralizedLoggingRegion = globalConfig.logging.centralizedLoggingRegion ?? globalConfig.homeRegion; - - const acceleratorResourceNames = new AcceleratorResourceNames({ - prefixes: prefixes, - centralizedLoggingRegion, - }); - - let centralLogBucketCmkParameter: string = acceleratorResourceNames.parameters.centralLogBucketCmkArn; - if (globalConfig.logging.centralLogBucket?.importedBucket?.name) { - centralLogBucketCmkParameter = acceleratorResourceNames.parameters.importedCentralLogBucketCmkArn; - } - - const centralLogsBucketKmsKeyArn = await getCentralLogBucketKmsKeyArn( - centralizedLoggingRegion, - context.partition, - accountsConfig.getLogArchiveAccountId(), - globalConfig.managementAccountAccessRole, - centralLogBucketCmkParameter, - orgsEnabled, - ); - logger.debug(`Central logs bucket kms key arn: ${centralLogsBucketKmsKeyArn}`); - - const networkConfig = NetworkConfig.load(context.configDirPath, replacementsConfig); - const securityConfig = SecurityConfig.load(context.configDirPath, replacementsConfig); - /** - * Load VPC/VPCE info for accounts, data perimeter and finalize stage only - */ - if ( - includeStage(context, { - stage: AcceleratorStage.FINALIZE, - account: accountsConfig.getManagementAccountId(), - region: globalRegion, - }) || - includeStage(context, { - stage: AcceleratorStage.CUSTOMIZATIONS, - account: accountsConfig.getManagementAccountId(), - region: globalRegion, - }) || - includeStage(context, { - stage: AcceleratorStage.ACCOUNTS, - account: accountsConfig.getManagementAccountId(), - region: globalRegion, - }) - ) { - const lookupTypeAndAccountIdMap = getLookupTypeAndAccountIdMap( - organizationConfig, - securityConfig, - accountsConfig, - context.configDirPath, - ); - const accountVpcIds = await loadVpcIds( - globalConfig, - accountsConfig, - networkConfig, - globalConfig.managementAccountAccessRole, - context.partition, - Array.from(lookupTypeAndAccountIdMap.get(POLICY_LOOKUP_TYPE.VPC_ID) || []), - securityConfig.resourcePolicyEnforcement?.networkPerimeter?.managedVpcOnly || false, - ); - const accountVpcEndpointIds = await loadVpcEndpointIds( - globalConfig.managementAccountAccessRole, - context.partition, - Array.from(lookupTypeAndAccountIdMap.get(POLICY_LOOKUP_TYPE.VPCE_ID) || []), - globalConfig.enabledRegions, - ); - networkConfig.accountVpcIds = accountVpcIds; - networkConfig.accountVpcEndpointIds = accountVpcEndpointIds; - } - - return { - configDirPath: context.configDirPath, - accountsConfig: accountsConfig, - customizationsConfig: getCustomizationsConfig(context.configDirPath, replacementsConfig), - globalConfig, - iamConfig: IamConfig.load(context.configDirPath, replacementsConfig), - networkConfig, - organizationConfig: organizationConfig, - securityConfig, - replacementsConfig: replacementsConfig, - partition: context.partition, - globalRegion, - centralizedLoggingRegion, - prefixes, - useExistingRoles: context.useExistingRoles, - centralLogsBucketKmsKeyArn, - ...acceleratorEnv, - }; -} - -/** - * Checks if the stage is at or before the bootstrap stage in the LZA pipeline - * @param command - * @param stage - * @returns - */ - -export function isBeforeBootstrapStage(command: string, stage?: string): boolean { - const preBootstrapStages = [ - AcceleratorStage.PREPARE, - AcceleratorStage.ACCOUNTS, - AcceleratorStage.BOOTSTRAP, - ] as string[]; - if (command === 'bootstrap') { - return true; - } - if (!stage) { - return false; - } - - return preBootstrapStages.includes(stage); -} - -/** - * Get customizationsConfig object - * @param configDirPath - * @returns - */ -export function getCustomizationsConfig( - configDirPath: string, - replacementsConfig: ReplacementsConfig, -): CustomizationsConfig { - let customizationsConfig: CustomizationsConfig; - - // Create empty customizationsConfig if optional configuration file does not exist - if (fs.existsSync(path.join(configDirPath, CustomizationsConfig.FILENAME))) { - customizationsConfig = CustomizationsConfig.load(configDirPath, replacementsConfig); - } else { - customizationsConfig = new CustomizationsConfig(); - } - return customizationsConfig; -} - -/** - * Get replacementsConfig object - * @param configDirPath - * @returns - */ -export function getReplacementsConfig(configDirPath: string, accountsConfig: AccountsConfig): ReplacementsConfig { - let replacementsConfig: ReplacementsConfig; - - // Create empty replacementsConfig if optional configuration file does not exist - if (fs.existsSync(path.join(configDirPath, ReplacementsConfig.FILENAME))) { - replacementsConfig = ReplacementsConfig.load(configDirPath, accountsConfig); - } else { - replacementsConfig = new ReplacementsConfig(undefined, accountsConfig); - } - return replacementsConfig; -} - -/** - * Load all the VPC IDs under for accounts and regions - * @param managementAccountAccessRole - * @param partition - * @param accountIds - * @param managedVpcOnly - * @returns A map from account Id to VPC IDs in the account - */ -async function loadVpcIds( - globalConfig: GlobalConfig, - accountConfig: AccountsConfig, - networkConfig: NetworkConfig, - managementAccountAccessRole: string, - partition: string, - accountIds: string[], - managedVpcOnly: boolean, -) { - const accountVpcIdMap: { [key: string]: string[] } = {}; - const accountNameToVpcNameMap = getManagedVpcNamesByAccountNames(networkConfig); - - for (const accountId of accountIds) { - const regions = globalConfig.enabledRegions; - const ec2Clients = await getEc2ClientsByAccountAndRegions( - partition, - accountId, - regions, - managementAccountAccessRole, - ); - - const accountName = accountConfig.getAccountNameById(accountId); - const managedVpcNames: Set = accountName ? new Set(accountNameToVpcNameMap.get(accountName)) : new Set(); - - const vpcIds = await getVpcIdsByAccount(ec2Clients, managedVpcOnly, managedVpcNames); - accountVpcIdMap[accountId] = vpcIds; - } - - return accountVpcIdMap; -} - -/** - * Get all VPC IDs from all enabled regions regions - * @param ec2Clients - * @param managedVpcOnly - * @param managedVpcNames - * @returns - */ -async function getVpcIdsByAccount( - ec2Clients: AWS.EC2[], - managedVpcOnly?: boolean, - managedVpcNames?: Set, -): Promise { - const vpcIds: string[] = []; - - for (const ec2Client of ec2Clients) { - // Get all VPC IDs under the region bound to the client - let nextToken: string | undefined = undefined; - do { - const params: AWS.EC2.DescribeVpcsRequest = {}; - if (nextToken) { - params.NextToken = nextToken; - } - - const response = await throttlingBackOff(() => ec2Client.describeVpcs(params).promise()); - if (response.Vpcs) { - let vpcList = response.Vpcs.filter(vpc => vpc.VpcId); - if (managedVpcOnly) { - vpcList = vpcList.filter(vpc => isLzaManagedVpc(vpc, managedVpcNames!)); - } - vpcList.forEach(vpc => vpcIds.push(vpc.VpcId!)); - } - - nextToken = response.NextToken; - } while (nextToken); - } - - return vpcIds; -} - -/** - * Get all the VPC Endpoint IDs from the account in all enabled regions - * @param ec2Clients - * @param managedVpcOnly - * @param managedVpcNames - * @returns - */ -async function getVpcEndpointIdsByAccount(ec2Clients: AWS.EC2[]): Promise { - const vpceIds: string[] = []; - - for (const ec2Client of ec2Clients) { - // List all VPC Endpoint IDs - let nextToken: string | undefined = undefined; - do { - const params: AWS.EC2.DescribeVpcsRequest = {}; - if (nextToken) { - params.NextToken = nextToken; - } - - const response = await throttlingBackOff(() => ec2Client.describeVpcEndpoints(params).promise()); - if (response.VpcEndpoints) { - response.VpcEndpoints.filter(vpce => vpce.VpcEndpointId).forEach(vpce => vpceIds.push(vpce.VpcEndpointId!)); - } - - nextToken = response.NextToken; - } while (nextToken); - } - - return vpceIds; -} - -/** - * Load all the VPC Endpoint IDs for accounts and regions - * @param managementAccountAccessRole - * @param partition - * @param accountIds - * @param regions - * @returns - */ -async function loadVpcEndpointIds( - managementAccountAccessRole: string, - partition: string, - accountIds: string[], - regions: string[], -) { - const accountVpcEndpointIdMap: { [key: string]: string[] } = {}; - - for (const accountId of accountIds) { - const ec2Clients = await getEc2ClientsByAccountAndRegions( - partition, - accountId, - regions, - managementAccountAccessRole, - ); - - const vpcEndpointId = await getVpcEndpointIdsByAccount(ec2Clients); - accountVpcEndpointIdMap[accountId] = vpcEndpointId; - } - - return accountVpcEndpointIdMap; -} - -/** - * Retrieve the accounts ID for each lookup type by extracting and parsing ACCEL_LOOKUP placeholder from SCPs. - * - * @param organizationConfig - * @param accountsConfig - * @param configDirPath - * @returns A map from POLICY_LOOKUP_TYPE to accounts ID - */ -function getLookupTypeAndAccountIdMap( - organizationConfig: OrganizationConfig, - securityConfig: SecurityConfig, - accountsConfig: AccountsConfig, - configDirPath: string, -) { - const map: Map> = new Map(); - map.set(POLICY_LOOKUP_TYPE.VPC_ID, new Set()); - map.set(POLICY_LOOKUP_TYPE.VPCE_ID, new Set()); - - // 1. Get path of all the service control policy and resource based policy templates - const policyPathSet = new Set(); - organizationConfig.serviceControlPolicies.forEach(scp => policyPathSet.add(scp.policy)); - securityConfig.resourcePolicyEnforcement?.policySets.forEach(policySet => - policySet.resourcePolicies.forEach(rcp => policyPathSet.add(rcp.document)), - ); - - // 2. Extra all the dynamic parameters from policy templates - const dynamicParams = new Set(); - for (const policyPath of policyPathSet) { - const policyContent: string = fs.readFileSync(path.join(configDirPath, policyPath), 'utf8'); - const matches = policyContent.match(ACCEL_POLICY_LOOKUP_REGEX); - matches?.forEach(match => dynamicParams.add(match)); - } - - // 3. Get ID of accounts mentioned in dynamic parameters - for (const dynamicParam of dynamicParams) { - ACCEL_POLICY_LOOKUP_REGEX.lastIndex = 0; - const parameterReplacementNeeded = ACCEL_POLICY_LOOKUP_REGEX.exec(dynamicParam); - if (parameterReplacementNeeded) { - const replacementArray = parameterReplacementNeeded[1].split(':'); - if (replacementArray.length < 2) { - throw new Error(`Invalid POLICY_LOOKUP_VALUE: ${parameterReplacementNeeded[1]}`); - } - - const lookupType = replacementArray[0]; - const lookupScope = replacementArray[1]; - const accountIds = getAccountsByLookupScope(accountsConfig, replacementArray, lookupScope); - - accountIds.forEach(id => map.get(lookupType)?.add(id)); - } - } - - return map; -} - -/** - * - * @param replacementArray - * @param lookupScope - * @returns - */ -function getAccountsByLookupScope( - accountsConfig: AccountsConfig, - replacementArray: string[], - lookupScope: string, -): string[] { - if (lookupScope === POLICY_LOOKUP_SCOPE.ORG) { - return accountsConfig.getAccountIds(); - } else if (lookupScope === POLICY_LOOKUP_SCOPE.ACCOUNT) { - const accountName = replacementArray[2]; - return [accountsConfig.getAccountId(accountName)]; - } else if (lookupScope === POLICY_LOOKUP_SCOPE.OU) { - const organizationUnit = replacementArray[2]; - - const accounts = accountsConfig.getAccounts(false); - return accounts - .filter(account => account.organizationalUnit.startsWith(organizationUnit)) - .map(account => accountsConfig.getAccountId(account.name)); - } - - return []; -} - -function includeStage( - context: AcceleratorContext, - props: { stage: string; account?: string; region?: string }, -): boolean { - if (!context.stage) { - // Do not include PIPELINE or TESTER_PIPELINE in full synth/diff - if (['pipeline', 'tester-pipeline'].includes(props.stage)) { - return false; - } - return true; // No stage, return all other stacks - } - if (context.stage === props.stage) { - if (!context.account && !context.region) { - return true; // No account or region, return all stacks for synth/diff - } - if (props.account === context.account && props.region === context.region) { - return true; - } - } - return false; -} - -function getManagedVpcNamesByAccountNames(networkConfig: NetworkConfig) { - const map = new Map(); - for (const vpc of networkConfig.vpcs) { - const vpcNames = map.get(vpc.account) || []; - vpcNames.push(vpc.name); - map.set(vpc.account, vpcNames); - } - - return map; -} - -/** - * Check if a VPC is a LZA-managed VPC defined in accounts config - * @param vpc - * @param managedVpcNames - * @returns - */ -function isLzaManagedVpc(vpc: AWS.EC2.Vpc, managedVpcNames: Set): boolean { - const tag = vpc.Tags?.find(tag => tag.Key === 'Name' && tag.Value && managedVpcNames.has(tag.Value)); - return !!tag; -} - -/** - * Get all ec2 clients for the account and regions - * - * @param partition - * @param accountId - * @param regions - * @param managementAccountAccessRole - * @param managedResourceOnly - * @returns - */ -async function getEc2ClientsByAccountAndRegions( - partition: string, - accountId: string, - regions: string[], - managementAccountAccessRole: string, -) { - const stsClient = new AWS.STS({ region: process.env['AWS_REGION'] }); - const cred = await throttlingBackOff(() => - stsClient - .assumeRole({ - RoleArn: `arn:${partition}:iam::${accountId}:role/${managementAccountAccessRole}`, - RoleSessionName: 'cdk-build-time', - }) - .promise(), - ); - - const ec2Clients: AWS.EC2[] = []; - regions.forEach(region => - ec2Clients.push( - new AWS.EC2({ - region: region, - credentials: { - accessKeyId: cred.Credentials!.AccessKeyId, - secretAccessKey: cred.Credentials!.SecretAccessKey, - sessionToken: cred.Credentials!.SessionToken, - }, - }), - ), - ); - - return ec2Clients; -} - -export async function writeImportResources(props: { - credentials: AWS.STS.Credentials | undefined; - globalConfig: GlobalConfig; - mapping: ASEAMappings; - accountsConfig: AccountsConfig; -}) { - const mappings = props.globalConfig.externalLandingZoneResources?.templateMap; - const mappingBucket = props.globalConfig.externalLandingZoneResources?.mappingFileBucket; - const credentials = setCredentials(props.credentials); - const aseaResourcesPath = path.join('asea-assets', 'new', 'aseaResources.json'); - const aseaResources = (await fs.promises.readFile(aseaResourcesPath, 'utf-8')).toString(); - const s3Client = new S3Client({ - credentials, - region: props.globalConfig.homeRegion, - }); - await s3Client.send( - new PutObjectCommand({ - Bucket: mappingBucket, - Key: 'aseaResources.json', - Body: aseaResources, - ServerSideEncryption: 'AES256', - }), - ); - const mappingPromises = []; - const s3Promises = []; - if (!mappings || !mappingBucket) { - return; - } - for (const [, mapping] of Object.entries(mappings)) { - mappingPromises.push(handleMapping(mapping)); - } - const updatedMappings = await Promise.all(mappingPromises); - for (const updatedMapping of updatedMappings) { - if (updatedMapping) { - s3Promises.push( - writeMappingToS3({ - resources: updatedMapping.resources, - mapping: updatedMapping.mapping, - stack: updatedMapping.stack, - s3Client: s3Client, - mappingBucket: mappingBucket!, - }), - ); - if (updatedMapping.nestedStacks) { - const nestedStackPromises = updatedMapping.nestedStacks.map(nestedStack => - writeMappingToS3({ - resources: nestedStack.resources, - mapping: nestedStack.mapping, - stack: nestedStack.stack, - s3Client: s3Client, - mappingBucket: mappingBucket!, - }), - ); - s3Promises.push(...nestedStackPromises); - } - } - } -} - -async function handleMapping(mapping: ASEAMapping) { - let pathSuffix = 'template.json'; - if (mapping.logicalResourceId) { - pathSuffix = 'nested.template.json'; - } - const stackPath = `cdk.out/phase${mapping.phase}-${mapping.accountId}-${mapping.region}/${mapping.stackName}.${pathSuffix}`; - const resourcePath = path.join('asea-assets', 'new', mapping.resourcePath); - const resourceFile = (await fs.promises.readFile(resourcePath, 'utf8')).toString(); - const stackFile = (await fs.promises.readFile(stackPath, 'utf-8')).toString(); - const resources: CfnResourceType[] = JSON.parse(resourceFile); - const stack = JSON.parse(stackFile); - const stackResources: StackResources = stack['Resources']; - if (!stackResources) { - return; - } - const updatedResources = addNewResourcesFromStack(resources, stackResources); - const nestedStacks = await handleNestedStacksMapping(mapping); - - return { - resources: updatedResources, - stack, - mapping, - nestedStacks, - }; -} -async function handleNestedStacksMapping(mapping: ASEAMapping) { - const nestedStacks = []; - const nestedStackMappings = mapping.nestedStacks; - if (!nestedStackMappings) { - return; - } - for (const key of Object.keys(nestedStackMappings)) { - try { - const nestedStackLocation = `cdk.out/phase${mapping.phase}-${mapping.accountId}-${mapping.region}`; - const resourcePath = path.join('asea-assets', 'new', nestedStackMappings[key].resourcePath); - const resourceFile = (await fs.promises.readFile(resourcePath, 'utf8')).toString(); - const resources: CfnResourceType[] = JSON.parse(resourceFile); - const nestedStackLogicalId = nestedStackMappings[key].logicalResourceId; - const directoryList = fs.readdirSync(nestedStackLocation); - const stackFileName = directoryList.find( - file => file.includes(nestedStackLogicalId) && file.includes('.nested.template.json'), - ); - if (!stackFileName) { - logger.error( - `Could not find the nested stack that contained the name ${nestedStackLogicalId} in directory ${nestedStackLocation}`, - ); - continue; - } - const stackString = ( - await fs.promises.readFile(path.join(nestedStackLocation, stackFileName), 'utf-8') - ).toString(); - const stack = JSON.parse(stackString); - const stackResources: StackResources = stack['Resources']; - const updatedResources = addNewResourcesFromStack(resources, stackResources); - nestedStacks.push({ - resources: updatedResources, - stack, - mapping: nestedStackMappings[key], - }); - } catch (err) { - logger.error(`Couldn't get stack resources`); - throw err; - } - } - return nestedStacks; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function addNewResourcesFromStack(resources: CfnResourceType[], stackResources: StackResources) { - // Turn into a hash map for faster processing - const resourceObj = resources.reduce((acc: { [key: string]: CfnResourceType }, resource) => { - acc[resource.logicalResourceId] = resource; - return acc; - }, {}); - - for (const [logicalId, stackResource] of Object.entries(stackResources)) { - if (!resourceObj[logicalId]) { - resourceObj[logicalId] = { - logicalResourceId: logicalId, - resourceType: stackResource['Type'], - resourceMetadata: { - Type: stackResource['Type'], - Properties: stackResource['Properties'], - }, - }; - } else { - resourceObj[logicalId].resourceMetadata['Properties'] = stackResource['Properties']; - } - } - //convert back to array - const updatedResources = Object.keys(resourceObj).map(key => resourceObj[key]); - return updatedResources; -} - -async function writeMappingToS3(props: { - resources: CfnResourceType[]; - mapping: ASEAMapping; - stack: unknown; - s3Client: S3Client; - mappingBucket: string; -}) { - const localPath = 'asea-assets'; - const writePromises = []; - const stackWriteRequest = new PutObjectCommand({ - Bucket: props.mappingBucket, - Key: props.mapping.templatePath, - Body: JSON.stringify(props.stack, null, 2), - ServerSideEncryption: 'AES256', - }); - - const localStackWrite = fs.promises.writeFile( - path.join(localPath, props.mapping.templatePath), - JSON.stringify(props.stack, null, 2), - ); - - const resourceWriteRequest = new PutObjectCommand({ - Bucket: props.mappingBucket, - Key: props.mapping.resourcePath, - Body: JSON.stringify(props.resources, null, 2), - ServerSideEncryption: 'AES256', - }); - - const localResourceWrite = fs.promises.writeFile( - path.join(localPath, props.mapping.resourcePath), - JSON.stringify(props.resources, null, 2), - ); - - writePromises.push(props.s3Client.send(stackWriteRequest)); - writePromises.push(await props.s3Client.send(resourceWriteRequest)); - writePromises.push(localStackWrite); - writePromises.push(localResourceWrite); - - return Promise.all(writePromises); -} - -function setCredentials(stsCredentials: AWS.STS.Credentials | undefined) { - let credentials; - if (stsCredentials && stsCredentials.AccessKeyId && stsCredentials.SecretAccessKey && stsCredentials.SessionToken) { - credentials = { - accessKeyId: stsCredentials.AccessKeyId, - secretAccessKey: stsCredentials.SecretAccessKey, - sessionToken: stsCredentials.SessionToken, - }; - } - return credentials; -} diff --git a/source/packages/@aws-accelerator/accelerator/utils/import-stack-resources.ts b/source/packages/@aws-accelerator/accelerator/utils/import-stack-resources.ts deleted file mode 100644 index 4d167a8..0000000 --- a/source/packages/@aws-accelerator/accelerator/utils/import-stack-resources.ts +++ /dev/null @@ -1,185 +0,0 @@ -import * as fs from 'fs'; -import { ASEAMapping, CfnResourceType, NestedStack } from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import * as winston from 'winston'; -import path from 'path'; -export class ImportStackResources { - stackMapping: ASEAMapping; - logger: winston.Logger; - cfnResources: CfnResourceType[]; - nestedStackResources: { [key: string]: ImportStackResources } | undefined; - constructor(props: { - stackMapping: ASEAMapping | NestedStack; - cfnResources: CfnResourceType[]; - nestedStackResources: { [key: string]: ImportStackResources } | undefined; - }) { - this.stackMapping = props.stackMapping; - this.logger = createLogger([ - `${this.stackMapping.accountId}-${this.stackMapping.stackName}-${this.stackMapping.region}`, - ]); - this.cfnResources = props.cfnResources; - this.nestedStackResources = props.nestedStackResources; - } - - public static async init(props: { stackMapping: ASEAMapping }) { - try { - const cfnResources = await this.loadCfnResources(path.join('asea-assets', props.stackMapping.resourcePath)); - const nestedStackResources = await this.loadNestedStackResources(props.stackMapping.nestedStacks); - return new ImportStackResources({ ...props, cfnResources, nestedStackResources }); - } catch (e) { - const logger = createLogger([ - `${props.stackMapping.accountId}-${props.stackMapping.stackName}-${props.stackMapping.region}`, - ]); - logger.error(JSON.stringify(props.stackMapping, null, 4)); - throw new Error(`${e}`); - } - } - - private static async loadCfnResources(filePath: string): Promise { - try { - const cfnResources = (await fs.promises.readFile(filePath)).toString(); - return JSON.parse(cfnResources); - } catch (e) { - throw new Error(`Unable to read file ${filePath}`); - } - } - - private static async loadNestedStackResources(nestedStacks: { [key: string]: NestedStack } | undefined) { - if (!nestedStacks) { - return; - } - const nestedStackResources: { [key: string]: ImportStackResources } = {}; - for (const [key, mapping] of Object.entries(nestedStacks)) { - const resources = await ImportStackResources.init({ stackMapping: mapping }); - nestedStackResources[key] = resources; - } - return nestedStackResources; - } - - public static initSync(props: { stackMapping: ASEAMapping }) { - const cfnResources = this.loadCfnResourcesSync(path.join('asea-assets', props.stackMapping.resourcePath)); - const nestedStackResources = this.loadNestedStackResourcesSync(props.stackMapping.nestedStacks); - return new ImportStackResources({ ...props, cfnResources, nestedStackResources }); - } - - private static loadCfnResourcesSync(filePath: string): CfnResourceType[] { - try { - const cfnResources = fs.readFileSync(filePath).toString(); - return JSON.parse(cfnResources); - } catch (e) { - throw new Error(`Unable to read file ${filePath}`); - } - } - - private static loadNestedStackResourcesSync(nestedStacks: { [key: string]: NestedStack } | undefined) { - if (!nestedStacks) { - return; - } - const nestedStackResources: { [key: string]: ImportStackResources } = {}; - for (const [key, mapping] of Object.entries(nestedStacks)) { - const resources = ImportStackResources.initSync({ stackMapping: mapping }); - nestedStackResources[key] = resources; - } - return nestedStackResources; - } - - public getResourceByName(propertyName: string, propertyValue: string) { - return this.cfnResources.find( - cfnResource => - cfnResource.resourceMetadata['Properties'][propertyName] === propertyValue && !cfnResource.isDeleted, - ); - } - - public getResourcesByRef(propertyName: string, logicalId: string) { - return this.cfnResources.filter( - cfnResource => - cfnResource.resourceMetadata['Properties'][propertyName].Ref === logicalId && !cfnResource.isDeleted, - ); - } - - public getResourcesByType(resourceType: string) { - return this.cfnResources.filter(cfnResource => cfnResource.resourceType === resourceType && !cfnResource.isDeleted); - } - - public getResourceByTag(value: string, name = 'Name') { - return this.cfnResources.find( - cfnResource => - cfnResource.resourceMetadata['Properties'].Tags && - cfnResource.resourceMetadata['Properties'].Tags.find( - (tag: { Key: string; Value: string }) => tag.Key === name && tag.Value === value && !cfnResource.isDeleted, - ), - ); - } - - public getResourceByTypeAndTag(resourceType: string, tagValue: string, tagName = 'Name') { - return this.cfnResources.find( - cfnResource => - cfnResource.resourceType === resourceType && - cfnResource.resourceMetadata['Properties'].Tags && - cfnResource.resourceMetadata['Properties'].Tags.find( - (tag: { Key: string; Value: string }) => tag.Key === tagName && tag.Value === tagValue, - ) && - !cfnResource.isDeleted, - ); - } - public getSSMParameterByName(physicalId: string) { - this.logger.info(`Name ${physicalId}`); - return this.getResourceByProperty('Name', physicalId); - } - public getResourceById(lzaResourceId: string) { - return this.cfnResources.find( - cfnResource => cfnResource.resourceIdentifier === lzaResourceId && !cfnResource.isDeleted, - ); - } - - public getResourceByLogicalId(logicalId: string) { - return this.cfnResources.find(cfnResource => cfnResource.logicalResourceId === logicalId && !cfnResource.isDeleted); - } - public getResourceByPropertyIgnoreDeletionFlag(propertyName: string, propertyValue: string) { - return this.cfnResources.find( - cfnResource => - cfnResource.resourceMetadata['Properties'][propertyName] && - cfnResource.resourceMetadata['Properties'][propertyName] === propertyValue, - ); - } - public getResourceByProperty(propertyName: string, propertyValue: string) { - return this.cfnResources.find( - cfnResource => - cfnResource.resourceMetadata['Properties'][propertyName] && - cfnResource.resourceMetadata['Properties'][propertyName] === propertyValue && - !cfnResource.isDeleted, - ); - } - - public getResourceByPropertyByPartialMatch(propertyName: string, propertyValue: string) { - return this.cfnResources.find( - cfnResource => - cfnResource.resourceMetadata['Properties'][propertyName] && - cfnResource.resourceMetadata['Properties'][propertyName].includes(propertyValue) && - !cfnResource.isDeleted, - ); - } - - public deleteResource(logicalId: string) { - const resource = this.cfnResources.find(cfnResource => cfnResource.logicalResourceId === logicalId); - if (resource) { - resource.isDeleted = true; - } else { - this.logger.error(`Resource with logicalId ${logicalId} not found`); - } - } - public setResourceProperties(logicalId: string, properties: { propertyName: string; propertyValue: string }[]) { - const resource = this.cfnResources.find(cfnResource => cfnResource.logicalResourceId === logicalId); - if (resource) { - for (const { propertyName, propertyValue } of properties) { - resource.resourceMetadata['Properties'][propertyName] = propertyValue; - } - } else { - this.logger.error(`Resource with logicalId ${logicalId} not found`); - } - } - - public getStackKey() { - return `${this.stackMapping.accountId}|${this.stackMapping.region}|${this.stackMapping.stackName}`; - } -} diff --git a/source/packages/@aws-accelerator/accelerator/utils/stack-utils.ts b/source/packages/@aws-accelerator/accelerator/utils/stack-utils.ts deleted file mode 100644 index 56af2f0..0000000 --- a/source/packages/@aws-accelerator/accelerator/utils/stack-utils.ts +++ /dev/null @@ -1,1403 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { ASEAMapping, AseaResourceMapping, GlobalConfig } from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import * as cdk from 'aws-cdk-lib'; -import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag'; -import { IConstruct } from 'constructs'; -import { version } from '../../../../package.json'; -import { AcceleratorStackNames } from '../lib/accelerator'; -import { AcceleratorStage } from '../lib/accelerator-stage'; -import { AcceleratorStackProps } from '../lib/stacks/accelerator-stack'; -import { AccountsStack } from '../lib/stacks/accounts-stack'; -import { ApplicationsStack } from '../lib/stacks/applications-stack'; -import { BootstrapStack } from '../lib/stacks/bootstrap-stack'; -import { CustomStack, generateCustomStackMappings, isIncluded } from '../lib/stacks/custom-stack'; -import { CustomizationsStack } from '../lib/stacks/customizations-stack'; -import { DependenciesStack } from '../lib/stacks/dependencies-stack/dependencies-stack'; -import { FinalizeStack } from '../lib/stacks/finalize-stack'; -import { IdentityCenterStack } from '../lib/stacks/identity-center-stack'; -import { KeyStack } from '../lib/stacks/key-stack'; -import { LoggingStack } from '../lib/stacks/logging-stack'; -import { NetworkAssociationsGwlbStack } from '../lib/stacks/network-stacks/network-associations-gwlb-stack/network-associations-gwlb-stack'; -import { NetworkAssociationsStack } from '../lib/stacks/network-stacks/network-associations-stack/network-associations-stack'; -import { NetworkPrepStack } from '../lib/stacks/network-stacks/network-prep-stack/network-prep-stack'; -import { NetworkVpcDnsStack } from '../lib/stacks/network-stacks/network-vpc-dns-stack/network-vpc-dns-stack'; -import { NetworkVpcEndpointsStack } from '../lib/stacks/network-stacks/network-vpc-endpoints-stack/network-vpc-endpoints-stack'; -import { NetworkVpcStack } from '../lib/stacks/network-stacks/network-vpc-stack/network-vpc-stack'; -import { OperationsStack } from '../lib/stacks/operations-stack'; -import { OrganizationsStack } from '../lib/stacks/organizations-stack'; -import { PipelineStack } from '../lib/stacks/pipeline-stack'; -import { PrepareStack } from '../lib/stacks/prepare-stack'; -import { SecurityAuditStack } from '../lib/stacks/security-audit-stack'; -import { SecurityResourcesStack } from '../lib/stacks/security-resources-stack'; -import { SecurityStack } from '../lib/stacks/security-stack'; -import { TesterPipelineStack } from '../lib/stacks/tester-pipeline-stack'; -import { AcceleratorContext, AcceleratorEnvironment, AcceleratorResourcePrefixes } from './app-utils'; -import { ImportAseaResourcesStack } from '../lib/stacks/import-asea-resources-stack'; -import { AcceleratorAspects, PermissionsBoundaryAspect } from '../lib/accelerator-aspects'; -import { ResourcePolicyEnforcementStack } from '../lib/stacks/resource-policy-enforcement-stack'; -import { DiagnosticsPackStack } from '../lib/stacks/diagnostics-pack-stack'; -import { AcceleratorToolkit } from '../lib/toolkit'; - -const logger = createLogger(['stack-utils']); -/** - * This function returns a CDK stack synthesizer based on configuration options - * @param props - * @param accountId - * @param region - * @returns - */ -function getStackSynthesizer( - props: AcceleratorStackProps, - accountId: string, - region: string, - stage: string | undefined = undefined, -) { - const customDeploymentRole = props.globalConfig.cdkOptions?.customDeploymentRole; - const managementAccountId = props.accountsConfig.getManagementAccountId(); - const centralizeBuckets = - props.globalConfig.centralizeCdkBuckets?.enable || props.globalConfig.cdkOptions?.centralizeBuckets; - const fileAssetBucketName = centralizeBuckets ? `cdk-accel-assets-${managementAccountId}-${region}` : undefined; - const bucketPrefix = centralizeBuckets ? `${accountId}/` : undefined; - if (customDeploymentRole && !isBeforeBootstrapStage(stage)) { - logger.info( - `Stack in account ${accountId} and region ${region} using Custom deployment role ${customDeploymentRole}`, - ); - const customDeploymentRoleArn = `arn:${props.partition}:iam::${accountId}:role/${customDeploymentRole}`; - - return new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - bucketPrefix: bucketPrefix, - fileAssetsBucketName: fileAssetBucketName, - cloudFormationExecutionRole: customDeploymentRoleArn, - deployRoleArn: customDeploymentRoleArn, - fileAssetPublishingRoleArn: customDeploymentRoleArn, - lookupRoleArn: customDeploymentRoleArn, - imageAssetPublishingRoleArn: customDeploymentRoleArn, - }); - } - if (props.globalConfig.cdkOptions?.useManagementAccessRole) { - logger.info(`Stack in account ${accountId} and region ${region} using CliCredentialSynthesizer`); - return new cdk.CliCredentialsStackSynthesizer({ - bucketPrefix: bucketPrefix, - fileAssetsBucketName: fileAssetBucketName, - }); - } else { - logger.info(`Stack in account ${accountId} and region ${region} using DefaultSynthesizer`); - return new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - bucketPrefix: bucketPrefix, - fileAssetsBucketName: fileAssetBucketName, - }); - } -} - -/** - * This function returns a CDK stack synthesizer based on configuration options - * @param props - * @param accountId - * @param region - * @param bootstrapAccountId - * @param qualifier - * @param roleName - * @returns - */ -function getAseaStackSynthesizer(props: { - accelProps: AcceleratorStackProps; - accountId: string; - region: string; - qualifier?: string; - roleName?: string; -}) { - const { accountId, region, qualifier, roleName, accelProps } = props; - const managementAccountId = accelProps.accountsConfig.getManagementAccountId(); - const centralizeBuckets = - accelProps.globalConfig.centralizeCdkBuckets?.enable || accelProps.globalConfig.cdkOptions?.centralizeBuckets; - const fileAssetsBucketName = centralizeBuckets ? `cdk-accel-assets-${managementAccountId}-${region}` : undefined; - const bucketPrefix = `${accountId}/`; - - if (accelProps.globalConfig.cdkOptions?.useManagementAccessRole) { - logger.info(`Stack in account ${accountId} and region ${region} using CliCredentialSynthesizer`); - return new cdk.CliCredentialsStackSynthesizer({ - bucketPrefix, - fileAssetsBucketName, - qualifier, - }); - } else { - logger.info(`Stack in account ${accountId} and region ${region} using DefaultSynthesizer`, roleName); - const executionRoleArn = `arn:aws:iam::${accountId}:role/${roleName!}`; - return new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - bucketPrefix, - fileAssetsBucketName, - qualifier, - cloudFormationExecutionRole: executionRoleArn, - deployRoleArn: executionRoleArn, - fileAssetPublishingRoleArn: executionRoleArn, - imageAssetPublishingRoleArn: executionRoleArn, - }); - } -} - -/** - * This function is required rather than using an Aspect class for two reasons: - * 1. Some resources do not support tag updates - * 2. Using Aspects for stacks that use the fs.writeFileSync() operation - * causes the application to quit during stack synthesis - * @param node - * @param partition - * @param globalConfig - * @param acceleratorPrefix - */ -function addAcceleratorTags( - node: IConstruct, - partition: string, - globalConfig: GlobalConfig, - acceleratorPrefix: string, -): void { - if (partition === 'aws-iso' || partition === 'aws-iso-b') { - return; - } - // Resource types that do not support tag updates - const excludeResourceTypes = [ - 'AWS::EC2::TransitGatewayRouteTable', - 'AWS::Route53Resolver::FirewallDomainList', - 'AWS::Route53Resolver::ResolverEndpoint', - 'AWS::Route53Resolver::ResolverRule', - ]; - - for (const resource of node.node.findAll()) { - if (resource instanceof cdk.CfnResource && !excludeResourceTypes.includes(resource.cfnResourceType)) { - if (resource instanceof cdk.aws_ec2.CfnTransitGateway && partition !== 'aws') { - continue; - } - if (resource.cfnResourceType === 'AWS::EC2::SecurityGroup') { - new cdk.Tag('Accel-P', acceleratorPrefix).visit(resource); - } - new cdk.Tag('Accelerator', acceleratorPrefix).visit(resource); - - if (globalConfig?.tags) { - globalConfig.tags.forEach(t => { - new cdk.Tag(t.key, t.value).visit(resource); - }); - } - } - } -} - -/** - * Compares app context with stack environment and returns a boolean value - * based on whether or not a given stack should be synthesized - * @param context - * @param props - * @returns - */ -export function includeStage( - context: AcceleratorContext, - props: { stage: string; account?: string; region?: string }, -): boolean { - if (!context.stage) { - // Do not include PIPELINE or TESTER_PIPELINE in full synth/diff - if (['pipeline', 'tester-pipeline'].includes(props.stage)) { - return false; - } - return true; // No stage, return all other stacks - } - if (context.stage === props.stage) { - if (!context.account && !context.region) { - return true; // No account or region, return all stacks for synth/diff - } - if (props.account === context.account && props.region === context.region) { - return true; - } - } - return false; -} - -/** - * Create Pipeline Stack - * @param app - * @param context - * @param acceleratorEnv - * @param resourcePrefixes - */ -export function createPipelineStack( - app: cdk.App, - context: AcceleratorContext, - acceleratorEnv: AcceleratorEnvironment, - resourcePrefixes: AcceleratorResourcePrefixes, - useExistingRoles: boolean, -) { - if (includeStage(context, { stage: AcceleratorStage.PIPELINE, account: context.account, region: context.region })) { - const pipelineStackName = AcceleratorToolkit.getNonConfigDependentStackName(AcceleratorStage.PIPELINE, { - stage: context.stage!, - accountId: context.account!, - region: context.region!, - }); - const pipelineStack = new PipelineStack(app, pipelineStackName, { - env: { account: context.account, region: context.region }, - description: `(SO0199-pipeline) Landing Zone Accelerator on AWS. Version ${version}.`, - terminationProtection: true, - partition: context.partition, - prefixes: resourcePrefixes, - useExistingRoles, - ...acceleratorEnv, - }); - cdk.Aspects.of(pipelineStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(pipelineStack).add(new PermissionsBoundaryAspect(context.account!, context.partition)); - - NagSuppressions.addStackSuppressions(pipelineStack, [ - { - id: 'AwsSolutions-IAM5', - reason: 'IAM role requires wildcard permissions.', - }, - ]); - } -} - -/** - * Create Tester Pipeline Stack - * @param rootApp - * @param context - * @param acceleratorEnv - * @param resourcePrefixes - */ -export function createTesterStack( - rootApp: cdk.App, - context: AcceleratorContext, - acceleratorEnv: AcceleratorEnvironment, - resourcePrefixes: AcceleratorResourcePrefixes, -) { - if ( - includeStage(context, { stage: AcceleratorStage.TESTER_PIPELINE, account: context.account, region: context.region }) - ) { - if (acceleratorEnv.managementCrossAccountRoleName) { - checkRootApp(rootApp); - const testerPipelineStackName = AcceleratorToolkit.getNonConfigDependentStackName( - AcceleratorStage.TESTER_PIPELINE, - { - stage: context.stage!, - accountId: context.account!, - region: context.region!, - }, - ); - const app = new cdk.App({ - outdir: `cdk.out/${testerPipelineStackName}`, - }); - const testerPipelineStack = new TesterPipelineStack(app, testerPipelineStackName, { - env: { account: context.account, region: context.region }, - description: `(SO0199-tester) Landing Zone Accelerator on AWS. Version ${version}.`, - terminationProtection: true, - prefixes: resourcePrefixes, - managementCrossAccountRoleName: acceleratorEnv.managementCrossAccountRoleName, - ...acceleratorEnv, - }); - cdk.Aspects.of(testerPipelineStack).add(new AwsSolutionsChecks()); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } - } -} - -/** - * Create Diagnostics Pack Stack - * @param rootApp - * @param context - * @param props - * @param accountId - * @param homeRegion - */ -export function createDiagnosticsPackStack( - app: cdk.App, - context: AcceleratorContext, - acceleratorEnv: AcceleratorEnvironment, - resourcePrefixes: AcceleratorResourcePrefixes, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.DIAGNOSTICS_PACK, - account: context.account, - region: context.region, - }) - ) { - const diagnosticsPackStackName = AcceleratorToolkit.getNonConfigDependentStackName( - AcceleratorStage.DIAGNOSTICS_PACK, - { - stage: context.stage!, - accountId: context.account!, - region: context.region!, - }, - ); - const diagnosticsPackStack = new DiagnosticsPackStack(app, diagnosticsPackStackName, { - env: { account: context.account, region: context.region }, - description: `(SO0199-pipeline) Landing Zone Accelerator on AWS. Version ${version}.`, - terminationProtection: true, - acceleratorPrefix: resourcePrefixes.accelerator, - ssmParamPrefix: resourcePrefixes.ssmParamName, - bucketNamePrefix: resourcePrefixes.bucketName, - installerStackName: acceleratorEnv.installerStackName, - configRepositoryName: acceleratorEnv.configRepositoryName, - qualifier: acceleratorEnv.qualifier, - }); - cdk.Aspects.of(diagnosticsPackStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(diagnosticsPackStack).add(new PermissionsBoundaryAspect(context.account!, context.partition)); - } -} - -/** - * Create Prepare Stack - * @param rootApp - * @param context - * @param props - * @param managementAccountId - * @param homeRegion - */ -export function createPrepareStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - managementAccountId: string, - homeRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.PREPARE, - account: managementAccountId, - region: homeRegion, - }) - ) { - checkRootApp(rootApp); - const prepareStackName = `${AcceleratorStackNames[AcceleratorStage.PREPARE]}-${managementAccountId}-${homeRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${prepareStackName}`, - }); - const prepareStack = new PrepareStack(app, `${prepareStackName}`, { - env: { - account: managementAccountId, - region: homeRegion, - }, - description: `(SO0199-prepare) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, managementAccountId, homeRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(prepareStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(prepareStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(prepareStack).add(new PermissionsBoundaryAspect(managementAccountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Finalize Stack - * @param rootApp - * @param context - * @param props - * @param managementAccountId - * @param globalRegion - */ -export function createFinalizeStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - managementAccountId: string, - globalRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.FINALIZE, - account: managementAccountId, - region: globalRegion, - }) - ) { - checkRootApp(rootApp); - const finalizeStackName = `${ - AcceleratorStackNames[AcceleratorStage.FINALIZE] - }-${managementAccountId}-${globalRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${finalizeStackName}`, - }); - const finalizeStack = new FinalizeStack(app, `${finalizeStackName}`, { - env: { - account: managementAccountId, - region: globalRegion, - }, - description: `(SO0199-finalize) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, managementAccountId, globalRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(finalizeStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(finalizeStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(finalizeStack).add(new PermissionsBoundaryAspect(managementAccountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Accounts Stack - * @param rootApp - * @param context - * @param props - * @param managementAccountId - * @param globalRegion - */ -export function createAccountsStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - managementAccountId: string, - globalRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.ACCOUNTS, - account: managementAccountId, - region: globalRegion, - }) - ) { - checkRootApp(rootApp); - const accountsStackName = `${ - AcceleratorStackNames[AcceleratorStage.ACCOUNTS] - }-${managementAccountId}-${globalRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${accountsStackName}`, - }); - const accountsStack = new AccountsStack(app, `${accountsStackName}`, { - env: { - account: managementAccountId, - region: globalRegion, - }, - description: `(SO0199-accounts) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, managementAccountId, globalRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(accountsStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(accountsStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(accountsStack).add(new PermissionsBoundaryAspect(managementAccountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Organizations Stack - * @param rootApp - * @param context - * @param props - * @param managementAccountId - * @param enabledRegion - */ -export function createOrganizationsStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - managementAccountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.ORGANIZATIONS, - account: managementAccountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const organizationStackName = `${ - AcceleratorStackNames[AcceleratorStage.ORGANIZATIONS] - }-${managementAccountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${organizationStackName}`, - }); - const organizationStack = new OrganizationsStack(app, `${organizationStackName}`, { - env: { - account: managementAccountId, - region: enabledRegion, - }, - description: `(SO0199-organizations) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, managementAccountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(organizationStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(organizationStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(organizationStack).add(new PermissionsBoundaryAspect(managementAccountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Security Audit Stack - * @param rootApp - * @param context - * @param props - * @param auditAccountId - * @param enabledRegion - */ -export function createSecurityAuditStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - auditAccountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.SECURITY_AUDIT, - account: auditAccountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const securityAuditStackName = `${ - AcceleratorStackNames[AcceleratorStage.SECURITY_AUDIT] - }-${auditAccountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${securityAuditStackName}`, - }); - const auditStack = new SecurityAuditStack(app, `${securityAuditStackName}`, { - env: { - account: auditAccountId, - region: enabledRegion, - }, - description: `(SO0199-securityaudit) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, auditAccountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(auditStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(auditStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(auditStack).add(new PermissionsBoundaryAspect(auditAccountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Creates the Key and Dependencies Stacks - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createKeyDependencyStacks( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.KEY, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const keyStackName = `${AcceleratorStackNames[AcceleratorStage.KEY]}-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${keyStackName}`, - }); - const keyStack = new KeyStack(app, `${keyStackName}`, { - env, - description: `(SO0199-key) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(keyStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(keyStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(keyStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - - const dependencyStackName = `${AcceleratorStackNames[AcceleratorStage.DEPENDENCIES]}-${accountId}-${enabledRegion}`; - const appDependency = new cdk.App({ - outdir: `cdk.out/${dependencyStackName}`, - }); - const dependencyStack = new DependenciesStack(appDependency, `${dependencyStackName}`, { - env, - description: `(SO0199-dependencies) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(dependencyStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(dependencyStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(dependencyStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(appDependency, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Bootstrap Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createBootstrapStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.BOOTSTRAP, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const bootstrapStackName = `${AcceleratorStackNames[AcceleratorStage.BOOTSTRAP]}-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${bootstrapStackName}`, - }); - const bootstrapStack = new BootstrapStack(app, `${bootstrapStackName}`, { - env, - description: `(SO0199-bootstrap) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(bootstrapStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(bootstrapStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(bootstrapStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Logging Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createLoggingStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.LOGGING, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const loggingStackName = `${AcceleratorStackNames[AcceleratorStage.LOGGING]}-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${loggingStackName}`, - }); - const loggingStack = new LoggingStack(app, `${loggingStackName}`, { - env, - description: `(SO0199-logging) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(loggingStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(loggingStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(loggingStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Security Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createSecurityStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.SECURITY, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const securityStackName = `${AcceleratorStackNames[AcceleratorStage.SECURITY]}-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${securityStackName}`, - }); - const securityStack = new SecurityStack(app, `${securityStackName}`, { - env, - description: `(SO0199-security) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(securityStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(securityStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(securityStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Operations Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - * @param accountWarming - */ -export function createOperationsStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, - accountWarming: boolean, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.OPERATIONS, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const operationsStackName = `${AcceleratorStackNames[AcceleratorStage.OPERATIONS]}-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${operationsStackName}`, - }); - const operationsStack = new OperationsStack(app, `${operationsStackName}`, { - env, - description: `(SO0199-operations) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - accountWarming, - }); - addAcceleratorTags(operationsStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(operationsStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(operationsStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Identity-Center Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - * @param accountWarming - */ -export function createIdentityCenterStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - accountId: string, - homeRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.IDENTITY_CENTER, - account: accountId, - region: homeRegion, - }) - ) { - checkRootApp(rootApp); - const identityCenterStackName = `${ - AcceleratorStackNames[AcceleratorStage.IDENTITY_CENTER] - }-${accountId}-${homeRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${identityCenterStackName}`, - }); - const identityCenterStack = new IdentityCenterStack(app, `${identityCenterStackName}`, { - env: { - account: accountId, - region: homeRegion, - }, - description: `(SO0199-identitycenter) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, homeRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(identityCenterStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(identityCenterStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(identityCenterStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Network Prep Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createNetworkPrepStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.NETWORK_PREP, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const networkPrepStackName = `${ - AcceleratorStackNames[AcceleratorStage.NETWORK_PREP] - }-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${networkPrepStackName}`, - }); - const networkPrepStack = new NetworkPrepStack(app, `${networkPrepStackName}`, { - env, - description: `(SO0199-networkprep) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(networkPrepStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(networkPrepStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(networkPrepStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create Security Resources Stack - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createSecurityResourcesStack( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.SECURITY_RESOURCES, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const securityResourcesStackName = `${ - AcceleratorStackNames[AcceleratorStage.SECURITY_RESOURCES] - }-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${securityResourcesStackName}`, - }); - const securityResourcesStack = new SecurityResourcesStack(app, `${securityResourcesStackName}`, { - env, - description: `(SO0199-securityresources) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(securityResourcesStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(securityResourcesStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(securityResourcesStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create all Network VPC stage stacks - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createNetworkVpcStacks( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.NETWORK_VPC, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const dnsStackName = `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC_DNS]}-${accountId}-${enabledRegion}`; - - const app = new cdk.App({ - outdir: `cdk.out/${dnsStackName}`, - }); - const vpcStack = new NetworkVpcStack( - app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC]}-${accountId}-${enabledRegion}`, - { - env, - description: `(SO0199-networkvpc) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }, - ); - addAcceleratorTags(vpcStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(vpcStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(vpcStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - - const endpointsStack = new NetworkVpcEndpointsStack( - app, - `${AcceleratorStackNames[AcceleratorStage.NETWORK_VPC_ENDPOINTS]}-${accountId}-${enabledRegion}`, - { - env, - description: `(SO0199-networkendpoints) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }, - ); - addAcceleratorTags(endpointsStack, context.partition, props.globalConfig, props.prefixes.accelerator); - endpointsStack.addDependency(vpcStack); - cdk.Aspects.of(endpointsStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(endpointsStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - const dnsStack = new NetworkVpcDnsStack(app, `${dnsStackName}`, { - env, - description: `(SO0199-networkdns) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(dnsStack, context.partition, props.globalConfig, props.prefixes.accelerator); - dnsStack.addDependency(endpointsStack); - cdk.Aspects.of(dnsStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(dnsStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create all Network Associations stage stacks - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createNetworkAssociationsStacks( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.NETWORK_ASSOCIATIONS, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const networkAssociationsStackName = `${ - AcceleratorStackNames[AcceleratorStage.NETWORK_ASSOCIATIONS] - }-${accountId}-${enabledRegion}`; - const networkGwlbStackName = `${ - AcceleratorStackNames[AcceleratorStage.NETWORK_ASSOCIATIONS_GWLB] - }-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${networkGwlbStackName}`, - }); - - const networkAssociationsStack = new NetworkAssociationsStack(app, `${networkAssociationsStackName}`, { - env, - description: `(SO0199-networkassociations) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(networkAssociationsStack, context.partition, props.globalConfig, props.prefixes.accelerator); - cdk.Aspects.of(networkAssociationsStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(networkAssociationsStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - - const networkGwlbStack = new NetworkAssociationsGwlbStack(app, networkGwlbStackName, { - env, - description: `(SO0199-networkgwlb) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - addAcceleratorTags(networkGwlbStack, context.partition, props.globalConfig, props.prefixes.accelerator); - // Since shared security groups are created in networkAssociations. NetworkGwlbStack depends on NetworkAssociationsStack - networkGwlbStack.addDependency(networkAssociationsStack); - cdk.Aspects.of(networkGwlbStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(networkAssociationsStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Create all Customizations stage stacks - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -export function createCustomizationsStacks( - rootApp: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if ( - includeStage(context, { - stage: AcceleratorStage.CUSTOMIZATIONS, - account: accountId, - region: enabledRegion, - }) - ) { - checkRootApp(rootApp); - const customizationsStackName = `${ - AcceleratorStackNames[AcceleratorStage.CUSTOMIZATIONS] - }-${accountId}-${enabledRegion}`; - const app = new cdk.App({ - outdir: `cdk.out/${customizationsStackName}`, - }); - const customizationsStack = new CustomizationsStack(app, `${customizationsStackName}`, { - env, - description: `(SO0199-customizations) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }); - cdk.Aspects.of(customizationsStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(customizationsStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - - createCustomStacks(app, props, env, accountId, enabledRegion); - - createApplicationsStacks(app, context, props, env, accountId, enabledRegion); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - - const resourcePolicyEnforcementStackName = `${ - AcceleratorStackNames[AcceleratorStage.RESOURCE_POLICY_ENFORCEMENT] - }-${accountId}-${enabledRegion}`; - - const resourcePolicyEnforcementStack = new ResourcePolicyEnforcementStack( - app, - `${resourcePolicyEnforcementStackName}`, - { - env, - description: `(SO0199-resource-policy-enforcement) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion, context.stage), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - }, - ); - addAcceleratorTags( - resourcePolicyEnforcementStack, - context.partition, - props.globalConfig, - props.prefixes.accelerator, - ); - cdk.Aspects.of(resourcePolicyEnforcementStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(resourcePolicyEnforcementStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } -} - -/** - * Import ASEA CloudFormation stacks manage resources using LZA CDK App - * @param rootApp - * @param context - * @param props - * @param accountId - * @param enabledRegion - * @returns - */ -export async function importAseaResourceStack( - rootApp: cdk.App, - rootContext: AcceleratorContext, - props: AcceleratorStackProps, - accountId: string, - enabledRegion: string, -) { - if ( - (!includeStage(rootContext, { - stage: AcceleratorStage.IMPORT_ASEA_RESOURCES, - account: accountId, - region: enabledRegion, - }) && - !includeStage(rootContext, { - stage: AcceleratorStage.POST_IMPORT_ASEA_RESOURCES, - account: accountId, - region: enabledRegion, - })) || - !props.globalConfig.externalLandingZoneResources?.importExternalLandingZoneResources - ) { - return; - } - // Since we use different apps and stacks are not part of rootApp, adding empty stack - // to app to avoid command failure for no stacks in app - checkRootApp(rootApp); - const aseaStackMap = props.globalConfig.externalLandingZoneResources?.templateMap; - const acceleratorPrefix = props.globalConfig.externalLandingZoneResources?.acceleratorPrefix; - - if (!aseaStackMap) { - logger.error(`Could not load asea mapping file from externalLandingZoneResources in global config`); - throw new Error(`Configuration validation failed at runtime.`); - } - - if (!acceleratorPrefix) { - logger.error(`Could not load accelerator prefix from externalLandingZoneResources in global config`); - throw new Error(`Configuration validation failed at runtime.`); - } - - const resourceMapping: AseaResourceMapping[] = []; - for (const phase of ['-1', '0', '1', '2', '3', '4', '5']) { - const app = new cdk.App({ - outdir: `cdk.out/phase${phase}-${accountId}-${enabledRegion}`, - }); - const importStackPromises = []; - const stacksByPhase: ASEAMapping[] = []; - Object.keys(aseaStackMap).forEach(key => { - if ( - aseaStackMap[key].accountId === accountId && - aseaStackMap[key].region === enabledRegion && - aseaStackMap[key].phase === phase - ) { - stacksByPhase.push(aseaStackMap[key]); - } - }); - if (stacksByPhase.length === 0) { - logger.warn(`No ASEA stack found for account ${accountId} in region ${enabledRegion} for ${phase}`); - continue; - } - const synthesizer = getAseaStackSynthesizer({ - accelProps: props, - accountId, - region: enabledRegion, - roleName: props.globalConfig.cdkOptions.customDeploymentRole || `${acceleratorPrefix}-PipelineRole`, - }); - for (const aseaStack of stacksByPhase) { - importStackPromises.push( - ImportAseaResourcesStack.init(app, aseaStack.stackName, { - ...props, - stackName: aseaStack.stackName, - synthesizer, - terminationProtection: props.globalConfig.terminationProtection ?? true, - stackInfo: aseaStack, - mapping: aseaStackMap, - env: { - account: accountId, - region: enabledRegion, - }, - stage: rootContext.stage! as - | AcceleratorStage.IMPORT_ASEA_RESOURCES - | AcceleratorStage.POST_IMPORT_ASEA_RESOURCES, - }), - ); - } - const resourceMappings = await Promise.all(importStackPromises); - const saveResourceMappingPromises = []; - for (const mapping of resourceMappings) { - saveResourceMappingPromises.push(mapping.saveLocalResourceFile()); - resourceMapping.push(...mapping.resourceMapping); - } - await Promise.all(saveResourceMappingPromises); - saveResourceMappingPromises.length = 0; - } - return resourceMapping; -} - -/** - * Saves Consolidated ASEA Resources from resource mapping - * @param context - * @param props - * @param resources - */ -export async function saveAseaResourceMapping( - context: AcceleratorContext, - props: AcceleratorStackProps, - resources: AseaResourceMapping[], -) { - if ( - context.stage && - (context.stage === AcceleratorStage.IMPORT_ASEA_RESOURCES || - context.stage === AcceleratorStage.POST_IMPORT_ASEA_RESOURCES) - ) { - await props.globalConfig.saveAseaResourceMapping(resources); - } -} - -/** - * Create custom CloudFormation stacks - * @param app - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -function createCustomStacks( - app: cdk.App, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - if (props.customizationsConfig?.customizations?.cloudFormationStacks) { - const customStackList = generateCustomStackMappings( - props.accountsConfig, - props.organizationConfig, - props.customizationsConfig, - accountId, - enabledRegion, - ); - for (const stack of customStackList ?? []) { - logger.info(`New custom stack ${stack.stackConfig.name}`); - const customStackName = `${stack.stackConfig.name}-${accountId}-${enabledRegion}`; - stack.stackObj = new CustomStack(app, `${customStackName}`, { - env, - description: stack.stackConfig.description, - runOrder: stack.stackConfig.runOrder, - stackName: stack.stackConfig.name, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion), - templateFile: stack.stackConfig.template, - terminationProtection: stack.stackConfig.terminationProtection, - parameters: stack.stackConfig.parameters, - ssmParamNamePrefix: props.prefixes.ssmParamName, - ...props, - }); - } - } -} - -/** - * Create custom applications stacks - * @param rootApp - * @param context - * @param props - * @param env - * @param accountId - * @param enabledRegion - */ -function createApplicationsStacks( - app: cdk.App, - context: AcceleratorContext, - props: AcceleratorStackProps, - env: cdk.Environment, - accountId: string, - enabledRegion: string, -) { - for (const application of props.customizationsConfig.applications ?? []) { - if ( - isIncluded( - application.deploymentTargets, - enabledRegion, - accountId, - props.accountsConfig, - props.organizationConfig, - ) - ) { - // application stacks are created in customization stage - // so the output directory will be customizations folder specific to that account and region - const applicationStackName = `${props.prefixes.accelerator}-App-${application.name}-${accountId}-${enabledRegion}`; - - const applicationStack = new ApplicationsStack(app, applicationStackName, { - env, - description: `(SO0199-customizations) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: getStackSynthesizer(props, accountId, enabledRegion), - terminationProtection: props.globalConfig.terminationProtection ?? true, - ...props, - appConfigItem: application, - }); - cdk.Aspects.of(applicationStack).add(new AwsSolutionsChecks()); - cdk.Aspects.of(applicationStack).add(new PermissionsBoundaryAspect(accountId, context.partition)); - new AcceleratorAspects(app, context.partition, context.useExistingRoles ?? false); - } - } -} - -function isBeforeBootstrapStage(stage?: string): boolean { - const preBootstrapStages = [ - AcceleratorStage.PREPARE, - AcceleratorStage.ACCOUNTS, - AcceleratorStage.BOOTSTRAP, - ] as string[]; - if (!stage) { - return false; - } - - return preBootstrapStages.includes(stage); -} - -/** - * Function to check if the root app has a placeholder - * this avoids command failure of no stacks in app - */ -function checkRootApp(rootApp: cdk.App): cdk.App | cdk.Stack { - if (!rootApp.node.tryFindChild(`placeHolder`)) { - return new cdk.Stack(rootApp, `placeHolder`, {}); - } else { - return rootApp; - } -} diff --git a/source/packages/@aws-accelerator/config/index.ts b/source/packages/@aws-accelerator/config/index.ts deleted file mode 100644 index 049633e..0000000 --- a/source/packages/@aws-accelerator/config/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/accounts-config'; -export * from './lib/common'; -export * from './lib/customizations-config'; -export * from './lib/global-config'; -export * from './lib/iam-config'; -export * from './lib/network-config'; -export * from './lib/organization-config'; -export * from './lib/replacements-config'; -export * from './lib/security-config'; -export * from './lib/models/accounts-config'; -export * from './lib/models/global-config'; -export * from './lib/models/iam-config'; -export * from './lib/models/organization-config'; -export * from './lib/models/security-config'; -export * from './validator/accounts-config-validator'; -export * from './validator/customizations-config-validator'; -export * from './validator/global-config-validator'; -export * from './validator/iam-config-validator'; -export * from './validator/organization-config-validator'; -export * from './validator/network-config-validator/network-config-validator'; -export * from './validator/security-config-validator'; -export * from './validator/replacements-config-validator'; -export * from './validator/common/common-validator-functions'; diff --git a/source/packages/@aws-accelerator/config/jest.config.js b/source/packages/@aws-accelerator/config/jest.config.js deleted file mode 100644 index f172f6a..0000000 --- a/source/packages/@aws-accelerator/config/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 10, - functions: 40, - lines: 50, - statements: 50, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/config/lib/accounts-config.ts b/source/packages/@aws-accelerator/config/lib/accounts-config.ts deleted file mode 100644 index 73a6101..0000000 --- a/source/packages/@aws-accelerator/config/lib/accounts-config.ts +++ /dev/null @@ -1,380 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -import * as i from './models/accounts-config'; -import { DeploymentTargets, parseAccountsConfig } from './common'; - -const logger = createLogger(['accounts-config']); - -export class AccountIdConfig implements i.IAccountIdConfig { - readonly email: string = ''; - readonly accountId: string = ''; - readonly status?: string = ''; -} - -export class AccountConfig implements i.IAccountConfig { - readonly name: string = ''; - readonly description: string = ''; - readonly email: string = ''; - readonly organizationalUnit: string = ''; - readonly warm: boolean | undefined = undefined; -} - -export class GovCloudAccountConfig implements i.IGovCloudAccountConfig { - readonly name: string = ''; - readonly description: string = ''; - readonly email: string = ''; - readonly organizationalUnit: string = ''; - readonly warm: boolean | undefined = undefined; - readonly enableGovCloud: boolean | undefined = undefined; -} - -export class AccountsConfig implements i.IAccountsConfig { - static readonly FILENAME = 'accounts-config.yaml'; - static readonly MANAGEMENT_ACCOUNT = 'Management'; - static readonly LOG_ARCHIVE_ACCOUNT = 'LogArchive'; - static readonly AUDIT_ACCOUNT = 'Audit'; - - readonly mandatoryAccounts: AccountConfig[] | GovCloudAccountConfig[] = []; - - readonly workloadAccounts: AccountConfig[] | GovCloudAccountConfig[] = []; - - public isGovCloudAccount(account: AccountConfig | GovCloudAccountConfig) { - if ('enableGovCloud' in account) { - return true; - } else { - return false; - } - } - - public anyGovCloudAccounts(): boolean { - for (const account of this.workloadAccounts) { - if (this.isGovCloudAccount(account)) { - return true; - } - } - return false; - } - - public isGovCloudEnabled(account: AccountConfig | GovCloudAccountConfig) { - if (this.isGovCloudAccount(account)) { - return (account as GovCloudAccountConfig).enableGovCloud; - } - return false; - } - - /** - * Optionally provide a list of AWS Account IDs to bypass the usage of the - * AWS Organizations Client lookup. This is not a readonly member since we - * will initialize it with values if it is not provided - */ - public accountIds: AccountIdConfig[] | undefined = undefined; - - /** - * - * @param props - * @param values - * @param validateConfig - */ - constructor( - props: { managementAccountEmail: string; logArchiveAccountEmail: string; auditAccountEmail: string }, - values?: i.IAccountsConfig, - ) { - if (values) { - Object.assign(this, values); - } else { - this.mandatoryAccounts = [ - { - name: AccountsConfig.MANAGEMENT_ACCOUNT, - description: - 'The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: props.managementAccountEmail, - organizationalUnit: 'Root', - warm: false, - }, - { - name: AccountsConfig.LOG_ARCHIVE_ACCOUNT, - description: - 'The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: props.logArchiveAccountEmail, - organizationalUnit: 'Security', - warm: false, - }, - { - name: AccountsConfig.AUDIT_ACCOUNT, - description: - 'The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: props.auditAccountEmail, - organizationalUnit: 'Security', - warm: false, - }, - ]; - } - } - - // Helper function to add an account id to the list - private _addAccountId(ids: string[], accountId: string) { - if (!ids.includes(accountId)) { - ids.push(accountId); - } - } - - /** - * - * @param dir - * @param validateConfig - * @returns - */ - static load(dir: string): AccountsConfig { - const buffer = fs.readFileSync(path.join(dir, AccountsConfig.FILENAME), 'utf8'); - const values = parseAccountsConfig(yaml.load(buffer)); - const managementAccountEmail = - (values.mandatoryAccounts as unknown as i.IBaseAccountConfig[]).find( - value => value.name == AccountsConfig.MANAGEMENT_ACCOUNT, - )?.email || '@example.com <----- UPDATE EMAIL ADDRESS'; - const logArchiveAccountEmail = - (values.mandatoryAccounts as unknown as i.IBaseAccountConfig[]).find( - value => value.name == AccountsConfig.MANAGEMENT_ACCOUNT, - )?.email || '@example.com <----- UPDATE EMAIL ADDRESS'; - const auditAccountEmail = - (values.mandatoryAccounts as unknown as i.IBaseAccountConfig[]).find( - value => value.name == AccountsConfig.MANAGEMENT_ACCOUNT, - )?.email || '@example.com <----- UPDATE EMAIL ADDRESS'; - - return new AccountsConfig( - { - managementAccountEmail, - logArchiveAccountEmail, - auditAccountEmail, - }, - values, - ); - } - - /** - * Loads account ids by utilizing the organizations client if account ids are - * not provided in the config. - */ - public async loadAccountIds( - partition: string, - enableSingleAccountMode: boolean, - isOrgsEnabled: boolean, - accountsConfig: AccountsConfig, - /** - * Management account credential when deployed from external account, otherwise this should remain undefined - */ - managementAccountCredentials?: AWS.Credentials, - ): Promise { - if (this.accountIds === undefined) { - this.accountIds = []; - } - if (this.accountIds.length == 0) { - if (enableSingleAccountMode) { - const stsClient = new AWS.STS({ region: process.env['AWS_REGION'] }); - const stsCallerIdentity = await throttlingBackOff(() => stsClient.getCallerIdentity({}).promise()); - const currentAccountId = stsCallerIdentity.Account!; - this.mandatoryAccounts.forEach(item => { - this.accountIds?.push({ email: item.email, accountId: currentAccountId }); - }); - // orgs are enabled - } else if (isOrgsEnabled) { - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ - region: 'us-gov-west-1', - credentials: managementAccountCredentials, - }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ - region: 'cn-northwest-1', - credentials: managementAccountCredentials, - }); - } else { - organizationsClient = new AWS.Organizations({ - region: 'us-east-1', - credentials: managementAccountCredentials, - }); - } - - let nextToken: string | undefined = undefined; - - do { - const page = await throttlingBackOff(() => - organizationsClient.listAccounts({ NextToken: nextToken }).promise(), - ); - - page.Accounts?.forEach(item => { - if (item.Email && item.Id) { - this.accountIds?.push({ email: item.Email, accountId: item.Id, status: item.Status }); - } - }); - nextToken = page.NextToken; - } while (nextToken); - - // if orgs is disabled, the accountId is read from accounts config. - //There should be 3 or more accounts in accounts config. - } else if (!isOrgsEnabled && (accountsConfig.accountIds ?? []).length > 2) { - for (const account of accountsConfig.accountIds ?? []) { - this.accountIds?.push({ email: account.email, accountId: account.accountId }); - } - // if orgs is disabled, the accountId is read from accounts config. - //But less than 3 account Ids are provided then throw an error - } else if (!isOrgsEnabled && (accountsConfig.accountIds ?? []).length < 3) { - throw new Error(`Organization is disabled, but the number of accounts in the accounts config is less than 3.`); - } - } - } - - public getAccountId(name: string): string { - const email = this.getAccount(name).email; - const accountId = this.accountIds?.find(item => item.email === email)?.accountId; - if (accountId) { - return accountId; - } - logger.error( - `Account ID not found for ${name}. Validate that the emails in the parameter ManagementAccountEmail of the AWSAccelerator-InstallerStack and account configs (accounts-config.yaml) match the correct account emails shown in AWS Organizations.`, - ); - throw new Error('configuration validation failed.'); - } - - public getAccountNameById(accountId: string): string | undefined { - const email = this.accountIds?.find(item => item.accountId === accountId)?.email; - const accounts = this.getAccounts(false); - const accountName = accounts.find(account => account.email === email)?.name; - - if (accountName) { - return accountName; - } - logger.error( - `Account Name not found for ${accountId}. Validate that the emails in the parameter ManagementAccountEmail of the AWSAccelerator-InstallerStack and account configs (accounts-config.yaml) match the correct account emails shown in AWS Organizations.`, - ); - throw new Error('configuration validation failed.'); - } - - public getAccountIds(): string[] { - const accountEmails = [...this.mandatoryAccounts, ...this.workloadAccounts].map(account => account.email); - const lzaAccounts = - this.accountIds?.filter(item => { - if (accountEmails.includes(item.email)) { - if (!item.status) { - return true; - } - if (item.status === 'ACTIVE') { - return true; - } - } - return false; - }) ?? []; - return lzaAccounts.map(account => account.accountId); - } - - public getAccount(name: string): AccountConfig { - const value = [...this.mandatoryAccounts, ...this.workloadAccounts].find(item => item.name == name); - if (value) { - return value; - } - logger.error( - `Account name not found for ${name}. Validate that the emails in the parameter ManagementAccountEmail of the AWSAccelerator-InstallerStack and account configs (accounts-config.yaml) match the correct account emails shown in AWS Organizations.`, - ); - throw new Error('configuration validation failed.'); - } - - public containsAccount(name: string): boolean { - const value = [...this.mandatoryAccounts, ...this.workloadAccounts].find(item => item.name == name); - if (value) { - return true; - } - - return false; - } - - public getAccounts(enableSingleAccountMode: boolean): (AccountConfig | GovCloudAccountConfig)[] { - if (enableSingleAccountMode) { - return [this.getManagementAccount()]; - } else { - return [...this.mandatoryAccounts, ...this.workloadAccounts]; - } - } - - public getAccountIdsFromDeploymentTarget(deploymentTargets: DeploymentTargets): string[] { - const accountIds: string[] = []; - - for (const ou of deploymentTargets.organizationalUnits ?? []) { - if (ou === 'Root') { - for (const account of [...this.mandatoryAccounts, ...this.workloadAccounts]) { - const accountId = this.getAccountId(account.name); - this._addAccountId(accountIds, accountId); - } - } else { - for (const account of [...this.mandatoryAccounts, ...this.workloadAccounts]) { - if (ou === account.organizationalUnit) { - const accountId = this.getAccountId(account.name); - this._addAccountId(accountIds, accountId); - } - } - } - } - - for (const account of deploymentTargets.accounts ?? []) { - const accountId = this.getAccountId(account); - this._addAccountId(accountIds, accountId); - } - - const excludedAccountIds = this.getExcludedAccountIds(deploymentTargets); - const filteredAccountIds = accountIds.filter(item => !excludedAccountIds.includes(item)); - - return filteredAccountIds; - } - - public getExcludedAccountIds(deploymentTargets: DeploymentTargets): string[] { - const accountIds: string[] = []; - - if (deploymentTargets.excludedAccounts) { - deploymentTargets.excludedAccounts.forEach(account => this._addAccountId(accountIds, this.getAccountId(account))); - } - - return accountIds; - } - - public getManagementAccount(): AccountConfig { - return this.getAccount(AccountsConfig.MANAGEMENT_ACCOUNT); - } - - public getLogArchiveAccount(): AccountConfig { - return this.getAccount(AccountsConfig.LOG_ARCHIVE_ACCOUNT); - } - - public getAuditAccount(): AccountConfig { - return this.getAccount(AccountsConfig.AUDIT_ACCOUNT); - } - - public getManagementAccountId(): string { - return this.getAccountId(AccountsConfig.MANAGEMENT_ACCOUNT); - } - - public getLogArchiveAccountId(): string { - return this.getAccountId(AccountsConfig.LOG_ARCHIVE_ACCOUNT); - } - - public getAuditAccountId(): string { - return this.getAccountId(AccountsConfig.AUDIT_ACCOUNT); - } -} diff --git a/source/packages/@aws-accelerator/config/lib/common/index.ts b/source/packages/@aws-accelerator/config/lib/common/index.ts deleted file mode 100644 index f3c35fe..0000000 --- a/source/packages/@aws-accelerator/config/lib/common/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './parse'; -export * from './types'; diff --git a/source/packages/@aws-accelerator/config/lib/common/parse.ts b/source/packages/@aws-accelerator/config/lib/common/parse.ts deleted file mode 100644 index 9c0c5f5..0000000 --- a/source/packages/@aws-accelerator/config/lib/common/parse.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import Ajv from 'ajv'; -import { IAccountsConfig } from '../models/accounts-config'; -import { IGlobalConfig } from '../models/global-config'; -import { IIamConfig } from '../models/iam-config'; -import { IOrganizationConfig } from '../models/organization-config'; -import { ISecurityConfig } from '../models/security-config'; -import { IReplacementsConfig } from '../models/replacements-config'; -import { ICustomizationsConfig } from '../models/customizations-config'; -import { INetworkConfig } from '../models/network-config'; -import * as accountsSchema from '../schemas/accounts-config.json'; -import * as globalSchema from '../schemas/global-config.json'; -import * as iamSchema from '../schemas/iam-config.json'; -import * as organizationSchema from '../schemas/organization-config.json'; -import * as securitySchema from '../schemas/security-config.json'; -import * as replacementsSchema from '../schemas/replacements-config.json'; -import * as customizationsSchema from '../schemas/customizations-config.json'; -import * as networkSchema from '../schemas/network-config.json'; - -const ajv = new Ajv({ allErrors: true, verbose: true }); - -interface JsonSchema { - $ref: string; - $schema: string; - definitions: { - [key: string]: object; - }; -} - -type FilteredErrors = { [key: string]: string }; - -/** - * Validates the provided content against the given JSON Schema. - * - * @param schema The JSON Schema object to validate the content against. - * @param content The content to be validated. - * @returns An object containing errors found during validation (filtered to ignore certain errors). - * - * @remarks - * Errors are filtered to ensure that the validation passes if a user provided - * extra properties that were not found in the schema. - */ -function validate(schema: JsonSchema, content: unknown): FilteredErrors { - ajv.validate(schema, content); - - const errors: FilteredErrors = {}; - if (ajv.errors !== undefined && ajv.errors !== null) { - if (ajv.errors.length > 0) { - ajv.errors.forEach(e => { - if ( - e.message !== undefined && - !e.message.includes('must NOT have additional properties') && - !e.message.includes('must match a schema in') - ) { - let message = e.message; - if (!message.includes(e.schema as string)) { - message += ` (${e.schema})`; - } - errors[e.instancePath] = message; - } - }); - } - } - - return errors; -} - -/** - * Parses the provided content against the given JSON Schema. If any errors are - * found an exception is thrown, messages are properly formatted and displayed - * back to the user. - * - * @param schema The JSON Schema to use for parsing. - * @param content The content to be parsed. - * @returns The parsed content with the correct interface type. - */ -function parse(schema: JsonSchema, content: unknown, file: string): T { - const errors = validate(schema, content); - if (Object.keys(errors).length > 0) { - const errorsList = Object.keys(errors).map(error => `* ${error} => ${errors[error]}`); - const errorMessage = errorsList.join('\n'); - throw new Error(`Could not parse content in ${file}:\n\n${errorMessage}\n\n`); - } - - return content as T; -} - -/** - * Parses provided content against the Accounts JSON Schema. - */ -export function parseAccountsConfig(content: unknown): IAccountsConfig { - return parse(accountsSchema, content, 'accounts-config'); -} -/** - * Parses provided content against the Global JSON Schema. - */ -export function parseGlobalConfig(content: unknown): IGlobalConfig { - return parse(globalSchema, content, 'global-config'); -} -/** - * Parses provided content against the IAM JSON Schema. - */ -export function parseIamConfig(content: unknown): IIamConfig { - return parse(iamSchema, content, 'iam-config'); -} -/** - * Parses provided content against the Organization JSON Schema. - */ -export function parseOrganizationConfig(content: unknown): IOrganizationConfig { - return parse(organizationSchema, content, 'organization-config'); -} -/** - * Parses provided content against the Security JSON Schema. - */ -export function parseSecurityConfig(content: unknown): ISecurityConfig { - return parse(securitySchema, content, 'security-config'); -} -/** - * Parses provided content against the Replacements JSON Schema. - */ -export function parseReplacementsConfig(content: unknown): IReplacementsConfig { - return parse(replacementsSchema, content, 'replacements-config'); -} -/** - * Parses provided content against the Customizations JSON Schema. - */ -export function parseCustomizationsConfig(content: unknown): ICustomizationsConfig { - return parse(customizationsSchema, content, 'customizations-config'); -} -/** - * Parses provided content against the Network JSON Schema. - */ -export function parseNetworkConfig(content: unknown): INetworkConfig { - return parse(networkSchema, content, 'network-config'); -} - -/** - * Checks if the provided content conforms to the specified JSON Schema. - * - * @param schema The JSON schema to validate against. - * @param interfaceName The name of the interface defined in the JSON schema. - * @param content The content to be checked against the specified interface. - * @returns Returns true if the content conforms to the interface, false otherwise. - */ -function is(schema: JsonSchema, interfaceName: string, content: unknown): boolean { - let newSchema = ajv.getSchema(interfaceName); - if (!newSchema) { - ajv.addSchema({ ...schema, $ref: `#/definitions/${interfaceName}` }, interfaceName); - newSchema = ajv.getSchema(interfaceName); - } - if (!newSchema?.schema) { - throw new Error(`Could not find schema for ${interfaceName}`); - } - const errors = validate(newSchema.schema as JsonSchema, content); - - return Object.keys(errors).length == 0; -} - -/** - * Checks if content conforms to any type in the Accounts JSON Schema. - */ -export function isAccountsType( - interfaceName: keyof typeof accountsSchema.definitions, - content: unknown, -): content is T { - return is(accountsSchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the Global JSON Schema. - */ -export function isGlobalType(interfaceName: keyof typeof globalSchema.definitions, content: unknown): content is T { - return is(globalSchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the IAM JSON Schema. - */ -export function isIamType(interfaceName: keyof typeof iamSchema.definitions, content: unknown): content is T { - return is(iamSchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the Organizations JSON Schema. - */ -export function isOrganizationType( - interfaceName: keyof typeof organizationSchema.definitions, - content: unknown, -): content is T { - return is(organizationSchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the Security JSON Schema. - */ -export function isSecurityType( - interfaceName: keyof typeof securitySchema.definitions, - content: unknown, -): content is T { - return is(securitySchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the Replacements JSON Schema. - */ -export function isReplacementsType( - interfaceName: keyof typeof replacementsSchema.definitions, - content: unknown, -): content is T { - return is(replacementsSchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the Customizations JSON Schema. - */ -export function isCustomizationsType( - interfaceName: keyof typeof customizationsSchema.definitions, - content: unknown, -): content is T { - return is(customizationsSchema, interfaceName, content); -} -/** - * Checks if content conforms to any type in the Network JSON Schema. - */ -export function isNetworkType( - interfaceName: keyof typeof networkSchema.definitions, - content: unknown, -): content is T { - return is(networkSchema, interfaceName, content); -} diff --git a/source/packages/@aws-accelerator/config/lib/common/types.ts b/source/packages/@aws-accelerator/config/lib/common/types.ts deleted file mode 100644 index 36e6a3e..0000000 --- a/source/packages/@aws-accelerator/config/lib/common/types.ts +++ /dev/null @@ -1,803 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { RegionName } from '@aws-accelerator/utils/lib/regions'; - -/** - * AWS Region - */ -export type Region = keyof typeof RegionName; - -export interface IDeploymentTargets { - organizationalUnits?: string[]; - accounts?: string[]; - excludedRegions?: string[]; - excludedAccounts?: string[]; -} - -/** - * Imported Bucket configuration with S3 managed key encryption. - * - * @remarks Use this configuration to use existing bucket, a bucket not created by accelerator solution. - */ -export interface IImportedS3ManagedEncryptionKeyBucketConfig { - /** - * Imported bucket name - */ - name: NonEmptyString; - /** - * Flag indicating Accelerator to apply solution generated policy to imported bucket. - * - * @remarks - * Accelerator solution creates bucket resource policy based on various security services enabled by the solution. - * Example when macie is enabled, macie service will need access to the bucket, - * accelerator solution dynamically generate policy statements based on various services require access to the bucket. - * - * Default value is false, accelerator managed policy will NOT be applied to bucket resource policy. - * When external policy files are provided through s3ResourcePolicyAttachments policy files, - * solution will add policies from the files to the imported bucket resource policy. - * If no external policy files are provided and value for this parameter is left to false, solution will not make changes to bucket resource policy. - * When value is set to true, accelerator solution will replace bucket resource policy with accelerator managed policies along with policies from external policy files if provided. - * - */ - applyAcceleratorManagedBucketPolicy?: boolean; -} - -export class ImportedS3ManagedEncryptionKeyBucketConfig implements IImportedS3ManagedEncryptionKeyBucketConfig { - readonly name: string = ''; - readonly applyAcceleratorManagedBucketPolicy: boolean | undefined = undefined; -} - -/** - * Custom policy overrides configuration for S3 resource policy - * - * @remarks Use this configuration to use provide files with JSON string to override bucket resource policy. - */ -export interface ICustomS3ResourcePolicyOverridesConfig { - /** - * S3 resource policy file - * - * @remarks - * S3 resource policy file containing JSON string with policy statements. Solution will overwrite bucket resource policy with the context of the file. - */ - policy?: NonEmptyString; -} - -export class CustomS3ResourcePolicyOverridesConfig implements ICustomS3ResourcePolicyOverridesConfig { - readonly policy: string | undefined = undefined; -} - -/** - * Imported Bucket configuration with CMK enabled. - */ -export interface IImportedCustomerManagedEncryptionKeyBucketConfig { - /** - * Imported bucket name - */ - name: NonEmptyString; - /** - * Flag indicating Accelerator to apply solution generated policy to imported bucket. - * - * @remarks - * Accelerator solution creates bucket resource policy based on various security services enabled by the solution. - * Example when macie is enabled, macie service will need access to the bucket, - * accelerator solution dynamically generate policy statements based on various services require access to the bucket. - * - * Default value is false, accelerator managed policy will NOT be applied to bucket resource policy. - * When external policy files are provided through s3ResourcePolicyAttachments policy files, - * solution will add policies from the files to the imported bucket resource policy. - * If no external policy files are provided and value for this parameter is left to false, solution will not make changes to bucket resource policy. - * When value is set to true, accelerator solution will replace bucket resource policy with accelerator managed policies along with policies from external policy files if provided. - * - */ - applyAcceleratorManagedBucketPolicy?: boolean; - /** - * Flag indicating solution should create CMK and apply to imported bucket. - * - * @remarks - * When the value is false, solution will not create KSM key, instead existing bucket encryption will be used and modified based on other parameters. - * When the value is true, solution will create KMS key and apply solution managed policy to the key. - * Once Accelerator pipeline executed with the value set to true, changing the value back to false, will case stack failure. - * Set this value to true when this will no longer be changed to false. - * - * @default - * false - */ - createAcceleratorManagedKey?: boolean; -} - -export class ImportedCustomerManagedEncryptionKeyBucketConfig - implements IImportedCustomerManagedEncryptionKeyBucketConfig -{ - readonly name: string = ''; - readonly applyAcceleratorManagedBucketPolicy: boolean | undefined = undefined; - readonly createAcceleratorManagedKey: boolean | undefined = undefined; -} - -/** - * Custom policy overrides configuration for S3 resource and KMS - */ -export interface ICustomS3ResourceAndKmsPolicyOverridesConfig { - /** - * S3 resource policy file - * - * @remarks - * S3 resource policy file containing JSON string with policy statements. Solution will overwrite bucket resource policy with the context of the file. - */ - s3Policy?: NonEmptyString; - /** - * KMS policy file - * - * @remarks - * S3 bucket encryption policy file containing JSON string with policy statements. Solution will overwrite bucket encryption key policy with the context of the file. - */ - kmsPolicy?: NonEmptyString; -} - -/** - * Custom policy overrides configuration for S3 resource and KMS - * - * @remarks Use this configuration to use provide files with JSON string to override bucket and KSM key policy. - */ -export class CustomS3ResourceAndKmsPolicyOverridesConfig implements ICustomS3ResourceAndKmsPolicyOverridesConfig { - readonly s3Policy: string | undefined = undefined; - readonly kmsPolicy: string | undefined = undefined; -} - -/** - * Deployment targets configuration. - * Deployment targets is an accelerator-specific - * configuration object that can be used for - * resources provisioned by the accelerator. - * Deployment targets allow you to specify - * multiple accounts and/or organizational units (OUs) - * as targets for resource deployment. - * - * The following example would deploy a resource - * to all accounts in the organization except the - * Management account: - * @example - * ``` - * deploymentTargets: - * organizationalUnits: - * - Root - * excludedAccounts: - * - Management - * ``` - */ -export class DeploymentTargets implements IDeploymentTargets { - /** - * Use this property to define one or more organizational units (OUs) - * as a deployment target. Resources are provisioned in each account - * contained within the OU. - * - * @remarks - * Any nested OUs that you would like to deploy resources to must be explicitly - * defined in this property. Deployment targets will not automatically deploy to - * nested OUs. - */ - readonly organizationalUnits: string[] = []; - /** - * Use this property to define one or more accounts as a deployment target. - */ - readonly accounts: string[] = []; - /** - * Use this property to explicitly define one or more regions to exclude from deployment. - * - * @remarks - * By default, all regions defined in the `enabledRegions` property of {@link GlobalConfig} are - * included in `deploymentTargets`. - */ - readonly excludedRegions: Region[] = []; - /** - * Use this property to explicitly define one or more accounts to exclude from deployment. - */ - readonly excludedAccounts: string[] = []; -} - -export type StorageClass = - | 'DEEP_ARCHIVE' - | 'GLACIER' - | 'GLACIER_IR' - | 'STANDARD_IA' - | 'INTELLIGENT_TIERING' - | 'ONEZONE_IA'; - -/** - * An email address - * - * @minLength 6 - * @maxLength 64 - * @pattern ['^\S+@\S+\.\S+$', '^\w+$'] - */ -export type EmailAddress = string; - -/** - * A string that has at least 1 character - * - * @minLength 1 - */ -export type NonEmptyString = string; - -/** - * A string that contains no spaces - * - * @pattern ^[^\s]*$ - * @minLength 1 - */ -export type NonEmptyNoSpaceString = string; - -export interface ITransition { - storageClass: StorageClass; - transitionAfter: number; -} - -export interface IResourcePolicyStatement { - policy: string; -} - -/** - * S3 bucket life cycle rules object. - * - * @example - * ``` - * lifecycleRules: - * - enabled: true - * id: ElbLifecycle-01 - * abortIncompleteMultipartUpload: 14 - * expiration: 3563 - * expiredObjectDeleteMarker: false - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * - enabled: true - * id: ElbLifecycle-02 - * abortIncompleteMultipartUpload: 14 - * expiredObjectDeleteMarker: true - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * ``` - */ -export interface ILifecycleRule { - /** - * Specifies a lifecycle rule that aborts incomplete multipart uploads to an Amazon S3 bucket. - */ - readonly abortIncompleteMultipartUpload?: number; - /** - * Whether this rule is enabled. - */ - readonly enabled?: boolean; - /** - * Indicates the number of days after creation when objects are deleted from Amazon S3 and Amazon Glacier. - */ - readonly expiration?: number; - /** - * Indicates whether Amazon S3 will remove a delete marker with no noncurrent versions. - * If set to true, the delete marker will be expired. - */ - readonly expiredObjectDeleteMarker?: boolean; - /** - * Friendly name for the rule. Rule name must be unique. - */ - readonly id?: string; - /** - * Time between when a new version of the object is uploaded to the bucket and when old versions of the object expire. - */ - readonly noncurrentVersionExpiration?: number; - /** - * One or more transition rules that specify when non-current objects transition to a specified storage class. - */ - readonly noncurrentVersionTransitions?: ITransition[]; - /** - * One or more transition rules that specify when an object transitions to a specified storage class. - */ - readonly transitions?: ITransition[]; - /** - * Object key prefix that identifies one or more objects to which this rule applies. - * @default - Rule applies to all objects - */ - readonly prefix?: NonEmptyString; -} - -export class LifeCycleRule implements ILifecycleRule { - readonly abortIncompleteMultipartUpload: number = 1; - readonly enabled: boolean = true; - readonly expiration: number = 1825; - readonly expiredObjectDeleteMarker: boolean = false; - readonly id: string = ''; - readonly noncurrentVersionExpiration: number = 366; - readonly noncurrentVersionTransitions: ITransition[] = []; - readonly transitions: ITransition[] = []; - readonly prefix: string | undefined = undefined; -} - -export interface IShareTargets { - organizationalUnits?: string[]; - accounts?: string[]; -} - -/** - * {@link https://docs.aws.amazon.com/ram/latest/userguide/what-is.html | Resource Access Manager (RAM)} share targets configuration. - * Share targets is an accelerator-specific - * configuration object that can be used for - * resources provisioned by the accelerator. - * Share targets allow you to specify - * multiple accounts and/or organizational units (OUs) - * as targets for RAM shares. RAM allows you to securely share - * resources between accounts and OUs within your organization. - * - * The following example would share a resource - * to all accounts in the organization: - * @example - * ``` - * shareTargets: - * organizationalUnits: - * - Root - * ``` - */ -export class ShareTargets implements IShareTargets { - /** - * Use this property to define one or more organizational units (OUs) - * as a share target. Resources can be consumed each account - * contained within the OU. - * - * @remarks - * Any nested OUs that you would like to share resources to must be explicitly - * defined in this property. Share targets will not automatically share to - * nested OUs. - */ - readonly organizationalUnits: string[] = []; - /** - * Use this property to define one or more accounts as a share target. - */ - readonly accounts: string[] = []; -} - -export type AllowDeny = 'allow' | 'deny'; -export type EnableDisable = 'enable' | 'disable'; -export type AvailabilityZone = 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; -export type ThresholdType = 'PERCENTAGE' | 'ABSOLUTE_VALUE'; -export type ComparisonOperator = 'GREATER_THAN' | 'LESS_THAN' | 'EQUAL_TO'; -export type SubscriptionType = 'EMAIL' | 'SNS'; -export type NotificationType = 'ACTUAL' | 'FORECASTED'; -export type SecurityHubSeverityLevel = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFORMATIONAL'; - -export interface ITag { - key: string; - value: string; -} - -export class Tag implements ITag { - readonly key: string = ''; - readonly value: string = ''; -} - -export interface ICfnParameter { - name: string; - value: string; -} - -export class CfnParameter implements ICfnParameter { - readonly name: string = ''; - readonly value: string = ''; -} - -export type TrafficType = 'ALL' | 'ACCEPT' | 'REJECT'; -export type LogDestinationType = 's3' | 'cloud-watch-logs'; - -/** - * Solution supported CloudWatch Log data protection categories - * - * @remarks - * Refer [Types of data that you can protect](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types.html) for more information. - */ -export enum CloudWatchLogDataProtectionCategories { - Credentials = 'Credentials', -} - -export interface IVpcFlowLogsS3BucketConfig { - lifecycleRules?: ILifecycleRule[]; - overrideS3LogPath?: NonEmptyString; -} - -export interface IVpcFlowLogsCloudWatchLogsConfig { - retentionInDays?: number; - kms?: NonEmptyString; -} - -export interface IVpcFlowLogsDestinationConfig { - s3?: IVpcFlowLogsS3BucketConfig; - cloudWatchLogs?: IVpcFlowLogsCloudWatchLogsConfig; -} - -export interface IVpcFlowLogsConfig { - trafficType: TrafficType; - maxAggregationInterval: number; - destinations: LogDestinationType[]; - destinationsConfig?: IVpcFlowLogsDestinationConfig; - defaultFormat: boolean; - customFields: NonEmptyString[]; -} - -export interface IPrefixConfig { - /** - * Indicates whether or not to add a custom prefix to LZA Default Centralized Logging location. - * If useCustomPrefix is set to true, logs will be stored in the Centralized Logging Bucket prefix. - */ - useCustomPrefix: boolean; - /** - * (Optional) Prefix to be used for Centralized Logging Path - */ - customOverride?: NonEmptyString; -} - -export class PrefixConfig implements IPrefixConfig { - /** - * Indicates whether or not to add a custom prefix to LZA Default Centralized Logging location. - * If useCustomPrefix is set to false, logs will be stored in the default LZA Centralized Logging Bucket prefix. - */ - readonly useCustomPrefix: boolean = false; - - /** - * @optional - * (Optional) Prefix to be used for Centralized Logging Path - */ - readonly customOverride = undefined; -} - -/** - * VPC flow logs S3 destination bucket configuration. - * - */ -class VpcFlowLogsS3BucketConfig implements IVpcFlowLogsS3BucketConfig { - /** - * @optional - * Flow log destination S3 bucket life cycle rules - */ - readonly lifecycleRules: LifeCycleRule[] = []; - - readonly overrideS3LogPath: string = ''; -} - -/** - * VPC flow logs CloudWatch logs destination configuration. - */ -class VpcFlowLogsCloudWatchLogsConfig implements IVpcFlowLogsCloudWatchLogsConfig { - /** - * @optional - * CloudWatchLogs retention days - */ - readonly retentionInDays = 3653; - /** - * @optional - * CloudWatchLogs encryption key name - */ - readonly kms = undefined; -} - -/** - * VPC flow logs destination configuration. - */ -class VpcFlowLogsDestinationConfig implements IVpcFlowLogsDestinationConfig { - /** - * S3 Flow log destination configuration - * Use following configuration to enable S3 flow log destination - * @example - * ``` - * destinations: - * s3: - * enable: true - * lifecycleRules: [] - * ``` - */ - readonly s3: VpcFlowLogsS3BucketConfig = new VpcFlowLogsS3BucketConfig(); - /** - * CloudWatchLogs Flow log destination configuration - * Use following configuration to enable CloudWatchLogs flow log destination - * @example - * ``` - * destinations: - * cloudWatchLogs: - * enable: true - * retentionInDays: 3653 - * ``` - */ - readonly cloudWatchLogs: VpcFlowLogsCloudWatchLogsConfig = new VpcFlowLogsCloudWatchLogsConfig(); -} - -/** - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html | Virtual Private Cloud (VPC) flow logs} configuration. - * - * @description - * Use this configuration to customize VPC flow log output. - * VPC Flow Logs is a feature that enables you to capture information - * about the IP traffic going to and from network interfaces in your VPC. - * Flow log data can be published to the following locations: Amazon CloudWatch Logs, Amazon S3. - * - * @example - * ``` - * vpcFlowLogs: - * trafficType: ALL - * maxAggregationInterval: 600 - * destinations: - * - s3 - * - cloud-watch-logs - * defaultFormat: false - * customFields: - * - version - * - account-id - * - interface-id - * - srcaddr - * - dstaddr - * - srcport - * - dstport - * - protocol - * - packets - * - bytes - * - start - * - end - * - action - * - log-status - * - vpc-id - * - subnet-id - * - instance-id - * - tcp-flags - * - type - * - pkt-srcaddr - * - pkt-dstaddr - * - region - * - az-id - * - pkt-src-aws-service - * - pkt-dst-aws-service - * - flow-direction - * - traffic-path - * ``` - */ -export class VpcFlowLogsConfig implements IVpcFlowLogsConfig { - /** - * The type of traffic to log. - * - * @see {@link trafficTypeEnum} - */ - readonly trafficType = 'ALL'; - /** - * The maximum log aggregation interval in days. - */ - readonly maxAggregationInterval: number = 600; - /** - * An array of destination serviced for storing logs. - * - * @see {@link NetworkConfigTypes.logDestinationTypeEnum} - */ - readonly destinations: LogDestinationType[] = ['s3', 'cloud-watch-logs']; - /** - * @optional - * VPC Flow log detonations properties. Use this property to specify S3 and CloudWatchLogs properties - * @see {@link VpcFlowLogsDestinationConfig} - */ - readonly destinationsConfig: VpcFlowLogsDestinationConfig = new VpcFlowLogsDestinationConfig(); - /** - * Enable to use the default log format for flow logs. - */ - readonly defaultFormat = false; - /** - * Custom fields to include in flow log outputs. - */ - readonly customFields = [ - 'version', - 'account-id', - 'interface-id', - 'srcaddr', - 'dstaddr', - 'srcport', - 'dstport', - 'protocol', - 'packets', - 'bytes', - 'start', - 'end', - 'action', - 'log-status', - 'vpc-id', - 'subnet-id', - 'instance-id', - 'tcp-flags', - 'type', - 'pkt-srcaddr', - 'pkt-dstaddr', - 'region', - 'az-id', - 'pkt-src-aws-service', - 'pkt-dst-aws-service', - 'flow-direction', - 'traffic-path', - ]; -} - -export type CfnResourceType = { - /** - * LogicalId of a resource in Amazon CloudFormation Stack - * Unique within the template - */ - logicalResourceId: string; - /** - * PhysicalId of a resource in Amazon CloudFormation Stack - * Use the physical IDs to identify resources outside of AWS CloudFormation templates - */ - physicalResourceId?: string; - /** - * The resource type identifies the type of resource that you are declaring - */ - resourceType: string; - /** - * The LZA resource identifier if available. - */ - resourceIdentifier?: string; - /** - * The resourceMetadata holds all resources and properties - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - resourceMetadata: { [key: string]: any }; - /** - * Deletion marker for imported resources - */ - isDeleted?: boolean; -}; - -export type AseaStackInfo = { - accountId: string; - accountKey: string; - region: string; - phase: string; - stackName: string; - templatePath: string; - resourcePath: CfnResourceType[]; - nestedStack?: boolean; -}; - -/** - * ASEA ResourceTypes used in Resource Mapping - */ -export enum AseaResourceType { - IAM_POLICY = 'IAM_POLICY', - IAM_ROLE = 'IAM_ROLE', - IAM_GROUP = 'IAM_GROUP', - IAM_USER = 'IAM_USER', - EC2_VPC = 'EC2_VPC', - EC2_VPC_CIDR = 'EC2_VPC_CIDR', - EC2_SUBNET = 'EC2_SUBNET', - EC2_IGW = 'EC2_VPC_IGW', - EC2_VPN_GW = 'EC2_VPC_VPN_GW', - EC2_SECURITY_GROUP = 'EC2_SECURITY_GROUP', - EC2_SECURITY_GROUP_INGRESS = 'EC2_SECURITY_GROUP_INGRESS', - EC2_SECURITY_GROUP_EGRESS = 'EC2_SECURITY_GROUP_EGRESS', - EC2_VPC_PEERING = 'EC2_VPC_PEERING_CONNECTION', - EC2_TARGET_GROUP = 'EC2_TARGET_GROUP', - EC2_NACL_SUBNET_ASSOCIATION = 'EC2_NACL_SUBNET_ASSOCIATION', - ROUTE_TABLE = 'ROUTE_TABLE', - TRANSIT_GATEWAY = 'TRANSIT_GATEWAY', - TRANSIT_GATEWAY_ROUTE_TABLE = 'TRANSIT_GATEWAY_ROUTE_TABLE', - TRANSIT_GATEWAY_ROUTE = 'TRANSIT_GATEWAY_ROUTE', - TRANSIT_GATEWAY_ATTACHMENT = 'TRANSIT_GATEWAY_ATTACHMENT', - TRANSIT_GATEWAY_PROPAGATION = 'TRANSIT_GATEWAY_PROPAGATION', - TRANSIT_GATEWAY_ASSOCIATION = 'TRANSIT_GATEWAY_ASSOCIATION', - NAT_GATEWAY = 'NAT_GATEWAY', - NFW = 'NETWORK_FIREWALL', - NFW_POLICY = 'NETWORK_FIREWALL_POLICY', - NFW_RULE_GROUP = 'NETWORK_FIREWALL_RULE_GROUP', - VPC_ENDPOINT = 'VPC_ENDPOINT', - ROUTE_53_PHZ_ID = 'ROUTE_53_PHZ', - ROUTE_53_QUERY_LOGGING = 'ROUTE_53_QUERY_LOGGING', - ROUTE_53_QUERY_LOGGING_ASSOCIATION = 'ROUTE_53_QUERY_LOGGING_ASSOCIATION', - ROUTE_53_RECORD_SET = 'ROUTE_53_RECORD_SET', - ROUTE_53_RESOLVER_ENDPOINT = 'ROUTE_53_RESOLVER_ENDPOINT', - SSM_RESOURCE_DATA_SYNC = 'SSM_RESOURCE_DATA_SYNC', - SSM_ASSOCIATION = 'SSM_ASSOCIATION', - FIREWALL_INSTANCE = 'EC2_INSTANCE', - MANAGED_AD = 'MANAGED_AD', - APPLICATION_LOAD_BALANCER = 'APPLICATION_LOAD_BALANCER', -} - -/** - * Consolidated type for ASEA Resource mapping - */ -export type AseaResourceMapping = { - accountId: string; - region: string; - resourceType: string; - resourceIdentifier: string; - isDeleted?: boolean; -}; - -export type ASEAMappings = { - [key: string]: ASEAMapping; -}; - -export type StackResources = { - [key: string]: { - Type: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Properties: { [key: string]: any }; - }; -}; - -export type ASEAMapping = { - stackName: string; - accountId: string; - accountKey: string; - region: string; - phase: string | undefined; - countVerified: boolean; - numberOfResources: number; - numberOfResourcesInTemplate: number; - templatePath: string; - resourcePath: string; - nestedStacks?: { [key: string]: NestedStack }; - parentStack?: string; - cfnResources: CfnResourceType[]; - logicalResourceId?: string; -}; - -export type NestedStack = { - stackName: string; - accountId: string; - accountKey: string; - region: string; - phase: string | undefined; - countVerified: boolean; - numberOfResources: number; - numberOfResourcesInTemplate: number; - templatePath: string; - resourcePath: string; - logicalResourceId: string; - stackKey: string; - cfnResources: CfnResourceType[]; -}; - -export enum AseaResourceTypePaths { - IAM = '/iam/', - VPC = '/network/vpc/', - VPC_PEERING = '/network/vpcPeering/', - TRANSIT_GATEWAY = '/network/transitGateways/', - NETWORK_FIREWALL = '/network/networkFirewall/', -} - -export type AssumedByType = 'service' | 'account' | 'principalArn' | 'provider'; -export type PrincipalType = 'USER' | 'GROUP'; -export type ParameterReplacementType = 'SSM' | 'String' | 'StringList'; - -/** - * AWS VPC ID - * - * @pattern ^vpc-.*|^$ - */ -export type AwsVpcId = string; - -/** - * AWS Account ID - * - * @minLength 12 - * @maxLength 12 - */ -export type AwsAccountId = string; diff --git a/source/packages/@aws-accelerator/config/lib/customizations-config.ts b/source/packages/@aws-accelerator/config/lib/customizations-config.ts deleted file mode 100644 index 96620dd..0000000 --- a/source/packages/@aws-accelerator/config/lib/customizations-config.ts +++ /dev/null @@ -1,386 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; -import * as t from './common'; -import * as i from './models/customizations-config'; -import { ReplacementsConfig } from './replacements-config'; - -export class FirewallStaticReplacementsConfig implements i.IFirewallStaticReplacementsConfig { - readonly key: string = ''; - readonly value: string = ''; -} - -export class Ec2FirewallInstanceConfig implements i.IEc2FirewallInstanceConfig { - readonly name: string = ''; - readonly launchTemplate: LaunchTemplateConfig = new LaunchTemplateConfig(); - readonly vpc: string = ''; - readonly account: string | undefined = undefined; - readonly configFile: string | undefined = undefined; - readonly configDir: string | undefined = undefined; - readonly detailedMonitoring: boolean | undefined = undefined; - readonly licenseFile: string | undefined = undefined; - readonly staticReplacements: FirewallStaticReplacementsConfig[] | undefined = undefined; - readonly terminationProtection: boolean | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class Ec2FirewallAutoScalingGroupConfig implements i.IEc2FirewallAutoScalingGroupConfig { - readonly name: string = ''; - readonly autoscaling = new AutoScalingConfig(); - readonly launchTemplate = new LaunchTemplateConfig(); - readonly vpc: string = ''; - readonly account: string | undefined = undefined; - readonly configFile: string | undefined = undefined; - readonly configDir: string | undefined = undefined; - readonly licenseFile: string | undefined = undefined; - readonly staticReplacements: FirewallStaticReplacementsConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class Ec2FirewallConfig implements i.IEc2FirewallConfig { - readonly autoscalingGroups: Ec2FirewallAutoScalingGroupConfig[] | undefined = undefined; - readonly instances: Ec2FirewallInstanceConfig[] | undefined = undefined; - readonly managerInstances: Ec2FirewallInstanceConfig[] | undefined = undefined; - readonly targetGroups: TargetGroupItemConfig[] | undefined = undefined; -} - -export class CloudFormationStackConfig implements i.ICloudFormationStack { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly description: string = ''; - readonly name: string = ''; - readonly regions: t.Region[] = ['us-east-1']; - readonly runOrder: number = 1; - readonly template: string = ''; - readonly terminationProtection: boolean = false; - readonly parameters: t.CfnParameter[] | undefined = undefined; -} - -export class CloudFormationStackSetConfig implements i.ICloudFormationStackSet { - readonly capabilities = undefined; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly description: string = ''; - readonly name: string = ''; - readonly regions: t.Region[] = ['us-east-1']; - readonly template: string = ''; - readonly parameters: t.CfnParameter[] | undefined = undefined; -} - -export class AlbListenerFixedResponseConfig implements i.IAlbListenerFixedResponseConfig { - readonly statusCode: string = ''; - readonly contentType: string | undefined = undefined; - readonly messageBody: string | undefined = undefined; -} - -export class AlbListenerForwardConfigTargetGroupStickinessConfig implements i.IAlbListenerTargetGroupStickinessConfig { - readonly durationSeconds: number | undefined = undefined; - readonly enabled: boolean | undefined = undefined; -} - -export class AlbListenerForwardConfig implements i.IAlbListenerForwardConfig { - readonly targetGroupStickinessConfig: AlbListenerForwardConfigTargetGroupStickinessConfig | undefined = undefined; -} - -export class AlbListenerRedirectConfig implements i.IAlbListenerRedirectConfig { - readonly statusCode: string | undefined = undefined; - readonly host: string | undefined = undefined; - readonly path: string | undefined = undefined; - readonly port: number | undefined = undefined; - readonly protocol: string | undefined = undefined; - readonly query: string | undefined = undefined; -} - -export class ApplicationLoadBalancerListenerConfig implements i.IAlbListenerConfig { - readonly name: string = ''; - readonly port: number = 80; - readonly protocol: i.AlbListenerProtocolEnum = 'HTTP'; - readonly type: i.AlbListenerTypeEnum = 'forward'; - readonly certificate: string | undefined = undefined; - readonly sslPolicy: i.SslPolicyAlbEnum | undefined = undefined; - readonly targetGroup: string = ''; - readonly fixedResponseConfig: AlbListenerFixedResponseConfig | undefined = undefined; - readonly forwardConfig: AlbListenerForwardConfig | undefined = undefined; - readonly order: number | undefined = undefined; - readonly redirectConfig: AlbListenerRedirectConfig | undefined = undefined; -} - -export class ApplicationLoadBalancerAttributesConfig implements i.IAlbAttributesConfig { - readonly deletionProtection: boolean | undefined = undefined; - readonly idleTimeout: number | undefined = undefined; - readonly routingHttpDesyncMitigationMode: i.AlbRoutingHttpConfigMitigationModeEnum | undefined = undefined; - readonly routingHttpDropInvalidHeader: boolean | undefined = undefined; - readonly routingHttpXAmznTlsCipherEnable: boolean | undefined = undefined; - readonly routingHttpXffClientPort: boolean | undefined = undefined; - readonly routingHttpXffHeaderProcessingMode: i.RoutingHttpXffHeaderProcessingModeEnum | undefined = undefined; - readonly http2Enabled: boolean | undefined = undefined; - readonly wafFailOpen: boolean | undefined = undefined; -} - -export class ApplicationLoadBalancerConfig implements i.IApplicationLoadBalancerConfig { - readonly name: string = ''; - readonly subnets: string[] = []; - readonly securityGroups: string[] = []; - readonly scheme: i.AlbSchemeEnum | undefined = undefined; - readonly attributes: ApplicationLoadBalancerAttributesConfig | undefined = undefined; - readonly listeners: ApplicationLoadBalancerListenerConfig[] | undefined = undefined; - readonly shareTargets: t.ShareTargets | undefined = undefined; -} - -export class TargetGroupAttributeConfig implements i.ITargetGroupAttributeTypes { - readonly deregistrationDelay: number | undefined = undefined; - readonly stickiness: boolean | undefined = undefined; - readonly stickinessType: i.TargetGroupAttributeStickinessType | undefined = undefined; - readonly algorithm: i.TargetGroupAttributeAlgorithm | undefined = undefined; - readonly slowStart: number | undefined = undefined; - readonly appCookieName: string | undefined = undefined; - readonly appCookieDuration: number | undefined = undefined; - readonly lbCookieDuration: number | undefined = undefined; - readonly connectionTermination: boolean | undefined = undefined; - readonly preserveClientIp: boolean | undefined = undefined; - readonly proxyProtocolV2: boolean | undefined = undefined; - readonly targetFailover: i.TargetGroupTargetFailoverType | undefined = undefined; -} - -export class TargetGroupHealthCheckConfig implements i.ITargetGroupHealthCheckType { - readonly interval: number | undefined = undefined; - readonly path: string | undefined = undefined; - readonly protocol: i.TargetGroupHealthCheckProtocolType | undefined = undefined; - readonly port: number | undefined = undefined; - readonly timeout: number | undefined = undefined; -} - -export class TargetGroupThresholdConfig implements i.ITargetGroupThresholdType { - readonly healthy: number | undefined = undefined; - readonly unhealthy: number | undefined = undefined; -} - -export class TargetGroupMatcherConfig implements i.ITargetGroupMatcherType { - readonly grpcCode: string | undefined = undefined; - readonly httpCode: string | undefined = undefined; -} - -export class NlbTargetTypeConfig implements i.INlbTargetType { - readonly account: string = ''; - readonly region: string = ''; - readonly nlbName: string = ''; -} - -export class TargetGroupItemConfig implements i.ITargetGroupItem { - readonly name: string = ''; - readonly port: number = 80; - readonly protocol: i.TargetGroupProtocolType = 'TCP'; - readonly protocolVersion: i.TargetGroupProtocolVersionType | undefined = undefined; - readonly type: i.TargetGroupType = 'instance'; - readonly attributes: TargetGroupAttributeConfig | undefined = undefined; - readonly healthCheck: TargetGroupHealthCheckConfig | undefined = undefined; - readonly targets: (string | NlbTargetTypeConfig)[] | undefined = undefined; - readonly threshold: TargetGroupThresholdConfig | undefined = undefined; - readonly matcher: TargetGroupMatcherConfig | undefined = undefined; - readonly shareTargets: t.ShareTargets | undefined = undefined; -} - -export class NetworkLoadBalancerListenerConfig implements i.INlbListenerConfig { - readonly name: string = ''; - readonly certificate: string | undefined = undefined; - readonly port: number | undefined = undefined; - readonly protocol: i.NlbProtocolEnum | undefined = undefined; - readonly alpnPolicy: i.AlpnPolicyEnum | undefined = undefined; - readonly sslPolicy: i.SslPolicyNlbEnum | undefined = undefined; - readonly targetGroup: string = ''; -} - -export class NetworkLoadBalancerConfig implements i.INetworkLoadBalancerConfig { - readonly name: string = ''; - readonly subnets: string[] = []; - readonly scheme: i.LoadBalancerSchemeEnum | undefined = undefined; - readonly deletionProtection: boolean | undefined = undefined; - readonly crossZoneLoadBalancing: boolean | undefined = undefined; - readonly listeners: NetworkLoadBalancerListenerConfig[] | undefined = undefined; -} - -export class EbsItemConfig implements i.IEbsItem { - readonly deleteOnTermination: boolean | undefined = undefined; - readonly encrypted: boolean | undefined = undefined; - readonly iops: number | undefined = undefined; - readonly kmsKeyId: string | undefined = undefined; - readonly snapshotId: string | undefined = undefined; - readonly throughput: number | undefined = undefined; - readonly volumeSize: number | undefined = undefined; - readonly volumeType: string | undefined = undefined; -} - -export class BlockDeviceMappingItem implements i.IBlockDeviceMappingItem { - readonly deviceName: string = ''; - readonly ebs: EbsItemConfig | undefined = undefined; -} - -export class PrivateIpAddressConfig implements i.IPrivateIpAddressItem { - readonly primary: boolean | undefined = undefined; - readonly privateIpAddress: string | undefined = undefined; -} - -export class NetworkInterfaceItemConfig implements i.INetworkInterfaceItem { - readonly associateCarrierIpAddress: boolean | undefined = undefined; - readonly associateElasticIp: boolean | undefined = undefined; - readonly associatePublicIpAddress: boolean | undefined = undefined; - readonly deleteOnTermination: boolean | undefined = undefined; - readonly description: string | undefined = undefined; - readonly deviceIndex: number | undefined = undefined; - readonly groups: string[] | undefined = undefined; - readonly interfaceType: string | undefined = undefined; - readonly networkCardIndex: number | undefined = undefined; - readonly networkInterfaceId: string | undefined = undefined; - readonly privateIpAddress: string | undefined = undefined; - readonly secondaryPrivateIpAddressCount: number | undefined = undefined; - readonly sourceDestCheck: boolean | undefined = undefined; - readonly subnetId: string | undefined = undefined; - readonly privateIpAddresses: PrivateIpAddressConfig[] | undefined = undefined; -} - -export class LaunchTemplateConfig implements i.ILaunchTemplateConfig { - readonly name: string = ''; - readonly blockDeviceMappings: BlockDeviceMappingItem[] | undefined = undefined; - readonly securityGroups: string[] | undefined = undefined; - readonly keyPair: string | undefined = undefined; - readonly iamInstanceProfile: string | undefined = undefined; - readonly imageId: string = ''; - readonly instanceType: string = ''; - readonly enforceImdsv2: boolean | undefined = undefined; - readonly networkInterfaces: NetworkInterfaceItemConfig[] | undefined = undefined; - readonly userData: string | undefined = undefined; -} - -export class AutoScalingConfig implements i.IAutoScalingConfig { - readonly name: string = ''; - readonly minSize: number = 0; - readonly maxSize: number = 4; - readonly desiredSize: number = 2; - readonly launchTemplate: string = ''; - readonly healthCheckGracePeriod: number | undefined = undefined; - readonly healthCheckType: i.AutoScalingHealthCheckTypeEnum | undefined = undefined; - readonly maxInstanceLifetime: number | undefined = undefined; - targetGroups: string[] | undefined = undefined; - subnets: string[] = []; -} - -export class AppConfigItem implements i.IAppConfigItem { - readonly name: string = ''; - readonly vpc: string = ''; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly targetGroups: TargetGroupItemConfig[] | undefined = undefined; - readonly networkLoadBalancer: NetworkLoadBalancerConfig | undefined = undefined; - readonly launchTemplate: LaunchTemplateConfig | undefined = undefined; - readonly autoscaling: AutoScalingConfig | undefined = undefined; - readonly applicationLoadBalancer: ApplicationLoadBalancerConfig | undefined = undefined; -} - -export class PortfolioAssociationConfig implements i.IPortfolioAssociatoinConfig { - readonly type: i.PortfolioAssociationType = 'Role'; - readonly name: string = ''; - readonly propagateAssociation: boolean = false; -} - -export class ProductVersionConfig implements i.IProductVersionConfig { - readonly name: string = ''; - readonly description: string = ''; - readonly template: string = ''; -} - -export class ProductSupportConfig implements i.IProductSupportConfig { - readonly email: string | undefined = undefined; - readonly url: string | undefined = undefined; - readonly description: string | undefined = undefined; -} - -export class TagOptionsConfig implements i.ITagOptionsConfig { - readonly key: string = ''; - readonly values: string[] = []; -} - -export class ProductLaunchConstraintConfig implements i.IProductLaunchConstraintConfig { - readonly type: i.ProductLaunchConstraintType = 'Role'; - readonly role: string = ''; -} - -export class ProductConstraintConfig implements i.IProductConstraintConfig { - launch: ProductLaunchConstraintConfig | undefined; - tagUpdate: boolean | undefined; - notifications: string[] | undefined; -} - -export class ProductConfig implements i.IProductConfig { - readonly name: string = ''; - readonly owner: string = ''; - readonly versions: ProductVersionConfig[] = []; - readonly description: string | undefined = undefined; - readonly distributor: string | undefined = undefined; - readonly support: ProductSupportConfig | undefined = undefined; - readonly tagOptions: TagOptionsConfig[] | undefined = undefined; - readonly constraints: ProductConstraintConfig | undefined = undefined; -} - -export class PortfolioConfig implements i.IPortfolioConfig { - readonly name: string = ''; - readonly provider: string = ''; - readonly account: string = ''; - readonly regions: t.Region[] = []; - readonly portfolioAssociations: PortfolioAssociationConfig[] = []; - readonly products: ProductConfig[] = []; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly shareTagOptions: boolean | undefined = undefined; - readonly tagOptions: TagOptionsConfig[] | undefined = undefined; -} - -export class CustomizationConfig implements i.ICustomizationConfig { - readonly cloudFormationStacks: CloudFormationStackConfig[] = []; - readonly cloudFormationStackSets: CloudFormationStackSetConfig[] = []; - readonly serviceCatalogPortfolios: PortfolioConfig[] = []; -} - -export class CustomizationsConfig implements i.ICustomizationsConfig { - static readonly FILENAME = 'customizations-config.yaml'; - - readonly customizations: CustomizationConfig = new CustomizationConfig(); - readonly applications: AppConfigItem[] = []; - readonly firewalls: Ec2FirewallConfig | undefined = undefined; - - /** - * - * @param values - */ - constructor(values?: i.ICustomizationsConfig) { - Object.assign(this, values); - } - - public getCustomStacks(): CloudFormationStackConfig[] { - return this.customizations?.cloudFormationStacks ?? []; - } - public getAppStacks(): AppConfigItem[] { - return this.applications ?? []; - } - - /** - * Load from config file content - * @param dir - * @param replacementsConfig - * @returns - */ - static load(dir: string, replacementsConfig?: ReplacementsConfig): CustomizationsConfig { - const initialBuffer = fs.readFileSync(path.join(dir, CustomizationsConfig.FILENAME), 'utf8'); - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseCustomizationsConfig(yaml.load(buffer)); - return new CustomizationsConfig(values); - } -} diff --git a/source/packages/@aws-accelerator/config/lib/global-config.ts b/source/packages/@aws-accelerator/config/lib/global-config.ts deleted file mode 100644 index 8355325..0000000 --- a/source/packages/@aws-accelerator/config/lib/global-config.ts +++ /dev/null @@ -1,987 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; -import * as AWS from 'aws-sdk'; -import { AssumeRoleCommandOutput } from '@aws-sdk/client-sts'; -import { SSMClient, GetParametersByPathCommand } from '@aws-sdk/client-ssm'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { directoryExists, fileExists, getCrossAccountCredentials } from '@aws-accelerator/utils/lib/common-functions'; - -import * as t from './common'; -import * as i from './models/global-config'; - -import { AccountsConfig } from './accounts-config'; -import { ReplacementsConfig } from './replacements-config'; -import { OrganizationConfig } from './organization-config'; - -const logger = createLogger(['global-config']); - -export class externalLandingZoneResourcesConfig implements i.IExternalLandingZoneResourcesConfig { - readonly importExternalLandingZoneResources = false; - readonly mappingFileBucket = ''; - readonly acceleratorPrefix = 'ASEA'; - readonly acceleratorName = 'ASEA'; - - templateMap: t.ASEAMappings = {}; - resourceList: t.AseaResourceMapping[] = []; - /** - * List of accountIds deployed using external Accelerator - */ - accountsDeployedExternally: string[] = []; - /** - * SSM Parameter mapping for resource types managed in both accelerators - */ - resourceParameters: { [key: string]: { [key: string]: string } } = {}; -} - -export class centralizeCdkBucketsConfig implements i.ICentralizeCdkBucketsConfig { - readonly enable = true; -} - -export class cdkOptionsConfig implements i.ICdkOptionsConfig { - readonly centralizeBuckets = true; - readonly useManagementAccessRole = true; - readonly customDeploymentRole = undefined; - readonly forceBootstrap = undefined; - /** - * Determines if the LZA pipeline will skip the static config validation step during the pipeline's Build phase. This can be helpful in cases where the config-validator incorrectly throws errors for a valid configuration. - */ - readonly skipStaticValidation = undefined; -} - -export class CloudTrailSettingsConfig implements i.ICloudTrailSettingsConfig { - multiRegionTrail = true; - globalServiceEvents = true; - managementEvents = true; - s3DataEvents = true; - lambdaDataEvents = true; - sendToCloudWatchLogs = true; - readonly apiErrorRateInsight = false; - readonly apiCallRateInsight = false; -} - -export class AccountCloudTrailConfig implements i.IAccountCloudTrailConfig { - readonly name = 'AWSAccelerator-Account-CloudTrail'; - readonly regions: string[] = []; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly settings = new CloudTrailSettingsConfig(); -} - -/** - * {@link GlobalConfig} / {@link ControlTowerConfig} / {@link ControlTowerLandingZoneConfig} / {@link ControlTowerLandingZoneLoggingConfig} - * - * @description - * AWS Control Tower Landing Zone logging configuration - * - * @remarks - * This allows you to manage logging options for the landing zone. - * In the log configuration section, you can configure the retention time of the Amazon S3 log archive bucket, and the retention time of the logs for access to the bucket. - * - * Please use the following configuration to configure AWS Control Tower Landing Zone logging configuration, with organization-level AWS CloudTrail configuration. - * @example - * ``` - * logging: - * loggingBucketRetentionDays: 365 - * accessLoggingBucketRetentionDays: 3650 - * organizationTrail: true - * ``` - */ -export class ControlTowerLandingZoneLoggingConfig implements i.IControlTowerLandingZoneLoggingConfig { - /** - * Retention time of the Amazon S3 log archive bucket - * - * @default - * 365 - */ - readonly loggingBucketRetentionDays: number = 365; - /** - * Retention time of the logs for access to the bucket. - * - * @default - * 3650 - */ - readonly accessLoggingBucketRetentionDays: number = 3650; - /** - * Flag indicates Organizational-level AWS CloudTrail configuration is configured or not. - * - * @remarks - * It is important to note that the CloudTrail configured by AWS Control Tower at the organization level is different from the CloudTrail deployed by the solution. In the event that AWS Control Tower and Solution defined CloudTrail are enabled, two cloud trails will be created. - * @default - * true - */ - readonly organizationTrail: boolean = true; -} - -/** - * {@link GlobalConfig} / {@link ControlTowerConfig} / {@link ControlTowerLandingZoneConfig} / {@link ControlTowerLandingZoneSecurityConfig} - * - * @description - * AWS Control Tower Landing Zone security configuration - * - * @remarks - * This allows you to manage security options for the landing zone. - * - * The following AWS Control Tower Landing Zone security example configuration sets up AWS account access with IAM Identity Center. - * @example - * ``` - * security: - * enableIdentityCenterAccess: true - * ``` - */ -export class ControlTowerLandingZoneSecurityConfig implements i.IControlTowerLandingZoneSecurityConfig { - /** - * Flag indicates AWS account access option. - * - * @remarks - * When this property is to true, AWS Control Tower sets up AWS account access with IAM Identity Center. Otherwise, please use self-managed AWS account access with IAM Identity Center or another method. - * - * @default - * true - */ - readonly enableIdentityCenterAccess: boolean = true; -} - -/** - * {@link GlobalConfig} / {@link ControlTowerConfig} / {@link ControlTowerLandingZoneConfig} - * - * @description - * AWS Control Tower Landing Zone configuration - * - * @remarks - * This allows you to manage AWS Control Tower Landing Zone configuration. - * - * Please use the following configuration to configure AWS Control Tower Landing Zone. - * @example - * ``` - * landingZone: - * version: '3.3' - * logging: - * loggingBucketRetentionDays: 365 - * accessLoggingBucketRetentionDays: 3650 - * organizationTrail: true - * security: - * enableIdentityCenterAccess: true - * ``` - */ -export class ControlTowerLandingZoneConfig implements i.IControlTowerLandingZoneConfig { - /** - * The landing zone version, for example, 3.3. - * - * @remarks - * Most AWS Control Tower Landing Zone operation needs the version to latest available version. - * The AWS Control Tower Landing Zone will be updated or reset when it drifts or when any configuration changes have been made in global-config. - * When the value of this property is set to the latest available version, AWS Control Tower Landing Zone can be updated or reset. - * The solution will fail if this property version is not set to the latest available version. - * If you wish to update or reset the AWS Control Tower Landing Zone, you will need to update this property to match the latest available version. - * - */ - readonly version: string = '3.3'; - /** - * AWS Control Tower Landing Zone logging configuration - * - * @see {@link ControlTowerLandingZoneLoggingConfig} for more information. - */ - readonly logging: ControlTowerLandingZoneLoggingConfig = new ControlTowerLandingZoneLoggingConfig(); - /** - * AWS Control Tower Landing Zone security configuration - * - * @see {@link ControlTowerLandingZoneSecurityConfig} for more information. - */ - readonly security: ControlTowerLandingZoneSecurityConfig = new ControlTowerLandingZoneSecurityConfig(); -} - -export abstract class ControlTowerControlConfig implements i.IControlTowerControlConfig { - readonly identifier: string = ''; - readonly enable: boolean = true; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly regions: t.Region[] | undefined = undefined; -} - -/** - * *{@link GlobalConfig} / {@link ControlTowerConfig}* - * - * AWS Control Tower Landing Zone configuration. - * - * Please use the following configuration to configure AWS Control Tower Landing Zone. - * @example - * ``` - * controlTower: - * enable: true - * landingZone: - * version: '3.3' - * logging: - * loggingBucketRetentionDays: 365 - * accessLoggingBucketRetentionDays: 3650 - * organizationTrail: true - * security: - * enableIdentityCenterAccess: true - * ``` - */ -export class ControlTowerConfig implements i.IControlTowerConfig { - /** - * Indicates whether AWS Control Tower enabled. - * - * When control tower is enabled, accelerator makes sure account configuration file have three mandatory AWS CT accounts. - * In AWS Control Tower, three shared accounts in your landing zone are provisioned automatically during setup: the management account, - * the log archive account, and the audit account. - */ - readonly enable: boolean = true; - /** - * A list of Control Tower controls to enable. - * - * Only Strongly recommended and Elective controls are permitted, with the exception of the Region deny guardrail. Please see this [page](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-controltower-enabledcontrol.html) for more information. - * - * @see {@link ControlTowerControlConfig} for more information. - */ - readonly controls: ControlTowerControlConfig[] = []; - /** - * AWS Control Tower Landing Zone configuration - * - * @see {@link ControlTowerLandingZoneConfig} for more information. - */ - readonly landingZone: ControlTowerLandingZoneConfig | undefined = undefined; -} - -export class ServiceEncryptionConfig implements i.IServiceEncryptionConfig { - readonly useCMK: boolean = false; - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CloudWatchLogsConfig}/ {@link CloudWatchDataProtectionConfig}/ {@link CloudWatchManagedDataProtectionIdentifierConfig}* - * - * @description - * AWS CloudWatch log data protection configuration - * - * @remarks - * Currently, only the [`Credentials`](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types-credentials.html) category is supported. - * - * @example - * ``` - * categories: - * - Credentials - * ``` - */ -export class CloudWatchManagedDataProtectionIdentifierConfig - implements i.ICloudWatchManagedDataProtectionIdentifierConfig -{ - /** - * CloudWatch Logs managed data identifiers configuration. - * - * @remarks - * The solution supports only identifiers associated with the `Credentials` category, you can find more information about `Credentials` category [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types-credentials.html) - * - * @default Credentials - */ - readonly categories: `${t.CloudWatchLogDataProtectionCategories}`[] = ['Credentials']; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CloudWatchLogsConfig}/ {@link CloudWatchDataProtectionConfig}* - * - * @description - * AWS CloudWatch log data protection configuration - * - * @example - * ``` - * dataProtection: - * managedDataIdentifiers: - * categories: - * - Credentials - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export class CloudWatchDataProtectionConfig implements i.ICloudWatchDataProtectionConfig { - /** - * CloudWatch Logs managed data identifiers configuration. - * - * @remarks - * Please review [CloudWatch Logs managed data identifiers for sensitive data types](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL-managed-data-identifiers.html) for more information. - * - * @default Credentials - */ - readonly managedDataIdentifiers: CloudWatchManagedDataProtectionIdentifierConfig = - new CloudWatchManagedDataProtectionIdentifierConfig(); - - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; - /** - * (OPTIONAL) Indicates whether existing CloudWatch Log data protection policy configuration can be overwritten. - * - * @default false - */ - readonly overrideExisting: boolean | undefined = undefined; -} - -export class S3EncryptionConfig implements i.IS3EncryptionConfig { - readonly createCMK: boolean = true; - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; -} - -export class S3GlobalConfig implements i.IS3GlobalConfig { - readonly encryption?: S3EncryptionConfig | undefined = undefined; -} - -export class LambdaConfig implements i.ILambdaConfig { - readonly encryption: ServiceEncryptionConfig | undefined = undefined; -} - -export class CloudTrailConfig implements i.ICloudTrailConfig { - readonly enable = false; - readonly organizationTrail = false; - readonly organizationTrailSettings = new CloudTrailSettingsConfig(); - readonly accountTrails: AccountCloudTrailConfig[] = []; - readonly lifecycleRules: t.LifeCycleRule[] = []; -} - -export class ServiceQuotaLimitsConfig implements i.IServiceQuotaLimitsConfig { - readonly serviceCode = ''; - readonly quotaCode = ''; - readonly desiredValue = 2000; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly regions?: t.Region[]; -} - -export class SessionManagerConfig implements i.ISessionManagerConfig { - readonly sendToCloudWatchLogs = false; - readonly sendToS3 = false; - readonly excludeRegions: t.Region[] = []; - readonly excludeAccounts: string[] = []; - readonly lifecycleRules: t.LifeCycleRule[] = []; - readonly attachPolicyToIamRoles = []; -} - -export class AccessLogBucketConfig implements i.IAccessLogBucketConfig { - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; - readonly enable: boolean | undefined = undefined; - readonly deploymentTargets?: t.DeploymentTargets | undefined = undefined; - readonly s3ResourcePolicyAttachments: t.IResourcePolicyStatement[] | undefined = undefined; - readonly importedBucket: t.IImportedS3ManagedEncryptionKeyBucketConfig | undefined = undefined; - readonly customPolicyOverrides: t.ICustomS3ResourcePolicyOverridesConfig | undefined = undefined; -} - -export class CentralLogBucketConfig implements i.ICentralLogBucketConfig { - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; - readonly s3ResourcePolicyAttachments: t.IResourcePolicyStatement[] | undefined = undefined; - readonly kmsResourcePolicyAttachments: t.IResourcePolicyStatement[] | undefined = undefined; - readonly importedBucket: t.ImportedCustomerManagedEncryptionKeyBucketConfig | undefined = undefined; - readonly customPolicyOverrides: t.CustomS3ResourceAndKmsPolicyOverridesConfig | undefined = undefined; -} - -export class AssetBucketConfig implements i.IAssetBucketConfig { - readonly s3ResourcePolicyAttachments: t.IResourcePolicyStatement[] | undefined = undefined; - readonly kmsResourcePolicyAttachments: t.IResourcePolicyStatement[] | undefined = undefined; - readonly importedBucket: t.IImportedCustomerManagedEncryptionKeyBucketConfig | undefined = undefined; - readonly customPolicyOverrides?: t.CustomS3ResourceAndKmsPolicyOverridesConfig | undefined = undefined; -} - -export class ElbLogBucketConfig implements i.IElbLogBucketConfig { - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; - readonly s3ResourcePolicyAttachments: t.IResourcePolicyStatement[] | undefined = undefined; - readonly importedBucket: t.ImportedS3ManagedEncryptionKeyBucketConfig | undefined = undefined; - readonly customPolicyOverrides: t.CustomS3ResourcePolicyOverridesConfig | undefined = undefined; -} - -export class CloudWatchLogsExclusionConfig implements i.ICloudWatchLogsExclusionConfig { - readonly organizationalUnits: string[] | undefined = undefined; - readonly regions: t.Region[] | undefined = undefined; - readonly accounts: string[] | undefined = undefined; - readonly excludeAll: boolean | undefined = undefined; - readonly logGroupNames: string[] | undefined = undefined; -} - -export class CloudWatchLogsConfig implements i.ICloudWatchLogsConfig { - readonly dynamicPartitioning: string | undefined = undefined; - readonly enable: boolean | undefined = undefined; - readonly encryption: ServiceEncryptionConfig | undefined = undefined; - readonly exclusions: CloudWatchLogsExclusionConfig[] | undefined = undefined; - readonly replaceLogDestinationArn: string | undefined = undefined; - readonly dataProtection: CloudWatchDataProtectionConfig | undefined = undefined; -} - -export class LoggingConfig implements i.ILoggingConfig { - readonly account = 'LogArchive'; - readonly centralizedLoggingRegion: undefined | string = undefined; - readonly cloudtrail: CloudTrailConfig = new CloudTrailConfig(); - readonly sessionManager: SessionManagerConfig = new SessionManagerConfig(); - readonly assetBucket: AssetBucketConfig | undefined = undefined; - readonly accessLogBucket: AccessLogBucketConfig | undefined = undefined; - readonly centralLogBucket: CentralLogBucketConfig | undefined = undefined; - readonly elbLogBucket: ElbLogBucketConfig | undefined = undefined; - readonly cloudwatchLogs: CloudWatchLogsConfig | undefined = undefined; -} - -export class CostAndUsageReportConfig implements i.ICostAndUsageReportConfig { - readonly additionalSchemaElements = ['']; - readonly compression = ''; - readonly format = ''; - readonly reportName = ''; - readonly s3Prefix = ''; - readonly timeUnit = ''; - readonly additionalArtifacts = undefined; - readonly refreshClosedReports = true; - readonly reportVersioning = ''; - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; -} - -export class BudgetReportConfig implements i.IBudgetReportConfig { - readonly amount = 2000; - readonly name = ''; - readonly timeUnit = ''; - readonly type = ''; - readonly includeUpfront = true; - readonly includeTax = true; - readonly includeSupport = true; - readonly includeOtherSubscription = true; - readonly includeSubscription = true; - readonly includeRecurring = true; - readonly includeDiscount = true; - readonly includeRefund = false; - readonly includeCredit = false; - readonly useAmortized = false; - readonly useBlended = false; - readonly subscriptionType = ''; - readonly unit = ''; - readonly notifications: NotificationConfig[] | undefined = [new NotificationConfig()]; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class NotificationConfig implements i.INotificationConfig { - readonly type: t.NotificationType = 'ACTUAL'; - readonly thresholdType: t.ThresholdType = 'PERCENTAGE'; - readonly threshold: number = 90; - readonly comparisonOperator: t.ComparisonOperator = 'GREATER_THAN'; - readonly subscriptionType: t.SubscriptionType = 'EMAIL'; - readonly address: string | undefined = ''; - readonly recipients: string[] | undefined = []; -} - -export class ReportConfig implements i.IReportConfig { - readonly costAndUsageReport = new CostAndUsageReportConfig(); - readonly budgets: BudgetReportConfig[] = []; -} - -export class VaultConfig implements i.IVaultConfig { - readonly name = 'BackupVault'; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly policy: string = ''; -} - -export class BackupConfig implements i.IBackupConfig { - readonly vaults: VaultConfig[] = []; -} - -export class SnsTopicConfig implements i.ISnsTopicConfig { - readonly name = 'Security'; - readonly emailAddresses = []; -} - -export class SnsConfig implements i.ISnsConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly topics: SnsTopicConfig[] = []; -} - -export class AcceleratorMetadataConfig implements i.IAcceleratorMetadataConfig { - readonly enable = false; - readonly account = ''; - readonly readOnlyAccessRoleArns: string[] = []; -} - -export class AcceleratorSettingsConfig implements i.IAcceleratorSettingsConfig { - readonly maxConcurrentStacks: number | undefined = undefined; -} - -export class SsmInventoryConfig implements i.ISsmInventoryConfig { - readonly enable = false; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class SsmParametersConfig implements i.ISsmParametersConfig { - readonly parameters: SsmParameterConfig[] = []; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class SsmParameterConfig implements i.ISsmParameterConfig { - readonly name = ''; - readonly path = ''; - readonly value = ''; -} - -export class GlobalConfig implements i.IGlobalConfig { - /** - * Global configuration file name, this file must be present in accelerator config repository - */ - static readonly FILENAME = 'global-config.yaml'; - readonly homeRegion: string = ''; - readonly enabledRegions: t.Region[] = []; - readonly managementAccountAccessRole: string = ''; - readonly cloudwatchLogRetentionInDays = 3653; - readonly centralizeCdkBuckets: centralizeCdkBucketsConfig | undefined = undefined; - readonly cdkOptions = new cdkOptionsConfig(); - readonly terminationProtection = true; - readonly externalLandingZoneResources: externalLandingZoneResourcesConfig | undefined = undefined; - readonly controlTower: ControlTowerConfig = new ControlTowerConfig(); - readonly logging: LoggingConfig = new LoggingConfig(); - readonly reports: ReportConfig | undefined = undefined; - readonly limits: ServiceQuotaLimitsConfig[] | undefined = undefined; - readonly ssmParameters: SsmParametersConfig[] | undefined; - readonly backup: BackupConfig | undefined = undefined; - readonly snsTopics: SnsConfig | undefined = undefined; - readonly ssmInventory: SsmInventoryConfig | undefined = undefined; - readonly tags: t.Tag[] = []; - readonly acceleratorMetadata: AcceleratorMetadataConfig | undefined = undefined; - readonly acceleratorSettings: AcceleratorSettingsConfig | undefined = undefined; - readonly lambda: LambdaConfig | undefined = undefined; - readonly s3: S3GlobalConfig | undefined = undefined; - - /** - * SSM IAM Role Parameters to be loaded for session manager policy attachments - */ - - iamRoleSsmParameters: { account: string; region: string; parametersByPath: { [key: string]: string } }[] = []; - - /** - * - * @param props - * @param values - * @param configDir - * @param validateConfig - */ - - constructor( - props: { - homeRegion: string; - controlTower: { enable: boolean; landingZone?: ControlTowerLandingZoneConfig }; - managementAccountAccessRole: string; - }, - values?: i.IGlobalConfig, - ) { - if (values) { - Object.assign(this, values); - } else { - this.homeRegion = props.homeRegion; - this.enabledRegions = [props.homeRegion as t.Region]; - this.controlTower = { - enable: props.controlTower.enable, - landingZone: props.controlTower.landingZone, - controls: [], - }; - this.managementAccountAccessRole = props.managementAccountAccessRole; - } - } - - public getSnsTopicNames(): string[] { - return this.snsTopics?.topics.flatMap(item => item.name) ?? []; - } - - /** - * Load from file in given directory - * @param dir - * @param validateConfig - * @returns - */ - static load(dir: string, replacementsConfig?: ReplacementsConfig): GlobalConfig { - const initialBuffer = fs.readFileSync(path.join(dir, GlobalConfig.FILENAME), 'utf8'); - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseGlobalConfig(yaml.load(buffer)); - - const homeRegion = values.homeRegion; - const controlTower = values.controlTower; - const managementAccountAccessRole = values.managementAccountAccessRole; - - return new GlobalConfig( - { - homeRegion, - controlTower: { enable: controlTower.enable, landingZone: controlTower.landingZone }, - managementAccountAccessRole, - }, - values, - ); - } - - /** - * Loads the file raw with default replacements placeholders just to get - * the management account access role. This is required to get the Role name - * that can be assumed to load the replacements, so cannot be done using the - * normal loading method. This is abstracted away so that this method of - * loading is not accidentally used to partially load config files. - */ - static loadRawGlobalConfig(dir: string): GlobalConfig { - const accountsConfig = AccountsConfig.load(dir); - const orgConfig = OrganizationConfig.load(dir); - let replacementsConfig: ReplacementsConfig; - - if (fs.existsSync(path.join(dir, ReplacementsConfig.FILENAME))) { - replacementsConfig = ReplacementsConfig.load(dir, accountsConfig, true); - } else { - replacementsConfig = new ReplacementsConfig(); - } - - replacementsConfig.loadReplacementValues({}, orgConfig.enable); - return GlobalConfig.load(dir, replacementsConfig); - } - - /** - * Load from string content - * @param content - */ - static loadFromString(content: string): GlobalConfig | undefined { - try { - const values = t.parseGlobalConfig(yaml.load(content)); - return new GlobalConfig(values); - } catch (e) { - logger.error('Error parsing input, global config undefined'); - logger.error(`${e}`); - throw new Error('Could not load global configuration'); - } - } - - public async saveAseaResourceMapping(resources: t.AseaResourceMapping[]) { - if ( - !this.externalLandingZoneResources?.importExternalLandingZoneResources || - !this.externalLandingZoneResources.mappingFileBucket - ) { - throw new Error( - `saveAseaResourceMapping can only be called when importExternalLandingZoneResources set as true and mappingFileBucket is provided`, - ); - } - const resourcesPath = path.join('asea-assets', 'new', 'aseaResources.json'); - await fs.promises.writeFile(resourcesPath, JSON.stringify(resources, null, 2)); - } - - public async loadExternalMapping() { - if (!this.externalLandingZoneResources?.importExternalLandingZoneResources) { - return; - } - if (!this.externalLandingZoneResources.mappingFileBucket) { - throw new Error( - `externalLandingZoneResources/mappingFileBucket is required when importExternalLandingZoneResources is set to true`, - ); - } - this.externalLandingZoneResources.accountsDeployedExternally = []; - const aseaAssetPath = 'asea-assets'; - await fs.promises.mkdir(aseaAssetPath, { recursive: true }); - - if (!directoryExists('asea-assets')) { - throw new Error(`Could not create temp directory ${aseaAssetPath} for asea assets`); - } - const s3Client = new AWS.S3({ region: this.homeRegion }); - const mappingFile = await this.downloadFile({ - relativePath: 'mapping.json', - tempDirectory: aseaAssetPath, - bucket: this.externalLandingZoneResources.mappingFileBucket, - s3Client, - }); - const resourceListFile = await this.downloadFile({ - relativePath: 'aseaResources.json', - tempDirectory: aseaAssetPath, - bucket: this.externalLandingZoneResources.mappingFileBucket, - s3Client, - }); - - const mapping = (await this.readJsonFromDisk(mappingFile)) as t.ASEAMappings; - this.externalLandingZoneResources.resourceList = await this.readJsonFromDisk(resourceListFile); - await this.downloadASEAStacksAndResources({ - s3Client, - mappingBucket: this.externalLandingZoneResources.mappingFileBucket, - tempDirectory: aseaAssetPath, - mapping, - }); - this.externalLandingZoneResources.templateMap = mapping; - const accounts = Object.keys(mapping).map(key => mapping[key].accountId); - this.externalLandingZoneResources.accountsDeployedExternally = [...new Set(accounts)]; - } - - private async downloadASEAStacksAndResources(props: { - s3Client: AWS.S3; - mappingBucket: string; - tempDirectory: string; - mapping: t.ASEAMappings; - }): Promise { - const downloads: Promise[] = []; - Object.keys(props.mapping).forEach(key => { - downloads.push( - this.downloadFile({ - bucket: props.mappingBucket, - s3Client: props.s3Client, - relativePath: props.mapping[key].templatePath, - tempDirectory: props.tempDirectory, - }), - ); - downloads.push( - this.downloadFile({ - bucket: props.mappingBucket, - s3Client: props.s3Client, - relativePath: props.mapping[key].resourcePath, - tempDirectory: props.tempDirectory, - }), - ); - const nestedStacks = props.mapping[key].nestedStacks; - if (nestedStacks) { - Object.keys(nestedStacks).forEach(nestedStackKey => { - downloads.push( - this.downloadFile({ - bucket: props.mappingBucket, - s3Client: props.s3Client, - relativePath: nestedStacks[nestedStackKey].templatePath, - tempDirectory: props.tempDirectory, - }), - ); - downloads.push( - this.downloadFile({ - bucket: props.mappingBucket, - s3Client: props.s3Client, - relativePath: nestedStacks[nestedStackKey].resourcePath, - tempDirectory: props.tempDirectory, - }), - ); - }); - } - }); - - return Promise.all(downloads); - } - - private async downloadFile(props: { relativePath: string; tempDirectory: string; bucket: string; s3Client: AWS.S3 }) { - const filePath = path.join(props.tempDirectory, props.relativePath); - const directory = filePath.split('/').slice(0, -1).join('/'); - if (!(await fileExists(filePath))) { - const s3Object = await this.getS3Object({ - bucket: props.bucket, - objectKey: props.relativePath, - s3Client: props.s3Client, - }); - await fs.promises.mkdir(directory, { recursive: true }); - if (filePath === 'asea-assets/aseaResources.json' && !s3Object?.body) { - await fs.promises.writeFile(filePath, s3Object?.body || '[]'); - } else { - await fs.promises.writeFile(filePath, s3Object?.body || ''); - } - } - - return filePath; - } - - async loadLzaResources(partition: string, prefix: string) { - if (!this.externalLandingZoneResources?.importExternalLandingZoneResources) return; - if (!this.externalLandingZoneResources.resourceParameters) { - this.externalLandingZoneResources.resourceParameters = {}; - } - const lzaResourcesPromises = []; - for (const region of this.enabledRegions) { - lzaResourcesPromises.push( - this.loadRegionLzaResources( - region, - partition, - prefix, - this.externalLandingZoneResources?.accountsDeployedExternally || [], - ), - ); - } - await Promise.all(lzaResourcesPromises); - } - public async loadIAMRoleSSMParameters( - region: string, - partition: string, - prefix: string, - accounts: string[], - managementAccountId: string, - isOrgEnabled: boolean, - ) { - const ssmPath = `${prefix}/iam/role/`; - const promises = []; - const ssmParameters = []; - if (isOrgEnabled) { - return; - } - for (const account of accounts) { - promises.push(this.loadIAMRoleSSMParametersByEnv(ssmPath, account, region, partition, managementAccountId)); - if (promises.length > 800) { - const resolvedPromises = await Promise.all(promises); - ssmParameters.push(...resolvedPromises); - promises.length = 0; - } - } - const resolvedPromises = await Promise.all(promises); - ssmParameters.push(...resolvedPromises); - promises.length = 0; - - this.iamRoleSsmParameters = ssmParameters; - } - - private async loadIAMRoleSSMParametersByEnv( - ssmPath: string, - account: string, - region: string, - partition: string, - managementAccountId: string, - ): Promise<{ - account: string; - region: string; - parametersByPath: { - [key: string]: string; - }; - }> { - let ssmClient = new SSMClient({ region }); - if (account !== managementAccountId) { - const crossAccountCredentials = await getCrossAccountCredentials( - account, - region, - partition, - this.managementAccountAccessRole, - 'acceleratorResourceMapping', - ); - if (!crossAccountCredentials) { - return { - account, - region, - parametersByPath: {}, - }; - } - ssmClient = this.getCrossAccountSsmClient(region, crossAccountCredentials); - } - const parametersByPath = await this.getParametersByPath(ssmPath, ssmClient); - return { - account, - region, - parametersByPath, - }; - } - private async loadRegionLzaResources( - region: string, - partition: string, - prefix: string, - accounts: string[], - ): Promise { - const getSsmPath = (resourceType: t.AseaResourceTypePaths) => `${prefix}${resourceType}`; - if (!this.externalLandingZoneResources?.importExternalLandingZoneResources) return; - for (const accountId of accounts) { - const crossAccountCredentials = await getCrossAccountCredentials( - accountId, - region, - partition, - this.managementAccountAccessRole, - 'acceleratorResourceMapping', - ); - - if (!crossAccountCredentials) { - return; - } - const ssmClient = await this.getCrossAccountSsmClient(region, crossAccountCredentials); - // Get Resources which are there in both external Accelerator and LZA - // Can load only resources which are maintained in both - // Loading all to avoid reading SSM Params multiple times - // Can also use DynamoDB for resource status instead of SSM Parameters, - // But with DynamoDB knowing resource creation status in CloudFormation is difficult - const ssmPromises = [ - this.getParametersByPath(getSsmPath(t.AseaResourceTypePaths.IAM), ssmClient), - this.getParametersByPath(getSsmPath(t.AseaResourceTypePaths.VPC), ssmClient), - this.getParametersByPath(getSsmPath(t.AseaResourceTypePaths.TRANSIT_GATEWAY), ssmClient), - this.getParametersByPath(getSsmPath(t.AseaResourceTypePaths.VPC_PEERING), ssmClient), - this.getParametersByPath(getSsmPath(t.AseaResourceTypePaths.NETWORK_FIREWALL), ssmClient), - ]; - const ssmResults = await Promise.all(ssmPromises); - this.externalLandingZoneResources.resourceParameters[`${accountId}-${region}`] = ssmResults.reduce( - (resources, result) => { - return { ...resources, ...result }; - }, - {}, - ); - } - } - - private async getParametersByPath(path: string, ssmClient: SSMClient) { - const parameters: { [key: string]: string } = {}; - try { - let nextToken: string | undefined = undefined; - do { - const parametersOutput = await throttlingBackOff(() => - ssmClient.send( - new GetParametersByPathCommand({ - Path: path, - MaxResults: 10, - NextToken: nextToken, - Recursive: true, - }), - ), - ); - nextToken = parametersOutput.NextToken; - parametersOutput.Parameters?.forEach(parameter => (parameters[parameter.Name!] = parameter.Value!)); - } while (nextToken); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - logger.error(`Failed to retrieve parameter path for path ${path}`); - throw new Error(err); - } - return parameters; - } - - private async getS3Object(props: { - bucket: string; - objectKey: string; - s3Client?: AWS.S3; - }): Promise<{ body: string; path: string } | undefined> { - let s3Client = props.s3Client; - if (!s3Client) { - s3Client = new AWS.S3({ region: this.homeRegion }); - } - try { - const response = await s3Client - .getObject({ - Bucket: props.bucket, - Key: props.objectKey, - }) - .promise(); - if (!response.Body) { - logger.error(`Could not load file from path s3://${props.bucket}/${props.objectKey}`); - return; - } - return { body: response.Body.toString(), path: props.objectKey }; - } catch (e) { - if ((e as AWS.AWSError).code === 'NoSuchKey') return; - throw e; - } - } - - private async readJsonFromDisk(mappingFilePath: string) { - const mappingFile = (await fs.readFileSync(mappingFilePath)).toString(); - return JSON.parse(mappingFile); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public loadJsonFromDisk(filePath: string): any { - try { - const file = fs.readFileSync(filePath).toString(); - return JSON.parse(file); - } catch (e) { - logger.error(`Failed to load file ${filePath}`); - throw e; - } - } - private getCrossAccountSsmClient(region: string, assumeRoleCredential: AssumeRoleCommandOutput) { - return new SSMClient({ - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials?.SessionToken, - }, - region: region, - }); - } -} diff --git a/source/packages/@aws-accelerator/config/lib/iam-config.ts b/source/packages/@aws-accelerator/config/lib/iam-config.ts deleted file mode 100644 index 6d2f3df..0000000 --- a/source/packages/@aws-accelerator/config/lib/iam-config.ts +++ /dev/null @@ -1,356 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { AccountsConfig } from './accounts-config'; -import { ReplacementsConfig } from './replacements-config'; -import * as t from './common'; -import * as i from './models/iam-config'; - -const logger = createLogger(['iam-config']); - -export class ManagedActiveDirectorySharedOuConfig implements i.IManagedActiveDirectorySharedOuConfig { - readonly organizationalUnits: string[] = []; - readonly excludedAccounts: string[] | undefined = undefined; -} - -export class ManagedActiveDirectorySecretConfig implements i.IManagedActiveDirectorySecretConfig { - readonly adminSecretName: string | undefined = undefined; - readonly account: string | undefined = undefined; - readonly region: t.Region = 'us-east-1'; -} - -export class ActiveDirectoryConfigurationInstanceUserDataConfig - implements i.IActiveDirectoryConfigurationInstanceUserDataConfig -{ - readonly scriptName = ''; - readonly scriptFilePath = ''; -} - -export class ActiveDirectoryPasswordPolicyConfig implements i.IActiveDirectoryPasswordPolicyConfig { - readonly history = 24; - readonly maximumAge = 90; - readonly minimumAge = 1; - readonly minimumLength = 14; - readonly complexity = true; - readonly reversible = false; - readonly failedAttempts = 6; - readonly lockoutDuration = 30; - readonly lockoutAttemptsReset = 30; -} - -export class ActiveDirectoryUserConfig implements i.IActiveDirectoryUserConfig { - readonly name = ''; - readonly email = ''; - readonly groups = []; -} - -export class ActiveDirectoryConfigurationInstanceConfig implements i.IActiveDirectoryConfigurationInstanceConfig { - readonly instanceType = ''; - readonly vpcName = ''; - readonly imagePath = ''; - readonly securityGroupInboundSources = []; - readonly instanceRole = ''; - readonly enableTerminationProtection: boolean | undefined = undefined; - readonly subnetName = ''; - readonly userDataScripts: ActiveDirectoryConfigurationInstanceUserDataConfig[] = []; - readonly adGroups: string[] = []; - readonly adPerAccountGroups: string[] = []; - readonly adConnectorGroup = ''; - readonly adUsers: ActiveDirectoryUserConfig[] = []; - readonly adPasswordPolicy: ActiveDirectoryPasswordPolicyConfig = new ActiveDirectoryPasswordPolicyConfig(); -} - -export class ManagedActiveDirectoryLogConfig implements i.IManagedActiveDirectoryLogConfig { - readonly groupName = ''; - readonly retentionInDays: number | undefined = undefined; -} - -export class ManagedActiveDirectoryVpcSettingsConfig implements i.IManagedActiveDirectoryVpcSettingsConfig { - readonly vpcName = ''; - readonly subnets = []; -} - -export class ManagedActiveDirectoryConfig implements i.IManagedActiveDirectoryConfig { - readonly name = ''; - readonly account = ''; - readonly region: t.Region = 'us-east-1'; - readonly dnsName = ''; - readonly netBiosDomainName = ''; - readonly description: string | undefined = undefined; - readonly edition = 'Standard'; - readonly vpcSettings: ManagedActiveDirectoryVpcSettingsConfig = new ManagedActiveDirectoryVpcSettingsConfig(); - readonly resolverRuleName = ''; - readonly secretConfig: ManagedActiveDirectorySecretConfig | undefined = undefined; - readonly sharedOrganizationalUnits: ManagedActiveDirectorySharedOuConfig | undefined = undefined; - readonly sharedAccounts: string[] | undefined = undefined; - readonly logs: ManagedActiveDirectoryLogConfig | undefined = undefined; - readonly activeDirectoryConfigurationInstance: ActiveDirectoryConfigurationInstanceConfig | undefined = undefined; -} - -export class SamlProviderConfig implements i.ISamlProviderConfig { - readonly name: string = ''; - readonly metadataDocument: string = ''; -} - -export class UserConfig implements i.IUserConfig { - readonly username: string = ''; - readonly boundaryPolicy: string = ''; - readonly group: string = ''; -} - -export class UserSetConfig implements i.IUserSetConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly users: UserConfig[] = []; -} - -export class PoliciesConfig implements i.IPoliciesConfig { - readonly awsManaged: string[] | undefined = undefined; - readonly customerManaged: string[] | undefined = undefined; -} - -export class GroupConfig implements i.IGroupConfig { - readonly name: string = ''; - readonly policies: PoliciesConfig | undefined = undefined; -} - -export class GroupSetConfig implements i.IGroupSetConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly groups: GroupConfig[] = []; -} - -export class AssumedByConfig implements i.IAssumedByConfig { - readonly principal: string = ''; - readonly type!: t.AssumedByType; -} - -export class RoleConfig implements i.IRoleConfig { - readonly assumedBy: AssumedByConfig[] = []; - readonly externalIds?: string[] | undefined; - readonly instanceProfile: boolean | undefined = undefined; - readonly boundaryPolicy: string = ''; - readonly name: string = ''; - readonly policies: PoliciesConfig | undefined = undefined; -} - -export class IdentityCenterConfig implements i.IIdentityCenterConfig { - readonly name: string = ''; - readonly delegatedAdminAccount: string | undefined = undefined; - readonly identityCenterPermissionSets: IdentityCenterPermissionSetConfig[] | undefined = undefined; - readonly identityCenterAssignments: IdentityCenterAssignmentConfig[] | undefined = undefined; -} - -export class PolicyConfig implements i.IPolicyConfig { - readonly name: string = ''; - readonly policy: string = ''; -} - -export class CustomerManagedPolicyReferenceConfig implements i.ICustomerManagedPolicyReferenceConfig { - readonly name: string = ''; - readonly path: string | undefined = undefined; -} - -export class PermissionsBoundaryConfig implements i.IPermissionsBoundaryConfig { - readonly awsManagedPolicyName: string | undefined = undefined; - readonly customerManagedPolicy: CustomerManagedPolicyReferenceConfig | undefined = undefined; -} - -export class IdentityCenterPoliciesConfig implements i.IIdentityCenterPoliciesConfig { - readonly awsManaged: string[] | undefined = undefined; - readonly customerManaged: string[] | undefined = undefined; - readonly acceleratorManaged: string[] | undefined = undefined; - readonly inlinePolicy: string | undefined = undefined; - readonly permissionsBoundary: PermissionsBoundaryConfig | undefined = undefined; -} - -export class IdentityCenterPermissionSetConfig implements i.IIdentityCenterPermissionSetConfig { - readonly name: string = ''; - readonly policies: IdentityCenterPoliciesConfig | undefined = undefined; - readonly sessionDuration: number | undefined = undefined; -} - -export class IdentityCenterAssignmentPrincipalConfig implements i.IIdentityCenterAssignmentPrincipalConfig { - readonly type: string = ''; - readonly name: string = ''; -} - -export class IdentityCenterAssignmentConfig implements i.IIdentityCenterAssignmentConfig { - readonly name: string = ''; - readonly permissionSetName: string = ''; - readonly principalId: string | undefined = undefined; - readonly principalType: t.PrincipalType | undefined = undefined; - readonly principals: IdentityCenterAssignmentPrincipalConfig[] | undefined = undefined; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class RoleSetConfig implements i.IRoleSetConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly path: string | undefined = undefined; - readonly roles: RoleConfig[] = []; -} - -export class PolicySetConfig implements i.IPolicySetConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly identityCenterDependency: boolean | undefined = undefined; - readonly policies: PolicyConfig[] = []; -} - -export class IamConfig implements i.IIamConfig { - /** - * A name for the iam config file in config repository - * - * @default iam-config.yaml - */ - static readonly FILENAME = 'iam-config.yaml'; - - readonly providers: SamlProviderConfig[] = []; - readonly policySets: PolicySetConfig[] = []; - readonly roleSets: RoleSetConfig[] = []; - readonly groupSets: GroupSetConfig[] = []; - readonly userSets: UserSetConfig[] = []; - readonly identityCenter: IdentityCenterConfig | undefined = undefined; - readonly managedActiveDirectories: ManagedActiveDirectoryConfig[] | undefined = undefined; - - /** - * - * @param values - */ - constructor(values?: i.IIamConfig) { - Object.assign(this, values); - } - - /** - * Load from config file content - * @param dir - * @param replacementsConfig - * @returns - */ - - static load(dir: string, replacementsConfig?: ReplacementsConfig): IamConfig { - const initialBuffer = fs.readFileSync(path.join(dir, IamConfig.FILENAME), 'utf8'); - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseIamConfig(yaml.load(buffer)); - return new IamConfig(values); - } - - /** - * Load from string content - * @param content - */ - static loadFromString(content: string): IamConfig | undefined { - try { - const values = t.parseIamConfig(yaml.load(content)); - return new IamConfig(values); - } catch (e) { - logger.error('Error parsing input, iam config undefined'); - logger.error(`${e}`); - throw new Error('Could not load iam configuration'); - } - } - - public getManageActiveDirectoryAdminSecretName(directoryName: string): string { - let directoryFound = false; - for (const managedActiveDirectory of this.managedActiveDirectories ?? []) { - if (managedActiveDirectory.name === directoryName) { - directoryFound = true; - if (managedActiveDirectory.secretConfig) { - if (managedActiveDirectory.secretConfig.adminSecretName) { - return managedActiveDirectory.secretConfig.adminSecretName; - } - } - } - } - if (directoryFound) { - return 'admin'; - } - logger.error(`getManageActiveDirectoryAdminSecretName Directory ${directoryName} not found in iam-config file`); - throw new Error('configuration validation failed.'); - } - - public getManageActiveDirectorySecretAccountName(directoryName: string): string { - let directoryFound = false; - let directoryAccount = ''; - for (const managedActiveDirectory of this.managedActiveDirectories ?? []) { - if (managedActiveDirectory.name === directoryName) { - directoryFound = true; - directoryAccount = managedActiveDirectory.account; - if (managedActiveDirectory.secretConfig) { - if (managedActiveDirectory.secretConfig.account) { - return managedActiveDirectory.secretConfig.account; - } else { - managedActiveDirectory.account; - } - } - } - } - if (directoryFound) { - return directoryAccount; - } - logger.error(`getManageActiveDirectoryAdminSecretName Directory ${directoryName} not found in iam-config file`); - throw new Error('configuration validation failed.'); - } - - public getManageActiveDirectorySecretRegion(directoryName: string): string { - for (const managedActiveDirectory of this.managedActiveDirectories ?? []) { - if (managedActiveDirectory.name === directoryName) { - if (managedActiveDirectory.secretConfig) { - if (managedActiveDirectory.secretConfig.region) { - return managedActiveDirectory.secretConfig.region; - } else { - return managedActiveDirectory.region; - } - } - } - } - logger.error(`getManageActiveDirectoryAdminSecretName Directory ${directoryName} not found in iam-config file`); - throw new Error('configuration validation failed.'); - } - - public getManageActiveDirectorySharedAccountNames(directoryName: string, configDir: string): string[] { - const activeDirectories = this.managedActiveDirectories ?? []; - const managedActiveDirectory = activeDirectories.find( - managedActiveDirectory => managedActiveDirectory.name === directoryName, - ); - if (!managedActiveDirectory) { - logger.error(`getManageActiveDirectoryAdminSecretName Directory ${directoryName} not found in iam-config file`); - throw new Error('configuration validation failed.'); - } - const accountsConfig = AccountsConfig.load(configDir); - - const sharedOuAccounts = - managedActiveDirectory.sharedOrganizationalUnits?.organizationalUnits - .map(ou => this.getAccountsByOU(ou, accountsConfig)) - .flat() ?? []; - const sharedAccounts = managedActiveDirectory.sharedAccounts ?? []; - const excludedAccounts = managedActiveDirectory.sharedOrganizationalUnits?.excludedAccounts ?? []; - const accounts = [...sharedAccounts, ...sharedOuAccounts]; - const filteredAccounts = accounts.filter(account => !excludedAccounts.includes(account)); - return [...new Set(filteredAccounts)]; - } - - private getAccountsByOU(ouName: string, accountsConfig: AccountsConfig) { - const allAccountItems = [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]; - const allAccounts = allAccountItems.map(accountItem => accountItem.name); - if (ouName === 'Root') { - return allAccounts; - } - return allAccountItems - .filter(accountItem => accountItem.organizationalUnit === ouName) - .map(accountItem => accountItem.name); - } -} diff --git a/source/packages/@aws-accelerator/config/lib/models/accounts-config.ts b/source/packages/@aws-accelerator/config/lib/models/accounts-config.ts deleted file mode 100644 index c8f353d..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/accounts-config.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; - -export interface IAccountsConfig { - mandatoryAccounts: IAccountConfig[] | IGovCloudAccountConfig[]; - workloadAccounts: IAccountConfig[] | IGovCloudAccountConfig[]; - accountIds?: IAccountIdConfig[]; -} - -export interface IBaseAccountConfig { - /** - * The friendly name that is assigned to the account for reference within the Accelerator. The name will be used to reference - * this account in other configuration files and not to lookup the account in AWS. - * - * For pre-existing accounts this does not need to match the AWS account name. - * - * When creating new accounts with the Accelerator, this name will be used as the AWS account name. - * - * The name should not contain any spaces as this isn't supported by the Accelerator. - */ - name: t.NonEmptyNoSpaceString; - /** - * The description is to used to provide more information about the account. - * This value is not used when creating accounts. - */ - description?: t.NonEmptyString; - /** - * The email address of the owner to assign to the account. The email address - * must not already be associated with another AWS account. You must use a - * valid email address. - * The address must be a minimum of 6 and a maximum of 64 characters long. - * All characters must be 7-bit ASCII characters - * There must be one and only one @ symbol, which separates the local name from the domain name. - * The local name can’t contain any of the following characters: whitespace, ” ‘ ( ) < > [ ] : ; , | % & - * The local name can’t begin with a dot (.) - * The domain name can consist of only the characters [a-z],[A-Z],[0-9], hyphen (-), or dot (.) - * The domain name can’t begin or end with a hyphen (-) or dot (.) - * The domain name must contain at least one dot - */ - email: t.EmailAddress; - /** - * The friendly name for the Organizational Unit that this account - * is a member of. - * This Organizational Unit must exist in the organization-config.yaml file. - */ - organizationalUnit?: t.NonEmptyString; -} - -/** - * {@link AccountsConfig} / {@link AccountConfig} - * - * @description - * Account configuration - * - * @example - * ``` - * - name: Workload01 - * description: Workload account 01 - * email: example-email+workload01@example.com - * organizationalUnit: Workloads - * warm: true - * ``` - */ -export interface IAccountConfig extends IBaseAccountConfig { - /** - * 'Warm' the account by creating an EC2 instance - * that runs for 15 minutes - * Use for new accounts that will need to have - * ec2 instance provisioned as part of the solution - * The 'warming' will take place in the operations stack - * This property may be removed after the account has - * been provisioned - */ - warm?: boolean; -} - -/** - * *{@link AccountsConfig} / {@link GovCloudAccountConfig} - * - * @description - * GovCloud Account configuration - * Used instead of the account configuration in the commercial - * partition when creating GovCloud partition linked accounts. - * - * @example - * ``` - * - name: Workload01 - * description: Workload account 01 - * email: example-email+workload01@example.com - * organizationalUnit: Workloads - * enableGovCloud: true - * ``` - */ -export interface IGovCloudAccountConfig extends IBaseAccountConfig { - /** - * Indicates whether or not a GovCloud partition account - * should be created. - */ - enableGovCloud?: boolean; -} - -export interface IAccountIdConfig { - email: t.EmailAddress; - accountId: t.AwsAccountId; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/customizations-config.ts b/source/packages/@aws-accelerator/config/lib/models/customizations-config.ts deleted file mode 100644 index 2b3155a..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/customizations-config.ts +++ /dev/null @@ -1,2608 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; - -export type TargetGroupProtocolType = 'TCP' | 'TLS' | 'UDP' | 'TCP_UDP' | 'HTTP' | 'HTTPS' | 'GENEVE'; - -export type TargetGroupProtocolVersionType = 'GRPC' | 'HTTP1' | 'HTTP2'; - -export type TargetGroupType = 'instance' | 'ip' | 'alb' | 'lambda'; - -export type TargetGroupAttributeStickinessType = - | 'lb_cookie' - | 'app_cookie' - | 'source_ip' - | 'source_ip_dest_ip' - | 'source_ip_dest_ip_proto'; - -export type TargetGroupAttributeAlgorithm = 'round_robin' | 'least_outstanding_requests'; - -export type TargetGroupHealthCheckProtocolType = 'HTTP' | 'HTTPS' | 'TCP'; - -export type TargetGroupTargetFailoverType = 'no_rebalance' | 'rebalance'; - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link TargetGroupItemConfig} / {@link TargetGroupHealthCheckConfig}* - * - * @description - * Configure health check for target group. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html} - * - * @example - * ``` - * healthCheck: - * interval: 5 - * path: '/' - * port: 80 - * protocol: TCP - * timeout: 30 - * ``` - */ -export interface ITargetGroupHealthCheckType { - /** - * The approximate amount of time, in seconds, between health checks of an individual target. The range is 5-300. - * If the target group protocol is TCP, TLS, UDP, TCP_UDP, HTTP or HTTPS, the default is 30 seconds. - * If the target group protocol is GENEVE, the default is 10 seconds. - */ - readonly interval?: number; - /** - * [HTTP/HTTPS health checks] The destination for health checks on the targets. - * [HTTP1 or HTTP2 protocol version] The ping path. The default is /. - * [GRPC protocol version] The path of a custom health check method with the format /package.service/method. The default is /AWS.ALB/healthcheck. - */ - readonly path?: t.NonEmptyString; - /** - * The port the load balancer uses when performing health checks on targets. - * If the protocol is HTTP, HTTPS, TCP, TLS, UDP, or TCP_UDP, the default is `traffic-port`, which is the port on which each target receives traffic from the load balancer. - * If the protocol is GENEVE, the default is port 80. - */ - readonly port?: number; - /** - * The protocol the load balancer uses when performing health checks on targets. - * For Application Load Balancers, the default is HTTP. - * For Network Load Balancers and Gateway Load Balancers, the default is TCP. - * The TCP protocol is not supported for health checks if the protocol of the target group is HTTP or HTTPS. - * GENEVE, TLS, UDP, and TCP_UDP protocols are not supported for health checks. - */ - readonly protocol?: TargetGroupHealthCheckProtocolType; - /** - * The amount of time, in seconds, during which no response from a target means a failed health check. - * The range is 2–120 seconds. - * For target groups with a protocol of HTTP, the default is 6 seconds. - * For target groups with a protocol of TCP, TLS or HTTPS, the default is 10 seconds. - * For target groups with a protocol of GENEVE, the default is 5 seconds. - */ - readonly timeout?: number; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link TargetGroupItemConfig} / {@link TargetGroupThresholdConfig}* - * - * @description - * Configure health check threshold for target group. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html} - * - * @example - * ``` - * threshold: - * healthy: 5 - * unhealthy: 5 - * ``` - */ -export interface ITargetGroupThresholdType { - /** - * The number of consecutive health check successes required before considering a target healthy. The range is 2-10. - * If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP or HTTPS, the default is 5. - * For target groups with a protocol of GENEVE, the default is 3. - */ - readonly healthy?: number; - /** - * The number of consecutive health check failures required before considering a target unhealthy. The range is 2-10. - * If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP or HTTPS, the default is 2. - * For target groups with a protocol of GENEVE, the default is 3. - */ - readonly unhealthy?: number; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link TargetGroupItemConfig} / {@link NlbTargetTypeConfig}* - * - * @description - * Add the ability to target an NLB created by the Landing Zone Accelerator - * - * - * @example - * ``` - * matcher: - * grpcCode: 5 - * httpCode: 5 - * ``` - */ -export interface ITargetGroupMatcherType { - /** - * You can specify values between 0 and 99. You can specify multiple values (for example, "0,1") or a range of values (for example, "0-5"). The default value is 12. - */ - readonly grpcCode?: t.NonEmptyString; - /** - * For Application Load Balancers, you can specify values between 200 and 499, with the default value being 200. You can specify multiple values (for example, "200,202") or a range of values (for example, "200-299"). - * For Network Load Balancers, you can specify values between 200 and 599, with the default value being 200-399. You can specify multiple values (for example, "200,202") or a range of values (for example, "200-299"). - * Note that when using shorthand syntax, some values such as commas need to be escaped. - */ - readonly httpCode?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link TargetGroupItemConfig} / {@link TargetGroupAttributeConfig}* - * - * @description - * Set attributes for target group. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_TargetGroupAttribute.html} - * - * @example - * ``` - * attributes: - * deregistrationDelay: 1200 - * stickiness: true - * # applies to application load balancer - * stickinessType: app_cookie - * algorithm: round_robin - * slowStart: 120 - * appCookieName: chocolate-chip - * appCookieDuration: 4800 - * lbCookieDuration: 4800 - * # applies to network load balancer - * connectionTermination: true - * preserveClientIp: true - * proxyProtocolV2: true - * # applies to Gateway Load Balancer - * targetFailover: rebalance - * ``` - */ - -export interface ITargetGroupAttributeTypes { - /** - * The amount of time, in seconds, for Elastic Load Balancing to wait before changing the state of a deregistering target from draining to unused. The range is 0-3600 seconds. The default value is 300 seconds. - */ - readonly deregistrationDelay?: number; - /** - * Indicates whether target stickiness is enabled. The value is true or false. The default is false. - */ - readonly stickiness?: boolean; - /** - * Indicates the type of stickiness. The possible values are: - * - lb_cookie and app_cookie for Application Load Balancers. - * - source_ip for Network Load Balancers. - * - source_ip_dest_ip and source_ip_dest_ip_proto for Gateway Load Balancers - */ - readonly stickinessType?: TargetGroupAttributeStickinessType; - /** - * The load balancing algorithm determines how the load balancer selects targets when routing requests. The value is round_robin or least_outstanding_requests. The default is round_robin. - * The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address. - */ - readonly algorithm?: TargetGroupAttributeAlgorithm; - /** - * The time period, in seconds, during which a newly registered target receives an increasing share of the traffic to the target group. After this time period ends, the target receives its full share of traffic. The range is 30-900 seconds (15 minutes). The default is 0 seconds (disabled). - * The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address. - */ - readonly slowStart?: number; - /** - * Indicates the name of the application-based cookie. Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP, and AWSALBTG; they're reserved for use by the load balancer. - * The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address. - */ - readonly appCookieName?: t.NonEmptyString; - /** - * The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the application-based cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). - * The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address. - */ - readonly appCookieDuration?: number; - /** - * The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). - * The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address. - */ - readonly lbCookieDuration?: number; - /** - * Indicates whether the load balancer terminates connections at the end of the deregistration timeout. The value is true or false. The default is false. - * The following attribute is supported only by Network Load Balancers. - */ - readonly connectionTermination?: boolean; - /** - * Indicates whether client IP preservation is enabled. The value is true or false. The default is disabled if the target group type is IP address and the target group protocol is TCP or TLS. Otherwise, the default is enabled. Client IP preservation cannot be disabled for UDP and TCP_UDP target groups. - * The following attribute is supported only by Network Load Balancers. - */ - readonly preserveClientIp?: boolean; - /** - * Indicates whether Proxy Protocol version 2 is enabled. The value is true or false. The default is false. - * The following attribute is supported only by Network Load Balancers. - */ - readonly proxyProtocolV2?: boolean; - /** - * Indicates how the Gateway Load Balancer handles existing flows when a target is deregistered or becomes unhealthy. - * The possible values are rebalance and no_rebalance. The default is no_rebalance - */ - readonly targetFailover?: TargetGroupTargetFailoverType; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link TargetGroupItemConfig} / {@link TargetGroupMatcherConfig}* - * - * @description - * The codes to use when checking for a successful response from a target. If the protocol version is gRPC, these are gRPC codes. Otherwise, these are HTTP codes. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_Matcher.html} - * - * @example - * ``` - * targets: - * - account: MyAccount - * region: us-east-1 - * nlbName: myNlb - * ``` - */ -export interface INlbTargetType { - /** - * Friendly Account Name where the NLB is deployed - */ - readonly account: t.NonEmptyString; - /** - * Region where the NLB is deployed - */ - readonly region: t.NonEmptyString; - /** - * Friendly name of the NLB - */ - readonly nlbName: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link TargetGroupItemConfig}* - * - * @description - * Target Group Configuration - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html} - * - * @example - * ``` - * targetGroups: - * - name: appA-nlb-tg-1 - * port: 80 - * protocol: TCP - * type: instance - * healthCheck: - * enabled: true - * port: 80 - * protocol: TCP - * - name: appA-alb-tg-1 - * port: 80 - * protocol: HTTP - * type: instance - * healthCheck: - * enabled: true - * port: 80 - * protocol: HTTP - * ``` - */ -export interface ITargetGroupItem { - /** - * The name of the target group. This value is used in {@link ApplicationLoadBalancerListenerConfig| Application Load Balancer listeners}, {@link NetworkLoadBalancerListenerConfig| Network Load Balancer listeners}, and {@link AutoScalingConfig| Autoscaling config}. - */ - readonly name: t.NonEmptyString; - /** - * The port on which the targets receive traffic. - */ - readonly port: number; - /** - * Target group protocol version. Should be one of HTTP, HTTPS, GENEVE, TCP, UDP, TCP_UDP or TLS - * The protocol to use for routing traffic to the targets. - * For Application Load Balancers, the supported protocols are HTTP and HTTPS. - * For Network Load Balancers, the supported protocols are TCP, TLS, UDP, or TCP_UDP. A TCP_UDP listener must be associated with a TCP_UDP target group. - * For Gateway Load Balancers, the supported protocol is GENEVE. - * @see {@link CustomizationsConfigTypes.targetGroupProtocolType} - */ - readonly protocol: TargetGroupProtocolType; - /** - * The protocol version. Should be one of 'GRPC', 'HTTP1', 'HTTP2'. Specify GRPC to send requests to targets using gRPC. Specify HTTP2 to send requests to targets using HTTP/2. The default is HTTP1, which sends requests to targets using HTTP/1.1. - * @see {@link CustomizationsConfigTypes.targetGroupProtocolVersionType} - */ - readonly protocolVersion?: TargetGroupProtocolVersionType; - /** - * The type of target that you must specify when registering targets with this target group. You can't specify targets for a target group using more than one target type. - * - `instance` - Register targets by instance ID. This is the default value. - * - `ip` - Register targets by IP address. You can specify IP addresses from the subnets of the virtual private cloud (VPC) for the target group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). You can't specify publicly routable IP addresses. - * `alb` - Register a single Application Load Balancer as a target. - * - * @see {@link CustomizationsConfigTypes.targetGroupType} - */ - readonly type: TargetGroupType; - /** - * Target Group Attributes. - * @see {@link CustomizationsConfigTypes.targetGroupAttributes} - */ - readonly attributes?: ITargetGroupAttributeTypes; - /** - * Target Group HealthCheck. - * @see {@link CustomizationsConfigTypes.targetGroupHealthCheckType} - */ - readonly healthCheck?: ITargetGroupHealthCheckType; - /** - * Target group targets. These targets should be the friendly names assigned to firewall instances. - * - * @remarks - * This property should only be defined if also defining EC2-based firewall instances. - * It should be left undefined for application configurations. - */ - readonly targets?: (t.NonEmptyString | INlbTargetType)[]; - /** - * Target Group Threshold. - * @see {@link CustomizationsConfigTypes.targetGroupThresholdType} - */ - readonly threshold?: ITargetGroupThresholdType; - /** - * The HTTP or gRPC codes to use when checking for a successful response from a target. For target groups with a protocol of TCP, TCP_UDP, UDP or TLS the range is 200-599. For target groups with a protocol of HTTP or HTTPS, the range is 200-499. - * @see {@link CustomizationsConfigTypes.targetGroupMatcherType} - */ - readonly matcher?: ITargetGroupMatcherType; - /** - * The accounts/OUs location where the Target Group will be deployed to. - */ - readonly shareTargets?: t.IShareTargets; -} - -export type NlbProtocolEnum = 'TCP' | 'UDP' | 'TLS' | 'TCP_UDP'; -export type AlpnPolicyEnum = 'HTTP1Only' | 'HTTP2Only' | 'HTTP2Optional' | 'HTTP2Preferred' | 'None'; -export type SslPolicyNlbEnum = - | 'ELBSecurityPolicy-TLS-1-0-2015-04' - | 'ELBSecurityPolicy-TLS-1-1-2017-01' - | 'ELBSecurityPolicy-TLS-1-2-2017-01' - | 'ELBSecurityPolicy-TLS-1-2-Ext-2018-06' - | 'ELBSecurityPolicy-FS-2018-06' - | 'ELBSecurityPolicy-FS-1-1-2019-08' - | 'ELBSecurityPolicy-FS-1-2-2019-08' - | 'ELBSecurityPolicy-FS-1-2-Res-2019-08' - | 'ELBSecurityPolicy-2015-05' - | 'ELBSecurityPolicy-FS-1-2-Res-2020-10' - | 'ELBSecurityPolicy-TLS13-1-2-2021-06' - | 'ELBSecurityPolicy-TLS13-1-2-Res-2021-06' - | 'ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06' - | 'ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06' - | 'ELBSecurityPolicy-TLS13-1-1-2021-06' - | 'ELBSecurityPolicy-TLS13-1-0-2021-06' - | 'ELBSecurityPolicy-TLS13-1-3-2021-06' - | 'ELBSecurityPolicy-2016-08'; - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link NetworkLoadBalancerConfig} / {@link NetworkLoadBalancerListenerConfig}* - * - * @description - * Application Load Balancer listener config. Currently only action type of `forward`, `redirect` and `fixed-response` is allowed. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateListener.html} - * - * @example - * ``` - * - name: appA-listener-1 - * port: 80 - * protocol: TCP - * targetGroup: appA-nlb-tg-1 - * ``` - */ -export interface INlbListenerConfig { - /** - * Name for Listener. - */ - readonly name: t.NonEmptyString; - /** - * ACM ARN of the certificate to be associated with the listener. - */ - readonly certificate?: t.NonEmptyString; - /** - * Port where the traffic is directed to. - */ - readonly port?: number; - /** - * Protocol used for the traffic. The supported protocols are TCP, TLS, UDP, or TCP_UDP. - * @see {@link CustomizationsConfigTypes.nlbProtocolEnum} - */ - readonly protocol?: NlbProtocolEnum; - /** - * {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/network/create-tls-listener.html#alpn-policies | Application-Layer Protocol Negotiation (ALPN) policy} for TLS encrypted traffic - * - * @description - * Application-Layer Protocol Negotiation (ALPN) policy} for TLS encrypted traffic - * - * @see {@link CustomizationsConfigTypes.alpnPolicyEnum} - */ - readonly alpnPolicy?: AlpnPolicyEnum; - /** - * {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/network/create-tls-listener.html#describe-ssl-policies|SSL policy} for TLS encrypted traffic - * - * @description - * SSL policy for TLS encrypted traffic - * - * @see {@link CustomizationsConfigTypes.sslPolicyNlbEnum} - */ - readonly sslPolicy?: SslPolicyNlbEnum; - /** - * Target Group to direct the traffic to. - */ - readonly targetGroup: t.NonEmptyString; -} - -export type LoadBalancerSchemeEnum = 'internet-facing' | 'internal'; - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link NetworkLoadBalancerConfig}* - * - * @description - * Network Load Balancer configuration. - * - * @example - * ``` - * networkLoadBalancer: - * name: appA-nlb-01 - * scheme: internet-facing - * deletionProtection: false - * subnets: - * - Public-Subnet-A - * - Public-Subnet-B - * listeners: - * - name: appA-listener-1 - * port: 80 - * protocol: TCP - * targetGroup: appA-nlb-tg-1 - * ``` - */ -export interface INetworkLoadBalancerConfig { - /** - * Load Balancer scheme. If undefined, the default of {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateLoadBalancer.html | ELBv2 CreateLoadBalancer API} is used. - * @see {@link CustomizationsConfigTypes.loadBalancerSchemeEnum} - */ - readonly scheme?: LoadBalancerSchemeEnum; - /** - * Deletion protection for Network Load Balancer. - */ - readonly deletionProtection?: boolean; - /** - * Subnets to launch the Network Load Balancer in. - */ - readonly subnets: t.NonEmptyString[]; - /** - * Name for Network Load Balancer. - */ - readonly name: t.NonEmptyString; - /** - * Cross Zone load balancing for Network Load Balancer. - */ - readonly crossZoneLoadBalancing?: boolean; - /** - * Listeners for Network Load Balancer. - * @see {@link NetworkLoadBalancerListenerConfig} - */ - readonly listeners?: INlbListenerConfig[]; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link LaunchTemplateConfig} / {@link NetworkInterfaceItemConfig}/ {@link PrivateIpAddressConfig}* - * - * @description - * Configure a secondary private IPv4 address for a network interface. - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_PrivateIpAddressSpecification.html} - * - * @example - * ``` - * - primary: true - * privateIpAddress: 10.10.10.10 - * - primary: false - * privateIpAddress: 10.10.10.11 - * ``` - */ -export interface IPrivateIpAddressItem { - /** - * Indicates whether the private IPv4 address is the primary private IPv4 address. Only one IPv4 address can be designated as primary. - */ - readonly primary?: boolean; - /** - * The private IPv4 address. - */ - readonly privateIpAddress?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link LaunchTemplateConfig} / {@link BlockDeviceMappingItem}/ {@link EbsItemConfig}* - * - * @description - * The parameters for a block device for an EBS volume. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_LaunchTemplateEbsBlockDeviceRequest.html} - * - * @example - * ``` - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * kmsKeyId: key1 - * ``` - */ -export interface IEbsItem { - /** - * Indicates whether the EBS volume is deleted on instance termination. - */ - readonly deleteOnTermination?: boolean; - /** - * Indicates whether the EBS volume is encrypted. Encrypted volumes can only be attached to instances that support Amazon EBS encryption. If you are creating a volume from a snapshot, you can't specify an encryption value. - * If encrypted is `true` and kmsKeyId is not provided, then accelerator checks for {@link EbsDefaultVolumeEncryptionConfig | default ebs encryption} in the config. - */ - readonly encrypted?: boolean; - /** - * The number of I/O operations per second (IOPS). For gp3, io1, and io2 volumes, this represents the number of IOPS that are provisioned for the volume. For gp2 volumes, this represents the baseline performance of the volume and the rate at which the volume accumulates I/O credits for bursting. - * This parameter is supported for io1, io2, and gp3 volumes only. This parameter is not supported for gp2, st1, sc1, or standard volumes. - */ - readonly iops?: number; - /** - * The ARN of the symmetric AWS Key Management Service (AWS KMS) CMK used for encryption. - */ - readonly kmsKeyId?: t.NonEmptyString; - /** - * The ID of the snapshot. - */ - readonly snapshotId?: t.NonEmptyString; - /** - * The throughput to provision for a gp3 volume, with a maximum of 1,000 MiB/s. - * Valid Range: Minimum value of 125. Maximum value of 1000. - */ - readonly throughput?: number; - /** - * The size of the volume, in GiBs. You must specify either a snapshot ID or a volume size. The following are the supported volumes sizes for each volume type: - * - gp2 and gp3: 1-16,384 - * - io1 and io2: 4-16,384 - * - st1 and sc1: 125-16,384 - * - standard: 1-1,024 - */ - readonly volumeSize?: number; - /** - * The volume type. - * Valid Values: `standard | io1 | io2 | gp2 | sc1 | st1 | gp3` - */ - readonly volumeType?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link LaunchTemplateConfig} / {@link BlockDeviceMappingItem}* - * - * @description - * The parameters for a block device mapping in launch template. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_LaunchTemplateBlockDeviceMappingRequest.html} - * - * @example - * ``` - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * kmsKeyId: key1 - * - deviceName: /dev/xvdb - * ebs: - * deleteOnTermination: true - * encrypted: true - * - deviceName: /dev/xvdc - * ebs: - * deleteOnTermination: true - * ``` - */ -export interface IBlockDeviceMappingItem { - /** - * The device name (for example, /dev/sdh or xvdh). - */ - readonly deviceName: t.NonEmptyString; - /** - * Parameters used to automatically set up EBS volumes when the instance is launched. - */ - readonly ebs?: IEbsItem; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link LaunchTemplateConfig} / {@link NetworkInterfaceItemConfig}* - * - * @description - * The parameters for a network interface. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_LaunchTemplateInstanceNetworkInterfaceSpecificationRequest.html} - * - * @example - * ``` - * networkInterfaces: - * - deleteOnTermination: true - * description: secondary network interface - * deviceIndex: 1 - * groups: - * # security group is from network-config.yaml under the same vpc - * - SharedServices-Main-sg - * # subnet is from network-config.yaml under the same vpc - * subnetId: SharedServices-App-A - * ``` - */ -export interface INetworkInterfaceItem { - /** - * Associates a Carrier IP address with eth0 for a new network interface. - * Use this option when you launch an instance in a Wavelength Zone and want to associate a Carrier IP address with the network interface. - */ - readonly associateCarrierIpAddress?: boolean; - /** - * Associate an elastic IP with the interface - * - * @remarks - * This property only applies to EC2-based firewall instances. - */ - readonly associateElasticIp?: boolean; - /** - * Associates a public IPv4 address with eth0 for a new network interface. - */ - readonly associatePublicIpAddress?: boolean; - /** - * Indicates whether the network interface is deleted when the instance is terminated. - */ - readonly deleteOnTermination?: boolean; - /** - * A description for the network interface. - */ - readonly description?: t.NonEmptyString; - /** - * The device index for the network interface attachment. - */ - readonly deviceIndex?: number; - /** - * Security group names to associate with this network interface. - * @see {@link SecurityGroupConfig} - */ - readonly groups?: t.NonEmptyString[]; - /** - * The type of network interface. To create an Elastic Fabric Adapter (EFA), specify efa. If you are not creating an EFA, specify interface or omit this parameter. - * Valid values: `interface | efa` - */ - readonly interfaceType?: t.NonEmptyString; - /** - * The index of the network card. Some instance types support multiple network cards. The primary network interface must be assigned to network card index 0. The default is network card index 0. - */ - readonly networkCardIndex?: number; - /** - * The ID of the network interface. - */ - readonly networkInterfaceId?: t.NonEmptyString; - /** - * The primary private IPv4 address of the network interface. - */ - readonly privateIpAddress?: t.NonEmptyString; - /** - * One or more private IPv4 addresses. - */ - readonly privateIpAddresses?: IPrivateIpAddressItem[]; - /** - * The number of secondary private IPv4 addresses to assign to a network interface. - */ - readonly secondaryPrivateIpAddressCount?: number; - /** - * If the value is true , source/destination checks are enabled; otherwise, they are disabled. The default value is true. - * You must disable source/destination checks if the instance runs services such as network address translation, routing, or firewalls. - * - * @remarks - * This property only applies to EC2-based firewall instances. - */ - readonly sourceDestCheck?: boolean; - /** - * Valid subnet name from network-config.yaml under the same vpc - */ - readonly subnetId?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallConfig} / {@link LaunchTemplateConfig} / {@link NetworkInterfaceItemConfig}* - * - * @description - * Configure a launch template for the application. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RequestLaunchTemplateData.html} - * - * @example - * ``` - * launchTemplate: - * name: appA-lt - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * # this kms key is in security-config.yaml under keyManagementService - * kmsKeyId: key1 - * securityGroups: - * # security group is from network-config.yaml under the same vpc - * - SharedServices-Main-Rsyslog-sg - * # Key pair should exist in that account and region - * keyName: keyName - * # this instance profile is in iam-config.yaml under roleSets - * iamInstanceProfile: EC2-Default-SSM-AD-Role - * # Local or public SSM parameter store lookup for Image ID - * imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - * instanceType: t3.xlarge - * # IMDSv2 is enabled by default. Disable it by setting this to false. - * enforceImdsv2: true - * networkInterfaces: - * - deleteOnTermination: true - * description: secondary network interface - * deviceIndex: 1 - * groups: - * # security group is from network-config.yaml under the same vpc - * - SharedServices-Main-Rsyslog-sg - * networkCardIndex: 1 - * # subnet is from network-config.yaml under the same vpc - * subnetId: SharedServices-App-A - * # this path is relative to the config repository and the content should be in regular text. - * # Its encoded in base64 before passing in to launch Template - * userData: appConfigs/appA/launchTemplate/userData.sh - * ``` - */ -export interface ILaunchTemplateConfig { - /* - * Name of Launch Template - */ - readonly name: t.NonEmptyString; - /* - * The block device mapping. - */ - readonly blockDeviceMappings?: IBlockDeviceMappingItem[]; - /** - * One or more security group names. These should be created under the VPC in network-config.yaml - */ - readonly securityGroups?: t.NonEmptyString[]; - /** - * The name of the key pair. LZA does not create keypair. This should exist in the account/region or else deployment will fail. - */ - readonly keyPair?: t.NonEmptyString; - /** - * Name of the instance profile created by accelerator in iam-config.yaml under roleSets - */ - readonly iamInstanceProfile?: t.NonEmptyString; - /** - * Valid AMI ID or a reference to ssm parameter store to get AMI ID. - * If ssm parameter is referenced it should follow the pattern - * ${ACCEL_LOOKUP::ImageId:/path/to/ssm/parameter/for/ami} - * - * For example to get the latest x86_64 amazon linux 2 ami, the value would be `${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}` - */ - readonly imageId: t.NonEmptyString; - /** - * Valid instance type which can be launched in the target account and region. - */ - readonly instanceType: t.NonEmptyString; - /** - * By default, {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html | IMDSv2} is enabled. Disable it by setting this to false. - */ - readonly enforceImdsv2?: boolean; - /** - * One or more network interfaces. If you specify a network interface, you must specify any security groups and subnets as part of the network interface. - */ - readonly networkInterfaces?: INetworkInterfaceItem[]; - /** - * Path to user data. - * The path is relative to the config repository and the content should be in regular text. - * It is encoded in base64 before passing in to Launch Template - * - * @remarks - * If defining user data for an EC2 firewall instance or AutoScaling group, you may use the variable - * `${ACCEL_LOOKUP::S3:BUCKET:firewall-config}` in order to dynamically resolve the name of the S3 bucket - * where S3 firewall configurations are stored by the accelerator. This bucket is used when the `configFile`, `configDir` or - * `licenseFile` properties are defined for a firewall. - * - * @see {@link Ec2FirewallAutoScalingGroupConfig} | {@link Ec2FirewallInstanceConfig} - */ - readonly userData?: t.NonEmptyString; -} - -export type AutoScalingHealthCheckTypeEnum = 'EC2' | 'ELB'; - -/** - * - * *{@link CustomizationsConfig} / {@link AppConfigItem} | {@link Ec2FirewallAutoScalingGroupConfig} / {@link AutoScalingConfig}* - * - * @description - * Autoscaling group configuration for the application. - * - * @see {@link https://docs.aws.amazon.com/autoscaling/ec2/APIReference/API_CreateAutoScalingGroup.html} - * - * @example - * ``` - * autoscaling: - * name: appA-asg-1 - * maxSize: 4 - * minSize: 1 - * desiredSize: 2 - * launchTemplate: appA-lt - * healthCheckGracePeriod: 300 - * healthCheckType: ELB - * targetGroups: - * - appA-nlb-tg-1 - * - appA-alb-tg-1 - * maxInstanceLifetime: 86400 - * ``` - */ -export interface IAutoScalingConfig { - /** - * The name of the Auto Scaling group. This name must be unique per Region per account. - * The name can contain any ASCII character 33 to 126 including most punctuation characters, digits, and upper and lowercased letters. - * *Note* You cannot use a colon (:) in the name. - */ - readonly name: t.NonEmptyString; - /** - * The minimum size of the group. - */ - readonly minSize: number; - /** - * The maximum size of the group. - */ - readonly maxSize: number; - /** - * The desired capacity is the initial capacity of the Auto Scaling group at the time of its creation and the capacity it attempts to maintain. It can scale beyond this capacity if you configure auto scaling. This number must be greater than or equal to the minimum size of the group and less than or equal to the maximum size of the group. - */ - readonly desiredSize: number; - /** - * Information used to specify the launch template and version to use to launch instances. - */ - readonly launchTemplate: t.NonEmptyString; - /** - * The amount of time, in seconds, that Amazon EC2 Auto Scaling waits before checking the health status of an EC2 instance that has come into service and marking it unhealthy due to a failed Elastic Load Balancing or custom health check. This is useful if your instances do not immediately pass these health checks after they enter the `InService` state. - * Defaults to 0 if unspecified. - */ - readonly healthCheckGracePeriod?: number; - /** - * The service to use for the health checks. The valid values are EC2 (default) and ELB. If you configure an Auto Scaling group to use load balancer (ELB) health checks, it considers the instance unhealthy if it fails either the EC2 status checks or the load balancer health checks. - */ - readonly healthCheckType?: AutoScalingHealthCheckTypeEnum; - /** - * Target group name array to associate with the Auto Scaling group. These names are from the {@link TargetGroupItemConfig|target group} set in the application. - * Instances are registered as targets with the target groups. The target groups receive incoming traffic and route requests to one or more registered targets. - */ - readonly targetGroups?: t.NonEmptyString[]; - /** - * List of subnet names for a virtual private cloud (VPC) where instances in the Auto Scaling group can be created. - * These subnets should be created under the VPC in network-config.yaml. - */ - readonly subnets: t.NonEmptyString[]; - /** - * The maximum instance lifetime specifies the maximum amount of time (in seconds) that an instance can be in service before it is terminated and replaced. A common use case might be a requirement to replace your instances on a schedule because of internal security policies or external compliance controls. - * You must specify a value of at least 86,400 seconds (one day). To clear a previously set value, specify a new value of 0. This setting applies to all current and future instances in your Auto Scaling group - */ - readonly maxInstanceLifetime?: number; -} - -export type AlbRoutingHttpConfigMitigationModeEnum = 'monitor' | 'defensive' | 'strictest'; - -export interface IAlbRoutingHttpConfig { - readonly desyncMitigationMode?: AlbRoutingHttpConfigMitigationModeEnum; - readonly dropInvalidHeader?: boolean; - readonly xAmznTlsCipherEnable?: boolean; - readonly xffClientPort?: boolean; -} - -export type AlbListenerProtocolEnum = 'HTTP' | 'HTTPS'; -export type AlbListenerTypeEnum = 'fixed-response' | 'forward' | 'redirect'; -export type SslPolicyAlbEnum = - | 'ELBSecurityPolicy-TLS-1-0-2015-04' - | 'ELBSecurityPolicy-TLS-1-1-2017-01' - | 'ELBSecurityPolicy-TLS-1-2-2017-01' - | 'ELBSecurityPolicy-TLS-1-2-Ext-2018-06' - | 'ELBSecurityPolicy-FS-2018-06' - | 'ELBSecurityPolicy-FS-1-1-2019-08' - | 'ELBSecurityPolicy-FS-1-2-2019-08' - | 'ELBSecurityPolicy-FS-1-2-Res-2019-08' - | 'ELBSecurityPolicy-2015-05' - | 'ELBSecurityPolicy-FS-1-2-Res-2020-10' - | 'ELBSecurityPolicy-2016-08'; - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig} / {@link ApplicationLoadBalancerListenerConfig} / {@link AlbListenerFixedResponseConfig}* - * - * @description - * Application load balancer listener fixed response config - * It returns a custom HTTP response. - * Applicable only when `type` under {@link ApplicationLoadBalancerListenerConfig | listener} is `fixed-response`. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_FixedResponseActionConfig.html} - * - * @example - * ``` - * fixedResponseConfig: - * statusCode: '200' - * contentType: text/plain - * messageBody: 'Hello World' - * ``` - */ -export interface IAlbListenerFixedResponseConfig { - /** - * The content type. - * Valid Values: text/plain | text/css | text/html | application/javascript | application/json - */ - readonly statusCode: t.NonEmptyString; - /** - * The message to send back. - */ - readonly contentType?: t.NonEmptyString; - /** - * The HTTP response code (2XX, 4XX, or 5XX). - */ - readonly messageBody?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig} / {@link ApplicationLoadBalancerListenerConfig} / {@link AlbListenerForwardConfig}/ {@link AlbListenerForwardConfigTargetGroupStickinessConfig}* - * - * @description - * Application Load balancer listener forward config target group stickiness config - * Applicable only when `type` under {@link ApplicationLoadBalancerListenerConfig | listener} is `forward`. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_TargetGroupStickinessConfig.html} - * - * @example - * ``` - * durationSeconds: 123 - * enabled: true - * ``` - */ -export interface IAlbListenerTargetGroupStickinessConfig { - /** - * The time period, in seconds, during which requests from a client should be routed to the same target group. The range is 1-604800 seconds (7 days). - */ - readonly durationSeconds?: number; - /** - * Indicates whether target group stickiness is enabled. - */ - readonly enabled?: boolean; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig} / {@link ApplicationLoadBalancerListenerConfig} / {@link AlbListenerForwardConfig} - * - * @description - * Application Load balancer listener forward config. Used to define forward action. - * Applicable only when `type` under {@link ApplicationLoadBalancerListenerConfig | listener} is `forward`. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_ForwardActionConfig.html} - * - * @example - * ``` - * forwardConfig: - * targetGroupStickinessConfig: - * durationSeconds: 123 - * enabled: true - *``` - */ -export interface IAlbListenerForwardConfig { - readonly targetGroupStickinessConfig?: IAlbListenerTargetGroupStickinessConfig; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig} / {@link ApplicationLoadBalancerListenerConfig} / {@link AlbListenerRedirectConfig}* - * - * @description - * Application Load balancer listener redirect config. Used to define redirect action. - * Applicable only when `type` under {@link ApplicationLoadBalancerListenerConfig | listener} is `redirect`. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_RedirectActionConfig.html} - * - * @example - * ``` - * redirectConfig: - * statusCode: HTTP_301 - * host: '#{host}' - * path: '/#{path}' - * port: 443 - * protocol: HTTPS - * query: '#{query}' - *``` - */ -export interface IAlbListenerRedirectConfig { - readonly statusCode?: t.NonEmptyString; - readonly host?: t.NonEmptyString; - readonly path?: t.NonEmptyString; - readonly port?: number; - readonly protocol?: t.NonEmptyString; - readonly query?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig} / {@link ApplicationLoadBalancerListenerConfig}* - * - * @description - * Application Load Balancer listener config. Currently only action type of `forward`, `redirect` and `fixed-response` is allowed. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateListener.html} - * - * @example - * ``` - * - name: appA-listener-0 - * port: 80 - * protocol: HTTP - * targetGroup: appA-alb-tg-0 - * order: 1 - * type: forward - * forwardConfig: - * targetGroupStickinessConfig: - * durationSeconds: 1000 - * enabled: true - * - name: appA-listener-1 - * port: 80 - * protocol: HTTP - * targetGroup: appA-alb-tg-1 - * order: 4 - * type: fixed-response - * fixedResponseConfig: - * statusCode: '200' - * contentType: text/plain - * messageBody: 'Hello World' - * - name: appA-listener-2 - * port: 80 - * protocol: HTTP - * targetGroup: appA-alb-tg-2 - * order: 2 - * type: redirect - * redirectConfig: - * statusCode: HTTP_301 - * host: '#{host}' - * path: '/#{path}' - * port: 443 - * protocol: HTTPS - * query: '#{query}' - * - name: appA-listener-3 - * port: 443 - * protocol: HTTPS - * targetGroup: appA-alb-tg-3 - * order: 3 - * type: forward - * certificate: 'arn:aws:acm:some-valid-region:111111111111:certificate/valid-certificate-hash' - * sslPolicy: ELBSecurityPolicy-2016-08 - * ``` - */ -export interface IAlbListenerConfig { - /** - * The name of the application load balancer listener - */ - readonly name: t.NonEmptyString; - /** - * Port of the application load balancer listener - */ - readonly port: number; - /** - * Protocol of the application load balancer listener. The supported protocols are HTTP and HTTPS - */ - readonly protocol: AlbListenerProtocolEnum; - /** - * Type of the application load balancer listener - */ - readonly type: AlbListenerTypeEnum; - /** - * Applies to HTTPS listeners. The default certificate for the listener. You must provide exactly one certificate arn or a certificate name which was created by LZA - */ - readonly certificate?: t.NonEmptyString; - /** - * The security policy that defines which protocols and ciphers are supported. - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies | Application Load Balancer Listener SSL Policies} - */ - readonly sslPolicy?: SslPolicyAlbEnum; - /** - * Target Group name to which traffic will be forwarded to. This name should be same as {@link ApplicationLoadBalancerTargetGroupConfig | targetGroup} name. - */ - readonly targetGroup: t.NonEmptyString; - /** - * Information for creating an action that returns a custom HTTP response. Specify only when type is `fixed-response`. - */ - readonly fixedResponseConfig?: IAlbListenerFixedResponseConfig; - /** - * Information for creating an action that distributes requests to targetGroup. Stickiness for targetGroup can be set here. - */ - readonly forwardConfig?: IAlbListenerForwardConfig; - /** - * The order for the action. This value is required for rules with multiple actions. The action with the lowest value for order is performed first - */ - readonly order?: number; - /** - * Information for creating a redirect action. Specify only when type is `redirect`. - */ - readonly redirectConfig?: IAlbListenerRedirectConfig; -} - -export type RoutingHttpXffHeaderProcessingModeEnum = 'append' | 'preserve' | 'remove'; - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig} / {@link ApplicationLoadBalancerAttributesConfig}* - * - * @description - * Application Load Balancer attributes config. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_LoadBalancerAttribute.html} - * - * @example - * ``` - * attributes: - * deletionProtection: true - * idleTimeout: 60 - * routingHttpDropInvalidHeader: true - * routingHttpXAmznTlsCipherEnable: true - * routingHttpXffClientPort: true - * routingHttpXffHeaderProcessingMode: 'append' - * http2Enabled: true - * wafFailOpen: true - * ``` - */ -export interface IAlbAttributesConfig { - /** - * Enable or disable deletion protection. - */ - readonly deletionProtection?: boolean; - /** - * The idle timeout value, in seconds. The valid range is 1-4000 seconds. The default is 60 seconds. - */ - readonly idleTimeout?: number; - /** - * Determines how the load balancer handles requests that might pose a security risk to your application. The possible values are `monitor` , `defensive` , and `strictest` . The default is `defensive`. - */ - readonly routingHttpDesyncMitigationMode?: AlbRoutingHttpConfigMitigationModeEnum; - /** - * Indicates whether HTTP headers with invalid header fields are removed by the load balancer ( true ) or routed to targets ( false ). The default is false. - */ - readonly routingHttpDropInvalidHeader?: boolean; - /** - * Indicates whether the two headers ( x-amzn-tls-version and x-amzn-tls-cipher-suite ), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. The x-amzn-tls-version header has information about the TLS protocol version negotiated with the client, and the x-amzn-tls-cipher-suite header has information about the cipher suite negotiated with the client. Both headers are in OpenSSL format. The possible values for the attribute are true and false . The default is false. - */ - readonly routingHttpXAmznTlsCipherEnable?: boolean; - /** - * Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer. The possible values are true and false . The default is false. - */ - readonly routingHttpXffClientPort?: boolean; - /** - * Enables you to modify, preserve, or remove the X-Forwarded-For header in the HTTP request before the Application Load Balancer sends the request to the target. The possible values are append, preserve, and remove. The default is append. - */ - readonly routingHttpXffHeaderProcessingMode?: RoutingHttpXffHeaderProcessingModeEnum; - /** - * Indicates whether HTTP/2 is enabled. The possible values are true and false. The default is true. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. - */ - readonly http2Enabled?: boolean; - /** - * Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. The possible values are true and false. The default is false. - */ - readonly wafFailOpen?: boolean; -} - -export type AlbSchemeEnum = 'internet-facing' | 'internal'; - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem} / {@link ApplicationLoadBalancerConfig}* - * - * @description - * Used to define Application Load Balancer configurations for the accelerator. - * - * @see {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateLoadBalancer.html} - * - * @example - * ``` - * applicationLoadBalancer: - * name: appA-alb-01 - * scheme: internet-facing - * subnets: - * - Public-Subnet-A - * - Public-Subnet-B - * securityGroups: - * - demo-app-sg - * listeners: - * - name: appA-listener-2 - * port: 80 - * protocol: HTTP - * targetGroup: appA-alb-tg-1 - * type: forward - * ``` - */ -export interface IApplicationLoadBalancerConfig { - /** - * The name of the application load balancer - */ - readonly name: t.NonEmptyString; - /** - * Subnets to launch the Application Load Balancer in. - */ - readonly subnets: t.NonEmptyString[]; - /** - * Security Groups to attach to the Application Load Balancer. - */ - readonly securityGroups: t.NonEmptyString[]; - /** - * Internal or internet facing scheme for Application Load Balancer. - */ - readonly scheme?: AlbSchemeEnum; - /** - * Attributes for Application Load Balancer. - */ - readonly attributes?: IAlbAttributesConfig; - /** - * Listeners for Application Load Balancer. - */ - readonly listeners?: IAlbListenerConfig[]; - /** - * The location where the Application Load Balancer(s) will be deployed to. - * * @remarks - * The accounts/OUs provided should contain the subnets specified that are distributed by Resource Access Manager by the `shareTargets` property - * for the respective subnets. - */ - readonly shareTargets?: t.IShareTargets; -} - -/** - * *{@link CustomizationsConfig} / {@link AppConfigItem}* - * - * @description - * Application configuration. - * Used to define two tier application configurations for the accelerator. - * - * @example - * ``` - * applications: - * - name: appA - * vpc: test1 - * deploymentTargets: - * accounts: - * - Management - * excludedRegions: - * - us-east-1 - * - us-west-2 - * autoscaling: - * name: appA-asg-1 - * maxSize: 4 - * minSize: 1 - * desiredSize: 2 - * launchTemplate: appA-lt - * healthCheckGracePeriod: 300 - * healthCheckType: ELB - * targetGroups: - * - appA-nlb-tg-1 - * - appA-alb-tg-1 - * subnets: - * - Private-Subnet-A - * - Private-Subnet-B - * maxInstanceLifetime: 86400 - * targetGroups: - * - name: appA-nlb-tg-1 - * port: 80 - * protocol: TCP - * type: instance - * connectionTermination: true - * preserveClientIp: true - * proxyProtocolV2: true - * healthCheck: - * enabled: true - * port: 80 - * protocol: TCP - * - name: appA-alb-tg-1 - * port: 80 - * protocol: HTTP - * type: instance - * connectionTermination: true - * preserveClientIp: true - * proxyProtocolV2: true - * healthCheck: - * enabled: true - * port: 80 - * protocol: HTTP - * networkLoadBalancer: - * name: appA-nlb-01 - * scheme: internet-facing - * deletionProtection: false - * subnets: - * - Public-Subnet-A - * - Public-Subnet-B - * listeners: - * - name: appA-listener-1 - * port: 80 - * protocol: TCP - * targetGroup: appA-nlb-tg-1 - * applicationLoadBalancer: - * name: appA-alb-01 - * scheme: internet-facing - * subnets: - * - Public-Subnet-A - * - Public-Subnet-B - * securityGroups: - * - demo-app-sg - * listeners: - * - name: appA-listener-2 - * port: 80 - * protocol: HTTP - * targetGroup: appA-alb-tg-1 - * type: forward - * launchTemplate: - * name: appA-lt - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * kmsKeyId: key1 - * - deviceName: /dev/xvdb - * ebs: - * deleteOnTermination: true - * encrypted: true - * - deviceName: /dev/xvdc - * ebs: - * deleteOnTermination: true - * securityGroups: - * - demo-app-sg - * iamInstanceProfile: EC2-Default-SSM-AD-Role - * imageId: ${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2} - * instanceType: t3.large - * userData: appConfigs/appA/launchTemplate/userData.sh - * ``` - */ -export interface IAppConfigItem { - /** - * The name of the application. This should be unique per application. - */ - readonly name: t.NonEmptyString; - /** - * VPC where the application will be deployed. The value should be a reference to the vpc in the network config under `vpcs:`. - */ - readonly vpc: t.NonEmptyString; - /** - * The location where the application will be deployed. - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * - * Target groups for the application - * - * @see {@link TargetGroupItemConfig} - */ - readonly targetGroups?: ITargetGroupItem[]; - /** - * Network Load Balancer for the application - * - * @see {@link NetworkLoadBalancerConfig} - */ - readonly networkLoadBalancer?: INetworkLoadBalancerConfig; - /** - * - * Launch Template for the application - * - * @see {@link LaunchTemplateConfig} - */ - readonly launchTemplate?: ILaunchTemplateConfig; - /** - * - * AutoScalingGroup for the application - * - * @see {@link AutoScalingConfig} - * - */ - readonly autoscaling?: IAutoScalingConfig; - /** - * - * Application Load Balancer for the application - * - * @see {@link ApplicationLoadBalancerConfig} - * - */ - readonly applicationLoadBalancer?: IApplicationLoadBalancerConfig; -} - -type CapabilityTypeEnum = 'CAPABILITY_IAM' | 'CAPABILITY_NAMED_IAM' | 'CAPABILITY_AUTO_EXPAND'; - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link CloudFormationStackConfig}* - * - * @description - * Defines a custom CloudFormation Stack to be deployed to the environment. - * - * @remarks - * - * Please note that deployed custom CloudFormation Stacks are not deleted if they are removed from customizations-config.yaml. - * All custom stacks deployed by LZA must be deleted manually if they are no longer needed. - * - * @see [Related CDK Issue ](https://github.com/aws/aws-cdk/issues/13676) - * - * @example - * ``` - * customizations: - * cloudFormationStacks: - * - deploymentTargets: - * organizationalUnits: - * - Infrastructure - * description: CloudFormation Stack deployed to accounts in the Infrastructure OU. - * name: InfrastructureStack - * regions: - * - us-east-1 - * runOrder: 2 - * template: cloudformation/InfraStack.yaml - * parameters: - * - name: Parameter1 - * value: Value1 - * - name: Parameter2 - * value: Value2 - * terminationProtection: true - * - deploymentTargets: - * accounts: - * - SharedServices - * description: Stack containing shared services resources. - * name: SharedServicesResources - * regions: - * - us-east-1 - * - us-east-2 - * runOrder: 1 - * template: cloudformation/SharedServicesStack.yaml - * terminationProtection: true - * - * ``` - */ -export interface ICloudFormationStack { - /** - * CloudFormation Stack deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * The description is to used to provide more information about the stack. - */ - readonly description?: t.NonEmptyString; - /** - * The friendly name that will be used as a base for the created CloudFormation Stack Name. - * The name should not contain any spaces as this isn't supported by the Accelerator. - */ - readonly name: t.NonEmptyString; - /** - * A list of AWS regions to deploy the stack to. - */ - readonly regions: t.Region[]; - /** - * The order to deploy the stack relative to the other stacks. Must be a positive integer. - * To deploy stacks in parallel, set runOrder of each stack to 1. - */ - readonly runOrder: number; - /** - * The file path to the template file defining the stack. - */ - readonly template: t.NonEmptyString; - /** - * The parameters to pass to the stack. - */ - readonly parameters?: t.ICfnParameter[]; - /** - * This determines whether to enable termination protection for the stack. - */ - readonly terminationProtection: boolean; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link CloudFormationStackSetConfig}* - * - * @description - * Defines a custom CloudFormation StackSet to be deployed to the environment. - * - * @example - * ``` - * customizations: - * cloudFormationStackSets: - * - capabilities: [CAPABILITY_IAM, CAPABILITY_NAMED_IAM, CAPABILITY_AUTO_EXPAND] - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * description: sample desc4 - * name: OrganizationalUnitStackSet - * regions: - * - us-east-1 - * template: cloudformation/OUStackSet.yaml - * - capabilities: [CAPABILITY_IAM] - * deploymentTargets: - * accounts: - * - SharedServices - * - Management - * description: - * name: AccountStackSet - * regions: - * - us-east-1 - * template: cloudformation/AccountStackSet.yaml - * - * ``` - */ -export interface ICloudFormationStackSet { - /** - * The CloudFormation capabilities enabled to deploy the stackset. - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html} - */ - readonly capabilities?: CapabilityTypeEnum[]; - /** - * CloudFormation StackSet deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * The description is to used to provide more information about the stackset. - */ - readonly description?: t.NonEmptyString; - /** - * The friendly name that will be used as a base for the created CloudFormation StackSet Name. - * The name should not contain any spaces as this isn't supported by the Accelerator. - */ - readonly name: t.NonEmptyString; - /** - * A list of regions to deploy the stackset. - */ - readonly regions: t.Region[]; - /** - * The file path to the template file used for deployment. - */ - readonly template: t.NonEmptyString; - /** - * The parameters to be passed to the stackset. - */ - readonly parameters?: t.ICfnParameter[]; -} - -export type PortfolioAssociationType = 'User' | 'Group' | 'Role' | 'PermissionSet'; - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} / {@link PortfolioAssociationConfig}* - * - * @description - * Portfolio Associations configuration - * - * @example - * ``` - * - type: Group - * name: Administrators - * - type: Role - * name: EC2-Default-SSM-AD-Role - * propagateAssociation: true - * - type: User - * name: breakGlassUser01 - * - type: PermissionSet - * name: AWSPowerUserAccess - * ``` - */ -export interface IPortfolioAssociatoinConfig { - /** - * Indicates the type of portfolio association, valid values are: Group, User, and Role. - */ - readonly type: PortfolioAssociationType; - /** - * Indicates the name of the principal to associate the portfolio with. - */ - readonly name: t.NonEmptyString; - /** - * Indicates whether the principal association should be created in accounts the portfolio is shared with. Verify the IAM principal exists in all accounts the portfolio is shared with before enabling. - * - * @remarks - * When you propagate a principal association, a potential privilege escalation path may occur. For a user in a recipient account who is not a Service Catalog Admin, but still has the ability to create Principals (Users/Roles), that user could create an IAM Principal that matches a principal name association for the portfolio. Although this user may not know which principal names are associated through Service Catalog, they may be able to guess the user. If this potential escalation path is a concern, then LZA recommends disabling propagation. - */ - readonly propagateAssociation?: boolean; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} / {@link ProductConfig} / {@link ProductVersionConfig}* - * - * @description - * Product Versions configuration - * - * @example - * ``` - * - name: v1 - * description: Product version 1 - * template: path/to/template.json - * ``` - */ -export interface IProductVersionConfig { - /** - * Name of the version of the product - */ - readonly name: t.NonEmptyString; - /** - * The product template. - */ - readonly template: t.NonEmptyString; - /** - * The version description - */ - readonly description?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} / {@link ProductConfig} / {@link ProductSupportConfig}* - * - * @description - * Product Support configuration - * - * @example - * ``` - * description: Product support details - * email: support@example.com - * url: support.example.com - * ``` - */ -export interface IProductSupportConfig { - /** - * The email address to report issues with the product - */ - readonly email?: t.NonEmptyString; - /** - * The url to the site where users can find support information or file tickets. - */ - readonly url?: t.NonEmptyString; - /** - * Support description of how users should use email contact and support link. - */ - readonly description?: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} | {@link ProductConfig} / {@link TagOptionsConfig}* - * - * @description - * Service Catalog TagOptions configuration. - * - * @example - * ``` - * - key: Environment - * values: [Dev, Test, Prod] - * ``` - */ -export interface ITagOptionsConfig { - /** - * The tag key - */ - readonly key: t.NonEmptyString; - /** - * An array of values that can be used for the tag key - */ - readonly values: t.NonEmptyString[]; -} - -export type ProductLaunchConstraintType = 'Role' | 'LocalRole'; - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} | {@link ProductConfig} / {@link ProductConstraintConfig} / {@link ProductLaunchConstraintConfig}* - * - * @description - * Service Catalog Product Constraint configuration. For more information see https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints.html - * - * @example - * ``` - * constraints: - * launch: - * type: localRole | Role - * role: string - * tagUpdate: true | false - * notifications: - * - topicName - * ``` - */ -export interface IProductLaunchConstraintConfig { - /** - * The type of launch constraint, either Role or LocalRole. For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-launchroleconstraint.html - */ - readonly type: ProductLaunchConstraintType; - /** - * The name of the IAM Role. - */ - readonly role: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} | {@link ProductConfig} / {@link ProductConstraintConfig}* - * - * @description - * Service Catalog Product Constraint configuration. For more information see https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints.html - * - * @example - * ``` - * constraints: - * launch: - * type: localRole | Role - * role: string - * tagUpdate: true | false - * notifications: - * - topicName - * ``` - */ -export interface IProductConstraintConfig { - /** - * Launch constraint role name and type, supports LocalRole or Role. - */ - readonly launch?: IProductLaunchConstraintConfig; - /** - * Determines if Service Catalog Tag Update constraint is enabled - */ - readonly tagUpdate?: boolean; - /** - * A list of SNS topic names to stream product notifications to - * - * @remarks - * The SNS Topic must exist in the same account and region. SNS Topic names are not validated, please ensure the SNS Topic exists in the account. - */ - readonly notifications?: t.NonEmptyString[]; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig} / {@link ProductConfig}* - * - * @description - * Service Catalog Products configuration - * - * @example - * ``` - * - name: Product01 - * description: Example product - * owner: Product-Owner - * versions: - * - name: v1 - * description: Product version 1 - * template: path/to/template.json - * constraints: - * launch: - * type: localRole | Role - * role: string - * tagUpdate: true | false - * notifications: - * - topicName - * ``` - */ -export interface IProductConfig { - /** - * The name of the product - */ - readonly name: t.NonEmptyString; - /** - * The owner of the product - */ - readonly owner: t.NonEmptyString; - /** - * Product version configuration - */ - readonly versions: IProductVersionConfig[]; - /** - * Product description - */ - readonly description?: t.NonEmptyString; - /** - * The name of the product's publisher. - */ - readonly distributor?: t.NonEmptyString; - /** - * Product support details. - */ - readonly support?: IProductSupportConfig; - /** - * Product TagOptions configuration - */ - readonly tagOptions?: ITagOptionsConfig[]; - /** - * Product Constraint configuration - */ - readonly constraints?: IProductConstraintConfig; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig} / {@link PortfolioConfig}* - * - * @description - * Service Catalog Portfolios configuration - * - * @example - * ``` - * - name: accelerator-portfolio - * provider: landing-zone-accelerator - * account: Management - * regions: - * - us-east-1 - * shareTargets: - * organizationalUnits: - * - Root - * shareTagOptions: true - * portfolioAssociations: - * - type: Group - * name: Administrators - * products: - * - name: Product01 - * description: Example product - * owner: Product-Owner - * constraints: - * launch: - * type: localRole | Role - * role: roleName - * tagUpdate: true | false - * notifications: - * - topicName - * versions: - * - name: v1 - * description: Product version 1 - * template: path/to/template.json - * tagOptions: - * - key: Environment - * values: [Dev, Test, Prod] - * ``` - * - */ -export interface IPortfolioConfig { - /** - * The name of the portfolio - */ - readonly name: t.NonEmptyString; - /** - * The name of the account to deploy the portfolio. - */ - readonly account: t.NonEmptyString; - /** - * The region names to deploy the portfolio. - */ - readonly regions: t.Region[]; - /** - * The provider of the portfolio - */ - readonly provider: t.NonEmptyString; - /** - * Configuration of portfolio associations to give access to IAM principals. - */ - readonly portfolioAssociations?: IPortfolioAssociatoinConfig[]; - /** - * Product Configuration - */ - readonly products?: IProductConfig[]; - /** - * Portfolio share target. Sharing portfolios to Organizational Units is only supported for portfolios in the Management account. - * - * @remarks - * Valid values are the friendly names of organizational unit(s) and/or account(s). - * - */ - readonly shareTargets?: t.IShareTargets; - /** - * Whether or not to share TagOptions with other account(s)/OU(s) - * - * @remarks - * This property is only applicable if the `shareTargets` property is defined - */ - readonly shareTagOptions?: boolean; - /** - * Portfolio TagOptions configuration - */ - readonly tagOptions?: ITagOptionsConfig[]; -} - -export interface IServiceCatalogConfig { - readonly portfolios: IPortfolioConfig[]; -} - -/** - * *{@link CustomizationsConfig} / {@link CustomizationConfig}* - * - * @description - * Defines CloudFormation Stacks and StackSets to be deployed to the environment. - * This feature supports the deployment of customer-provided CloudFormation templates to AWS - * accounts and/or organizational units. These deployments can leverage independent CloudFormation stacks - * or CloudFormation StackSets depending on the customer's deployment preference. - * - */ -export interface ICustomizationConfig { - readonly cloudFormationStacks?: ICloudFormationStack[]; - readonly cloudFormationStackSets?: ICloudFormationStackSet[]; - readonly serviceCatalogPortfolios?: IPortfolioConfig[]; -} - -/** - * *{@link CustomizationsConfig} / {@link Ec2FirewallConfig} / {@link Ec2FirewallInstanceConfig} | {@link Ec2FirewallAutoScalingGroupConfig} / {@link FirewallStaticReplacementsConfig}* - * - * @description - * Firewall Static Replacements Config - * - * @example - * ``` - * - key: CORP_CIDR_RANGE - * value: 10.0.0.0/16 - * ``` - */ -export interface IFirewallStaticReplacementsConfig { - /** - * The key name for the static replacement - */ - readonly key: t.NonEmptyString; - /** - * The value of the static replacement - */ - readonly value: t.NonEmptyString; -} - -/** - * *{@link CustomizationsConfig} / {@link Ec2FirewallConfig} / {@link Ec2FirewallInstanceConfig}* - * - * @description - * EC2 firewall instance configuration. - * Use to define an array of standalone firewall instances - * - * @example - * ``` - * - name: accelerator-firewall - * launchTemplate: - * name: firewall-lt - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * volumeSize: 20 - * enforceImdsv2: true - * iamInstanceProfile: firewall-profile - * imageId: ami-123xyz - * instanceType: c6i.xlarge - * networkInterfaces: - * - deleteOnTermination: true - * description: Primary interface - * deviceIndex: 0 - * groups: - * - firewall-data-sg - * subnetId: firewall-data-subnet-a - * - deleteOnTermination: true - * description: Management interface - * deviceIndex: 1 - * groups: - * - firewall-mgmt-sg - * subnetId: firewall-mgmt-subnet-a - * userData: path/to/userdata.txt - * vpc: Network-Inspection - * tags: [] - * ``` - * - */ -export interface IEc2FirewallInstanceConfig { - /** - * The friendly name of the firewall instance - * - * @remarks - * **CAUTION**: Changing values under this property after initial deployment will cause an instance replacement. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The launch template for the firewall instance - * - * @remarks - * **CAUTION**: Changing values under this property after initial deployment will cause an instance replacement. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly launchTemplate: ILaunchTemplateConfig; - /** - * The friendly name of the VPC to deploy the firewall instance to - * - * @remarks - * This VPC must contain the subnet(s) defined for the network interfaces under the `launchTemplate` property - */ - readonly vpc: t.NonEmptyString; - /** - * (OPTIONAL) The logical name of the account to deploy the firewall instance to - * - * @remarks - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly account?: t.NonEmptyString; - /** - * (OPTIONAL) Specify a relative S3 object path to pull a firewall configuration file from. - * - * For example, if your S3 object path is `s3://path/to/config.txt`, specify `path/to/config.txt` for this property. - * - * **NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, - * update the name of the configuration file. - * - * @remarks - * Setting this property allows you to make use of firewall configuration replacements. This allows you to - * configure your firewall instance dynamically using values determined at CDK runtime. - * - * **NOTE**: The configuration file must be uploaded to the accelerator-created assets bucket in the home region of - * your Management account. This is the `${AcceleratorPrefix}-assets` bucket, not the `cdk-accel-assets` bucket. - * - * The transformed configuration file will be uploaded to `${AcceleratorPrefix}-firewall-config` bucket in the account and region your firewall instance - * is deployed to. This config file can be consumed by third-party firewall vendors that support pulling a configuration file from S3. - * - * Supported replacements: - * * Hostname replacement - look up the name of the firewall instance - * * Format: `${ACCEL_LOOKUP::EC2:INSTANCE:HOSTNAME}` -- translates to the logical name of the instance as configured in customizations-config.yaml. - * * VPC replacements - look up metadata about the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:VPC:_}`, where `` is a type listed below, - * and `` is the index of the VPC CIDR range. - * * Metadata types: - * * CIDR - the VPC CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the VPC CIDR (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the VPC CIDR (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the VPC CIDR (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary VPC CIDR is index `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPC:CIDR_0}` - translates to the primary CIDR range of the VPC - * * Subnet replacements - look up metadata about subnets in the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:SUBNET::}`, where `` is a type listed - * below, and `` is the logical name of the subnet as defined in `network-config.yaml`. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Example usage: `${ACCEL_LOOKUP::EC2:SUBNET:CIDR:firewall-data-subnet-a}` - translates to the CIDR range of a subnet named `firewall-data-subnet-a` - * * Network interface IP replacements - look up public and private IP addresses assigned to firewall network interfaces: - * * Format: `${ACCEL_LOOKUP::EC2:ENI_:_}`, where `` is the device index - * of the network interface as defined in the firewall launch template, `` is either a public or private IP of the interface, - * and `` is the index of the interface IP address. - * * IP types: - * * PRIVATEIP - a private IP associated with the interface - * * PUBLICIP - a public IP associated with the interface - * * Index numbering is zero-based, so the primary interface of the instance is `0` and its primary IP address is also `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:PRIVATEIP_0}` - translates to the primary private IP address of the primary network interface - * * Network interface subnet replacements - look up metadata about the subnet a network interface is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:ENI_:SUBNET_}`, where `` is the device index - * of the network interface as defined in the firewall launch template and `` is a type listed below. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary interface of the instance is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_CIDR}` - translates to the subnet CIDR range of the primary network interface - * * VPN replacements - look up metadata about VPNs that are directly connected to the EC2 firewall instance. NOTE: these replacements are - * only supported for EC2 firewalls that are referenced in a {@link CustomerGatewayConfig} in network-config.yaml. - * * Format: `${ACCEL_LOOKUP::EC2:VPN::}`, where `` is a type listed - * below, and `` is the logical name of the VPN connection as defined in `network-config.yaml`. - * * Metadata types: - * * AWS_BGPASN - the BGP autonomous system number (ASN) of the AWS gateway device - * * CGW_BGPASN - the BGP autonomous system number (ASN) of the customer gateway device - * * CGW_OUTSIDEIP - the outside (public) IP address of the customer gateway device - * * AWS_INSIDEIP_ - the inside (link-local) IP address of the AWS gateway device, where is the index number of the VPN tunnel - * * CGW_INSIDEIP_ - the inside (link-local) IP address of the customer gateway device, where is the index number of the VPN tunnel - * * AWS_OUTSIDEIP_ - the outside (public) IP address of the AWS gateway device, where is the index number of the VPN tunnel - * * INSIDE_CIDR_ - the inside (link-local) CIDR range of the tunnel, where is the index number of the VPN tunnel - * * INSIDE_NETMASK_ - the inside (link-local) subnet mask of the tunnel, where is the index number of the VPN tunnel - * * PSK_ - the pre-shared key of the tunnel, where is the index number of the VPN tunnel - * * Index numbering is zero-based, so the primary VPN tunnel is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPN:AWS_OUTSIDEIP_0:accelerator-vpn}` - translates to the AWS-side public IP of the primary VPN tunnel for a VPN named `accelerator-vpn` - * - * * For replacements that are supported in firewall userdata, see {@link LaunchTemplateConfig.userData}. - */ - readonly configFile?: t.NonEmptyString; - /** - * (OPTIONAL) Specify a relative S3 directory path to pull a firewall configuration directory. - * - * Either configFile or configDir can be set but not both. - * - * For example, if your S3 folder path is `s3://path/to/config`, specify `path/to/config` for this property. - * - * **NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, - * update the name of the configuration directory. - * - * @remarks - * Setting this property allows you to make use of firewall configuration replacements. This allows you to - * configure your firewall instance dynamically using values determined at CDK runtime. - * - * **NOTE**: The configuration directory must be uploaded to the accelerator-created assets bucket in the home region of - * your Management account. This is the `${AcceleratorPrefix}-assets` bucket, not the `cdk-accel-assets` bucket. - * - * The transformed configuration directory will be uploaded to `${AcceleratorPrefix}-firewall-config` bucket in the account and region your firewall instance - * is deployed to. This config directory can be consumed by third-party firewall vendors that support pulling a configuration directory from S3. - * - * Supported replacements: - * * Hostname replacement - look up the name of the firewall instance - * * Format: `${ACCEL_LOOKUP::EC2:INSTANCE:HOSTNAME}` -- translates to the logical name of the instance as configured in customizations-config.yaml. - * * VPC replacements - look up metadata about the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:VPC:_}`, where `` is a type listed below, - * and `` is the index of the VPC CIDR range. - * * Metadata types: - * * CIDR - the VPC CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the VPC CIDR (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the VPC CIDR (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the VPC CIDR (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary VPC CIDR is index `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPC:CIDR_0}` - translates to the primary CIDR range of the VPC - * * Subnet replacements - look up metadata about subnets in the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:SUBNET::}`, where `` is a type listed - * below, and `` is the logical name of the subnet as defined in `network-config.yaml`. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Example usage: `${ACCEL_LOOKUP::EC2:SUBNET:CIDR:firewall-data-subnet-a}` - translates to the CIDR range of a subnet named `firewall-data-subnet-a` - * * Network interface IP replacements - look up public and private IP addresses assigned to firewall network interfaces: - * * Format: `${ACCEL_LOOKUP::EC2:ENI_:_}`, where `` is the device index - * of the network interface as defined in the firewall launch template, `` is either a public or private IP of the interface, - * and `` is the index of the interface IP address. - * * IP types: - * * PRIVATEIP - a private IP associated with the interface - * * PUBLICIP - a public IP associated with the interface - * * Index numbering is zero-based, so the primary interface of the instance is `0` and its primary IP address is also `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:PRIVATEIP_0}` - translates to the primary private IP address of the primary network interface - * * Network interface subnet replacements - look up metadata about the subnet a network interface is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:ENI_:SUBNET_}`, where `` is the device index - * of the network interface as defined in the firewall launch template and `` is a type listed below. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary interface of the instance is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_CIDR}` - translates to the subnet CIDR range of the primary network interface - * * VPN replacements - look up metadata about VPNs that are directly connected to the EC2 firewall instance. NOTE: these replacements are - * only supported for EC2 firewalls that are referenced in a {@link CustomerGatewayConfig} in network-config.yaml. - * * Format: `${ACCEL_LOOKUP::EC2:VPN::}`, where `` is a type listed - * below, and `` is the logical name of the VPN connection as defined in `network-config.yaml`. - * * Metadata types: - * * AWS_BGPASN - the BGP autonomous system number (ASN) of the AWS gateway device - * * CGW_BGPASN - the BGP autonomous system number (ASN) of the customer gateway device - * * CGW_OUTSIDEIP - the outside (public) IP address of the customer gateway device - * * AWS_INSIDEIP_ - the inside (link-local) IP address of the AWS gateway device, where is the index number of the VPN tunnel - * * CGW_INSIDEIP_ - the inside (link-local) IP address of the customer gateway device, where is the index number of the VPN tunnel - * * AWS_OUTSIDEIP_ - the outside (public) IP address of the AWS gateway device, where is the index number of the VPN tunnel - * * INSIDE_CIDR_ - the inside (link-local) CIDR range of the tunnel, where is the index number of the VPN tunnel - * * INSIDE_NETMASK_ - the inside (link-local) subnet mask of the tunnel, where is the index number of the VPN tunnel - * * PSK_ - the pre-shared key of the tunnel, where is the index number of the VPN tunnel - * * Index numbering is zero-based, so the primary VPN tunnel is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPN:AWS_OUTSIDEIP_0:accelerator-vpn}` - translates to the AWS-side public IP of the primary VPN tunnel for a VPN named `accelerator-vpn` - * * AWS Secrets Manager Secret replacements - look up the secret from AWS Secrets Manager secret in management account. The secret must be stored in the same region the firewall is deployed to. - * * Format: `${ACCEL_LOOKUP::SECRETS_MANAGER:}` -- translates to the secure string from AWS Secrets Manager secret. - * - * * For replacements that are supported in firewall userdata, see {@link LaunchTemplateConfig.userData}. - */ - readonly configDir?: t.NonEmptyString; - /** - * (OPTIONAL) Specify true to enable detailed monitoring. Otherwise, basic monitoring is enabled. - */ - readonly detailedMonitoring?: boolean; - /** - * (OPTIONAL) Specify a relative S3 object path to pull a firewall license file from. - * - * For example, if your S3 object path is `s3://path/to/license.lic`, specify `path/to/license.lic` for this property. - * - * **NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, - * update the name of the license file. - * - * @remarks - * The license file must be uploaded to the accelerator-created assets bucket in the home region of - * your Management account. This is the `${AcceleratorPrefix}-assets` bucket, not the `cdk-accel-assets` bucket. - * - * The license file will be uploaded to `${AcceleratorPrefix}-firewall-config` bucket in the account and region your firewall instance - * is deployed to. This license file can be consumed by third-party firewall vendors that support pulling a license file from S3. - * - * * For replacements that are supported in firewall userdata, see {@link LaunchTemplateConfig.userData}. - */ - readonly licenseFile?: t.NonEmptyString; - /** - * (OPTIONAL) Static firewall configuration replacements definition. - * - * @remarks - * Use this property to define static key/value pairs that can be referenced as variables in firewall configuration files. - * - * If setting this property, the `configFile` or `configDir` property MUST also be set. - * - * Replacement syntax: - * * Format: `${ACCEL_LOOKUP::CUSTOM:}`, where `` is the key name for the replacement as defined in `customizations-config.yaml`. - * * Example usage: `${ACCEL_LOOKUP::CUSTOM:CORP_CIDR_RANGE}` - translates to the static value entered for CORP_CIDR_RANGE. - * - * @see {@link Ec2FirewallInstanceConfig.configFile} - * @see {@link Ec2FirewallInstanceConfig.configDir} - */ - readonly staticReplacements?: IFirewallStaticReplacementsConfig[]; - /** - * (OPTIONAL) If you set this parameter to true , you can't terminate the instance using the Amazon EC2 console, CLI, or API. - * - * More information: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingDisableAPITermination - * - * @remarks - * When finished configuring your firewall instance, it is highly recommended to enable this property in order to prevent - * accidental instance replacement or termination. - */ - readonly terminationProtection?: boolean; - /** - * (OPTIONAL) An array of tags - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link CustomizationsConfig} / {@link Ec2FirewallConfig} / {@link Ec2FirewallAutoScalingGroupConfig}* - * - * @description - * EC2 firewall autoscaling group configuration. - * Used to define EC2-based firewall instances to be deployed in an autoscaling group. - * - * ``` - * - name: accelerator-firewall-asg - * autoscaling: - * name: firewall-asg - * maxSize: 4 - * minSize: 1 - * desiredSize: 2 - * launchTemplate: firewall-lt - * healthCheckGracePeriod: 300 - * healthCheckType: ELB - * targetGroups: - * - firewall-gwlb-tg - * subnets: - * - firewall-subnet-a - * - firewall-subnet-b - * maxInstanceLifetime: 86400 - * launchTemplate: - * name: firewall-lt - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * volumeSize: 20 - * enforceImdsv2: true - * iamInstanceProfile: firewall-profile - * imageId: ami-123xyz - * instanceType: c6i.xlarge - * networkInterfaces: - * - deleteOnTermination: true - * description: Primary interface - * deviceIndex: 0 - * groups: - * - firewall-data-sg - * - deleteOnTermination: true - * description: Management interface - * deviceIndex: 1 - * groups: - * - firewall-mgmt-sg - * userData: path/to/userdata.txt - * vpc: Network-Inspection - * tags: [] - * ``` - */ -export interface IEc2FirewallAutoScalingGroupConfig { - /** - * The friendly name of the firewall instance - * - * @remarks - * **CAUTION**: Changing values under this property after initial deployment will cause an autoscaling group replacement. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * An AutoScaling Group configuration - */ - readonly autoscaling: IAutoScalingConfig; - /** - * The launch template for the firewall instance - * - * @remarks - * **CAUTION**: Changing values under this property after initial deployment will cause instance replacements - * in your autoscaling group. This will not impact downstream dependencies, but may impact your network connectivity - * and/or throughput. - */ - readonly launchTemplate: ILaunchTemplateConfig; - /** - * The friendly name of the VPC to deploy the firewall instance to - * - * @remarks - * This VPC must contain the subnet(s) defined for the network interfaces under the `launchTemplate` property - */ - readonly vpc: t.NonEmptyString; - /** - * (OPTIONAL) The logical name of the account to deploy the firewall autoscaling group to - * - * @remarks - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly account?: t.NonEmptyString; - /** - * (OPTIONAL) Specify a relative S3 object path to pull a firewall configuration file from. - * - * For example, if your S3 object path is `s3://path/to/config.txt`, specify `path/to/config.txt` for this property. - * - * **NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, - * update the name of the configuration file. - * - * @remarks - * Setting this property allows you to make use of firewall configuration replacements. This allows you to - * configure your firewall instance dynamically using values determined at CDK runtime. - * - * **NOTE**: The configuration file must be uploaded to the accelerator-created assets bucket in the home region of - * your Management account. This is the `${AcceleratorPrefix}-assets` bucket, not the `cdk-accel-assets` bucket. - * - * The transformed configuration file will be uploaded to `${AcceleratorPrefix}-firewall-config` bucket in the account and region your firewall instance - * is deployed to. This config file can be consumed by third-party firewall vendors that support pulling a configuration file from S3. - * - * Supported replacements: - * * VPC replacements - look up metadata about the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:VPC:_}`, where `` is a type listed below, - * and `` is the index of the VPC CIDR range. - * * Metadata types: - * * CIDR - the VPC CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the VPC CIDR (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the VPC CIDR (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the VPC CIDR (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary VPC CIDR is index `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPC:CIDR_0}` - translates to the primary CIDR range of the VPC - * * Subnet replacements - look up metadata about subnets in the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:SUBNET::}`, where `` is a type listed - * below, and `` is the logical name of the subnet as defined in `network-config.yaml`. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Example usage: `${ACCEL_LOOKUP::EC2:SUBNET:CIDR:firewall-data-subnet-a}` - translates to the CIDR range of a subnet named `firewall-data-subnet-a` - * * Hostname, network interface, and VPN replacements are NOT supported for firewall AutoScaling groups. - * - * For replacements that are supported in firewall userdata, see {@link LaunchTemplateConfig.userData}. - */ - readonly configFile?: t.NonEmptyString; - /** - * (OPTIONAL) Specify a relative S3 directory path to pull a firewall configuration directory. - * - * Either configFile or configDir can be set but not both. - * - * For example, if your S3 folder path is `s3://path/to/config`, specify `path/to/config` for this property. - * - * **NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, - * update the name of the configuration directory. - * - * @remarks - * Setting this property allows you to make use of firewall configuration replacements. This allows you to - * configure your firewall instance dynamically using values determined at CDK runtime. - * - * **NOTE**: The configuration directory must be uploaded to the accelerator-created assets bucket in the home region of - * your Management account. This is the `${AcceleratorPrefix}-assets` bucket, not the `cdk-accel-assets` bucket. - * - * The transformed configuration directory will be uploaded to `${AcceleratorPrefix}-firewall-config` bucket in the account and region your firewall instance - * is deployed to. This config directory can be consumed by third-party firewall vendors that support pulling a configuration directory from S3. - * - * Supported replacements: - * * Hostname replacement - look up the name of the firewall instance - * * Format: `${ACCEL_LOOKUP::EC2:INSTANCE:HOSTNAME}` -- translates to the logical name of the instance as configured in customizations-config.yaml. - * * VPC replacements - look up metadata about the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:VPC:_}`, where `` is a type listed below, - * and `` is the index of the VPC CIDR range. - * * Metadata types: - * * CIDR - the VPC CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the VPC CIDR (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the VPC CIDR (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the VPC CIDR (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary VPC CIDR is index `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPC:CIDR_0}` - translates to the primary CIDR range of the VPC - * * Subnet replacements - look up metadata about subnets in the VPC the firewall is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:SUBNET::}`, where `` is a type listed - * below, and `` is the logical name of the subnet as defined in `network-config.yaml`. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Example usage: `${ACCEL_LOOKUP::EC2:SUBNET:CIDR:firewall-data-subnet-a}` - translates to the CIDR range of a subnet named `firewall-data-subnet-a` - * * Network interface IP replacements - look up public and private IP addresses assigned to firewall network interfaces: - * * Format: `${ACCEL_LOOKUP::EC2:ENI_:_}`, where `` is the device index - * of the network interface as defined in the firewall launch template, `` is either a public or private IP of the interface, - * and `` is the index of the interface IP address. - * * IP types: - * * PRIVATEIP - a private IP associated with the interface - * * PUBLICIP - a public IP associated with the interface - * * Index numbering is zero-based, so the primary interface of the instance is `0` and its primary IP address is also `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:PRIVATEIP_0}` - translates to the primary private IP address of the primary network interface - * * Network interface subnet replacements - look up metadata about the subnet a network interface is deployed to: - * * Format: `${ACCEL_LOOKUP::EC2:ENI_:SUBNET_}`, where `` is the device index - * of the network interface as defined in the firewall launch template and `` is a type listed below. - * * Metadata types: - * * CIDR - the subnet CIDR range in CIDR notation (i.e. 10.0.0.0/16) - * * NETMASK - the network mask of the subnet (i.e. 255.255.0.0) - * * NETWORKIP - the network address of the subnet (i.e. 10.0.0.0) - * * ROUTERIP - the VPC router address of the subnet (i.e. 10.0.0.1) - * * Index numbering is zero-based, so the primary interface of the instance is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_CIDR}` - translates to the subnet CIDR range of the primary network interface - * * VPN replacements - look up metadata about VPNs that are directly connected to the EC2 firewall instance. NOTE: these replacements are - * only supported for EC2 firewalls that are referenced in a {@link CustomerGatewayConfig} in network-config.yaml. - * * Format: `${ACCEL_LOOKUP::EC2:VPN::}`, where `` is a type listed - * below, and `` is the logical name of the VPN connection as defined in `network-config.yaml`. - * * Metadata types: - * * AWS_BGPASN - the BGP autonomous system number (ASN) of the AWS gateway device - * * CGW_BGPASN - the BGP autonomous system number (ASN) of the customer gateway device - * * CGW_OUTSIDEIP - the outside (public) IP address of the customer gateway device - * * AWS_INSIDEIP_ - the inside (link-local) IP address of the AWS gateway device, where is the index number of the VPN tunnel - * * CGW_INSIDEIP_ - the inside (link-local) IP address of the customer gateway device, where is the index number of the VPN tunnel - * * AWS_OUTSIDEIP_ - the outside (public) IP address of the AWS gateway device, where is the index number of the VPN tunnel - * * INSIDE_CIDR_ - the inside (link-local) CIDR range of the tunnel, where is the index number of the VPN tunnel - * * INSIDE_NETMASK_ - the inside (link-local) subnet mask of the tunnel, where is the index number of the VPN tunnel - * * PSK_ - the pre-shared key of the tunnel, where is the index number of the VPN tunnel - * * Index numbering is zero-based, so the primary VPN tunnel is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:VPN:AWS_OUTSIDEIP_0:accelerator-vpn}` - translates to the AWS-side public IP of the primary VPN tunnel for a VPN named `accelerator-vpn` - * * AWS Secrets Manager Secret replacements - look up the secret from AWS Secrets Manager secret in management account. The secret must be stored in the same region the firewall is deployed to. - * * Format: `${ACCEL_LOOKUP::SECRETS_MANAGER:}` -- translates to the secure string from AWS Secrets Manager secret. - * - * * For replacements that are supported in firewall userdata, see {@link LaunchTemplateConfig.userData}. - */ - readonly configDir?: t.NonEmptyString; - /** - * (OPTIONAL) Specify a relative S3 object path to pull a firewall license file from. - * - * For example, if your S3 object path is `s3://path/to/license.lic`, specify `path/to/license.lic` for this property. - * - * **NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, - * update the name of the license file. - * - * @remarks - * The license file must be uploaded to the accelerator-created assets bucket in the home region of - * your Management account. This is the `${AcceleratorPrefix}-assets` bucket, not the `cdk-accel-assets` bucket. - * - * The license file will be uploaded to `${AcceleratorPrefix}-firewall-config` bucket in the account and region your firewall instance - * is deployed to. This license file can be consumed by third-party firewall vendors that support pulling a license file from S3. - * - * * For replacements that are supported in firewall userdata, see {@link LaunchTemplateConfig.userData}. - */ - readonly licenseFile?: t.NonEmptyString; - /** - * (OPTIONAL) Static firewall configuration replacements definition. - * - * @remarks - * Use this property to define static key/value pairs that can be referenced as replacement variables in firewall configuration files. - * - * If setting this property, the `configFile` or `configDir` property MUST also be set. - * - * Replacement syntax: - * * Format: `${ACCEL_LOOKUP::CUSTOM:}`, where `` is the key name for the replacement as defined in `customizations-config.yaml`. - * * Example usage: `${ACCEL_LOOKUP::CUSTOM:CORP_CIDR_RANGE}` - translates to the static value entered for CORP_CIDR_RANGE. - * - * @see {@link Ec2FirewallAutoScalingGroupConfig.configFile} - * @see {@link Ec2FirewallAutoScalingGroupConfig.configDir} - */ - readonly staticReplacements?: IFirewallStaticReplacementsConfig[]; - /** - * (OPTIONAL) An array of tags - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link CustomizationsConfig} / {@link Ec2FirewallConfig}* - * - * @description - * EC2 firewall configuration. - * Used to define EC2-based firewall and management appliances - * - * @example - * Standalone instances: - * ``` - * instances: - * - name: accelerator-firewall - * launchTemplate: - * name: firewall-lt - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * volumeSize: 20 - * enforceImdsv2: true - * iamInstanceProfile: firewall-profile - * imageId: ami-123xyz - * instanceType: c6i.xlarge - * networkInterfaces: - * - deleteOnTermination: true - * description: Primary interface - * deviceIndex: 0 - * groups: - * - firewall-data-sg - * subnetId: firewall-data-subnet-a - * - deleteOnTermination: true - * description: Management interface - * deviceIndex: 1 - * groups: - * - firewall-mgmt-sg - * subnetId: firewall-mgmt-subnet-a - * userData: path/to/userdata.txt - * vpc: Network-Inspection - * targetGroups: - * - name: firewall-gwlb-tg - * port: 6081 - * protocol: GENEVE - * type: instance - * healthCheck: - * enabled: true - * port: 80 - * protocol: TCP - * targets: - * - accelerator-firewall - * ``` - * - * Autoscaling group: - * ``` - * autoscalingGroups: - * - name: accelerator-firewall-asg - * autoscaling: - * name: firewall-asg - * maxSize: 4 - * minSize: 1 - * desiredSize: 2 - * launchTemplate: firewall-lt - * healthCheckGracePeriod: 300 - * healthCheckType: ELB - * targetGroups: - * - firewall-gwlb-tg - * subnets: - * - firewall-subnet-a - * - firewall-subnet-b - * maxInstanceLifetime: 86400 - * launchTemplate: - * name: firewall-lt - * blockDeviceMappings: - * - deviceName: /dev/xvda - * ebs: - * deleteOnTermination: true - * encrypted: true - * volumeSize: 20 - * enforceImdsv2: true - * iamInstanceProfile: firewall-profile - * imageId: ami-123xyz - * instanceType: c6i.xlarge - * networkInterfaces: - * - deleteOnTermination: true - * description: Primary interface - * deviceIndex: 0 - * groups: - * - firewall-data-sg - * - deleteOnTermination: true - * description: Management interface - * deviceIndex: 1 - * groups: - * - firewall-mgmt-sg - * userData: path/to/userdata.txt - * vpc: Network-Inspection - * targetGroups: - * - name: firewall-gwlb-tg - * port: 6081 - * protocol: GENEVE - * type: instance - * healthCheck: - * enabled: true - * port: 80 - * protocol: TCP - * ``` - * - */ -export interface IEc2FirewallConfig { - /** - * Define EC2-based firewall instances in autoscaling groups - */ - readonly autoscalingGroups?: IEc2FirewallAutoScalingGroupConfig[]; - /** - * Define EC2-based firewall standalone instances - */ - readonly instances?: IEc2FirewallInstanceConfig[]; - /** - * Define EC2-based firewall management instances - */ - readonly managerInstances?: IEc2FirewallInstanceConfig[]; - /** - * Define target groups for EC2-based firewalls - */ - readonly targetGroups?: ITargetGroupItem[]; -} - -/** - * *{@link CustomizationsConfig}* - * - * @description - * Defines custom CloudFormation and external web and application tier resources. We recommend creating resources - * with native LZA features where possible. - */ -export interface ICustomizationsConfig { - readonly customizations?: ICustomizationConfig; - readonly applications?: IAppConfigItem[]; - readonly firewalls?: IEc2FirewallConfig; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/global-config.ts b/source/packages/@aws-accelerator/config/lib/models/global-config.ts deleted file mode 100644 index d4dac68..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/global-config.ts +++ /dev/null @@ -1,2371 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; -/** - * {@link IGlobalConfig} / {@link IControlTowerConfig} / {@link IControlTowerLandingZoneConfig} / {@link IControlTowerLandingZoneLoggingConfig} - * - * AWS Control Tower Landing Zone logging configuration - * - * @remarks - * This allows you to manage logging options for the landing zone. - * In the log configuration section, you can configure the retention time of the Amazon S3 log archive bucket, and the retention time of the logs for access to the bucket. - * - * @example - * ``` - * logging: - * loggingBucketRetentionDays: 365 - * accessLoggingBucketRetentionDays: 3650 - * organizationTrail: true - * ``` - */ -export interface IControlTowerLandingZoneLoggingConfig { - /** - * Retention time of the Amazon S3 log archive bucket - * - * @default - * 365 - */ - readonly loggingBucketRetentionDays: number; - /** - * Retention time of the logs for access to the bucket. - * - * @default - * 3650 - */ - readonly accessLoggingBucketRetentionDays: number; - /** - * Flag indicates Organizational-level AWS CloudTrail configuration is configured or not. - * - * @remarks - * It is important to note that the CloudTrail configured by AWS Control Tower Landing Zone at the organization level is different from the CloudTrail deployed by the solution. In the event that AWS Control Tower Landing Zone and Solution defined CloudTrail are enabled, two cloud trails will be created. - * @default - * true - */ - readonly organizationTrail: boolean; -} - -/** - * {@link IGlobalConfig} / {@link IControlTowerConfig} / {@link IControlTowerLandingZoneConfig} / {@link IControlTowerLandingZoneSecurityConfig} - * AWS Control Tower Landing Zone security configuration - * - * @remarks - * This allows you to manage security options for the landing zone. - * - * @example - * ``` - * security: - * enableIdentityCenterAccess: true - * ``` - */ -export interface IControlTowerLandingZoneSecurityConfig { - /** - * Flag indicates AWS account access option. - * - * @remarks - * When this property is to true, AWS Control Tower sets up AWS account access with IAM Identity Center. Otherwise, please use self-managed AWS account access with IAM Identity Center or another method. - * - * @default - * true - */ - readonly enableIdentityCenterAccess: boolean; -} - -/** - * {@link IGlobalConfig} / {@link IControlTowerConfig} / {@link IControlTowerLandingZoneConfig} - * - * @description - * AWS Control Tower Landing Zone configuration - * - * @remarks - * This allows you to manage AWS Control Tower Landing Zone configuration. - * - * - * @example - * - * ``` - * landingZone: - * version: '3.3' - * logging: - * loggingBucketRetentionDays: 365 - * accessLoggingBucketRetentionDays: 3650 - * organizationTrail: true - * security: - * enableIdentityCenterAccess: true - * ``` - */ -export interface IControlTowerLandingZoneConfig { - /** - * The landing zone version, for example, 3.3. - * - * @remarks - * Most AWS Control Tower Landing Zone operation needs the version to latest available version. - * The AWS Control Tower Landing Zone will be updated or reset when it drifts or when any configuration changes have been made in global-config. - * When the value of this property is set to the latest available version, AWS Control Tower Landing Zone can be updated or reset. - * The solution will fail if this property version is not set to the latest available version. - * If you wish to update or reset the AWS Control Tower Landing Zone, you will need to update this property to match the latest available version. - * - */ - readonly version: string; - /** - * AWS Control Tower Landing Zone logging configuration - * - * @see {@link IControlTowerLandingZoneLoggingConfig} for more information. - */ - readonly logging: IControlTowerLandingZoneLoggingConfig; - /** - * AWS Control Tower Landing Zone security configuration - * - * @see {@link IControlTowerLandingZoneSecurityConfig} for more information. - */ - readonly security: IControlTowerLandingZoneSecurityConfig; -} - -/** - * {@link IGlobalConfig} / {@link IControlTowerConfig} / {@link IControlTowerControlConfig} - * - * @description - * Control Tower controls - * - * @see ControlTowerControlConfig - * - * This allows you to enable Strongly Recommended or Elective Controls - * https://docs.aws.amazon.com/controltower/latest/userguide/optional-controls.html - * - * @remarks AWS Control Tower is limited to 10 concurrent operations, where enabling a control for one Organizational Unit constitutes a single operation. - * To avoid throttling, please enable controls in batches of 10 or fewer each pipeline run. Keep in mind other Control Tower operations may use up some of the available quota. - * - * @example - * controlTowerControls: - * - identifier: AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS - * enable: true - * deploymentTargets: - * organizationalUnits: - * - Workloads - */ -export interface IControlTowerControlConfig { - /** - * Control Tower control identifier, for Strongly Recommended or Elective controls this should start with AWS-GR - */ - readonly identifier: t.NonEmptyString; - /** - * Control enabled - */ - readonly enable: boolean; - /** - * Control Tower control deployment targets, controls can only be deployed to Organizational Units - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * (Optional) Region(s) where this service quota increase will be requested. Service Quota increases will be requested in the home region only if this property is not defined. - */ - readonly regions?: t.Region[]; -} - -/** - * *{@link IGlobalConfig} / {@link IControlTowerConfig} - * - * @description - * AWS Control Tower Landing Zone configuration - * - * @example - * ``` - * controlTower: - * enable: true - * landingZone: - * version: '3.3' - * logging: - * loggingBucketRetentionDays: 365 - * accessLoggingBucketRetentionDays: 3650 - * organizationTrail: true - * security: - * enableIdentityCenterAccess: true - * ``` - */ -export interface IControlTowerConfig { - /** - * Indicates whether AWS Control Tower Landing Zone enabled. - * - * When control tower is enabled, accelerator makes sure account configuration file have three mandatory AWS CT accounts. - * In AWS Control Tower, three shared accounts in your landing zone are provisioned automatically during setup: the management account, - * the log archive account, and the audit account. - */ - readonly enable: boolean; - /** - * A list of Control Tower controls to enable. - * - * Only Strongly recommended and Elective controls are permitted, with the exception of the Region deny guardrail. Please see this page for more information: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-controltower-enabledcontrol.html - * - * @see {@link IControlTowerControlConfig} - * - * @remarks - * Only Strongly recommended and Elective controls are permitted, with the exception of the Region deny guardrail. Please see this page for more information: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-controltower-enabledcontrol.html - */ - readonly controls?: IControlTowerControlConfig[]; - - /** - * AWS Control Tower Landing Zone configuration - * - * @see {@link IControlTowerLandingZoneConfig} for more information. - */ - readonly landingZone?: IControlTowerLandingZoneConfig; -} - -/** - * *{@link GlobalConfig} / {@link S3GlobalConfig} / {@link S3EncryptionConfig}* - * - * @description - * AWS S3 encryption configuration settings - * - * @example - * ``` - * encryption: - * createCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IS3EncryptionConfig { - /** - * Flag indicates whether solution will create CMK for S3 bucket encryption. - * Note: This configuration is not applicable to the assets S3 bucket. This bucket will always have a key generated and applied. - * - * @remarks - * When set to `true`, the solution will create AWS KMS CMK which will be used by the S3 for server-side encryption. - * - * @default true - */ - readonly createCMK: boolean; - /** - * To control target environments (AWS Account and Region) for the given `createCMK` setting, you may optionally specify deployment targets. - * Leaving `deploymentTargets` undefined will apply `createCMK` setting to all accounts and enabled regions. - */ - readonly deploymentTargets?: t.IDeploymentTargets; -} - -/** - * *{@link GlobalConfig} / {@link S3GlobalConfig}* - * - * @description - * AWS S3 global encryption configuration settings - * - * @example - * ``` - * encryption: - * createCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IS3GlobalConfig { - /** - * S3 encryption configuration. - * - * @remarks - * Please use the following configuration to disable AWS KMS CMK for AWS S3 bucket encryption. - * In the absence of this property, the solution will deploy the AWS KMS CMK in every environment (AWS Account and Region). - * The solution will disregard this property and create CMKs to encrypt the installer bucket, pipeline bucket, and solution deployed CentralLogs bucket, - * because AWS KMS CMK is always used to encrypt installer buckets, pipeline buckets, and solution deployed CentralLogs buckets. - * - * @example - * ``` - * s3: - * encryption: - * createCMK: false - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - * @default undefined - */ - readonly encryption?: IS3EncryptionConfig; -} - -/** - * *{@link GlobalConfig} / {@link centralizeCdkBucketsConfig}* - * - * @description - * AWS CDK Centralization configuration - * ***Deprecated*** - * Replaced by cdkOptions in global config - * - * @example - * ``` - * centralizeCdkBuckets: - * enable: true - * ``` - */ -export interface ICentralizeCdkBucketsConfig { - /** - * ***Deprecated*** - * Replaced by cdkOptions in global config. - * - * Indicates whether CDK stacks in workload accounts will utilize S3 buckets in the management account rather than within the account. - * - * When the accelerator deploys resources using the AWS CDK, assets are first built and stored in S3. By default, the S3 bucket is - * located within the deployment target account. - */ - readonly enable: boolean; -} - -/** - * *{@link GlobalConfig} / {@link cdkOptionsConfig}* - * - * @description - * AWS CDK options configuration. This lets you customize the operation of the CDK within LZA, specifically: - * - * centralizeBuckets: Enabling this option modifies the CDK bootstrap process to utilize a single S3 bucket per region located in the management account for CDK assets generated by LZA. Otherwise, CDK will create a new S3 bucket in every account and every region supported by LZA. - * useManagementAccessRole: Enabling this option modifies CDK operations to use the IAM role specified in the `managementAccountAccessRole` option in `global-config.yaml` rather than the default roles created by CDK. Default CDK roles will still be created, but will remain unused. Any stacks previously deployed by LZA will retain their [associated execution role](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html). For more information on these roles, please see [here](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-contract). - * - * @example - * ``` - * cdkOptions: - * centralizeBuckets: true - * useManagementAccessRole: true - * ``` - */ -export interface ICdkOptionsConfig { - /** - * Indicates whether CDK stacks in workload accounts will utilize S3 buckets in the management account rather than within the account. - * - * When the accelerator deploys resources using the AWS CDK, assets are first built and stored in S3. By default, the S3 bucket is - * located within the deployment target account. - */ - readonly centralizeBuckets: boolean; - /** - * Indicates whether CDK operations use the IAM role specified in the `managementAccountAccessRole` option in `global-config.yaml` rather than the default roles created by CDK. - * - * The roles created and leveraged by CDK by default can be found [here](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-contract). - */ - readonly useManagementAccessRole: boolean; - /** - * Creates a deployment role in all accounts in the home region with the name specified in the parameter. This role is used by the LZA for all CDK deployment tasks. - */ - readonly customDeploymentRole?: string; - /** - * Forces the Accelerator to deploy the bootstrapping stack and circumvent the ssm parameter check. This option is needed when adding or removing a custom deployment role - */ - readonly forceBootstrap?: boolean; -} - -/** - * *{@link GlobalConfig} / {@link externalLandingZoneResourcesConfig}* - * - * @description - * External Landing Zone Resources Config - * - * @example - * ``` - * externalLandingZoneResourcesConfig: - * importExternalLandingZoneResources: true - * ``` - */ -export interface IExternalLandingZoneResourcesConfig { - /** - * When the accelerator deploys resources using the AWS CDK, assets are first built and stored in S3. By default, the S3 bucket is - * located within the deployment target account. - */ - readonly importExternalLandingZoneResources: boolean; - readonly mappingFileBucket?: string; - readonly acceleratorPrefix: t.NonEmptyString; - readonly acceleratorName: t.NonEmptyString; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CloudTrailConfig} / ({@link AccountCloudTrailConfig}) / {@link CloudTrailSettingsConfig}* - * - * @description - * AWS CloudTrail Settings configuration - * - * @example - * ``` - * multiRegionTrail: true - * globalServiceEvents: true - * managementEvents: true - * s3DataEvents: true - * lambdaDataEvents: true - * sendToCloudWatchLogs: true - * apiErrorRateInsight: false - * apiCallRateInsight: false - * ``` - */ -export interface ICloudTrailSettingsConfig { - /** - * Whether or not this trail delivers log files from all regions in the account. - */ - multiRegionTrail: boolean; - /** - * For global services such as AWS Identity and Access Management (IAM), AWS STS, Amazon CloudFront, - * and Route 53, events are delivered to any trail that includes global services, - * and are logged as occurring in US East Region. - */ - globalServiceEvents: boolean; - /** - * Management events provide insight into management operations that are - * on resources in your AWS account. These are also known as control plane operations. - * Management events can also include non-API events that occur in your account. - * For example, when a user logs in to your account, CloudTrail logs the ConsoleLogin event. - * Enabling will set ReadWriteType.ALL - */ - managementEvents: boolean; - /** - * Adds an S3 Data Event Selector for filtering events that match S3 operations. - * These events provide insight into the resource operations performed on or within a resource. - * These are also known as data plane operations. - */ - s3DataEvents: boolean; - /** - * Adds an Lambda Data Event Selector for filtering events that match Lambda operations. - * These events provide insight into the resource operations performed on or within a resource. - * These are also known as data plane operations. - */ - lambdaDataEvents: boolean; - /** - * If CloudTrail pushes logs to CloudWatch Logs in addition to S3. CloudWatch Logs - * will also be replicated to S3. - */ - sendToCloudWatchLogs: boolean; - /** - * Will enable CloudTrail Insights and enable the API Error Rate Insight - */ - readonly apiErrorRateInsight: boolean; - /** - * Will enable CloudTrail Insights and enable the API Call Rate Insight - */ - readonly apiCallRateInsight: boolean; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CloudTrailConfig} / {@link AccountCloudTrailConfig}* - * - * @description - * Account CloudTrail config - * - * @example - * ``` - * - name: AWSAccelerator-Account-CloudTrail - * regions: - * - us-east-1 - * deploymentTargets: - * organizationalUnits: - * - Root - * settings: - * multiRegionTrail: true - * globalServiceEvents: true - * managementEvents: true - * s3DataEvents: true - * lambdaDataEvents: true - * sendToCloudWatchLogs: true - * apiErrorRateInsight: false - * apiCallRateInsight: false - * ``` - */ -export interface IAccountCloudTrailConfig { - /** - * Name that will be used to create the CloudTrail. - */ - readonly name: string; - /** - * Region(s) that this account trail will be deployed in. - */ - readonly regions: t.NonEmptyString[]; - /** - * Which OU's or Accounts the trail will be deployed to - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * Settings for the CloudTrail log - */ - readonly settings: ICloudTrailSettingsConfig; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CloudTrailConfig} / {@link AccountCloudTrailConfig}* - * - * @description - * AWS Cloudtrail configuration - * - * @example - * ``` - * cloudtrail: - * enable: true - * organizationTrail: true - * organizationTrailSettings: - * multiRegionTrail: true - * globalServiceEvents: true - * managementEvents: true - * s3DataEvents: true - * lambdaDataEvents: true - * sendToCloudWatchLogs: true - * apiErrorRateInsight: false - * apiCallRateInsight: false - * accountTrails: [] - * lifecycleRules: [] - * ``` - */ -export interface ICloudTrailConfig { - /** - * Indicates whether AWS Cloudtrail enabled. - * - * Cloudtrail a service that helps you enable governance, compliance, and operational and risk auditing of your AWS account. - * This setting does not create any trails. You will also need to either and organization trail - * or setup account level trails. - */ - readonly enable: boolean; - /** - * Indicates whether AWS OrganizationTrail enabled. - * - * When OrganizationTrail and cloudtrail is enabled accelerator will enable trusted access designates CloudTrail as a trusted service in your organization. - * A trusted service can query the organization's structure and create service-linked roles in the organization's accounts. - */ - readonly organizationTrail: boolean; - /** - * Optional configuration of the organization trail. OrganizationTrail must be enabled - * in order to use these settings - */ - readonly organizationTrailSettings?: ICloudTrailSettingsConfig; - /** - * Optional configuration of account level CloudTrails. Can be used with or without - * an Organization Trail - */ - readonly accountTrails?: IAccountCloudTrailConfig[]; - /** - * Optional S3 Log Bucket Lifecycle rules - */ - readonly lifecycleRules?: t.ILifecycleRule[]; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link SessionManagerConfig}* - * - * @description - * AWS Service Quotas configuration - */ -export interface IServiceQuotaLimitsConfig { - /** - * Indicates which service Service Quota is changing the limit for. - */ - readonly serviceCode: string; - /** - * Indicates the code for the service as these are tied to the account. - * - */ - readonly quotaCode: string; - /** - * Value associated with the limit change. - */ - readonly desiredValue: number; - /** - * List of AWS Account names to be included in the Service Quota changes - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * (Optional) Region(s) where this service quota increase will be requested. Service Quota increases will be requested in the home region only if this property is not defined. - */ - readonly regions?: t.Region[]; -} - -/** - * @description - * AWS SessionManager configuration - * - * @example - * ``` - * sessionManager: - * sendToCloudWatchLogs: true - * sendToS3: true - * excludeRegions: [] - * excludeAccounts: [] - * lifecycleRules: [] - * attachPolicyToIamRoles: - * - EC2-Default-SSM-AD-Role - * ``` - */ -export interface ISessionManagerConfig { - /** - * Indicates whether sending SessionManager logs to CloudWatchLogs enabled. - */ - readonly sendToCloudWatchLogs: boolean; - /** - * Indicates whether sending SessionManager logs to S3 enabled. - * - * When this flag is on, accelerator will send session manager logs to Central log bucket in LogArchive account. - */ - readonly sendToS3: boolean; - /** - * List of AWS Region names to be excluded from configuring SessionManager configuration - */ - readonly excludeRegions?: t.Region[]; - /** - * List of AWS Account names to be excluded from configuring SessionManager configuration - */ - readonly excludeAccounts?: string[]; - /** - * S3 Lifecycle rule for log storage - */ - readonly lifecycleRules?: t.ILifecycleRule[]; - /** - * List of IAM EC2 roles that the Session Manager - * access policy should be attached to - */ - readonly attachPolicyToIamRoles?: string[]; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link AssetBucketConfig}* - * - * @description - * Accelerator global S3 asset bucket configuration - * - * @example - * ``` - * assetBucket: - * s3ResourcePolicyAttachments: - * - policy: s3-policies/policy1.json - * importedBucket: - * name: aws-accelerator-assets - * applyAcceleratorManagedBucketPolicy: true - * ``` - */ -export interface IAssetBucketConfig { - /** - * JSON policy files. - * - * @remarks - * Policy statements from these files will be added to the bucket resource policy. - * This property can not be used when customPolicyOverrides.s3Policy property has value. - * - * Note: When Block Public Access is enabled for S3 on the AWS account, you can't specify a policy that would make - * the S3 Bucket public. - */ - readonly s3ResourcePolicyAttachments?: t.IResourcePolicyStatement[]; - /** - * JSON policy files. - * - * @remarks - * Policy statements from these files will be added to the bucket encryption key policy. - * This property can not be used when customPolicyOverrides.kmsPolicy property has value. - * When imported CentralLogs bucket used with createAcceleratorManagedKey set to false, this property can not have any value. - */ - readonly kmsResourcePolicyAttachments?: t.IResourcePolicyStatement[]; - /** - * Imported bucket configuration. - * - * @remarks - * Use this configuration when accelerator will import existing Assets bucket. - * - * Use the following configuration to imported Assets bucket, manage bucket resource policy and apply bucket encryption through the solution. - * ``` - * importedBucket: - * name: aws-assets - * applyAcceleratorManagedBucketPolicy: true - * createAcceleratorManagedKey: true - * ``` - * - * @default - * undefined - */ - readonly importedBucket?: t.IImportedCustomerManagedEncryptionKeyBucketConfig; - /** - * Custom policy overrides configuration. - * - * @remarks - * Use this configuration to provide JSON string policy file for bucket resource policy. - * Bucket resource policy will be over written by content of this file, so when using these option policy files must contain complete policy document. - * When customPolicyOverrides.s3Policy defined importedBucket.applyAcceleratorManagedBucketPolicy can not be set to true also s3ResourcePolicyAttachments property can not be defined. - * - * Use the following configuration to apply custom bucket resource policy overrides through policy JSON file. - * ``` - * customPolicyOverrides: - * s3Policy: path/to/policy.json - * kmsPolicy: kms/full-central-logs-bucket-key-policy.json - * ``` - * - * @default - * undefined - */ - readonly customPolicyOverrides?: t.CustomS3ResourceAndKmsPolicyOverridesConfig; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link AccessLogBucketConfig}* - * - * @description - * Accelerator global S3 access logging configuration - * - * @example - * ``` - * accessLogBucket: - * enable: true - * deploymentTargets: - * organizationalUnits: - * - Root - * s3ResourcePolicyAttachments: - * - policy: s3-policies/policy1.json - * lifecycleRules: - * - enabled: true - * id: AccessLifecycle-01 - * abortIncompleteMultipartUpload: 14 - * expiration: 3563 - * expiredObjectDeleteMarker: false - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * - enabled: true - * id: AccessLifecycle-02 - * abortIncompleteMultipartUpload: 14 - * expiredObjectDeleteMarker: true - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * importedBucket: - * name: existing-access-log-bucket - * applyAcceleratorManagedBucketPolicy: true - * ``` - */ -export interface IAccessLogBucketConfig { - /** - * Declaration of (S3 Bucket) Lifecycle rules. - */ - readonly lifecycleRules?: t.ILifecycleRule[]; - /** - * Flag indicating S3 access logging bucket is enable by solution. - * - * @remarks - * When this property is undefined solution will create S3 access log bucket. You can use `deploymentTargets` to control target accounts and regions for the given `accessLogBucket` configuration. - * In the solution, this property will be ignored and S3 Access log buckets will be created for the installer bucket, - * pipeline bucket, solution deployed CentralLogs bucket, and solution deployed Assets bucket, since these buckets always have server access logging enabled. - */ - readonly enable?: boolean; - /** - * To control target environments (AWS Account and Region) for the given `accessLogBucket` setting, you may optionally specify deployment targets. - * Leaving `deploymentTargets` undefined will apply `useCMK` setting to all accounts and enabled regions. - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * JSON policy files. - * - * @remarks - * Policy statements from these files will be added to the bucket resource policy. - * This property can not be used when customPolicyOverrides.s3Policy property has value. - * - * Note: When Block Public Access is enabled for S3 on the AWS account, you can't specify a policy that would make - * the S3 Bucket public. - */ - readonly s3ResourcePolicyAttachments?: t.IResourcePolicyStatement[]; - /** - * Imported bucket configuration. - * - * @remarks - * Use this configuration when accelerator will import existing AccessLogs bucket. - * - * Use the following configuration to imported AccessLogs bucket, manage bucket resource policy through solution. - * ``` - * importedBucket: - * name: existing-access-log-bucket - * applyAcceleratorManagedBucketPolicy: true - * ``` - * - * @default - * undefined - */ - readonly importedBucket?: t.IImportedS3ManagedEncryptionKeyBucketConfig; - /** - * Custom policy overrides configuration. - * - * @remarks - * Use this configuration to provide JSON string policy file for bucket resource policy. - * Bucket resource policy will be over written by content of this file, so when using these option policy files must contain complete policy document. - * When customPolicyOverrides.s3Policy defined importedBucket.applyAcceleratorManagedBucketPolicy can not be set to true also s3ResourcePolicyAttachments property can not be defined. - * - * Use the following configuration to apply custom bucket resource policy overrides through policy JSON file. - * ``` - * customPolicyOverrides: - * s3Policy: path/to/policy.json - * ``` - * - * @default - * undefined - */ - readonly customPolicyOverrides?: t.ICustomS3ResourcePolicyOverridesConfig; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CentralLogBucketConfig}* - * - * @description - * Accelerator global S3 central logging configuration - * - * @example - * ``` - * centralLogBucket: - * applyAcceleratorManagedPolicy: true - * lifecycleRules: - * - enabled: true - * id: CentralLifecycleRule-01 - * abortIncompleteMultipartUpload: 14 - * expiration: 3563 - * expiredObjectDeleteMarker: false - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * - enabled: true - * id: CentralLifecycleRule-02 - * abortIncompleteMultipartUpload: 14 - * expiredObjectDeleteMarker: true - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * s3ResourcePolicyAttachments: - * - policy: s3-policies/policy1.json - * kmsResourcePolicyAttachments: - * - policy: kms-policies/policy1.json - * importedBucket: - * name: central-log-bucket - * applyAcceleratorManagedBucketPolicy: true - * createAcceleratorManagedKey: false - * ``` - */ -export interface ICentralLogBucketConfig { - /** - * Declaration of (S3 Bucket) Lifecycle rules. - * Configure additional resource policy attachments - */ - readonly lifecycleRules?: t.ILifecycleRule[]; - /** - * JSON policy files. - * - * @remarks - * Policy statements from these files will be added to the bucket resource policy. - * This property can not be used when customPolicyOverrides.s3Policy property has value. - * - * Note: When Block Public Access is enabled for S3 on the AWS account, you can't specify a policy that would make - * the S3 Bucket public. - */ - readonly s3ResourcePolicyAttachments?: t.IResourcePolicyStatement[]; - /** - * JSON policy files. - * - * @remarks - * Policy statements from these files will be added to the bucket encryption key policy. - * This property can not be used when customPolicyOverrides.kmsPolicy property has value. - * When imported CentralLogs bucket used with createAcceleratorManagedKey set to false, this property can not have any value. - */ - readonly kmsResourcePolicyAttachments?: t.IResourcePolicyStatement[]; - /** - * Imported bucket configuration. - * - * @remarks - * Use this configuration when accelerator will import existing CentralLogs bucket. - * - * Use the following configuration to imported CentralLogs bucket, manage bucket resource policy and kms policy through solution. - * ``` - * importedBucket: - * name: existing-central-log-bucket - * applyAcceleratorManagedBucketPolicy: true - * createAcceleratorManagedKey: true - * ``` - * - * @default - * undefined - */ - readonly importedBucket?: t.IImportedS3ManagedEncryptionKeyBucketConfig; - /** - * Custom policy overrides configuration. - * - * @remarks - * Use this configuration to provide JSON string policy file for bucket resource policy and KMS key policy. - * Bucket resource policy and kms key policy will be over written by content of this file, so when using these option policy files must contain complete policy document. - * When customPolicyOverrides.s3Policy defined importedBucket.applyAcceleratorManagedBucketPolicy can not be set to true also s3ResourcePolicyAttachments property can not be defined. - * When customPolicyOverrides.kmsPolicy defined kmsResourcePolicyAttachments property can not be defined. - * - * - * Use the following configuration to apply custom bucket resource policy and KMS policy overrides through policy JSON file. - * ``` - * customPolicyOverrides: - * s3Policy: path/to/policy.json - * kmsPolicy: kms/full-central-logs-bucket-key-policy.json - * ``` - * - * @default - * undefined - */ - readonly customPolicyOverrides?: t.ICustomS3ResourceAndKmsPolicyOverridesConfig; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link ElbLogBucketConfig}* - * - * @description - * Accelerator global S3 elb logging configuration - * - * @example - * ``` - * elbLogBucket: - * applyAcceleratorManagedPolicy: true - * lifecycleRules: - * - enabled: true - * id: ElbLifecycleRule-01 - * abortIncompleteMultipartUpload: 14 - * expiration: 3563 - * expiredObjectDeleteMarker: false - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * - enabled: true - * id: ElbLifecycleRule-02 - * abortIncompleteMultipartUpload: 14 - * expiredObjectDeleteMarker: true - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * s3ResourcePolicyAttachments: - * - policy: s3-policies/policy1.json - * importedBucket: - * name: elb-logs-bucket - * applyAcceleratorManagedBucketPolicy: true - * ``` - */ -export interface IElbLogBucketConfig { - /** - * Declaration of (S3 Bucket) Lifecycle rules. - * Configure additional resource policy attachments - */ - readonly lifecycleRules?: t.ILifecycleRule[]; - /** - * JSON policy files. - * - * @remarks - * Policy statements from these files will be added to the bucket resource policy. - * This property can not be used when customPolicyOverrides.s3Policy property has value. - * - * Note: When Block Public Access is enabled for S3 on the AWS account, you can't specify a policy that would make - * the S3 Bucket public. - */ - readonly s3ResourcePolicyAttachments?: t.IResourcePolicyStatement[]; - /** - * Imported bucket configuration. - * - * @remarks - * Use this configuration when accelerator will import existing ElbLogs bucket. - * - * Use the following configuration to imported ElbLogs bucket, manage bucket resource policy through solution. - * ``` - * importedBucket: - * name: existing-elb-log-bucket - * applyAcceleratorManagedBucketPolicy: true - * ``` - * - * @default - * undefined - */ - readonly importedBucket?: t.IImportedS3ManagedEncryptionKeyBucketConfig; - /** - * Custom policy overrides configuration. - * - * @remarks - * Use this configuration to provide JSON string policy file for bucket resource policy. - * Bucket resource policy will be over written by content of this file, so when using these option policy files must contain complete policy document. - * When customPolicyOverrides.s3Policy defined importedBucket.applyAcceleratorManagedBucketPolicy can not be set to true also s3ResourcePolicyAttachments property can not be defined. - * - * Use the following configuration to apply custom bucket resource policy overrides through policy JSON file. - * ``` - * customPolicyOverrides: - * s3Policy: path/to/policy.json - * ``` - * - * @default - * undefined - */ - readonly customPolicyOverrides?: t.ICustomS3ResourcePolicyOverridesConfig; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig} / {@link CloudWatchLogsConfig}/ {@link CloudWatchLogsExclusionConfig}* - * - * @description - * Accelerator global CloudWatch Logs exclusion configuration - * - * @example - * ``` - * organizationalUnits: - * - Sandbox - * regions: - * - us-west-1 - * - us-west-2 - * accounts: - * - WorkloadAccount1 - * excludeAll: true - * logGroupNames: - * - 'test/*' - * - '/appA/*' - * - * ``` - */ -export interface ICloudWatchLogsExclusionConfig { - /** - * List of OUs that the exclusion will apply to - */ - readonly organizationalUnits?: t.NonEmptyString[]; - /** - * List of regions where the exclusion will be applied to. If no value is supplied, exclusion is applied to all enabled regions. - */ - readonly regions?: t.Region[]; - /** - * List of accounts where the exclusion will be applied to - */ - readonly accounts?: t.NonEmptyString[]; - /** - * Exclude replication on all logs. By default this is set to false. - * - * @remarks - * If undefined, this is set to false. When set to true, it disables replication on entire OU or account for that region. - * Setting OU as `Root` with no region specified and making this true will fail validation since that usage is redundant. - * Instead use the {@link CloudWatchLogsConfig | enable} parameter in cloudwatch log config which will disable replication across all accounts in all regions. - */ - readonly excludeAll?: boolean; - /** - * List of log groups names where the exclusion will be applied to - * - * @remarks - * Wild cards are supported. These log group names are added in the eventbridge payload which triggers lambda. If `excludeAll` is used then all logGroups are excluded and this parameter is not used. - */ - readonly logGroupNames?: t.NonEmptyString[]; -} - -/** - * *{@link IGlobalConfig} / {@link ILoggingConfig} / {@link ICloudWatchLogsConfig}* - * - * @description - * Accelerator global CloudWatch Logs logging configuration - * - * @remarks - * You can decide to use AWS KMS CMK or server-side encryption for the log data at rest. When this `encryption` property is undefined, the solution will deploy AWS KMS CMK to encrypt AWS CloudWatch log data at rest. - * You can use `deploymentTargets` to control target accounts and regions for the given `useCMK` configuration. - * please see [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/data-protection.html) or [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) for more information. - * - * Please review [CloudWatch Logs managed data identifiers for sensitive data types](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL-managed-data-identifiers.html) for more information. - * - * @example - * ``` - * cloudwatchLogs: - * dynamicPartitioning: path/to/filter.json - * # default is true, if undefined this is set to true - * # if set to false, no replication is performed which is useful in test or temporary environments - * enable: true - * encryption: - * useCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * replaceLogDestinationArn: arn:aws:logs:us-east-1:111111111111:destination:ReplaceDestination - * exclusions: - * # in these OUs do not do log replication - * - organizationalUnits: - * - Research - * - ProofOfConcept - * excludeAll: true - * # in these accounts exclude pattern testApp - * - accounts: - * - WorkloadAccount1 - * - WorkloadAccount1 - * logGroupNames: - * - testApp* - * # in these accounts exclude logs in specific regions - * - accounts: - * - WorkloadAccount1 - * - WorkloadAccount1 - * regions: - * - us-west-2 - * - eu-west-1 - * logGroupNames: - * - pattern1* - * dataProtection: - * managedDataIdentifiers: - * categories: - * - Credentials - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - * - */ -export interface ICloudWatchLogsConfig { - /** - * Declaration of Dynamic Partition for Kinesis Firehose. - * - * @remarks - * Kinesis firehose Dynamic Partition allows streaming Cloudwatch logs data to be assigned to a specific prefix. The input provided here is the path to log filter JSON file array. More details in the link: https://docs.aws.amazon.com/solutions/latest/landing-zone-accelerator-on-aws/centralized-logging.html - * Each item in the array is of the format - * ``` - * { "logGroupPattern": "LogGroupName", "s3Prefix": "s3-prefix" } - * ``` - * The logs end up in central logs bucket under prefix CloudWatchLogs. - * In the above example, the log group with `LogGroupName` will stream to `s3:///CloudWatchLogs/s3-prefix/` - * - * It is possible to use `*` for grouping log groups into same prefix. So, in the example below: - * ``` - * [{ "logGroupPattern": "Application*", "s3Prefix": "app" }] - * ``` - * The above will take log groups with name `ApplicationA`, `ApplicationB`, `ApplicationC` into s3 prefix `app`. - * Please make sure that `logGroupPattern` do not conflict each other as the logs are streamed to one destination and not replicated. - * For example, extending the above example to below - * ``` - * [{ "logGroupPattern": "Application*", "s3Prefix": "app" }, { "logGroupPattern": "App*", "s3Prefix": "apple" }] - * ``` - * In the above case, logs from `ApplicationA` can either end up in `app` or `apple`. They will not be replicated to both prefixes. - * - * For more information on Kinesis Firehose dynamic partitioning limits please refer to:: - * https://docs.aws.amazon.com/firehose/latest/dev/limits.html - * - * - */ - readonly dynamicPartitioning?: t.NonEmptyString; - /** - * Enable or disable CloudWatch replication - */ - readonly enable?: boolean; - /** - * Encryption setting for AWS CloudWatch log group data. - * - * @remarks - * For more information please refer {@link ServiceEncryptionConfig} - */ - readonly encryption?: IServiceEncryptionConfig; - /** - * Exclude Log Groups during replication - */ - readonly exclusions?: ICloudWatchLogsExclusionConfig[]; - /** - * Customer defined log subscription filter destination arn, that is associated with with the existing log group. - * Accelerator solution needs to disassociate this destination before configuring solution defined subscription filter destination. - * - * @default - * undefined - * - * @remarks - * When no value provided, accelerator solution will not attempt to remove existing customer defined log subscription filter destination. - * When existing log group(s) have two subscription filter destinations defined, and none of that is solution configured subscription filter destination, - * then solution will fail to configure log replication for such log groups and as a result pipeline will fail. - */ - readonly replaceLogDestinationArn?: t.NonEmptyString; - /** - * CloudWatch Log data protection configuration - */ - readonly dataProtection?: ICloudWatchDataProtectionConfig; -} - -/** - * *{@link GlobalConfig} / {@link LoggingConfig}* - * - * @description - * Global logging configuration - * - * @example - * ``` - * logging: - * account: LogArchive - * centralizedLoggingRegion: us-east-1 - * cloudtrail: - * enable: false - * organizationTrail: false - * sessionManager: - * sendToCloudWatchLogs: false - * sendToS3: true - * ``` - */ -export interface ILoggingConfig { - /** - * Accelerator logging account name. - * Accelerator use LogArchive account for global logging. - * This account maintains consolidated logs. - */ - readonly account: t.NonEmptyString; - /** - * Accelerator central logs bucket region name. - * Accelerator use CentralLogs bucket to store various log files, Accelerator created buckets and CWL replicates to CentralLogs bucket. - * CentralLogs bucket region is optional, when not provided this bucket will be created in Accelerator home region. - */ - readonly centralizedLoggingRegion?: t.NonEmptyString; - /** - * CloudTrail logging configuration - */ - readonly cloudtrail: ICloudTrailConfig; - /** - * SessionManager logging configuration - */ - readonly sessionManager: ISessionManagerConfig; - /** - * Declaration of a (S3 Bucket) Lifecycle rule configuration. - */ - readonly accessLogBucket?: IAccessLogBucketConfig; - /** - * Declaration of a (S3 Bucket) configuration. - */ - readonly assetBucket?: IAssetBucketConfig; - /** - * Declaration of a (S3 Bucket) Lifecycle rule configuration. - */ - readonly centralLogBucket?: ICentralLogBucketConfig; - /** - * Declaration of a (S3 Bucket) Lifecycle rule configuration. - */ - readonly elbLogBucket?: IElbLogBucketConfig; - /** - * CloudWatch Logging configuration. - */ - readonly cloudwatchLogs?: ICloudWatchLogsConfig; -} - -/** - * *{@link GlobalConfig} / {@link ReportConfig} / {@link CostAndUsageReportConfig}* - * - * @description - * CostAndUsageReport configuration - * - * @example - * ``` - * costAndUsageReport: - * compression: Parquet - * format: Parquet - * reportName: accelerator-cur - * s3Prefix: cur - * timeUnit: DAILY - * refreshClosedReports: true - * reportVersioning: CREATE_NEW_REPORT - * lifecycleRules: - * - enabled: true - * id: CostAndUsageBucketLifecycleRule-01 - * abortIncompleteMultipartUpload: 14 - * expiration: 3563 - * expiredObjectDeleteMarker: false - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * - enabled: true - * id: CostAndUsageBucketLifecycleRule-02 - * abortIncompleteMultipartUpload: 14 - * expiredObjectDeleteMarker: true - * noncurrentVersionExpiration: 3653 - * noncurrentVersionTransitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * transitions: - * - storageClass: GLACIER - * transitionAfter: 365 - * prefix: PREFIX - * ``` - */ -export interface ICostAndUsageReportConfig { - /** - * A list of strings that indicate additional content that Amazon Web Services includes in the report, such as individual resource IDs. - */ - readonly additionalSchemaElements?: t.NonEmptyString[]; - /** - * The compression format that Amazon Web Services uses for the report. - */ - readonly compression: string; - /** - * The format that Amazon Web Services saves the report in. - */ - readonly format: string; - /** - * The name of the report that you want to create. The name must be unique, is case sensitive, and can't include spaces. - */ - readonly reportName: t.NonEmptyString; - /** - * The prefix that Amazon Web Services adds to the report name when Amazon Web Services delivers the report. Your prefix can't include spaces. - */ - readonly s3Prefix: t.NonEmptyString; - /** - * The granularity of the line items in the report. - */ - readonly timeUnit: 'HOURLY' | 'DAILY' | 'MONTHLY' | string; - /** - * A list of manifests that you want Amazon Web Services to create for this report. - */ - readonly additionalArtifacts?: ('REDSHIFT' | 'QUICKSIGHT' | 'ATHENA' | string)[]; - /** - * Whether you want Amazon Web Services to update your reports after they have been finalized if Amazon Web Services detects charges related to previous months. These charges can include refunds, credits, or support fees. - */ - readonly refreshClosedReports: boolean; - /** - * Whether you want Amazon Web Services to overwrite the previous version of each report or to deliver the report in addition to the previous versions. - */ - readonly reportVersioning: 'CREATE_NEW_REPORT' | 'OVERWRITE_REPORT' | string; - /** - * Declaration of (S3 Bucket) Lifecycle rules. - */ - readonly lifecycleRules?: t.ILifecycleRule[]; -} - -/** - * *{@link GlobalConfig} / {@link ReportConfig} / {@link BudgetReportConfig} / {@link NotificationConfig}* - * - * @description - * Notification configuration - * - * @example - * ``` - * notifications: - * - type: ACTUAL - * thresholdType: PERCENTAGE - * threshold: 90 - * comparisonOperator: GREATER_THAN - * subscriptionType: EMAIL - * recipients: - * - myemail+pa1-budg@example.com - * - myemail+pa2-budg@example.com - * ``` - */ -export interface INotificationConfig { - /** - * The comparison that's used for the notification that's associated with a budget. - */ - readonly type: t.NotificationType | string; - /** - * The type of threshold for a notification.For ABSOLUTE_VALUE thresholds, - * AWS notifies you when you go over or are forecasted to go over your total cost threshold. - * For PERCENTAGE thresholds, AWS notifies you when you go over or are forecasted to go over a certain percentage of your forecasted spend. - * For example,if you have a budget for 200 dollars and you have a PERCENTAGE threshold of 80%, AWS notifies you when you go over 160 dollars. - */ - readonly thresholdType: t.ThresholdType | string; - /** - * The comparison that's used for this notification. - */ - readonly comparisonOperator: t.ComparisonOperator | string; - /** - * The type of threshold associate with a notification. - */ - readonly threshold?: number; - /** - * The address that AWS sends budget notifications to, either an SNS topic or an email. - * - * @deprecated - * This is a temporary property and it has been deprecated. - * Please use recipients property to specify address for budget notifications. - */ - readonly address?: t.NonEmptyString; - /** - * The recipients list that AWS sends budget notifications to, either an SNS topic or an email. - */ - readonly recipients?: t.NonEmptyString[]; - /** - * The type of notification that AWS sends to a subscriber. - */ - readonly subscriptionType: t.SubscriptionType | string; -} - -/** - * *{@link GlobalConfig} / {@link ReportConfig} / {@link BudgetReportConfig}* - * - * @description - * BudgetReport configuration - * - * @example - * ``` - * budgets: - * - name: accel-budget - * timeUnit: MONTHLY - * type: COST - * amount: 2000 - * includeUpfront: true - * includeTax: true - * includeSupport: true - * includeSubscription: true - * includeRecurring: true - * includeOtherSubscription: true - * includeDiscount: true - * includeCredit: false - * includeRefund: false - * useBlended: false - * useAmortized: false - * unit: USD - * notifications: - * - type: ACTUAL - * thresholdType: PERCENTAGE - * threshold: 90 - * comparisonOperator: GREATER_THAN - * subscriptionType: EMAIL - * recipients: - * - myemail+pa1-budg@example.com - * - myemail+pa2-budg@example.com - * ``` - */ -export interface IBudgetReportConfig { - /** - * The cost or usage amount that's associated with a budget forecast, actual spend, or budget threshold. - * - * @default 2000 - */ - readonly amount: number; - /** - * The name of a budget. The value must be unique within an account. BudgetName can't include : and \ characters. If you don't include value for BudgetName in the template, Billing and Cost Management assigns your budget a randomly generated name. - */ - readonly name: t.NonEmptyString; - /** - * The length of time until a budget resets the actual and forecasted spend. DAILY is available only for RI_UTILIZATION and RI_COVERAGE budgets. - */ - readonly timeUnit: 'DAILY' | 'MONTHLY' | 'QUARTERLY' | 'ANNUALLY' | string; - /** - * Specifies whether this budget tracks costs, usage, RI utilization, RI coverage, Savings Plans utilization, or Savings Plans coverage. - */ - readonly type: - | 'USAGE' - | 'COST' - | 'RI_UTILIZATION' - | 'RI_COVERAGE' - | 'SAVINGS_PLANS_UTILIZATION' - | 'SAVINGS_PLANS_COVERAGE' - | string; - /** - * Specifies whether a budget includes upfront RI costs. - * - * @default true - */ - readonly includeUpfront?: boolean; - /** - * Specifies whether a budget includes taxes. - * - * @default true - */ - readonly includeTax?: boolean; - /** - * Specifies whether a budget includes support subscription fees. - * - * @default true - */ - readonly includeSupport?: boolean; - /** - * Specifies whether a budget includes non-RI subscription costs. - * - * @default true - */ - readonly includeOtherSubscription?: boolean; - /** - * Specifies whether a budget includes subscriptions. - * - * @default true - */ - readonly includeSubscription?: boolean; - /** - * Specifies whether a budget includes recurring fees such as monthly RI fees. - * - * @default true - */ - readonly includeRecurring?: boolean; - /** - * Specifies whether a budget includes discounts. - * - * @default true - */ - readonly includeDiscount?: boolean; - /** - * Specifies whether a budget includes refunds. - * - * @default true - */ - readonly includeRefund?: boolean; - /** - * Specifies whether a budget includes credits. - * - * @default true - */ - readonly includeCredit?: boolean; - /** - * Specifies whether a budget uses the amortized rate. - * - * @default false - */ - readonly useAmortized?: boolean; - /** - * Specifies whether a budget uses a blended rate. - * - * @default false - */ - readonly useBlended?: boolean; - /** - * The type of notification that AWS sends to a subscriber. - * - * An enum value that specifies the target subscription type either EMAIL or SNS - */ - readonly subscriptionType?: t.SubscriptionType | string; - /** - * The unit of measurement that's used for the budget forecast, actual spend, or budget threshold, such as USD or GBP. - */ - readonly unit?: t.NonEmptyString; - /** - * The type of threshold for a notification. For ABSOLUTE_VALUE thresholds, - * AWS notifies you when you go over or are forecasted to go over your total cost threshold. - * For PERCENTAGE thresholds, AWS notifies you when you go over or are forecasted to go over a certain percentage of your forecasted spend. For example, - * if you have a budget for 200 dollars and you have a PERCENTAGE threshold of 80%, AWS notifies you when you go over 160 dollars. - */ - /** - * The comparison that's used for the notification that's associated with a budget. - */ - readonly notifications?: INotificationConfig[]; - /** - * List of OU's and accounts to be configured for Budgets configuration - */ - readonly deploymentTargets?: t.IDeploymentTargets; -} - -/** - * {@link GlobalConfig} / {@link ReportConfig} - * - * @description - * Accelerator report configuration - */ -export interface IReportConfig { - /** - * Cost and usage report configuration - * - * If you want to create cost and usage report with daily granularity of the line items in the report, you need to provide below value for this parameter. - * - * @example - * ``` - * costAndUsageReport: - * compression: Parquet - * format: Parquet - * reportName: accelerator-cur - * s3Prefix: cur - * timeUnit: DAILY - * refreshClosedReports: true - * reportVersioning: CREATE_NEW_REPORT - * lifecycleRules: - * storageClass: DEEP_ARCHIVE - * enabled: true - * multiPart: 1 - * expiration: 1825 - * deleteMarker: false - * nonCurrentExpiration: 366 - * transitionAfter: 365 - * ``` - */ - readonly costAndUsageReport?: ICostAndUsageReportConfig; - /** - * Budget report configuration - * - * If you want to create budget report with monthly granularity of the line items in the report and other default parameters , you need to provide below value for this parameter. - * - * @example - * ``` - * budgets: - * - name: accel-budget - * timeUnit: MONTHLY - * type: COST - * amount: 2000 - * includeUpfront: true - * includeTax: true - * includeSupport: true - * includeSubscription: true - * includeRecurring: true - * includeOtherSubscription: true - * includeDiscount: true - * includeCredit: false - * includeRefund: false - * useBlended: false - * useAmortized: false - * unit: USD - * notifications: - * - type: ACTUAL - * thresholdType: PERCENTAGE - * threshold: 90 - * comparisonOperator: GREATER_THAN - * subscriptionType: EMAIL - * address: myemail+pa-budg@example.com - * ``` - */ - readonly budgets?: IBudgetReportConfig[]; -} - -/** - * *{@link GlobalConfig} / {@link BackupConfig} / {@link VaultConfig}* - * - * @description - * AWS Backup vault configuration - * - * @example - * ``` - * - name: BackupVault - * deploymentTargets: - * organizationalUnits: - * - Root - * policy: policies/backup-vault-policy.json - * ``` - */ -export interface IVaultConfig { - /** - * Name that will be used to create the vault. - */ - readonly name: t.NonEmptyString; - - /** - * Which OU's or Accounts the vault will be deployed to - */ - readonly deploymentTargets: t.IDeploymentTargets; - - /** - * The path to a JSON file defining Backup Vault access policy - */ - readonly policy?: t.NonEmptyString; -} - -/** - * *{@link GlobalConfig} / {@link BackupConfig}* - * - * @description - * AWS Backup configuration - * - * @example - * ``` - * backup: - * vaults: - * - name: BackupVault - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IBackupConfig { - /** - * List of AWS Backup Vaults - */ - readonly vaults: IVaultConfig[]; -} - -/** - * - * *{@link GlobalConfig} / {@link SnsConfig} / {@link SnsTopicConfig}* - * - * @description - * SNS Topics Configuration - * - * To send CloudWatch Alarms and SecurityHub notifications - * you will need to configure at least one SNS Topic - * For SecurityHub notification you will need - * to set the deployment target to Root in order - * to receive notifications from all accounts - * - * @example - * ``` - * snsTopics: - * deploymentTargets: - * organizationalUnits: - * - Root - * topics: - * - name: Security - * emailAddresses: - * - SecurityNotifications@example.com - * ``` - */ -export interface ISnsTopicConfig { - /** - * *{@link GlobalConfig} / {@link SnsTopicConfig} / {@link TopicConfig}* - * - * SNS Topic Config - * - * @example - * ``` - * - name: Security - * emailAddresses: - * - SecurityNotifications@example.com - * ``` - */ - /** - * List of SNS Topics definition - */ - - /** - * SNS Topic Name - */ - readonly name: t.NonEmptyString; - - /** - * List of email address for notification - */ - readonly emailAddresses: t.EmailAddress[]; -} - -/** - * *{@link GlobalConfig} / {@link SnsConfig}* - * - * @description - * SNS Configuration - */ -export interface ISnsConfig { - /** - * Deployment targets for SNS topics - * SNS Topics will always be deployed to the Log Archive account - * email subscriptions will be in the Log Archive account - * All other accounts and regions will forward to the Logging account - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * List of SNS Topics - */ - readonly topics?: ISnsTopicConfig[]; -} - -/** - * *{@link GlobalConfig} / {@link AcceleratorMetadataConfig}* - * - * @description - * Accelerator Metadata - * - * Creates a new bucket in the log archive account to retrieve metadata for the accelerator environment - * - * @example - * ``` - * acceleratorMetadataConfig: - * enable: true - * account: Logging - * readOnlyAccessRoleArns: - * - arn:aws:iam::111111111111:role/test-access-role - * ``` - */ -export interface IAcceleratorMetadataConfig { - /** - * Enable Accelerator Metadata - */ - readonly enable: boolean; - readonly account: string; - readonly readOnlyAccessRoleArns: string[]; -} - -/** - * *{@link GlobalConfig} / {@link AcceleratorSettingsConfig}* - * - * @description - * Accelerator Settings Configuration - * Allows setting additional properties for accelerator - * - * @example - * ``` - * acceleratorSettings: - * maxConcurrentStacks: 250 - * ``` - * - */ -export interface IAcceleratorSettingsConfig { - /** - * Accelerator Settings - */ - - /** - * Set maximum number of concurrent stacks that can be processed at a time while transpiling the application. - * If no value is specified it defaults to 250 - */ - readonly maxConcurrentStacks?: number; -} - -/** - * *{@link GlobalConfig} / {@link ServiceEncryptionConfig}* - * - * @description - * AWS service encryption configuration settings - * - * @example - * ``` - * encryption: - * useCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IServiceEncryptionConfig { - /** - * Flag indicates whether Accelerator deployed AWS Service will use AWS KMS CMK for encryption or Service managed KMS. - * - * @remarks - * When set to `true`, the solution will create AWS KMS CMK which will be used by the service for encryption. Example, when flag set to `true` for AWS Lambda service, the solution will create AWS KMS CMK to encrypt lambda function environment variables, otherwise AWS managed key will be used for environment variables encryption. - * - * @default false - */ - readonly useCMK: boolean; - /** - * To control target environments (AWS Account and Region) for the given `useCMK` setting, you may optionally specify deployment targets. - * Leaving `deploymentTargets` undefined will apply `useCMK` setting to all accounts and enabled regions. - */ - readonly deploymentTargets?: t.IDeploymentTargets; -} - -/** - * *{@link IGlobalConfig} / {@link ILoggingConfig} / {@link ICloudWatchLogsConfig}/ {@link ICloudWatchDataProtectionConfig} / {@link ICloudWatchManagedDataProtectionIdentifierConfig}* - * - * @description - * AWS CloudWatch log data protection configuration - * - * @remarks - * Currently, only the [`Credentials`](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types-credentials.html) category is supported. - * @example - * ``` - * categories: - * - Credentials - * ``` - */ -export interface ICloudWatchManagedDataProtectionIdentifierConfig { - /** - * CloudWatch Logs managed data identifiers configuration. - * - * @remarks - * The solution supports only identifiers associated with the `Credentials` category, you can find more information about `Credentials` category [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/protect-sensitive-log-data-types-credentials.html) - * - * @default Credentials - */ - readonly categories: `${t.CloudWatchLogDataProtectionCategories}`[]; -} - -/** - * *{@link IGlobalConfig} / {@link ILoggingConfig} / {@link ICloudWatchLogsConfig}/ {@link ICloudWatchDataProtectionConfig}* - * - * @description - * AWS CloudWatch Log data protection configuration, you can find more information [here](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/faq/Logging/cwl/) - * - * @example - * ``` - * dataProtection: - * managedDataIdentifiers: - * categories: - * - Credentials - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface ICloudWatchDataProtectionConfig { - /** - * CloudWatch Logs managed data identifiers configuration. - * - * @remarks - * Please review [CloudWatch Logs managed data identifiers for sensitive data types](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL-managed-data-identifiers.html) for more information. - * - * @default Credentials - */ - readonly managedDataIdentifiers: ICloudWatchManagedDataProtectionIdentifierConfig; - /** - * To control target environments (AWS Account and Region) for the given `categories` setting, you may optionally specify deployment targets. - * Leaving `deploymentTargets` undefined will apply `categories` setting to all accounts and enabled regions. - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * (OPTIONAL) Indicates whether existing CloudWatch Log data protection configuration can be overwritten. - * - * @default false - */ - readonly overrideExisting?: boolean; -} - -/** - * *{@link GlobalConfig} / {@link lambdaConfig}* - * - * @description - * Lambda Function configuration settings - * - * @example - * ``` - * encryption: - * useCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface ILambdaConfig { - /** - * Encryption setting for AWS Lambda environment variables. - * - * @remarks - * For more information please refer {@link ServiceEncryptionConfig} - */ - readonly encryption?: IServiceEncryptionConfig; -} - -/** - * *{@link GlobalConfig} / {@link SsmInventoryConfig}* - * - * @description - * SSM Inventory Configuration - * - * @example - * ``` - * ssmInventoryConfig: - * enable: true - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * ``` - * - */ -export interface ISsmInventoryConfig { - /** - * Enable SSM Inventory - */ - readonly enable: boolean; - /** - * Configure the Deployment Targets - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * *{@link GlobalConfig} / {@link ssmParametersConfig}* - * - * @description - * SSM Parameters Configuration - * - * @example - * ``` - * ssmParameters: - * - deploymentTargets: - * organizationalUnits: - * - Workloads - * parameters: - * - name: MyWorkloadParameter - * path: /my/custom/path/variable - * value: 'MySSMParameterValue' - * ``` - * - */ -export interface ISsmParametersConfig { - /** - * A list of SSM parameters to create - */ - readonly parameters: ISsmParameterConfig[]; - /** - * Configure the Deployment Targets - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * *{@link GlobalConfig} / {@link ssmParametersConfig} / {@link ssmParameterConfig}* - * - * @description - * SSM Parameter Configuration - * - * @example - * ``` - * ssmParameters: - * - deploymentTargets: - * organizationalUnits: - * - Workloads - * parameters: - * - name: WorkloadsSsmParameter - * path: /my/custom/path/variable - * value: 'MySSMParameterValue' - * ``` - * - */ -export interface ISsmParameterConfig { - /** - * The friendly name of the SSM Parameter, this is used to generate the CloudFormation Logical Id. - */ - readonly name: t.NonEmptyString; - /** - * The path or name used when creating SSM Parameter. - */ - readonly path: t.NonEmptyString; - /** - * The value of the SSM Parameter - */ - readonly value: t.NonEmptyString; -} - -/** - * Accelerator global configuration - */ -export interface IGlobalConfig { - /** - * Accelerator home region name. The region where accelerator pipeline deployed. - * - * To use us-east-1 as home region for the accelerator, you need to provide below value for this parameter. - * Note: Variable HOME_REGION created for future usage of home region in the file - * - * @example - * ``` - * homeRegion: &HOME_REGION us-east-1 - * ``` - */ - readonly homeRegion: t.NonEmptyString; - /** - * List of AWS Region names where accelerator will be deployed. Home region must be part of this list. - * - * To add us-west-2 along with home region for accelerator deployment, you need to provide below value for this parameter. - * - * @example - * ``` - * enabledRegions: - * - *HOME_REGION - * - us-west-2 - * ``` - */ - readonly enabledRegions: t.Region[]; - /** - * This role trusts the management account, allowing users in the management - * account to assume the role, as permitted by the management account - * administrator. The role has administrator permissions in the new member - * account. - * - * Examples: - * - AWSControlTowerExecution - * - OrganizationAccountAccessRole - */ - readonly managementAccountAccessRole: t.NonEmptyString; - /** - * Global {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogsConcepts.html | CloudWatch Logs retention in days} configuration. - * - * @remarks - * This retention setting will be applied to all CloudWatch log groups created by the accelerator. - * Additionally, this retention setting will be applied to any CloudWatch log groups that already exist - * in the target environment if the log group's retention setting is LOWER than this configured value. - * - */ - readonly cloudwatchLogRetentionInDays: number; - /** - * ***Deprecated*** - * - * NOTICE: The configuration of CDK buckets is being moved - * to cdkOptions in the Global Config. This block is deprecated and - * will be removed in a future release - * @see {@link cdkOptionsConfig} - * - * To indicate workload accounts should utilize the cdk-assets S3 buckets in the management account, you need to provide below value for this parameter. - * - * @example - * ``` - * centralizeCdkBuckets: - * enable: true - * ``` - */ - readonly centralizeCdkBuckets?: ICentralizeCdkBucketsConfig; - /** - * AWS CDK options configuration. This lets you customize the operation of the CDK within LZA, specifically: - * - * centralizeBuckets: Enabling this option modifies the CDK bootstrap process to utilize a single S3 bucket per region located in the management account for CDK assets generated by LZA. Otherwise, CDK will create a new S3 bucket in every account and every region supported by LZA. - * useManagementAccessRole: Enabling this option modifies CDK operations to use the IAM role specified in the `managementAccountAccessRole` option in `global-config.yaml` rather than the default roles created by CDK. Default CDK roles will still be created, but will remain unused. Any stacks previously deployed by LZA will retain their [associated execution role](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html). For more information on these roles, please see [here](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-contract). - * - * @example - * ``` - * cdkOptions: - * centralizeBuckets: true - * useManagementAccessRole: true - * ``` - */ - readonly cdkOptions?: ICdkOptionsConfig; - /** - * Whether to enable termination protection for this stack. - */ - readonly terminationProtection?: boolean; - /** - * AWS Control Tower Landing Zone configuration - * - * To indicate environment has control tower enabled, you need to provide below value for this parameter. - * - * @example - * ``` - * controlTower: - * enable: true - * ``` - */ - readonly controlTower: IControlTowerConfig; - /** - * ExternalLandingZoneResourcesConfig. - * - * centralizeBuckets: Enabling this option modifies the CDK bootstrap process to utilize a single S3 bucket per region located in the management account for CDK assets generated by LZA. Otherwise, CDK will create a new S3 bucket in every account and every region supported by LZA. - * - * @example - * ``` - * externalLandingZoneResources: - * importExternalLandingZoneResources: false - * ``` - */ - readonly externalLandingZoneResources?: IExternalLandingZoneResourcesConfig; - /** - * Accelerator logging configuration - * - * To enable organization trail and session manager logs sending to S3, you need to provide below value for this parameter. - * - * @example - * ``` - * logging: - * account: LogArchive - * cloudtrail: - * enable: false - * organizationTrail: false - * cloudtrailInsights: - * apiErrorRateInsight: true - * apiCallRateInsight: true - * sessionManager: - * sendToCloudWatchLogs: false - * sendToS3: true - * cloudwatchLogs: - * dynamicPartitioning: logging/dynamic-partition.json - * ``` - */ - readonly logging: ILoggingConfig; - /** - * Report configuration - * - * To enable budget report along with cost and usage report, you need to provide below value for this parameter. - * - * @example - * ``` - * reports: - * costAndUsageReport: - * compression: Parquet - * format: Parquet - * reportName: accelerator-cur - * s3Prefix: cur - * timeUnit: DAILY - * refreshClosedReports: true - * reportVersioning: CREATE_NEW_REPORT - * budgets: - * - name: accel-budget - * timeUnit: MONTHLY - * type: COST - * amount: 2000 - * includeUpfront: true - * includeTax: true - * includeSupport: true - * includeSubscription: true - * includeRecurring: true - * includeOtherSubscription: true - * includeDiscount: true - * includeCredit: false - * includeRefund: false - * useBlended: false - * useAmortized: false - * unit: USD - * notifications: - * - type: ACTUAL - * thresholdType: PERCENTAGE - * threshold: 90 - * comparisonOperator: GREATER_THAN - * subscriptionType: EMAIL - * address: myemail+pa-budg@example.com - * ``` - */ - readonly reports?: IReportConfig; - /** - * AWS Service Quota - Limit configuration - * - * To enable limits within service quota, you need to provide below value for this parameter. - * - * @example - * ``` - * limits: - * - serviceCode: lambda - * quotaCode: L-2ACBD22F - * desiredValue: 2000 - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * ``` - */ - readonly limits?: IServiceQuotaLimitsConfig[]; - /** - * Backup Vaults Configuration - * - * To generate vaults, you need to provide below value for this parameter. - * - * @example - * ``` - * backup: - * vaults: - * - name: MyBackUpVault - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ - readonly backup?: IBackupConfig; - /** - * SNS Topics Configuration - * - * To send CloudWatch Alarms and SecurityHub notifications - * you will need to configure at least one SNS Topic - * For SecurityHub notification you will need - * to set the deployment target to Root in order - * to receive notifications from all accounts - * - * @example - * ``` - * snsTopics: - * deploymentTargets: - * organizationalUnits: - * - Root - * topics: - * - name: Security - * emailAddresses: - * - SecurityNotifications@example.com - * ``` - */ - readonly snsTopics?: ISnsConfig; - /** - * SSM Inventory Configuration - * - * [EC2 prerequisites](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-walk.html) - * [Connectivity prerequisites](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-prereqs.html) - * - * @example - * ``` - * ssmInventory: - * enable: true - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * ``` - * - */ - readonly ssmInventory?: ISsmInventoryConfig; - /** - * Custom Tags for all resources created by Landing Zone Accelerator that can be tagged. - * - * @example - * ``` - * tags: - * - key: Environment - * value: Dev - * - key: ResourceOwner - * value: AcmeApp - * - key: CostCenter - * value: '123' - * ``` - **/ - readonly tags?: t.ITag[]; - /** - * SSM parameter configurations - * - * Create SSM parameters through the LZA. Parameters can be deployed to Organizational Units or Accounts using deploymentTargets - * - * @example - * ``` - * ssmParameters: - * - deploymentTargets: - * organizationalUnits: - * - Workloads - * parameters: - * - name: WorkloadParameter - * path: /my/custom/path/variable - * value: 'MySSMParameterValue' - * ``` - */ - readonly ssmParameters?: ISsmParametersConfig[]; - /** - * Accelerator Metadata Configuration - * Creates a bucket in the logging account to enable accelerator metadata collection - * - * @example - * ``` - * acceleratorMetadata: - * enable: true - * account: Logging - * ``` - * - */ - readonly acceleratorMetadata?: IAcceleratorMetadataConfig; - /** - * Accelerator Settings Configuration - * Allows setting additional properties for accelerator - * - * @example - * ``` - * acceleratorSettings: - * maxConcurrentStacks: 250 - * ``` - * - */ - readonly acceleratorSettings?: IAcceleratorSettingsConfig; - - /** - * AWS Lambda Function environment variables encryption configuration options. - * - * @remarks - * You can decide to use AWS KMS CMK or AWS managed key for Lambda function environment variables encryption. When this property is undefined, the solution will deploy AWS KMS CMK to encrypt function environment variables. - * You can use `deploymentTargets` to control target accounts and regions for the given `useCMK` configuration. - * - * For more information please see [here](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-encryption) - * - * @example - * ``` - * lambda: - * encryption: - * useCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ - readonly lambda?: ILambdaConfig; - /** - * AWS S3 global configuration options. - * - * @remarks - * You can decide to create AWS KMS CMK for AWS S3 server side encryption. When this property is undefined, the solution will deploy AWS KMS CMK to encrypt AWS S3 bucket. - * You can use `deploymentTargets` to control target accounts and regions for the given `createCMK` configuration. - * This configuration is not applicable to LogArchive's central logging region, because the solution deployed CentralLogs bucket always encrypted with AWS KMS CMK. - * This configuration is not applicable to the Management account Asset bucket in the home region. This bucket will always have a key generated and applied to the bucket if it is created. - * This configuration is not applicable to the assets S3 bucket if the bucket is created. This bucket will always have a key generated and applied. - * - * For more information please see [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryption.html) - * - * @example - * ``` - * s3: - * createCMK: true - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ - readonly s3?: IS3GlobalConfig; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/iam-config.ts b/source/packages/@aws-accelerator/config/lib/models/iam-config.ts deleted file mode 100644 index 3963a8c..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/iam-config.ts +++ /dev/null @@ -1,1510 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; - -/** - * *{@link IamConfig} / {@link PolicySetConfig} / {@link PolicyConfig}* - * - * @description - * Use this configuration to define accelerator managed IAM managed policies. - * - * @remarks Initial set of permissions to add to this policy document are read from the input file provided in policy JSON file. - * - * @example - * ``` - * - name: Default-Boundary-Policy - * policy: path/to/policy.json - * ``` - */ -export interface IPolicyConfig { - /** - * The name of the managed policy. - */ - readonly name: t.NonEmptyString; - /** - * A JSON file containing policy boundary definition. - */ - readonly policy: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link SamlProviderConfig}* - * - * @description - * SAML provider configuration - * - * @example - * ``` - * providers: - * - name: accelerator-provider - * metadataDocument: path/to/metadata.xml - * ``` - */ -export interface ISamlProviderConfig { - /** - * The name of the provider to create. - * - * This parameter allows a string of characters consisting of upper and lowercase alphanumeric characters with no spaces. You can also include any of the following characters: _+=,.@- - * - * Length must be between 1 and 128 characters. - * - * @default a CloudFormation generated name - */ - readonly name: t.NonEmptyString; - /** - * SAML metadata document XML file, this file must be present in config repository - */ - readonly metadataDocument: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link UserSetConfig} / {@link UserConfig}* - * - * @description - * IAM User configuration - * - * @example - * ``` - * - username: accelerator-user - * boundaryPolicy: Default-Boundary-Policy - * group: Admins - * ``` - */ -export interface IUserConfig { - /** - * A name for the IAM user. For valid values, see the UserName parameter for the CreateUser action in the IAM API Reference. - * If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the user name. - * - * If you specify a name, you cannot perform updates that require replacement of this resource. - * You can perform updates that require no or some interruption. If you must replace the resource, specify a new name. - */ - readonly username: t.NonEmptyString; - /** - * Group to add this user to. - */ - readonly group: t.NonEmptyString; - /** - * AWS supports permissions boundaries for IAM entities (users or roles). - * A permissions boundary is an advanced feature for using a managed policy to set the maximum permissions that an identity-based policy can grant to an IAM entity. - * An entity's permissions boundary allows it to perform only the actions that are allowed by both its identity-based policies and its permissions boundaries. - * - * Permission boundary is derived from iam-policies/boundary-policy.json file in config repository - */ - readonly boundaryPolicy?: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link UserSetConfig}* - * - * @description - * User set configuration - * - * ``` - * userSets: - * - deploymentTargets: - * accounts: - * - Management - * users: - * - username: accelerator-user - * boundaryPolicy: Default-Boundary-Policy - * group: Admins - * ``` - */ -export interface IUserSetConfig { - /** - * User set's deployment target - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * List os user objects - */ - readonly users: IUserConfig[]; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link PermissionsBoundaryConfig} / {@link CustomerManagedPolicyReferenceConfig}* - * - * @description - * Customer Managed Policy Reference Config - * - * @example - * ``` - * permissionsBoundary: - customerManagedPolicy: - name: AcceleratorManagedPolicy - path: / - * ``` - */ -export interface ICustomerManagedPolicyReferenceConfig { - /** - * Identity Center PermissionSet permissions boundary customer managed policy name. - * - * @remarks The name of the IAM policy that you have configured in each account where you want to deploy your permission set. - * If you want use accelerator deployed customer managed policy, specify the name from policySets object of iam-config.yaml file. - * - * {@link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sso-permissionset-customermanagedpolicyreference.html#cfn-sso-permissionset-customermanagedpolicyreference-name | CustomerManagedPolicyReference} name. - */ - readonly name: t.NonEmptyString; - /** - * The path to the IAM policy that you have configured in each account where you want to deploy your permission set. - * - * @remarks The default is `/` . For more information, see [Friendly names and paths](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-friendly-names) in the *IAM User Guide* . - */ - readonly path?: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link PermissionsBoundaryConfig}* - * - * @description - * Specify either customerManagedPolicy to use the name and path of a customer managed policy, or managedPolicy to use the ARN of an AWS managed policy. - * - * @example - * ``` - * permissionsBoundary: - * customerManagedPolicy: - * name: AcceleratorManagedPolicy - * path: / - * awsManagedPolicyName: PowerUserAccess - * ``` - */ -export interface IPermissionsBoundaryConfig { - /** - * The AWS managed policy name that you want to attach to a permission set as a permissions boundary. - * - * @remarks You must have an IAM policy that matches the name and path in each AWS account where you want to deploy your permission set. - * - */ - readonly awsManagedPolicyName?: t.NonEmptyString; - /** - * Specifies the name and path of a customer managed policy. - * - * @remarks You must have an IAM policy that matches the name and path in each AWS account where you want to deploy your permission set. - * - * {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sso-permissionset-permissionsboundary.html#cfn-sso-permissionset-permissionsboundary-customermanagedpolicyreference | CustomerManagedPolicyReference} - * - * @see {@link IamConfig} / {@link IdentityCenterConfig} / {@link PermissionsBoundaryConfig} / {@link CustomerManagedPolicyReferenceConfig} - */ - readonly customerManagedPolicy?: ICustomerManagedPolicyReferenceConfig; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterPoliciesConfig}* - * - * @description - * Identity Center Permission Set Policy Configuration - * - * @example - * ``` - * policies: - * awsManaged: - * - arn:aws:iam::aws:policy/AdministratorAccess - * - PowerUserAccess - * customerManaged: - * - ResourceConfigurationCollectorPolicy - * acceleratorManaged: - * - AcceleratorManagedPolicy01 - * - AcceleratorManagedPolicy02 - * inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - * permissionsBoundary: - * customerManagedPolicy: - * name: AcceleratorManagedPolicy - * path: / - * awsManagedPolicyName: PowerUserAccess - * ``` - */ -export interface IIdentityCenterPoliciesConfig { - /** - * List of AWS managed policies that would be attached to permission set. - * - * @remarks This list can contain policy name or policy arn - */ - readonly awsManaged?: t.NonEmptyString[]; - /** - * List of the names and paths of the customer managed policies that would be attached to permission set. - * - * @remarks This list can contain only existing customer managed policy names, Accelerator expect these policies would be present prior deployment. - */ - readonly customerManaged?: t.NonEmptyString[]; - /** - * List of the names customer managed policies that would be attached to permission set. - * - * @remarks Specify the names of policies created by Accelerator solution. Solution will create these policies before attaching to permission set. - * To create policies through Accelerator and attach to permission set, you need to specify policies in policySets object of iam-config.yaml file with identityCenterDependency flag on. - * Accelerator managed policy name must be part of policySets object of iam-config.yaml file. - */ - readonly acceleratorManaged?: t.NonEmptyString[]; - /** - * The inline policy that is attached to the permission set. - * - * {@link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-permissionset.html#cfn-sso-permissionset-inlinepolicy | InlinePolicy} reference - */ - readonly inlinePolicy?: t.NonEmptyString; - /** - * - * Specifies the configuration of the AWS managed or customer managed policy that you want to set as a permissions boundary. - * - * @remarks Specify either customerManagedPolicy to use the name and path of a customer managed policy, or managedPolicy name to use the ARN of an AWS managed policy. - * - * @see {@link IamConfig} / {@link IdentityCenterConfig} / {@link PermissionsBoundaryConfig} - */ - readonly permissionsBoundary?: IPermissionsBoundaryConfig; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterPermissionSetConfig}* - * - * @description - * Identity Center Permission Set Configuration - * - * @example - * ``` - * name: identityCenter1 - * identityCenterPermissionSets: - * - name: PermissionSet1 - * policies: - * awsManaged: - * - arn:aws:iam::aws:policy/AdministratorAccess - * - PowerUserAccess - * customerManaged: - * - ResourceConfigurationCollectorPolicy - * acceleratorManaged: - * - AcceleratorManagedPolicy01 - * - AcceleratorManagedPolicy02 - * inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - * permissionsBoundary: - * customerManagedPolicy: - * name: AcceleratorManagedPolicy - * path: / - * awsManagedPolicyName: PowerUserAccess - * sessionDuration: 60 - * ``` - */ -export interface IIdentityCenterPermissionSetConfig { - /** - * A name for the Identity Center Permission Set Configuration - */ - readonly name: t.NonEmptyString; - /** - * Policy Configuration for Customer Managed Permissions and AWS Managed Permissions - * - * @see {@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterPoliciesConfig} - */ - readonly policies?: IIdentityCenterPoliciesConfig; - /** - * A number value (in minutes). The length of time that the application user sessions are valid for in the ISO-8601 standard. - * @default undefined - */ - readonly sessionDuration?: number; -} - -/** - * *{@link IamConfig} / {@link GroupConfig} | {@link RoleConfig} / {@link PoliciesConfig}* - * - * @description - * IAM policies configuration - * - * @example - * ``` - * awsManaged: - * - AdministratorAccess - * customerManaged: - * - PolicyName - * ``` - */ -export interface IPoliciesConfig { - /** - * List of AWS managed policies. Values can be policy arn or policy name - */ - readonly awsManaged?: t.NonEmptyString[]; - /** - * List of Customer managed policies - */ - readonly customerManaged?: t.NonEmptyString[]; -} - -/** - * *{@link IamConfig} / {@link GroupSetConfig} / {@link GroupConfig}* - * - * @description - * IAM group configuration - * - * @example - * ``` - * - name: Admins - * policies: - * awsManaged: - * - AdministratorAccess - * ``` - */ -export interface IGroupConfig { - /** - * A name for the IAM group. For valid values, see the GroupName parameter for the CreateGroup action in the IAM API Reference. - * If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the group name. - * - * If you specify a name, you must specify the CAPABILITY_NAMED_IAM value to acknowledge your template's capabilities. - * For more information, see Acknowledging IAM Resources in AWS CloudFormation Templates. - */ - readonly name: t.NonEmptyString; - /** - * List of policy objects - */ - readonly policies?: IPoliciesConfig; -} - -/** - * *{@link IamConfig} / {@link GroupSetConfig}* - * - * @description - * IAM group set configuration - * - * @example - * ``` - * groupSets: - * - deploymentTargets: - * accounts: - * - Management - * groups: - * - name: Admins - * policies: - * awsManaged: - * - AdministratorAccess - * ``` - */ -export interface IGroupSetConfig { - /** - * Group set's deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * List of IAM group objects - */ - readonly groups: IGroupConfig[]; -} - -/** - * *{@link IamConfig} / {@link RoleSetConfig} / {@link RoleConfig} / {@link AssumedByConfig}* - * - * @description - * AssumedBy configuration - * - * Service principal: - * @example - * ``` - * - principal: ec2.amazonaws.com - * type: service - * ``` - * - * Account principals can be defined using either the account ID (with quotes), the account arn or the name assigned to the account in the accounts-config.yaml. - * - * - * @example - * ``` - * assumedBy: - * - type: account - * principal: '111111111111' - * ``` - * @example - * ``` - * assumedBy: - * - type: account - * principal: Audit - * ``` - * @example - * ``` - * assumedBy: - * - type: account - * principal: 'arn:aws:iam::111111111111:root' - * `` - * @example - * ``` - * assumedBy: - * - type: principalArn - * principal: 'arn:aws:iam::111122223333:role/path/role-name' - * `` - * @remarks In order to use a Principal ARN in the assume role policy, the principal must exist. - * - */ -export interface IAssumedByConfig { - /** - * IAM principal of either service, account, principalArn or provider type. - * - * IAM principal of sns service type (i.e. new ServicePrincipal('sns.amazonaws.com')), which can assume this role. - */ - readonly type: t.AssumedByType; - /** - * Type of IAM principal type like service, account, principalArn or provider, which can assume this role. - */ - readonly principal?: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link RoleSetConfig} / {@link RoleConfig}* - * - * @description - * IAM Role configuration - * - * @example - * ``` - * - name: EC2-Default-SSM-AD-Role - * assumedBy: - * - principal: ec2.amazonaws.com - * type: service - * boundaryPolicy: Default-Boundary-Policy - * instanceProfile: true - * policies: - * awsManaged: - * - AmazonSSMManagedInstanceCore - * - AmazonSSMDirectoryServiceAccess - * - CloudWatchAgentServerPolicy - * ``` - */ -export interface IRoleConfig { - /** - * A name for the role - */ - readonly name: t.NonEmptyString; - /** - * Indicates whether role is used for EC2 instance profile - */ - readonly instanceProfile?: boolean; - /** - * List of IDs that the role assumer needs to provide one of when assuming this role - * @remarks For more information about granting third party access to assume an IAM Role, please reference the [documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). - * From the documentation, this will apply a similar stanza in the assume role policy document of your IAM role. - * - * ``` - * "Principal": {"AWS": "Example Corp's AWS account ID"}, - * "Condition": {"StringEquals": {"sts:ExternalId": "Unique ID Assigned by Example Corp"}} - * ``` - * @example - * ``` - * - name: Test-Arn-Role - * assumedBy: - * - type: principalArn - * principal: "arn:aws:iam::555555555555:user/TestUser" - * externalIds: - * - "777777777777" - * ``` - */ - readonly externalIds?: t.NonEmptyString[]; - /** - * AssumedBy configuration - */ - readonly assumedBy: IAssumedByConfig[]; - /** - * List of policies for the role - */ - readonly policies?: IPoliciesConfig; - /** - * A permissions boundary configuration - */ - readonly boundaryPolicy?: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link RoleSetConfig}* - * - * @description - * Role set configuration - * - * @example - * ``` - * roleSets: - * - deploymentTargets: - * organizationalUnits: - * - Root - * roles: - * - name: EC2-Default-SSM-AD-Role - * assumedBy: - * - principal: ec2.amazonaws.com - * type: service - * boundaryPolicy: Default-Boundary-Policy - * instanceProfile: true - * policies: - * awsManaged: - * - AmazonSSMManagedInstanceCore - * - AmazonSSMDirectoryServiceAccess - * - CloudWatchAgentServerPolicy - * ``` - */ -export interface IRoleSetConfig { - /** - * Role set deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * The path to the role - */ - readonly path?: t.NonEmptyString; - /** - * List of role objects - */ - readonly roles: IRoleConfig[]; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterAssignmentPrincipalConfig}* - * - * @description - * Identity Center Permission Set Assignment Principal Configuration - * - * @remarks Use this configuration to provide principals of USER or GROUP type for assignment. - * - * @example - * ``` - * principals: - * - type: USER - * name: accelerator - * - type: GROUP - * name: admin - * ``` - */ -export interface IIdentityCenterAssignmentPrincipalConfig { - /** - * Assignment principal type - * - * @remarks Possible value for this property can be USER or GROUP - */ - readonly type: t.NonEmptyString; - /** - * Name of the principal - * - * @remarks Identity Center user or group name - */ - readonly name: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterAssignmentConfig}* - * - * @description - * Identity Center Assignment Configuration - * - * @example - * ``` - * identityCenterAssignments: - * - name: Assignment1 - * permissionSetName: PermissionSet1 - * principals: - * - type: USER - * name: accelerator - * - type: GROUP - * name: admin - * principalId: "a4e81468-1001-70f0-9c12-56a6aa967ca4" - * principalType: USER - * deploymentTargets: - * accounts: - * - LogArchive - * - name: Assignment2 - * permissionSetName: PermissionSet2 - * principals: - * - type: USER - * name: accelerator - * - type: GROUP - * name: admin - * principalId: "a4e81468-1001-70f0-9c12-56a6aa967ca4" - * principalType: GROUP - * deploymentTargets: - * organizationalUnits: - * - Security - * ``` - */ -export interface IIdentityCenterAssignmentConfig { - /** - * Permission Set name that will be used for the Assignment. - */ - readonly permissionSetName: t.NonEmptyString; - /** - * PrincipalId that will be used for the Assignment - * - * @deprecated - * This is a temporary property and it has been deprecated. - * Please use principals property to specify principal name for assignment. - */ - readonly principalId?: t.NonEmptyString; - /** - * PrincipalType that will be used for the Assignment - * - * @deprecated - * This is a temporary property and it has been deprecated. - * Please use principals property to specify principal type for assignment. - */ - readonly principalType?: t.PrincipalType; - /** - * Assignment principal configuration list. - * - * @remarks - * Assignment principal's type can be either USER or GROUP. - * Every principal in the list needs type and the name of principal. - * - * @example - * ``` - * principal: - * - type: USER - * name: accelerator - * - type: GROUP - * name: admin - * ``` - * - * @see {@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterAssignmentPrincipalConfig} - */ - readonly principals?: IIdentityCenterAssignmentPrincipalConfig[]; - /** - * Identity Center assignment deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * The Name for the Assignment. - */ - readonly name: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link IdentityCenterConfig}* - * - * @description - * Identity Center Configuration - * - * @example - * ``` - * identityCenter: - * name: identityCenter1 - * delegatedAdminAccount: Audit - * identityCenterPermissionSets: - * - name: PermissionSet1 - * policies: - * awsManaged: - * - arn:aws:iam::aws:policy/AdministratorAccess - * - PowerUserAccess - * customerManaged: - * - ResourceConfigurationCollectorPolicy - * acceleratorManaged: - * - AcceleratorManagedPolicy01 - * - AcceleratorManagedPolicy02 - * inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - * permissionsBoundary: - * customerManagedPolicy: - * name: AcceleratorManagedPolicy - * path: / - * awsManagedPolicyName: PowerUserAccess - * sessionDuration: 60 - * identityCenterAssignments: - * - name: Assignment1 - * permissionSetName: PermissionSet1 - * principals: - * - type: USER - * name: accelerator - * - type: GROUP - * name: admin - * deploymentTargets: - * accounts: - * - LogArchive - * ``` - */ -export interface IIdentityCenterConfig { - /** - * A name for the Identity Center Configuration - */ - readonly name: t.NonEmptyString; - /** - * Override for Delegated Admin Account - * - * @remarks All Accelerator managed Identity Center Permission Sets and Assignments must be removed before changing the service's delegated administrator. To change this property: - * - * Remove or comment out the existing PermissionSets and Assignments from identityCenter configuration from iam-config.yaml. - * Important: You must leave identityCenter, name, and delegatedAdminAccount. - * Run the pipeline to remove the resources. - * Add or uncomment the desired identityCenter configuration to iam-config.yaml. - * Set the delegatedAdminAccount property to the desired new delegated administrator account. - * Run the pipeline to update the delegated admin and create Identity Center resources. - */ - readonly delegatedAdminAccount?: t.NonEmptyString; - /** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterPermissionSetConfig}* - * - * @description - * List of PermissionSets - */ - readonly identityCenterPermissionSets?: IIdentityCenterPermissionSetConfig[]; - /** - * *{@link IamConfig} / {@link IdentityCenterConfig} / {@link IdentityCenterAssignmentConfig}* - * - * @description - * List of Assignments - */ - readonly identityCenterAssignments?: IIdentityCenterAssignmentConfig[]; -} - -/** - * *{@link IamConfig} / {@link PolicySetConfig}* - * - * @description - * Policy set configuration - * - * @example - * ``` - * policySets: - * - deploymentTargets: - * organizationalUnits: - * - Root - * identityCenterDependency: false - * policies: - * - name: Default-Boundary-Policy - * policy: path/to/policy.json - * ``` - */ -export interface IPolicySetConfig { - /** - * Policy set deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * Flag indicates if the policy is used in Identity Center PermissionSet assignments. - * - * @remarks When the policy is used in Identity Center PermissionSet assignments, policy must be present in each deployment target accounts of identityCenterAssignments. - * When this flag is set to true policy is created in dependency stack. - */ - readonly identityCenterDependency?: boolean; - /** - * List of Policies - * - * @remarks Use this configuration to define accelerator managed IAM managed policies. - * - * @see {@link IamConfig} / {@link PolicySetConfig} / {@link PolicyConfig} - */ - readonly policies: IPolicyConfig[]; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig} / {@link ActiveDirectoryUserConfig}* - * - * @description - * Active directory user configuration - */ -export interface IActiveDirectoryUserConfig { - /** - * Active directory user name - */ - readonly name: t.NonEmptyString; - /** - * Active directory user email - */ - readonly email: t.NonEmptyString; - /** - * Active directory user group names - */ - readonly groups: t.NonEmptyString[]; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig} / {@link ActiveDirectoryPasswordPolicyConfig}* - * - * @description - * Managed active directory user password policy configuration - */ -export interface IActiveDirectoryPasswordPolicyConfig { - readonly history: number; - readonly maximumAge: number; - readonly minimumAge: number; - readonly minimumLength: number; - readonly complexity: boolean; - readonly reversible: boolean; - readonly failedAttempts: number; - readonly lockoutDuration: number; - readonly lockoutAttemptsReset: number; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig} / {@link ActiveDirectoryConfigurationInstanceUserDataConfig}* - * - * @description - * User data scripts to create users, groups, password policy. - * - * Accelerator can provision users, groups when following user data scripts are provided, these scripts are part of Accelerator sample configuration - * @example - * ``` - * userDataScripts: - * - scriptName: JoinDomain - * scriptFilePath: ad-config-scripts/Join-Domain.ps1 - * - scriptName: InitializeRDGW ## Do not Need - * scriptFilePath: ad-config-scripts/Initialize-RDGW.ps1 - * - scriptName: AWSQuickStart - * scriptFilePath: ad-config-scripts/AWSQuickStart.psm1 - * - scriptName: ADGroupSetup - * scriptFilePath: ad-config-scripts/AD-group-setup.ps1 - * - scriptName: ADUserSetup - * scriptFilePath: ad-config-scripts/AD-user-setup.ps1 - * - scriptName: ADUserGroupSetup - * scriptFilePath: ad-config-scripts/AD-user-group-setup.ps1 - * - scriptName: ADGroupGrantPermissionsSetup - * scriptFilePath: ad-config-scripts/AD-group-grant-permissions-setup.ps1 - * - scriptName: ADConnectorPermissionsSetup - * scriptFilePath: ad-config-scripts/AD-connector-permissions-setup.ps1 - * - scriptName: ConfigurePasswordPolicy - * scriptFilePath: ad-config-scripts/Configure-password-policy.ps1 - * ``` - */ -export interface IActiveDirectoryConfigurationInstanceUserDataConfig { - /** - * Friendly name for the user data script - */ - readonly scriptName: t.NonEmptyString; - /** - * Script file path - */ - readonly scriptFilePath: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig}* - * - * @description - * Active directory configuration instance configuration. The machine will be used to configure and manage active directory configuration. - * Accelerator can create user, groups when following configuration provided - * - * @example - * - * ``` - * activeDirectoryConfigurationInstance: - * instanceType: t3.large - * vpcName: MyVpc - * subnetName: subnet - * imagePath: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base - * securityGroupInboundSources: - * - 10.0.0.0/16 - * instanceRole: EC2-Default-SSM-AD-Role - * enableTerminationProtection: false - * userDataScripts: - * - scriptName: JoinDomain - * scriptFilePath: ad-config-scripts/Join-Domain.ps1 - * - scriptName: InitializeRDGW ## Do not Need - * scriptFilePath: ad-config-scripts/Initialize-RDGW.ps1 - * - scriptName: AWSQuickStart - * scriptFilePath: ad-config-scripts/AWSQuickStart.psm1 - * - scriptName: ADGroupSetup - * scriptFilePath: ad-config-scripts/AD-group-setup.ps1 - * - scriptName: ADUserSetup - * scriptFilePath: ad-config-scripts/AD-user-setup.ps1 - * - scriptName: ADUserGroupSetup - * scriptFilePath: ad-config-scripts/AD-user-group-setup.ps1 - * - scriptName: ADGroupGrantPermissionsSetup - * scriptFilePath: ad-config-scripts/AD-group-grant-permissions-setup.ps1 - * - scriptName: ADConnectorPermissionsSetup - * scriptFilePath: ad-config-scripts/AD-connector-permissions-setup.ps1 - * - scriptName: ConfigurePasswordPolicy - * scriptFilePath: ad-config-scripts/Configure-password-policy.ps1 - * adGroups: - * - aws-Provisioning - * - aws-Billing - * adPerAccountGroups: - * - "*-Admin" - * - "*-PowerUser" - * - "*-View" - * adConnectorGroup: ADConnector-grp - * sharedAccounts: - * - Management - * - Audit - * - LogArchive - * adPasswordPolicy: - * history: 24 - * maximumAge: 90 - * minimumAge: 1 - * minimumLength: 14 - * complexity: true - * reversible: false - * failedAttempts: 6 - * lockoutDuration: 30 - * lockoutAttemptsReset: 30 - * adUsers: - * - name: adconnector-usr - * email: example-adconnector-usr@example.com - * groups: - * - ADConnector-grp - * - name: user1 - * email: example-user1@example.com - * groups: - * - aws-Provisioning - * - "*-View" - * - "*-Admin" - * - "*-PowerUser" - * - AWS Delegated Administrators - * - name: user2 - * email: example-user2@example.com - * groups: - * - aws-Provisioning - * - "*-View" - * ``` - */ -export interface IActiveDirectoryConfigurationInstanceConfig { - /** - * Ec2 instance type - */ - readonly instanceType: t.NonEmptyString; - /** - * Ec2 instance vpc name - */ - readonly vpcName: t.NonEmptyString; - /** - * Ec2 image path - */ - readonly imagePath: t.NonEmptyString; - /** - * Ec2 security group inbound sources - * - */ - readonly securityGroupInboundSources: t.NonEmptyString[]; - /** - * Ec2 instance role name - */ - readonly instanceRole: t.NonEmptyString; - /** - * Flag for Ec2 instance enable api termination protection - * @default false - */ - readonly enableTerminationProtection?: boolean; - /** - * Ec2 instance subnet name - */ - readonly subnetName: t.NonEmptyString; - /** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig} / {@link ActiveDirectoryConfigurationInstanceUserDataConfig}* - * - * @description - * Instance user data script configuration - */ - readonly userDataScripts: IActiveDirectoryConfigurationInstanceUserDataConfig[]; - /** - * Active directory group list - */ - readonly adGroups: t.NonEmptyString[]; - /** - * Active directory per account group list - */ - readonly adPerAccountGroups: t.NonEmptyString[]; - /** - * Active directory connector group - */ - readonly adConnectorGroup: t.NonEmptyString; - /** - * Active directory user list - */ - readonly adUsers: IActiveDirectoryUserConfig[]; - /** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig} {@link ActiveDirectoryPasswordPolicyConfig}* - * - * @description - * Active directory user password policy - */ - readonly adPasswordPolicy: IActiveDirectoryPasswordPolicyConfig; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ManagedActiveDirectoryVpcSettingsConfig}* - * - * @description - * Specifies the VPC settings of the Microsoft AD directory server in AWS - * - * @example - * ``` - * vpcSettings: - * vpcName: MyVpc - * subnets: - * - subnet1 - * - subnet2 - * ``` - */ -export interface IManagedActiveDirectoryVpcSettingsConfig { - /** - * Friendly name of the vpc where active directory will be deployed - */ - readonly vpcName: t.NonEmptyString; - /** - * Friendly name of the vpc subnets, where active directory will be deployed - * - * Minimum of two subnets from two different availability zone is required - */ - readonly subnets: t.NonEmptyString[]; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig}* - * - * @description - * Active directory logs configuration - */ -export interface IManagedActiveDirectoryLogConfig { - /** - * Active directory log group name, that will be used to receive the security logs from your domain controllers. We recommend pre-pending the name with /aws/directoryservice/, but that is not required. - * - * @default undefined, Accelerator will create log group name as /aws/directoryservice/DirectoryServiceName - */ - readonly groupName: t.NonEmptyString; - /** - * Log group retention in days - */ - readonly retentionInDays?: number; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ManagedActiveDirectorySecretConfig}* - * - * @description - * Active directory admin user secret configuration. - * - * @example - * - * ``` - * secretConfig: - * account: Audit - * region: us-east-1 - * adminSecretName: admin - * ``` - */ -export interface IManagedActiveDirectorySecretConfig { - /** - * Active directory admin user secret account name. When no account name provided Accelerator will create the secret into the account MAD exists - * - * Note: Please do not use the Management account for the admin user secret account name. - */ - readonly account?: t.NonEmptyString; - /** - * Active directory admin user secret region name. When no region name provided Accelerator will create the secret into the region MAD exists - */ - readonly region?: t.Region; - /** - * Active directory admin user secret account name. When no account name provided Accelerator will create the secret into the account MAD exists - * - * Note: Please do not use the Management account for the admin user secret account name. - */ - readonly adminSecretName?: t.NonEmptyString; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ManagedActiveDirectorySharedOuConfig}* - * - * @description - * Active directory shared ou configuration. - * - * @example - * ``` - * sharedOrganizationalUnits: - * organizationalUnits: - * - root - * excludedAccounts: - * - Audit - * ``` - */ -export interface IManagedActiveDirectorySharedOuConfig { - readonly organizationalUnits: t.NonEmptyString[]; - readonly excludedAccounts?: t.NonEmptyString[]; -} - -/** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig}* - * - * @description - * Managed Active directory configuration. - * - * @example - * ``` - * managedActiveDirectories: - * - name: AcceleratorManagedActiveDirectory - * type: AWS Managed Microsoft AD - * account: Network - * region: us-east-1 - * dnsName: example.com - * netBiosDomainName: example - * description: Example managed active directory - * edition: Enterprise - * resolverRuleName: example-com-rule - * vpcSettings: - * vpcName: ManagedAdVpc - * subnets: - * - subnet1 - * - subnet2 - * secretConfig: - * account: Audit - * region: us-east-1 - * adminSecretName: admin - * sharedOrganizationalUnits: - * organizationalUnits: - * - Root - * excludedAccounts: - * - Management - * logs: - * groupName: /aws/directoryservice/AcceleratorManagedActiveDirectory - * retentionInDays: 30 - * ``` - */ -export interface IManagedActiveDirectoryConfig { - /** - * Friendly name for the active directory - */ - readonly name: t.NonEmptyString; - /** - * Active directory deploy target account - */ - readonly account: t.NonEmptyString; - /** - * Active directory deploy target region - */ - readonly region: t.Region; - /** - * A fully qualified domain name. This name will resolve inside your VPC only. It does not need to be publicly resolvable. - */ - readonly dnsName: t.NonEmptyString; - /** - * A short identifier for your Net BIOS domain name. - */ - readonly netBiosDomainName: t.NonEmptyString; - /** - * Descriptive text that appears on the details page after the directory has been created. - */ - readonly description?: t.NonEmptyString; - /** - * Active directory edition, example AWS Managed Microsoft AD is available in two editions: Standard and Enterprise - */ - readonly edition: 'Standard' | 'Enterprise'; - /** - * *{@link IamConfig} / {@link ManagedActiveDirectoryVpcSettingsConfig}* - * - * @description - * Specifies the VPC settings of the Microsoft AD directory server in AWS - * - * @example - * ``` - * vpcSettings: - * vpcName: MyVpc - * subnets: - * - subnet1 - * - subnet2 - * ``` - */ - readonly vpcSettings: IManagedActiveDirectoryVpcSettingsConfig; - /** - * (OPTIONAL) Active directory route 53 resolver rule name - * - * @remarks - * This is the `name` property of a Route 53 resolver rule as defined in - * network-config.yaml {@link ResolverRuleConfig}. When this property is defined, - * the configured resolver rule will be updated with the IP addresses of the Managed AD instances. - */ - readonly resolverRuleName?: t.NonEmptyString; - /** - * (OPTIONAL) Active directory admin user secret configuration. - * - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ManagedActiveDirectorySecretConfig} - */ - readonly secretConfig?: IManagedActiveDirectorySecretConfig; - /** - * (OPTIONAL) Active directory shared ou configuration. - * - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ManagedActiveDirectorySharedOuConfig} - */ - readonly sharedOrganizationalUnits?: IManagedActiveDirectorySharedOuConfig; - /** - * (OPTIONAL) Active directory shared account name list. - */ - readonly sharedAccounts?: t.NonEmptyString[]; - /** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ManagedActiveDirectoryLogConfig} - * - * @description - * (OPTIONAL) Active directory logs configuration - */ - readonly logs?: IManagedActiveDirectoryLogConfig; - /** - * *{@link IamConfig} / {@link ManagedActiveDirectoryConfig} / {@link ActiveDirectoryConfigurationInstanceConfig}* - * - * @description - * (OPTIONAL) Active directory instance to configure active directory - */ - readonly activeDirectoryConfigurationInstance?: IActiveDirectoryConfigurationInstanceConfig; -} - -/** - * IAM configuration - */ -export interface IIamConfig { - /** - * Accelerator home region name. - * - * @example - * ``` - * homeRegion: &HOME_REGION us-east-1 - * ``` - */ - readonly homeRegion?: t.Region; - /** - * SAML provider configuration - * To configure SAML configuration, you need to provide the following values for this parameter. - * Replace provider name and metadata document file. Document file must be in config repository - * - * @example - * ``` - * providers: - * - name: - * metadataDocument: - * ``` - * - * @see {@link IamConfig} / {@link SamlProviderConfig} - */ - readonly providers?: ISamlProviderConfig[]; - /** - * Policy set configuration. - * - * To configure IAM policy named Default-Boundary-Policy with permission boundary defined in iam-policies/boundary-policy.json file, you need to provide following values for this parameter. - * - * @example - *``` - * policySets: - * - deploymentTargets: - * organizationalUnits: - * - Root - * identityCenterDependency: false - * policies: - * - name: Default-Boundary-Policy - * policy: iam-policies/boundary-policy.json - * ``` - * - * @see {@link IamConfig} / {@link PolicySetConfig} - */ - readonly policySets?: IPolicySetConfig[]; - /** - * Role sets configuration - * - * @remarks To configure EC2-Default-SSM-AD-Role role to be assumed by ec2 service into Root and Infrastructure organizational units, - * you need to provide following values for this parameter. This role will have AmazonSSMManagedInstanceCore, AmazonSSMDirectoryServiceAccess and CloudWatchAgentServerPolicy policy - * with permission boundary defined by Default-Boundary-Policy - * - * @example - * ``` - * roleSets: - * - deploymentTargets: - * organizationalUnits: - * - Root - * roles: - * - name: EC2-Default-SSM-AD-Role - * assumedBy: - * - type: service - * principal: ec2.amazonaws.com - * policies: - * awsManaged: - * - AmazonSSMManagedInstanceCore - * - AmazonSSMDirectoryServiceAccess - * - CloudWatchAgentServerPolicy - * boundaryPolicy: Default-Boundary-Policy - * ``` - * - * @see {@link IamConfig} / {@link RoleSetConfig} - */ - readonly roleSets?: IRoleSetConfig[]; - /** - * Group set configuration - * - * @remarks To configure IAM group named Administrators into Root and Infrastructure organizational units, you need to provide following values for this parameter. - * - * @example - * ``` - * groupSets: - * - deploymentTargets: - * organizationalUnits: - * - Root - * groups: - * - name: Administrators - * policies: - * awsManaged: - * - AdministratorAccess - * ``` - * - * @see {@link IamConfig} / {@link GroupSetConfig} - */ - readonly groupSets?: IGroupSetConfig[]; - /** - * User set configuration - * - * @remarks To configure breakGlassUser01 user into Administrators in Management account, you need to provide following values for this parameter. - * - * @example - * ``` - * userSets: - * - deploymentTargets: - * accounts: - * - Management - * users: - * - username: breakGlassUser01 - * group: Administrators - * boundaryPolicy: Default-Boundary-Policy - * ``` - * - * @see {@link IamConfig} / {@link UserSetConfig} - * - */ - readonly userSets?: IUserSetConfig[]; - /** - * @description - * Managed active directory configuration - * - * @remarks To configure AWS Microsoft managed active directory of enterprise edition, along with accelerator provisioned EC2 instance to pre configure directory users. group, - * you need to provide following values for this parameter. - * - * @example - * ``` - * managedActiveDirectories: - * - name: AcceleratorManagedActiveDirectory - * type: AWS Managed Microsoft AD - * account: Network - * region: us-east-1 - * dnsName: example.com - * netBiosDomainName: example - * description: Example managed active directory - * edition: Enterprise - * resolverRuleName: example-com-rule - * vpcSettings: - * vpcName: ManagedAdVpc - * subnets: - * - subnet1 - * - subnet2 - * secretConfig: - * account: Audit - * region: us-east-1 - * adminSecretName: admin - * sharedOrganizationalUnits: - * organizationalUnits: - * - Root - * excludedAccounts: - * - Management - * logs: - * groupName: /aws/directoryservice/AcceleratorManagedActiveDirectory - * retentionInDays: 30 - * activeDirectoryConfigurationInstance: - * instanceType: t3.large - * vpcName: MyVpc - * subnetName: subnet - * imagePath: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base - * securityGroupInboundSources: - * - 10.0.0.0/16 - * instanceRole: EC2-Default-SSM-AD-Role - * enableTerminationProtection: false - * userDataScripts: - * - scriptName: JoinDomain - * scriptFilePath: ad-config-scripts/Join-Domain.ps1 - * - scriptName: InitializeRDGW ## Do not Need - * scriptFilePath: ad-config-scripts/Initialize-RDGW.ps1 - * - scriptName: AWSQuickStart - * scriptFilePath: ad-config-scripts/AWSQuickStart.psm1 - * - scriptName: ADGroupSetup - * scriptFilePath: ad-config-scripts/AD-group-setup.ps1 - * - scriptName: ADUserSetup - * scriptFilePath: ad-config-scripts/AD-user-setup.ps1 - * - scriptName: ADUserGroupSetup - * scriptFilePath: ad-config-scripts/AD-user-group-setup.ps1 - * - scriptName: ADGroupGrantPermissionsSetup - * scriptFilePath: ad-config-scripts/AD-group-grant-permissions-setup.ps1 - * - scriptName: ADConnectorPermissionsSetup - * scriptFilePath: ad-config-scripts/AD-connector-permissions-setup.ps1 - * - scriptName: ConfigurePasswordPolicy - * scriptFilePath: ad-config-scripts/Configure-password-policy.ps1 - * adGroups: - * - aws-Provisioning - * - aws-Billing - * adPerAccountGroups: - * - "*-Admin" - * - "*-PowerUser" - * - "*-View" - * adConnectorGroup: ADConnector-grp - * sharedAccounts: - * - Management - * - Audit - * - LogArchive - * adPasswordPolicy: - * history: 24 - * maximumAge: 90 - * minimumAge: 1 - * minimumLength: 14 - * complexity: true - * reversible: false - * failedAttempts: 6 - * lockoutDuration: 30 - * lockoutAttemptsReset: 30 - * adUsers: - * - name: adconnector-usr - * email: example-adconnector-usr@example.com - * groups: - * - ADConnector-grp - * - name: user1 - * email: example-user1@example.com - * groups: - * - aws-Provisioning - * - "*-View" - * - "*-Admin" - * - "*-PowerUser" - * - AWS Delegated Administrators - * - name: user2 - * email: example-user2@example.com - * groups: - * - aws-Provisioning - * - "*-View" - * ``` - * - * @see {@link IamConfig} / {@link ManagedActiveDirectoryConfig} - */ - readonly managedActiveDirectories?: IManagedActiveDirectoryConfig[]; - /** - * - * Identity Center configuration - * - * @remarks To configure Identity Center, you need to provide following values for this parameter. - * - * @example - * ``` - * identityCenter: - * name: identityCenter1 - * delegatedAdminAccount: Audit - * identityCenterPermissionSets: - * - name: PermissionSet1 - * policies: - * awsManaged: - * - arn:aws:iam::aws:policy/AdministratorAccess - * - PowerUserAccess - * customerManaged: - * - ResourceConfigurationCollectorPolicy - * acceleratorManaged: - * - AcceleratorManagedPolicy01 - * - AcceleratorManagedPolicy02 - * inlinePolicy: iam-policies/sso-permissionSet1-inline-policy.json - * permissionsBoundary: - * customerManagedPolicy: - * name: AcceleratorManagedPolicy - * path: / - * awsManagedPolicyName: PowerUserAccess - * sessionDuration: 60 - * identityCenterAssignments: - * - name: Assignment1 - * permissionSetName: PermissionSet1 - * principals: - * - type: USER - * name: accelerator - * - type: GROUP - * name: admin - * deploymentTargets: - * accounts: - * - LogArchive - * ``` - * - * @see {@link IamConfig} / {@link IdentityCenterConfig} - */ - readonly identityCenter?: IIdentityCenterConfig; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/network-config.ts b/source/packages/@aws-accelerator/config/lib/models/network-config.ts deleted file mode 100644 index 9e84abd..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/network-config.ts +++ /dev/null @@ -1,7312 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; -import * as ci from '../models/customizations-config'; - -/** - * *{@link NetworkConfig} / {@link DefaultVpcsConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html | Default Virtual Private Cloud (VPC)} configuration. - * - * @description - * Use this configuration to delete default VPCs in your environment. - * - * @remarks - * If there are resources with network interfaces (such as EC2 instances) in your default VPCs, enabling this option - * will cause a core pipeline failure. Please clean up any dependencies before - * enabling this option. - * - * @example - * ``` - * defaultVpc: - * delete: true - * excludeAccounts: [] - * excludeRegions: [] - * ``` - */ -export interface IDefaultVpcsConfig { - /** - * Enable to delete default VPCs. - */ - readonly delete: boolean; - /** - * (OPTIONAL) Include an array of friendly account names - * to exclude from default VPC deletion. - * - * @remarks - * Note: This is the logical name for accounts as defined in accounts-config.yaml. - */ - readonly excludeAccounts?: string[]; - /** - * (OPTIONAL) Include an array of AWS regions - * to exclude from default VPC deletion. - * - * @remarks - * Note: The regions included in the array must exist in the `enabledRegions` section of the global-config.yaml. - */ - readonly excludeRegions?: t.Region[]; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig} / {@link TransitGatewayRouteTableConfig} / {@link TransitGatewayRouteEntryConfig} / {@link TransitGatewayRouteTableVpcEntryConfig}* - * - * @description - * Transit Gateway VPC static route entry configuration. - * Use this configuration to define an account and VPC name as a target for Transit Gateway static route entries. - * - * @remarks - * The targeted VPC must have a Transit Gateway attachment defined. @see {@link TransitGatewayAttachmentConfig} - * - * @example - * ``` - * account: Network - * vpcName: Network-Inspection - * ``` - */ -export interface ITransitGatewayRouteTableVpcEntryConfig { - /** - * The friendly name of the account where the VPC resides. - * - * @remarks - * Note: This is the logical `name` property for the account as defined in accounts-config.yaml. - */ - readonly account: t.NonEmptyString; - /** - * The friendly name of the VPC. - * - * @remarks - * Note: This is the logical `name` property for the VPC as defined in network-config.yaml. - */ - readonly vpcName: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig} / {@link TransitGatewayRouteTableConfig} / {@link TransitGatewayRouteEntryConfig} / {@link TransitGatewayRouteTableDxGatewayEntryConfig}* - * - * @description - * Transit Gateway Direct Connect Gateway static route entry configuration. - * Use this configuration to define a Direct Connect Gateway attachment as a target for Transit - * Gateway static routes. - * - * @remarks - * The targeted Direct Connect Gateway must have a Transit Gateway association defined. @see {@link DxTransitGatewayAssociationConfig} - * - * @example - * ``` - * directConnectGatewayName: Accelerator-DXGW - * ``` - */ -export interface ITransitGatewayRouteTableDxGatewayEntryConfig { - /** - * The name of the Direct Connect Gateway - * - * @remarks - * Note: This is the logical `name` property of the Direct Connect Gateway as defined in network-config.yaml. Do not use `gatewayName`. - */ - readonly directConnectGatewayName: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig} / {@link TransitGatewayRouteTableConfig} / {@link TransitGatewayRouteEntryConfig} / {@link TransitGatewayRouteTableVpnEntryConfig}* - * - * @description - * Transit Gateway VPN static route entry configuration. - * Use this configuration to define a VPN attachment as a target for Transit - * Gateway static routes. - * - * @remarks - * The targeted VPN must have a Transit Gateway attachment defined. @see {@link VpnConnectionConfig} - * - * @example - * ``` - * vpnConnectionName: accelerator-vpc - * ``` - */ -export interface ITransitGatewayRouteTableVpnEntryConfig { - /** - * The name of the VPN connection - * - * @remarks - * Note: This is the `name` property of the VPN connection as defined in network-config.yaml. - */ - readonly vpnConnectionName: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig} / {@link TransitGatewayRouteTableConfig} / {@link TransitGatewayRouteEntryConfig} / {@link TransitGatewayRouteTableTgwPeeringEntryConfig}* - * - * @description - * Transit Gateway peering static route entry configuration. - * Used to define a peering attachment as a target for Transit - * Gateway static routes. - * - * @remarks - * The targeted peering attachment must be defined in network-config.yaml. @see {@link TransitGatewayPeeringConfig} - * - * @example - * ``` - * transitGatewayPeeringName: Accelerator-TGW-Peering - * ``` - */ -export interface ITransitGatewayRouteTableTgwPeeringEntryConfig { - /** - * The name of the Transit Gateway peering connection - * - * @remarks - * Note: This is the logical `name` property of the Transit Gateway peering connection as defined in network-config.yaml. - * - * @see {@link TransitGatewayPeeringConfig} - */ - readonly transitGatewayPeeringName: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig} / {@link TransitGatewayRouteTableConfig} / {@link TransitGatewayRouteEntryConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/how-transit-gateways-work.html#tgw-routing-overview | Transit Gateway static route entry} configuration. - * - * @description - * Use this configuration to define static route entries in a Transit Gateway route table. - * - * @example - * Destination IPv4 CIDR: - * ``` - * - destinationCidrBlock: 0.0.0.0/0 - * attachment: - * account: Network - * vpcName: Network-Inspection - * ``` - * Destination IPv6 CIDR: - * ``` - * - destinationCidrBlock: ::/0 - * attachment: - * account: Network - * vpcName: Network-Inspection - * ``` - * Destination prefix list: - * ``` - * - destinationPrefixList: accelerator-pl - * attachment: - * vpnConnectionName: accelerator-vpn - * ``` - * Blackhole IPv4 route: - * ``` - * - destinationCidrBlock: 1.1.1.1/32 - * blackhole: true - * ``` - * - * Blackhole IPv6 route: - * ``` - * - destinationCidrBlock: fd00::/8 - * blackhole: true - * ``` - */ -export interface ITransitGatewayRouteEntryConfig { - /** - * The destination IPv4/v6 CIDR block for the route table entry. - * - * @remarks - * Use IPv4/v6 CIDR notation, i.e. 10.0.0.0/16, fd00::/8. Leave undefined if specifying a destination prefix list. - * - */ - readonly destinationCidrBlock?: t.NonEmptyString; - /** - * The friendly name of a prefix list for the route table entry. - * - * @remarks - * This is the logical `name` property of a prefix list as defined in network-config.yaml. - * Leave undefined if specifying a CIDR destination. - * - * @see {@link PrefixListConfig} - */ - readonly destinationPrefixList?: t.NonEmptyString; - /** - * (OPTIONAL) Enable to create a blackhole for the destination CIDR. - * Leave undefined if specifying a VPC destination. - */ - readonly blackhole?: boolean; - /** - * The target {@link https://docs.aws.amazon.com/vpc/latest/tgw/working-with-transit-gateways.html | Transit Gateway attachment} for the route table entry. Supported attachment types include: - * - * - VPC - * - Direct Connect Gateway - * - VPN - * - Transit Gateway Peering - * - * @remarks - * **CAUTION**: Changing the attachment type or target after initial deployment creates a new route table entry. - * To avoid core pipeline failures, use multiple core pipeline runs to 1) delete the existing route entry and then 2) add the new route entry. - * - * Note: Leave undefined if specifying a blackhole destination. - * - * @see {@link TransitGatewayRouteTableVpcEntryConfig} {@link TransitGatewayRouteTableDxGatewayEntryConfig} {@link TransitGatewayRouteTableVpnEntryConfig} - */ - readonly attachment?: - | ITransitGatewayRouteTableVpcEntryConfig - | ITransitGatewayRouteTableDxGatewayEntryConfig - | ITransitGatewayRouteTableVpnEntryConfig - | ITransitGatewayRouteTableTgwPeeringEntryConfig; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig} / {@link TransitGatewayRouteTableConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/how-transit-gateways-work.html#tgw-routing-overview | Transit Gateway route table} configuration. - * - * @description - * Use this configuration define route tables for your Transit Gateway. Route tables are used to configure - * routing behaviors for your Transit Gateway. - * - * The following example creates a TGW route table called Network-Main-Shared with no static route entries: - * @example - * ``` - * - name: Network-Main-Shared - * routes: [] - * ``` - */ -export interface ITransitGatewayRouteTableConfig { - /** - * A friendly name for the Transit Gateway route table. - * - * @remarks - * **CAUTION**: Changing this property after initial deployment will cause a route table recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) An array of tag objects for the Transit Gateway route table. - */ - readonly tags?: t.ITag[]; - /** - * An array of Transit Gateway route entry configuration objects. - * - * @see {@link TransitGatewayRouteEntryConfig} - */ - readonly routes: ITransitGatewayRouteEntryConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayPeeringConfig} / {@link TransitGatewayPeeringRequesterConfig}* - * - * @description - * Transit Gateway (TGW) peering requester configuration. - * Use this configuration to define the requester side of the peering attachment. - * - * @example - * ``` - * transitGatewayName: SharedServices-Main - * account: SharedServices - * region: us-west-2 - * routeTableAssociations: SharedServices-Main-Core - * tags: - * - key: Name - * value: Network-Main-And-SharedServices-Main-Peering - * ``` - */ -export interface ITransitGatewayPeeringRequesterConfig { - /** - * The friendly name of the requester transit gateway - * - * @remarks - * This is the logical `name` property of the requester transit gateway as defined in network-config.yaml. - * - * **CAUTION**: Changing this property after initial deployment will cause the peering attachment to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * @see {@link TransitGatewayConfig} - */ - readonly transitGatewayName: t.NonEmptyString; - /** - * The friendly name of the account of the requester transit gateway - * - * @remarks - * This is the logical `account` property of the requester transit gateway as defined in network-config.yaml. - * - * @see {@link TransitGatewayConfig} - */ - readonly account: t.NonEmptyString; - /** - * The name of the region the accepter transit gateway resides in - * - * @see {@link TransitGatewayConfig} - */ - readonly region: t.Region; - /** - * The friendly name of TGW route table to associate with this peering attachment. - * - * @remarks - * This is the logical `name` property of a route table for the requester TGW as defined in network-config.yaml. - * - * @see {@link TransitGatewayRouteTableConfig} - */ - readonly routeTableAssociations: t.NonEmptyString; - /** - * (OPTIONAL) An array of tag objects for the Transit Gateway Peering. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayPeeringConfig} / {@link TransitGatewayPeeringAccepterConfig}* - * - * @description - * Transit Gateway (TGW) peering accepter configuration. - * Use this configuration to define the accepter side of the peering attachment. - * - * @example - * ``` - * transitGatewayName: Network-Main - * account: Network - * region: us-east-1 - * routeTableAssociations: Network-Main-Core - * autoAccept: true - * applyTags: false - * ``` - */ -export interface ITransitGatewayPeeringAccepterConfig { - /** - * The friendly name of the accepter transit gateway - * - * @remarks - * This is the logical `name` property of the accepter transit gateway as defined in network-config.yaml. - * - * **CAUTION**: Changing this property after initial deployment will cause the peering attachment to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * @see {@link TransitGatewayConfig} - */ - readonly transitGatewayName: t.NonEmptyString; - /** - * The friendly name of the account of the accepter transit gateway - * - * @remarks - * This is the logical `account` property of the accepter transit gateway as defined in network-config.yaml. - * - * @see {@link TransitGatewayConfig} - */ - readonly account: t.NonEmptyString; - /** - * The name of the region the accepter transit gateway resides in - * - * @see {@link TransitGatewayConfig} - */ - readonly region: t.Region; - /** - * The friendly name of TGW route table to associate with this peering attachment. - * - * @remarks - * This is the logical `name` property of a route table for the accepter TGW as defined in network-config.yaml. - * - * @see {@link TransitGatewayRouteTableConfig} - */ - readonly routeTableAssociations: t.NonEmptyString; - /** - * (OPTIONAL) Peering request auto accept flag. - * Note: When this flag is set to `true`, the peering request will be automatically - * accepted by the accelerator. - */ - readonly autoAccept?: boolean; - /** - * (OPTIONAL) Peering request apply tags flag. - * Note: When this flag is set to `true`, the requester attachment tags are replicated - * to the accepter attachment. - */ - readonly applyTags?: boolean; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayPeeringConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/how-transit-gateways-work.html#tgw-route-table-peering | Transit Gateway (TGW) peering} configuration. - * - * @description - * Use this configuration to define a peering attachment between two TGWs. - * - * @remarks - * Use autoAccept `true` if you'd like the accelerator to automatically accept the peering attachment - * Use applyTags `true' if you'd like the requester attachment tags to be replicated to the accepter attachment - * - * Note: accepter property autoAccept and applyTags are optional. Default value for autoAccept is `true` and applyTags is `false`. - * - * The following example creates a cross-account and cross-region peering connection - * between a requester TGW named SharedServices-Main and accepter TGW named Network-Main: - * @example - * ``` - * transitGatewayPeering: - * - name: Network-Main-And-SharedServices-Main-Peering - * autoAccept: false - * requester: - * transitGatewayName: SharedServices-Main - * account: SharedServices - * region: us-west-2 - * routeTableAssociations: SharedServices-Main-Core - * tags: - * - key: Name - * value: Network-Main-And-SharedServices-Main-Peering - * accepter: - * transitGatewayName: Network-Main - * account: Network - * region: us-east-1 - * routeTableAssociations: Network-Main-Core - * autoAccept: true - * applyTags: false - * - * ``` - */ -export interface ITransitGatewayPeeringConfig { - /** - * The friendly name of TGW peering. - */ - readonly name: t.NonEmptyString; - /** - * Peering attachment requester configuration. - * - * @see {@link TransitGatewayPeeringRequesterConfig} - */ - readonly requester: ITransitGatewayPeeringRequesterConfig; - /** - * Peering attachment accepter configuration - * - * @see {@link TransitGatewayPeeringAccepterConfig} - */ - readonly accepter: ITransitGatewayPeeringAccepterConfig; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/what-is-transit-gateway.html | Transit Gateway (TGW)} configuration. - * - * @description - * Use this configuration to define Transit Gateways for your environment. - * A transit gateway acts as a virtual router for traffic flowing between your virtual private clouds (VPCs) and on-premises networks. - * - * The following example creates a TGW called Network-Main in the Network account in the us-east-1 region. - * @example - * ``` - * transitGateways: - * - name: Network-Main - * account: Network - * region: us-east-1 - * shareTargets: - * organizationalUnits: [] - * asn: 65000 - * dnsSupport: enable - * vpnEcmpSupport: enable - * defaultRouteTableAssociation: disable - * defaultRouteTablePropagation: disable - * autoAcceptSharingAttachments: enable - * routeTables: [] - * tags: [] - * ``` - * - * The following example creates a TGW with a static IPv4 and IPv6 address - * @example - * ``` - * transitGateways: - * - name: Network-Main - * account: Network - * region: us-east-1 - * transitGatewayCidrBlocks: - * - 10.5.0.0/24 - * transitGatewayIpv6CidrBlocks: - * - 2001:db8::/64 - * shareTargets: - * organizationalUnits: [] - * asn: 65000 - * dnsSupport: enable - * vpnEcmpSupport: enable - * defaultRouteTableAssociation: disable - * defaultRouteTablePropagation: disable - * autoAcceptSharingAttachments: enable - * routeTables: [] - * tags: [] - * ``` - */ -export interface ITransitGatewayConfig { - /** - * A friendly name for the Transit Gateway. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the Transit Gateway to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the account to deploy the Transit Gateway. - * - * @remarks - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly account: t.NonEmptyString; - /** - * The region name to deploy the Transit Gateway. - */ - readonly region: t.Region; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * (OPTIONAL) A list of transit gateway IPv4 CIDR blocks. - */ - readonly transitGatewayCidrBlocks?: t.NonEmptyString[]; - - /** - * (OPTIONAL) A list of transit gateway IPv6 CIDR blocks. - */ - readonly transitGatewayIpv6CidrBlocks?: t.NonEmptyString[]; - - /** - * A Border Gateway Protocol (BGP) Autonomous System Number (ASN). - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the Transit Gateway to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * The range is 64512 to 65534 for 16-bit ASNs. - * - * The range is 4200000000 to 4294967294 for 32-bit ASNs. - */ - readonly asn: number; - /** - * Configure DNS support between VPCs. - * - * @remarks - * Enable this option if you need the VPC to resolve public IPv4 DNS host names - * to private IPv4 addresses when queried from instances in another VPC attached - * to the transit gateway. - */ - readonly dnsSupport: t.EnableDisable; - /** - * Equal Cost Multipath (ECMP) routing support between VPN tunnels. - * - * @remarks - * Enable this option if you need Equal Cost Multipath (ECMP) routing support between VPN tunnels. - * If connections advertise the same CIDRs, the traffic is distributed equally between them. - */ - readonly vpnEcmpSupport: t.EnableDisable; - /** - * Configure default route table association. - * - * @remarks - * Enable this option to automatically associate transit gateway attachments with the default - * route table for the transit gateway. - */ - readonly defaultRouteTableAssociation: t.EnableDisable; - /** - * Configure default route table propagation. - * - * @remarks - * Enable this option to automatically propagate transit gateway attachments to the default - * route table for the transit gateway. - */ - readonly defaultRouteTablePropagation: t.EnableDisable; - /** - * Enable this option to automatically accept cross-account attachments. - */ - readonly autoAcceptSharingAttachments: t.EnableDisable; - /** - * An array of Transit Gateway route table configuration objects. - * - * @see {@link TransitGatewayRouteTableConfig} - */ - readonly routeTables: ITransitGatewayRouteTableConfig[]; - /** - * (OPTIONAL) An array of tag objects for the Transit Gateway. - */ - readonly tags?: t.ITag[]; -} - -export type DxVirtualInterfaceType = 'private' | 'transit'; - -export type IpVersionType = 'ipv4' | 'ipv6'; - -/** - * *{@link NetworkConfig} / {@link DxGatewayConfig} / {@link DxVirtualInterfaceConfig}* - * - * {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/Welcome.html#overview-components | Direct Connect (DX) virtual interface (VIF)} configuration. - * - * @description - * Use this configuration to create a virtual interface to a DX Gateway. Virtual interfaces - * enable access to your AWS services from your on-premises environment. - * - * The following example creates a transit VIF called Accelerator-VIF in the Network account - * on a DX connection with resource ID dxcon-example: - * @example - * ``` - * - name: Accelerator-VIF - * region: us-east-1 - * connectionId: dxcon-example - * customerAsn: 64512 - * interfaceName: Accelerator-VIF - * ownerAccount: Network - * type: transit - * vlan: 100 - * ``` - */ -export interface IDxVirtualInterfaceConfig { - /** - * A friendly name for the virtual interface. This name - * is used as a logical reference for the resource in - * the accelerator. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the virtual interface to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The resource ID of the {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/Welcome.html#overview-components | DX connection} - * the virtual interface will be created on - * - * @remarks - * This is the resource ID of an existing DX connection in your environment. Resource IDs should be the the format `dxcon-xxxxxx` - */ - readonly connectionId: t.NonEmptyString; - /** - * A Border Gateway Protocol (BGP) Autonomous System Number (ASN) for the customer side of the connection. - * - * @remarks - * This ASN must be unique from the Amazon side ASN. - * The ASN for the Amazon side is determined by the DX Gateway it is created on. - * - * Note: The valid values are 1 to 2147483647 - */ - readonly customerAsn: number; - /** - * The name of the virtual interface. - * This name will show as the name of the resource - * in the AWS console and API. - * - * @remarks - * This name can be changed without replacing the physical resource. - */ - readonly interfaceName: t.NonEmptyString; - /** - * The friendly name of the owning account of the DX connection. - * - * @remarks - * Please note this is the owning account of the **physical** DX connection, not the virtual interface. - * - * If specifying an account that differs from the account of the Direct Connect Gateway, this will - * create a {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/WorkingWithVirtualInterfaces.html#hosted-vif | hosted VIF allocation} - * from the connection owner account to the Direct Connect Gateway owner account. - * Hosted VIFs must be manually confirmed before they can be used or updated by the accelerator. - */ - readonly ownerAccount: t.NonEmptyString; - /** - * The region of the virtual interface. - * - * @remarks - * Please note this region must match the region where the physical connection is hosted. - */ - readonly region: t.Region; - /** - * The type of the virtual interface - * - * @remarks - * `private` virtual interfaces can only be created on DX gateways associated with virtual private gateways. - * - * `transit` virtual interfaces can only be created on DX gateways associated with transit gateways. - */ - readonly type: DxVirtualInterfaceType; - /** - * The virtual local area network (VLAN) tag to use for this virtual interface. - * - * @remarks - * This must be a unique VLAN tag that's not already in use on your connection. - * - * The value must be between 1 and 4094 - */ - readonly vlan: number; - /** - * (OPTIONAL) The address family to use for this virtual interface. - * - * Default - ipv4 - */ - readonly addressFamily?: IpVersionType; - /** - * (OPTIONAL) The peer IP address to use for Amazon's side of the virtual interface. - * - * Default - randomly-generated by Amazon - */ - readonly amazonAddress?: t.NonEmptyString; - /** - * (OPTIONAL) The peer IP address to use for customer's side of the virtual interface. - * - * Default - randomly-generated by Amazon - */ - readonly customerAddress?: t.NonEmptyString; - /** - * (OPTIONAL) Enable SiteLink for this virtual interface. - * - * Default - false - */ - readonly enableSiteLink?: boolean; - /** - * (OPTIONAL) Enable jumbo frames for the virtual interface. - * - * Default - standard 1500 MTU frame size - */ - readonly jumboFrames?: boolean; - /** - * (OPTIONAL) An array of tags to apply to the virtual interface. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link DxGatewayConfig} / {@link DxTransitGatewayAssociationConfig}* - * - * {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/direct-connect-transit-gateways.html | Direct Connect Gateway transit gateway association} configuration. - * - * @description - * Use this configuration to define transit gateway attachments for a DX gateway. - * - * @example - * ``` - * - name: Network-Main - * account: Network - * allowedPrefixes: - * - 10.0.0.0/8 - * - 192.168.0.0/24 - * routeTableAssociations: - * - Network-Main-Core - * routeTablePropagations: - * - Network-Main-Core - * - Network-Main-Shared - * - Network-Main-Segregated - * ``` - */ -export interface IDxTransitGatewayAssociationConfig { - /** - * The friendly name of the transit gateway to associate. - * - * @remarks - * This is the logical `name` property of the transit gateway as defined in network-config.yaml. - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the account the transit gateway is deployed to. - * - * @remarks - * This is the `account` property of the transit gateway as defined in network-config.yaml. - * - * If specifying an account that differs from the account of the Direct Connect Gateway, this will - * create an {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/multi-account-associate-tgw.html | association proposal} - * from the transit gateway owner account to the Direct Connect Gateway owner account. - * Proposals must be manually approved. Proposal associations **cannot** also have configured transit gateway - * route table associations or propagations. - */ - readonly account: t.NonEmptyString; - /** - * An array of CIDR prefixes that are allowed to advertise over this transit gateway association. - * - * @remarks - * Use CIDR notation, i.e. 10.0.0.0/16 - * - * @see {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/allowed-to-prefixes.html} - */ - readonly allowedPrefixes: t.NonEmptyString[]; - /** - * (OPTIONAL) The friendly name of TGW route table(s) to associate with this attachment. - * - * @remarks - * This is the logical `name` property of the route table(s) as defined in network-config.yaml. - * @see {@link TransitGatewayRouteTableConfig} - */ - readonly routeTableAssociations?: t.NonEmptyString[]; - /** - * (OPTIONAL) The friendly name of TGW route table(s) to propagate routes from this attachment. - * - * @remarks - * This is the logical `name` property of the route table(s) as defined in network-config.yaml. - * @see {@link TransitGatewayRouteTableConfig} - */ - readonly routeTablePropagations?: t.NonEmptyString[]; -} - -/** - * *{@link NetworkConfig} / {@link DxGatewayConfig}* - * - * {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/direct-connect-gateways-intro.html | Direct Connect Gateway (DXGW)} configuration. - * Use this configuration to define DXGWs, - * {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/Welcome.html#overview-components | virtual interfaces}, - * and {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/direct-connect-gateways.html | DXGW associations}. - * - * @description - * A DXGW is a globally-available resource than can be used to connect your VPCs to your on-premise infrastructure. - * - * @example - * ``` - * directConnectGateways: - * - name: Accelerator-DXGW - * account: Network - * asn: 64512 - * gatewayName: Accelerator-DXGW - * virtualInterfaces: [] - * transitGatewayAssociations: [] - * ``` - */ -export interface IDxGatewayConfig { - /** - * A friendly name for the DX Gateway. - * This name is used as a logical reference - * for the resource in the accelerator. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the DXGW to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the account to deploy the DX Gateway. - * - * @remarks - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly account: t.NonEmptyString; - /** - * A Border Gateway Protocol (BGP) Autonomous System Number (ASN). - * - * @remarks - * The range is 64512 to 65534 for 16-bit ASNs. - * - * The range is 4200000000 to 4294967294 for 32-bit ASNs. - */ - readonly asn: number; - /** - * The name of the Direct Connect Gateway. - * This name will show as the name of the resource - * in the AWS console and API. - * - * @remarks - * This name can be changed without replacing the physical resource. - */ - readonly gatewayName: t.NonEmptyString; - /** - * (OPTIONAL) An array of virtual interface configurations. Creates virtual interfaces on the DX gateway. - * - * @see {@link DxVirtualInterfaceConfig} - */ - readonly virtualInterfaces?: IDxVirtualInterfaceConfig[]; - /** - * (OPTIONAL) An array of transit gateway association configurations. Creates transit gateway attachments for this DX gateway. - * - * @see {@link DxTransitGatewayAssociationConfig} - */ - readonly transitGatewayAssociations?: IDxTransitGatewayAssociationConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link IpamConfig} / {@link IpamScopeConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/ipam/add-scope-ipam.html | VPC IPAM scope} configuration. - * - * @description - * Use this configuration to define custom private IPAM scopes for your VPCs. - * An IPAM scope is the highest-level container for an IPAM. Within scopes, pools can be created. - * Custom IPAM scopes can be used to create pools and manage resources that use the same IP space. - * - * @example - * ``` - * - name: accelerator-scope - * description: Custom scope - * tags: [] - * ``` - */ -export interface IIpamScopeConfig { - /** - * A friendly name for the IPAM scope. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the scope to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) Description for the IPAM scope. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) An array of tag objects for the IPAM scope. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link IpamConfig} / {@link IpamPoolConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/ipam/how-it-works-ipam.html | VPC IPAM pool} configuration. - * - * @description - * Use this configuration to define custom IPAM pools for your VPCs. A pool is a collection of contiguous - * IP address ranges. IPAM pools enable you to organize your IP addresses according to your routing and security needs. - * - * @example - * Base pool: - * ``` - * - name: accelerator-base-pool - * description: Base IPAM pool - * provisionedCidrs: - * - 10.0.0.0/16 - * tags: [] - * ``` - * Regional pool: - * ``` - * - name: accelerator-regional-pool - * description: Regional pool for us-east-1 - * locale: us-east-1 - * provisionedCidrs: - * - 10.0.0.0/24 - * sourceIpamPool: accelerator-base-pool - * ``` - */ -export interface IIpamPoolConfig { - /** - * A friendly name for the IPAM pool. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the pool to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The address family for the IPAM pool. - * - * @remarks - * The default value is `ipv4`. - * - * @see {@link IpVersionType} - */ - readonly addressFamily?: IpVersionType; - /** - * (OPTIONAL) The friendly name of the IPAM scope to assign the IPAM pool to. - * - * @remarks - * Note: This is the logical `name` property of the scope as defined in network-config.yaml. - * Leave this property undefined to create the pool in the default private scope. - * - * @see {@link IpamScopeConfig} - */ - readonly scope?: t.NonEmptyString; - /** - * (OPTIONAL) The default netmask length of IPAM allocations for this pool. - * - * @remarks - * Setting this property will enforce a default netmask length for all IPAM allocations in this pool. - */ - readonly allocationDefaultNetmaskLength?: number; - /** - * (OPTIONAL) The maximum netmask length of IPAM allocations for this pool. - * - * @remarks - * Setting this property will enforce a maximum netmask length for all IPAM allocations in this pool. - * This value must be larger than the `allocationMinNetmaskLength` value. - */ - readonly allocationMaxNetmaskLength?: number; - /** - * (OPTIONAL) The minimum netmask length of IPAM allocations for this pool. - * - * @remarks - * Setting this property will enforce a minimum netmask length for all IPAM allocations in this pool. - * This value must be less than the `allocationMaxNetmaskLength` value. - */ - readonly allocationMinNetmaskLength?: number; - /** - * (OPTIONAL) An array of tags that are required for resources that use CIDRs from this IPAM pool. - * - * @remarks - * Resources that do not have these tags will not be allowed to allocate space from the pool. - */ - readonly allocationResourceTags?: t.ITag[]; - /** - * (OPTIONAL) If set to `true`, IPAM will continuously look for resources within the CIDR range of this pool - * and automatically import them as allocations into your IPAM. - */ - readonly autoImport?: boolean; - /** - * (OPTIONAL) A description for the IPAM pool. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) The AWS Region where you want to make an IPAM pool available for allocations. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment - * will cause the pool to be recreated. - * Please be aware that any downstream dependencies may cause - * this property update to fail. - * - * Only resources in the same Region as the locale of the pool can get IP address allocations from the pool. - * A base (top-level) pool does not require a locale. - * A regional pool requires a locale. - */ - readonly locale?: t.Region; - /** - * An array of CIDR ranges to provision for the IPAM pool. - * - * @remarks - * **CAUTION**: Changing or removing an existing provisioned CIDR range after initial deployment may impact downstream VPC allocations. - * Appending additional provisioned CIDR ranges does not impact downstream resources. - * - * Use CIDR notation, i.e. 10.0.0.0/16. - * If defining a regional pool, the provisioned CIDRs must be a subset of the source IPAM pool's CIDR ranges. - */ - readonly provisionedCidrs?: t.NonEmptyString[]; - /** - * (OPTIONAL) Determines if a pool is publicly advertisable. - * - * @remarks - * This option is not available for pools with AddressFamily set to ipv4. - */ - readonly publiclyAdvertisable?: boolean; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Pools must be shared to any accounts/OUs that require IPAM allocations. - * The pool does not need to be shared with the delegated administrator account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * (OPTIONAL) The friendly name of the source IPAM pool to create this IPAM pool from. - * - * @remarks - * Only define this value when creating regional IPAM pools. Leave undefined for top-level pools. - */ - readonly sourceIpamPool?: t.NonEmptyString; - /** - * (OPTIONAL) An array of tag objects for the IPAM pool. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link IpamConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/ipam/what-it-is-ipam.html | Virtual Private Cloud (VPC) IP Address Manager (IPAM)} configuration. - * - * @description - * Use this configuration to define an AWS-managed VPC IPAM. - * IPAM is a feature that makes it easier for you to plan, track, and monitor IP addresses for your AWS workloads. - * - * The following example defines an IPAM that is capable of operating in the us-east-1 and us-west-2 regions: - * @example - * ``` - * ipams: - * - name: accelerator-ipam - * region: us-east-1 - * description: Accelerator IPAM - * operatingRegions: - * - us-east-1 - * - us-west-2 - * scopes: [] - * pools: [] - * tags: [] - * ``` - */ -export interface IIpamConfig { - /** - * A friendly name for the IPAM. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the IPAM to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The region to deploy the IPAM. - * - * @remarks - * Note that IPAMs must be deployed to a single region but may be used to manage allocations in multiple regions. - * Configure the `operatingRegions` property to define multiple regions to manage. - */ - readonly region: t.Region; - /** - * (OPTIONAL) A description for the IPAM. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) An array of regions that the IPAM will manage. - */ - readonly operatingRegions?: t.Region[]; - /** - * (OPTIONAL) An array of IPAM scope configurations to create under the IPAM. - * - * @see {@link IpamScopeConfig} - */ - readonly scopes?: IIpamScopeConfig[]; - /** - * An optional array of IPAM pool configurations to create under the IPAM. - * - * @see {@link IpamPoolConfig} - */ - readonly pools?: IIpamPoolConfig[]; - /** - * (OPTIONAL) An array of tag objects for the IPAM. - */ - readonly tags?: t.ITag[]; -} - -export type RouteTableEntryType = - | 'transitGateway' - | 'natGateway' - | 'internetGateway' - | 'egressOnlyIgw' - | 'local' - | 'localGateway' - | 'gatewayEndpoint' - | 'gatewayLoadBalancerEndpoint' - | 'networkFirewall' - | 'networkInterface' - | 'virtualPrivateGateway' - | 'vpcPeering'; - -export type GatewayRouteTableType = 'internetGateway' | 'virtualPrivateGateway'; - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link RouteTableConfig} / {@link RouteTableEntryConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Route_Tables.html | VPC route table} static route entry configuration. - * - * @description - * Use this configuration to define static route entries in a VPC subnet or gateway route table. - * Static routes are used determine traffic flow from your subnet to a defined destination address and target. - * - * @example - * Transit Gateway Attachment - * ``` - * - name: TgwRoute - * destination: 0.0.0.0/0 - * type: transitGateway - * target: Network-Main - * ``` - * - * NAT Gateway - * ``` - * - name: NatRoute - * destination: 0.0.0.0/0 - * type: natGateway - * target: Nat-A - * ``` - * - * Internet Gateway - * ``` - * - name: IgwRoute - * destination: 0.0.0.0/0 - * type: internetGateway - * ``` - * - * VPC Peering - * ``` - * - name: PeerRoute - * destination: 10.0.0.0/16 - * type: vpcPeering - * target: Peering - * ``` - * - * Network Firewall with CIDR destination: - * ``` - * - name: NfwRoute - * destination: 0.0.0.0/0 - * type: networkFirewall - * target: accelerator-firewall - * targetAvailabilityZone: a - * ``` - * - * Network Firewall with subnet destination: - * ``` - * - name: NfwRoute - * destination: subnet-a - * type: networkFirewall - * target: accelerator-firewall - * targetAvailabilityZone: a - * ``` - * - * Gateway Load Balancer Endpoint with CIDR destination: - * ``` - * - name: GwlbRoute - * destination: 0.0.0.0/0 - * type: gatewayLoadBalancerEndpoint - * target: Endpoint-A - * ``` - * - * Gateway Load Balancer Endpoint with subnet destination: - * ``` - * - name: GwlbRoute - * destination: subnet-a - * type: gatewayLoadBalancerEndpoint - * target: Endpoint-A - * ``` - * - * Local Gateway associated with an AWS Outpost: - * ``` - * - name: LgwRoute - * destination: 10.0.0.0/16 - * type: localGateway - * target: LocalGateway-A - * ``` - * - * Network Interface associated with a dynamic lookup: - * * **NOTE:** This lookup value is not supported for firewalls defined in {@link Ec2FirewallAutoScalingGroupConfig}. The interface must have the associateElasticIp property set to 'true' or the sourceDestCheck property set to 'false' - * ``` - * - name: EniRoute - * destination: 10.0.0.0/16 - * type: networkInterface - * target: ${ACCEL_LOOKUP::EC2:ENI_0:accelerator-firewall:Id} - * ``` - * - * Network Interface associated with an explicit ENI Id: - * ``` - * - name: EniRoute - * destination: 10.0.0.0/16 - * type: networkInterface - * target: eni-0123456789abcdef - * ``` - * - * IPv6 route targeting an Egress-only IGW: - * ``` - * - name: EigwRoute - * ipv6Destination: ::/0 - * type: egressOnlyIgw - * ``` - * - */ -export interface IRouteTableEntryConfig { - /** - * A friendly name for the route table. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the route table to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) The destination IPv4 CIDR block or dynamic subnet reference for the route table entry. - * - * @remarks - * You can either use IPv4 CIDR notation (i.e. 10.0.0.0/16) or target a subnet by referencing its logical `name` property. - * If referencing a subnet name, the subnet MUST be defined in the same VPC. This feature is intended for ingress routing scenarios - * where a gateway route table must target a Gateway Load Balancer or Network Firewall endpoint in a dynamic IPAM-created subnet. - * @see {@link SubnetConfig} and {@link RouteTableConfig}. - * - * `destination`, `ipv6Destination`, or `destinationPrefixList` must be specified for the following route entry types: - * `transitGateway`, `natGateway`, `internetGateway`, `networkInterface`, `vpcPeering`, `virtualPrivateGateway`. - * - * `destination` or `ipv6Destination` MUST be specified for route entry type `networkFirewall` or `gatewayLoadBalancerEndpoint`. - * - * Note: Leave undefined for route entry type `gatewayEndpoint`. - */ - readonly destination?: t.NonEmptyString; - /** - * The friendly name of the destination prefix list for the route table entry. - * - * @remarks - * This is the logical `name` property of the prefix list as defined in network-config.yaml. - * - * `destination`, `ipv6Destination`, or `destinationPrefixList` must be specified for the following route entry types: - * `transitGateway`, `natGateway`, `internetGateway`, `egressOnlyIgw`, `networkInterface`, `vpcPeering`, `virtualPrivateGateway`. - * - * Cannot be specified for route entry type `networkFirewall` or `gatewayLoadBalancerEndpoint`. Use `destination` or `ipv6Destination` instead. - * - * Note: Leave undefined for route entry type `gatewayEndpoint`. - * - * @see {@link PrefixListConfig} - */ - readonly destinationPrefixList?: t.NonEmptyString; - /** - * (OPTIONAL) The destination IPv6 CIDR block or dynamic subnet reference for the route table entry. - * - * @remarks - * You can either use IPv6 CIDR notation (i.e. fd00::/8) or target a subnet by referencing its logical `name` property. - * If referencing a subnet name, the subnet MUST be defined in the same VPC. This feature is intended for ingress routing scenarios - * where a gateway route table must target a Gateway Load Balancer or Network Firewall endpoint in a dynamic IPAM-created subnet. - * @see {@link SubnetConfig} and {@link RouteTableConfig}. - * - * `destination`, `ipv6Destination`, or `destinationPrefixList` must be specified for the following route entry types: - * `transitGateway`, `natGateway`, `internetGateway`, `egressOnlyIgw`, `networkInterface`, `vpcPeering`, `virtualPrivateGateway`. - * - * `destination` or `ipv6Destination` MUST be specified for route entry type `networkFirewall` or `gatewayLoadBalancerEndpoint`. - * - * Note: Leave undefined for route entry type `gatewayEndpoint`. - */ - readonly ipv6Destination?: t.NonEmptyString; - /** - * The destination type of route table entry. - * - * @see {@link NetworkConfigTypes.routeTableEntryTypeEnum} - */ - readonly type?: RouteTableEntryType; - /** - * The friendly name of the destination target. - * - * @remarks - * Use `s3` or `dynamodb` as the string when specifying a route entry type of `gatewayEndpoint`. - * - * This is the logical `name` property of other target types as defined in network-config.yaml. - * - * Note: Leave undefined for route entry type `internetGateway`, `egressOnlyIgw`, or `virtualPrivateGateway`. - */ - readonly target?: t.NonEmptyString; - /** - * The Availability Zone (AZ) the target resides in. - * - * @remarks - * Include only the letter of the AZ name (i.e. 'a' for 'us-east-1a') to target a subnet created in a specific AZ. Use an integer - * (i.e. 1) for subnets using a physical mapping ID to an AZ. Use the availability zone suffix e.g. "laz-1a" for Local Zones. Please reference the documentation {@link https://docs.aws.amazon.com/ram/latest/userguide/working-with-az-ids.html | Availability Zone IDs for your AWS resources} - * for more information. - * - * Note: Leave undefined for targets of route entry types other than `networkFirewall`. - */ - readonly targetAvailabilityZone?: t.NonEmptyString | number; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link RouteTableConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Route_Tables.html | Virtual Private Cloud (VPC) route table} configuration. - * - * @description - * Use this configuration to define custom route tables for your VPC. - * Route tables contain a set of rules, called routes, to determine where network traffic from a subnet or gateway is directed. - * - * @example Subnet route table - * ``` - * - name: SubnetRouteTable - * routes: [] - * tags: [] - * ``` - * @example Gateway route table - * ``` - * - name: GatewayRouteTable - * gatewayAssociation: internetGateway - * routes: [] - * tags: [] - * ``` - */ -export interface IRouteTableConfig { - /** - * A friendly name for the VPC route table. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the route table to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * Designate a gateway to associate this route table with. - * - * @remarks - * Note: Only define this property when creating a gateway route table. Leave undefined for subnet route tables. - */ - readonly gatewayAssociation?: GatewayRouteTableType; - /** - * An array of VPC route table entry configuration objects. - * - * @see {@link RouteTableEntryConfig} - */ - readonly routes?: IRouteTableEntryConfig[]; - /** - * (OPTIONAL) An array of tag objects for the VPC route table. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / ({@link SubnetConfig}) / {@link IpamAllocationConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/ipam/how-it-works-ipam.html | VPC IPAM allocation} configuration. - * - * @description - * Use this configuration to dynamically assign a VPC or subnet CIDR from an IPAM pool. - * - * @example - * VPC allocations: - * ``` - * - ipamPoolName: accelerator-regional-pool - * netmaskLength: 24 - * ``` - * Subnet allocations: - * ``` - * ipamPoolName: accelerator-regional-pool - * netmaskLength: 24 - * ``` - */ -export interface IIpamAllocationConfig { - /** - * The IPAM pool name to request the allocation from. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPC or subnet to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * This is the logical `name` property of the IPAM pool as defined in network-config.yaml. - * The IPAM pool referenced must either be deployed to or have `shareTargets` - * configured for the account(s)/OU(s) that will be requesting the allocation. - * - * @see {@link IpamPoolConfig} - */ - readonly ipamPoolName: t.NonEmptyString; - /** - * The subnet mask length to request. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPC or subnet to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Specify only the CIDR prefix length for the subnet, i.e. 24. If the IPAM pool - * referenced in `ipamPoolName` does not have enough space for this allocation, - * resource creation will fail. - * - * @see {@link IpamPoolConfig} - */ - readonly netmaskLength: number; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SubnetConfig} / {@link SubnetPrivateDnsConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html#subnet-settings | Subnet Resource-Based Name} configuration. - * - * @description - * Use this configuration to define custom DNS name settings for your VPC subnets. - * - * @example - * ``` - * enableDnsAAAARecord: true - * enableDnsARecord: true - * hostNameType: resource-name - * ``` - */ -export interface ISubnetPrivateDnsConfig { - /** - * (OPTIONAL) Indicates whether to respond to DNS queries for instance hostname with DNS AAAA records. - * - * @default false - */ - readonly enableDnsAAAARecord?: boolean; - /** - * (OPTIONAL) Indicates whether to respond to DNS queries for instance hostnames with DNS A records. - * - * @default false - */ - readonly enableDnsARecord?: boolean; - /** - * The type of hostname for EC2 instances. - * - * @remarks - * For IPv4 only subnets, an instance DNS name must be based on the instance IPv4 address. - * For IPv6 only subnets, an instance DNS name must be based on the instance ID. - * For dual-stack subnets, you can specify whether DNS names use the instance IPv4 address or the instance ID. - */ - readonly hostnameType?: 'ip-name' | 'resource-name'; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SubnetConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html | Virtual Private Cloud (VPC) subnet} configuration. - * - * @description - * Use this configuration to define subnets for your VPC. - * A subnet is a range of IP addresses in your VPC that can be used to create AWS resources, such as EC2 instances. - * - * @example - * Static IPv4 CIDR: - * ``` - * - name: accelerator-cidr-subnet-a - * availabilityZone: a - * routeTable: accelerator-cidr-subnet-a - * ipv4CidrBlock: 10.0.0.0/26 - * tags: [] - * ``` - * Using the Physical ID for an Availability Zone - * ``` - * - name: accelerator-cidr-subnet-a - * availabilityZone: 1 - * routeTable: accelerator-cidr-subnet-a - * ipv4CidrBlock: 10.0.0.0/26 - * tags: [] - * ``` - * IPAM allocation: - * ``` - * - name: accelerator-ipam-subnet-a - * availabilityZone: a - * routeTable: accelerator-cidr-subnet-a - * ipamAllocation: - * ipamPoolName: accelerator-regional-pool - * netmaskLength: 26 - * tags: [] - * ``` - * Static IPv6 CIDR: - * ``` - * - name: accelerator-cidr-subnet-1 - * availabilityZone: 1 - * routeTable: accelerator-cidr-subnet-1 - * ipv6CidrBlock: fd00::/64 - * tags: [] - * ``` - */ -export interface ISubnetConfig { - /** - * A friendly name for the VPC subnet. - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a subnet recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) Indicates whether a network interface created in this subnet receives an IPv6 address on creation. - * - * @remarks - * If you specify this property, you must also specify the `ipv6CidrBlock` property. - * - * This property defaults to `false`. - */ - readonly assignIpv6OnCreation?: boolean; - /** - * The Availability Zone (AZ) the subnet resides in. - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a subnet recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Include only the letter of the AZ name (i.e. 'a' for 'us-east-1a') to have the subnet created in a specific AZ. Use an integer - * (i.e. 1) for a physical mapping ID to an AZ. Please reference the documentation {@link https://docs.aws.amazon.com/ram/latest/userguide/working-with-az-ids.html | Availability Zone IDs for your AWS resources} - * for more information. - */ - readonly availabilityZone?: t.NonEmptyString | number; - /** - * (OPTIONAL) Indicates whether DNS queries made to the Amazon-provided DNS Resolver in this subnet should return synthetic IPv6 addresses for IPv4-only destinations. - * - * For more information, see {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html#nat-gateway-nat64-dns64 | DNS64 and NAT64} in the Amazon Virtual Private Cloud User Guide. - */ - readonly enableDns64?: boolean; - /** - * The friendly name of the route table to associate with the subnet. - */ - readonly routeTable?: t.NonEmptyString; - /** - * The IPv4 CIDR block to associate with the subnet. - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a subnet recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly ipv4CidrBlock?: t.NonEmptyString; - /** - * (OPTIONAL) The IPv6 CIDR block to associate with the subnet. - * - * @remarks - * Use IPv6 CIDR notation, i.e. fd00::/64. Possible IPv6 netmask lengths are between /44 and /64 in increments of /4. - * - * **Note**: Only providing an IPv6 CIDR block or IPv6 IPAM allocation will create an IPv6-only subnet. You must also specify an - * IPv4 CIDR or IPAM allocation to create a dual-stack subnet. See {@link https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html#subnet-basics | Subnet basics} for more information. - * - */ - readonly ipv6CidrBlock?: t.NonEmptyString; - /** - * (OPTIONAL) Configure automatic mapping of public IPs. - * - * @remarks - * Enables you to configure the auto-assign IP settings to automatically request a public - * IPv4 address for a new network interface in this subnet. - */ - readonly mapPublicIpOnLaunch?: boolean; - /** - * The IPAM pool configuration for the subnet. - * - * @see {@link IpamAllocationConfig} - * - * @remarks - * Must be using AWS-managed IPAM and allocate a CIDR to the VPC this subnet will be created in. - * Define IPAM configuration in `centralNetworkServices`. @see {@link CentralNetworkServicesConfig} - */ - readonly ipamAllocation?: IIpamAllocationConfig; - /** - * (OPTIONAL) Private DNS name options for the subnet. - * - * @see {@link SubnetPrivateDnsConfig} - */ - readonly privateDnsOptions?: ISubnetPrivateDnsConfig; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * NOTE: When sharing subnets, security groups created in this VPC will be automatically replicated - * to the share target accounts. If tags are configured for the VPC and/or subnet, they are also replicated. - * - * @see {@link SecurityGroupConfig} - * - * Targets can be account names and/or organizational units. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * (OPTIONAL) An array of tag objects for the VPC subnet. - */ - readonly tags?: t.ITag[]; - /** - * (OPTIONAL) The friendly name for the outpost to attach to the subnet - * - * @remarks - * This is the logical `name` of the outpost as defined in network-config.yaml. - * - * @see {@link OutpostsConfig} - */ - readonly outpost?: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link NatGatewayConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html | Network Address Translation (NAT) Gateway} configuration. - * - * @description - * Use this configuration to define AWS-managed NAT Gateways for your VPC. - * You can use a NAT gateway so that instances in a private subnet can connect to services outside your VPCs. - * - * @example - * NAT gateway with accelerator-provisioned elastic IP - * ``` - * - name: accelerator-nat-gw - * subnet: accelerator-cidr-subnet-a - * tags: [] - * ``` - * - * NAT gateway with user-provided elastic IP allocation ID - * ``` - * - name: accelerator-nat-gw - * allocationId: eipalloc-acbdefg123456 - * subnet: accelerator-cidr-subnet-a - * tags: [] - * ``` - * - * NAT gateway with private connectivity - * ``` - * - name: accelerator-nat-gw - * private: true - * subnet: accelerator-cidr-subnet-a - * tags: [] - * ``` - */ -export interface INatGatewayConfig { - /** - * A friendly name for the NAT Gateway. - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a NAT gateway recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the subnet for the NAT Gateway to be deployed. - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a NAT gateway recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly subnet: t.NonEmptyString; - /** - * (OPTIONAL) The allocation ID of the Elastic IP address that's associated with the NAT gateway. - * This allocation ID must exist in the target account the NAT gateway is deployed to. - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a NAT gateway recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * NOTE: Leaving this property undefined results in the accelerator provisioning a new elastic IP. - * - * To retrieve the `allocationId` of your Elastic IP address, perform the following: - * 1. Open the Amazon VPC console at https://console.aws.amazon.com/vpc/. - * 2. In the navigation pane, choose Elastic IPs. - * 3. Select the Elastic IP address and reference the value in the `Allocation ID` column. The format - * should be `eipalloc-abc123xyz`. - */ - readonly allocationId?: t.NonEmptyString; - /** - * (OPTIONAL) Set `true` to define a NAT gateway with private connectivity type - * - * @remarks - * **CAUTION**: changing this property after initial deployment will cause a NAT gateway recreation. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Set to `false` or leave undefined to create a public-facing NAT gateway - */ - readonly private?: boolean; - /** - * (OPTIONAL) An array of tag objects for the NAT Gateway. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link TransitGatewayAttachmentConfig} / {@link TransitGatewayAttachmentTargetConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/tgw-vpc-attachments.html | Transit Gateway attachment} target configuration. - * - * @description - * Use this configuration to target a Transit Gateway when defining an attachment for your VPC. - * - * @example - * ``` - * - name: Network-Main - * account: Network - * ``` - */ -export interface ITransitGatewayAttachmentTargetConfig { - /** - * A friendly name for the attachment target Transit Gateway. - * - * @remarks - * This is the logical `name` property of the Transit Gateway as defined in network-config.yaml. - * - * @see {@link TransitGatewayConfig} - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the account for the attachment target Transit Gateway. - * - * @remarks - * This is the logical `account` property of the Transit Gateway as defined in network-config.yaml. - * - * @see {@link TransitGatewayConfig}. - */ - readonly account: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link TransitGatewayAttachmentConfig} / {@link TransitGatewayAttachmentOptionsConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/tgw-vpc-attachments.html | Transit Gateway attachment} options configuration. - * - * @description - * Used to specify advanced options for the VPC attachment. - * - * @example - * ``` - * applianceModeSupport: enable - * dnsSupport: enable - * ipv6Support disable - * ``` - */ -export interface ITransitGatewayAttachmentOptionsConfig { - /** - * (OPTIONAL) Enable to configure DNS support for the attachment. This option is enabled by default. - */ - readonly dnsSupport?: t.EnableDisable; - /** - * (OPTIONAL) Enable to configure IPv6 support for the attachment. This option is disabled by default. - */ - readonly ipv6Support?: t.EnableDisable; - /** - * (OPTIONAL) Enable to configure appliance mode for the attachment. This option is disabled by default. - * - * @remarks - * Appliance mode ensures only a single network interface is chosen for the entirety of a traffic flow, - * enabling stateful deep packet inspection for the attached VPC. - * - * @see {@link https://docs.aws.amazon.com/vpc/latest/tgw/transit-gateway-appliance-scenario.html} - */ - readonly applianceModeSupport?: t.EnableDisable; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link TransitGatewayAttachmentConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/tgw-vpc-attachments.html | Transit Gateway VPC attachment} configuration. - * - * @description - * Use this configuration to define a Transit Gateway attachment to your VPC. - * Transit Gateway attachments allow you to interconnect your virtual private clouds (VPCs) and on-premises networks. - * Defining a VPC attachment deploys an elastic network interface within VPC subnets, - * which is then used by the transit gateway to route traffic to and from the chosen subnets. - * - * @example - * ``` - * - name: Network-Inspection - * transitGateway: - * name: Network-Main - * account: Network - * subnets: [] - * routeTableAssociations: [] - * routeTablePropagations: [] - * options: - * applianceModeSupport: enable - * tags: [] - * ``` - */ -export interface ITransitGatewayAttachmentConfig { - /** - * A friendly name for the Transit Gateway attachment. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the attachment to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * A Transit Gateway attachment target configuration object. - * - * @see {@link TransitGatewayAttachmentTargetConfig} - */ - readonly transitGateway: ITransitGatewayAttachmentTargetConfig; - /** - * An array of the friendly names of VPC subnets for the attachment to be deployed. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment causes a new attachment to be created. - * VPCs can only have a single attachment at a time. - * To avoid core pipeline failures, use multiple core pipeline runs to 1) delete the existing VPC attachment and any - * downstream dependencies and then 2) create a new attachment with your updated subnets. - * - * This is the logical `name` property of the subnet as defined in network-config.yaml. - * - * @see {@link SubnetConfig} - */ - readonly subnets: t.NonEmptyString[]; - /** - * (OPTIONAL) A Transit Gateway attachment options configuration. - * - * @see {@link TransitGatewayAttachmentOptionsConfig} - */ - readonly options?: ITransitGatewayAttachmentOptionsConfig; - /** - * The friendly name of a Transit Gateway route table to associate the attachment to. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment causes a new association to be created. - * Attachments can only have a single association at a time. - * To avoid core pipeline failures, use multiple core pipeline runs to 1) delete the existing association and then 2) add the new association. - * - * This is the logical `name` property of the route table as defined in network-config.yaml. - * - * @see {@link TransitGatewayRouteTableConfig} - */ - readonly routeTableAssociations?: t.NonEmptyString[]; - /** - * An array of friendly names of Transit Gateway route tables to propagate the attachment. - */ - readonly routeTablePropagations?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of tag objects for the Transit Gateway attachment. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link TransitGatewayConnectConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/tgw/tgw-connect.html | Transit Gateway Connect VPC attachment} configuration. - * - * @description - * Use this configuration to define a Transit Gateway Connect attachment to your VPC. - * A Transit Gateway Connect attachment will establish a connection between a transit gateway and third-party virtual appliances (such as SD-WAN appliances) - * running in a VPC. A Connect attachment supports the Generic Routing Encapsulation (GRE) tunnel protocol for high performance, - * and Border Gateway Protocol (BGP) for dynamic routing. - * - * @example - * ``` - * - name: Network-Vpc-Tgw-Connect - * region: us-east-1 - * transitGateway: - * name: Network-Main - * account: Network - * vpc: - * vpcName: Network - * vpcAttachment: Network-Proxy - * options: - * protocol: gre - * ``` - * - * * @description - * Use this configuration to define a Transit Gateway Connect attachment to your Direct Connect Gateway. - * - * @example - * ``` - * - name: Network-Dx-Tgw-Connect - * region: us-east-1 - * transitGateway: - * name: Network-Main - * account: Network - * directConnect: Dx-Onprem-IAD - * options: - * protocol: gre - * ``` - */ -export interface ITransitGatewayConnectConfig { - /** - * A friendly name for the Transit Gateway Connect attachment. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the attachment to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The AWS Region for the attachment. - * - * @remarks - * This must be set in the same region as the Transit Gateway. - */ - readonly region: t.NonEmptyString; - /** - * The Transit Gateway configuration object to set the Transit Gateway Connect. - * - * @see {@link TransitGatewayAttachmentTargetConfig} - */ - readonly transitGateway: ITransitGatewayAttachmentTargetConfig; - /** - * The VPC Attachment that belongs to the Transit Gateway that a Transit Gateway Connect Attachment is being made for. - * @see {@link TransitGatewayConnectVpcConfig} - * - * @remarks - * Either `vpc` or `directConnect` must be provided, not both. - */ - readonly vpc?: ITransitGatewayConnectVpcConfig; - /** - * (OPTIONAL) The Direct Connect Gateway Attachment that belongs to the Transit Gateway that a Transit Gateway Connect Attachment is being made for. - * @see {@link TransitGatewayConnectDirectConnectConfig} - * - * @remarks - * Either `vpc` or `directConnect` must be provided, not both. - */ - readonly directConnect?: t.NonEmptyString; - /** - * (OPTIONAL) Options around the Transit Gateway Connect - * @see {@link TransitGatewayConnectOptionsConfig} - */ - readonly options?: ITransitGatewayConnectOptionsConfig; - /** - * (OPTIONAL) An array of tag objects for the Transit Gateway attachment. - */ - readonly tags?: t.ITag[]; -} - -export interface ITransitGatewayConnectVpcConfig { - /** - * The name of the VPC - */ - readonly vpcName: t.NonEmptyString; - /** - * The name of the VPC attachment - */ - readonly vpcAttachment: t.NonEmptyString; -} - -export interface ITransitGatewayConnectOptionsConfig { - /** - * The tunnel protocl for the Transit Gateway Connect - */ - readonly protocol: TransitGatewayConnectProtocol; -} - -export type TransitGatewayConnectProtocol = 'gre'; -export type IpAddressFamilyType = 'IPv4' | 'IPv6'; - -/** - * *{@link NetworkConfig} / {@link PrefixListConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/managed-prefix-lists.html | Customer-managed prefix list} configuration. - * - * @description - * Use this configuration to define custom prefix lists for your environment. - * A managed prefix list is a set of one or more CIDR blocks. - * You can use prefix lists to make it easier to configure and maintain your security groups and route tables. - * - * The following example creates a prefix list named `accelerator-pl` that may contain up to 10 entries. - * The prefix list is deployed to all accounts in the organization. - * - * @example - * CURRENT SYNTAX: use the following syntax when defining prefix lists for v1.4.0 and newer. - * The additional example underneath is provided for backward compatibility. - * ``` - * prefixLists: - * - name: accelerator-pl - * deploymentTargets: - * organizationalUnits: - * - Root - * addressFamily: IPv4 - * maxEntries: 10 - * entries: - * - 10.0.0.0/16 - * tags: [] - * ``` - * - * THE BELOW EXAMPLE SYNTAX IS DEPRECATED: use the above syntax when defining new prefix lists. - * ``` - * prefixLists: - * - name: accelerator-pl - * accounts: - * - Network - * regions: - * - us-east-1 - * addressFamily: IPv4 - * maxEntries: 10 - * entries: - * - 10.0.0.0/16 - * tags: [] - * ``` - */ - -export interface IPrefixListConfig { - /** - * A friendly name for the prefix list. - * - * @remarks - * **CAUTION**: Changing this value will cause the prefix list to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * (DEPRECATED) An array of friendly names for the accounts the prefix list is deployed. - * - * @remarks - * **NOTE**: This property is deprecated as of v1.4.0. It is recommended to use `deploymentTargets` instead. - * - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly accounts?: t.NonEmptyString[]; - /** - * (DEPRECATED) An array of region names for the prefix list to be deployed. - * - * @remarks - * **NOTE**: This property is deprecated as of v1.4.0. It is recommended to use `deploymentTargets` instead. - * - * @see {@link Region} - */ - readonly regions?: t.Region[]; - /** - * Prefix List deployment targets - * - * @remarks - * Targets can be account names and/or organizational units. - * Prefix lists must be deployed to account(s)/OU(s) of - * any VPC subnet route tables, Transit Gateway route tables, - * or VPC security groups that will consume them. - * - * @see {@link DeploymentTargets} - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * The IP address family of the prefix list. - */ - readonly addressFamily: IpAddressFamilyType; - /** - * The maximum allowed entries in the prefix list. - */ - readonly maxEntries: number; - /** - * An array of CIDR entries for the prefix list. - * - * @remarks - * The number of entries must be less than or equal to the `maxEntries` value. - * - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly entries: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of tag objects for the prefix list. - */ - readonly tags?: t.ITag[]; -} - -export type GatewayEndpointType = 's3' | 'dynamodb'; - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link GatewayEndpointConfig} / {@link GatewayEndpointServiceConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html | VPC gateway endpoint} service configuration. - * - * @description - * Use this configuration to define the service and endpoint policy for gateway endpoints. - * - * @example - * ``` - * - service: s3 - * policy: Default - * ``` - */ -export interface IGatewayEndpointServiceConfig { - /** - * The name of the service to create the endpoint for - * - * @see {@link NetworkConfigTypes.gatewayEndpointEnum} - */ - readonly service: GatewayEndpointType; - /** - * (OPTIONAL) The friendly name of a policy for the gateway endpoint. If left undefined, the default policy will be used. - * - * @remarks - * This is the logical `name` property of the endpoint policy as defined in network-config.yaml. - * - * @see {@link EndpointPolicyConfig} - */ - readonly policy?: t.NonEmptyString; - /** - * (OPTIONAL) Specify whether or not a policy is applied to the endpoint. By default, if no policy is specified in the `policy` property, a default policy is applied. Specifying this option as `false` will ensure no policy is applied to the endpoint. This property defaults to `true` if not specified. - */ - readonly applyPolicy?: boolean; - /** - * (OPTIONAL) The full name of the service to create the endpoint for. - * - * @remarks - * This property can be used to input the full endpoint service names that do not - * conform with the standard `com.amazonaws..` syntax. - */ - readonly serviceName?: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link GatewayEndpointConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html | VPC gateway endpoint} configuration. - * - * @description - * Use this configuration to define gateway endpoints for your VPC. - * A gateway endpoint targets specific IP routes in an Amazon VPC route table, - * in the form of a prefix-list, used for traffic destined to Amazon DynamoDB - * or Amazon Simple Storage Service (Amazon S3). - * - * @example - * ``` - * defaultPolicy: Default - * endpoints [] - * ``` - */ -export interface IGatewayEndpointConfig { - /** - * The friendly name of the default policy for the gateway endpoints. - * - * @remarks - * This is the logical `name` property of the endpoint policy as defined in network-config.yaml. - * - * @see {@link EndpointPolicyConfig} - */ - readonly defaultPolicy: t.NonEmptyString; - /** - * An array of endpoints to create. - */ - readonly endpoints: IGatewayEndpointServiceConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link InterfaceEndpointConfig} / {@link InterfaceEndpointServiceConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/privatelink/privatelink-access-aws-services.html | VPC interface endpoint} service configuration. - * - * @description - * Use this configuration to define the service and endpoint policy for gateway endpoints. - * - * @example - * ``` - * - service: ec2 - * policy: Default - * ``` - */ -export interface IInterfaceEndpointServiceConfig { - /** - * The name of the service to create the endpoint for. - * - * @remarks - * The solution team does not keep a record of all possible interface endpoints - * that can be deployed. A full list of services that support interface endpoints - * can be found in the following documentation: {@link https://docs.aws.amazon.com/vpc/latest/privatelink/aws-services-privatelink-support.html}. - * - * **NOTE**: The service name to input in this property is the suffix value after `com.amazonaws.` noted in the above reference. - * Availability of interface endpoints as well as features such as endpoint - * policies may differ depending on region. Please use the instructions provided in the above reference - * to determine endpoint features and regional availability before deployment. - */ - readonly service: t.NonEmptyString; - /** - * (OPTIONAL) The full name of the service to create the endpoint for. - * - * @remarks - * This property can be used to input the full endpoint service names that do not - * conform with the standard `com.amazonaws..` syntax. - */ - readonly serviceName?: t.NonEmptyString; - /** - * (OPTIONAL) The friendly name of a policy for the interface endpoint. If left undefined, the default policy will be used. - * - * @remarks - * This is the logical `name` property of the endpoint policy as defined in network-config.yaml. - * - * @see {@link EndpointPolicyConfig} - */ - readonly policy?: t.NonEmptyString; - /** - * (OPTIONAL) Specify whether or not a policy is applied to the endpoint. By default, if no policy is specified in the `policy` property, a default policy is applied. Specifying this option as `false` will ensure no policy is applied to the endpoint. This property defaults to `true` if not specified. - */ - readonly applyPolicy?: boolean; - /** - * (OPTIONAL) Apply the provided security group for this interface endpoint. - */ - readonly securityGroup?: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link InterfaceEndpointConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/privatelink/privatelink-access-aws-services.html | VPC interface endpoint} configuration. - * - * @description - * Use this configuration to define interface endpoints for your VPC. - * Interface endpoints powered by AWS PrivateLink to connect your VPC to AWS services as if they were in your VPC, without the use of an internet gateway. - * - * @example - * ``` - * defaultPolicy: Default - * endpoints: [] - * subnets: [] - * ``` - */ -export interface IInterfaceEndpointConfig { - /** - * The friendly name of the default policy for the interface endpoints. - * - * @remarks - * This is the logical `name` property of the endpoint policy as defined in network-config.yaml. - * - * @see {@link EndpointPolicyConfig} - */ - readonly defaultPolicy: t.NonEmptyString; - /** - * An array of VPC interface endpoint services to be deployed. - * - * @see {@link InterfaceEndpointServiceConfig} - */ - readonly endpoints: IInterfaceEndpointServiceConfig[]; - /** - * An array of the friendly names of VPC subnets for the endpoints to be deployed. - * - * @remarks - * This is the logical `name` property of the VPC subnet as defined in network-config.yaml. - * - * @see {@link SubnetConfig} - */ - readonly subnets: t.NonEmptyString[]; - /** - * (OPTIONAL) Enable to define interface endpoints as centralized endpoints. - * - * @remarks - * Endpoints defined as centralized endpoints will have Route 53 private hosted zones - * created for each of them. These hosted zones are associated with any VPCs configured - * with the `useCentralEndpoints` property enabled. - * - * **NOTE**: You may only define one centralized endpoint VPC per region. - * - * For additional information on this pattern, please refer to - * {@link https://github.com/awslabs/landing-zone-accelerator-on-aws/blob/main/FAQ.md#how-do-i-define-a-centralized-interface-endpoint-vpc | our FAQ}. - */ - readonly central?: boolean; - /** - * (OPTIONAL) An array of source CIDRs allowed to communicate with the endpoints. - * - * @remarks - * These CIDRs are used to create ingress rules in a security group - * that is created and attached to the interface endpoints. - * By default, all traffic (0.0.0.0/0) is allowed. - * - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly allowedCidrs?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of tag objects for the private hosted zones associated with the VPC Interface endpoints. - */ - readonly tags?: t.ITag[]; -} - -export type SecurityGroupRuleType = - | 'RDP' - | 'SSH' - | 'HTTP' - | 'HTTPS' - | 'MSSQL' - | 'MYSQL/AURORA' - | 'REDSHIFT' - | 'POSTGRESQL' - | 'ORACLE-RDS' - | 'TCP' - | 'UDP' - | 'ICMP' - | 'ALL'; - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SecurityGroupConfig} / {@link SecurityGroupRuleConfig} / {@link SubnetSourceConfig}* - * - * @description - * VPC subnet security group source configuration. - * Use this configuration to dynamically reference subnet CIDRs in a security group rule. - * - * @example - * ``` - * - account: Network - * vpc: Network-Inspection - * subnets: [] - * ``` - */ -export interface ISubnetSourceConfig { - /** - * The friendly name of the account in which the VPC subnet resides. - * - * @remarks - * This is the `account` property of the VPC as defined in network-config.yaml. - * If referencing a VPC template, use the logical `name` property of an account - * the template targets in its `deploymentTargets` property. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly account: t.NonEmptyString; - /** - * The friendly name of the VPC in which the subnet resides. - * - * @remarks - * This is the logical `name` property of the VPC or VPC template as defined in network-config.yaml. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly vpc: t.NonEmptyString; - /** - * An array of the friendly names of subnets to reference. - * - * @remarks - * This is the logical `name` property of the subnet as defined in network-config.yaml. - * - * Each subnet must exist in the source VPC targeted in the `vpc` property. A security group rule will be created - * for each referenced subnet in this array. - * - * @see {@link SubnetConfig} - */ - readonly subnets: t.NonEmptyString[]; - /** - * (OPTIONAL) Indicates whether to target the IPv6 CIDR associated with a subnet. - * - * @remarks - * Leave this property undefined or set to `false` to target a subnet's IPv4 CIDR. - */ - readonly ipv6?: boolean; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SecurityGroupConfig} / {@link SecurityGroupRuleConfig} / {@link SecurityGroupSourceConfig}* - * - * @description - * Security group source configuration. - * Use this configuration to define a security group as a source of a security group rule. - * - * @example - * ``` - * - securityGroups: - * - accelerator-sg - * ``` - */ -export interface ISecurityGroupSourceConfig { - /** - * An array of the friendly names of security group rules to reference. - * - * @remarks - * This is the logical `name` property of the security group as defined in network-config.yaml. - * - * Referenced security groups must exist in the same VPC this rule is being created in. A security group rule will be created - * for each referenced security group in this array. - * - * @see {@link SecurityGroupConfig} - */ - readonly securityGroups: t.NonEmptyString[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SecurityGroupConfig} / {@link SecurityGroupRuleConfig} / {@link PrefixListSourceConfig}* - * - * @description - * Prefix list security group source configuration. - * Use this configuration to define a custom prefix list as a source in a security group rule. - * - * @example - * ``` - * - prefixLists: - * - accelerator-pl - * ``` - */ -export interface IPrefixListSourceConfig { - /** - * An array of the friendly names of prefix lists to reference. - * - * @remarks - * This is the logical `name` property of the prefix list as defined in network-config.yaml. - * - * The referenced prefix lists must be deployed to the account(s) the VPC or VPC template is deployed to. - * For VPCs using Resource Access Manager (RAM) shared subnets, the referenced prefix lists must also be - * deployed to those shared accounts. - * - * @see {@link PrefixListConfig} - */ - readonly prefixLists: t.NonEmptyString[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SecurityGroupConfig} / {@link SecurityGroupRuleConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/security-group-rules.html | Security group rule} configuration. - * - * @description - * Use this configuration to define ingress and egress rules for your security groups. - * The rules of a security group control the inbound traffic that's allowed to reach the resources - * that are associated with the security group. The rules also control the outbound traffic that's - * allowed to leave them. - * - * @example - * CIDR source: - * ``` - * - description: Remote access security group - * types: - * - RDP - * - SSH - * sources: - * - 10.0.0.0/16 - * ``` - * Security group source: - * ``` - * - description: Remote access security group - * types: - * - RDP - * - SSH - * sources: - * - securityGroups: - * - accelerator-sg - * ``` - * Prefix list source: - * ``` - * - description: Remote access security group - * types: - * - RDP - * - SSH - * sources: - * - prefixLists: - * - accelerator-pl - * ``` - * Subnet source: - * ``` - * - description: Remote access security group - * types: - * - RDP - * - SSH - * sources: - * - account: Network - * vpc: Network-Endpoints - * subnets: - * - Network-Endpoints-A - * ``` - * IP Protocol: - * ``` - * - description: 'IP Protocol Rule' - * ipProtocols: - * - ESP - * - IDRP - * - ST - * sources: - * - 10.0.0.0/8 - * ``` - */ -export interface ISecurityGroupRuleConfig { - /** - * A description for the security group rule. - */ - readonly description: t.NonEmptyString; - /** - * (OPTIONAL) An array of port/protocol types to include in the security group rule. - * - * @remarks - * - Use `ALL` to create a rule that allows all ports/protocols. - * - Use `ICMP` along with `fromPort` and `toPort` to create ICMP protocol rules. ICMP `fromPort`/`toPort` values use the same convention as the {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-security-group-egress.html#cfn-ec2-securitygroupegress-fromport | CloudFormation reference}. - * - Use `TCP` or `UDP` along with `fromPort` and `toPort` to create TCP/UDP rules that target a range of ports. - * - Use any of the other common types included to create a rule that allows that specific application port/protocol. - * - You can leave this property undefined and use `tcpPorts` and `udpPorts` independently to define multiple TCP/UDP rules. - * - * @see {@link NetworkConfigTypes.securityGroupRuleTypeEnum} - */ - readonly types?: SecurityGroupRuleType[]; - /** - * (OPTIONAL) An array of custom IP Protocols for the security group rule - * - * @remarks - * Use only IP protocols that aren't either of the following: 'RDP', 'SSH', 'HTTP', 'HTTPS', 'MSSQL', - * 'MYSQL/AURORA', 'REDSHIFT', 'POSTGRESQL', 'ORACLE-RDS', 'TCP', 'UDP','ICMP','ALL'. - * - * For input values, please use values from the `Keyword` column via - https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml - * - * NOTE: Can only use `ipProtocols` or 'types'. If you need to allow the same source IP address, use multiple ingress/egress - * rules. - * - * - * - */ - readonly ipProtocols?: string[]; - /** - * (OPTIONAL) An array of TCP ports to include in the security group rule. - * - * @remarks - * Use this property when you need to define ports that are not the common applications available in `types`. - * Leave undefined if using the `types` property. - */ - readonly tcpPorts?: number[]; - /** - * (OPTIONAL) An array of UDP ports to include in the security group rule. - * - * @remarks - * Use this property when you need to define ports that are not the common applications available in `types`. - * Leave undefined if using the `types` property. - */ - readonly udpPorts?: number[]; - /** - * (OPTIONAL) The port to start from in the security group rule. - * - * @remarks - * Use only for rules that are using the TCP, UDP, or ICMP types. Leave undefined for other rule types. - * - * For TCP/UDP rules, this is the start of the port range. - * - * For ICMP rules, this is the ICMP type number. A value of -1 indicates all types. - * The value of `toPort` must also be -1 if this value is -1. - */ - readonly fromPort?: number; - /** - * (OPTIONAL) The port to end with in the security group rule. - * - * @remarks - * Use only for rules that are using the TCP, UDP, or ICMP types. Leave undefined for other rule types. - * - * For TCP/UDP type rules, this is the end of the port range. - * - * For ICMP type rules, this is the ICMP code number. A value of -1 indicates all types. - * The value must be -1 if the value of `fromPort` is -1. - */ - readonly toPort?: number; - /** - * An array of sources for the security group rule. - * - * @remarks - * Valid sources are CIDR ranges, security group rules, prefix lists, and subnets. - * - * @see - * {@link SecurityGroupSourceConfig} | {@link PrefixListSourceConfig} | {@link SubnetSourceConfig} - */ - readonly sources: (t.NonEmptyString | ISubnetSourceConfig | ISecurityGroupSourceConfig | IPrefixListSourceConfig)[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link SecurityGroupConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/security-groups.html | Security group} configuration. - * - * @description - * Use this configuration to define security groups in your VPC. - * A security group acts as a firewall that controls the traffic - * allowed to and from the resources in your VPC. - * You can choose the ports and protocols to allow for inbound and outbound traffic. - * - * The following example creates a security group that allows inbound RDP and SSH traffic from source CIDR 10.0.0.0/16. - * It also allows all outbound traffic. - * @example - * ``` - * - name: accelerator-sg - * description: Accelerator security group - * inboundRules: - * - description: Remote access security group rule - * types: - * - RDP - * - SSH - * sources: - * - 10.0.0.0/16 - * outboundRules: - * - description: Allow all outbound - * types: - * - ALL - * sources: - * - 0.0.0.0/0 - * ``` - */ -export interface ISecurityGroupConfig { - /** - * The friendly name of the security group. - * - * @remarks - * **CAUTION**: Changing this value after initial deployment will cause the security group to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) A description for the security group. - */ - readonly description?: t.NonEmptyString; - /** - * An array of security group rule configurations for ingress rules. - * - * @remarks - * **NOTE**: Changing values under this configuration object after initial deployment - * may cause some interruptions to network traffic while the security group is being updated. - * - * @see {@link SecurityGroupRuleConfig} - */ - readonly inboundRules: ISecurityGroupRuleConfig[]; - /** - * An array of security group rule configurations for egress rules. - * - * @remarks - * **NOTE**: Changing values under this configuration object after initial deployment - * may cause some interruptions to network traffic while the security group is being updated. - * - * @see {@link SecurityGroupRuleConfig} - */ - readonly outboundRules: ISecurityGroupRuleConfig[]; - /** - * (OPTIONAL) An array of tag objects for the security group. - */ - readonly tags?: t.ITag[]; -} - -export type InstanceTenancyType = 'default' | 'dedicated'; - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link NetworkAclConfig} / {@link NetworkAclInboundRuleConfig} | {@link NetworkAclOutboundRuleConfig} / {@link NetworkAclSubnetSelection}* - * - * @description - * Network ACL subnet selection configuration. - * Use this configuration to dynamically reference a subnet as a source/destination for a network ACL. - * - * @example - * ``` - * account: Network - * vpc: Network-Inspection - * subnet: Network-Inspection-A - * ``` - */ -export interface INetworkAclSubnetSelection { - /** - * The friendly name of the account of the subnet. - * - * @remarks - * This is the `account` property of the VPC as defined in network-config.yaml. - * If referencing a VPC template, use the logical `name` property of an account - * the template targets in its `deploymentTargets` property. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly account?: t.NonEmptyString; - /** - * The friendly name of the VPC of the subnet. - * - * @remarks - * This is the logical `name` property of the VPC or VPC template as defined in network-config.yaml. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly vpc: t.NonEmptyString; - /** - * The friendly name of the subnet. - * - * @remarks - * This is the logical `name` property of the subnet as defined in network-config.yaml. - * - * Each subnet must exist in the source VPC targeted in the `vpc` property. A security group rule will be created - * for each referenced subnet in this array. - * - * @see {@link SubnetConfig} - */ - readonly subnet: t.NonEmptyString; - /** - * (OPTIONAL) Indicates whether to target the IPv6 CIDR associated with a subnet. - * - * @remarks - * Leave this property undefined or set to `false` to target a subnet's IPv4 CIDR. - */ - readonly ipv6?: boolean; - /** - * (OPTIONAL) The region that the subnet is located in. - * - * @remarks - * This property only needs to be defined if targeting a subnet in a different region - * than the one in which this VPC is deployed. - */ - readonly region?: t.Region; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link NetworkAclConfig} / {@link NetworkAclInboundRuleConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html#nacl-rules | Network ACL inbound rule} configuration. - * - * @description - * Use this configuration to define inbound rules for your network ACLs. - * An inbound rule allows or denies specific inbound traffic at the subnet level. - * - * The following example allows inbound SSH traffic from source CIDR 10.0.0.0/16: - * @example - * ``` - * - rule: 200 - * protocol: 6 - * fromPort: 22 - * toPort: 22 - * action: allow - * source: 10.0.0.0/16 - * ``` - */ -export interface INetworkAclInboundRuleConfig { - /** - * The rule ID number for the rule. - * - * @remarks - * **CAUTION**: Changing this property value causes the rule to be recreated. - * This may temporarily impact your network traffic while the rule is updated. - * - * Rules are evaluated in order from low to high and must be unique per direction. - * As soon as a rule matches traffic, it's applied - * regardless of any higher-numbered rule that might contradict it. - */ - readonly rule: number; - /** - * The {@link https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml | IANA protocol number} for the network ACL rule. - * You may also specify -1 for all protocols. - */ - readonly protocol: number; - /** - * The port to start from in the network ACL rule. - */ - readonly fromPort: number; - /** - * The port to end with in the network ACL rule. - */ - readonly toPort: number; - /** - * The action for the network ACL rule. - */ - readonly action: t.AllowDeny; - /** - * The source of the network ACL rule. - * - * @remarks - * Possible values are a CIDR range or a network ACL subnet selection configuration. - * - * @see {@link NetworkAclSubnetSelection} - */ - readonly source: t.NonEmptyString | INetworkAclSubnetSelection; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link NetworkAclConfig} / {@link NetworkAclOutboundRuleConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html#nacl-rules | Network ACL outbound rule} configuration. - * - * @description - * Use this configuration to define outbound rules for your network ACLs. - * An outbound rule allows or denies specific outbound traffic at the subnet level. - * - * The following example allows outbound TCP traffic in the ephemeral port ranges to destination CIDR 10.0.0.0/16: - * @example - * ``` - * - rule: 200 - * protocol: 6 - * fromPort: 1024 - * toPort: 65535 - * action: allow - * destination: 10.0.0.0/16 - * ``` - */ -export interface INetworkAclOutboundRuleConfig { - /** - * The rule ID number for the rule. - * - * @remarks - * **CAUTION**: Changing this property value causes the rule to be recreated. - * This may temporarily impact your network traffic while the rule is updated. - * - * Rules are evaluated in order from low to high and must be unique per direction. - * As soon as a rule matches traffic, it's applied - * regardless of any higher-numbered rule that might contradict it. - */ - readonly rule: number; - /** - * The {@link https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml | IANA protocol number} for the network ACL rule. - * You may also specify -1 for all protocols. - */ - readonly protocol: number; - /** - * The port to start from in the network ACL rule. - */ - readonly fromPort: number; - /** - * The port to end with in the network ACL rule. - */ - readonly toPort: number; - /** - * The action for the network ACL rule. - */ - readonly action: t.AllowDeny; - /** - * The destination of the network ACL rule. - * - * @remarks - * Possible values are a CIDR range or a network ACL subnet selection configuration. - * - * @see {@link NetworkAclSubnetSelection} - */ - readonly destination: t.NonEmptyString | INetworkAclSubnetSelection; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link NetworkAclConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html | Network access control list (ACL)} configuration. - * - * @description - * Use this configuration to define custom network ACLs for your VPC. - * A network ACL allows or denies specific inbound or outbound traffic at the subnet level. - * Network ACLs are stateless, which means that responses to allowed inbound traffic are subject - * to the rules for outbound traffic (and vice versa). - * - * The following example shows an inbound and outbound rule that would allow - * inbound SSH traffic from the CIDR range 10.0.0.0/16. - * @example - * ``` - * - name: accelerator-nacl - * subnetAssociations: - * - Subnet-A - * inboundRules: - * - rule: 200 - * protocol: 6 - * fromPort: 22 - * toPort: 22 - * action: allow - * source: 10.0.0.0/16 - * outboundRules: - * - rule: 200 - * protocol: 6 - * fromPort: 1024 - * toPort: 65535 - * action: allow - * destination: 10.0.0.0/16 - * tags: [] - * ``` - */ -export interface INetworkAclConfig { - /** - * The name of the Network ACL. - * - * @remarks - * **CAUTION**: Changing this property value causes the network ACL to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * Please also note that your network traffic may be temporarily impacted while the ACL is updated. - */ - readonly name: t.NonEmptyString; - /** - * A list of subnets to associate with the Network ACL - * - * @remarks - * This is the logical `name` property of the subnet as defined in network-config.yaml. - * - * @see {@link SubnetConfig} - */ - readonly subnetAssociations: t.NonEmptyString[]; - /** - * (OPTIONAL) A list of inbound rules to define for the Network ACL - * - * @see {@link NetworkAclInboundRuleConfig} - */ - readonly inboundRules?: INetworkAclInboundRuleConfig[]; - /** - * (OPTIONAL) A list of outbound rules to define for the Network ACL - * - * @see {@link NetworkAclOutboundRuleConfig} - */ - readonly outboundRules?: INetworkAclOutboundRuleConfig[]; - /** - * (OPTIONAL) A list of tags to attach to the Network ACL - */ - readonly tags?: t.ITag[]; -} - -export type NetbiosNodeType = 1 | 2 | 4 | 8; - -/** - * *{@link NetworkConfig} / {@link DhcpOptsConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_DHCP_Options.html | VPC Dynamic Host Configuration Protocol (DHCP) options sets} configuration. - * - * @description - * Use this configuration to define custom DHCP options sets for your VPCs. - * Custom DHCP option sets give you control over the DNS servers, domain names, - * or Network Time Protocol (NTP) servers used by the devices in your VPC. - * - * The following example creates a DHCP option set named `accelerator-dhcp-opts` - * in the `Network` account in the `us-east-1` region. The options set assigns - * a domain name of `example.com` to hosts in the VPC and configures the DNS - * server to `1.1.1.1`. - * @example - * ``` - * dhcpOptions: - * - name: accelerator-dhcp-opts - * accounts: - * - Network - * regions: - * - us-east-1 - * domainName: example.com - * domainNameServers - * - 1.1.1.1 - * tags: [] - * ``` - */ -export interface IDhcpOptsConfig { - /** - * A friendly name for the DHCP options set. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the DHCP options set to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * An array of friendly account names to deploy the options set. - * - * @remarks - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly accounts: t.NonEmptyString[]; - /** - * An array of regions to deploy the options set. - * - * @see {@link Region} - */ - readonly regions: t.Region[]; - /** - * (OPTIONAL) A domain name to assign to hosts using the options set. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the DHCP options set to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly domainName?: t.NonEmptyString; - /** - * (OPTIONAL) An array of IP addresses for domain name servers. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the DHCP options set to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly domainNameServers?: t.NonEmptyString[]; - /** - * (OPTIONAL An array of IP addresses for NetBIOS servers. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the DHCP options set to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly netbiosNameServers?: t.NonEmptyString[]; - /** - * (OPTIONAL) The NetBIOS node type number. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the DHCP options set to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * @see {@link NetworkConfigTypes.netbiosNodeEnum} - */ - readonly netbiosNodeType?: NetbiosNodeType; - /** - * (OPTIONAL) An array of IP addresses for NTP servers. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the DHCP options set to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly ntpServers?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of tags for the options set. - */ - readonly tags?: t.ITag[]; -} - -export type MutationProtectionType = 'ENABLED' | 'DISABLED'; - -export interface IVpcDnsFirewallAssociationConfig { - readonly name: t.NonEmptyString; - readonly priority: number; - readonly mutationProtection?: MutationProtectionType; - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link EndpointPolicyConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-access.html | Virtual Private Cloud (VPC) endpoint policy} configuration. - * - * @description - * Use this configuration to define VPC endpoint policies for your VPC gateway and interface endpoints. - * The endpoint policy is a JSON policy document that controls which AWS principals can use the VPC - * endpoint to access the endpoint service. - * - * The following example defines an endpoint policy named `Default` and references a path - * where a JSON policy document is stored: - * @example - * ``` - * endpointPolicies: - * - name: Default - * document: path/to/document.json - * ``` - */ -export interface IEndpointPolicyConfig { - /** - * A friendly name for the endpoint policy. - * - * @remarks - * You use this logical `name` property as a reference to apply this policy - * to VPC gateway and interface endpoint configurations. - * - * @see {@link GatewayEndpointConfig} | {@link InterfaceEndpointConfig} - */ - readonly name: t.NonEmptyString; - /** - * A file path for a JSON-formatted policy document. - * - * @remarks - * The referenced file path must exist in your accelerator configuration repository. - * The document must be valid JSON syntax. - */ - readonly document: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} / {@link OutpostsConfig} / {@link LocalGatewayConfig} / {@link LocalGatewayRouteTableConfig}* - * - * {@link https://docs.aws.amazon.com/outposts/latest/userguide/routing.html | Outposts Local Gateway route table} configuration. - * - * @description - * Use this configuration to reference route tables for your Outposts local gateway. - * Outpost subnet route tables on a rack can include a route to your on-premises network. - * The local gateway routes this traffic for low latency routing to the on-premises network. - * - * @example - * ``` - * - name: accelerator-local-gateway-rtb - * id: lgw-rtb-abcxyz - * ``` - */ -export interface ILocalGatewayRouteTableConfig { - /** - * A friendly name for the Route Table - * - * @remarks - * This is a logical `name` property that can be used to reference the route table in subnet configurations. - * - * @see {@link SubnetConfig} - */ - readonly name: t.NonEmptyString; - /** - * The id for the Route Table - * - * @remarks - * This is an existing resource ID for the local gateway route table. - * The local gateway route table must exist in the account and region - * the accelerator-provisioned subnet is deployed to. - * - * To find the resource ID for the local gateway route table, please see the following instructions: {@link https://docs.aws.amazon.com/outposts/latest/userguide/routing.html#view-routes} - */ - readonly id: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} / {@link OutpostsConfig} / {@link LocalGatewayConfig}* - * - * {@link https://docs.aws.amazon.com/outposts/latest/userguide/outposts-local-gateways.html | Outposts Local Gateway} configuration. - * - * @description - * Use this configuration to reference existing local gateways for your Outposts. - * The local gateway for your Outpost rack enables connectivity from your Outpost subnets to - * all AWS services that are available in the parent Region, in the same way that you access them from an Availability Zone subnet. - * - * @example - * ``` - * name: accelerator-lgw - * id: lgw-abcxyz - * ``` - */ -export interface ILocalGatewayConfig { - /** - * A friendly name for the Local Gateway - */ - readonly name: t.NonEmptyString; - /** - * The id for the Local Gateway - * - * @remarks - * This is an existing resource ID for the local gateway. - * The local gateway must exist in the account and region - * the accelerator-provisioned subnet is deployed to. - * - * To find the resource ID for the local gateway, please see the following instructions: {@link https://docs.aws.amazon.com/outposts/latest/userguide/outposts-local-gateways.html#working-with-lgw} - */ - readonly id: t.NonEmptyString; - /** - * The route tables for the Local Gateway - */ - readonly routeTables: ILocalGatewayRouteTableConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} / {@link OutpostsConfig}* - * - * {@link https://docs.aws.amazon.com/outposts/latest/userguide/what-is-outposts.html | AWS Outposts} configuration. - * - * @description - * Use this configuration to reference Outposts that exist in your environment. - * AWS Outposts enables customers to build and run applications on premises using the same - * programming interfaces as in AWS Regions, while using local compute and storage resources - * for lower latency and local data processing needs. - * - * @example - * ``` - * - name: accelerator-outpost - * arn: - * availabilityZone: a - * localGateway: - * name: accelerator-lgw - * id: lgw-abcxyz - * routeTables: [] - * ``` - */ -export interface IOutpostsConfig { - /** - * A friendly name for the Outpost - * - * @remarks - * This is a logical `name` property that can be used to reference the outpost in subnet configurations. - * - * @see {@link SubnetConfig} - */ - readonly name: t.NonEmptyString; - /** - * The ARN for the Outpost - * - * @remarks - * This is an existing resource ARN for the outpost. - * The outpost must exist in the account and region - * the accelerator-provisioned subnet is deployed to. - * - * To find the resource ARN for the outpost, please reference **To view the Outpost details**: {@link https://docs.aws.amazon.com/outposts/latest/userguide/work-with-outposts.html#manage-outpost} - */ - readonly arn: t.NonEmptyString; - /** - * The availability zone where the Outpost resides - * - * @remarks - * Include only the letter of the AZ name (i.e. 'a' for 'us-east-1a') to target a subnet created in a specific AZ. Use an integer - * (i.e. 1) for subnets using a physical mapping ID to an AZ. Please reference the documentation {@link https://docs.aws.amazon.com/ram/latest/userguide/working-with-az-ids.html | Availability Zone IDs for your AWS resources} - * for more information. - */ - readonly availabilityZone: t.NonEmptyString | number; - /** - * The Local Gateway configuration for the Outpost - */ - readonly localGateway?: ILocalGatewayConfig; -} - -export type DpdTimeoutActionType = 'clear' | 'none' | 'restart'; -export type StartupActionType = 'add' | 'start'; -export type IkeVersionType = 1 | 2; -export type Phase1DhGroupType = 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24; -export type Phase2DhGroupType = 2 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24; -export type EncryptionAlgorithmType = 'AES128' | 'AES256' | 'AES128-GCM-16' | 'AES256-GCM-16'; -export type IntegrityAlgorithmType = 'SHA1' | 'SHA2-256' | 'SHA2-384' | 'SHA2-512'; -export type VpnLoggingOutputFormatType = 'json' | 'text'; - -/** - * *{@link NetworkConfig} / {@link CustomerGatewayConfig} / {@link VpnConnectionConfig} / {@link VpnTunnelOptionsSpecificationsConfig} / {@link Phase1Config}* - * - * @description - * Internet Key Exchange (IKE) Phase 1 tunnel options configuration. - * Use this configuration to restrict the permitted Diffie-Hellman group numbers, encryption algorithms, and integrity algorithms for IKE Phase 1 negotiations. - * You may also modify the Phase 1 lifetime for the VPN tunnel. - * - * @example - * ``` - * dhGroups: [14, 20, 24] - * encryptionAlgorithms: [AES256, AES256-GCM-16] - * integrityAlgorithms: [SHA2-256, SHA2-384, SHA2-512] - * lifetime: 3600 - * ``` - */ -export interface IPhase1Config { - /** - * (OPTIONAL) An array of permitted Diffie-Hellman group numbers used in the IKE Phase 1 for initial authentication. - * - * Default - `[2, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]` - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly dhGroups?: Phase1DhGroupType[]; - /** - * (OPTIONAL) An array of encryption algorithms permitted for IKE Phase 1 negotiations. - * - * Default - `[AES128, AES256, AES128-GCM-16, AES256-GCM-16]` - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly encryptionAlgorithms?: EncryptionAlgorithmType[]; - /** - * (OPTIONAL) An array of integrity algorithms permitted for IKE Phase 1 negotiations. - * - * Default - `[SHA1, SHA2-256, SHA2-384, SHA2-512]` - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly integrityAlgorithms?: IntegrityAlgorithmType[]; - /** - * (OPTIONAL) The IKE Phase 1 lifetime (in seconds) for the VPN tunnel. - * - * Default: `28800` (8 hours) - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * You can specify a value between 900 and 28800 - */ - readonly lifetimeSeconds?: number; -} - -/** - * *{@link NetworkConfig} / {@link CustomerGatewayConfig} / {@link VpnConnectionConfig} / {@link VpnTunnelOptionsSpecificationsConfig} / {@link Phase2Config}* - * - * @description - * Internet Key Exchange (IKE) Phase 2 tunnel options configuration. - * Use this configuration to restrict the permitted Diffie-Hellman group numbers, encryption algorithms, and integrity algorithms for IKE Phase 2 negotiations. - * You may also modify the Phase 2 lifetime for the VPN tunnel. - * - * @example - * ``` - * dhGroups: [14, 20, 24] - * encryptionAlgorithms: [AES256, AES256-GCM-16] - * integrityAlgorithms: [SHA2-256, SHA2-384, SHA2-512] - * lifetime: 1800 - * ``` - */ -export interface IPhase2Config { - /** - * (OPTIONAL) An array of permitted Diffie-Hellman group numbers used in the IKE Phase 2 negotiations. - * - * Default - `[2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]` - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly dhGroups?: Phase2DhGroupType[]; - /** - * (OPTIONAL) An array of encryption algorithms permitted for IKE Phase 2 negotiations. - * - * Default - `[AES128, AES256, AES128-GCM-16, AES256-GCM-16]` - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly encryptionAlgorithms?: EncryptionAlgorithmType[]; - /** - * (OPTIONAL) An array of integrity algorithms permitted for IKE Phase 2 negotiations. - * - * Default - `[SHA1, SHA2-256, SHA2-384, SHA2-512]` - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly integrityAlgorithms?: IntegrityAlgorithmType[]; - /** - * (OPTIONAL) The IKE Phase 2 lifetime (in seconds) for the VPN tunnel. - * - * Default: `3600` (1 hour) - * - * @remarks - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * You can specify a value between 900 and 3600 - */ - readonly lifetimeSeconds?: number; -} - -/** - * *{@link NetworkConfig} / {@link CustomerGatewayConfig} / {@link VpnConnectionConfig} / {@link VpnTunnelOptionsSpecificationsConfig} / {@link VpnLoggingConfig}* - * - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/monitoring-logs.html | AWS Site-to-Site VPN logging} configuration. - * - * @description - * Use this configuration to define CloudWatch log groups for your Site-to-Site VPN connections. - * AWS Site-to-Site VPN logs provide you with deeper visibility into your Site-to-Site VPN deployments. - * With this feature, you have access to Site-to-Site VPN connection logs that provide details on IP Security (IPsec) tunnel establishment, - * Internet Key Exchange (IKE) negotiations, and dead peer detection (DPD) protocol messages. - * - * @example - * Custom settings: - * ``` - * enable: true - * logGroupName: /vpn/logs/accelerator-vpn/tunnel1 - * outputFormat: text - * ``` - * - * Default settings: - * ``` - * enable: true - * ``` - */ -export interface IVpnLoggingConfig { - /** - * (OPTIONAL) Enable site-to-site VPN tunnel logging to CloudWatch Logs. - * - * @remarks - * If you enable this property, a log group will be created along with the VPN connection. - * You may customize the name of the log group using the `logGroupName` property. - * - * The global {@link cloudwatchLogRetentionInDays} configuration and accelerator-provisioned KMS key - * will be applied to the log group. - */ - readonly enable?: boolean; - /** - * (OPTIONAL) The name of the CloudWatch Logs log group that you would like tunnel logs to be sent to. - * - * Default - Randomly generated name based on CDK stack and VPN resource name. - * - * @remarks - * If defined, this value must be unique within the account the VPN connection is deployed to. - * For security purposes, your custom log group name will be prefixed with the Accelerator prefix - * value (AWSAccelerator or the custom prefix defined in the installer stack) - */ - readonly logGroupName?: t.NonEmptyString; - /** - * (OPTIONAL) The output format of the VPN tunnel logs. - * - * Default - `json` - */ - readonly outputFormat?: VpnLoggingOutputFormatType; -} - -/** - * *{@link NetworkConfig} / {@link CustomerGatewayConfig} / {@link VpnConnectionConfig} / {@link VpnTunnelOptionsSpecificationsConfig}* - * - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/VPNTunnels.html | VPN tunnel options} specification configuration. - * - * @description - * Use this configuration to define optional tunnel configurations for a site-to-site VPN connection. - * - * **IMPORTANT**: After initial deployment of your VPN connection with any of the v1.5.0+ options noted below, you can only make property changes to one VPN tunnel per core pipeline run. - * You may make multiple property changes in that one VPN tunnel if necessary. Trying to modify properties in both tunnels will result in a pipeline failure. This is due to the fact that - * only a single mutating API call can be made at a time for AWS Site-to-Site VPN connections. - * - * Note: you may manually roll back the resulting CloudFormation stack should you encounter this failure. More details on how to skip failed resources in the following reference: - * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html - * - * - * @example - * Versions v1.5.0 and up: - * ``` - * - dpdTimeoutAction: restart - * dpdTimeoutSeconds: 60 - * ikeVersions: [2] - * logging: - * enable: true - * phase1: - * dhGroups: [14] - * encryptionAlgorithms: [AES256] - * integrityAlgorithms: [SHA2-256] - * phase2: - * dhGroups: [14] - * encryptionAlgorithms: [AES256] - * integrityAlgorithms: [SHA2-256] - * tunnelInsideCidr: 169.254.200.0/30 - * preSharedKey: Key1-AbcXyz - * - dpdTimeoutAction: restart - * dpdTimeoutSeconds: 60 - * ikeVersions: [2] - * logging: - * enable: true - * phase1: - * dhGroups: [14] - * encryptionAlgorithms: [AES256] - * integrityAlgorithms: [SHA2-256] - * phase2: - * dhGroups: [14] - * encryptionAlgorithms: [AES256] - * integrityAlgorithms: [SHA2-256] - * tunnelInsideCidr: 169.254.200.100/30 - * preSharedKey: Key1-AbcXyz - * ``` - * Versions prior to v1.5.0: - * ``` - * - tunnelInsideCidr: 169.254.200.0/30 - * preSharedKey: Key1-AbcXyz - * - tunnelInsideCidr: 169.254.200.100/30 - * preSharedKey: Key1-AbcXyz - * ``` - */ -export interface IVpnTunnelOptionsSpecificationsConfig { - /** - * (OPTIONAL) Dead Peer Detection (DPD) timeout action. You can specify the action to take after DPD timeout occurs. - * - * Default - `clear` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * Available actions: - * * `clear`: End the IKE session when DPD timeout occurs (stop the tunnel and clear the routes) - * * `none`: Take no action when DPD timeout occurs - * * `restart`: Restart the IKE session when DPD timeout occurs - */ - readonly dpdTimeoutAction?: DpdTimeoutActionType; - /** - * (OPTIONAL) The duration, in seconds, after which Dead Peer Detection (DPD) timeout occurs. - * - * Default - `30` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * The value must be 30 seconds or higher. - */ - readonly dpdTimeoutSeconds?: number; - /** - * (OPTIONAL) The Internet Key Exchange (IKE) versions that are permitted on the tunnel. - * - * Default - `ikev1`,`ikev2` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * Only include one or both versions of IKE in the array. - */ - readonly ikeVersions?: IkeVersionType[]; - /** - * (OPTIONAL) Site-to-Site VPN CloudWatch logging configuration. - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - */ - readonly logging?: IVpnLoggingConfig; - /** - * (OPTIONAL) Internet Key Exchange (IKE) phase 1 configuration. - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly phase1?: IPhase1Config; - /** - * (OPTIONAL) Internet Key Exchange (IKE) phase 2 configuration. - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly phase2?: IPhase2Config; - /** - * (OPTIONAL): The Secrets Manager name that stores the pre-shared key (PSK), that exists in the - * same account and region that the VPN Connection will be created in. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Include the random hash suffix value in the Secrets Manager name. This can be found using the - * following procedure: - * 1. Navigate to the {@link https://us-east-1.console.aws.amazon.com/secretsmanager/listsecrets | Secrets Manager console}. - * 2. Select the region you stored the secret in. - * 3. Click on the name of the secret. - * 4. Under **Secret details**, the **Secret ARN** contains the full name of the secret, - * including the random hash suffix. This is the value after **secret:** in the ARN. - * - * NOTE: The `preSharedKey` (PSK) parameter is optional. If a PSK is not provided, Amazon will generate a - * PSK for you. - */ - readonly preSharedKey?: t.NonEmptyString; - /** - * (OPTIONAL) The percentage of the rekey window (determined by the rekey margin time) within which the rekey time is randomly selected. - * - * Default - `100` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * You can specify a percentage value between 0 and 100. - */ - readonly rekeyFuzzPercentage?: number; - /** - * (OPTIONAL) The margin time in seconds before the phase 1 and phase 2 lifetime expires, - * during which the AWS side of the VPN connection performs an IKE rekey. - * - * Default - `270` (4.5 minutes) - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * You can specify a number between 60 and half of the value of the phase 2 lifetime. - * The exact time of the rekey is randomly selected based on the value for rekey fuzz. - */ - readonly rekeyMarginTimeSeconds?: number; - /** - * (OPTIONAL) The number of packets in an IKE replay window. - * - * Default - `1024` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * You can specify a value between 64 and 2048. - */ - readonly replayWindowSize?: number; - /** - * (OPTIONAL) The action to take when the establishing the tunnel for the VPN connection. - * By default, your customer gateway device must initiate the IKE negotiation and bring up the tunnel. - * Specify `start` for Amazon Web Services to initiate the IKE negotiation. - * - * Default - `add` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - */ - readonly startupAction?: StartupActionType; - /** - * (OPTIONAL): The range of inside IP addresses for the tunnel. Any specified CIDR blocks must be unique across - * all VPN connections that use the same virtual private gateway. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * The following CIDR blocks are reserved and cannot be used: - 169.254.0.0/30 - 169.254.1.0/30 - - * 169.254.2.0/30 - 169.254.3.0/30 - 169.254.4.0/30 - 169.254.5.0/30 - 169.254.169.252/30 - */ - readonly tunnelInsideCidr?: t.NonEmptyString; - /** - * (OPTIONAL) Enable tunnel endpoint lifecycle control. This feature provides control over the schedule of endpoint replacements. - * For more information, see {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/tunnel-endpoint-lifecycle.html | Tunnel Endpoint Lifecycle Control}. - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - */ - readonly tunnelLifecycleControl?: boolean; -} - -/** - * *{@link NetworkConfig} / {@link CustomerGatewayConfig} / {@link VpnConnectionConfig}* - * - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/VPC_VPN.html | Site-to-site VPN Connection} configuration. - * - * @description - * Use this configuration to define the VPN connections that - * terminate either on a Transit Gateway or virtual private gateway. - * A VPN connection refers to the connection between your VPC and your own on-premises network. - * You can enable access to your remote network from your VPC by creating an - * AWS Site-to-Site VPN (Site-to-Site VPN) connection, and configuring routing - * to pass traffic through the connection. - * - * **IMPORTANT**: After initial deployment of your VPN connection with any of the v1.5.0+ options noted below, you can make property changes in one of {@link VpnConnectionConfig} or {@link VpnTunnelOptionsSpecificationsConfig}, but not both. - * You may make multiple property changes in one of those configurations if necessary. Trying to modify properties in both configurations will result in a pipeline failure. This is due to the fact that - * only a single mutating API call can be made at a time for AWS Site-to-Site VPN connections. - * - * Note: you may manually roll back the resulting CloudFormation stack should you encounter this failure. More details on how to skip failed resources in the following reference: - * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html - * - * @example - * VPN termination at a Transit Gateway: - * ``` - * - name: accelerator-vpn - * transitGateway: Network-Main - * routeTableAssociations: - * - Network-Main-Core - * routeTablePropagations: - * - Network-Main-Core - * staticRoutesOnly: false - * # Tunnel specifications are optional -- additional tunnel options available in configuration reference - * tunnelSpecifications: - * - tunnelInsideCidr: 169.254.200.0/30 - * preSharedKey: Key1-AbcXyz - * - tunnelInsideCidr: 169.254.200.100/30 - * preSharedKey: Key1-AbcXyz - * ``` - * VPN termination at a VPC: - * ``` - * - name: accelerator-vpn - * vpc: Inspection-Vpc - * staticRoutesOnly: false - * # Tunnel specifications are optional -- additional tunnel options available in configuration reference - * tunnelSpecifications: - * - tunnelInsideCidr: 169.254.200.0/30 - * preSharedKey: Key1-AbcXyz - * - tunnelInsideCidr: 169.254.200.100/30 - * preSharedKey: Key1-AbcXyz - * ``` - */ -export interface IVpnConnectionConfig { - /** - * The name of the VPN Connection. - * - * The value of this property will be utilized as the logical id for this - * resource. Any references to this object should specify this value. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) The Amazon-side IPv4 CIDR range that is allowed through the site-to-site VPN tunnel. - * Configuring this option restricts the Amazon-side CIDR range that can communicate with your - * local network. - * - * Default - `0.0.0.0/0` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, both of your VPN tunnel endpoints will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * Use CIDR notation, i.e. 10.0.0.0/16. - */ - readonly amazonIpv4NetworkCidr?: t.NonEmptyString; - /** - * (OPTIONAL) The customer-side IPv4 CIDR range that is allowed through the site-to-site VPN tunnel. - * Configuring this option restricts the local CIDR range that can communicate with your AWS environment. - * - * Default - `0.0.0.0/0` - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, both of your VPN tunnel endpoints will become temporarily unavailable. Please see - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/endpoint-replacements.html#endpoint-replacements-for-vpn-modifications | Customer initiated endpoint replacements} for - * additional details. - * - * Use CIDR notation, i.e. 10.0.0.0/16. - */ - readonly customerIpv4NetworkCidr?: t.NonEmptyString; - /** - * (OPTIONAL) Enable Site-to-Site VPN Acceleration. - * For more information, see {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/accelerated-vpn.html | Accelerated Site-to-Site VPN connections}. - * - * @remarks - * **CAUTION:** if you configure this property on a VPN connection that was deployed prior to v1.5.0, your VPN connection - * will be recreated. Please be aware that any downstream dependencies may cause this property update to fail. To ensure - * a clean replacement, we highly recommend deleting the original connection and its downstream dependencies prior to making this change. - * - * If you update this property after deployment, your VPN tunnel will be recreated. VPN acceleration can only - * be enabled/disabled on initial VPN connection creation. - * - * **NOTE:** Accelerated VPNs are only supported on VPNs terminating on transit gateways. - */ - readonly enableVpnAcceleration?: boolean; - /** - * The logical name of the Transit Gateway that the customer Gateway is attached to - * so that a VPN connection is established. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Must specify either the Transit Gateway name or the Virtual Private Gateway, not - * both. - */ - readonly transitGateway?: t.NonEmptyString; - /** - * (OPTIONAL) An array of Transit Gateway route table names to associate the VPN attachment to - * - * @remarks - * This is the `name` property of the Transit Gateway route table - * - * This property should only be defined if creating a VPN connection to a Transit Gateway. - * Leave undefined for VPN connections to virtual private gateways. - */ - readonly routeTableAssociations?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of Transit Gateway route table names to propagate the VPN attachment to - * - * @remarks - * This is the `name` property of the Transit Gateway route table - * - * This property should only be defined if creating a VPN connection to a Transit Gateway. - * Leave undefined for VPN connections to virtual private gateways. - */ - readonly routeTablePropagations?: t.NonEmptyString[]; - /** - * (OPTIONAL) If creating a VPN connection for a device that doesn't support Border Gateway Protocol (BGP) - * declare true as a value, otherwise, use false. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly staticRoutesOnly?: boolean; - /** - * The logical name of the Virtual Private Cloud that a Virtual Private Gateway is attached to. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Must specify either the Transit Gateway name or the Virtual Private Gateway, not - * both. - */ - readonly vpc?: t.NonEmptyString; - /** - * (OPTIONAL) Define the optional VPN Tunnel configuration - * @see {@link VpnTunnelOptionsSpecificationsConfig} - */ - readonly tunnelSpecifications?: IVpnTunnelOptionsSpecificationsConfig[]; - /** - * (OPTIONAL) An array of tags for the VPN Connection. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CustomerGatewayConfig}* - * - * {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/your-cgw.html | Customer Gateway (CGW)} Configuration. - * - * @description - * Use this configuration to define Customer Gateways and site-to-site VPN connections. - * A customer gateway device is a physical or software appliance that you own or manage in - * your on-premises network (on your side of a Site-to-Site VPN connection). - * A VPN connection refers to the connection between your VPC and your own on-premises network. - * - * @example - * ``` - * customerGateways: - * - name: accelerator-cgw - * account: Network - * region: *HOME_REGION - * ipAddress: 1.1.1.1 - * asn: 65500 - * vpnConnections: - * - name: accelerator-vpn - * transitGateway: Network-Main - * routeTableAssociations: - * - Network-Main-Core - * routeTablePropagations: - * - Network-Main-Core - * staticRoutesOnly: false - * tunnelSpecifications: - * - tunnelInsideCidr: 169.254.200.0/30 - * preSharedKey: Key1-AbcXyz - * - tunnelInsideCidr: 169.254.200.100/30 - * preSharedKey: Key2-AbcXyz - * ``` - */ -export interface ICustomerGatewayConfig { - /** - * The name of the CGW. - * - * The value of this property will be utilized as the logical id for this - * resource. Any references to this object should specify this value. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The logical name of the account to deploy the Customer Gateway to. This value should match the name of the account recorded - * in the accounts-config.yaml file. - */ - readonly account: t.NonEmptyString; - /** - * The AWS region to provision the customer gateway in - */ - readonly region: t.Region; - /** - * Defines the IP address of the Customer Gateway - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * To define a customer gateway that references an external appliance (i.e. on-premise or otherwise external to the accelerator), use a public-facing IPv4 address (i.e. 1.2.3.4). - * - * This property supports `ACCEL_LOOKUP` replacement variables to target the public IP address of a network interface attached to an - * {@link Ec2FirewallInstanceConfig} defined in `customizations-config.yaml`. The target network interface MUST be configured with the `associateElasticIp` property set to `true`. - * - * **NOTE:** This lookup value is not supported for firewalls defined in {@link Ec2FirewallAutoScalingGroupConfig}. - * - * Supported replacement: - * * Network interface replacement - look up a network interface attached to a firewall instance defined in `customizations-config.yaml` - * * Format:`${ACCEL_LOOKUP::EC2:ENI_:}`, where `` is the device index of the network interface - * as defined in the firewall launch template and `` is the name of the firewall instance. - * * Index numbering is zero-based, so the primary interface of the instance is `0`. - * * Example usage: `${ACCEL_LOOKUP::EC2:ENI_0:accelerator-firewall}` - translates to the primary public IP address of the primary network interface of a firewall named `accelerator-firewall`. - */ - readonly ipAddress: t.NonEmptyString; - /** - * Define the ASN used for the Customer Gateway - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPN to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * The private ASN range is 64512 to 65534. The default is 65000. - */ - readonly asn: number; - /** - * Define tags for the Customer Gateway - */ - readonly tags?: t.ITag[]; - /** - * Define the optional VPN Connection configuration - * @see {@link VpnConnectionConfig} - */ - readonly vpnConnections?: IVpnConnectionConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} / {@link VirtualPrivateGatewayConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpn-connections.html | Virtual Private Gateway} Configuration. - * - * @description - * Used to define Virtual Private Gateways that are attached to a VPC. - * You can create an IPsec VPN connection between your VPC and your remote network. - * On the AWS side of the Site-to-Site VPN connection, a virtual private gateway or transit - * gateway provides two VPN endpoints (tunnels) for automatic failover. - * - * @example - * ``` - * virtualPrivateGateway: - * asn: 65500 - * ``` - */ -export interface IVirtualPrivateGatewayConfig { - /** - * Define the ASN (Amazon Side) used for the Virtual Private Gateway - * - * @remarks - * The private ASN range is 64512 to 65534. The default is 65000. - */ - readonly asn: number; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} / {@link LoadBalancersConfig}* - * - * {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html | Elastic Load Balancers} Configuration. - * - * @description - * Use this configuration to define Application Load Balancers (ALBs) or - * Network Load Balancers (NLBs) to be deployed in the specified VPC subnets. - */ -export interface ILoadBalancersConfig { - /** - * (OPTIONAL) An array of Application Load Balancer (ALB) configurations. - * Use this property to define ALBs to be deployed in the specified VPC subnets. - * - * @see {@link ApplicationLoadBalancerConfig} - */ - readonly applicationLoadBalancers?: ci.IApplicationLoadBalancerConfig[]; - /** - * (OPTIONAL) An array of Network Load Balancer (NLB) configurations. - * Use this property to define NLBs to be deployed in the specified VPC subnets. - * - * @see {@link NetworkLoadBalancerConfig} - */ - readonly networkLoadBalancers?: ci.INetworkLoadBalancerConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig} | {@link VpcTemplatesConfig} / {@link VpcIpv6Config}* - * - * @description - * VPC IPv6 static CIDR configuration. Use this to associate a static IPv6 CIDR block to your VPC. - * - * @example - * Use an Amazon-provided /56 CIDR: - * ``` - * - amazonProvided: true - * ``` - * - * Use a BYOIP address pool with a default /56 CIDR: - * ``` - * - byoipPoolId: ipv6Pool-ec2-123abcxyz - * ``` - * - * Use a specific CIDR range of a BYOIP address pool: - * ``` - * - byoipPoolId: ipv6Pool-ec2-123abcxyz - * cidrBlock: fd00::/48 - * ``` - */ -export interface IVpcIpv6Config { - /** - * (OPTIONAL) Indicates whether Amazon automatically provisions a /56 IPv6 CIDR block for the VPC. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the CIDR block to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * Leave this property undefined if using a Bring-Your-Own-IP (BYOIP) address pool. - */ - readonly amazonProvided?: boolean; - /** - * (OPTIONAL) Associate an IPv6 CIDR block with your VPC. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the CIDR block to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * You MUST also specify `boipPoolId` if configuring this property. - * You may leave this property undefined to have Amazon automatically provision a /56 CIDR - * from your BYOIP address pool. - * Possible IPv6 netmask lengths are between /44 and /60 in increments of /4. - */ - readonly cidrBlock?: t.NonEmptyString; - /** - * (OPTIONAL) Used to define the Bring-Your-Own-IP (BYOIP) address pool ID to use for the IPv6 CIDR block. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the CIDR block to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * You must have configured a BYOIP address pool in the account the VPC is being provisioned in. - * For more information on setting up an address pool, see {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-byoip.html | Bring your own IP addresses (BYOIP) in Amazon EC2}. - */ - readonly byoipPoolId?: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html | Virtual Private Cloud (VPC)} configuration. - * - * @description - * Use this configuration to define a VPC that is deployed to a single account and region. - * With Amazon Virtual Private Cloud (Amazon VPC), you can launch AWS resources in a logically - * isolated virtual network that you've defined. This virtual network closely resembles a traditional - * network that you'd operate in your own data center, with the benefits of using the scalable infrastructure of AWS. - * - * @example - * Static CIDR: - * ``` - * vpcs: - * - name: Network-Inspection - * account: Network - * region: us-east-1 - * cidrs: - * - 10.0.0.0/24 - * enableDnsHostnames: true - * enableDnsSupport: true - * instanceTenancy: default - * routeTables: [] - * subnets: [] - * natGateways: [] - * transitGatewayAttachments: [] - * tags: [] - * ``` - * IPAM allocation: - * ``` - * vpcs: - * - name: Network-Inspection - * account: Network - * region: us-east-1 - * ipamAllocations: - * - ipamPoolName: accelerator-regional-pool - * netmaskLength: 24 - * enableDnsHostnames: true - * enableDnsSupport: true - * instanceTenancy: default - * routeTables: [] - * subnets: [] - * natGateways: [] - * transitGatewayAttachments: [] - * tags: [] - * ``` - * - * IPv6 static CIDR: - * ``` - * vpcs: - * - name: Network-Inspection - * account: Network - * region: us-east-1 - * cidrs: - * - 10.0.0.0/24 - * ipv6Cidrs: - * - byoipPool: ipv6Pool-ec2-123abcxyz - * enableDnsHostnames: true - * enableDnsSupport: true - * instanceTenancy: default - * routeTables: [] - * subnets: [] - * natGateways: [] - * transitGatewayAttachments: [] - * tags: [] - * ``` - */ -export interface IVpcConfig { - /** - * The friendly name of the VPC. - * - * The value of this property will be utilized as the logical id for this - * resource. Any references to this object should specify this value. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPC to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The logical name of the account to deploy the VPC to - * - * @remarks - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly account: t.NonEmptyString; - /** - * The AWS region to deploy the VPC to - */ - readonly region: t.Region; - /** - * (OPTIONAL) A list of IPv4 CIDRs to associate with the VPC. - * - * @remarks - * **CAUTION**: Changing or removing an existing CIDR value after initial deployment causes the VPC to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * You can add additional CIDRs to the VPC without this recreation occurring. - * - * NOTE: Expanding a VPC with additional CIDRs is subject to {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html#add-cidr-block-restrictions | these restrictions}. - * - * At least one CIDR should be - * provided if not using `ipamAllocations`. - * - * Use IPv4 CIDR notation, i.e. 10.0.0.0/16 - */ - readonly cidrs?: t.NonEmptyString[]; - /** - * (OPTIONAL) Determine if the all traffic ingress and egress rules are deleted - * in the default security group of a VPC. - * - * @remarks - * - * If the `defaultSecurityGroupRulesDeletion` parameter is set to `true`, the solution - * will proceed in removing the default ingress and egress All Traffic (0.0.0.0/0) for that - * respective VPC's default security group. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/default-custom-security-groups.html#default-security-group} - * - */ - readonly defaultSecurityGroupRulesDeletion?: boolean; - /** - * (OPTIONAL) The friendly name of a custom DHCP options set. - * - * @remarks - * This is the logical `name` property of the DHCP options set as defined in network-config.yaml. - * - * @see {@link DhcpOptsConfig} - */ - readonly dhcpOptions?: t.NonEmptyString; - /** - * (OPTIONAL) An array of DNS firewall VPC association configurations. - * Use this property to associate Route 53 resolver DNS firewall - * rule groups with the VPC. - * - * @see {@link NetworkConfigTypes.vpcDnsFirewallAssociationConfig} - * - * @remarks - * The DNS firewall rule groups must be deployed in the same region of the VPC and `shareTargets` must - * be configured to capture the account that this VPC is deployed to. If deploying this VPC to the delegated - * admin account, `shareTargets` is not required. - * - * @see {@link DnsFirewallRuleGroupConfig} - */ - readonly dnsFirewallRuleGroups?: IVpcDnsFirewallAssociationConfig[]; - /** - * (OPTIONAL) Create an {@link https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html | Egress-only internet gateway (EIGW)} for the VPC - */ - readonly egressOnlyIgw?: boolean; - /** - * Enable DNS hostname support for the VPC. - * - * @see {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html} - */ - readonly enableDnsHostnames?: boolean; - /** - * Enable DNS support for the VPC. - * - * @see {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html} - */ - readonly enableDnsSupport?: boolean; - /** - * (OPTIONAL) An array of gateway endpoints for the VPC. - * Use this property to define S3 or DynamoDB gateway endpoints for the VPC. - * - * @see {@link GatewayEndpointConfig} - */ - readonly gatewayEndpoints?: IGatewayEndpointConfig; - /** - * (OPTIONAL) Define instance tenancy for the VPC. The default value is `default`. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html} - */ - readonly instanceTenancy?: InstanceTenancyType; - /** - * (OPTIONAL) A list of VPC interface endpoints. - * Use this property to define VPC interface endpoints for the VPC. - * - * @see {@link InterfaceEndpointConfig} - */ - readonly interfaceEndpoints?: IInterfaceEndpointConfig; - /** - * Defines if an {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html | internet gateway} should be added to the VPC - */ - readonly internetGateway?: boolean; - /** - * (OPTIONAL) An array of IPAM allocation configurations. - * - * @see {@link IpamAllocationConfig} - * - * @remarks - * **CAUTION**: Changing or removing an existing IPAM allocation value after initial deployment causes the VPC to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * You can add additional IPAM allocations to the VPC without this recreation occurring. - * - * NOTE: Expanding a VPC with additional CIDRs is subject to {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html#add-cidr-block-restrictions | these restrictions}. - * - * IPAM pools defined in network-config.yaml must be deployed to the same region of the VPC and `shareTargets` must - * be configured to capture the account that this VPC is deployed to. If deploying this VPC to the delegated - * admin account, `shareTargets` is not required. - * - * @see {@link IpamPoolConfig} - * - */ - readonly ipamAllocations?: IIpamAllocationConfig[]; - /** - * (OPTIONAL) An array of IPv6 CIDR block configurations. - * - * @see {@link VpcIpv6Config} - * - * @remarks - * **CAUTION**: Changing or removing an existing IPv6 CIDR block may cause unexpected behavior if there are subnets provisioned using the CIDR. - * Please be aware that any downstream dependencies may cause this property update to fail. - * You can add additional IPv6 CIDR blocks to the VPC without interruptions occurring. - * - * At least one IPv4 static CIDR or IPAM allocation MUST be configured along with any IPv6 CIDR blocks. - * A VPC cannot be created without an IPv4 CIDR. - */ - readonly ipv6Cidrs?: IVpcIpv6Config[]; - /** - * (OPTIONAL) An array of NAT gateway configurations for the VPC. - * Use this property to configure the NAT gateways for the VPC. - * - * @see {@link NatGatewayConfig} - */ - readonly natGateways?: INatGatewayConfig[]; - /** - * (OPTIONAL) When set to true, this VPC will be configured to utilize centralized - * endpoints. This includes having the Route 53 Private Hosted Zone - * associated with this VPC. Centralized endpoints are configured per - * region, and can span to spoke accounts - * - * @default false - * - * @remarks - * A VPC deployed in the same region as this VPC in network-config.yaml must be configured with {@link InterfaceEndpointConfig} - * `central` property set to `true` to utilize centralized endpoints. - */ - readonly useCentralEndpoints?: boolean; - /** - * (OPTIONAL) A list of Security Groups to deploy for this VPC - * - * @default undefined - * - * @remarks - * As of version 1.4.0, if any {@link SubnetConfig} for this VPC is configured with a `shareTargets` property, - * the accelerator automatically replicates security groups configured in this - * VPC to the shared account(s). - */ - readonly securityGroups?: ISecurityGroupConfig[]; - /** - * (OPTIONAL) A list of Network Access Control Lists (ACLs) to deploy for this VPC - * - * @default undefined - * - * @see {@link NetworkAclConfig} - */ - readonly networkAcls?: INetworkAclConfig[]; - /** - * (OPTIONAL) A list of DNS query log configuration names. - * - * @remarks - * This is the logical `name` property of the Route 53 resolver query logs configuration as defined - * in network-config.yaml. The `shareTargets` property must be configured to capture the account that - * this VPC is deployed to. If deploying this VPC to the delegated admin account, `shareTargets` is not required. - * - * @see {@link DnsQueryLogsConfig} - */ - readonly queryLogs?: t.NonEmptyString[]; - /** - * (OPTIONAL) A list of Route 53 resolver rule names. - * - * @remarks - * This is the logical `name` property of the Route 53 resolver rules configuration as defined - * in network-config.yaml. The `shareTargets` property must be configured to capture the account that - * this VPC is deployed to. If deploying this VPC to the delegated admin account, `shareTargets` is not required. - * - * @see {@link ResolverRuleConfig} - */ - readonly resolverRules?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of route table configurations for the VPC. - * Use this property to configure the route tables for the VPC. - * - * @see {@link RouteTableConfig} - */ - readonly routeTables?: IRouteTableConfig[]; - /** - * (OPTIONAL) An array of subnet configurations for the VPC. - * Use this property to configure the subnets for the VPC. - * - * @see {@link SubnetConfig} - */ - readonly subnets?: ISubnetConfig[]; - /** - * (OPTIONAL) An array of Transit Gateway attachment configurations. - * Use this property to configure the Transit Gateway attachments for the VPC. - * - * @see {@link TransitGatewayAttachmentConfig} - */ - readonly transitGatewayAttachments?: ITransitGatewayAttachmentConfig[]; - /** - * (OPTIONAL) A list of tags to apply to this VPC - * - * @default undefined - * - * @remarks - * As of version 1.2.0, if any {@link SubnetConfig} for this VPC is configured with a `shareTargets` property, - * the accelerator automatically replicates tags configured in this - * VPC to the shared account(s). - * - */ - readonly tags?: t.ITag[]; - /** - * (OPTIONAL) An array of Local Gateway Route table configurations. - * Use this configuration to associate Outposts Local Gateway Route tables with the VPC. - */ - readonly outposts?: IOutpostsConfig[]; - /** - * (OPTIONAL) Virtual Private Gateway configuration. - * Use this property to configure a Virtual Private Gateway for the VPC. - * - * @default undefined - */ - readonly virtualPrivateGateway?: IVirtualPrivateGatewayConfig; - /** - * VPC flog log configuration. - * Use this property to define a VPC-specific VPC flow logs configuration. - * - * @remarks - * If defined, this configuration is preferred over a global - * VPC flow logs configuration. - * - * @see {@link VpcFlowLogsConfig} - */ - readonly vpcFlowLogs?: t.IVpcFlowLogsConfig; - /** - * Elastic Load Balancing configuration. - * Use this property to define Elastic Load Balancers for this VPC. - * - * @see {@link LoadBalancersConfig} - */ - readonly loadBalancers?: ILoadBalancersConfig; - /** - * Target group configuration. - * Use this property to define target groups for this VPC. - * - * @see {@link TargetGroupItemConfig} - */ - readonly targetGroups?: ci.ITargetGroupItem[]; - /** - * A Route 53 resolver configuration local to the VPC. - * - * @see {@link ResolverConfig} - */ - readonly vpcRoute53Resolver?: IResolverConfig; -} - -/** - * *{@link NetworkConfig} / {@link VpcTemplatesConfig}* - * - * {@link https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html | Virtual Private Cloud (VPC)} templates configuration. - * - * @description - * Use this configuration to define a VPC using a standard configuration that is deployed to multiple account(s)/OU(s) defined using a `deploymentTargets` property. - * With Amazon Virtual Private Cloud (Amazon VPC), you can launch AWS resources in a logically - * isolated virtual network that you've defined. This virtual network closely resembles a traditional - * network that you'd operate in your own data center, with the benefits of using the scalable infrastructure of AWS. - * - * Static CIDR: - * ``` - * vpcTemplates: - * - name: Accelerator-Template - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * region: us-east-1 - * cidrs: - * - 10.0.0.0/24 - * enableDnsHostnames: true - * enableDnsSupport: true - * instanceTenancy: default - * routeTables: [] - * subnets: [] - * natGateways: [] - * transitGatewayAttachments: [] - * tags: [] - * ``` - * IPAM allocation: - * ``` - * vpcTemplates: - * - name: Accelerator-Template - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * region: us-east-1 - * ipamAllocations: - * - ipamPoolName: accelerator-regional-pool - * netmaskLength: 24 - * enableDnsHostnames: true - * enableDnsSupport: true - * instanceTenancy: default - * routeTables: [] - * subnets: [] - * natGateways: [] - * transitGatewayAttachments: [] - * tags: [] - * ``` - * Static IPv6 CIDR: - * ``` - * vpcTemplates: - * - name: Accelerator-Template - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * region: us-east-1 - * cidrs: - * - 10.0.0.0/24 - * ipv6Cidrs: - * - amazonProvided: true - * enableDnsHostnames: true - * enableDnsSupport: true - * instanceTenancy: default - * routeTables: [] - * subnets: [] - * natGateways: [] - * transitGatewayAttachments: [] - * tags: [] - * ``` - */ -export interface IVpcTemplatesConfig { - /** - * The friendly name of the VPC. - * - * The value of this property will be utilized as the logical id for this - * resource. Any references to this object should specify this value. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the VPC to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The AWS region to deploy the VPCs to - */ - readonly region: t.Region; - /** - * VPC deployment targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * The `excludedRegions` property is ignored for VPC templates, - * as a VPC template can only be deployed to a single region. - * - * @see {@link DeploymentTargets} - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * (OPTIONAL) A list of IPv4 CIDRs to associate with the VPC. - * - * @remarks - * **CAUTION**: Changing or removing an existing CIDR value after initial deployment causes the VPC to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * You can add additional CIDRs to the VPC without this recreation occurring. - * - * NOTE: Expanding a VPC with additional CIDRs is subject to {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html#add-cidr-block-restrictions | these restrictions}. - * - * At least one CIDR should be - * provided if not using `ipamAllocations`. - * - * Use IPv4 CIDR notation, i.e. 10.0.0.0/16 - */ - readonly cidrs?: t.NonEmptyString[]; - /** - * (OPTIONAL) Determine if the all traffic ingress and egress rules are deleted - * in the default security group of a VPC. - * - * @remarks - * - * If the `defaultSecurityGroupRulesDeletion` parameter is set to `true`, the solution - * will proceed in removing the default ingress and egress All Traffic (0.0.0.0/0) for that - * respective VPC's default security group. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/default-custom-security-groups.html#default-security-group} - */ - readonly defaultSecurityGroupRulesDeletion?: boolean; - /** - * (OPTIONAL) The friendly name of a custom DHCP options set. - * - * @remarks - * This is the logical `name` property of the DHCP options set as defined in network-config.yaml. - * - * @see {@link DhcpOptsConfig} - */ - readonly dhcpOptions?: t.NonEmptyString; - /** - * (OPTIONAL) An array of DNS firewall VPC association configurations. - * Use this property to associate Route 53 resolver DNS firewall - * rule groups with the VPC. - * - * @see {@link NetworkConfigTypes.vpcDnsFirewallAssociationConfig} - * - * @remarks - * The DNS firewall rule groups must be deployed in the same region of the VPC and `shareTargets` must - * be configured to capture the account(s)/OU(s) that this VPC template is deployed to. If deploying this VPC to the delegated - * admin account, `shareTargets` is not required for that account. - * - * @see {@link DnsFirewallRuleGroupConfig} - */ - readonly dnsFirewallRuleGroups?: IVpcDnsFirewallAssociationConfig[]; - /** - * (OPTIONAL) Create an {@link https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html | Egress-only internet gateway (EIGW)} for the VPC - */ - readonly egressOnlyIgw?: boolean; - /** - * Enable DNS hostname support for the VPC. - * - * @see {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html} - */ - readonly enableDnsHostnames?: boolean; - /** - * Enable DNS support for the VPC. - * - * @see {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html} - */ - readonly enableDnsSupport?: boolean; - /** - * (OPTIONAL) An array of gateway endpoints for the VPC. - * Use this property to define S3 or DynamoDB gateway endpoints for the VPC. - * - * @see {@link GatewayEndpointConfig} - */ - readonly gatewayEndpoints?: IGatewayEndpointConfig; - /** - * (OPTIONAL) Define instance tenancy for the VPC. The default value is `default`. - * - * @see {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html} - */ - readonly instanceTenancy?: InstanceTenancyType; - /** - * (OPTIONAL) An array of IPv6 CIDR block configurations. - * - * @see {@link VpcIpv6Config} - * - * @remarks - * **CAUTION**: Changing or removing an existing IPv6 CIDR block may cause unexpected behavior if there are subnets provisioned using the CIDR. - * Please be aware that any downstream dependencies may cause this property update to fail. - * You can add additional IPv6 CIDR blocks to the VPC without interruptions occurring. - * - * At least one IPv4 static CIDR or IPAM allocation MUST be configured along with any IPv6 CIDR blocks. - * A VPC cannot be created without an IPv4 CIDR. - */ - readonly ipv6Cidrs?: IVpcIpv6Config[]; - /** - * (OPTIONAL) A list of VPC interface endpoints. - * Use this property to define VPC interface endpoints for the VPC. - * - * @see {@link InterfaceEndpointConfig} - */ - readonly interfaceEndpoints?: IInterfaceEndpointConfig; - /** - * Defines if an {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html | internet gateway} should be added to the VPC - */ - readonly internetGateway?: boolean; - /** - * (OPTIONAL) An array of IPAM allocation configurations. - * - * @see {@link IpamAllocationConfig} - * - * @remarks - * **CAUTION**: Changing or removing an existing IPAM allocation value after initial deployment causes the VPC to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * You can add additional IPAM allocations to the VPC without this recreation occurring. - * - * NOTE: Expanding a VPC with additional CIDRs is subject to {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html#add-cidr-block-restrictions | these restrictions}. - * - * IPAM pools defined in network-config.yaml must be deployed to the same region of the VPC and `shareTargets` must - * be configured to capture the account(s)/OU(s) that this VPC template is deployed to. If deploying this VPC to the delegated - * admin account, `shareTargets` is not required for that account. - * - * @see {@link IpamPoolConfig} - */ - readonly ipamAllocations?: IIpamAllocationConfig[]; - /** - * (OPTIONAL) An array of NAT gateway configurations for the VPC. - * Use this property to configure the NAT gateways for the VPC. - * - * @see {@link NatGatewayConfig} - */ - readonly natGateways?: INatGatewayConfig[]; - /** - * (OPTIONAL) When set to true, this VPC will be configured to utilize centralized - * endpoints. This includes having the Route 53 Private Hosted Zone - * associated with this VPC. Centralized endpoints are configured per - * region, and can span to spoke accounts - * - * @default false - * - * @remarks - * A VPC deployed in the same region as this VPC in network-config.yaml must be configured with {@link InterfaceEndpointConfig} - * `central` property set to `true` to utilize centralized endpoints. - */ - readonly useCentralEndpoints?: boolean; - /** - * (OPTIONAL) A list of Security Groups to deploy for this VPC - * - * @default undefined - */ - readonly securityGroups?: ISecurityGroupConfig[]; - /** - * (OPTIONAL) A list of Network Access Control Lists (ACLs) to deploy for this VPC - * - * @default undefined - * - * @see {@link NetworkAclConfig} - */ - readonly networkAcls?: INetworkAclConfig[]; - /** - * (OPTIONAL) A list of DNS query log configuration names. - * - * @remarks - * This is the logical `name` property of the Route 53 resolver query logs configuration as defined - * in network-config.yaml. The `shareTargets` property must be configured to capture the account(s)/OUs that - * this VPC template is deployed to. If deploying this VPC to the delegated admin account, `shareTargets` is not required for that account. - * - * @see {@link DnsQueryLogsConfig} - */ - readonly queryLogs?: t.NonEmptyString[]; - /** - * (OPTIONAL) A list of Route 53 resolver rule names. - * - * @remarks - * This is the logical `name` property of the Route 53 resolver rules configuration as defined - * in network-config.yaml. The `shareTargets` property must be configured to capture the account(s)/OUs that - * this VPC template is deployed to. If deploying this VPC to the delegated admin account, `shareTargets` is not required for that account. - * - * @see {@link ResolverRuleConfig} - */ - readonly resolverRules?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of route table configurations for the VPC. - * Use this property to configure the route tables for the VPC. - * - * @see {@link RouteTableConfig} - */ - readonly routeTables?: IRouteTableConfig[]; - /** - * (OPTIONAL) An array of subnet configurations for the VPC. - * Use this property to configure the subnets for the VPC. - * - * @see {@link SubnetConfig} - */ - readonly subnets?: ISubnetConfig[]; - /** - * (OPTIONAL) An array of Transit Gateway attachment configurations. - * Use this property to configure the Transit Gateway attachments for the VPC. - * - * @see {@link TransitGatewayAttachmentConfig} - */ - readonly transitGatewayAttachments?: ITransitGatewayAttachmentConfig[]; - /** - * (OPTIONAL) Virtual Private Gateway configuration. - * Use this property to configure a Virtual Private Gateway for the VPC. - * - * @default undefined - */ - readonly virtualPrivateGateway?: IVirtualPrivateGatewayConfig; - /** - * (OPTIONAL) A list of tags to apply to this VPC - * - * @default undefined - * - */ - readonly tags?: t.ITag[]; - /** - * VPC flog log configuration. - * Use this property to define a VPC-specific VPC flow logs configuration. - * - * @remarks - * If defined, this configuration is preferred over a global - * VPC flow logs configuration. - * - * @see {@link VpcFlowLogsConfig} - */ - readonly vpcFlowLogs?: t.IVpcFlowLogsConfig; - /** - * Elastic Load Balancing configuration. - * Use this property to define Elastic Load Balancers for this VPC. - * - * @see {@link LoadBalancersConfig} - */ - readonly loadBalancers?: ILoadBalancersConfig; - /** - * Target group configuration. - * Use this property to define target groups for this VPC. - * - * @see {@link TargetGroupItemConfig} - */ - readonly targetGroups?: ci.ITargetGroupItem[]; -} - -export type RuleType = 'FORWARD' | 'RECURSIVE' | 'SYSTEM'; - -export interface IRuleTargetIps { - readonly ip: t.NonEmptyString; - readonly port?: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link ResolverConfig} / ({@link ResolverEndpointConfig}) / {@link ResolverRuleConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-rules-managing.html | Route 53 resolver rule} configuration. - * - * @description - * Use this configuration to define resolver SYSTEM and FORWARD rules for your resolver. - * If you want Resolver to forward queries for specified domain names to your network, - * you create one forwarding rule for each domain name and specify the name of the - * domain for which you want to forward queries. - * - * @remarks - * FORWARD rules should be defined under an OUTBOUND {@link ResolverEndpointConfig}. SYSTEM rules - * should be defined directly under {@link ResolverConfig}. - * - * The following example creates a forwarding rule for `example.com` that is shared with the - * entire organization. This rule targets an example on-prem IP address of `1.1.1.1`. - * @example - * ``` - * - name: accelerator-rule - * domainName: example.com - * ruleType: FORWARD - * shareTargets: - * organizationalUnits: - * - Root - * targetIps: - * - ip: 1.1.1.1 - * tags: [] - * ``` - */ -export interface IResolverRuleConfig { - /** - * A friendly name for the resolver rule. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the rule to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The domain name for the resolver rule. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment may cause some interruptions - * to your network traffic. - */ - readonly domainName: t.NonEmptyString; - /** - * (OPTIONAL) Regions to exclude from SYSTEM rule deployment. - * - * @remarks - * Only define this property if creating a `SYSTEM` rule type. - * This does not apply to rules of type `FORWARD`. - */ - readonly excludedRegions?: t.Region[]; - /** - * (OPTIONAL) The friendly name of an inbound endpoint to target. - * - * @remarks - * This is the logical `name` property of an INBOUND endpoint as defined in network-config.yaml. - * - * Use this property to define resolver rules for resolving DNS records across subdomains - * hosted within the accelerator environment. This creates a FORWARD rule that targets - * the IP addresses of an INBOUND endpoint. - * - * @see {@link ResolverEndpointConfig} - */ - readonly inboundEndpointTarget?: t.NonEmptyString; - /** - * (OPTIONAL) The type of rule to create. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the rule to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * When you want to forward DNS queries for specified domain name to resolvers on your network, - * specify FORWARD. - * - * When you have a forwarding rule to forward DNS queries for a domain to your network and you want - * Resolver to process queries for a subdomain of that domain, specify SYSTEM. - * - * Currently, only the Resolver service can create rules that have a value of RECURSIVE for ruleType. - * Do not use type RECURSIVE. This is reserved for future use. - * - * @see {@link NetworkConfigTypes.ruleTypeEnum} - */ - readonly ruleType?: RuleType; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Targets must include the account(s)/OU(s) of any VPCs that - * the rule will be associated with. - * You do not need to target the delegated admin account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * (OPTIONAL) An array of target IP configurations for the resolver rule. - * - * @remarks - * Use this property to define target IP addresses/ports to forward DNS queries to. - * Only define a port if the DNS server is using a non-standard port (i.e. any port other than port 53). - * - * @see {@link NetworkConfigTypes.ruleTargetIps} - */ - readonly targetIps?: IRuleTargetIps[]; - /** - * (OPTIONAL) An array of tags for the resolver rule. - */ - readonly tags?: t.ITag[]; -} - -export type ResolverEndpointType = 'INBOUND' | 'OUTBOUND'; - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link ResolverConfig} / {@link ResolverEndpointConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-overview-DSN-queries-to-vpc.html | Route 53 resolver endpoint} configuration. - * - * @description - * Use this configuration to define inbound and outbound resolver endpoints. - * Route 53 Resolver contains endpoints that you configure to answer DNS queries to - * and from your on-premises environment. - * - * - * @example - * Outbound endpoint: - * ``` - * - name: accelerator-outbound - * type: OUTBOUND - * vpc: Network-Endpoints - * allowedCidrs: - * - 10.0.0.0/16 - * subnets: - * - Subnet-A - * - Subnet-B - * rules: [] - * tags: [] - * ``` - * Inbound Endpoint: - * ``` - * - name: accelerator-inbound - * type: INBOUND - * vpc: Network-Endpoints - * allowedCidrs: - * - 10.0.0.0/16 - * subnets: - * - Subnet-A - * - Subnet-B - * tags: [] - * ``` - */ -export interface IResolverEndpointConfig { - /** - * The friendly name of the resolver endpoint. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the rule to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The type of resolver endpoint to deploy. - * - * INBOUND: allows DNS queries to your VPC from your network - * - * OUTBOUND: allows DNS queries from your VPC to your network - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the rule to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * @see {@link NetworkConfigTypes.resolverEndpointTypeEnum} - */ - readonly type: ResolverEndpointType; - /** - * The friendly name of the VPC to deploy the resolver endpoint to. - * - * @remarks - * This is the logical `name` property of a VPC as defined in network-config.yaml. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly vpc: t.NonEmptyString; - /** - * An array of friendly names for subnets to deploy the resolver endpoint to. - * - * @remarks - * This is the logical `name` property of subnets as defined in network-config.yaml. - * Subnets must be contained within the VPC referenced in the `vpc` property. - * - * @see {@link SubnetConfig} - */ - readonly subnets: t.NonEmptyString[]; - /** - * (OPTIONAL) The allowed ingress/egress CIDRs for the resolver endpoint security group. - * - * @remarks - * When resolver endpoints are defined, a security group is automatically created by the accelerator for the endpoints. - * You can use this property to specify an array of CIDRs you would like to be explicitly allowed - * in this security group. Otherwise, all IPs (0.0.0.0/0) are allowed for the direction - * based on the `type` property of the endpoint. - */ - readonly allowedCidrs?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of resolver rule configurations for the endpoint. - * - * @remarks - * Resolver rules should only be defined for outbound endpoints. This - * property should be left undefined for inbound endpoints. - * - * @see {@link ResolverRuleConfig} - */ - readonly rules?: IResolverRuleConfig[]; - /** - * (OPTIONAL) An array of tags for the resolver endpoint. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link ResolverConfig} / {@link DnsQueryLogsConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-query-logs.html | Route 53 Resolver DNS query logging} configuration. - * - * @description - * Use this configuration to define a centralized query logging configuration that can - * be associated with VPCs in your environment. - * You can use this configuration to log queries that originate from your VPCs, - * queries to your inbound and outbound resolver endpoints, - * and queries that use Route 53 Resolver DNS firewall to allow, block, or monitor - * domain lists. - * - * The following example creates a query logging configuration that logs to both - * S3 and a CloudWatch Logs log group. It is shared with the entire organization. - * @example - * ``` - * name: accelerator-query-logs - * destinations: - * - s3 - * - cloud-watch-logs - * shareTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IDnsQueryLogsConfig { - /** - * The friendly name of the query logging config. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the configuration to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * An array of destination services used to store the logs. - */ - readonly destinations: t.LogDestinationType[]; - /** - * Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Targets must include the account(s)/OU(s) of any VPCs that - * the logging configuration will be associated with. - * You do not need to target the delegated admin account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - readonly excludedRegions?: t.Region[]; -} - -export type DnsFirewallRuleActionType = 'ALLOW' | 'ALERT' | 'BLOCK'; -export type DnsFirewallBlockResponseType = 'NODATA' | 'NXDOMAIN' | 'OVERRIDE'; -export type DnsFirewallManagedDomainListsType = - | 'AWSManagedDomainsAggregateThreatList' - | 'AWSManagedDomainsBotnetCommandandControl' - | 'AWSManagedDomainsMalwareDomainList'; - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link ResolverConfig} / {@link DnsFirewallRuleGroupConfig} / {@link DnsFirewallRulesConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-rule-settings.html |Route 53 DNS firewall rule} configuration. - * - * @description - * Use this configuration to define individual rules for your DNS firewall. - * This allows you to define the DNS firewall behavior for your VPCs. - * - * - * @example - * The following example creates a rule that blocks requests from a custom list of domains. - * The custom domain list path must exist in your accelerator configuration repository. - * ``` - * - name: accelerator-dns-rule - * action: BLOCK - * priority: 100 - * blockResponse: NXDOMAIN - * customDomainList: path/to/domains.txt - * ``` - * - * The following example creates a rule referencing an AWS-managed domain list. - * The managed domain list must be available in the region you are deploying - * the rule to. - * ``` - * - name: accelerator-dns-rule - * action: BLOCK - * priority: 200 - * blockResponse: NODATA - * managedDomainList: AWSManagedDomainsAggregateThreatList - * ``` - */ -export interface IDnsFirewallRulesConfig { - /** - * A friendly name for the DNS firewall rule. - */ - readonly name: t.NonEmptyString; - /** - * An action for the DNS firewall rule to take on matching requests. - * - * @see {@link NetworkConfigTypes.dnsFirewallRuleActionTypeEnum} - */ - readonly action: DnsFirewallRuleActionType; - /** - * The priority of the DNS firewall rule. - * - * @remarks - * Rules are evaluated in order from low to high number. - * Priority values must be unique in each defined rule group. - */ - readonly priority: number; - /** - * (OPTIONAL) Configure an override domain for BLOCK actions. - * This is a custom DNS record to send back in response to the query. - * - * @remarks - * Only define this property if your are using a `blockResponse` of OVERRIDE. - */ - readonly blockOverrideDomain?: t.NonEmptyString; - /** - * (OPTIONAL) Configure a time-to-live (TTL) for the override domain. - * This is the recommended amount of time for the DNS resolver or - * web browser to cache the override record and use it in response to this query, - * if it is received again. By default, this is zero, and the record isn't cached. - * - * @remarks - * Only define this property if your are using a `blockResponse` of OVERRIDE. - * - */ - readonly blockOverrideTtl?: number; - /** - * Configure a specific response type for BLOCK actions. - * Block response types are defined here: {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-rule-actions.html} - * - * @see {@link NetworkConfigTypes.dnsFirewallBlockResponseTypeEnum} - */ - readonly blockResponse?: DnsFirewallBlockResponseType; - /** - * A file containing a custom domain list in TXT format. - * - * @remarks - * The file must exist in your accelerator configuration repository. - * The file must contain domain names separated by newlines. - * - * Include only one of `customDomainList` or `managedDomainList` for each rule definition. - */ - readonly customDomainList?: t.NonEmptyString; - /** - * Configure a rule that uses an AWS-managed domain list. - * AWS-managed domain lists are defined here: {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-managed-domain-lists.html}. - * - * @remarks - * Before using a managed domain list, please ensure that it is available in the region you are deploying it to. - * Regional availability of managed domain lists is included in the link above. - * - * Include only one of `customDomainList` or `managedDomainList` for each rule definition. - * - * @see {@link NetworkConfigTypes.dnsFirewallManagedDomainListEnum} - */ - readonly managedDomainList?: DnsFirewallManagedDomainListsType; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link ResolverConfig} / {@link DnsFirewallRuleGroupConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-rule-groups.html | Route 53 DNS firewall rule group} configuration. - * - * @description - * Use this configuration to define a group of rules for your DNS firewall. - * Rule groups contain one to many rules that can be associated with VPCs in your environment. - * These rules allow you to define the behavior of your DNS firewall. - * - * The following example creates a rule group that contains one rule entry. - * The rule blocks a list of custom domains contained in a file in the accelerator - * configuration repository. The rule group is shared to the entire organization. - * @example - * ``` - * - name: accelerator-rule-group - * regions: - * - us-east-1 - * rules: - * - name: accelerator-dns-rule - * action: BLOCK - * priority: 100 - * blockResponse: NXDOMAIN - * customDomainList: path/to/domains.txt - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * ``` - */ -export interface IDnsFirewallRuleGroupConfig { - /** - * A friendly name for the DNS firewall rule group. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the configuration to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The regions to deploy the rule group to. - * - * @see {@link Region} - */ - readonly regions: t.Region[]; - /** - * An array of DNS firewall rule configurations. - * - * @see {@link DnsFirewallRulesConfig} - */ - readonly rules: IDnsFirewallRulesConfig[]; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Targets must include the account(s)/OU(s) of any VPCs that - * the logging configuration will be associated with. - * You do not need to target the delegated admin account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * An array of tags for the rule group. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link VpcConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver.html Route 53 Resolver} configuration. - * - * @description - * Use this configuration to define local resolver endpoints and Route 53 query logging to the VPC. - * - * @example - * ``` - * vpcRoute53Resolver: - * endpoints: - * - name: accelerator-outbound - * type: OUTBOUND - * vpc: Network-Endpoints - * allowedCidrs: - * - 10.0.0.0/16 - * subnets: - * - Subnet-A - * - Subnet-B - * rules: [] - * tags: [] - * queryLogs: - * name: accelerator-query-logs - * destinations: - * - s3 - * - cloud-watch-logs - * shareTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IVpcResolverConfig { - /** - * (OPTIONAL) An array of Route 53 resolver endpoint configurations. - * - * @see {@link ResolverEndpointConfig} - */ - readonly endpoints?: IResolverEndpointConfig[]; - /** - * (OPTIONAL) A Route 53 resolver DNS query logging configuration. - * - * @see {@link DnsQueryLogsConfig} - */ - readonly queryLogs?: IDnsQueryLogsConfig; -} -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link ResolverConfig}* - * - * {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver.html Route 53 Resolver} configuration. - * - * @description - * Use this configuration to define several features of Route 53 resolver, including resolver endpoints, - * DNS firewall rule groups, and DNS query logs. - * Amazon Route 53 Resolver responds recursively to DNS queries from AWS resources for public records, - * Amazon VPC-specific DNS names, and Amazon Route 53 private hosted zones, and is available by default in all VPCs. - * - * @example - * ``` - * route53Resolver: - * endpoints: - * - name: accelerator-outbound - * type: OUTBOUND - * vpc: Network-Endpoints - * allowedCidrs: - * - 10.0.0.0/16 - * subnets: - * - Subnet-A - * - Subnet-B - * rules: [] - * tags: [] - * firewallRuleGroups: - * - name: accelerator-rule-group - * regions: - * - us-east-1 - * rules: - * - name: accelerator-dns-rule - * action: BLOCK - * priority: 100 - * blockResponse: NXDOMAIN - * customDomainList: path/to/domains.txt - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * queryLogs: - * name: accelerator-query-logs - * destinations: - * - s3 - * - cloud-watch-logs - * shareTargets: - * organizationalUnits: - * - Root - * ``` - */ - -export interface IResolverConfig { - /** - * (OPTIONAL) An array of Route 53 resolver endpoint configurations. - * - * @see {@link ResolverEndpointConfig} - */ - readonly endpoints?: IResolverEndpointConfig[]; - /** - * (OPTIONAL) An array of Route 53 DNS firewall rule group configurations. - * - * @see {@link DnsFirewallRuleGroupConfig} - */ - readonly firewallRuleGroups?: IDnsFirewallRuleGroupConfig[]; - /** - * (OPTIONAL) A Route 53 resolver DNS query logging configuration. - * - * @see {@link DnsQueryLogsConfig} - */ - readonly queryLogs?: IDnsQueryLogsConfig; - /** - * (OPTIONAL) An array of Route 53 resolver rules. - * - * @remarks - * This `rules` property should only be used for rules of type `SYSTEM`. - * For rules of type `FORWARD`, define under the {@link ResolverEndpointConfig} configuration object. - */ - readonly rules?: IResolverRuleConfig[]; -} - -export type NfwRuleType = 'STATEFUL' | 'STATELESS'; -export type NfwGeneratedRulesType = 'ALLOWLIST' | 'DENYLIST'; -export type NfwTargetType = 'TLS_SNI' | 'HTTP_HOST'; -export type NfwStatefulRuleActionType = 'ALERT' | 'DROP' | 'PASS'; -export type NfwStatefulRuleDirectionType = 'ANY' | 'FORWARD'; -export type NfwStatefulRuleProtocolType = - | 'DCERPC' - | 'DHCP' - | 'DNS' - | 'FTP' - | 'HTTP' - | 'ICMP' - | 'IKEV2' - | 'IMAP' - | 'IP' - | 'KRB5' - | 'MSN' - | 'NTP' - | 'SMB' - | 'SMTP' - | 'SSH' - | 'TCP' - | 'TFTP' - | 'TLS' - | 'UDP'; -export type NfwStatelessRuleActionType = 'aws:pass' | 'aws:drop' | 'aws:forward_to_sfe'; -export type NfwStatefulDefaultActionType = - | 'aws:drop_strict' - | 'aws:drop_established' - | 'aws:alert_strict' - | 'aws:alert_established'; -export type NfwStatelessRuleTcpFlagType = 'FIN' | 'SYN' | 'RST' | 'PSH' | 'ACK' | 'URG' | 'ECE' | 'CWR'; -export type NfwStatefulRuleOptionsType = 'DEFAULT_ACTION_ORDER' | 'STRICT_ORDER'; -export type NfwLogType = 'ALERT' | 'FLOW'; - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwRuleSourceListConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateful-rule-groups-ips.html | Network Firewall stateful rule} source list configuration. - * - * @description - * Use this configuration to define DNS domain allow and deny lists for Network Firewall. - * Domain lists allow you to configure domain name filtering for your Network Firewall. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-rulessourcelist.html} for more details. - * - * The following example creates a deny list for all subdomains of `example.com`. - * It checks packets for both TLS_SNI as well as HTTP_HOST headers with this value. - * @example - * ``` - * generatedRulesType: DENYLIST - * targets: - * - .example.com - * targetTypes: ['TLS_SNI', 'HTTP_HOST'] - * ``` - */ -export interface INfwRuleSourceListConfig { - /** - * The type of rules to generate from the source list. - */ - readonly generatedRulesType: NfwGeneratedRulesType; - /** - * An array of target domain names. - * - * @remarks - * Supported values are as fallows: - * Explicit domain names such as `www.example.com`. - * Wildcard domain names should be prefaced with a `.`. For example: `.example.com` - */ - readonly targets: t.NonEmptyString[]; - /** - * An array of protocol types to inspect. - * - * @see {@link NetworkConfigTypes.nfwTargetType} - */ - readonly targetTypes: NfwTargetType[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwRuleSourceStatefulRuleConfig} / {@link NfwRuleSourceStatefulRuleHeaderConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateful-rule-groups-ips.html | Network Firewall stateful rule} header configuration. - * - * @description - * Use this configuration to define stateful rules for Network Firewall in an IP packet header format. - * This header format can be used instead of Suricata-compatible rules to define your stateful firewall - * filtering behavior. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-header.html} for more details. - * - * The following example creates a stateful rule that inspects all traffic from source 10.1.0.0/16 to destination - * 10.0.0.0/16: - * @example - * ``` - * source: 10.1.0.0/16 - * sourcePort: ANY - * destination: 10.0.0.0/16 - * destinationPort: ANY - * direction: FORWARD - * protocol: IP - * ``` - */ -export interface INfwRuleSourceStatefulRuleHeaderConfig { - /** - * The destination CIDR range to inspect for. - * - * @remarks - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly destination: t.NonEmptyString; - /** - * The destination port or port range to inspect. - * - * @remarks - * To specify a port range, separate the values with a colon `:`. - * For example: `80:443`. To specify all ports, use `ANY`. - */ - readonly destinationPort: t.NonEmptyString; - /** - * The direction of the traffic flow to inspect. - * - * @remarks - * Use `ANY` to match bidirectional traffic. - * - * Use `FORWARD` to match only traffic going from the source to destination. - */ - readonly direction: NfwStatefulRuleDirectionType; - /** - * The protocol to inspect. - * - * @remarks - * To specify all traffic, use `IP`. - */ - readonly protocol: NfwStatefulRuleProtocolType; - /** - * The source CIDR range to inspect for. - * - * @remarks - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly source: t.NonEmptyString; - /** - * The source port or port range to inspect. - * - * @remarks - * To specify a port range, separate the values with a colon `:`. - * For example: `80:443`. To specify all ports, use `ANY`. - */ - readonly sourcePort: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwRuleSourceStatefulRuleConfig} / {@link NfwRuleSourceStatefulRuleOptionsConfig}* - * - * @description - * Network Firewall stateful rule options configuration. - * Use this configuration to specify keywords and setting metadata for stateful rules. - * - * @remarks - * Keywords and settings can be used to define specific metadata for - * stateful firewall rules that are defined using the {@link NfwRuleSourceStatefulRuleHeaderConfig}. - * For Suricata-compatible rules, include the rule options in the Suricata string. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-ruleoption.html}. - * - * The following example creates a `sid` keyword with a value of 100: - * @example - * ``` - * - keyword: sid - * settings: ['100'] - * ``` - */ -export interface INfwRuleSourceStatefulRuleOptionsConfig { - /** - * A Suricata-compatible keyword. - */ - readonly keyword: t.NonEmptyString; - /** - * An array of values for the keyword. - */ - readonly settings?: t.NonEmptyString[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwRuleSourceStatefulRuleConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateful-rule-groups-ips.html | Network Firewall stateful rule} configuration. - * - * @description - * Use this configuration to define stateful rules for Network Firewall in an IP packet header format. - * This header format can be used instead of Suricata-compatible rules to define your stateful firewall - * filtering behavior. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-statefulrule.html} - * - * @example - * ``` - * - action: PASS - * header: - * source: 10.1.0.0/16 - * sourcePort: ANY - * destination: 10.0.0.0/16 - * destinationPort: ANY - * direction: FORWARD - * protocol: IP - * ruleOptions: - * - keyword: sid - * settings: ['100'] - * ``` - */ -export interface INfwRuleSourceStatefulRuleConfig { - /** - * The action type for the stateful rule. - * - * @see {@link NetworkConfigTypes.nfwStatefulRuleActionType} - */ - readonly action: NfwStatefulRuleActionType; - /** - * A Network Firewall stateful rule header configuration. - * - * @see {@link NfwRuleSourceStatefulRuleHeaderConfig} - */ - readonly header: INfwRuleSourceStatefulRuleHeaderConfig; - /** - * An array of Network Firewall stateful rule options configurations. - * - * @see {@link NfwRuleSourceStatefulRuleOptionsConfig} - */ - readonly ruleOptions: INfwRuleSourceStatefulRuleOptionsConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceCustomActionConfig} / {@link NfwRuleSourceCustomActionDefinitionConfig} / {@link NfwRuleSourceCustomActionDimensionConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/rule-action.html#rule-action-stateless | Network Firewall stateless custom action} dimensions. - * - * @description - * Use this configuration to define custom action dimensions to log in CloudWatch metrics. - * You can optionally specify a named custom action to apply. - * For this action, Network Firewall assigns a dimension to Amazon CloudWatch metrics - * with the name set to CustomAction and a value that you specify. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-dimension.html} - * - * @example - * ``` - * dimensions: - * - CustomValue - * ``` - */ -export interface INfwRuleSourceCustomActionDimensionConfig { - /** - * An array of values of the custom metric dimensions to log. - */ - readonly dimensions: t.NonEmptyString[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceCustomActionConfig} / {@link NfwRuleSourceCustomActionDefinitionConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/rule-action.html#rule-action-stateless | Network Firewall stateless custom action} definition configuration. - * - * @description - * Use this configuration to define custom CloudWatch metrics for Network Firewall. - * You can optionally specify a named custom action to apply. - * For this action, Network Firewall assigns a dimension to Amazon CloudWatch metrics - * with the name set to CustomAction and a value that you specify. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-actiondefinition.html} - * - * @example - * ``` - * publishMetricAction: - * dimensions: - * - CustomValue - * ``` - */ -export interface INfwRuleSourceCustomActionDefinitionConfig { - /** - * A Network Firewall custom action dimensions configuration. - * - * @see {@link NfwRuleSourceCustomActionDimensionConfig} - */ - readonly publishMetricAction: INfwRuleSourceCustomActionDimensionConfig; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceCustomActionConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/rule-action.html#rule-action-stateless | Network Firewall stateless custom action} configuration. - * - * @description - * Use this configuration to define to define custom actions for Network Firewall. - * You can optionally specify a named custom action to apply. - * For this action, Network Firewall assigns a dimension to Amazon CloudWatch metrics - * with the name set to CustomAction and a value that you specify. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-customaction.html} - * - * @example - * ``` - * actionDefinition: - * publishMetricAction: - * dimensions: - * - CustomValue - * actionName: CustomAction - * ``` - */ -export interface INfwRuleSourceCustomActionConfig { - /** - * A Network Firewall custom action definition configuration. - * - * @see {@link NfwRuleSourceCustomActionDefinitionConfig} - */ - readonly actionDefinition: INfwRuleSourceCustomActionDefinitionConfig; - /** - * A friendly name for the custom action. - */ - readonly actionName: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceStatelessRuleConfig} / {@link NfwRuleSourceStatelessRuleDefinitionConfig} / {@link NfwRuleSourceStatelessMatchAttributesConfig} / {@link NfwRuleSourceStatelessPortRangeConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-rule-groups-5-tuple.html | Network Firewall stateless rule} port range configuration. - * - * @description - * Use this configuration to define a port range in stateless rules. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-portrange.html} - * - * @example - * ``` - * - fromPort: 22 - * toPort: 22 - * ``` - */ -export interface INfwRuleSourceStatelessPortRangeConfig { - /** - * The port to start from in the range. - */ - readonly fromPort: number; - /** - * The port to end with in the range. - */ - readonly toPort: number; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceStatelessRuleConfig} / {@link NfwRuleSourceStatelessRuleDefinitionConfig} / {@link NfwRuleSourceStatelessMatchAttributesConfig} / {@link NfwRuleSourceStatelessTcpFlagsConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-rule-groups-5-tuple.html | Network Firewall stateless rule} TCP flags configuration. - * - * @description - * Use this configuration to define TCP flags to inspect in stateless rules. - * Optional, standard TCP flag settings, which indicate which flags to inspect and the values to inspect for. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-tcpflagfield.html} - * - * @example - * ``` - * - flags: ['SYN', 'ECE'] - * masks: [] - * ``` - */ -export interface INfwRuleSourceStatelessTcpFlagsConfig { - /** - * An array of TCP flags. - * - * @remarks - * Used in conjunction with the Masks setting to define the flags that must be set - * and flags that must not be set in order for the packet to match. - * This setting can only specify values that are also specified in the Masks setting. - */ - readonly flags: NfwStatelessRuleTcpFlagType[]; - /** - * The set of flags to consider in the inspection. - * - * @remarks - * For the flags that are specified in the masks setting, the following must be true - * for the packet to match: - * The ones that are set in this flags setting must be set in the packet. - * The ones that are not set in this flags setting must also not be set in the packet. - * To inspect all flags in the valid values list, leave this with no setting. - */ - readonly masks: NfwStatelessRuleTcpFlagType[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceStatelessRuleConfig} / {@link NfwRuleSourceStatelessRuleDefinitionConfig} / {@link NfwRuleSourceStatelessMatchAttributesConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-rule-groups-5-tuple.html | Network Firewall stateless rule} match attributes configuration. - * - * @description - * Use this configuration to define stateless rule match attributes for Network Firewall. - * To be a match, a packet must satisfy all of the match settings in the rule. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-matchattributes.html} - * - * @example - * ``` - * protocols: [6] - * sources: - * - 10.1.0.0/16 - * sourcePorts: - * - fromPort: 1024 - * toPort: 65535 - * destinations: - * - 10.0.0.0/16 - * destinationPorts: - * - fromPort: 22 - * toPort: 22 - * ``` - */ -export interface INfwRuleSourceStatelessMatchAttributesConfig { - /** - * (OPTIONAL) An array of Network Firewall stateless port range configurations. - * - * @remarks - * The destination ports to inspect for. If not specified, this matches with any destination port. - * This setting is only used for protocols 6 (TCP) and 17 (UDP). - * - * @see {@link NfwRuleSourceStatelessPortRangeConfig} - */ - readonly destinationPorts?: INfwRuleSourceStatelessPortRangeConfig[]; - /** - * (OPTIONAL) An array of destination CIDR ranges to inspect for. - * - * @remarks - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly destinations?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of IP protocol numbers to inspect for. - */ - readonly protocols?: number[]; - /** - * (OPTIONAL) An array of Network Firewall stateless port range configurations. - * - * @remarks - * The source ports to inspect for. If not specified, this matches with any source port. - * This setting is only used for protocols 6 (TCP) and 17 (UDP). - * - * @see {@link NfwRuleSourceStatelessPortRangeConfig} - */ - readonly sourcePorts?: INfwRuleSourceStatelessPortRangeConfig[]; - /** - * (OPTIONAL) An array of source CIDR ranges to inspect for. - * - * @remarks - * Use CIDR notation, i.e. 10.0.0.0/16 - */ - readonly sources?: t.NonEmptyString[]; - /** - * (OPTIONAL) An array of Network Firewall stateless TCP flag configurations. - * - * @see {@link NfwRuleSourceStatelessTcpFlagsConfig} - */ - readonly tcpFlags?: INfwRuleSourceStatelessTcpFlagsConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceStatelessRuleConfig} / {@link NfwRuleSourceStatelessRuleDefinitionConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-rule-groups-5-tuple.html | Network Firewall stateless rule} definition configuration. - * - * @description - * Use this configuration to define a stateless rule definition for your Network Firewall. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-ruledefinition.html} - * - * @example - * ``` - * actions: ['aws:pass'] - * matchAttributes: - * protocols: [6] - * sources: - * - 10.1.0.0/16 - * sourcePorts: - * - fromPort: 1024 - * toPort: 65535 - * destinations: - * - 10.0.0.0/16 - * destinationPorts: - * - fromPort: 22 - * toPort: 22 - * ``` - */ -export interface INfwRuleSourceStatelessRuleDefinitionConfig { - /** - * An array of actions to take using the stateless rule engine. - */ - readonly actions: (t.NonEmptyString | NfwStatelessRuleActionType)[]; - /** - * A Network Firewall stateless rule match attributes configuration. - * - * @see {@link NfwRuleSourceStatelessMatchAttributesConfig} - */ - readonly matchAttributes: INfwRuleSourceStatelessMatchAttributesConfig; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig} / {@link NfwRuleSourceStatelessRuleConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-rule-groups-5-tuple.html | Network Firewall stateless rule} configuration. - * - * @description - * Use this configuration to define stateless rule for your Network Firewall. - * Network Firewall supports the standard stateless 5-tuple rule specification - * for network traffic inspection. When Network Firewall finds a match between - * a rule's inspection criteria and a packet, we say that the packet matches - * the rule and its rule group, and Network Firewall applies the rule's specified action to the packet. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-statelessrule.html}. - * - * The following example creates a stateless rule that allows SSH traffic from source 10.1.0.0/16 - * to destination 10.0.0.0/16. The rule has a priority value of 100: - * @example - * ``` - * - priority: 100 - * ruleDefinition: - * actions: ['aws:pass'] - * matchAttributes: - * sources: - * - 10.1.0.0/16 - * sourcePorts: - * - fromPort: 1024 - * toPort: 65535 - * destinations: - * - 10.0.0.0/16 - * destinationPorts: - * - fromPort: 22 - * toPort: 22 - * ``` - */ -export interface INfwRuleSourceStatelessRuleConfig { - /** - * The priority number for the rule. - * - * @remarks - * Priority is evaluated in order from low to high. - * Priority numbers must be unique within a rule group. - */ - readonly priority: number; - /** - * A Network Firewall stateless rule definition configuration. - * - * @see {@link NfwRuleSourceStatelessRuleDefinitionConfig} - */ - readonly ruleDefinition: INfwRuleSourceStatelessRuleDefinitionConfig; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig} / {@link NfwStatelessRulesAndCustomActionsConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-rule-groups-5-tuple.html | Network Firewall stateless rules} and - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/rule-action.html#rule-action-stateless | custom actions} configuration. - * - * @description - * Use this configuration to define stateless rules and custom actions for Network Firewall. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-statelessrulesandcustomactions.html} - * - * @example - * ``` - * statelessRules: - * - priority: 100 - * ruleDefinition: - * actions: ['aws:pass'] - * matchAttributes: - * sources: - * - 10.1.0.0/16 - * sourcePorts: - * - fromPort: 1024 - * toPort: 65535 - * destinations: - * - 10.0.0.0/16 - * destinationPorts: - * - fromPort: 22 - * toPort: 22 - * customActions: - * actionDefinition: - * publishMetricAction: - * dimensions: - * - CustomValue - * actionName: CustomAction - * ``` - */ -export interface INfwStatelessRulesAndCustomActionsConfig { - /** - * An array of Network Firewall stateless rule configurations. - * - * @see {@link NfwRuleSourceStatelessRuleConfig} - */ - readonly statelessRules: INfwRuleSourceStatelessRuleConfig[]; - /** - * An array of Network Firewall custom action configurations. - * - * @see {@link NfwRuleSourceCustomActionConfig} - */ - readonly customActions?: INfwRuleSourceCustomActionConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleSourceConfig}* - * - * @description - * Network Firewall rule source configuration. - * Use this configuration to define stateful and/or stateless rules for your Network Firewall. - * The following rules sources are supported: - * - File with list of Suricata-compatible rules - * - Domain list - * - Single Suricata-compatible rule - * - Stateful rule in IP header format - * - Stateless rules and custom actions - * - * @see {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/rule-sources.html} - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-rulessource.html} - * - * @example - * File with list of Suricata rules: - * ``` - * rulesFile: path/to/rules.txt - * ``` - * Domain list: - * ``` - * rulesSourceList: - * generatedRulesType: DENYLIST - * targets: - * - .example.com - * targetTypes: ['TLS_SNI', 'HTTP_HOST'] - * ``` - * Single Suricata rule: - * ``` - * rulesString: 'pass ip 10.1.0.0/16 any -> 10.0.0.0/16 any (sid:100;)' - * ``` - * Stateful rule in IP header format: - * ``` - * statefulRules: - * - action: PASS - * header: - * source: 10.1.0.0/16 - * sourcePort: ANY - * destination: 10.0.0.0/16 - * destinationPort: ANY - * direction: FORWARD - * protocol: IP - * ruleOptions: - * - keyword: sid - * settings: ['100'] - * ``` - * Stateless rules: - * ``` - * statelessRulesAndCustomActions: - * statelessRules: - * - priority: 100 - * ruleDefinition: - * actions: ['aws:pass'] - * matchAttributes: - * sources: - * - 10.1.0.0/16 - * sourcePorts: - * - fromPort: 1024 - * toPort: 65535 - * destinations: - * - 10.0.0.0/16 - * destinationPorts: - * - fromPort: 22 - * toPort: 22 - * ``` - */ -export interface INfwRuleSourceConfig { - /** - * (OPTIONAL) A Network Firewall rule source list configuration. - * Use this property to define a domain list for Network Firewall. - * - * @see {@link NfwRuleSourceListConfig} - */ - readonly rulesSourceList?: INfwRuleSourceListConfig; - /** - * (OPTIONAL) A Suricata-compatible stateful rule string. - * Use this property to define a single Suricata-compatible rule for Network Firewall. - * - * @see {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/suricata-examples.html#suricata-example-rule-with-variables} - */ - readonly rulesString?: t.NonEmptyString; - /** - * (OPTIONAL) An array of Network Firewall stateful rule IP header configurations. - * Use this property to define a stateful rule in IP header format for Network Firewall. - * - * @see {@link NfwRuleSourceStatefulRuleConfig} - */ - readonly statefulRules?: INfwRuleSourceStatefulRuleConfig[]; - /** - * (OPTIONAL) A Network Firewall stateless rules and custom action configuration. - * Use this property to define stateless rules and custom actions for Network Firewall. - * - * @see {@link NfwStatelessRulesAndCustomActionsConfig} - */ - readonly statelessRulesAndCustomActions?: INfwStatelessRulesAndCustomActionsConfig; - /** - * (OPTIONAL) Suricata rules file. - * Use this property to define a Suricata-compatible rules file for Network Firewall. - * - * @remarks - * The path must exist in your accelerator configuration repository. - * The file must be formatted with Suricata-compatible rules separated - * by newlines. - * - * @see {@link https://suricata.readthedocs.io/en/suricata-6.0.2/rules/intro.html} - * - */ - readonly rulesFile?: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleVariableConfig} / {@link NfwRuleVariableDefinitionConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/suricata-examples.html#suricata-example-rule-with-variables | Network Firewall rule variable} definition configuration. - * - * @description - * Use this configuration to define rule variable definitions for Network Firewall. - * Rule variables can be used in Suricata-compatible and domain list rule definitions. - * They are not supported in stateful rule IP header definitions. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-rulevariables.html} - * - * @example - * ``` - * - name: HOME_NET - * definition: ['10.0.0.0/16'] - * ``` - */ -export interface INfwRuleVariableDefinitionConfig { - /** - * A name for the rule variable. - */ - readonly name: t.NonEmptyString; - /** - * An array of values for the rule variable. - */ - readonly definition: t.NonEmptyString[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig} / {@link NfwRuleVariableConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/suricata-examples.html#suricata-example-rule-with-variables | Network Firewall rule variable} configuration. - * - * @description - * Use this configuration to define rule variable definitions for Network Firewall. - * Rule variables can be used in Suricata-compatible and domain list rule definitions. - * They are not supported in stateful rule IP header definitions. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-rulevariables.html} - * - * @example - * CURRENT SYNTAX: use the following syntax when defining new rule variables in v1.3.1 and newer. - * The additional example underneath is provided for backward compatibility. - * ``` - * ipSets: - * - name: HOME_NET - * definition: ['10.0.0.0/16'] - * portSets: - * - name: HOME_NET - * definition: ['80', '443'] - * ``` - * - * THE BELOW EXAMPLE SYNTAX IS DEPRECATED: use the above syntax when defining new or more than one rule variable - * ``` - * ipSets: - * name: HOME_NET - * definition: ['10.0.0.0/16'] - * portSets: - * name: HOME_NET - * definition: ['80', '443'] - * ``` - */ -export interface INfwRuleVariableConfig { - /** - * A Network Firewall rule variable definition configuration. - * - * @see {@link NfwRuleVariableDefinitionConfig} - */ - readonly ipSets: INfwRuleVariableDefinitionConfig | INfwRuleVariableDefinitionConfig[]; - /** - * A Network Firewall rule variable definition configuration. - * - * @see {@link NfwRuleVariableDefinitionConfig} - */ - readonly portSets: INfwRuleVariableDefinitionConfig | INfwRuleVariableDefinitionConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig} / {@link NfwRuleGroupRuleConfig}* - * - * @description - * Network Firewall rule group rule configuration. - * Used to define rules for a Network Firewall rule group. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-rulegroup-rulegroup.html} - * - * @example - * ``` - * rulesSource: - * rulesFile: path/to/rules.txt - * ruleVariables: - * ipSets: - * - name: HOME_NET - * definition: ['10.0.0.0/16'] - * portSets: - * - name: HOME_NET - * definition: ['80', '443'] - * ``` - */ -export interface INfwRuleGroupRuleConfig { - /** - * A Network Firewall rule source configuration. - * - * @see {@link NfwRuleSourceConfig} - */ - readonly rulesSource: INfwRuleSourceConfig; - /** - * A Network Firewall rule variable configuration. - * - * @see {@link NfwRuleVariableConfig} - */ - readonly ruleVariables?: INfwRuleVariableConfig; - /** - * A stateful rule option for the rule group. - * - * @see {@link NetworkConfigTypes.nfwStatefulRuleOptionsType} - */ - readonly statefulRuleOptions?: NfwStatefulRuleOptionsType; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwRuleGroupConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateful-rule-groups-ips.html | Network Firewall rule group} configuration. - * - * @description - * Use this configuration to define stateful and stateless rule groups for Network Firewall. - * An AWS Network Firewall rule group is a reusable set of criteria for inspecting and handling network traffic. - * You add one or more rule groups to a firewall policy as part of policy configuration. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-rulegroup.html} - * - * @example - * Stateful rule group: - * ``` - * - name: accelerator-stateful-group - * regions: - * - us-east-1 - * capacity: 100 - * type: STATEFUL - * ruleGroup: - * rulesSource: - * rulesFile: path/to/rules.txt - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * ``` - * Stateless rule group: - * ``` - * - name: accelerator-stateless-group - * regions: - * - us-east-1 - * capacity: 100 - * type: STATELESS - * ruleGroup: - * rulesSource: - * statelessRulesAndCustomActions: - * statelessRules: - * - priority: 100 - * ruleDefinition: - * actions: ['aws:pass'] - * matchAttributes: - * sources: - * - 10.1.0.0/16 - * sourcePorts: - * - fromPort: 1024 - * toPort: 65535 - * destinations: - * - 10.0.0.0/16 - * destinationPorts: - * - fromPort: 22 - * toPort: 22 - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * ``` - */ -export interface INfwRuleGroupConfig { - /** - * A friendly name for the rule group. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the rule group to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The regions to deploy the rule group to. - * - * @see {@link Region} - */ - readonly regions: t.Region[]; - /** - * The capacity of the rule group. - */ - readonly capacity: number; - /** - * The type of rules in the rule group. - */ - readonly type: NfwRuleType; - /** - * (OPTIONAL) A description for the rule group. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) A Network Firewall rule configuration. - * - * @see {@link NfwRuleGroupRuleConfig} - */ - readonly ruleGroup?: INfwRuleGroupRuleConfig; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Targets must be configured for account(s)/OU(s) that require - * access to the rule group. A target is not required for the - * delegated admin account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * (OPTIONAL) An array of tags for the rule group. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwFirewallPolicyConfig} / {@link NfwFirewallPolicyPolicyConfig} / {@link NfwStatefulRuleGroupReferenceConfig}* - * - * @description - * Network Firewall stateful rule group reference configuration. - * Use this configuration to reference a stateful rule group in a Network Firewall policy. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statefulrulegroupreference.html} - * - * @example - * ``` - * - name: accelerator-stateful-group - * ``` - */ -export interface INfwStatefulRuleGroupReferenceConfig { - /** - * The friendly name of the rule group. - * - * @remarks - * This is the logical `name` property of the rule group as defined in network-config.yaml. - * - * @see {@link NfwRuleGroupConfig} - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) If using strict ordering, a priority number for the rule. - */ - readonly priority?: number; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwFirewallPolicyConfig} / {@link NfwFirewallPolicyPolicyConfig} / {@link NfwStatelessRuleGroupReferenceConfig}* - * - * @description - * Network Firewall stateless rule group reference configuration. - * Use this configuration to reference a stateless rule group in a Network Firewall policy. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-statelessrulegroupreference.html} - * - * @example - * ``` - * - name: accelerator-stateless-group - * priority: 100 - * ``` - */ -export interface INfwStatelessRuleGroupReferenceConfig { - /** - * The friendly name of the rule group. - * - * @remarks - * This is the logical `name` property of the rule group as defined in network-config.yaml. - * - * @see {@link NfwRuleGroupConfig} - */ - readonly name: t.NonEmptyString; - /** - * A priority number for the rule. - */ - readonly priority: number; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwFirewallPolicyConfig} / {@link NfwFirewallPolicyPolicyConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-policies.html | Network Firewall policy} policy configuration. - * - * @description - * Use this configuration to define how the Network Firewall policy will behave. - * An AWS Network Firewall firewall policy defines the monitoring and protection behavior - * for a firewall. The details of the behavior are defined in the rule groups that you add - * to your policy, and in some policy default settings. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-firewallpolicy-firewallpolicy.html} - * - * @example: - * ``` - * statelessDefaultActions: ['aws:forward_to_sfe'] - * statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - * statefulRuleGroups: - * - name: accelerator-stateful-group - * statelessRuleGroups: - * - name: accelerator-stateless-group - * priority: 100 - * ``` - */ -export interface INfwFirewallPolicyPolicyConfig { - /** - * (OPTIONAL) An array of default actions to take on packets evaluated by the stateful engine. - */ - readonly statefulDefaultActions?: NfwStatefulDefaultActionType[]; - /** - * (OPTIONAL) Define how the stateful engine will evaluate packets. - * - * @remarks - * Default is DEFAULT_ACTION_ORDER. This property must be specified - * if creating a STRICT_ORDER policy. - */ - readonly statefulEngineOptions?: NfwStatefulRuleOptionsType; - /** - * {OPTIONAL) An array of Network Firewall stateful rule group reference configurations. - * - * @see {@link NfwStatefulRuleGroupReferenceConfig} - */ - readonly statefulRuleGroups?: INfwStatefulRuleGroupReferenceConfig[]; - /** - * (OPTIONAL) An array of Network Firewall custom action configurations. - * - * @see {@link NfwRuleSourceCustomActionConfig} - */ - readonly statelessCustomActions?: INfwRuleSourceCustomActionConfig[]; - /** - * An array of default actions to take on packets evaluated by the stateless engine. - * - * @remarks - * If using a custom action, the action must be defined in the `statelessCustomActions` property. - */ - readonly statelessDefaultActions: (NfwStatelessRuleActionType | t.NonEmptyString)[]; - /** - * An array of default actions to take on fragmented packets. - * - * @remarks - * If using a custom action, the action must be defined in the `statelessCustomActions` property. - */ - readonly statelessFragmentDefaultActions: (NfwStatelessRuleActionType | t.NonEmptyString)[]; - /** - * (OPTIONAL) An array of Network Firewall stateless rule group reference configurations. - * - * @see {@link NfwStatelessRuleGroupReferenceConfig} - */ - readonly statelessRuleGroups?: INfwStatelessRuleGroupReferenceConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwFirewallPolicyConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-policies.html | Network Firewall policy} configuration. - * - * @description - * Use this configuration to define a Network Firewall policy. - * An AWS Network Firewall firewall policy defines the monitoring and protection behavior - * for a firewall. The details of the behavior are defined in the rule groups that you add - * to your policy, and in some policy default settings. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-firewallpolicy.html} - * - * @example - * ``` - * - name: accelerator-nfw-policy - * firewallPolicy: - * statelessDefaultActions: ['aws:forward_to_sfe'] - * statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - * statefulRuleGroups: - * - name: accelerator-stateful-group - * statelessRuleGroups: - * - name: accelerator-stateless-group - * priority: 100 - * regions: - * - us-east-1 - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * ``` - */ -export interface INfwFirewallPolicyConfig { - /** - * A friendly name for the policy. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the policy to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * Use this property to define specific behaviors and rule groups - * to associate with the policy. - * - * @see {@link NfwFirewallPolicyPolicyConfig} - */ - readonly firewallPolicy: INfwFirewallPolicyPolicyConfig; - /** - * The regions to deploy the policy to. - * - * @see {@link Region} - */ - readonly regions: t.Region[]; - /** - * (OPTIONAL) A description for the policy. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) Resource Access Manager (RAM) share targets. - * - * @remarks - * Targets can be account names and/or organizational units. - * Targets must be configured for account(s)/OU(s) that require - * access to the policy. A target is not required for the - * delegated admin account. - * - * @see {@link ShareTargets} - */ - readonly shareTargets?: t.IShareTargets; - /** - * (OPTIONAL) An array of tags for the policy. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwFirewallConfig} / {@link NfwLoggingConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-logging.html | Network Firewall logging} configuration. - * - * @description - * Use this configuration to define logging destinations for Network Firewall. - * You can configure AWS Network Firewall logging for your firewall's stateful engine. - * Logging gives you detailed information about network traffic, including the time that - * the stateful engine received a packet, detailed information about the packet, and any - * stateful rule action taken against the packet. The logs are published to the log destination - * that you've configured, where you can retrieve and view them. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-networkfirewall-loggingconfiguration-logdestinationconfig.html} - * - * The following example configures Network Firewall to send ALERT-level logs to S3: - * @example - * ``` - * - destination: s3 - * type: ALERT - * ``` - */ -export interface INfwLoggingConfig { - /** - * The destination service to log to. - * - * @see {@link logDestinationTypeEnum} - */ - readonly destination: t.LogDestinationType; - /** - * The type of actions to log. - */ - readonly type: NfwLogType; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig} / {@link NfwFirewallConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewalls.html | Network Firewall firewall} configuration. - * - * @description - * Use this configuration to define a Network Firewall firewall. - * An AWS Network Firewall firewall connects a firewall policy, - * which defines network traffic monitoring and filtering behavior, - * to the VPC that you want to protect. The firewall configuration - * includes specifications for the Availability Zones and subnets - * where the firewall endpoints are placed. It also defines high-level - * settings like the firewall logging configuration and tagging on the AWS firewall resource. - * - * @see {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-firewall.html}. - * - * The following example creates a firewall named `accelerator-nfw` in the VPC named `Network-Inspection`. Firewall - * endpoints are deployed to the subnets named `Subnet-A` and `Subnet-B` in that VPC. - * @example - * ``` - * - name: accelerator-nfw - * description: Accelerator Firewall - * firewallPolicy: accelerator-nfw-policy - * subnets: - * - Subnet-A - * - Subnet-B - * vpc: Network-Inspection - * loggingConfiguration: - * - destination: s3 - * type: ALERT - * tags: [] - * ``` - */ -export interface INfwFirewallConfig { - /** - * A friendly name for the firewall. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the firewall to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the Network Firewall policy. - * - * @remarks - * This is the logical `name` property of the policy as defined in network-config.yaml. - * - * @see {@link NfwFirewallPolicyConfig} - */ - readonly firewallPolicy: t.NonEmptyString; - /** - * An array of the friendly names of subnets to deploy Network Firewall to. - * - * @remarks - * This is the logical `name` property of the subnets as defined in network-config.yaml. - * The listed subnets must exist in the VPC referenced in the `vpc` property. - */ - readonly subnets: t.NonEmptyString[]; - /** - * The friendly name of the VPC to deploy Network Firewall to. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the firewall to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * This is the logical `name` property of the VPC as defined in network-config.yaml. - * - * @see {@link VpcConfig} - */ - readonly vpc: t.NonEmptyString; - /** - * (OPTIONAL) Enable for deletion protection on the firewall. - */ - readonly deleteProtection?: boolean; - /** - * (OPTIONAL) A description for the firewall. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) Enable to disallow firewall policy changes. - */ - readonly firewallPolicyChangeProtection?: boolean; - /** - * (OPTIONAL) Enable to disallow firewall subnet changes. - */ - readonly subnetChangeProtection?: boolean; - /** - * (OPTIONAL) An array of Network Firewall logging configurations. - * - * @see {@link NfwLoggingConfig} - */ - readonly loggingConfiguration?: INfwLoggingConfig[]; - /** - * (OPTIONAL) An array of tags for the firewall. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link NfwConfig}* - * - * {@link https://docs.aws.amazon.com/network-firewall/latest/developerguide/what-is-aws-network-firewall.html | Network Firewall} configuration. - * - * @description - * Use this configuration to define Network Firewalls in your environment. - * AWS Network Firewall is a stateful, managed, network firewall and intrusion - * detection and prevention service for your virtual private cloud (VPC) that - * you create in Amazon Virtual Private Cloud (Amazon VPC). - * With Network Firewall, you can filter traffic at the perimeter of your VPC. - * This includes filtering traffic going to and coming from an internet gateway, - * NAT gateway, or over VPN or AWS Direct Connect. - * - * The following example creates a simple Network Firewall rule group, policy, - * and firewall. The policy and rule group are shared with the entire organization. - * The firewall endpoints are created in subnets named `Subnet-A` and `Subnet-B` - * in the VPC named `Network-Inspection`. - * - * @example - * ``` - * networkFirewall: - * firewalls: - * - name: accelerator-nfw - * description: Accelerator Firewall - * firewallPolicy: accelerator-nfw-policy - * subnets: - * - Subnet-A - * - Subnet-B - * vpc: Network-Inspection - * loggingConfiguration: - * - destination: s3 - * type: ALERT - * tags: [] - * policies: - * - name: accelerator-nfw-policy - * firewallPolicy: - * statelessDefaultActions: ['aws:forward_to_sfe'] - * statelessFragmentDefaultActions: ['aws:forward_to_sfe'] - * statefulRuleGroups: - * - name: accelerator-stateful-group - * statelessRuleGroups: - * - name: accelerator-stateless-group - * priority: 100 - * regions: - * - us-east-1 - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * rules: - * - name: accelerator-stateful-group - * regions: - * - us-east-1 - * capacity: 100 - * type: STATEFUL - * ruleGroup: - * rulesSource: - * rulesFile: path/to/rules.txt - * shareTargets: - * organizationalUnits: - * - Root - * tags: [] - * ``` - */ -export interface INfwConfig { - /** - * An array of Network Firewall firewall configurations. - * - * @see {@link NfwFirewallConfig} - */ - readonly firewalls: INfwFirewallConfig[]; - /** - * An array of Network Firewall policy configurations. - * - * @see {@link NfwFirewallPolicyConfig} - */ - readonly policies: INfwFirewallPolicyConfig[]; - /** - * An array of Network Firewall rule group configurations. - * - * @see {@link NfwRuleGroupConfig} - */ - readonly rules: INfwRuleGroupConfig[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link GwlbConfig} / {@link GwlbEndpointConfig}* - * - * {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/gateway/introduction.html#gateway-load-balancer-overview | Gateway Load Balancer endpoint} configuration. - * - * @description - * Use this configuration to define endpoints for your Gateway Load Balancer. - * Gateway Load Balancers use Gateway Load Balancer endpoints to securely exchange - * traffic across VPC boundaries. A Gateway Load Balancer endpoint is a VPC endpoint - * that provides private connectivity between virtual appliances in the service provider - * VPC and application servers in the service consumer VPC. - * - * The following example creates two Gateway Load Balancer endpoints, - * `Endpoint-A` and `Endpoint-B`. The endpoints are created in subnets named - * `Network-Inspection-A` and `Network-Inspection-B`, respectively, in the VPC named - * `Network-Inspection`. - * @example - * ``` - * - name: Endpoint-A - * account: Network - * subnet: Network-Inspection-A - * vpc: Network-Inspection - * - name: Endpoint-B - * account: Network - * subnet: Network-Inspection-B - * vpc: Network-Inspection - * ``` - */ -export interface IGwlbEndpointConfig { - /** - * The friendly name of the Gateway Load Balancer endpoint. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the endpoint to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * The friendly name of the account to deploy the endpoint to. - * - * @remarks - * This is the `account` property of the VPC referenced in the `vpc` property. - * For VPC templates, ensure the account referenced is included in `deploymentTargets`. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly account: t.NonEmptyString; - /** - * The friendly name of the subnet to deploy the Gateway Load Balancer endpoint to. - * - * @remarks - * This is the friendly name of the subnet as defined in network-config.yaml. - * The subnet must be defined in the `subnets` property of the VPC referenced in the `vpc` property. - * - * @see {@link SubnetConfig} - */ - readonly subnet: t.NonEmptyString; - /** - * The friendly name of the VPC to deploy the Gateway Load Balancer endpoint to. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the endpoint to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * This is the logical `name` property of the VPC as defined in network-config.yaml. - * - * @see {@link VpcConfig} | {@link VpcTemplatesConfig} - */ - readonly vpc: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig} / {@link GwlbConfig}* - * - * {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/gateway/introduction.html#gateway-load-balancer-overview | Gateway Load Balancer} configuration. - * - * @description - * Use to define Gateway Load Balancer configurations for the accelerator. - * Gateway Load Balancers enable you to deploy, scale, and manage virtual appliances, - * such as firewalls, intrusion detection and prevention systems, and deep packet inspection - * systems. It combines a transparent network gateway (that is, a single entry and exit point - * for all traffic) and distributes traffic while scaling your virtual appliances with the demand. - * - * @example - * ``` - * gatewayLoadBalancers: - * - name: Accelerator-GWLB - * subnets: - * - Network-Inspection-Firewall-A - * - Network-Inspection-Firewall-B - * vpc: Network-Inspection - * deletionProtection: true - * endpoints: - * - name: Endpoint-A - * account: Network - * subnet: Network-Inspection-A - * vpc: Network-Inspection - * - name: Endpoint-B - * account: Network - * subnet: Network-Inspection-B - * vpc: Network-Inspection - * ``` - */ -export interface IGwlbConfig { - /** - * The friendly name of the Gateway Load Balancer. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes the load balancer to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - */ - readonly name: t.NonEmptyString; - /** - * An array of Gateway Load Balancer endpoint configurations. - * - * @see {@link GwlbEndpointConfig} - */ - readonly endpoints: IGwlbEndpointConfig[]; - /** - * An array of friendly names of subnets to deploy the Gateway Load Balancer to. - * - * @remarks - * This is the logical `name` property of the subnets as defined in network-config.yaml. - * The subnets referenced must exist in the VPC referenced in the `vpc` property. - * - * @see {@link SubnetConfig} - */ - readonly subnets: t.NonEmptyString[]; - /** - * The friendly name of the VPC to deploy the Gateway Load Balancer to. - * - * @remarks - * This is the logical `name` property of the VPC as defined in network-config.yaml. - * VPC templates are not a supported target for Gateway Load Balancers. - * - * @see {@link VpcConfig} - */ - readonly vpc: t.NonEmptyString; - /** - * (OPTIONAL) Set an override for the account the Gateway Load Balancer is deployed to. - * - * @remarks - * This is the `account` property of the VPC referenced in the `vpc` property. - * - * This value defaults to the value set for the central network services delegated admin account. - * Only set this value if you would like your Gateway Load Balancer deployed to an account other than - * the configured delegated admin account. - */ - readonly account?: t.NonEmptyString; - /** - * (OPTIONAL) Whether to enable cross-zone load balancing. - */ - readonly crossZoneLoadBalancing?: boolean; - /** - * (OPTIONAL) Whether to enable deletion protection. - */ - readonly deletionProtection?: boolean; - /** - * (OPTIONAL) The friendly name of a target group to forward traffic to - * - * @remarks - * This target group must be defined in `Ec2FirewallConfig` - * in the `customizations-config.yaml` configuration file - */ - readonly targetGroup?: t.NonEmptyString; - /** - * (OPTIONAL) An array of CloudFormation tag objects. - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link NetworkConfig} / {@link CentralNetworkServicesConfig}* - * - * @description - * Central network services configuration. - * Use this configuration to define centralized networking services for your environment. - * Central network services enables you to easily designate a central account that owns your - * core network infrastructure. These network resources can be shared with other - * accounts in your organization so that workload accounts can consume them. - * - * @example - * ``` - * centralNetworkServices: - * delegatedAdminAccount: Network - * gatewayLoadBalancers: [] - * ipams: [] - * networkFirewall: - * firewalls: [] - * policies: [] - * rules: [] - * route53Resolver: - * endpoints: [] - * firewallRuleGroups: [] - * queryLogs: - * name: accelerator-query-logs - * destinations: - * - cloud-watch-logs - * - s3 - * shareTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface ICentralNetworkServicesConfig { - /** - * The friendly name of the delegated administrator account for network services. - * Resources configured under `centralNetworkServices` will be created in this account. - * - * @remarks - * **CAUTION**: Changing this property value after initial deployment causes all central network services to be recreated. - * Please be aware that any downstream dependencies may cause this property update to fail. - * - * This is the logical `name` property of the account as defined in accounts-config.yaml. - */ - readonly delegatedAdminAccount: t.NonEmptyString; - /** - * An array of Gateway Load Balancer configurations. - * - * @see {@link GwlbConfig} - */ - readonly gatewayLoadBalancers?: IGwlbConfig[]; - /** - * An array of IPAM configurations. - * - * @see {@link IpamConfig} - */ - readonly ipams?: IIpamConfig[]; - /** - * A Route 53 resolver configuration. - * - * @see {@link ResolverConfig} - */ - readonly route53Resolver?: IResolverConfig; - /** - * A Network Firewall configuration. - * - * @see {@link NfwConfig} - */ - readonly networkFirewall?: INfwConfig; -} - -/** - * *{@link NetworkConfig} / {@link VpcPeeringConfig}* - * - * @description - * VPC peering configuration. - * Used to define VPC peering connections. - * - * VPC can be from vpc or vpcTemplates configuration. - * - * @remarks - * **CAUTION**: Both vpcs can't be from vpcTemplates. - * - * @example - * ``` - * vpcPeering: - * - name: Peering - * vpcs: - * - VPC-A - * - VPC-B - * tags: [] - * ``` - * Between VPC Template and VPC - * ``` - * vpcPeering: - * - name: Peering - * vpcs: - * - VPC-Template-A - * - VPC-B - * tags: [] - * ``` - */ -export interface IVpcPeeringConfig { - /** - * A friendly name for the peering connection. - */ - readonly name: t.NonEmptyString; - /** - * The VPCs to peer. - * - * VPC can be from vpc or vpcTemplates configuration. - * - * @remarks - * **CAUTION**: Both vpcs can't be from vpcTemplates. - */ - readonly vpcs: t.NonEmptyString[]; - /** - * An array of tags for the peering connection. - */ - readonly tags?: t.ITag[]; -} - -/** - * An optional ELB root account ID - */ -export interface IElbAccountIdsConfig { - readonly region: t.NonEmptyString; - readonly accountId: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link FirewallManagerConfig} / {@link FirewallManagerNotificationChannelConfig}* - * - * @description - * An optional Firewall Manager Service Config - */ -export interface IFirewallManagerNotificationChannelConfig { - /** - * The SNS Topic Name to publish to. - */ - readonly snsTopic: t.NonEmptyString; - /** - * Enables the FMS notification channel. Defaults to enabled. - */ - readonly region: t.NonEmptyString; -} - -/** - * *{@link NetworkConfig} / {@link FirewallManagerConfig}* - * - * @description - * An optional Firewall Manager Service Config - */ -export interface IFirewallManagerServiceConfig { - /** - * The friendly account name to deploy the FMS configuration - */ - readonly delegatedAdminAccount: t.NonEmptyString; - /** - * The FMS Notification Channel Configuration - */ - readonly notificationChannels?: IFirewallManagerNotificationChannelConfig[]; -} - -export type CertificateConfigType = 'import' | 'request'; -export type CertificateValidationType = 'EMAIL' | 'DNS'; - -/** - * *{@link NetworkConfig} / {@link CertificateConfig}* - * - * @description - * Amazon Certificate Manager (ACM) Configuration - * - * {@link https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html | Import certificate} or {@link https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html | Request certificate} from ACM - * - * @example - * ``` - * - name: cert1 - * type: import - * privKey: cert1/privKey.key - * cert: cert1/cert.crt - * chain: cert1/chain.csr - * deploymentTargets: - * accounts: - * - WorkloadAccount1 - * - WorkloadAccount2 - * - name: cert2 - * type: request - * validation: DNS - * domain: example.com - * san: - * - www.example.com - * - www.example.net - * - e.co - * deploymentTargets: - * OU: - * - Infrastructure - * ``` - */ -export interface ICertificateConfig { - /** - * Name of the certificate. This should be unique in the certificates array. Duplicate names will fail the validation. - */ - readonly name: t.NonEmptyString; - /** - * Type of ACM cert. Valid values are `import` or `request` - */ - readonly type: CertificateConfigType; - /** - * Path to the private key in S3 assets bucket. The bucket value is in the outputs of Pipeline stack in home region. Path should be given relative to the bucket. - * The private key that matches the public key in the certificate. - * This value should be provided when type is set to import or else validation fails. - */ - readonly privKey?: t.NonEmptyString; - /** - * Path to certificate in S3 assets bucket. The bucket value is in the outputs of Pipeline stack in home region. Path should be given relative to the bucket. - * The certificate to import. - * This value should be provided when type is set to import or else validation fails. - */ - readonly cert?: t.NonEmptyString; - /** - * Path to the PEM encoded certificate chain in S3 assets bucket. The bucket value is in the outputs of Pipeline stack in home region. Path should be given relative to the bucket. - * This value is optional when type is set to import. - */ - readonly chain?: t.NonEmptyString; - /** - * The method you want to use if you are requesting a public certificate to validate that you own or control domain. You can validate with DNS or validate with email. - * Valid values are 'DNS' or 'EMAIL'. - * This value should be provided when type is set to request or else validation fails. - */ - readonly validation?: CertificateValidationType; - /** - * Fully qualified domain name (FQDN), such as www.example.com, that you want to secure with an ACM certificate. Use an asterisk (*) to create a wildcard certificate that protects several sites in the same domain. For example, *.example.com protects www.example.com, site.example.com, and images.example.com. - * In compliance with RFC 5280, the length of the domain name (technically, the Common Name) that you provide cannot exceed 64 octets (characters), including periods. To add a longer domain name, specify it in the Subject Alternative Name field, which supports names up to 253 octets in length. - * This value should be provided when type is set to request or else validation fails. - */ - readonly domain?: t.NonEmptyString; - /** - * Additional FQDNs to be included in the Subject Alternative Name extension of the ACM certificate. For example, add the name www.example.net to a certificate for which the DomainName field is www.example.com if users can reach your site by using either name. - */ - readonly san?: t.NonEmptyString[]; - /** - * ACM deployment target. This should be provided to deploy ACM into OUs or account. - */ - readonly deploymentTargets?: t.IDeploymentTargets; -} - -/** - * Network Configuration. - * Used to define a network configuration for the accelerator. - */ -export interface INetworkConfig { - /** - * Accelerator home region name. - * - * @example - * ``` - * homeRegion: &HOME_REGION us-east-1 - * ``` - */ - readonly homeRegion?: t.Region; - /** - * A default VPC configuration. - * - * @see {@link DefaultVpcsConfig} - */ - readonly defaultVpc: IDefaultVpcsConfig; - /** - * A list of VPC configurations. - * An array of VPC endpoint policies. - * - * @see {@link EndpointPolicyConfig} - */ - readonly endpointPolicies: IEndpointPolicyConfig[]; - /** - * An array of Transit Gateway configurations. - * - * @see {@link TransitGatewayConfig} - */ - readonly transitGateways: ITransitGatewayConfig[]; - /** - * An array of Transit Gateway Connect configurations. - * - * @see {@link TransitGatewayConnectConfig} - */ - readonly transitGatewayConnects?: ITransitGatewayConnectConfig[]; - - /** - * Transit Gateway peering configuration. - * - * @see {@link TransitGatewayPeeringConfig} - */ - readonly transitGatewayPeering?: ITransitGatewayPeeringConfig[]; - /** - * An array of VPC configurations. - * - * @see {@link VpcConfig} - */ - readonly vpcs: IVpcConfig[]; - /** - * A VPC flow logs configuration. - * - * @see {@link VpcFlowLogsConfig} - */ - readonly vpcFlowLogs?: t.IVpcFlowLogsConfig; - /** - * An optional Central Network services configuration. - * - * @see {@link CentralNetworkServicesConfig} - */ - readonly centralNetworkServices?: ICentralNetworkServicesConfig; - /** - * An array of Customer Gateway configurations. - * - * @see {@link CustomerGatewayConfig} - */ - readonly customerGateways?: ICustomerGatewayConfig[]; - /** - * An optional list of DHCP options set configurations. - * - * @see {@link DhcpOptsConfig} - */ - readonly dhcpOptions?: IDhcpOptsConfig[]; - /** - * An optional array of Direct Connect Gateway configurations. - * - * @example - * ``` - * directConnectGateways: - * - name: Accelerator-DXGW - * account: Network - * asn: 64512 - * virtualInterfaces: [] - * transitGatewayAssociations: [] - * ``` - * @see {@link DxGatewayConfig} - */ - readonly directConnectGateways?: IDxGatewayConfig[]; - /** - * An optional list of prefix list set configurations. - */ - readonly prefixLists?: IPrefixListConfig[]; - /** - * An optional list of VPC peering configurations - * - * @see {@link VpcPeeringConfig} - */ - readonly vpcPeering?: IVpcPeeringConfig[]; - /** - * An optional list of VPC template configurations - * - * @see {@link VpcTemplatesConfig} - */ - readonly vpcTemplates?: IVpcTemplatesConfig[]; - /** - * An optional ELB root account ID - */ - readonly elbAccountIds?: IElbAccountIdsConfig[]; - /** - * Firewall manager service configuration - */ - readonly firewallManagerService?: IFirewallManagerServiceConfig; - /** - * Certificate manager configuration - */ - readonly certificates?: ICertificateConfig[]; - /** - * A map between account Id and all the VPC IDs in the account. - * - * Currently, the dynamic values will only be loaded in FinalizeStack for SCP finalization. - * Only the account VPCs referred in SCPs by ACCEL_LOOKUP will be loaded. - */ - readonly accountVpcIds?: { [key: t.NonEmptyString]: t.NonEmptyString[] }; - /** - * A map between account Id and all the VPC Endpoint IDs in the account. - * - * Currently, the dynamic values will only be loaded in FinalizeStack for SCP finalization. - * Only the account VPC Endpoints referred by ACCEL_LOOKUP in SCPs will be loaded. - */ - readonly accountVpcEndpointIds?: { [key: t.NonEmptyString]: t.NonEmptyString[] }; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/organization-config.ts b/source/packages/@aws-accelerator/config/lib/models/organization-config.ts deleted file mode 100644 index a8c7825..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/organization-config.ts +++ /dev/null @@ -1,337 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; - -/** - * *{@link OrganizationConfig} / {@link OrganizationalUnitConfig}* - * - * @description - * AWS Organizational Unit (OU) configuration - * - * @example - * ``` - * organizationalUnits: - * - name: Sandbox - * - name: Suspended - * ignore: true - * ``` - */ -export interface IOrganizationalUnitConfig { - /** - * The name and nested path that you want to assign to the OU. - * When referring to OU's in the other configuration files ensure - * that the name matches what has been provided here. - * For example if you wanted an OU directly off of root just supply the OU name. - * Always configure all of the OUs in the path. - * A nested OU configuration would be like this - * - name: Sandbox - * - name: Sandbox/Pipeline - * - name: Sandbox/Development - * - name: Sandbox/Development/Application1 - */ - readonly name: t.NonEmptyString; - /** - * Optional property used to ignore organizational unit and - * the associated accounts - * Default value is false - */ - readonly ignore?: boolean; -} - -/** - * *{@link OrganizationConfig} / {@link OrganizationalUnitIdConfig} - * - * @description - * Organizational unit id configuration - * - * @example - * ``` - * organizationalUnitIds: - * - name: Sandbox - * id: o-abc123 - * arn: - * ``` - */ -export interface IOrganizationalUnitIdConfig { - /** - * A name for the OU - */ - readonly name: t.NonEmptyString; - /** - * OU id - */ - readonly id: t.NonEmptyString; - /** - * OU arn - */ - readonly arn: t.NonEmptyString; -} - -/** - * *{@link OrganizationConfig} / {@link QuarantineNewAccountsConfig}* - * - * @description - * Quarantine SCP application configuration - * - * @example - * ``` - * quarantineNewAccounts: - * enable: true - * scpPolicyName: QuarantineAccounts - * ``` - */ -export interface IQuarantineNewAccountsConfig { - /** - * Indicates where or not a Quarantine policy is applied - * when new accounts are created. If enabled all accounts created by - * any means will have the configured policy applied. - */ - readonly enable: boolean; - /** - * The policy to apply to new accounts. This value must exist - * if the feature is enabled. The name must also match - * a policy that is defined in the serviceControlPolicy section. - */ - readonly scpPolicyName?: t.NonEmptyString; -} - -/** - * *{@link OrganizationConfig} / {@link ServiceControlPolicyConfig}* - * - * @description - * Service control policy configuration - * - * @example - * ``` - * serviceControlPolicies: - * - name: QuarantineAccounts - * description: Quarantine accounts - * policy: path/to/policy.json - * type: customerManaged - * deploymentTargets: - * organizationalUnits: [] - * ``` - */ -export interface IServiceControlPolicyConfig { - /** - * The friendly name to assign to the policy. - * The regex pattern that is used to validate this parameter is a string of any of the characters in the ASCII character range. - */ - readonly name: t.NonEmptyString; - /** - * A description to assign to the policy. - */ - readonly description: t.NonEmptyString; - /** - * Service control definition json file. This file must be present in config repository - */ - readonly policy: t.NonEmptyString; - /** - * Kind of service control policy - */ - readonly type: 'awsManaged' | 'customerManaged'; - /** - * Service control policy deployment targets - */ - readonly strategy?: 'deny-list' | 'allow-list'; - /** - * Service control policy strategy. - * https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_strategies.html - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * *{@link OrganizationConfig} / {@link TaggingPolicyConfig}* - * - * @description - * Organizations tag policy. - * - * Tag policies help you standardize tags on all tagged resources across your organization. - * You can use tag policies to define tag keys (including how they should be capitalized) and their allowed values. - * - * @example - * ``` - * taggingPolicies: - * - name: TagPolicy - * description: Organization Tagging Policy - * policy: tagging-policies/org-tag-policy.json - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface ITaggingPolicyConfig { - /** - * The friendly name to assign to the policy. - * The regex pattern that is used to validate this parameter is a string of any of the characters in the ASCII character range. - */ - readonly name: t.NonEmptyString; - /** - * A description to assign to the policy. - */ - readonly description: t.NonEmptyString; - /** - * Tagging policy definition json file. This file must be present in config repository - */ - readonly policy: t.NonEmptyString; - /** - * Tagging policy deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * *{@link OrganizationConfig} / {@link BackupPolicyConfig}* - * - * @description - * Organization backup policy - * - * Backup policies enable you to deploy organization-wide backup plans to help ensure compliance across your organization's accounts. - * Using policies helps ensure consistency in how you implement your backup plans - * - * @example - * ``` - * backupPolicies: - * - name: BackupPolicy - * description: Organization Backup Policy - * policy: backup-policies/org-backup-policies.json - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ -export interface IBackupPolicyConfig { - /** - * The friendly name to assign to the policy. - * The regex pattern that is used to validate this parameter is a string of any of the characters in the ASCII character range. - */ - readonly name: t.NonEmptyString; - /** - * A description to assign to the policy. - */ - readonly description: t.NonEmptyString; - /** - * Backup policy definition json file. This file must be present in config repository - */ - readonly policy: t.NonEmptyString; - /** - * Backup policy deployment targets - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * Organization configuration - */ -export interface IOrganizationConfig { - /** - * Indicates whether AWS Organization enabled. - * - */ - readonly enable: boolean; - /** - * A Record of Organizational Unit configurations - * - * @see OrganizationalUnitConfig - * - * To create Security and Infrastructure OU in root , you need to provide following values for this parameter. - * Nested OU's start at root and configure all of the ou's in the path - * - * @example - * ``` - * organizationalUnits: - * - name: Security - * - name: Infrastructure - * - name: Sandbox - * - name: Sandbox/Pipeline - * - name: Sandbox/Development - * - name: Sandbox/Development/Application1 - * ``` - */ - readonly organizationalUnits: IOrganizationalUnitConfig[]; - /** - * Optionally provide a list of Organizational Unit IDs to bypass the usage of the - * AWS Organizations Client lookup. This is not a readonly member since we - * will initialize it with values if it is not provided - */ - readonly organizationalUnitIds?: IOrganizationalUnitIdConfig[]; - /** - * A record of Quarantine New Accounts configuration - * @see QuarantineNewAccountsConfig - */ - readonly quarantineNewAccounts?: IQuarantineNewAccountsConfig; - /** - * A Record of Service Control Policy configurations - * - * @see ServiceControlPolicyConfig - * - * To create service control policy named DenyDeleteVpcFlowLogs from service-control-policies/deny-delete-vpc-flow-logs.json file in config repository, you need to provide following values for this parameter. - * - * @example - * ``` - * serviceControlPolicies: - * - name: DenyDeleteVpcFlowLogs - * description: > - * This SCP prevents users or roles in any affected account from deleting - * Amazon Elastic Compute Cloud (Amazon EC2) flow logs or CloudWatch log - * groups or log streams. - * policy: service-control-policies/deny-delete-vpc-flow-logs.json - * type: customerManaged - * strategy: deny-list # defines SCP strategy - deny-list or allow-list. See https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_strategies.html - * deploymentTargets: - * organizationalUnits: - * - Security - * ``` - */ - readonly serviceControlPolicies: IServiceControlPolicyConfig[]; - /** - * A Record of Tagging Policy configurations - * - * @see TaggingPolicyConfig - * - * To create tagging policy named TagPolicy from tagging-policies/org-tag-policy.json file in config repository, you need to provide following values for this parameter. - * - * @example - * ``` - * taggingPolicies: - * - name: TagPolicy - * description: Organization Tagging Policy - * policy: tagging-policies/org-tag-policy.json - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ - readonly taggingPolicies: ITaggingPolicyConfig[]; - /** - * A Record of Backup Policy configurations - * - * @see BackupPolicyConfig - * - * To create backup policy named BackupPolicy from backup-policies/org-backup-policies.json file in config repository, you need to provide following values for this parameter. - * - * @example - * ``` - * backupPolicies: - * - name: BackupPolicy - * description: Organization Backup Policy - * policy: backup-policies/org-backup-policies.json - * deploymentTargets: - * organizationalUnits: - * - Root - * ``` - */ - readonly backupPolicies: IBackupPolicyConfig[]; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/replacements-config.ts b/source/packages/@aws-accelerator/config/lib/models/replacements-config.ts deleted file mode 100644 index 91b00ff..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/replacements-config.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; - -/** - * *{@link ReplacementsConfig} / {@link ParameterReplacementConfig}* - * - * @description - * Fixed replacement value to apply throughout config files. Loaded from SSM - * parameters in the management account in the HOME_REGION. - * - * @remarks These SSM Parameters must exist with non-null values before they are added to the replacements-config.yaml file. - * - * @example - * ``` - * globalReplacements: - * - key: FndPrefix - * path: /accelerator/replacements/FndPrefix - * - key: BudgetEmail - * value: /accelerator/replacements/BudgetEmail - * - key: ProtectTagKey - * value: /accelerator/replacements/ProtectTagKey - * - key: ProtectTagValue - * value: /accelerator/replacements/ProtectTagValue - * ``` - */ -export interface IParameterReplacement { - /** - * Key of the replacement placeholder - */ - readonly key: t.NonEmptyString; - /** - * Path of the SSM Parameter containing the value to replace - */ - readonly path: t.NonEmptyString; -} - -/** - * *{@link ReplacementsConfig} / {@link ParameterReplacementConfig}* - * - * @description - * Fixed replacement value to apply throughout config files. Loaded from SSM - * parameters in the management account in the HOME_REGION. - * - * @remarks These SSM Parameters must exist with non-null values before they are added to the replacements-config.yaml file. - * - * @example - * ``` - * globalReplacements: - * - key: FndPrefix - * type: 'SSM' - * path: /accelerator/replacements/FndPrefix - * - key: BudgetEmail - * type: 'SSM' - * path: /accelerator/replacements/BudgetEmail - * - key: ProtectTagKey - * type: 'SSM' - * path: /accelerator/replacements/ProtectTagKey - * - key: ProtectTagValue - * type: 'SSM' - * path: /accelerator/replacements/ProtectTagValue - * - key: ALLOWED_CORPORATE_CIDRS - * type: 'StringList' - * value: - * - 10.0.1.0/24 - * - 10.0.2.0/24 - * - key: ALLOWED_PRINCIPAL_ARN - * type: 'String' - * value: arn:aws:iam::*:role/AWSA* - * ``` - */ -export interface IParameterReplacementV2 { - /** - * Key of the replacement placeholder - */ - readonly key: t.NonEmptyString; - /** - * Path of the SSM Parameter containing the value to replace - */ - readonly path?: t.NonEmptyString; - /** - * Type of the global parameters - * */ - readonly type: t.ParameterReplacementType; - /** - * Value of the parameter if type is string or array - */ - readonly value?: t.NonEmptyString | t.NonEmptyString[]; -} - -/** - * Accelerator replacements configuration - */ -export interface IReplacementsConfig { - /** - * The set of placeholder parameters (key/path pairs) that will be merged with yaml configuration files. - */ - readonly globalReplacements?: (IParameterReplacement | IParameterReplacementV2)[]; -} diff --git a/source/packages/@aws-accelerator/config/lib/models/security-config.ts b/source/packages/@aws-accelerator/config/lib/models/security-config.ts deleted file mode 100644 index f70e2e4..0000000 --- a/source/packages/@aws-accelerator/config/lib/models/security-config.ts +++ /dev/null @@ -1,2336 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as t from '../common/types'; - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SnsSubscriptionConfig}* - * - * @description - * AWS SNS Notification subscription configuration - * ***Deprecated*** - * Replaced by snsTopics in global config - * - * @example - * ``` - * snsSubscriptions: - * - level: High - * email: @example.com - * - level: Medium - * email: @example.com - * - level: Low - * email: @example.com - * ``` - */ -export interface ISnsSubscriptionConfig { - /** - * Notification level high, medium or low - */ - readonly level: t.NonEmptyString; - /** - * Subscribing email address - */ - readonly email: t.NonEmptyString; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link S3PublicAccessBlockConfig}* - * - * {@link https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html} | AWS S3 block public access configuration. - * - * @description - * This will create the Public Access Block configuration for the AWS account. - * - * @remarks - * If the `PublicAccessBlock` configurations are different between the bucket and the account, Amazon S3 will align with - * the most restrictive combination between the bucket-level and account-level settings. - * - * @example - * ``` - * s3PublicAccessBlock: - * enable: true - * excludeAccounts: [] - * ``` - */ -export interface IS3PublicAccessBlockConfig { - /** - * Indicates whether AWS S3 block public access is enabled. - */ - readonly enable: boolean; - /** - * List of AWS Account names to be excluded from configuring S3 PublicAccessBlock - */ - readonly excludeAccounts?: string[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link ScpRevertChangesConfig}* - * - * @description - * AWS Service Control Policies Revert Manual Changes configuration - * - * @example - * ``` - * scpRevertChangesConfig: - * enable: true - * snsTopicName: Security - * ``` - */ -export interface IScpRevertChangesConfig { - /** - * Indicates whether manual changes to Service Control Policies are automatically reverted. - */ - readonly enable: boolean; - /** - * (OPTIONAL) The name of the SNS Topic to send alerts to when SCPs are changed manually - */ - readonly snsTopicName?: t.NonEmptyString; -} - -/** - * *{@link SecurityConfig} / {@link KeyManagementServiceConfig} / {@link KeyConfig}* - * - * {@link https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-mgmt} | AWS KMS Key configuration. - * - * @description - * Use this configuration to define your customer managed key (CMK) and where it's deployed to along with - * it's management properties. - * - * @example - * ``` - * - name: ExampleKey - * deploymentTargets: - * organizationalUnits: - * - Root - * alias: alias/example/key - * policy: path/to/policy.json - * description: Example KMS key - * enabled: true - * enableKeyRotation: true - * removalPolicy: retain - * ``` - */ -export interface IKeyConfig { - /** - * Unique Key name for logical reference - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) Initial alias to add to the key - * - * @remarks - * - * Note: If changing this value, a new CMK with the new alias will be created. - */ - readonly alias?: t.NonEmptyString; - /** - * (OPTIONAL)Key policy file path. This file must be available in accelerator config repository. - */ - readonly policy?: t.NonEmptyString; - /** - * (OPTIONAL) A description of the key. - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) Indicates whether AWS KMS rotates the key. - * @default true - */ - readonly enableKeyRotation?: boolean; - /** - * (OPTIONAL) Indicates whether the key is available for use. - * @default - Key is enabled. - */ - readonly enabled?: boolean; - /** - * (OPTIONAL) Whether the encryption key should be retained when it is removed from the Stack. - * @default retain - */ - readonly removalPolicy?: 'destroy' | 'retain' | 'snapshot'; - /** - * This configuration determines which accounts and/or OUs the CMK is deployed to. - * - * To deploy KMS key into Root and Infrastructure organizational units, you need to provide below value for this parameter. - * - * @example - * ``` - * - deploymentTargets: - * organizationalUnits: - * - Root - * - Infrastructure - * ``` - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link MacieConfig}* - * - * @description - * Amazon Macie Configuration - * Use this configuration to enable Amazon Macie within your AWS Organization along with it's reporting configuration. - * - * @example - * ``` - * macie: - * enable: true - * excludeRegions: [] - * policyFindingsPublishingFrequency: FIFTEEN_MINUTES - * publishSensitiveDataFindings: true - * ``` - */ -export interface IMacieConfig { - /** - * Indicates whether AWS Macie enabled. - */ - readonly enable: boolean; - /** - * List of AWS Region names to be excluded from configuring Amazon Macie - */ - readonly excludeRegions?: t.Region[]; - /** - * (OPTIONAL) Specifies how often to publish updates to policy findings for the account. This includes publishing updates to Security Hub and Amazon EventBridge (formerly called Amazon CloudWatch Events). - * An enum value that specifies how frequently findings are published - * Possible values FIFTEEN_MINUTES, ONE_HOUR, or SIX_HOURS - */ - readonly policyFindingsPublishingFrequency?: 'FIFTEEN_MINUTES' | 'ONE_HOUR' | 'SIX_HOURS'; - /** - * Specifies whether to publish sensitive data findings to Security Hub. If you set this value to true, Amazon Macie automatically publishes all sensitive data findings that weren't suppressed by a findings filter. The default value is false. - */ - readonly publishSensitiveDataFindings: boolean; - /** - * (OPTIONAL) Declaration of a S3 Lifecycle rule. - */ - readonly lifecycleRules?: t.ILifecycleRule[] | undefined; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link GuardDutyConfig} / {@link GuardDutyS3ProtectionConfig}* - * - * {@link https://docs.aws.amazon.com/guardduty/latest/ug/s3-protection.html} | AWS GuardDuty S3 Protection configuration. - * - * @description - * Use this configuration to enable S3 Protection with Amazon GuardDuty to monitor object-level API operations for potential - * security risks for data within Amazon S3 buckets. - * - * @example - * ``` - * enable: true - * excludeRegions: [] - * ``` - */ -export interface IGuardDutyS3ProtectionConfig { - /** - * Indicates whether AWS GuardDuty S3 Protection enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring Amazon GuardDuty S3 Protection - */ - readonly excludeRegions?: t.Region[]; -} - -/** - * AWS GuardDuty EKS Protection configuration. - */ -export interface IGuardDutyEksProtectionConfig { - /** - * Indicates whether AWS GuardDuty EKS Protection enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring Amazon GuardDuty EKS Protection - */ - readonly excludeRegions?: t.Region[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link GuardDutyConfig} / {@link GuardDutyExportFindingsConfig}* - * - * {@link https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_exportfindings.html} | AWS GuardDuty Export Findings configuration. - * - * @description - * Use this configuration to export Amazon GuardDuty findings to Amazon CloudWatch Events, and, optionally, to an Amazon S3 bucket. - * - * @example - * ``` - * enable: true - * overrideExisting: true - * destinationType: S3 - * exportFrequency: FIFTEEN_MINUTES - * ``` - */ -export interface IGuardDutyExportFindingsConfig { - /** - * Indicates whether AWS GuardDuty Export Findings enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) Indicates whether AWS GuardDuty Export Findings can be overwritten. - */ - readonly overrideExisting?: boolean; - /** - * The type of resource for the publishing destination. Currently only Amazon S3 buckets are supported. - */ - readonly destinationType: 'S3'; - /** - * An enum value that specifies how frequently findings are exported, such as to CloudWatch Events. - * Possible values FIFTEEN_MINUTES, ONE_HOUR, or SIX_HOURS - */ - readonly exportFrequency: 'FIFTEEN_MINUTES' | 'ONE_HOUR' | 'SIX_HOURS'; - /** - * (OPTIONAL) AWS GuardDuty Prefix for centralized logging path. - */ - readonly overrideGuardDutyPrefix?: t.IPrefixConfig; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link GuardDutyConfig}* - * - * @description - * AWS GuardDuty configuration - * Use this configuration to enable Amazon GuardDuty for an AWS Organization, as well as other modular - * feature protections. - * - * - * @example - * ``` - * guardduty: - * enable: true - * excludeRegions: [] - * s3Protection: - * enable: true - * excludeRegions: [] - * eksProtection: - * enable: true - * excludedRegions: [] - * exportConfiguration: - * enable: true - * overrideExisting: true - * destinationType: S3 - * exportFrequency: FIFTEEN_MINUTES - * lifecycleRules: [] - * ``` - */ -export interface IGuardDutyConfig { - /** - * Indicates whether AWS GuardDuty enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring Amazon GuardDuty - * - * Please only specify one of the `excludeRegions` or `deploymentTargets` properties. - * - */ - readonly excludeRegions?: t.Region[]; - /** - * (OPTIONAL) Deployment targets for GuardDuty - * - * We highly recommend enabling GuardDuty across all accounts and enabled regions within your organization. - * `deploymentTargets` should only be used when more granular control is required, not as a default configuration - * Please only specify one of the `deploymentTargets` or `excludeRegions` properties. - * - * Note: The delegated admin account defined in centralSecurityServices will always have GuardDuty enabled - * - * @see {@link DeploymentTargets} - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * (OPTIONAL) Enables/disables the auto enabling of GuardDuty for any account including the new accounts joining the organization - * - * It is recommended to set the value to `false` when using the `deploymentTargets` property to enable GuardDuty only on targeted accounts mentioned in the deploymentTargets. If you do not define or do not set it to `false` any new accounts joining the organization will automatically be enabled with GuardDuty. - * - * @default true - */ - readonly autoEnableOrgMembers?: boolean; - /** - * AWS GuardDuty S3 Protection configuration. - * @type object - */ - readonly s3Protection: IGuardDutyS3ProtectionConfig; - /** - * (OPTIONAL) AWS GuardDuty EKS Protection configuration. - * @type object - */ - readonly eksProtection?: IGuardDutyEksProtectionConfig; - /** - * AWS GuardDuty Export Findings configuration. - * @type object - */ - readonly exportConfiguration: IGuardDutyExportFindingsConfig; - /** - * (OPTIONAL) Declaration of a S3 Lifecycle rule. - */ - readonly lifecycleRules?: t.ILifecycleRule[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link AuditManagerConfig} / {@link AuditManagerDefaultReportsDestinationConfig}* - * - * @description - * AWS Audit Manager Default Reports Destination configuration. - * Use this configuration to enable a destination for reports generated by AWS Audit Manager. - * - * @example - * ``` - * enable: true - * destinationType: S3 - * ``` - */ -export interface IAuditManagerDefaultReportsDestinationConfig { - /** - * Indicates whether AWS Audit Manager Default Reports enabled. - */ - readonly enable: boolean; - /** - * The type of resource for the publishing destination. Currently only Amazon S3 buckets are supported. - */ - readonly destinationType: 'S3'; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link AuditManagerConfig}* - * - * {@link https://docs.aws.amazon.com/audit-manager/latest/userguide/what-is.html } | AWS Audit Manager configuration - * - * @description - * Use this configuration to enable AWS Audit Manager for an AWS Organization. - * - * @example - * ``` - * auditManager: - * enable: true - * excludeRegions: [] - * defaultReportsConfiguration: - * enable: true - * destinationType: S3 - * lifecycleRules: [] - * ``` - */ -export interface IAuditManagerConfig { - /** - * Indicates whether AWS Audit Manager enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring AWS Audit Manager. Please ensure any regions enabled in the global configuration that do not support Audit Manager are added to the excluded regions list. {@link https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ | Supported services by region}. - */ - readonly excludeRegions?: t.Region[]; - /** - * AWS Audit Manager Default Reports configuration. - * @type object - */ - readonly defaultReportsConfiguration: IAuditManagerDefaultReportsDestinationConfig; - /** - * (OPTIONAL) Declaration of a S3 Lifecycle rule. - */ - readonly lifecycleRules?: t.ILifecycleRule[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link DetectiveConfig}* - * - * {@link https://docs.aws.amazon.com/detective/latest/adminguide/what-is-detective.html} | Amazon Detective configuration - * - * @description - * Use this configuration to enable Amazon Detective for an AWS Organization that allows users to analyze, investigate, and - * quickly identify the root cause of security findings or suspicious activities. - * - * @example - * ``` - * detective: - * enable: true - * excludeRegions: [] - * ``` - */ -export interface IDetectiveConfig { - /** - * Indicates whether Amazon Detective is enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring Amazon Detective. Please ensure any regions enabled in the global configuration that do not support Amazon Detective are added to the excluded regions list. {@link https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ | Supported services by region}. - */ - readonly excludeRegions?: t.Region[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SecurityHubConfig} / {@link SecurityHubStandardConfig}* - * - * {@link https://docs.aws.amazon.com/securityhub/latest/userguide/standards-reference.html} | AWS Security Hub standards configuration. - * - * @description - * Use this configuration to define the security standard(s) that are enabled through Amazon Security Hub and which accounts and/or - * organization units that the controls are deployed to. - * - * @example - * ``` - * - name: PCI DSS v3.2.1 - * deploymentTargets: - * organizationalUnits: - * - Root - * enable: true - * controlsToDisable: - * # Refer to the document for the controls - * # https://docs.aws.amazon.com/securityhub/latest/userguide/pci-standard.html - * - Control1 - * - Control2 - * ``` - */ -export interface ISecurityHubStandardConfig { - /** - * An enum value that specifies one of three security standards supported by Security Hub - * Possible values are 'AWS Foundational Security Best Practices v1.0.0', - * 'CIS AWS Foundations Benchmark v1.2.0', - * 'CIS AWS Foundations Benchmark v1.4.0', - * 'CIS AWS Foundations Benchmark v3.0.0', - * 'NIST Special Publication 800-53 Revision 5, - * and 'PCI DSS v3.2.1' - */ - readonly name: - | 'AWS Foundational Security Best Practices v1.0.0' - | 'CIS AWS Foundations Benchmark v1.2.0' - | 'CIS AWS Foundations Benchmark v1.4.0' - | 'CIS AWS Foundations Benchmark v3.0.0' - | 'NIST Special Publication 800-53 Revision 5' - | 'PCI DSS v3.2.1' - | ''; - /** - * (OPTIONAL) Deployment targets for AWS Security Hub standard. - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * Indicates whether given AWS Security Hub standard enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) An array of control names to be disabled for the given security standards - */ - readonly controlsToDisable?: t.NonEmptyString[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SecurityHubConfig} / {@link SecurityHubLoggingConfig} / {@link SecurityHubLoggingCloudwatchConfig}* - * - * @description - * Security Hub Logging CloudWatch Config - * - * @example - * ``` - * enable: true - * logLevel: MEDIUM - * ``` - */ -export interface ISecurityHubLoggingCloudwatchConfig { - /** - * Security hub to cloudwatch logging is enabled by default. - */ - readonly enable: boolean; - /** - * (OPTIONAL) CloudWatch Log Group Name - * @remarks - * Note: Log Group name must be unique in the account and region. - * - * The name of the log group SecurityHub Events are forwarded to. LZA will create a - * log group with this name if the property is provided, unless the log group already exists. - */ - readonly logGroupName?: string; - /** - * (OPTIONAL) Security Hub logging level - * - * @remarks - * Note: Values accepted are CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL - * - * Security Hub findings for events at the Level provided and above will be logged to CloudWatch Logs - * For example, if you specify the HIGH level findings will be sent to CloudWatch Logs for HIGH and CRITICAL - */ - readonly logLevel?: t.SecurityHubSeverityLevel; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SecurityHubConfig} / {@link SecurityHubLoggingConfig}* - * - * @description - * Security Hub Logging Config - * - * @example - * ``` - * logging: - * cloudWatch: - * enable: true - * logLevel: MEDIUM - * logGroupName: /Custom/SecurityHubLogGroup - * ``` - */ -export interface ISecurityHubLoggingConfig { - /** - * Data store to ship the Security Hub logs to. - */ - readonly cloudWatch?: ISecurityHubLoggingCloudwatchConfig; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SecurityHubConfig}* - * - * {@link https://docs.aws.amazon.com/securityhub/latest/userguide/what-is-securityhub.html} | AWS Security Hub configuration - * - * @description - * Use this configuration to enable Amazon Security Hub for an AWS Organization along with it's auditing configuration. - * - * @example - * ``` - * securityHub: - * enable: true - * regionAggregation: true - * excludeRegions: [] - * standards: - * - name: AWS Foundational Security Best Practices v1.0.0 - * deploymentTargets: - * organizationalUnits: - * - Root - * enable: true - * controlsToDisable: - * # Refer to the document for the controls - * # https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html - * - Control1 - * - Control2 - * logging: - * cloudWatch: - * enable: true - * logLevel: MEDIUM - * ``` - */ -export interface ISecurityHubConfig { - /** - * Indicates whether AWS Security Hub is enabled (AWSConfig is required for enabling SecurityHub) - */ - readonly enable: boolean; - /** - * (OPTIONAL) Indicates whether Security Hub results are aggregated in the Home Region. - */ - readonly regionAggregation?: boolean; - /** - * (OPTIONAL) SNS Topic for Security Hub notifications. - * - * @remarks - * Note: Topic must exist in the global config - */ - readonly snsTopicName?: string; - /** - * (OPTIONAL) Security Hub notification level - * - * @remarks - * Note: Values accepted are CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL - * - * Notifications will be sent for events at the Level provided and above - * Example, if you specify the HIGH level notifications will - * be sent for HIGH and CRITICAL - */ - readonly notificationLevel?: string; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring Security Hub - */ - readonly excludeRegions?: t.Region[]; - /** - * (OPTIONAL) Deployment targets for SecurityHub - * - * We highly recommend enabling SecurityHub across all accounts and enabled regions within your organization. - * `deploymentTargets` should only be used when more granular control is required, not as a default configuration - * Please only specify one of the `deploymentTargets` or `excludeRegions` properties. - * - * Note: The delegated admin account defined in centralSecurityServices will always have SecurityHub enabled. - * - * @see {@link DeploymentTargets} - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * (OPTIONAL) Enables/disables the auto enabling of SecurityHub for any account including the new accounts joining the organization - * - * It is recommended to set the value to `false` when using the `deploymentTargets` property to enable SecurityHub only on targeted accounts mentioned in the deploymentTargets. If you do not define or do not set it to `false` any new accounts joining the organization will automatically be enabled with SecurityHub. - * - * @default true - */ - readonly autoEnableOrgMembers?: boolean; - /** - * Security Hub standards configuration - */ - readonly standards: ISecurityHubStandardConfig[]; - /** - * (OPTIONAL) Security Hub logs are sent to CloudWatch logs by default. This option can enable or disable the logging. - * - * @remarks - * By default, if nothing is given `true` is taken. In order to stop logging, set this parameter to `false`. - * Please note, this option can be toggled but log group with `/${acceleratorPrefix}-SecurityHub` will remain in the account for every enabled region and will need to be manually deleted. This is designed to ensure no accidental loss of data occurs. - */ - readonly logging?: ISecurityHubLoggingConfig; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link EbsDefaultVolumeEncryptionConfig}* - * - * {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#encryption-by-default | AWS EBS default encryption} configuration. - * - * @description - * Use this configuration to enable enforced encryption of new EBS volumes and snapshots created in an AWS environment. - * - * @example - * Deployment targets: - * ``` - * ebsDefaultVolumeEncryption: - * enable: true - * kmsKey: ExampleKey - * deploymentTargets: - * organizationalUnits: - * - Workloads - * ``` - * - * Excluded regions: - * ``` - * ebsDefaultVolumeEncryption: - * enable: true - * kmsKey: ExampleKey - * excludeRegions: [] - * ``` - */ -export interface IEbsDefaultVolumeEncryptionConfig { - /** - * Indicates whether AWS EBS volume have default encryption enabled. - */ - readonly enable: boolean; - /** - * (OPTIONAL) KMS key to encrypt EBS volume. - * - * @remarks - * Note: When no value is provided Landing Zone Accelerator will create the KMS key. - */ - readonly kmsKey?: t.NonEmptyString; - /** - * (OPTIONAL) Deployment targets for EBS default volume encryption - * - * @remarks - * You can limit the OUs, accounts, and regions that EBS default volume encryption is deployed to. Please - * only specify one of the `deploymentTargets` or `excludeRegions` properties. `deploymentTargets` allows you - * to be more granular about where default EBS volume encryption is enabled across your environment. - * - * @see {@link DeploymentTargets} - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring AWS EBS volume default encryption - */ - readonly excludeRegions?: t.Region[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SsmAutomationConfig} / {@link DocumentSetConfig} / {@link DocumentConfig}* - * - * {@link https://docs.aws.amazon.com/systems-manager/latest/userguide/documents.html} | AWS Systems Manager document configuration - * - * @description - * Use this configuration to define AWS System Manager documents (SSM documents) that can be used on managed instances in an - * environment. - * - * @example - * ``` - * - name: SSM-ELB-Enable-Logging - * template: path/to/document.yaml - * ``` - */ -export interface IDocumentConfig { - /** - * Name of document to be created - */ - readonly name: t.NonEmptyString; - /** - * Document template file path. This file must be available in accelerator config repository. - */ - readonly template: t.NonEmptyString; - /** - * Specify a target type to define the kinds of resources the document can run on. For example, to run a document on EC2 instances, specify the following value: /AWS::EC2::Instance. If you specify a value of '/' the document can run on all types of resources. If you don't specify a value, the document can't run on any resources. For a list of valid resource types, see AWS resource and property types reference in the AWS CloudFormation User Guide. - * Ref: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html - * Length Constraints: Maximum length of 200. - * Pattern: ^\/[\w\.\-\:\/]*$ - */ - readonly targetType?: t.NonEmptyString; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SsmAutomationConfig} / {@link DocumentSetConfig}* - * - * @description - * AWS Systems Manager document sharing configuration - * - * @example - * ``` - * - shareTargets: - * organizationalUnits: - * - Root - * documents: - * - name: SSM-ELB-Enable-Logging - * template: path/to/document.yaml - * ``` - */ -export interface IDocumentSetConfig { - /** - * Document share target, valid value should be any organizational unit. - * Document will be shared with every account within the given OU - */ - readonly shareTargets: t.IShareTargets; - /** - * List of the documents to be shared - */ - readonly documents: IDocumentConfig[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig} / {@link SsmAutomationConfig}* - * - * @description - * AWS Systems Manager automation configuration - * - * @example - * ``` - * ssmAutomation: - * excludeRegions: [] - * documentSets: - * - shareTargets: - * organizationalUnits: - * - Root - * documents: - * - name: SSM-ELB-Enable-Logging - * template: path/to/document.yaml - * ``` - */ -export interface ISsmAutomationConfig { - /** - * (OPTIONAL) List of AWS Region names to be excluded from configuring block S3 public access - */ - readonly excludeRegions?: t.Region[]; - /** - * List of documents for automation - */ - readonly documentSets: IDocumentSetConfig[]; -} - -/** - * *{@link SecurityConfig} / {@link CentralSecurityServicesConfig}* - * - * @description - * AWS Accelerator central security services configuration - * - * @example - * ``` - * centralSecurityServices: - * delegatedAdminAccount: Audit - * ebsDefaultVolumeEncryption: - * enable: true - * excludeRegions: [] - * s3PublicAccessBlock: - * enable: true - * excludeAccounts: [] - * scpRevertChangesConfig: - * enable: true - * snsTopicName: Security - * guardduty: - * enable: true - * excludeRegions: [] - * s3Protection: - * enable: true - * excludeRegions: [] - * eksProtection: - * enable: true - * excludeRegions: [] - * exportConfiguration: - * enable: true - * overrideExisting: true - * destinationType: S3 - * exportFrequency: FIFTEEN_MINUTES - * macie: - * enable: true - * excludeRegions: [] - * policyFindingsPublishingFrequency: FIFTEEN_MINUTES - * publishSensitiveDataFindings: true - * snsSubscriptions: [] - * securityHub: - * enable: true - * regionAggregation: true - * snsTopicName: Security - * notificationLevel: HIGH - * excludeRegions: [] - * standards: - * - name: AWS Foundational Security Best Practices v1.0.0 - * enable: true - * - name: PCI DSS v3.2.1 - * enable: true - * controlsToDisable: - * # Refer to the document for the controls - * # https://docs.aws.amazon.com/securityhub/latest/userguide/pci-standard.html - * - Control1 - * - Control2 - * - name: CIS AWS Foundations Benchmark v1.2.0 - * enable: true - * - name: CIS AWS Foundations Benchmark v1.4.0 - * enable: true - * controlsToDisable: - * # Refer to the document for the controls - * # https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html#cis1v4-standard - * - Control1 - * - Control2 - * - name: CIS AWS Foundations Benchmark v3.0.0 - * enable: true - * - name: NIST Special Publication 800-53 Revision 5 - * enable: true - * controlsToDisable: - * # Refer to the document for the controls - * # https://docs.aws.amazon.com/securityhub/latest/userguide/nist-standard.html - * - Control1 - * - Control2 - * ssmAutomation: - * documentSets: [] - *``` - */ -export interface ICentralSecurityServicesConfig { - /** - * Designated administrator account name for accelerator security services. - * AWS organizations designate a member account as a delegated administrator for the - * organization users and roles from that account can perform administrative actions for security services like - * Macie, GuardDuty, Detective and Security Hub. Without designated administrator account administrative tasks for - * security services are performed only by users or roles in the organization's management account. - * This helps you to separate management of the organization from management of these security services. - * Accelerator use Audit account as designated administrator account. - * @type string - * @default Audit - * - * To make Audit account as designated administrator account for every security services configured by accelerator, you need to provide below value for this parameter - * @example - * ``` - * delegatedAdminAccount: Audit - * ``` - */ - readonly delegatedAdminAccount: t.NonEmptyString; - /** - * AWS Elastic Block Store default encryption configuration - * - * Accelerator use this parameter to configure EBS default encryption. - * Accelerator will create KMS key for every AWS environment (account and region), which will be used as default EBS encryption key. - * - * To enable EBS default encryption in every region accelerator implemented, you need to provide below value for this parameter. - * - * @example - * ``` - * ebsDefaultVolumeEncryption: - * enable: true - * excludeRegions: [] - * ``` - */ - readonly ebsDefaultVolumeEncryption: IEbsDefaultVolumeEncryptionConfig; - /** - * AWS S3 public access block configuration - * - * Accelerator use this parameter to block AWS S3 public access - * - * To enable S3 public access blocking in every region accelerator implemented, you need to provide below value for this parameter. - * - * @example - * ``` - * s3PublicAccessBlock: - * enable: true - * excludeAccounts: [] - * ``` - */ - readonly s3PublicAccessBlock: IS3PublicAccessBlockConfig; - /** - * (OPTIONAL) AWS Service Control Policies Revert Manual Changes configuration - * - * @example - * ``` - * scpRevertChangesConfig: - * enable: true - * snsTopicName: Security - * ``` - */ - readonly scpRevertChangesConfig?: IScpRevertChangesConfig; - /** - * AWS SNS subscription configuration - * Deprecated - * - * NOTICE: The configuration of SNS topics is being moved - * to the Global Config. This block is deprecated and - * will be removed in a future release - * - * Accelerator use this parameter to define AWS SNS notification configuration. - * - * To enable high, medium and low SNS notifications, you need to provide below value for this parameter. - * @example - * ``` - * snsSubscriptions: - * - level: High - * email: @example.com - * - level: Medium - * email: @example.com - * - level: Low - * email: @example.com - * ``` - */ - readonly snsSubscriptions?: ISnsSubscriptionConfig[]; - /** - * Amazon Macie Configuration - * - * Accelerator use this parameter to define AWS Macie configuration. - * - * To enable Macie in every region accelerator implemented and - * set fifteen minutes of frequency to publish updates to policy findings for the account with - * publishing sensitive data findings to Security Hub. - * you need to provide below value for this parameter. - * @example - * ``` - * macie: - * enable: true - * excludeRegions: [] - * policyFindingsPublishingFrequency: FIFTEEN_MINUTES - * publishSensitiveDataFindings: true - * ``` - */ - readonly macie: IMacieConfig; - /** - * Amazon GuardDuty Configuration - */ - readonly guardduty: IGuardDutyConfig; - /** - * (OPTIONAL) Amazon Audit Manager Configuration - */ - readonly auditManager?: IAuditManagerConfig; - /** - * (OPTIONAL) Amazon Detective Configuration - */ - readonly detective?: IDetectiveConfig; - /** - * AWS Security Hub configuration - * - * Accelerator use this parameter to define AWS Security Hub configuration. - * - * To enable AWS Security Hub for all regions and - * enable "AWS Foundational Security Best Practices v1.0.0" security standard, deployment targets and disable controls - * you need provide below value for this parameter. - * - * @example - * ``` - * securityHub: - * enable: true - * regionAggregation: true - * snsTopicName: Security - * notificationLevel: HIGH - * excludeRegions: [] - * standards: - * - name: AWS Foundational Security Best Practices v1.0.0 - * deploymentTargets: - * organizationalUnits: - * - Root - * enable: true - * controlsToDisable: - * # Refer to the document for the control ID - * # https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html - * - Control1 - * - Control2 - * logging: - * cloudwatch: - * enabled: true - * logLevel: MEDIUM - * ``` - */ - readonly securityHub: ISecurityHubConfig; - /** - * AWS Systems Manager Document configuration - * - * Accelerator use this parameter to define AWS Systems Manager documents configuration. - * SSM documents are created in designated administrator account for security services, i.e. Audit account. - * - * To create a SSM document named as "SSM-ELB-Enable-Logging" in every region accelerator implemented and share this - * document with Root organizational unit(OU), you need to provide below value for this parameter. - * To share document to specific account uncomment accounts list. A valid SSM document template file ssm-documents/ssm-elb-enable-logging.yaml - * must be present in Accelerator config repository. Accelerator will use this template file to create the document. - * - * @example - * ``` - * ssmAutomation: - * excludeRegions: [] - * documentSets: - * - shareTargets: - * organizationalUnits: - * - Root - * # accounts: - * # - Network - * documents: - * - name: SSM-ELB-Enable-Logging - * template: ssm-documents/ssm-elb-enable-logging.yaml - * ``` - */ - readonly ssmAutomation: ISsmAutomationConfig; -} - -/** - * *{@link SecurityConfig} / {@link KeyManagementServiceConfig}* - * - * @description - * KMS key management service configuration - * - * @example - * ``` - * keySets: - * - name: ExampleKey - * deploymentTargets: - * organizationalUnits: - * - Root - * alias: alias/example/key - * policy: path/to/policy.json - * description: Example KMS key - * enabled: true - * enableKeyRotation: true - * removalPolicy: retain - * ``` - */ -export interface IKeyManagementServiceConfig { - readonly keySets: IKeyConfig[]; -} - -export enum ResourceTypeEnum { - S3_BUCKET = 'S3_BUCKET', - KMS_KEY = 'KMS_KEY', - IAM_ROLE = 'IAM_ROLE', - SECRETS_MANAGER_SECRET = 'SECRETS_MANAGER_SECRET', - ECR_REPOSITORY = 'ECR_REPOSITORY', - OPENSEARCH_DOMAIN = 'OPENSEARCH_DOMAIN', - SNS_TOPIC = 'SNS_TOPIC', - SQS_QUEUE = 'SQS_QUEUE', - APIGATEWAY_REST_API = 'APIGATEWAY_REST_API', - LEX_BOT = 'LEX_BOT', - EFS_FILE_SYSTEM = 'EFS_FILE_SYSTEM', - EVENTBRIDGE_EVENTBUS = 'EVENTBRIDGE_EVENTBUS', - BACKUP_VAULT = 'BACKUP_VAULT', - CODEARTIFACT_REPOSITORY = 'CODEARTIFACT_REPOSITORY', - CERTIFICATE_AUTHORITY = 'CERTIFICATE_AUTHORITY', - LAMBDA_FUNCTION = 'LAMBDA_FUNCTION', -} - -export interface IResourcePolicyConfig { - readonly resourceType: keyof typeof ResourceTypeEnum; - readonly document: t.NonEmptyString; -} - -export interface IResourcePolicyRemediation { - /** - * The remediation is triggered automatically. - */ - readonly automatic: boolean; - /** - * Maximum time in seconds that AWS Config runs auto-remediation. If you do not select a number, the default is 60 seconds. - */ - readonly retryAttemptSeconds?: number; - /** - * The maximum number of failed attempts for auto-remediation. If you do not select a number, the default is 5. - */ - readonly maximumAutomaticAttempts?: number; -} - -export interface IResourcePolicySetConfig { - /** - * The deployment targets - accounts/OUs where the config rule and remediation action will be deployed to - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * A list of resource policy templates for different types of resources - */ - readonly resourcePolicies: IResourcePolicyConfig[]; - /** - * The input parameters which will be set as environment variable in Custom Config Rule Lambda and Remediation lambda - * - * Meanwhile, 'SourceAccount' is a reserved parameters for allow-only resource policy -- Lambda_Function and CERTIFICATE_AUTHORITY. - * For example, 'SourceAccount: 123456789012,987654321098' means requests from these two accounts can be allowed. - * Apart from these two, No other external accounts can access a lambda function or Certificate Authority. - * - */ - readonly inputParameters?: { [key: string]: string }; -} - -/** - * *{@link SecurityConfig} / {@link ResourcePolicyEnforcementConfig}/{@link NetworkPerimeterConfig}* - * - * @description - * Network Perimeter Config. - * - * If managedVpcOnly is true, all the VPCs in accounts will be included while parameter `ACCEL_LOOKUP:VPC|VPC_ID:XX` is used. - * If managedVpcOnly is false, only the VPC created by LZA will be included while parameter `ACCEL_LOOKUP:VPC|VPC_ID:XX` is used. - */ -export interface INetworkPerimeterConfig { - readonly managedVpcOnly?: boolean; -} - -/** - * *{@link SecurityConfig} / {@link ResourcePolicyEnforcementConfig}* - * - * @description - * Resource Policy Enforcement Config. The configuration allows you to deploy AWS Config rules to - * automatically apply resource-based policies to AWS resources including S3 buckets, IAM roles, and KMS keys etc. - * AWS Organization is required to support it. - * - * Here are a list of supported service {@link SecurityConfigTypes.resourceTypeEnum } - * - * @example - * ``` - * - * resourcePolicyEnforcement: - * enable: true - * remediation: - * automatic: false - * retryAttemptSeconds: 60 - * maximumAutomaticAttempts: 5 - * policySets: - * - resourcePolicies: - * - resourceType: KMS - * document: resource-policies/kms-workload.json - * inputParameters: - * SourceAccount: 123456789012,987654321098 - * allowedAccountList: {{ ALLOWED_EXTERNAL_ACCOUNTS }} # The parameter `ALLOWED_EXTERNAL_ACCOUNTS` is defined in replacement config. - * deploymentTargets: - * accounts: - * - Root - */ -export interface IResourcePolicyEnforcementConfig { - readonly enable: boolean; - readonly remediation: IResourcePolicyRemediation; - readonly policySets: IResourcePolicySetConfig[]; - readonly networkPerimeter?: INetworkPerimeterConfig; -} - -/** - * *{@link SecurityConfig} / {@link AccessAnalyzerConfig}* - * - * @description - * AWS AccessAnalyzer configuration - * - * @example - * ``` - * accessAnalyzer: - * enable: true - * ``` - */ -export interface IAccessAnalyzerConfig { - /** - * Indicates whether AWS AccessAnalyzer enabled in your organization. - * - * @remarks - * Note: Once enabled, IAM Access Analyzer examines policies and reports a list of findings for resources that grant public or cross-account access from outside your AWS Organizations in the IAM console and through APIs. - */ - readonly enable: boolean; -} - -/** - * *{@link SecurityConfig} / {@link IamPasswordPolicyConfig}* - * - * @description - * IAM password policy configuration - * - * @example - * ``` - * iamPasswordPolicy: - * allowUsersToChangePassword: true - * hardExpiry: false - * requireUppercaseCharacters: true - * requireLowercaseCharacters: true - * requireSymbols: true - * requireNumbers: true - * minimumPasswordLength: 14 - * passwordReusePrevention: 24 - * maxPasswordAge: 90 - * ``` - */ -export interface IIamPasswordPolicyConfig { - /** - * Allows all IAM users in your account to use the AWS Management Console to change their own passwords. - * - * @default true - */ - readonly allowUsersToChangePassword: boolean; - /** - * Prevents IAM users who are accessing the account via the AWS Management Console from setting a new console password after their password has expired. - * The IAM user cannot access the console until an administrator resets the password. - * - * @default true - */ - readonly hardExpiry: boolean; - /** - * Specifies whether IAM user passwords must contain at least one uppercase character from the ISO basic Latin alphabet (A to Z). - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one uppercase character. - * - * @default true - */ - readonly requireUppercaseCharacters: boolean; - /** - * Specifies whether IAM user passwords must contain at least one lowercase character from the ISO basic Latin alphabet (a to z). - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one lowercase character. - * - * @default true - */ - readonly requireLowercaseCharacters: boolean; - /** - * Specifies whether IAM user passwords must contain at least one of the following non-alphanumeric characters: - * - * ! @ # $ % ^ & * ( ) _ + - = [ ] { } | ' - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one symbol character. - * - * @default true - */ - readonly requireSymbols: boolean; - /** - * Specifies whether IAM user passwords must contain at least one numeric character (0 to 9). - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one numeric character. - * - * @default true - */ - readonly requireNumbers: boolean; - /** - * The minimum number of characters allowed in an IAM user password. - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of 6. - * - * @default 14 - */ - readonly minimumPasswordLength: number; - /** - * Specifies the number of previous passwords that IAM users are prevented from reusing. - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of 0. - * The result is that IAM users are not prevented from reusing previous passwords. - * - * @default 24 - */ - readonly passwordReusePrevention: number; - /** - * The number of days that an IAM user password is valid. - * - * Note: If you do not specify a value for this parameter, then the operation uses the default value of 0. The result is that IAM user passwords never expire. - * - * @default 90 - */ - readonly maxPasswordAge: number; -} - -export interface ICustomRuleLambdaType { - /** - * The source code file path of your Lambda function. This is a zip file containing lambda function, this file must be available in config repository. - */ - readonly sourceFilePath: t.NonEmptyString; - /** - * The name of the method within your code that Lambda calls to execute your function. The format includes the file name. It can also include namespaces and other qualifiers, depending on the runtime. - * For more information, see https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-features.html#gettingstarted-features-programmingmodel. - */ - readonly handler: t.NonEmptyString; - /** - * The runtime environment for the Lambda function that you are uploading. For valid values, see the Runtime property in the AWS Lambda Developer Guide. - */ - readonly runtime: t.NonEmptyString; - /** - * Lambda execution role policy definition file - */ - readonly rolePolicyFile: t.NonEmptyString; - /** - * Lambda function execution timeout in seconds - */ - readonly timeout?: number; -} - -export interface ITriggeringResourceType { - /** - * An enum to identify triggering resource types. - * Possible values ResourceId, Tag, or ResourceTypes - * - * Triggering resource can be lookup by resource id, tags or resource types. - */ - readonly lookupType: 'ResourceId' | 'Tag' | 'ResourceTypes' | string; - /** - * Resource lookup type, resource can be lookup by tag or types. When resource needs to lookup by tag, this field will have tag name. - */ - readonly lookupKey: t.NonEmptyString; - /** - * Resource lookup value, when resource lookup using tag, this field will have tag value to search resource. - */ - readonly lookupValue: t.NonEmptyString[]; -} - -export interface ICustomRuleConfigType { - /** - * The Lambda function to run. - */ - readonly lambda: ICustomRuleLambdaType; - /** - * Whether to run the rule on a fixed frequency. - * - * @default true - */ - readonly periodic?: boolean; - /** - * The maximum frequency at which the AWS Config rule runs evaluations. - * - * Default: - * MaximumExecutionFrequency.TWENTY_FOUR_HOURS - */ - readonly maximumExecutionFrequency: - | 'One_Hour' - | 'Three_Hours' - | 'Six_Hours' - | 'Twelve_Hours' - | 'TwentyFour_Hours' - | string; - /** - * Whether to run the rule on configuration changes. - * - * Default: - * false - */ - readonly configurationChanges?: boolean; - /** - * Defines which resources trigger an evaluation for an AWS Config rule. - */ - readonly triggeringResources: ITriggeringResourceType; -} - -/** - * Config rule remediation input parameter configuration type - */ -export interface IRemediationParametersConfigType { - /** - * Name of the parameter - */ - readonly name: t.NonEmptyString; - /** - * Parameter value - */ - readonly value: t.NonEmptyString; - /** - * Data type of the parameter, allowed value (StringList or String) - */ - readonly type: 'String' | 'StringList'; -} - -// export interface IConfigRuleRemediationType { -// readonly name: t.NonEmptyString; -// readonly value: t.NonEmptyString; -// readonly type: 'String' | 'StringList'; -// } - -export interface IConfigRuleRemediationType { - /** - * Remediation assume role policy definition json file. This file must be present in config repository. - * - * Create your own custom remediation actions using AWS Systems Manager Automation documents. - * When a role needed to be created to perform custom remediation actions, role permission needs to be defined in this file. - */ - readonly rolePolicyFile: t.NonEmptyString; - /** - * The remediation is triggered automatically. - */ - readonly automatic: boolean; - /** - * Target ID is the name of the public document. - * - * The name of the AWS SSM document to perform custom remediation actions. - */ - readonly targetId: t.NonEmptyString; - /** - * Name of the account owning the public document to perform custom remediation actions. - * Accelerator creates these documents in Audit account and shared with other accounts. - */ - readonly targetAccountName?: t.NonEmptyString; - /** - * Version of the target. For example, version of the SSM document. - * - * If you make backward incompatible changes to the SSM document, you must call PutRemediationConfiguration API again to ensure the remediations can run. - */ - readonly targetVersion?: t.NonEmptyString; - /** - * Target SSM document remediation lambda function - */ - readonly targetDocumentLambda?: ICustomRuleLambdaType; - /** - * Maximum time in seconds that AWS Config runs auto-remediation. If you do not select a number, the default is 60 seconds. - * - * For example, if you specify RetryAttemptSeconds as 50 seconds and MaximumAutomaticAttempts as 5, AWS Config will run auto-remediations 5 times within 50 seconds before throwing an exception. - */ - readonly retryAttemptSeconds?: number; - /** - * The maximum number of failed attempts for auto-remediation. If you do not select a number, the default is 5. - * - * For example, if you specify MaximumAutomaticAttempts as 5 with RetryAttemptSeconds as 50 seconds, AWS Config will put a RemediationException on your behalf for the failing resource after the 5th failed attempt within 50 seconds. - */ - readonly maximumAutomaticAttempts?: number; - /** - * List of remediation parameters - * - */ - readonly parameters?: IRemediationParametersConfigType[]; - /** - * List of AWS Region names to be excluded from applying remediation - */ - readonly excludeRegions?: t.Region[]; -} - -/** - * *{@link SecurityConfig} / {@link AwsConfig} / {@link AwsConfigRuleSet} / {@link ConfigRule}* - * - * @description - * AWS ConfigRule configuration - * - * @example - * Managed Config rule: - * ``` - * - name: accelerator-iam-user-group-membership-check - * complianceResourceTypes: - * - AWS::IAM::User - * identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - * ``` - * Custom Config rule: - * ``` - * - name: accelerator-attach-ec2-instance-profile - * type: Custom - * description: Custom rule for checking EC2 instance IAM profile attachment - * inputParameters: - * customRule: - * lambda: - * sourceFilePath: path/to/function.zip - * handler: index.handler - * runtime: nodejsXX.x - * rolePolicyFile: path/to/policy.json - * periodic: true - * maximumExecutionFrequency: Six_Hours - * configurationChanges: true - * triggeringResources: - * lookupType: ResourceTypes - * lookupKey: ResourceTypes - * lookupValue: - * - AWS::EC2::Instance - * ``` - * Managed Config rule with remediation: - * ``` - * - name: accelerator-s3-bucket-server-side-encryption-enabled - * identifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED - * complianceResourceTypes: - * - AWS::S3::Bucket - * remediation: - * rolePolicyFile: path/to/policy.json - * automatic: true - * targetId: Put-S3-Encryption - * retryAttemptSeconds: 60 - * maximumAutomaticAttempts: 5 - * parameters: - * - name: BucketName - * value: RESOURCE_ID - * type: String - * - name: KMSMasterKey - * value: ${ACCEL_LOOKUP::KMS} - * type: StringList - * ``` - */ -export interface IConfigRule { - /** - * A name for the AWS Config rule. - * - * @remarks - * Note: Changing this value of an AWS Config Rule will trigger a new resource creation. - */ - readonly name: t.NonEmptyString; - /** - * (OPTIONAL) A description about this AWS Config rule. - * - */ - readonly description?: t.NonEmptyString; - /** - * (OPTIONAL) The identifier of the AWS managed rule. - */ - readonly identifier?: t.NonEmptyString; - /** - * (OPTIONAL) Input parameter values that are passed to the AWS Config rule. - */ - readonly inputParameters?: { [key: t.NonEmptyString]: t.NonEmptyString } | null; // TODO: Did this work? - /** - * (OPTIONAL) Defines which resources trigger an evaluation for an AWS Config rule. - */ - readonly complianceResourceTypes?: t.NonEmptyString[]; - /** - * (OPTIONAL) Config rule type Managed or Custom. For custom config rule, this parameter value is Custom, when creating managed config rule this parameter value can be undefined or empty string - */ - readonly type?: t.NonEmptyString; - /** - * (OPTIONAL) A custom config rule is backed by AWS Lambda function. This is required when creating custom config rule. - */ - readonly customRule?: ICustomRuleConfigType; - /** - * A remediation for the config rule, auto remediation to automatically remediate noncompliant resources. - */ - readonly remediation?: IConfigRuleRemediationType; - /** - * (OPTIONAL) Tags for the config rule - */ - readonly tags?: t.ITag[]; -} - -/** - * *{@link SecurityConfig} / {@link AwsConfig} / {@link AwsConfigRuleSet}* - * - * @description - * List of AWS Config rules - * - * @example - * ``` - * - deploymentTargets: - * organizationalUnits: - * - Root - * rules: - * - name: accelerator-iam-user-group-membership-check - * complianceResourceTypes: - * - AWS::IAM::User - * identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - * ``` - */ -export interface IAwsConfigRuleSet { - /** - * Config ruleset deployment target. - * - * To configure AWS Config rules into Root and Infrastructure organizational units, you need to provide below value for this parameter. - * - * @example - * ``` - * - deploymentTargets: - * organizationalUnits: - * - Root - * - Infrastructure - * ``` - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * AWS Config rule set - * - * Following example will create a custom rule named accelerator-attach-ec2-instance-profile with remediation - * and a managed rule named accelerator-iam-user-group-membership-check without remediation - * - * @example - * ``` - * rules: - * - name: accelerator-attach-ec2-instance-profile - * type: Custom - * description: Custom role to remediate ec2 instance profile to EC2 instances - * inputParameters: - * customRule: - * lambda: - * sourceFilePath: custom-config-rules/attach-ec2-instance-profile.zip - * handler: index.handler - * runtime: nodejsXX.x - * timeout: 3 - * periodic: true - * maximumExecutionFrequency: Six_Hours - * configurationChanges: true - * triggeringResources: - * lookupType: ResourceTypes - * lookupKey: ResourceTypes - * lookupValue: - * - AWS::EC2::Instance - * - name: accelerator-iam-user-group-membership-check - * complianceResourceTypes: - * - AWS::IAM::User - * identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - * ``` - */ - readonly rules: IConfigRule[]; -} - -/** - * *{@link SecurityConfig} / {@link AwsConfig} / {@link AwsConfigAggregation}* - * - * @description - * AWS Config Aggregation Configuration - * Not used in Control Tower environment - * Aggregation will be configured in all enabled regions - * unless specifically excluded - * If the delegatedAdmin account is not provided - * config will be aggregated to the management account - * - * @example - * AWS Config Aggregation with a delegated admin account: - * ``` - * aggregation: - * enable: true - * delegatedAdminAccount: LogArchive - * ``` - * AWS Config Aggregation in the management account: - * ``` - * configAggregation: - * enable: true - * ``` - */ -export interface IAwsConfigAggregation { - readonly enable: boolean; - readonly delegatedAdminAccount?: t.NonEmptyString; -} - -/** - * *{@link SecurityConfig} / {@link AwsConfig}* - * - * @description - * AWS Config Recorder and Rules - * - * @example - * ``` - * awsConfig: - * enableConfigurationRecorder: false - * ** enableDeliveryChannel DEPRECATED - * enableDeliveryChannel: true - * overrideExisting: false - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * aggregation: - * enable: true - * delegatedAdminAccount: LogArchive - * ruleSets: - * - deploymentTargets: - * organizationalUnits: - * - Root - * rules: - * - name: accelerator-iam-user-group-membership-check - * complianceResourceTypes: - * - AWS::IAM::User - * identifier: IAM_USER_GROUP_MEMBERSHIP_CHECK - * ``` - */ -export interface IAwsConfig { - /** - * Indicates whether AWS Config recorder enabled. - * - * To enable AWS Config, you must create a configuration recorder - * - * ConfigurationRecorder resource describes the AWS resource types for which AWS Config records configuration changes. The configuration recorder stores the configurations of the supported resources in your account as configuration items. - */ - readonly enableConfigurationRecorder: boolean; - /** - * (OPTIONAL) AWS Config deployment target. - * - * Leaving `deploymentTargets` undefined will enable AWS Config across all accounts and enabled regions. - * - * We highly recommend enabling AWS Config across all accounts and enabled regions within your organization. - * `deploymentTargets` should only be used when more granular control is required, not as a default configuration. - * - * To enable AWS Config into Infrastructure organizational unit, you need to provide below value for this parameter. - * - * Note: The delegated admin account defined in centralSecurityServices will always have AwsConfig enabled - * - * @example - * ``` - * - deploymentTargets: - * organizationalUnits: - * - Infrastructure - * ``` - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * Indicates whether delivery channel enabled. - * - * AWS Config uses the delivery channel to deliver the configuration changes to your Amazon S3 bucket. - * DEPRECATED - */ - readonly enableDeliveryChannel?: boolean; - /** - * Indicates whether or not to override existing config recorder settings - * Must be enabled if any account and region combination has an - * existing config recorder, even if config recording is turned off - * The Landing Zone Accelerator will override the settings in all configured - * accounts and regions - * ** Do not enable this setting if you have deployed LZA - * ** successfully with enableConfigurationRecorder set to true - * ** and overrideExisting either unset or set to false - * ** Doing so will cause a resource conflict - * When the overrideExisting property is enabled - * ensure that any scp's are not blocking the passRole - * iam permission for the iam role name {acceleratorPrefix}Config - */ - readonly overrideExisting?: boolean; - /** - * Config Recorder Aggregation configuration - */ - readonly aggregation?: IAwsConfigAggregation; - /** - * AWS Config rule sets - */ - readonly ruleSets?: IAwsConfigRuleSet[]; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig} / {@link MetricSetConfig} / {@link MetricConfig}* - * - * @description - * AWS CloudWatch Metric configuration - * - * @example - * ``` - * - filterName: MetricFilter - * logGroupName: aws-controltower/CloudTrailLogs - * filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - * metricNamespace: LogMetrics - * metricName: RootAccountUsage - * metricValue: "1" - * treatMissingData: notBreaching - * ``` - */ -export interface IMetricConfig { - /** - * Metric filter name - */ - readonly filterName: t.NonEmptyString; - /** - * The log group to create the filter on. - */ - readonly logGroupName: t.NonEmptyString; - /** - * Pattern to search for log events. - */ - readonly filterPattern: t.NonEmptyString; - /** - * The namespace of the metric to emit. - */ - readonly metricNamespace: t.NonEmptyString; - /** - * The name of the metric to emit. - */ - readonly metricName: t.NonEmptyString; - /** - * The value to emit for the metric. - * - * Can either be a literal number (typically “1”), or the name of a field in the structure to take the value from the matched event. If you are using a field value, the field value must have been matched using the pattern. - * - * @remarks - * Note: If you want to specify a field from a matched JSON structure, use '$.fieldName', and make sure the field is in the pattern (if only as '$.fieldName = *'). - * If you want to specify a field from a matched space-delimited structure, use '$fieldName'. - */ - readonly metricValue: t.NonEmptyString; - /** - * Sets how this alarm is to handle missing data points. - */ - readonly treatMissingData?: t.NonEmptyString; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig} / {@link MetricSetConfig}* - * - * @description - * AWS CloudWatch Metric set configuration - * - * @example - * ``` - * - regions: - * - us-east-1 - * deploymentTargets: - * organizationalUnits: - * - Root - * metrics: - * - filterName: MetricFilter - * logGroupName: aws-controltower/CloudTrailLogs - * filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - * metricNamespace: LogMetrics - * metricName: RootAccountUsage - * metricValue: "1" - * treatMissingData: notBreaching - * ``` - */ -export interface IMetricSetConfig { - /** - * (OPTIONAL) AWS region names to configure CloudWatch Metrics - */ - readonly regions?: t.Region[]; - /** - * Deployment targets for CloudWatch Metrics configuration - */ - readonly deploymentTargets?: t.IDeploymentTargets; - /** - * AWS CloudWatch Metric list - * - * Following example will create metric filter RootAccountMetricFilter for aws-controltower/CloudTrailLogs log group - * - * @example - * ``` - * metrics: - * # CIS 1.1 – Avoid the use of the "root" account - * - filterName: RootAccountMetricFilter - * logGroupName: aws-controltower/CloudTrailLogs - * filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - * metricNamespace: LogMetrics - * metricName: RootAccount - * metricValue: "1" - * ``` - */ - readonly metrics: IMetricConfig[]; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig} / {@link AlarmSetConfig} / {@link AlarmConfig}* - * - * @description - * AWS CloudWatch Alarm configuration - * - * @example - * ``` - * - alarmName: CIS-1.1-RootAccountUsage - * alarmDescription: Alarm for usage of "root" account - * snsAlertLevel: Low - * metricName: RootAccountUsage - * namespace: LogMetrics - * comparisonOperator: GreaterThanOrEqualToThreshold - * evaluationPeriods: 1 - * period: 300 - * statistic: Sum - * threshold: 1 - * treatMissingData: notBreaching - * ``` - */ -export interface IAlarmConfig { - /** - * Name of the alarm - */ - readonly alarmName: t.NonEmptyString; - /** - * Description for the alarm - */ - readonly alarmDescription: t.NonEmptyString; - /** - * Alert SNS notification level - * Deprecated - */ - readonly snsAlertLevel?: t.NonEmptyString; - /** - * (OPTIONAL) SNS Topic Name - * SNS Topic Name from global config - */ - readonly snsTopicName?: t.NonEmptyString; - /** - * Name of the metric. - */ - readonly metricName: t.NonEmptyString; - /** - * Namespace of the metric. - */ - readonly namespace: t.NonEmptyString; - /** - * Comparison to use to check if metric is breaching - */ - readonly comparisonOperator: t.NonEmptyString; - /** - * The number of periods over which data is compared to the specified threshold. - */ - readonly evaluationPeriods: number; - /** - * The period over which the specified statistic is applied. - */ - readonly period: number; - /** - * What functions to use for aggregating. - * - * Can be one of the following: - * - “Minimum” | “min” - * - “Maximum” | “max” - * - “Average” | “avg” - * - “Sum” | “sum” - * - “SampleCount | “n” - * - “pNN.NN” - */ - readonly statistic: t.NonEmptyString; - /** - * The value against which the specified statistic is compared. - */ - readonly threshold: number; - /** - * Sets how this alarm is to handle missing data points. - */ - readonly treatMissingData: t.NonEmptyString; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig} / {@link AlarmSetConfig}}* - * - * @description - * AWS CloudWatch Alarm sets - * - * @example - * ``` - * - regions: - * - us-east-1 - * deploymentTargets: - * organizationalUnits: - * - Root - * alarms: - * - alarmName: CIS-1.1-RootAccountUsage - * alarmDescription: Alarm for usage of "root" account - * snsAlertLevel: Low - * metricName: RootAccountUsage - * namespace: LogMetrics - * comparisonOperator: GreaterThanOrEqualToThreshold - * evaluationPeriods: 1 - * period: 300 - * statistic: Sum - * threshold: 1 - * treatMissingData: notBreaching - * ``` - */ -export interface IAlarmSetConfig { - /** - * AWS region names to configure CloudWatch Alarms - */ - readonly regions?: t.Region[]; - /** - * Deployment targets for CloudWatch Alarms configuration - */ - readonly deploymentTargets: t.IDeploymentTargets; - /** - * List of AWS CloudWatch Alarms - * - * Following example will create CIS-1.1-RootAccountUsage alarm for RootAccountUsage metric with notification level low - * - * @example - * ``` - * alarms: - * # CIS 1.1 – Avoid the use of the "root" account - * - alarmName: CIS-1.1-RootAccountUsage - * alarmDescription: Alarm for usage of "root" account - * snsAlertLevel: Low (Deprecated) - * snsTopicName: Alarms - * metricName: RootAccountUsage - * namespace: LogMetrics - * comparisonOperator: GreaterThanOrEqualToThreshold - * evaluationPeriods: 1 - * period: 300 - * statistic: Sum - * threshold: 1 - * treatMissingData: notBreaching - * ``` - */ - readonly alarms: IAlarmConfig[]; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig} / {@link LogGroupsConfig} / {@link EncryptionConfig}* - * - * {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html | CloudWatch log group encryption} configuration. - * - * @description - * Use this configuration to enable encryption for a log group. - * - * @example - * Key name reference example: - * ``` - * kmsKeyName: key1 - * ``` - * Solution-managed KMS key example: - * ``` - * useLzaManagedKey: true - * ``` - * Existing KMS key reference: - * ``` - * kmsKeyArn: arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab - * ``` - * - */ -export interface IEncryptionConfig { - /** - * (OPTIONAL) Use this property to reference a - * KMS Key Name that is created by Landing Zone Accelerator. - * - * @remarks - * CAUTION: When importing an existing AWS CloudWatch Logs Group that has encryption enabled. If specifying the - * encryption configuration with any KMS parameter under the encryption configuration, Landing Zone Accelerator - * on AWS will associate a new key with the log group. It is recommend to verify if any processes or applications are using the previous key, - * and has access to the new key before updating. - * - * This is the logical `name` property of the key as defined in security-config.yaml. - * - * @see {@link KeyConfig} - */ - readonly kmsKeyName?: t.NonEmptyString; - /** - * (OPTIONAL) Reference the KMS Key Arn that is used to encrypt the AWS CloudWatch Logs Group. This should be a - * KMS Key that is not managed by Landing Zone Accelerator. - * - * @remarks - * CAUTION: When importing an existing AWS CloudWatch Logs Group that has encryption enabled. If specifying the - * encryption configuration with any KMS parameter under the encryption configuration, Landing Zone Accelerator - * on AWS will associate a new key with the log group. It is recommend to verify if any processes or applications are using the previous key, - * and has access to the new key before updating. - * - * Note: If using the `kmsKeyArn` parameter to encrypt your AWS CloudWatch Logs Groups. It's important that the logs - * service is provided the necessary cryptographic API calls to the CMK. For more information on how to manage the - * CMK for logs service access, please review the documentation. - * - * @see {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html} - * - */ - readonly kmsKeyArn?: t.NonEmptyString; - /** - * (OPTIONAL) Set this property to `true` if you would like to use the - * default CloudWatch Logs KMS CMK that is deployed by Landing Zone Accelerator. - * - * @remarks - * CAUTION: When importing an existing AWS CloudWatch Logs Group that has encryption enabled. If specifying the - * encryption configuration with any KMS parameter under the encryption configuration, Landing Zone Accelerator - * on AWS will associate a new key with the log group. It is recommend to verify if any processes or applications are using the previous key, - * and has access to the new key before updating. - * - * This key is deployed to all accounts managed by the solution by default. - * - */ - readonly useLzaManagedKey?: boolean; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig} / {@link LogGroupsConfig}* - * - * {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogsConcepts.html | CloudWatch log group} configuration. - * - * @description - * Use this configuration to deploy CloudWatch log groups to your environment. - * You can also import existing log groups into your accelerator configuration. - * Log groups define groups of log streams that share the same retention, monitoring, and access control settings. - * - * @example - * CloudWatch Log Group that is using a CMK that is being managed by Landing Zone Accelerator on AWS. - * ``` - * - logGroupName: Log1 - * logRetentionInDays: 365 - * terminationProtected: true - * encryption: - * kmsKeyName: key1 - * deploymentTargets: - * accounts: - * - Production - * ``` - * CloudWatch Log Group that uses the Landing Zone Accelerator on AWS CMK for CloudWatch Logs Groups. - * ``` - * - logGroupName: Log1 - * logRetentionInDays: 365 - * terminationProtected: true - * encryption: - * useLzaManagedKey: true - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * ``` - * CloudWatch Log Group that uses an existing KMS Key that's not managed by Landing Zone Accelerator on AWS. - * ``` - * - logGroupName: Log1 - * logRetentionInDays: 365 - * terminationProtected: true - * encryption: - * kmsKeyArn: arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab - * deploymentTargets: - * accounts: - * - Production - * ``` - */ -export interface ILogGroupsConfig { - /** - * Name of the CloudWatch log group - * - * @remarks - * If importing an existing log group, this must be the name of the - * group as it exists in your account. - */ - readonly logGroupName: t.NonEmptyString; - /** - * (OPTIONAL) How long, in days, the log contents will be retained. - * - * To retain all logs, set this value to undefined. - * - * @default undefined - */ - readonly logRetentionInDays: number; - /** - * (OPTIONAL) Set this property to `false` if you would like the log group - * to be deleted if it is removed from the solution configuration file. - * - * @default true - */ - readonly terminationProtected?: boolean; - /** - * (OPTIONAL) The encryption configuration of the AWS CloudWatch Logs Group. - * - * @remarks - * CAUTION: If importing an existing AWS CloudWatch Logs Group that has encryption enabled. If specifying the - * encryption configuration with any KMS parameter under the encryption configuration, Landing Zone Accelerator - * on AWS will associate a new key with the log group. The same situation is applied for a log group that is - * created by Landing Zone Accelerator on AWS where specifying a new KMS parameter will update the KMS key used - * to encrypt the log group. It is recommend to verify if any processes or applications are using the previous key, - * and has access to the new key before updating. - */ - readonly encryption?: IEncryptionConfig; - /** - * Deployment targets for CloudWatch Logs - * - * @see {@link DeploymentTargets} - */ - readonly deploymentTargets: t.IDeploymentTargets; -} - -/** - * *{@link SecurityConfig} / {@link CloudWatchConfig}* - * - * @description - * AWS CloudWatch configuration - * - * @example - * ``` - * cloudWatch: - * metricSets: - * - regions: - * - us-east-1 - * deploymentTargets: - * organizationalUnits: - * - Root - * metrics: - * - filterName: MetricFilter - * logGroupName: aws-controltower/CloudTrailLogs - * filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - * metricNamespace: LogMetrics - * metricName: RootAccountUsage - * metricValue: "1" - * treatMissingData: notBreaching - * alarmSets: - * - regions: - * - us-east-1 - * deploymentTargets: - * organizationalUnits: - * - Root - * alarms: - * - alarmName: CIS-1.1-RootAccountUsage - * alarmDescription: Alarm for usage of "root" account - * snsAlertLevel: Low - * metricName: RootAccountUsage - * namespace: LogMetrics - * comparisonOperator: GreaterThanOrEqualToThreshold - * evaluationPeriods: 1 - * period: 300 - * statistic: Sum - * threshold: 1 - * treatMissingData: notBreaching - * logGroups: - * - name: Log1 - * terminationProtected: true - * encryption: - * kmsKeyName: key1 - * deploymentTargets: - * accounts: - * - Production - * - name: Log2 - * terminationProtected: false - * deploymentTargets: - * organizationalUnits: - * - Infrastructure - * ``` - */ -export interface ICloudWatchConfig { - /** - * List AWS CloudWatch Metrics configuration - * - * Following example will create metric filter RootAccountMetricFilter for aws-controltower/CloudTrailLogs log group - * - * @example - * ``` - * metrics: - * # CIS 1.1 – Avoid the use of the "root" account - * - filterName: RootAccountMetricFilter - * logGroupName: aws-controltower/CloudTrailLogs - * filterPattern: '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}' - * metricNamespace: LogMetrics - * metricName: RootAccount - * metricValue: "1" - * ``` - */ - readonly metricSets: IMetricSetConfig[]; - /** - * List AWS CloudWatch Alarms configuration - * - * Following example will create CIS-1.1-RootAccountUsage alarm for RootAccountUsage metric with notification level low - * - * @example - * ``` - * alarms: - * # CIS 1.1 – Avoid the use of the "root" account - * - alarmName: CIS-1.1-RootAccountUsage - * alarmDescription: Alarm for usage of "root" account - * snsAlertLevel: Low (Deprecated) - * snsTopicName: Alarms - * metricName: RootAccountUsage - * namespace: LogMetrics - * comparisonOperator: GreaterThanOrEqualToThreshold - * evaluationPeriods: 1 - * period: 300 - * statistic: Sum - * threshold: 1 - * treatMissingData: notBreaching - * ``` - */ - readonly alarmSets: IAlarmSetConfig[]; - /** - * (OPTIONAL) List CloudWatch Logs configuration - * - * The Following is an example of deploying CloudWatch Logs to multiple regions - * - * @example - * ``` - * logGroups: - * - logGroupName: Log1 - * terminationProtected: true - * encryption: - * useLzaManagedKey: true - * deploymentTarget: - * account: Production - * - logGroupName: Log2 - * terminationProtected: false - * deploymentTarget: - * organization: Infrastructure - * ``` - */ - readonly logGroups?: ILogGroupsConfig[]; -} - -/** - * Accelerator security configuration - */ -export interface ISecurityConfig { - /** - * Accelerator home region name. - * - * @example - * ``` - * homeRegion: &HOME_REGION us-east-1 - * ``` - */ - readonly homeRegion?: t.Region; - /** - * Central security configuration - */ - readonly centralSecurityServices: ICentralSecurityServicesConfig; - readonly accessAnalyzer: IAccessAnalyzerConfig; - readonly iamPasswordPolicy: IIamPasswordPolicyConfig; - readonly awsConfig: IAwsConfig; - readonly cloudWatch: ICloudWatchConfig; - readonly keyManagementService?: IKeyManagementServiceConfig; - readonly resourcePolicyEnforcement?: IResourcePolicyEnforcementConfig; -} diff --git a/source/packages/@aws-accelerator/config/lib/network-config.ts b/source/packages/@aws-accelerator/config/lib/network-config.ts deleted file mode 100644 index bf0de5b..0000000 --- a/source/packages/@aws-accelerator/config/lib/network-config.ts +++ /dev/null @@ -1,952 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import * as t from './common'; -import * as i from './models/network-config'; -import * as CustomizationsConfig from './customizations-config'; -import { ReplacementsConfig } from './replacements-config'; - -const logger = createLogger(['network-config']); - -export class DefaultVpcsConfig implements i.IDefaultVpcsConfig { - readonly delete = false; - readonly excludeAccounts: string[] | undefined = []; - readonly excludeRegions: t.Region[] | undefined = undefined; -} - -export class TransitGatewayRouteTableVpcEntryConfig implements i.ITransitGatewayRouteTableVpcEntryConfig { - readonly account: string = ''; - readonly vpcName: string = ''; -} - -export class TransitGatewayRouteTableDxGatewayEntryConfig implements i.ITransitGatewayRouteTableDxGatewayEntryConfig { - readonly directConnectGatewayName: string = ''; -} - -export class TransitGatewayRouteTableVpnEntryConfig implements i.ITransitGatewayRouteTableVpnEntryConfig { - readonly vpnConnectionName: string = ''; -} - -export class TransitGatewayRouteTableTgwPeeringEntryConfig implements i.ITransitGatewayRouteTableTgwPeeringEntryConfig { - readonly transitGatewayPeeringName: string = ''; -} - -export class TransitGatewayRouteEntryConfig implements i.ITransitGatewayRouteEntryConfig { - readonly destinationCidrBlock: string | undefined = undefined; - readonly destinationPrefixList: string | undefined = undefined; - readonly blackhole: boolean | undefined = undefined; - readonly attachment: - | TransitGatewayRouteTableVpcEntryConfig - | TransitGatewayRouteTableDxGatewayEntryConfig - | TransitGatewayRouteTableVpnEntryConfig - | TransitGatewayRouteTableTgwPeeringEntryConfig - | undefined = undefined; -} - -export class TransitGatewayRouteTableConfig implements i.ITransitGatewayRouteTableConfig { - readonly name: string = ''; - readonly tags: t.Tag[] | undefined = undefined; - readonly routes: TransitGatewayRouteEntryConfig[] = []; -} - -export class TransitGatewayPeeringRequesterConfig implements i.ITransitGatewayPeeringRequesterConfig { - readonly transitGatewayName: string = ''; - readonly account: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly routeTableAssociations: string = ''; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class TransitGatewayPeeringAccepterConfig implements i.ITransitGatewayPeeringAccepterConfig { - readonly transitGatewayName: string = ''; - readonly account: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly routeTableAssociations: string = ''; - readonly autoAccept: boolean | undefined = undefined; - readonly applyTags: boolean | undefined = undefined; -} - -export class TransitGatewayPeeringConfig implements i.ITransitGatewayPeeringConfig { - readonly name: string = ''; - readonly requester = new TransitGatewayPeeringRequesterConfig(); - readonly accepter = new TransitGatewayPeeringAccepterConfig(); -} - -export class TransitGatewayConfig implements i.ITransitGatewayConfig { - readonly name: string = ''; - readonly account: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly asn: number = 65521; - readonly dnsSupport: t.EnableDisable = 'enable'; - readonly vpnEcmpSupport: t.EnableDisable = 'enable'; - readonly defaultRouteTableAssociation: t.EnableDisable = 'enable'; - readonly defaultRouteTablePropagation: t.EnableDisable = 'enable'; - readonly autoAcceptSharingAttachments: t.EnableDisable = 'disable'; - readonly routeTables: TransitGatewayRouteTableConfig[] = []; - readonly transitGatewayCidrBlocks: t.NonEmptyString[] | undefined = undefined; - readonly transitGatewayIpv6CidrBlocks: t.NonEmptyString[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class DxVirtualInterfaceConfig implements i.IDxVirtualInterfaceConfig { - readonly name: string = ''; - readonly connectionId: string = ''; - readonly customerAsn: number = 64512; - readonly interfaceName: string = ''; - readonly ownerAccount: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly type: i.DxVirtualInterfaceType = 'transit'; - readonly vlan: number = 1; - readonly addressFamily: i.IpVersionType | undefined = undefined; - readonly amazonAddress: string | undefined = undefined; - readonly customerAddress: string | undefined = undefined; - readonly enableSiteLink: boolean | undefined = undefined; - readonly jumboFrames: boolean | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class DxTransitGatewayAssociationConfig implements i.IDxTransitGatewayAssociationConfig { - readonly name: string = ''; - readonly account: string = ''; - readonly allowedPrefixes: string[] = []; - readonly routeTableAssociations: string[] | undefined = undefined; - readonly routeTablePropagations: string[] | undefined = undefined; -} - -export class DxGatewayConfig implements i.IDxGatewayConfig { - readonly name: string = ''; - readonly account: string = ''; - readonly asn: number = 64512; - readonly gatewayName: string = ''; - readonly virtualInterfaces: DxVirtualInterfaceConfig[] | undefined = undefined; - readonly transitGatewayAssociations: DxTransitGatewayAssociationConfig[] | undefined = undefined; -} - -export class IpamScopeConfig implements i.IIpamScopeConfig { - readonly name: string = ''; - readonly description: string | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class IpamPoolConfig implements i.IIpamPoolConfig { - readonly addressFamily: i.IpVersionType | undefined = 'ipv4'; - readonly name: string = ''; - readonly scope: string | undefined = undefined; - readonly allocationDefaultNetmaskLength: number | undefined = undefined; - readonly allocationMaxNetmaskLength: number | undefined = undefined; - readonly allocationMinNetmaskLength: number | undefined = undefined; - readonly allocationResourceTags: t.Tag[] | undefined = undefined; - readonly autoImport: boolean | undefined = undefined; - readonly description: string | undefined = undefined; - readonly locale: t.Region | undefined = undefined; - readonly provisionedCidrs: string[] | undefined = undefined; - readonly publiclyAdvertisable: boolean | undefined = undefined; - readonly shareTargets: t.ShareTargets = new t.ShareTargets(); - readonly sourceIpamPool: string | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class IpamConfig implements i.IIpamConfig { - readonly name: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly description: string | undefined = undefined; - readonly operatingRegions: t.Region[] | undefined = undefined; - readonly scopes: IpamScopeConfig[] | undefined = undefined; - readonly pools: IpamPoolConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class RouteTableEntryConfig implements i.IRouteTableEntryConfig { - readonly name: string = ''; - readonly destination: string | undefined = undefined; - readonly destinationPrefixList: string | undefined = undefined; - readonly ipv6Destination: string | undefined = undefined; - readonly type: i.RouteTableEntryType | undefined = undefined; - readonly target: string | undefined = undefined; - readonly targetAvailabilityZone: string | number | undefined = undefined; -} - -export class RouteTableConfig implements i.IRouteTableConfig { - readonly name: string = ''; - readonly gatewayAssociation: i.GatewayRouteTableType | undefined = undefined; - readonly routes: RouteTableEntryConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class SubnetPrivateDnsConfig implements i.ISubnetPrivateDnsConfig { - readonly enableDnsAAAARecord: boolean | undefined = undefined; - readonly enableDnsARecord: boolean | undefined = undefined; - readonly hostnameType: 'ip-name' | 'resource-name' | undefined = undefined; -} - -export class SubnetConfig implements i.ISubnetConfig { - readonly name: string = ''; - readonly assignIpv6OnCreation: boolean | undefined = undefined; - readonly availabilityZone: string | number | undefined = undefined; - readonly enableDns64: boolean | undefined = undefined; - readonly routeTable: string | undefined = undefined; - readonly ipamAllocation: IpamAllocationConfig | undefined = undefined; - readonly ipv4CidrBlock: string | undefined = undefined; - readonly ipv6CidrBlock: string | undefined = undefined; - readonly mapPublicIpOnLaunch: boolean | undefined = undefined; - readonly privateDnsOptions: SubnetPrivateDnsConfig | undefined = undefined; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; - readonly outpost: string | undefined = undefined; -} - -export class NatGatewayConfig implements i.INatGatewayConfig { - readonly name: string = ''; - readonly subnet: string = ''; - readonly allocationId: string | undefined = undefined; - readonly private: boolean | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class TransitGatewayAttachmentTargetConfig implements i.ITransitGatewayAttachmentTargetConfig { - readonly name: string = ''; - readonly account: string = ''; -} - -export class TransitGatewayAttachmentOptionsConfig implements i.ITransitGatewayAttachmentOptionsConfig { - readonly applianceModeSupport: t.EnableDisable | undefined = undefined; - readonly dnsSupport: t.EnableDisable | undefined = undefined; - readonly ipv6Support: t.EnableDisable | undefined = undefined; -} - -export class LocalGatewayRouteTableConfig implements i.ILocalGatewayRouteTableConfig { - readonly name: string = ''; - readonly id: string = ''; -} - -export class LocalGatewayConfig implements i.ILocalGatewayConfig { - readonly name: string = ''; - readonly id: string = ''; - readonly routeTables: LocalGatewayRouteTableConfig[] = []; -} - -export class OutpostsConfig implements i.IOutpostsConfig { - readonly name: string = ''; - readonly arn: string = ''; - readonly availabilityZone: string | number = ''; - readonly localGateway: LocalGatewayConfig | undefined = undefined; -} - -export class TransitGatewayAttachmentConfig implements i.ITransitGatewayAttachmentConfig { - readonly name: string = ''; - readonly transitGateway: TransitGatewayAttachmentTargetConfig = new TransitGatewayAttachmentTargetConfig(); - readonly subnets: string[] = []; - readonly routeTableAssociations: string[] | undefined = undefined; - readonly routeTablePropagations: string[] | undefined = undefined; - readonly options: TransitGatewayAttachmentOptionsConfig | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class TransitGatewayConnectConfig implements i.ITransitGatewayConnectConfig { - readonly name: string = ''; - readonly region: string = ''; - readonly transitGateway: TransitGatewayAttachmentTargetConfig = new TransitGatewayAttachmentTargetConfig(); - readonly vpc?: i.ITransitGatewayConnectVpcConfig | undefined; - readonly directConnect?: string = ''; - readonly options: TransitGatewayConnectOptionsConfig | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class TransitGatewayConnectOptionsConfig implements i.ITransitGatewayConnectOptionsConfig { - readonly protocol: i.TransitGatewayConnectProtocol = 'gre'; -} - -export class TransitGatewayConnectVpcConfig implements i.ITransitGatewayConnectVpcConfig { - readonly vpcName: string = ''; - readonly vpcAttachment: string = ''; -} - -export class GatewayEndpointServiceConfig implements i.IGatewayEndpointServiceConfig { - readonly service: i.GatewayEndpointType = 's3'; - readonly policy: string | undefined = undefined; - readonly applyPolicy: boolean = true; - readonly serviceName: string | undefined = undefined; -} - -export class GatewayEndpointConfig implements i.IGatewayEndpointConfig { - readonly defaultPolicy: string = ''; - readonly endpoints: GatewayEndpointServiceConfig[] = []; -} - -export class InterfaceEndpointServiceConfig implements i.IInterfaceEndpointServiceConfig { - readonly service: string = ''; - readonly serviceName: string | undefined = undefined; - readonly policy: string | undefined = undefined; - readonly applyPolicy: boolean | undefined = true; - readonly securityGroup: string | undefined = undefined; -} - -export class InterfaceEndpointConfig implements i.IInterfaceEndpointConfig { - readonly defaultPolicy: string = ''; - readonly endpoints: InterfaceEndpointServiceConfig[] = [new InterfaceEndpointServiceConfig()]; - readonly subnets: string[] = []; - readonly central: boolean | undefined = undefined; - readonly allowedCidrs: string[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} -export class SubnetSourceConfig implements i.ISubnetSourceConfig { - readonly account: string = ''; - readonly vpc: string = ''; - readonly subnets: string[] = []; - /** - * (OPTIONAL) Indicates whether to target the IPv6 CIDR associated with a subnet. - * - * @remarks - * Leave this property undefined or set to `false` to target a subnet's IPv4 CIDR. - */ - readonly ipv6: boolean | undefined = undefined; -} - -export class SecurityGroupSourceConfig implements i.ISecurityGroupSourceConfig { - readonly securityGroups: string[] = []; -} - -export class PrefixListSourceConfig implements i.IPrefixListSourceConfig { - readonly prefixLists: string[] = []; -} - -export class PrefixListConfig implements i.IPrefixListConfig { - readonly name: string = ''; - readonly accounts: string[] | undefined = undefined; - readonly regions: t.Region[] | undefined = undefined; - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; - readonly addressFamily: i.IpAddressFamilyType = 'IPv4'; - readonly maxEntries: number = 1; - readonly entries: string[] = []; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class SecurityGroupRuleConfig implements i.ISecurityGroupRuleConfig { - readonly description: string = ''; - readonly types: i.SecurityGroupRuleType[] | undefined = undefined; - readonly tcpPorts: number[] | undefined = undefined; - readonly udpPorts: number[] | undefined = undefined; - readonly fromPort: number | undefined = undefined; - readonly toPort: number | undefined = undefined; - readonly sources: (t.NonEmptyString | SubnetSourceConfig | SecurityGroupSourceConfig | PrefixListSourceConfig)[] = []; - readonly ipProtocols: string[] = []; -} - -export class SecurityGroupConfig implements i.ISecurityGroupConfig { - readonly name: string = ''; - readonly description: string | undefined = undefined; - readonly inboundRules: SecurityGroupRuleConfig[] = []; - readonly outboundRules: SecurityGroupRuleConfig[] = []; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class NetworkAclSubnetSelection implements i.INetworkAclSubnetSelection { - readonly account: string = ''; - readonly vpc: string = ''; - readonly subnet: string = ''; - readonly ipv6: boolean | undefined = undefined; - readonly region: t.Region | undefined = undefined; -} - -export class NetworkAclInboundRuleConfig implements i.INetworkAclInboundRuleConfig { - readonly rule: number = 100; - readonly protocol: number = -1; - readonly fromPort: number = -1; - readonly toPort: number = -1; - readonly action: t.AllowDeny = 'allow'; - readonly source: string | NetworkAclSubnetSelection = ''; -} - -export class NetworkAclOutboundRuleConfig implements i.INetworkAclOutboundRuleConfig { - readonly rule: number = 100; - readonly protocol: number = -1; - readonly fromPort: number = -1; - readonly toPort: number = -1; - readonly action: t.AllowDeny = 'allow'; - readonly destination: string | NetworkAclSubnetSelection = ''; -} - -export class NetworkAclConfig implements i.INetworkAclConfig { - readonly name: string = ''; - readonly subnetAssociations: string[] = []; - readonly inboundRules: NetworkAclInboundRuleConfig[] | undefined = undefined; - readonly outboundRules: NetworkAclOutboundRuleConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class IpamAllocationConfig implements i.IIpamAllocationConfig { - readonly ipamPoolName: string = ''; - readonly netmaskLength: number = 24; -} - -export class DhcpOptsConfig implements i.IDhcpOptsConfig { - readonly name: string = ''; - readonly accounts: string[] = ['']; - readonly regions: t.Region[] = ['us-east-1']; - readonly domainName: string | undefined = undefined; - readonly domainNameServers: string[] | undefined = undefined; - readonly netbiosNameServers: string[] | undefined = undefined; - readonly netbiosNodeType: i.NetbiosNodeType | undefined = undefined; - readonly ntpServers: string[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class EndpointPolicyConfig implements i.IEndpointPolicyConfig { - readonly name: string = ''; - readonly document: string = ''; -} - -export class VpnLoggingConfig implements i.IVpnLoggingConfig { - readonly enable: boolean | undefined = undefined; - readonly logGroupName: string | undefined = undefined; - readonly outputFormat: i.VpnLoggingOutputFormatType | undefined = undefined; -} - -export class Phase1Config implements i.IPhase1Config { - readonly dhGroups: i.Phase1DhGroupType[] | undefined = undefined; - readonly encryptionAlgorithms: i.EncryptionAlgorithmType[] | undefined = undefined; - readonly integrityAlgorithms: i.IntegrityAlgorithmType[] | undefined = undefined; - readonly lifetimeSeconds: number | undefined = undefined; -} - -export class Phase2Config implements i.IPhase2Config { - readonly dhGroups: i.Phase2DhGroupType[] | undefined = undefined; - readonly encryptionAlgorithms: i.EncryptionAlgorithmType[] | undefined = undefined; - readonly integrityAlgorithms: i.IntegrityAlgorithmType[] | undefined = undefined; - readonly lifetimeSeconds: number | undefined = undefined; -} - -export class VpnTunnelOptionsSpecificationsConfig implements i.IVpnTunnelOptionsSpecificationsConfig { - readonly dpdTimeoutAction: i.DpdTimeoutActionType | undefined = undefined; - readonly dpdTimeoutSeconds: number | undefined = undefined; - readonly ikeVersions: i.IkeVersionType[] | undefined = undefined; - readonly logging: VpnLoggingConfig | undefined = undefined; - readonly phase1: Phase1Config | undefined = undefined; - readonly phase2: Phase2Config | undefined = undefined; - readonly preSharedKey: string | undefined = undefined; - readonly rekeyFuzzPercentage: number | undefined = undefined; - readonly rekeyMarginTimeSeconds: number | undefined = undefined; - readonly replayWindowSize: number | undefined = undefined; - readonly startupAction: i.StartupActionType | undefined = undefined; - readonly tunnelInsideCidr: string | undefined = undefined; - readonly tunnelLifecycleControl: boolean | undefined = undefined; -} - -export class VpnConnectionConfig implements i.IVpnConnectionConfig { - readonly name: string = ''; - readonly amazonIpv4NetworkCidr: string | undefined = undefined; - readonly customerIpv4NetworkCidr: string | undefined = undefined; - readonly enableVpnAcceleration: boolean | undefined = undefined; - readonly transitGateway: string | undefined = undefined; - readonly vpc: string | undefined = undefined; - readonly routeTableAssociations: string[] | undefined = undefined; - readonly routeTablePropagations: string[] | undefined = undefined; - readonly staticRoutesOnly: boolean | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; - readonly tunnelSpecifications: VpnTunnelOptionsSpecificationsConfig[] | undefined = undefined; -} - -export class CustomerGatewayConfig implements i.ICustomerGatewayConfig { - readonly name: string = ''; - readonly account: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly ipAddress: string = ''; - readonly asn: number = 65000; - readonly tags: t.Tag[] | undefined = undefined; - readonly vpnConnections: VpnConnectionConfig[] | undefined = undefined; -} - -export class LoadBalancersConfig implements i.ILoadBalancersConfig { - readonly applicationLoadBalancers: CustomizationsConfig.ApplicationLoadBalancerConfig[] | undefined = undefined; - readonly networkLoadBalancers: CustomizationsConfig.NetworkLoadBalancerConfig[] | undefined = undefined; -} - -export class VirtualPrivateGatewayConfig implements i.IVirtualPrivateGatewayConfig { - readonly asn: number = 65000; -} - -export class VpcIpv6Config implements i.IVpcIpv6Config { - readonly amazonProvided: boolean | undefined = undefined; - readonly cidrBlock: string | undefined = undefined; - readonly byoipPoolId: string | undefined = undefined; -} - -export class VpcConfig implements i.IVpcConfig { - readonly name: string = ''; - readonly account: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly cidrs: string[] | undefined = undefined; - readonly defaultSecurityGroupRulesDeletion: boolean | undefined = false; - readonly dhcpOptions: string | undefined = undefined; - readonly dnsFirewallRuleGroups: i.IVpcDnsFirewallAssociationConfig[] | undefined = undefined; - readonly egressOnlyIgw: boolean | undefined = undefined; - readonly internetGateway: boolean | undefined = undefined; - readonly enableDnsHostnames: boolean | undefined = true; - readonly enableDnsSupport: boolean | undefined = true; - readonly instanceTenancy: i.InstanceTenancyType | undefined = 'default'; - readonly ipamAllocations: IpamAllocationConfig[] | undefined = undefined; - readonly ipv6Cidrs: VpcIpv6Config[] | undefined = undefined; - readonly queryLogs: string[] | undefined = undefined; - readonly resolverRules: string[] | undefined = undefined; - readonly routeTables: RouteTableConfig[] | undefined = undefined; - readonly subnets: SubnetConfig[] | undefined = undefined; - readonly natGateways: NatGatewayConfig[] | undefined = undefined; - readonly transitGatewayAttachments: TransitGatewayAttachmentConfig[] | undefined = undefined; - readonly outposts: OutpostsConfig[] | undefined = undefined; - readonly gatewayEndpoints: GatewayEndpointConfig | undefined = undefined; - readonly interfaceEndpoints: InterfaceEndpointConfig | undefined = undefined; - readonly useCentralEndpoints: boolean | undefined = false; - readonly securityGroups: SecurityGroupConfig[] | undefined = undefined; - readonly networkAcls: NetworkAclConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; - readonly virtualPrivateGateway: VirtualPrivateGatewayConfig | undefined = undefined; - readonly vpcFlowLogs: t.VpcFlowLogsConfig | undefined = undefined; - readonly loadBalancers: LoadBalancersConfig | undefined = undefined; - readonly targetGroups: CustomizationsConfig.TargetGroupItemConfig[] | undefined = undefined; - readonly vpcRoute53Resolver: VpcResolverConfig | undefined = undefined; -} - -export class VpcTemplatesConfig implements i.IVpcTemplatesConfig { - readonly name: string = ''; - readonly region: t.Region = 'us-east-1'; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly cidrs: string[] | undefined = undefined; - readonly ipamAllocations: IpamAllocationConfig[] | undefined = undefined; - readonly defaultSecurityGroupRulesDeletion: boolean | undefined = false; - readonly dhcpOptions: string | undefined = undefined; - readonly dnsFirewallRuleGroups: i.IVpcDnsFirewallAssociationConfig[] | undefined = undefined; - readonly egressOnlyIgw: boolean | undefined = undefined; - readonly internetGateway: boolean | undefined = undefined; - readonly enableDnsHostnames: boolean | undefined = true; - readonly enableDnsSupport: boolean | undefined = true; - readonly instanceTenancy: i.InstanceTenancyType | undefined = 'default'; - readonly ipv6Cidrs: VpcIpv6Config[] | undefined = undefined; - readonly queryLogs: string[] | undefined = undefined; - readonly resolverRules: string[] | undefined = undefined; - readonly routeTables: RouteTableConfig[] | undefined = undefined; - readonly subnets: SubnetConfig[] | undefined = undefined; - readonly natGateways: NatGatewayConfig[] | undefined = undefined; - readonly transitGatewayAttachments: TransitGatewayAttachmentConfig[] | undefined = undefined; - readonly gatewayEndpoints: GatewayEndpointConfig | undefined = undefined; - readonly interfaceEndpoints: InterfaceEndpointConfig | undefined = undefined; - readonly useCentralEndpoints: boolean | undefined = false; - readonly securityGroups: SecurityGroupConfig[] | undefined = undefined; - readonly networkAcls: NetworkAclConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; - readonly virtualPrivateGateway: VirtualPrivateGatewayConfig | undefined = undefined; - readonly vpcFlowLogs: t.VpcFlowLogsConfig | undefined = undefined; - readonly loadBalancers: LoadBalancersConfig | undefined = undefined; - readonly targetGroups: CustomizationsConfig.TargetGroupItemConfig[] | undefined = undefined; -} - -export class ResolverRuleConfig implements i.IResolverRuleConfig { - readonly name: string = ''; - readonly domainName: string = ''; - readonly excludedRegions: t.Region[] | undefined = undefined; - readonly inboundEndpointTarget: string | undefined = undefined; - readonly ruleType: i.RuleType | undefined = 'FORWARD'; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; - readonly targetIps: i.IRuleTargetIps[] | undefined = undefined; -} - -export class ResolverEndpointConfig implements i.IResolverEndpointConfig { - readonly name: string = ''; - readonly type: i.ResolverEndpointType = 'INBOUND'; - readonly vpc: string = ''; - readonly subnets: string[] = []; - readonly allowedCidrs: string[] | undefined = undefined; - readonly rules: ResolverRuleConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class DnsQueryLogsConfig implements i.IDnsQueryLogsConfig { - readonly name: string = ''; - readonly destinations: t.LogDestinationType[] = ['s3']; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly excludedRegions: t.Region[] | undefined = undefined; -} - -export class DnsFirewallRulesConfig implements i.IDnsFirewallRulesConfig { - readonly name: string = ''; - readonly action: i.DnsFirewallRuleActionType = 'ALERT'; - readonly priority: number = 100; - readonly blockOverrideDomain: string | undefined = undefined; - readonly blockOverrideTtl: number | undefined = undefined; - readonly blockResponse: i.DnsFirewallBlockResponseType | undefined = undefined; - readonly customDomainList: string | undefined = undefined; - readonly managedDomainList: i.DnsFirewallManagedDomainListsType | undefined = undefined; -} - -export class DnsFirewallRuleGroupConfig implements i.IDnsFirewallRuleGroupConfig { - readonly name: string = ''; - readonly regions: t.Region[] = ['us-east-1']; - readonly rules: DnsFirewallRulesConfig[] = []; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class LocalResolverConfig implements i.IResolverConfig { - readonly endpoints: ResolverEndpointConfig[] | undefined = undefined; - readonly queryLogs: DnsQueryLogsConfig | undefined = undefined; -} - -export class VpcResolverConfig implements i.IResolverConfig { - readonly endpoints: ResolverEndpointConfig[] | undefined = undefined; - readonly queryLogs: DnsQueryLogsConfig | undefined = undefined; -} -export class ResolverConfig implements i.IResolverConfig { - readonly endpoints: ResolverEndpointConfig[] | undefined = undefined; - readonly firewallRuleGroups: DnsFirewallRuleGroupConfig[] | undefined = undefined; - readonly queryLogs: DnsQueryLogsConfig | undefined = undefined; - readonly rules: ResolverRuleConfig[] | undefined = undefined; -} - -export class NfwRuleSourceListConfig implements i.INfwRuleSourceListConfig { - readonly generatedRulesType: i.NfwGeneratedRulesType = 'DENYLIST'; - readonly targets: string[] = []; - readonly targetTypes: i.NfwTargetType[] = ['TLS_SNI']; -} - -export class NfwRuleSourceStatefulRuleHeaderConfig implements i.INfwRuleSourceStatefulRuleHeaderConfig { - readonly destination: string = ''; - readonly destinationPort: string = ''; - readonly direction: i.NfwStatefulRuleDirectionType = 'ANY'; - readonly protocol: i.NfwStatefulRuleProtocolType = 'IP'; - readonly source: string = ''; - readonly sourcePort: string = ''; -} - -export class NfwRuleSourceStatefulRuleOptionsConfig implements i.INfwRuleSourceStatefulRuleOptionsConfig { - readonly keyword: string = ''; - readonly settings: string[] | undefined = undefined; -} - -export class NfwRuleSourceStatefulRuleConfig implements i.INfwRuleSourceStatefulRuleConfig { - readonly action: i.NfwStatefulRuleActionType = 'DROP'; - readonly header: NfwRuleSourceStatefulRuleHeaderConfig = new NfwRuleSourceStatefulRuleHeaderConfig(); - readonly ruleOptions: NfwRuleSourceStatefulRuleOptionsConfig[] = [new NfwRuleSourceStatefulRuleOptionsConfig()]; -} - -export class NfwRuleSourceCustomActionDimensionConfig implements i.INfwRuleSourceCustomActionDimensionConfig { - readonly dimensions: string[] = []; -} - -export class NfwRuleSourceCustomActionDefinitionConfig implements i.INfwRuleSourceCustomActionDefinitionConfig { - readonly publishMetricAction: NfwRuleSourceCustomActionDimensionConfig = - new NfwRuleSourceCustomActionDimensionConfig(); -} - -export class NfwRuleSourceCustomActionConfig implements i.INfwRuleSourceCustomActionConfig { - readonly actionDefinition: NfwRuleSourceCustomActionDefinitionConfig = - new NfwRuleSourceCustomActionDefinitionConfig(); - readonly actionName: string = ''; -} - -export class NfwRuleSourceStatelessPortRangeConfig implements i.INfwRuleSourceStatelessPortRangeConfig { - readonly fromPort: number = 123; - readonly toPort: number = 123; -} - -export class NfwRuleSourceStatelessTcpFlagsConfig implements i.INfwRuleSourceStatelessTcpFlagsConfig { - readonly flags: i.NfwStatelessRuleTcpFlagType[] = []; - readonly masks: i.NfwStatelessRuleTcpFlagType[] = []; -} - -export class NfwRuleSourceStatelessMatchAttributesConfig implements i.INfwRuleSourceStatelessMatchAttributesConfig { - readonly destinationPorts: NfwRuleSourceStatelessPortRangeConfig[] | undefined = undefined; - readonly destinations: string[] | undefined = undefined; - readonly protocols: number[] | undefined = undefined; - readonly sourcePorts: NfwRuleSourceStatelessPortRangeConfig[] | undefined = undefined; - readonly sources: string[] | undefined = undefined; - readonly tcpFlags: NfwRuleSourceStatelessTcpFlagsConfig[] | undefined = undefined; -} - -export class NfwRuleSourceStatelessRuleDefinitionConfig implements i.INfwRuleSourceStatelessRuleDefinitionConfig { - readonly actions: i.NfwStatelessRuleActionType[] | string[] = ['aws:drop']; - readonly matchAttributes: NfwRuleSourceStatelessMatchAttributesConfig = - new NfwRuleSourceStatelessMatchAttributesConfig(); -} - -export class NfwRuleSourceStatelessRuleConfig implements i.INfwRuleSourceStatelessRuleConfig { - readonly priority: number = 123; - readonly ruleDefinition: NfwRuleSourceStatelessRuleDefinitionConfig = - new NfwRuleSourceStatelessRuleDefinitionConfig(); -} - -export class NfwStatelessRulesAndCustomActionsConfig implements i.INfwStatelessRulesAndCustomActionsConfig { - readonly statelessRules: NfwRuleSourceStatelessRuleConfig[] = [new NfwRuleSourceStatelessRuleConfig()]; - readonly customActions: NfwRuleSourceCustomActionConfig[] | undefined = undefined; -} - -export class NfwRuleSourceConfig implements i.INfwRuleSourceConfig { - readonly rulesSourceList: NfwRuleSourceListConfig | undefined = undefined; - readonly rulesString: string | undefined = undefined; - readonly statefulRules: NfwRuleSourceStatefulRuleConfig[] | undefined = undefined; - readonly statelessRulesAndCustomActions: NfwStatelessRulesAndCustomActionsConfig | undefined = undefined; - readonly rulesFile: string | undefined = undefined; -} - -export class NfwRuleVariableDefinitionConfig implements i.INfwRuleVariableDefinitionConfig { - readonly name: string = ''; - readonly definition: string[] = []; -} - -export class NfwRuleVariableConfig implements i.INfwRuleVariableConfig { - readonly ipSets: NfwRuleVariableDefinitionConfig | NfwRuleVariableDefinitionConfig[] = [ - new NfwRuleVariableDefinitionConfig(), - ]; - readonly portSets: NfwRuleVariableDefinitionConfig | NfwRuleVariableDefinitionConfig[] = [ - new NfwRuleVariableDefinitionConfig(), - ]; -} - -export class NfwRuleGroupRuleConfig implements i.INfwRuleGroupRuleConfig { - readonly rulesSource: NfwRuleSourceConfig = new NfwRuleSourceConfig(); - readonly ruleVariables: NfwRuleVariableConfig | undefined = undefined; - readonly statefulRuleOptions: i.NfwStatefulRuleOptionsType | undefined = undefined; -} - -export class NfwRuleGroupConfig implements i.INfwRuleGroupConfig { - readonly name: string = ''; - readonly regions: t.Region[] = []; - readonly capacity: number = 123; - readonly type: i.NfwRuleType = 'STATEFUL'; - readonly description: string | undefined = undefined; - readonly ruleGroup: NfwRuleGroupRuleConfig | undefined = undefined; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class NfwStatefulRuleGroupReferenceConfig implements i.INfwStatefulRuleGroupReferenceConfig { - readonly name: string = ''; - readonly priority: number | undefined = undefined; -} - -export class NfwStatelessRuleGroupReferenceConfig implements i.INfwStatelessRuleGroupReferenceConfig { - readonly name: string = ''; - readonly priority: number = 123; -} - -export class NfwFirewallPolicyPolicyConfig implements i.INfwFirewallPolicyPolicyConfig { - readonly statelessDefaultActions: string[] | i.NfwStatelessRuleActionType[] = []; - readonly statelessFragmentDefaultActions: string[] | i.NfwStatelessRuleActionType[] = []; - readonly statefulDefaultActions: i.NfwStatefulDefaultActionType[] | undefined = undefined; - readonly statefulEngineOptions: i.NfwStatefulRuleOptionsType | undefined = undefined; - readonly statefulRuleGroups: NfwStatefulRuleGroupReferenceConfig[] | undefined = undefined; - readonly statelessCustomActions: NfwRuleSourceCustomActionConfig[] | undefined = undefined; - readonly statelessRuleGroups: NfwStatelessRuleGroupReferenceConfig[] | undefined = undefined; -} - -export class NfwFirewallPolicyConfig implements i.INfwFirewallPolicyConfig { - readonly name: string = ''; - readonly firewallPolicy: NfwFirewallPolicyPolicyConfig = new NfwFirewallPolicyPolicyConfig(); - readonly regions: t.Region[] = []; - readonly description: string | undefined = undefined; - readonly shareTargets: t.ShareTargets | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class NfwLoggingConfig implements i.INfwLoggingConfig { - readonly destination: t.LogDestinationType = 's3'; - readonly type: i.NfwLogType = 'ALERT'; -} - -export class NfwFirewallConfig implements i.INfwFirewallConfig { - readonly name: string = ''; - readonly firewallPolicy: string = ''; - readonly subnets: string[] = []; - readonly vpc: string = ''; - readonly deleteProtection: boolean | undefined = undefined; - readonly description: string | undefined = undefined; - readonly firewallPolicyChangeProtection: boolean | undefined = undefined; - readonly subnetChangeProtection: boolean | undefined = undefined; - readonly loggingConfiguration: NfwLoggingConfig[] | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class NfwConfig implements i.INfwConfig { - readonly firewalls: NfwFirewallConfig[] = []; - readonly policies: NfwFirewallPolicyConfig[] = []; - readonly rules: NfwRuleGroupConfig[] = []; -} - -export class GwlbEndpointConfig implements i.IGwlbEndpointConfig { - readonly name: string = ''; - readonly account: string = ''; - readonly subnet: string = ''; - readonly vpc: string = ''; -} - -export class GwlbConfig implements i.IGwlbConfig { - readonly name: string = ''; - readonly endpoints: GwlbEndpointConfig[] = []; - readonly subnets: string[] = []; - readonly vpc: string = ''; - readonly account: string | undefined = undefined; - readonly crossZoneLoadBalancing: boolean | undefined = undefined; - readonly deletionProtection: boolean | undefined = undefined; - readonly targetGroup: string | undefined = undefined; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class CentralNetworkServicesConfig implements i.ICentralNetworkServicesConfig { - readonly delegatedAdminAccount: string = ''; - readonly gatewayLoadBalancers: GwlbConfig[] | undefined = undefined; - readonly ipams: IpamConfig[] | undefined = undefined; - readonly route53Resolver: ResolverConfig | undefined = undefined; - readonly networkFirewall: NfwConfig | undefined = undefined; -} - -export class VpcPeeringConfig implements i.IVpcPeeringConfig { - readonly name: string = ''; - readonly vpcs: string[] = []; - readonly tags: t.Tag[] | undefined = undefined; -} - -export class ElbAccountIdsConfig implements i.IElbAccountIdsConfig { - readonly region: string = ''; - readonly accountId: string = ''; -} - -export class FirewallManagerNotificationChannelConfig implements i.IFirewallManagerNotificationChannelConfig { - readonly region: string = ''; - readonly snsTopic: string = ''; -} - -export class CertificateConfig implements i.ICertificateConfig { - readonly name: string = ''; - readonly type: i.CertificateConfigType = 'import'; - readonly privKey: string | undefined = undefined; - readonly cert: string | undefined = undefined; - readonly chain: string | undefined = undefined; - readonly validation: i.CertificateValidationType = 'EMAIL'; - readonly domain: string | undefined = undefined; - readonly san: string[] | undefined = undefined; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class FirewallManagerConfig implements i.IFirewallManagerServiceConfig { - readonly delegatedAdminAccount: string = ''; - readonly notificationChannels: FirewallManagerNotificationChannelConfig[] | undefined = undefined; -} - -export class NetworkConfig implements i.INetworkConfig { - /** - * The name of the network configuration file. - */ - static readonly FILENAME = 'network-config.yaml'; - - readonly defaultVpc: DefaultVpcsConfig = new DefaultVpcsConfig(); - readonly transitGateways: TransitGatewayConfig[] = []; - readonly transitGatewayConnects: TransitGatewayConnectConfig[] | undefined = undefined; - readonly transitGatewayPeering: TransitGatewayPeeringConfig[] | undefined = undefined; - readonly customerGateways: CustomerGatewayConfig[] | undefined = undefined; - readonly endpointPolicies: EndpointPolicyConfig[] = []; - readonly vpcs: VpcConfig[] = []; - readonly vpcFlowLogs: t.VpcFlowLogsConfig | undefined = undefined; - readonly dhcpOptions: DhcpOptsConfig[] | undefined = undefined; - readonly centralNetworkServices: CentralNetworkServicesConfig | undefined = undefined; - readonly directConnectGateways: DxGatewayConfig[] | undefined = undefined; - readonly prefixLists: PrefixListConfig[] | undefined = undefined; - readonly vpcPeering: VpcPeeringConfig[] | undefined = undefined; - readonly vpcTemplates: VpcTemplatesConfig[] | undefined = undefined; - readonly elbAccountIds: ElbAccountIdsConfig[] | undefined = undefined; - readonly firewallManagerService: FirewallManagerConfig | undefined = undefined; - readonly certificates: CertificateConfig[] | undefined = undefined; - public accountVpcIds: Record | undefined = undefined; - public accountVpcEndpointIds: Record | undefined = undefined; - - /** - * - * @param values - */ - constructor(values?: i.INetworkConfig) { - Object.assign(this, values); - } - - /** - * Function to get list of account names which will be used as account principal for TGE peering role - * @param accepterAccountName - * @returns - */ - public getTgwRequestorAccountNames(accepterAccountName: string): string[] { - const accountNames: string[] = []; - - for (const transitGatewayPeeringItem of this.transitGatewayPeering ?? []) { - if (transitGatewayPeeringItem.accepter.account === accepterAccountName) { - accountNames.push(transitGatewayPeeringItem.requester.account); - } - } - return accountNames; - } - - /** - * Function to get requester or accepter config of tgw peering - * @param peeringName - * @param peerType - * @returns - */ - public getTgwPeeringRequesterAccepterConfig( - peeringName: string, - peerType: 'requester' | 'accepter', - ): TransitGatewayPeeringRequesterConfig | TransitGatewayPeeringAccepterConfig | undefined { - for (const transitGatewayPeering of this.transitGatewayPeering ?? []) { - if (transitGatewayPeering.name === peeringName) { - if (peerType === 'requester') { - return transitGatewayPeering.requester; - } else { - return transitGatewayPeering.accepter; - } - } - } - - logger.error(`Transit gateway peering ${peeringName} not found !!!`); - throw new Error('configuration validation failed.'); - } - - /** - * - * @param dir - * @returns - */ - static load(dir: string, replacementsConfig?: ReplacementsConfig): NetworkConfig { - const initialBuffer = fs.readFileSync(path.join(dir, NetworkConfig.FILENAME), 'utf8'); - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseNetworkConfig(yaml.load(buffer)); - - return new NetworkConfig(values); - } - - /** - * Load from string content - * @param content - */ - static loadFromString(content: string): NetworkConfig | undefined { - try { - const values = t.parseNetworkConfig(yaml.load(content)); - return new NetworkConfig(values); - } catch (e) { - logger.error('Error parsing input, network config undefined'); - logger.error(`${e}`); - throw new Error('could not load configuration.'); - } - } -} diff --git a/source/packages/@aws-accelerator/config/lib/organization-config.ts b/source/packages/@aws-accelerator/config/lib/organization-config.ts deleted file mode 100644 index f3ad7a7..0000000 --- a/source/packages/@aws-accelerator/config/lib/organization-config.ts +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { loadOrganizationalUnits } from '@aws-accelerator/utils/lib/load-organization-config'; - -import * as t from './common'; -import * as i from './models/organization-config'; -import { ReplacementsConfig } from './replacements-config'; -import { AccountsConfig } from './accounts-config'; - -const logger = createLogger(['organization-config']); - -export abstract class OrganizationalUnitConfig implements i.IOrganizationalUnitConfig { - readonly name: string = ''; - readonly ignore: boolean | undefined = undefined; -} - -export abstract class OrganizationalUnitIdConfig implements i.IOrganizationalUnitIdConfig { - readonly name: string = ''; - readonly id: string = ''; - readonly arn: string = ''; -} - -export abstract class QuarantineNewAccountsConfig implements i.IQuarantineNewAccountsConfig { - readonly enable: boolean = true; - readonly scpPolicyName: string = 'QuarantineAccounts'; -} - -export abstract class ServiceControlPolicyConfig implements i.IServiceControlPolicyConfig { - readonly name: string = ''; - readonly description: string = ''; - readonly policy: string = ''; - readonly type = 'customerManaged'; - readonly strategy = 'deny-list'; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export abstract class TaggingPolicyConfig implements i.ITaggingPolicyConfig { - readonly name: string = ''; - readonly description: string = ''; - readonly policy: string = ''; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export abstract class BackupPolicyConfig implements i.IBackupPolicyConfig { - readonly name: string = ''; - readonly description: string = ''; - readonly policy: string = ''; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class OrganizationConfig implements i.IOrganizationConfig { - /** - * A name for the organization config file in config repository - * - * @default organization-config.yaml - */ - static readonly FILENAME = 'organization-config.yaml'; - - readonly enable = true; - readonly organizationalUnits: OrganizationalUnitConfig[] = [ - { - name: 'Security', - ignore: undefined, - }, - { - name: 'Infrastructure', - ignore: undefined, - }, - ]; - - public organizationalUnitIds: OrganizationalUnitIdConfig[] | undefined = undefined; - readonly quarantineNewAccounts: QuarantineNewAccountsConfig | undefined = undefined; - readonly serviceControlPolicies: ServiceControlPolicyConfig[] = []; - readonly taggingPolicies: TaggingPolicyConfig[] = []; - readonly backupPolicies: BackupPolicyConfig[] = []; - - /** - * - * @param values - * @param configDir - * @param validateConfig - */ - constructor(values?: i.IOrganizationConfig) { - if (values) { - Object.assign(this, values); - } - } - - /** - * Load from config file content - * @param dir - * @param validateConfig - * @returns - */ - static load(dir: string, replacementsConfig?: ReplacementsConfig): OrganizationConfig { - const initialBuffer = fs.readFileSync(path.join(dir, OrganizationConfig.FILENAME), 'utf8'); - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseOrganizationConfig(yaml.load(buffer)); - return new OrganizationConfig(values); - } - - /** - * Loads the file raw with default replacements placeholders to determine if organizations is enabled. - */ - static loadRawOrganizationsConfig(dir: string): OrganizationConfig { - const accountsConfig = AccountsConfig.load(dir); - const orgConfig = OrganizationConfig.load(dir); - let replacementsConfig: ReplacementsConfig; - - if (fs.existsSync(path.join(dir, ReplacementsConfig.FILENAME))) { - replacementsConfig = ReplacementsConfig.load(dir, accountsConfig, true); - } else { - replacementsConfig = new ReplacementsConfig(); - } - - replacementsConfig.loadReplacementValues({}, orgConfig.enable); - return OrganizationConfig.load(dir, replacementsConfig); - } - - /** - * Load from string - * @param initialBuffer - * @param replacementsConfig - * @returns - */ - static loadFromString(initialBuffer: string, replacementsConfig?: ReplacementsConfig): OrganizationConfig { - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseOrganizationConfig(yaml.load(buffer)); - return new OrganizationConfig(values); - } - - /** - * Load from buffer - * @param dir - * @param replacementsConfig - * @returns - */ - static loadBuffer(dir: string, replacementsConfig?: ReplacementsConfig): string { - const initialBuffer = fs.readFileSync(path.join(dir, OrganizationConfig.FILENAME), 'utf8'); - return replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - } - - /** - * Load from string content - * @param partition - */ - public async loadOrganizationalUnitIds(partition: string): Promise { - if (!this.enable) { - // do nothing - return; - } else { - this.organizationalUnitIds = []; - } - if (this.organizationalUnitIds?.length == 0) { - this.organizationalUnitIds = await loadOrganizationalUnits(partition, this.organizationalUnits); - } - } - - public getOrganizationId(): string | undefined { - if (!this.enable) { - return undefined; - } else { - // We can get the AWS Organization Id without an API call here - // because we already retrieved OU ARNs which contain the Organization Id. - // We know every organization has at least one OU so we - // can get the Organization Id from parsing the first OU ARN. - const orgId = this.organizationalUnitIds![0].arn.split('/')[1]; - if (orgId) { - return orgId; - } - } - logger.error('Organizations not enabled or error getting Organization Id'); - throw new Error('configuration validation failed.'); - } - - public getOrganizationalUnitId(name: string): string { - if (!this.enable) { - // do nothing - } else { - const ou = this.organizationalUnitIds?.find(item => item.name === name); - if (ou) { - return ou.id; - } - } - logger.error(`Could not get Organization ID for name: ${name}. Organizations not enabled or OU doesn't exist`); - throw new Error('configuration validation failed.'); - } - - public getOrganizationalUnitArn(name: string): string { - if (!this.enable) { - // do nothing - } else { - const ou = this.organizationalUnitIds?.find(item => item.name === name); - if (ou) { - return ou.arn; - } - } - logger.error(`Could not get Organization Arn for name: ${name}. Organizations not enabled or OU doesn't exist`); - throw new Error('configuration validation failed.'); - } - - public isIgnored(name: string): boolean { - if (!this.enable) { - return false; - } - const ou = this.organizationalUnits?.find(item => item.name === name); - if (ou?.ignore) { - return true; - } - return false; - } - - public getPath(name: string): string { - //get the parent path - const pathIndex = name.lastIndexOf('/'); - const ouPath = name.slice(0, pathIndex + 1).slice(0, -1); - if (ouPath === '') { - return '/'; - } - return '/' + ouPath; - } - - public getOuName(name: string): string { - const result = name.split('/').pop(); - if (result === undefined) { - return name; - } - return result; - } - - public getParentOuName(name: string): string { - const parentOuPath = this.getPath(name); - const result = parentOuPath.split('/').pop(); - if (result === undefined) { - return '/'; - } - return result; - } -} diff --git a/source/packages/@aws-accelerator/config/lib/replacements-config.ts b/source/packages/@aws-accelerator/config/lib/replacements-config.ts deleted file mode 100644 index 3c4cf4e..0000000 --- a/source/packages/@aws-accelerator/config/lib/replacements-config.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import AWS from 'aws-sdk'; -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -import * as Handlebars from 'handlebars'; -import { AccountsConfig } from './accounts-config'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -import * as t from './common'; -import * as i from './models/replacements-config'; - -const logger = createLogger(['replacements-config']); - -export abstract class ParameterReplacementConfig implements i.IParameterReplacement { - readonly key: string = ''; - readonly path: string = ''; -} - -export abstract class ParameterReplacementConfigV2 implements i.IParameterReplacementV2 { - readonly key: string = ''; - readonly path: string = ''; - readonly type: t.ParameterReplacementType = 'SSM'; - readonly value: string | string[] | undefined = undefined; -} - -export interface ReplacementsConfigProps { - readonly region?: string; -} - -export class ReplacementsConfig implements i.IReplacementsConfig { - /** - * Replacements configuration file name, this file must be present in accelerator config repository - */ - public static readonly FILENAME = 'replacements-config.yaml'; - /** - * The prefix that needs to be appended in the parameters used in policy files. - * - * For example, ${POLICY_PARAMETER_PREFIX}:ALLOWED_CORPORATE_CIDRS should be used in policy file - * if ALLOWED_CORPORATE_CIDRS is defined in replacement-config.yaml - */ - public static readonly POLICY_PARAMETER_PREFIX = 'ACCEL_LOOKUP::CUSTOM'; - - readonly globalReplacements: (ParameterReplacementConfig | ParameterReplacementConfigV2)[] = []; - - readonly accountsConfig: AccountsConfig | undefined = undefined; - - placeholders: { [key: string]: string | string[] } = {}; - validateOnly = false; - - /** - * - * @param props - * @param values - * @param configDir - * @param validateConfig - */ - constructor(values?: i.IReplacementsConfig, accountsConfig?: AccountsConfig, validateOnly = false) { - this.accountsConfig = accountsConfig; - this.validateOnly = validateOnly; - - if (values) { - Object.assign(this, values); - } - } - - /** - * Load from config file content - * @param dir - * @param validateConfig - * @returns - */ - static load(dir: string, accountsConfig: AccountsConfig, validateOnly = false): ReplacementsConfig { - if (!fs.existsSync(path.join(dir, ReplacementsConfig.FILENAME))) return new ReplacementsConfig(); - - const buffer = fs.readFileSync(path.join(dir, ReplacementsConfig.FILENAME), 'utf8'); - if (!yaml.load(buffer)) return new ReplacementsConfig(); - const values = t.parseReplacementsConfig(yaml.load(buffer)); - return new ReplacementsConfig(values, accountsConfig, validateOnly); - } - - /** - * Loads replacement values by utilizing the systems manager client - */ - public async loadReplacementValues(props: ReplacementsConfigProps, orgsEnabled: boolean): Promise { - const errors: string[] = []; - - if (!this.validateOnly && orgsEnabled) { - logger.info('Loading replacements config substitution values'); - const ssmClient = new AWS.SSM({ region: props.region }); - - for (const item of this.globalReplacements) { - if (item.path || (item as ParameterReplacementConfigV2).type === 'SSM') { - try { - logger.info(`Loading parameter at path ${item.path}`); - const t = await throttlingBackOff(() => ssmClient.getParameter({ Name: item.path! }).promise()); - const parameterValue = t.Parameter!.Value; - if (parameterValue === undefined) { - logger.error(`Invalid parameter value for ${item.path}`); - errors.push(`Invalid parameter value for ${item.path}`); - } else { - this.placeholders[item.key] = parameterValue; - } - } catch (e) { - logger.error(`Message [${e}] for path [${item.path}]`); - errors.push(`Message [${e}] for path [${item.path}]`); - } - } else if ((item as ParameterReplacementConfigV2).value) { - this.placeholders[item.key] = (item as ParameterReplacementConfigV2).value!; - } - } - - if (errors.length) { - throw new Error(`${ReplacementsConfig.FILENAME} has has ${errors.length} issues: ${errors.join(' ')}`); - } - } else { - for (const item of this.globalReplacements) { - logger.debug(`Loading replacement for validation purposes => ${item.key} - ${item.path} `); - this.placeholders[item.key] = item.key; - } - } - - if (this.accountsConfig) { - [...this.accountsConfig.mandatoryAccounts, ...this.accountsConfig.workloadAccounts].forEach(item => { - logger.debug(`Adding account ${item.name}`); - this.placeholders[item.name] = item.name; - }); - } - } - - public preProcessBuffer(initialBuffer: string): string { - if (!this.validateOnly && this.accountsConfig) { - // Register the 'account' helper function with Handlebars - // only if 'validateOnly' is falsy and 'accountsConfig' is truthy - Handlebars.registerHelper('account', accountName => { - logger.debug(`Handlebars looking up account id for ${accountName}`); - // Get the account ID by calling the 'getAccountId' method on 'accountsConfig' - // with the provided 'accountName' if 'accountName' is truthy - // Otherwise, 'accountId' will be falsy (undefined) - const accountId = accountName && this.accountsConfig?.getAccountId(accountName); - logger.debug( - `Handlebars ${ - accountId - ? `looking up account id for ${accountName}` - : 'account helper triggered by an undefined accountName' - }`, - ); - // If 'accountId' is falsy, the function will implicitly return 'undefined' - return accountId; - }); - } else { - Handlebars.registerHelper('account', accountName => { - logger.debug(`Validating received account name ${accountName}, responding with generic account Id`); - return '111122223333'; - }); - } - - Handlebars.registerHelper('helperMissing', function (context, options) { - const tokenName = options?.name ?? context?.name; - if (tokenName && tokenName !== 'account') { - logger.warn(`Ignoring replacement ${tokenName} because it is not present in replacements-config.yaml`); - return new Handlebars.SafeString(`{{${tokenName}}}`); - } - return; - }); - - // Replace instances of "{{resolve:" with "\{{resolve:" to ignore replacement behavior - const dynamicRefRegex = /"{{resolve:/g; - const escapedBuffer = initialBuffer.replace(dynamicRefRegex, '"\\{{resolve:'); - const template = Handlebars.compile(escapedBuffer); - const output = template(this.placeholders); - return output; - } -} diff --git a/source/packages/@aws-accelerator/config/lib/schemas/accounts-config.json b/source/packages/@aws-accelerator/config/lib/schemas/accounts-config.json deleted file mode 100644 index dd1fc05..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/accounts-config.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "$ref": "#/definitions/IAccountsConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AwsAccountId": { - "description": "AWS Account ID", - "maxLength": 12, - "minLength": 12, - "type": "string" - }, - "EmailAddress": { - "description": "An email address", - "maxLength": 64, - "minLength": 6, - "pattern": "['^\\S+@\\S+\\.\\S+$', '^\\w+$']", - "type": "string" - }, - "IAccountConfig": { - "additionalProperties": false, - "description": "Account configuration", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "The description is to used to provide more information about the account. This value is not used when creating accounts." - }, - "email": { - "$ref": "#/definitions/EmailAddress", - "description": "The email address of the owner to assign to the account. The email address must not already be associated with another AWS account. You must use a valid email address. The address must be a minimum of 6 and a maximum of 64 characters long. All characters must be 7-bit ASCII characters There must be one and only one @ symbol, which separates the local name from the domain name. The local name can’t contain any of the following characters: whitespace, ” ‘ ( ) < > [ ] : ; , | % & The local name can’t begin with a dot (.) The domain name can consist of only the characters [a-z],[A-Z],[0-9], hyphen (-), or dot (.) The domain name can’t begin or end with a hyphen (-) or dot (.) The domain name must contain at least one dot" - }, - "name": { - "$ref": "#/definitions/NonEmptyNoSpaceString", - "description": "The friendly name that is assigned to the account for reference within the Accelerator. The name will be used to reference this account in other configuration files and not to lookup the account in AWS.\n\nFor pre-existing accounts this does not need to match the AWS account name.\n\nWhen creating new accounts with the Accelerator, this name will be used as the AWS account name.\n\nThe name should not contain any spaces as this isn't supported by the Accelerator." - }, - "organizationalUnit": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name for the Organizational Unit that this account is a member of. This Organizational Unit must exist in the organization-config.yaml file." - }, - "warm": { - "description": "'Warm' the account by creating an EC2 instance that runs for 15 minutes Use for new accounts that will need to have ec2 instance provisioned as part of the solution The 'warming' will take place in the operations stack This property may be removed after the account has been provisioned", - "type": "boolean" - } - }, - "required": [ - "email", - "name" - ], - "type": "object" - }, - "IAccountIdConfig": { - "additionalProperties": false, - "properties": { - "accountId": { - "$ref": "#/definitions/AwsAccountId" - }, - "email": { - "$ref": "#/definitions/EmailAddress" - } - }, - "required": [ - "email", - "accountId" - ], - "type": "object" - }, - "IAccountsConfig": { - "additionalProperties": false, - "properties": { - "accountIds": { - "items": { - "$ref": "#/definitions/IAccountIdConfig" - }, - "type": "array" - }, - "mandatoryAccounts": { - "anyOf": [ - { - "items": { - "$ref": "#/definitions/IAccountConfig" - }, - "type": "array" - }, - { - "items": { - "$ref": "#/definitions/IGovCloudAccountConfig" - }, - "type": "array" - } - ] - }, - "workloadAccounts": { - "anyOf": [ - { - "items": { - "$ref": "#/definitions/IAccountConfig" - }, - "type": "array" - }, - { - "items": { - "$ref": "#/definitions/IGovCloudAccountConfig" - }, - "type": "array" - } - ] - } - }, - "required": [ - "mandatoryAccounts", - "workloadAccounts" - ], - "type": "object" - }, - "IGovCloudAccountConfig": { - "additionalProperties": false, - "description": "GovCloud Account configuration\nUsed instead of the account configuration in the commercial\npartition when creating GovCloud partition linked accounts.", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "The description is to used to provide more information about the account. This value is not used when creating accounts." - }, - "email": { - "$ref": "#/definitions/EmailAddress", - "description": "The email address of the owner to assign to the account. The email address must not already be associated with another AWS account. You must use a valid email address. The address must be a minimum of 6 and a maximum of 64 characters long. All characters must be 7-bit ASCII characters There must be one and only one @ symbol, which separates the local name from the domain name. The local name can’t contain any of the following characters: whitespace, ” ‘ ( ) < > [ ] : ; , | % & The local name can’t begin with a dot (.) The domain name can consist of only the characters [a-z],[A-Z],[0-9], hyphen (-), or dot (.) The domain name can’t begin or end with a hyphen (-) or dot (.) The domain name must contain at least one dot" - }, - "enableGovCloud": { - "description": "Indicates whether or not a GovCloud partition account should be created.", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyNoSpaceString", - "description": "The friendly name that is assigned to the account for reference within the Accelerator. The name will be used to reference this account in other configuration files and not to lookup the account in AWS.\n\nFor pre-existing accounts this does not need to match the AWS account name.\n\nWhen creating new accounts with the Accelerator, this name will be used as the AWS account name.\n\nThe name should not contain any spaces as this isn't supported by the Accelerator." - }, - "organizationalUnit": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name for the Organizational Unit that this account is a member of. This Organizational Unit must exist in the organization-config.yaml file." - } - }, - "required": [ - "email", - "name" - ], - "type": "object" - }, - "NonEmptyNoSpaceString": { - "description": "A string that contains no spaces", - "minLength": 1, - "pattern": "^[^\\s]*$", - "type": "string" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/customizations-config.json b/source/packages/@aws-accelerator/config/lib/schemas/customizations-config.json deleted file mode 100644 index f37264f..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/customizations-config.json +++ /dev/null @@ -1,1703 +0,0 @@ -{ - "$ref": "#/definitions/ICustomizationsConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AlbListenerProtocolEnum": { - "enum": [ - "HTTP", - "HTTPS" - ], - "type": "string" - }, - "AlbListenerTypeEnum": { - "enum": [ - "fixed-response", - "forward", - "redirect" - ], - "type": "string" - }, - "AlbRoutingHttpConfigMitigationModeEnum": { - "enum": [ - "monitor", - "defensive", - "strictest" - ], - "type": "string" - }, - "AlbSchemeEnum": { - "enum": [ - "internet-facing", - "internal" - ], - "type": "string" - }, - "AlpnPolicyEnum": { - "enum": [ - "HTTP1Only", - "HTTP2Only", - "HTTP2Optional", - "HTTP2Preferred", - "None" - ], - "type": "string" - }, - "AutoScalingHealthCheckTypeEnum": { - "enum": [ - "EC2", - "ELB" - ], - "type": "string" - }, - "IAlbAttributesConfig": { - "additionalProperties": false, - "description": "Application Load Balancer attributes config.", - "properties": { - "deletionProtection": { - "description": "Enable or disable deletion protection.", - "type": "boolean" - }, - "http2Enabled": { - "description": "Indicates whether HTTP/2 is enabled. The possible values are true and false. The default is true. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens.", - "type": "boolean" - }, - "idleTimeout": { - "description": "The idle timeout value, in seconds. The valid range is 1-4000 seconds. The default is 60 seconds.", - "type": "number" - }, - "routingHttpDesyncMitigationMode": { - "$ref": "#/definitions/AlbRoutingHttpConfigMitigationModeEnum", - "description": "Determines how the load balancer handles requests that might pose a security risk to your application. The possible values are `monitor` , `defensive` , and `strictest` . The default is `defensive`." - }, - "routingHttpDropInvalidHeader": { - "description": "Indicates whether HTTP headers with invalid header fields are removed by the load balancer ( true ) or routed to targets ( false ). The default is false.", - "type": "boolean" - }, - "routingHttpXAmznTlsCipherEnable": { - "description": "Indicates whether the two headers ( x-amzn-tls-version and x-amzn-tls-cipher-suite ), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. The x-amzn-tls-version header has information about the TLS protocol version negotiated with the client, and the x-amzn-tls-cipher-suite header has information about the cipher suite negotiated with the client. Both headers are in OpenSSL format. The possible values for the attribute are true and false . The default is false.", - "type": "boolean" - }, - "routingHttpXffClientPort": { - "description": "Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer. The possible values are true and false . The default is false.", - "type": "boolean" - }, - "routingHttpXffHeaderProcessingMode": { - "$ref": "#/definitions/RoutingHttpXffHeaderProcessingModeEnum", - "description": "Enables you to modify, preserve, or remove the X-Forwarded-For header in the HTTP request before the Application Load Balancer sends the request to the target. The possible values are append, preserve, and remove. The default is append." - }, - "wafFailOpen": { - "description": "Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. The possible values are true and false. The default is false.", - "type": "boolean" - } - }, - "type": "object" - }, - "IAlbListenerConfig": { - "additionalProperties": false, - "description": "Application Load Balancer listener config. Currently only action type of `forward`, `redirect` and `fixed-response` is allowed.", - "properties": { - "certificate": { - "$ref": "#/definitions/NonEmptyString", - "description": "Applies to HTTPS listeners. The default certificate for the listener. You must provide exactly one certificate arn or a certificate name which was created by LZA" - }, - "fixedResponseConfig": { - "$ref": "#/definitions/IAlbListenerFixedResponseConfig", - "description": "Information for creating an action that returns a custom HTTP response. Specify only when type is `fixed-response`." - }, - "forwardConfig": { - "$ref": "#/definitions/IAlbListenerForwardConfig", - "description": "Information for creating an action that distributes requests to targetGroup. Stickiness for targetGroup can be set here." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the application load balancer listener" - }, - "order": { - "description": "The order for the action. This value is required for rules with multiple actions. The action with the lowest value for order is performed first", - "type": "number" - }, - "port": { - "description": "Port of the application load balancer listener", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/AlbListenerProtocolEnum", - "description": "Protocol of the application load balancer listener. The supported protocols are HTTP and HTTPS" - }, - "redirectConfig": { - "$ref": "#/definitions/IAlbListenerRedirectConfig", - "description": "Information for creating a redirect action. Specify only when type is `redirect`." - }, - "sslPolicy": { - "$ref": "#/definitions/SslPolicyAlbEnum", - "description": "The security policy that defines which protocols and ciphers are supported." - }, - "targetGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "Target Group name to which traffic will be forwarded to. This name should be same as {@link ApplicationLoadBalancerTargetGroupConfig targetGroup } name." - }, - "type": { - "$ref": "#/definitions/AlbListenerTypeEnum", - "description": "Type of the application load balancer listener" - } - }, - "required": [ - "name", - "port", - "protocol", - "type", - "targetGroup" - ], - "type": "object" - }, - "IAlbListenerFixedResponseConfig": { - "additionalProperties": false, - "description": "Application load balancer listener fixed response config\nIt returns a custom HTTP response.\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `fixed-response`.", - "properties": { - "contentType": { - "$ref": "#/definitions/NonEmptyString", - "description": "The message to send back." - }, - "messageBody": { - "$ref": "#/definitions/NonEmptyString", - "description": "The HTTP response code (2XX, 4XX, or 5XX)." - }, - "statusCode": { - "$ref": "#/definitions/NonEmptyString", - "description": "The content type. Valid Values: text/plain | text/css | text/html | application/javascript | application/json" - } - }, - "required": [ - "statusCode" - ], - "type": "object" - }, - "IAlbListenerForwardConfig": { - "additionalProperties": false, - "description": "Application Load balancer listener forward config. Used to define forward action.\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `forward`.", - "properties": { - "targetGroupStickinessConfig": { - "$ref": "#/definitions/IAlbListenerTargetGroupStickinessConfig" - } - }, - "type": "object" - }, - "IAlbListenerRedirectConfig": { - "additionalProperties": false, - "description": "Application Load balancer listener redirect config. Used to define redirect action.\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `redirect`.", - "properties": { - "host": { - "$ref": "#/definitions/NonEmptyString" - }, - "path": { - "$ref": "#/definitions/NonEmptyString" - }, - "port": { - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/NonEmptyString" - }, - "query": { - "$ref": "#/definitions/NonEmptyString" - }, - "statusCode": { - "$ref": "#/definitions/NonEmptyString" - } - }, - "type": "object" - }, - "IAlbListenerTargetGroupStickinessConfig": { - "additionalProperties": false, - "description": "Application Load balancer listener forward config target group stickiness config\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `forward`.", - "properties": { - "durationSeconds": { - "description": "The time period, in seconds, during which requests from a client should be routed to the same target group. The range is 1-604800 seconds (7 days).", - "type": "number" - }, - "enabled": { - "description": "Indicates whether target group stickiness is enabled.", - "type": "boolean" - } - }, - "type": "object" - }, - "IAppConfigItem": { - "additionalProperties": false, - "description": "Application configuration.\nUsed to define two tier application configurations for the accelerator.", - "properties": { - "applicationLoadBalancer": { - "$ref": "#/definitions/IApplicationLoadBalancerConfig", - "description": "Application Load Balancer for the application" - }, - "autoscaling": { - "$ref": "#/definitions/IAutoScalingConfig", - "description": "AutoScalingGroup for the application" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "The location where the application will be deployed." - }, - "launchTemplate": { - "$ref": "#/definitions/ILaunchTemplateConfig", - "description": "Launch Template for the application" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the application. This should be unique per application." - }, - "networkLoadBalancer": { - "$ref": "#/definitions/INetworkLoadBalancerConfig", - "description": "Network Load Balancer for the application" - }, - "targetGroups": { - "description": "Target groups for the application", - "items": { - "$ref": "#/definitions/ITargetGroupItem" - }, - "type": "array" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "VPC where the application will be deployed. The value should be a reference to the vpc in the network config under `vpcs:`." - } - }, - "required": [ - "name", - "vpc", - "deploymentTargets" - ], - "type": "object" - }, - "IApplicationLoadBalancerConfig": { - "additionalProperties": false, - "description": "Used to define Application Load Balancer configurations for the accelerator.", - "properties": { - "attributes": { - "$ref": "#/definitions/IAlbAttributesConfig", - "description": "Attributes for Application Load Balancer." - }, - "listeners": { - "description": "Listeners for Application Load Balancer.", - "items": { - "$ref": "#/definitions/IAlbListenerConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the application load balancer" - }, - "scheme": { - "$ref": "#/definitions/AlbSchemeEnum", - "description": "Internal or internet facing scheme for Application Load Balancer." - }, - "securityGroups": { - "description": "Security Groups to attach to the Application Load Balancer.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "The location where the Application Load Balancer(s) will be deployed to.\n*" - }, - "subnets": { - "description": "Subnets to launch the Application Load Balancer in.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "name", - "subnets", - "securityGroups" - ], - "type": "object" - }, - "IAutoScalingConfig": { - "additionalProperties": false, - "description": "Autoscaling group configuration for the application.", - "properties": { - "desiredSize": { - "description": "The desired capacity is the initial capacity of the Auto Scaling group at the time of its creation and the capacity it attempts to maintain. It can scale beyond this capacity if you configure auto scaling. This number must be greater than or equal to the minimum size of the group and less than or equal to the maximum size of the group.", - "type": "number" - }, - "healthCheckGracePeriod": { - "description": "The amount of time, in seconds, that Amazon EC2 Auto Scaling waits before checking the health status of an EC2 instance that has come into service and marking it unhealthy due to a failed Elastic Load Balancing or custom health check. This is useful if your instances do not immediately pass these health checks after they enter the `InService` state. Defaults to 0 if unspecified.", - "type": "number" - }, - "healthCheckType": { - "$ref": "#/definitions/AutoScalingHealthCheckTypeEnum", - "description": "The service to use for the health checks. The valid values are EC2 (default) and ELB. If you configure an Auto Scaling group to use load balancer (ELB) health checks, it considers the instance unhealthy if it fails either the EC2 status checks or the load balancer health checks." - }, - "launchTemplate": { - "$ref": "#/definitions/NonEmptyString", - "description": "Information used to specify the launch template and version to use to launch instances." - }, - "maxInstanceLifetime": { - "description": "The maximum instance lifetime specifies the maximum amount of time (in seconds) that an instance can be in service before it is terminated and replaced. A common use case might be a requirement to replace your instances on a schedule because of internal security policies or external compliance controls. You must specify a value of at least 86,400 seconds (one day). To clear a previously set value, specify a new value of 0. This setting applies to all current and future instances in your Auto Scaling group", - "type": "number" - }, - "maxSize": { - "description": "The maximum size of the group.", - "type": "number" - }, - "minSize": { - "description": "The minimum size of the group.", - "type": "number" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the Auto Scaling group. This name must be unique per Region per account. The name can contain any ASCII character 33 to 126 including most punctuation characters, digits, and upper and lowercased letters.\n*Note* You cannot use a colon (:) in the name." - }, - "subnets": { - "description": "List of subnet names for a virtual private cloud (VPC) where instances in the Auto Scaling group can be created. These subnets should be created under the VPC in network-config.yaml.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "targetGroups": { - "description": "Target group name array to associate with the Auto Scaling group. These names are from the {@link TargetGroupItemConfig target group } set in the application. Instances are registered as targets with the target groups. The target groups receive incoming traffic and route requests to one or more registered targets.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "name", - "minSize", - "maxSize", - "desiredSize", - "launchTemplate", - "subnets" - ], - "type": "object" - }, - "IBlockDeviceMappingItem": { - "additionalProperties": false, - "description": "The parameters for a block device mapping in launch template.", - "properties": { - "deviceName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The device name (for example, /dev/sdh or xvdh)." - }, - "ebs": { - "$ref": "#/definitions/IEbsItem", - "description": "Parameters used to automatically set up EBS volumes when the instance is launched." - } - }, - "required": [ - "deviceName" - ], - "type": "object" - }, - "ICfnParameter": { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "name", - "value" - ], - "type": "object" - }, - "ICloudFormationStack": { - "additionalProperties": false, - "description": "Defines a custom CloudFormation Stack to be deployed to the environment.", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "CloudFormation Stack deployment targets" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "The description is to used to provide more information about the stack." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name that will be used as a base for the created CloudFormation Stack Name. The name should not contain any spaces as this isn't supported by the Accelerator." - }, - "parameters": { - "description": "The parameters to pass to the stack.", - "items": { - "$ref": "#/definitions/ICfnParameter" - }, - "type": "array" - }, - "regions": { - "description": "A list of AWS regions to deploy the stack to.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "runOrder": { - "description": "The order to deploy the stack relative to the other stacks. Must be a positive integer. To deploy stacks in parallel, set runOrder of each stack to 1.", - "type": "number" - }, - "template": { - "$ref": "#/definitions/NonEmptyString", - "description": "The file path to the template file defining the stack." - }, - "terminationProtection": { - "description": "This determines whether to enable termination protection for the stack.", - "type": "boolean" - } - }, - "required": [ - "deploymentTargets", - "name", - "regions", - "runOrder", - "template", - "terminationProtection" - ], - "type": "object" - }, - "ICloudFormationStackSet": { - "additionalProperties": false, - "description": "Defines a custom CloudFormation StackSet to be deployed to the environment.", - "properties": { - "capabilities": { - "description": "The CloudFormation capabilities enabled to deploy the stackset.", - "items": { - "enum": [ - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM", - "CAPABILITY_AUTO_EXPAND" - ], - "type": "string" - }, - "type": "array" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "CloudFormation StackSet deployment targets" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "The description is to used to provide more information about the stackset." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name that will be used as a base for the created CloudFormation StackSet Name. The name should not contain any spaces as this isn't supported by the Accelerator." - }, - "parameters": { - "description": "The parameters to be passed to the stackset.", - "items": { - "$ref": "#/definitions/ICfnParameter" - }, - "type": "array" - }, - "regions": { - "description": "A list of regions to deploy the stackset.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "template": { - "$ref": "#/definitions/NonEmptyString", - "description": "The file path to the template file used for deployment." - } - }, - "required": [ - "deploymentTargets", - "name", - "regions", - "template" - ], - "type": "object" - }, - "ICustomizationConfig": { - "additionalProperties": false, - "description": "Defines CloudFormation Stacks and StackSets to be deployed to the environment.\nThis feature supports the deployment of customer-provided CloudFormation templates to AWS\naccounts and/or organizational units. These deployments can leverage independent CloudFormation stacks\nor CloudFormation StackSets depending on the customer's deployment preference.", - "properties": { - "cloudFormationStackSets": { - "items": { - "$ref": "#/definitions/ICloudFormationStackSet" - }, - "type": "array" - }, - "cloudFormationStacks": { - "items": { - "$ref": "#/definitions/ICloudFormationStack" - }, - "type": "array" - }, - "serviceCatalogPortfolios": { - "items": { - "$ref": "#/definitions/IPortfolioConfig" - }, - "type": "array" - } - }, - "type": "object" - }, - "ICustomizationsConfig": { - "additionalProperties": false, - "description": "Defines custom CloudFormation and external web and application tier resources. We recommend creating resources\nwith native LZA features where possible.", - "properties": { - "applications": { - "items": { - "$ref": "#/definitions/IAppConfigItem" - }, - "type": "array" - }, - "customizations": { - "$ref": "#/definitions/ICustomizationConfig" - }, - "firewalls": { - "$ref": "#/definitions/IEc2FirewallConfig" - } - }, - "type": "object" - }, - "IDeploymentTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedAccounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "IEbsItem": { - "additionalProperties": false, - "description": "The parameters for a block device for an EBS volume.", - "properties": { - "deleteOnTermination": { - "description": "Indicates whether the EBS volume is deleted on instance termination.", - "type": "boolean" - }, - "encrypted": { - "description": "Indicates whether the EBS volume is encrypted. Encrypted volumes can only be attached to instances that support Amazon EBS encryption. If you are creating a volume from a snapshot, you can't specify an encryption value. If encrypted is `true` and kmsKeyId is not provided, then accelerator checks for {@link EbsDefaultVolumeEncryptionConfig default ebs encryption } in the config.", - "type": "boolean" - }, - "iops": { - "description": "The number of I/O operations per second (IOPS). For gp3, io1, and io2 volumes, this represents the number of IOPS that are provisioned for the volume. For gp2 volumes, this represents the baseline performance of the volume and the rate at which the volume accumulates I/O credits for bursting. This parameter is supported for io1, io2, and gp3 volumes only. This parameter is not supported for gp2, st1, sc1, or standard volumes.", - "type": "number" - }, - "kmsKeyId": { - "$ref": "#/definitions/NonEmptyString", - "description": "The ARN of the symmetric AWS Key Management Service (AWS KMS) CMK used for encryption." - }, - "snapshotId": { - "$ref": "#/definitions/NonEmptyString", - "description": "The ID of the snapshot." - }, - "throughput": { - "description": "The throughput to provision for a gp3 volume, with a maximum of 1,000 MiB/s. Valid Range: Minimum value of 125. Maximum value of 1000.", - "type": "number" - }, - "volumeSize": { - "description": "The size of the volume, in GiBs. You must specify either a snapshot ID or a volume size. The following are the supported volumes sizes for each volume type:\n- gp2 and gp3: 1-16,384\n- io1 and io2: 4-16,384\n- st1 and sc1: 125-16,384\n- standard: 1-1,024", - "type": "number" - }, - "volumeType": { - "$ref": "#/definitions/NonEmptyString", - "description": "The volume type. Valid Values: `standard | io1 | io2 | gp2 | sc1 | st1 | gp3`" - } - }, - "type": "object" - }, - "IEc2FirewallAutoScalingGroupConfig": { - "additionalProperties": false, - "description": "EC2 firewall autoscaling group configuration.\nUsed to define EC2-based firewall instances to be deployed in an autoscaling group.\n\n```\n- name: accelerator-firewall-asg\n autoscaling:\n name: firewall-asg\n maxSize: 4\n minSize: 1\n desiredSize: 2\n launchTemplate: firewall-lt\n healthCheckGracePeriod: 300\n healthCheckType: ELB\n targetGroups:\n - firewall-gwlb-tg\n subnets:\n - firewall-subnet-a\n - firewall-subnet-b\n maxInstanceLifetime: 86400\n launchTemplate:\n name: firewall-lt\n blockDeviceMappings:\n - deviceName: /dev/xvda\n ebs:\n deleteOnTermination: true\n encrypted: true\n volumeSize: 20\n enforceImdsv2: true\n iamInstanceProfile: firewall-profile\n imageId: ami-123xyz\n instanceType: c6i.xlarge\n networkInterfaces:\n - deleteOnTermination: true\n description: Primary interface\n deviceIndex: 0\n groups:\n - firewall-data-sg\n - deleteOnTermination: true\n description: Management interface\n deviceIndex: 1\n groups:\n - firewall-mgmt-sg\n userData: path/to/userdata.txt\n vpc: Network-Inspection\n tags: []\n```", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The logical name of the account to deploy the firewall autoscaling group to" - }, - "autoscaling": { - "$ref": "#/definitions/IAutoScalingConfig", - "description": "An AutoScaling Group configuration" - }, - "configDir": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Specify a relative S3 directory path to pull a firewall configuration directory.\n\nEither configFile or configDir can be set but not both.\n\nFor example, if your S3 folder path is `s3://path/to/config`, specify `path/to/config` for this property.\n\n**NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, update the name of the configuration directory." - }, - "configFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Specify a relative S3 object path to pull a firewall configuration file from.\n\nFor example, if your S3 object path is `s3://path/to/config.txt`, specify `path/to/config.txt` for this property.\n\n**NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, update the name of the configuration file." - }, - "launchTemplate": { - "$ref": "#/definitions/ILaunchTemplateConfig", - "description": "The launch template for the firewall instance" - }, - "licenseFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Specify a relative S3 object path to pull a firewall license file from.\n\nFor example, if your S3 object path is `s3://path/to/license.lic`, specify `path/to/license.lic` for this property.\n\n**NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, update the name of the license file." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the firewall instance" - }, - "staticReplacements": { - "description": "(OPTIONAL) Static firewall configuration replacements definition.", - "items": { - "$ref": "#/definitions/IFirewallStaticReplacementsConfig" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tags", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC to deploy the firewall instance to" - } - }, - "required": [ - "name", - "autoscaling", - "launchTemplate", - "vpc" - ], - "type": "object" - }, - "IEc2FirewallConfig": { - "additionalProperties": false, - "description": "EC2 firewall configuration.\nUsed to define EC2-based firewall and management appliances", - "properties": { - "autoscalingGroups": { - "description": "Define EC2-based firewall instances in autoscaling groups", - "items": { - "$ref": "#/definitions/IEc2FirewallAutoScalingGroupConfig" - }, - "type": "array" - }, - "instances": { - "description": "Define EC2-based firewall standalone instances", - "items": { - "$ref": "#/definitions/IEc2FirewallInstanceConfig" - }, - "type": "array" - }, - "managerInstances": { - "description": "Define EC2-based firewall management instances", - "items": { - "$ref": "#/definitions/IEc2FirewallInstanceConfig" - }, - "type": "array" - }, - "targetGroups": { - "description": "Define target groups for EC2-based firewalls", - "items": { - "$ref": "#/definitions/ITargetGroupItem" - }, - "type": "array" - } - }, - "type": "object" - }, - "IEc2FirewallInstanceConfig": { - "additionalProperties": false, - "description": "EC2 firewall instance configuration.\nUse to define an array of standalone firewall instances", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The logical name of the account to deploy the firewall instance to" - }, - "configDir": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Specify a relative S3 directory path to pull a firewall configuration directory.\n\nEither configFile or configDir can be set but not both.\n\nFor example, if your S3 folder path is `s3://path/to/config`, specify `path/to/config` for this property.\n\n**NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, update the name of the configuration directory." - }, - "configFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Specify a relative S3 object path to pull a firewall configuration file from.\n\nFor example, if your S3 object path is `s3://path/to/config.txt`, specify `path/to/config.txt` for this property.\n\n**NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, update the name of the configuration file." - }, - "detailedMonitoring": { - "description": "(OPTIONAL) Specify true to enable detailed monitoring. Otherwise, basic monitoring is enabled.", - "type": "boolean" - }, - "launchTemplate": { - "$ref": "#/definitions/ILaunchTemplateConfig", - "description": "The launch template for the firewall instance" - }, - "licenseFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Specify a relative S3 object path to pull a firewall license file from.\n\nFor example, if your S3 object path is `s3://path/to/license.lic`, specify `path/to/license.lic` for this property.\n\n**NOTE:** The custom resource backing this feature does not force update on every core pipeline run. To update the resource, update the name of the license file." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the firewall instance" - }, - "staticReplacements": { - "description": "(OPTIONAL) Static firewall configuration replacements definition.", - "items": { - "$ref": "#/definitions/IFirewallStaticReplacementsConfig" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tags", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "terminationProtection": { - "description": "(OPTIONAL) If you set this parameter to true , you can't terminate the instance using the Amazon EC2 console, CLI, or API.\n\nMore information: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingDisableAPITermination", - "type": "boolean" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC to deploy the firewall instance to" - } - }, - "required": [ - "name", - "launchTemplate", - "vpc" - ], - "type": "object" - }, - "IFirewallStaticReplacementsConfig": { - "additionalProperties": false, - "description": "Firewall Static Replacements Config", - "properties": { - "key": { - "$ref": "#/definitions/NonEmptyString", - "description": "The key name for the static replacement" - }, - "value": { - "$ref": "#/definitions/NonEmptyString", - "description": "The value of the static replacement" - } - }, - "required": [ - "key", - "value" - ], - "type": "object" - }, - "ILaunchTemplateConfig": { - "additionalProperties": false, - "description": "Configure a launch template for the application.", - "properties": { - "blockDeviceMappings": { - "items": { - "$ref": "#/definitions/IBlockDeviceMappingItem" - }, - "type": "array" - }, - "enforceImdsv2": { - "description": "By default, {@link https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html IMDSv2 } is enabled. Disable it by setting this to false.", - "type": "boolean" - }, - "iamInstanceProfile": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the instance profile created by accelerator in iam-config.yaml under roleSets" - }, - "imageId": { - "$ref": "#/definitions/NonEmptyString", - "description": "Valid AMI ID or a reference to ssm parameter store to get AMI ID. If ssm parameter is referenced it should follow the pattern ${ACCEL_LOOKUP::ImageId:/path/to/ssm/parameter/for/ami}\n\nFor example to get the latest x86_64 amazon linux 2 ami, the value would be `${ACCEL_LOOKUP::ImageId:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}`" - }, - "instanceType": { - "$ref": "#/definitions/NonEmptyString", - "description": "Valid instance type which can be launched in the target account and region." - }, - "keyPair": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the key pair. LZA does not create keypair. This should exist in the account/region or else deployment will fail." - }, - "name": { - "$ref": "#/definitions/NonEmptyString" - }, - "networkInterfaces": { - "description": "One or more network interfaces. If you specify a network interface, you must specify any security groups and subnets as part of the network interface.", - "items": { - "$ref": "#/definitions/INetworkInterfaceItem" - }, - "type": "array" - }, - "securityGroups": { - "description": "One or more security group names. These should be created under the VPC in network-config.yaml", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "userData": { - "$ref": "#/definitions/NonEmptyString", - "description": "Path to user data. The path is relative to the config repository and the content should be in regular text. It is encoded in base64 before passing in to Launch Template" - } - }, - "required": [ - "name", - "imageId", - "instanceType" - ], - "type": "object" - }, - "INetworkInterfaceItem": { - "additionalProperties": false, - "description": "The parameters for a network interface.", - "properties": { - "associateCarrierIpAddress": { - "description": "Associates a Carrier IP address with eth0 for a new network interface. Use this option when you launch an instance in a Wavelength Zone and want to associate a Carrier IP address with the network interface.", - "type": "boolean" - }, - "associateElasticIp": { - "description": "Associate an elastic IP with the interface", - "type": "boolean" - }, - "associatePublicIpAddress": { - "description": "Associates a public IPv4 address with eth0 for a new network interface.", - "type": "boolean" - }, - "deleteOnTermination": { - "description": "Indicates whether the network interface is deleted when the instance is terminated.", - "type": "boolean" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "A description for the network interface." - }, - "deviceIndex": { - "description": "The device index for the network interface attachment.", - "type": "number" - }, - "groups": { - "description": "Security group names to associate with this network interface.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "interfaceType": { - "$ref": "#/definitions/NonEmptyString", - "description": "The type of network interface. To create an Elastic Fabric Adapter (EFA), specify efa. If you are not creating an EFA, specify interface or omit this parameter. Valid values: `interface | efa`" - }, - "networkCardIndex": { - "description": "The index of the network card. Some instance types support multiple network cards. The primary network interface must be assigned to network card index 0. The default is network card index 0.", - "type": "number" - }, - "networkInterfaceId": { - "$ref": "#/definitions/NonEmptyString", - "description": "The ID of the network interface." - }, - "privateIpAddress": { - "$ref": "#/definitions/NonEmptyString", - "description": "The primary private IPv4 address of the network interface." - }, - "privateIpAddresses": { - "description": "One or more private IPv4 addresses.", - "items": { - "$ref": "#/definitions/IPrivateIpAddressItem" - }, - "type": "array" - }, - "secondaryPrivateIpAddressCount": { - "description": "The number of secondary private IPv4 addresses to assign to a network interface.", - "type": "number" - }, - "sourceDestCheck": { - "description": "If the value is true , source/destination checks are enabled; otherwise, they are disabled. The default value is true. You must disable source/destination checks if the instance runs services such as network address translation, routing, or firewalls.", - "type": "boolean" - }, - "subnetId": { - "$ref": "#/definitions/NonEmptyString", - "description": "Valid subnet name from network-config.yaml under the same vpc" - } - }, - "type": "object" - }, - "INetworkLoadBalancerConfig": { - "additionalProperties": false, - "description": "Network Load Balancer configuration.", - "properties": { - "crossZoneLoadBalancing": { - "description": "Cross Zone load balancing for Network Load Balancer.", - "type": "boolean" - }, - "deletionProtection": { - "description": "Deletion protection for Network Load Balancer.", - "type": "boolean" - }, - "listeners": { - "description": "Listeners for Network Load Balancer.", - "items": { - "$ref": "#/definitions/INlbListenerConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name for Network Load Balancer." - }, - "scheme": { - "$ref": "#/definitions/LoadBalancerSchemeEnum", - "description": "Load Balancer scheme. If undefined, the default of {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateLoadBalancer.html ELBv2 CreateLoadBalancer API } is used." - }, - "subnets": { - "description": "Subnets to launch the Network Load Balancer in.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "subnets", - "name" - ], - "type": "object" - }, - "INlbListenerConfig": { - "additionalProperties": false, - "description": "Application Load Balancer listener config. Currently only action type of `forward`, `redirect` and `fixed-response` is allowed.", - "properties": { - "alpnPolicy": { - "$ref": "#/definitions/AlpnPolicyEnum", - "description": "Application-Layer Protocol Negotiation (ALPN) policy} for TLS encrypted traffic" - }, - "certificate": { - "$ref": "#/definitions/NonEmptyString", - "description": "ACM ARN of the certificate to be associated with the listener." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name for Listener." - }, - "port": { - "description": "Port where the traffic is directed to.", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/NlbProtocolEnum", - "description": "Protocol used for the traffic. The supported protocols are TCP, TLS, UDP, or TCP_UDP." - }, - "sslPolicy": { - "$ref": "#/definitions/SslPolicyNlbEnum", - "description": "SSL policy for TLS encrypted traffic" - }, - "targetGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "Target Group to direct the traffic to." - } - }, - "required": [ - "name", - "targetGroup" - ], - "type": "object" - }, - "INlbTargetType": { - "additionalProperties": false, - "description": "The codes to use when checking for a successful response from a target. If the protocol version is gRPC, these are gRPC codes. Otherwise, these are HTTP codes.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly Account Name where the NLB is deployed" - }, - "nlbName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly name of the NLB" - }, - "region": { - "$ref": "#/definitions/NonEmptyString", - "description": "Region where the NLB is deployed" - } - }, - "required": [ - "account", - "region", - "nlbName" - ], - "type": "object" - }, - "IPortfolioAssociatoinConfig": { - "additionalProperties": false, - "description": "Portfolio Associations configuration", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Indicates the name of the principal to associate the portfolio with." - }, - "propagateAssociation": { - "description": "Indicates whether the principal association should be created in accounts the portfolio is shared with. Verify the IAM principal exists in all accounts the portfolio is shared with before enabling.", - "type": "boolean" - }, - "type": { - "$ref": "#/definitions/PortfolioAssociationType", - "description": "Indicates the type of portfolio association, valid values are: Group, User, and Role." - } - }, - "required": [ - "type", - "name" - ], - "type": "object" - }, - "IPortfolioConfig": { - "additionalProperties": false, - "description": "Service Catalog Portfolios configuration", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the account to deploy the portfolio." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the portfolio" - }, - "portfolioAssociations": { - "description": "Configuration of portfolio associations to give access to IAM principals.", - "items": { - "$ref": "#/definitions/IPortfolioAssociatoinConfig" - }, - "type": "array" - }, - "products": { - "description": "Product Configuration", - "items": { - "$ref": "#/definitions/IProductConfig" - }, - "type": "array" - }, - "provider": { - "$ref": "#/definitions/NonEmptyString", - "description": "The provider of the portfolio" - }, - "regions": { - "description": "The region names to deploy the portfolio.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "shareTagOptions": { - "description": "Whether or not to share TagOptions with other account(s)/OU(s)", - "type": "boolean" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "Portfolio share target. Sharing portfolios to Organizational Units is only supported for portfolios in the Management account." - }, - "tagOptions": { - "description": "Portfolio TagOptions configuration", - "items": { - "$ref": "#/definitions/ITagOptionsConfig" - }, - "type": "array" - } - }, - "required": [ - "name", - "account", - "regions", - "provider" - ], - "type": "object" - }, - "IPrivateIpAddressItem": { - "additionalProperties": false, - "description": "Configure a secondary private IPv4 address for a network interface.", - "properties": { - "primary": { - "description": "Indicates whether the private IPv4 address is the primary private IPv4 address. Only one IPv4 address can be designated as primary.", - "type": "boolean" - }, - "privateIpAddress": { - "$ref": "#/definitions/NonEmptyString", - "description": "The private IPv4 address." - } - }, - "type": "object" - }, - "IProductConfig": { - "additionalProperties": false, - "description": "Service Catalog Products configuration", - "properties": { - "constraints": { - "$ref": "#/definitions/IProductConstraintConfig", - "description": "Product Constraint configuration" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "Product description" - }, - "distributor": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the product's publisher." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the product" - }, - "owner": { - "$ref": "#/definitions/NonEmptyString", - "description": "The owner of the product" - }, - "support": { - "$ref": "#/definitions/IProductSupportConfig", - "description": "Product support details." - }, - "tagOptions": { - "description": "Product TagOptions configuration", - "items": { - "$ref": "#/definitions/ITagOptionsConfig" - }, - "type": "array" - }, - "versions": { - "description": "Product version configuration", - "items": { - "$ref": "#/definitions/IProductVersionConfig" - }, - "type": "array" - } - }, - "required": [ - "name", - "owner", - "versions" - ], - "type": "object" - }, - "IProductConstraintConfig": { - "additionalProperties": false, - "description": "Service Catalog Product Constraint configuration. For more information see https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints.html", - "properties": { - "launch": { - "$ref": "#/definitions/IProductLaunchConstraintConfig", - "description": "Launch constraint role name and type, supports LocalRole or Role." - }, - "notifications": { - "description": "A list of SNS topic names to stream product notifications to", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tagUpdate": { - "description": "Determines if Service Catalog Tag Update constraint is enabled", - "type": "boolean" - } - }, - "type": "object" - }, - "IProductLaunchConstraintConfig": { - "additionalProperties": false, - "description": "Service Catalog Product Constraint configuration. For more information see https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints.html", - "properties": { - "role": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the IAM Role." - }, - "type": { - "$ref": "#/definitions/ProductLaunchConstraintType", - "description": "The type of launch constraint, either Role or LocalRole. For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-launchroleconstraint.html" - } - }, - "required": [ - "type", - "role" - ], - "type": "object" - }, - "IProductSupportConfig": { - "additionalProperties": false, - "description": "Product Support configuration", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "Support description of how users should use email contact and support link." - }, - "email": { - "$ref": "#/definitions/NonEmptyString", - "description": "The email address to report issues with the product" - }, - "url": { - "$ref": "#/definitions/NonEmptyString", - "description": "The url to the site where users can find support information or file tickets." - } - }, - "type": "object" - }, - "IProductVersionConfig": { - "additionalProperties": false, - "description": "Product Versions configuration", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "The version description" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the version of the product" - }, - "template": { - "$ref": "#/definitions/NonEmptyString", - "description": "The product template." - } - }, - "required": [ - "name", - "template" - ], - "type": "object" - }, - "IShareTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "ITag": { - "additionalProperties": false, - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "key", - "value" - ], - "type": "object" - }, - "ITagOptionsConfig": { - "additionalProperties": false, - "description": "Service Catalog TagOptions configuration.", - "properties": { - "key": { - "$ref": "#/definitions/NonEmptyString", - "description": "The tag key" - }, - "values": { - "description": "An array of values that can be used for the tag key", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "key", - "values" - ], - "type": "object" - }, - "ITargetGroupAttributeTypes": { - "additionalProperties": false, - "description": "Set attributes for target group.", - "properties": { - "algorithm": { - "$ref": "#/definitions/TargetGroupAttributeAlgorithm", - "description": "The load balancing algorithm determines how the load balancer selects targets when routing requests. The value is round_robin or least_outstanding_requests. The default is round_robin. The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address." - }, - "appCookieDuration": { - "description": "The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the application-based cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address.", - "type": "number" - }, - "appCookieName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Indicates the name of the application-based cookie. Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP, and AWSALBTG; they're reserved for use by the load balancer. The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address." - }, - "connectionTermination": { - "description": "Indicates whether the load balancer terminates connections at the end of the deregistration timeout. The value is true or false. The default is false. The following attribute is supported only by Network Load Balancers.", - "type": "boolean" - }, - "deregistrationDelay": { - "description": "The amount of time, in seconds, for Elastic Load Balancing to wait before changing the state of a deregistering target from draining to unused. The range is 0-3600 seconds. The default value is 300 seconds.", - "type": "number" - }, - "lbCookieDuration": { - "description": "The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address.", - "type": "number" - }, - "preserveClientIp": { - "description": "Indicates whether client IP preservation is enabled. The value is true or false. The default is disabled if the target group type is IP address and the target group protocol is TCP or TLS. Otherwise, the default is enabled. Client IP preservation cannot be disabled for UDP and TCP_UDP target groups. The following attribute is supported only by Network Load Balancers.", - "type": "boolean" - }, - "proxyProtocolV2": { - "description": "Indicates whether Proxy Protocol version 2 is enabled. The value is true or false. The default is false. The following attribute is supported only by Network Load Balancers.", - "type": "boolean" - }, - "slowStart": { - "description": "The time period, in seconds, during which a newly registered target receives an increasing share of the traffic to the target group. After this time period ends, the target receives its full share of traffic. The range is 30-900 seconds (15 minutes). The default is 0 seconds (disabled). The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address.", - "type": "number" - }, - "stickiness": { - "description": "Indicates whether target stickiness is enabled. The value is true or false. The default is false.", - "type": "boolean" - }, - "stickinessType": { - "$ref": "#/definitions/TargetGroupAttributeStickinessType", - "description": "Indicates the type of stickiness. The possible values are: - lb_cookie and app_cookie for Application Load Balancers. - source_ip for Network Load Balancers. - source_ip_dest_ip and source_ip_dest_ip_proto for Gateway Load Balancers" - }, - "targetFailover": { - "$ref": "#/definitions/TargetGroupTargetFailoverType", - "description": "Indicates how the Gateway Load Balancer handles existing flows when a target is deregistered or becomes unhealthy. The possible values are rebalance and no_rebalance. The default is no_rebalance" - } - }, - "type": "object" - }, - "ITargetGroupHealthCheckType": { - "additionalProperties": false, - "description": "Configure health check for target group.", - "properties": { - "interval": { - "description": "The approximate amount of time, in seconds, between health checks of an individual target. The range is 5-300. If the target group protocol is TCP, TLS, UDP, TCP_UDP, HTTP or HTTPS, the default is 30 seconds. If the target group protocol is GENEVE, the default is 10 seconds.", - "type": "number" - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "[HTTP/HTTPS health checks] The destination for health checks on the targets. [HTTP1 or HTTP2 protocol version] The ping path. The default is /. [GRPC protocol version] The path of a custom health check method with the format /package.service/method. The default is /AWS.ALB/healthcheck." - }, - "port": { - "description": "The port the load balancer uses when performing health checks on targets. If the protocol is HTTP, HTTPS, TCP, TLS, UDP, or TCP_UDP, the default is `traffic-port`, which is the port on which each target receives traffic from the load balancer. If the protocol is GENEVE, the default is port 80.", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/TargetGroupHealthCheckProtocolType", - "description": "The protocol the load balancer uses when performing health checks on targets. For Application Load Balancers, the default is HTTP. For Network Load Balancers and Gateway Load Balancers, the default is TCP. The TCP protocol is not supported for health checks if the protocol of the target group is HTTP or HTTPS. GENEVE, TLS, UDP, and TCP_UDP protocols are not supported for health checks." - }, - "timeout": { - "description": "The amount of time, in seconds, during which no response from a target means a failed health check. The range is 2–120 seconds. For target groups with a protocol of HTTP, the default is 6 seconds. For target groups with a protocol of TCP, TLS or HTTPS, the default is 10 seconds. For target groups with a protocol of GENEVE, the default is 5 seconds.", - "type": "number" - } - }, - "type": "object" - }, - "ITargetGroupItem": { - "additionalProperties": false, - "description": "Target Group Configuration", - "properties": { - "attributes": { - "$ref": "#/definitions/ITargetGroupAttributeTypes", - "description": "Target Group Attributes." - }, - "healthCheck": { - "$ref": "#/definitions/ITargetGroupHealthCheckType", - "description": "Target Group HealthCheck." - }, - "matcher": { - "$ref": "#/definitions/ITargetGroupMatcherType", - "description": "The HTTP or gRPC codes to use when checking for a successful response from a target. For target groups with a protocol of TCP, TCP_UDP, UDP or TLS the range is 200-599. For target groups with a protocol of HTTP or HTTPS, the range is 200-499." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the target group. This value is used in {@link ApplicationLoadBalancerListenerConfig Application Load Balancer listeners } , {@link NetworkLoadBalancerListenerConfig Network Load Balancer listeners } , and {@link AutoScalingConfig Autoscaling config } ." - }, - "port": { - "description": "The port on which the targets receive traffic.", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/TargetGroupProtocolType", - "description": "Target group protocol version. Should be one of HTTP, HTTPS, GENEVE, TCP, UDP, TCP_UDP or TLS The protocol to use for routing traffic to the targets. For Application Load Balancers, the supported protocols are HTTP and HTTPS. For Network Load Balancers, the supported protocols are TCP, TLS, UDP, or TCP_UDP. A TCP_UDP listener must be associated with a TCP_UDP target group. For Gateway Load Balancers, the supported protocol is GENEVE." - }, - "protocolVersion": { - "$ref": "#/definitions/TargetGroupProtocolVersionType", - "description": "The protocol version. Should be one of 'GRPC', 'HTTP1', 'HTTP2'. Specify GRPC to send requests to targets using gRPC. Specify HTTP2 to send requests to targets using HTTP/2. The default is HTTP1, which sends requests to targets using HTTP/1.1." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "The accounts/OUs location where the Target Group will be deployed to." - }, - "targets": { - "description": "Target group targets. These targets should be the friendly names assigned to firewall instances.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "$ref": "#/definitions/INlbTargetType" - } - ] - }, - "type": "array" - }, - "threshold": { - "$ref": "#/definitions/ITargetGroupThresholdType", - "description": "Target Group Threshold." - }, - "type": { - "$ref": "#/definitions/TargetGroupType", - "description": "The type of target that you must specify when registering targets with this target group. You can't specify targets for a target group using more than one target type.\n- `instance` - Register targets by instance ID. This is the default value.\n- `ip` - Register targets by IP address. You can specify IP addresses from the subnets of the virtual private cloud (VPC) for the target group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). You can't specify publicly routable IP addresses. `alb` - Register a single Application Load Balancer as a target." - } - }, - "required": [ - "name", - "port", - "protocol", - "type" - ], - "type": "object" - }, - "ITargetGroupMatcherType": { - "additionalProperties": false, - "description": "Add the ability to target an NLB created by the Landing Zone Accelerator", - "properties": { - "grpcCode": { - "$ref": "#/definitions/NonEmptyString", - "description": "You can specify values between 0 and 99. You can specify multiple values (for example, \"0,1\") or a range of values (for example, \"0-5\"). The default value is 12." - }, - "httpCode": { - "$ref": "#/definitions/NonEmptyString", - "description": "For Application Load Balancers, you can specify values between 200 and 499, with the default value being 200. You can specify multiple values (for example, \"200,202\") or a range of values (for example, \"200-299\"). For Network Load Balancers, you can specify values between 200 and 599, with the default value being 200-399. You can specify multiple values (for example, \"200,202\") or a range of values (for example, \"200-299\"). Note that when using shorthand syntax, some values such as commas need to be escaped." - } - }, - "type": "object" - }, - "ITargetGroupThresholdType": { - "additionalProperties": false, - "description": "Configure health check threshold for target group.", - "properties": { - "healthy": { - "description": "The number of consecutive health check successes required before considering a target healthy. The range is 2-10. If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP or HTTPS, the default is 5. For target groups with a protocol of GENEVE, the default is 3.", - "type": "number" - }, - "unhealthy": { - "description": "The number of consecutive health check failures required before considering a target unhealthy. The range is 2-10. If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP or HTTPS, the default is 2. For target groups with a protocol of GENEVE, the default is 3.", - "type": "number" - } - }, - "type": "object" - }, - "LoadBalancerSchemeEnum": { - "enum": [ - "internet-facing", - "internal" - ], - "type": "string" - }, - "NlbProtocolEnum": { - "enum": [ - "TCP", - "UDP", - "TLS", - "TCP_UDP" - ], - "type": "string" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - }, - "PortfolioAssociationType": { - "enum": [ - "User", - "Group", - "Role", - "PermissionSet" - ], - "type": "string" - }, - "ProductLaunchConstraintType": { - "enum": [ - "Role", - "LocalRole" - ], - "type": "string" - }, - "Region": { - "description": "AWS Region", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-south-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-southeast-4", - "ca-central-1", - "ca-west-1", - "cn-north-1", - "cn-northwest-1", - "eu-central-1", - "eu-central-2", - "eu-north-1", - "eu-south-1", - "eu-south-2", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "il-central-1", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-gov-west-1", - "us-gov-east-1", - "us-iso-east-1", - "us-isob-east-1", - "us-iso-west-1", - "us-west-1", - "us-west-2" - ], - "type": "string" - }, - "RoutingHttpXffHeaderProcessingModeEnum": { - "enum": [ - "append", - "preserve", - "remove" - ], - "type": "string" - }, - "SslPolicyAlbEnum": { - "enum": [ - "ELBSecurityPolicy-TLS-1-0-2015-04", - "ELBSecurityPolicy-TLS-1-1-2017-01", - "ELBSecurityPolicy-TLS-1-2-2017-01", - "ELBSecurityPolicy-TLS-1-2-Ext-2018-06", - "ELBSecurityPolicy-FS-2018-06", - "ELBSecurityPolicy-FS-1-1-2019-08", - "ELBSecurityPolicy-FS-1-2-2019-08", - "ELBSecurityPolicy-FS-1-2-Res-2019-08", - "ELBSecurityPolicy-2015-05", - "ELBSecurityPolicy-FS-1-2-Res-2020-10", - "ELBSecurityPolicy-2016-08" - ], - "type": "string" - }, - "SslPolicyNlbEnum": { - "enum": [ - "ELBSecurityPolicy-TLS-1-0-2015-04", - "ELBSecurityPolicy-TLS-1-1-2017-01", - "ELBSecurityPolicy-TLS-1-2-2017-01", - "ELBSecurityPolicy-TLS-1-2-Ext-2018-06", - "ELBSecurityPolicy-FS-2018-06", - "ELBSecurityPolicy-FS-1-1-2019-08", - "ELBSecurityPolicy-FS-1-2-2019-08", - "ELBSecurityPolicy-FS-1-2-Res-2019-08", - "ELBSecurityPolicy-2015-05", - "ELBSecurityPolicy-FS-1-2-Res-2020-10", - "ELBSecurityPolicy-TLS13-1-2-2021-06", - "ELBSecurityPolicy-TLS13-1-2-Res-2021-06", - "ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06", - "ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", - "ELBSecurityPolicy-TLS13-1-1-2021-06", - "ELBSecurityPolicy-TLS13-1-0-2021-06", - "ELBSecurityPolicy-TLS13-1-3-2021-06", - "ELBSecurityPolicy-2016-08" - ], - "type": "string" - }, - "TargetGroupAttributeAlgorithm": { - "enum": [ - "round_robin", - "least_outstanding_requests" - ], - "type": "string" - }, - "TargetGroupAttributeStickinessType": { - "enum": [ - "lb_cookie", - "app_cookie", - "source_ip", - "source_ip_dest_ip", - "source_ip_dest_ip_proto" - ], - "type": "string" - }, - "TargetGroupHealthCheckProtocolType": { - "enum": [ - "HTTP", - "HTTPS", - "TCP" - ], - "type": "string" - }, - "TargetGroupProtocolType": { - "enum": [ - "TCP", - "TLS", - "UDP", - "TCP_UDP", - "HTTP", - "HTTPS", - "GENEVE" - ], - "type": "string" - }, - "TargetGroupProtocolVersionType": { - "enum": [ - "GRPC", - "HTTP1", - "HTTP2" - ], - "type": "string" - }, - "TargetGroupTargetFailoverType": { - "enum": [ - "no_rebalance", - "rebalance" - ], - "type": "string" - }, - "TargetGroupType": { - "enum": [ - "instance", - "ip", - "alb", - "lambda" - ], - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/global-config.json b/source/packages/@aws-accelerator/config/lib/schemas/global-config.json deleted file mode 100644 index 97ff4e8..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/global-config.json +++ /dev/null @@ -1,1618 +0,0 @@ -{ - "$ref": "#/definitions/IGlobalConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "ComparisonOperator": { - "enum": [ - "GREATER_THAN", - "LESS_THAN", - "EQUAL_TO" - ], - "type": "string" - }, - "CustomS3ResourceAndKmsPolicyOverridesConfig": { - "additionalProperties": false, - "description": "Custom policy overrides configuration for S3 resource and KMS", - "properties": { - "kmsPolicy": { - "description": "KMS policy file", - "type": "string" - }, - "s3Policy": { - "description": "S3 resource policy file", - "type": "string" - } - }, - "type": "object" - }, - "EmailAddress": { - "description": "An email address", - "maxLength": 64, - "minLength": 6, - "pattern": "['^\\S+@\\S+\\.\\S+$', '^\\w+$']", - "type": "string" - }, - "IAcceleratorMetadataConfig": { - "additionalProperties": false, - "description": "Accelerator Metadata\n\nCreates a new bucket in the log archive account to retrieve metadata for the accelerator environment", - "properties": { - "account": { - "type": "string" - }, - "enable": { - "description": "Enable Accelerator Metadata", - "type": "boolean" - }, - "readOnlyAccessRoleArns": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "enable", - "account", - "readOnlyAccessRoleArns" - ], - "type": "object" - }, - "IAcceleratorSettingsConfig": { - "additionalProperties": false, - "description": "Accelerator Settings Configuration\nAllows setting additional properties for accelerator", - "properties": { - "maxConcurrentStacks": { - "description": "Set maximum number of concurrent stacks that can be processed at a time while transpiling the application. If no value is specified it defaults to 250", - "type": "number" - } - }, - "type": "object" - }, - "IAccessLogBucketConfig": { - "additionalProperties": false, - "description": "Accelerator global S3 access logging configuration", - "properties": { - "customPolicyOverrides": { - "$ref": "#/definitions/ICustomS3ResourcePolicyOverridesConfig", - "default": "undefined", - "description": "Custom policy overrides configuration." - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "To control target environments (AWS Account and Region) for the given `accessLogBucket` setting, you may optionally specify deployment targets. Leaving `deploymentTargets` undefined will apply `useCMK` setting to all accounts and enabled regions." - }, - "enable": { - "description": "Flag indicating S3 access logging bucket is enable by solution.", - "type": "boolean" - }, - "importedBucket": { - "$ref": "#/definitions/IImportedS3ManagedEncryptionKeyBucketConfig", - "default": "undefined", - "description": "Imported bucket configuration." - }, - "lifecycleRules": { - "description": "Declaration of (S3 Bucket) Lifecycle rules.", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "s3ResourcePolicyAttachments": { - "description": "JSON policy files.", - "items": { - "$ref": "#/definitions/IResourcePolicyStatement" - }, - "type": "array" - } - }, - "type": "object" - }, - "IAccountCloudTrailConfig": { - "additionalProperties": false, - "description": "Account CloudTrail config", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Which OU's or Accounts the trail will be deployed to" - }, - "name": { - "description": "Name that will be used to create the CloudTrail.", - "type": "string" - }, - "regions": { - "description": "Region(s) that this account trail will be deployed in.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "settings": { - "$ref": "#/definitions/ICloudTrailSettingsConfig", - "description": "Settings for the CloudTrail log" - } - }, - "required": [ - "name", - "regions", - "deploymentTargets", - "settings" - ], - "type": "object" - }, - "IAssetBucketConfig": { - "additionalProperties": false, - "description": "Accelerator global S3 asset bucket configuration", - "properties": { - "customPolicyOverrides": { - "$ref": "#/definitions/CustomS3ResourceAndKmsPolicyOverridesConfig", - "default": "undefined", - "description": "Custom policy overrides configuration." - }, - "importedBucket": { - "$ref": "#/definitions/IImportedCustomerManagedEncryptionKeyBucketConfig", - "default": "undefined", - "description": "Imported bucket configuration." - }, - "kmsResourcePolicyAttachments": { - "description": "JSON policy files.", - "items": { - "$ref": "#/definitions/IResourcePolicyStatement" - }, - "type": "array" - }, - "s3ResourcePolicyAttachments": { - "description": "JSON policy files.", - "items": { - "$ref": "#/definitions/IResourcePolicyStatement" - }, - "type": "array" - } - }, - "type": "object" - }, - "IBackupConfig": { - "additionalProperties": false, - "description": "AWS Backup configuration", - "properties": { - "vaults": { - "description": "List of AWS Backup Vaults", - "items": { - "$ref": "#/definitions/IVaultConfig" - }, - "type": "array" - } - }, - "required": [ - "vaults" - ], - "type": "object" - }, - "IBudgetReportConfig": { - "additionalProperties": false, - "description": "BudgetReport configuration", - "properties": { - "amount": { - "default": 2000, - "description": "The cost or usage amount that's associated with a budget forecast, actual spend, or budget threshold.", - "type": "number" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "List of OU's and accounts to be configured for Budgets configuration" - }, - "includeCredit": { - "default": true, - "description": "Specifies whether a budget includes credits.", - "type": "boolean" - }, - "includeDiscount": { - "default": true, - "description": "Specifies whether a budget includes discounts.", - "type": "boolean" - }, - "includeOtherSubscription": { - "default": true, - "description": "Specifies whether a budget includes non-RI subscription costs.", - "type": "boolean" - }, - "includeRecurring": { - "default": true, - "description": "Specifies whether a budget includes recurring fees such as monthly RI fees.", - "type": "boolean" - }, - "includeRefund": { - "default": true, - "description": "Specifies whether a budget includes refunds.", - "type": "boolean" - }, - "includeSubscription": { - "default": true, - "description": "Specifies whether a budget includes subscriptions.", - "type": "boolean" - }, - "includeSupport": { - "default": true, - "description": "Specifies whether a budget includes support subscription fees.", - "type": "boolean" - }, - "includeTax": { - "default": true, - "description": "Specifies whether a budget includes taxes.", - "type": "boolean" - }, - "includeUpfront": { - "default": true, - "description": "Specifies whether a budget includes upfront RI costs.", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of a budget. The value must be unique within an account. BudgetName can't include : and \\ characters. If you don't include value for BudgetName in the template, Billing and Cost Management assigns your budget a randomly generated name." - }, - "notifications": { - "description": "The comparison that's used for the notification that's associated with a budget.", - "items": { - "$ref": "#/definitions/INotificationConfig" - }, - "type": "array" - }, - "subscriptionType": { - "anyOf": [ - { - "$ref": "#/definitions/SubscriptionType" - }, - { - "type": "string" - } - ], - "description": "The type of notification that AWS sends to a subscriber.\n\nAn enum value that specifies the target subscription type either EMAIL or SNS" - }, - "timeUnit": { - "description": "The length of time until a budget resets the actual and forecasted spend. DAILY is available only for RI_UTILIZATION and RI_COVERAGE budgets.", - "type": "string" - }, - "type": { - "description": "Specifies whether this budget tracks costs, usage, RI utilization, RI coverage, Savings Plans utilization, or Savings Plans coverage.", - "type": "string" - }, - "unit": { - "$ref": "#/definitions/NonEmptyString", - "description": "The unit of measurement that's used for the budget forecast, actual spend, or budget threshold, such as USD or GBP." - }, - "useAmortized": { - "default": false, - "description": "Specifies whether a budget uses the amortized rate.", - "type": "boolean" - }, - "useBlended": { - "default": false, - "description": "Specifies whether a budget uses a blended rate.", - "type": "boolean" - } - }, - "required": [ - "amount", - "name", - "timeUnit", - "type" - ], - "type": "object" - }, - "ICdkOptionsConfig": { - "additionalProperties": false, - "description": "AWS CDK options configuration. This lets you customize the operation of the CDK within LZA, specifically:\n\ncentralizeBuckets: Enabling this option modifies the CDK bootstrap process to utilize a single S3 bucket per region located in the management account for CDK assets generated by LZA. Otherwise, CDK will create a new S3 bucket in every account and every region supported by LZA.\nuseManagementAccessRole: Enabling this option modifies CDK operations to use the IAM role specified in the `managementAccountAccessRole` option in `global-config.yaml` rather than the default roles created by CDK. Default CDK roles will still be created, but will remain unused. Any stacks previously deployed by LZA will retain their [associated execution role](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html). For more information on these roles, please see [here](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-contract).", - "properties": { - "centralizeBuckets": { - "description": "Indicates whether CDK stacks in workload accounts will utilize S3 buckets in the management account rather than within the account.\n\nWhen the accelerator deploys resources using the AWS CDK, assets are first built and stored in S3. By default, the S3 bucket is located within the deployment target account.", - "type": "boolean" - }, - "customDeploymentRole": { - "description": "Creates a deployment role in all accounts in the home region with the name specified in the parameter. This role is used by the LZA for all CDK deployment tasks.", - "type": "string" - }, - "forceBootstrap": { - "description": "Forces the Accelerator to deploy the bootstrapping stack and circumvent the ssm parameter check. This option is needed when adding or removing a custom deployment role", - "type": "boolean" - }, - "useManagementAccessRole": { - "description": "Indicates whether CDK operations use the IAM role specified in the `managementAccountAccessRole` option in `global-config.yaml` rather than the default roles created by CDK.\n\nThe roles created and leveraged by CDK by default can be found [here](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-contract).", - "type": "boolean" - } - }, - "required": [ - "centralizeBuckets", - "useManagementAccessRole" - ], - "type": "object" - }, - "ICentralLogBucketConfig": { - "additionalProperties": false, - "description": "Accelerator global S3 central logging configuration", - "properties": { - "customPolicyOverrides": { - "$ref": "#/definitions/ICustomS3ResourceAndKmsPolicyOverridesConfig", - "default": "undefined", - "description": "Custom policy overrides configuration." - }, - "importedBucket": { - "$ref": "#/definitions/IImportedS3ManagedEncryptionKeyBucketConfig", - "default": "undefined", - "description": "Imported bucket configuration." - }, - "kmsResourcePolicyAttachments": { - "description": "JSON policy files.", - "items": { - "$ref": "#/definitions/IResourcePolicyStatement" - }, - "type": "array" - }, - "lifecycleRules": { - "description": "Declaration of (S3 Bucket) Lifecycle rules. Configure additional resource policy attachments", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "s3ResourcePolicyAttachments": { - "description": "JSON policy files.", - "items": { - "$ref": "#/definitions/IResourcePolicyStatement" - }, - "type": "array" - } - }, - "type": "object" - }, - "ICentralizeCdkBucketsConfig": { - "additionalProperties": false, - "description": "AWS CDK Centralization configuration\n***Deprecated***\nReplaced by cdkOptions in global config", - "properties": { - "enable": { - "description": "***Deprecated*** Replaced by cdkOptions in global config.\n\nIndicates whether CDK stacks in workload accounts will utilize S3 buckets in the management account rather than within the account.\n\nWhen the accelerator deploys resources using the AWS CDK, assets are first built and stored in S3. By default, the S3 bucket is located within the deployment target account.", - "type": "boolean" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "ICloudTrailConfig": { - "additionalProperties": false, - "description": "AWS Cloudtrail configuration", - "properties": { - "accountTrails": { - "description": "Optional configuration of account level CloudTrails. Can be used with or without an Organization Trail", - "items": { - "$ref": "#/definitions/IAccountCloudTrailConfig" - }, - "type": "array" - }, - "enable": { - "description": "Indicates whether AWS Cloudtrail enabled.\n\nCloudtrail a service that helps you enable governance, compliance, and operational and risk auditing of your AWS account. This setting does not create any trails. You will also need to either and organization trail or setup account level trails.", - "type": "boolean" - }, - "lifecycleRules": { - "description": "Optional S3 Log Bucket Lifecycle rules", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "organizationTrail": { - "description": "Indicates whether AWS OrganizationTrail enabled.\n\nWhen OrganizationTrail and cloudtrail is enabled accelerator will enable trusted access designates CloudTrail as a trusted service in your organization. A trusted service can query the organization's structure and create service-linked roles in the organization's accounts.", - "type": "boolean" - }, - "organizationTrailSettings": { - "$ref": "#/definitions/ICloudTrailSettingsConfig", - "description": "Optional configuration of the organization trail. OrganizationTrail must be enabled in order to use these settings" - } - }, - "required": [ - "enable", - "organizationTrail" - ], - "type": "object" - }, - "ICloudTrailSettingsConfig": { - "additionalProperties": false, - "description": "AWS CloudTrail Settings configuration", - "properties": { - "apiCallRateInsight": { - "description": "Will enable CloudTrail Insights and enable the API Call Rate Insight", - "type": "boolean" - }, - "apiErrorRateInsight": { - "description": "Will enable CloudTrail Insights and enable the API Error Rate Insight", - "type": "boolean" - }, - "globalServiceEvents": { - "description": "For global services such as AWS Identity and Access Management (IAM), AWS STS, Amazon CloudFront, and Route 53, events are delivered to any trail that includes global services, and are logged as occurring in US East Region.", - "type": "boolean" - }, - "lambdaDataEvents": { - "description": "Adds an Lambda Data Event Selector for filtering events that match Lambda operations. These events provide insight into the resource operations performed on or within a resource. These are also known as data plane operations.", - "type": "boolean" - }, - "managementEvents": { - "description": "Management events provide insight into management operations that are on resources in your AWS account. These are also known as control plane operations. Management events can also include non-API events that occur in your account. For example, when a user logs in to your account, CloudTrail logs the ConsoleLogin event. Enabling will set ReadWriteType.ALL", - "type": "boolean" - }, - "multiRegionTrail": { - "description": "Whether or not this trail delivers log files from all regions in the account.", - "type": "boolean" - }, - "s3DataEvents": { - "description": "Adds an S3 Data Event Selector for filtering events that match S3 operations. These events provide insight into the resource operations performed on or within a resource. These are also known as data plane operations.", - "type": "boolean" - }, - "sendToCloudWatchLogs": { - "description": "If CloudTrail pushes logs to CloudWatch Logs in addition to S3. CloudWatch Logs will also be replicated to S3.", - "type": "boolean" - } - }, - "required": [ - "multiRegionTrail", - "globalServiceEvents", - "managementEvents", - "s3DataEvents", - "lambdaDataEvents", - "sendToCloudWatchLogs", - "apiErrorRateInsight", - "apiCallRateInsight" - ], - "type": "object" - }, - "ICloudWatchDataProtectionConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch Log data protection configuration, you can find more information [here](https://awslabs.github.io/landing-zone-accelerator-on-aws/latest/faq/Logging/cwl/)", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "To control target environments (AWS Account and Region) for the given `categories` setting, you may optionally specify deployment targets. Leaving `deploymentTargets` undefined will apply `categories` setting to all accounts and enabled regions." - }, - "managedDataIdentifiers": { - "$ref": "#/definitions/ICloudWatchManagedDataProtectionIdentifierConfig", - "default": "Credentials", - "description": "CloudWatch Logs managed data identifiers configuration." - }, - "overrideExisting": { - "default": false, - "description": "(OPTIONAL) Indicates whether existing CloudWatch Log data protection configuration can be overwritten.", - "type": "boolean" - } - }, - "required": [ - "managedDataIdentifiers" - ], - "type": "object" - }, - "ICloudWatchLogsConfig": { - "additionalProperties": false, - "description": "Accelerator global CloudWatch Logs logging configuration", - "properties": { - "dataProtection": { - "$ref": "#/definitions/ICloudWatchDataProtectionConfig", - "description": "CloudWatch Log data protection configuration" - }, - "dynamicPartitioning": { - "$ref": "#/definitions/NonEmptyString", - "description": "Declaration of Dynamic Partition for Kinesis Firehose." - }, - "enable": { - "description": "Enable or disable CloudWatch replication", - "type": "boolean" - }, - "encryption": { - "$ref": "#/definitions/IServiceEncryptionConfig", - "description": "Encryption setting for AWS CloudWatch log group data." - }, - "exclusions": { - "description": "Exclude Log Groups during replication", - "items": { - "$ref": "#/definitions/ICloudWatchLogsExclusionConfig" - }, - "type": "array" - }, - "replaceLogDestinationArn": { - "$ref": "#/definitions/NonEmptyString", - "default": "undefined", - "description": "Customer defined log subscription filter destination arn, that is associated with with the existing log group. Accelerator solution needs to disassociate this destination before configuring solution defined subscription filter destination." - } - }, - "type": "object" - }, - "ICloudWatchLogsExclusionConfig": { - "additionalProperties": false, - "description": "Accelerator global CloudWatch Logs exclusion configuration", - "properties": { - "accounts": { - "description": "List of accounts where the exclusion will be applied to", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "excludeAll": { - "description": "Exclude replication on all logs. By default this is set to false.", - "type": "boolean" - }, - "logGroupNames": { - "description": "List of log groups names where the exclusion will be applied to", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "organizationalUnits": { - "description": "List of OUs that the exclusion will apply to", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "regions": { - "description": "List of regions where the exclusion will be applied to. If no value is supplied, exclusion is applied to all enabled regions.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "type": "object" - }, - "ICloudWatchManagedDataProtectionIdentifierConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch log data protection configuration", - "properties": { - "categories": { - "default": "Credentials", - "description": "CloudWatch Logs managed data identifiers configuration.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "categories" - ], - "type": "object" - }, - "IControlTowerConfig": { - "additionalProperties": false, - "description": "AWS Control Tower Landing Zone configuration", - "properties": { - "controls": { - "description": "A list of Control Tower controls to enable.\n\nOnly Strongly recommended and Elective controls are permitted, with the exception of the Region deny guardrail. Please see this page for more information: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-controltower-enabledcontrol.html", - "items": { - "$ref": "#/definitions/IControlTowerControlConfig" - }, - "type": "array" - }, - "enable": { - "description": "Indicates whether AWS Control Tower Landing Zone enabled.\n\nWhen control tower is enabled, accelerator makes sure account configuration file have three mandatory AWS CT accounts. In AWS Control Tower, three shared accounts in your landing zone are provisioned automatically during setup: the management account, the log archive account, and the audit account.", - "type": "boolean" - }, - "landingZone": { - "$ref": "#/definitions/IControlTowerLandingZoneConfig", - "description": "AWS Control Tower Landing Zone configuration" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IControlTowerControlConfig": { - "additionalProperties": false, - "description": "Control Tower controls", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Control Tower control deployment targets, controls can only be deployed to Organizational Units" - }, - "enable": { - "description": "Control enabled", - "type": "boolean" - }, - "identifier": { - "$ref": "#/definitions/NonEmptyString", - "description": "Control Tower control identifier, for Strongly Recommended or Elective controls this should start with AWS-GR" - }, - "regions": { - "description": "(Optional) Region(s) where this service quota increase will be requested. Service Quota increases will be requested in the home region only if this property is not defined.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "identifier", - "enable", - "deploymentTargets" - ], - "type": "object" - }, - "IControlTowerLandingZoneConfig": { - "additionalProperties": false, - "description": "AWS Control Tower Landing Zone configuration", - "properties": { - "logging": { - "$ref": "#/definitions/IControlTowerLandingZoneLoggingConfig", - "description": "AWS Control Tower Landing Zone logging configuration" - }, - "security": { - "$ref": "#/definitions/IControlTowerLandingZoneSecurityConfig", - "description": "AWS Control Tower Landing Zone security configuration" - }, - "version": { - "description": "The landing zone version, for example, 3.3.", - "type": "string" - } - }, - "required": [ - "version", - "logging", - "security" - ], - "type": "object" - }, - "IControlTowerLandingZoneLoggingConfig": { - "additionalProperties": false, - "description": "{@link IGlobalConfig } / {@link IControlTowerConfig } / {@link IControlTowerLandingZoneConfig } / {@link IControlTowerLandingZoneLoggingConfig } \n\nAWS Control Tower Landing Zone logging configuration", - "properties": { - "accessLoggingBucketRetentionDays": { - "default": 3650, - "description": "Retention time of the logs for access to the bucket.", - "type": "number" - }, - "loggingBucketRetentionDays": { - "default": 365, - "description": "Retention time of the Amazon S3 log archive bucket", - "type": "number" - }, - "organizationTrail": { - "default": true, - "description": "Flag indicates Organizational-level AWS CloudTrail configuration is configured or not.", - "type": "boolean" - } - }, - "required": [ - "loggingBucketRetentionDays", - "accessLoggingBucketRetentionDays", - "organizationTrail" - ], - "type": "object" - }, - "IControlTowerLandingZoneSecurityConfig": { - "additionalProperties": false, - "description": "{@link IGlobalConfig } / {@link IControlTowerConfig } / {@link IControlTowerLandingZoneConfig } / {@link IControlTowerLandingZoneSecurityConfig } AWS Control Tower Landing Zone security configuration", - "properties": { - "enableIdentityCenterAccess": { - "default": true, - "description": "Flag indicates AWS account access option.", - "type": "boolean" - } - }, - "required": [ - "enableIdentityCenterAccess" - ], - "type": "object" - }, - "ICostAndUsageReportConfig": { - "additionalProperties": false, - "description": "CostAndUsageReport configuration", - "properties": { - "additionalArtifacts": { - "description": "A list of manifests that you want Amazon Web Services to create for this report.", - "items": { - "type": "string" - }, - "type": "array" - }, - "additionalSchemaElements": { - "description": "A list of strings that indicate additional content that Amazon Web Services includes in the report, such as individual resource IDs.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "compression": { - "description": "The compression format that Amazon Web Services uses for the report.", - "type": "string" - }, - "format": { - "description": "The format that Amazon Web Services saves the report in.", - "type": "string" - }, - "lifecycleRules": { - "description": "Declaration of (S3 Bucket) Lifecycle rules.", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "refreshClosedReports": { - "description": "Whether you want Amazon Web Services to update your reports after they have been finalized if Amazon Web Services detects charges related to previous months. These charges can include refunds, credits, or support fees.", - "type": "boolean" - }, - "reportName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the report that you want to create. The name must be unique, is case sensitive, and can't include spaces." - }, - "reportVersioning": { - "description": "Whether you want Amazon Web Services to overwrite the previous version of each report or to deliver the report in addition to the previous versions.", - "type": "string" - }, - "s3Prefix": { - "$ref": "#/definitions/NonEmptyString", - "description": "The prefix that Amazon Web Services adds to the report name when Amazon Web Services delivers the report. Your prefix can't include spaces." - }, - "timeUnit": { - "description": "The granularity of the line items in the report.", - "type": "string" - } - }, - "required": [ - "compression", - "format", - "reportName", - "s3Prefix", - "timeUnit", - "refreshClosedReports", - "reportVersioning" - ], - "type": "object" - }, - "ICustomS3ResourceAndKmsPolicyOverridesConfig": { - "additionalProperties": false, - "description": "Custom policy overrides configuration for S3 resource and KMS", - "properties": { - "kmsPolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "KMS policy file" - }, - "s3Policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "S3 resource policy file" - } - }, - "type": "object" - }, - "ICustomS3ResourcePolicyOverridesConfig": { - "additionalProperties": false, - "description": "Custom policy overrides configuration for S3 resource policy", - "properties": { - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "S3 resource policy file" - } - }, - "type": "object" - }, - "IDeploymentTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedAccounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "IElbLogBucketConfig": { - "additionalProperties": false, - "description": "Accelerator global S3 elb logging configuration", - "properties": { - "customPolicyOverrides": { - "$ref": "#/definitions/ICustomS3ResourcePolicyOverridesConfig", - "default": "undefined", - "description": "Custom policy overrides configuration." - }, - "importedBucket": { - "$ref": "#/definitions/IImportedS3ManagedEncryptionKeyBucketConfig", - "default": "undefined", - "description": "Imported bucket configuration." - }, - "lifecycleRules": { - "description": "Declaration of (S3 Bucket) Lifecycle rules. Configure additional resource policy attachments", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "s3ResourcePolicyAttachments": { - "description": "JSON policy files.", - "items": { - "$ref": "#/definitions/IResourcePolicyStatement" - }, - "type": "array" - } - }, - "type": "object" - }, - "IExternalLandingZoneResourcesConfig": { - "additionalProperties": false, - "description": "External Landing Zone Resources Config", - "properties": { - "acceleratorName": { - "$ref": "#/definitions/NonEmptyString" - }, - "acceleratorPrefix": { - "$ref": "#/definitions/NonEmptyString" - }, - "importExternalLandingZoneResources": { - "description": "When the accelerator deploys resources using the AWS CDK, assets are first built and stored in S3. By default, the S3 bucket is located within the deployment target account.", - "type": "boolean" - }, - "mappingFileBucket": { - "type": "string" - } - }, - "required": [ - "importExternalLandingZoneResources", - "acceleratorPrefix", - "acceleratorName" - ], - "type": "object" - }, - "IGlobalConfig": { - "additionalProperties": false, - "description": "Accelerator global configuration", - "properties": { - "acceleratorMetadata": { - "$ref": "#/definitions/IAcceleratorMetadataConfig", - "description": "Accelerator Metadata Configuration Creates a bucket in the logging account to enable accelerator metadata collection" - }, - "acceleratorSettings": { - "$ref": "#/definitions/IAcceleratorSettingsConfig", - "description": "Accelerator Settings Configuration Allows setting additional properties for accelerator" - }, - "backup": { - "$ref": "#/definitions/IBackupConfig", - "description": "Backup Vaults Configuration\n\nTo generate vaults, you need to provide below value for this parameter." - }, - "cdkOptions": { - "$ref": "#/definitions/ICdkOptionsConfig", - "description": "AWS CDK options configuration. This lets you customize the operation of the CDK within LZA, specifically:\n\ncentralizeBuckets: Enabling this option modifies the CDK bootstrap process to utilize a single S3 bucket per region located in the management account for CDK assets generated by LZA. Otherwise, CDK will create a new S3 bucket in every account and every region supported by LZA. useManagementAccessRole: Enabling this option modifies CDK operations to use the IAM role specified in the `managementAccountAccessRole` option in `global-config.yaml` rather than the default roles created by CDK. Default CDK roles will still be created, but will remain unused. Any stacks previously deployed by LZA will retain their [associated execution role](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html). For more information on these roles, please see [here](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-contract)." - }, - "centralizeCdkBuckets": { - "$ref": "#/definitions/ICentralizeCdkBucketsConfig", - "description": "***Deprecated***\n\nNOTICE: The configuration of CDK buckets is being moved to cdkOptions in the Global Config. This block is deprecated and will be removed in a future release" - }, - "cloudwatchLogRetentionInDays": { - "description": "Global {@link https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogsConcepts.html CloudWatch Logs retention in days } configuration.", - "type": "number" - }, - "controlTower": { - "$ref": "#/definitions/IControlTowerConfig", - "description": "AWS Control Tower Landing Zone configuration\n\nTo indicate environment has control tower enabled, you need to provide below value for this parameter." - }, - "enabledRegions": { - "description": "List of AWS Region names where accelerator will be deployed. Home region must be part of this list.\n\nTo add us-west-2 along with home region for accelerator deployment, you need to provide below value for this parameter.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "externalLandingZoneResources": { - "$ref": "#/definitions/IExternalLandingZoneResourcesConfig", - "description": "ExternalLandingZoneResourcesConfig.\n\ncentralizeBuckets: Enabling this option modifies the CDK bootstrap process to utilize a single S3 bucket per region located in the management account for CDK assets generated by LZA. Otherwise, CDK will create a new S3 bucket in every account and every region supported by LZA." - }, - "homeRegion": { - "$ref": "#/definitions/NonEmptyString", - "description": "Accelerator home region name. The region where accelerator pipeline deployed.\n\nTo use us-east-1 as home region for the accelerator, you need to provide below value for this parameter. Note: Variable HOME_REGION created for future usage of home region in the file" - }, - "lambda": { - "$ref": "#/definitions/ILambdaConfig", - "description": "AWS Lambda Function environment variables encryption configuration options." - }, - "limits": { - "description": "AWS Service Quota - Limit configuration\n\nTo enable limits within service quota, you need to provide below value for this parameter.", - "items": { - "$ref": "#/definitions/IServiceQuotaLimitsConfig" - }, - "type": "array" - }, - "logging": { - "$ref": "#/definitions/ILoggingConfig", - "description": "Accelerator logging configuration\n\nTo enable organization trail and session manager logs sending to S3, you need to provide below value for this parameter." - }, - "managementAccountAccessRole": { - "$ref": "#/definitions/NonEmptyString", - "description": "This role trusts the management account, allowing users in the management account to assume the role, as permitted by the management account administrator. The role has administrator permissions in the new member account.\n\nExamples:\n- AWSControlTowerExecution\n- OrganizationAccountAccessRole" - }, - "reports": { - "$ref": "#/definitions/IReportConfig", - "description": "Report configuration\n\nTo enable budget report along with cost and usage report, you need to provide below value for this parameter." - }, - "s3": { - "$ref": "#/definitions/IS3GlobalConfig", - "description": "AWS S3 global configuration options." - }, - "snsTopics": { - "$ref": "#/definitions/ISnsConfig", - "description": "SNS Topics Configuration\n\nTo send CloudWatch Alarms and SecurityHub notifications you will need to configure at least one SNS Topic For SecurityHub notification you will need to set the deployment target to Root in order to receive notifications from all accounts" - }, - "ssmInventory": { - "$ref": "#/definitions/ISsmInventoryConfig", - "description": "SSM Inventory Configuration\n\n[EC2 prerequisites](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-walk.html) [Connectivity prerequisites](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-prereqs.html)" - }, - "ssmParameters": { - "description": "SSM parameter configurations\n\nCreate SSM parameters through the LZA. Parameters can be deployed to Organizational Units or Accounts using deploymentTargets", - "items": { - "$ref": "#/definitions/ISsmParametersConfig" - }, - "type": "array" - }, - "tags": { - "description": "Custom Tags for all resources created by Landing Zone Accelerator that can be tagged.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "terminationProtection": { - "description": "Whether to enable termination protection for this stack.", - "type": "boolean" - } - }, - "required": [ - "homeRegion", - "enabledRegions", - "managementAccountAccessRole", - "cloudwatchLogRetentionInDays", - "controlTower", - "logging" - ], - "type": "object" - }, - "IImportedCustomerManagedEncryptionKeyBucketConfig": { - "additionalProperties": false, - "description": "Imported Bucket configuration with CMK enabled.", - "properties": { - "applyAcceleratorManagedBucketPolicy": { - "description": "Flag indicating Accelerator to apply solution generated policy to imported bucket.", - "type": "boolean" - }, - "createAcceleratorManagedKey": { - "default": false, - "description": "Flag indicating solution should create CMK and apply to imported bucket.", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Imported bucket name" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IImportedS3ManagedEncryptionKeyBucketConfig": { - "additionalProperties": false, - "description": "Imported Bucket configuration with S3 managed key encryption.", - "properties": { - "applyAcceleratorManagedBucketPolicy": { - "description": "Flag indicating Accelerator to apply solution generated policy to imported bucket.", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Imported bucket name" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "ILambdaConfig": { - "additionalProperties": false, - "description": "Lambda Function configuration settings", - "properties": { - "encryption": { - "$ref": "#/definitions/IServiceEncryptionConfig", - "description": "Encryption setting for AWS Lambda environment variables." - } - }, - "type": "object" - }, - "ILifecycleRule": { - "additionalProperties": false, - "description": "S3 bucket life cycle rules object.", - "properties": { - "abortIncompleteMultipartUpload": { - "description": "Specifies a lifecycle rule that aborts incomplete multipart uploads to an Amazon S3 bucket.", - "type": "number" - }, - "enabled": { - "description": "Whether this rule is enabled.", - "type": "boolean" - }, - "expiration": { - "description": "Indicates the number of days after creation when objects are deleted from Amazon S3 and Amazon Glacier.", - "type": "number" - }, - "expiredObjectDeleteMarker": { - "description": "Indicates whether Amazon S3 will remove a delete marker with no noncurrent versions. If set to true, the delete marker will be expired.", - "type": "boolean" - }, - "id": { - "description": "Friendly name for the rule. Rule name must be unique.", - "type": "string" - }, - "noncurrentVersionExpiration": { - "description": "Time between when a new version of the object is uploaded to the bucket and when old versions of the object expire.", - "type": "number" - }, - "noncurrentVersionTransitions": { - "description": "One or more transition rules that specify when non-current objects transition to a specified storage class.", - "items": { - "$ref": "#/definitions/ITransition" - }, - "type": "array" - }, - "prefix": { - "$ref": "#/definitions/NonEmptyString", - "default": "- Rule applies to all objects", - "description": "Object key prefix that identifies one or more objects to which this rule applies." - }, - "transitions": { - "description": "One or more transition rules that specify when an object transitions to a specified storage class.", - "items": { - "$ref": "#/definitions/ITransition" - }, - "type": "array" - } - }, - "type": "object" - }, - "ILoggingConfig": { - "additionalProperties": false, - "description": "Global logging configuration", - "properties": { - "accessLogBucket": { - "$ref": "#/definitions/IAccessLogBucketConfig", - "description": "Declaration of a (S3 Bucket) Lifecycle rule configuration." - }, - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "Accelerator logging account name. Accelerator use LogArchive account for global logging. This account maintains consolidated logs." - }, - "assetBucket": { - "$ref": "#/definitions/IAssetBucketConfig", - "description": "Declaration of a (S3 Bucket) configuration." - }, - "centralLogBucket": { - "$ref": "#/definitions/ICentralLogBucketConfig", - "description": "Declaration of a (S3 Bucket) Lifecycle rule configuration." - }, - "centralizedLoggingRegion": { - "$ref": "#/definitions/NonEmptyString", - "description": "Accelerator central logs bucket region name. Accelerator use CentralLogs bucket to store various log files, Accelerator created buckets and CWL replicates to CentralLogs bucket. CentralLogs bucket region is optional, when not provided this bucket will be created in Accelerator home region." - }, - "cloudtrail": { - "$ref": "#/definitions/ICloudTrailConfig", - "description": "CloudTrail logging configuration" - }, - "cloudwatchLogs": { - "$ref": "#/definitions/ICloudWatchLogsConfig", - "description": "CloudWatch Logging configuration." - }, - "elbLogBucket": { - "$ref": "#/definitions/IElbLogBucketConfig", - "description": "Declaration of a (S3 Bucket) Lifecycle rule configuration." - }, - "sessionManager": { - "$ref": "#/definitions/ISessionManagerConfig", - "description": "SessionManager logging configuration" - } - }, - "required": [ - "account", - "cloudtrail", - "sessionManager" - ], - "type": "object" - }, - "INotificationConfig": { - "additionalProperties": false, - "description": "Notification configuration", - "properties": { - "address": { - "$ref": "#/definitions/NonEmptyString", - "deprecated": "This is a temporary property and it has been deprecated.\nPlease use recipients property to specify address for budget notifications.", - "description": "The address that AWS sends budget notifications to, either an SNS topic or an email." - }, - "comparisonOperator": { - "anyOf": [ - { - "$ref": "#/definitions/ComparisonOperator" - }, - { - "type": "string" - } - ], - "description": "The comparison that's used for this notification." - }, - "recipients": { - "description": "The recipients list that AWS sends budget notifications to, either an SNS topic or an email.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "subscriptionType": { - "anyOf": [ - { - "$ref": "#/definitions/SubscriptionType" - }, - { - "type": "string" - } - ], - "description": "The type of notification that AWS sends to a subscriber." - }, - "threshold": { - "description": "The type of threshold associate with a notification.", - "type": "number" - }, - "thresholdType": { - "anyOf": [ - { - "$ref": "#/definitions/ThresholdType" - }, - { - "type": "string" - } - ], - "description": "The type of threshold for a notification.For ABSOLUTE_VALUE thresholds, AWS notifies you when you go over or are forecasted to go over your total cost threshold. For PERCENTAGE thresholds, AWS notifies you when you go over or are forecasted to go over a certain percentage of your forecasted spend. For example,if you have a budget for 200 dollars and you have a PERCENTAGE threshold of 80%, AWS notifies you when you go over 160 dollars." - }, - "type": { - "anyOf": [ - { - "$ref": "#/definitions/NotificationType" - }, - { - "type": "string" - } - ], - "description": "The comparison that's used for the notification that's associated with a budget." - } - }, - "required": [ - "type", - "thresholdType", - "comparisonOperator", - "subscriptionType" - ], - "type": "object" - }, - "IReportConfig": { - "additionalProperties": false, - "description": "Accelerator report configuration", - "properties": { - "budgets": { - "description": "Budget report configuration\n\nIf you want to create budget report with monthly granularity of the line items in the report and other default parameters , you need to provide below value for this parameter.", - "items": { - "$ref": "#/definitions/IBudgetReportConfig" - }, - "type": "array" - }, - "costAndUsageReport": { - "$ref": "#/definitions/ICostAndUsageReportConfig", - "description": "Cost and usage report configuration\n\nIf you want to create cost and usage report with daily granularity of the line items in the report, you need to provide below value for this parameter." - } - }, - "type": "object" - }, - "IResourcePolicyStatement": { - "additionalProperties": false, - "properties": { - "policy": { - "type": "string" - } - }, - "required": [ - "policy" - ], - "type": "object" - }, - "IS3EncryptionConfig": { - "additionalProperties": false, - "description": "AWS S3 encryption configuration settings", - "properties": { - "createCMK": { - "default": true, - "description": "Flag indicates whether solution will create CMK for S3 bucket encryption. Note: This configuration is not applicable to the assets S3 bucket. This bucket will always have a key generated and applied.", - "type": "boolean" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "To control target environments (AWS Account and Region) for the given `createCMK` setting, you may optionally specify deployment targets. Leaving `deploymentTargets` undefined will apply `createCMK` setting to all accounts and enabled regions." - } - }, - "required": [ - "createCMK" - ], - "type": "object" - }, - "IS3GlobalConfig": { - "additionalProperties": false, - "description": "AWS S3 global encryption configuration settings", - "properties": { - "encryption": { - "$ref": "#/definitions/IS3EncryptionConfig", - "default": "undefined", - "description": "S3 encryption configuration." - } - }, - "type": "object" - }, - "IServiceEncryptionConfig": { - "additionalProperties": false, - "description": "AWS service encryption configuration settings", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "To control target environments (AWS Account and Region) for the given `useCMK` setting, you may optionally specify deployment targets. Leaving `deploymentTargets` undefined will apply `useCMK` setting to all accounts and enabled regions." - }, - "useCMK": { - "default": false, - "description": "Flag indicates whether Accelerator deployed AWS Service will use AWS KMS CMK for encryption or Service managed KMS.", - "type": "boolean" - } - }, - "required": [ - "useCMK" - ], - "type": "object" - }, - "IServiceQuotaLimitsConfig": { - "additionalProperties": false, - "description": "AWS Service Quotas configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "List of AWS Account names to be included in the Service Quota changes" - }, - "desiredValue": { - "description": "Value associated with the limit change.", - "type": "number" - }, - "quotaCode": { - "description": "Indicates the code for the service as these are tied to the account.", - "type": "string" - }, - "regions": { - "description": "(Optional) Region(s) where this service quota increase will be requested. Service Quota increases will be requested in the home region only if this property is not defined.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "serviceCode": { - "description": "Indicates which service Service Quota is changing the limit for.", - "type": "string" - } - }, - "required": [ - "serviceCode", - "quotaCode", - "desiredValue", - "deploymentTargets" - ], - "type": "object" - }, - "ISessionManagerConfig": { - "additionalProperties": false, - "description": "AWS SessionManager configuration", - "properties": { - "attachPolicyToIamRoles": { - "description": "List of IAM EC2 roles that the Session Manager access policy should be attached to", - "items": { - "type": "string" - }, - "type": "array" - }, - "excludeAccounts": { - "description": "List of AWS Account names to be excluded from configuring SessionManager configuration", - "items": { - "type": "string" - }, - "type": "array" - }, - "excludeRegions": { - "description": "List of AWS Region names to be excluded from configuring SessionManager configuration", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "lifecycleRules": { - "description": "S3 Lifecycle rule for log storage", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "sendToCloudWatchLogs": { - "description": "Indicates whether sending SessionManager logs to CloudWatchLogs enabled.", - "type": "boolean" - }, - "sendToS3": { - "description": "Indicates whether sending SessionManager logs to S3 enabled.\n\nWhen this flag is on, accelerator will send session manager logs to Central log bucket in LogArchive account.", - "type": "boolean" - } - }, - "required": [ - "sendToCloudWatchLogs", - "sendToS3" - ], - "type": "object" - }, - "ISnsConfig": { - "additionalProperties": false, - "description": "SNS Configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Deployment targets for SNS topics SNS Topics will always be deployed to the Log Archive account email subscriptions will be in the Log Archive account All other accounts and regions will forward to the Logging account" - }, - "topics": { - "description": "List of SNS Topics", - "items": { - "$ref": "#/definitions/ISnsTopicConfig" - }, - "type": "array" - } - }, - "type": "object" - }, - "ISnsTopicConfig": { - "additionalProperties": false, - "description": "SNS Topics Configuration\n\nTo send CloudWatch Alarms and SecurityHub notifications\nyou will need to configure at least one SNS Topic\nFor SecurityHub notification you will need\nto set the deployment target to Root in order\nto receive notifications from all accounts", - "properties": { - "emailAddresses": { - "description": "List of email address for notification", - "items": { - "$ref": "#/definitions/EmailAddress" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "SNS Topic Name" - } - }, - "required": [ - "name", - "emailAddresses" - ], - "type": "object" - }, - "ISsmInventoryConfig": { - "additionalProperties": false, - "description": "SSM Inventory Configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Configure the Deployment Targets" - }, - "enable": { - "description": "Enable SSM Inventory", - "type": "boolean" - } - }, - "required": [ - "enable", - "deploymentTargets" - ], - "type": "object" - }, - "ISsmParameterConfig": { - "additionalProperties": false, - "description": "SSM Parameter Configuration", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the SSM Parameter, this is used to generate the CloudFormation Logical Id." - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "The path or name used when creating SSM Parameter." - }, - "value": { - "$ref": "#/definitions/NonEmptyString", - "description": "The value of the SSM Parameter" - } - }, - "required": [ - "name", - "path", - "value" - ], - "type": "object" - }, - "ISsmParametersConfig": { - "additionalProperties": false, - "description": "SSM Parameters Configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Configure the Deployment Targets" - }, - "parameters": { - "description": "A list of SSM parameters to create", - "items": { - "$ref": "#/definitions/ISsmParameterConfig" - }, - "type": "array" - } - }, - "required": [ - "parameters", - "deploymentTargets" - ], - "type": "object" - }, - "ITag": { - "additionalProperties": false, - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "key", - "value" - ], - "type": "object" - }, - "ITransition": { - "additionalProperties": false, - "properties": { - "storageClass": { - "$ref": "#/definitions/StorageClass" - }, - "transitionAfter": { - "type": "number" - } - }, - "required": [ - "storageClass", - "transitionAfter" - ], - "type": "object" - }, - "IVaultConfig": { - "additionalProperties": false, - "description": "AWS Backup vault configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Which OU's or Accounts the vault will be deployed to" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name that will be used to create the vault." - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "The path to a JSON file defining Backup Vault access policy" - } - }, - "required": [ - "name", - "deploymentTargets" - ], - "type": "object" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - }, - "NotificationType": { - "enum": [ - "ACTUAL", - "FORECASTED" - ], - "type": "string" - }, - "Region": { - "description": "AWS Region", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-south-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-southeast-4", - "ca-central-1", - "ca-west-1", - "cn-north-1", - "cn-northwest-1", - "eu-central-1", - "eu-central-2", - "eu-north-1", - "eu-south-1", - "eu-south-2", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "il-central-1", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-gov-west-1", - "us-gov-east-1", - "us-iso-east-1", - "us-isob-east-1", - "us-iso-west-1", - "us-west-1", - "us-west-2" - ], - "type": "string" - }, - "StorageClass": { - "enum": [ - "DEEP_ARCHIVE", - "GLACIER", - "GLACIER_IR", - "STANDARD_IA", - "INTELLIGENT_TIERING", - "ONEZONE_IA" - ], - "type": "string" - }, - "SubscriptionType": { - "enum": [ - "EMAIL", - "SNS" - ], - "type": "string" - }, - "ThresholdType": { - "enum": [ - "PERCENTAGE", - "ABSOLUTE_VALUE" - ], - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/iam-config.json b/source/packages/@aws-accelerator/config/lib/schemas/iam-config.json deleted file mode 100644 index 6224154..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/iam-config.json +++ /dev/null @@ -1,937 +0,0 @@ -{ - "$ref": "#/definitions/IIamConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AssumedByType": { - "enum": [ - "service", - "account", - "principalArn", - "provider" - ], - "type": "string" - }, - "IActiveDirectoryConfigurationInstanceConfig": { - "additionalProperties": false, - "description": "Active directory configuration instance configuration. The machine will be used to configure and manage active directory configuration.\nAccelerator can create user, groups when following configuration provided", - "properties": { - "adConnectorGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "Active directory connector group" - }, - "adGroups": { - "description": "Active directory group list", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "adPasswordPolicy": { - "$ref": "#/definitions/IActiveDirectoryPasswordPolicyConfig", - "description": "Active directory user password policy" - }, - "adPerAccountGroups": { - "description": "Active directory per account group list", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "adUsers": { - "description": "Active directory user list", - "items": { - "$ref": "#/definitions/IActiveDirectoryUserConfig" - }, - "type": "array" - }, - "enableTerminationProtection": { - "default": false, - "description": "Flag for Ec2 instance enable api termination protection", - "type": "boolean" - }, - "imagePath": { - "$ref": "#/definitions/NonEmptyString", - "description": "Ec2 image path" - }, - "instanceRole": { - "$ref": "#/definitions/NonEmptyString", - "description": "Ec2 instance role name" - }, - "instanceType": { - "$ref": "#/definitions/NonEmptyString", - "description": "Ec2 instance type" - }, - "securityGroupInboundSources": { - "description": "Ec2 security group inbound sources", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "subnetName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Ec2 instance subnet name" - }, - "userDataScripts": { - "description": "Instance user data script configuration", - "items": { - "$ref": "#/definitions/IActiveDirectoryConfigurationInstanceUserDataConfig" - }, - "type": "array" - }, - "vpcName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Ec2 instance vpc name" - } - }, - "required": [ - "instanceType", - "vpcName", - "imagePath", - "securityGroupInboundSources", - "instanceRole", - "subnetName", - "userDataScripts", - "adGroups", - "adPerAccountGroups", - "adConnectorGroup", - "adUsers", - "adPasswordPolicy" - ], - "type": "object" - }, - "IActiveDirectoryConfigurationInstanceUserDataConfig": { - "additionalProperties": false, - "description": "User data scripts to create users, groups, password policy.\n\nAccelerator can provision users, groups when following user data scripts are provided, these scripts are part of Accelerator sample configuration", - "properties": { - "scriptFilePath": { - "$ref": "#/definitions/NonEmptyString", - "description": "Script file path" - }, - "scriptName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly name for the user data script" - } - }, - "required": [ - "scriptName", - "scriptFilePath" - ], - "type": "object" - }, - "IActiveDirectoryPasswordPolicyConfig": { - "additionalProperties": false, - "description": "Managed active directory user password policy configuration", - "properties": { - "complexity": { - "type": "boolean" - }, - "failedAttempts": { - "type": "number" - }, - "history": { - "type": "number" - }, - "lockoutAttemptsReset": { - "type": "number" - }, - "lockoutDuration": { - "type": "number" - }, - "maximumAge": { - "type": "number" - }, - "minimumAge": { - "type": "number" - }, - "minimumLength": { - "type": "number" - }, - "reversible": { - "type": "boolean" - } - }, - "required": [ - "history", - "maximumAge", - "minimumAge", - "minimumLength", - "complexity", - "reversible", - "failedAttempts", - "lockoutDuration", - "lockoutAttemptsReset" - ], - "type": "object" - }, - "IActiveDirectoryUserConfig": { - "additionalProperties": false, - "description": "Active directory user configuration", - "properties": { - "email": { - "$ref": "#/definitions/NonEmptyString", - "description": "Active directory user email" - }, - "groups": { - "description": "Active directory user group names", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Active directory user name" - } - }, - "required": [ - "name", - "email", - "groups" - ], - "type": "object" - }, - "IAssumedByConfig": { - "additionalProperties": false, - "description": "AssumedBy configuration\n\nService principal:", - "properties": { - "principal": { - "$ref": "#/definitions/NonEmptyString", - "description": "Type of IAM principal type like service, account, principalArn or provider, which can assume this role." - }, - "type": { - "$ref": "#/definitions/AssumedByType", - "description": "IAM principal of either service, account, principalArn or provider type.\n\nIAM principal of sns service type (i.e. new ServicePrincipal('sns.amazonaws.com')), which can assume this role." - } - }, - "required": [ - "type" - ], - "type": "object" - }, - "ICustomerManagedPolicyReferenceConfig": { - "additionalProperties": false, - "description": "Customer Managed Policy Reference Config", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Identity Center PermissionSet permissions boundary customer managed policy name." - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "The path to the IAM policy that you have configured in each account where you want to deploy your permission set." - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IDeploymentTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedAccounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "IGroupConfig": { - "additionalProperties": false, - "description": "IAM group configuration", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the IAM group. For valid values, see the GroupName parameter for the CreateGroup action in the IAM API Reference. If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the group name.\n\nIf you specify a name, you must specify the CAPABILITY_NAMED_IAM value to acknowledge your template's capabilities. For more information, see Acknowledging IAM Resources in AWS CloudFormation Templates." - }, - "policies": { - "$ref": "#/definitions/IPoliciesConfig", - "description": "List of policy objects" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IGroupSetConfig": { - "additionalProperties": false, - "description": "IAM group set configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Group set's deployment targets" - }, - "groups": { - "description": "List of IAM group objects", - "items": { - "$ref": "#/definitions/IGroupConfig" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "groups" - ], - "type": "object" - }, - "IIamConfig": { - "additionalProperties": false, - "description": "IAM configuration", - "properties": { - "groupSets": { - "description": "Group set configuration", - "items": { - "$ref": "#/definitions/IGroupSetConfig" - }, - "type": "array" - }, - "homeRegion": { - "$ref": "#/definitions/Region", - "description": "Accelerator home region name." - }, - "identityCenter": { - "$ref": "#/definitions/IIdentityCenterConfig", - "description": "Identity Center configuration" - }, - "managedActiveDirectories": { - "description": "Managed active directory configuration", - "items": { - "$ref": "#/definitions/IManagedActiveDirectoryConfig" - }, - "type": "array" - }, - "policySets": { - "description": "Policy set configuration.\n\nTo configure IAM policy named Default-Boundary-Policy with permission boundary defined in iam-policies/boundary-policy.json file, you need to provide following values for this parameter.", - "items": { - "$ref": "#/definitions/IPolicySetConfig" - }, - "type": "array" - }, - "providers": { - "description": "SAML provider configuration To configure SAML configuration, you need to provide the following values for this parameter. Replace provider name and metadata document file. Document file must be in config repository", - "items": { - "$ref": "#/definitions/ISamlProviderConfig" - }, - "type": "array" - }, - "roleSets": { - "description": "Role sets configuration", - "items": { - "$ref": "#/definitions/IRoleSetConfig" - }, - "type": "array" - }, - "userSets": { - "description": "User set configuration", - "items": { - "$ref": "#/definitions/IUserSetConfig" - }, - "type": "array" - } - }, - "type": "object" - }, - "IIdentityCenterAssignmentConfig": { - "additionalProperties": false, - "description": "Identity Center Assignment Configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Identity Center assignment deployment targets" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The Name for the Assignment." - }, - "permissionSetName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Permission Set name that will be used for the Assignment." - }, - "principalId": { - "$ref": "#/definitions/NonEmptyString", - "deprecated": "This is a temporary property and it has been deprecated.\nPlease use principals property to specify principal name for assignment.", - "description": "PrincipalId that will be used for the Assignment" - }, - "principalType": { - "$ref": "#/definitions/PrincipalType", - "deprecated": "This is a temporary property and it has been deprecated.\nPlease use principals property to specify principal type for assignment.", - "description": "PrincipalType that will be used for the Assignment" - }, - "principals": { - "description": "Assignment principal configuration list.", - "items": { - "$ref": "#/definitions/IIdentityCenterAssignmentPrincipalConfig" - }, - "type": "array" - } - }, - "required": [ - "permissionSetName", - "deploymentTargets", - "name" - ], - "type": "object" - }, - "IIdentityCenterAssignmentPrincipalConfig": { - "additionalProperties": false, - "description": "Identity Center Permission Set Assignment Principal Configuration", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the principal" - }, - "type": { - "$ref": "#/definitions/NonEmptyString", - "description": "Assignment principal type" - } - }, - "required": [ - "type", - "name" - ], - "type": "object" - }, - "IIdentityCenterConfig": { - "additionalProperties": false, - "description": "Identity Center Configuration", - "properties": { - "delegatedAdminAccount": { - "$ref": "#/definitions/NonEmptyString", - "description": "Override for Delegated Admin Account" - }, - "identityCenterAssignments": { - "description": "List of Assignments", - "items": { - "$ref": "#/definitions/IIdentityCenterAssignmentConfig" - }, - "type": "array" - }, - "identityCenterPermissionSets": { - "description": "List of PermissionSets", - "items": { - "$ref": "#/definitions/IIdentityCenterPermissionSetConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the Identity Center Configuration" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IIdentityCenterPermissionSetConfig": { - "additionalProperties": false, - "description": "Identity Center Permission Set Configuration", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the Identity Center Permission Set Configuration" - }, - "policies": { - "$ref": "#/definitions/IIdentityCenterPoliciesConfig", - "description": "Policy Configuration for Customer Managed Permissions and AWS Managed Permissions" - }, - "sessionDuration": { - "default": "undefined", - "description": "A number value (in minutes). The length of time that the application user sessions are valid for in the ISO-8601 standard.", - "type": "number" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IIdentityCenterPoliciesConfig": { - "additionalProperties": false, - "description": "Identity Center Permission Set Policy Configuration", - "properties": { - "acceleratorManaged": { - "description": "List of the names customer managed policies that would be attached to permission set.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "awsManaged": { - "description": "List of AWS managed policies that would be attached to permission set.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "customerManaged": { - "description": "List of the names and paths of the customer managed policies that would be attached to permission set.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "inlinePolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "The inline policy that is attached to the permission set.\n\n {@link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-permissionset.html#cfn-sso-permissionset-inlinepolicy InlinePolicy } reference" - }, - "permissionsBoundary": { - "$ref": "#/definitions/IPermissionsBoundaryConfig", - "description": "Specifies the configuration of the AWS managed or customer managed policy that you want to set as a permissions boundary." - } - }, - "type": "object" - }, - "IManagedActiveDirectoryConfig": { - "additionalProperties": false, - "description": "Managed Active directory configuration.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "Active directory deploy target account" - }, - "activeDirectoryConfigurationInstance": { - "$ref": "#/definitions/IActiveDirectoryConfigurationInstanceConfig", - "description": "(OPTIONAL) Active directory instance to configure active directory" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "Descriptive text that appears on the details page after the directory has been created." - }, - "dnsName": { - "$ref": "#/definitions/NonEmptyString", - "description": "A fully qualified domain name. This name will resolve inside your VPC only. It does not need to be publicly resolvable." - }, - "edition": { - "description": "Active directory edition, example AWS Managed Microsoft AD is available in two editions: Standard and Enterprise", - "enum": [ - "Standard", - "Enterprise" - ], - "type": "string" - }, - "logs": { - "$ref": "#/definitions/IManagedActiveDirectoryLogConfig", - "description": "(OPTIONAL) Active directory logs configuration" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly name for the active directory" - }, - "netBiosDomainName": { - "$ref": "#/definitions/NonEmptyString", - "description": "A short identifier for your Net BIOS domain name." - }, - "region": { - "$ref": "#/definitions/Region", - "description": "Active directory deploy target region" - }, - "resolverRuleName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Active directory route 53 resolver rule name" - }, - "secretConfig": { - "$ref": "#/definitions/IManagedActiveDirectorySecretConfig", - "description": "(OPTIONAL) Active directory admin user secret configuration.\n\n* {@link IamConfig } / {@link ManagedActiveDirectoryConfig } / {@link ManagedActiveDirectorySecretConfig }" - }, - "sharedAccounts": { - "description": "(OPTIONAL) Active directory shared account name list.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "sharedOrganizationalUnits": { - "$ref": "#/definitions/IManagedActiveDirectorySharedOuConfig", - "description": "(OPTIONAL) Active directory shared ou configuration.\n\n* {@link IamConfig } / {@link ManagedActiveDirectoryConfig } / {@link ManagedActiveDirectorySharedOuConfig }" - }, - "vpcSettings": { - "$ref": "#/definitions/IManagedActiveDirectoryVpcSettingsConfig", - "description": "Specifies the VPC settings of the Microsoft AD directory server in AWS" - } - }, - "required": [ - "name", - "account", - "region", - "dnsName", - "netBiosDomainName", - "edition", - "vpcSettings" - ], - "type": "object" - }, - "IManagedActiveDirectoryLogConfig": { - "additionalProperties": false, - "description": "Active directory logs configuration", - "properties": { - "groupName": { - "$ref": "#/definitions/NonEmptyString", - "default": "undefined, Accelerator will create log group name as /aws/directoryservice/DirectoryServiceName", - "description": "Active directory log group name, that will be used to receive the security logs from your domain controllers. We recommend pre-pending the name with /aws/directoryservice/, but that is not required." - }, - "retentionInDays": { - "description": "Log group retention in days", - "type": "number" - } - }, - "required": [ - "groupName" - ], - "type": "object" - }, - "IManagedActiveDirectorySecretConfig": { - "additionalProperties": false, - "description": "Active directory admin user secret configuration.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "Active directory admin user secret account name. When no account name provided Accelerator will create the secret into the account MAD exists\n\nNote: Please do not use the Management account for the admin user secret account name." - }, - "adminSecretName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Active directory admin user secret account name. When no account name provided Accelerator will create the secret into the account MAD exists\n\nNote: Please do not use the Management account for the admin user secret account name." - }, - "region": { - "$ref": "#/definitions/Region", - "description": "Active directory admin user secret region name. When no region name provided Accelerator will create the secret into the region MAD exists" - } - }, - "type": "object" - }, - "IManagedActiveDirectorySharedOuConfig": { - "additionalProperties": false, - "description": "Active directory shared ou configuration.", - "properties": { - "excludedAccounts": { - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "organizationalUnits" - ], - "type": "object" - }, - "IManagedActiveDirectoryVpcSettingsConfig": { - "additionalProperties": false, - "description": "Specifies the VPC settings of the Microsoft AD directory server in AWS", - "properties": { - "subnets": { - "description": "Friendly name of the vpc subnets, where active directory will be deployed\n\nMinimum of two subnets from two different availability zone is required", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "vpcName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly name of the vpc where active directory will be deployed" - } - }, - "required": [ - "vpcName", - "subnets" - ], - "type": "object" - }, - "IPermissionsBoundaryConfig": { - "additionalProperties": false, - "description": "Specify either customerManagedPolicy to use the name and path of a customer managed policy, or managedPolicy to use the ARN of an AWS managed policy.", - "properties": { - "awsManagedPolicyName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The AWS managed policy name that you want to attach to a permission set as a permissions boundary." - }, - "customerManagedPolicy": { - "$ref": "#/definitions/ICustomerManagedPolicyReferenceConfig", - "description": "Specifies the name and path of a customer managed policy." - } - }, - "type": "object" - }, - "IPoliciesConfig": { - "additionalProperties": false, - "description": "IAM policies configuration", - "properties": { - "awsManaged": { - "description": "List of AWS managed policies. Values can be policy arn or policy name", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "customerManaged": { - "description": "List of Customer managed policies", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "type": "object" - }, - "IPolicyConfig": { - "additionalProperties": false, - "description": "Use this configuration to define accelerator managed IAM managed policies.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the managed policy." - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "A JSON file containing policy boundary definition." - } - }, - "required": [ - "name", - "policy" - ], - "type": "object" - }, - "IPolicySetConfig": { - "additionalProperties": false, - "description": "Policy set configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Policy set deployment targets" - }, - "identityCenterDependency": { - "description": "Flag indicates if the policy is used in Identity Center PermissionSet assignments.", - "type": "boolean" - }, - "policies": { - "description": "List of Policies", - "items": { - "$ref": "#/definitions/IPolicyConfig" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "policies" - ], - "type": "object" - }, - "IRoleConfig": { - "additionalProperties": false, - "description": "IAM Role configuration", - "properties": { - "assumedBy": { - "description": "AssumedBy configuration", - "items": { - "$ref": "#/definitions/IAssumedByConfig" - }, - "type": "array" - }, - "boundaryPolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "A permissions boundary configuration" - }, - "externalIds": { - "description": "List of IDs that the role assumer needs to provide one of when assuming this role", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "instanceProfile": { - "description": "Indicates whether role is used for EC2 instance profile", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the role" - }, - "policies": { - "$ref": "#/definitions/IPoliciesConfig", - "description": "List of policies for the role" - } - }, - "required": [ - "name", - "assumedBy" - ], - "type": "object" - }, - "IRoleSetConfig": { - "additionalProperties": false, - "description": "Role set configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Role set deployment targets" - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "The path to the role" - }, - "roles": { - "description": "List of role objects", - "items": { - "$ref": "#/definitions/IRoleConfig" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "roles" - ], - "type": "object" - }, - "ISamlProviderConfig": { - "additionalProperties": false, - "description": "SAML provider configuration", - "properties": { - "metadataDocument": { - "$ref": "#/definitions/NonEmptyString", - "description": "SAML metadata document XML file, this file must be present in config repository" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "default": "a CloudFormation generated name", - "description": "The name of the provider to create.\n\nThis parameter allows a string of characters consisting of upper and lowercase alphanumeric characters with no spaces. You can also include any of the following characters: _+=,.@-\n\nLength must be between 1 and 128 characters." - } - }, - "required": [ - "name", - "metadataDocument" - ], - "type": "object" - }, - "IUserConfig": { - "additionalProperties": false, - "description": "IAM User configuration", - "properties": { - "boundaryPolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "AWS supports permissions boundaries for IAM entities (users or roles). A permissions boundary is an advanced feature for using a managed policy to set the maximum permissions that an identity-based policy can grant to an IAM entity. An entity's permissions boundary allows it to perform only the actions that are allowed by both its identity-based policies and its permissions boundaries.\n\nPermission boundary is derived from iam-policies/boundary-policy.json file in config repository" - }, - "group": { - "$ref": "#/definitions/NonEmptyString", - "description": "Group to add this user to." - }, - "username": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the IAM user. For valid values, see the UserName parameter for the CreateUser action in the IAM API Reference. If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the user name.\n\nIf you specify a name, you cannot perform updates that require replacement of this resource. You can perform updates that require no or some interruption. If you must replace the resource, specify a new name." - } - }, - "required": [ - "username", - "group" - ], - "type": "object" - }, - "IUserSetConfig": { - "additionalProperties": false, - "description": "User set configuration\n\n```\nuserSets:\n - deploymentTargets:\n accounts:\n - Management\n users:\n - username: accelerator-user\n boundaryPolicy: Default-Boundary-Policy\n group: Admins\n```", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "User set's deployment target" - }, - "users": { - "description": "List os user objects", - "items": { - "$ref": "#/definitions/IUserConfig" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "users" - ], - "type": "object" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - }, - "PrincipalType": { - "enum": [ - "USER", - "GROUP" - ], - "type": "string" - }, - "Region": { - "description": "AWS Region", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-south-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-southeast-4", - "ca-central-1", - "ca-west-1", - "cn-north-1", - "cn-northwest-1", - "eu-central-1", - "eu-central-2", - "eu-north-1", - "eu-south-1", - "eu-south-2", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "il-central-1", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-gov-west-1", - "us-gov-east-1", - "us-iso-east-1", - "us-isob-east-1", - "us-iso-west-1", - "us-west-1", - "us-west-2" - ], - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/network-config.json b/source/packages/@aws-accelerator/config/lib/schemas/network-config.json deleted file mode 100644 index c9c890c..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/network-config.json +++ /dev/null @@ -1,5187 +0,0 @@ -{ - "$ref": "#/definitions/INetworkConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AlbListenerProtocolEnum": { - "enum": [ - "HTTP", - "HTTPS" - ], - "enum": [ - "HTTP", - "HTTPS" - ], - "type": "string" - }, - "AlbListenerTypeEnum": { - "enum": [ - "fixed-response", - "forward", - "redirect" - ], - "type": "string" - }, - "AlbRoutingHttpConfigMitigationModeEnum": { - "enum": [ - "monitor", - "defensive", - "strictest" - ], - "type": "string" - }, - "AlbSchemeEnum": { - "enum": [ - "internet-facing", - "internal" - ], - "type": "string" - }, - "AllowDeny": { - "enum": [ - "allow", - "deny" - ], - "type": "string" - }, - "AlpnPolicyEnum": { - "enum": [ - "HTTP1Only", - "HTTP2Only", - "HTTP2Optional", - "HTTP2Preferred", - "None" - ], - "type": "string" - }, - "CertificateConfigType": { - "enum": [ - "import", - "request" - ], - "type": "string" - }, - "CertificateValidationType": { - "enum": [ - "EMAIL", - "DNS" - ], - "type": "string" - }, - "DnsFirewallBlockResponseType": { - "enum": [ - "NODATA", - "NXDOMAIN", - "OVERRIDE" - ], - "type": "string" - }, - "DnsFirewallManagedDomainListsType": { - "enum": [ - "AWSManagedDomainsAggregateThreatList", - "AWSManagedDomainsBotnetCommandandControl", - "AWSManagedDomainsMalwareDomainList" - ], - "type": "string" - }, - "DnsFirewallRuleActionType": { - "enum": [ - "ALLOW", - "ALERT", - "BLOCK" - ], - "type": "string" - }, - "DpdTimeoutActionType": { - "enum": [ - "clear", - "none", - "restart" - ], - "type": "string" - }, - "DxVirtualInterfaceType": { - "enum": [ - "private", - "transit" - ], - "type": "string" - }, - "EnableDisable": { - "enum": [ - "enable", - "disable" - ], - "type": "string" - }, - "EncryptionAlgorithmType": { - "enum": [ - "AES128", - "AES256", - "AES128-GCM-16", - "AES256-GCM-16" - ], - "type": "string" - }, - "GatewayEndpointType": { - "enum": [ - "s3", - "dynamodb" - ], - "type": "string" - }, - "GatewayRouteTableType": { - "enum": [ - "internetGateway", - "virtualPrivateGateway" - ], - "type": "string" - }, - "IAlbAttributesConfig": { - "additionalProperties": false, - "description": "Application Load Balancer attributes config.", - "properties": { - "deletionProtection": { - "description": "Enable or disable deletion protection.", - "type": "boolean" - }, - "http2Enabled": { - "description": "Indicates whether HTTP/2 is enabled. The possible values are true and false. The default is true. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens.", - "type": "boolean" - }, - "idleTimeout": { - "description": "The idle timeout value, in seconds. The valid range is 1-4000 seconds. The default is 60 seconds.", - "type": "number" - }, - "routingHttpDesyncMitigationMode": { - "$ref": "#/definitions/AlbRoutingHttpConfigMitigationModeEnum", - "description": "Determines how the load balancer handles requests that might pose a security risk to your application. The possible values are `monitor` , `defensive` , and `strictest` . The default is `defensive`." - }, - "routingHttpDropInvalidHeader": { - "description": "Indicates whether HTTP headers with invalid header fields are removed by the load balancer ( true ) or routed to targets ( false ). The default is false.", - "type": "boolean" - }, - "routingHttpXAmznTlsCipherEnable": { - "description": "Indicates whether the two headers ( x-amzn-tls-version and x-amzn-tls-cipher-suite ), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. The x-amzn-tls-version header has information about the TLS protocol version negotiated with the client, and the x-amzn-tls-cipher-suite header has information about the cipher suite negotiated with the client. Both headers are in OpenSSL format. The possible values for the attribute are true and false . The default is false.", - "type": "boolean" - }, - "routingHttpXffClientPort": { - "description": "Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer. The possible values are true and false . The default is false.", - "type": "boolean" - }, - "routingHttpXffHeaderProcessingMode": { - "$ref": "#/definitions/RoutingHttpXffHeaderProcessingModeEnum", - "description": "Enables you to modify, preserve, or remove the X-Forwarded-For header in the HTTP request before the Application Load Balancer sends the request to the target. The possible values are append, preserve, and remove. The default is append." - }, - "wafFailOpen": { - "description": "Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. The possible values are true and false. The default is false.", - "type": "boolean" - } - }, - "type": "object" - }, - "IAlbListenerConfig": { - "additionalProperties": false, - "description": "Application Load Balancer listener config. Currently only action type of `forward`, `redirect` and `fixed-response` is allowed.", - "properties": { - "certificate": { - "$ref": "#/definitions/NonEmptyString", - "description": "Applies to HTTPS listeners. The default certificate for the listener. You must provide exactly one certificate arn or a certificate name which was created by LZA" - }, - "fixedResponseConfig": { - "$ref": "#/definitions/IAlbListenerFixedResponseConfig", - "description": "Information for creating an action that returns a custom HTTP response. Specify only when type is `fixed-response`." - }, - "forwardConfig": { - "$ref": "#/definitions/IAlbListenerForwardConfig", - "description": "Information for creating an action that distributes requests to targetGroup. Stickiness for targetGroup can be set here." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the application load balancer listener" - }, - "order": { - "description": "The order for the action. This value is required for rules with multiple actions. The action with the lowest value for order is performed first", - "type": "number" - }, - "port": { - "description": "Port of the application load balancer listener", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/AlbListenerProtocolEnum", - "description": "Protocol of the application load balancer listener. The supported protocols are HTTP and HTTPS" - }, - "redirectConfig": { - "$ref": "#/definitions/IAlbListenerRedirectConfig", - "description": "Information for creating a redirect action. Specify only when type is `redirect`." - }, - "sslPolicy": { - "$ref": "#/definitions/SslPolicyAlbEnum", - "description": "The security policy that defines which protocols and ciphers are supported." - }, - "targetGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "Target Group name to which traffic will be forwarded to. This name should be same as {@link ApplicationLoadBalancerTargetGroupConfig targetGroup } name." - }, - "type": { - "$ref": "#/definitions/AlbListenerTypeEnum", - "description": "Type of the application load balancer listener" - } - }, - "required": [ - "name", - "port", - "protocol", - "type", - "targetGroup" - ], - "type": "object" - }, - "IAlbListenerFixedResponseConfig": { - "additionalProperties": false, - "description": "Application load balancer listener fixed response config\nIt returns a custom HTTP response.\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `fixed-response`.", - "properties": { - "contentType": { - "$ref": "#/definitions/NonEmptyString", - "description": "The message to send back." - }, - "messageBody": { - "$ref": "#/definitions/NonEmptyString", - "description": "The HTTP response code (2XX, 4XX, or 5XX)." - }, - "statusCode": { - "$ref": "#/definitions/NonEmptyString", - "description": "The content type. Valid Values: text/plain | text/css | text/html | application/javascript | application/json" - } - }, - "required": [ - "statusCode" - ], - "type": "object" - }, - "IAlbListenerForwardConfig": { - "additionalProperties": false, - "description": "Application Load balancer listener forward config. Used to define forward action.\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `forward`.", - "properties": { - "targetGroupStickinessConfig": { - "$ref": "#/definitions/IAlbListenerTargetGroupStickinessConfig" - } - }, - "type": "object" - }, - "IAlbListenerRedirectConfig": { - "additionalProperties": false, - "description": "Application Load balancer listener redirect config. Used to define redirect action.\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `redirect`.", - "properties": { - "host": { - "$ref": "#/definitions/NonEmptyString" - }, - "path": { - "$ref": "#/definitions/NonEmptyString" - }, - "port": { - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/NonEmptyString" - }, - "query": { - "$ref": "#/definitions/NonEmptyString" - }, - "statusCode": { - "$ref": "#/definitions/NonEmptyString" - } - }, - "type": "object" - }, - "IAlbListenerTargetGroupStickinessConfig": { - "additionalProperties": false, - "description": "Application Load balancer listener forward config target group stickiness config\nApplicable only when `type` under {@link ApplicationLoadBalancerListenerConfig listener} is `forward`.", - "properties": { - "durationSeconds": { - "description": "The time period, in seconds, during which requests from a client should be routed to the same target group. The range is 1-604800 seconds (7 days).", - "type": "number" - }, - "enabled": { - "description": "Indicates whether target group stickiness is enabled.", - "type": "boolean" - } - }, - "type": "object" - }, - "IApplicationLoadBalancerConfig": { - "additionalProperties": false, - "description": "Used to define Application Load Balancer configurations for the accelerator.", - "properties": { - "attributes": { - "$ref": "#/definitions/IAlbAttributesConfig", - "description": "Attributes for Application Load Balancer." - }, - "listeners": { - "description": "Listeners for Application Load Balancer.", - "items": { - "$ref": "#/definitions/IAlbListenerConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the application load balancer" - }, - "scheme": { - "$ref": "#/definitions/AlbSchemeEnum", - "description": "Internal or internet facing scheme for Application Load Balancer." - }, - "securityGroups": { - "description": "Security Groups to attach to the Application Load Balancer.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "The location where the Application Load Balancer(s) will be deployed to.\n*" - }, - "subnets": { - "description": "Subnets to launch the Application Load Balancer in.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "name", - "subnets", - "securityGroups" - ], - "type": "object" - }, - "ICentralNetworkServicesConfig": { - "additionalProperties": false, - "description": "Central network services configuration.\nUse this configuration to define centralized networking services for your environment.\nCentral network services enables you to easily designate a central account that owns your\ncore network infrastructure. These network resources can be shared with other\naccounts in your organization so that workload accounts can consume them.", - "properties": { - "delegatedAdminAccount": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the delegated administrator account for network services. Resources configured under `centralNetworkServices` will be created in this account." - }, - "gatewayLoadBalancers": { - "description": "An array of Gateway Load Balancer configurations.", - "items": { - "$ref": "#/definitions/IGwlbConfig" - }, - "type": "array" - }, - "ipams": { - "description": "An array of IPAM configurations.", - "items": { - "$ref": "#/definitions/IIpamConfig" - }, - "type": "array" - }, - "networkFirewall": { - "$ref": "#/definitions/INfwConfig", - "description": "A Network Firewall configuration." - }, - "route53Resolver": { - "$ref": "#/definitions/IResolverConfig", - "description": "A Route 53 resolver configuration." - } - }, - "required": [ - "delegatedAdminAccount" - ], - "type": "object" - }, - "ICertificateConfig": { - "additionalProperties": false, - "description": "Amazon Certificate Manager (ACM) Configuration\n\n{@link https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html Import certificate} or {@link https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html Request certificate} from ACM", - "properties": { - "cert": { - "$ref": "#/definitions/NonEmptyString", - "description": "Path to certificate in S3 assets bucket. The bucket value is in the outputs of Pipeline stack in home region. Path should be given relative to the bucket. The certificate to import. This value should be provided when type is set to import or else validation fails." - }, - "chain": { - "$ref": "#/definitions/NonEmptyString", - "description": "Path to the PEM encoded certificate chain in S3 assets bucket. The bucket value is in the outputs of Pipeline stack in home region. Path should be given relative to the bucket. This value is optional when type is set to import." - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "ACM deployment target. This should be provided to deploy ACM into OUs or account." - }, - "domain": { - "$ref": "#/definitions/NonEmptyString", - "description": "Fully qualified domain name (FQDN), such as www.example.com, that you want to secure with an ACM certificate. Use an asterisk (*) to create a wildcard certificate that protects several sites in the same domain. For example, *.example.com protects www.example.com, site.example.com, and images.example.com. In compliance with RFC 5280, the length of the domain name (technically, the Common Name) that you provide cannot exceed 64 octets (characters), including periods. To add a longer domain name, specify it in the Subject Alternative Name field, which supports names up to 253 octets in length. This value should be provided when type is set to request or else validation fails." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the certificate. This should be unique in the certificates array. Duplicate names will fail the validation." - }, - "privKey": { - "$ref": "#/definitions/NonEmptyString", - "description": "Path to the private key in S3 assets bucket. The bucket value is in the outputs of Pipeline stack in home region. Path should be given relative to the bucket. The private key that matches the public key in the certificate. This value should be provided when type is set to import or else validation fails." - }, - "san": { - "description": "Additional FQDNs to be included in the Subject Alternative Name extension of the ACM certificate. For example, add the name www.example.net to a certificate for which the DomainName field is www.example.com if users can reach your site by using either name.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "type": { - "$ref": "#/definitions/CertificateConfigType", - "description": "Type of ACM cert. Valid values are `import` or `request`" - }, - "validation": { - "$ref": "#/definitions/CertificateValidationType", - "description": "The method you want to use if you are requesting a public certificate to validate that you own or control domain. You can validate with DNS or validate with email. Valid values are 'DNS' or 'EMAIL'. This value should be provided when type is set to request or else validation fails." - } - }, - "required": [ - "name", - "type" - ], - "type": "object" - }, - "ICustomerGatewayConfig": { - "additionalProperties": false, - "description": "Use this configuration to define Customer Gateways and site-to-site VPN connections.\nA customer gateway device is a physical or software appliance that you own or manage in\nyour on-premises network (on your side of a Site-to-Site VPN connection).\nA VPN connection refers to the connection between your VPC and your own on-premises network.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The logical name of the account to deploy the Customer Gateway to. This value should match the name of the account recorded in the accounts-config.yaml file." - }, - "asn": { - "description": "Define the ASN used for the Customer Gateway", - "type": "number" - }, - "ipAddress": { - "$ref": "#/definitions/NonEmptyString", - "description": "Defines the IP address of the Customer Gateway" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the CGW.\n\nThe value of this property will be utilized as the logical id for this resource. Any references to this object should specify this value." - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The AWS region to provision the customer gateway in" - }, - "tags": { - "description": "Define tags for the Customer Gateway", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "vpnConnections": { - "description": "Define the optional VPN Connection configuration", - "items": { - "$ref": "#/definitions/IVpnConnectionConfig" - }, - "type": "array" - } - }, - "required": [ - "name", - "account", - "region", - "ipAddress", - "asn" - ], - "type": "object" - }, - "IDefaultVpcsConfig": { - "additionalProperties": false, - "description": "Use this configuration to delete default VPCs in your environment.", - "properties": { - "delete": { - "description": "Enable to delete default VPCs.", - "type": "boolean" - }, - "excludeAccounts": { - "description": "(OPTIONAL) Include an array of friendly account names to exclude from default VPC deletion.", - "items": { - "type": "string" - }, - "type": "array" - }, - "excludeRegions": { - "description": "(OPTIONAL) Include an array of AWS regions to exclude from default VPC deletion.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "delete" - ], - "type": "object" - }, - "IDeploymentTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedAccounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "IDhcpOptsConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom DHCP options sets for your VPCs.\nCustom DHCP option sets give you control over the DNS servers, domain names,\nor Network Time Protocol (NTP) servers used by the devices in your VPC.\n\nThe following example creates a DHCP option set named `accelerator-dhcp-opts`\nin the `Network` account in the `us-east-1` region. The options set assigns\na domain name of `example.com` to hosts in the VPC and configures the DNS\nserver to `1.1.1.1`.", - "properties": { - "accounts": { - "description": "An array of friendly account names to deploy the options set.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "domainName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A domain name to assign to hosts using the options set." - }, - "domainNameServers": { - "description": "(OPTIONAL) An array of IP addresses for domain name servers.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the DHCP options set." - }, - "netbiosNameServers": { - "description": "(OPTIONAL An array of IP addresses for NetBIOS servers.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "netbiosNodeType": { - "$ref": "#/definitions/NetbiosNodeType", - "description": "(OPTIONAL) The NetBIOS node type number." - }, - "ntpServers": { - "description": "(OPTIONAL) An array of IP addresses for NTP servers.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "regions": { - "description": "An array of regions to deploy the options set.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the options set.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "accounts", - "regions" - ], - "type": "object" - }, - "IDnsFirewallRuleGroupConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a group of rules for your DNS firewall.\nRule groups contain one to many rules that can be associated with VPCs in your environment.\nThese rules allow you to define the behavior of your DNS firewall.\n\nThe following example creates a rule group that contains one rule entry.\nThe rule blocks a list of custom domains contained in a file in the accelerator\nconfiguration repository. The rule group is shared to the entire organization.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the DNS firewall rule group." - }, - "regions": { - "description": "The regions to deploy the rule group to.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "rules": { - "description": "An array of DNS firewall rule configurations.", - "items": { - "$ref": "#/definitions/IDnsFirewallRulesConfig" - }, - "type": "array" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "tags": { - "description": "An array of tags for the rule group.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "regions", - "rules" - ], - "type": "object" - }, - "IDnsFirewallRulesConfig": { - "additionalProperties": false, - "description": "Use this configuration to define individual rules for your DNS firewall.\nThis allows you to define the DNS firewall behavior for your VPCs.", - "properties": { - "action": { - "$ref": "#/definitions/DnsFirewallRuleActionType", - "description": "An action for the DNS firewall rule to take on matching requests." - }, - "blockOverrideDomain": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Configure an override domain for BLOCK actions. This is a custom DNS record to send back in response to the query." - }, - "blockOverrideTtl": { - "description": "(OPTIONAL) Configure a time-to-live (TTL) for the override domain. This is the recommended amount of time for the DNS resolver or web browser to cache the override record and use it in response to this query, if it is received again. By default, this is zero, and the record isn't cached.", - "type": "number" - }, - "blockResponse": { - "$ref": "#/definitions/DnsFirewallBlockResponseType", - "description": "Configure a specific response type for BLOCK actions. Block response types are defined here: {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-rule-actions.html }" - }, - "customDomainList": { - "$ref": "#/definitions/NonEmptyString", - "description": "A file containing a custom domain list in TXT format." - }, - "managedDomainList": { - "$ref": "#/definitions/DnsFirewallManagedDomainListsType", - "description": "Configure a rule that uses an AWS-managed domain list. AWS-managed domain lists are defined here: {@link https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-managed-domain-lists.html } ." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the DNS firewall rule." - }, - "priority": { - "description": "The priority of the DNS firewall rule.", - "type": "number" - } - }, - "required": [ - "name", - "action", - "priority" - ], - "type": "object" - }, - "IDnsQueryLogsConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a centralized query logging configuration that can\nbe associated with VPCs in your environment.\nYou can use this configuration to log queries that originate from your VPCs,\nqueries to your inbound and outbound resolver endpoints,\nand queries that use Route 53 Resolver DNS firewall to allow, block, or monitor\ndomain lists.\n\nThe following example creates a query logging configuration that logs to both\nS3 and a CloudWatch Logs log group. It is shared with the entire organization.", - "properties": { - "destinations": { - "description": "An array of destination services used to store the logs.", - "items": { - "$ref": "#/definitions/LogDestinationType" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the query logging config." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "Resource Access Manager (RAM) share targets." - } - }, - "required": [ - "name", - "destinations" - ], - "type": "object" - }, - "IDxGatewayConfig": { - "additionalProperties": false, - "description": "A DXGW is a globally-available resource than can be used to connect your VPCs to your on-premise infrastructure.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account to deploy the DX Gateway." - }, - "asn": { - "description": "A Border Gateway Protocol (BGP) Autonomous System Number (ASN).", - "type": "number" - }, - "gatewayName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the Direct Connect Gateway. This name will show as the name of the resource in the AWS console and API." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the DX Gateway. This name is used as a logical reference for the resource in the accelerator." - }, - "transitGatewayAssociations": { - "description": "(OPTIONAL) An array of transit gateway association configurations. Creates transit gateway attachments for this DX gateway.", - "items": { - "$ref": "#/definitions/IDxTransitGatewayAssociationConfig" - }, - "type": "array" - }, - "virtualInterfaces": { - "description": "(OPTIONAL) An array of virtual interface configurations. Creates virtual interfaces on the DX gateway.", - "items": { - "$ref": "#/definitions/IDxVirtualInterfaceConfig" - }, - "type": "array" - } - }, - "required": [ - "name", - "account", - "asn", - "gatewayName" - ], - "type": "object" - }, - "IDxTransitGatewayAssociationConfig": { - "additionalProperties": false, - "description": "Use this configuration to define transit gateway attachments for a DX gateway.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account the transit gateway is deployed to." - }, - "allowedPrefixes": { - "description": "An array of CIDR prefixes that are allowed to advertise over this transit gateway association.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the transit gateway to associate." - }, - "routeTableAssociations": { - "description": "(OPTIONAL) The friendly name of TGW route table(s) to associate with this attachment.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "routeTablePropagations": { - "description": "(OPTIONAL) The friendly name of TGW route table(s) to propagate routes from this attachment.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "name", - "account", - "allowedPrefixes" - ], - "type": "object" - }, - "IDxVirtualInterfaceConfig": { - "additionalProperties": false, - "description": "Use this configuration to create a virtual interface to a DX Gateway. Virtual interfaces\nenable access to your AWS services from your on-premises environment.\n\nThe following example creates a transit VIF called Accelerator-VIF in the Network account\non a DX connection with resource ID dxcon-example:", - "properties": { - "addressFamily": { - "$ref": "#/definitions/IpVersionType", - "description": "(OPTIONAL) The address family to use for this virtual interface.\n\nDefault - ipv4" - }, - "amazonAddress": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The peer IP address to use for Amazon's side of the virtual interface.\n\nDefault - randomly-generated by Amazon" - }, - "connectionId": { - "$ref": "#/definitions/NonEmptyString", - "description": "The resource ID of the {@link https://docs.aws.amazon.com/directconnect/latest/UserGuide/Welcome.html#overview-components DX connection } the virtual interface will be created on" - }, - "customerAddress": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The peer IP address to use for customer's side of the virtual interface.\n\nDefault - randomly-generated by Amazon" - }, - "customerAsn": { - "description": "A Border Gateway Protocol (BGP) Autonomous System Number (ASN) for the customer side of the connection.", - "type": "number" - }, - "enableSiteLink": { - "description": "(OPTIONAL) Enable SiteLink for this virtual interface.\n\nDefault - false", - "type": "boolean" - }, - "interfaceName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the virtual interface. This name will show as the name of the resource in the AWS console and API." - }, - "jumboFrames": { - "description": "(OPTIONAL) Enable jumbo frames for the virtual interface.\n\nDefault - standard 1500 MTU frame size", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the virtual interface. This name is used as a logical reference for the resource in the accelerator." - }, - "ownerAccount": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the owning account of the DX connection." - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The region of the virtual interface." - }, - "tags": { - "description": "(OPTIONAL) An array of tags to apply to the virtual interface.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "type": { - "$ref": "#/definitions/DxVirtualInterfaceType", - "description": "The type of the virtual interface" - }, - "vlan": { - "description": "The virtual local area network (VLAN) tag to use for this virtual interface.", - "type": "number" - } - }, - "required": [ - "name", - "connectionId", - "customerAsn", - "interfaceName", - "ownerAccount", - "region", - "type", - "vlan" - ], - "type": "object" - }, - "IElbAccountIdsConfig": { - "additionalProperties": false, - "description": "An optional ELB root account ID", - "properties": { - "accountId": { - "$ref": "#/definitions/NonEmptyString" - }, - "region": { - "$ref": "#/definitions/NonEmptyString" - } - }, - "required": [ - "region", - "accountId" - ], - "type": "object" - }, - "IEndpointPolicyConfig": { - "additionalProperties": false, - "description": "Use this configuration to define VPC endpoint policies for your VPC gateway and interface endpoints.\nThe endpoint policy is a JSON policy document that controls which AWS principals can use the VPC\nendpoint to access the endpoint service.\n\nThe following example defines an endpoint policy named `Default` and references a path\nwhere a JSON policy document is stored:", - "properties": { - "document": { - "$ref": "#/definitions/NonEmptyString", - "description": "A file path for a JSON-formatted policy document." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the endpoint policy." - } - }, - "required": [ - "name", - "document" - ], - "type": "object" - }, - "IFirewallManagerNotificationChannelConfig": { - "additionalProperties": false, - "description": "An optional Firewall Manager Service Config", - "properties": { - "region": { - "$ref": "#/definitions/NonEmptyString", - "description": "Enables the FMS notification channel. Defaults to enabled." - }, - "snsTopic": { - "$ref": "#/definitions/NonEmptyString", - "description": "The SNS Topic Name to publish to." - } - }, - "required": [ - "snsTopic", - "region" - ], - "type": "object" - }, - "IFirewallManagerServiceConfig": { - "additionalProperties": false, - "description": "An optional Firewall Manager Service Config", - "properties": { - "delegatedAdminAccount": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly account name to deploy the FMS configuration" - }, - "notificationChannels": { - "description": "The FMS Notification Channel Configuration", - "items": { - "$ref": "#/definitions/IFirewallManagerNotificationChannelConfig" - }, - "type": "array" - } - }, - "required": [ - "delegatedAdminAccount" - ], - "type": "object" - }, - "IGatewayEndpointConfig": { - "additionalProperties": false, - "description": "Use this configuration to define gateway endpoints for your VPC.\nA gateway endpoint targets specific IP routes in an Amazon VPC route table,\nin the form of a prefix-list, used for traffic destined to Amazon DynamoDB\nor Amazon Simple Storage Service (Amazon S3).", - "properties": { - "defaultPolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the default policy for the gateway endpoints." - }, - "endpoints": { - "description": "An array of endpoints to create.", - "items": { - "$ref": "#/definitions/IGatewayEndpointServiceConfig" - }, - "type": "array" - } - }, - "required": [ - "defaultPolicy", - "endpoints" - ], - "type": "object" - }, - "IGatewayEndpointServiceConfig": { - "additionalProperties": false, - "description": "Use this configuration to define the service and endpoint policy for gateway endpoints.", - "properties": { - "applyPolicy": { - "description": "(OPTIONAL) Specify whether or not a policy is applied to the endpoint. By default, if no policy is specified in the `policy` property, a default policy is applied. Specifying this option as `false` will ensure no policy is applied to the endpoint. This property defaults to `true` if not specified.", - "type": "boolean" - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of a policy for the gateway endpoint. If left undefined, the default policy will be used." - }, - "service": { - "$ref": "#/definitions/GatewayEndpointType", - "description": "The name of the service to create the endpoint for" - }, - "serviceName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The full name of the service to create the endpoint for." - } - }, - "required": [ - "service" - ], - "type": "object" - }, - "IGwlbConfig": { - "additionalProperties": false, - "description": "Use to define Gateway Load Balancer configurations for the accelerator.\nGateway Load Balancers enable you to deploy, scale, and manage virtual appliances,\nsuch as firewalls, intrusion detection and prevention systems, and deep packet inspection\nsystems. It combines a transparent network gateway (that is, a single entry and exit point\nfor all traffic) and distributes traffic while scaling your virtual appliances with the demand.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Set an override for the account the Gateway Load Balancer is deployed to." - }, - "crossZoneLoadBalancing": { - "description": "(OPTIONAL) Whether to enable cross-zone load balancing.", - "type": "boolean" - }, - "deletionProtection": { - "description": "(OPTIONAL) Whether to enable deletion protection.", - "type": "boolean" - }, - "endpoints": { - "description": "An array of Gateway Load Balancer endpoint configurations.", - "items": { - "$ref": "#/definitions/IGwlbEndpointConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the Gateway Load Balancer." - }, - "subnets": { - "description": "An array of friendly names of subnets to deploy the Gateway Load Balancer to.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of CloudFormation tag objects.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "targetGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of a target group to forward traffic to" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC to deploy the Gateway Load Balancer to." - } - }, - "required": [ - "name", - "endpoints", - "subnets", - "vpc" - ], - "type": "object" - }, - "IGwlbEndpointConfig": { - "additionalProperties": false, - "description": "Use this configuration to define endpoints for your Gateway Load Balancer.\nGateway Load Balancers use Gateway Load Balancer endpoints to securely exchange\ntraffic across VPC boundaries. A Gateway Load Balancer endpoint is a VPC endpoint\nthat provides private connectivity between virtual appliances in the service provider\nVPC and application servers in the service consumer VPC.\n\nThe following example creates two Gateway Load Balancer endpoints,\n`Endpoint-A` and `Endpoint-B`. The endpoints are created in subnets named\n`Network-Inspection-A` and `Network-Inspection-B`, respectively, in the VPC named\n`Network-Inspection`.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account to deploy the endpoint to." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the Gateway Load Balancer endpoint." - }, - "subnet": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the subnet to deploy the Gateway Load Balancer endpoint to." - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC to deploy the Gateway Load Balancer endpoint to." - } - }, - "required": [ - "name", - "account", - "subnet", - "vpc" - ], - "type": "object" - }, - "IInterfaceEndpointConfig": { - "additionalProperties": false, - "description": "Use this configuration to define interface endpoints for your VPC.\nInterface endpoints powered by AWS PrivateLink to connect your VPC to AWS services as if they were in your VPC, without the use of an internet gateway.", - "properties": { - "allowedCidrs": { - "description": "(OPTIONAL) An array of source CIDRs allowed to communicate with the endpoints.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "central": { - "description": "(OPTIONAL) Enable to define interface endpoints as centralized endpoints.", - "type": "boolean" - }, - "defaultPolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the default policy for the interface endpoints." - }, - "endpoints": { - "description": "An array of VPC interface endpoint services to be deployed.", - "items": { - "$ref": "#/definitions/IInterfaceEndpointServiceConfig" - }, - "type": "array" - }, - "subnets": { - "description": "An array of the friendly names of VPC subnets for the endpoints to be deployed.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the private hosted zones associated with the VPC Interface endpoints.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "defaultPolicy", - "endpoints", - "subnets" - ], - "type": "object" - }, - "IInterfaceEndpointServiceConfig": { - "additionalProperties": false, - "description": "Use this configuration to define the service and endpoint policy for gateway endpoints.", - "properties": { - "applyPolicy": { - "description": "(OPTIONAL) Specify whether or not a policy is applied to the endpoint. By default, if no policy is specified in the `policy` property, a default policy is applied. Specifying this option as `false` will ensure no policy is applied to the endpoint. This property defaults to `true` if not specified.", - "type": "boolean" - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of a policy for the interface endpoint. If left undefined, the default policy will be used." - }, - "securityGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Apply the provided security group for this interface endpoint." - }, - "service": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the service to create the endpoint for." - }, - "serviceName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The full name of the service to create the endpoint for." - } - }, - "required": [ - "service" - ], - "type": "object" - }, - "IIpamAllocationConfig": { - "additionalProperties": false, - "description": "Use this configuration to dynamically assign a VPC or subnet CIDR from an IPAM pool.", - "properties": { - "ipamPoolName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The IPAM pool name to request the allocation from." - }, - "netmaskLength": { - "description": "The subnet mask length to request.", - "type": "number" - } - }, - "required": [ - "ipamPoolName", - "netmaskLength" - ], - "type": "object" - }, - "IIpamConfig": { - "additionalProperties": false, - "description": "Use this configuration to define an AWS-managed VPC IPAM.\nIPAM is a feature that makes it easier for you to plan, track, and monitor IP addresses for your AWS workloads.\n\nThe following example defines an IPAM that is capable of operating in the us-east-1 and us-west-2 regions:", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description for the IPAM." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the IPAM." - }, - "operatingRegions": { - "description": "(OPTIONAL) An array of regions that the IPAM will manage.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "pools": { - "description": "An optional array of IPAM pool configurations to create under the IPAM.", - "items": { - "$ref": "#/definitions/IIpamPoolConfig" - }, - "type": "array" - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The region to deploy the IPAM." - }, - "scopes": { - "description": "(OPTIONAL) An array of IPAM scope configurations to create under the IPAM.", - "items": { - "$ref": "#/definitions/IIpamScopeConfig" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the IPAM.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "region" - ], - "type": "object" - }, - "IIpamPoolConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom IPAM pools for your VPCs. A pool is a collection of contiguous\nIP address ranges. IPAM pools enable you to organize your IP addresses according to your routing and security needs.", - "properties": { - "addressFamily": { - "$ref": "#/definitions/IpVersionType", - "description": "The address family for the IPAM pool." - }, - "allocationDefaultNetmaskLength": { - "description": "(OPTIONAL) The default netmask length of IPAM allocations for this pool.", - "type": "number" - }, - "allocationMaxNetmaskLength": { - "description": "(OPTIONAL) The maximum netmask length of IPAM allocations for this pool.", - "type": "number" - }, - "allocationMinNetmaskLength": { - "description": "(OPTIONAL) The minimum netmask length of IPAM allocations for this pool.", - "type": "number" - }, - "allocationResourceTags": { - "description": "(OPTIONAL) An array of tags that are required for resources that use CIDRs from this IPAM pool.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "autoImport": { - "description": "(OPTIONAL) If set to `true`, IPAM will continuously look for resources within the CIDR range of this pool and automatically import them as allocations into your IPAM.", - "type": "boolean" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description for the IPAM pool." - }, - "locale": { - "$ref": "#/definitions/Region", - "description": "(OPTIONAL) The AWS Region where you want to make an IPAM pool available for allocations." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the IPAM pool." - }, - "provisionedCidrs": { - "description": "An array of CIDR ranges to provision for the IPAM pool.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "publiclyAdvertisable": { - "description": "(OPTIONAL) Determines if a pool is publicly advertisable.", - "type": "boolean" - }, - "scope": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of the IPAM scope to assign the IPAM pool to." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "sourceIpamPool": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of the source IPAM pool to create this IPAM pool from." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the IPAM pool.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IIpamScopeConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom private IPAM scopes for your VPCs.\nAn IPAM scope is the highest-level container for an IPAM. Within scopes, pools can be created.\nCustom IPAM scopes can be used to create pools and manage resources that use the same IP space.", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Description for the IPAM scope." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the IPAM scope." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the IPAM scope.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "ILifecycleRule": { - "additionalProperties": false, - "description": "S3 bucket life cycle rules object.", - "properties": { - "abortIncompleteMultipartUpload": { - "description": "Specifies a lifecycle rule that aborts incomplete multipart uploads to an Amazon S3 bucket.", - "type": "number" - }, - "enabled": { - "description": "Whether this rule is enabled.", - "type": "boolean" - }, - "expiration": { - "description": "Indicates the number of days after creation when objects are deleted from Amazon S3 and Amazon Glacier.", - "type": "number" - }, - "expiredObjectDeleteMarker": { - "description": "Indicates whether Amazon S3 will remove a delete marker with no noncurrent versions. If set to true, the delete marker will be expired.", - "type": "boolean" - }, - "id": { - "description": "Friendly name for the rule. Rule name must be unique.", - "type": "string" - }, - "noncurrentVersionExpiration": { - "description": "Time between when a new version of the object is uploaded to the bucket and when old versions of the object expire.", - "type": "number" - }, - "noncurrentVersionTransitions": { - "description": "One or more transition rules that specify when non-current objects transition to a specified storage class.", - "items": { - "$ref": "#/definitions/ITransition" - }, - "type": "array" - }, - "prefix": { - "$ref": "#/definitions/NonEmptyString", - "default": "- Rule applies to all objects", - "description": "Object key prefix that identifies one or more objects to which this rule applies." - }, - "transitions": { - "description": "One or more transition rules that specify when an object transitions to a specified storage class.", - "items": { - "$ref": "#/definitions/ITransition" - }, - "type": "array" - } - }, - "type": "object" - }, - "ILoadBalancersConfig": { - "additionalProperties": false, - "description": "Use this configuration to define Application Load Balancers (ALBs) or\nNetwork Load Balancers (NLBs) to be deployed in the specified VPC subnets.", - "properties": { - "applicationLoadBalancers": { - "description": "(OPTIONAL) An array of Application Load Balancer (ALB) configurations. Use this property to define ALBs to be deployed in the specified VPC subnets.", - "items": { - "$ref": "#/definitions/IApplicationLoadBalancerConfig" - }, - "type": "array" - }, - "networkLoadBalancers": { - "description": "(OPTIONAL) An array of Network Load Balancer (NLB) configurations. Use this property to define NLBs to be deployed in the specified VPC subnets.", - "items": { - "$ref": "#/definitions/INetworkLoadBalancerConfig" - }, - "type": "array" - } - }, - "type": "object" - }, - "ILocalGatewayConfig": { - "additionalProperties": false, - "description": "Use this configuration to reference existing local gateways for your Outposts.\nThe local gateway for your Outpost rack enables connectivity from your Outpost subnets to\nall AWS services that are available in the parent Region, in the same way that you access them from an Availability Zone subnet.", - "properties": { - "id": { - "$ref": "#/definitions/NonEmptyString", - "description": "The id for the Local Gateway" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Local Gateway" - }, - "routeTables": { - "description": "The route tables for the Local Gateway", - "items": { - "$ref": "#/definitions/ILocalGatewayRouteTableConfig" - }, - "type": "array" - } - }, - "required": [ - "name", - "id", - "routeTables" - ], - "type": "object" - }, - "ILocalGatewayRouteTableConfig": { - "additionalProperties": false, - "description": "Use this configuration to reference route tables for your Outposts local gateway.\nOutpost subnet route tables on a rack can include a route to your on-premises network.\nThe local gateway routes this traffic for low latency routing to the on-premises network.", - "properties": { - "id": { - "$ref": "#/definitions/NonEmptyString", - "description": "The id for the Route Table" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Route Table" - } - }, - "required": [ - "name", - "id" - ], - "type": "object" - }, - "INatGatewayConfig": { - "additionalProperties": false, - "description": "Use this configuration to define AWS-managed NAT Gateways for your VPC.\nYou can use a NAT gateway so that instances in a private subnet can connect to services outside your VPCs.", - "properties": { - "allocationId": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The allocation ID of the Elastic IP address that's associated with the NAT gateway. This allocation ID must exist in the target account the NAT gateway is deployed to." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the NAT Gateway." - }, - "private": { - "description": "(OPTIONAL) Set `true` to define a NAT gateway with private connectivity type", - "type": "boolean" - }, - "subnet": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the subnet for the NAT Gateway to be deployed." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the NAT Gateway.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "subnet" - ], - "type": "object" - }, - "INetworkAclConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom network ACLs for your VPC.\nA network ACL allows or denies specific inbound or outbound traffic at the subnet level.\nNetwork ACLs are stateless, which means that responses to allowed inbound traffic are subject\nto the rules for outbound traffic (and vice versa).\n\nThe following example shows an inbound and outbound rule that would allow\ninbound SSH traffic from the CIDR range 10.0.0.0/16.", - "properties": { - "inboundRules": { - "description": "(OPTIONAL) A list of inbound rules to define for the Network ACL", - "items": { - "$ref": "#/definitions/INetworkAclInboundRuleConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the Network ACL." - }, - "outboundRules": { - "description": "(OPTIONAL) A list of outbound rules to define for the Network ACL", - "items": { - "$ref": "#/definitions/INetworkAclOutboundRuleConfig" - }, - "type": "array" - }, - "subnetAssociations": { - "description": "A list of subnets to associate with the Network ACL", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) A list of tags to attach to the Network ACL", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "subnetAssociations" - ], - "type": "object" - }, - "INetworkAclInboundRuleConfig": { - "additionalProperties": false, - "description": "Use this configuration to define inbound rules for your network ACLs.\nAn inbound rule allows or denies specific inbound traffic at the subnet level.\n\nThe following example allows inbound SSH traffic from source CIDR 10.0.0.0/16:", - "properties": { - "action": { - "$ref": "#/definitions/AllowDeny", - "description": "The action for the network ACL rule." - }, - "fromPort": { - "description": "The port to start from in the network ACL rule.", - "type": "number" - }, - "protocol": { - "description": "The {@link https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml IANA protocol number } for the network ACL rule. You may also specify -1 for all protocols.", - "type": "number" - }, - "rule": { - "description": "The rule ID number for the rule.", - "type": "number" - }, - "source": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "$ref": "#/definitions/INetworkAclSubnetSelection" - } - ], - "description": "The source of the network ACL rule." - }, - "toPort": { - "description": "The port to end with in the network ACL rule.", - "type": "number" - } - }, - "required": [ - "rule", - "protocol", - "fromPort", - "toPort", - "action", - "source" - ], - "type": "object" - }, - "INetworkAclOutboundRuleConfig": { - "additionalProperties": false, - "description": "Use this configuration to define outbound rules for your network ACLs.\nAn outbound rule allows or denies specific outbound traffic at the subnet level.\n\nThe following example allows outbound TCP traffic in the ephemeral port ranges to destination CIDR 10.0.0.0/16:", - "properties": { - "action": { - "$ref": "#/definitions/AllowDeny", - "description": "The action for the network ACL rule." - }, - "destination": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "$ref": "#/definitions/INetworkAclSubnetSelection" - } - ], - "description": "The destination of the network ACL rule." - }, - "fromPort": { - "description": "The port to start from in the network ACL rule.", - "type": "number" - }, - "protocol": { - "description": "The {@link https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml IANA protocol number } for the network ACL rule. You may also specify -1 for all protocols.", - "type": "number" - }, - "rule": { - "description": "The rule ID number for the rule.", - "type": "number" - }, - "toPort": { - "description": "The port to end with in the network ACL rule.", - "type": "number" - } - }, - "required": [ - "rule", - "protocol", - "fromPort", - "toPort", - "action", - "destination" - ], - "type": "object" - }, - "INetworkAclSubnetSelection": { - "additionalProperties": false, - "description": "Network ACL subnet selection configuration.\nUse this configuration to dynamically reference a subnet as a source/destination for a network ACL.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account of the subnet." - }, - "ipv6": { - "description": "(OPTIONAL) Indicates whether to target the IPv6 CIDR associated with a subnet.", - "type": "boolean" - }, - "region": { - "$ref": "#/definitions/Region", - "description": "(OPTIONAL) The region that the subnet is located in." - }, - "subnet": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the subnet." - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC of the subnet." - } - }, - "required": [ - "vpc", - "subnet" - ], - "type": "object" - }, - "INetworkConfig": { - "additionalProperties": false, - "description": "Network Configuration. Used to define a network configuration for the accelerator.", - "properties": { - "accountVpcEndpointIds": { - "additionalProperties": { - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "description": "A map between account Id and all the VPC Endpoint IDs in the account.\n\nCurrently, the dynamic values will only be loaded in FinalizeStack for SCP finalization. Only the account VPC Endpoints referred by ACCEL_LOOKUP in SCPs will be loaded.", - "type": "object" - }, - "accountVpcIds": { - "additionalProperties": { - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "description": "A map between account Id and all the VPC IDs in the account.\n\nCurrently, the dynamic values will only be loaded in FinalizeStack for SCP finalization. Only the account VPCs referred in SCPs by ACCEL_LOOKUP will be loaded.", - "type": "object" - }, - "centralNetworkServices": { - "$ref": "#/definitions/ICentralNetworkServicesConfig", - "description": "An optional Central Network services configuration." - }, - "certificates": { - "description": "Certificate manager configuration", - "items": { - "$ref": "#/definitions/ICertificateConfig" - }, - "type": "array" - }, - "customerGateways": { - "description": "An array of Customer Gateway configurations.", - "items": { - "$ref": "#/definitions/ICustomerGatewayConfig" - }, - "type": "array" - }, - "defaultVpc": { - "$ref": "#/definitions/IDefaultVpcsConfig", - "description": "A default VPC configuration." - }, - "dhcpOptions": { - "description": "An optional list of DHCP options set configurations.", - "items": { - "$ref": "#/definitions/IDhcpOptsConfig" - }, - "type": "array" - }, - "directConnectGateways": { - "description": "An optional array of Direct Connect Gateway configurations.", - "items": { - "$ref": "#/definitions/IDxGatewayConfig" - }, - "type": "array" - }, - "elbAccountIds": { - "description": "An optional ELB root account ID", - "items": { - "$ref": "#/definitions/IElbAccountIdsConfig" - }, - "type": "array" - }, - "endpointPolicies": { - "description": "A list of VPC configurations. An array of VPC endpoint policies.", - "items": { - "$ref": "#/definitions/IEndpointPolicyConfig" - }, - "type": "array" - }, - "firewallManagerService": { - "$ref": "#/definitions/IFirewallManagerServiceConfig", - "description": "Firewall manager service configuration" - }, - "homeRegion": { - "$ref": "#/definitions/Region", - "description": "Accelerator home region name." - }, - "prefixLists": { - "description": "An optional list of prefix list set configurations.", - "items": { - "$ref": "#/definitions/IPrefixListConfig" - }, - "type": "array" - }, - "transitGatewayConnects": { - "description": "An array of Transit Gateway Connect configurations.", - "items": { - "$ref": "#/definitions/ITransitGatewayConnectConfig" - }, - "type": "array" - }, - "transitGatewayPeering": { - "description": "Transit Gateway peering configuration.", - "items": { - "$ref": "#/definitions/ITransitGatewayPeeringConfig" - }, - "type": "array" - }, - "transitGateways": { - "description": "An array of Transit Gateway configurations.", - "items": { - "$ref": "#/definitions/ITransitGatewayConfig" - }, - "type": "array" - }, - "vpcFlowLogs": { - "$ref": "#/definitions/IVpcFlowLogsConfig", - "description": "A VPC flow logs configuration." - }, - "vpcPeering": { - "description": "An optional list of VPC peering configurations", - "items": { - "$ref": "#/definitions/IVpcPeeringConfig" - }, - "type": "array" - }, - "vpcTemplates": { - "description": "An optional list of VPC template configurations", - "items": { - "$ref": "#/definitions/IVpcTemplatesConfig" - }, - "type": "array" - }, - "vpcs": { - "description": "An array of VPC configurations.", - "items": { - "$ref": "#/definitions/IVpcConfig" - }, - "type": "array" - } - }, - "required": [ - "defaultVpc", - "endpointPolicies", - "transitGateways", - "vpcs" - ], - "type": "object" - }, - "INetworkLoadBalancerConfig": { - "additionalProperties": false, - "description": "Network Load Balancer configuration.", - "properties": { - "crossZoneLoadBalancing": { - "description": "Cross Zone load balancing for Network Load Balancer.", - "type": "boolean" - }, - "deletionProtection": { - "description": "Deletion protection for Network Load Balancer.", - "type": "boolean" - }, - "listeners": { - "description": "Listeners for Network Load Balancer.", - "items": { - "$ref": "#/definitions/INlbListenerConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name for Network Load Balancer." - }, - "scheme": { - "$ref": "#/definitions/LoadBalancerSchemeEnum", - "description": "Load Balancer scheme. If undefined, the default of {@link https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateLoadBalancer.html ELBv2 CreateLoadBalancer API } is used." - }, - "subnets": { - "description": "Subnets to launch the Network Load Balancer in.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "subnets", - "name" - ], - "type": "object" - }, - "INfwConfig": { - "additionalProperties": false, - "description": "Use this configuration to define Network Firewalls in your environment.\nAWS Network Firewall is a stateful, managed, network firewall and intrusion\ndetection and prevention service for your virtual private cloud (VPC) that\nyou create in Amazon Virtual Private Cloud (Amazon VPC).\nWith Network Firewall, you can filter traffic at the perimeter of your VPC.\nThis includes filtering traffic going to and coming from an internet gateway,\nNAT gateway, or over VPN or AWS Direct Connect.\n\nThe following example creates a simple Network Firewall rule group, policy,\nand firewall. The policy and rule group are shared with the entire organization.\nThe firewall endpoints are created in subnets named `Subnet-A` and `Subnet-B`\nin the VPC named `Network-Inspection`.", - "properties": { - "firewalls": { - "description": "An array of Network Firewall firewall configurations.", - "items": { - "$ref": "#/definitions/INfwFirewallConfig" - }, - "type": "array" - }, - "policies": { - "description": "An array of Network Firewall policy configurations.", - "items": { - "$ref": "#/definitions/INfwFirewallPolicyConfig" - }, - "type": "array" - }, - "rules": { - "description": "An array of Network Firewall rule group configurations.", - "items": { - "$ref": "#/definitions/INfwRuleGroupConfig" - }, - "type": "array" - } - }, - "required": [ - "firewalls", - "policies", - "rules" - ], - "type": "object" - }, - "INfwFirewallConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a Network Firewall firewall.\nAn AWS Network Firewall firewall connects a firewall policy,\nwhich defines network traffic monitoring and filtering behavior,\nto the VPC that you want to protect. The firewall configuration\nincludes specifications for the Availability Zones and subnets\nwhere the firewall endpoints are placed. It also defines high-level\nsettings like the firewall logging configuration and tagging on the AWS firewall resource.", - "properties": { - "deleteProtection": { - "description": "(OPTIONAL) Enable for deletion protection on the firewall.", - "type": "boolean" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description for the firewall." - }, - "firewallPolicy": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the Network Firewall policy." - }, - "firewallPolicyChangeProtection": { - "description": "(OPTIONAL) Enable to disallow firewall policy changes.", - "type": "boolean" - }, - "loggingConfiguration": { - "description": "(OPTIONAL) An array of Network Firewall logging configurations.", - "items": { - "$ref": "#/definitions/INfwLoggingConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the firewall." - }, - "subnetChangeProtection": { - "description": "(OPTIONAL) Enable to disallow firewall subnet changes.", - "type": "boolean" - }, - "subnets": { - "description": "An array of the friendly names of subnets to deploy Network Firewall to.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the firewall.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC to deploy Network Firewall to." - } - }, - "required": [ - "name", - "firewallPolicy", - "subnets", - "vpc" - ], - "type": "object" - }, - "INfwFirewallPolicyConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a Network Firewall policy.\nAn AWS Network Firewall firewall policy defines the monitoring and protection behavior\nfor a firewall. The details of the behavior are defined in the rule groups that you add\nto your policy, and in some policy default settings.", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description for the policy." - }, - "firewallPolicy": { - "$ref": "#/definitions/INfwFirewallPolicyPolicyConfig", - "description": "Use this property to define specific behaviors and rule groups to associate with the policy." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the policy." - }, - "regions": { - "description": "The regions to deploy the policy to.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the policy.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "firewallPolicy", - "regions" - ], - "type": "object" - }, - "INfwFirewallPolicyPolicyConfig": { - "additionalProperties": false, - "description": "Use this configuration to define how the Network Firewall policy will behave.\nAn AWS Network Firewall firewall policy defines the monitoring and protection behavior\nfor a firewall. The details of the behavior are defined in the rule groups that you add\nto your policy, and in some policy default settings.", - "properties": { - "statefulDefaultActions": { - "description": "(OPTIONAL) An array of default actions to take on packets evaluated by the stateful engine.", - "items": { - "$ref": "#/definitions/NfwStatefulDefaultActionType" - }, - "type": "array" - }, - "statefulEngineOptions": { - "$ref": "#/definitions/NfwStatefulRuleOptionsType", - "description": "(OPTIONAL) Define how the stateful engine will evaluate packets." - }, - "statefulRuleGroups": { - "description": "{OPTIONAL) An array of Network Firewall stateful rule group reference configurations.", - "items": { - "$ref": "#/definitions/INfwStatefulRuleGroupReferenceConfig" - }, - "type": "array" - }, - "statelessCustomActions": { - "description": "(OPTIONAL) An array of Network Firewall custom action configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceCustomActionConfig" - }, - "type": "array" - }, - "statelessDefaultActions": { - "description": "An array of default actions to take on packets evaluated by the stateless engine.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/NfwStatelessRuleActionType" - }, - { - "$ref": "#/definitions/NonEmptyString" - } - ] - }, - "type": "array" - }, - "statelessFragmentDefaultActions": { - "description": "An array of default actions to take on fragmented packets.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/NfwStatelessRuleActionType" - }, - { - "$ref": "#/definitions/NonEmptyString" - } - ] - }, - "type": "array" - }, - "statelessRuleGroups": { - "description": "(OPTIONAL) An array of Network Firewall stateless rule group reference configurations.", - "items": { - "$ref": "#/definitions/INfwStatelessRuleGroupReferenceConfig" - }, - "type": "array" - } - }, - "required": [ - "statelessDefaultActions", - "statelessFragmentDefaultActions" - ], - "type": "object" - }, - "INfwLoggingConfig": { - "additionalProperties": false, - "description": "Use this configuration to define logging destinations for Network Firewall.\nYou can configure AWS Network Firewall logging for your firewall's stateful engine.\nLogging gives you detailed information about network traffic, including the time that\nthe stateful engine received a packet, detailed information about the packet, and any\nstateful rule action taken against the packet. The logs are published to the log destination\nthat you've configured, where you can retrieve and view them.", - "properties": { - "destination": { - "$ref": "#/definitions/LogDestinationType", - "description": "The destination service to log to." - }, - "type": { - "$ref": "#/definitions/NfwLogType", - "description": "The type of actions to log." - } - }, - "required": [ - "destination", - "type" - ], - "type": "object" - }, - "INfwRuleGroupConfig": { - "additionalProperties": false, - "description": "Use this configuration to define stateful and stateless rule groups for Network Firewall.\nAn AWS Network Firewall rule group is a reusable set of criteria for inspecting and handling network traffic.\nYou add one or more rule groups to a firewall policy as part of policy configuration.", - "properties": { - "capacity": { - "description": "The capacity of the rule group.", - "type": "number" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description for the rule group." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the rule group." - }, - "regions": { - "description": "The regions to deploy the rule group to.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "ruleGroup": { - "$ref": "#/definitions/INfwRuleGroupRuleConfig", - "description": "(OPTIONAL) A Network Firewall rule configuration." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the rule group.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "type": { - "$ref": "#/definitions/NfwRuleType", - "description": "The type of rules in the rule group." - } - }, - "required": [ - "name", - "regions", - "capacity", - "type" - ], - "type": "object" - }, - "INfwRuleGroupRuleConfig": { - "additionalProperties": false, - "description": "Network Firewall rule group rule configuration.\nUsed to define rules for a Network Firewall rule group.", - "properties": { - "ruleVariables": { - "$ref": "#/definitions/INfwRuleVariableConfig", - "description": "A Network Firewall rule variable configuration." - }, - "rulesSource": { - "$ref": "#/definitions/INfwRuleSourceConfig", - "description": "A Network Firewall rule source configuration." - }, - "statefulRuleOptions": { - "$ref": "#/definitions/NfwStatefulRuleOptionsType", - "description": "A stateful rule option for the rule group." - } - }, - "required": [ - "rulesSource" - ], - "type": "object" - }, - "INfwRuleSourceConfig": { - "additionalProperties": false, - "description": "Network Firewall rule source configuration.\nUse this configuration to define stateful and/or stateless rules for your Network Firewall.\nThe following rules sources are supported:\n- File with list of Suricata-compatible rules\n- Domain list\n- Single Suricata-compatible rule\n- Stateful rule in IP header format\n- Stateless rules and custom actions", - "properties": { - "rulesFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Suricata rules file. Use this property to define a Suricata-compatible rules file for Network Firewall." - }, - "rulesSourceList": { - "$ref": "#/definitions/INfwRuleSourceListConfig", - "description": "(OPTIONAL) A Network Firewall rule source list configuration. Use this property to define a domain list for Network Firewall." - }, - "rulesString": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A Suricata-compatible stateful rule string. Use this property to define a single Suricata-compatible rule for Network Firewall." - }, - "statefulRules": { - "description": "(OPTIONAL) An array of Network Firewall stateful rule IP header configurations. Use this property to define a stateful rule in IP header format for Network Firewall.", - "items": { - "$ref": "#/definitions/INfwRuleSourceStatefulRuleConfig" - }, - "type": "array" - }, - "statelessRulesAndCustomActions": { - "$ref": "#/definitions/INfwStatelessRulesAndCustomActionsConfig", - "description": "(OPTIONAL) A Network Firewall stateless rules and custom action configuration. Use this property to define stateless rules and custom actions for Network Firewall." - } - }, - "type": "object" - }, - "INfwRuleSourceCustomActionConfig": { - "additionalProperties": false, - "description": "Use this configuration to define to define custom actions for Network Firewall.\nYou can optionally specify a named custom action to apply.\nFor this action, Network Firewall assigns a dimension to Amazon CloudWatch metrics\nwith the name set to CustomAction and a value that you specify.", - "properties": { - "actionDefinition": { - "$ref": "#/definitions/INfwRuleSourceCustomActionDefinitionConfig", - "description": "A Network Firewall custom action definition configuration." - }, - "actionName": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the custom action." - } - }, - "required": [ - "actionDefinition", - "actionName" - ], - "type": "object" - }, - "INfwRuleSourceCustomActionDefinitionConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom CloudWatch metrics for Network Firewall.\nYou can optionally specify a named custom action to apply.\nFor this action, Network Firewall assigns a dimension to Amazon CloudWatch metrics\nwith the name set to CustomAction and a value that you specify.", - "properties": { - "publishMetricAction": { - "$ref": "#/definitions/INfwRuleSourceCustomActionDimensionConfig", - "description": "A Network Firewall custom action dimensions configuration." - } - }, - "required": [ - "publishMetricAction" - ], - "type": "object" - }, - "INfwRuleSourceCustomActionDimensionConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom action dimensions to log in CloudWatch metrics.\nYou can optionally specify a named custom action to apply.\nFor this action, Network Firewall assigns a dimension to Amazon CloudWatch metrics\nwith the name set to CustomAction and a value that you specify.", - "properties": { - "dimensions": { - "description": "An array of values of the custom metric dimensions to log.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "dimensions" - ], - "type": "object" - }, - "INfwRuleSourceListConfig": { - "additionalProperties": false, - "description": "Use this configuration to define DNS domain allow and deny lists for Network Firewall.\nDomain lists allow you to configure domain name filtering for your Network Firewall.", - "properties": { - "generatedRulesType": { - "$ref": "#/definitions/NfwGeneratedRulesType", - "description": "The type of rules to generate from the source list." - }, - "targetTypes": { - "description": "An array of protocol types to inspect.", - "items": { - "$ref": "#/definitions/NfwTargetType" - }, - "type": "array" - }, - "targets": { - "description": "An array of target domain names.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "generatedRulesType", - "targets", - "targetTypes" - ], - "type": "object" - }, - "INfwRuleSourceStatefulRuleConfig": { - "additionalProperties": false, - "description": "Use this configuration to define stateful rules for Network Firewall in an IP packet header format.\nThis header format can be used instead of Suricata-compatible rules to define your stateful firewall\nfiltering behavior.", - "properties": { - "action": { - "$ref": "#/definitions/NfwStatefulRuleActionType", - "description": "The action type for the stateful rule." - }, - "header": { - "$ref": "#/definitions/INfwRuleSourceStatefulRuleHeaderConfig", - "description": "A Network Firewall stateful rule header configuration." - }, - "ruleOptions": { - "description": "An array of Network Firewall stateful rule options configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceStatefulRuleOptionsConfig" - }, - "type": "array" - } - }, - "required": [ - "action", - "header", - "ruleOptions" - ], - "type": "object" - }, - "INfwRuleSourceStatefulRuleHeaderConfig": { - "additionalProperties": false, - "description": "Use this configuration to define stateful rules for Network Firewall in an IP packet header format.\nThis header format can be used instead of Suricata-compatible rules to define your stateful firewall\nfiltering behavior.", - "properties": { - "destination": { - "$ref": "#/definitions/NonEmptyString", - "description": "The destination CIDR range to inspect for." - }, - "destinationPort": { - "$ref": "#/definitions/NonEmptyString", - "description": "The destination port or port range to inspect." - }, - "direction": { - "$ref": "#/definitions/NfwStatefulRuleDirectionType", - "description": "The direction of the traffic flow to inspect." - }, - "protocol": { - "$ref": "#/definitions/NfwStatefulRuleProtocolType", - "description": "The protocol to inspect." - }, - "source": { - "$ref": "#/definitions/NonEmptyString", - "description": "The source CIDR range to inspect for." - }, - "sourcePort": { - "$ref": "#/definitions/NonEmptyString", - "description": "The source port or port range to inspect." - } - }, - "required": [ - "destination", - "destinationPort", - "direction", - "protocol", - "source", - "sourcePort" - ], - "type": "object" - }, - "INfwRuleSourceStatefulRuleOptionsConfig": { - "additionalProperties": false, - "description": "Network Firewall stateful rule options configuration.\nUse this configuration to specify keywords and setting metadata for stateful rules.", - "properties": { - "keyword": { - "$ref": "#/definitions/NonEmptyString", - "description": "A Suricata-compatible keyword." - }, - "settings": { - "description": "An array of values for the keyword.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "keyword" - ], - "type": "object" - }, - "INfwRuleSourceStatelessMatchAttributesConfig": { - "additionalProperties": false, - "description": "Use this configuration to define stateless rule match attributes for Network Firewall.\nTo be a match, a packet must satisfy all of the match settings in the rule.", - "properties": { - "destinationPorts": { - "description": "(OPTIONAL) An array of Network Firewall stateless port range configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceStatelessPortRangeConfig" - }, - "type": "array" - }, - "destinations": { - "description": "(OPTIONAL) An array of destination CIDR ranges to inspect for.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "protocols": { - "description": "(OPTIONAL) An array of IP protocol numbers to inspect for.", - "items": { - "type": "number" - }, - "type": "array" - }, - "sourcePorts": { - "description": "(OPTIONAL) An array of Network Firewall stateless port range configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceStatelessPortRangeConfig" - }, - "type": "array" - }, - "sources": { - "description": "(OPTIONAL) An array of source CIDR ranges to inspect for.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tcpFlags": { - "description": "(OPTIONAL) An array of Network Firewall stateless TCP flag configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceStatelessTcpFlagsConfig" - }, - "type": "array" - } - }, - "type": "object" - }, - "INfwRuleSourceStatelessPortRangeConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a port range in stateless rules.", - "properties": { - "fromPort": { - "description": "The port to start from in the range.", - "type": "number" - }, - "toPort": { - "description": "The port to end with in the range.", - "type": "number" - } - }, - "required": [ - "fromPort", - "toPort" - ], - "type": "object" - }, - "INfwRuleSourceStatelessRuleConfig": { - "additionalProperties": false, - "description": "Use this configuration to define stateless rule for your Network Firewall.\nNetwork Firewall supports the standard stateless 5-tuple rule specification\nfor network traffic inspection. When Network Firewall finds a match between\n a rule's inspection criteria and a packet, we say that the packet matches\nthe rule and its rule group, and Network Firewall applies the rule's specified action to the packet.", - "properties": { - "priority": { - "description": "The priority number for the rule.", - "type": "number" - }, - "ruleDefinition": { - "$ref": "#/definitions/INfwRuleSourceStatelessRuleDefinitionConfig", - "description": "A Network Firewall stateless rule definition configuration." - } - }, - "required": [ - "priority", - "ruleDefinition" - ], - "type": "object" - }, - "INfwRuleSourceStatelessRuleDefinitionConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a stateless rule definition for your Network Firewall.", - "properties": { - "actions": { - "description": "An array of actions to take using the stateless rule engine.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "$ref": "#/definitions/NfwStatelessRuleActionType" - } - ] - }, - "type": "array" - }, - "matchAttributes": { - "$ref": "#/definitions/INfwRuleSourceStatelessMatchAttributesConfig", - "description": "A Network Firewall stateless rule match attributes configuration." - } - }, - "required": [ - "actions", - "matchAttributes" - ], - "type": "object" - }, - "INfwRuleSourceStatelessTcpFlagsConfig": { - "additionalProperties": false, - "description": "Use this configuration to define TCP flags to inspect in stateless rules.\nOptional, standard TCP flag settings, which indicate which flags to inspect and the values to inspect for.", - "properties": { - "flags": { - "description": "An array of TCP flags.", - "items": { - "$ref": "#/definitions/NfwStatelessRuleTcpFlagType" - }, - "type": "array" - }, - "masks": { - "description": "The set of flags to consider in the inspection.", - "items": { - "$ref": "#/definitions/NfwStatelessRuleTcpFlagType" - }, - "type": "array" - } - }, - "required": [ - "flags", - "masks" - ], - "type": "object" - }, - "INfwRuleVariableConfig": { - "additionalProperties": false, - "description": "Use this configuration to define rule variable definitions for Network Firewall.\nRule variables can be used in Suricata-compatible and domain list rule definitions.\nThey are not supported in stateful rule IP header definitions.", - "properties": { - "ipSets": { - "anyOf": [ - { - "$ref": "#/definitions/INfwRuleVariableDefinitionConfig" - }, - { - "items": { - "$ref": "#/definitions/INfwRuleVariableDefinitionConfig" - }, - "type": "array" - } - ], - "description": "A Network Firewall rule variable definition configuration." - }, - "portSets": { - "anyOf": [ - { - "$ref": "#/definitions/INfwRuleVariableDefinitionConfig" - }, - { - "items": { - "$ref": "#/definitions/INfwRuleVariableDefinitionConfig" - }, - "type": "array" - } - ], - "description": "A Network Firewall rule variable definition configuration." - } - }, - "required": [ - "ipSets", - "portSets" - ], - "type": "object" - }, - "INfwRuleVariableDefinitionConfig": { - "additionalProperties": false, - "description": "Use this configuration to define rule variable definitions for Network Firewall.\nRule variables can be used in Suricata-compatible and domain list rule definitions.\nThey are not supported in stateful rule IP header definitions.", - "properties": { - "definition": { - "description": "An array of values for the rule variable.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the rule variable." - } - }, - "required": [ - "name", - "definition" - ], - "type": "object" - }, - "INfwStatefulRuleGroupReferenceConfig": { - "additionalProperties": false, - "description": "Network Firewall stateful rule group reference configuration.\nUse this configuration to reference a stateful rule group in a Network Firewall policy.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the rule group." - }, - "priority": { - "description": "(OPTIONAL) If using strict ordering, a priority number for the rule.", - "type": "number" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "INfwStatelessRuleGroupReferenceConfig": { - "additionalProperties": false, - "description": "Network Firewall stateless rule group reference configuration.\nUse this configuration to reference a stateless rule group in a Network Firewall policy.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the rule group." - }, - "priority": { - "description": "A priority number for the rule.", - "type": "number" - } - }, - "required": [ - "name", - "priority" - ], - "type": "object" - }, - "INfwStatelessRulesAndCustomActionsConfig": { - "additionalProperties": false, - "description": "Use this configuration to define stateless rules and custom actions for Network Firewall.", - "properties": { - "customActions": { - "description": "An array of Network Firewall custom action configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceCustomActionConfig" - }, - "type": "array" - }, - "statelessRules": { - "description": "An array of Network Firewall stateless rule configurations.", - "items": { - "$ref": "#/definitions/INfwRuleSourceStatelessRuleConfig" - }, - "type": "array" - } - }, - "required": [ - "statelessRules" - ], - "type": "object" - }, - "INlbListenerConfig": { - "additionalProperties": false, - "description": "Application Load Balancer listener config. Currently only action type of `forward`, `redirect` and `fixed-response` is allowed.", - "properties": { - "alpnPolicy": { - "$ref": "#/definitions/AlpnPolicyEnum", - "description": "Application-Layer Protocol Negotiation (ALPN) policy} for TLS encrypted traffic" - }, - "certificate": { - "$ref": "#/definitions/NonEmptyString", - "description": "ACM ARN of the certificate to be associated with the listener." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name for Listener." - }, - "port": { - "description": "Port where the traffic is directed to.", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/NlbProtocolEnum", - "description": "Protocol used for the traffic. The supported protocols are TCP, TLS, UDP, or TCP_UDP." - }, - "sslPolicy": { - "$ref": "#/definitions/SslPolicyNlbEnum", - "description": "SSL policy for TLS encrypted traffic" - }, - "targetGroup": { - "$ref": "#/definitions/NonEmptyString", - "description": "Target Group to direct the traffic to." - } - }, - "required": [ - "name", - "targetGroup" - ], - "type": "object" - }, - "INlbTargetType": { - "additionalProperties": false, - "description": "The codes to use when checking for a successful response from a target. If the protocol version is gRPC, these are gRPC codes. Otherwise, these are HTTP codes.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly Account Name where the NLB is deployed" - }, - "nlbName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Friendly name of the NLB" - }, - "region": { - "$ref": "#/definitions/NonEmptyString", - "description": "Region where the NLB is deployed" - } - }, - "required": [ - "account", - "region", - "nlbName" - ], - "type": "object" - }, - "IOutpostsConfig": { - "additionalProperties": false, - "description": "Use this configuration to reference Outposts that exist in your environment.\nAWS Outposts enables customers to build and run applications on premises using the same\nprogramming interfaces as in AWS Regions, while using local compute and storage resources\nfor lower latency and local data processing needs.", - "properties": { - "arn": { - "$ref": "#/definitions/NonEmptyString", - "description": "The ARN for the Outpost" - }, - "availabilityZone": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "type": "number" - } - ], - "description": "The availability zone where the Outpost resides" - }, - "localGateway": { - "$ref": "#/definitions/ILocalGatewayConfig", - "description": "The Local Gateway configuration for the Outpost" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Outpost" - } - }, - "required": [ - "name", - "arn", - "availabilityZone" - ], - "type": "object" - }, - "IPhase1Config": { - "additionalProperties": false, - "description": "Internet Key Exchange (IKE) Phase 1 tunnel options configuration.\nUse this configuration to restrict the permitted Diffie-Hellman group numbers, encryption algorithms, and integrity algorithms for IKE Phase 1 negotiations.\nYou may also modify the Phase 1 lifetime for the VPN tunnel.", - "properties": { - "dhGroups": { - "description": "(OPTIONAL) An array of permitted Diffie-Hellman group numbers used in the IKE Phase 1 for initial authentication.\n\nDefault - `[2, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]`", - "items": { - "$ref": "#/definitions/Phase1DhGroupType" - }, - "type": "array" - }, - "encryptionAlgorithms": { - "description": "(OPTIONAL) An array of encryption algorithms permitted for IKE Phase 1 negotiations.\n\nDefault - `[AES128, AES256, AES128-GCM-16, AES256-GCM-16]`", - "items": { - "$ref": "#/definitions/EncryptionAlgorithmType" - }, - "type": "array" - }, - "integrityAlgorithms": { - "description": "(OPTIONAL) An array of integrity algorithms permitted for IKE Phase 1 negotiations.\n\nDefault - `[SHA1, SHA2-256, SHA2-384, SHA2-512]`", - "items": { - "$ref": "#/definitions/IntegrityAlgorithmType" - }, - "type": "array" - }, - "lifetimeSeconds": { - "description": "(OPTIONAL) The IKE Phase 1 lifetime (in seconds) for the VPN tunnel.\n\nDefault: `28800` (8 hours)", - "type": "number" - } - }, - "type": "object" - }, - "IPhase2Config": { - "additionalProperties": false, - "description": "Internet Key Exchange (IKE) Phase 2 tunnel options configuration.\nUse this configuration to restrict the permitted Diffie-Hellman group numbers, encryption algorithms, and integrity algorithms for IKE Phase 2 negotiations.\nYou may also modify the Phase 2 lifetime for the VPN tunnel.", - "properties": { - "dhGroups": { - "description": "(OPTIONAL) An array of permitted Diffie-Hellman group numbers used in the IKE Phase 2 negotiations.\n\nDefault - `[2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]`", - "items": { - "$ref": "#/definitions/Phase2DhGroupType" - }, - "type": "array" - }, - "encryptionAlgorithms": { - "description": "(OPTIONAL) An array of encryption algorithms permitted for IKE Phase 2 negotiations.\n\nDefault - `[AES128, AES256, AES128-GCM-16, AES256-GCM-16]`", - "items": { - "$ref": "#/definitions/EncryptionAlgorithmType" - }, - "type": "array" - }, - "integrityAlgorithms": { - "description": "(OPTIONAL) An array of integrity algorithms permitted for IKE Phase 2 negotiations.\n\nDefault - `[SHA1, SHA2-256, SHA2-384, SHA2-512]`", - "items": { - "$ref": "#/definitions/IntegrityAlgorithmType" - }, - "type": "array" - }, - "lifetimeSeconds": { - "description": "(OPTIONAL) The IKE Phase 2 lifetime (in seconds) for the VPN tunnel.\n\nDefault: `3600` (1 hour)", - "type": "number" - } - }, - "type": "object" - }, - "IPrefixListConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom prefix lists for your environment.\nA managed prefix list is a set of one or more CIDR blocks.\nYou can use prefix lists to make it easier to configure and maintain your security groups and route tables.\n\nThe following example creates a prefix list named `accelerator-pl` that may contain up to 10 entries.\nThe prefix list is deployed to all accounts in the organization.", - "properties": { - "accounts": { - "description": "(DEPRECATED) An array of friendly names for the accounts the prefix list is deployed.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "addressFamily": { - "$ref": "#/definitions/IpAddressFamilyType", - "description": "The IP address family of the prefix list." - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Prefix List deployment targets" - }, - "entries": { - "description": "An array of CIDR entries for the prefix list.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "maxEntries": { - "description": "The maximum allowed entries in the prefix list.", - "type": "number" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the prefix list." - }, - "regions": { - "description": "(DEPRECATED) An array of region names for the prefix list to be deployed.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the prefix list.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "addressFamily", - "maxEntries", - "entries" - ], - "type": "object" - }, - "IPrefixListSourceConfig": { - "additionalProperties": false, - "description": "Prefix list security group source configuration.\nUse this configuration to define a custom prefix list as a source in a security group rule.", - "properties": { - "prefixLists": { - "description": "An array of the friendly names of prefix lists to reference.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "prefixLists" - ], - "type": "object" - }, - "IResolverConfig": { - "additionalProperties": false, - "description": "Use this configuration to define several features of Route 53 resolver, including resolver endpoints,\nDNS firewall rule groups, and DNS query logs.\nAmazon Route 53 Resolver responds recursively to DNS queries from AWS resources for public records,\nAmazon VPC-specific DNS names, and Amazon Route 53 private hosted zones, and is available by default in all VPCs.", - "properties": { - "endpoints": { - "description": "(OPTIONAL) An array of Route 53 resolver endpoint configurations.", - "items": { - "$ref": "#/definitions/IResolverEndpointConfig" - }, - "type": "array" - }, - "firewallRuleGroups": { - "description": "(OPTIONAL) An array of Route 53 DNS firewall rule group configurations.", - "items": { - "$ref": "#/definitions/IDnsFirewallRuleGroupConfig" - }, - "type": "array" - }, - "queryLogs": { - "$ref": "#/definitions/IDnsQueryLogsConfig", - "description": "(OPTIONAL) A Route 53 resolver DNS query logging configuration." - }, - "rules": { - "description": "(OPTIONAL) An array of Route 53 resolver rules.", - "items": { - "$ref": "#/definitions/IResolverRuleConfig" - }, - "type": "array" - } - }, - "type": "object" - }, - "IResolverEndpointConfig": { - "additionalProperties": false, - "description": "Use this configuration to define inbound and outbound resolver endpoints.\nRoute 53 Resolver contains endpoints that you configure to answer DNS queries to\nand from your on-premises environment.", - "properties": { - "allowedCidrs": { - "description": "(OPTIONAL) The allowed ingress/egress CIDRs for the resolver endpoint security group.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the resolver endpoint." - }, - "rules": { - "description": "(OPTIONAL) An array of resolver rule configurations for the endpoint.", - "items": { - "$ref": "#/definitions/IResolverRuleConfig" - }, - "type": "array" - }, - "subnets": { - "description": "An array of friendly names for subnets to deploy the resolver endpoint to.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the resolver endpoint.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "type": { - "$ref": "#/definitions/ResolverEndpointType", - "description": "The type of resolver endpoint to deploy.\n\nINBOUND: allows DNS queries to your VPC from your network\n\nOUTBOUND: allows DNS queries from your VPC to your network" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC to deploy the resolver endpoint to." - } - }, - "required": [ - "name", - "type", - "vpc", - "subnets" - ], - "type": "object" - }, - "IResolverRuleConfig": { - "additionalProperties": false, - "description": "Use this configuration to define resolver SYSTEM and FORWARD rules for your resolver.\nIf you want Resolver to forward queries for specified domain names to your network,\nyou create one forwarding rule for each domain name and specify the name of the\ndomain for which you want to forward queries.", - "properties": { - "domainName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The domain name for the resolver rule." - }, - "excludedRegions": { - "description": "(OPTIONAL) Regions to exclude from SYSTEM rule deployment.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "inboundEndpointTarget": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of an inbound endpoint to target." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the resolver rule." - }, - "ruleType": { - "$ref": "#/definitions/RuleType", - "description": "(OPTIONAL) The type of rule to create." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the resolver rule.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "targetIps": { - "description": "(OPTIONAL) An array of target IP configurations for the resolver rule.", - "items": { - "$ref": "#/definitions/IRuleTargetIps" - }, - "type": "array" - } - }, - "required": [ - "name", - "domainName" - ], - "type": "object" - }, - "IRouteTableConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom route tables for your VPC.\nRoute tables contain a set of rules, called routes, to determine where network traffic from a subnet or gateway is directed.", - "properties": { - "gatewayAssociation": { - "$ref": "#/definitions/GatewayRouteTableType", - "description": "Designate a gateway to associate this route table with." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the VPC route table." - }, - "routes": { - "description": "An array of VPC route table entry configuration objects.", - "items": { - "$ref": "#/definitions/IRouteTableEntryConfig" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the VPC route table.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IRouteTableEntryConfig": { - "additionalProperties": false, - "description": "Use this configuration to define static route entries in a VPC subnet or gateway route table.\nStatic routes are used determine traffic flow from your subnet to a defined destination address and target.", - "properties": { - "destination": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The destination IPv4 CIDR block or dynamic subnet reference for the route table entry." - }, - "destinationPrefixList": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the destination prefix list for the route table entry." - }, - "ipv6Destination": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The destination IPv6 CIDR block or dynamic subnet reference for the route table entry." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the route table." - }, - "target": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the destination target." - }, - "targetAvailabilityZone": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "type": "number" - } - ], - "description": "The Availability Zone (AZ) the target resides in." - }, - "type": { - "$ref": "#/definitions/RouteTableEntryType", - "description": "The destination type of route table entry." - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IRuleTargetIps": { - "additionalProperties": false, - "properties": { - "ip": { - "$ref": "#/definitions/NonEmptyString" - }, - "port": { - "$ref": "#/definitions/NonEmptyString" - } - }, - "required": [ - "ip" - ], - "type": "object" - }, - "ISecurityGroupConfig": { - "additionalProperties": false, - "description": "Use this configuration to define security groups in your VPC.\nA security group acts as a firewall that controls the traffic\nallowed to and from the resources in your VPC.\nYou can choose the ports and protocols to allow for inbound and outbound traffic.\n\nThe following example creates a security group that allows inbound RDP and SSH traffic from source CIDR 10.0.0.0/16.\nIt also allows all outbound traffic.", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description for the security group." - }, - "inboundRules": { - "description": "An array of security group rule configurations for ingress rules.", - "items": { - "$ref": "#/definitions/ISecurityGroupRuleConfig" - }, - "type": "array" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the security group." - }, - "outboundRules": { - "description": "An array of security group rule configurations for egress rules.", - "items": { - "$ref": "#/definitions/ISecurityGroupRuleConfig" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the security group.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "inboundRules", - "outboundRules" - ], - "type": "object" - }, - "ISecurityGroupRuleConfig": { - "additionalProperties": false, - "description": "Use this configuration to define ingress and egress rules for your security groups.\nThe rules of a security group control the inbound traffic that's allowed to reach the resources\nthat are associated with the security group. The rules also control the outbound traffic that's\nallowed to leave them.", - "properties": { - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "A description for the security group rule." - }, - "fromPort": { - "description": "(OPTIONAL) The port to start from in the security group rule.", - "type": "number" - }, - "ipProtocols": { - "description": "(OPTIONAL) An array of custom IP Protocols for the security group rule", - "items": { - "type": "string" - }, - "type": "array" - }, - "sources": { - "description": "An array of sources for the security group rule.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "$ref": "#/definitions/ISubnetSourceConfig" - }, - { - "$ref": "#/definitions/ISecurityGroupSourceConfig" - }, - { - "$ref": "#/definitions/IPrefixListSourceConfig" - } - ] - }, - "type": "array" - }, - "tcpPorts": { - "description": "(OPTIONAL) An array of TCP ports to include in the security group rule.", - "items": { - "type": "number" - }, - "type": "array" - }, - "toPort": { - "description": "(OPTIONAL) The port to end with in the security group rule.", - "type": "number" - }, - "types": { - "description": "(OPTIONAL) An array of port/protocol types to include in the security group rule.", - "items": { - "$ref": "#/definitions/SecurityGroupRuleType" - }, - "type": "array" - }, - "udpPorts": { - "description": "(OPTIONAL) An array of UDP ports to include in the security group rule.", - "items": { - "type": "number" - }, - "type": "array" - } - }, - "required": [ - "description", - "sources" - ], - "type": "object" - }, - "ISecurityGroupSourceConfig": { - "additionalProperties": false, - "description": "Security group source configuration.\nUse this configuration to define a security group as a source of a security group rule.", - "properties": { - "securityGroups": { - "description": "An array of the friendly names of security group rules to reference.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "securityGroups" - ], - "type": "object" - }, - "IShareTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "ISubnetConfig": { - "additionalProperties": false, - "description": "Use this configuration to define subnets for your VPC.\nA subnet is a range of IP addresses in your VPC that can be used to create AWS resources, such as EC2 instances.", - "properties": { - "assignIpv6OnCreation": { - "description": "(OPTIONAL) Indicates whether a network interface created in this subnet receives an IPv6 address on creation.", - "type": "boolean" - }, - "availabilityZone": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "type": "number" - } - ], - "description": "The Availability Zone (AZ) the subnet resides in." - }, - "enableDns64": { - "description": "(OPTIONAL) Indicates whether DNS queries made to the Amazon-provided DNS Resolver in this subnet should return synthetic IPv6 addresses for IPv4-only destinations.\n\nFor more information, see {@link https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html#nat-gateway-nat64-dns64 DNS64 and NAT64 } in the Amazon Virtual Private Cloud User Guide.", - "type": "boolean" - }, - "ipamAllocation": { - "$ref": "#/definitions/IIpamAllocationConfig", - "description": "The IPAM pool configuration for the subnet." - }, - "ipv4CidrBlock": { - "$ref": "#/definitions/NonEmptyString", - "description": "The IPv4 CIDR block to associate with the subnet." - }, - "ipv6CidrBlock": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The IPv6 CIDR block to associate with the subnet." - }, - "localZone": { - "$ref": "#/definitions/NonEmptyString", - "description": "The Zone ID of the local zone." - }, - "mapPublicIpOnLaunch": { - "description": "(OPTIONAL) Configure automatic mapping of public IPs.", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the VPC subnet." - }, - "outpost": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name for the outpost to attach to the subnet" - }, - "privateDnsOptions": { - "$ref": "#/definitions/ISubnetPrivateDnsConfig", - "description": "(OPTIONAL) Private DNS name options for the subnet." - }, - "routeTable": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the route table to associate with the subnet." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the VPC subnet.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "ISubnetPrivateDnsConfig": { - "additionalProperties": false, - "description": "Use this configuration to define custom DNS name settings for your VPC subnets.", - "properties": { - "enableDnsAAAARecord": { - "default": false, - "description": "(OPTIONAL) Indicates whether to respond to DNS queries for instance hostname with DNS AAAA records.", - "type": "boolean" - }, - "enableDnsARecord": { - "default": false, - "description": "(OPTIONAL) Indicates whether to respond to DNS queries for instance hostnames with DNS A records.", - "type": "boolean" - }, - "hostnameType": { - "description": "The type of hostname for EC2 instances.", - "enum": [ - "ip-name", - "resource-name" - ], - "type": "string" - } - }, - "type": "object" - }, - "ISubnetSourceConfig": { - "additionalProperties": false, - "description": "VPC subnet security group source configuration.\nUse this configuration to dynamically reference subnet CIDRs in a security group rule.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account in which the VPC subnet resides." - }, - "ipv6": { - "description": "(OPTIONAL) Indicates whether to target the IPv6 CIDR associated with a subnet.", - "type": "boolean" - }, - "subnets": { - "description": "An array of the friendly names of subnets to reference.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC in which the subnet resides." - } - }, - "required": [ - "account", - "vpc", - "subnets" - ], - "type": "object" - }, - "ITag": { - "additionalProperties": false, - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "key", - "value" - ], - "type": "object" - }, - "ITargetGroupAttributeTypes": { - "additionalProperties": false, - "description": "Set attributes for target group.", - "properties": { - "algorithm": { - "$ref": "#/definitions/TargetGroupAttributeAlgorithm", - "description": "The load balancing algorithm determines how the load balancer selects targets when routing requests. The value is round_robin or least_outstanding_requests. The default is round_robin. The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address." - }, - "appCookieDuration": { - "description": "The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the application-based cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address.", - "type": "number" - }, - "appCookieName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Indicates the name of the application-based cookie. Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP, and AWSALBTG; they're reserved for use by the load balancer. The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address." - }, - "connectionTermination": { - "description": "Indicates whether the load balancer terminates connections at the end of the deregistration timeout. The value is true or false. The default is false. The following attribute is supported only by Network Load Balancers.", - "type": "boolean" - }, - "deregistrationDelay": { - "description": "The amount of time, in seconds, for Elastic Load Balancing to wait before changing the state of a deregistering target from draining to unused. The range is 0-3600 seconds. The default value is 300 seconds.", - "type": "number" - }, - "lbCookieDuration": { - "description": "The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds). The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address.", - "type": "number" - }, - "preserveClientIp": { - "description": "Indicates whether client IP preservation is enabled. The value is true or false. The default is disabled if the target group type is IP address and the target group protocol is TCP or TLS. Otherwise, the default is enabled. Client IP preservation cannot be disabled for UDP and TCP_UDP target groups. The following attribute is supported only by Network Load Balancers.", - "type": "boolean" - }, - "proxyProtocolV2": { - "description": "Indicates whether Proxy Protocol version 2 is enabled. The value is true or false. The default is false. The following attribute is supported only by Network Load Balancers.", - "type": "boolean" - }, - "slowStart": { - "description": "The time period, in seconds, during which a newly registered target receives an increasing share of the traffic to the target group. After this time period ends, the target receives its full share of traffic. The range is 30-900 seconds (15 minutes). The default is 0 seconds (disabled). The following attribute is supported only if the load balancer is an Application Load Balancer and the target is an instance or an IP address.", - "type": "number" - }, - "stickiness": { - "description": "Indicates whether target stickiness is enabled. The value is true or false. The default is false.", - "type": "boolean" - }, - "stickinessType": { - "$ref": "#/definitions/TargetGroupAttributeStickinessType", - "description": "Indicates the type of stickiness. The possible values are: - lb_cookie and app_cookie for Application Load Balancers. - source_ip for Network Load Balancers. - source_ip_dest_ip and source_ip_dest_ip_proto for Gateway Load Balancers" - }, - "targetFailover": { - "$ref": "#/definitions/TargetGroupTargetFailoverType", - "description": "Indicates how the Gateway Load Balancer handles existing flows when a target is deregistered or becomes unhealthy. The possible values are rebalance and no_rebalance. The default is no_rebalance" - } - }, - "type": "object" - }, - "ITargetGroupHealthCheckType": { - "additionalProperties": false, - "description": "Configure health check for target group.", - "properties": { - "interval": { - "description": "The approximate amount of time, in seconds, between health checks of an individual target. The range is 5-300. If the target group protocol is TCP, TLS, UDP, TCP_UDP, HTTP or HTTPS, the default is 30 seconds. If the target group protocol is GENEVE, the default is 10 seconds.", - "type": "number" - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "[HTTP/HTTPS health checks] The destination for health checks on the targets. [HTTP1 or HTTP2 protocol version] The ping path. The default is /. [GRPC protocol version] The path of a custom health check method with the format /package.service/method. The default is /AWS.ALB/healthcheck." - }, - "port": { - "description": "The port the load balancer uses when performing health checks on targets. If the protocol is HTTP, HTTPS, TCP, TLS, UDP, or TCP_UDP, the default is `traffic-port`, which is the port on which each target receives traffic from the load balancer. If the protocol is GENEVE, the default is port 80.", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/TargetGroupHealthCheckProtocolType", - "description": "The protocol the load balancer uses when performing health checks on targets. For Application Load Balancers, the default is HTTP. For Network Load Balancers and Gateway Load Balancers, the default is TCP. The TCP protocol is not supported for health checks if the protocol of the target group is HTTP or HTTPS. GENEVE, TLS, UDP, and TCP_UDP protocols are not supported for health checks." - }, - "timeout": { - "description": "The amount of time, in seconds, during which no response from a target means a failed health check. The range is 2–120 seconds. For target groups with a protocol of HTTP, the default is 6 seconds. For target groups with a protocol of TCP, TLS or HTTPS, the default is 10 seconds. For target groups with a protocol of GENEVE, the default is 5 seconds.", - "type": "number" - } - }, - "type": "object" - }, - "ITargetGroupItem": { - "additionalProperties": false, - "description": "Target Group Configuration", - "properties": { - "attributes": { - "$ref": "#/definitions/ITargetGroupAttributeTypes", - "description": "Target Group Attributes." - }, - "healthCheck": { - "$ref": "#/definitions/ITargetGroupHealthCheckType", - "description": "Target Group HealthCheck." - }, - "matcher": { - "$ref": "#/definitions/ITargetGroupMatcherType", - "description": "The HTTP or gRPC codes to use when checking for a successful response from a target. For target groups with a protocol of TCP, TCP_UDP, UDP or TLS the range is 200-599. For target groups with a protocol of HTTP or HTTPS, the range is 200-499." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the target group. This value is used in {@link ApplicationLoadBalancerListenerConfig Application Load Balancer listeners } , {@link NetworkLoadBalancerListenerConfig Network Load Balancer listeners } , and {@link AutoScalingConfig Autoscaling config } ." - }, - "port": { - "description": "The port on which the targets receive traffic.", - "type": "number" - }, - "protocol": { - "$ref": "#/definitions/TargetGroupProtocolType", - "description": "Target group protocol version. Should be one of HTTP, HTTPS, GENEVE, TCP, UDP, TCP_UDP or TLS The protocol to use for routing traffic to the targets. For Application Load Balancers, the supported protocols are HTTP and HTTPS. For Network Load Balancers, the supported protocols are TCP, TLS, UDP, or TCP_UDP. A TCP_UDP listener must be associated with a TCP_UDP target group. For Gateway Load Balancers, the supported protocol is GENEVE." - }, - "protocolVersion": { - "$ref": "#/definitions/TargetGroupProtocolVersionType", - "description": "The protocol version. Should be one of 'GRPC', 'HTTP1', 'HTTP2'. Specify GRPC to send requests to targets using gRPC. Specify HTTP2 to send requests to targets using HTTP/2. The default is HTTP1, which sends requests to targets using HTTP/1.1." - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "The accounts/OUs location where the Target Group will be deployed to." - }, - "targets": { - "description": "Target group targets. These targets should be the friendly names assigned to firewall instances.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "$ref": "#/definitions/INlbTargetType" - } - ] - }, - "type": "array" - }, - "threshold": { - "$ref": "#/definitions/ITargetGroupThresholdType", - "description": "Target Group Threshold." - }, - "type": { - "$ref": "#/definitions/TargetGroupType", - "description": "The type of target that you must specify when registering targets with this target group. You can't specify targets for a target group using more than one target type.\n- `instance` - Register targets by instance ID. This is the default value.\n- `ip` - Register targets by IP address. You can specify IP addresses from the subnets of the virtual private cloud (VPC) for the target group, the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and the RFC 6598 range (100.64.0.0/10). You can't specify publicly routable IP addresses. `alb` - Register a single Application Load Balancer as a target." - } - }, - "required": [ - "name", - "port", - "protocol", - "type" - ], - "type": "object" - }, - "ITargetGroupMatcherType": { - "additionalProperties": false, - "description": "Add the ability to target an NLB created by the Landing Zone Accelerator", - "properties": { - "grpcCode": { - "$ref": "#/definitions/NonEmptyString", - "description": "You can specify values between 0 and 99. You can specify multiple values (for example, \"0,1\") or a range of values (for example, \"0-5\"). The default value is 12." - }, - "httpCode": { - "$ref": "#/definitions/NonEmptyString", - "description": "For Application Load Balancers, you can specify values between 200 and 499, with the default value being 200. You can specify multiple values (for example, \"200,202\") or a range of values (for example, \"200-299\"). For Network Load Balancers, you can specify values between 200 and 599, with the default value being 200-399. You can specify multiple values (for example, \"200,202\") or a range of values (for example, \"200-299\"). Note that when using shorthand syntax, some values such as commas need to be escaped." - } - }, - "type": "object" - }, - "ITargetGroupThresholdType": { - "additionalProperties": false, - "description": "Configure health check threshold for target group.", - "properties": { - "healthy": { - "description": "The number of consecutive health check successes required before considering a target healthy. The range is 2-10. If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP or HTTPS, the default is 5. For target groups with a protocol of GENEVE, the default is 3.", - "type": "number" - }, - "unhealthy": { - "description": "The number of consecutive health check failures required before considering a target unhealthy. The range is 2-10. If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP or HTTPS, the default is 2. For target groups with a protocol of GENEVE, the default is 3.", - "type": "number" - } - }, - "type": "object" - }, - "ITransitGatewayAttachmentConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a Transit Gateway attachment to your VPC.\nTransit Gateway attachments allow you to interconnect your virtual private clouds (VPCs) and on-premises networks.\nDefining a VPC attachment deploys an elastic network interface within VPC subnets,\nwhich is then used by the transit gateway to route traffic to and from the chosen subnets.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Transit Gateway attachment." - }, - "options": { - "$ref": "#/definitions/ITransitGatewayAttachmentOptionsConfig", - "description": "(OPTIONAL) A Transit Gateway attachment options configuration." - }, - "routeTableAssociations": { - "description": "The friendly name of a Transit Gateway route table to associate the attachment to.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "routeTablePropagations": { - "description": "An array of friendly names of Transit Gateway route tables to propagate the attachment.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "subnets": { - "description": "An array of the friendly names of VPC subnets for the attachment to be deployed.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the Transit Gateway attachment.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "transitGateway": { - "$ref": "#/definitions/ITransitGatewayAttachmentTargetConfig", - "description": "A Transit Gateway attachment target configuration object." - } - }, - "required": [ - "name", - "transitGateway", - "subnets" - ], - "type": "object" - }, - "ITransitGatewayAttachmentOptionsConfig": { - "additionalProperties": false, - "description": "Used to specify advanced options for the VPC attachment.", - "properties": { - "applianceModeSupport": { - "$ref": "#/definitions/EnableDisable", - "description": "(OPTIONAL) Enable to configure appliance mode for the attachment. This option is disabled by default." - }, - "dnsSupport": { - "$ref": "#/definitions/EnableDisable", - "description": "(OPTIONAL) Enable to configure DNS support for the attachment. This option is enabled by default." - }, - "ipv6Support": { - "$ref": "#/definitions/EnableDisable", - "description": "(OPTIONAL) Enable to configure IPv6 support for the attachment. This option is disabled by default." - } - }, - "type": "object" - }, - "ITransitGatewayAttachmentTargetConfig": { - "additionalProperties": false, - "description": "Use this configuration to target a Transit Gateway when defining an attachment for your VPC.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account for the attachment target Transit Gateway." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the attachment target Transit Gateway." - } - }, - "required": [ - "name", - "account" - ], - "type": "object" - }, - "ITransitGatewayConfig": { - "additionalProperties": false, - "description": "Use this configuration to define Transit Gateways for your environment.\nA transit gateway acts as a virtual router for traffic flowing between your virtual private clouds (VPCs) and on-premises networks.\n\nThe following example creates a TGW called Network-Main in the Network account in the us-east-1 region.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account to deploy the Transit Gateway." - }, - "asn": { - "description": "A Border Gateway Protocol (BGP) Autonomous System Number (ASN).", - "type": "number" - }, - "autoAcceptSharingAttachments": { - "$ref": "#/definitions/EnableDisable", - "description": "Enable this option to automatically accept cross-account attachments." - }, - "defaultRouteTableAssociation": { - "$ref": "#/definitions/EnableDisable", - "description": "Configure default route table association." - }, - "defaultRouteTablePropagation": { - "$ref": "#/definitions/EnableDisable", - "description": "Configure default route table propagation." - }, - "dnsSupport": { - "$ref": "#/definitions/EnableDisable", - "description": "Configure DNS support between VPCs." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Transit Gateway." - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The region name to deploy the Transit Gateway." - }, - "routeTables": { - "description": "An array of Transit Gateway route table configuration objects.", - "items": { - "$ref": "#/definitions/ITransitGatewayRouteTableConfig" - }, - "type": "array" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "(OPTIONAL) Resource Access Manager (RAM) share targets." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the Transit Gateway.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "transitGatewayCidrBlocks": { - "description": "(OPTIONAL) A list of transit gateway IPv4 CIDR blocks.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "transitGatewayIpv6CidrBlocks": { - "description": "(OPTIONAL) A list of transit gateway IPv6 CIDR blocks.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "vpnEcmpSupport": { - "$ref": "#/definitions/EnableDisable", - "description": "Equal Cost Multipath (ECMP) routing support between VPN tunnels." - } - }, - "required": [ - "name", - "account", - "region", - "asn", - "dnsSupport", - "vpnEcmpSupport", - "defaultRouteTableAssociation", - "defaultRouteTablePropagation", - "autoAcceptSharingAttachments", - "routeTables" - ], - "type": "object" - }, - "ITransitGatewayConnectConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a Transit Gateway Connect attachment to your Direct Connect Gateway.", - "properties": { - "directConnect": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The Direct Connect Gateway Attachment that belongs to the Transit Gateway that a Transit Gateway Connect Attachment is being made for." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Transit Gateway Connect attachment." - }, - "options": { - "$ref": "#/definitions/ITransitGatewayConnectOptionsConfig", - "description": "(OPTIONAL) Options around the Transit Gateway Connect" - }, - "region": { - "$ref": "#/definitions/NonEmptyString", - "description": "The AWS Region for the attachment." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the Transit Gateway attachment.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "transitGateway": { - "$ref": "#/definitions/ITransitGatewayAttachmentTargetConfig", - "description": "The Transit Gateway configuration object to set the Transit Gateway Connect." - }, - "vpc": { - "$ref": "#/definitions/ITransitGatewayConnectVpcConfig", - "description": "The VPC Attachment that belongs to the Transit Gateway that a Transit Gateway Connect Attachment is being made for." - } - }, - "required": [ - "name", - "region", - "transitGateway" - ], - "type": "object" - }, - "ITransitGatewayConnectOptionsConfig": { - "additionalProperties": false, - "properties": { - "protocol": { - "$ref": "#/definitions/TransitGatewayConnectProtocol", - "description": "The tunnel protocl for the Transit Gateway Connect" - } - }, - "required": [ - "protocol" - ], - "type": "object" - }, - "ITransitGatewayConnectVpcConfig": { - "additionalProperties": false, - "properties": { - "vpcAttachment": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the VPC attachment" - }, - "vpcName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the VPC" - } - }, - "required": [ - "vpcName", - "vpcAttachment" - ], - "type": "object" - }, - "ITransitGatewayPeeringAccepterConfig": { - "additionalProperties": false, - "description": "Transit Gateway (TGW) peering accepter configuration.\nUse this configuration to define the accepter side of the peering attachment.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account of the accepter transit gateway" - }, - "applyTags": { - "description": "(OPTIONAL) Peering request apply tags flag. Note: When this flag is set to `true`, the requester attachment tags are replicated to the accepter attachment.", - "type": "boolean" - }, - "autoAccept": { - "description": "(OPTIONAL) Peering request auto accept flag. Note: When this flag is set to `true`, the peering request will be automatically accepted by the accelerator.", - "type": "boolean" - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The name of the region the accepter transit gateway resides in" - }, - "routeTableAssociations": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of TGW route table to associate with this peering attachment." - }, - "transitGatewayName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the accepter transit gateway" - } - }, - "required": [ - "transitGatewayName", - "account", - "region", - "routeTableAssociations" - ], - "type": "object" - }, - "ITransitGatewayPeeringConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a peering attachment between two TGWs.", - "properties": { - "accepter": { - "$ref": "#/definitions/ITransitGatewayPeeringAccepterConfig", - "description": "Peering attachment accepter configuration" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of TGW peering." - }, - "requester": { - "$ref": "#/definitions/ITransitGatewayPeeringRequesterConfig", - "description": "Peering attachment requester configuration." - } - }, - "required": [ - "name", - "requester", - "accepter" - ], - "type": "object" - }, - "ITransitGatewayPeeringRequesterConfig": { - "additionalProperties": false, - "description": "Transit Gateway (TGW) peering requester configuration.\nUse this configuration to define the requester side of the peering attachment.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account of the requester transit gateway" - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The name of the region the accepter transit gateway resides in" - }, - "routeTableAssociations": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of TGW route table to associate with this peering attachment." - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the Transit Gateway Peering.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "transitGatewayName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the requester transit gateway" - } - }, - "required": [ - "transitGatewayName", - "account", - "region", - "routeTableAssociations" - ], - "type": "object" - }, - "ITransitGatewayRouteEntryConfig": { - "additionalProperties": false, - "description": "Use this configuration to define static route entries in a Transit Gateway route table.", - "properties": { - "attachment": { - "anyOf": [ - { - "$ref": "#/definitions/ITransitGatewayRouteTableVpcEntryConfig" - }, - { - "$ref": "#/definitions/ITransitGatewayRouteTableDxGatewayEntryConfig" - }, - { - "$ref": "#/definitions/ITransitGatewayRouteTableVpnEntryConfig" - }, - { - "$ref": "#/definitions/ITransitGatewayRouteTableTgwPeeringEntryConfig" - } - ], - "description": "The target {@link https://docs.aws.amazon.com/vpc/latest/tgw/working-with-transit-gateways.html Transit Gateway attachment } for the route table entry. Supported attachment types include:\n\n- VPC\n- Direct Connect Gateway\n- VPN\n- Transit Gateway Peering" - }, - "blackhole": { - "description": "(OPTIONAL) Enable to create a blackhole for the destination CIDR. Leave undefined if specifying a VPC destination.", - "type": "boolean" - }, - "destinationCidrBlock": { - "$ref": "#/definitions/NonEmptyString", - "description": "The destination IPv4/v6 CIDR block for the route table entry." - }, - "destinationPrefixList": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of a prefix list for the route table entry." - } - }, - "type": "object" - }, - "ITransitGatewayRouteTableConfig": { - "additionalProperties": false, - "description": "Use this configuration define route tables for your Transit Gateway. Route tables are used to configure\nrouting behaviors for your Transit Gateway.\n\nThe following example creates a TGW route table called Network-Main-Shared with no static route entries:", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the Transit Gateway route table." - }, - "routes": { - "description": "An array of Transit Gateway route entry configuration objects.", - "items": { - "$ref": "#/definitions/ITransitGatewayRouteEntryConfig" - }, - "type": "array" - }, - "tags": { - "description": "(OPTIONAL) An array of tag objects for the Transit Gateway route table.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "routes" - ], - "type": "object" - }, - "ITransitGatewayRouteTableDxGatewayEntryConfig": { - "additionalProperties": false, - "description": "Transit Gateway Direct Connect Gateway static route entry configuration.\nUse this configuration to define a Direct Connect Gateway attachment as a target for Transit\nGateway static routes.", - "properties": { - "directConnectGatewayName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the Direct Connect Gateway" - } - }, - "required": [ - "directConnectGatewayName" - ], - "type": "object" - }, - "ITransitGatewayRouteTableTgwPeeringEntryConfig": { - "additionalProperties": false, - "description": "Transit Gateway peering static route entry configuration.\nUsed to define a peering attachment as a target for Transit\nGateway static routes.", - "properties": { - "transitGatewayPeeringName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the Transit Gateway peering connection" - } - }, - "required": [ - "transitGatewayPeeringName" - ], - "type": "object" - }, - "ITransitGatewayRouteTableVpcEntryConfig": { - "additionalProperties": false, - "description": "Transit Gateway VPC static route entry configuration.\nUse this configuration to define an account and VPC name as a target for Transit Gateway static route entries.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the account where the VPC resides." - }, - "vpcName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC." - } - }, - "required": [ - "account", - "vpcName" - ], - "type": "object" - }, - "ITransitGatewayRouteTableVpnEntryConfig": { - "additionalProperties": false, - "description": "Transit Gateway VPN static route entry configuration.\nUse this configuration to define a VPN attachment as a target for Transit\nGateway static routes.", - "properties": { - "vpnConnectionName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the VPN connection" - } - }, - "required": [ - "vpnConnectionName" - ], - "type": "object" - }, - "ITransition": { - "additionalProperties": false, - "properties": { - "storageClass": { - "$ref": "#/definitions/StorageClass" - }, - "transitionAfter": { - "type": "number" - } - }, - "required": [ - "storageClass", - "transitionAfter" - ], - "type": "object" - }, - "IVirtualPrivateGatewayConfig": { - "additionalProperties": false, - "description": "Used to define Virtual Private Gateways that are attached to a VPC.\nYou can create an IPsec VPN connection between your VPC and your remote network.\nOn the AWS side of the Site-to-Site VPN connection, a virtual private gateway or transit\ngateway provides two VPN endpoints (tunnels) for automatic failover.", - "properties": { - "asn": { - "description": "Define the ASN (Amazon Side) used for the Virtual Private Gateway", - "type": "number" - } - }, - "required": [ - "asn" - ], - "type": "object" - }, - "IVpcConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a VPC that is deployed to a single account and region.\nWith Amazon Virtual Private Cloud (Amazon VPC), you can launch AWS resources in a logically\nisolated virtual network that you've defined. This virtual network closely resembles a traditional\nnetwork that you'd operate in your own data center, with the benefits of using the scalable infrastructure of AWS.", - "properties": { - "account": { - "$ref": "#/definitions/NonEmptyString", - "description": "The logical name of the account to deploy the VPC to" - }, - "cidrs": { - "description": "(OPTIONAL) A list of IPv4 CIDRs to associate with the VPC.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "defaultSecurityGroupRulesDeletion": { - "description": "(OPTIONAL) Determine if the all traffic ingress and egress rules are deleted in the default security group of a VPC.", - "type": "boolean" - }, - "dhcpOptions": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of a custom DHCP options set." - }, - "dnsFirewallRuleGroups": { - "description": "(OPTIONAL) An array of DNS firewall VPC association configurations. Use this property to associate Route 53 resolver DNS firewall rule groups with the VPC.", - "items": { - "$ref": "#/definitions/IVpcDnsFirewallAssociationConfig" - }, - "type": "array" - }, - "egressOnlyIgw": { - "description": "(OPTIONAL) Create an {@link https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html Egress-only internet gateway (EIGW) } for the VPC", - "type": "boolean" - }, - "enableDnsHostnames": { - "description": "Enable DNS hostname support for the VPC.", - "type": "boolean" - }, - "enableDnsSupport": { - "description": "Enable DNS support for the VPC.", - "type": "boolean" - }, - "gatewayEndpoints": { - "$ref": "#/definitions/IGatewayEndpointConfig", - "description": "(OPTIONAL) An array of gateway endpoints for the VPC. Use this property to define S3 or DynamoDB gateway endpoints for the VPC." - }, - "instanceTenancy": { - "$ref": "#/definitions/InstanceTenancyType", - "description": "(OPTIONAL) Define instance tenancy for the VPC. The default value is `default`." - }, - "interfaceEndpoints": { - "$ref": "#/definitions/IInterfaceEndpointConfig", - "description": "(OPTIONAL) A list of VPC interface endpoints. Use this property to define VPC interface endpoints for the VPC." - }, - "internetGateway": { - "description": "Defines if an {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html internet gateway } should be added to the VPC", - "type": "boolean" - }, - "ipamAllocations": { - "description": "(OPTIONAL) An array of IPAM allocation configurations.", - "items": { - "$ref": "#/definitions/IIpamAllocationConfig" - }, - "type": "array" - }, - "ipv6Cidrs": { - "description": "(OPTIONAL) An array of IPv6 CIDR block configurations.", - "items": { - "$ref": "#/definitions/IVpcIpv6Config" - }, - "type": "array" - }, - "loadBalancers": { - "$ref": "#/definitions/ILoadBalancersConfig", - "description": "Elastic Load Balancing configuration. Use this property to define Elastic Load Balancers for this VPC." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC.\n\nThe value of this property will be utilized as the logical id for this resource. Any references to this object should specify this value." - }, - "natGateways": { - "description": "(OPTIONAL) An array of NAT gateway configurations for the VPC. Use this property to configure the NAT gateways for the VPC.", - "items": { - "$ref": "#/definitions/INatGatewayConfig" - }, - "type": "array" - }, - "networkAcls": { - "default": "undefined", - "description": "(OPTIONAL) A list of Network Access Control Lists (ACLs) to deploy for this VPC", - "items": { - "$ref": "#/definitions/INetworkAclConfig" - }, - "type": "array" - }, - "outposts": { - "description": "(OPTIONAL) An array of Local Gateway Route table configurations. Use this configuration to associate Outposts Local Gateway Route tables with the VPC.", - "items": { - "$ref": "#/definitions/IOutpostsConfig" - }, - "type": "array" - }, - "queryLogs": { - "description": "(OPTIONAL) A list of DNS query log configuration names.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The AWS region to deploy the VPC to" - }, - "resolverRules": { - "description": "(OPTIONAL) A list of Route 53 resolver rule names.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "routeTables": { - "description": "(OPTIONAL) An array of route table configurations for the VPC. Use this property to configure the route tables for the VPC.", - "items": { - "$ref": "#/definitions/IRouteTableConfig" - }, - "type": "array" - }, - "securityGroups": { - "default": "undefined", - "description": "(OPTIONAL) A list of Security Groups to deploy for this VPC", - "items": { - "$ref": "#/definitions/ISecurityGroupConfig" - }, - "type": "array" - }, - "subnets": { - "description": "(OPTIONAL) An array of subnet configurations for the VPC. Use this property to configure the subnets for the VPC.", - "items": { - "$ref": "#/definitions/ISubnetConfig" - }, - "type": "array" - }, - "tags": { - "default": "undefined", - "description": "(OPTIONAL) A list of tags to apply to this VPC", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "targetGroups": { - "description": "Target group configuration. Use this property to define target groups for this VPC.", - "items": { - "$ref": "#/definitions/ITargetGroupItem" - }, - "type": "array" - }, - "transitGatewayAttachments": { - "description": "(OPTIONAL) An array of Transit Gateway attachment configurations. Use this property to configure the Transit Gateway attachments for the VPC.", - "items": { - "$ref": "#/definitions/ITransitGatewayAttachmentConfig" - }, - "type": "array" - }, - "useCentralEndpoints": { - "default": false, - "description": "(OPTIONAL) When set to true, this VPC will be configured to utilize centralized endpoints. This includes having the Route 53 Private Hosted Zone associated with this VPC. Centralized endpoints are configured per region, and can span to spoke accounts", - "type": "boolean" - }, - "virtualPrivateGateway": { - "$ref": "#/definitions/IVirtualPrivateGatewayConfig", - "default": "undefined", - "description": "(OPTIONAL) Virtual Private Gateway configuration. Use this property to configure a Virtual Private Gateway for the VPC." - }, - "vpcFlowLogs": { - "$ref": "#/definitions/IVpcFlowLogsConfig", - "description": "VPC flog log configuration. Use this property to define a VPC-specific VPC flow logs configuration." - }, - "vpcRoute53Resolver": { - "$ref": "#/definitions/IResolverConfig", - "description": "A Route 53 resolver configuration local to the VPC." - } - }, - "required": [ - "name", - "account", - "region" - ], - "type": "object" - }, - "IVpcDnsFirewallAssociationConfig": { - "additionalProperties": false, - "properties": { - "mutationProtection": { - "$ref": "#/definitions/MutationProtectionType" - }, - "name": { - "$ref": "#/definitions/NonEmptyString" - }, - "priority": { - "type": "number" - }, - "tags": { - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - } - }, - "required": [ - "name", - "priority" - ], - "type": "object" - }, - "IVpcFlowLogsCloudWatchLogsConfig": { - "additionalProperties": false, - "properties": { - "kms": { - "$ref": "#/definitions/NonEmptyString" - }, - "retentionInDays": { - "type": "number" - } - }, - "type": "object" - }, - "IVpcFlowLogsConfig": { - "additionalProperties": false, - "properties": { - "customFields": { - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "defaultFormat": { - "type": "boolean" - }, - "destinations": { - "items": { - "$ref": "#/definitions/LogDestinationType" - }, - "type": "array" - }, - "destinationsConfig": { - "$ref": "#/definitions/IVpcFlowLogsDestinationConfig" - }, - "maxAggregationInterval": { - "type": "number" - }, - "trafficType": { - "$ref": "#/definitions/TrafficType" - } - }, - "required": [ - "trafficType", - "maxAggregationInterval", - "destinations", - "defaultFormat", - "customFields" - ], - "type": "object" - }, - "IVpcFlowLogsDestinationConfig": { - "additionalProperties": false, - "properties": { - "cloudWatchLogs": { - "$ref": "#/definitions/IVpcFlowLogsCloudWatchLogsConfig" - }, - "s3": { - "$ref": "#/definitions/IVpcFlowLogsS3BucketConfig" - } - }, - "type": "object" - }, - "IVpcFlowLogsS3BucketConfig": { - "additionalProperties": false, - "properties": { - "lifecycleRules": { - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "overrideS3LogPath": { - "$ref": "#/definitions/NonEmptyString" - } - }, - "type": "object" - }, - "IVpcIpv6Config": { - "additionalProperties": false, - "description": "VPC IPv6 static CIDR configuration. Use this to associate a static IPv6 CIDR block to your VPC.", - "properties": { - "amazonProvided": { - "description": "(OPTIONAL) Indicates whether Amazon automatically provisions a /56 IPv6 CIDR block for the VPC.", - "type": "boolean" - }, - "byoipPoolId": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Used to define the Bring-Your-Own-IP (BYOIP) address pool ID to use for the IPv6 CIDR block." - }, - "cidrBlock": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Associate an IPv6 CIDR block with your VPC." - } - }, - "type": "object" - }, - "IVpcPeeringConfig": { - "additionalProperties": false, - "description": "VPC peering configuration.\nUsed to define VPC peering connections.\n\nVPC can be from vpc or vpcTemplates configuration.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A friendly name for the peering connection." - }, - "tags": { - "description": "An array of tags for the peering connection.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "vpcs": { - "description": "The VPCs to peer.\n\nVPC can be from vpc or vpcTemplates configuration.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "name", - "vpcs" - ], - "type": "object" - }, - "IVpcTemplatesConfig": { - "additionalProperties": false, - "description": "Use this configuration to define a VPC using a standard configuration that is deployed to multiple account(s)/OU(s) defined using a `deploymentTargets` property.\nWith Amazon Virtual Private Cloud (Amazon VPC), you can launch AWS resources in a logically\nisolated virtual network that you've defined. This virtual network closely resembles a traditional\nnetwork that you'd operate in your own data center, with the benefits of using the scalable infrastructure of AWS.\n\nStatic CIDR:\n```\nvpcTemplates:\n - name: Accelerator-Template\n deploymentTargets:\n organizationalUnits:\n - Infrastructure\n region: us-east-1\n cidrs:\n - 10.0.0.0/24\n enableDnsHostnames: true\n enableDnsSupport: true\n instanceTenancy: default\n routeTables: []\n subnets: []\n natGateways: []\n transitGatewayAttachments: []\n tags: []\n```\nIPAM allocation:\n```\nvpcTemplates:\n - name: Accelerator-Template\n deploymentTargets:\n organizationalUnits:\n - Infrastructure\n region: us-east-1\n ipamAllocations:\n - ipamPoolName: accelerator-regional-pool\n netmaskLength: 24\n enableDnsHostnames: true\n enableDnsSupport: true\n instanceTenancy: default\n routeTables: []\n subnets: []\n natGateways: []\n transitGatewayAttachments: []\n tags: []\n```\nStatic IPv6 CIDR:\n```\nvpcTemplates:\n - name: Accelerator-Template\n deploymentTargets:\n organizationalUnits:\n - Infrastructure\n region: us-east-1\n cidrs:\n - 10.0.0.0/24\n ipv6Cidrs:\n - amazonProvided: true\n enableDnsHostnames: true\n enableDnsSupport: true\n instanceTenancy: default\n routeTables: []\n subnets: []\n natGateways: []\n transitGatewayAttachments: []\n tags: []\n```", - "properties": { - "cidrs": { - "description": "(OPTIONAL) A list of IPv4 CIDRs to associate with the VPC.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "defaultSecurityGroupRulesDeletion": { - "description": "(OPTIONAL) Determine if the all traffic ingress and egress rules are deleted in the default security group of a VPC.", - "type": "boolean" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "VPC deployment targets." - }, - "dhcpOptions": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The friendly name of a custom DHCP options set." - }, - "dnsFirewallRuleGroups": { - "description": "(OPTIONAL) An array of DNS firewall VPC association configurations. Use this property to associate Route 53 resolver DNS firewall rule groups with the VPC.", - "items": { - "$ref": "#/definitions/IVpcDnsFirewallAssociationConfig" - }, - "type": "array" - }, - "egressOnlyIgw": { - "description": "(OPTIONAL) Create an {@link https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html Egress-only internet gateway (EIGW) } for the VPC", - "type": "boolean" - }, - "enableDnsHostnames": { - "description": "Enable DNS hostname support for the VPC.", - "type": "boolean" - }, - "enableDnsSupport": { - "description": "Enable DNS support for the VPC.", - "type": "boolean" - }, - "gatewayEndpoints": { - "$ref": "#/definitions/IGatewayEndpointConfig", - "description": "(OPTIONAL) An array of gateway endpoints for the VPC. Use this property to define S3 or DynamoDB gateway endpoints for the VPC." - }, - "instanceTenancy": { - "$ref": "#/definitions/InstanceTenancyType", - "description": "(OPTIONAL) Define instance tenancy for the VPC. The default value is `default`." - }, - "interfaceEndpoints": { - "$ref": "#/definitions/IInterfaceEndpointConfig", - "description": "(OPTIONAL) A list of VPC interface endpoints. Use this property to define VPC interface endpoints for the VPC." - }, - "internetGateway": { - "description": "Defines if an {@link https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html internet gateway } should be added to the VPC", - "type": "boolean" - }, - "ipamAllocations": { - "description": "(OPTIONAL) An array of IPAM allocation configurations.", - "items": { - "$ref": "#/definitions/IIpamAllocationConfig" - }, - "type": "array" - }, - "ipv6Cidrs": { - "description": "(OPTIONAL) An array of IPv6 CIDR block configurations.", - "items": { - "$ref": "#/definitions/IVpcIpv6Config" - }, - "type": "array" - }, - "loadBalancers": { - "$ref": "#/definitions/ILoadBalancersConfig", - "description": "Elastic Load Balancing configuration. Use this property to define Elastic Load Balancers for this VPC." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name of the VPC.\n\nThe value of this property will be utilized as the logical id for this resource. Any references to this object should specify this value." - }, - "natGateways": { - "description": "(OPTIONAL) An array of NAT gateway configurations for the VPC. Use this property to configure the NAT gateways for the VPC.", - "items": { - "$ref": "#/definitions/INatGatewayConfig" - }, - "type": "array" - }, - "networkAcls": { - "default": "undefined", - "description": "(OPTIONAL) A list of Network Access Control Lists (ACLs) to deploy for this VPC", - "items": { - "$ref": "#/definitions/INetworkAclConfig" - }, - "type": "array" - }, - "queryLogs": { - "description": "(OPTIONAL) A list of DNS query log configuration names.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "region": { - "$ref": "#/definitions/Region", - "description": "The AWS region to deploy the VPCs to" - }, - "resolverRules": { - "description": "(OPTIONAL) A list of Route 53 resolver rule names.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "routeTables": { - "description": "(OPTIONAL) An array of route table configurations for the VPC. Use this property to configure the route tables for the VPC.", - "items": { - "$ref": "#/definitions/IRouteTableConfig" - }, - "type": "array" - }, - "securityGroups": { - "default": "undefined", - "description": "(OPTIONAL) A list of Security Groups to deploy for this VPC", - "items": { - "$ref": "#/definitions/ISecurityGroupConfig" - }, - "type": "array" - }, - "subnets": { - "description": "(OPTIONAL) An array of subnet configurations for the VPC. Use this property to configure the subnets for the VPC.", - "items": { - "$ref": "#/definitions/ISubnetConfig" - }, - "type": "array" - }, - "tags": { - "default": "undefined", - "description": "(OPTIONAL) A list of tags to apply to this VPC", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "targetGroups": { - "description": "Target group configuration. Use this property to define target groups for this VPC.", - "items": { - "$ref": "#/definitions/ITargetGroupItem" - }, - "type": "array" - }, - "transitGatewayAttachments": { - "description": "(OPTIONAL) An array of Transit Gateway attachment configurations. Use this property to configure the Transit Gateway attachments for the VPC.", - "items": { - "$ref": "#/definitions/ITransitGatewayAttachmentConfig" - }, - "type": "array" - }, - "useCentralEndpoints": { - "default": false, - "description": "(OPTIONAL) When set to true, this VPC will be configured to utilize centralized endpoints. This includes having the Route 53 Private Hosted Zone associated with this VPC. Centralized endpoints are configured per region, and can span to spoke accounts", - "type": "boolean" - }, - "virtualPrivateGateway": { - "$ref": "#/definitions/IVirtualPrivateGatewayConfig", - "default": "undefined", - "description": "(OPTIONAL) Virtual Private Gateway configuration. Use this property to configure a Virtual Private Gateway for the VPC." - }, - "vpcFlowLogs": { - "$ref": "#/definitions/IVpcFlowLogsConfig", - "description": "VPC flog log configuration. Use this property to define a VPC-specific VPC flow logs configuration." - } - }, - "required": [ - "name", - "region", - "deploymentTargets" - ], - "type": "object" - }, - "IVpnConnectionConfig": { - "additionalProperties": false, - "description": "Use this configuration to define the VPN connections that\nterminate either on a Transit Gateway or virtual private gateway.\nA VPN connection refers to the connection between your VPC and your own on-premises network.\nYou can enable access to your remote network from your VPC by creating an\nAWS Site-to-Site VPN (Site-to-Site VPN) connection, and configuring routing\nto pass traffic through the connection.\n\n**IMPORTANT**: After initial deployment of your VPN connection with any of the v1.5.0+ options noted below, you can make property changes in one of {@link VpnConnectionConfig } or {@link VpnTunnelOptionsSpecificationsConfig }, but not both.\nYou may make multiple property changes in one of those configurations if necessary. Trying to modify properties in both configurations will result in a pipeline failure. This is due to the fact that\nonly a single mutating API call can be made at a time for AWS Site-to-Site VPN connections.\n\nNote: you may manually roll back the resulting CloudFormation stack should you encounter this failure. More details on how to skip failed resources in the following reference:\nhttps://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html", - "properties": { - "amazonIpv4NetworkCidr": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The Amazon-side IPv4 CIDR range that is allowed through the site-to-site VPN tunnel. Configuring this option restricts the Amazon-side CIDR range that can communicate with your local network.\n\nDefault - `0.0.0.0/0`" - }, - "customerIpv4NetworkCidr": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The customer-side IPv4 CIDR range that is allowed through the site-to-site VPN tunnel. Configuring this option restricts the local CIDR range that can communicate with your AWS environment.\n\nDefault - `0.0.0.0/0`" - }, - "enableVpnAcceleration": { - "description": "(OPTIONAL) Enable Site-to-Site VPN Acceleration. For more information, see {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/accelerated-vpn.html Accelerated Site-to-Site VPN connections } .", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the VPN Connection.\n\nThe value of this property will be utilized as the logical id for this resource. Any references to this object should specify this value." - }, - "routeTableAssociations": { - "description": "(OPTIONAL) An array of Transit Gateway route table names to associate the VPN attachment to", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "routeTablePropagations": { - "description": "(OPTIONAL) An array of Transit Gateway route table names to propagate the VPN attachment to", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "staticRoutesOnly": { - "description": "(OPTIONAL) If creating a VPN connection for a device that doesn't support Border Gateway Protocol (BGP) declare true as a value, otherwise, use false.", - "type": "boolean" - }, - "tags": { - "description": "(OPTIONAL) An array of tags for the VPN Connection.", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "transitGateway": { - "$ref": "#/definitions/NonEmptyString", - "description": "The logical name of the Transit Gateway that the customer Gateway is attached to so that a VPN connection is established." - }, - "tunnelSpecifications": { - "description": "(OPTIONAL) Define the optional VPN Tunnel configuration", - "items": { - "$ref": "#/definitions/IVpnTunnelOptionsSpecificationsConfig" - }, - "type": "array" - }, - "vpc": { - "$ref": "#/definitions/NonEmptyString", - "description": "The logical name of the Virtual Private Cloud that a Virtual Private Gateway is attached to." - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IVpnLoggingConfig": { - "additionalProperties": false, - "description": "Use this configuration to define CloudWatch log groups for your Site-to-Site VPN connections.\nAWS Site-to-Site VPN logs provide you with deeper visibility into your Site-to-Site VPN deployments.\nWith this feature, you have access to Site-to-Site VPN connection logs that provide details on IP Security (IPsec) tunnel establishment,\nInternet Key Exchange (IKE) negotiations, and dead peer detection (DPD) protocol messages.", - "properties": { - "enable": { - "description": "(OPTIONAL) Enable site-to-site VPN tunnel logging to CloudWatch Logs.", - "type": "boolean" - }, - "logGroupName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The name of the CloudWatch Logs log group that you would like tunnel logs to be sent to.\n\nDefault - Randomly generated name based on CDK stack and VPN resource name." - }, - "outputFormat": { - "$ref": "#/definitions/VpnLoggingOutputFormatType", - "description": "(OPTIONAL) The output format of the VPN tunnel logs.\n\nDefault - `json`" - } - }, - "type": "object" - }, - "IVpnTunnelOptionsSpecificationsConfig": { - "additionalProperties": false, - "description": "Use this configuration to define optional tunnel configurations for a site-to-site VPN connection.\n\n**IMPORTANT**: After initial deployment of your VPN connection with any of the v1.5.0+ options noted below, you can only make property changes to one VPN tunnel per core pipeline run.\nYou may make multiple property changes in that one VPN tunnel if necessary. Trying to modify properties in both tunnels will result in a pipeline failure. This is due to the fact that\nonly a single mutating API call can be made at a time for AWS Site-to-Site VPN connections.\n\nNote: you may manually roll back the resulting CloudFormation stack should you encounter this failure. More details on how to skip failed resources in the following reference:\nhttps://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html", - "properties": { - "dpdTimeoutAction": { - "$ref": "#/definitions/DpdTimeoutActionType", - "description": "(OPTIONAL) Dead Peer Detection (DPD) timeout action. You can specify the action to take after DPD timeout occurs.\n\nDefault - `clear`" - }, - "dpdTimeoutSeconds": { - "description": "(OPTIONAL) The duration, in seconds, after which Dead Peer Detection (DPD) timeout occurs.\n\nDefault - `30`", - "type": "number" - }, - "ikeVersions": { - "description": "(OPTIONAL) The Internet Key Exchange (IKE) versions that are permitted on the tunnel.\n\nDefault - `ikev1`,`ikev2`", - "items": { - "$ref": "#/definitions/IkeVersionType" - }, - "type": "array" - }, - "logging": { - "$ref": "#/definitions/IVpnLoggingConfig", - "description": "(OPTIONAL) Site-to-Site VPN CloudWatch logging configuration." - }, - "phase1": { - "$ref": "#/definitions/IPhase1Config", - "description": "(OPTIONAL) Internet Key Exchange (IKE) phase 1 configuration." - }, - "phase2": { - "$ref": "#/definitions/IPhase2Config", - "description": "(OPTIONAL) Internet Key Exchange (IKE) phase 2 configuration." - }, - "preSharedKey": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL): The Secrets Manager name that stores the pre-shared key (PSK), that exists in the same account and region that the VPN Connection will be created in." - }, - "rekeyFuzzPercentage": { - "description": "(OPTIONAL) The percentage of the rekey window (determined by the rekey margin time) within which the rekey time is randomly selected.\n\nDefault - `100`", - "type": "number" - }, - "rekeyMarginTimeSeconds": { - "description": "(OPTIONAL) The margin time in seconds before the phase 1 and phase 2 lifetime expires, during which the AWS side of the VPN connection performs an IKE rekey.\n\nDefault - `270` (4.5 minutes)", - "type": "number" - }, - "replayWindowSize": { - "description": "(OPTIONAL) The number of packets in an IKE replay window.\n\nDefault - `1024`", - "type": "number" - }, - "startupAction": { - "$ref": "#/definitions/StartupActionType", - "description": "(OPTIONAL) The action to take when the establishing the tunnel for the VPN connection. By default, your customer gateway device must initiate the IKE negotiation and bring up the tunnel. Specify `start` for Amazon Web Services to initiate the IKE negotiation.\n\nDefault - `add`" - }, - "tunnelInsideCidr": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL): The range of inside IP addresses for the tunnel. Any specified CIDR blocks must be unique across all VPN connections that use the same virtual private gateway." - }, - "tunnelLifecycleControl": { - "description": "(OPTIONAL) Enable tunnel endpoint lifecycle control. This feature provides control over the schedule of endpoint replacements. For more information, see {@link https://docs.aws.amazon.com/vpn/latest/s2svpn/tunnel-endpoint-lifecycle.html Tunnel Endpoint Lifecycle Control } .", - "type": "boolean" - } - }, - "type": "object" - }, - "IkeVersionType": { - "enum": [ - 1, - 2 - ], - "type": "number" - }, - "InstanceTenancyType": { - "enum": [ - "default", - "dedicated" - ], - "type": "string" - }, - "IntegrityAlgorithmType": { - "enum": [ - "SHA1", - "SHA2-256", - "SHA2-384", - "SHA2-512" - ], - "type": "string" - }, - "IpAddressFamilyType": { - "enum": [ - "IPv4", - "IPv6" - ], - "type": "string" - }, - "IpVersionType": { - "enum": [ - "ipv4", - "ipv6" - ], - "type": "string" - }, - "LoadBalancerSchemeEnum": { - "enum": [ - "internet-facing", - "internal" - ], - "type": "string" - }, - "LogDestinationType": { - "enum": [ - "s3", - "cloud-watch-logs" - ], - "type": "string" - }, - "MutationProtectionType": { - "enum": [ - "ENABLED", - "DISABLED" - ], - "type": "string" - }, - "NetbiosNodeType": { - "enum": [ - 1, - 2, - 4, - 8 - ], - "type": "number" - }, - "NfwGeneratedRulesType": { - "enum": [ - "ALLOWLIST", - "DENYLIST" - ], - "type": "string" - }, - "NfwLogType": { - "enum": [ - "ALERT", - "FLOW" - ], - "type": "string" - }, - "NfwRuleType": { - "enum": [ - "STATEFUL", - "STATELESS" - ], - "type": "string" - }, - "NfwStatefulDefaultActionType": { - "enum": [ - "aws:drop_strict", - "aws:drop_established", - "aws:alert_strict", - "aws:alert_established" - ], - "type": "string" - }, - "NfwStatefulRuleActionType": { - "enum": [ - "ALERT", - "DROP", - "PASS" - ], - "type": "string" - }, - "NfwStatefulRuleDirectionType": { - "enum": [ - "ANY", - "FORWARD" - ], - "type": "string" - }, - "NfwStatefulRuleOptionsType": { - "enum": [ - "DEFAULT_ACTION_ORDER", - "STRICT_ORDER" - ], - "type": "string" - }, - "NfwStatefulRuleProtocolType": { - "enum": [ - "DCERPC", - "DHCP", - "DNS", - "FTP", - "HTTP", - "ICMP", - "IKEV2", - "IMAP", - "IP", - "KRB5", - "MSN", - "NTP", - "SMB", - "SMTP", - "SSH", - "TCP", - "TFTP", - "TLS", - "UDP" - ], - "type": "string" - }, - "NfwStatelessRuleActionType": { - "enum": [ - "aws:pass", - "aws:drop", - "aws:forward_to_sfe" - ], - "type": "string" - }, - "NfwStatelessRuleTcpFlagType": { - "enum": [ - "FIN", - "SYN", - "RST", - "PSH", - "ACK", - "URG", - "ECE", - "CWR" - ], - "type": "string" - }, - "NfwTargetType": { - "enum": [ - "TLS_SNI", - "HTTP_HOST" - ], - "type": "string" - }, - "NlbProtocolEnum": { - "enum": [ - "TCP", - "UDP", - "TLS", - "TCP_UDP" - ], - "type": "string" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - }, - "Phase1DhGroupType": { - "enum": [ - 2, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24 - ], - "type": "number" - }, - "Phase2DhGroupType": { - "enum": [ - 2, - 5, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24 - ], - "type": "number" - }, - "Region": { - "description": "AWS Region", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-south-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-southeast-4", - "ca-central-1", - "ca-west-1", - "cn-north-1", - "cn-northwest-1", - "eu-central-1", - "eu-central-2", - "eu-north-1", - "eu-south-1", - "eu-south-2", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "il-central-1", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-gov-west-1", - "us-gov-east-1", - "us-iso-east-1", - "us-isob-east-1", - "us-iso-west-1", - "us-west-1", - "us-west-2" - ], - "type": "string" - }, - "ResolverEndpointType": { - "enum": [ - "INBOUND", - "OUTBOUND" - ], - "type": "string" - }, - "RouteTableEntryType": { - "enum": [ - "transitGateway", - "natGateway", - "internetGateway", - "egressOnlyIgw", - "local", - "localGateway", - "gatewayEndpoint", - "gatewayLoadBalancerEndpoint", - "networkFirewall", - "networkInterface", - "virtualPrivateGateway", - "vpcPeering" - ], - "type": "string" - }, - "RoutingHttpXffHeaderProcessingModeEnum": { - "enum": [ - "append", - "preserve", - "remove" - ], - "type": "string" - }, - "RuleType": { - "enum": [ - "FORWARD", - "RECURSIVE", - "SYSTEM" - ], - "type": "string" - }, - "SecurityGroupRuleType": { - "enum": [ - "RDP", - "SSH", - "HTTP", - "HTTPS", - "MSSQL", - "MYSQL/AURORA", - "REDSHIFT", - "POSTGRESQL", - "ORACLE-RDS", - "TCP", - "UDP", - "ICMP", - "ALL" - ], - "type": "string" - }, - "SslPolicyAlbEnum": { - "enum": [ - "ELBSecurityPolicy-TLS-1-0-2015-04", - "ELBSecurityPolicy-TLS-1-1-2017-01", - "ELBSecurityPolicy-TLS-1-2-2017-01", - "ELBSecurityPolicy-TLS-1-2-Ext-2018-06", - "ELBSecurityPolicy-FS-2018-06", - "ELBSecurityPolicy-FS-1-1-2019-08", - "ELBSecurityPolicy-FS-1-2-2019-08", - "ELBSecurityPolicy-FS-1-2-Res-2019-08", - "ELBSecurityPolicy-2015-05", - "ELBSecurityPolicy-FS-1-2-Res-2020-10", - "ELBSecurityPolicy-2016-08" - ], - "type": "string" - }, - "SslPolicyNlbEnum": { - "enum": [ - "ELBSecurityPolicy-TLS-1-0-2015-04", - "ELBSecurityPolicy-TLS-1-1-2017-01", - "ELBSecurityPolicy-TLS-1-2-2017-01", - "ELBSecurityPolicy-TLS-1-2-Ext-2018-06", - "ELBSecurityPolicy-FS-2018-06", - "ELBSecurityPolicy-FS-1-1-2019-08", - "ELBSecurityPolicy-FS-1-2-2019-08", - "ELBSecurityPolicy-FS-1-2-Res-2019-08", - "ELBSecurityPolicy-2015-05", - "ELBSecurityPolicy-FS-1-2-Res-2020-10", - "ELBSecurityPolicy-TLS13-1-2-2021-06", - "ELBSecurityPolicy-TLS13-1-2-Res-2021-06", - "ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06", - "ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", - "ELBSecurityPolicy-TLS13-1-1-2021-06", - "ELBSecurityPolicy-TLS13-1-0-2021-06", - "ELBSecurityPolicy-TLS13-1-3-2021-06", - "ELBSecurityPolicy-2016-08" - ], - "type": "string" - }, - "StartupActionType": { - "enum": [ - "add", - "start" - ], - "type": "string" - }, - "StorageClass": { - "enum": [ - "DEEP_ARCHIVE", - "GLACIER", - "GLACIER_IR", - "STANDARD_IA", - "INTELLIGENT_TIERING", - "ONEZONE_IA" - ], - "type": "string" - }, - "TargetGroupAttributeAlgorithm": { - "enum": [ - "round_robin", - "least_outstanding_requests" - ], - "type": "string" - }, - "TargetGroupAttributeStickinessType": { - "enum": [ - "lb_cookie", - "app_cookie", - "source_ip", - "source_ip_dest_ip", - "source_ip_dest_ip_proto" - ], - "type": "string" - }, - "TargetGroupHealthCheckProtocolType": { - "enum": [ - "HTTP", - "HTTPS", - "TCP" - ], - "type": "string" - }, - "TargetGroupProtocolType": { - "enum": [ - "TCP", - "TLS", - "UDP", - "TCP_UDP", - "HTTP", - "HTTPS", - "GENEVE" - ], - "type": "string" - }, - "TargetGroupProtocolVersionType": { - "enum": [ - "GRPC", - "HTTP1", - "HTTP2" - ], - "type": "string" - }, - "TargetGroupTargetFailoverType": { - "enum": [ - "no_rebalance", - "rebalance" - ], - "type": "string" - }, - "TargetGroupType": { - "enum": [ - "instance", - "ip", - "alb", - "lambda" - ], - "type": "string" - }, - "TrafficType": { - "enum": [ - "ALL", - "ACCEPT", - "REJECT" - ], - "type": "string" - }, - "TransitGatewayConnectProtocol": { - "const": "gre", - "type": "string" - }, - "VpnLoggingOutputFormatType": { - "enum": [ - "json", - "text" - ], - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/organization-config.json b/source/packages/@aws-accelerator/config/lib/schemas/organization-config.json deleted file mode 100644 index 3180263..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/organization-config.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "$ref": "#/definitions/IOrganizationConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "IBackupPolicyConfig": { - "additionalProperties": false, - "description": "Organization backup policy\n\nBackup policies enable you to deploy organization-wide backup plans to help ensure compliance across your organization's accounts.\nUsing policies helps ensure consistency in how you implement your backup plans", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Backup policy deployment targets" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "A description to assign to the policy." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name to assign to the policy. The regex pattern that is used to validate this parameter is a string of any of the characters in the ASCII character range." - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "Backup policy definition json file. This file must be present in config repository" - } - }, - "required": [ - "name", - "description", - "policy", - "deploymentTargets" - ], - "type": "object" - }, - "IDeploymentTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedAccounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "IOrganizationConfig": { - "additionalProperties": false, - "description": "Organization configuration", - "properties": { - "backupPolicies": { - "description": "A Record of Backup Policy configurations", - "items": { - "$ref": "#/definitions/IBackupPolicyConfig" - }, - "type": "array" - }, - "enable": { - "description": "Indicates whether AWS Organization enabled.", - "type": "boolean" - }, - "organizationalUnitIds": { - "description": "Optionally provide a list of Organizational Unit IDs to bypass the usage of the AWS Organizations Client lookup. This is not a readonly member since we will initialize it with values if it is not provided", - "items": { - "$ref": "#/definitions/IOrganizationalUnitIdConfig" - }, - "type": "array" - }, - "organizationalUnits": { - "description": "A Record of Organizational Unit configurations", - "items": { - "$ref": "#/definitions/IOrganizationalUnitConfig" - }, - "type": "array" - }, - "quarantineNewAccounts": { - "$ref": "#/definitions/IQuarantineNewAccountsConfig", - "description": "A record of Quarantine New Accounts configuration" - }, - "serviceControlPolicies": { - "description": "A Record of Service Control Policy configurations", - "items": { - "$ref": "#/definitions/IServiceControlPolicyConfig" - }, - "type": "array" - }, - "taggingPolicies": { - "description": "A Record of Tagging Policy configurations", - "items": { - "$ref": "#/definitions/ITaggingPolicyConfig" - }, - "type": "array" - } - }, - "required": [ - "enable", - "organizationalUnits", - "serviceControlPolicies", - "taggingPolicies", - "backupPolicies" - ], - "type": "object" - }, - "IOrganizationalUnitConfig": { - "additionalProperties": false, - "description": "AWS Organizational Unit (OU) configuration", - "properties": { - "ignore": { - "description": "Optional property used to ignore organizational unit and the associated accounts Default value is false", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name and nested path that you want to assign to the OU. When referring to OU's in the other configuration files ensure that the name matches what has been provided here. For example if you wanted an OU directly off of root just supply the OU name. Always configure all of the OUs in the path. A nested OU configuration would be like this\n- name: Sandbox\n- name: Sandbox/Pipeline\n- name: Sandbox/Development\n- name: Sandbox/Development/Application1" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IOrganizationalUnitIdConfig": { - "additionalProperties": false, - "description": "Organizational unit id configuration", - "properties": { - "arn": { - "$ref": "#/definitions/NonEmptyString", - "description": "OU arn" - }, - "id": { - "$ref": "#/definitions/NonEmptyString", - "description": "OU id" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the OU" - } - }, - "required": [ - "name", - "id", - "arn" - ], - "type": "object" - }, - "IQuarantineNewAccountsConfig": { - "additionalProperties": false, - "description": "Quarantine SCP application configuration", - "properties": { - "enable": { - "description": "Indicates where or not a Quarantine policy is applied when new accounts are created. If enabled all accounts created by any means will have the configured policy applied.", - "type": "boolean" - }, - "scpPolicyName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The policy to apply to new accounts. This value must exist if the feature is enabled. The name must also match a policy that is defined in the serviceControlPolicy section." - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IServiceControlPolicyConfig": { - "additionalProperties": false, - "description": "Service control policy configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Service control policy strategy. https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_strategies.html" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "A description to assign to the policy." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name to assign to the policy. The regex pattern that is used to validate this parameter is a string of any of the characters in the ASCII character range." - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "Service control definition json file. This file must be present in config repository" - }, - "strategy": { - "description": "Service control policy deployment targets", - "enum": [ - "deny-list", - "allow-list" - ], - "type": "string" - }, - "type": { - "description": "Kind of service control policy", - "enum": [ - "awsManaged", - "customerManaged" - ], - "type": "string" - } - }, - "required": [ - "name", - "description", - "policy", - "type", - "deploymentTargets" - ], - "type": "object" - }, - "ITaggingPolicyConfig": { - "additionalProperties": false, - "description": "Organizations tag policy.\n\nTag policies help you standardize tags on all tagged resources across your organization.\nYou can use tag policies to define tag keys (including how they should be capitalized) and their allowed values.", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Tagging policy deployment targets" - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "A description to assign to the policy." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "The friendly name to assign to the policy. The regex pattern that is used to validate this parameter is a string of any of the characters in the ASCII character range." - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "Tagging policy definition json file. This file must be present in config repository" - } - }, - "required": [ - "name", - "description", - "policy", - "deploymentTargets" - ], - "type": "object" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/replacements-config.json b/source/packages/@aws-accelerator/config/lib/schemas/replacements-config.json deleted file mode 100644 index 0c9b617..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/replacements-config.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "$ref": "#/definitions/IReplacementsConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "IParameterReplacement": { - "additionalProperties": false, - "description": "Fixed replacement value to apply throughout config files. Loaded from SSM\nparameters in the management account in the HOME_REGION.", - "properties": { - "key": { - "$ref": "#/definitions/NonEmptyString", - "description": "Key of the replacement placeholder" - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "Path of the SSM Parameter containing the value to replace" - } - }, - "required": [ - "key", - "path" - ], - "type": "object" - }, - "IParameterReplacementV2": { - "additionalProperties": false, - "description": "Fixed replacement value to apply throughout config files. Loaded from SSM\nparameters in the management account in the HOME_REGION.", - "properties": { - "key": { - "$ref": "#/definitions/NonEmptyString", - "description": "Key of the replacement placeholder" - }, - "path": { - "$ref": "#/definitions/NonEmptyString", - "description": "Path of the SSM Parameter containing the value to replace" - }, - "type": { - "$ref": "#/definitions/ParameterReplacementType", - "description": "Type of the global parameters" - }, - "value": { - "anyOf": [ - { - "$ref": "#/definitions/NonEmptyString" - }, - { - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - ], - "description": "Value of the parameter if type is string or array" - } - }, - "required": [ - "key", - "type" - ], - "type": "object" - }, - "IReplacementsConfig": { - "additionalProperties": false, - "description": "Accelerator replacements configuration", - "properties": { - "globalReplacements": { - "description": "The set of placeholder parameters (key/path pairs) that will be merged with yaml configuration files.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/IParameterReplacement" - }, - { - "$ref": "#/definitions/IParameterReplacementV2" - } - ] - }, - "type": "array" - } - }, - "type": "object" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - }, - "ParameterReplacementType": { - "enum": [ - "SSM", - "String", - "StringList" - ], - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/schemas/security-config.json b/source/packages/@aws-accelerator/config/lib/schemas/security-config.json deleted file mode 100644 index 7b07e41..0000000 --- a/source/packages/@aws-accelerator/config/lib/schemas/security-config.json +++ /dev/null @@ -1,1682 +0,0 @@ -{ - "$ref": "#/definitions/ISecurityConfig", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "IAccessAnalyzerConfig": { - "additionalProperties": false, - "description": "AWS AccessAnalyzer configuration", - "properties": { - "enable": { - "description": "Indicates whether AWS AccessAnalyzer enabled in your organization.", - "type": "boolean" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IAlarmConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch Alarm configuration", - "properties": { - "alarmDescription": { - "$ref": "#/definitions/NonEmptyString", - "description": "Description for the alarm" - }, - "alarmName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the alarm" - }, - "comparisonOperator": { - "$ref": "#/definitions/NonEmptyString", - "description": "Comparison to use to check if metric is breaching" - }, - "evaluationPeriods": { - "description": "The number of periods over which data is compared to the specified threshold.", - "type": "number" - }, - "metricName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the metric." - }, - "namespace": { - "$ref": "#/definitions/NonEmptyString", - "description": "Namespace of the metric." - }, - "period": { - "description": "The period over which the specified statistic is applied.", - "type": "number" - }, - "snsAlertLevel": { - "$ref": "#/definitions/NonEmptyString", - "description": "Alert SNS notification level Deprecated" - }, - "snsTopicName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) SNS Topic Name SNS Topic Name from global config" - }, - "statistic": { - "$ref": "#/definitions/NonEmptyString", - "description": "What functions to use for aggregating.\n\nCan be one of the following:\n- “Minimum” | “min”\n- “Maximum” | “max”\n- “Average” | “avg”\n- “Sum” | “sum”\n- “SampleCount | “n”\n- “pNN.NN”" - }, - "threshold": { - "description": "The value against which the specified statistic is compared.", - "type": "number" - }, - "treatMissingData": { - "$ref": "#/definitions/NonEmptyString", - "description": "Sets how this alarm is to handle missing data points." - } - }, - "required": [ - "alarmName", - "alarmDescription", - "metricName", - "namespace", - "comparisonOperator", - "evaluationPeriods", - "period", - "statistic", - "threshold", - "treatMissingData" - ], - "type": "object" - }, - "IAlarmSetConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch Alarm sets", - "properties": { - "alarms": { - "description": "List of AWS CloudWatch Alarms\n\nFollowing example will create CIS-1.1-RootAccountUsage alarm for RootAccountUsage metric with notification level low", - "items": { - "$ref": "#/definitions/IAlarmConfig" - }, - "type": "array" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Deployment targets for CloudWatch Alarms configuration" - }, - "regions": { - "description": "AWS region names to configure CloudWatch Alarms", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "alarms" - ], - "type": "object" - }, - "IAuditManagerConfig": { - "additionalProperties": false, - "description": "Use this configuration to enable AWS Audit Manager for an AWS Organization.", - "properties": { - "defaultReportsConfiguration": { - "$ref": "#/definitions/IAuditManagerDefaultReportsDestinationConfig", - "description": "AWS Audit Manager Default Reports configuration." - }, - "enable": { - "description": "Indicates whether AWS Audit Manager enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring AWS Audit Manager. Please ensure any regions enabled in the global configuration that do not support Audit Manager are added to the excluded regions list. {@link https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ Supported services by region } .", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "lifecycleRules": { - "description": "(OPTIONAL) Declaration of a S3 Lifecycle rule.", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - } - }, - "required": [ - "enable", - "defaultReportsConfiguration" - ], - "type": "object" - }, - "IAuditManagerDefaultReportsDestinationConfig": { - "additionalProperties": false, - "description": "AWS Audit Manager Default Reports Destination configuration.\nUse this configuration to enable a destination for reports generated by AWS Audit Manager.", - "properties": { - "destinationType": { - "const": "S3", - "description": "The type of resource for the publishing destination. Currently only Amazon S3 buckets are supported.", - "type": "string" - }, - "enable": { - "description": "Indicates whether AWS Audit Manager Default Reports enabled.", - "type": "boolean" - } - }, - "required": [ - "enable", - "destinationType" - ], - "type": "object" - }, - "IAwsConfig": { - "additionalProperties": false, - "description": "AWS Config Recorder and Rules", - "properties": { - "aggregation": { - "$ref": "#/definitions/IAwsConfigAggregation", - "description": "Config Recorder Aggregation configuration" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "(OPTIONAL) AWS Config deployment target.\n\nLeaving `deploymentTargets` undefined will enable AWS Config across all accounts and enabled regions.\n\nWe highly recommend enabling AWS Config across all accounts and enabled regions within your organization. `deploymentTargets` should only be used when more granular control is required, not as a default configuration.\n\nTo enable AWS Config into Infrastructure organizational unit, you need to provide below value for this parameter.\n\nNote: The delegated admin account defined in centralSecurityServices will always have AwsConfig enabled" - }, - "enableConfigurationRecorder": { - "description": "Indicates whether AWS Config recorder enabled.\n\nTo enable AWS Config, you must create a configuration recorder\n\nConfigurationRecorder resource describes the AWS resource types for which AWS Config records configuration changes. The configuration recorder stores the configurations of the supported resources in your account as configuration items.", - "type": "boolean" - }, - "enableDeliveryChannel": { - "description": "Indicates whether delivery channel enabled.\n\nAWS Config uses the delivery channel to deliver the configuration changes to your Amazon S3 bucket. DEPRECATED", - "type": "boolean" - }, - "overrideExisting": { - "description": "Indicates whether or not to override existing config recorder settings Must be enabled if any account and region combination has an existing config recorder, even if config recording is turned off The Landing Zone Accelerator will override the settings in all configured accounts and regions\n** Do not enable this setting if you have deployed LZA\n** successfully with enableConfigurationRecorder set to true\n** and overrideExisting either unset or set to false\n** Doing so will cause a resource conflict When the overrideExisting property is enabled ensure that any scp's are not blocking the passRole iam permission for the iam role name {acceleratorPrefix}Config", - "type": "boolean" - }, - "ruleSets": { - "description": "AWS Config rule sets", - "items": { - "$ref": "#/definitions/IAwsConfigRuleSet" - }, - "type": "array" - } - }, - "required": [ - "enableConfigurationRecorder" - ], - "type": "object" - }, - "IAwsConfigAggregation": { - "additionalProperties": false, - "description": "AWS Config Aggregation Configuration\nNot used in Control Tower environment\nAggregation will be configured in all enabled regions\nunless specifically excluded\nIf the delegatedAdmin account is not provided\nconfig will be aggregated to the management account", - "properties": { - "delegatedAdminAccount": { - "$ref": "#/definitions/NonEmptyString" - }, - "enable": { - "type": "boolean" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IAwsConfigRuleSet": { - "additionalProperties": false, - "description": "List of AWS Config rules", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Config ruleset deployment target.\n\nTo configure AWS Config rules into Root and Infrastructure organizational units, you need to provide below value for this parameter." - }, - "rules": { - "description": "AWS Config rule set\n\nFollowing example will create a custom rule named accelerator-attach-ec2-instance-profile with remediation and a managed rule named accelerator-iam-user-group-membership-check without remediation", - "items": { - "$ref": "#/definitions/IConfigRule" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "rules" - ], - "type": "object" - }, - "ICentralSecurityServicesConfig": { - "additionalProperties": false, - "description": "AWS Accelerator central security services configuration", - "properties": { - "auditManager": { - "$ref": "#/definitions/IAuditManagerConfig", - "description": "(OPTIONAL) Amazon Audit Manager Configuration" - }, - "delegatedAdminAccount": { - "$ref": "#/definitions/NonEmptyString", - "default": "Audit\n\nTo make Audit account as designated administrator account for every security services configured by accelerator, you need to provide below value for this parameter", - "description": "Designated administrator account name for accelerator security services. AWS organizations designate a member account as a delegated administrator for the organization users and roles from that account can perform administrative actions for security services like Macie, GuardDuty, Detective and Security Hub. Without designated administrator account administrative tasks for security services are performed only by users or roles in the organization's management account. This helps you to separate management of the organization from management of these security services. Accelerator use Audit account as designated administrator account." - }, - "detective": { - "$ref": "#/definitions/IDetectiveConfig", - "description": "(OPTIONAL) Amazon Detective Configuration" - }, - "ebsDefaultVolumeEncryption": { - "$ref": "#/definitions/IEbsDefaultVolumeEncryptionConfig", - "description": "AWS Elastic Block Store default encryption configuration\n\nAccelerator use this parameter to configure EBS default encryption. Accelerator will create KMS key for every AWS environment (account and region), which will be used as default EBS encryption key.\n\nTo enable EBS default encryption in every region accelerator implemented, you need to provide below value for this parameter." - }, - "guardduty": { - "$ref": "#/definitions/IGuardDutyConfig", - "description": "Amazon GuardDuty Configuration" - }, - "macie": { - "$ref": "#/definitions/IMacieConfig", - "description": "Amazon Macie Configuration\n\nAccelerator use this parameter to define AWS Macie configuration.\n\nTo enable Macie in every region accelerator implemented and set fifteen minutes of frequency to publish updates to policy findings for the account with publishing sensitive data findings to Security Hub. you need to provide below value for this parameter." - }, - "s3PublicAccessBlock": { - "$ref": "#/definitions/IS3PublicAccessBlockConfig", - "description": "AWS S3 public access block configuration\n\nAccelerator use this parameter to block AWS S3 public access\n\nTo enable S3 public access blocking in every region accelerator implemented, you need to provide below value for this parameter." - }, - "scpRevertChangesConfig": { - "$ref": "#/definitions/IScpRevertChangesConfig", - "description": "(OPTIONAL) AWS Service Control Policies Revert Manual Changes configuration" - }, - "securityHub": { - "$ref": "#/definitions/ISecurityHubConfig", - "description": "AWS Security Hub configuration\n\nAccelerator use this parameter to define AWS Security Hub configuration.\n\nTo enable AWS Security Hub for all regions and enable \"AWS Foundational Security Best Practices v1.0.0\" security standard, deployment targets and disable controls you need provide below value for this parameter." - }, - "snsSubscriptions": { - "description": "AWS SNS subscription configuration Deprecated\n\nNOTICE: The configuration of SNS topics is being moved to the Global Config. This block is deprecated and will be removed in a future release\n\nAccelerator use this parameter to define AWS SNS notification configuration.\n\nTo enable high, medium and low SNS notifications, you need to provide below value for this parameter.", - "items": { - "$ref": "#/definitions/ISnsSubscriptionConfig" - }, - "type": "array" - }, - "ssmAutomation": { - "$ref": "#/definitions/ISsmAutomationConfig", - "description": "AWS Systems Manager Document configuration\n\nAccelerator use this parameter to define AWS Systems Manager documents configuration. SSM documents are created in designated administrator account for security services, i.e. Audit account.\n\nTo create a SSM document named as \"SSM-ELB-Enable-Logging\" in every region accelerator implemented and share this document with Root organizational unit(OU), you need to provide below value for this parameter. To share document to specific account uncomment accounts list. A valid SSM document template file ssm-documents/ssm-elb-enable-logging.yaml must be present in Accelerator config repository. Accelerator will use this template file to create the document." - } - }, - "required": [ - "delegatedAdminAccount", - "ebsDefaultVolumeEncryption", - "s3PublicAccessBlock", - "macie", - "guardduty", - "securityHub", - "ssmAutomation" - ], - "type": "object" - }, - "ICloudWatchConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch configuration", - "properties": { - "alarmSets": { - "description": "List AWS CloudWatch Alarms configuration\n\nFollowing example will create CIS-1.1-RootAccountUsage alarm for RootAccountUsage metric with notification level low", - "items": { - "$ref": "#/definitions/IAlarmSetConfig" - }, - "type": "array" - }, - "logGroups": { - "description": "(OPTIONAL) List CloudWatch Logs configuration\n\nThe Following is an example of deploying CloudWatch Logs to multiple regions", - "items": { - "$ref": "#/definitions/ILogGroupsConfig" - }, - "type": "array" - }, - "metricSets": { - "description": "List AWS CloudWatch Metrics configuration\n\nFollowing example will create metric filter RootAccountMetricFilter for aws-controltower/CloudTrailLogs log group", - "items": { - "$ref": "#/definitions/IMetricSetConfig" - }, - "type": "array" - } - }, - "required": [ - "metricSets", - "alarmSets" - ], - "type": "object" - }, - "IConfigRule": { - "additionalProperties": false, - "description": "AWS ConfigRule configuration", - "properties": { - "complianceResourceTypes": { - "description": "(OPTIONAL) Defines which resources trigger an evaluation for an AWS Config rule.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "customRule": { - "$ref": "#/definitions/ICustomRuleConfigType", - "description": "(OPTIONAL) A custom config rule is backed by AWS Lambda function. This is required when creating custom config rule." - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description about this AWS Config rule." - }, - "identifier": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The identifier of the AWS managed rule." - }, - "inputParameters": { - "anyOf": [ - { - "additionalProperties": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "object" - }, - { - "type": "null" - } - ], - "description": "(OPTIONAL) Input parameter values that are passed to the AWS Config rule." - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "A name for the AWS Config rule." - }, - "remediation": { - "$ref": "#/definitions/IConfigRuleRemediationType", - "description": "A remediation for the config rule, auto remediation to automatically remediate noncompliant resources." - }, - "tags": { - "description": "(OPTIONAL) Tags for the config rule", - "items": { - "$ref": "#/definitions/ITag" - }, - "type": "array" - }, - "type": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Config rule type Managed or Custom. For custom config rule, this parameter value is Custom, when creating managed config rule this parameter value can be undefined or empty string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "IConfigRuleRemediationType": { - "additionalProperties": false, - "properties": { - "automatic": { - "description": "The remediation is triggered automatically.", - "type": "boolean" - }, - "excludeRegions": { - "description": "List of AWS Region names to be excluded from applying remediation", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "maximumAutomaticAttempts": { - "description": "The maximum number of failed attempts for auto-remediation. If you do not select a number, the default is 5.\n\nFor example, if you specify MaximumAutomaticAttempts as 5 with RetryAttemptSeconds as 50 seconds, AWS Config will put a RemediationException on your behalf for the failing resource after the 5th failed attempt within 50 seconds.", - "type": "number" - }, - "parameters": { - "description": "List of remediation parameters", - "items": { - "$ref": "#/definitions/IRemediationParametersConfigType" - }, - "type": "array" - }, - "retryAttemptSeconds": { - "description": "Maximum time in seconds that AWS Config runs auto-remediation. If you do not select a number, the default is 60 seconds.\n\nFor example, if you specify RetryAttemptSeconds as 50 seconds and MaximumAutomaticAttempts as 5, AWS Config will run auto-remediations 5 times within 50 seconds before throwing an exception.", - "type": "number" - }, - "rolePolicyFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "Remediation assume role policy definition json file. This file must be present in config repository.\n\nCreate your own custom remediation actions using AWS Systems Manager Automation documents. When a role needed to be created to perform custom remediation actions, role permission needs to be defined in this file." - }, - "targetAccountName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the account owning the public document to perform custom remediation actions. Accelerator creates these documents in Audit account and shared with other accounts." - }, - "targetDocumentLambda": { - "$ref": "#/definitions/ICustomRuleLambdaType", - "description": "Target SSM document remediation lambda function" - }, - "targetId": { - "$ref": "#/definitions/NonEmptyString", - "description": "Target ID is the name of the public document.\n\nThe name of the AWS SSM document to perform custom remediation actions." - }, - "targetVersion": { - "$ref": "#/definitions/NonEmptyString", - "description": "Version of the target. For example, version of the SSM document.\n\nIf you make backward incompatible changes to the SSM document, you must call PutRemediationConfiguration API again to ensure the remediations can run." - } - }, - "required": [ - "rolePolicyFile", - "automatic", - "targetId" - ], - "type": "object" - }, - "ICustomRuleConfigType": { - "additionalProperties": false, - "properties": { - "configurationChanges": { - "description": "Whether to run the rule on configuration changes.\n\nDefault: false", - "type": "boolean" - }, - "lambda": { - "$ref": "#/definitions/ICustomRuleLambdaType", - "description": "The Lambda function to run." - }, - "maximumExecutionFrequency": { - "description": "The maximum frequency at which the AWS Config rule runs evaluations.\n\nDefault: MaximumExecutionFrequency.TWENTY_FOUR_HOURS", - "type": "string" - }, - "periodic": { - "default": true, - "description": "Whether to run the rule on a fixed frequency.", - "type": "boolean" - }, - "triggeringResources": { - "$ref": "#/definitions/ITriggeringResourceType", - "description": "Defines which resources trigger an evaluation for an AWS Config rule." - } - }, - "required": [ - "lambda", - "maximumExecutionFrequency", - "triggeringResources" - ], - "type": "object" - }, - "ICustomRuleLambdaType": { - "additionalProperties": false, - "properties": { - "handler": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the method within your code that Lambda calls to execute your function. The format includes the file name. It can also include namespaces and other qualifiers, depending on the runtime. For more information, see https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-features.html#gettingstarted-features-programmingmodel." - }, - "rolePolicyFile": { - "$ref": "#/definitions/NonEmptyString", - "description": "Lambda execution role policy definition file" - }, - "runtime": { - "$ref": "#/definitions/NonEmptyString", - "description": "The runtime environment for the Lambda function that you are uploading. For valid values, see the Runtime property in the AWS Lambda Developer Guide." - }, - "sourceFilePath": { - "$ref": "#/definitions/NonEmptyString", - "description": "The source code file path of your Lambda function. This is a zip file containing lambda function, this file must be available in config repository." - }, - "timeout": { - "description": "Lambda function execution timeout in seconds", - "type": "number" - } - }, - "required": [ - "sourceFilePath", - "handler", - "runtime", - "rolePolicyFile" - ], - "type": "object" - }, - "IDeploymentTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedAccounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "excludedRegions": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "IDetectiveConfig": { - "additionalProperties": false, - "description": "Use this configuration to enable Amazon Detective for an AWS Organization that allows users to analyze, investigate, and\nquickly identify the root cause of security findings or suspicious activities.", - "properties": { - "enable": { - "description": "Indicates whether Amazon Detective is enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring Amazon Detective. Please ensure any regions enabled in the global configuration that do not support Amazon Detective are added to the excluded regions list. {@link https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ Supported services by region } .", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IDocumentConfig": { - "additionalProperties": false, - "description": "Use this configuration to define AWS System Manager documents (SSM documents) that can be used on managed instances in an\nenvironment.", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of document to be created" - }, - "targetType": { - "$ref": "#/definitions/NonEmptyString", - "description": "Specify a target type to define the kinds of resources the document can run on. For example, to run a document on EC2 instances, specify the following value: /AWS::EC2::Instance. If you specify a value of '/' the document can run on all types of resources. If you don't specify a value, the document can't run on any resources. For a list of valid resource types, see AWS resource and property types reference in the AWS CloudFormation User Guide. Ref: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html Length Constraints: Maximum length of 200. Pattern: ^\\/[\\w\\.\\-\\:\\/]*$" - }, - "template": { - "$ref": "#/definitions/NonEmptyString", - "description": "Document template file path. This file must be available in accelerator config repository." - } - }, - "required": [ - "name", - "template" - ], - "type": "object" - }, - "IDocumentSetConfig": { - "additionalProperties": false, - "description": "AWS Systems Manager document sharing configuration", - "properties": { - "documents": { - "description": "List of the documents to be shared", - "items": { - "$ref": "#/definitions/IDocumentConfig" - }, - "type": "array" - }, - "shareTargets": { - "$ref": "#/definitions/IShareTargets", - "description": "Document share target, valid value should be any organizational unit. Document will be shared with every account within the given OU" - } - }, - "required": [ - "shareTargets", - "documents" - ], - "type": "object" - }, - "IEbsDefaultVolumeEncryptionConfig": { - "additionalProperties": false, - "description": "Use this configuration to enable enforced encryption of new EBS volumes and snapshots created in an AWS environment.", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "(OPTIONAL) Deployment targets for EBS default volume encryption" - }, - "enable": { - "description": "Indicates whether AWS EBS volume have default encryption enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring AWS EBS volume default encryption", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "kmsKey": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) KMS key to encrypt EBS volume." - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IEncryptionConfig": { - "additionalProperties": false, - "description": "Use this configuration to enable encryption for a log group.", - "properties": { - "kmsKeyArn": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Reference the KMS Key Arn that is used to encrypt the AWS CloudWatch Logs Group. This should be a KMS Key that is not managed by Landing Zone Accelerator." - }, - "kmsKeyName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Use this property to reference a KMS Key Name that is created by Landing Zone Accelerator." - }, - "useLzaManagedKey": { - "description": "(OPTIONAL) Set this property to `true` if you would like to use the default CloudWatch Logs KMS CMK that is deployed by Landing Zone Accelerator.", - "type": "boolean" - } - }, - "type": "object" - }, - "IGuardDutyConfig": { - "additionalProperties": false, - "description": "AWS GuardDuty configuration\nUse this configuration to enable Amazon GuardDuty for an AWS Organization, as well as other modular\nfeature protections.", - "properties": { - "autoEnableOrgMembers": { - "default": true, - "description": "(OPTIONAL) Enables/disables the auto enabling of GuardDuty for any account including the new accounts joining the organization\n\nIt is recommended to set the value to `false` when using the `deploymentTargets` property to enable GuardDuty only on targeted accounts mentioned in the deploymentTargets. If you do not define or do not set it to `false` any new accounts joining the organization will automatically be enabled with GuardDuty.", - "type": "boolean" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "(OPTIONAL) Deployment targets for GuardDuty\n\nWe highly recommend enabling GuardDuty across all accounts and enabled regions within your organization. `deploymentTargets` should only be used when more granular control is required, not as a default configuration Please only specify one of the `deploymentTargets` or `excludeRegions` properties.\n\nNote: The delegated admin account defined in centralSecurityServices will always have GuardDuty enabled" - }, - "eksProtection": { - "$ref": "#/definitions/IGuardDutyEksProtectionConfig", - "description": "(OPTIONAL) AWS GuardDuty EKS Protection configuration." - }, - "enable": { - "description": "Indicates whether AWS GuardDuty enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring Amazon GuardDuty\n\nPlease only specify one of the `excludeRegions` or `deploymentTargets` properties.", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "exportConfiguration": { - "$ref": "#/definitions/IGuardDutyExportFindingsConfig", - "description": "AWS GuardDuty Export Findings configuration." - }, - "lifecycleRules": { - "description": "(OPTIONAL) Declaration of a S3 Lifecycle rule.", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "s3Protection": { - "$ref": "#/definitions/IGuardDutyS3ProtectionConfig", - "description": "AWS GuardDuty S3 Protection configuration." - } - }, - "required": [ - "enable", - "s3Protection", - "exportConfiguration" - ], - "type": "object" - }, - "IGuardDutyEksProtectionConfig": { - "additionalProperties": false, - "description": "AWS GuardDuty EKS Protection configuration.", - "properties": { - "enable": { - "description": "Indicates whether AWS GuardDuty EKS Protection enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring Amazon GuardDuty EKS Protection", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IGuardDutyExportFindingsConfig": { - "additionalProperties": false, - "description": "Use this configuration to export Amazon GuardDuty findings to Amazon CloudWatch Events, and, optionally, to an Amazon S3 bucket.", - "properties": { - "destinationType": { - "const": "S3", - "description": "The type of resource for the publishing destination. Currently only Amazon S3 buckets are supported.", - "type": "string" - }, - "enable": { - "description": "Indicates whether AWS GuardDuty Export Findings enabled.", - "type": "boolean" - }, - "exportFrequency": { - "description": "An enum value that specifies how frequently findings are exported, such as to CloudWatch Events. Possible values FIFTEEN_MINUTES, ONE_HOUR, or SIX_HOURS", - "enum": [ - "FIFTEEN_MINUTES", - "ONE_HOUR", - "SIX_HOURS" - ], - "type": "string" - }, - "overrideExisting": { - "description": "(OPTIONAL) Indicates whether AWS GuardDuty Export Findings can be overwritten.", - "type": "boolean" - }, - "overrideGuardDutyPrefix": { - "$ref": "#/definitions/IPrefixConfig", - "description": "(OPTIONAL) AWS GuardDuty Prefix for centralized logging path." - } - }, - "required": [ - "enable", - "destinationType", - "exportFrequency" - ], - "type": "object" - }, - "IGuardDutyS3ProtectionConfig": { - "additionalProperties": false, - "description": "Use this configuration to enable S3 Protection with Amazon GuardDuty to monitor object-level API operations for potential\nsecurity risks for data within Amazon S3 buckets.", - "properties": { - "enable": { - "description": "Indicates whether AWS GuardDuty S3 Protection enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring Amazon GuardDuty S3 Protection", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IIamPasswordPolicyConfig": { - "additionalProperties": false, - "description": "IAM password policy configuration", - "properties": { - "allowUsersToChangePassword": { - "default": true, - "description": "Allows all IAM users in your account to use the AWS Management Console to change their own passwords.", - "type": "boolean" - }, - "hardExpiry": { - "default": true, - "description": "Prevents IAM users who are accessing the account via the AWS Management Console from setting a new console password after their password has expired. The IAM user cannot access the console until an administrator resets the password.", - "type": "boolean" - }, - "maxPasswordAge": { - "default": 90, - "description": "The number of days that an IAM user password is valid.\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of 0. The result is that IAM user passwords never expire.", - "type": "number" - }, - "minimumPasswordLength": { - "default": 14, - "description": "The minimum number of characters allowed in an IAM user password.\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of 6.", - "type": "number" - }, - "passwordReusePrevention": { - "default": 24, - "description": "Specifies the number of previous passwords that IAM users are prevented from reusing.\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of 0. The result is that IAM users are not prevented from reusing previous passwords.", - "type": "number" - }, - "requireLowercaseCharacters": { - "default": true, - "description": "Specifies whether IAM user passwords must contain at least one lowercase character from the ISO basic Latin alphabet (a to z).\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one lowercase character.", - "type": "boolean" - }, - "requireNumbers": { - "default": true, - "description": "Specifies whether IAM user passwords must contain at least one numeric character (0 to 9).\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one numeric character.", - "type": "boolean" - }, - "requireSymbols": { - "default": true, - "description": "Specifies whether IAM user passwords must contain at least one of the following non-alphanumeric characters:\n\n! @ # $ % ^ & * ( ) _ + - = [ ] { } | '\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one symbol character.", - "type": "boolean" - }, - "requireUppercaseCharacters": { - "default": true, - "description": "Specifies whether IAM user passwords must contain at least one uppercase character from the ISO basic Latin alphabet (A to Z).\n\nNote: If you do not specify a value for this parameter, then the operation uses the default value of false. The result is that passwords do not require at least one uppercase character.", - "type": "boolean" - } - }, - "required": [ - "allowUsersToChangePassword", - "hardExpiry", - "requireUppercaseCharacters", - "requireLowercaseCharacters", - "requireSymbols", - "requireNumbers", - "minimumPasswordLength", - "passwordReusePrevention", - "maxPasswordAge" - ], - "type": "object" - }, - "IKeyConfig": { - "additionalProperties": false, - "description": "Use this configuration to define your customer managed key (CMK) and where it's deployed to along with\nit's management properties.", - "properties": { - "alias": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) Initial alias to add to the key" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "This configuration determines which accounts and/or OUs the CMK is deployed to.\n\nTo deploy KMS key into Root and Infrastructure organizational units, you need to provide below value for this parameter." - }, - "description": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) A description of the key." - }, - "enableKeyRotation": { - "default": true, - "description": "(OPTIONAL) Indicates whether AWS KMS rotates the key.", - "type": "boolean" - }, - "enabled": { - "default": "- Key is enabled.", - "description": "(OPTIONAL) Indicates whether the key is available for use.", - "type": "boolean" - }, - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Unique Key name for logical reference" - }, - "policy": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL)Key policy file path. This file must be available in accelerator config repository." - }, - "removalPolicy": { - "default": "retain", - "description": "(OPTIONAL) Whether the encryption key should be retained when it is removed from the Stack.", - "enum": [ - "destroy", - "retain", - "snapshot" - ], - "type": "string" - } - }, - "required": [ - "name", - "deploymentTargets" - ], - "type": "object" - }, - "IKeyManagementServiceConfig": { - "additionalProperties": false, - "description": " KMS key management service configuration", - "properties": { - "keySets": { - "items": { - "$ref": "#/definitions/IKeyConfig" - }, - "type": "array" - } - }, - "required": [ - "keySets" - ], - "type": "object" - }, - "ILifecycleRule": { - "additionalProperties": false, - "description": "S3 bucket life cycle rules object.", - "properties": { - "abortIncompleteMultipartUpload": { - "description": "Specifies a lifecycle rule that aborts incomplete multipart uploads to an Amazon S3 bucket.", - "type": "number" - }, - "enabled": { - "description": "Whether this rule is enabled.", - "type": "boolean" - }, - "expiration": { - "description": "Indicates the number of days after creation when objects are deleted from Amazon S3 and Amazon Glacier.", - "type": "number" - }, - "expiredObjectDeleteMarker": { - "description": "Indicates whether Amazon S3 will remove a delete marker with no noncurrent versions. If set to true, the delete marker will be expired.", - "type": "boolean" - }, - "id": { - "description": "Friendly name for the rule. Rule name must be unique.", - "type": "string" - }, - "noncurrentVersionExpiration": { - "description": "Time between when a new version of the object is uploaded to the bucket and when old versions of the object expire.", - "type": "number" - }, - "noncurrentVersionTransitions": { - "description": "One or more transition rules that specify when non-current objects transition to a specified storage class.", - "items": { - "$ref": "#/definitions/ITransition" - }, - "type": "array" - }, - "prefix": { - "$ref": "#/definitions/NonEmptyString", - "default": "- Rule applies to all objects", - "description": "Object key prefix that identifies one or more objects to which this rule applies." - }, - "transitions": { - "description": "One or more transition rules that specify when an object transitions to a specified storage class.", - "items": { - "$ref": "#/definitions/ITransition" - }, - "type": "array" - } - }, - "type": "object" - }, - "ILogGroupsConfig": { - "additionalProperties": false, - "description": "Use this configuration to deploy CloudWatch log groups to your environment.\nYou can also import existing log groups into your accelerator configuration.\nLog groups define groups of log streams that share the same retention, monitoring, and access control settings.", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Deployment targets for CloudWatch Logs" - }, - "encryption": { - "$ref": "#/definitions/IEncryptionConfig", - "description": "(OPTIONAL) The encryption configuration of the AWS CloudWatch Logs Group." - }, - "logGroupName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the CloudWatch log group" - }, - "logRetentionInDays": { - "default": "undefined", - "description": "(OPTIONAL) How long, in days, the log contents will be retained.\n\nTo retain all logs, set this value to undefined.", - "type": "number" - }, - "terminationProtected": { - "default": true, - "description": "(OPTIONAL) Set this property to `false` if you would like the log group to be deleted if it is removed from the solution configuration file.", - "type": "boolean" - } - }, - "required": [ - "logGroupName", - "logRetentionInDays", - "deploymentTargets" - ], - "type": "object" - }, - "IMacieConfig": { - "additionalProperties": false, - "description": "Amazon Macie Configuration\nUse this configuration to enable Amazon Macie within your AWS Organization along with it's reporting configuration.", - "properties": { - "enable": { - "description": "Indicates whether AWS Macie enabled.", - "type": "boolean" - }, - "excludeRegions": { - "description": "List of AWS Region names to be excluded from configuring Amazon Macie", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "lifecycleRules": { - "description": "(OPTIONAL) Declaration of a S3 Lifecycle rule.", - "items": { - "$ref": "#/definitions/ILifecycleRule" - }, - "type": "array" - }, - "policyFindingsPublishingFrequency": { - "description": "(OPTIONAL) Specifies how often to publish updates to policy findings for the account. This includes publishing updates to Security Hub and Amazon EventBridge (formerly called Amazon CloudWatch Events). An enum value that specifies how frequently findings are published Possible values FIFTEEN_MINUTES, ONE_HOUR, or SIX_HOURS", - "enum": [ - "FIFTEEN_MINUTES", - "ONE_HOUR", - "SIX_HOURS" - ], - "type": "string" - }, - "publishSensitiveDataFindings": { - "description": "Specifies whether to publish sensitive data findings to Security Hub. If you set this value to true, Amazon Macie automatically publishes all sensitive data findings that weren't suppressed by a findings filter. The default value is false.", - "type": "boolean" - } - }, - "required": [ - "enable", - "publishSensitiveDataFindings" - ], - "type": "object" - }, - "IMetricConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch Metric configuration", - "properties": { - "filterName": { - "$ref": "#/definitions/NonEmptyString", - "description": "Metric filter name" - }, - "filterPattern": { - "$ref": "#/definitions/NonEmptyString", - "description": "Pattern to search for log events." - }, - "logGroupName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The log group to create the filter on." - }, - "metricName": { - "$ref": "#/definitions/NonEmptyString", - "description": "The name of the metric to emit." - }, - "metricNamespace": { - "$ref": "#/definitions/NonEmptyString", - "description": "The namespace of the metric to emit." - }, - "metricValue": { - "$ref": "#/definitions/NonEmptyString", - "description": "The value to emit for the metric.\n\nCan either be a literal number (typically “1”), or the name of a field in the structure to take the value from the matched event. If you are using a field value, the field value must have been matched using the pattern." - }, - "treatMissingData": { - "$ref": "#/definitions/NonEmptyString", - "description": "Sets how this alarm is to handle missing data points." - } - }, - "required": [ - "filterName", - "logGroupName", - "filterPattern", - "metricNamespace", - "metricName", - "metricValue" - ], - "type": "object" - }, - "IMetricSetConfig": { - "additionalProperties": false, - "description": "AWS CloudWatch Metric set configuration", - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "Deployment targets for CloudWatch Metrics configuration" - }, - "metrics": { - "description": "AWS CloudWatch Metric list\n\nFollowing example will create metric filter RootAccountMetricFilter for aws-controltower/CloudTrailLogs log group", - "items": { - "$ref": "#/definitions/IMetricConfig" - }, - "type": "array" - }, - "regions": { - "description": "(OPTIONAL) AWS region names to configure CloudWatch Metrics", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "metrics" - ], - "type": "object" - }, - "INetworkPerimeterConfig": { - "additionalProperties": false, - "description": "Network Perimeter Config.\n\nIf managedVpcOnly is true, all the VPCs in accounts will be included while parameter `ACCEL_LOOKUP:VPC|VPC_ID:XX` is used.\nIf managedVpcOnly is false, only the VPC created by LZA will be included while parameter `ACCEL_LOOKUP:VPC|VPC_ID:XX` is used.", - "properties": { - "managedVpcOnly": { - "type": "boolean" - } - }, - "type": "object" - }, - "IPrefixConfig": { - "additionalProperties": false, - "properties": { - "customOverride": { - "$ref": "#/definitions/NonEmptyString", - "description": "(Optional) Prefix to be used for Centralized Logging Path" - }, - "useCustomPrefix": { - "description": "Indicates whether or not to add a custom prefix to LZA Default Centralized Logging location. If useCustomPrefix is set to true, logs will be stored in the Centralized Logging Bucket prefix.", - "type": "boolean" - } - }, - "required": [ - "useCustomPrefix" - ], - "type": "object" - }, - "IRemediationParametersConfigType": { - "additionalProperties": false, - "description": "Config rule remediation input parameter configuration type", - "properties": { - "name": { - "$ref": "#/definitions/NonEmptyString", - "description": "Name of the parameter" - }, - "type": { - "description": "Data type of the parameter, allowed value (StringList or String)", - "enum": [ - "String", - "StringList" - ], - "type": "string" - }, - "value": { - "$ref": "#/definitions/NonEmptyString", - "description": "Parameter value" - } - }, - "required": [ - "name", - "value", - "type" - ], - "type": "object" - }, - "IResourcePolicyConfig": { - "additionalProperties": false, - "properties": { - "document": { - "$ref": "#/definitions/NonEmptyString" - }, - "resourceType": { - "enum": [ - "S3_BUCKET", - "KMS_KEY", - "IAM_ROLE", - "SECRETS_MANAGER_SECRET", - "ECR_REPOSITORY", - "OPENSEARCH_DOMAIN", - "SNS_TOPIC", - "SQS_QUEUE", - "APIGATEWAY_REST_API", - "LEX_BOT", - "EFS_FILE_SYSTEM", - "EVENTBRIDGE_EVENTBUS", - "BACKUP_VAULT", - "CODEARTIFACT_REPOSITORY", - "CERTIFICATE_AUTHORITY", - "LAMBDA_FUNCTION" - ], - "type": "string" - } - }, - "required": [ - "resourceType", - "document" - ], - "type": "object" - }, - "IResourcePolicyEnforcementConfig": { - "additionalProperties": false, - "description": "Resource Policy Enforcement Config. The configuration allows you to deploy AWS Config rules to\nautomatically apply resource-based policies to AWS resources including S3 buckets, IAM roles, and KMS keys etc.\nAWS Organization is required to support it.\n\nHere are a list of supported service {@link SecurityConfigTypes.resourceTypeEnum }", - "properties": { - "enable": { - "type": "boolean" - }, - "networkPerimeter": { - "$ref": "#/definitions/INetworkPerimeterConfig" - }, - "policySets": { - "items": { - "$ref": "#/definitions/IResourcePolicySetConfig" - }, - "type": "array" - }, - "remediation": { - "$ref": "#/definitions/IResourcePolicyRemediation" - } - }, - "required": [ - "enable", - "remediation", - "policySets" - ], - "type": "object" - }, - "IResourcePolicyRemediation": { - "additionalProperties": false, - "properties": { - "automatic": { - "description": "The remediation is triggered automatically.", - "type": "boolean" - }, - "maximumAutomaticAttempts": { - "description": "The maximum number of failed attempts for auto-remediation. If you do not select a number, the default is 5.", - "type": "number" - }, - "retryAttemptSeconds": { - "description": "Maximum time in seconds that AWS Config runs auto-remediation. If you do not select a number, the default is 60 seconds.", - "type": "number" - } - }, - "required": [ - "automatic" - ], - "type": "object" - }, - "IResourcePolicySetConfig": { - "additionalProperties": false, - "properties": { - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "The deployment targets - accounts/OUs where the config rule and remediation action will be deployed to" - }, - "inputParameters": { - "additionalProperties": { - "type": "string" - }, - "description": "The input parameters which will be set as environment variable in Custom Config Rule Lambda and Remediation lambda\n\nMeanwhile, 'SourceAccount' is a reserved parameters for allow-only resource policy -- Lambda_Function and CERTIFICATE_AUTHORITY. For example, 'SourceAccount: 123456789012,987654321098' means requests from these two accounts can be allowed. Apart from these two, No other external accounts can access a lambda function or Certificate Authority.", - "type": "object" - }, - "resourcePolicies": { - "description": "A list of resource policy templates for different types of resources", - "items": { - "$ref": "#/definitions/IResourcePolicyConfig" - }, - "type": "array" - } - }, - "required": [ - "deploymentTargets", - "resourcePolicies" - ], - "type": "object" - }, - "IS3PublicAccessBlockConfig": { - "additionalProperties": false, - "description": "This will create the Public Access Block configuration for the AWS account.", - "properties": { - "enable": { - "description": "Indicates whether AWS S3 block public access is enabled.", - "type": "boolean" - }, - "excludeAccounts": { - "description": "List of AWS Account names to be excluded from configuring S3 PublicAccessBlock", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "IScpRevertChangesConfig": { - "additionalProperties": false, - "description": "AWS Service Control Policies Revert Manual Changes configuration", - "properties": { - "enable": { - "description": "Indicates whether manual changes to Service Control Policies are automatically reverted.", - "type": "boolean" - }, - "snsTopicName": { - "$ref": "#/definitions/NonEmptyString", - "description": "(OPTIONAL) The name of the SNS Topic to send alerts to when SCPs are changed manually" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "ISecurityConfig": { - "additionalProperties": false, - "description": "Accelerator security configuration", - "properties": { - "accessAnalyzer": { - "$ref": "#/definitions/IAccessAnalyzerConfig" - }, - "awsConfig": { - "$ref": "#/definitions/IAwsConfig" - }, - "centralSecurityServices": { - "$ref": "#/definitions/ICentralSecurityServicesConfig", - "description": "Central security configuration" - }, - "cloudWatch": { - "$ref": "#/definitions/ICloudWatchConfig" - }, - "homeRegion": { - "$ref": "#/definitions/Region", - "description": "Accelerator home region name." - }, - "iamPasswordPolicy": { - "$ref": "#/definitions/IIamPasswordPolicyConfig" - }, - "keyManagementService": { - "$ref": "#/definitions/IKeyManagementServiceConfig" - }, - "resourcePolicyEnforcement": { - "$ref": "#/definitions/IResourcePolicyEnforcementConfig" - } - }, - "required": [ - "centralSecurityServices", - "accessAnalyzer", - "iamPasswordPolicy", - "awsConfig", - "cloudWatch" - ], - "type": "object" - }, - "ISecurityHubConfig": { - "additionalProperties": false, - "description": "Use this configuration to enable Amazon Security Hub for an AWS Organization along with it's auditing configuration.", - "properties": { - "autoEnableOrgMembers": { - "default": true, - "description": "(OPTIONAL) Enables/disables the auto enabling of SecurityHub for any account including the new accounts joining the organization\n\nIt is recommended to set the value to `false` when using the `deploymentTargets` property to enable SecurityHub only on targeted accounts mentioned in the deploymentTargets. If you do not define or do not set it to `false` any new accounts joining the organization will automatically be enabled with SecurityHub.", - "type": "boolean" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "(OPTIONAL) Deployment targets for SecurityHub\n\nWe highly recommend enabling SecurityHub across all accounts and enabled regions within your organization. `deploymentTargets` should only be used when more granular control is required, not as a default configuration Please only specify one of the `deploymentTargets` or `excludeRegions` properties.\n\nNote: The delegated admin account defined in centralSecurityServices will always have SecurityHub enabled." - }, - "enable": { - "description": "Indicates whether AWS Security Hub is enabled (AWSConfig is required for enabling SecurityHub)", - "type": "boolean" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring Security Hub", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - }, - "logging": { - "$ref": "#/definitions/ISecurityHubLoggingConfig", - "description": "(OPTIONAL) Security Hub logs are sent to CloudWatch logs by default. This option can enable or disable the logging." - }, - "notificationLevel": { - "description": "(OPTIONAL) Security Hub notification level", - "type": "string" - }, - "regionAggregation": { - "description": "(OPTIONAL) Indicates whether Security Hub results are aggregated in the Home Region.", - "type": "boolean" - }, - "snsTopicName": { - "description": "(OPTIONAL) SNS Topic for Security Hub notifications.", - "type": "string" - }, - "standards": { - "description": "Security Hub standards configuration", - "items": { - "$ref": "#/definitions/ISecurityHubStandardConfig" - }, - "type": "array" - } - }, - "required": [ - "enable", - "standards" - ], - "type": "object" - }, - "ISecurityHubLoggingCloudwatchConfig": { - "additionalProperties": false, - "description": "Security Hub Logging CloudWatch Config", - "properties": { - "enable": { - "description": "Security hub to cloudwatch logging is enabled by default.", - "type": "boolean" - }, - "logGroupName": { - "description": "(OPTIONAL) CloudWatch Log Group Name", - "type": "string" - }, - "logLevel": { - "$ref": "#/definitions/SecurityHubSeverityLevel", - "description": "(OPTIONAL) Security Hub logging level" - } - }, - "required": [ - "enable" - ], - "type": "object" - }, - "ISecurityHubLoggingConfig": { - "additionalProperties": false, - "description": "Security Hub Logging Config", - "properties": { - "cloudWatch": { - "$ref": "#/definitions/ISecurityHubLoggingCloudwatchConfig", - "description": "Data store to ship the Security Hub logs to." - } - }, - "type": "object" - }, - "ISecurityHubStandardConfig": { - "additionalProperties": false, - "description": "Use this configuration to define the security standard(s) that are enabled through Amazon Security Hub and which accounts and/or\norganization units that the controls are deployed to.", - "properties": { - "controlsToDisable": { - "description": "(OPTIONAL) An array of control names to be disabled for the given security standards", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - }, - "deploymentTargets": { - "$ref": "#/definitions/IDeploymentTargets", - "description": "(OPTIONAL) Deployment targets for AWS Security Hub standard." - }, - "enable": { - "description": "Indicates whether given AWS Security Hub standard enabled.", - "type": "boolean" - }, - "name": { - "description": "An enum value that specifies one of three security standards supported by Security Hub Possible values are 'AWS Foundational Security Best Practices v1.0.0', 'CIS AWS Foundations Benchmark v1.2.0', 'CIS AWS Foundations Benchmark v1.4.0', 'CIS AWS Foundations Benchmark v3.0.0', 'NIST Special Publication 800-53 Revision 5, and 'PCI DSS v3.2.1'", - "enum": [ - "AWS Foundational Security Best Practices v1.0.0", - "CIS AWS Foundations Benchmark v1.2.0", - "CIS AWS Foundations Benchmark v1.4.0", - "CIS AWS Foundations Benchmark v3.0.0", - "NIST Special Publication 800-53 Revision 5", - "PCI DSS v3.2.1", - "" - ], - "type": "string" - } - }, - "required": [ - "name", - "enable" - ], - "type": "object" - }, - "IShareTargets": { - "additionalProperties": false, - "properties": { - "accounts": { - "items": { - "type": "string" - }, - "type": "array" - }, - "organizationalUnits": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "ISnsSubscriptionConfig": { - "additionalProperties": false, - "description": "AWS SNS Notification subscription configuration\n***Deprecated***\nReplaced by snsTopics in global config", - "properties": { - "email": { - "$ref": "#/definitions/NonEmptyString", - "description": "Subscribing email address" - }, - "level": { - "$ref": "#/definitions/NonEmptyString", - "description": "Notification level high, medium or low" - } - }, - "required": [ - "level", - "email" - ], - "type": "object" - }, - "ISsmAutomationConfig": { - "additionalProperties": false, - "description": "AWS Systems Manager automation configuration", - "properties": { - "documentSets": { - "description": "List of documents for automation", - "items": { - "$ref": "#/definitions/IDocumentSetConfig" - }, - "type": "array" - }, - "excludeRegions": { - "description": "(OPTIONAL) List of AWS Region names to be excluded from configuring block S3 public access", - "items": { - "$ref": "#/definitions/Region" - }, - "type": "array" - } - }, - "required": [ - "documentSets" - ], - "type": "object" - }, - "ITag": { - "additionalProperties": false, - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "key", - "value" - ], - "type": "object" - }, - "ITransition": { - "additionalProperties": false, - "properties": { - "storageClass": { - "$ref": "#/definitions/StorageClass" - }, - "transitionAfter": { - "type": "number" - } - }, - "required": [ - "storageClass", - "transitionAfter" - ], - "type": "object" - }, - "ITriggeringResourceType": { - "additionalProperties": false, - "properties": { - "lookupKey": { - "$ref": "#/definitions/NonEmptyString", - "description": "Resource lookup type, resource can be lookup by tag or types. When resource needs to lookup by tag, this field will have tag name." - }, - "lookupType": { - "description": "An enum to identify triggering resource types. Possible values ResourceId, Tag, or ResourceTypes\n\nTriggering resource can be lookup by resource id, tags or resource types.", - "type": "string" - }, - "lookupValue": { - "description": "Resource lookup value, when resource lookup using tag, this field will have tag value to search resource.", - "items": { - "$ref": "#/definitions/NonEmptyString" - }, - "type": "array" - } - }, - "required": [ - "lookupType", - "lookupKey", - "lookupValue" - ], - "type": "object" - }, - "NonEmptyString": { - "description": "A string that has at least 1 character", - "minLength": 1, - "type": "string" - }, - "Region": { - "description": "AWS Region", - "enum": [ - "af-south-1", - "ap-east-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-south-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-southeast-4", - "ca-central-1", - "ca-west-1", - "cn-north-1", - "cn-northwest-1", - "eu-central-1", - "eu-central-2", - "eu-north-1", - "eu-south-1", - "eu-south-2", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "il-central-1", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-gov-west-1", - "us-gov-east-1", - "us-iso-east-1", - "us-isob-east-1", - "us-iso-west-1", - "us-west-1", - "us-west-2" - ], - "type": "string" - }, - "SecurityHubSeverityLevel": { - "enum": [ - "CRITICAL", - "HIGH", - "MEDIUM", - "LOW", - "INFORMATIONAL" - ], - "type": "string" - }, - "StorageClass": { - "enum": [ - "DEEP_ARCHIVE", - "GLACIER", - "GLACIER_IR", - "STANDARD_IA", - "INTELLIGENT_TIERING", - "ONEZONE_IA" - ], - "type": "string" - } - } -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/lib/security-config.ts b/source/packages/@aws-accelerator/config/lib/security-config.ts deleted file mode 100644 index baba798..0000000 --- a/source/packages/@aws-accelerator/config/lib/security-config.ts +++ /dev/null @@ -1,427 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import * as t from './common'; -import * as i from './models/security-config'; -import { ReplacementsConfig } from './replacements-config'; - -const logger = createLogger(['security-config']); - -export class S3PublicAccessBlockConfig implements i.IS3PublicAccessBlockConfig { - readonly enable = false; - readonly excludeAccounts: string[] = []; -} - -export class ScpRevertChangesConfig implements i.IScpRevertChangesConfig { - readonly enable = false; - readonly snsTopicName = undefined; -} - -export class KeyConfig implements i.IKeyConfig { - readonly name = ''; - readonly alias = ''; - readonly policy = ''; - readonly description = ''; - readonly enableKeyRotation = true; - readonly enabled = true; - readonly removalPolicy = 'retain'; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); -} - -export class KeyManagementServiceConfig implements i.IKeyManagementServiceConfig { - readonly keySets: KeyConfig[] = []; -} - -export class ResourcePolicyConfig implements i.IResourcePolicyConfig { - readonly resourceType: keyof typeof i.ResourceTypeEnum = 'S3_BUCKET'; - readonly document: string = ''; -} - -export class ResourcePolicyRemediation implements i.IResourcePolicyRemediation { - readonly automatic = true; - readonly retryAttemptSeconds = 0; - readonly maximumAutomaticAttempts = 0; -} - -export class ResourcePolicySetConfig implements i.IResourcePolicySetConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly resourcePolicies: ResourcePolicyConfig[] = []; - readonly inputParameters: { [key: string]: string } | undefined = {}; -} - -export class NetworkPerimeterConfig implements i.INetworkPerimeterConfig { - readonly managedVpcOnly = true; -} - -export class ResourcePolicyEnforcementConfig implements i.IResourcePolicyEnforcementConfig { - static readonly DEFAULT_RULE_NAME = 'Resource-Policy-Compliance-Check'; - static readonly DEFAULT_SSM_DOCUMENT_NAME = `Attach-Resource-Based-Policy`; - - readonly enable = false; - readonly remediation: ResourcePolicyRemediation = new ResourcePolicyRemediation(); - readonly policySets: ResourcePolicySetConfig[] = []; - readonly networkPerimeter: NetworkPerimeterConfig | undefined = undefined; -} - -export class MacieConfig implements i.IMacieConfig { - readonly enable = false; - readonly excludeRegions: t.Region[] = []; - readonly policyFindingsPublishingFrequency = 'FIFTEEN_MINUTES'; - readonly publishSensitiveDataFindings = true; - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; -} - -export class GuardDutyS3ProtectionConfig implements i.IGuardDutyS3ProtectionConfig { - readonly enable = false; - readonly excludeRegions: t.Region[] = []; -} - -export class GuardDutyEksProtectionConfig implements i.IGuardDutyEksProtectionConfig { - readonly enable = false; - readonly excludeRegions: t.Region[] = []; -} - -export class GuardDutyExportFindingsConfig implements i.IGuardDutyExportFindingsConfig { - readonly enable = false; - readonly overrideExisting = false; - readonly destinationType = 'S3'; - readonly exportFrequency = 'FIFTEEN_MINUTES'; - readonly overrideGuardDutyPrefix: t.PrefixConfig | undefined = undefined; -} - -export class GuardDutyConfig implements i.IGuardDutyConfig { - readonly enable = false; - readonly excludeRegions: t.Region[] = []; - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; - readonly autoEnableOrgMembers: boolean | undefined = undefined; - readonly s3Protection: GuardDutyS3ProtectionConfig = new GuardDutyS3ProtectionConfig(); - readonly eksProtection: GuardDutyEksProtectionConfig | undefined = undefined; - readonly exportConfiguration: GuardDutyExportFindingsConfig = new GuardDutyExportFindingsConfig(); - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; -} - -export class AuditManagerDefaultReportsDestinationConfig implements i.IAuditManagerDefaultReportsDestinationConfig { - readonly enable = false; - readonly destinationType = 'S3'; -} - -export class AuditManagerConfig implements i.IAuditManagerConfig { - readonly enable = false; - readonly excludeRegions: t.Region[] = []; - readonly defaultReportsConfiguration: AuditManagerDefaultReportsDestinationConfig = - new AuditManagerDefaultReportsDestinationConfig(); - readonly lifecycleRules: t.LifeCycleRule[] | undefined = undefined; -} - -export class DetectiveConfig implements i.IDetectiveConfig { - readonly enable = false; - readonly excludeRegions: t.Region[] = []; -} - -export class SecurityHubStandardConfig implements i.ISecurityHubStandardConfig { - readonly name = ''; - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; - readonly enable = true; - readonly controlsToDisable: string[] = []; -} -export class SecurityHubLoggingCloudwatchConfig implements i.ISecurityHubLoggingCloudwatchConfig { - readonly enable = true; - readonly logGroupName? = undefined; - readonly logLevel? = undefined; -} - -export class SecurityHubLoggingConfig implements i.ISecurityHubLoggingConfig { - readonly cloudWatch: SecurityHubLoggingCloudwatchConfig | undefined = undefined; -} - -export class SecurityHubConfig implements i.ISecurityHubConfig { - readonly enable = false; - readonly regionAggregation = false; - readonly snsTopicName = undefined; - readonly notificationLevel = undefined; - readonly excludeRegions: t.Region[] = []; - readonly deploymentTargets: t.DeploymentTargets | undefined = undefined; - readonly autoEnableOrgMembers: boolean | undefined = undefined; - readonly standards: SecurityHubStandardConfig[] = []; - readonly logging: SecurityHubLoggingConfig | undefined = undefined; -} - -export class SnsSubscriptionConfig implements i.ISnsSubscriptionConfig { - readonly level: string = ''; - readonly email: string = ''; -} - -export class EbsDefaultVolumeEncryptionConfig implements i.IEbsDefaultVolumeEncryptionConfig { - readonly enable = false; - readonly kmsKey: undefined | string = undefined; - readonly deploymentTargets?: t.DeploymentTargets | undefined; - readonly excludeRegions: t.Region[] = []; -} - -export class DocumentConfig implements i.IDocumentConfig { - readonly name: string = ''; - readonly template: string = ''; - readonly targetType: string | undefined = undefined; -} - -export class DocumentSetConfig implements i.IDocumentSetConfig { - readonly shareTargets: t.ShareTargets = new t.ShareTargets(); - readonly documents: DocumentConfig[] = []; -} - -export class SsmAutomationConfig implements i.ISsmAutomationConfig { - readonly excludeRegions: t.Region[] = []; - readonly documentSets: DocumentSetConfig[] = []; -} - -export class CentralSecurityServicesConfig implements i.ICentralSecurityServicesConfig { - readonly delegatedAdminAccount = 'Audit'; - readonly ebsDefaultVolumeEncryption: EbsDefaultVolumeEncryptionConfig = new EbsDefaultVolumeEncryptionConfig(); - readonly s3PublicAccessBlock: S3PublicAccessBlockConfig = new S3PublicAccessBlockConfig(); - readonly scpRevertChangesConfig: ScpRevertChangesConfig = new ScpRevertChangesConfig(); - readonly snsSubscriptions: SnsSubscriptionConfig[] = []; - readonly macie: MacieConfig = new MacieConfig(); - readonly guardduty: GuardDutyConfig = new GuardDutyConfig(); - readonly auditManager: AuditManagerConfig | undefined = undefined; - readonly detective: DetectiveConfig | undefined = undefined; - readonly securityHub: SecurityHubConfig = new SecurityHubConfig(); - readonly ssmAutomation: SsmAutomationConfig = new SsmAutomationConfig(); -} - -export class AccessAnalyzerConfig implements i.IAccessAnalyzerConfig { - readonly enable = false; -} - -export class IamPasswordPolicyConfig implements i.IIamPasswordPolicyConfig { - readonly allowUsersToChangePassword = true; - readonly hardExpiry = false; - readonly requireUppercaseCharacters = true; - readonly requireLowercaseCharacters = true; - readonly requireSymbols = true; - readonly requireNumbers = true; - readonly minimumPasswordLength = 14; - readonly passwordReusePrevention = 24; - readonly maxPasswordAge = 90; -} - -export class AwsConfigAggregation implements i.IAwsConfigAggregation { - readonly enable = true; - readonly delegatedAdminAccount: string | undefined = undefined; -} - -export class ConfigRuleRemediation implements i.IConfigRuleRemediationType { - readonly rolePolicyFile = ''; - readonly automatic = true; - readonly targetId = ''; - readonly targetAccountName = ''; - readonly targetVersion = ''; - readonly targetDocumentLambda = { - sourceFilePath: '', - handler: '', - runtime: '', - rolePolicyFile: '', - timeout: 3, - }; - readonly retryAttemptSeconds = 0; - readonly maximumAutomaticAttempts = 0; - readonly parameters = []; - readonly excludeRegions: t.Region[] = []; -} - -export class ConfigRule implements i.IConfigRule { - readonly name = ''; - readonly description = ''; - readonly identifier = ''; - readonly inputParameters = {}; - readonly complianceResourceTypes: string[] = []; - readonly type = ''; - readonly tags = []; - readonly customRule = { - lambda: { - sourceFilePath: '', - handler: '', - runtime: '', - rolePolicyFile: '', - timeout: 3, - }, - periodic: true, - maximumExecutionFrequency: 'TwentyFour_Hours', - configurationChanges: true, - triggeringResources: { - lookupType: '', - lookupKey: '', - lookupValue: [], - }, - }; - readonly remediation: ConfigRuleRemediation = new ConfigRuleRemediation(); -} - -export class AwsConfigRuleSet implements i.IAwsConfigRuleSet { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly rules: ConfigRule[] = []; -} - -export class AwsConfig implements i.IAwsConfig { - readonly enableConfigurationRecorder = false; - readonly deploymentTargets: t.DeploymentTargets | undefined; - readonly enableDeliveryChannel: boolean | undefined; - readonly overrideExisting: boolean | undefined; - readonly aggregation: AwsConfigAggregation | undefined; - readonly ruleSets: AwsConfigRuleSet[] = []; -} - -export class MetricConfig implements i.IMetricConfig { - readonly filterName: string = ''; - readonly logGroupName: string = ''; - readonly filterPattern: string = ''; - readonly metricNamespace: string = ''; - readonly metricName: string = ''; - readonly metricValue: string = ''; - readonly treatMissingData: string | undefined = undefined; -} - -export class MetricSetConfig implements i.IMetricSetConfig { - readonly regions: t.Region[] | undefined = undefined; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly metrics: MetricConfig[] = []; -} - -export class AlarmConfig implements i.IAlarmConfig { - readonly alarmName: string = ''; - readonly alarmDescription: string = ''; - readonly snsAlertLevel: string = ''; - readonly snsTopicName: string = ''; - readonly metricName: string = ''; - readonly namespace: string = ''; - readonly comparisonOperator: string = ''; - readonly evaluationPeriods: number = 1; - readonly period: number = 300; - readonly statistic: string = ''; - readonly threshold: number = 1; - readonly treatMissingData: string = ''; -} - -export class AlarmSetConfig implements i.IAlarmSetConfig { - readonly regions: t.Region[] | undefined = undefined; - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly alarms: AlarmConfig[] = []; -} - -export class EncryptionConfig implements i.IEncryptionConfig { - readonly kmsKeyName: string | undefined = undefined; - readonly kmsKeyArn: string | undefined = undefined; - readonly useLzaManagedKey: boolean | undefined = undefined; -} - -export class LogGroupsConfig implements i.ILogGroupsConfig { - readonly deploymentTargets: t.DeploymentTargets = new t.DeploymentTargets(); - readonly encryption: EncryptionConfig | undefined = undefined; - readonly logGroupName: string = ''; - readonly logRetentionInDays = 3653; - readonly terminationProtected: boolean | undefined = undefined; -} - -export class CloudWatchConfig implements i.ICloudWatchConfig { - readonly metricSets: MetricSetConfig[] = []; - readonly alarmSets: AlarmSetConfig[] = []; - readonly logGroups: LogGroupsConfig[] | undefined = undefined; -} - -export class SecurityConfig implements i.ISecurityConfig { - /** - * Security configuration file name, this file must be present in accelerator config repository - */ - static readonly FILENAME = 'security-config.yaml'; - - readonly centralSecurityServices: CentralSecurityServicesConfig = new CentralSecurityServicesConfig(); - readonly accessAnalyzer: AccessAnalyzerConfig = new AccessAnalyzerConfig(); - readonly iamPasswordPolicy: IamPasswordPolicyConfig = new IamPasswordPolicyConfig(); - readonly awsConfig: AwsConfig = new AwsConfig(); - readonly cloudWatch: CloudWatchConfig = new CloudWatchConfig(); - readonly keyManagementService: KeyManagementServiceConfig = new KeyManagementServiceConfig(); - readonly resourcePolicyEnforcement: ResourcePolicyEnforcementConfig | undefined; - - /** - * - * @param values - * @param configDir - * @param validateConfig - */ - constructor(values?: i.ISecurityConfig) { - if (values) { - Object.assign(this, values); - } - } - - /** - * Return delegated-admin-account name - */ - public getDelegatedAccountName(): string { - return this.centralSecurityServices.delegatedAdminAccount; - } - - /** - * - * @param dir - * @param replacementsConfig - * @returns - */ - - static load(dir: string, replacementsConfig?: ReplacementsConfig): SecurityConfig { - const initialBuffer = fs.readFileSync(path.join(dir, SecurityConfig.FILENAME), 'utf8'); - const buffer = replacementsConfig ? replacementsConfig.preProcessBuffer(initialBuffer) : initialBuffer; - const values = t.parseSecurityConfig(yaml.load(buffer)); - return new SecurityConfig(values); - } - - /** - * Load from string content - * @param content - */ - static loadFromString(content: string): SecurityConfig | undefined { - try { - const values = t.parseSecurityConfig(yaml.load(content)); - return new SecurityConfig(values); - } catch (e) { - logger.error('Error parsing input, security config undefined'); - logger.error(`${e}`); - throw new Error('could not load configuration'); - } - } -} - -/** - * Function to validate remediation rule name in security-config - * @param remediationRule: SecurityConfigTypes.configRuleRemediationType - * @param errors - * @returns boolean - */ -export function IsPublicSsmDoc(documentName: string) { - // any document starting with AWS- prefix is amazon owned document - // Ref: https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_CreateDocument.html#API_CreateDocument_RequestSyntax - // You can't use the following strings as document name prefixes. These are reserved by AWS for use as document name prefixes: - // - aws - // - amazon - // - amzn - const reservedPrefix = [/^AWS-/i, /^AMZN-/i, /^AMAZON-/i, /^AWSEC2-/i, /^AWSConfigRemediation-/i, /^AWSSupport-/i]; - if (reservedPrefix.some(obj => obj.test(documentName))) { - return true; - } - return false; -} diff --git a/source/packages/@aws-accelerator/config/package.json b/source/packages/@aws-accelerator/config/package.json deleted file mode 100644 index 9db970c..0000000 --- a/source/packages/@aws-accelerator/config/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@aws-accelerator/config", - "version": "0.0.0", - "private": true, - "description": "The configuration file library for the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "yarn schema:accounts && yarn schema:global && yarn schema:iam && yarn schema:organization && yarn schema:security && yarn schema:replacements && yarn schema:customizations && yarn schema:network && tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "watch": "tsc -w", - "test": "jest --coverage --ci --passWithNoTests", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' 'validator/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' 'validator/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "schema:accounts": "ts-json-schema-generator --path lib/models/accounts-config.ts --type IAccountsConfig -o lib/schemas/accounts-config.json", - "schema:global": "ts-json-schema-generator --path lib/models/global-config.ts --type IGlobalConfig -o lib/schemas/global-config.json", - "schema:iam": "ts-json-schema-generator --path lib/models/iam-config.ts --type IIamConfig -o lib/schemas/iam-config.json", - "schema:organization": "ts-json-schema-generator --path lib/models/organization-config.ts --type IOrganizationConfig -o lib/schemas/organization-config.json", - "schema:security": "ts-json-schema-generator --path lib/models/security-config.ts --type ISecurityConfig -o lib/schemas/security-config.json", - "schema:replacements": "ts-json-schema-generator --path lib/models/replacements-config.ts --type IReplacementsConfig -o lib/schemas/replacements-config.json", - "schema:customizations": "ts-json-schema-generator --path lib/models/customizations-config.ts --type ICustomizationsConfig -o lib/schemas/customizations-config.json", - "schema:network": "ts-json-schema-generator --path lib/models/network-config.ts --type INetworkConfig -o lib/schemas/network-config.json" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "@typescript-eslint/eslint-plugin": "5.53.0", - "@typescript-eslint/parser": "5.53.0", - "aws-cdk-lib": "2.93.0", - "email-validator": "2.0.4", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-json-schema-generator": "1.4.1", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "dependencies": { - "ajv": "8.12.0", - "email-validator": "2.0.4", - "ip-num": "1.5.0" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/config/test/accounts-config.test.ts b/source/packages/@aws-accelerator/config/test/accounts-config.test.ts deleted file mode 100644 index eeca22b..0000000 --- a/source/packages/@aws-accelerator/config/test/accounts-config.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, it, expect } from '@jest/globals'; -import { AccountIdConfig, AccountConfig, GovCloudAccountConfig, AccountsConfig } from '../lib/accounts-config'; -import * as path from 'path'; - -const accountsConfigObject = { - mandatoryAccounts: [ - { - name: 'Management', - description: - 'The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-management-account@example.com', - organizationalUnit: 'Root', - warm: false, - }, - { - name: 'LogArchive', - description: - 'The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-logarchive-account@example.com', - organizationalUnit: 'Security', - warm: false, - }, - { - name: 'Audit', - description: - 'The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-audit-account@example.com', - organizationalUnit: 'Security', - warm: false, - }, - ], - workloadAccounts: [ - { - name: 'SharedServices', - description: 'The SharedServices account', - email: 'shared-services@example.com', - organizationalUnit: 'Infrastructure', - warm: false, - }, - { - name: 'Network', - description: 'The Network account', - email: 'network@example.com', - organizationalUnit: 'Infrastructure', - warm: false, - }, - ], - accountIds: [ - { - email: 'some-management-account@example.com', - accountId: '111111111111', - }, - { email: 'some-audit-account@example.com', accountId: '222222222222' }, - { - email: 'some-logarchive-account@example.com', - accountId: '333333333333', - }, - { - email: 'shared-services@example.com', - accountId: '444444444444', - }, - { email: 'network@example.com', accountId: '555555555555' }, - ], -}; - -describe('accounts-config', () => { - const accountIdConfig = new AccountIdConfig(); - const accountConfig = new AccountConfig(); - const govCloudAccountConfig = new GovCloudAccountConfig(); - - describe('AccountIdConfig', () => { - it('is tested', () => { - expect(accountIdConfig.email).toEqual(''); - expect(accountIdConfig.accountId).toEqual(''); - }); - }); - describe('AccountConfig', () => { - it('is tested', () => { - expect(accountConfig.name).toEqual(''); - expect(accountConfig.description).toEqual(''); - expect(accountConfig.email).toEqual(''); - expect(accountConfig.organizationalUnit).toEqual(''); - }); - }); - describe('GovCloudAccountConfig', () => { - it('is tested', () => { - expect(govCloudAccountConfig.name).toEqual(''); - expect(govCloudAccountConfig.description).toEqual(''); - expect(govCloudAccountConfig.email).toEqual(''); - expect(govCloudAccountConfig.organizationalUnit).toEqual(''); - expect(govCloudAccountConfig.enableGovCloud).toBe(undefined); - }); - }); - describe('AccountsConfig', () => { - const configA = new AccountsConfig({ - managementAccountEmail: 'hello@example.com', - logArchiveAccountEmail: 'log@example.com', - auditAccountEmail: 'audit@example.com', - }); - const configB = new AccountsConfig( - { - managementAccountEmail: 'hello@example.com', - logArchiveAccountEmail: 'log@example.com', - auditAccountEmail: 'audit@example.com', - }, - { - mandatoryAccounts: [ - { - name: 'hello', - email: 'world@example.com', - description: undefined, - organizationalUnit: undefined, - warm: undefined, - }, - ], - workloadAccounts: [govCloudAccountConfig], - accountIds: [], - }, - ); - const configC = new AccountsConfig( - { - managementAccountEmail: 'some-management-account@example.com', - logArchiveAccountEmail: 'some-logarchive-account@example.com', - auditAccountEmail: 'some-audit-account@example.com', - }, - accountsConfigObject, - ); - - it('is a govcloud account', () => { - expect(configA.isGovCloudAccount(accountConfig)).toBe(false); - expect(configA.isGovCloudAccount(govCloudAccountConfig)).toBe(true); - }); - it('has any govcloud accounts', () => { - expect(configA.anyGovCloudAccounts()).toBe(false); - expect(configB.anyGovCloudAccounts()).toBe(true); - }); - it('has govcloud enabled', () => { - expect(configB.isGovCloudEnabled(accountConfig)).toBe(false); - }); - it('using config dir: validates config and gets results', () => { - expect(configC.getManagementAccountId()).toBe('111111111111'); - expect(configC.getManagementAccount()).toStrictEqual({ - description: - 'The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-management-account@example.com', - name: 'Management', - organizationalUnit: 'Root', - warm: false, - }); - - expect(configC.getLogArchiveAccountId()).toBe('333333333333'); - expect(configC.getLogArchiveAccount()).toStrictEqual({ - name: 'LogArchive', - description: - 'The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-logarchive-account@example.com', - organizationalUnit: 'Security', - warm: false, - }); - - expect(configC.getAuditAccount()).toStrictEqual({ - name: 'Audit', - description: - 'The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-audit-account@example.com', - organizationalUnit: 'Security', - warm: false, - }); - expect(configC.getAuditAccountId()).toBe('222222222222'); - }); - - it('contains account name', () => { - expect(configC.containsAccount('Audit')).toBe(true); - expect(configC.containsAccount('notpresent')).toBe(false); - }); - - it('get account name', () => { - expect(configC.getAccount('Audit')).toEqual({ - name: 'Audit', - description: - 'The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-audit-account@example.com', - organizationalUnit: 'Security', - warm: false, - }); - expect(() => { - configC.getAccount('notpresent'); - }).toThrow('configuration validation failed.'); - }); - - it('get account ID', () => { - expect(() => { - configC.getAccountId('missing'); - }).toThrow('configuration validation failed.'); - }); - - it('load config successfully', () => { - const loadedConfig = AccountsConfig.load(path.resolve('../accelerator/test/configs/snapshot-only')); - expect(loadedConfig && typeof loadedConfig === 'object').toBe(true); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/common-types.test.ts b/source/packages/@aws-accelerator/config/test/common-types.test.ts deleted file mode 100644 index 814b428..0000000 --- a/source/packages/@aws-accelerator/config/test/common-types.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { parseOrganizationConfig } from '../lib/common'; -import { describe, it, expect } from '@jest/globals'; -import * as fs from 'fs'; -import * as yaml from 'js-yaml'; -import * as path from 'path'; - -describe('parse method', () => { - describe('parse a valid config', () => { - it('should return an object', () => { - const buffer = fs.readFileSync( - path.resolve('../accelerator/test/configs/snapshot-only/organization-config.yaml'), - 'utf8', - ); - const r = parseOrganizationConfig(yaml.load(buffer)); - expect(r && typeof r === 'object').toBe(true); - }); - }); - describe('parse an incomplete config', () => { - it('should throw an error', () => { - const loadedyaml = ` -{ - enable: true, - organizationalUnits: [ { name: 'Security' }, { name: 'Infrastructure' } ], - quarantineNewAccounts: { enable: true, scpPolicyName: 'Quarantine' }, - }`; - expect(() => { - parseOrganizationConfig(loadedyaml); - }).toThrow(); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/customizations-config.test.ts b/source/packages/@aws-accelerator/config/test/customizations-config.test.ts deleted file mode 100644 index 728c4a5..0000000 --- a/source/packages/@aws-accelerator/config/test/customizations-config.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CustomizationsConfig, - CloudFormationStackConfig, - CloudFormationStackSetConfig, - AlbListenerFixedResponseConfig, - AlbListenerForwardConfigTargetGroupStickinessConfig, - AlbListenerForwardConfig, - AlbListenerRedirectConfig, - ApplicationLoadBalancerListenerConfig, - ApplicationLoadBalancerAttributesConfig, - ApplicationLoadBalancerConfig, - TargetGroupItemConfig, - NetworkLoadBalancerListenerConfig, - NetworkLoadBalancerConfig, - EbsItemConfig, - PrivateIpAddressConfig, - BlockDeviceMappingItem, - NetworkInterfaceItemConfig, - LaunchTemplateConfig, - AutoScalingConfig, - AppConfigItem, -} from '../lib/customizations-config'; -import { describe, expect, it } from '@jest/globals'; -import * as path from 'path'; - -describe('CustomizationsConfig', () => { - describe('Test config', () => { - const customizationsConfigFromFile = CustomizationsConfig.load( - path.resolve('../accelerator/test/configs/snapshot-only'), - ); - const customizationsConfig = new CustomizationsConfig(); - - it('has loaded successfully', () => { - expect(customizationsConfigFromFile.customizations.cloudFormationStacks.length).toBe(3); - expect(customizationsConfig.customizations.cloudFormationStacks.length).toBe(0); - if (customizationsConfigFromFile.applications) { - expect(customizationsConfigFromFile.applications.length).toBe(8); - expect(customizationsConfig.applications.length).toBe(0); - } - }); - const cloudFormationStackConfig = new CloudFormationStackConfig(); - expect(cloudFormationStackConfig.description).toEqual(''); - - const cloudFormationStackSetConfig = new CloudFormationStackSetConfig(); - expect(cloudFormationStackSetConfig.description).toEqual(''); - - const albListenerFixedResponseConfig = new AlbListenerFixedResponseConfig(); - expect(albListenerFixedResponseConfig.statusCode).toEqual(''); - - const albListenerForwardConfigTargetGroupStickinessConfig = - new AlbListenerForwardConfigTargetGroupStickinessConfig(); - expect(albListenerForwardConfigTargetGroupStickinessConfig.durationSeconds).toEqual(undefined); - - const albListenerForwardConfig = new AlbListenerForwardConfig(); - expect(albListenerForwardConfig.targetGroupStickinessConfig).toEqual(undefined); - - const albListenerRedirectConfig = new AlbListenerRedirectConfig(); - expect(albListenerRedirectConfig.statusCode).toEqual(undefined); - - const applicationLoadBalancerListenerConfig = new ApplicationLoadBalancerListenerConfig(); - expect(applicationLoadBalancerListenerConfig.name).toEqual(''); - - const applicationLoadBalancerAttributesConfig = new ApplicationLoadBalancerAttributesConfig(); - expect(applicationLoadBalancerAttributesConfig.deletionProtection).toEqual(undefined); - - const applicationLoadBalancerConfig = new ApplicationLoadBalancerConfig(); - expect(applicationLoadBalancerConfig.name).toEqual(''); - - const targetGroupItemConfig = new TargetGroupItemConfig(); - expect(targetGroupItemConfig.protocolVersion).toEqual(undefined); - - const networkLoadBalancerListenerConfig = new NetworkLoadBalancerListenerConfig(); - expect(networkLoadBalancerListenerConfig.certificate).toEqual(undefined); - - const networkLoadBalancerConfig = new NetworkLoadBalancerConfig(); - expect(networkLoadBalancerConfig.deletionProtection).toEqual(undefined); - - const ebsItemConfig = new EbsItemConfig(); - expect(ebsItemConfig.deleteOnTermination).toEqual(undefined); - - const blockDeviceMappingItem = new BlockDeviceMappingItem(); - expect(blockDeviceMappingItem.ebs).toEqual(undefined); - - const privateIpAddressConfig = new PrivateIpAddressConfig(); - expect(privateIpAddressConfig.primary).toEqual(undefined); - - const networkInterfaceItemConfig = new NetworkInterfaceItemConfig(); - expect(networkInterfaceItemConfig.associateCarrierIpAddress).toEqual(undefined); - - const launchTemplateConfig = new LaunchTemplateConfig(); - expect(launchTemplateConfig.blockDeviceMappings).toEqual(undefined); - - const autoScalingConfig = new AutoScalingConfig(); - expect(autoScalingConfig.healthCheckGracePeriod).toEqual(undefined); - - expect(autoScalingConfig.maxInstanceLifetime).toEqual(undefined); - - const appConfigItem = new AppConfigItem(); - expect(appConfigItem.targetGroups).toEqual(undefined); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/global-config.test.ts b/source/packages/@aws-accelerator/config/test/global-config.test.ts deleted file mode 100644 index dea4a7e..0000000 --- a/source/packages/@aws-accelerator/config/test/global-config.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - GlobalConfig, - CostAndUsageReportConfig, - BudgetReportConfig, - ServiceQuotaLimitsConfig, - SsmParameterConfig, - SsmParametersConfig, - SsmInventoryConfig, - AcceleratorSettingsConfig, - AcceleratorMetadataConfig, - SnsConfig, - SnsTopicConfig, - BackupConfig, - VaultConfig, - ReportConfig, - externalLandingZoneResourcesConfig, - centralizeCdkBucketsConfig, - AccountCloudTrailConfig, - AccessLogBucketConfig, - CentralLogBucketConfig, - ElbLogBucketConfig, - CloudWatchLogsExclusionConfig, - CloudWatchLogsConfig, -} from '../lib/global-config'; -import { describe, it, expect } from '@jest/globals'; -import * as path from 'path'; -import * as fs from 'fs'; - -describe('GlobalConfig', () => { - describe('Test config', () => { - it('has loaded successfully', () => { - // const globalConfig = new GlobalConfig({ - // homeRegion: 'us-east-1', - // }); - const globalConfigFromFile = GlobalConfig.load(path.resolve('../accelerator/test/configs/snapshot-only')); - - expect(globalConfigFromFile.ssmParameters?.length).toBe(1); - expect(globalConfigFromFile.ssmParameters?.at(0)?.parameters?.at(0)?.name).toBe('parameterTest'); - expect(globalConfigFromFile.ssmParameters?.at(0)?.parameters?.at(0)?.path).toBe('/my/parameter/structure'); - expect(globalConfigFromFile.ssmParameters?.at(0)?.parameters?.at(0)?.value).toBe('parameterTestValue'); - - // expect(globalConfig.accountNames).toStrictEqual([]); - // expect(globalConfigFromFile.accountNames).toStrictEqual([ - // 'Management', - // 'LogArchive', - // 'Audit', - // 'SharedServices', - // 'Network', - // ]); - }); - - it('loads from string', () => { - const buffer = fs.readFileSync( - path.join('../accelerator/test/configs/snapshot-only', GlobalConfig.FILENAME), - 'utf8', - ); - const globalConfigFromString = GlobalConfig.loadFromString(buffer); - if (!globalConfigFromString) { - throw new Error('globalConfigFromString is not defined'); - } - // expect(globalConfigFromString.accountNames).toStrictEqual([]); - - //expect(GlobalConfig.loadFromString('corrupt str')).toBe(undefined); - }); - - it('tests CostAndUsageReportConfig', () => { - const curConfig = new CostAndUsageReportConfig(); - expect(curConfig.additionalSchemaElements).toStrictEqual(['']); - expect(curConfig.compression).toEqual(''); - expect(curConfig.format).toEqual(''); - expect(curConfig.reportName).toEqual(''); - expect(curConfig.reportName).toEqual(''); - expect(curConfig.s3Prefix).toEqual(''); - expect(curConfig.timeUnit).toEqual(''); - expect(curConfig.additionalArtifacts).toBe(undefined); - expect(curConfig.refreshClosedReports).toBe(true); - expect(curConfig.reportVersioning).toEqual(''); - expect(curConfig.lifecycleRules).toBe(undefined); - }); - - it('tests BudgetReportConfig', () => { - const brConfig = new BudgetReportConfig(); - expect(brConfig.amount).toStrictEqual(2000); - expect(brConfig.name).toEqual(''); - expect(brConfig.type).toEqual(''); - expect(brConfig.subscriptionType).toEqual(''); - expect(brConfig.unit).toEqual(''); - expect(brConfig.timeUnit).toEqual(''); - expect(brConfig.includeUpfront).toBe(true); - expect(brConfig.includeTax).toBe(true); - expect(brConfig.includeSupport).toBe(true); - expect(brConfig.includeRecurring).toBe(true); - expect(brConfig.includeDiscount).toBe(true); - expect(brConfig.includeRefund).toBe(false); - expect(brConfig.includeCredit).toBe(false); - expect(brConfig.useAmortized).toBe(false); - expect(brConfig.useBlended).toBe(false); - }); - - it('tests ServiceQuotaLimitsConfig', () => { - const serviceQuotaLimitsConfig = new ServiceQuotaLimitsConfig(); - expect(serviceQuotaLimitsConfig.serviceCode).toEqual(''); - expect(serviceQuotaLimitsConfig.quotaCode).toEqual(''); - expect(serviceQuotaLimitsConfig.desiredValue).toEqual(2000); - expect(serviceQuotaLimitsConfig.deploymentTargets).toEqual({ - accounts: [], - excludedAccounts: [], - excludedRegions: [], - organizationalUnits: [], - }); - }); - it('test static types', () => { - expect(new AccessLogBucketConfig().lifecycleRules).toEqual(undefined); - expect(new CentralLogBucketConfig().lifecycleRules).toEqual(undefined); - expect(new ElbLogBucketConfig().lifecycleRules).toEqual(undefined); - expect(new CloudWatchLogsExclusionConfig().regions).toEqual(undefined); - expect(new CloudWatchLogsConfig().enable).toEqual(undefined); - - expect(new centralizeCdkBucketsConfig().enable).toEqual(true); - expect(new AccountCloudTrailConfig().regions).toEqual([]); - expect(new externalLandingZoneResourcesConfig().mappingFileBucket).toEqual(''); - expect(new ReportConfig().budgets).toEqual([]); - expect(new VaultConfig().policy).toEqual(''); - expect(new SsmParameterConfig().name).toEqual(''); - expect(new SsmParametersConfig().parameters).toEqual([]); - expect(new SsmInventoryConfig().enable).toBeFalsy; - - expect(new AcceleratorSettingsConfig().maxConcurrentStacks).toBeUndefined; - - expect(new AcceleratorMetadataConfig().enable).toBeFalsy; - expect(new SnsConfig().topics).toEqual([]); - expect(new SnsTopicConfig().emailAddresses).toEqual([]); - expect(new BackupConfig().vaults).toEqual([]); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/iam-config.test.ts b/source/packages/@aws-accelerator/config/test/iam-config.test.ts deleted file mode 100644 index 47b3c17..0000000 --- a/source/packages/@aws-accelerator/config/test/iam-config.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - IamConfig, - UserConfig, - PolicySetConfig, - PolicyConfig, - RoleSetConfig, - SamlProviderConfig, - GroupSetConfig, - GroupConfig, - PoliciesConfig, - UserSetConfig, - AssumedByConfig, - RoleConfig, - IdentityCenterAssignmentConfig, - IdentityCenterConfig, - IdentityCenterPermissionSetConfig, -} from '../lib/iam-config'; -import { describe, it, expect } from '@jest/globals'; -import * as path from 'path'; -import * as fs from 'fs'; - -describe('IamConfig', () => { - describe('Test config', () => { - // it('has loaded successfully', () => { - // const iamConfig = new IamConfig(); - // const iamConfigFromFile = IamConfig.load(path.resolve('../accelerator/test/configs/all-enabled'), true); - // expect(iamConfig.ouIdNames).toEqual(['Root']); - // expect(iamConfigFromFile.ouIdNames).toEqual(['Root', 'Security', 'Infrastructure']); - // }); - - it('test static types', () => { - const groupSetConfig = new GroupSetConfig(); - expect(groupSetConfig.groups).toEqual([]); - - const groupConfig = new GroupConfig(); - expect(groupConfig.name).toEqual(''); - - const policiesConfig = new PoliciesConfig(); - expect(policiesConfig.awsManaged).toEqual(undefined); - - const userSetConfig = new UserSetConfig(); - expect(userSetConfig.users).toEqual([]); - - const userConfig = new UserConfig(); - expect(userConfig.username).toEqual(''); - - const samlProviderConfig = new SamlProviderConfig(); - expect(samlProviderConfig.name).toEqual(''); - - const assumedByConfig = new AssumedByConfig(); - expect(assumedByConfig.principal).toEqual(''); - - const roleConfig = new RoleConfig(); - expect(roleConfig.assumedBy).toEqual([]); - - const roleSetConfig = new RoleSetConfig(); - expect(roleSetConfig.roles).toEqual([]); - - const policyConfig = new PolicyConfig(); - expect(policyConfig.name).toEqual(''); - - const policySetConfig = new PolicySetConfig(); - expect(policySetConfig.policies).toEqual([]); - - const identityCenterConfig = new IdentityCenterConfig(); - expect(identityCenterConfig.name).toEqual(''); - - const identityCenterAssignmentConfig = new IdentityCenterAssignmentConfig(); - expect(identityCenterAssignmentConfig.name).toEqual(''); - - const identityCenterPermissionSetConfig = new IdentityCenterPermissionSetConfig(); - expect(identityCenterPermissionSetConfig.name).toEqual(''); - }); - }); - - it('loads from string', () => { - const buffer = fs.readFileSync(path.join('../accelerator/test/configs/snapshot-only', IamConfig.FILENAME), 'utf8'); - const iamConfigFromString = IamConfig.loadFromString(buffer); - if (!iamConfigFromString) { - throw new Error('iamConfigFromString is not defined'); - } - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/network-config.test.ts b/source/packages/@aws-accelerator/config/test/network-config.test.ts deleted file mode 100644 index 6605f29..0000000 --- a/source/packages/@aws-accelerator/config/test/network-config.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { - NetworkConfig, - VpcPeeringConfig, - CentralNetworkServicesConfig, - FirewallManagerConfig, - GwlbConfig, - GwlbEndpointConfig, - NfwFirewallPolicyConfig, - NfwFirewallPolicyPolicyConfig, - NfwStatelessRuleGroupReferenceConfig, - NfwStatefulRuleGroupReferenceConfig, - NfwRuleGroupConfig, - NfwRuleGroupRuleConfig, - NfwRuleVariableConfig, - NfwRuleVariableDefinitionConfig, - NfwRuleSourceConfig, - NfwStatelessRulesAndCustomActionsConfig, - NfwRuleSourceStatelessRuleConfig, - NfwRuleSourceStatelessRuleDefinitionConfig, - NfwRuleSourceStatelessMatchAttributesConfig, - NfwRuleSourceStatelessTcpFlagsConfig, - NfwRuleSourceStatelessPortRangeConfig, - NfwRuleSourceCustomActionConfig, - NfwRuleSourceCustomActionDefinitionConfig, - NfwRuleSourceCustomActionDimensionConfig, - NfwRuleSourceStatefulRuleConfig, - NfwRuleSourceStatefulRuleOptionsConfig, - NfwRuleSourceStatefulRuleHeaderConfig, - NfwRuleSourceListConfig, - ResolverConfig, - DnsFirewallRuleGroupConfig, - DnsFirewallRulesConfig, - DnsQueryLogsConfig, - ResolverEndpointConfig, - ResolverRuleConfig, - VpcTemplatesConfig, -} from '../lib/network-config'; - -import { VpcFlowLogsConfig } from '../lib/common/types'; - -import { describe, it, expect } from '@jest/globals'; -import * as path from 'path'; -import * as fs from 'fs'; - -describe('NetworkConfig', () => { - describe('Test config', () => { - // const networkConfigFromFile = NetworkConfig.load(path.resolve('../accelerator/test/configs/all-enabled'), true); - it('has loaded successfully', () => { - const networkConfig = new NetworkConfig(); - expect(networkConfig.vpcs).toEqual([]); - // expect(networkConfigFromFile.accountNames).toEqual([ - // 'Management', - // 'LogArchive', - // 'Audit', - // 'SharedServices', - // 'Network', - // ]); - }); - - it('loads from string', () => { - const buffer = fs.readFileSync( - path.join('../accelerator/test/configs/snapshot-only', NetworkConfig.FILENAME), - 'utf8', - ); - const networkConfigFromString = NetworkConfig.loadFromString(buffer); - if (!networkConfigFromString) { - throw new Error('networkConfigFromString is not defined'); - } - // expect(networkConfigFromString.accountNames).toStrictEqual([]); - //expect(NetworkConfig.loadFromString('corrupt str')).toBe(undefined); - }); - - it('test static types', () => { - const vpcPeeringConfig = new VpcPeeringConfig(); - expect(vpcPeeringConfig.name).toEqual(''); - - const centralNetworkServicesConfig = new CentralNetworkServicesConfig(); - expect(centralNetworkServicesConfig.delegatedAdminAccount).toEqual(''); - - const firewallManagerServiceConfig = new FirewallManagerConfig(); - expect(firewallManagerServiceConfig.delegatedAdminAccount).toEqual(''); - - const gwlbConfig = new GwlbConfig(); - expect(gwlbConfig.name).toEqual(''); - - const gwlbEndpointConfig = new GwlbEndpointConfig(); - expect(gwlbEndpointConfig.name).toEqual(''); - - const nfwFirewallPolicyConfig = new NfwFirewallPolicyConfig(); - expect(nfwFirewallPolicyConfig.name).toEqual(''); - - const nfwFirewallPolicyPolicyConfig = new NfwFirewallPolicyPolicyConfig(); - expect(nfwFirewallPolicyPolicyConfig.statelessDefaultActions).toEqual([]); - - const nfwStatelessRuleGroupReferenceConfig = new NfwStatelessRuleGroupReferenceConfig(); - expect(nfwStatelessRuleGroupReferenceConfig.name).toEqual(''); - - const nfwStatefulRuleGroupReferenceConfig = new NfwStatefulRuleGroupReferenceConfig(); - expect(nfwStatefulRuleGroupReferenceConfig.name).toEqual(''); - - const nfwRuleGroupConfig = new NfwRuleGroupConfig(); - expect(nfwRuleGroupConfig.name).toEqual(''); - - const nfwRuleGroupRuleConfig = new NfwRuleGroupRuleConfig(); - expect(nfwRuleGroupRuleConfig.ruleVariables).toEqual(undefined); - - const nfwRuleVariableConfig = new NfwRuleVariableConfig(); - expect(nfwRuleVariableConfig.ipSets).toEqual([{ name: '', definition: [] }]); - - const nfwRuleVariableDefinitionConfig = new NfwRuleVariableDefinitionConfig(); - expect(nfwRuleVariableDefinitionConfig.name).toEqual(''); - - const nfwRuleSourceConfig = new NfwRuleSourceConfig(); - expect(nfwRuleSourceConfig.rulesSourceList).toEqual(undefined); - - const nfwStatelessRulesAndCustomActionsConfig = new NfwStatelessRulesAndCustomActionsConfig(); - expect(nfwStatelessRulesAndCustomActionsConfig.customActions).toEqual(undefined); - - const nfwRuleSourceStatelessRuleConfig = new NfwRuleSourceStatelessRuleConfig(); - expect(nfwRuleSourceStatelessRuleConfig.priority).toEqual(123); - - const nfwRuleSourceStatelessRuleDefinitionConfig = new NfwRuleSourceStatelessRuleDefinitionConfig(); - expect(nfwRuleSourceStatelessRuleDefinitionConfig.actions).toEqual(['aws:drop']); - - const nfwRuleSourceStatelessMatchAttributesConfig = new NfwRuleSourceStatelessMatchAttributesConfig(); - expect(nfwRuleSourceStatelessMatchAttributesConfig.sources).toEqual(undefined); - - const nfwRuleSourceStatelessTcpFlagsConfig = new NfwRuleSourceStatelessTcpFlagsConfig(); - expect(nfwRuleSourceStatelessTcpFlagsConfig.flags).toEqual([]); - - const nfwRuleSourceStatelessPortRangeConfig = new NfwRuleSourceStatelessPortRangeConfig(); - expect(nfwRuleSourceStatelessPortRangeConfig.fromPort).toEqual(123); - - const nfwRuleSourceCustomActionConfig = new NfwRuleSourceCustomActionConfig(); - expect(nfwRuleSourceCustomActionConfig.actionName).toEqual(''); - - const nfwRuleSourceCustomActionDefinitionConfig = new NfwRuleSourceCustomActionDefinitionConfig(); - expect(nfwRuleSourceCustomActionDefinitionConfig.publishMetricAction.dimensions).toEqual([]); - - const nfwRuleSourceCustomActionDimensionConfig = new NfwRuleSourceCustomActionDimensionConfig(); - expect(nfwRuleSourceCustomActionDimensionConfig.dimensions).toEqual([]); - - const nfwRuleSourceStatefulRuleConfig = new NfwRuleSourceStatefulRuleConfig(); - expect(nfwRuleSourceStatefulRuleConfig.action).toEqual('DROP'); - - const nfwRuleSourceStatefulRuleOptionsConfig = new NfwRuleSourceStatefulRuleOptionsConfig(); - expect(nfwRuleSourceStatefulRuleOptionsConfig.keyword).toEqual(''); - - const nfwRuleSourceStatefulRuleHeaderConfig = new NfwRuleSourceStatefulRuleHeaderConfig(); - expect(nfwRuleSourceStatefulRuleHeaderConfig.destination).toEqual(''); - - const nfwRuleSourceListConfig = new NfwRuleSourceListConfig(); - expect(nfwRuleSourceListConfig.targets).toEqual([]); - - const resolverConfig = new ResolverConfig(); - expect(resolverConfig.endpoints).toEqual(undefined); - - const dnsFirewallRuleGroupConfig = new DnsFirewallRuleGroupConfig(); - expect(dnsFirewallRuleGroupConfig.name).toEqual(''); - - const dnsFirewallRulesConfig = new DnsFirewallRulesConfig(); - expect(dnsFirewallRulesConfig.name).toEqual(''); - - const dnsQueryLogsConfig = new DnsQueryLogsConfig(); - expect(dnsQueryLogsConfig.name).toEqual(''); - - const resolverEndpointConfig = new ResolverEndpointConfig(); - expect(resolverEndpointConfig.name).toEqual(''); - - const resolverRuleConfig = new ResolverRuleConfig(); - expect(resolverRuleConfig.name).toEqual(''); - - const vpcFlowLogsConfig = new VpcFlowLogsConfig(); - expect(vpcFlowLogsConfig.trafficType).toEqual('ALL'); - - const vpcTemplatesConfig = new VpcTemplatesConfig(); - expect(vpcTemplatesConfig.name).toEqual(''); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/organization-config.test.ts b/source/packages/@aws-accelerator/config/test/organization-config.test.ts deleted file mode 100644 index 16db019..0000000 --- a/source/packages/@aws-accelerator/config/test/organization-config.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { OrganizationConfig } from '../lib/organization-config'; -import { describe, expect } from '@jest/globals'; -import * as path from 'path'; - -describe('OrganizationConfig', () => { - describe('Test config', () => { - const organizationConfigFromFile = OrganizationConfig.load( - path.resolve('../accelerator/test/configs/snapshot-only'), - ); - const organizationConfig = new OrganizationConfig(); - it('has loaded successfully', () => { - expect(organizationConfigFromFile.enable).toBe(true); - expect(organizationConfig.enable).toBe(true); - }); - - it('gets organization lookup', () => { - expect(() => { - organizationConfigFromFile.getOrganizationalUnitId('hello'); - }).toThrow(); - expect(organizationConfigFromFile.getOrganizationalUnitId('Security')).toEqual('ou-asdf-11111111'); - - expect(() => { - organizationConfigFromFile.getOrganizationalUnitArn('hello'); - }).toThrow(); - expect(organizationConfigFromFile.getOrganizationalUnitArn('Security')).toEqual( - 'arn:aws:organizations::111111111111:ou/o-asdf123456/ou-asdf-11111111', - ); - - expect(organizationConfigFromFile.getPath('Security')).toEqual('/'); - expect(organizationConfigFromFile.getPath('Security/MorePath')).toEqual('/Security'); - - expect(organizationConfigFromFile.getOuName('Security')).toEqual('Security'); - expect(organizationConfigFromFile.getOuName('Security/MorePath')).toEqual('MorePath'); - - expect(organizationConfigFromFile.getParentOuName('Security')).toEqual(''); - expect(organizationConfigFromFile.getParentOuName('Security/MorePath')).toEqual('Security'); - expect(organizationConfigFromFile.getParentOuName('')).toEqual(''); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/replacements-config.test.ts b/source/packages/@aws-accelerator/config/test/replacements-config.test.ts deleted file mode 100644 index 464a4a4..0000000 --- a/source/packages/@aws-accelerator/config/test/replacements-config.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, expect, it } from '@jest/globals'; -import * as path from 'path'; -import { GlobalConfig } from '../lib/global-config'; -import { AccountsConfig } from '../lib/accounts-config'; -import { ReplacementsConfig } from '../lib/replacements-config'; - -const accountsConfigObject = { - mandatoryAccounts: [ - { - name: 'Management', - description: - 'The management (primary) account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-management-account@example.com', - organizationalUnit: 'Root', - warm: false, - }, - { - name: 'LogArchive', - description: - 'The log archive account. Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-logarchive-account@example.com', - organizationalUnit: 'Security', - warm: false, - }, - { - name: 'Audit', - description: - 'The security audit account (also referred to as the audit account). Do not change the name field for this mandatory account. Note, the account name key does not need to match the AWS account name.', - email: 'some-audit-account@example.com', - organizationalUnit: 'Security', - warm: false, - }, - ], - workloadAccounts: [ - { - name: 'SharedServices', - description: 'The SharedServices account', - email: 'shared-services@example.com', - organizationalUnit: 'Infrastructure', - warm: false, - }, - { - name: 'Network', - description: 'The Network account', - email: 'network@example.com', - organizationalUnit: 'Infrastructure', - warm: false, - }, - ], - accountIds: [ - { - email: 'some-management-account@example.com', - accountId: '111111111111', - }, - { email: 'some-audit-account@example.com', accountId: '222222222222' }, - { - email: 'some-logarchive-account@example.com', - accountId: '333333333333', - }, - { - email: 'shared-services@example.com', - accountId: '444444444444', - }, - { email: 'network@example.com', accountId: '555555555555' }, - ], -}; - -const accountConfig = new AccountsConfig( - { - managementAccountEmail: 'some-management-account@example.com', - logArchiveAccountEmail: 'some-logarchive-account@example.com', - auditAccountEmail: 'some-audit-account@example.com', - }, - accountsConfigObject, -); -const replacementsConfig = ReplacementsConfig.load( - path.resolve('../accelerator/test/configs/snapshot-only'), - accountConfig, -); -let globalConfigWithReplacements: GlobalConfig; - -describe('GlobalConfig', () => { - describe('Test config', () => { - it('has loaded successfully', async () => { - await replacementsConfig.loadReplacementValues({ region: 'us-east-1' }, true); - globalConfigWithReplacements = GlobalConfig.load( - path.resolve('../accelerator/test/configs/snapshot-only'), - replacementsConfig, - ); - expect(globalConfigWithReplacements.homeRegion).toBe('us-east-1'); - }); - - it('has ignored undefined replacements', () => { - expect(globalConfigWithReplacements.tags[1].value).toEqual('{{UNDEFINED_PLACEHOLDER}}'); - }); - - it('has loaded defined replacements from replacements-config.yaml', () => { - expect(globalConfigWithReplacements.tags[2].value).toEqual('TagReplacementValue'); - }); - - it('has loaded account id replacements successfully', () => { - expect(globalConfigWithReplacements.tags[3].value).toEqual('111111111111'); - }); - - it('does not modify ssm dynamic references', () => { - expect(globalConfigWithReplacements.tags[4].value).toEqual('{{resolve:ssm:/accelerator/lza-prefix}}'); - }); - }); -}); - -describe('Replacement config', () => { - describe('Test config', () => { - const replacementsConfig = ReplacementsConfig.load( - path.resolve('../accelerator/test/configs/snapshot-only'), - accountConfig, - true, - ); - it('has loaded successfully', () => { - expect(replacementsConfig.globalReplacements).toHaveLength(4); - }); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/security-config.test.ts b/source/packages/@aws-accelerator/config/test/security-config.test.ts deleted file mode 100644 index 4474ab5..0000000 --- a/source/packages/@aws-accelerator/config/test/security-config.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, expect, it } from '@jest/globals'; -import * as fs from 'fs'; -import * as path from 'path'; -import { - SecurityConfig, - KeyConfig, - GuardDutyEksProtectionConfig, - AuditManagerDefaultReportsDestinationConfig, - AuditManagerConfig, - DetectiveConfig, - SecurityHubStandardConfig, - SecurityHubLoggingCloudwatchConfig, - SecurityHubLoggingConfig, - SnsSubscriptionConfig, - DocumentConfig, - DocumentSetConfig, - AwsConfigAggregation, - ConfigRule, - AwsConfigRuleSet, - MetricConfig, - MetricSetConfig, - AlarmConfig, - AlarmSetConfig, - EncryptionConfig, - LogGroupsConfig, - IsPublicSsmDoc, -} from '../lib/security-config'; - -describe('SecurityConfig', () => { - describe('Test config', () => { - const securityConfigFromFile = SecurityConfig.load(path.resolve('../accelerator/test/configs/snapshot-only')); - it('has loaded successfully', () => { - expect(securityConfigFromFile.getDelegatedAccountName()).toBe('Audit'); - }); - - expect(new KeyConfig().name).toEqual(''); - - expect(new GuardDutyEksProtectionConfig().enable).toBe(false); - - expect(new AuditManagerDefaultReportsDestinationConfig().enable).toBe(false); - - expect(new AuditManagerConfig().enable).toBe(false); - - expect(new DetectiveConfig().enable).toBe(false); - - expect(new SecurityHubStandardConfig().enable).toBe(true); - - expect(new SecurityHubLoggingCloudwatchConfig().enable).toBe(true); - - expect(new SecurityHubLoggingConfig().cloudWatch).toBe(undefined); - - expect(new SnsSubscriptionConfig().email).toBe(''); - - expect(new DocumentConfig().name).toBe(''); - - expect(new DocumentSetConfig().documents).toStrictEqual([]); - - expect(new AwsConfigAggregation().enable).toBe(true); - - expect(new ConfigRule().name).toBe(''); - - expect(new AwsConfigRuleSet().rules).toStrictEqual([]); - - expect(new MetricConfig().filterName).toBe(''); - - expect(new MetricSetConfig().regions).toBeUndefined; - - expect(new AlarmConfig().alarmName).toBe(''); - - expect(new AlarmSetConfig().regions).toBeUndefined; - - expect(new EncryptionConfig().kmsKeyName).toBeUndefined; - - expect(new LogGroupsConfig().encryption).toBeUndefined; - }); -}); - -describe('should throw an exception for wrong config', () => { - function loadError() { - SecurityConfig.loadFromString('some random string'); - } - - const errMsg = 'could not load configuration'; - expect(loadError).toThrow(new Error(errMsg)); -}); - -describe('should return right values for correct config', () => { - const buffer = fs.readFileSync( - path.join(path.resolve('../accelerator/test/configs/snapshot-only'), SecurityConfig.FILENAME), - 'utf8', - ); - const securityConfigFromString = SecurityConfig.loadFromString(buffer); - expect(securityConfigFromString?.awsConfig.enableConfigurationRecorder).toBe(true); -}); - -describe('isPublicSsmDoc', () => { - expect(IsPublicSsmDoc('AWSDoc')).toBeFalsy(); // not public doc - expect(IsPublicSsmDoc('Doc')).toBeFalsy(); // not public doc - expect(IsPublicSsmDoc('AWSAccelerator-Attach-IAM-Instance-Profile')).toBeFalsy(); // not public doc - expect(IsPublicSsmDoc('AWS-AWSAccelerator-Attach-IAM-Instance-Profile')).toBeTruthy(); //public doc -}); diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/duplicate-emails.test.ts b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/duplicate-emails.test.ts deleted file mode 100644 index a379828..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/duplicate-emails.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AccountsConfigValidator } from '../../../../validator/accounts-config-validator'; -import { OrganizationConfig } from '../../../../lib/organization-config'; -import { AccountsConfig } from '../../../../lib/accounts-config'; -import { describe, it, expect } from '@jest/globals'; -import * as path from 'path'; - -// it should throw error when duplicate emails are found -describe('AccountsConfigValidator', () => { - it('should throw error when duplicate emails are found', () => { - const loadedAccounts = AccountsConfig.load( - path.resolve('./test/validation/accounts-config/duplicate-emails/no-org-config'), - ); - const loadedOus = OrganizationConfig.load( - path.resolve('./test/validation/accounts-config/duplicate-emails/no-org-config'), - ); - function duplicateEmailError() { - new AccountsConfigValidator(loadedAccounts, loadedOus); - } - const errMsg = `accounts-config.yaml has 1 issues:\nDuplicate email: alias+no-org-tenant01@example.com, associated with multiple accounts: Tenant01, Tenant01Duplicate`; - expect(duplicateEmailError).toThrow(new Error(errMsg)); - }); -}); diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/accounts-config.yaml b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/accounts-config.yaml deleted file mode 100644 index 23698a6..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/accounts-config.yaml +++ /dev/null @@ -1,43 +0,0 @@ -mandatoryAccounts: - - name: Management - description: The management (primary) account - email: alias+no-org-root@example.com - organizationalUnit: Root - - name: LogArchive - description: The log archive account - email: alias+no-org-log@example.com - organizationalUnit: Root - - name: Audit - description: The security audit account (also referred to as the audit account) - email: alias+no-org-audit@example.com - organizationalUnit: Root -workloadAccounts: - - name: SharedServices - description: The shared services account - email: alias+no-org-shared@example.com - organizationalUnit: Root - - name: Network - description: The network account - email: alias+no-org-network@example.com - organizationalUnit: Root - - name: Tenant01 - description: Tenant 01 Account - email: alias+no-org-tenant01@example.com - organizationalUnit: Root - - name: Tenant01Duplicate - description: Tenant 01 Account - email: alias+no-org-tenant01@example.com - organizationalUnit: Root -accountIds: - - email: alias+no-org-root@example.com - accountId: '111111111111' - - email: alias+no-org-log@example.com - accountId: '222222222222' - - email: alias+no-org-audit@example.com - accountId: '333333333333' - - email: alias+no-org-shared@example.com - accountId: '444444444444' - - email: alias+no-org-network@example.com - accountId: '555555555555' - - email: alias+no-org-tenant01@example.com - accountId: '666666666666' diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/global-config.yaml b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/global-config.yaml deleted file mode 100644 index b66bff2..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/global-config.yaml +++ /dev/null @@ -1,16 +0,0 @@ -homeRegion: &HOME_REGION us-gov-west-1 -enabledRegions: - - *HOME_REGION -managementAccountAccessRole: OrganizationAccountAccessRole -cloudwatchLogRetentionInDays: 365 -terminationProtection: false -controlTower: - enable: false -logging: - account: LogArchive - cloudtrail: - enable: false - organizationTrail: false - sessionManager: - sendToCloudWatchLogs: false - sendToS3: false \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/iam-config.yaml b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/iam-config.yaml deleted file mode 100644 index 579b516..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/iam-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -providers: [] -policySets: [] -roleSets: [] -groupSets: [] -userSets: [] diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/network-config.yaml b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/network-config.yaml deleted file mode 100644 index c19603e..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/network-config.yaml +++ /dev/null @@ -1,45 +0,0 @@ -homeRegion: &HOME_REGION us-gov-west-1 -defaultVpc: - delete: false -transitGateways: [] -endpointPolicies: - - name: Default - document: vpc-endpoint-policies/default.json - - name: Ec2 - document: vpc-endpoint-policies/ec2.json -vpcs: [] -vpcFlowLogs: - trafficType: ALL - maxAggregationInterval: 600 - destinations: - - s3 - - cloud-watch-logs - defaultFormat: false - customFields: - - version - - account-id - - interface-id - - srcaddr - - dstaddr - - srcport - - dstport - - protocol - - packets - - bytes - - start - - end - - action - - log-status - - vpc-id - - subnet-id - - instance-id - - tcp-flags - - type - - pkt-srcaddr - - pkt-dstaddr - - region - - az-id - - pkt-src-aws-service - - pkt-dst-aws-service - - flow-direction - - traffic-path \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/organization-config.yaml b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/organization-config.yaml deleted file mode 100644 index 08976cd..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/organization-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -enable: false -organizationalUnits: [] -serviceControlPolicies: [] -taggingPolicies: [] -backupPolicies: [] diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/security-config.yaml b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/security-config.yaml deleted file mode 100644 index 4d6dce1..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/security-config.yaml +++ /dev/null @@ -1,51 +0,0 @@ -homeRegion: &HOME_REGION us-gov-west-1 -centralSecurityServices: - delegatedAdminAccount: Audit - ebsDefaultVolumeEncryption: - enable: false - excludeRegions: [] - s3PublicAccessBlock: - enable: false - excludeAccounts: [] - snsSubscriptions: [] - macie: - enable: false - excludeRegions: [] - policyFindingsPublishingFrequency: FIFTEEN_MINUTES - publishSensitiveDataFindings: true - guardduty: - enable: false - excludeRegions: [] - s3Protection: - enable: false - excludeRegions: [] - exportConfiguration: - enable: false - destinationType: S3 - exportFrequency: FIFTEEN_MINUTES - securityHub: - enable: false - excludeRegions: [] - standards: [] - ssmAutomation: - excludeRegions: [] - documentSets: [] -accessAnalyzer: - enable: false -iamPasswordPolicy: - allowUsersToChangePassword: true - hardExpiry: false - requireUppercaseCharacters: true - requireLowercaseCharacters: true - requireSymbols: true - requireNumbers: true - minimumPasswordLength: 14 - passwordReusePrevention: 24 - maxPasswordAge: 90 -awsConfig: - enableConfigurationRecorder: true - enableDeliveryChannel: true - ruleSets: [] -cloudWatch: - metricSets: [] - alarmSets: [] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/default.json b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/default.json deleted file mode 100644 index a812fa1..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/ec2.json b/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/ec2.json deleted file mode 100644 index 4c707d8..0000000 --- a/source/packages/@aws-accelerator/config/test/validation/accounts-config/duplicate-emails/no-org-config/vpc-endpoint-policies/ec2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "ec2:*", - "Resource": "*" - } - ] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/config/tsconfig.json b/source/packages/@aws-accelerator/config/tsconfig.json deleted file mode 100644 index 489753d..0000000 --- a/source/packages/@aws-accelerator/config/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "index.ts", "validator/network-config-validator.ts", "lib/schemas/*.json"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/config/validator/accounts-config-validator.ts b/source/packages/@aws-accelerator/config/validator/accounts-config-validator.ts deleted file mode 100644 index ce72bd9..0000000 --- a/source/packages/@aws-accelerator/config/validator/accounts-config-validator.ts +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import * as emailValidator from 'email-validator'; -import { AccountsConfig } from '../lib/accounts-config'; -import { OrganizationConfig } from '../lib/organization-config'; - -export class AccountsConfigValidator { - constructor(values: AccountsConfig, organizationConfig: OrganizationConfig) { - const ouIdNames: string[] = ['Root']; - - const errors: string[] = []; - - const logger = createLogger(['accounts-config-validator']); - - logger.info(`${AccountsConfig.FILENAME} file validation started`); - - // - // Get list of OU ID names from organization config file - // - ouIdNames.push(...this.getOuIdNames(organizationConfig)); - // - // Validate OU name for account - // - this.validateAccountOrganizationalUnit(values, ouIdNames, organizationConfig, errors); - // - // Verify mandatory account names did not change - // - this.validateMandatoryAccountNames(values, errors); - // - // Verify account names are unique and name without space - // - this.validateAccountNames(values, errors); - // - // Email validation - // - this.validateEmails(values, errors); - - if (errors.length) { - throw new Error(`${AccountsConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - - /** - * Function to validate email formats, default and duplicate email checks - * @param values - */ - private validateEmails(values: AccountsConfig, errors: string[]) { - const emails = [...values.mandatoryAccounts, ...values.workloadAccounts].map(item => item.email); - const defaultEmails = ['management-account@example.com', 'log-archive@example.com', 'audit@example.com']; - - // - // validate email format - // - emails.forEach(item => { - if (!emailValidator.validate(item)) { - errors.push(`Invalid email ${item}.`); - } - }); - - // - // default email check - // - defaultEmails.forEach(item => { - if (emails.indexOf(item) !== -1) { - errors.push(`Default email (${item}) found.`); - } - }); - - // Check for duplicates altered to allow for single account deployment - const singleAccountDeployment = process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] - ? process.env['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] === 'true' - : false; - if (singleAccountDeployment) { - return; - } else { - this.findDuplicateEmails(values, errors); - } - } - - /** - * Finds duplicate emails in the given accounts configuration. - * - * @param values - The AccountsConfig object containing mandatory and workload accounts. - * @param errors - An array to store error messages for duplicate emails. - * - * @description This function iterates over the mandatory and workload accounts in the provided AccountsConfig object. - * It checks for duplicate emails by maintaining a Map of emails and their associated account names. - * If a duplicate email is found, it adds an error message to the `errors` array with the duplicate email - * and the associated account names. - * Adding account name will help find all affected accounts in arrays with over 100 objects. - */ - private findDuplicateEmails(values: AccountsConfig, errors: string[]) { - const emailMap = new Map(); - const allAccounts = [...values.mandatoryAccounts, ...values.workloadAccounts]; - - for (const account of allAccounts) { - const { name, email } = account; - if (emailMap.has(email)) { - const accountNames = emailMap.get(email); - accountNames?.push(name); - errors.push(`Duplicate email: ${email}, associated with multiple accounts: ${accountNames?.join(', ')}`); - } else { - emailMap.set(email, [name]); - } - } - } - - /** - * Function to verify account names are unique and name without space - * @param values - */ - private validateAccountNames(values: AccountsConfig, errors: string[]) { - const accountNames = [...values.mandatoryAccounts, ...values.workloadAccounts].map(item => item.name); - if (new Set(accountNames).size !== accountNames.length) { - errors.push(`Duplicate account names defined [${accountNames}].`); - } - - for (const account of [...values.mandatoryAccounts, ...values.workloadAccounts]) { - if (account.name.indexOf(' ') > 0) { - errors.push(`Account name (${account.name}) found with spaces. Please remove spaces and retry the pipeline.`); - } - } - } - - /** - * Function to verify mandatory account names did not change - * @param values - */ - private validateMandatoryAccountNames(values: AccountsConfig, errors: string[]) { - for (const accountName of [ - AccountsConfig.MANAGEMENT_ACCOUNT, - AccountsConfig.AUDIT_ACCOUNT, - AccountsConfig.LOG_ARCHIVE_ACCOUNT, - ]) { - if (!values.mandatoryAccounts.find(item => item.name === accountName)) { - errors.push(`Unable to find mandatory account with name ${accountName}.`); - } - } - } - - /** - * Function to validate existence of account deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateAccountOrganizationalUnit( - values: AccountsConfig, - ouIdNames: string[], - organizationConfig: OrganizationConfig, - errors: string[], - ) { - for (const account of [...values.mandatoryAccounts, ...values.workloadAccounts] ?? []) { - if (account.organizationalUnit) { - // Check if OU exists - if (!ouIdNames.includes(account.organizationalUnit)) { - errors.push( - `OU ${account.organizationalUnit} for account ${account.name} does not exist in organization-config.yaml file.`, - ); - } else { - // Check if OU is ignored - const isIgnoredOu = organizationConfig.organizationalUnits.find( - ou => ou.name === account.organizationalUnit && ou.ignore, - ); - if (isIgnoredOu) { - errors.push( - `OU ${account.organizationalUnit} for account ${account.name} is ignored. Please remove the account from accounts-config.yaml or target a different OU`, - ); - } - } - } - } - } - - /** - * Prepare list of OU ids from organization config file - * @param configDir - */ - private getOuIdNames(organizationConfig: OrganizationConfig): string[] { - const ouIdNames: string[] = []; - - for (const organizationalUnit of organizationConfig.organizationalUnits) { - ouIdNames.push(organizationalUnit.name); - } - return ouIdNames; - } -} diff --git a/source/packages/@aws-accelerator/config/validator/common/common-validator-functions.ts b/source/packages/@aws-accelerator/config/validator/common/common-validator-functions.ts deleted file mode 100644 index cf6db61..0000000 --- a/source/packages/@aws-accelerator/config/validator/common/common-validator-functions.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import fs from 'fs'; -import path from 'path'; -import { AccountsConfig } from '../../lib/accounts-config'; -import * as t from '../../lib/common'; -import { GlobalConfig } from '../../lib/global-config'; -import { ReplacementsConfig } from '../../lib/replacements-config'; - -/** - * Class for common helper functions - */ -export class CommonValidatorFunctions { - static ACCEL_POLICY_STATIC_PARAMETER_LOOKUP_REGEX = /\${ACCEL_LOOKUP::CUSTOM:([a-zA-Z0-9-_]*)}/g; - - /** - * Get account names for a deployment target object - * @param targets - * @returns - */ - public static getAccountNamesFromDeploymentTargets( - accountsConfig: AccountsConfig, - deploymentTargets: t.DeploymentTargets, - ): string[] { - const accountNames: string[] = []; - - for (const ou of deploymentTargets.organizationalUnits ?? []) { - if (ou === 'Root') { - for (const account of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accountNames.push(account.name); - } - } else { - for (const account of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - if (ou === account.organizationalUnit) { - accountNames.push(account.name); - } - } - } - } - - for (const account of deploymentTargets.accounts ?? []) { - accountNames.push(account); - } - - const filterAccountNames = accountNames.filter(item => !deploymentTargets.excludedAccounts?.includes(item)); - - return [...new Set(filterAccountNames)]; - } - - /** - * Get account names for a share target or deployment target object - * @param targets - * @returns - */ - public static getAccountNamesFromTargets( - accountsConfig: AccountsConfig, - targets: t.DeploymentTargets | t.ShareTargets, - ): string[] { - const accountNames: string[] = []; - - for (const ou of targets.organizationalUnits ?? []) { - if (ou === 'Root') { - for (const account of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accountNames.push(account.name); - } - } else { - for (const account of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - if (ou === account.organizationalUnit) { - accountNames.push(account.name); - } - } - } - } - - for (const account of targets.accounts ?? []) { - accountNames.push(account); - } - - const filterAccountNames = t.isCustomizationsType('IDeploymentTargets', targets) - ? accountNames.filter(item => !targets.excludedAccounts?.includes(item)) - : accountNames; - - return [...new Set(filterAccountNames)]; - } - - /** - * Function that will retrieve the regions from the deploymentTargets object that is passed in. - * @param target {@link t.DeploymentTargets} - * @param globalConfig {@link globalConfig} - * @returns - */ - public static getRegionsFromDeploymentTargets(target: t.DeploymentTargets, global: GlobalConfig): t.Region[] { - const enabledRegions: t.Region[] = global.enabledRegions; - if (target.excludedRegions) { - return enabledRegions.filter(region => !target.excludedRegions.includes(region)); - } - - return enabledRegions; - } - - /** - * Function receives input of the account and region from the deploymentTargets, and provides a combined list - * of environments with the format of account-region (e.g. Dev-us-east-1) - * @param accountsConfig {@link AccountsConfig} - * @param target {@link t.DeploymentTargets} - * @param global - * @returns - */ - public static getEnvironmentsFromDeploymentTargets( - accountsConfig: AccountsConfig, - target: t.DeploymentTargets, - globalConfig: GlobalConfig, - ): string[] { - const environments: string[] = []; - const enabledRegions = CommonValidatorFunctions.getRegionsFromDeploymentTargets(target, globalConfig); - const accountConfigs = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets(accountsConfig, target); - - for (const accountConfig of accountConfigs) { - for (const enableRegion of enabledRegions) { - environments.push(accountConfig + '-' + enableRegion); - } - } - return environments; - } - /** - * Function receives input list of two account-regions and checks to see if first list is in the second list - * of environments with the format of account-region (e.g. Dev-us-east-1) - * useful for comparing DeploymentTargets of two entities in validation - * @param source - * @param target - * @returns - * true if source is part of target - * false if source is not part of target - */ - public static compareDeploymentEnvironments(source: string[], target: string[]): { match: boolean; message: string } { - // make sure the arrays are unique - const uniqueSource = [...new Set(source)]; - const uniqueTarget = [...new Set(target)]; - - let match = false; - let message = ''; - if (uniqueSource.length > uniqueTarget.length) { - console.log(`Src length: ${uniqueSource.length}, target length: ${uniqueTarget.length}`); - //Source array is bigger than target - message = 'Source length exceeds target'; - } else if (uniqueSource.sort().toString() == uniqueTarget.sort().toString()) { - // Source and target are exactly the same - match = true; - message = 'Source and target are same'; - } else if ( - !(uniqueSource.sort().toString() == uniqueTarget.sort().toString()) && - uniqueSource.filter(x => !uniqueTarget.includes(x)).length > 0 - ) { - // Source and target are not the same - // There is an element in source that is not in target - message = 'Source not in target'; - } else if ( - !(uniqueSource.sort().toString() == uniqueTarget.sort().toString()) && - uniqueSource.filter(x => !uniqueTarget.includes(x)).length === 0 - ) { - // Source and target are not the same - // There is an element in source that is in target - message = 'Source is in target'; - match = true; - } - return { match, message }; - } - - public static validateStaticParameters( - replacementConfig: ReplacementsConfig | undefined, - configDir: string, - policyFilePaths: string[], - reservedParameters: Set, - errors: string[], - ) { - for (const policyPath of policyFilePaths) { - const policyContent: string = fs.readFileSync(path.join(configDir, policyPath), 'utf8'); - const matches = [...policyContent.matchAll(CommonValidatorFunctions.ACCEL_POLICY_STATIC_PARAMETER_LOOKUP_REGEX)]; - new Set(matches.map(match => match[1])).forEach(parameterName => { - if (reservedParameters.has(parameterName)) return; - if (!replacementConfig?.placeholders[parameterName]) - errors.push(`Missing values for static parameter ${parameterName} used in ${policyPath}.`); - }); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/customizations-config-validator.ts b/source/packages/@aws-accelerator/config/validator/customizations-config-validator.ts deleted file mode 100644 index de8cd8d..0000000 --- a/source/packages/@aws-accelerator/config/validator/customizations-config-validator.ts +++ /dev/null @@ -1,1670 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { AccountsConfig } from '../lib/accounts-config'; -import { - AppConfigItem, - CustomizationsConfig, - NlbTargetTypeConfig, - ApplicationLoadBalancerConfig, - TargetGroupItemConfig, - NetworkLoadBalancerConfig, - Ec2FirewallInstanceConfig, - Ec2FirewallAutoScalingGroupConfig, -} from '../lib/customizations-config'; -import { GlobalConfig } from '../lib/global-config'; -import { IamConfig } from '../lib/iam-config'; -import { NetworkConfig, VpcConfig, VpcTemplatesConfig } from '../lib/network-config'; -import { OrganizationConfig } from '../lib/organization-config'; -import { SecurityConfig } from '../lib/security-config'; -import { CommonValidatorFunctions } from './common/common-validator-functions'; -import { INetworkConfig, ISubnetConfig } from '../lib/models/network-config'; -import { - IBlockDeviceMappingItem, - ICustomizationsConfig, - IEc2FirewallAutoScalingGroupConfig, - IEc2FirewallInstanceConfig, - INetworkInterfaceItem, - ITargetGroupItem, -} from '../lib/models/customizations-config'; -import { DeploymentTargets, Region, ShareTargets, isCustomizationsType, isNetworkType } from '../lib/common'; - -/** - * Customizations Configuration validator. - * Validates customization configuration - */ -export class CustomizationsConfigValidator { - constructor( - values: CustomizationsConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - iamConfig: IamConfig, - networkConfig: NetworkConfig, - organizationConfig: OrganizationConfig, - securityConfig: SecurityConfig, - configDir: string, - ) { - const ouIdNames: string[] = ['Root']; - - const errors: string[] = []; - const logger = createLogger(['customizations-config-validator']); - - logger.info(`${CustomizationsConfig.FILENAME} file validation started`); - - // - // Get list of OU ID names from organization config file - ouIdNames.push(...this.getOuIdNames(organizationConfig)); - - // - // Get list of Account names from account config file - const accountNames = this.getAccountNames(accountsConfig); - - // - // Instantiate helper methods - const helpers = new CustomizationHelperMethods(accountsConfig, iamConfig, globalConfig); - - // - // Start Validation - // Validate customizations - new CustomizationValidator( - values, - accountsConfig, - globalConfig, - networkConfig, - securityConfig, - ouIdNames, - configDir, - accountNames, - helpers, - errors, - ); - - // Validate firewalls - new FirewallValidator(values, networkConfig, securityConfig, accountsConfig, configDir, helpers, errors); - - if (errors.length) { - throw new Error(`${CustomizationsConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - /** - * Prepare list of OU ids from organization config file - * @param organizationConfig - * @returns - */ - private getOuIdNames(organizationConfig: OrganizationConfig): string[] { - const ouIdNames: string[] = []; - for (const organizationalUnit of organizationConfig.organizationalUnits) { - ouIdNames.push(organizationalUnit.name); - } - return ouIdNames; - } - - /** - * Prepare list of Account names from account config file - * @param configDir - */ - private getAccountNames(accountsConfig: AccountsConfig): string[] { - const accountNames: string[] = []; - - for (const accountItem of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accountNames.push(accountItem.name); - } - return accountNames; - } -} - -/** - * Class to validate customizations - */ -class CustomizationValidator { - constructor( - values: CustomizationsConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - networkConfig: NetworkConfig, - securityConfig: SecurityConfig, - ouIdNames: string[], - configDir: string, - accountNames: string[], - helpers: CustomizationHelperMethods, - errors: string[], - ) { - // Validate deployment target Account Names - this.validateDeploymentTargetAccountNames(values, accountNames, errors); - - // Validate deployment target Organizational Unit Names - this.validateDeploymentTargetOUs(values, ouIdNames, errors); - - // Validate stack name lengths - this.validateStackNameLength(values, errors); - - // Validate stack names are unique - this.validateStackNameForUniqueness(values, errors); - - // Validate presence of template file - this.validateTemplateFile(configDir, values, errors); - - // Validate applications inputs - this.validateApplicationsInputs( - values, - { - configDir, - accountsConfig, - globalConfig, - networkConfig, - securityConfig, - }, - helpers, - errors, - ); - - // Validate Service Catalog portfolio inputs - this.validateServiceCatalogInputs(values, accountsConfig, errors, accountNames, ouIdNames); - - /** - * @remarks - * Customizations require region validation for the following resources: - * - Custom CloudFormation Stacks - * - Custom CloudFormation StackSets - * - Service Catalog Portfolios - * - * Region validation is not required for: - * - Applications (region is already checked) - * - Firewalls (not an option) - */ - this.validateCustomizationsConfigRegions(values, errors, globalConfig); - } - - /** - * Validates the regions specified in the customizations configuration. - * - * This function iterates through the CloudFormation stacks, CloudFormation stack sets, and Service Catalog portfolios - * defined in the customizations configuration. For each of these, it calls the `validateCustomizationsRegions` function - * to validate the regions specified for that resource. - * - * @param values - The customizations configuration object. - * @param errors - An array to store any validation errors. - * @param globalConfig - The global configuration object. - */ - private validateCustomizationsConfigRegions( - values: ICustomizationsConfig, - errors: string[], - globalConfig: GlobalConfig, - ) { - for (const stack of values.customizations?.cloudFormationStacks ?? []) { - this.validateCustomizationsRegions( - stack.name, - 'CloudFormation Stack', - stack.regions, - globalConfig.enabledRegions, - errors, - ); - } - - for (const stackSet of values.customizations?.cloudFormationStackSets ?? []) { - this.validateCustomizationsRegions( - stackSet.name, - 'CloudFormation StackSet', - stackSet.regions, - globalConfig.enabledRegions, - errors, - ); - } - - for (const serviceCatalogPortfolio of values.customizations?.serviceCatalogPortfolios ?? []) { - this.validateCustomizationsRegions( - serviceCatalogPortfolio.name, - 'Service Catalog Portfolio', - serviceCatalogPortfolio.regions, - globalConfig.enabledRegions, - errors, - ); - } - } - - /** - * Validates the regions specified for a given resource against the enabled regions. - * - * @param resourceName - The name of the resource being validated. - * @param resourceType - The type of the resource being validated (e.g., CloudFormation Stack, CloudFormation StackSet, Service Catalog Portfolio). - * @param regions - An array of regions specified for the resource. - * @param enabledRegions - An array of enabled regions to validate against. - * @param errors - An array to store any error messages generated during validation. - */ - private validateCustomizationsRegions( - resourceName: string, - resourceType: string, - regions: string[], - enabledRegions: Region[], - errors: string[], - ) { - for (const region of regions) { - if (!enabledRegions.includes(region as Region)) { - errors.push( - `Invalid region ${region} specified for ${resourceType} ${resourceName}. Region must be part of enabled regions: ${enabledRegions}.`, - ); - } - } - } - - /** - * Function to validate template file existence - * @param configDir - * @param values - */ - private validateTemplateFile(configDir: string, values: ICustomizationsConfig, errors: string[]) { - for (const cloudFormationStack of values.customizations?.cloudFormationStacks ?? []) { - if (!fs.existsSync(path.join(configDir, cloudFormationStack.template))) { - errors.push( - `Invalid or missing template file ${cloudFormationStack.template} for CloudFormation Stack ${cloudFormationStack.name} !!!`, - ); - } - } - for (const cloudFormationStackSet of values.customizations?.cloudFormationStackSets ?? []) { - if (!fs.existsSync(path.join(configDir, cloudFormationStackSet.template))) { - errors.push( - `Invalid or missing template file ${cloudFormationStackSet.template} for CloudFormation StackSet ${cloudFormationStackSet.name} !!!`, - ); - } - } - for (const serviceCatalogPortfolio of values.customizations?.serviceCatalogPortfolios ?? []) { - for (const serviceCatalogProduct of serviceCatalogPortfolio.products ?? []) { - for (const productVersion of serviceCatalogProduct.versions ?? []) { - if (!fs.existsSync(path.join(configDir, productVersion.template))) { - errors.push( - `Product version ${productVersion.name} template file ${productVersion.template} of portfolio ${serviceCatalogPortfolio.name} not found !!!`, - ); - } - } - } - } - } - - /** - * Function to validate stack and stackset names - * @param configDir - * @param values - */ - private validateStackNameLength(values: ICustomizationsConfig, errors: string[]) { - for (const cloudFormationStack of values.customizations?.cloudFormationStacks ?? []) { - if (cloudFormationStack.name.length > 128) { - errors.push( - `Provided CloudFormation Stack name ${cloudFormationStack.name} exceeds limit of 128 characters !!!`, - ); - } - } - for (const cloudFormationStackSet of values.customizations?.cloudFormationStackSets ?? []) { - if (cloudFormationStackSet.name.length > 128) { - errors.push( - `Provided CloudFormation StackSet name ${cloudFormationStackSet.name} exceeds limit of 128 characters !!!`, - ); - } - } - } - - /** - * Function to validate stack and stackset names are unique - * @param values - */ - private validateStackNameForUniqueness(values: ICustomizationsConfig, errors: string[]) { - const stackNames = [...(values.customizations?.cloudFormationStacks ?? [])].map(item => item.name); - const stackSetNames = [...(values.customizations?.cloudFormationStackSets ?? [])].map(item => item.name); - - if (new Set(stackNames).size !== stackNames.length) { - errors.push(`Duplicate custom stack names defined [${stackNames}].`); - } - - if (new Set(stackSetNames).size !== stackSetNames.length) { - errors.push(`Duplicate custom stackset names defined [${stackSetNames}].`); - } - } - - /** - * Function to validate existence of stack and stackset deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateDeploymentTargetOUs(values: ICustomizationsConfig, ouIdNames: string[], errors: string[]) { - for (const stack of values.customizations?.cloudFormationStacks ?? []) { - for (const ou of stack.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for CloudFormation Stack does not exists in organization-config.yaml file.`, - ); - } - } - } - for (const stackSet of values.customizations?.cloudFormationStackSets ?? []) { - for (const ou of stackSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for CloudFormation StackSet does not exists in organization-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of stack and stackset target account names - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateDeploymentTargetAccountNames( - values: ICustomizationsConfig, - accountNames: string[], - errors: string[], - ) { - for (const stack of values.customizations?.cloudFormationStacks ?? []) { - for (const account of stack.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for CloudFormation Stack does not exists in accounts-config.yaml file.`, - ); - } - } - } - for (const stackSet of values.customizations?.cloudFormationStackSets ?? []) { - for (const account of stackSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for CloudFormation StackSet does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate the application config inputs. - * Each app is taken and its VPC is checked. - * Within the VPC, the subnets and security groups are checked. - * If KMS is provided, kms key in security-config.yaml is checked. - * @param configDir - * @param values - * @param errors - */ - private validateApplicationsInputs( - values: ICustomizationsConfig, - configs: { - configDir: string; - accountsConfig: AccountsConfig; - globalConfig: GlobalConfig; - networkConfig: NetworkConfig; - securityConfig: SecurityConfig; - }, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - const appNames: string[] = []; - for (const app of values.applications ?? []) { - appNames.push(app.name); - - //check if appName with prefixes is over 128 characters - this.checkAppName(app as AppConfigItem, configs.globalConfig, errors); - // check if vpc actually exists - const vpcCheck = helpers.checkVpcInConfig(app.vpc, configs.networkConfig); - if (!vpcCheck) { - errors.push(`Application ${app.name}: VPC ${app.vpc} does not exist in file network-config.yaml`); - } else if (vpcCheck) { - if (app.applicationLoadBalancer) { - this.checkAlb( - app.applicationLoadBalancer as ApplicationLoadBalancerConfig, - vpcCheck, - { - networkConfig: configs.networkConfig, - accountsConfig: configs.accountsConfig, - globalConfig: configs.globalConfig, - }, - { - appName: app.name, - appVpc: app.vpc, - appTargetGroups: (app.targetGroups as TargetGroupItemConfig[]) ?? undefined, - deploymentTargets: app.deploymentTargets as DeploymentTargets, - }, - helpers, - errors, - ); - } - if (app.networkLoadBalancer) { - this.checkNlb( - app.networkLoadBalancer as NetworkLoadBalancerConfig, - vpcCheck, - { - networkConfig: configs.networkConfig, - accountsConfig: configs.accountsConfig, - globalConfig: configs.globalConfig, - }, - { - appName: app.name, - appVpc: app.vpc, - appTargetGroups: (app.targetGroups as TargetGroupItemConfig[]) ?? undefined, - deploymentTargets: app.deploymentTargets as DeploymentTargets, - }, - helpers, - errors, - ); - } - this.checkLaunchTemplate(app as AppConfigItem, vpcCheck, helpers, configs.securityConfig, errors); - this.checkAutoScaling(app as AppConfigItem, vpcCheck, helpers, configs.accountsConfig, errors); - } - // Validate file - if (app.launchTemplate?.userData) { - if (!fs.existsSync(path.join(configs.configDir, app.launchTemplate.userData))) { - errors.push(`Launch Template file ${app.launchTemplate.userData} not found, for ${app.name} !!!`); - } - } - } - // Check for duplicate app names - if (appNames.length > 1) { - const duplicateAppNames = appNames.some(element => { - return appNames.indexOf(element) !== appNames.lastIndexOf(element); - }); - if (duplicateAppNames) { - errors.push(`There are duplicates in application names. Application names: ${appNames}`); - } - } - } - private checkAppName(app: AppConfigItem, globalConfig: GlobalConfig, errors: string[]) { - // Validate app name - if (!app.name) { - errors.push(`[Application ${app.name}]: Application name is required`); - } - - // Validate app vpc - if (!app.vpc) { - errors.push(`[Application ${app.vpc}]: Application vpc is required`); - } - - const allEnabledRegions = globalConfig.enabledRegions; - let filteredRegions: Region[]; - if (app.deploymentTargets.excludedAccounts && app.deploymentTargets.excludedAccounts.length > 0) { - filteredRegions = allEnabledRegions.filter(obj => !app.deploymentTargets.excludedAccounts.includes(obj)); - } else { - filteredRegions = allEnabledRegions; - } - if (filteredRegions.length === 0) { - errors.push(`[Application ${app.name}]: Has no deployment targets. Please consider removing this item.`); - } - this.checkAppNameLength(app.name, filteredRegions, errors); - } - private checkAppNameLength(appName: string, targetRegion: string[], errors: string[]) { - for (const regionItem of targetRegion) { - const solutionPrefix = process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'; - const stackName = `${solutionPrefix}-App-${appName}-0123456789012-${regionItem}`; - if (stackName.length > 128) { - errors.push(`[Application ${appName}]: Application name ${stackName} is over 128 characters.`); - } - } - } - private checkLaunchTemplate( - app: AppConfigItem, - vpcCheck: VpcConfig | VpcTemplatesConfig, - helpers: CustomizationHelperMethods, - loadSecurityConfig: SecurityConfig, - errors: string[], - ) { - if (app.launchTemplate) { - const launchTemplateSecurityGroups = app.launchTemplate.securityGroups ?? []; - const ltSgCheck = helpers.checkSecurityGroupInConfig(launchTemplateSecurityGroups, vpcCheck); - if (ltSgCheck === false) { - errors.push( - `Launch Template ${ - app.launchTemplate!.name - } does not have security groups ${launchTemplateSecurityGroups.join(',')} in VPC ${app.vpc}.`, - ); - } - if (app.launchTemplate.blockDeviceMappings) { - helpers.checkBlockDeviceMappings( - app.launchTemplate.blockDeviceMappings, - loadSecurityConfig, - app.launchTemplate.name, - errors, - ); - } - } - } - - private checkAutoScaling( - app: AppConfigItem, - vpcCheck: VpcConfig | VpcTemplatesConfig, - helpers: CustomizationHelperMethods, - accountsConfig: AccountsConfig, - errors: string[], - ) { - if (app.autoscaling) { - const allTargetGroupNames = app.targetGroups?.map(tg => tg.name); - const asgTargetGroupNames = app.autoscaling.targetGroups ?? []; - const compareTargetGroupNames = helpers.compareArrays(asgTargetGroupNames, allTargetGroupNames ?? []); - if (compareTargetGroupNames.length > 0) { - errors.push( - `Autoscaling group ${ - app.autoscaling.name - } has target groups that are not defined in application config. Autoscaling target groups: ${asgTargetGroupNames.join( - ',', - )} all target groups: ${allTargetGroupNames?.join(',')}`, - ); - } - const duplicateAsgSubnets = app.autoscaling.subnets.some(element => { - return app.autoscaling!.subnets.indexOf(element) !== app.autoscaling!.subnets.lastIndexOf(element); - }); - if (duplicateAsgSubnets) { - errors.push( - `There are duplicate subnets in Autoscaling group ${app.autoscaling.name} subnets in ${ - app.name - }. Subnets: ${app.autoscaling!.subnets.join(',')}`, - ); - } - const asgSubnetsCheck = helpers.checkSubnetsInConfig(app.autoscaling.subnets, vpcCheck); - if (asgSubnetsCheck === false) { - errors.push( - `Autoscaling group ${app.autoscaling.name} does not have subnets ${app.autoscaling!.subnets.join( - ',', - )} in VPC ${app.vpc}`, - ); - } - if ( - asgSubnetsCheck && - !this.checkSubnetsTarget(app.autoscaling.subnets, vpcCheck, app.deploymentTargets, accountsConfig, errors) - ) { - errors.push( - `AutoScaling group ${app.autoscaling.name} has subnets ${app.autoscaling.subnets.join( - ',', - )} which are not created or shared in deploymentTargets`, - ); - } - } - } - - private checkNlb( - nlb: NetworkLoadBalancerConfig, - vpcCheck: VpcConfig | VpcTemplatesConfig, - configs: { - networkConfig: NetworkConfig; - accountsConfig: AccountsConfig; - globalConfig: GlobalConfig; - }, - appInfo: { - appName: string; - appVpc: string; - appTargetGroups: TargetGroupItemConfig[] | undefined; - deploymentTargets: DeploymentTargets; - }, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - if (nlb.subnets.length < 1) { - errors.push( - `Network Load Balancer ${nlb.name} does not have enough subnets in ${appInfo.appName}. At least one subnet is required.`, - ); - } - const duplicateNlbSubnets = nlb.subnets.some(element => { - return nlb.subnets.indexOf(element) !== nlb.subnets.lastIndexOf(element); - }); - if (duplicateNlbSubnets) { - errors.push( - `There are duplicates in Network Load Balancer ${nlb.name} subnets in ${ - appInfo.appName - }. Subnets: ${nlb.subnets.join(',')}`, - ); - } - const nlbSubnetsCheck = helpers.checkSubnetsInConfig(nlb.subnets, vpcCheck); - if (nlbSubnetsCheck === false) { - errors.push( - `Network Load Balancer ${nlb.name} does not have subnets ${nlb.subnets.join(',')} in VPC ${appInfo.appVpc}`, - ); - } - if ( - nlbSubnetsCheck && - !this.checkSubnetsTarget(nlb.subnets, vpcCheck, appInfo.deploymentTargets, configs.accountsConfig, errors) - ) { - errors.push( - `Network Load Balancer ${nlb.name} has subnets ${nlb.subnets.join( - ',', - )} which are not created or shared in deploymentTargets`, - ); - } - const allTargetGroupNames = appInfo.appTargetGroups?.map(tg => tg.name); - const nlbTargetGroupNames = nlb.listeners?.map(tg => tg.targetGroup); - const compareTargetGroupNames = helpers.compareArrays(nlbTargetGroupNames ?? [], allTargetGroupNames ?? []); - if (compareTargetGroupNames.length > 0) { - errors.push( - `Network Load Balancer ${ - nlb.name - } has target groups that are not defined in application config. NLB target groups: ${nlbTargetGroupNames?.join( - ',', - )} all target groups: ${allTargetGroupNames?.join(',')}`, - ); - } - const listenerNameCert = (nlb.listeners ?? []) - .filter(obj => obj.certificate) - .map(obj => { - return { name: obj.name, certificate: obj.certificate }; - }); - if (listenerNameCert.length > 0) { - this.checkListenerCerts( - listenerNameCert, - nlb.name, - appInfo.appName, - configs, - appInfo.deploymentTargets, - 'Network Load Balancer', - errors, - ); - } - } - - private checkAlb( - alb: ApplicationLoadBalancerConfig, - vpcCheck: VpcConfig | VpcTemplatesConfig, - configs: { - networkConfig: NetworkConfig; - accountsConfig: AccountsConfig; - globalConfig: GlobalConfig; - }, - appInfo: { - appName: string; - appVpc: string; - appTargetGroups: TargetGroupItemConfig[] | undefined; - deploymentTargets: DeploymentTargets; - }, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - if (alb.securityGroups.length === 0) { - errors.push( - `Application Load Balancer ${alb.name} does not have security groups in ${appInfo.appName}. At least one security group is required`, - ); - } - const albSgCheck = helpers.checkSecurityGroupInConfig(alb.securityGroups, vpcCheck); - if (albSgCheck === false) { - errors.push(`Application Load Balancer ${alb.name} does not have security groups in VPC ${appInfo.appVpc}.`); - } - if (alb.subnets.length < 2) { - errors.push( - `Application Load Balancer ${alb.name} does not have enough subnets in ${appInfo.appName}. At least two subnets are required in different AZs`, - ); - } - const duplicateAlbSubnets = alb.subnets.some(element => { - return alb.subnets.indexOf(element) !== alb.subnets.lastIndexOf(element); - }); - if (duplicateAlbSubnets) { - errors.push( - `There are duplicates in Application Load Balancer ${alb.name} subnets in ${ - appInfo.appName - }. Subnets: ${alb.subnets.join(',')}`, - ); - } - const albSubnetsCheck = helpers.checkSubnetsInConfig(alb.subnets, vpcCheck); - if (albSubnetsCheck === false) { - errors.push( - `Application Load Balancer ${alb.name} does not have subnets ${alb.subnets.join(',')} in VPC ${appInfo.appVpc}`, - ); - } - - if ( - albSubnetsCheck && - !this.checkSubnetsTarget(alb.subnets, vpcCheck, appInfo.deploymentTargets, configs.accountsConfig, errors) - ) { - errors.push(`Application Load Balancer ${alb.name} have invalid subnets configuration`); - } - const allTargetGroupNames = appInfo.appTargetGroups?.map(tg => tg.name); - const albTargetGroupNames = alb.listeners?.map(tg => tg.targetGroup); - const compareTargetGroupNames = helpers.compareArrays(albTargetGroupNames ?? [], allTargetGroupNames ?? []); - if (compareTargetGroupNames.length > 0) { - errors.push( - `Application Load Balancer ${ - alb.name - } has target groups that are not defined in application config. ALB target groups: ${albTargetGroupNames?.join( - ',', - )} all target groups: ${allTargetGroupNames?.join(',')}`, - ); - } - const listenerNameCert = (alb.listeners ?? []) - .filter(obj => obj.certificate) - .map(obj => { - return { name: obj.name, certificate: obj.certificate }; - }); - if (listenerNameCert.length > 0) { - this.checkListenerCerts( - listenerNameCert, - alb.name, - appInfo.appName, - configs, - appInfo.deploymentTargets, - 'Application Load Balancer', - errors, - ); - } - } - - private checkListenerCerts( - listeners: { name: string; certificate: string | undefined }[], - albName: string, - appName: string, - configs: { networkConfig: NetworkConfig; accountsConfig: AccountsConfig; globalConfig: GlobalConfig }, - listenerDeploymentTargets: DeploymentTargets, - loadBalancerType: string, - errors: string[], - ) { - // check to see if certificates are used - const getListenerWithCertificate = listeners.filter(obj => obj.certificate); - - if (getListenerWithCertificate.length > 0 && !configs.networkConfig.certificates) { - errors.push(`Found listeners with certificates but no certificates specified in network-config.yaml`); - } else if (getListenerWithCertificate.length > 0 && configs.networkConfig.certificates) { - for (const listener of getListenerWithCertificate) { - this.verifyCertDeployment( - { - listener, - loadBalancerType, - loadBalancerName: albName, - deploymentTargets: listenerDeploymentTargets, - appName, - }, - configs, - errors, - ); - } - } - } - - private verifyCertDeployment( - listenerData: { - listener: { name: string; certificate: string | undefined }; - loadBalancerType: string; - loadBalancerName: string; - deploymentTargets: DeploymentTargets; - appName: string; - }, - configs: { networkConfig: NetworkConfig; accountsConfig: AccountsConfig; globalConfig: GlobalConfig }, - errors: string[], - ) { - // find listener cert in network cert - const compareCertNames = configs.networkConfig.certificates!.find( - obj => obj.name === listenerData.listener.certificate, - ); - - if (!compareCertNames) { - errors.push( - `Listener: ${listenerData.listener.name} has certificate: ${listenerData.listener.certificate} but no such certificate specified in network-config.yaml`, - ); - } else { - // check in the lookup cert where the deployment targets are and match that - const listenerCert = configs.networkConfig.certificates!.find( - obj => obj.name === listenerData.listener.certificate, - ); - - if (listenerCert?.deploymentTargets) { - const listenerCertEnv = CommonValidatorFunctions.getEnvironmentsFromDeploymentTargets( - configs.accountsConfig, - listenerCert.deploymentTargets, - configs.globalConfig, - ); - const listenerInputEnv = CommonValidatorFunctions.getEnvironmentsFromDeploymentTargets( - configs.accountsConfig, - listenerData.deploymentTargets, - configs.globalConfig, - ); - const compareCertListener = CommonValidatorFunctions.compareDeploymentEnvironments( - listenerInputEnv, - listenerCertEnv, - ); - - if (!compareCertListener.match && compareCertListener.message === 'Source length exceeds target') { - errors.push( - `Listener ${listenerData.listener.name} under ${listenerData.loadBalancerType}: ${listenerData.loadBalancerName} in application ${listenerData.appName} with certificate: ${listenerData.listener.certificate} is being deployed across: ${listenerInputEnv} but certificate ${listenerCert.name} is only deployed to ${listenerCertEnv}`, - ); - } else if (!compareCertListener.match && compareCertListener.message === 'Source not in target') { - const missingCertEnv = listenerInputEnv.filter(certEnv => !listenerCertEnv.includes(certEnv)); - errors.push( - `Listener ${listenerData.listener.name} under ${listenerData.loadBalancerType}: ${listenerData.loadBalancerName} in application ${listenerData.appName} is being deployed to ${listenerInputEnv}. Network config shows certificate: ${listenerCert.name} is deployed at ${listenerCertEnv}. Certificate is missing accounts-regions: ${missingCertEnv}`, - ); - } - } - } - } - - /** - * Function to validate the service catalog config inputs. - * @param configDir - * @param values - * @param errors - */ - private validateServiceCatalogInputs( - values: ICustomizationsConfig, - accountsConfig: AccountsConfig, - errors: string[], - accountNames: string[], - ouIdNames: string[], - ) { - const managementAccount = accountsConfig.getManagementAccount().name; - - this.validateServiceCatalogShareTargetAccounts(values, accountNames, errors); - - this.validateServiceCatalogShareTargetOUs(values, ouIdNames, errors, managementAccount); - - // Validate portfolio names are unique - this.validatePortfolioNameForUniqueness(values, errors); - } - - /** - * Function to validate portfolio names are unique - * @param values - */ - private validatePortfolioNameForUniqueness(values: ICustomizationsConfig, errors: string[]) { - const portfolioNames = [...(values.customizations?.serviceCatalogPortfolios ?? [])].map(item => item.name); - - if (new Set(portfolioNames).size !== portfolioNames.length) { - errors.push(`Duplicate Service Catalog Portfolio names defined [${portfolioNames}].`); - } - } - - /** - * Function to validate existence of Service Catalog share target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateServiceCatalogShareTargetAccounts( - values: ICustomizationsConfig, - accountNames: string[], - errors: string[], - ) { - for (const portfolio of values.customizations?.serviceCatalogPortfolios ?? []) { - for (const account of portfolio?.shareTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Share target account ${account} for Service Catalog portfolio ${portfolio.name} does not exist in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of Service Catalog share target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateServiceCatalogShareTargetOUs( - values: ICustomizationsConfig, - ouIdNames: string[], - errors: string[], - managementAccount: string, - ) { - for (const portfolio of values.customizations?.serviceCatalogPortfolios ?? []) { - for (const ou of portfolio?.shareTargets?.organizationalUnits ?? []) { - if (portfolio.account !== managementAccount) { - errors.push( - `Error sharing Service Catalog portfolio ${portfolio.name} with Organizational Unit ${ou}. Sharing portfolios to Organizational Units is only supported in the Management account.`, - ); - } - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Share target OU ${ou} for Service Catalog portfolio ${portfolio.name} does not exist in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate if subnet is available by local or shared target in deployment target. - */ - private checkSubnetsTarget( - subnets: string[], - vpcCheck: VpcConfig | VpcTemplatesConfig, - deploymentTargets: DeploymentTargets, - accountsConfig: AccountsConfig, - errors: string[], - ) { - let isValid = true; - const subnetsInConfig: ISubnetConfig[] = subnets.map( - (subnet: string) => vpcCheck.subnets!.find(item => item.name === subnet)!, - ); - for (const subnet of subnetsInConfig) { - const subnetTargets = new Set([ - ...CommonValidatorFunctions.getAccountNamesFromTargets( - accountsConfig, - (subnet.shareTargets ?? {}) as ShareTargets, - ), - ...('deploymentTargets' in vpcCheck - ? CommonValidatorFunctions.getAccountNamesFromTargets(accountsConfig, vpcCheck.deploymentTargets) - : [vpcCheck.account]), - ]); - const deploymentTargetAccounts = CommonValidatorFunctions.getAccountNamesFromTargets( - accountsConfig, - deploymentTargets, - ); - - for (const targetItem of deploymentTargetAccounts) { - if (!subnetTargets.has(targetItem)) { - isValid = false; - errors.push( - `Subnet ${subnet.name} defined in launchTemplate or autoScalingGroup is not created or shared in account ${targetItem}`, - ); - } - } - } - return isValid; - } -} - -class CustomizationHelperMethods { - private accountsConfig: AccountsConfig; - private iamConfig: IamConfig; - private globalConfig: GlobalConfig; - - constructor(accountsConfig: AccountsConfig, iamConfig: IamConfig, globalConfig: GlobalConfig) { - this.accountsConfig = accountsConfig; - this.iamConfig = iamConfig; - this.globalConfig = globalConfig; - } - /** - * Get regions of deploymentTargets - */ - public getRegionsFromDeploymentTarget(deploymentTargets: DeploymentTargets): string[] { - if (deploymentTargets.excludedRegions) { - return this.globalConfig.enabledRegions.filter(obj => !deploymentTargets.excludedRegions.includes(obj)); - } else { - return this.globalConfig.enabledRegions; - } - } - /** - * Validate if VPC name is in config file - * @param string - */ - public checkVpcInConfig(vpcName: string, values: INetworkConfig) { - for (const vpcItem of values.vpcs ?? []) { - if (vpcName === vpcItem.name) { - // vpc name exists in network config. Return to function - return vpcItem as VpcConfig; - } - } - for (const vpcItem of values.vpcTemplates! ?? []) { - if (vpcName === vpcItem.name) { - // vpc name exists in network config. Return to function - return vpcItem as VpcTemplatesConfig; - } - } - // Looped through entire config and no matching vpc were found. Raise error - return undefined; - } - /** - * Validate if Security Group is in config file and if that security group is in the right vpc - * @param string - */ - public checkSecurityGroupInConfig(securityGroupNames: string[], vpcItem: VpcConfig | VpcTemplatesConfig) { - // vpc name exists in network config. - // Check within vpc to see if security group exists - - if (!vpcItem.securityGroups) { - return false; - } - - // Get all security group names - const vpcSgs = vpcItem.securityGroups!.map(obj => { - return obj.name; - }); - - // compare input to security groups in vpcs - if (this.compareArrays(securityGroupNames, vpcSgs ?? []).length === 0) { - return true; - } else { - return false; - } - } - public compareArrays(array1: string[], array2: string[]) { - return array1.filter(element => { - return !array2.includes(element); - }); - } - public checkSubnetsInConfig(subnets: string[], vpcItem: VpcConfig | VpcTemplatesConfig) { - // get all subnets within the vpc - const vpcSubnets = vpcItem.subnets?.map(obj => { - return obj.name; - }); - // compare input to subnets in vpcs - - if (this.compareArrays(subnets, vpcSubnets ?? []).length === 0) { - return true; - } else { - return false; - } - } - - public checkBlockDeviceMappings( - blockDeviceMappings: IBlockDeviceMappingItem[], - securityConfig: SecurityConfig, - launchTemplateName: string, - errors: string[], - ) { - for (const blockDeviceMapping of blockDeviceMappings) { - if (blockDeviceMapping.ebs && blockDeviceMapping.ebs.encrypted && blockDeviceMapping.ebs.kmsKeyId === undefined) { - if (securityConfig.centralSecurityServices.ebsDefaultVolumeEncryption.enable === false) { - errors.push( - `EBS volume ${blockDeviceMapping.deviceName} in launch template ${launchTemplateName} is encrypted and no kmsKey is specified. Central Security ebs is disabled so no KMS key can be used.`, - ); - } - } - if (blockDeviceMapping.ebs && blockDeviceMapping.ebs.encrypted && blockDeviceMapping.ebs.kmsKeyId) { - const allKeys = securityConfig.keyManagementService?.keySets.map(obj => obj.name); - const filterKey = allKeys?.find(obj => { - return obj === blockDeviceMapping.ebs!.kmsKeyId; - }); - if (!filterKey) { - errors.push( - `EBS volume ${ - blockDeviceMapping.deviceName - } in launch template ${launchTemplateName} is encrypted and kmsKey ${ - blockDeviceMapping.ebs.kmsKeyId - } specified does not exist. All keys: ${allKeys?.join(',')}.`, - ); - } - } - } - } - - public getIamUsersDeployedToAccount(accountName: string) { - const usernameList = []; - for (const userSetItem of this.iamConfig.userSets ?? []) { - const deploymentAccountNames = this.getAccountNamesFromDeploymentTarget(userSetItem.deploymentTargets); - if (deploymentAccountNames.includes(accountName)) { - usernameList.push(...userSetItem.users.map(a => a.username)); - } - } - return usernameList; - } - - public getIamGroupsDeployedToAccount(accountName: string) { - const groupList = []; - for (const groupSetItem of this.iamConfig.groupSets ?? []) { - const deploymentAccountNames = this.getAccountNamesFromDeploymentTarget(groupSetItem.deploymentTargets); - if (deploymentAccountNames.includes(accountName)) { - groupList.push(...groupSetItem.groups.map(a => a.name)); - } - } - return groupList; - } - - public getIamRolesDeployedToAccount(accountName: string) { - const roleList = []; - for (const roleSetItem of this.iamConfig.roleSets ?? []) { - const deploymentAccountNames = this.getAccountNamesFromDeploymentTarget(roleSetItem.deploymentTargets); - if (deploymentAccountNames.includes(accountName)) { - roleList.push(...roleSetItem.roles.map(a => a.name)); - } - } - return roleList; - } - - public getAccountNamesFromDeploymentTarget(deploymentTargets: DeploymentTargets): string[] { - const accountNames: string[] = []; - // Helper function to add an account to the list - const addAccountName = (accountName: string) => { - if (!accountNames.includes(accountName)) { - accountNames.push(accountName); - } - }; - /** - * @param configDir - * @returns - */ - - for (const ou of deploymentTargets.organizationalUnits ?? []) { - if (ou === 'Root') { - for (const account of [...this.accountsConfig.mandatoryAccounts, ...this.accountsConfig.workloadAccounts]) { - addAccountName(account.name); - } - } else { - for (const account of [...this.accountsConfig.mandatoryAccounts, ...this.accountsConfig.workloadAccounts]) { - if (ou === account.organizationalUnit) { - addAccountName(account.name); - } - } - } - } - - for (const account of deploymentTargets.accounts ?? []) { - addAccountName(account); - } - - return accountNames; - } -} - -class FirewallValidator { - constructor( - values: CustomizationsConfig, - networkConfig: NetworkConfig, - securityConfig: SecurityConfig, - accountsConfig: AccountsConfig, - configDir: string, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - // Validate firewall instances - this.validateFirewalls(values, networkConfig, securityConfig, accountsConfig, configDir, helpers, errors); - } - - private validateFirewalls( - values: CustomizationsConfig, - networkConfig: NetworkConfig, - securityConfig: SecurityConfig, - accountsConfig: AccountsConfig, - configDir: string, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - // Validate firewall instance configs - this.validateFirewallInstances(values, helpers, configDir, networkConfig, securityConfig, accountsConfig, errors); - // Validate firewall ASG configs - this.validateFirewallAsgs(values, helpers, configDir, networkConfig, securityConfig, accountsConfig, errors); - // Validate firewall target groups - this.validateFirewallTargetGroups(values, helpers, errors); - } - - /** - * Validate firewall instances - * @param values - * @param helpers - * @param configDir - * @param networkConfig - * @param securityConfig - * @param errors - */ - private validateFirewallInstances( - values: CustomizationsConfig, - helpers: CustomizationHelperMethods, - configDir: string, - networkConfig: NetworkConfig, - securityConfig: SecurityConfig, - accountsConfig: AccountsConfig, - errors: string[], - ) { - const firewallInstances = [...(values.firewalls?.instances ?? []), ...(values.firewalls?.managerInstances ?? [])]; - for (const firewall of firewallInstances) { - // Validate VPC - const vpc = helpers.checkVpcInConfig(firewall.vpc, networkConfig); - if (!vpc) { - errors.push(`[Firewall instance ${firewall.name}]: VPC ${firewall.vpc} does not exist in network-config.yaml`); - } - if (vpc && isNetworkType('IVpcTemplatesConfig', vpc)) { - errors.push(`[Firewall instance ${firewall.name}]: VPC templates are not supported`); - } - - // Firewall instance launch templates must have network interface definitions - if (!firewall.launchTemplate.networkInterfaces) { - errors.push( - `[Firewall instance ${firewall.name}]: launch template must include at least one network interface configuration`, - ); - } - - if (firewall.configDir && firewall.configFile) { - errors.push( - `[Firewall instance ${firewall.name}]: Either configDir or configFile property should be provided but not both in configuration`, - ); - } - - // Validate launch template - if (isNetworkType('IVpcConfig', vpc) && firewall.launchTemplate.networkInterfaces) { - this.validateLaunchTemplate(vpc, firewall, configDir, securityConfig, accountsConfig, helpers, errors); - this.validateReplacementConfig(firewall, errors); - } - } - } - - /** - * Checks if the object structure is correct when static replacements are defined - * @param firewall Ec2FirewallInstanceConfig | Ec2FirewallAutoScalingGroupConfig - * @param errors string[] - */ - private validateReplacementConfig( - firewall: Ec2FirewallInstanceConfig | Ec2FirewallAutoScalingGroupConfig, - errors: string[], - ) { - if (firewall.staticReplacements && !(firewall.configFile || firewall.configDir)) { - errors.push( - `[Firewall ${firewall.name}]: configFile or configDir property must be set when defining static firewall replacements configuration`, - ); - } - } - - private validateFirewallAsgs( - values: CustomizationsConfig, - helpers: CustomizationHelperMethods, - configDir: string, - networkConfig: NetworkConfig, - securityConfig: SecurityConfig, - accountsConfig: AccountsConfig, - errors: string[], - ) { - for (const group of values.firewalls?.autoscalingGroups ?? []) { - // Validate VPC - const vpc = helpers.checkVpcInConfig(group.vpc, networkConfig); - if (!vpc) { - errors.push(`[Firewall ASG ${group.name}]: VPC ${group.vpc} does not exist in network-config.yaml`); - } - if (vpc && isNetworkType('IVpcTemplatesConfig', vpc)) { - errors.push(`[Firewall ASG ${group.name}]: VPC templates are not supported`); - } - - // Validate EIP and source/dest check is not assigned to any interface - if (group.launchTemplate.networkInterfaces) { - if (group.launchTemplate.networkInterfaces.find(item => item.associateElasticIp)) { - errors.push( - `[Firewall ASG ${group.name}]: cannot define associateElasticIp property for ASG network interfaces`, - ); - } - if (group.launchTemplate.networkInterfaces.find(item => item.sourceDestCheck === false)) { - errors.push( - `[Firewall ASG ${group.name}]: cannot define sourceDestCheck property for ASG network interfaces`, - ); - } - } - - if (group.configDir && group.configFile) { - errors.push( - `[ASG ${group.name}]: Either configDir or configFile property should be provided but not both for ASG`, - ); - } - - // Validate launch template - if (isNetworkType('IVpcConfig', vpc)) { - this.validateLaunchTemplate(vpc, group, configDir, securityConfig, accountsConfig, helpers, errors); - this.validateReplacementConfig(group, errors); - this.validateAsgTargetGroups(values, group, errors); - } - } - } - - private validateFirewallTargetGroups( - values: CustomizationsConfig, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - for (const group of values.firewalls?.targetGroups ?? []) { - if (group.type !== 'instance') { - errors.push(`[Firewall target group ${group.name}]: target group must be instance type`); - } - if (group.type === 'instance' && group.targets) { - const instancesExist = this.checkTargetsInConfig(helpers, group.targets, values.firewalls?.instances ?? []); - if (!instancesExist) { - errors.push( - `[Firewall target group ${group.name}]: target group references firewall instance that does not exist`, - ); - } - - if (instancesExist) { - this.checkInstanceVpcs(group, values.firewalls!.instances!, errors); - } - } - } - } - - private checkTargetsInConfig( - helpers: CustomizationHelperMethods, - targets: (string | NlbTargetTypeConfig)[], - config: IEc2FirewallInstanceConfig[], - ): boolean { - // Retrieve target groups - const targetInstances = config.map(instance => { - return instance.name; - }); - - const targetStrings = targets.filter(target => typeof target === 'string') as string[]; - - // Compare arrays - if (helpers.compareArrays(targetStrings, targetInstances).length === 0) { - return true; - } - return false; - } - - private checkInstanceVpcs(group: ITargetGroupItem, config: IEc2FirewallInstanceConfig[], errors: string[]) { - // Retrieve instance configs - const instances: IEc2FirewallInstanceConfig[] = []; - group.targets!.forEach(target => instances.push(config.find(item => item.name === target)!)); - - // Map VPCs - const vpcs = instances.map(item => { - return item.vpc; - }); - - if (vpcs.some(vpc => vpc !== vpcs[0])) { - errors.push(`[Firewall target group ${group.name}]: targeted instances are in separate VPCs`); - } - } - - /** - * Validate a firewall launch template - * @param vpc - * @param firewall - * @param configDir - * @param securityConfig - * @param helpers - * @param errors - */ - private validateLaunchTemplate( - vpc: VpcConfig, - firewall: IEc2FirewallInstanceConfig | IEc2FirewallAutoScalingGroupConfig, - configDir: string, - securityConfig: SecurityConfig, - accountsConfig: AccountsConfig, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - // Validate security groups - this.validateLaunchTemplateSecurityGroups(vpc, firewall, helpers, errors); - // Validate subnets - this.validateLaunchTemplateSubnets(vpc, firewall, helpers, accountsConfig, errors); - // Validate IAM instance profile - this.validateIamInstanceProfile(vpc, firewall, helpers, errors); - // Validate block devices - if (firewall.launchTemplate.blockDeviceMappings) { - helpers.checkBlockDeviceMappings( - firewall.launchTemplate.blockDeviceMappings, - securityConfig, - firewall.launchTemplate.name, - errors, - ); - } - // Validate user data file exists - if (firewall.launchTemplate.userData) { - if (!fs.existsSync(path.join(configDir, firewall.launchTemplate.userData))) { - errors.push(`[Firewall ${firewall.name}]: launch template user data file not found`); - } - } - } - - /** - * Validates that security groups are appropriately attached - * to a launch template and that they exist in the target VPC - * @param vpc - * @param firewall - * @param helpers - * @param errors - */ - private validateLaunchTemplateSecurityGroups( - vpc: VpcConfig, - firewall: IEc2FirewallInstanceConfig | IEc2FirewallAutoScalingGroupConfig, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - const interfaces = firewall.launchTemplate.networkInterfaces; - const firewallLaunchTemplateSecurityGroups = firewall.launchTemplate.securityGroups ?? []; - if (firewallLaunchTemplateSecurityGroups.length === 0) { - // Validate network interfaces are configured - if (!interfaces) { - errors.push( - `[Firewall ${firewall.name}]: network interfaces must be configured if launch template securityGroups property is empty`, - ); - } - // Validate network interfaces have at least one group assigned - if (interfaces && !this.includesInterfaceGroups(interfaces)) { - errors.push( - `[Firewall ${firewall.name}]: security groups must be attached per network interface if launch template securityGroups property is empty`, - ); - } - } - - // Validate security groups - if (!helpers.checkSecurityGroupInConfig(firewallLaunchTemplateSecurityGroups, vpc)) { - errors.push( - `[Firewall ${firewall.name}]: launch template references security groups that do not exist in VPC ${vpc.name}`, - ); - } - for (const interfaceItem of interfaces ?? []) { - if (interfaceItem.groups) { - if (!helpers.checkSecurityGroupInConfig(interfaceItem.groups, vpc)) { - errors.push( - `[Firewall ${firewall.name}]: launch template network interface references security group that does not exist in VPC ${vpc.name}`, - ); - } - } - } - } - - /** - * Validates that there is at least one security group attached - * to each network interface defined in a launch template - * @param interfaces - * @returns - */ - private includesInterfaceGroups(interfaces: INetworkInterfaceItem[]): boolean { - for (const interfaceItem of interfaces) { - if (!interfaceItem.groups || interfaceItem.groups.length === 0) { - return false; - } - } - return true; - } - - /** - * Validate subnets in the firewall configuration - * @param vpc - * @param firewall - * @param helpers - * @param errors - */ - private validateLaunchTemplateSubnets( - vpc: VpcConfig, - firewall: IEc2FirewallInstanceConfig | IEc2FirewallAutoScalingGroupConfig, - helpers: CustomizationHelperMethods, - accountsConfig: AccountsConfig, - errors: string[], - ) { - if (isCustomizationsType('IEc2FirewallAutoScalingGroupConfig', firewall)) { - this.validateAsgLaunchTemplateSubnets(vpc, firewall, helpers, errors); - } else { - this.validateInstanceLaunchTemplateSubnets(vpc, firewall, helpers, accountsConfig, errors); - } - } - - /** - * Validate subnets for firewall instances - * @param vpc - * @param firewall - * @param helpers - * @param errors - */ - private validateInstanceLaunchTemplateSubnets( - vpc: VpcConfig, - firewall: IEc2FirewallInstanceConfig, - helpers: CustomizationHelperMethods, - accountsConfig: AccountsConfig, - errors: string[], - ) { - // Validate a subnet is associated with each network interface - if (!this.includesSubnet(firewall.launchTemplate.networkInterfaces!)) { - errors.push( - `[Firewall instance ${firewall.name}]: launch template network interface configurations must include subnet attachments`, - ); - } - // Validate subnets are in the VPC - const interfaceSubnets = firewall.launchTemplate.networkInterfaces!.map(interfaceItem => { - return interfaceItem.subnetId!; - }); - // Subnet configs - const subnets: ISubnetConfig[] = []; - const subnetsExist = helpers.checkSubnetsInConfig(interfaceSubnets, vpc); - if (!subnetsExist) { - errors.push( - `[Firewall instance ${firewall.name}]: launch template network interface references subnet that does not exist in VPC ${vpc.name}`, - ); - } - // Validate subnet AZs - if (subnetsExist) { - // Retrieve subnet configs - interfaceSubnets.forEach(subnet => subnets.push(vpc.subnets!.find(item => item.name === subnet)!)); - - // Map AZs - const subnetAzs = subnets.map(subnetItem => { - return subnetItem.availabilityZone; - }); - - if (subnetAzs.some(az => az !== subnetAzs[0])) { - errors.push( - `[Firewall instance ${firewall.name}]: launch template network interfaces reference subnets that reside in different AZs in VPC ${vpc.name}`, - ); - } - } - const isFirewallLocal = !firewall.account || vpc.account === firewall.account; - if (isFirewallLocal) { - // No more validations to perform - return; - } - const invalidInterfaceSubnets: string[] = []; - subnets.map( - subnet => - !CommonValidatorFunctions.getAccountNamesFromTargets( - accountsConfig, - (subnet.shareTargets ?? {}) as ShareTargets, - ).includes(firewall.account!) && invalidInterfaceSubnets.push(subnet.name), - ); - invalidInterfaceSubnets.forEach(subnetName => - errors.push( - `[Firewall instance ${firewall.name}]: launch template network interface references Subnet ${subnetName} does not share to Account ${firewall.account}`, - ), - ); - } - - /** - * Validate subnets for firewall ASGs - * @param vpc - * @param firewall - * @param helpers - * @param errors - */ - private validateAsgLaunchTemplateSubnets( - vpc: VpcConfig, - firewall: IEc2FirewallAutoScalingGroupConfig, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - // Validate subnets are not defined in network interfaces - if (firewall.launchTemplate.networkInterfaces) { - if (this.includesSubnet(firewall.launchTemplate.networkInterfaces)) { - errors.push( - `[Firewall ASG ${firewall.name}]: launch template network interface configurations cannot include subnet attachments. Define subnets under the autoscaling property instead`, - ); - } - } - // Validate subnets are in the VPC - if (!helpers.checkSubnetsInConfig(firewall.autoscaling.subnets, vpc)) { - errors.push( - `[Firewall ASG ${firewall.name}]: autoscaling configuration references subnet that does not exist in VPC ${vpc.name}`, - ); - } - // Check for duplicate subnets - const duplicateAsgSubnets = firewall.autoscaling.subnets.some(element => { - return firewall.autoscaling!.subnets.indexOf(element) !== firewall.autoscaling!.subnets.lastIndexOf(element); - }); - if (duplicateAsgSubnets) { - errors.push( - `There are duplicate subnets in Autoscaling group ${firewall.autoscaling.name} subnets in ${ - firewall.name - }. Subnets: ${firewall.autoscaling!.subnets.join(',')}`, - ); - } - } - - private includesSubnet(interfaces: INetworkInterfaceItem[]) { - for (const interfaceItem of interfaces) { - if (!interfaceItem.subnetId) { - return false; - } - } - return true; - } - - /** - * Validate firewall IAM instance profile - * @param vpc - * @param firewall - * @param helpers - * @param errors - */ - private validateIamInstanceProfile( - vpc: VpcConfig, - firewall: IEc2FirewallInstanceConfig | IEc2FirewallAutoScalingGroupConfig, - helpers: CustomizationHelperMethods, - errors: string[], - ) { - // - // Validate IAM instance profile exists if configFile, configDir or licenseFile are defined - if ( - (firewall.configFile || firewall.licenseFile || firewall.configDir) && - !firewall.launchTemplate.iamInstanceProfile - ) { - errors.push( - `[Firewall ${firewall.name}]: IAM instance profile must be defined in the launch template when either configFile, licenseFile or configDir properties are defined`, - ); - } - // - // Validate IAM instance profile is deployed to the account - if (firewall.launchTemplate.iamInstanceProfile) { - const accountIamRoles = helpers.getIamRolesDeployedToAccount(vpc.account); - if (!accountIamRoles.includes(firewall.launchTemplate.iamInstanceProfile)) { - errors.push( - `[Firewall ${firewall.name}]: IAM instance profile is not deployed to the firewall VPC target account. Target VPC account: ${vpc.account}`, - ); - } - } - } - - private validateAsgTargetGroups( - values: CustomizationsConfig, - group: IEc2FirewallAutoScalingGroupConfig, - errors: string[], - ) { - if (group.autoscaling.targetGroups) { - // Validate target groups are defined - if (!values.firewalls?.targetGroups) { - errors.push( - `[Firewall ASG ${group.name}]: targetGroups property references a target group that does not exist`, - ); - } - // Validate length of array - if (group.autoscaling.targetGroups.length > 1) { - errors.push( - `[Firewall ASG ${group.name}]: targetGroups property may only contain a single target group reference`, - ); - } - // Validate target group exists - const targetGroup = values.firewalls?.targetGroups?.find( - item => item.name === group.autoscaling.targetGroups![0], - ); - if (group.autoscaling.targetGroups.length === 1 && !targetGroup) { - errors.push( - `[Firewall ASG ${group.name}]: targetGroups property references a target group that does not exist`, - ); - } - // Validate target group does not have instance targets - if (targetGroup && targetGroup.targets) { - errors.push( - `[Firewall ASG ${group.name}]: targetGroups property references a target group with instance targets`, - ); - } - // Validate target group type - if (targetGroup && targetGroup.type !== 'instance') { - errors.push(`[Firewall ASG ${group.name}]: targetGroups property must reference an instance type target group`); - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/global-config-validator.ts b/source/packages/@aws-accelerator/config/validator/global-config-validator.ts deleted file mode 100644 index bb79e4e..0000000 --- a/source/packages/@aws-accelerator/config/validator/global-config-validator.ts +++ /dev/null @@ -1,1282 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import * as emailValidator from 'email-validator'; -import fs from 'fs'; -import path from 'path'; -import winston from 'winston'; -import { AccountsConfig } from '../lib/accounts-config'; -import { GlobalConfig } from '../lib/global-config'; -import { IamConfig } from '../lib/iam-config'; -import { SecurityConfig } from '../lib/security-config'; -import { OrganizationConfig } from '../lib/organization-config'; -import { CommonValidatorFunctions } from './common/common-validator-functions'; -import { DeploymentTargets } from '../lib/common'; - -export class GlobalConfigValidator { - constructor( - values: GlobalConfig, - accountsConfig: AccountsConfig, - iamConfig: IamConfig, - organizationConfig: OrganizationConfig, - securityConfig: SecurityConfig, - configDir: string, - ) { - const ouIdNames: string[] = ['Root']; - - const errors: string[] = []; - - const logger = createLogger(['global-config-validator']); - - logger.info(`${GlobalConfig.FILENAME} file validation started`); - - // - // Get list of OU ID names from organization config file - // - ouIdNames.push(...this.getOuIdNames(organizationConfig)); - // - // Get array of account names from accounts config file - // - const accountNames = this.getAccountNames(accountsConfig); - // - // Validate logging account name - // - this.validateLoggingAccountName(values, accountNames, errors); - // - // Validate CentralLogs bucket region name - // - this.validateCentralLogsBucketRegionName(values, errors); - // - // Validate budget deployment target OU - // - this.validateBudgetDeploymentTargetOUs(values, ouIdNames, errors); - // - // budget subscribers address validation - // - this.validateBudgetSubscriberAddress(values, errors); - // - // budget notification email validation - // - this.validateBudgetNotificationEmailIds(values, errors); - - // - // Validate CentralLogs bucket policies. - // - this.validateImportedCentralLogsBucketPolicies(configDir, values, errors); - // - // Validate CentralLogs bucket encryption policies. - // - this.validateImportedCentralLogsBucketKmsPolicies(configDir, values, errors); - // - // lifecycle rule expiration validation - // - this.validateLifecycleRuleExpirationForCentralLogBucket(values, errors); - this.validateLifecycleRuleExpirationForAccessLogBucket(values, errors); - this.validateLifecycleRuleExpirationForReports(values, errors); - // - // Validate Imported ELB logs bucket resource policies - // - this.validateImportedElbLogsBucketPolicies(configDir, values, errors); - // - // Validate Imported Access logs bucket resource policies - // - this.validateImportedAccessLogsBucketPolicies(configDir, values, errors); - // - // Validate configuration of Imported Assets bucket - // - this.validateImportedAssetBucketConfig(configDir, accountsConfig, values, errors); - // - // validate cloudwatch logging - // - this.validateCloudWatch(values, configDir, ouIdNames, accountNames, errors); - // - // cloudtrail settings validation - // - this.validateCloudTrailSettings(values, errors); - // - // snsTopics settings validation - // - this.validateSnsTopics(values, logger, errors); - // - // central log bucket resource policy attachment validation - // - this.validateCentralLogsS3ResourcePolicyFileExists(configDir, values, errors); - this.validateCentralLogsKmsResourcePolicyFileExists(configDir, values, errors); - // - // sessionManager settings validation - // - this.validateSessionManager(values, iamConfig, errors); - // - // metadata validation - // - this.validateAcceleratorMetadata(values, accountNames, errors); - // - // cdkOptions validation - // - this.validateCdkOptions(values, errors); - // - // Validate AWS ControlTower configuration - // - this.validateControlTowerConfiguration(values, organizationConfig, errors); - // - // Service Limit Quotas validation - // - this.validateServiceLimitQuotas(values, errors); - // - // AWS Backup validation - // - this.validateAwsBackup(configDir, values, errors); - - // - // Max concurrency validation - // - this.validateMaxConcurrency(values, errors); - - // - // Validate deployment targets - // - this.validateDeploymentTargetAccountNames(values, accountNames, errors); - this.validateDeploymentTargetOUs(values, ouIdNames, errors); - - // - // bucket policy validation - // - if (securityConfig.centralSecurityServices.s3PublicAccessBlock.enable) { - this.validateAccessLogsS3Policy(configDir, values, errors); - this.validateCentralLogsS3Policy(configDir, values, errors); - this.validateElbLogsS3Policy(configDir, values, errors); - } - - // - // Validate AccessLogs bucket configuration - // - this.validateAccessLogsBucketConfigDeploymentTargetOUs(values, ouIdNames, errors); - this.validateAccessLogsBucketConfigDeploymentTargetAccounts(values, accountNames, errors); - - // - // Validate S3 configuration - // - this.validateS3ConfigDeploymentTargetOUs(values, ouIdNames, errors); - this.validateS3ConfigDeploymentTargetAccounts(values, accountNames, errors); - - if (errors.length) { - throw new Error(`${GlobalConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - - /** - * Prepare list of OU ids from organization config file - * @param organizationConfig - * @returns - */ - private getOuIdNames(organizationConfig: OrganizationConfig): string[] { - const ouIdNames: string[] = []; - - for (const organizationalUnit of organizationConfig.organizationalUnits) { - ouIdNames.push(organizationalUnit.name); - } - return ouIdNames; - } - - /** - * Prepare list of Account names from account config file - * @param accountsConfig - * @returns - */ - private getAccountNames(accountsConfig: AccountsConfig): string[] { - const accountNames: string[] = []; - - for (const accountItem of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accountNames.push(accountItem.name); - } - return accountNames; - } - - /** - * Function to validate existence of budget deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateBudgetDeploymentTargetOUs(values: GlobalConfig, ouIdNames: string[], errors: string[]) { - for (const budget of values.reports?.budgets ?? []) { - for (const ou of budget.deploymentTargets?.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for budget ${budget.name} does not exist in organization-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of logging target account name - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateLoggingAccountName(values: GlobalConfig, accountNames: string[], errors: string[]) { - if (accountNames.indexOf(values.logging.account) === -1) { - errors.push( - `Deployment target account ${values.logging.account} for logging does not exist in accounts-config.yaml file.`, - ); - } - } - - /** - * Function to validate existence of central logs bucket region in enabled region list - * CentralLogs bucket region name must part of pipeline enabled region - * @param values - * @param errors - */ - private validateCentralLogsBucketRegionName(values: GlobalConfig, errors: string[]) { - if (values.logging.centralizedLoggingRegion) { - for (const region of values.enabledRegions) { - if (region === values.logging.centralizedLoggingRegion) { - return; - } - } - errors.push( - `CentralLogs bucket region name ${ - values.logging.centralizedLoggingRegion - } not part of pipeline enabled regions [${values.enabledRegions.toString()}].`, - ); - } - } - - /** - * Function to validate budget subscriber address - * @param values - */ - private validateBudgetSubscriberAddress(values: GlobalConfig, errors: string[]) { - for (const budget of values.reports?.budgets ?? []) { - for (const notification of budget.notifications ?? []) { - if (notification.address && notification.recipients) { - errors.push(`Cannot specify an address and a list of recipients for budget ${budget.name}.`); - } else if (!notification.address && !notification.recipients) { - errors.push(`Provide either an address or a list of recipients for budget ${budget.name}.`); - } - } - } - } - - /** - * Function to validate budget notification email address - * @param values - */ - private validateBudgetNotificationEmailIds(values: GlobalConfig, errors: string[]) { - for (const budget of values.reports?.budgets ?? []) { - for (const notification of budget.notifications ?? []) { - if (notification.subscriptionType === 'EMAIL') { - if (Array.isArray(notification.recipients)) { - for (const recipient of notification.recipients) { - if (!emailValidator.validate(recipient)) { - errors.push(`Invalid report notification email ${recipient}.`); - } - } - } else if (!emailValidator.validate(notification.address!)) { - errors.push(`Invalid report notification email ${notification.address!}.`); - } - } else if (notification.subscriptionType === 'SNS') { - const snsGetArnRegex = new RegExp('^arn:.*:sns:.*:(.*):(.*)$'); - if (Array.isArray(notification.recipients)) { - for (const recipient of notification.recipients) { - if (!snsGetArnRegex.test(recipient)) { - errors.push(`The following SNS Topic Arn is malformatted: ${recipient}.`); - } - if (notification.recipients.length > 1) { - errors.push( - `SNS subscription type can have only one SNS topic as a recipient: ${notification.recipients}.`, - ); - } - } - } else if (!snsGetArnRegex.test(notification.address!)) { - errors.push(`The following SNS Topic Arn is malformatted: ${notification.address}.`); - } - } - } - } - } - - /** - * Function to validate S3 lifecycle rules for Cost Reporting - * @param values - */ - private validateLifecycleRuleExpirationForReports(values: GlobalConfig, errors: string[]) { - const ruleNames: string[] = []; - for (const lifecycleRule of values.reports?.costAndUsageReport?.lifecycleRules ?? []) { - if (ruleNames.includes(lifecycleRule.id)) { - errors.push('LifeCycle rule ids must be unique.'); - } - ruleNames.push(lifecycleRule.id); - if (lifecycleRule.expiration && !lifecycleRule.noncurrentVersionExpiration) { - errors.push('You must supply a value for noncurrentVersionExpiration. Cost Reporting'); - } - if (!lifecycleRule.abortIncompleteMultipartUpload) { - errors.push('You must supply a value for abortIncompleteMultipartUpload. Cost Reporting'); - } - if (lifecycleRule.expiration && lifecycleRule.expiredObjectDeleteMarker) { - errors.push('You may not configure expiredObjectDeleteMarker with expiration. Cost Reporting'); - } - } - } - - /** - * Function to validate imported AccessLogs bucket policies - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedAccessLogsBucketPolicies(configDir: string, values: GlobalConfig, errors: string[]) { - if (!values.logging.accessLogBucket) { - return; - } - - const accessLogBucketItem = values.logging.accessLogBucket; - const importedBucketItem = accessLogBucketItem.importedBucket; - - if (importedBucketItem && accessLogBucketItem.customPolicyOverrides?.policy) { - if (!fs.existsSync(path.join(configDir, accessLogBucketItem.customPolicyOverrides?.policy))) { - errors.push( - `AccessLogs bucket custom policy overrides file ${accessLogBucketItem.customPolicyOverrides?.policy} not found !!!`, - ); - } - - if (importedBucketItem.applyAcceleratorManagedBucketPolicy) { - errors.push( - `Imported AccessLogs bucket with customPolicyOverrides.policy can not have applyAcceleratorManagedPolicy set to true.`, - ); - } - if (accessLogBucketItem.s3ResourcePolicyAttachments) { - errors.push( - `Imported AccessLogs bucket with customPolicyOverrides.policy can not have s3ResourcePolicyAttachments.`, - ); - } - } - - for (const s3ResourcePolicyAttachment of accessLogBucketItem.s3ResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, s3ResourcePolicyAttachment.policy))) { - errors.push( - `AccessLogs bucket resource policy attachment file ${s3ResourcePolicyAttachment.policy} not found !!!`, - ); - } - } - } - - /** - * Function to validate imported Assets bucket config - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedAssetBucketConfig( - configDir: string, - accountsConfig: AccountsConfig, - values: GlobalConfig, - errors: string[], - ) { - this.validateImportedAssetBucketPolicies(configDir, values, errors); - this.validateImportedAssetBucketKmsPolicies(configDir, values, errors); - this.validateCmkExistsInManagementAccount(accountsConfig, values, errors); - } - /** - * Function to validate imported Assets S3 bucket policies - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedAssetBucketPolicies(configDir: string, values: GlobalConfig, errors: string[]) { - if (!values.logging.assetBucket) { - return; - } - const assetBucketItem = values.logging.assetBucket!; - const importedBucketItem = assetBucketItem.importedBucket; - - if (importedBucketItem && assetBucketItem.customPolicyOverrides?.s3Policy) { - if (!fs.existsSync(path.join(configDir, assetBucketItem.customPolicyOverrides?.s3Policy))) { - errors.push( - `Assets bucket custom policy overrides file ${assetBucketItem.customPolicyOverrides?.s3Policy} not found !!!`, - ); - } - - if (importedBucketItem.applyAcceleratorManagedBucketPolicy) { - errors.push( - `Imported Assets bucket with customPolicyOverrides.policy can not have applyAcceleratorManagedPolicy set to true.`, - ); - } - if (assetBucketItem.s3ResourcePolicyAttachments) { - errors.push( - `Imported AccessLogs bucket with customPolicyOverrides.policy can not have s3ResourcePolicyAttachments.`, - ); - } - } - - for (const s3ResourcePolicyAttachment of assetBucketItem.s3ResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, s3ResourcePolicyAttachment.policy))) { - errors.push( - `AccessLogs bucket resource policy attachment file ${s3ResourcePolicyAttachment.policy} not found !!!`, - ); - } - } - } - - /** - * Function to validate imported CentralLogs bucket kms policies - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedAssetBucketKmsPolicies(configDir: string, values: GlobalConfig, errors: string[]) { - if (!values.logging.assetBucket) { - return; - } - - const assetBucketItem = values.logging.assetBucket; - const importedBucketItem = assetBucketItem.importedBucket; - - if (!importedBucketItem) { - return; - } - - const createAcceleratorManagedKey = importedBucketItem.createAcceleratorManagedKey ?? false; - - if (assetBucketItem.customPolicyOverrides?.kmsPolicy) { - if (!fs.existsSync(path.join(configDir, assetBucketItem.customPolicyOverrides.kmsPolicy))) { - errors.push( - `Assets bucket encryption custom policy overrides file ${assetBucketItem.customPolicyOverrides.kmsPolicy} not found !!!`, - ); - } - - if (assetBucketItem.kmsResourcePolicyAttachments) { - errors.push( - `Imported Assets bucket with customPolicyOverrides.kmsPolicy can not have policy attachments through centralLogBucketItem.kmsResourcePolicyAttachments.`, - ); - } - } - - if (!createAcceleratorManagedKey && assetBucketItem.kmsResourcePolicyAttachments) { - errors.push( - `Imported Assets bucket with createAcceleratorManagedKey set to false can not have policy attachments through centralLogBucketItem.kmsResourcePolicyAttachments. Accelerator will not be able to attach policies for the bucket key not created by solution.`, - ); - } - - for (const kmsResourcePolicyAttachment of assetBucketItem.kmsResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, kmsResourcePolicyAttachment.policy))) { - errors.push( - `Assets bucket encryption policy attachment file ${kmsResourcePolicyAttachment.policy} not found !!!`, - ); - } - } - } - - /** - * Function to validate if S3 CMK exists in Management Account when using an imported Asset S3 Bucket - * @param accountsConfig {@link AccountsConfig} - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateCmkExistsInManagementAccount(accountsConfig: AccountsConfig, values: GlobalConfig, errors: string[]) { - if (values.s3?.encryption?.createCMK) { - if (values.logging.assetBucket?.importedBucket?.createAcceleratorManagedKey) { - const cmkDeploymentTargetSets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - values.s3?.encryption?.deploymentTargets as DeploymentTargets, - ); - const managementAccountInTargets = cmkDeploymentTargetSets.find(item => item === 'Management'); - const homeRegionInTargets = !values.s3?.encryption?.deploymentTargets?.excludedRegions.find( - item => item === values.homeRegion, - ); - if (!managementAccountInTargets || !homeRegionInTargets) { - errors.push( - `Imported Assets bucket with createAcceleratorManagedKey being set to true has to have the CDK deployed to the Management account in the home region.`, - ); - } - } - } - } - - /** - * Function to validate imported ElbLogs bucket policies - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedElbLogsBucketPolicies(configDir: string, values: GlobalConfig, errors: string[]) { - if (!values.logging.elbLogBucket) { - return; - } - - const elbLogBucketItem = values.logging.elbLogBucket; - const importedBucketItem = elbLogBucketItem.importedBucket; - - if (importedBucketItem && elbLogBucketItem.customPolicyOverrides?.policy) { - if (!fs.existsSync(path.join(configDir, elbLogBucketItem.customPolicyOverrides?.policy))) { - errors.push( - `ElbLogs bucket custom policy overrides file ${elbLogBucketItem.customPolicyOverrides?.policy} not found !!!`, - ); - } - - if (importedBucketItem.applyAcceleratorManagedBucketPolicy) { - errors.push( - `Imported ElbLogs bucket with customPolicyOverrides.policy can not have applyAcceleratorManagedPolicy set to true.`, - ); - } - if (elbLogBucketItem.s3ResourcePolicyAttachments) { - errors.push( - `Imported ElbLogs bucket with customPolicyOverrides.policy can not have s3ResourcePolicyAttachments.`, - ); - } - } - - for (const s3ResourcePolicyAttachment of elbLogBucketItem.s3ResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, s3ResourcePolicyAttachment.policy))) { - errors.push( - `ElbLogs bucket resource policy attachment file ${s3ResourcePolicyAttachment.policy} not found !!!`, - ); - } - } - } - - /** - * Function to validate imported CentralLogs bucket policies - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedCentralLogsBucketPolicies(configDir: string, values: GlobalConfig, errors: string[]) { - if (!values.logging.centralLogBucket) { - return; - } - - const centralLogBucketItem = values.logging.centralLogBucket; - const importedBucketItem = centralLogBucketItem.importedBucket; - - if (importedBucketItem && centralLogBucketItem.customPolicyOverrides?.s3Policy) { - if (!fs.existsSync(path.join(configDir, centralLogBucketItem.customPolicyOverrides.s3Policy))) { - errors.push( - `CentralLogs bucket custom policy overrides file ${centralLogBucketItem.customPolicyOverrides.s3Policy} not found !!!`, - ); - } - - if (importedBucketItem.applyAcceleratorManagedBucketPolicy) { - errors.push( - `Imported CentralLogs bucket with customPolicyOverrides.s3Policy can not have applyAcceleratorManagedPolicy set to true.`, - ); - } - if (centralLogBucketItem.s3ResourcePolicyAttachments) { - errors.push( - `Imported CentralLogs bucket with customPolicyOverrides.s3Policy can not have s3ResourcePolicyAttachments.`, - ); - } - } - - for (const s3ResourcePolicyAttachment of centralLogBucketItem.s3ResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, s3ResourcePolicyAttachment.policy))) { - errors.push( - `CentralLogs bucket resource policy attachment file ${s3ResourcePolicyAttachment.policy} not found !!!`, - ); - } - } - } - - /** - * Function to validate imported CentralLogs bucket kms policies - * @param configDir string - * @param values {@link GlobalConfig} - * @param errors string[] - * @returns - */ - private validateImportedCentralLogsBucketKmsPolicies(configDir: string, values: GlobalConfig, errors: string[]) { - if (!values.logging.centralLogBucket) { - return; - } - - const centralLogBucketItem = values.logging.centralLogBucket; - const importedBucketItem = centralLogBucketItem.importedBucket; - - if (!importedBucketItem) { - return; - } - - const createAcceleratorManagedKey = importedBucketItem.createAcceleratorManagedKey ?? false; - - if (centralLogBucketItem.customPolicyOverrides?.kmsPolicy) { - if (!fs.existsSync(path.join(configDir, centralLogBucketItem.customPolicyOverrides.kmsPolicy))) { - errors.push( - `CentralLogs bucket encryption custom policy overrides file ${centralLogBucketItem.customPolicyOverrides.kmsPolicy} not found !!!`, - ); - } - - if (centralLogBucketItem.kmsResourcePolicyAttachments) { - errors.push( - `Imported CentralLogs bucket with customPolicyOverrides.kmsPolicy can not have policy attachments through centralLogBucketItem.kmsResourcePolicyAttachments.`, - ); - } - } - - if (!createAcceleratorManagedKey && centralLogBucketItem.kmsResourcePolicyAttachments) { - errors.push( - `Imported CentralLogs bucket with createAcceleratorManagedKey set to false can not have policy attachments through centralLogBucketItem.kmsResourcePolicyAttachments. Accelerator will not be able to attach policies for the bucket key not created by solution.`, - ); - } - - for (const kmsResourcePolicyAttachment of centralLogBucketItem.kmsResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, kmsResourcePolicyAttachment.policy))) { - errors.push( - `CentralLogs bucket encryption policy attachment file ${kmsResourcePolicyAttachment.policy} not found !!!`, - ); - } - } - } - - /** - * Function to validate S3 lifecycle rules Central Log Bucket - * @param values - */ - private validateLifecycleRuleExpirationForCentralLogBucket(values: GlobalConfig, errors: string[]) { - const ruleNames: string[] = []; - for (const lifecycleRule of values.logging.centralLogBucket?.lifecycleRules ?? []) { - if (ruleNames.includes(lifecycleRule.id)) { - errors.push('LifeCycle rule ids must be unique.'); - } - ruleNames.push(lifecycleRule.id); - - if (lifecycleRule.expiration && !lifecycleRule.noncurrentVersionExpiration) { - errors.push('You must supply a value for noncurrentVersionExpiration. Central Log Bucket'); - } - if (!lifecycleRule.abortIncompleteMultipartUpload) { - errors.push('You must supply a value for abortIncompleteMultipartUpload. Central Log Bucket'); - } - if (lifecycleRule.expiration && lifecycleRule.expiredObjectDeleteMarker) { - errors.push('You may not configure expiredObjectDeleteMarker with expiration. Central Log Bucket'); - } - } - } - - private validateLifecycleRuleExpirationForAccessLogBucket(values: GlobalConfig, errors: string[]) { - const ruleNames: string[] = []; - for (const lifecycleRule of values.logging.accessLogBucket?.lifecycleRules ?? []) { - if (ruleNames.includes(lifecycleRule.id)) { - errors.push('LifeCycle rule ids must be unique.'); - } - ruleNames.push(lifecycleRule.id); - - if (lifecycleRule.expiration && !lifecycleRule.noncurrentVersionExpiration) { - errors.push('You must supply a value for noncurrentVersionExpiration. S3 Access Log Bucket'); - } - if (!lifecycleRule.abortIncompleteMultipartUpload) { - errors.push('You must supply a value for abortIncompleteMultipartUpload. S3 Access Log Bucket'); - } - if (lifecycleRule.expiration && lifecycleRule.expiredObjectDeleteMarker) { - errors.push('You may not configure expiredObjectDeleteMarker with expiration. S3 Access Log Bucket'); - } - } - } - - /** - * Validate CloudWatch Logs replication - */ - private validateCloudWatch( - values: GlobalConfig, - configDir: string, - ouIdNames: string[], - accountNames: string[], - errors: string[], - ) { - if (values.logging.cloudwatchLogs?.enable ?? true) { - if (values.logging.cloudwatchLogs?.dynamicPartitioning) { - // - // validate cloudwatch logging dynamic partition - // - this.validateCloudWatchDynamicPartition(values, configDir, errors); - } - if (values.logging.cloudwatchLogs?.exclusions) { - // - // validate cloudwatch logs exclusion config - // - this.validateCloudWatchExclusions(values, ouIdNames, accountNames, errors); - } - } - } - - /** - * Validate Cloudwatch logs exclusion inputs - */ - private validateCloudWatchExclusions( - values: GlobalConfig, - ouIdNames: string[], - accountNames: string[], - errors: string[], - ) { - for (const exclusion of values.logging.cloudwatchLogs?.exclusions ?? []) { - // check if users input array of Organization Units is valid - this.validateCloudWatchExclusionsTargets(exclusion.organizationalUnits ?? [], ouIdNames, errors); - // check if users input array of accounts is valid - this.validateCloudWatchExclusionsTargets(exclusion.accounts ?? [], accountNames, errors); - // check if OU is root and excludeAll is provided - const foundRoot = exclusion.organizationalUnits?.find(ou => { - return ou === 'Root'; - }); - if (foundRoot && exclusion.excludeAll === true) { - errors.push(`CloudWatch exclusion found root OU with excludeAll instead set enable: false cloudwatchLogs.`); - } - - // if logGroupNames are provided then ensure OUs or accounts are provided - const ouLength = exclusion.organizationalUnits?.length ?? 0; - const accountLength = exclusion.accounts?.length ?? 0; - if (exclusion.logGroupNames && ouLength === 0 && accountLength === 0) { - errors.push( - `CloudWatch exclusion logGroupNames (${exclusion.logGroupNames.join( - ',', - )}) are provided but no account or OU specified.`, - ); - } - - // if excludeAll is provided then ensure OU or accounts are provided - if (exclusion.excludeAll === true && ouLength === 0 && accountLength === 0) { - errors.push(`CloudWatch exclusion excludeAll was specified but no account or OU specified.`); - } - - // either specify logGroupNames or excludeAll - if (exclusion.excludeAll === undefined && exclusion.logGroupNames === undefined) { - errors.push(`CloudWatch exclusion either specify excludeAll or logGroupNames.`); - } - } - } - - private validateCloudWatchExclusionsTargets(inputList: string[], globalList: string[], errors: string[]) { - for (const input of inputList) { - // from the input list pick each element, - // if OU or account name is in global config pass - // else bubble up the error - if (!globalList.includes(input)) { - errors.push(`CloudWatch exclusions invalid value ${input} provided. Current values: ${globalList.join(',')}.`); - } - } - } - - /** - * Function to validate CloudWatch Logs Dynamic Partition and enforce format, key-value provided - * @param values - */ - private validateCloudWatchDynamicPartition(values: GlobalConfig, configDir: string, errors: string[]) { - const exampleString = JSON.stringify([ - { - logGroupPattern: '/AWSAccelerator-SecurityHub', - s3Prefix: 'security-hub', - }, - ]); - - const errorMessage = `Please make dynamic partition in json array with key as logGroupPattern and s3Prefix. Here is an example: ${exampleString}`; - - if (values.logging.cloudwatchLogs?.dynamicPartitioning) { - //read the file in - const dynamicPartitionValue = fs.readFileSync( - path.join(configDir, values.logging.cloudwatchLogs?.dynamicPartitioning), - 'utf-8', - ); - if (JSON.parse(dynamicPartitionValue)) { - this.checkForArray(JSON.parse(dynamicPartitionValue), errorMessage, errors); - } else { - errors.push(`Not valid Json for Dynamic Partition in CloudWatch logs. ${errorMessage}`); - } - } - } - - private validateSnsTopics(values: GlobalConfig, logger: winston.Logger, errors: string[]) { - if (values.snsTopics) { - for (const snsTopic of values.snsTopics.topics ?? []) { - logger.info(`email count: ${snsTopic.emailAddresses.length}`); - if (snsTopic.emailAddresses.length < 1) { - errors.push(`Must be at least one email address for the snsTopic named ${snsTopic.name}`); - } - } - } - } - - private validateSessionManager(values: GlobalConfig, iamConfig: IamConfig, errors: string[]) { - const iamRoleNames: string[] = []; - for (const roleSet of iamConfig.roleSets) { - for (const role of roleSet.roles) { - iamRoleNames.push(role.name); - } - } - for (const iamRoleName of values.logging.sessionManager.attachPolicyToIamRoles ?? []) { - if (!iamRoleNames.find(item => item === iamRoleName)) { - errors.push( - `Could not find the role named ${iamRoleName} in the IAM config file. Role was used in the Session Manager configuration.`, - ); - } - } - } - - /** - * Validate s3 resource policy file existence - * @param configDir - * @param values - * @returns - */ - private validateCentralLogsS3ResourcePolicyFileExists(configDir: string, values: GlobalConfig, errors: string[]) { - for (const policy of values.logging.centralLogBucket?.s3ResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, policy.policy))) { - errors.push(`Policy definition file ${policy.policy} not found !!!`); - } - } - } - - /** - * Validate s3 resource policy file existence - * @param configDir - * @param values - * @returns - */ - private validateCentralLogsKmsResourcePolicyFileExists(configDir: string, values: GlobalConfig, errors: string[]) { - for (const policy of values.logging.centralLogBucket?.kmsResourcePolicyAttachments ?? []) { - if (!fs.existsSync(path.join(configDir, policy.policy))) { - errors.push(`Policy definition file ${policy.policy} not found !!!`); - } - } - } - - /** - * Validate Access Log S3 bucket policy for AWS Principal if block public access is enabled. - * @param configDir - * @param values - * @returns - */ - private validateAccessLogsS3Policy(configDir: string, values: GlobalConfig, errors: string[]) { - for (const s3ResourcePolicyAttachment of values.logging.accessLogBucket?.s3ResourcePolicyAttachments ?? []) { - const principalValue = fs.readFileSync(path.join(configDir, s3ResourcePolicyAttachment.policy), 'utf-8'); - const tempValue = JSON.parse(principalValue); - for (const item of tempValue.Statement ?? []) { - if ( - item.Effect === 'Allow' && - (item.Principal.AWS === '*' || item.Principal === '*') && - !item.Condition.StringEquals?.['aws:PrincipalOrgID'] - ) { - errors.push( - `Adding policy will make the Access Log S3 Bucket public and conflicts with the Block Public Access setting.`, - ); - } - } - } - } - - /** - * Function to validate existence of S3 configuration deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateS3ConfigDeploymentTargetAccounts(values: GlobalConfig, accountNames: string[], errors: string[]) { - if (!values.s3?.encryption?.deploymentTargets) { - return; - } - for (const account of values.s3.encryption.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for S3 encryption configuration does not exists in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of S3 bucket config deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateS3ConfigDeploymentTargetOUs(values: GlobalConfig, ouIdNames: string[], errors: string[]) { - if (!values.s3?.encryption?.deploymentTargets) { - return; - } - for (const ou of values.s3.encryption.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for S3 encryption configuration does not exist in organization-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of AccessLogs bucket configuration deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateAccessLogsBucketConfigDeploymentTargetAccounts( - values: GlobalConfig, - accountNames: string[], - errors: string[], - ) { - if (!values.logging.accessLogBucket?.deploymentTargets) { - return; - } - for (const account of values.logging.accessLogBucket.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for AccessLogs bucket encryption configuration does not exists in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of AccessLogs bucket config deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateAccessLogsBucketConfigDeploymentTargetOUs( - values: GlobalConfig, - ouIdNames: string[], - errors: string[], - ) { - if (!values.logging.accessLogBucket?.deploymentTargets) { - return; - } - for (const ou of values.logging.accessLogBucket.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for AccessLogs bucket configuration does not exist in organization-config.yaml file.`, - ); - } - } - } - - /** - * Validate Central S3 bucket policy for AWS Principal if block public access is enabled. - * @param configDir - * @param values - * @returns - */ - private validateCentralLogsS3Policy(configDir: string, values: GlobalConfig, errors: string[]) { - for (const s3ResourcePolicyAttachment of values.logging.centralLogBucket?.s3ResourcePolicyAttachments ?? []) { - const principalValue = fs.readFileSync(path.join(configDir, s3ResourcePolicyAttachment.policy), 'utf-8'); - const tempValue = JSON.parse(principalValue); - for (const item of tempValue.Statement ?? []) { - if ( - item.Effect === 'Allow' && - (item.Principal.AWS === '*' || item.Principal === '*') && - !item.Condition.StringEquals?.['aws:PrincipalOrgID'] - ) { - errors.push( - `Adding policy will make the Central S3 Bucket public and conflicts with the Block Public Access setting.`, - ); - } - } - } - } - - /** - * Validate ELB Log S3 bucket policy for AWS Principal if block public access is enabled. - * @param configDir - * @param values - * @returns - */ - private validateElbLogsS3Policy(configDir: string, values: GlobalConfig, errors: string[]) { - for (const s3ResourcePolicyAttachment of values.logging.elbLogBucket?.s3ResourcePolicyAttachments ?? []) { - const principalValue = fs.readFileSync(path.join(configDir, s3ResourcePolicyAttachment.policy), 'utf-8'); - const tempValue = JSON.parse(principalValue); - for (const item of tempValue.Statement ?? []) { - if ( - item.Effect === 'Allow' && - (item.Principal.AWS === '*' || item.Principal === '*') && - !item.Condition.StringEquals?.['aws:PrincipalOrgID'] - ) { - errors.push( - `Adding policy will make the ELB Log S3 Bucket public and conflicts with the Block Public Access setting.`, - ); - } - } - } - } - - // Check if input is valid array and proceed to check schema - private checkForArray(inputStr: string, errorMessage: string, errors: string[]) { - if (Array.isArray(inputStr)) { - this.checkSchema(inputStr, errorMessage, errors); - } else { - errors.push(`Provided file is not a JSON array. ${errorMessage}`); - } - } - - // check schema of each json input. Even if one is wrong abort, report bad item and provide example. - private checkSchema(inputStr: string, errorMessage: string, errors: string[]) { - for (const eachItem of inputStr) { - if (!this.isDynamicLogType(eachItem)) { - errors.push(`Key value ${JSON.stringify(eachItem)} is incorrect. ${errorMessage}`); - } - } - } - - // Validate this value with a custom type guard - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private isDynamicLogType(o: any): o is { logGroupPattern: string; s3Prefix: string } { - return 'logGroupPattern' in o && 's3Prefix' in o; - } - - /* Function to validate CloudTrail configuration - * If multiRegion is enabled then globalServiceEvents - * must be enabled as well - */ - private validateCloudTrailSettings(values: GlobalConfig, errors: string[]) { - if ( - values.logging.cloudtrail.organizationTrail && - values.logging.cloudtrail.organizationTrailSettings?.multiRegionTrail && - !values.logging.cloudtrail.organizationTrailSettings.globalServiceEvents - ) { - errors.push( - `The organization CloudTrail setting multiRegionTrail is enabled, the globalServiceEvents must be enabled as well`, - ); - } - for (const accountTrail of values.logging.cloudtrail.accountTrails ?? []) { - if (accountTrail.settings.multiRegionTrail && !accountTrail.settings.globalServiceEvents) { - errors.push( - `The account CloudTrail with the name ${accountTrail.name} setting multiRegionTrail is enabled, the globalServiceEvents must be enabled as well`, - ); - } - } - } - - private validateAcceleratorMetadata(values: GlobalConfig, accountNames: string[], errors: string[]) { - if (!values.acceleratorMetadata) { - return; - } - if (!accountNames.find(account => account === values.acceleratorMetadata?.account)) { - errors.push( - `The account with the name ${values.acceleratorMetadata.account} defined in acceleratorMetadata does not exist in the accounts config`, - ); - } - } - - /* Function to validate cdkOptions configuration - * We currently have two valid settings to enable centralizing CDK buckets: cdkOptions.centralizeBuckets and centralizeCdkBuckets - * We want to ensure users only specify one before centralizeCdkBuckets is no longer supported. - */ - private validateCdkOptions(values: GlobalConfig, errors: string[]) { - if (values?.cdkOptions && values?.centralizeCdkBuckets) { - errors.push( - `Cannot specify values for both cdkOptions and centralizeCdkBuckets. Please delete centralizeCdkBuckets and use cdkOptions`, - ); - } - - if (!values?.cdkOptions?.centralizeBuckets && values?.cdkOptions?.useManagementAccessRole) { - errors.push(`cdkOptions.centralizeBuckets must be set to true to enable cdkOptions.useManagementAccessRole`); - } - - if (!values?.cdkOptions?.centralizeBuckets && values?.cdkOptions?.customDeploymentRole) { - errors.push(`cdkOptions.centralizeBuckets must be set to true to enable cdkOptions.customDeploymentRole`); - } - } - - /** - * Function to validate AWS ControlTower configuration - * @param values {@link GlobalConfig} - * @param organizationConfig {@link OrganizationConfig} - * @param errors string[] - */ - private validateControlTowerConfiguration( - values: GlobalConfig, - organizationConfig: OrganizationConfig, - errors: string[], - ) { - if (values.controlTower.enable && !organizationConfig.enable) { - errors.push( - 'The AWS ControlTower cannot be enabled when the Organization enable property is set to false in organization-config.yaml file.', - ); - } - - if (!values.controlTower.enable && values.controlTower.landingZone) { - errors.push( - 'The AWS ControlTower LandingZone configuration cannot be provided when the ControlTower enable property is set to false', - ); - } - - if (values.controlTower.landingZone && !organizationConfig.enable) { - errors.push( - 'The AWS ControlTower LandingZone configuration cannot be provided when the Organization enable property is set to false in organization-config.yaml file.', - ); - } - this.validateControlTowerControls(values, errors); - } - - private validateControlTowerControls(values: GlobalConfig, errors: string[]) { - for (const control of values.controlTower.controls ?? []) { - // Check control identifier starts with AW-GR - if (!control.identifier.startsWith('AWS-GR')) { - errors.push( - `Invalid Control Tower control ${control.identifier}, only strongly recommended or elective Control Tower controls are supported`, - ); - } - - // Check deploymentTargets does not contain accounts - if (control.deploymentTargets?.accounts?.length > 0) { - errors.push( - `Control Tower controls can only be deployed to Organizational Units. Please remove all account deployment targets from ${control.identifier}`, - ); - } - } - } - - private validateServiceLimitQuotas(values: GlobalConfig, errors: string[]) { - const globalServices = ['account', 'cloudfront', 'iam', 'organizations', 'route53']; - for (const limit of values.limits ?? []) { - // Check for global services and us-east-1 or us-gov-west-1 enabled - if ( - globalServices.includes(limit.serviceCode) && - !values.enabledRegions.includes('us-east-1') && - !values.enabledRegions.includes('us-gov-west-1') - ) { - errors.push( - `Service limit increase requested for ${limit.serviceCode}, but global region not included in enabledRegions. Please add the global region for your partition to request service limit increases for global services.`, - ); - } - } - } - - private validateAwsBackup(configDir: string, values: GlobalConfig, errors: string[]) { - for (const vault of values.backup?.vaults ?? []) { - if (vault?.policy) { - if (!fs.existsSync(path.join(configDir, vault.policy))) { - errors.push(`Policy definition file for Backup Vault ${vault.name} not found !!!`); - } - } - } - } - - /** - * validateMaxConcurrency - */ - private validateMaxConcurrency(values: GlobalConfig, errors: string[]) { - const maxConcurrentStacks = values.acceleratorSettings?.maxConcurrentStacks ?? 250; - if (maxConcurrentStacks > 250) { - errors.push( - `Provided acceleratorSettings.maxConcurrentStacks: ${values.acceleratorSettings! - .maxConcurrentStacks!} it cannot be greater than 250 `, - ); - } - } - - /** - * Function to validate Deployment targets account name for security services - * @param values - */ - private validateDeploymentTargetAccountNames(values: GlobalConfig, accountNames: string[], errors: string[]) { - this.validateLambdaEncryptionConfigDeploymentTargetAccounts(values, accountNames, errors); - this.validateCloudWatchLogsEncryptionConfigDeploymentTargetAccounts(values, accountNames, errors); - } - - /** - * Function to validate Deployment targets OU name for security services - * @param values - */ - private validateDeploymentTargetOUs(values: GlobalConfig, ouIdNames: string[], errors: string[]) { - this.validateLambdaEncryptionConfigDeploymentTargetOUs(values, ouIdNames, errors); - this.validateCloudWatchLogsEncryptionDeploymentTargetOUs(values, ouIdNames, errors); - } - - /** - * Function to validate existence of Lambda encryption configuration deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateLambdaEncryptionConfigDeploymentTargetOUs( - values: GlobalConfig, - ouIdNames: string[], - errors: string[], - ) { - if (!values.lambda?.encryption) { - return; - } - - for (const ou of values.lambda.encryption.deploymentTargets?.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for lambda encryption configuration does not exists in organization-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of Lambda encryption configuration deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateLambdaEncryptionConfigDeploymentTargetAccounts( - values: GlobalConfig, - accountNames: string[], - errors: string[], - ) { - if (!values.lambda?.encryption) { - return; - } - for (const account of values.lambda.encryption?.deploymentTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for Lambda encryption configuration does not exists in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of CloudWatch log group encryption configuration deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateCloudWatchLogsEncryptionConfigDeploymentTargetAccounts( - values: GlobalConfig, - accountNames: string[], - errors: string[], - ) { - if (!values.logging.cloudwatchLogs?.encryption) { - return; - } - for (const account of values.logging.cloudwatchLogs.encryption.deploymentTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for CloudWatch logs encryption configuration does not exists in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of CloudWatch encryption deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateCloudWatchLogsEncryptionDeploymentTargetOUs( - values: GlobalConfig, - ouIdNames: string[], - errors: string[], - ) { - if (!values.logging.cloudwatchLogs?.encryption) { - return; - } - for (const ou of values.logging.cloudwatchLogs.encryption.deploymentTargets?.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for CloudWatch logs encryption does not exist in organization-config.yaml file.`, - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/iam-config-validator.ts b/source/packages/@aws-accelerator/config/validator/iam-config-validator.ts deleted file mode 100644 index 525762f..0000000 --- a/source/packages/@aws-accelerator/config/validator/iam-config-validator.ts +++ /dev/null @@ -1,1213 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as fs from 'fs'; -import * as path from 'path'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { AccountsConfig } from '../lib/accounts-config'; -import { CommonValidatorFunctions } from './common/common-validator-functions'; -import { DeploymentTargets } from '../lib/common'; -import { IamConfig, PolicySetConfig } from '../lib/iam-config'; -import { IIamConfig, IIdentityCenterConfig } from '../lib/models/iam-config'; -import { NetworkConfig } from '../lib/network-config'; -import { OrganizationConfig } from '../lib/organization-config'; -import { SecurityConfig } from '../lib/security-config'; -import { hasDuplicates } from './utils/common-validator-functions'; - -type VpcSubnetListsType = { - vpcName: string; - subnetName: string; - subnetAz: string | number; -}; - -/** - * IAM Configuration validator. - * Validates iam configuration - */ -export class IamConfigValidator { - constructor( - values: IamConfig, - accountsConfig: AccountsConfig, - networkConfig: NetworkConfig, - organizationConfig: OrganizationConfig, - securityConfig: SecurityConfig, - configDir: string, - ) { - const ouIdNames: string[] = ['Root']; - const keyNames: string[] = []; - - const errors: string[] = []; - const logger = createLogger(['iam-config-validator']); - - logger.info(`${IamConfig.FILENAME} file validation started`); - - // - // Get list of OU ID names from organization config file - ouIdNames.push(...this.getOuIdNames(organizationConfig)); - - // - // Get list of Account names from account config file - const accountNames = this.getAccountNames(accountsConfig); - - // - // Get list of Kms key names from security config file - this.getKmsKeyNames(keyNames, securityConfig); - - // - // Get Vpc and subnet lists - // - const vpcSubnetLists = this.getVpcSubnetLists(networkConfig); - - // - // Start Validation - - // - // Validate IAM policies - // - this.validatePolicies(configDir, values, errors); - // - // Validate IAM roles - // - this.validateRoles(values, errors); - - // Validate target OU names - this.validateDeploymentTargetOUs(values, ouIdNames, errors); - - // Validate target account names - this.validateDeploymentTargetAccountNames(values, accountNames, errors); - - // Validate Identity Center Object - this.validateIdentityCenter(values, accountsConfig, errors); - - // - // Validate IAM principal assignments for roles - // - this.validateIamPolicyTargets(values, accountsConfig, errors); - - // Validate Managed active directory - new ManagedActiveDirectoryValidator(values, vpcSubnetLists, ouIdNames, accountNames, errors); - - if (errors.length) { - throw new Error(`${IamConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - - /** - * Prepare list of OU ids from organization config file - * @param organizationConfig - * @returns - */ - private getOuIdNames(organizationConfig: OrganizationConfig): string[] { - const ouIdNames: string[] = []; - for (const organizationalUnit of organizationConfig.organizationalUnits) { - ouIdNames.push(organizationalUnit.name); - } - return ouIdNames; - } - - /** - * Prepare list of Account names from account config file - * @param accountsConfig - * @returns - */ - private getAccountNames(accountsConfig: AccountsConfig): string[] { - const accountNames: string[] = []; - for (const accountItem of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accountNames.push(accountItem.name); - } - return accountNames; - } - - /** - * Prepare list of kms key names from security config file - * @param configDir - */ - private getKmsKeyNames(keyNames: string[], securityConfig: SecurityConfig) { - const keySets = securityConfig.keyManagementService?.keySets; - if (!keySets) { - return; - } - for (const keySet of keySets) { - keyNames.push(keySet.name); - } - } - - /** - * Function to create vpc and subnet lists - * @param networkConfig - * @returns - */ - private getVpcSubnetLists(networkConfig: NetworkConfig): VpcSubnetListsType[] { - const vpcSubnetLists: VpcSubnetListsType[] = []; - const vpcs = [...networkConfig.vpcs, ...(networkConfig.vpcTemplates ?? [])]; - for (const vpc of vpcs) { - for (const subnet of vpc.subnets ?? []) { - vpcSubnetLists.push({ - vpcName: vpc.name, - subnetName: subnet.name, - subnetAz: subnet.availabilityZone ? subnet.availabilityZone : '', - }); - } - } - return vpcSubnetLists; - } - - /** - * Validate IAM policies - * @param configDir - * @param values - * @param errors - */ - private validatePolicies(configDir: string, values: IamConfig, errors: string[]) { - // - // Validate policy file existence - // - this.validatePolicyFileExists(configDir, values, errors); - // - // Validate policy names - // - this.validatePolicyNames(values, errors); - } - - /** - * Validate policy file existence - * @param configDir - * @param values - * @returns - */ - private validatePolicyFileExists(configDir: string, values: IIamConfig, errors: string[]) { - const policies: { name: string; policyFile: string }[] = []; - for (const policySet of values.policySets ?? []) { - for (const policy of policySet.policies) { - policies.push({ name: policy.name, policyFile: policy.policy }); - } - } - - for (const policy of policies) { - if (!fs.existsSync(path.join(configDir, policy.policyFile))) { - errors.push(`Policy definition file ${policy.policyFile} not found, for ${policy.name} !!!`); - } - } - } - - /** - * Checks policy names for duplicate values - * @param values - * @param errors - */ - private validatePolicyNames(values: IamConfig, errors: string[]) { - const policyNames: string[] = []; - - values.policySets?.forEach(policySet => { - policySet.policies?.forEach(policy => { - policyNames.push(policy.name); - }); - }); - - // Check names for duplicates - if (hasDuplicates(policyNames)) { - errors.push(`Duplicate policy names defined. Policy names must be unique. Policy names defined: ${policyNames}`); - } - } - - /** - * Validate IAM roles - * @param values - * @param errors - */ - private validateRoles(values: IamConfig, errors: string[]) { - // - // Validate role names - // - this.validateRoleNames(values, errors); - } - - /** - * Checks role names for duplicate values - * @param values - * @param errors - */ - private validateRoleNames(values: IamConfig, errors: string[]) { - const roleNames: string[] = []; - - values.roleSets?.forEach(roleSet => { - roleSet.roles?.forEach(role => { - roleNames.push(role.name); - }); - }); - - // Check names for duplicates - if (hasDuplicates(roleNames)) { - errors.push(`Duplicate role names defined. Role names must be unique. Role names defined: ${roleNames}`); - } - } - - /** - * Function to validate managed policy availability for IAM resources - * @param values - * @param accountsConfig - * @param errors - */ - private validateIamPolicyTargets(values: IIamConfig, accountsConfig: AccountsConfig, errors: string[]) { - for (const policyItem of values.policySets ?? []) { - // Validate IAM Users - this.validateIamUserTarget(values, accountsConfig, policyItem as PolicySetConfig, errors); - - // Validate IAM Roles - this.validateIamRoleTarget(values, accountsConfig, policyItem as PolicySetConfig, errors); - - // Validate IAM Groups - this.validateIamGroupTarget(values, accountsConfig, policyItem as PolicySetConfig, errors); - } - } - - /** - * Function to validate managed policy availability for IAM users - * @param values - * @param accountsConfig - * @param policyItem PolicySetConfig - * @param errors - */ - private validateIamUserTarget( - values: IIamConfig, - accountsConfig: AccountsConfig, - policyItem: PolicySetConfig, - errors: string[], - ) { - const invalidIamUserTargets: string[] = []; - for (const iamItem of values.userSets ?? []) { - for (const userItem of iamItem.users) { - if (userItem.boundaryPolicy) { - policyItem.policies.find(item => { - if (userItem.boundaryPolicy === item.name) { - const userDeploymentTargetSets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - iamItem.deploymentTargets as DeploymentTargets, - ); - const policyTargets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - policyItem.deploymentTargets as DeploymentTargets, - ); - // Check the policies and validate that they're deployment is available for respective user boundary-policy configuration. - for (const targetItem of userDeploymentTargetSets ?? []) { - if (policyTargets.indexOf(targetItem) === -1) { - if (!invalidIamUserTargets.includes(userItem.username)) { - invalidIamUserTargets.push(userItem.username); - } - } - } - } - }); - } - } - } - if (invalidIamUserTargets.length > 0) { - errors.push( - `Deployment target account(s) for the following user(s): ${invalidIamUserTargets.join( - ', ', - )} reference policies that are not available in the target accounts.`, - ); - } - } - - /** - * Function to validate managed policy availability for IAM Roles - * @param values - * @param accountsConfig - * @param policyItem PolicySetConfig - * @param errors - */ - private validateIamRoleTarget( - values: IIamConfig, - accountsConfig: AccountsConfig, - policyItem: PolicySetConfig, - errors: string[], - ) { - const invalidIamRoleTargets: string[] = []; - for (const iamItem of values.roleSets ?? []) { - for (const roleItem of iamItem.roles) { - if (roleItem.boundaryPolicy) { - policyItem.policies.find(item => { - if (roleItem.boundaryPolicy === item.name) { - const userDeploymentTargetSets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - iamItem.deploymentTargets as DeploymentTargets, - ); - const policyTargets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - policyItem.deploymentTargets as DeploymentTargets, - ); - // Check the boundary policy and validate that it's deployment is available for respective role boundary-policy configuration. - for (const targetItem of userDeploymentTargetSets ?? []) { - if (policyTargets.indexOf(targetItem) === -1) { - if (!invalidIamRoleTargets.includes(roleItem.name)) { - invalidIamRoleTargets.push(roleItem.name); - } - } - } - } - }); - } - // Check the customer managed policies and validate that it's deployment is available for configurations.. - for (const customerPolicy of roleItem.policies?.customerManaged ?? []) { - policyItem.policies.find(item => { - if (customerPolicy === item.name) { - const userDeploymentTargetSets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - iamItem.deploymentTargets as DeploymentTargets, - ); - const policyTargets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - policyItem.deploymentTargets as DeploymentTargets, - ); - // Check the policies and validate that they're deployment is available for respective user boundary-policy configuration. - for (const targetItem of userDeploymentTargetSets ?? []) { - if (policyTargets.indexOf(targetItem) === -1) { - if (!invalidIamRoleTargets.includes(roleItem.name)) { - invalidIamRoleTargets.push(roleItem.name); - } - } - } - } - }); - } - } - } - if (invalidIamRoleTargets.length > 0) { - errors.push( - `Deployment target account(s) for the following role(s): ${invalidIamRoleTargets.join( - ', ', - )} reference policies that are not available in the target accounts.`, - ); - } - } - - /** - * Function to validate managed policy availability for IAM Groups - * @param values - * @param accountsConfig - * @param policyItem PolicySetConfig - * @param errors - */ - private validateIamGroupTarget( - values: IIamConfig, - accountsConfig: AccountsConfig, - policyItem: PolicySetConfig, - errors: string[], - ) { - const invalidIamGroupTargets: string[] = []; - for (const iamItem of values.groupSets ?? []) { - for (const groupItem of iamItem.groups) { - // Check the customer managed policies and validate that it's deployment is available for configurations.. - for (const customerPolicy of groupItem.policies?.customerManaged ?? []) { - policyItem.policies.find(item => { - if (customerPolicy === item.name) { - const userDeploymentTargetSets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - iamItem.deploymentTargets as DeploymentTargets, - ); - const policyTargets = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - policyItem.deploymentTargets as DeploymentTargets, - ); - // Check the policies and validate that they're deployment is available for respective group policy configuration. - for (const targetItem of userDeploymentTargetSets ?? []) { - if (policyTargets.indexOf(targetItem) === -1) { - if (!invalidIamGroupTargets.includes(groupItem.name)) { - invalidIamGroupTargets.push(groupItem.name); - } - } - } - } - }); - } - } - } - if (invalidIamGroupTargets.length > 0) { - errors.push( - `Deployment target account(s) for the following group(s): ${invalidIamGroupTargets.join( - ', ', - )} reference policies that are not available in the target accounts.`, - ); - } - } - - /** - * Function to validate Identity Center object - * @param values - * @param errors - */ - private validateIdentityCenter(iamConfig: IIamConfig, accountsConfig: AccountsConfig, errors: string[]) { - // - //Function to validate PermissionSet and Assignment names are unique - // - this.validateIdentityCenterPermissionSetNameForUniqueness(iamConfig, errors); - - // - // Validate Identity Center PermissionSet policies - // - this.validateIdentityCenterPermissionSetPolicies(iamConfig, accountsConfig, errors); - - // - // Validate Identity Center PermissionSet names - // - this.validateIdentityCenterPermissionSetInAssignments(iamConfig, errors); - - // - // Validate PermissionSet permissions boundary - // - this.validateIdentityCenterPermissionSetPermissionsBoundary(iamConfig, errors); - } - - /** - * Function to validate PermissionSet and Assignment names are unique - * @param values - */ - private validateIdentityCenterPermissionSetNameForUniqueness(iamConfig: IIamConfig, errors: string[]) { - const identityCenter = iamConfig.identityCenter; - const assignmentNames = [...(identityCenter?.identityCenterAssignments ?? [])].map(item => item.name); - const permissionSetNames = [...(identityCenter?.identityCenterPermissionSets ?? [])].map(item => item.name); - - if (hasDuplicates(assignmentNames)) { - errors.push(`Duplicate Identity Center Assignment names defined [${assignmentNames}].`); - } - - if (hasDuplicates(permissionSetNames)) { - errors.push(`Duplicate Identity Center Permission Set names defined [${permissionSetNames}].`); - } - } - - /** - * Function to validate Identity Center Permission set names in assignment - * @param iamConfig - * @param errors - */ - private validateIdentityCenterPermissionSetInAssignments(iamConfig: IIamConfig, errors: string[]) { - if (iamConfig.identityCenter) { - const permissionSetNames = [...(iamConfig.identityCenter?.identityCenterPermissionSets ?? [])].map( - item => item.name, - ); - - const identityCenterAssignments = iamConfig.identityCenter.identityCenterAssignments ?? []; - for (const identityCenterAssignment of identityCenterAssignments) { - if (!permissionSetNames.includes(identityCenterAssignment.permissionSetName)) { - errors.push( - `Identity center ${iamConfig.identityCenter.name} assignments ${ - identityCenterAssignment.name - } uses permission set ${ - identityCenterAssignment.permissionSetName - }, which is not found in identityCenterPermissionSets, available permission names are [${permissionSetNames.join( - ',', - )}].`, - ); - } - - const principals = identityCenterAssignment.principals ?? []; - const groups: string[] = []; - const users: string[] = []; - for (const principal of principals) { - if (principal.type === 'USER') { - users.push(principal.name); - } - if (principal.type === 'GROUP') { - groups.push(principal.name); - } - } - - // check duplicates for groups in principals - if (hasDuplicates(groups)) { - errors.push( - `Duplicate groups in principals [${groups.join(',')}] defined in IdentityCenter ${ - iamConfig.identityCenter.name - } for assignment ${identityCenterAssignment.name} `, - ); - } - - // check duplicates for users in principals - if (hasDuplicates(users)) { - errors.push( - `Duplicate users in principals [${users.join(',')}] defined in IdentityCenter ${ - iamConfig.identityCenter.name - } for assignment ${identityCenterAssignment.name} `, - ); - } - } - } - } - - /** - * Function to validate Identity Center Permission set permissionsBoundary - * @param iamConfig - * @param errors - */ - private validateIdentityCenterPermissionSetPermissionsBoundary(iamConfig: IIamConfig, errors: string[]) { - if (iamConfig.identityCenter) { - for (const identityCenterPermissionSet of iamConfig.identityCenter.identityCenterPermissionSets ?? []) { - if (identityCenterPermissionSet.policies?.permissionsBoundary) { - if ( - identityCenterPermissionSet.policies.permissionsBoundary.customerManagedPolicy && - identityCenterPermissionSet.policies.permissionsBoundary.awsManagedPolicyName - ) { - errors.push( - `Identity center ${iamConfig.identityCenter.name} permission set ${identityCenterPermissionSet.name} permissions boundary can either have customerManagedPolicy or managedPolicy, both the properties can't be defined.`, - ); - } - } - } - } - } - - private validateIdentityCenterPermissionSetPolicies( - iamConfig: IIamConfig, - accountsConfig: AccountsConfig, - errors: string[], - ) { - if (iamConfig.identityCenter) { - const policies: { name: string; accountNames: string[] }[] = []; - for (const policySet of iamConfig.policySets ?? []) { - for (const policyItem of policySet.policies) { - policies.push({ - name: policyItem.name, - accountNames: CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - policySet.deploymentTargets as DeploymentTargets, - ), - }); - } - } - - const identityCenter = iamConfig.identityCenter; - for (const identityCenterPermissionSet of identityCenter.identityCenterPermissionSets ?? []) { - // check duplicates for awsManaged polices - const awsManagedPolicies = identityCenterPermissionSet.policies?.awsManaged ?? []; - if (hasDuplicates(awsManagedPolicies)) { - errors.push( - `Duplicate AWS managed policy names [${awsManagedPolicies.join(',')}] defined in IdentityCenter ${ - identityCenter.name - } for permission set ${identityCenterPermissionSet.name} `, - ); - } - // check duplicates for customerManaged polices - const customerManagedPolicies = identityCenterPermissionSet.policies?.customerManaged ?? []; - if (hasDuplicates(customerManagedPolicies)) { - errors.push( - `Duplicate customer managed policy names [${customerManagedPolicies.join(',')}] defined in IdentityCenter ${ - identityCenter.name - } for permission set ${identityCenterPermissionSet.name} `, - ); - } - // check duplicates for customerManaged polices - const acceleratorManagedPolicies = identityCenterPermissionSet.policies?.acceleratorManaged ?? []; - if (hasDuplicates(acceleratorManagedPolicies)) { - errors.push( - `Duplicate lza managed policy names [${acceleratorManagedPolicies.join(',')}] defined in IdentityCenter ${ - identityCenter.name - } for permission set ${identityCenterPermissionSet.name} `, - ); - } - for (const lzaManagedPolicy of acceleratorManagedPolicies) { - const filteredPolicyItem = policies.find(item => item.name === lzaManagedPolicy); - if (!filteredPolicyItem) { - errors.push( - `Identity Center ${iamConfig.identityCenter.name}, permission set ${identityCenterPermissionSet.name}, lza managed policy ${lzaManagedPolicy} not found in policySets of iam-config.yaml file !!!`, - ); - } else { - // Validate LZA managed policy deploy target match assignment deploy target accounts - const assignmentAccountNames = this.getIdentityCenterAssignmentDeployAccountNames( - identityCenter, - accountsConfig, - identityCenterPermissionSet.name, - ); - - if (!assignmentAccountNames.every(item => filteredPolicyItem.accountNames.includes(item))) { - errors.push( - `Identity Center ${iamConfig.identityCenter.name}, permission set ${ - identityCenterPermissionSet.name - } assignments target deploy accounts [${assignmentAccountNames.join( - ',', - )}], are not part of lza managed policy ${lzaManagedPolicy} deploy target accounts [${filteredPolicyItem.accountNames.join( - ',', - )}] !!!`, - ); - } - } - } - } - } - } - - /** - * Function to get Identity Center Assignment deploy target account names - * @param identityCenter - * @param accountsConfig - * @param identityCenterPermissionSetName - * @returns - */ - private getIdentityCenterAssignmentDeployAccountNames( - identityCenter: IIdentityCenterConfig, - accountsConfig: AccountsConfig, - identityCenterPermissionSetName: string, - ): string[] { - const accountNames: string[] = []; - for (const identityCenterAssignmentItem of identityCenter.identityCenterAssignments ?? []) { - if (identityCenterAssignmentItem.permissionSetName === identityCenterPermissionSetName) { - accountNames.push( - ...CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - identityCenterAssignmentItem.deploymentTargets as DeploymentTargets, - ), - ); - } - } - - return [...new Set(accountNames)]; - } - - /** - * Function to validate existence of Assignment target account names - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateAssignmentAccountNames(values: IIamConfig, accountNames: string[], errors: string[]) { - const identityCenter = values.identityCenter; - for (const assignment of identityCenter?.identityCenterAssignments ?? []) { - for (const account of assignment.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for user sets does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of Assignment target account names exist for IAM policies or that arn or account ids match correct format - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateAssignmentPrincipalsForIamRoles(values: IIamConfig, accountNames: string[], errors: string[]) { - for (const roleSetItem of values.roleSets!) { - for (const roleItem of roleSetItem.roles) { - for (const assumedByItem of roleItem.assumedBy) { - if (assumedByItem.type === 'account') { - const accountIdRegex = /^\d{12}$/; - const accountArnRegex = new RegExp('^arn:.*$'); - // test if principal length exceeds IAM Role length limit of 2048 characters. - // Ref: https://docs.aws.amazon.com/IAM/latest/APIReference/API_Role.html - // this will mitigate polynomial regular expression used on uncontrolled data - if (assumedByItem.principal!.length > 2048) { - errors.push(`The account ID defined in arn ${assumedByItem.principal} is too long`); - } else if (accountIdRegex.test(assumedByItem.principal!)) { - continue; - } else if (accountArnRegex.test(assumedByItem.principal!)) { - const accountArnGetIdRegex = new RegExp('^arn:.*:.*::(.*):.*$'); - const accountId = accountArnGetIdRegex.exec(assumedByItem.principal!); - if (!accountIdRegex.test(accountId![1])) { - errors.push(`Account ID defined in arn ${assumedByItem.principal} is not a valid account ID`); - } - const accountArnRegex = new RegExp('^arn:.+:.+::\\d{12}:(root$|.*user.*(:|/).*$|.*role.*(:|/).*$)'); - if (!accountArnRegex.test(assumedByItem.principal!)) { - errors.push(`The arn ${assumedByItem.principal} is not a valid arn for a trust policy`); - } - } else { - const account = assumedByItem.principal; - if (accountNames.indexOf(account!) === -1) { - errors.push(`Cannot find an account with the name ${account} in accounts-config.yaml`); - } - } - } else if (assumedByItem.type === 'principalArn') { - const accountArnRegex = new RegExp('^arn:.+:iam::\\d{12}:(user|group|role)/.*$'); - if (!accountArnRegex.test(assumedByItem.principal!)) { - errors.push(`The arn ${assumedByItem.principal} is not a valid principal arn for a trust policy`); - } - if (assumedByItem.principal!.length > 2048) { - errors.push(`The principal defined in arn ${assumedByItem.principal} is too long`); - } - } - } - } - } - } - - /** - * Function to validate existence of Assignment deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateAssignmentDeploymentTargetOUs(values: IIamConfig, ouIdNames: string[], errors: string[]) { - const identityCenter = values.identityCenter; - for (const assignment of identityCenter?.identityCenterAssignments ?? []) { - for (const ou of assignment.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for assignment does not exist in organization-config.yaml file.`); - } - } - } - } - - /** - * Function to validate existence of policy sets target account names - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validatePolicySetsAccountNames(values: IIamConfig, accountNames: string[], errors: string[]) { - for (const policySet of values.policySets ?? []) { - for (const account of policySet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for policy sets does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of role sets target account names - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateRoleSetsAccountNames(values: IIamConfig, accountNames: string[], errors: string[]) { - for (const roleSet of values.roleSets ?? []) { - for (const account of roleSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for role sets does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of group sets target account names - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateGroupSetsAccountNames(values: IIamConfig, accountNames: string[], errors: string[]) { - for (const groupSet of values.groupSets ?? []) { - for (const account of groupSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for group sets does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of user sets target account names - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateUserSetsAccountNames(values: IIamConfig, accountNames: string[], errors: string[]) { - for (const userSet of values.userSets ?? []) { - for (const account of userSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for user sets does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate Deployment targets OU name for IAM services - * @param values - */ - private validateDeploymentTargetAccountNames(values: IIamConfig, accountNames: string[], errors: string[]) { - // - // Validate policy sets account name - // - this.validatePolicySetsAccountNames(values, accountNames, errors); - - // - // Validate role sets account name - // - this.validateRoleSetsAccountNames(values, accountNames, errors); - - // - // Validate group sets account name - // - this.validateGroupSetsAccountNames(values, accountNames, errors); - - // - // Validate user sets account name - // - this.validateUserSetsAccountNames(values, accountNames, errors); - - // - // Validate Identity Center assignments account name - // - this.validateAssignmentAccountNames(values, accountNames, errors); - - // - // Validate IAM principal assignments for roles - // - this.validateAssignmentPrincipalsForIamRoles(values, accountNames, errors); - } - - /** - * Function to validate existence of policy sets deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validatePolicySetsDeploymentTargetOUs(values: IIamConfig, ouIdNames: string[], errors: string[]) { - for (const policySet of values.policySets ?? []) { - for (const ou of policySet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for policy set does not exists in organization-config.yaml file.`); - } - } - } - } - - /** - * Function to validate existence of role sets deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateRoleSetsDeploymentTargetOUs(values: IIamConfig, ouIdNames: string[], errors: string[]) { - for (const roleSet of values.roleSets ?? []) { - for (const ou of roleSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for role set does not exists in organization-config.yaml file.`); - } - } - } - } - - /** - * Function to validate existence of group sets deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateGroupSetsDeploymentTargetOUs(values: IIamConfig, ouIdNames: string[], errors: string[]) { - for (const groupSet of values.groupSets ?? []) { - for (const ou of groupSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for group set does not exists in organization-config.yaml file.`); - } - } - } - } - - /** - * Function to validate existence of user sets deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateUserSetsDeploymentTargetOUs(values: IIamConfig, ouIdNames: string[], errors: string[]) { - for (const userSet of values.userSets ?? []) { - for (const ou of userSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for user set does not exists in organization-config.yaml file.`); - } - } - } - } - - /** - * Function to validate Deployment targets OU name for IAM services - * @param values - * @param ouIdNames - * @param errors - */ - private validateDeploymentTargetOUs(values: IIamConfig, ouIdNames: string[], errors: string[]) { - // - // Validate policy sets OU name - // - this.validatePolicySetsDeploymentTargetOUs(values, ouIdNames, errors); - - // - // Validate role sets OU name - // - this.validateRoleSetsDeploymentTargetOUs(values, ouIdNames, errors); - - // - // Validate group sets OU name - // - this.validateGroupSetsDeploymentTargetOUs(values, ouIdNames, errors); - - // - // Validate user sets OU name - // - this.validateUserSetsDeploymentTargetOUs(values, ouIdNames, errors); - - this.validateAssignmentDeploymentTargetOUs(values, ouIdNames, errors); - } -} - -/** - * Class to validate managed active directory - */ -class ManagedActiveDirectoryValidator { - private readonly validConfigSets: string[] = [ - 'JoinDomain', - 'AWSQuickStart', - 'ADGroupSetup', - 'ADUserSetup', - 'ADUserGroupSetup', - 'ADConnectorPermissionsSetup', - 'ConfigurePasswordPolicy', - 'ADGroupGrantPermissionsSetup', - ]; - constructor( - values: IamConfig, - vpcSubnetLists: VpcSubnetListsType[], - ouIdNames: string[], - accountNames: string[], - errors: string[], - ) { - // - // Validate mandatory user data scripts - // - this.validateMandatoryUserDataScripts(values, errors); - - // - // Validate instance security group source list - // - this.validateSecurityGroupInboundSources(values, errors); - - // - // Validate ad user groups - // - this.validateAdUserGroups(values, errors); - - this.validateMadVpcSettings(values, vpcSubnetLists, errors); - - // - // Validate MAD sharing configuration - // - this.validateMadSharingConfig(values, ouIdNames, accountNames, errors); - - // - // Validate MAD secret configuration - // - this.validateMadSecretConfig(values, errors); - } - - /** - * Function to validate instance security group inbound sources - * @param values - * @param errors - */ - private validateSecurityGroupInboundSources(values: IamConfig, errors: string[]) { - for (const managedActiveDirectory of values.managedActiveDirectories ?? []) { - if (managedActiveDirectory.activeDirectoryConfigurationInstance) { - if (managedActiveDirectory.activeDirectoryConfigurationInstance.securityGroupInboundSources.length === 0) { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: instance security group inbound source list empty !!!`, - ); - } - } - } - } - - /** - * Function to validate mandatory user data scripts for AD configuration - * @param values - * @param errors - */ - private validateMandatoryUserDataScripts(values: IamConfig, errors: string[]) { - for (const managedActiveDirectory of values.managedActiveDirectories ?? []) { - if (managedActiveDirectory.activeDirectoryConfigurationInstance) { - for (const configSet of this.validConfigSets) { - const foundScriptObject = managedActiveDirectory.activeDirectoryConfigurationInstance.userDataScripts.find( - item => item.scriptName === configSet, - ); - if (foundScriptObject) { - if (configSet === 'AWSQuickStart' && path.extname(foundScriptObject.scriptFilePath) !== '.psm1') { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: configuration instance user data script ${configSet} is a powerShell module, file extension must be .psm1 !!!`, - ); - } - if (configSet !== 'AWSQuickStart' && path.extname(foundScriptObject.scriptFilePath) !== '.ps1') { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: configuration instance user data script ${configSet} is a powerShell script, file extension must be .ps1 !!!`, - ); - } - } else { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: configuration instance missing ${configSet} user data script !!!`, - ); - } - } - } - } - } - - /** - * Function to validate ad user's valid group names - * @param values - * @param errors - */ - private validateAdUserGroups(values: IamConfig, errors: string[]) { - for (const managedActiveDirectory of values.managedActiveDirectories ?? []) { - if (managedActiveDirectory.activeDirectoryConfigurationInstance) { - const allGroups = managedActiveDirectory.activeDirectoryConfigurationInstance.adGroups; - allGroups.push( - ...managedActiveDirectory.activeDirectoryConfigurationInstance.adPerAccountGroups, - managedActiveDirectory.activeDirectoryConfigurationInstance.adConnectorGroup, - 'AWS Delegated Administrators', - ); - for (const adUser of managedActiveDirectory.activeDirectoryConfigurationInstance.adUsers) { - const allValidGroups = adUser.groups.every(item => { - return allGroups.includes(item); - }); - - if (!allValidGroups) { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: ad user ${ - adUser.name - } groups ${adUser.groups.join(',')} are not part of ad groups ${allGroups.join(',')} !!!`, - ); - } - } - } - } - } - - /** - * Function to validate ad vpc settings - * @param values - * @param vpcSubnetLists - * @param errors - */ - private validateMadVpcSettings( - values: IIamConfig, - vpcSubnetLists: { vpcName: string; subnetName: string; subnetAz: string | number }[], - errors: string[], - ) { - for (const managedActiveDirectory of values.managedActiveDirectories ?? []) { - const madVpc = vpcSubnetLists.filter(item => item.vpcName === managedActiveDirectory.vpcSettings.vpcName); - if (madVpc.length === 0) { - errors.push( - `Managed active directory ${managedActiveDirectory.name} vpc ${managedActiveDirectory.vpcSettings.vpcName} not found in network-config file`, - ); - } else { - const madSubnets: { - vpcName: string; - subnetName: string; - subnetAz: string | number; - }[] = []; - - if (managedActiveDirectory.vpcSettings.subnets.length < 2) { - errors.push( - `Managed active directory ${managedActiveDirectory.name} needs minimum of 2 subnets from 2 different availability zone `, - ); - } else { - for (const madSubnet of managedActiveDirectory.vpcSettings.subnets ?? []) { - const madSubnetItem = madVpc.find(item => item.subnetName === madSubnet); - if (madSubnetItem) { - madSubnets.push(madSubnetItem); - } else { - errors.push( - `Managed active directory ${managedActiveDirectory.name} subnet ${madSubnet} not found for vpc ${managedActiveDirectory.vpcSettings.vpcName} in network-config file`, - ); - } - } - if (managedActiveDirectory.vpcSettings.subnets.length !== madSubnets.length) { - errors.push( - `Number of subnets for managed active directory ${managedActiveDirectory.name} does not match with vpc configuration in network-config file.`, - ); - } - - // now check subnets are of different availability zones - const madSubnetAzs: (string | number)[] = []; - for (const madSubnetName of madSubnets) { - madSubnetAzs.push(madSubnetName.subnetAz); - } - - const subnetSet = new Set(madSubnetAzs); - - if (subnetSet.size === 1) { - errors.push( - `Managed active directory ${managedActiveDirectory.name} subnets must be from two different availability zone, subnets defined are from "${madSubnetAzs[0]}" availability zone`, - ); - } - } - } - } - } - - /** - * Function to validate managed active directory sharing configuration - * @param values - * @param ouIdNames - * @param accountNames - * @param errors - */ - private validateMadSharingConfig(values: IIamConfig, ouIdNames: string[], accountNames: string[], errors: string[]) { - for (const managedActiveDirectory of values.managedActiveDirectories ?? []) { - if (managedActiveDirectory.sharedOrganizationalUnits) { - if (managedActiveDirectory.sharedOrganizationalUnits.organizationalUnits.length === 0) { - errors.push(`No shared target OU listed for managed active directory ${managedActiveDirectory.name}.`); - } else { - for (const ou of managedActiveDirectory.sharedOrganizationalUnits.organizationalUnits) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Shared target OU ${ou} for managed active directory ${managedActiveDirectory.name} does not exists in organization-config.yaml file.`, - ); - } - } - } - } - - if (managedActiveDirectory.sharedAccounts) { - if (managedActiveDirectory.sharedAccounts.length === 0) { - errors.push(`No shared target account listed for managed active directory ${managedActiveDirectory.name}.`); - } else { - for (const account of managedActiveDirectory.sharedAccounts) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Shared target account ${account} for managed active directory ${managedActiveDirectory.name} does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - } - } - - /** - * Function to validate managed active directory secret configuration - * @param values - * @param ouIdNames - * @param accountNames - * @param errors - */ - private validateMadSecretConfig(values: IIamConfig, errors: string[]) { - for (const managedActiveDirectory of values.managedActiveDirectories ?? []) { - if (managedActiveDirectory.account === 'Management') { - if ( - managedActiveDirectory.secretConfig?.account === 'Management' || - !managedActiveDirectory.secretConfig?.account - ) { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: secretConfig needs to specify an account that isn't the Management account.`, - ); - } - } - if (managedActiveDirectory.secretConfig?.account === 'Management') { - errors.push( - `[Managed Active Directory: ${managedActiveDirectory.name}]: secretConfig needs to specify an account that isn't the Management account.`, - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/central-network-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/central-network-validator.ts deleted file mode 100644 index aa08783..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/central-network-validator.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CustomizationsConfig } from '../../lib/customizations-config'; -import { NetworkConfig } from '../../lib/network-config'; -import { GatewayLoadBalancersValidator } from './gateway-load-balancers-validator'; -import { IpamValidator } from './ipam-validator'; -import { NetworkFirewallValidator } from './network-firewall-validator'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import { Route53ResolverValidator } from './route53-resolver-validator'; - -/** - * Class to validate central network services - */ -export class CentralNetworkValidator { - constructor( - values: NetworkConfig, - configDir: string, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - // Validate delegated admin account name - this.validateDelegatedAdmin(values, helpers, errors); - - // Validate central network services - new GatewayLoadBalancersValidator(values, helpers, errors, customizationsConfig); - new IpamValidator(values, helpers, errors); - new NetworkFirewallValidator(values, configDir, helpers, errors); - new Route53ResolverValidator(values, configDir, helpers, errors); - } - - /** - * Validate delegated admin account exists - * @param values - * @param helpers - * @param errors - */ - private validateDelegatedAdmin(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - if (values.centralNetworkServices) { - const delegatedAdmin = values.centralNetworkServices.delegatedAdminAccount; - if (!helpers.accountExists(delegatedAdmin)) { - errors.push( - `Central network services delegated admin account ${delegatedAdmin} does not exist in accounts-config.yaml`, - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/certificates-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/certificates-validator.ts deleted file mode 100644 index 08df035..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/certificates-validator.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CertificateConfig, NetworkConfig } from '../../lib/network-config'; -import { hasDuplicates } from '../utils/common-validator-functions'; - -export class CertificatesValidator { - constructor(values: NetworkConfig, errors: string[]) { - // - // Validate ACM certificate configurations - // - this.validateCertificates(values, errors); - } - private validateCertificates(values: NetworkConfig, errors: string[]) { - const allCertificateNames: string[] = []; - for (const certificate of values.certificates ?? []) { - allCertificateNames.push(certificate.name); - // check certificate import keys - if (certificate.type === 'import') { - this.checkImportCertificateInput(certificate, errors); - } - // check certificate request keys - if (certificate.type === 'request') { - this.checkRequestCertificateInput(certificate, errors); - } - } - // check certificate for duplicate names - this.checkCertificateForDuplicateNames(allCertificateNames, errors); - } - private checkImportCertificateInput(certificate: CertificateConfig, errors: string[]) { - // when cert is set to import users must mention a privateKey and certificate - if (!certificate.privKey || !certificate.cert) { - errors.push( - `Certificate: ${ - certificate.name - } is set to import which requires both privKey and cert. Found: ${JSON.stringify(certificate)}`, - ); - } - } - private checkRequestCertificateInput(certificate: CertificateConfig, errors: string[]) { - // when cert is set to request users must mention a privateKey and certificate - if (!certificate.domain || !certificate.validation) { - errors.push( - `Certificate: ${ - certificate.name - } is set to request which requires both validation and domain. Found: ${JSON.stringify(certificate)}`, - ); - } - } - - private checkCertificateForDuplicateNames(allCertificateNames: string[], errors: string[]) { - if (hasDuplicates(allCertificateNames)) { - errors.push(`There are duplicates in certificate names. Certificate names: ${allCertificateNames.join(',')}`); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/customer-gateways-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/customer-gateways-validator.ts deleted file mode 100644 index 74fb4b6..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/customer-gateways-validator.ts +++ /dev/null @@ -1,512 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { IPv4CidrRange } from 'ip-num'; -import { CustomizationsConfig, Ec2FirewallInstanceConfig } from '../../lib/customizations-config'; -import { - CustomerGatewayConfig, - NetworkConfig, - VpcConfig, - VpcTemplatesConfig, - VpnConnectionConfig, - VpnTunnelOptionsSpecificationsConfig, -} from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import { isNetworkType } from '../../lib/common'; - -/** - * Class to validate Customer Gateways - */ -export class CustomerGatewaysValidator { - constructor( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - // - // Validate gateway load balancers deployment account names - // - this.validateCgwTargetAccounts(values, helpers, errors); - // - // Validate CGW configuration - // - this.validateCgwConfiguration(values, helpers, errors, customizationsConfig); - } - - private validateCgwTargetAccounts(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const cgw of values.customerGateways ?? []) { - if (!helpers.accountExists(cgw.account)) { - errors.push( - `Target account ${cgw.account} for customer gateway ${cgw.name} does not exist in accounts-config.yaml file`, - ); - } - } - } - - /** - * Validate customer gateways and VPN confections - * @param values - */ - private validateCgwConfiguration( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - for (const cgw of values.customerGateways ?? []) { - if (cgw.asn < 1 || cgw.asn > 2147483647) { - errors.push(`[Customer Gateway ${cgw.name}]: ASN ${cgw.asn} out of range 1-2147483647`); - } - // Validate CGW IP targets - this.validateCgwIpTarget(cgw, helpers, errors, customizationsConfig); - // Validate VPN configurations - this.validateVpnConfiguration(cgw, values, helpers, errors); - } - } - - /** - * Validates that the CGW is targeting - * @param cgw CustomerGatewayConfig - * @param helpers NetworkValidatorFunctions - * @param errors string[] - * @param customizationsConfig CustomizationsConfig | undefined - */ - private validateCgwIpTarget( - cgw: CustomerGatewayConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - if (!helpers.isValidIpv4(cgw.ipAddress)) { - if (!this.isValidFirewallReference(cgw, helpers, errors, customizationsConfig)) { - errors.push( - `[Customer Gateway ${cgw.name}]: IP address must either be a valid IPv4 address or EC2 firewall reference variable. Value entered: ${cgw.ipAddress}`, - ); - } - } - } - - /** - * Validates that the referenced firewall exists in customizations config - * @param cgw CustomerGatewayConfig - * @param helpers NetworkValidatorFunctions - * @param errors string[] - * @param customizationsConfig CustomizationsConfig | undefined - * @returns boolean - */ - private isValidFirewallReference( - cgw: CustomerGatewayConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ): boolean { - // - // Match variable pattern - if (!helpers.matchesRegex(cgw.ipAddress, '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:.+}$')) { - errors.push( - `[Customer Gateway ${cgw.name}]: Incorrect EC2 firewall reference variable entered. Pattern accepted: "^\\$\{ACCEL_LOOKUP::EC2:ENI_\\d:.+}$" Value entered: ${cgw.ipAddress}`, - ); - return false; - } else { - // - // Check that customizations config is defined - if (!customizationsConfig) { - errors.push( - `[Customer Gateway ${cgw.name}]: EC2 firewall reference variable entered but customizations-config.yaml is not defined.`, - ); - return false; - } else { - // - // Check that firewall exists - const firewallName = cgw.ipAddress.split(':')[4].replace('}', ''); - const firewall = customizationsConfig.firewalls?.instances?.find(instance => instance.name === firewallName); - if (!firewall) { - errors.push( - `[Customer Gateway ${cgw.name}]: EC2 firewall instance "${firewallName}" is not defined in customizations-config.yaml`, - ); - return false; - } - // - // Check device index for elastic IP - this.validateFirewallInterface(cgw, firewall, errors); - } - } - return true; - } - - /** - * Validates that the referenced network interface has an elastic IP associated - * @param cgw CustomerGatewayConfig - * @param firewall Ec2FirewallInstanceConfig - * @param errors string[] - */ - private validateFirewallInterface(cgw: CustomerGatewayConfig, firewall: Ec2FirewallInstanceConfig, errors: string[]) { - if (!firewall.launchTemplate.networkInterfaces) { - errors.push( - `[Customer Gateway ${cgw.name}]: EC2 firewall instance "${firewall.name}" launch template does not have network interfaces defined in customizations-config.yaml`, - ); - } else { - const deviceIndex = Number(cgw.ipAddress.split(':')[3].split('_')[1]); - if (deviceIndex > firewall.launchTemplate.networkInterfaces.length - 1) { - errors.push( - `[Customer Gateway ${cgw.name}]: EC2 firewall instance "${firewall.name}" device index ${deviceIndex} does not exist in customizations-config.yaml`, - ); - } else { - const networkInterface = firewall.launchTemplate.networkInterfaces[deviceIndex]; - if (!networkInterface.associateElasticIp) { - errors.push( - `[Customer Gateway ${cgw.name}]: EC2 firewall instance "${firewall.name}" device index ${deviceIndex} does not have the associateElasticIp property set in customizations-config.yaml`, - ); - } - } - } - } - - /** - * Validate site-to-site VPN connections - * @param cgw - * @param values - * @param helpers - * @param errors - */ - private validateVpnConfiguration( - cgw: CustomerGatewayConfig, - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - cgw.vpnConnections?.forEach(vpn => { - // Validate if VPC termination and Transit Gateway is provided in the same VPN Config - if (vpn.vpc && vpn.transitGateway) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: Both TGW and VPC provided in the same VPN Configuration`, - ); - } - - // Validate that either a VPC or Transit Gateway is provided in the VPN Config - if (!vpn.vpc && !vpn.transitGateway) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: Neither a valid TGW or VPC provided in the config`, - ); - } - - // Validate local/remote IP ranges - this.validateVpnConnectionAllowedCidrs(cgw, vpn, helpers, errors); - - // Validate length of tunnel specifications - this.validateTunnelSpecifications(cgw, vpn, helpers, errors); - - // Handle TGW and VGW Logic respectively - if (vpn.vpc) { - this.validateVirtualPrivateGatewayVpnConfiguration(cgw, vpn, helpers, errors); - } else if (vpn.transitGateway) { - this.validateTransitGatewayVpnConfiguration(cgw, vpn, values, errors); - } - }); - } - - /** - * Validate VPN allowed CIDR ranges - * @param cgw CustomerGatewayConfig - * @param vpn VpnConnectionConfig - * @param helpers NetworkValidatorFunctions - * @param errors string[] - */ - private validateVpnConnectionAllowedCidrs( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (vpn.amazonIpv4NetworkCidr && !helpers.isValidIpv4Cidr(vpn.amazonIpv4NetworkCidr)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: Amazon allowed IPv4 network CIDR is not a valid CIDR range. Configured CIDR: ${vpn.amazonIpv4NetworkCidr}`, - ); - } - if (vpn.customerIpv4NetworkCidr && !helpers.isValidIpv4Cidr(vpn.customerIpv4NetworkCidr)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: Customer allowed IPv4 network CIDR is not a valid CIDR range. Configured CIDR: ${vpn.amazonIpv4NetworkCidr}`, - ); - } - } - - /** - * Validate VPN tunnel options specification - * @param cgw CustomerGatewayConfig - * @param vpn VpnConnectionConfig - * @param helpers NetworkValidatorFunctions - * @param errors string[] - */ - private validateTunnelSpecifications( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (vpn.tunnelSpecifications) { - // - // Validate only two tunnels are configured - if (vpn.tunnelSpecifications.length < 2 || vpn.tunnelSpecifications.length > 2) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: tunnel specifications must have exactly 2 definitions`, - ); - } - vpn.tunnelSpecifications.forEach((tunnel, index) => { - // - // Validate tunnel options IKE/DPD timers - this.validateTunnelOptionsTimers(cgw, vpn, tunnel, index, errors); - // - // Validate remaining tunnel options - this.validateRemainingTunnelOptions(cgw, vpn, tunnel, index, errors); - // - // Validate tunnel IPs - this.validateTunnelIps(cgw, vpn, tunnel, index, helpers, errors); - }); - } - } - - /** - * Validates the various IKE/DPD timers for the tunnel - * @param cgw CustomerGatewayConfig - * @param vpn VpnConnectionConfig - * @param tunnel VpnTunnelOptionsSpecificationsConfig - * @param index number - * @param errors string[] - */ - private validateTunnelOptionsTimers( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - tunnel: VpnTunnelOptionsSpecificationsConfig, - index: number, - errors: string[], - ) { - // - // Validate DPD timeout - if (tunnel.dpdTimeoutSeconds && tunnel.dpdTimeoutSeconds < 30) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: DPD timeout must be 30 seconds or higher. DPD timeout configured: ${tunnel.dpdTimeoutSeconds}`, - ); - } - // - // Validate Phase 1 and 2 lifetimes - if ( - tunnel.phase1?.lifetimeSeconds && - (tunnel.phase1.lifetimeSeconds < 900 || tunnel.phase1.lifetimeSeconds > 28800) - ) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Phase 1 lifetime must be between 900 and 28800 seconds. Lifetime configured: ${tunnel.phase1.lifetimeSeconds}`, - ); - } - if ( - tunnel.phase2?.lifetimeSeconds && - (tunnel.phase2.lifetimeSeconds < 900 || tunnel.phase2.lifetimeSeconds > 3600) - ) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Phase 2 lifetime must be between 900 and 3600 seconds. Lifetime configured: ${tunnel.phase2.lifetimeSeconds}`, - ); - } - // - // Validate rekey margin time - const p2Lifetime = tunnel.phase2?.lifetimeSeconds ?? 3600; - if ( - tunnel.rekeyMarginTimeSeconds && - (tunnel.rekeyMarginTimeSeconds < 60 || tunnel.rekeyMarginTimeSeconds > p2Lifetime / 2) - ) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Rekey margin time must be between 60 seconds and half of the configured Phase 2 lifetime. Rekey margin configured: ${tunnel.rekeyMarginTimeSeconds}. Phase 2 lifetime configured: ${p2Lifetime}`, - ); - } - } - - /** - * Validates the remaining tunnel options - * @param cgw CustomerGatewayConfig - * @param vpn VpnConnectionConfig - * @param tunnel VpnTunnelOptionsSpecificationsConfig - * @param index number - * @param errors string[] - */ - private validateRemainingTunnelOptions( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - tunnel: VpnTunnelOptionsSpecificationsConfig, - index: number, - errors: string[], - ) { - // - // Validate rekey fuzz percentage - if (tunnel.rekeyFuzzPercentage && (tunnel.rekeyFuzzPercentage < 0 || tunnel.rekeyFuzzPercentage > 100)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Rekey fuzz percentage must be between 0 and 100 percent. Percentage configured: ${tunnel.rekeyFuzzPercentage}`, - ); - } - // - // Validate replay window size - if (tunnel.replayWindowSize && (tunnel.replayWindowSize < 64 || tunnel.replayWindowSize > 2048)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Replay window size must be between 64 and 2048 packets. Window size configured: ${tunnel.replayWindowSize}`, - ); - } - // - // Validate startup action is only used for IKEv2 tunnels - const ikeVersions = tunnel.ikeVersions ?? [1, 2]; - if (tunnel.startupAction === 'start' && !ikeVersions.includes(2)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Startup action can only be modified on IKEv2 tunnels. IKE versions configured: ${ikeVersions}`, - ); - } - } - - /** - * Validate tunnel inside IPv4 addresses - * @param cgw CustomerGatewayConfig - * @param vpn VpnConnectionConfig - * @param tunnel VpnTunnelOptionsSpecificationsConfig - * @param index number - * @param helpers NetworkValidatorFunctions - * @param errors string[] - */ - private validateTunnelIps( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - tunnel: VpnTunnelOptionsSpecificationsConfig, - index: number, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (tunnel.tunnelInsideCidr) { - // - // Validate the tunnel CIDR is valid - if (!helpers.isValidIpv4Cidr(tunnel.tunnelInsideCidr)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Tunnel inside CIDR must be a valid IPv4 CIDR range. CIDR configured: ${tunnel.tunnelInsideCidr}`, - ); - } else { - const tunnelCidrPool = IPv4CidrRange.fromCidr('169.254.0.0/16'); - const tunnelCidr = IPv4CidrRange.fromCidr(tunnel.tunnelInsideCidr); - if (!tunnelCidr.inside(tunnelCidrPool)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name} tunnel ${index}]: Tunnel inside CIDR must be contained within the range 169.254.0.0/16. CIDR configured: ${tunnel.tunnelInsideCidr}`, - ); - } - } - } - } - - /** - * Validate site-to-site VPN connections for Virtual Private Gateways - * @param cgw - * @param vpn - * @param values - * @param helpers - * @param errors - */ - private validateVirtualPrivateGatewayVpnConfiguration( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const vpc = helpers.getVpc(vpn.vpc!); - // Validate that the VPC referenced in the VPN Connection exists. - if (!vpc) { - errors.push(`[Customer Gateway ${cgw.name} VPN Connection ${vpn.name}]: VPC ${vpn.vpc} referenced doesn't exist`); - } else { - // Validate that the VPC referenced has a VGW attached - if (!vpc.virtualPrivateGateway) { - errors.push( - `[Customer Gateway ${cgw.name} VPN Connection ${vpn.name}]: VPC ${vpn.vpc} referenced doesn't have an attached Virtual Private Gateway`, - ); - } - - // Validate VPC account and CGW account match - if (isNetworkType('IVpcConfig', vpc) && vpc.account !== cgw.account) { - errors.push( - `[Customer Gateway ${cgw.name} VPN Connection ${vpn.name}]: VPC ${vpn.vpc} referenced does not reside in the same account as the CGW`, - ); - } - if ( - isNetworkType('IVpcTemplatesConfig', vpc) && - !helpers.getVpcAccountNames(vpc).includes(cgw.account) - ) { - errors.push( - `[Customer Gateway ${cgw.name} VPN Connection ${vpn.name}]: VPC ${vpn.vpc} referenced does not reside in the same account as the CGW`, - ); - } - - // Validate that TGW route table propagations or associations aren't configured for a VPN Connection terminating at a VPC - if (vpn.routeTableAssociations || vpn.routeTablePropagations) { - errors.push( - `[Customer Gateway ${cgw.name} VPN Connection ${vpn.name}]: VPC ${vpn.vpc} does not support Transit Gateway Route Table Associations or Propagations`, - ); - } - - // Validate accelerated VPN is not enabled - if (vpn.enableVpnAcceleration) { - errors.push( - `[Customer Gateway ${cgw.name} VPN Connection ${vpn.name}]: VGW VPN connections do not support VPN acceleration`, - ); - } - } - } - - /** - * Validate site-to-site VPN connections for Transit Gateways - * @param cgw - * @param vpn - * @param values - * @param errors - */ - private validateTransitGatewayVpnConfiguration( - cgw: CustomerGatewayConfig, - vpn: VpnConnectionConfig, - values: NetworkConfig, - errors: string[], - ) { - const tgw = values.transitGateways.find(item => item.name === vpn.transitGateway); - - if (!tgw) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: Transit Gateway ${vpn.transitGateway} does not exist`, - ); - } - - // Validate TGW account matches - if (tgw && tgw.account !== cgw.account) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: VPN connection must reside in the same account as Transit Gateway ${tgw.name}`, - ); - } - - // Validate associations/propagations - if (tgw) { - const routeTableArray: string[] = []; - if (vpn.routeTableAssociations) { - routeTableArray.push(...vpn.routeTableAssociations); - } - if (vpn.routeTablePropagations) { - routeTableArray.push(...vpn.routeTablePropagations); - } - const tgwRouteTableSet = new Set(routeTableArray); - - for (const routeTable of tgwRouteTableSet ?? []) { - if (!tgw.routeTables.find(item => item.name === routeTable)) { - errors.push( - `[Customer Gateway ${cgw.name} VPN connection ${vpn.name}]: route table ${routeTable} does not exist on Transit Gateway ${tgw.name}`, - ); - } - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/dhcp-options-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/dhcp-options-validator.ts deleted file mode 100644 index b60a601..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/dhcp-options-validator.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { NetworkConfig, DhcpOptsConfig } from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; - -interface IpAddresses { - ipv4: string[]; - ipv6: string[]; -} - -/** - * Class to validate DHCP options sets - */ -export class DhcpOptionsValidator { - constructor(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate DHCP options names - // - this.validateDhcpOptNames(values, helpers, errors); - // - // Validate DHCP options names - // - this.validateDhcpOptAccountNames(values, helpers, errors); - // - // Validate DHCP configuration - // - this.validateDhcpOptConfiguration(values, helpers, errors); - } - - private validateDhcpOptNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const setNames: string[] = []; - values.dhcpOptions?.forEach(set => setNames.push(set.name)); - - if (helpers.hasDuplicates(setNames)) { - errors.push( - `Duplicate DHCP options set names exist. DHCP options set names must be unique. DHCP options set names in file: ${setNames}`, - ); - } - } - - /** - * Validate DHCP options account names - * @param values - * @param helpers - * @param errors - */ - private validateDhcpOptAccountNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.dhcpOptions?.forEach(set => { - set.accounts.forEach(account => { - if (!helpers.accountExists(account)) { - errors.push( - `Target account ${account} for DHCP options set ${set.name} does not exist in accounts-config.yaml file`, - ); - } - }); - }); - } - - private validateDhcpOptConfiguration(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.dhcpOptions?.forEach(set => { - // Validate domain name - this.validateDomainName(set, helpers, errors); - // Validate IP addresses - this.validateIpAddresses(set, helpers, errors); - }); - } - - /** - * Validate DHCP option set domain name - * @param set - * @param helpers - * @param errors - */ - private validateDomainName(set: DhcpOptsConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // Validate regex - if (set.domainName && !helpers.matchesRegex(set.domainName, '^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$')) { - errors.push( - `[DHCP options set ${set.name}]: domainName "${set.domainName}" is invalid. Domain names must match the pattern "^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$"`, - ); - } - // Validate regional domain names are not deployed to more than one region - const isRegionalName = set.domainName - ? set.domainName === 'ec2.internal' || helpers.matchesRegex(set.domainName, '^.+\\.compute\\.internal$') - : false; - if (set.regions.length > 1 && set.domainName && isRegionalName) { - errors.push( - `[DHCP options set ${set.name}]: domainName "${set.domainName}" is invalid. Domain name is deployed to multiple regions but specified Amazon-provided regional domain name`, - ); - } - } - - /** - * Validate IP addresses defined for DHCP options set - * @param set - * @param helpers - * @param errors - */ - private validateIpAddresses(set: DhcpOptsConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate DNS IP addresses - set.domainNameServers?.forEach(dnsIp => { - if (dnsIp !== 'AmazonProvidedDNS' && !helpers.isValidIpv4(dnsIp) && !helpers.isValidIpv6(dnsIp)) { - errors.push( - `[DHCP options set ${set.name}]: IP address "${dnsIp}" is invalid. Values for domainNameServers must be either a valid IPv4/v6 address or AmazonProvidedDNS`, - ); - } - }); - // - // Validate NTP IP addresses - set.ntpServers?.forEach(ntpIp => { - if (!helpers.isValidIpv4(ntpIp) && !helpers.isValidIpv6(ntpIp)) { - errors.push( - `[DHCP options set ${set.name}]: IP address "${ntpIp}" is invalid. Values for ntpServers must be a valid IPv4/v6 address`, - ); - } - }); - // - // Validate NetBIOS name servers (only supports IPv4) - set.netbiosNameServers?.forEach(netBiosIp => { - if (!helpers.isValidIpv4(netBiosIp)) { - errors.push( - `[DHCP options set ${set.name}]: IP address "${netBiosIp}" is invalid. Values for netbiosNameServers must be a valid IPv4 address`, - ); - } - }); - // - // Filter and store IPs - const domainNameServers: IpAddresses = { - ipv4: set.domainNameServers?.filter(dnsIpv4 => helpers.isValidIpv4(dnsIpv4)) ?? [], - ipv6: set.domainNameServers?.filter(dnsIpv6 => helpers.isValidIpv6(dnsIpv6)) ?? [], - }; - const ntpServers: IpAddresses = { - ipv4: set.ntpServers?.filter(ntpIpv4 => helpers.isValidIpv4(ntpIpv4)) ?? [], - ipv6: set.ntpServers?.filter(ntpIpv6 => helpers.isValidIpv6(ntpIpv6)) ?? [], - }; - const netbiosNameServers: IpAddresses = { - ipv4: set.netbiosNameServers?.filter(netBiosIpv4 => helpers.isValidIpv4(netBiosIpv4)) ?? [], - ipv6: [], - }; - // - // Validate length of IP addresses - this.validateIpAddressLength(domainNameServers, ntpServers, netbiosNameServers, set.name, errors); - } - - /** - * Validate length of each IP address type - * @param domainNameServers - * @param ntpServers - * @param netbiosNameServers - * @param setName - * @param errors - */ - private validateIpAddressLength( - domainNameServers: IpAddresses, - ntpServers: IpAddresses, - netbiosNameServers: IpAddresses, - setName: string, - errors: string[], - ) { - // - // Validate total length of DNS IPs - if (domainNameServers.ipv4.length > 4 || domainNameServers.ipv6.length > 4) { - errors.push( - `[DHCP options set ${setName}]: maximum domainNameServers server address threshold breached -- IPv4: ${domainNameServers.ipv4.length} IPv6: ${domainNameServers.ipv6.length}. No more than 4 servers of each IP version type may be defined`, - ); - } - // - // Validate total length of NTP IPs - if (ntpServers.ipv4.length > 4 || ntpServers.ipv6.length > 4) { - errors.push( - `[DHCP options set ${setName}]: maximum ntpServers server address threshold breached -- IPv4: ${ntpServers.ipv4.length} IPv6: ${ntpServers.ipv6.length}. No more than 4 servers of each IP version type may be defined`, - ); - } - // - // Validate total length of NetBIOS IPs - if (netbiosNameServers.ipv4.length > 4) { - errors.push( - `[DHCP options set ${setName}]: netbiosNameServers has ${netbiosNameServers.ipv4.length} IPv4 servers defined. A maximum of 4 servers may be defined`, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/direct-connect-gateways-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/direct-connect-gateways-validator.ts deleted file mode 100644 index 3028b59..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/direct-connect-gateways-validator.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DxGatewayConfig, DxVirtualInterfaceConfig, NetworkConfig } from '../../lib/network-config'; - -/** - * Class to validate direct connect gateways - */ -export class DirectConnectGatewaysValidator { - constructor(values: NetworkConfig, errors: string[]) { - // - // Validate DX gateway configurations - // - this.validateDxConfiguration(values, errors); - } - - /** - * Function to validate peer IP addresses for virtual interfaces. - * @param dxgw - * @param vif - */ - private validateDxVirtualInterfaceAddresses(dxgw: DxGatewayConfig, vif: DxVirtualInterfaceConfig, errors: string[]) { - // Catch error if one peer IP is defined and not the other - if (vif.amazonAddress && !vif.customerAddress) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: Amazon peer IP defined but customer peer IP undefined for ${vif.name}`, - ); - } - if (!vif.amazonAddress && vif.customerAddress) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: Customer peer IP defined but Amazon peer IP undefined for ${vif.name}`, - ); - } - // Catch error if addresses match - if (vif.amazonAddress && vif.customerAddress) { - if (vif.amazonAddress === vif.customerAddress) { - errors.push(`[Direct Connect Gateway ${dxgw.name}]: Amazon peer IP and customer peer IP match for ${vif.name}`); - } - } - } - - /** - * Function to validate DX virtual interface configurations. - * @param dxgw - */ - private validateDxVirtualInterfaces(dxgw: DxGatewayConfig, errors: string[]) { - for (const vif of dxgw.virtualInterfaces ?? []) { - // Catch error for private VIFs with transit gateway associations - if (vif.type === 'private' && dxgw.transitGatewayAssociations) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: cannot specify private virtual interface ${vif.name} with transit gateway associations`, - ); - } - // Catch error if ASNs match - if (dxgw.asn === vif.customerAsn) { - errors.push(`[Direct Connect Gateway ${dxgw.name}]: Amazon ASN and customer ASN match for ${vif.name}`); - } - // Catch error if ASN is not in the correct range - if (vif.customerAsn < 1 || vif.customerAsn > 2147483647) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: ASN ${vif.customerAsn} out of range 1-2147483647 for virtual interface ${vif.name}`, - ); - } - // Catch error if VIF VLAN is not in range - if (vif.vlan < 1 || vif.vlan > 4094) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: VLAN ${vif.vlan} out of range 1-4094 for virtual interface ${vif.name}`, - ); - } - // Validate peer IP addresses - this.validateDxVirtualInterfaceAddresses(dxgw, vif, errors); - } - } - - /** - * Function to validate DX gateway transit gateway assocations. - * @param values - * @param dxgw - */ - private validateDxTransitGatewayAssociations(values: NetworkConfig, dxgw: DxGatewayConfig, errors: string[]) { - for (const tgwAssociation of dxgw.transitGatewayAssociations ?? []) { - const tgw = values.transitGateways.find( - item => item.name === tgwAssociation.name && item.account === tgwAssociation.account, - ); - // Catch error if TGW isn't found - if (!tgw) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: cannot find matching transit gateway for TGW association ${tgwAssociation.name}`, - ); - } - // Catch error if ASNs match - if (tgw!.asn === dxgw.asn) { - errors.push(`[Direct Connect Gateway ${dxgw.name}]: DX Gateway ASN and TGW ASN match for ${tgw!.name}`); - } - // Catch error if TGW and DXGW account don't match and associations/propagations are configured - if (tgw!.account !== dxgw.account) { - if (tgwAssociation.routeTableAssociations || tgwAssociation.routeTablePropagations) { - errors.push( - `[Direct Connect Gateway ${dxgw.name}]: DX Gateway association proposals cannot have TGW route table associations or propagations defined`, - ); - } - } - } - } - - /** - * Function to validate DX gateway configurations. - * @param values - */ - private validateDxConfiguration(values: NetworkConfig, errors: string[]) { - for (const dxgw of values.directConnectGateways ?? []) { - // Validate virtual interfaces - this.validateDxVirtualInterfaces(dxgw, errors); - // Validate transit gateway attachments - this.validateDxTransitGatewayAssociations(values, dxgw, errors); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/endpoint-policies-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/endpoint-policies-validator.ts deleted file mode 100644 index 4e2e6ef..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/endpoint-policies-validator.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import path from 'path'; -import * as fs from 'fs'; -import { NetworkConfig } from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import { ReplacementsConfig } from '../../lib/replacements-config'; -import { CommonValidatorFunctions } from '../common/common-validator-functions'; - -/** - * Class to validate endpoint policies - */ -export class EndpointPoliciesValidator { - constructor( - values: NetworkConfig, - replacementsConfig: ReplacementsConfig | undefined, - configDir: string, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // - // Validate endpoint policy names are unique - // - this.validateEndpointPolicyNames(values, helpers, errors); - // - // Validate endpoint policy document exists - // - this.validateEndpointPolicyDocumentFile(values, configDir, errors); - // - // Validate parameters in vpc endpoint policy document - // - this.validateVpcEndpointParameters(configDir, values, replacementsConfig, errors); - } - /** - * Method to validate endpoint policy names are unique - * @param values - * @param helpers - * @param errors - */ - private validateEndpointPolicyNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const policyNames = values.endpointPolicies.map(policy => { - return policy.name; - }); - // Validate names are unique - if (helpers.hasDuplicates(policyNames)) { - errors.push( - `Duplicate endpoint policy names exist. Endpoint policy names must be unique. Endpoint policy names in file: ${policyNames}`, - ); - } - } - /** - * Function to validate Endpoint policy document file existence - * @param values - * @param configDir - * @param errors - */ - private validateEndpointPolicyDocumentFile(values: NetworkConfig, configDir: string, errors: string[]) { - for (const policyItem of values.endpointPolicies ?? []) { - if (!fs.existsSync(path.join(configDir, policyItem.document))) { - errors.push(`Endpoint policy ${policyItem.name} document file ${policyItem.document} not found!`); - } - } - } - - /** - * Function to validate if static parameter in vpc endpoint policy file is defined in replacements config - * @param configDir - * @param networkConfig - * @param replacementConfig - * @param errors - */ - private validateVpcEndpointParameters( - configDir: string, - networkConfig: NetworkConfig, - replacementConfig: ReplacementsConfig | undefined, - errors: string[], - ) { - const policyPaths = networkConfig.endpointPolicies.map(policy => policy.document); - CommonValidatorFunctions.validateStaticParameters(replacementConfig, configDir, policyPaths, new Set(), errors); - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/firewall-manager-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/firewall-manager-validator.ts deleted file mode 100644 index 61b429f..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/firewall-manager-validator.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { FirewallManagerNotificationChannelConfig, NetworkConfig } from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; - -export class FirewallManagerValidator { - constructor(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate DX gateway configurations - // - this.validateFmsConfig(values, helpers, errors); - } - - /** - * Function to validate the FMS configuration. - * @param values - */ - private validateFmsConfig(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const fmsConfiguration = values.firewallManagerService; - if (!fmsConfiguration) { - return; - } - if (!helpers.accountExists(fmsConfiguration?.delegatedAdminAccount || '')) { - errors.push( - `Delegated Admin Account ${fmsConfiguration?.delegatedAdminAccount} name does not exist in Accounts configuration`, - ); - } - for (const channel of fmsConfiguration?.notificationChannels || []) { - this.validatFmsNotificationChannels(channel, helpers, errors); - } - } - - private validatFmsNotificationChannels( - notificationChannel: FirewallManagerNotificationChannelConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (!helpers.snsTopicExists(notificationChannel.snsTopic)) { - errors.push(`The SNS Topic name ${notificationChannel.snsTopic} for the notification channel does not exist.`); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/gateway-load-balancers-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/gateway-load-balancers-validator.ts deleted file mode 100644 index 631e003..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/gateway-load-balancers-validator.ts +++ /dev/null @@ -1,318 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { isNetworkType } from '../../lib/common'; -import { - CustomizationsConfig, - Ec2FirewallAutoScalingGroupConfig, - Ec2FirewallInstanceConfig, - TargetGroupItemConfig, -} from '../../lib/customizations-config'; -import { GwlbConfig, NetworkConfig, SubnetConfig, VpcConfig, VpcTemplatesConfig } from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; - -/** - * Class to validate Gateway LoadBalancers - */ -export class GatewayLoadBalancersValidator { - constructor( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - // - // Validate gateway load balancers deployment account names - // - this.validateGwlbDeploymentTargetAccounts(values, helpers, errors); - - // - // Validate GWLB configuration - // - this.validateGwlbConfiguration(values, helpers, errors, customizationsConfig); - } - - /** - * Function to validate existence of GWLB deployment target accounts - * Make sure deployment target accounts are part of account config file - * @param values - * @param helpers - * @param errors - */ - private validateGwlbDeploymentTargetAccounts( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const gwlb of values.centralNetworkServices?.gatewayLoadBalancers ?? []) { - for (const endpoint of gwlb.endpoints ?? []) { - if (!helpers.accountExists(endpoint.account)) { - errors.push( - `Deployment target account ${endpoint.account} for Gateway Load Balancer ${gwlb.name} endpoint ${endpoint.name} does not exist in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Validate Gateway Load Balancer endpoint configuration - * @param gwlb - * @param helpers - * @param values - */ - private validateGwlbEndpoints(gwlb: GwlbConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const gwlbEndpoint of gwlb.endpoints ?? []) { - const vpc = helpers.getVpc(gwlbEndpoint.vpc); - if (!vpc) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} endpoint ${gwlbEndpoint.name}]: VPC ${gwlbEndpoint.vpc} does not exist`, - ); - } - - // Validate subnet - if (vpc && !helpers.getSubnet(vpc, gwlbEndpoint.subnet)) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} endpoint ${gwlbEndpoint.name}]: subnet ${gwlbEndpoint.subnet} does not exist in VPC ${vpc.name}`, - ); - } - } - } - - /** - * Validate Gateway Load Balancer configuration - * @param values - * @param helpers - * @param errors - * @param customizationsConfig - */ - private validateGwlbConfiguration( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - for (const gwlb of values.centralNetworkServices?.gatewayLoadBalancers ?? []) { - const vpc = helpers.getVpc(gwlb.vpc); - if (!vpc) { - errors.push(`[Gateway Load Balancer ${gwlb.name}]: VPC ${gwlb.vpc} does not exist`); - } - - // Validate VPC and subnets - if (vpc) { - this.validateGwlbVpc(values, gwlb, vpc, errors); - this.validateGwlbSubnets(gwlb, vpc, helpers, errors); - } - // Validate endpoints - this.validateGwlbEndpoints(gwlb, helpers, errors); - // Validate target groups - if (gwlb.targetGroup) { - this.validateGwlbTargetGroup(gwlb, errors, customizationsConfig); - } - } - } - - /** - * Validate VPC is correct type and deployed to delegated admin account - * @param values - * @param gwlb - * @param vpc - * @param errors - */ - private validateGwlbVpc( - values: NetworkConfig, - gwlb: GwlbConfig, - vpc: VpcConfig | VpcTemplatesConfig, - errors: string[], - ) { - if (isNetworkType('IVpcTemplatesConfig', vpc)) { - errors.push( - `[Gateway Load Balancer ${gwlb.name}]: VPC templates are not a supported target VPC type for Gateway Load Balancer`, - ); - } else { - const vpcAccount = gwlb.account ? gwlb.account : values.centralNetworkServices!.delegatedAdminAccount; - if (vpc.account !== vpcAccount) { - errors.push( - `[Gateway Load Balancer ${gwlb.name}]: VPC "${vpc.name}" is not deployed to account "${vpcAccount}". Gateway Load Balancers must either be deployed to the delegated admin account or the account specified in the "account" property`, - ); - } - } - } - - /** - * Validate GWLB subnets - * @param gwlb - * @param vpc - * @param helpers - * @param errors - */ - private validateGwlbSubnets( - gwlb: GwlbConfig, - vpc: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate subnets exist in VPC - const validSubnets: SubnetConfig[] = []; - for (const gwlbSubnet of gwlb.subnets ?? []) { - const subnet = helpers.getSubnet(vpc, gwlbSubnet); - if (!subnet) { - errors.push(`[Gateway Load Balancer ${gwlb.name}]: subnet ${gwlbSubnet} does not exist in VPC ${vpc.name}`); - } - if (subnet) { - validSubnets.push(subnet); - } - } - - // Validate subnets are in different AZs - if (validSubnets.length === gwlb.subnets.length) { - const azs = validSubnets.map(item => { - return item.availabilityZone ? item.availabilityZone : ''; - }); - - if (helpers.hasDuplicates(azs)) { - errors.push( - `[Gateway Load Balancer ${gwlb.name}]: targeted subnets reside in duplicate availability zones. Please target unique AZs. AZs targeted: ${azs}`, - ); - } - } - } - - /** - * Validate Gateway Load Balancer target group - * @param gwlb - * @param errors - * @param customizationsConfig - */ - private validateGwlbTargetGroup(gwlb: GwlbConfig, errors: string[], customizationsConfig?: CustomizationsConfig) { - // Pull values from customizations config - const firewallInstances = customizationsConfig?.firewalls?.instances; - const autoscalingGroups = customizationsConfig?.firewalls?.autoscalingGroups; - const targetGroups = customizationsConfig?.firewalls?.targetGroups; - - // Fetch target group from customizations config - let targetGroup: TargetGroupItemConfig | undefined = undefined; - if (!targetGroups) { - errors.push( - `[Gateway Load Balancer ${gwlb.name}]: target group ${gwlb.targetGroup} not found in customizations-config.yaml`, - ); - } else { - targetGroup = targetGroups.find(group => group.name === gwlb.targetGroup); - } - - if (!targetGroup) { - errors.push( - `[Gateway Load Balancer ${gwlb.name}]: target group ${gwlb.targetGroup} not found in customizations-config.yaml`, - ); - } - - if (targetGroup) { - this.validateTargetGroupProps(gwlb, targetGroup, errors); - } - - if (targetGroup && targetGroup.targets) { - this.validateTargetGroupTargets(gwlb, targetGroup, firewallInstances!, errors); - } - - if (targetGroup && !targetGroup.targets) { - this.validateTargetGroupAsg(gwlb, targetGroup, errors, autoscalingGroups); - } - } - - /** - * Validate target group properties - * @param gwlb - * @param targetGroup - * @param errors - */ - private validateTargetGroupProps(gwlb: GwlbConfig, targetGroup: TargetGroupItemConfig, errors: string[]) { - if (targetGroup.port !== 6081) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: only port 6081 is supported.`, - ); - } - if (targetGroup.protocol !== 'GENEVE') { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: only GENEVE protocol is supported.`, - ); - } - } - - /** - * Validate firewall instances and GWLB reside in the same VPC - * @param gwlb - * @param targetGroup - * @param firewallInstances - * @param errors - */ - private validateTargetGroupTargets( - gwlb: GwlbConfig, - targetGroup: TargetGroupItemConfig, - firewallInstances: Ec2FirewallInstanceConfig[], - errors: string[], - ) { - // Instance VPCs are validated in customizations config. We just need to grab the first element - const firewall = firewallInstances.find(instance => instance.name === targetGroup.targets![0]); - - if (!firewall) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: firewall instance ${ - targetGroup.targets![0] - } not found in customizations-config.yaml`, - ); - } - - if (firewall && firewall.vpc !== gwlb.vpc) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: targets do not exist in the same VPC as the load balancer`, - ); - } - } - - /** - * Validate ASG and GWLB reside in the same VPC - * @param gwlb - * @param targetGroup - * @param autoscalingGroups - * @param errors - */ - private validateTargetGroupAsg( - gwlb: GwlbConfig, - targetGroup: TargetGroupItemConfig, - errors: string[], - autoscalingGroups?: Ec2FirewallAutoScalingGroupConfig[], - ) { - // Validate ASGs exist in customizations-config - if (!autoscalingGroups) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: target group contains no targets and is not referenced in a firewall ASG. Either define instance targets or reference this target group in an ASG in customizations-config.yaml`, - ); - } else { - const asg = autoscalingGroups.find( - group => group.autoscaling.targetGroups && group.autoscaling.targetGroups[0] === targetGroup.name, - ); - - if (!asg) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: firewall ASG for target group not found in customizations-config.yaml`, - ); - } - - if (asg && asg.vpc !== gwlb.vpc) { - errors.push( - `[Gateway Load Balancer ${gwlb.name} target group ${targetGroup.name}]: targets do not exist in the same VPC as the load balancer`, - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/ipam-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/ipam-validator.ts deleted file mode 100644 index 46f2c78..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/ipam-validator.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { IPv4CidrRange } from 'ip-num'; -import { NetworkConfig, IpamConfig, IpamPoolConfig } from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; - -/** - * Class to validate ipam - */ -export class IpamValidator { - constructor(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate IPAM names are unique - // - this.validateIpamNames(values, helpers, errors); - // - // Validate IPAM regions are unique - // - this.validateIpamRegions(values, helpers, errors); - // - // Validate Ipam deployment Ou names - this.validateIpamPoolShareTargetOUs(values, helpers, errors); - // - // Validate Ipam deployment account names - // - this.validateIpamPoolShareTargetAccounts(values, helpers, errors); - // - // Validate IPAM pools - // - this.validateIpamPoolConfigurations(values, helpers, errors); - } - - /** - * Method to validate uniqueness of IPAM names - * - * @param values - * @param helpers - * @param helpers - */ - private validateIpamNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const ipams = values.centralNetworkServices?.ipams; - if (ipams) { - const ipamNames = ipams.map(ipam => { - return ipam.name; - }); - - // Validate IPAM names are unique - if (helpers.hasDuplicates(ipamNames)) { - errors.push(`Duplicate IPAM names exist. IPAM names must be unique. IPAM names in file: ${ipamNames}`); - } - - // Validate scope and pool names - for (const ipam of ipams) { - this.validateIpamScopeNames(ipam, helpers, errors); - this.validateIpamPoolNames(ipam, helpers, errors); - } - } - } - - /** - * Validate uniqueness of IPAM regions - * @param values - * @param helpers - * @param errors - */ - private validateIpamRegions(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const ipams = values.centralNetworkServices?.ipams; - if (ipams) { - const ipamRegions = ipams.map(ipam => { - return ipam.region; - }); - - if (helpers.hasDuplicates(ipamRegions)) { - errors.push( - `Duplicate IPAM regions exist. You may only deploy one IPAM per region. IPAM regions in file: ${ipamRegions}`, - ); - } - } - } - - /** - * Validate uniqueness of IPAM scope names - * @param ipam - * @param helpers - * @param errors - */ - private validateIpamScopeNames(ipam: IpamConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - if (ipam.scopes) { - const scopeNames = ipam.scopes.map(scope => { - return scope.name; - }); - - if (helpers.hasDuplicates(scopeNames)) { - errors.push( - `[IPAM ${ipam.name}]: duplicate IPAM scope names exist. IPAM scope names must be unique. IPAM scope names for this IPAM: ${scopeNames}`, - ); - } - } - } - - /** - * Validate uniqueness of IPAM pool names - * @param ipam - * @param helpers - * @param errors - */ - private validateIpamPoolNames(ipam: IpamConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - if (ipam.pools) { - const poolNames = ipam.pools.map(pool => { - return pool.name; - }); - - if (helpers.hasDuplicates(poolNames)) { - errors.push( - `[IPAM ${ipam.name}]: duplicate IPAM pool names exist. IPAM pool names must be unique. IPAM pool names for this IPAM: ${poolNames}`, - ); - } - } - } - - /** - * Function to validate existence of IPAM pool deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - * @param helpers - * @param errors - */ - private validateIpamPoolShareTargetOUs(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const ipam of values.centralNetworkServices?.ipams ?? []) { - for (const pool of ipam.pools ?? []) { - for (const ou of pool.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for IPAM pool ${pool.name} does not exist in organization-config.yaml file`, - ); - } - } - } - } - } - - /** - * Function to validate existence of IPAM pool deployment target accounts - * Make sure deployment target accounts are part of account config file - * @param values - * @param helpers - * @param errors - */ - private validateIpamPoolShareTargetAccounts( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const ipam of values.centralNetworkServices?.ipams ?? []) { - for (const pool of ipam.pools ?? []) { - for (const account of pool.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for IPAM pool ${pool.name} does not exist in accounts-config.yaml file`, - ); - } - } - } - } - } - - /** - * Validate IPAM pool configurations - * @param values - * @param helpers - * @param errors - */ - private validateIpamPoolConfigurations(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const ipam of values.centralNetworkServices?.ipams ?? []) { - const allPools = this.getPools(ipam); - let allValid = true; - - // Validate provisioned CIDRs - for (const pool of ipam.pools ?? []) { - const validCidrs = this.validateProvisionedCidrs(ipam, pool, helpers, errors); - if (!validCidrs) { - allValid = false; - } - } - - // Validate nested pools - if (allValid) { - this.validateNestedPools(ipam, allPools, errors); - } - } - } - - /** - * Get IPAM pools configured for a given IPAM - * @param ipam - * @returns - */ - private getPools(ipam: IpamConfig): Map { - const poolMap = new Map(); - for (const pool of ipam.pools ?? []) { - poolMap.set(pool.name, pool); - } - return poolMap; - } - - /** - * Validate provisioned CIDRs are in CIDR format - * @param ipam - * @param pool - * @param helpers - * @param errors - */ - private validateProvisionedCidrs( - ipam: IpamConfig, - pool: IpamPoolConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - for (const cidr of pool.provisionedCidrs ?? []) { - if (!helpers.isValidIpv4Cidr(cidr)) { - errors.push( - `[IPAM ${ipam.name} pool ${pool.name}]: provisioned CIDR ${cidr} is invalid. Please enter a valid CIDR`, - ); - return false; - } - } - return true; - } - - /** - * Validate nested IPAM pools - * @param ipam - * @param allPools - * @param errors - */ - private validateNestedPools(ipam: IpamConfig, allPools: Map, errors: string[]) { - for (const pool of ipam.pools ?? []) { - if (pool.sourceIpamPool) { - // Validate that the base pool exists - const basePool = allPools.get(pool.sourceIpamPool); - if (!basePool) { - errors.push(`[IPAM ${ipam.name} pool ${pool.name}] source IPAM pool ${pool.sourceIpamPool} does not exist`); - } - - // Validate that the provisioned CIDRs are contained within the base pool - if (basePool) { - this.validateNestedPoolCidrs(ipam, basePool, pool, errors); - } - } - } - } - - /** - * Validate CIDRs within nested pools are contained in the base pool - * @param ipam - * @param basePool - * @param nestedPool - * @param errors - */ - private validateNestedPoolCidrs( - ipam: IpamConfig, - basePool: IpamPoolConfig, - nestedPool: IpamPoolConfig, - errors: string[], - ) { - const validCidrs: string[] = []; - if (nestedPool.provisionedCidrs) { - for (const baseRangeString of basePool.provisionedCidrs ?? []) { - const baseRange = IPv4CidrRange.fromCidr(baseRangeString); - - for (const nestedRangeString of nestedPool.provisionedCidrs ?? []) { - const nestedRange = IPv4CidrRange.fromCidr(nestedRangeString); - - if (nestedRange.inside(baseRange) || nestedRange.isEquals(baseRange)) { - validCidrs.push(nestedRangeString); - } - } - } - - if (validCidrs.length !== nestedPool.provisionedCidrs.length) { - errors.push( - `[IPAM ${ipam.name} pool ${nestedPool.name}] nested pool contains provisioned CIDRs that are not within source pool ${basePool.name}. Source pool: ${basePool.provisionedCidrs} Nested pool: ${nestedPool.provisionedCidrs}`, - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/network-config-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/network-config-validator.ts deleted file mode 100644 index aefaab5..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/network-config-validator.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { AccountConfig, AccountsConfig, GovCloudAccountConfig } from '../../lib/accounts-config'; -import { CustomizationsConfig } from '../../lib/customizations-config'; -import { GlobalConfig } from '../../lib/global-config'; -import { NetworkConfig } from '../../lib/network-config'; -import { OrganizationConfig } from '../../lib/organization-config'; -import { SecurityConfig } from '../../lib/security-config'; -import { CentralNetworkValidator } from './central-network-validator'; -import { CertificatesValidator } from './certificates-validator'; -import { CustomerGatewaysValidator } from './customer-gateways-validator'; -import { DhcpOptionsValidator } from './dhcp-options-validator'; -import { DirectConnectGatewaysValidator } from './direct-connect-gateways-validator'; -import { EndpointPoliciesValidator } from './endpoint-policies-validator'; -import { FirewallManagerValidator } from './firewall-manager-validator'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import { PrefixListValidator } from './prefix-list-validator'; -import { TransitGatewayValidator } from './transit-gateway-validator'; -import { VpcValidator } from './vpc-validator'; -import { ReplacementsConfig } from '../../lib/replacements-config'; - -/** - * Network Configuration validator. - * Validates network configuration - */ -export class NetworkConfigValidator { - constructor( - values: NetworkConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - organizationConfig: OrganizationConfig, - securityConfig: SecurityConfig, - replacementsConfig: ReplacementsConfig | undefined, - configDir: string, - customizationsConfig?: CustomizationsConfig, - ) { - const ouIdNames: string[] = ['Root']; - - const errors: string[] = []; - const logger = createLogger(['network-config-validator']); - - logger.info(`${NetworkConfig.FILENAME} file validation started`); - - // - // Get list of OU ID names from organization config file - ouIdNames.push(...this.getOuIdNames(organizationConfig)); - - // - // Get list of Account names from account config file - const accounts = this.getAccounts(accountsConfig); - - // - // Get the list of sns topic names from global and security config files - const snsTopicNames = this.getSnsTopicNames(globalConfig, securityConfig); - - // - // Instantiate helper method class - const helpers = new NetworkValidatorFunctions( - values, - ouIdNames, - accounts, - snsTopicNames, - globalConfig.enabledRegions, - ); - - // - // Start Validation - new CentralNetworkValidator(values, configDir, helpers, errors, customizationsConfig); - new TransitGatewayValidator(values, helpers, errors); - new DhcpOptionsValidator(values, helpers, errors); - new EndpointPoliciesValidator(values, replacementsConfig, configDir, helpers, errors); - new PrefixListValidator(values, helpers, errors); - new VpcValidator(values, helpers, errors, customizationsConfig); - new CustomerGatewaysValidator(values, helpers, errors, customizationsConfig); - new DirectConnectGatewaysValidator(values, errors); - new FirewallManagerValidator(values, helpers, errors); - new CertificatesValidator(values, errors); - - if (errors.length) { - throw new Error(`${NetworkConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - /** - * Prepare list of OU ids from organization config file - * @param organizationConfig - * @returns - */ - private getOuIdNames(organizationConfig: OrganizationConfig): string[] { - const ouIdNames: string[] = []; - for (const organizationalUnit of organizationConfig.organizationalUnits) { - ouIdNames.push(organizationalUnit.name); - } - return ouIdNames; - } - - /** - * Prepare list of Account names from account config file - * @param accountsConfig - */ - private getAccounts(accountsConfig: AccountsConfig): (AccountConfig | GovCloudAccountConfig)[] { - const accounts: (AccountConfig | GovCloudAccountConfig)[] = []; - - for (const accountItem of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accounts.push(accountItem); - } - return accounts; - } - /** - * Prepare list of SNS Topic names from global and security config files - * @param configDir - */ - private getSnsTopicNames(globalConfig: GlobalConfig, securityConfig: SecurityConfig): string[] { - const snsTopicNames: string[] = []; - const securitySnsSubscriptions = - securityConfig.centralSecurityServices.snsSubscriptions?.map(snsSubscription => snsSubscription.level) ?? []; - const globalSnsSubscriptions = globalConfig.snsTopics?.topics.map(topic => topic.name) ?? []; - snsTopicNames.push(...securitySnsSubscriptions); - snsTopicNames.push(...globalSnsSubscriptions); - - return snsTopicNames; - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/network-firewall-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/network-firewall-validator.ts deleted file mode 100644 index 973c0ec..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/network-firewall-validator.ts +++ /dev/null @@ -1,1440 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import path from 'path'; -import * as fs from 'fs'; -import { - NetworkConfig, - NfwRuleGroupConfig, - NfwStatelessRulesAndCustomActionsConfig, - NfwRuleSourceStatelessRuleDefinitionConfig, - NfwRuleSourceStatelessMatchAttributesConfig, - NfwFirewallPolicyConfig, - NfwRuleSourceCustomActionConfig, - NfwRuleSourceStatefulRuleConfig, - NfwRuleSourceStatefulRuleOptionsConfig, - NfwRuleVariableDefinitionConfig, - NfwFirewallConfig, - SubnetConfig, -} from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import { isNetworkType } from '../../lib/common'; -import { NfwStatelessRuleActionType } from '../../lib/models/network-config'; - -/** - * Class to validate network firewall - */ -export class NetworkFirewallValidator { - constructor(values: NetworkConfig, configDir: string, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate rule groups - // - this.validateRuleGroups(values, helpers, errors); - // - // Valiodate rule variables - // - this.validateRuleVariables(values, helpers, errors); - // - // Validate suricata rule file - // - this.validateSuricataFile(values, configDir, errors); - // - // Validate policies - // - this.validatePolicies(values, helpers, errors); - // - // Validate firewalls - // - this.validateFirewalls(values, helpers, errors); - } - - private validateRuleGroups(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate rule group names - // - this.validateRuleGroupNames(values, helpers, errors); - // - // Validate rule group share target OUs - // - this.validateRuleGroupShareTargetOus(values, helpers, errors); - // - // Validate rule group share target account names - // - this.validateRuleGroupShareTargetAccountNames(values, helpers, errors); - // - // Confirm that the rule group definition is valid - // - const allValid = this.validateRulesSourceDefinition(values, helpers, errors); - - if (allValid) { - // - // Validate rule group rules - // - this.validateRuleGroupRules(values, helpers, errors); - } - } - - /** - * Validate uniqueness of rule group names - * @param values - * @param helpers - * @param errors - */ - private validateRuleGroupNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const ruleGroups = values.centralNetworkServices?.networkFirewall?.rules; - if (ruleGroups) { - const ruleGroupNames = ruleGroups.map(group => { - return group.name; - }); - - if (helpers.hasDuplicates(ruleGroupNames)) { - errors.push( - `Duplicate Network Firewall rule group names exist. Rule group names must be unique. Rule group names in file: ${ruleGroupNames}`, - ); - } - - for (const name of ruleGroupNames) { - if (!helpers.matchesRegex(name, '^[a-zA-Z0-9-]+$')) { - errors.push( - `Network Firewall rule group name "${name}" is invalid. Rule group names must match the pattern "^[a-zA-Z0-9-]+$"`, - ); - } - } - } - } - - /** - * Validate rule group share target OUs - * @param values - * @param helpers - * @param errors - */ - private validateRuleGroupShareTargetOus(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const group of values.centralNetworkServices?.networkFirewall?.rules ?? []) { - for (const ou of group.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for Network Firewall rule group ${group.name} does not exist in organization-config.yaml file`, - ); - } - } - } - } - - /** - * Validate rule group share target accounts - * @param values - * @param helpers - * @param errors - */ - private validateRuleGroupShareTargetAccountNames( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const group of values.centralNetworkServices?.networkFirewall?.rules ?? []) { - for (const account of group.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for Network Firewall rule group ${group.name} does not exist in accounts-config.yaml file`, - ); - } - } - } - } - - /** - * Validate that each rule group only contains a single rule source - * @param values - * @param errors - * @returns - */ - private validateRulesSourceDefinition(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - let allValid = true; - for (const rule of values.centralNetworkServices?.networkFirewall?.rules ?? []) { - const ruleSource = rule.ruleGroup?.rulesSource; - - if (ruleSource) { - const keys = helpers.getObjectKeys(ruleSource); - - if (keys.length > 1) { - allValid = false; - errors.push( - `[Network Firewall rule group ${rule.name}]: rules source has multiple properties defined. Please only define a single rules source per rule group. Rules sources for this rule: ${keys}`, - ); - } - } - } - return allValid; - } - - /** - * Validate rules in each rule group type - * @param values - * @param helpers - * @param errors - */ - private validateRuleGroupRules(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.centralNetworkServices?.networkFirewall?.rules.forEach(rule => { - const rulesSource = rule.ruleGroup?.rulesSource; - if (rulesSource) { - // Validate stateless rules and custom actions - if (rulesSource.statelessRulesAndCustomActions) { - this.validateStatelessRuleGroups(rule, helpers, errors); - this.validateCustomActions(rule, helpers, errors); - } - // Validate stateful rules - if (rulesSource.statefulRules) { - this.validateStatefulRuleGroups(rule, helpers, errors); - } - // Validate domain lists - if (rulesSource.rulesSourceList) { - this.validateDomainList(rule, helpers, errors); - } - // Validate rule strings - if (rulesSource.rulesString) { - this.validateRuleString(rule, errors); - } - } - }); - } - - /** - * Validate stateless rule groups - * @param rule - * @param helpers - * @param errors - */ - private validateStatelessRuleGroups(rule: NfwRuleGroupConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const statelessRules = rule.ruleGroup!.rulesSource.statelessRulesAndCustomActions!; - // Validate the rule type is STATELESS - if (rule.type !== 'STATELESS') { - errors.push( - `[Network Firewall rule group ${rule.name}]: rule group type for rulesSource statelessRulesAndCustomActions must be STATELESS`, - ); - } - - // Validate priorities are unique - const priorities = statelessRules.statelessRules.map(item => { - return item.priority.toString(); - }); - if (helpers.hasDuplicates(priorities)) { - errors.push( - `[Network Firewall rule group ${rule.name}]: Duplicate priorities in rule group. Please assign unique priority values. Priorities in rule group: ${priorities}`, - ); - } - - // Validate priorities are within constraints - let allValid = true; - for (const priority of priorities) { - if (parseInt(priority) < 1 || parseInt(priority) > 65535) { - allValid = false; - } - } - - if (!allValid) { - errors.push( - `[Network firewall rule group ${rule.name}]: Invalid priority value in rule group. Priority must be a number between 1 and 65535. Priorities in rule group: ${priorities}`, - ); - } - - // Validate if the rule group includes options only available to stateful groups - if (rule.ruleGroup?.ruleVariables || rule.ruleGroup?.statefulRuleOptions) { - errors.push( - `[Network firewall rule group ${rule.name}]: stateless rule groups cannot contain the ruleVariables or statefulRuleOptions properties`, - ); - } - - // Validate rule definition - this.validateStatelessRuleDefinitions(rule, statelessRules, helpers, errors); - } - - /** - * Validate stateless rule group definitions - * @param rule - * @param statelessRules - * @param helpers - * @param errors - */ - private validateStatelessRuleDefinitions( - rule: NfwRuleGroupConfig, - statelessRules: NfwStatelessRulesAndCustomActionsConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const ruleItem of statelessRules.statelessRules) { - const definition = ruleItem.ruleDefinition; - - // Validate ports and CIDRs - this.validateStatelessRuleDefinitionHeader(rule, definition, helpers, errors); - } - } - - /** - * Validate IP header details for stateless rule groups - * @param rule - * @param definition - * @param helpers - * @param errors - */ - private validateStatelessRuleDefinitionHeader( - rule: NfwRuleGroupConfig, - definition: NfwRuleSourceStatelessRuleDefinitionConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const attributes = definition.matchAttributes; - // Validate CIDRs - const cidrs = [...(attributes.sources ?? []), ...(attributes.destinations ?? [])] ?? []; - cidrs.forEach(cidr => { - if (!helpers.isValidIpv4Cidr(cidr)) { - errors.push( - `[Network Firewall rule group ${rule.name}]: invalid CIDR ${cidr} in matchAttributes configuration`, - ); - } - }); - - // Validate ports - if (attributes.destinationPorts || attributes.sourcePorts) { - const isValidProtocol = attributes.protocols - ? attributes.protocols.includes(6) || attributes.protocols.includes(17) - : true; - - if (!isValidProtocol) { - errors.push( - `[Network Firewall rule group ${rule.name}]: matchAttributes protocols must include 6 (TCP) and/or 17 (UDP) if sourcePorts or destinationPorts are defined`, - ); - } - - // Validate port range values - this.validateStatelessRuleDefinitionPortRanges(rule, attributes, errors); - } - - // Validate TCP flags - if (attributes.tcpFlags) { - this.validateStatelessRuleDefinitionTcpFlags(rule, attributes, errors); - } - } - - /** - * Validate stateless rule group port ranges - * @param rule - * @param attributes - * @param errors - */ - private validateStatelessRuleDefinitionPortRanges( - rule: NfwRuleGroupConfig, - attributes: NfwRuleSourceStatelessMatchAttributesConfig, - errors: string[], - ) { - // Validate protocol number - const isTcpProtocol = attributes.protocols ? attributes.protocols.includes(6) : true; - - if (!isTcpProtocol) { - errors.push( - `[Network Firewall rule group ${rule.name}]: matchAttributes protocols must include 6 (TCP) if tcpFlags are defined`, - ); - } - // Validate attributes - const portRanges = [...(attributes.destinationPorts ?? []), ...(attributes.sourcePorts ?? [])] ?? []; - portRanges.forEach(portRange => { - const isValidPortRange = portRange.fromPort <= portRange.toPort; - const portRangeString = `fromPort: ${portRange.fromPort}, toPort: ${portRange.toPort}`; - - if (!isValidPortRange) { - errors.push( - `[Network Firewall rule group ${rule.name}]: fromPort must be less than or equal to toPort. Defined port range: ${portRangeString}`, - ); - } - - if (isValidPortRange && (portRange.fromPort < 0 || portRange.fromPort > 65535)) { - errors.push( - `[Network Firewall rule group ${rule.name}]: fromPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - - if (isValidPortRange && (portRange.toPort < 0 || portRange.toPort > 65535)) { - errors.push( - `[Network Firewall rule group ${rule.name}]: toPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - }); - } - - /** - * Validate stateless rule definition TCP flags - * @param rule - * @param attributes - * @param errors - */ - private validateStatelessRuleDefinitionTcpFlags( - rule: NfwRuleGroupConfig, - attributes: NfwRuleSourceStatelessMatchAttributesConfig, - errors: string[], - ) { - for (const flagItem of attributes.tcpFlags ?? []) { - const nonMatchingFlags = flagItem.flags.filter(item => !flagItem.masks.includes(item)); - - if (nonMatchingFlags.length > 0) { - errors.push( - `[Network Firewall rule group ${rule.name}]: invalid TCP flags contained in rule definition. If masks are defined, flags can only contain values also contained in masks. Flags: ${flagItem.flags}, Masks: ${flagItem.masks}`, - ); - } - } - } - - /** - * Validate stateless custom actions - * @param rule - * @param helpers - * @param errors - */ - private validateCustomActions(rule: NfwRuleGroupConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const customActions = rule.ruleGroup?.rulesSource.statelessRulesAndCustomActions?.customActions; - const statelessRules = rule.ruleGroup?.rulesSource.statelessRulesAndCustomActions?.statelessRules; - let allValid = true; - - for (const ruleItem of statelessRules ?? []) { - for (const action of ruleItem.ruleDefinition.actions) { - if (!isNetworkType('NfwStatelessRuleActionType', action) && !customActions) { - errors.push( - `[Network Firewall rule group ${rule.name}]: ruleDefinition custom action "${action}" is invalid. No matching actionName defined under the customActions property`, - ); - } - } - } - - if (customActions) { - allValid = this.validateCustomActionNames(rule, customActions, helpers, errors); - } - - if (customActions && allValid) { - this.validateStatelessRuleActions(rule, customActions, errors); - } - } - - /** - * Validate custom action names and dimensions - * @param resource - * @param customActions - * @param helpers - * @param errors - * @returns - */ - private validateCustomActionNames( - resource: NfwRuleGroupConfig | NfwFirewallPolicyConfig, - customActions: NfwRuleSourceCustomActionConfig[], - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const actionNames = customActions.map(item => { - return item.actionName; - }); - let allValid = true; - const resourceType = isNetworkType('INfwRuleGroupConfig', resource) ? 'rule group' : 'policy'; - - // Check if duplicate action names are defined - if (helpers.hasDuplicates(actionNames)) { - allValid = false; - errors.push( - `[Network Firewall ${resourceType} ${resource.name}]: customActions definition has duplicate actionNames defined. Please define unique actionNames. actionNames in file: ${actionNames}`, - ); - } - - // Validate action name regex - for (const actionName of actionNames) { - if (!helpers.matchesRegex(actionName, '^[a-zA-Z0-9]+$')) { - allValid = false; - errors.push( - `[Network Firewall ${resourceType} ${resource.name}]: customActions actionName "${actionName}" is invalid. actionName must match regular expression "^[a-zA-Z0-9]+$"`, - ); - } - } - - // Validate action definition regex - for (const action of customActions) { - for (const dimension of action.actionDefinition.publishMetricAction.dimensions) { - if (!helpers.matchesRegex(dimension, '^[a-zA-Z0-9-_ ]+$')) { - allValid = false; - errors.push( - `[Network Firewall ${resourceType} ${resource.name}]: customActions actionDefinition dimension "${dimension}" is invalid. Dimension must match regular expression "^[a-zA-Z0-9-_ ]+$"`, - ); - } - } - } - return allValid; - } - - /** - * Validate stateless custom actions defined in a rule definition - * @param rule - * @param customActions - * @param errors - */ - private validateStatelessRuleActions( - rule: NfwRuleGroupConfig, - customActions: NfwRuleSourceCustomActionConfig[], - errors: string[], - ) { - const ruleDefinitions = rule.ruleGroup?.rulesSource.statelessRulesAndCustomActions?.statelessRules; - const actionNames = customActions.map(item => { - return item.actionName; - }); - - for (const definition of ruleDefinitions ?? []) { - for (const action of definition.ruleDefinition.actions) { - if ( - !isNetworkType('NfwStatelessRuleActionType', action) && - !actionNames.includes(action) - ) { - errors.push( - `[Network Firewall rule group ${rule.name}]: ruleDefinition custom action "${action}" is invalid. Custom actions must be defined under the customActions property`, - ); - } - } - } - } - - private validateStatefulRuleGroups(rule: NfwRuleGroupConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const statefulRules = rule.ruleGroup!.rulesSource.statefulRules!; - // Validate rule type - if (rule.type !== 'STATEFUL') { - errors.push( - `[Network Firewall rule group ${rule.name}]: rule group type for rulesSource statefulRules must be STATEFUL`, - ); - } - - // Validate sids are unique - const allValid = this.validateStatefulSids(rule, statefulRules, helpers, errors); - - // Validate rule definitions - if (allValid) { - for (const ruleItem of statefulRules) { - // Validate rule header - this.validateStatefulRuleHeader(rule, ruleItem, helpers, errors); - // Validate rule options - this.validateStatefulRuleOptions(rule, ruleItem, helpers, errors); - } - } - } - - /** - * Returns true if stateful rules all have unique and valid sid options - * @param rule - * @param statefulRules - * @param helpers - * @param errors - * @returns - */ - private validateStatefulSids( - rule: NfwRuleGroupConfig, - statefulRules: NfwRuleSourceStatefulRuleConfig[], - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - let allValid = true; - - // Retrieve sids from rule options - const sids: NfwRuleSourceStatefulRuleOptionsConfig[] = []; - for (const ruleItem of statefulRules) { - for (const optionItem of ruleItem.ruleOptions) { - if (optionItem.keyword === 'sid') { - sids.push(optionItem); - } - } - } - - // Validate there is a sid for each rule - if (sids.length < statefulRules.length) { - allValid = false; - errors.push( - `[Network Firewall rule group ${rule.name}]: one or more stateful rule definition rule options does not include "sid" keyword. "sid" keyword is required for all stateful rules`, - ); - } - if (sids.length > statefulRules.length) { - allValid = false; - errors.push( - `[Network Firewall rule group ${rule.name}]: one or more stateful rule definition rule options includes multiple "sid" keywords. Only one "sid" keyword may be defined for all stateful rules`, - ); - } - - // Validate sid ids - const idsValid = this.validateStatefulSidSettings(rule, sids, helpers, errors); - if (!idsValid) { - allValid = false; - } - - if (idsValid) { - // Validate sids are unique - const ids = sids.map(item => { - return item.settings![0]; - }); - - if (helpers.hasDuplicates(ids)) { - allValid = false; - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule options "sid" IDs contain duplicates. "sid" IDs must be unique`, - ); - } - } - return allValid; - } - - /** - * Returns true if sid settings values are unique and valid - * @param rule - * @param sids - * @param helpers - * @param errors - * @returns - */ - private validateStatefulSidSettings( - rule: NfwRuleGroupConfig, - sids: NfwRuleSourceStatefulRuleOptionsConfig[], - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - // Validate sid settings - let idsValid = true; - for (const sid of sids) { - if (!sid.settings) { - idsValid = false; - errors.push( - `[Network Firewall rule group ${rule.name}]: one or more stateful rule options "sid" keywords does not have a corresponding ID set in settings`, - ); - } - - if (sid.settings && (sid.settings.length > 1 || !helpers.matchesRegex(sid.settings[0], '^\\d+$'))) { - idsValid = false; - errors.push( - `[Network Firewall rule group ${rule.name}]: one or more stateful rule options "sid" IDs are invalid. "sid" IDs must be a number in string quotes`, - ); - } - } - return idsValid; - } - - /** - * Validate stateful rule header object - * @param rule - * @param statefulRule - * @param helpers - * @param errors - */ - private validateStatefulRuleHeader( - rule: NfwRuleGroupConfig, - statefulRule: NfwRuleSourceStatefulRuleConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const anyArray = ['any', 'ANY']; - // Validate IP addresses - [statefulRule.header.destination, statefulRule.header.source].forEach(cidr => { - if (!anyArray.includes(cidr) && !helpers.isValidIpv4Cidr(cidr)) { - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule header IP source/destination "${cidr}" is invalid. Valid values are a CIDR range or the string "ANY"`, - ); - } - }); - // Validate port ranges - [statefulRule.header.destinationPort, statefulRule.header.sourcePort].forEach(port => { - // Validate the port is using the correct format - if (!anyArray.includes(port) && !helpers.matchesRegex(port, '^\\d{1,5}(:\\d{1,5})?$')) { - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule header source/destination port "${port}" is invalid. Valid values are a single port, port range separated by colon (1990:1994), or the string "ANY"`, - ); - } - // Validate port ranges - if (helpers.matchesRegex(port, '^\\d{1,5}:\\d{1,5}$')) { - const fromPort = parseInt(port.split(':')[0]); - const toPort = parseInt(port.split(':')[1]); - - if (fromPort > toPort) { - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule header source/destination port range "${port}" is invalid. fromPort is greater than toPort`, - ); - } - - if (fromPort < 0 || fromPort > 65535) { - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule header source/destination port range "${port}" is invalid. fromPort is outside range 0-65535`, - ); - } - - if (toPort < 0 || toPort > 65535) { - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule header source/destination port range "${port}" is invalid. toPort is outside range 0-65535`, - ); - } - } - }); - } - - /** - * Validate stateful rule options - * @param rule - * @param statefulRule - * @param helpers - * @param errors - */ - private validateStatefulRuleOptions( - rule: NfwRuleGroupConfig, - statefulRule: NfwRuleSourceStatefulRuleConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const keywords = statefulRule.ruleOptions.map(option => { - return option.keyword; - }); - - // Validate there are not duplicate keywords - if (helpers.hasDuplicates(keywords)) { - errors.push( - `[Network Firewall rule group ${rule.name}]: stateful rule options has duplicate keywords. Please define unique keywords. Keywords in file: ${keywords}`, - ); - } - } - - /** - * Validate stateful domain lists - * @param rule - * @param helpers - * @param errors - */ - private validateDomainList(rule: NfwRuleGroupConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const domainList = rule.ruleGroup!.rulesSource.rulesSourceList!; - // Validate rule type - if (rule.type !== 'STATEFUL') { - errors.push( - `[Network Firewall rule group ${rule.name}]: rule group type for rulesSource rulesSourceList must be STATEFUL`, - ); - } - // Validate targets - for (const target of domainList.targets) { - if (!helpers.matchesRegex(target, '^\\.?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$')) { - errors.push( - `[Network Firewall rule group ${rule.name}]: target "${target}" is invalid. Targets must be formatted ".example.com" for wildcard domains and "example.com" for explicit match domains`, - ); - } - } - } - - /** - * Validate stateful rule variables - * @param values - * @param helpers - * @param errors - */ - private validateRuleVariables(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const rule of values.centralNetworkServices?.networkFirewall?.rules ?? []) { - if (rule.ruleGroup?.ruleVariables) { - if (rule.type !== 'STATEFUL') { - errors.push( - `[Network Firewall rule group ${rule.name}]: ruleVariables may only be applied to STATEFUL rule groups`, - ); - } else { - this.validateRuleVariableDefinitions(rule, helpers, errors); - } - } - } - } - - /** - * Validate rule variable definitions - * @param rule - * @param helpers - * @param errors - */ - private validateRuleVariableDefinitions( - rule: NfwRuleGroupConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const ipSets = this.getRuleVariableDefinitions(rule.ruleGroup!.ruleVariables!.ipSets); - const portSets = this.getRuleVariableDefinitions(rule.ruleGroup!.ruleVariables!.portSets); - - // Validate CIDRs - ipSets.forEach(ipSet => { - ipSet.definition.forEach(cidr => { - if (!helpers.isValidIpv4Cidr(cidr)) { - errors.push( - `[Network Firewall rule group ${rule.name} rule variable ${ipSet.name}]: invalid CIDR ${cidr}. Value must be a valid IPv4 CIDR range`, - ); - } - }); - }); - - // Validate ports - portSets.forEach(portSet => { - portSet.definition.forEach(port => { - if (!helpers.matchesRegex(port, '^\\d{1,5}$')) { - errors.push( - `[Network Firewall rule group ${rule.name} rule variable ${portSet.name}]: invalid port "${port}". Valid value is a single TCP/UDP port between 0-65535`, - ); - } - if (helpers.matchesRegex(port, '^\\d{1,5}$') && (parseInt(port) < 0 || parseInt(port) > 65535)) { - errors.push( - `[Network Firewall rule group ${rule.name} rule variable ${portSet.name}]: invalid port "${port}". Valid value is a single TCP/UDP port between 0-65535`, - ); - } - }); - }); - } - - /** - * Returns an array of rule variable definitions - * @param definition - * @returns - */ - private getRuleVariableDefinitions( - definition: NfwRuleVariableDefinitionConfig | NfwRuleVariableDefinitionConfig[], - ): NfwRuleVariableDefinitionConfig[] { - const variableDefinitions: NfwRuleVariableDefinitionConfig[] = []; - - if (Array.isArray(definition)) { - variableDefinitions.push(...definition); - } else { - variableDefinitions.push(definition); - } - return variableDefinitions; - } - - private validateRuleString(rule: NfwRuleGroupConfig, errors: string[]) { - if (rule.type !== 'STATEFUL') { - errors.push( - `[Network Firewall rule group ${rule.name}]: rule group type for rulesSource rulesString must be STATEFUL`, - ); - } - - const suricataRuleActionType = ['alert', 'pass', 'drop', 'reject', 'rejectsrc', 'rejectdst', 'rejectboth']; - const ruleSplit = rule.ruleGroup!.rulesSource.rulesString!.split(' '); - if (!suricataRuleActionType.includes(ruleSplit[0])) { - errors.push( - `[Network Firewall rule group ${rule.name}]: invalid rule string. String must start with one of the following valid Suricata actions: ${suricataRuleActionType}`, - ); - } - } - - /** - * Function to validate Endpoint policy document file existence - * @param configDir - */ - private validateSuricataFile(values: NetworkConfig, configDir: string, errors: string[]) { - values.centralNetworkServices?.networkFirewall?.rules.forEach(rule => { - if (rule.ruleGroup?.rulesSource.rulesFile) { - if (!fs.existsSync(path.join(configDir, rule.ruleGroup?.rulesSource.rulesFile))) { - errors.push(`Suricata rules file ${rule.ruleGroup?.rulesSource.rulesFile} not found !!`); - } else { - const fileContent = fs.readFileSync(path.join(configDir, rule.ruleGroup?.rulesSource.rulesFile), 'utf8'); - const rules: string[] = []; - // Suricata supported action type list - // @link https://suricata.readthedocs.io/en/suricata-6.0.2/rules/intro.html#action - const suricataRuleActionType = ['alert', 'pass', 'drop', 'reject', 'rejectsrc', 'rejectdst', 'rejectboth']; - fileContent.split(/\r?\n/).forEach(line => { - const ruleAction = line.split(' ')[0]; - if (suricataRuleActionType.includes(ruleAction)) { - rules.push(line); - } - }); - - if (rules.length === 0) { - errors.push(`No rule definition found in suricata rules file ${rule.ruleGroup?.rulesSource.rulesFile}!!`); - } - - if (rule.type !== 'STATEFUL') { - errors.push( - `[Network Firewall rule group ${rule.name}]: rule group type for rulesSource rulesFile must be STATEFUL`, - ); - } - } - } - }); - } - - /** - * Validate firewall policies - * @param values - * @param helpers - * @param errors - */ - private validatePolicies(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate policy names - // - this.validatePolicyNames(values, helpers, errors); - // - // Validate policy share target OUs - // - this.validatePolicyShareTargetOus(values, helpers, errors); - // - // Validate policy share target accounts - // - this.validatePolicyShareTargetAccounts(values, helpers, errors); - // - // Validate policy rule groups - // - this.validatePolicyRuleGroups(values, helpers, errors); - // - // Validate policy custom actions - // - this.validatePolicyCustomActions(values, helpers, errors); - } - - /** - * Get all rule groups defined in configuration - * @param values - * @returns - */ - private getRuleGroups(values: NetworkConfig): Map { - const ruleGroups = new Map(); - values.centralNetworkServices?.networkFirewall?.rules.forEach(rule => { - ruleGroups.set(rule.name, rule); - }); - - return ruleGroups; - } - - /** - * Validate firewall policy names - * @param values - * @param helpers - * @param errors - */ - private validatePolicyNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const policies = values.centralNetworkServices?.networkFirewall?.policies; - if (policies) { - const policyNames = policies.map(policy => { - return policy.name; - }); - - if (helpers.hasDuplicates(policyNames)) { - errors.push( - `Duplicate Network Firewall policy names exist. Policy names must be unique. Policy names in file: ${policyNames}`, - ); - } - - for (const name of policyNames) { - if (!helpers.matchesRegex(name, '^[a-zA-Z0-9-]+$')) { - errors.push( - `Network Firewall policy name "${name}" is invalid. Policy names must match the pattern "^[a-zA-Z0-9-]+$"`, - ); - } - } - } - } - - /** - * Validate firewall policy share target OUs - * @param values - * @param helpers - * @param errors - */ - private validatePolicyShareTargetOus(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const policy of values.centralNetworkServices?.networkFirewall?.policies ?? []) { - for (const ou of policy.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for Network Firewall policy ${policy.name} does not exist in organization-config.yaml file`, - ); - } - } - } - } - - /** - * Validate firewall policy share target accounts - * @param values - * @param helpers - * @param errors - */ - private validatePolicyShareTargetAccounts( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const policy of values.centralNetworkServices?.networkFirewall?.policies ?? []) { - for (const account of policy.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for Network Firewall policy ${policy.name} does not exist in accounts-config.yaml file`, - ); - } - } - } - } - - /** - * Validate rule groups defined in firewall policies - * @param values - * @param helpers - * @param errors - */ - private validatePolicyRuleGroups(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const allRules = this.getRuleGroups(values); - for (const policy of values.centralNetworkServices?.networkFirewall?.policies ?? []) { - // Validate stateless policies - this.validatePolicyStatelessRuleGroups(policy, allRules, helpers, errors); - // Validate stateful policies - this.validatePolicyStatefulRuleGroups(policy, allRules, helpers, errors); - } - } - - /** - * Validate stateless rule groups defined in policy - * @param policy - * @param allRules - * @param helpers - * @param errors - */ - private validatePolicyStatelessRuleGroups( - policy: NfwFirewallPolicyConfig, - allRules: Map, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (policy.firewallPolicy.statelessRuleGroups) { - const statelessPolicyNames = policy.firewallPolicy.statelessRuleGroups.map(group => { - return group.name; - }); - const statelessPolicyPriorities = policy.firewallPolicy.statelessRuleGroups.map(ruleGroup => { - return ruleGroup.priority.toString(); - }); - - // Validate there are no duplicates - if (helpers.hasDuplicates(statelessPolicyNames)) { - errors.push( - `[Network Firewall policy ${policy.name}]: Duplicate stateless rule group references exist. Rule group reference names must be unique. Rule group names in file: ${statelessPolicyNames}`, - ); - } - if (helpers.hasDuplicates(statelessPolicyPriorities)) { - errors.push( - `[Network Firewall policy ${policy.name}]: Duplicate stateless rule group priorities exist. Rule group priorities must be unique. Rule group priorities in file: ${statelessPolicyPriorities}`, - ); - } - - // Validate priorities are within constraints - if (!this.validatePriorityValues(statelessPolicyPriorities)) { - errors.push( - `[Network Firewall policy ${policy.name}]: Invalid priority value in stateless rule group reference. Priority must be a number between 1 and 65535. Priorities in policy: ${statelessPolicyPriorities}`, - ); - } - - // Validate rule group references - this.validatePolicyRuleGroupReferences(policy, allRules, statelessPolicyNames, 'STATELESS', errors); - } - } - - /** - * Returns true if priority values are within constraints - * @param priorities - * @returns - */ - private validatePriorityValues(priorities: string[]): boolean { - let allValid = true; - for (const priority of priorities) { - if (parseInt(priority) < 1 || parseInt(priority) > 65535) { - allValid = false; - } - } - return allValid; - } - - /** - * Validate stateful rule groups - * @param policy - * @param allRules - * @param helpers - * @param errors - */ - private validatePolicyStatefulRuleGroups( - policy: NfwFirewallPolicyConfig, - allRules: Map, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (policy.firewallPolicy.statefulRuleGroups) { - const statefulPolicyNames = policy.firewallPolicy.statefulRuleGroups.map(group => { - return group.name; - }); - - // Validate there are no duplicates - if (helpers.hasDuplicates(statefulPolicyNames)) { - errors.push( - `[Network Firewall policy ${policy.name}]: Duplicate stateful rule group references exist. Rule group reference names must be unique. Rule group names in file: ${statefulPolicyNames}`, - ); - } - - // Validate STRICT ORDER is set if default actions are defined - if ( - policy.firewallPolicy.statefulDefaultActions && - policy.firewallPolicy.statefulEngineOptions !== 'STRICT_ORDER' - ) { - errors.push( - `[Network Firewall policy ${policy.name}]: STRICT_ORDER must be set for statefulEngineOptions property if defining statefulDefaultActions`, - ); - } - - // Validate STRICT_ORDER policies - this.validatePolicyStatefulStrictOrder(policy, helpers, errors); - // Validate eulw group references - this.validatePolicyRuleGroupReferences(policy, allRules, statefulPolicyNames, 'STATEFUL', errors); - } - } - - /** - * Validate stateful STRICT_ORDER policies - * @param policy - * @param helpers - * @param errors - */ - private validatePolicyStatefulStrictOrder( - policy: NfwFirewallPolicyConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const priorities: string[] = []; - const statefulRuleGroups = policy.firewallPolicy.statefulRuleGroups!; - statefulRuleGroups.forEach(reference => { - if (reference.priority) { - priorities.push(reference.priority.toString()); - } - }); - - if (priorities.length > 0) { - // Validate strict order is defined - const strictOrder = policy.firewallPolicy.statefulEngineOptions === 'STRICT_ORDER'; - if (!strictOrder) { - errors.push( - `[Network Firewall policy ${policy.name}]: STRICT_ORDER must be set for statefulEngineOptions property if defining rule group priority values`, - ); - } - // Validate all rule groups have a prioity set - if (strictOrder && statefulRuleGroups.length > priorities.length) { - errors.push( - `[Network Firewall policy ${policy.name}]: priority values must be set for each rule group when defining a STRICT_ORDER policy`, - ); - } - // Validate priorities are unique - if (strictOrder && helpers.hasDuplicates(priorities)) { - errors.push( - `[Network Firewall policy ${policy.name}]: Duplicate stateful rule group priorities exist. Rule group priorities must be unique. Rule group priorities in file: ${priorities}`, - ); - } - // Validate priority values - if (strictOrder && !this.validatePriorityValues(priorities)) { - errors.push( - `[Network Firewall policy ${policy.name}]: Invalid priority value in stateful rule group reference. Priority must be a number between 1 and 65535. Priorities in policy: ${priorities}`, - ); - } - } - } - - /** - * Validate rule group reference attributes - * @param policy - * @param allRules - * @param policyNames - * @param groupType - * @param errors - */ - private validatePolicyRuleGroupReferences( - policy: NfwFirewallPolicyConfig, - allRules: Map, - policyNames: string[], - groupType: 'STATEFUL' | 'STATELESS', - errors: string[], - ) { - for (const name of policyNames) { - const group = allRules.get(name); - // Validate rule group exists - if (!group) { - errors.push(`[Network Firewall policy ${policy.name}]: rule group "${name}" does not exist`); - } - - if (group) { - // Validate regions match - const regionMismatch = policy.regions.some(region => !group.regions.includes(region)); - if (regionMismatch) { - errors.push( - `[Network Firewall policy ${policy.name}]: rule group "${name}" is not deployed to one or more region(s) the policy is deployed to. Policy regions: ${policy.regions}; Rule group regions: ${group.regions}`, - ); - } - // Validate policy is the correct type - if (group.type !== groupType) { - errors.push( - `[Network Firewall policy ${policy.name}]: rule group reference "${name}" is not configured as a ${groupType} rule group type`, - ); - } - } - } - } - - /** - * Validate firewall policy stateless custom actions - * @param values - * @param helpers - * @param errors - */ - private validatePolicyCustomActions(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const policy of values.centralNetworkServices?.networkFirewall?.policies ?? []) { - const customActions = policy.firewallPolicy.statelessCustomActions; - - for (const action of [ - ...policy.firewallPolicy.statelessDefaultActions, - ...policy.firewallPolicy.statelessFragmentDefaultActions, - ]) { - if (!isNetworkType('NfwStatelessRuleActionType', action) && !customActions) { - errors.push( - `[Network Firewall policy ${policy.name}]: stateless custom action "${action}" is invalid. No matching actionName defined under the statelessCustomActions property`, - ); - } - } - - // Validate custom actions are all valid - let allValid = true; - if (customActions) { - allValid = this.validateCustomActionNames(policy, customActions, helpers, errors); - } - // Validate default actions - if (customActions && allValid) { - this.validatePolicyDefaultActions(policy, customActions, errors); - } - } - } - - /** - * Validate firewall policy stateless default actions - * @param policy - * @param customActions - * @param errors - */ - private validatePolicyDefaultActions( - policy: NfwFirewallPolicyConfig, - customActions: NfwRuleSourceCustomActionConfig[], - errors: string[], - ) { - const actionNames = customActions.map(item => { - return item.actionName; - }); - - for (const action of [ - ...policy.firewallPolicy.statelessDefaultActions, - ...policy.firewallPolicy.statelessFragmentDefaultActions, - ]) { - if ( - !isNetworkType('NfwStatelessRuleActionType', action) && - !actionNames.includes(action) - ) { - errors.push( - `[Network Firewall policy ${policy.name}]: stateless custom action "${action}" is invalid. No matching actionName defined under the statelessCustomActions property`, - ); - } - } - } - - private validateFirewalls(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate firewall names - // - this.validateFirewallNames(values, helpers, errors); - // - // Validate firewall deployment targets - // - this.validateFirewallDeploymentTargets(values, helpers, errors); - } - - /** - * Validate firewall names - * @param values - * @param helpers - * @param errors - */ - private validateFirewallNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const firewalls = values.centralNetworkServices?.networkFirewall?.firewalls; - - if (firewalls) { - const firewallNames = firewalls.map(firewall => { - return firewall.name; - }); - - // Validate there are no duplicate names - if (helpers.hasDuplicates(firewallNames)) { - errors.push( - `Duplicate Network Firewall firewall names exist. Firewall names must be unique. Firewall names in file: ${firewallNames}`, - ); - } - - // Validate regex - for (const name of firewallNames) { - if (!helpers.matchesRegex(name, '^[a-zA-Z0-9-]+$')) { - errors.push( - `Network Firewall name "${name}" is invalid. Firewall name must match the pattern "^[a-zA-Z0-9-]+$"`, - ); - } - } - } - } - - private validateFirewallDeploymentTargets( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const allPolicies = this.getPolicies(values); - for (const firewall of values.centralNetworkServices?.networkFirewall?.firewalls ?? []) { - // Validate VPC and subnet configuration - const vpcValid = this.validateFirewallVpc(firewall, helpers, errors); - - // Validate logging configurations - this.validateFirewallLoggingConfigurations(firewall, helpers, errors); - - if (vpcValid) { - // Validate VPC target account(s) with policies - this.validateFirewallTargetAccount(firewall, allPolicies, helpers, errors); - } - } - } - - /** - * Validate the target VPC and subnets for the firewall - * @param firewall - * @param helpers - * @param errors - * @returns - */ - private validateFirewallVpc( - firewall: NfwFirewallConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - // Validate VPC exists - let allValid = true; - const vpc = helpers.getVpc(firewall.vpc); - if (!vpc) { - allValid = false; - errors.push(`[Network Firewall firewall ${firewall.name}]: VPC "${firewall.vpc}" does not exist`); - } - - if (vpc) { - // Validate subnets exist within the VPC - const subnets: SubnetConfig[] = []; - firewall.subnets.forEach(subnetItem => { - const subnet = helpers.getSubnet(vpc, subnetItem); - if (!subnet) { - allValid = false; - errors.push( - `[Network Firewall firewall ${firewall.name}]: subnet "${subnetItem}" does not exist in VPC "${vpc.name}"`, - ); - } else { - subnets.push(subnet); - } - }); - - // Validate there are no duplicate subnet AZs/names - const azs: (string | number)[] = []; - const subnetNames: string[] = []; - subnets.forEach(item => { - azs.push(item.availabilityZone ? item.availabilityZone : ''); - subnetNames.push(item.name); - }); - if (helpers.hasDuplicates(azs)) { - allValid = false; - errors.push( - `[Network Firewall firewall ${firewall.name}]: subnets with duplicate AZs targeted. Subnet AZs must be unique. Subnet AZs in file: ${azs}`, - ); - } - if (helpers.hasDuplicates(subnetNames)) { - allValid = false; - errors.push( - `[Network Firewall firewall ${firewall.name}]: duplicate subnets targeted. Target subnets must be unique. Subnets in file: ${subnetNames}`, - ); - } - } - return allValid; - } - - /** - * Validate if there are duplicate firewall logging configuration types - * @param firewall - * @param helpers - * @param errors - */ - private validateFirewallLoggingConfigurations( - firewall: NfwFirewallConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (firewall.loggingConfiguration) { - // Validate there are no duplicates - const loggingTypes: string[] = []; - firewall.loggingConfiguration.forEach(config => loggingTypes.push(config.type)); - - if (helpers.hasDuplicates(loggingTypes)) { - errors.push( - `[Network Firewall firewall ${firewall.name}]: duplicate logging configuration types. Each logging type must be unique. Logging types in file: ${loggingTypes}`, - ); - } - } - } - - /** - * Validate firewall target account against firewall policy shares - * @param firewall - * @param allPolicies - * @param helpers - * @param errors - */ - private validateFirewallTargetAccount( - firewall: NfwFirewallConfig, - allPolicies: Map, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate policy exists - const firewallPolicy = allPolicies.get(firewall.firewallPolicy); - if (!firewallPolicy) { - errors.push( - `[Network Firewall firewall ${firewall.name}]: firewall policy "${firewall.firewallPolicy}" does not exist`, - ); - } else { - // Validate RAM shares exist in desired accounts - const vpc = helpers.getVpc(firewall.vpc)!; - const vpcAccountNames = helpers.getVpcAccountNames(vpc); - const policyAccountNames = helpers.getDelegatedAdminShareTargets(firewallPolicy.shareTargets); - const targetComparison = helpers.compareTargetAccounts(vpcAccountNames, policyAccountNames); - - if (targetComparison.length > 0) { - errors.push( - `[Network Firewall firewall ${firewall.name}]: firewall policy "${firewall.firewallPolicy}" is not shared with one or more target OU(s)/account(s) for VPC "${vpc.name}." Missing accounts: ${targetComparison}`, - ); - } - // Validate regions match - if (!firewallPolicy.regions.includes(vpc.region)) { - errors.push( - `[Network Firewall firewall ${firewall.name}]: firewall policy "${firewall.firewallPolicy}" target region(s) do not match region for VPC "${vpc.name}." Policy regions: ${firewallPolicy.regions}; VPC region: ${vpc.region}`, - ); - } - } - } - - /** - * Return all policies definied in the configuration - * @param values - * @returns - */ - private getPolicies(values: NetworkConfig): Map { - const policies = new Map(); - values.centralNetworkServices?.networkFirewall?.policies.forEach(policy => policies.set(policy.name, policy)); - return policies; - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/network-validator-functions.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/network-validator-functions.ts deleted file mode 100644 index 34b8654..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/network-validator-functions.ts +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { IPv4, IPv4CidrRange, IPv6, IPv6CidrRange } from 'ip-num'; -import { AccountConfig, GovCloudAccountConfig } from '../../lib/accounts-config'; -import { NetworkConfig, VpcConfig, VpcTemplatesConfig, SubnetConfig } from '../../lib/network-config'; -import * as t from '../../lib/common'; - -/** - * Class for helper functions - */ -export class NetworkValidatorFunctions { - private ouIdNames: string[]; - private accountNames: string[]; - private accounts: (AccountConfig | GovCloudAccountConfig)[]; - private snsTopicNames: string[]; - private values: NetworkConfig; - private enabledRegions: t.Region[]; - - constructor( - values: NetworkConfig, - ouIdNames: string[], - accounts: (AccountConfig | GovCloudAccountConfig)[], - snsTopicNames: string[], - enabledRegions: t.Region[], - ) { - this.ouIdNames = ouIdNames; - this.accounts = accounts; - this.snsTopicNames = snsTopicNames; - this.accountNames = accounts.map(account => { - return account.name; - }); - this.enabledRegions = enabledRegions; - this.values = values; - } - /** - * Get deployment regions for a deployment target object - * @param targets - * @returns - */ - public getRegionsFromDeploymentTarget(targets: t.DeploymentTargets): t.Region[] { - const enabledRegions: t.Region[] = this.enabledRegions; - - return enabledRegions.filter(region => !targets.excludedRegions?.includes(region)); - } - - /** - * Get account names for a share target or deployment target object - * @param targets - * @returns - */ - public getAccountNamesFromTarget(targets: t.DeploymentTargets | t.ShareTargets): string[] { - const accountNames: string[] = []; - // Add accounts based on OU targets - for (const ou of targets.organizationalUnits ?? []) { - if (ou === 'Root') { - this.accounts.forEach(rootOuItem => accountNames.push(rootOuItem.name)); - } else { - this.accounts.forEach(account => { - if (ou === account.organizationalUnit) { - accountNames.push(account.name); - } - }); - } - } - // Add accounts based on explicit accounts names - targets.accounts?.forEach(item => accountNames.push(item)); - - return [...new Set(accountNames)]; - } - - /** - * Get excluded account names for a deployment target object - * @param deploymentTargets - * @returns - */ - private getExcludedAccountNames(deploymentTargets: t.DeploymentTargets): string[] { - const accountIds: string[] = []; - - if (deploymentTargets.excludedAccounts) { - deploymentTargets.excludedAccounts.forEach(account => accountIds.push(account)); - } - - return accountIds; - } - - /** - * Returns the deployment target account names - * for a VPC or VPC template - * @param vpcItem - * @returns - */ - public getVpcAccountNames(vpcItem: VpcConfig | VpcTemplatesConfig): string[] { - let vpcAccountNames: string[]; - - if (t.isNetworkType('IVpcConfig', vpcItem)) { - vpcAccountNames = [vpcItem.account]; - } else { - const excludedAccountNames = this.getExcludedAccountNames(vpcItem.deploymentTargets); - vpcAccountNames = this.getAccountNamesFromTarget(vpcItem.deploymentTargets).filter( - item => !excludedAccountNames.includes(item), - ); - } - - return vpcAccountNames; - } - - /** - * Returns target accounts for resources shared from the delegated admin account - * @param targets - * @returns - */ - public getDelegatedAdminShareTargets(targets?: t.ShareTargets): string[] { - return targets - ? [...this.getAccountNamesFromTarget(targets), this.values.centralNetworkServices!.delegatedAdminAccount] - : [this.values.centralNetworkServices!.delegatedAdminAccount]; - } - - /** - * Given two arrays of account names, returns items in arr1 not included in arr2 - * @param arr1 - * @param arr2 - * @returns - */ - public compareTargetAccounts(arr1: string[], arr2: string[]): string[] { - return arr1.filter(item => !arr2.includes(item)); - } - - /** - * Returns true if an array contains duplicate values - * @param arr - * @returns - */ - public hasDuplicates(arr: (string | number)[]): boolean { - return new Set(arr).size !== arr.length; - } - - /** - * Returns true if a given account name exists in accounts-config.yaml - * @param account - * @returns - */ - public accountExists(account: string) { - return this.accountNames.includes(account); - } - - /** - * Returns true if a given OU name exists in organization-config.yaml - * @param ou - * @returns - */ - public ouExists(ou: string) { - return this.ouIdNames.includes(ou); - } - - /** - * Returns true if a given SNS topic name exists in global-config.yaml or security-config.yaml - * @param topic - * @returns - */ - public snsTopicExists(topic: string) { - return this.snsTopicNames.includes(topic); - } - - /** - * Given a name, returns a VPC or VPC template config - * @param values - * @param vpcName - * @returns - */ - public getVpc(vpcName: string): VpcConfig | VpcTemplatesConfig | undefined { - const vpcs = [...this.values.vpcs, ...(this.values.vpcTemplates ?? [])]; - return vpcs.find(item => item.name === vpcName); - } - - /** - * Given a VPC and subnet name, returns a subnet - * @param vpc - * @param subnetName - * @returns - */ - public getSubnet(vpc: VpcConfig | VpcTemplatesConfig, subnetName: string): SubnetConfig | undefined { - return vpc.subnets?.find(item => item.name === subnetName); - } - - /** - * Returns true if the given CIDR is valid - * @param cidr - * @returns - */ - public isValidIpv4Cidr(cidr?: string): boolean { - if (!cidr) { - return false; - } - - try { - IPv4CidrRange.fromCidr(cidr); - } catch (e) { - return false; - } - return true; - } - - /** - * Returns true if valid CIDR is valid - * @param cidr - * @returns - */ - public isValidIpv6Cidr(cidr?: string): boolean { - if (!cidr) { - return false; - } - - try { - IPv6CidrRange.fromCidr(cidr); - } catch (e) { - return false; - } - return true; - } - - /** - * Returns true if valid IPv4 address - * @param ip - * @returns - */ - public isValidIpv4(ip: string): boolean { - try { - IPv4.fromString(ip); - } catch (e) { - return false; - } - return true; - } - - /** - * Returns true if valid IPv6 address - * @param ip - * @returns - */ - public isValidIpv6(ip: string): boolean { - try { - IPv6.fromString(ip); - } catch (e) { - return false; - } - return true; - } - - /** - * Returns an array of object property keys that have a defined value - * @param obj - * @returns - */ - public getObjectKeys(obj: object): string[] { - const keys: string[] = []; - for (const [key, val] of Object.entries(obj)) { - if (val !== undefined) { - keys.push(key); - } - } - return keys; - } - - /** - * Returns true if a given value matches a regular expression - * @param value - * @param expression - * @returns - */ - public matchesRegex(value: string, expression: string): boolean { - const regex = new RegExp(expression); - return regex.test(value); - } - - /** - * Returns true if region is included in the enabledRegions - * @param global - * @returns - */ - public isEnabledRegion(region: string): boolean { - return this.enabledRegions.includes(region as t.Region); - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/prefix-list-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/prefix-list-validator.ts deleted file mode 100644 index c60a5bb..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/prefix-list-validator.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { NetworkConfig } from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; - -/** - * Class to validate customer-managed prefix lists - */ -export class PrefixListValidator { - constructor(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate prefix list names - // - this.validatePrefixListNames(values, helpers, errors); - // - // Validate prefix list account names - // - this.validatePrefixListAccountNames(values, helpers, errors); - // - // Validate entries - // - this.validatePrefixListEntries(values, helpers, errors); - } - - /** - * Validate prefix list names - * @param values - * @param helpers - * @param errors - */ - private validatePrefixListNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const listNames: string[] = []; - values.prefixLists?.forEach(list => listNames.push(list.name)); - - if (helpers.hasDuplicates(listNames)) { - errors.push( - `Duplicate prefix list names exist. Prefix list names must be unique. Prefix list names in file: ${listNames}`, - ); - } - } - - /** - * Validate prefix list account names - * @param values - * @param helpers - * @param errors - */ - private validatePrefixListAccountNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.prefixLists?.forEach(list => { - if (list.accounts && list.deploymentTargets) { - errors.push(`Cannot define both accounts and deploymentTargets for prefixList ${list.name}`); - return; - } - const deploymentTargetAccounts = []; - if (list.accounts) { - deploymentTargetAccounts.push(...list.accounts); - } - if (list.deploymentTargets?.accounts) { - deploymentTargetAccounts.push(...list.deploymentTargets.accounts); - } - deploymentTargetAccounts.forEach(account => { - if (!helpers.accountExists(account)) { - errors.push( - `Target account ${account} for prefix list ${list.name} does not exist in accounts-config.yaml file`, - ); - } - }); - if (list.deploymentTargets?.organizationalUnits) { - list.deploymentTargets.organizationalUnits.forEach(ou => { - if (!helpers.ouExists(ou)) { - errors.push(`Target OU ${ou} for prefix list ${list.name} does not exist in accounts-config.yaml file`); - } - }); - } - }); - } - - /** - * Validate prefix list entries - * @param values - * @param helpers - * @param errors - */ - private validatePrefixListEntries(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.prefixLists?.forEach(list => { - // Validate number of entries - if (list.entries.length > list.maxEntries) { - errors.push( - `[Prefix list ${list.name}]: maximum number of entries exceeded. Number of entries defined: ${list.entries.length} Max entries allowed: ${list.maxEntries}`, - ); - } - // Validate CIDR ranges - list.entries.forEach(entry => { - if (list.addressFamily === 'IPv4') { - if (!helpers.isValidIpv4Cidr(entry)) { - errors.push( - `[Prefix list ${list.name}]: entry "${entry}" is invalid. Value must be a valid IPv4 CIDR range`, - ); - } - } - if (list.addressFamily === 'IPv6') { - if (!helpers.isValidIpv6Cidr(entry)) { - errors.push( - `[Prefix list ${list.name}]: entry "${entry}" is invalid. Value must be a valid IPv6 CIDR range`, - ); - } - } - }); - }); - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/route53-resolver-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/route53-resolver-validator.ts deleted file mode 100644 index 84db01a..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/route53-resolver-validator.ts +++ /dev/null @@ -1,920 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import path from 'path'; -import * as fs from 'fs'; -import { - NetworkConfig, - DnsQueryLogsConfig, - DnsFirewallRuleGroupConfig, - DnsFirewallRulesConfig, - ResolverEndpointConfig, - VpcConfig, - SubnetConfig, - ResolverRuleConfig, - VpcTemplatesConfig, -} from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import { isNetworkType } from '../../lib/common'; - -/** - * Class to validate Route53Resolver - */ -export class Route53ResolverValidator { - constructor(values: NetworkConfig, configDir: string, helpers: NetworkValidatorFunctions, errors: string[]) { - const domainLists: { name: string; document: string }[] = []; - // - // Prepare Custom domain list - // - this.prepareCustomDomainList(values, domainLists); - // - // Custom domain lists - // - this.validateCustomDomainListDocumentFile(configDir, domainLists, errors); - // - // Validate query logs - // - this.validateQueryLogs(values, helpers, errors); - // - // Validate DNS firewall rules - // - this.validateDnsFirewallRuleGroups(values, helpers, errors); - // - // Validate resolver endpoints - // - this.validateResolverEndpoints(values, helpers, errors); - // - // Validate resolver rules - // - this.validateResolverRules(values, helpers, errors); - // - // Validate VPC Query Log configuration - // - this.validateLocalQueryLogConfig(values, errors); - } - - /** - * Function to prepare custom domain list - * @param values - */ - private prepareCustomDomainList(values: NetworkConfig, domainLists: { name: string; document: string }[]) { - for (const ruleGroup of values.centralNetworkServices?.route53Resolver?.firewallRuleGroups ?? []) { - for (const rule of ruleGroup.rules) { - if (rule.customDomainList) { - domainLists.push({ name: rule.name, document: rule.customDomainList }); - } - } - } - } - - /** - * Function to validate custom domain list document file existence - * @param configDir - */ - private validateCustomDomainListDocumentFile( - configDir: string, - domainLists: { name: string; document: string }[], - errors: string[], - ) { - for (const list of domainLists) { - if (!fs.existsSync(path.join(configDir, list.document))) { - errors.push(`DNS firewall custom domain list ${list.name} document file ${list.document} not found!`); - } - } - } - - private validateQueryLogs(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const queryLogs = values.centralNetworkServices?.route53Resolver?.queryLogs; - - if (queryLogs) { - // - // Validate query log destinations - // - this.validateQueryLogDestinations(queryLogs, helpers, errors); - // - // Validate query log share target OUs - // - this.validateQueryLogShareTargetOus(queryLogs, helpers, errors); - // - // Validate query log share target accounts - // - this.validateQueryLogShareTargetAccounts(queryLogs, helpers, errors); - } - } - - /** - * Validate query log destinations - * @param queryLogs - * @param helpers - * @param errors - */ - private validateQueryLogDestinations( - queryLogs: DnsQueryLogsConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate there are no duplicates - if (helpers.hasDuplicates(queryLogs.destinations)) { - errors.push( - `[Resolver query logs ${queryLogs.name}]: duplicate destinations configured. Destinations must be unique. Destinations in file: ${queryLogs.destinations}`, - ); - } - } - - /** - * Validate query log share target OUs - * @param queryLogs - * @param helpers - * @param errors - */ - private validateQueryLogShareTargetOus( - queryLogs: DnsQueryLogsConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const ou of queryLogs.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for Resolver query logs ${queryLogs.name} does not exist in organization-config.yaml`, - ); - } - } - } - - /** - * Validate query logs share target accounts - * @param queryLogs - * @param helpers - * @param errors - */ - private validateQueryLogShareTargetAccounts( - queryLogs: DnsQueryLogsConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const account of queryLogs.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for Resolver query logs ${queryLogs.name} does not exist in accounts-config.yaml`, - ); - } - } - } - - /** - * Validate firewall rule groups - * @param values - * @param helpers - * @param errors - */ - private validateDnsFirewallRuleGroups(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate firewall rule names - // - this.validateFirewallRuleNames(values, helpers, errors); - // - // Validate firewall share target OUs - // - this.validateFirewallRuleShareTargetOus(values, helpers, errors); - // - // Validate firewall share target OUs - // - this.validateFirewallRuleShareTargetAccounts(values, helpers, errors); - // - // Validate firewall rules - // - this.validateFirewallRules(values, helpers, errors); - } - - /** - * Validate firewall rule names - * @param values - * @param helpers - * @param errors - */ - private validateFirewallRuleNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const ruleNames: string[] = []; - values.centralNetworkServices?.route53Resolver?.firewallRuleGroups?.forEach(rule => ruleNames.push(rule.name)); - - if (helpers.hasDuplicates(ruleNames)) { - errors.push( - `Resolver firewall rule groups contain duplicate names. Rule group names must be unique. Rule group names in file: ${ruleNames}`, - ); - } - } - - /** - * Validate firewall share target OUs - * @param values - * @param helpers - * @param errors - */ - private validateFirewallRuleShareTargetOus( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const rule of values.centralNetworkServices?.route53Resolver?.firewallRuleGroups ?? []) { - for (const ou of rule.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for Resolver firewall rule group ${rule.name} does not exist in organization-config.yaml`, - ); - } - } - } - } - - /** - * Validate firewall share target accounts - * @param values - * @param helpers - * @param errors - */ - private validateFirewallRuleShareTargetAccounts( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const rule of values.centralNetworkServices?.route53Resolver?.firewallRuleGroups ?? []) { - for (const account of rule.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for Resolver firewall rule group ${rule.name} does not exist in accounts-config.yaml`, - ); - } - } - } - } - - /** - * Validate firewall rule group rules - * @param values - * @param helpers - * @param errors - */ - private validateFirewallRules(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const group of values.centralNetworkServices?.route53Resolver?.firewallRuleGroups ?? []) { - // - // Validate firewall rule names - // - this.validateRuleGroupRuleNames(group, helpers, errors); - // - // Validate firewall rule priorities - // - this.validateRuleGroupRulePriorities(group, helpers, errors); - for (const rule of group.rules) { - // - // Validate shape of rule object - // - this.validateRuleStructure(rule, helpers, errors); - } - } - } - - /** - * Validate rule names in a rule group - * @param group - * @param helpers - * @param errors - */ - private validateRuleGroupRuleNames( - group: DnsFirewallRuleGroupConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const ruleNames: string[] = []; - group.rules.forEach(rule => ruleNames.push(rule.name)); - if (helpers.hasDuplicates(ruleNames)) { - errors.push( - `[Resolver firewall rule group ${group.name}]: duplicate rule names. Rule names must be unique for each rule group. Rule names in file: ${ruleNames}`, - ); - } - } - - /** - * Validate rule priorities in a rule group - * @param group - * @param helpers - * @param errors - */ - private validateRuleGroupRulePriorities( - group: DnsFirewallRuleGroupConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const priorities: string[] = []; - group.rules.forEach(rule => priorities.push(rule.priority.toString())); - if (helpers.hasDuplicates(priorities)) { - errors.push( - `[Resolver firewall rule group ${group.name}]: duplicate rule priorities. Rule priorities must be unique for each rule group. Rule priorities in file: ${priorities}`, - ); - } - } - - /** - * Validate the shape of the rule object - * @param rule - * @param helpers - * @param errors - */ - private validateRuleStructure(rule: DnsFirewallRulesConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const keys = helpers.getObjectKeys(rule); - const blockKeys = ['blockResponse', 'blockOverrideDomain', 'blockOverrideTtl']; - - // Validate domain list - if (rule.customDomainList && rule.managedDomainList) { - errors.push( - `[Resolver firewall rule ${rule.name}]: you may only specify one of customDomainList or managedDomainList`, - ); - } - if (!rule.customDomainList && !rule.managedDomainList) { - errors.push( - `[Resolver firewall rule ${rule.name}]: you must specify one of customDomainList or managedDomainList`, - ); - } - - // Validate non-BLOCK actions - if (rule.action !== 'BLOCK' && keys.some(key => blockKeys.includes(key))) { - errors.push( - `[Resolver firewall rule ${rule.name}]: cannot specify the following rule properties for ${rule.action} action: ${blockKeys}`, - ); - } - - // Validate block actions - if (rule.action === 'BLOCK') { - this.validateBlockActions(rule, keys, errors); - } - } - - /** - * Validate block actions for the rule object - * @param rule - * @param keys - * @param errors - */ - private validateBlockActions(rule: DnsFirewallRulesConfig, keys: string[], errors: string[]) { - const blockOverrideKeys = ['blockOverrideDomain', 'blockOverrideTtl']; - // Ensure the BLOCK action has a blockResponse - if (!rule.blockResponse) { - errors.push(`[Resolver firewall rule ${rule.name}]: BLOCK actions require the blockResponse property`); - } else { - // Validate non-OVERRIDE BLOCK actions - if (rule.blockResponse !== 'OVERRIDE' && keys.some(key => blockOverrideKeys.includes(key))) { - errors.push( - `[Resolver firewall rule ${rule.name}]: cannot specify the following rule properties for BLOCK actions with ${rule.blockResponse} block response: ${blockOverrideKeys}`, - ); - } - // Validate OVERRIDE BLOCK actions - if (rule.blockResponse === 'OVERRIDE' && !blockOverrideKeys.some(overrideKey => keys.includes(overrideKey))) { - errors.push( - `[Resolver firewall rule ${rule.name}]: must specify the following rule properties for BLOCK actions with OVERRIDE block response: ${blockOverrideKeys}`, - ); - } - } - } - - private validateResolverEndpoints(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate endpoint names - // - this.validateResolverEndpointNames(values, helpers, errors); - // - // Validate resolver endpoint properties - // - this.validateResolverEndpointProps(values, helpers, errors); - } - - /** - * Validate resolver endpoint names - * @param values - * @param helpers - * @param errors - */ - private validateResolverEndpointNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const endpointNames: string[] = []; - values.centralNetworkServices?.route53Resolver?.endpoints?.forEach(endpoint => endpointNames.push(endpoint.name)); - - // Valiadate there are no duplicates - if (helpers.hasDuplicates(endpointNames)) { - errors.push( - `Resolver endpoints contain duplicate names. Endpoint names must be unique. Endpoint names in file: ${endpointNames}`, - ); - } - - // Validate regex - endpointNames.forEach(name => { - if (!helpers.matchesRegex(name, '(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)')) { - errors.push( - `Resolver endpoint name "${name}" is invalid. Endpoint names must match the pattern "(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)"`, - ); - } - }); - } - - /** - * Validate resolver endpoint properties - * @param values - * @param helpers - * @param errors - */ - private validateResolverEndpointProps(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const endpoint of values.centralNetworkServices?.route53Resolver?.endpoints ?? []) { - // - // Validate endpoint object structure - // - const allValid = this.validateResolverEndpointStructure(endpoint, helpers, errors); - // - // Validate rules - // - if (allValid) { - this.validateResolverEndpointVpcs(values, endpoint, helpers, errors); - } - } - } - - /** - * Validate structure and property values of the endpoint - * @param endpoint - * @param helpers - * @param errors - * @returns - */ - private validateResolverEndpointStructure( - endpoint: ResolverEndpointConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - let allValid = true; - - // Validate rules are not set on inbound endpoint - if (endpoint.type === 'INBOUND' && endpoint.rules) { - allValid = false; - errors.push(`[Resolver endpoint ${endpoint.name}]: INBOUND endpoint type cannot have associated resolver rules`); - } - - // Validate rule types - if (endpoint.type === 'OUTBOUND') { - endpoint.rules?.forEach(rule => { - if (rule.ruleType && rule.ruleType !== 'FORWARD') { - allValid = false; - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: rules associated with OUTBOUND endpoints can only specify FORWARD rule type`, - ); - } - }); - } - - // Validate allowed CIDRs - const cidrs: string[] = []; - let cidrsValid = true; - endpoint.allowedCidrs?.forEach(cidr => cidrs.push(cidr)); - - cidrs.forEach(item => { - if (!helpers.isValidIpv4Cidr(item)) { - allValid = false; - cidrsValid = false; - errors.push( - `[Resolver endpoint ${endpoint.name}]: allowedCidr "${item}" is invalid. Value must be a valid IPv4 CIDR range`, - ); - } - }); - - if (cidrsValid && helpers.hasDuplicates(cidrs)) { - allValid = false; - errors.push(`[Resolver endpoint ${endpoint.name}]: endpoint has duplicate allowed CIDRs`); - } - return allValid; - } - - /** - * Validate endpoint target VPC attributes - * @param values - * @param endpoint - * @param helpers - * @param errors - */ - private validateResolverEndpointVpcs( - values: NetworkConfig, - endpoint: ResolverEndpointConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const delegatedAdmin = values.centralNetworkServices!.delegatedAdminAccount; - - // Validate VPC - const vpc = helpers.getVpc(endpoint.vpc); - if (!vpc) { - errors.push(`[Resolver endpoint ${endpoint.name}]: VPC "${endpoint.vpc}" does not exist`); - } else { - // Validate the target is not a VPC template - if (isNetworkType('IVpcTemplatesConfig', vpc)) { - errors.push( - `[Resolver endpoint ${endpoint.name}]: VPC templates are not a supported target VPC type for Resolver endpoints`, - ); - } - - if (isNetworkType('IVpcConfig', vpc)) { - // Validate we are targeting delegated admin account - if (vpc.account !== delegatedAdmin) { - errors.push( - `[Resolver endpoint ${endpoint.name}]: VPC "${vpc.name}" is not deployed to delegated admin account "${delegatedAdmin}". Resolver endpoints must be deployed to the delegated admin account`, - ); - } - // Validate VPC subnets - this.validateResolverEndpointSubnets(endpoint, vpc, helpers, errors); - } - } - } - - private validateResolverEndpointSubnets( - endpoint: ResolverEndpointConfig, - vpc: VpcConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const subnets: SubnetConfig[] = []; - endpoint.subnets.forEach(item => { - const subnet = helpers.getSubnet(vpc, item); - // Validate subnet exists - if (!subnet) { - errors.push(`[Resolver endpoint ${endpoint.name}]: VPC "${vpc.name}" does not contain the subnet "${item}"`); - } else { - subnets.push(subnet); - } - }); - - // Validate there are no duplicate subnets or AZs - const subnetNames: string[] = []; - const subnetAzs: (string | number)[] = []; - subnets.forEach(subnetItem => { - subnetNames.push(subnetItem.name); - subnetAzs.push(subnetItem.availabilityZone ? subnetItem.availabilityZone : ''); - }); - - if (helpers.hasDuplicates(subnetNames)) { - errors.push( - `[Resolver endpoint ${endpoint.name}]: endpoint has duplicate subnets defined. Subnets must be unique. Subnets in file: ${subnetNames}`, - ); - } - if (helpers.hasDuplicates(subnetAzs)) { - errors.push( - `[Resolver endpoint ${endpoint.name}]: endpoint has duplicate subnet AZs defined. AZs must be unique. AZs in file: ${subnetAzs}`, - ); - } - } - - private validateResolverRules(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate Resolver rule share target OUs - // - this.validateResolverRuleShareTargetOus(values, helpers, errors); - // - // Validate Resolver rule share target accounts - // - this.validateResolverRuleShareTargetAccounts(values, helpers, errors); - // - // Validate SYSTEM rules - // - this.validateSystemRules(values, helpers, errors); - // - // Validate FORWARD rules - // - this.validateForwardRules(values, helpers, errors); - } - - /** - * Validate Resolver rule share target OUs - * @param values - * @param helpers - * @param errors - */ - private validateResolverRuleShareTargetOus( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // SYSTEM rules - values.centralNetworkServices?.route53Resolver?.rules?.forEach(systemRule => { - systemRule.shareTargets?.organizationalUnits?.forEach(ou => { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for Resolver rule ${systemRule.name} does not exist in organization-config.yaml`, - ); - } - }); - }); - - // FORWARD rules - values.centralNetworkServices?.route53Resolver?.endpoints?.forEach(endpoint => { - endpoint.rules?.forEach(forwardRule => { - forwardRule.shareTargets?.organizationalUnits?.forEach(ou => { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for Resolver endpoint ${endpoint.name} rule ${forwardRule.name} does not exist in organization-config.yaml`, - ); - } - }); - }); - }); - } - - /** - * Validate Resolver rule share target accounts - * @param values - * @param helpers - * @param errors - */ - private validateResolverRuleShareTargetAccounts( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // SYSTEM rules - values.centralNetworkServices?.route53Resolver?.rules?.forEach(systemRule => { - systemRule.shareTargets?.accounts?.forEach(account => { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for Resolver rule ${systemRule.name} does not exist in accounts-config.yaml`, - ); - } - }); - }); - - // FORWARD rules - values.centralNetworkServices?.route53Resolver?.endpoints?.forEach(endpoint => { - endpoint.rules?.forEach(forwardRule => { - forwardRule.shareTargets?.accounts?.forEach(account => { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for Resolver endpoint ${endpoint.name} rule ${forwardRule.name} does not exist in accounts-config.yaml`, - ); - } - }); - }); - }); - } - - /** - * Validate SYSTEM rules - * @param values - * @param helpers - * @param errors - */ - private validateSystemRules(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // Validate rule names - this.validateSystemRuleNames(values, helpers, errors); - - // Validate rule properties - for (const rule of values.centralNetworkServices?.route53Resolver?.rules ?? []) { - // Validate only SYSTEM rules are created in this block - if (rule.ruleType && rule.ruleType !== 'SYSTEM') { - errors.push(`[Resolver rule ${rule.name}]: rules not defined under an endpoint must use the SYSTEM rule type`); - } - // Validate there are no targets for the SYSTEM rule - if (rule.inboundEndpointTarget || rule.targetIps) { - errors.push(`[Resolver rule ${rule.name}]: SYSTEM rule type cannot include targets`); - } - // Validate domain name regex - if (!helpers.matchesRegex(rule.domainName, '(^\\.$)|(^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$)')) { - errors.push( - `[Resolver rule ${rule.name}]: domain name "${rule.domainName}" is invalid. Domain name must match the pattern "(^\\.$)|(^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$)"`, - ); - } - } - } - - /** - * Validate SYSTEM rule names - * @param values - * @param helpers - * @param errors - */ - private validateSystemRuleNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const ruleNames: string[] = []; - values.centralNetworkServices?.route53Resolver?.rules?.forEach(item => { - ruleNames.push(item.name); - }); - - // Validate rule names are unique - if (helpers.hasDuplicates(ruleNames)) { - errors.push( - `Resolver SYSTEM rules has duplicates. Resolver rule names must be unique. Rule names in file: ${ruleNames}`, - ); - } - - // Validate regex - ruleNames.forEach(name => { - if (!helpers.matchesRegex(name, '(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)')) { - errors.push( - `Resolver SYSTEM rule name "${name}" is invalid. Resolver rule names must match the pattern "(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)"`, - ); - } - }); - } - - /** - * Validate FORWARD rules - * @param values - * @param helpers - * @param errors - */ - private validateForwardRules(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const endpointMap = this.getResolverEndpoints(values); - - // Validate FORMWARD rule names - this.validateForwardRuleNames(values, helpers, errors); - - // Validate FORWARD rule properties - for (const endpoint of values.centralNetworkServices?.route53Resolver?.endpoints ?? []) { - for (const rule of endpoint.rules ?? []) { - const targetValid = this.validateForwardRuleProps(endpoint, rule, helpers, errors); - - if (targetValid) { - this.validateForwardRuleTargets(endpoint, rule, endpointMap, helpers, errors); - } - } - } - } - - /** - * Return a map of endpoint configurations - * @param values - * @returns - */ - private getResolverEndpoints(values: NetworkConfig): Map { - const endpoints = new Map(); - values.centralNetworkServices?.route53Resolver?.endpoints?.forEach(endpoint => - endpoints.set(endpoint.name, endpoint), - ); - return endpoints; - } - - /** - * Validate FORWARD rule names - * @param values - * @param helpers - * @param errors - */ - private validateForwardRuleNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const ruleNames: string[] = []; - values.centralNetworkServices?.route53Resolver?.endpoints?.forEach(endpoint => { - endpoint.rules?.forEach(rule => ruleNames.push(rule.name)); - }); - - // Validate rule names are unique - if (helpers.hasDuplicates(ruleNames)) { - errors.push( - `Resolver FORWARD rules has duplicates. Resolver rule names must be unique. Rule names in file: ${ruleNames}`, - ); - } - - // Validate regex - ruleNames.forEach(name => { - if (!helpers.matchesRegex(name, '(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)')) { - errors.push( - `Resolver FORWARD rule name "${name}" is invalid. Resolver rule names must match the pattern "(?!^[0-9]+$)(^[a-zA-Z0-9-_]+$)"`, - ); - } - }); - } - - /** - * Validate FORWARD rule props - * @param endpoint - * @param rule - * @param helpers - * @param errors - * @returns - */ - private validateForwardRuleProps( - endpoint: ResolverEndpointConfig, - rule: ResolverRuleConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - // Validate domain name - if (!helpers.matchesRegex(rule.domainName, '(^\\.$)|(^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$)')) { - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: domain name ${rule.domainName} is invalid. Domain name must match the pattern "(^\\.$)(^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,8}$)"`, - ); - } - - // Validate target type - let targetValid = true; - if (rule.targetIps && rule.inboundEndpointTarget) { - targetValid = false; - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: cannot define a FORWARD rule with both targetIp and inboundEndpointTarget properties`, - ); - } - if (!rule.targetIps && !rule.inboundEndpointTarget) { - targetValid = false; - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: FORWARD rule must be defined with one of targetIp or inboundEndpointTarget properties`, - ); - } - - return targetValid; - } - - /** - * Validate FORWARD rule targets - * @param endpoint - * @param rule - * @param endpointMap - * @param helpers - * @param errors - */ - private validateForwardRuleTargets( - endpoint: ResolverEndpointConfig, - rule: ResolverRuleConfig, - endpointMap: Map, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate target IPs - rule.targetIps?.forEach(target => { - if (!helpers.isValidIpv4(target.ip)) { - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: IP "${target.ip}" is invalid. Value must be a valid IPv4 address`, - ); - } - - // Validate ports - if (target.port) { - if (!helpers.matchesRegex(target.port, '^\\d{1,5}$')) { - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: target port "${target.port}" is invalid. Port must be a valid port in range 0-65535`, - ); - } else { - if (parseInt(target.port) < 0 || parseInt(target.port) > 65535) { - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: target port "${target.port}" is invalid. Port must be a valid port in range 0-65535`, - ); - } - } - } - }); - - if (rule.inboundEndpointTarget) { - this.validateForwardRuleInboundTarget(endpoint, rule, endpointMap, errors); - } - } - - private validateForwardRuleInboundTarget( - endpoint: ResolverEndpointConfig, - rule: ResolverRuleConfig, - endpointMap: Map, - errors: string[], - ) { - // Validate inbound endpoints - const inboundTarget = endpointMap.get(rule.inboundEndpointTarget!); - - if (!inboundTarget) { - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: target endpoint "${rule.inboundEndpointTarget}" does not exist`, - ); - } else { - if (inboundTarget.type !== 'INBOUND') { - errors.push( - `[Resolver endpoint ${endpoint.name} rule ${rule.name}]: target endpoint "${rule.inboundEndpointTarget}" is not an INBOUND endpoint`, - ); - } - } - } - - /** - * Validate Multiple Query Logging Config Associations for a VPC. - * @param values - * @param errors - */ - private validateLocalQueryLogConfig(values: NetworkConfig, errors: string[]) { - // Validate that centralized query logs and local query logs aren't referenced in the same VPC - const invalidConfig: string[] = []; - for (const vpcItem of values.vpcs ?? []) { - if (vpcItem.queryLogs && vpcItem.vpcRoute53Resolver?.queryLogs) { - if (!invalidConfig.includes(vpcItem.name)) { - invalidConfig.push(vpcItem.name); - } - } - } - if (invalidConfig.length > 0) { - errors.push( - `These VPCS: [${invalidConfig.join( - ', ', - )}] have multiple Route53 query logs associated in the config. Please only specify a queryLog from the central network services or from the local vpcResolver config. `, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/transit-gateway-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/transit-gateway-validator.ts deleted file mode 100644 index da1c401..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/transit-gateway-validator.ts +++ /dev/null @@ -1,700 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { isNetworkType } from '../../lib/common'; -import { - NetworkConfig, - TransitGatewayConfig, - TransitGatewayRouteTableConfig, - TransitGatewayRouteEntryConfig, - TransitGatewayRouteTableVpcEntryConfig, - DxGatewayConfig, - TransitGatewayRouteTableDxGatewayEntryConfig, - CustomerGatewayConfig, - TransitGatewayRouteTableVpnEntryConfig, - TransitGatewayRouteTableTgwPeeringEntryConfig, -} from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; - -/** - * Class to validate transit gateway - */ -export class TransitGatewayValidator { - constructor(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate transit gateway names - // - this.validateTgwNames(values, helpers, errors); - // - // Validate Tgw account name - // - this.validateTgwAccountName(values, helpers, errors); - // - // Validate transit gateways and route table used in peering configuration - // - this.validateTgwPeeringTransitGatewaysAndRouteTables(values, errors); - // - // Validate peering name used in route table - // - this.validateTgwPeeringName(values, errors); - // - // Validate TGW deployment target OUs - // - this.validateTgwShareTargetOUs(values, helpers, errors); - // - // Validate TGW deployment target account names - // - this.validateTgwShareTargetAccounts(values, helpers, errors); - // - // Validate TGW configurations - // - this.validateTgwConfiguration(values, helpers, errors); - // - // Validate TGW Connect attachment configurations - // - this.validateTgwConnectConfiguration(values, errors); - } - - /** - * Validate transit gateway names - * @param values - * @param helpers - * @param errors - */ - private validateTgwNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const tgwNames: string[] = []; - values.transitGateways.forEach(tgw => tgwNames.push(tgw.name)); - - if (helpers.hasDuplicates(tgwNames)) { - errors.push( - `Duplicate transit gateway names defined. Transit gateway names must be unique. Transit gateway names in file: ${tgwNames}`, - ); - } - } - - /** - * Function to validate TGW route table transitGatewayPeeringName property value - * Value for transitGatewayPeeringName must be from the list of transitGatewayPeering object - * @param values - * @param errors - */ - private validateTgwPeeringName(values: NetworkConfig, errors: string[]) { - for (const transitGateway of values.transitGateways) { - for (const routeTable of transitGateway.routeTables) { - for (const route of routeTable.routes) { - if ( - isNetworkType( - 'ITransitGatewayRouteTableTgwPeeringEntryConfig', - route.attachment, - ) - ) { - const attachment: TransitGatewayRouteTableTgwPeeringEntryConfig = route.attachment; - if (!values.transitGatewayPeering?.find(item => item.name === attachment.transitGatewayPeeringName)) { - errors.push( - `Transit gateway ${transitGateway.name} route table ${routeTable.name} validation error, transitGatewayPeeringName property value ${attachment.transitGatewayPeeringName} not found in transitGatewayPeering list!!!! `, - ); - } - } - } - } - } - } - - /** - * Function to validate transit gateways and route tables used TGW Peering configuration - * @param values - * @param errors - */ - private validateTgwPeeringTransitGatewaysAndRouteTables(values: NetworkConfig, errors: string[]) { - values.transitGatewayPeering?.forEach(transitGatewayPeering => { - // Accepter TGW validation - const accepterTransitGateway = values.transitGateways.find( - item => - item.account === transitGatewayPeering.accepter.account && - item.region === transitGatewayPeering.accepter.region && - item.name === transitGatewayPeering.accepter.transitGatewayName, - ); - if (!accepterTransitGateway) { - errors.push( - `Transit gateway peering ${transitGatewayPeering.name} validation error, accepter transit gateway ${transitGatewayPeering.accepter.transitGatewayName} in ${transitGatewayPeering.accepter.account} account and ${transitGatewayPeering.accepter.region} not found!!!! `, - ); - } else { - if ( - !accepterTransitGateway.routeTables.find( - item => item.name === transitGatewayPeering.accepter.routeTableAssociations, - ) - ) { - errors.push( - `Transit gateway peering ${transitGatewayPeering.name} validation error, accepter transit gateway ${transitGatewayPeering.accepter.transitGatewayName} in ${transitGatewayPeering.accepter.account} account and ${transitGatewayPeering.accepter.region}, route table ${transitGatewayPeering.accepter.routeTableAssociations} not found!!!! `, - ); - } - } - - // Requester TGW validation - const requesterTransitGateway = values.transitGateways.find( - item => - item.account === transitGatewayPeering.requester.account && - item.region === transitGatewayPeering.requester.region && - item.name === transitGatewayPeering.requester.transitGatewayName, - ); - if (!requesterTransitGateway) { - errors.push( - `Transit gateway peering ${transitGatewayPeering.name} validation error, requester transit gateway ${transitGatewayPeering.requester.transitGatewayName} in ${transitGatewayPeering.accepter.account} account and ${transitGatewayPeering.accepter.region} not found!!!! `, - ); - } else { - if ( - !requesterTransitGateway.routeTables.find( - item => item.name === transitGatewayPeering.requester.routeTableAssociations, - ) - ) { - errors.push( - `Transit gateway peering ${transitGatewayPeering.name} validation error, requester transit gateway ${transitGatewayPeering.accepter.transitGatewayName} in ${transitGatewayPeering.accepter.account} account and ${transitGatewayPeering.accepter.region}, route table ${transitGatewayPeering.requester.routeTableAssociations} not found!!!! `, - ); - } - } - }); - } - - /** - * Function to validate existence of Transit Gateway deployment target OUs - * Make sure share target OUs are part of Organization config file - * @param values - * @param helpers - * @param errors - */ - private validateTgwShareTargetOUs(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const transitGateway of values.transitGateways ?? []) { - for (const ou of transitGateway.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Share target OU ${ou} for transit gateways ${transitGateway.name} does not exist in organization-config.yaml file`, - ); - } - } - } - } - - /** - * Function to validate existence of transit deployment target accounts - * Make sure share target accounts are part of account config file - * @param values - */ - private validateTgwShareTargetAccounts(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const transitGateway of values.transitGateways ?? []) { - for (const account of transitGateway.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Share target account ${account} for transit gateway ${transitGateway.name} does not exist in accounts-config.yaml file`, - ); - } - } - } - } - - /** - * Function to validate existence of transit gateway account name - * Make sure target account is part of account config file - * @param values - * @param helpers - * @param errors - */ - private validateTgwAccountName(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const transitGateway of values.transitGateways ?? []) { - if (!helpers.accountExists(transitGateway.account)) { - errors.push( - `Transit Gateway "${transitGateway.name}" account name "${transitGateway.account}" does not exist in accounts-config.yaml file`, - ); - } - } - } - - /** - * Function to validate conditional dependencies for TGW configurations - * @param values - * @param helpers - * @param errors - */ - private validateTgwConfiguration(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate transit gateway route table names - // - this.validateTgwRouteTableNames(values, helpers, errors); - // - // Validate transit gateway ASN - // - this.validateTgwAsns(values, errors); - // - // Validate transit gateway static CIDRs - // - this.validateTgwCidrs(values, helpers, errors); - // - // Validate transit gateway route table structure - // - const allValid = this.validateTransitGatewayRouteStructure(values, errors); - - for (const tgw of values.transitGateways ?? []) { - for (const routeTable of tgw.routeTables ?? []) { - if (allValid) { - // Validate CIDR route destinations - this.validateTgwRouteCidrDestinations(tgw, routeTable, helpers, errors); - // Validate prefix list route destinations - this.validateTgwRoutePrefixListDestinations(values, tgw, routeTable, helpers, errors); - // Validate static route entries - this.validateTgwStaticRouteEntries(values, tgw, routeTable, helpers, errors); - } - } - } - } - - /** - * Function to validate Transit Gateway Connect configurations - * @param values - * @param helpers - * @param errors - */ - private validateTgwConnectConfiguration(values: NetworkConfig, errors: string[]) { - // Validate use of VPC of Direct Connect configurations - this.validateDxVpcTgwConnectConfigurations(values, errors); - // Validate resources specified in Transit Gateway Connect - this.validateTgwConnectResources(values, errors); - // Validate Transit Gateway region for connect attachment - this.validateTgwConnectRegion(values, errors); - } - - /** - * Function to validate attachments referenced for Transit Gateway Connect configuration - * @param values - * @param errors - */ - private validateDxVpcTgwConnectConfigurations(values: NetworkConfig, errors: string[]) { - for (const connectItem of values.transitGatewayConnects ?? []) { - if (connectItem.directConnect && connectItem.vpc) { - errors.push( - `[Transit Gateway Connect ${connectItem.name}]: Direct Connect VPC and VPC cannot be specified together`, - ); - } - } - } - - /** - * Function to validate if resources specified in Transit Gateway Connect Attachments exist. - */ - private validateTgwConnectResources(values: NetworkConfig, errors: string[]) { - for (const connectItem of values.transitGatewayConnects ?? []) { - const validTransitGateway = values.transitGateways.find(item => item.name === connectItem.transitGateway.name); - if (!validTransitGateway) { - errors.push( - `[Transit Gateway Connect ${connectItem.name}]: The Transit Gateway ${connectItem.transitGateway.name} not found in configuration`, - ); - } - if (connectItem.directConnect) { - const validDirectConnectGateway = values.directConnectGateways!.find( - item => item.name === connectItem.directConnect, - ); - if (!validDirectConnectGateway) { - errors.push( - `[Transit Gateway Connect ${connectItem.name}]: Direct Connect ${connectItem.directConnect} not found in configuration`, - ); - } - } - if (connectItem.vpc) { - const validVpcName = values.vpcs.find(item => item.name === connectItem.vpc?.vpcName); - if (!validVpcName) { - errors.push( - `[Transit Gateway Connect ${connectItem.name}]: VPC ${connectItem.vpc.vpcName} not found in configuration`, - ); - } - } - } - } - - /** - * Function to validate if resources specified in Transit Gateway Connect Attachments reside in the same region.. - */ - private validateTgwConnectRegion(values: NetworkConfig, errors: string[]) { - for (const connectItem of values.transitGatewayConnects ?? []) { - const vpcItem = values.vpcs.find(item => item.name === connectItem.vpc?.vpcName); - const tgwItem = values.transitGateways.find(item => item.name === connectItem.transitGateway.name); - if (vpcItem?.region !== tgwItem?.region) { - errors.push( - `[Transit Gateway Connect ${connectItem.name}]: The VPC: ${vpcItem?.name} and Transit Gateway: ${tgwItem?.name} must be in the same region for a Transit Gateway Connect attachment.`, - ); - } - } - } - - /** - * Validate transit gateway route table names - * @param values - * @param helpers - * @param errors - */ - private validateTgwRouteTableNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.transitGateways.forEach(tgw => { - const tableNames: string[] = []; - tgw.routeTables.forEach(table => tableNames.push(table.name)); - - if (helpers.hasDuplicates(tableNames)) { - errors.push( - `[Transit gateway ${tgw.name}]: duplicate route table names defined. Route table names must be unique for each TGW. Table names in file: ${tableNames}`, - ); - } - }); - } - - /** - * Validate transit gateway ASNs - * @param values - * @param errors - */ - private validateTgwAsns(values: NetworkConfig, errors: string[]) { - values.transitGateways.forEach(tgw => { - const asnRange16Bit = tgw.asn >= 64512 && tgw.asn <= 65534; - const asnRange32Bit = tgw.asn >= 4200000000 && tgw.asn <= 4294967294; - - if (!asnRange16Bit && !asnRange32Bit) { - errors.push( - `[Transit gateway ${tgw.name}]: ASN is not within range. Valid values are 64512-65534 for 16-bit ASNs and 4200000000-4294967294 for 32-bit ASNs`, - ); - } - }); - } - - /** - * Validate transit gateway static CIDRs - * @param values - * @param helpers - * @param errors - */ - private validateTgwCidrs(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.transitGateways.forEach(tgw => { - if (tgw.transitGatewayCidrBlocks) { - tgw.transitGatewayCidrBlocks.forEach(ipv4Cidr => { - if (!helpers.isValidIpv4Cidr(ipv4Cidr)) { - errors.push( - `[Transit gateway ${tgw.name}]: static IPv4 CIDR ${ipv4Cidr} is not valid. Static CIDRs must be in CIDR notation`, - ); - } - }); - } - if (tgw.transitGatewayIpv6CidrBlocks) { - tgw.transitGatewayIpv6CidrBlocks.forEach(ipv6Cidr => { - if (!helpers.isValidIpv6Cidr(ipv6Cidr)) { - errors.push( - `[Transit gateway ${tgw.name}]: static IPv6 CIDR ${ipv6Cidr} is not valid. Static CIDRs must be in CIDR notation`, - ); - } - }); - } - const totalCidrBlocks = [...(tgw.transitGatewayCidrBlocks ?? []), ...(tgw.transitGatewayIpv6CidrBlocks ?? [])]; - if (totalCidrBlocks.length > 5) { - errors.push(`[Transit gateway ${tgw.name}]: cannot have more than 5 static CIDRs. `); - } - }); - } - /** - * Validate route entries are using the correct structure - * @param values - * @param errors - * @returns - */ - private validateTransitGatewayRouteStructure(values: NetworkConfig, errors: string[]) { - let allValid = true; - values.transitGateways.forEach(tgw => { - tgw.routeTables.forEach(routeTable => { - routeTable.routes.forEach(entry => { - // Catch error if an attachment and blackhole are both defined - if (entry.attachment && entry.blackhole) { - allValid = false; - errors.push( - `[Transit Gateway route table ${routeTable.name}]: cannot define both an attachment and blackhole target`, - ); - } - // Catch error if neither attachment or blackhole are defined - if (!entry.attachment && !entry.blackhole) { - allValid = false; - errors.push( - `[Transit Gateway route table ${routeTable.name}]: must define either an attachment or blackhole target`, - ); - } - // Catch error if destination CIDR and prefix list are both defined - if (entry.destinationCidrBlock && entry.destinationPrefixList) { - allValid = false; - errors.push( - `[Transit Gateway route table ${routeTable.name}]: cannot define both a destination CIDR and destination prefix list`, - ); - } - // Catch error if destination CIDR and prefix list are not defined - if (!entry.destinationCidrBlock && !entry.destinationPrefixList) { - allValid = false; - errors.push( - `[Transit Gateway route table ${routeTable.name}]: must define either a destination CIDR or destination prefix list`, - ); - } - }); - }); - }); - return allValid; - } - - /** - * Validate CIDR destinations for TGW route tables - * @param values - * @param tgw - * @param routeTable - * @param helpers - * @param errors - */ - private validateTgwRouteCidrDestinations( - tgw: TransitGatewayConfig, - routeTable: TransitGatewayRouteTableConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Create arrays of CIDR and prefix list destinations - const cidrs: string[] = []; - routeTable.routes.forEach(entry => { - if (entry.destinationCidrBlock) { - cidrs.push(entry.destinationCidrBlock); - } - }); - - // Validate there are no duplicates - if (helpers.hasDuplicates(cidrs)) { - errors.push( - `[Transit gateway ${tgw.name} route table ${routeTable.name}]: duplicate CIDR destinations defined. Destinations must be unique. CIDRs defined in file: ${cidrs}`, - ); - } - - // Validate CIDRs - cidrs.forEach(cidr => { - if (!helpers.isValidIpv4Cidr(cidr) && !helpers.isValidIpv6Cidr(cidr)) { - errors.push( - `[Transit gateway ${tgw.name} route table ${routeTable.name}]: destination CIDR "${cidr}" is invalid. Value must be a valid IPv4/v6 CIDR range`, - ); - } - }); - } - - /** - * Validate prefix list destinations for TGW route tables - * @param values - * @param tgw - * @param routeTable - * @param helpers - * @param errors - */ - private validateTgwRoutePrefixListDestinations( - values: NetworkConfig, - tgw: TransitGatewayConfig, - routeTable: TransitGatewayRouteTableConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Create array of prefix list destinations - const prefixLists: string[] = []; - routeTable.routes.forEach(entry => { - if (entry.destinationPrefixList) { - prefixLists.push(entry.destinationPrefixList); - } - }); - - // Validate there are no duplicates - if (helpers.hasDuplicates(prefixLists)) { - errors.push( - `[Transit gateway ${tgw.name} route table ${routeTable.name}]: duplicate prefix list destinations defined. Destinations must be unique. Prefix lists defined in file: ${prefixLists}`, - ); - } - - // Validate prefix list exists in the same account/region as TGW - prefixLists.forEach(listName => { - const prefixList = values.prefixLists?.find(pl => pl.name === listName); - if (!prefixList) { - errors.push( - `[Transit gateway ${tgw.name} route table ${routeTable.name}]: prefix list "${listName}" not found`, - ); - } - const accounts = []; - const regions = []; - if (prefixList?.accounts) { - accounts.push(...prefixList.accounts); - } - if (prefixList?.regions) { - regions.push(...prefixList.regions); - } - if (prefixList?.deploymentTargets) { - accounts.push(...helpers.getAccountNamesFromTarget(prefixList.deploymentTargets)); - regions.push(...helpers.getRegionsFromDeploymentTarget(prefixList.deploymentTargets)); - } - if (!accounts.includes(tgw.account)) { - errors.push( - `[Transit gateway ${tgw.name} route table ${routeTable.name}]: prefix list "${listName}" is not deployed to the same account as the TGW`, - ); - } - if (!regions.includes(tgw.region)) { - errors.push( - `[Transit gateway ${tgw.name} route table ${routeTable.name}]: prefix list "${listName}" is not deployed to the same region as the TGW`, - ); - } - }); - } - - /** - * Function to validate TGW route table entries - */ - private validateTgwStaticRouteEntries( - values: NetworkConfig, - tgw: TransitGatewayConfig, - routeTable: TransitGatewayRouteTableConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Retrieve relevant configs - const dxgws = [...(values.directConnectGateways ?? [])]; - const cgws = [...(values.customerGateways ?? [])]; - - for (const entry of routeTable.routes ?? []) { - // Validate VPC attachment routes - this.validateVpcStaticRouteEntry(tgw.name, routeTable.name, entry, helpers, errors); - - // Validate DX Gateway routes - this.validateDxGatewayStaticRouteEntry(dxgws, tgw, routeTable.name, entry, errors); - - // Validate VPN static route entry - this.validateVpnStaticRouteEntry(cgws, tgw.name, routeTable.name, entry, errors); - } - } - - /** - * Function to validate transit gateway static route entries for VPC attachments - * @param tgwName - * @param routeTableName - * @param entry - * @param helpers - * @param errors - */ - private validateVpcStaticRouteEntry( - tgwName: string, - routeTableName: string, - entry: TransitGatewayRouteEntryConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if ( - entry.attachment && - isNetworkType('ITransitGatewayRouteTableVpcEntryConfig', entry.attachment) - ) { - const vpcAttachment = entry.attachment as TransitGatewayRouteTableVpcEntryConfig; - const vpc = helpers.getVpc(vpcAttachment.vpcName); - - // Validate VPC exists and resides in the expected account - if (!vpc) { - errors.push( - `[Transit Gateway ${tgwName} route table ${routeTableName}]: cannot find VPC "${vpcAttachment.vpcName}"`, - ); - } - if (vpc && !helpers.getVpcAccountNames(vpc).includes(vpcAttachment.account)) { - errors.push( - `[Transit Gateway ${tgwName} route table ${routeTableName}]: VPC "${vpcAttachment.vpcName}" is not deployed to account "${vpcAttachment.account}"`, - ); - } - } - } - - /** - * Function to validate transit gateway static route entries for DX attachments - * @param dxgws - * @param routeTableName - * @param tgw - * @param entry - * @param errors - */ - private validateDxGatewayStaticRouteEntry( - dxgws: DxGatewayConfig[], - tgw: TransitGatewayConfig, - routeTableName: string, - entry: TransitGatewayRouteEntryConfig, - errors: string[], - ) { - if ( - entry.attachment && - isNetworkType( - 'ITransitGatewayRouteTableDxGatewayEntryConfig', - entry.attachment, - ) - ) { - const dxAttachment = entry.attachment as TransitGatewayRouteTableDxGatewayEntryConfig; - const dxgw = dxgws.find(item => item.name === dxAttachment.directConnectGatewayName); - // Catch error if DXGW doesn't exist - if (!dxgw) { - errors.push( - `[Transit Gateway ${tgw.name} route table ${routeTableName}]: cannot find DX Gateway ${dxAttachment.directConnectGatewayName}`, - ); - } - if (dxgw) { - // Catch error if DXGW is not in the same account as the TGW - if (dxgw.account !== tgw.account) { - errors.push( - `[Transit Gateway ${tgw.name} route table ${routeTableName}]: cannot add route entry for DX Gateway ${dxAttachment.directConnectGatewayName}. DX Gateway and TGW ${tgw.name} reside in separate accounts`, - ); - } - // Catch error if there is no association with the TGW - if (!dxgw.transitGatewayAssociations || !dxgw.transitGatewayAssociations.find(item => item.name === tgw.name)) { - errors.push( - `[Transit Gateway ${tgw.name} route table ${routeTableName}]: cannot add route entry for DX Gateway ${dxAttachment.directConnectGatewayName}. DX Gateway and TGW ${tgw.name} are not associated`, - ); - } - } - } - } - - /** - * Function to validate transit gateway static route entries for VPN attachments - * @param cgws - * @param tgwName - * @param routeTableName - * @param entry - */ - private validateVpnStaticRouteEntry( - cgws: CustomerGatewayConfig[], - tgwName: string, - routeTableName: string, - entry: TransitGatewayRouteEntryConfig, - errors: string[], - ) { - if ( - entry.attachment && - isNetworkType('ITransitGatewayRouteTableVpnEntryConfig', entry.attachment) - ) { - const vpnAttachment = entry.attachment as TransitGatewayRouteTableVpnEntryConfig; - const cgw = cgws.find(cgwItem => - cgwItem.vpnConnections?.find(vpnItem => vpnItem.name === vpnAttachment.vpnConnectionName), - ); - // Validate VPN exists and is attached to this TGW - if (!cgw) { - errors.push( - `[Transit Gateway ${tgwName} route table ${routeTableName}]: cannot find customer gateway with VPN "${vpnAttachment.vpnConnectionName}"`, - ); - } - - // Validate VPN is attached to this TGW - if (cgw) { - const vpn = cgw.vpnConnections?.find(attachment => attachment.name === vpnAttachment.vpnConnectionName); - if (vpn && (!vpn.transitGateway || vpn.transitGateway !== tgwName)) { - errors.push( - `[Transit Gateway ${tgwName} route table ${routeTableName}]: VPN "${vpnAttachment.vpnConnectionName}" is not attached to this TGW`, - ); - } - } - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/network-config-validator/vpc-validator.ts b/source/packages/@aws-accelerator/config/validator/network-config-validator/vpc-validator.ts deleted file mode 100644 index bd3b244..0000000 --- a/source/packages/@aws-accelerator/config/validator/network-config-validator/vpc-validator.ts +++ /dev/null @@ -1,3170 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { ShareTargets, isNetworkType } from '../../lib/common'; -import { - ApplicationLoadBalancerConfig, - CustomizationsConfig, - Ec2FirewallInstanceConfig, -} from '../../lib/customizations-config'; -import { - NetworkAclSubnetSelection, - NetworkConfig, - NfwFirewallConfig, - PrefixListSourceConfig, - ResolverRuleConfig, - RouteTableEntryConfig, - SecurityGroupConfig, - SecurityGroupRuleConfig, - SecurityGroupSourceConfig, - SubnetConfig, - SubnetSourceConfig, - TransitGatewayAttachmentConfig, - TransitGatewayConfig, - VpcConfig, - VpcTemplatesConfig, -} from '../../lib/network-config'; -import { NetworkValidatorFunctions } from './network-validator-functions'; -import * as cdk from 'aws-cdk-lib'; - -/** - * Class to validate Vpcs - */ -export class VpcValidator { - private centralEndpointVpcRegions: string[]; - private customizationsConfig?: CustomizationsConfig; - constructor( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - customizationsConfig?: CustomizationsConfig, - ) { - this.customizationsConfig = customizationsConfig; - // - // Determine if there is a central endpoint VPC - // - this.centralEndpointVpcRegions = this.getCentralEndpointVpcs(values, helpers, errors); - // - // Validate VPC names are unique - this.validateVpcNames(values, helpers, errors); - // - // Validate VPC template deployment target ou names - this.validateVpcTemplatesDeploymentTargetOUs(values, helpers, errors); - // - // Validate Vpc templates deployment account names - // - this.validateVpcTemplatesDeploymentTargetAccounts(values, helpers, errors); - // - // Validate vpc account name - // - this.validateVpcAccountName(values, helpers, errors); - // - // Validate vpc tgw name - // - this.validateVpcTgwAccountName(values, helpers, errors); - // - // Validate VPC configurations - // - this.validateVpcConfiguration(values, helpers, errors); - // - // Validate Outpost and Local Gateway (LGW) configurations - // - this.validateOutpostConfiguration(values, helpers, errors); - // - // Validate VPC peering configurations - // - this.validateVpcPeeringConfiguration(values, errors); - // - // Validate Default VPC Configuration - // - this.validateDefaultVpcConfiguration(values, helpers, errors); - } - - private getCentralEndpointVpcs( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): string[] { - const vpcs = [...values.vpcs, ...(values.vpcTemplates ?? [])]; - const centralVpcs: VpcConfig[] = []; - const unsupportedRegions = ['us-gov-west-1', 'us-gov-east-1']; - // Get VPCs marked as central; do not allow VPC templates - vpcs.forEach(vpc => { - if (vpc.interfaceEndpoints?.central && !isNetworkType('IVpcConfig', vpc)) { - errors.push(`[VPC ${vpc.name}]: cannot define a VPC template as a central interface endpoint VPC`); - } - if (vpc.interfaceEndpoints?.central && isNetworkType('IVpcConfig', vpc)) { - centralVpcs.push(vpc); - } - }); - - // Check regions - const vpcRegions: string[] = []; - centralVpcs.forEach(vpc => vpcRegions.push(vpc.region)); - if (helpers.hasDuplicates(vpcRegions)) { - errors.push( - `More than one central endpoint VPC configured in a single region. One central endpoint VPC per region is supported. Central endpoint VPC regions configured: ${vpcRegions}`, - ); - } - if (vpcRegions.some(region => unsupportedRegions.includes(region))) { - errors.push( - `Central endpoints VPC configured in an unsupported region. Central endpoint VPC regions configured: ${vpcRegions}`, - ); - } - return vpcRegions; - } - - /** - * Validate uniqueness of VPC names - * @param values - * @param helpers - * @param errors - */ - private validateVpcNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const vpcs = [...values.vpcs, ...(values.vpcTemplates ?? [])]; - const vpcNames = vpcs.map(vpc => { - return vpc.name; - }); - - // Validate no VPC names are duplicated - if (helpers.hasDuplicates(vpcNames)) { - errors.push(`Duplicate VPC/VPC template names exist. VPC names must be unique. VPC names in file: ${vpcNames}`); - } - } - - /** - * Function to validate VPC template deployment target ou names - * @param values - * @param helpers - * @param errors - */ - private validateVpcTemplatesDeploymentTargetOUs( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const vpc of values.vpcTemplates ?? []) { - for (const ou of vpc.deploymentTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `Deployment target OU ${ou} for VPC template ${vpc.name} does not exist in organization-config.yaml file`, - ); - } - } - } - } - - /** - * Function to validate existence of VPC deployment target accounts - * Make sure deployment target accounts are part of account config file - * @param values - * @param helpers - * @param errors - */ - private validateVpcTemplatesDeploymentTargetAccounts( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const vpc of values.vpcTemplates ?? []) { - for (const account of vpc.deploymentTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `Deployment target account ${account} for VPC template ${vpc.name} does not exist in accounts-config.yaml file`, - ); - } - } - } - } - - /** - * Function to validate existence of vpc account name - * Make sure target account is part of account config file - * @param values - * @param helpers - * @param errors - */ - private validateVpcAccountName(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const vpcItem of values.vpcs ?? []) { - if (!helpers.accountExists(vpcItem.account)) { - errors.push( - `VPC "${vpcItem.name}" account name "${vpcItem.account}" does not exist in accounts-config.yaml file`, - ); - } - } - } - - /** - * Function to validate existence of vpc transit gateway account name - * Make sure deployment target accounts are part of account config file - * @param values - */ - private validateVpcTgwAccountName(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - for (const vpcItem of values.vpcs ?? []) { - for (const tgwAttachment of vpcItem.transitGatewayAttachments ?? []) { - if (!helpers.accountExists(tgwAttachment.transitGateway.account)) { - errors.push( - `VPC "${vpcItem.name}" TGW attachment "${tgwAttachment.transitGateway.name}" account name "${tgwAttachment.transitGateway.account}" does not exist in accounts-config.yaml file`, - ); - } - } - } - } - - /** - * Validate route tables for a given VPC - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateRouteTables( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate route tables names - this.validateRouteTableNames(vpcItem, helpers, errors); - // Validate route entries - this.validateRouteTableEntries(values, vpcItem, helpers, errors); - } - - /** - * Validate route table and route entry names - * @param vpcItem - * @param helpers - * @param errors - */ - private validateRouteTableNames( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const routeTableNames: string[] = []; - - vpcItem.routeTables?.forEach(routeTable => { - const routeEntryNames: string[] = []; - routeTableNames.push(routeTable.name); - - routeTable.routes?.forEach(route => { - routeEntryNames.push(route.name); - }); - - // Check if there are duplicate route entry names - if (helpers.hasDuplicates(routeEntryNames)) { - errors.push( - `[VPC ${vpcItem.name} route table ${routeTable.name}]: duplicate route entry names defined. Route entry names must be unique per route table. Route entry names configured: ${routeEntryNames}`, - ); - } - }); - - // Check if there are duplicate route table names - if (helpers.hasDuplicates(routeTableNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate route table names defined. Route table names must be unique per VPC. Route table names configured: ${routeTableNames}`, - ); - } - } - - /** - * Validate route entries have a valid destination configured - * @param routeTableEntryItem - * @param routeTableName - * @param vpcName - */ - private validateRouteEntryDestination( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (routeTableEntryItem.destinationPrefixList) { - // Check if a CIDR destination is also defined - if (routeTableEntryItem.destination || routeTableEntryItem.ipv6Destination) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} using destination and destinationPrefixList. Please choose only one destination type`, - ); - } - - // Throw error if network firewall or GWLB are the target - if (['networkFirewall', 'gatewayLoadBalancerEndpoint'].includes(routeTableEntryItem.type!)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} with type ${routeTableEntryItem.type} does not support destinationPrefixList`, - ); - } - - // Throw error if prefix list doesn't exist - if (!values.prefixLists?.find(item => item.name === routeTableEntryItem.destinationPrefixList)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} destinationPrefixList ${routeTableEntryItem.destinationPrefixList} does not exist`, - ); - } - } else if (routeTableEntryItem.destination || routeTableEntryItem.ipv6Destination) { - // Validate the destination CIDR or subnet - this.validateRouteEntryDestinationCidr(routeTableEntryItem, routeTableName, vpcItem, helpers, errors); - } else if (!['vpcPeering'].includes(routeTableEntryItem.type!)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} must define either destinationPrefixList, destination or ipv6Destination if type is not vpcPeering or gatewayEndpoint`, - ); - } - } - - /** - * Validate route entry destination CIDR or subnet reference is valid - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - * @param helpers - * @param errors - */ - private validateRouteEntryDestinationCidr( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (!routeTableEntryItem.destination && !routeTableEntryItem.ipv6Destination) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} does not have a destination defined`, - ); - } else { - if (routeTableEntryItem.destination && routeTableEntryItem.ipv6Destination) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} has both a destination and ipv6Destination defined. Please choose only one.`, - ); - } - if ( - !helpers.isValidIpv4Cidr(routeTableEntryItem.destination) && - !helpers.isValidIpv6Cidr(routeTableEntryItem.ipv6Destination) - ) { - // Check if subnet exists in the VPC - if (!helpers.getSubnet(vpcItem, routeTableEntryItem.ipv6Destination ?? routeTableEntryItem.destination!)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${ - routeTableEntryItem.name - } destination "${ - routeTableEntryItem.ipv6Destination ?? routeTableEntryItem.destination - }" is not a valid IPv4/v6 CIDR or subnet name`, - ); - } - // Validate target type - if (!['natGateway', 'networkFirewall', 'gatewayLoadBalancerEndpoint'].includes(routeTableEntryItem.type!)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} destination "${routeTableEntryItem.destination}" is not valid. Route entry type ${routeTableEntryItem.type} does not support dynamic subnet destinations`, - ); - } - } - } - } - - /** - * Validate IGW routes are associated with a VPC with an IGW attached - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - */ - private validateIgwRouteEntry( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - errors: string[], - ) { - if (!vpcItem.internetGateway) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} is targeting an IGW, but no IGW is attached to the VPC`, - ); - } - } - - /** - * Validate EIGW routes are associated with a VPC with an EIGW attached - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - */ - private validateEigwRouteEntry( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - errors: string[], - ) { - if (!vpcItem.egressOnlyIgw) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} is targeting an Egress-only IGW, but no EIGW is attached to the VPC. Please add "egressOnlyIgw: true" to the VPC configuration.`, - ); - } - } - - /** - * Validate VGW routes are associated with a VPC with an Virtual Private Gateway attached - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - */ - private validateVgwRouteEntry( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - errors: string[], - ) { - if (!vpcItem.virtualPrivateGateway) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} is targeting an VGW, but no VGW is attached to the VPC`, - ); - } - } - - /** - * Validate LGW routes are associated with a VPC with an Outpost and Local Gateway attached - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - */ - private validateLgwRouteEntry( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - errors: string[], - ) { - if (!('outposts' in vpcItem)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} is targeting an LGW, but no Outpost and LGW are associated with the VPC`, - ); - } - - const vpc = vpcItem as VpcConfig; - let lgwNameFound = false; - for (const outpost of vpc.outposts ?? []) { - if (outpost.localGateway?.name === routeTableEntryItem.target) { - lgwNameFound = true; - } - } - - if (!lgwNameFound) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} is targeting LGW ${routeTableEntryItem.target}, but the LGW is not associated with the VPC`, - ); - } - } - - /** - * Validate route table entries have a valid target configured - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - * @param values - * @param helpers - * @param errors - */ - private validateRouteEntryTarget( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const gwlbs = values.centralNetworkServices?.gatewayLoadBalancers; - const networkFirewalls = values.centralNetworkServices?.networkFirewall?.firewalls ?? []; - const tgws = values.transitGateways; - const vpcs = [...values.vpcs, ...(values.vpcTemplates ?? [])]; - const vpcPeers = values.vpcPeering; - - // Throw error if no target defined - if (!routeTableEntryItem.target) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} of type ${routeTableEntryItem.type} must include a target`, - ); - } - - // Throw error if GWLB endpoint doesn't exist - if ( - routeTableEntryItem.type === 'gatewayLoadBalancerEndpoint' && - !gwlbs?.find(item => item.endpoints.find(endpoint => endpoint.name === routeTableEntryItem.target)) - ) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target ${routeTableEntryItem.target} does not exist`, - ); - } - - // - // Validate network firewall route entry - this.validateNfwRouteEntry(routeTableEntryItem, routeTableName, vpcItem, networkFirewalls, helpers, errors); - - // Validate network interface route entry - if (routeTableEntryItem.type === 'networkInterface') { - this.validateNetworkInterfaceRouteEntry(routeTableEntryItem, routeTableName, vpcItem, helpers, errors); - } - - // Throw error if NAT gateway doesn't exist - if ( - routeTableEntryItem.type === 'natGateway' && - !vpcs.find(item => item.natGateways?.find(nat => nat.name === routeTableEntryItem.target)) - ) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target ${routeTableEntryItem.target} does not exist`, - ); - } - - // Throw error if transit gateway doesn't exist - if (routeTableEntryItem.type === 'transitGateway' && !tgws.find(item => item.name === routeTableEntryItem.target)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target ${routeTableEntryItem.target} does not exist`, - ); - } - - // Throw error if VPC peering doesn't exist - if ( - routeTableEntryItem.type === 'vpcPeering' && - !vpcPeers?.find(item => item.name === routeTableEntryItem.target) - ) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target ${routeTableEntryItem.target} does not exist`, - ); - } - } - - /** - * Validate network firewall route entry - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNetworkInterfaceRouteEntry( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (!routeTableEntryItem.target) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} with type networkInterface requires the 'target' property`, - ); - } - - if ( - !helpers.matchesRegex(routeTableEntryItem.target!, '\\${ACCEL_LOOKUP::EC2:ENI_([a-zA-Z0-9-/:]*)}') && - !helpers.matchesRegex(routeTableEntryItem.target!, '^eni-(\\d|[a-f]){17}$') - ) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} has invalid target. Target may be an ENI Id or accepted pattern: "^\\$\{ACCEL_LOOKUP::EC2:ENI_([a-zA-Z0-9-/:]*)}" Value entered: ${routeTableEntryItem.target} `, - ); - } - - if (helpers.matchesRegex(routeTableEntryItem.target!, '\\${ACCEL_LOOKUP::EC2:ENI_([a-zA-Z0-9-/:]*)}')) { - if (!this.isValidFirewallReference(routeTableEntryItem, vpcItem.name, errors)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} has invalid lookup target. Accepted pattern: "^\\$\{ACCEL_LOOKUP::EC2:ENI_([a-zA-Z0-9-/:]*)}" Value entered: ${routeTableEntryItem.target}`, - ); - } - } - } - - /** - * Validates that the referenced firewall exists in customizations config - * @param routeTableEntryItem: RouteTableEntryConfig, - * @param errors string[] - * @returns boolean - */ - private isValidFirewallReference( - routeTableEntryItem: RouteTableEntryConfig, - vpcName: string, - errors: string[], - ): boolean { - // - // Check that customizations config is defined - if (!this.customizationsConfig) { - errors.push( - `[Route Table entry: ${routeTableEntryItem.name}]: EC2 firewall reference variable entered but customizations-config.yaml is not defined.`, - ); - return false; - } else { - // Check that firewall exists - const lookupComponents = routeTableEntryItem.target!.split(':'); - const eniIndex = lookupComponents[3].split('_').pop(); - const firewallName = lookupComponents[4].replace(/\}$/, ''); - const firewall = this.customizationsConfig.firewalls?.instances?.find(instance => instance.name === firewallName); - - if (!firewall) { - errors.push( - `[Route Table entry: ${routeTableEntryItem.name}]: EC2 firewall instance "${firewallName}" is not defined in customizations-config.yaml`, - ); - return false; - } - - if (!eniIndex) { - errors.push( - `[Route Table entry: ${routeTableEntryItem.name}]: Unable to parse ENI index of EC2 firewall instance "${firewallName}" from pattern ${routeTableEntryItem.target}`, - ); - return false; - } - - if (vpcName !== firewall.vpc) { - errors.push( - `[Route Table entry: ${routeTableEntryItem.name}]: Firewall "${firewallName}" in route target ${routeTableEntryItem.target} must exist in the same VPC the route is created`, - ); - return false; - } - // - // Check device index - this.validateFirewallInterface(routeTableEntryItem.name, firewall, eniIndex, errors); - } - return true; - } - - /** - * Validates that the referenced network interface has an elastic IP associated or sourceDestCheck set to false - * @param routeTableEntryName string - * @param firewall Ec2FirewallInstanceConfig - * @param eniIndex string - * @param errors string[] - */ - private validateFirewallInterface( - routeTableEntryName: string, - firewall: Ec2FirewallInstanceConfig, - eniIndex: string, - errors: string[], - ) { - if (!firewall.launchTemplate.networkInterfaces) { - errors.push( - `[Route Table entry: ${routeTableEntryName}]: EC2 firewall instance "${firewall.name}" launch template does not have network interfaces defined in customizations-config.yaml`, - ); - } else { - const deviceIndex = Number(eniIndex); - if (deviceIndex > firewall.launchTemplate.networkInterfaces.length - 1) { - errors.push( - `[Route Table entry: ${routeTableEntryName}]: EC2 firewall instance "${firewall.name}" device index ${deviceIndex} does not exist in customizations-config.yaml`, - ); - } else { - const networkInterface = firewall.launchTemplate.networkInterfaces[deviceIndex]; - if ( - !networkInterface.associateElasticIp && - (networkInterface.sourceDestCheck === undefined || networkInterface.sourceDestCheck === true) - ) { - errors.push( - `[Route Table entry: ${routeTableEntryName}]: EC2 firewall instance "${firewall.name}" device index ${deviceIndex} must have the associateElasticIp set to true or sourceDestCheck property set to false in customizations-config.yaml`, - ); - } - } - } - } - - /** - * Validate route table entries - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateRouteTableEntries( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.routeTables?.forEach(routeTableItem => { - routeTableItem.routes?.forEach(entry => { - // Validate destination exists - if (entry.type && entry.type !== 'gatewayEndpoint') { - this.validateRouteEntryDestination(entry, routeTableItem.name, vpcItem, values, helpers, errors); - } - - // Validate IGW route - if (entry.type && entry.type === 'internetGateway') { - this.validateIgwRouteEntry(entry, routeTableItem.name, vpcItem, errors); - } - - // Validate IGW route - if (entry.type && entry.type === 'egressOnlyIgw') { - this.validateEigwRouteEntry(entry, routeTableItem.name, vpcItem, errors); - } - - // Validate VGW route - if (entry.type && entry.type === 'virtualPrivateGateway') { - this.validateVgwRouteEntry(entry, routeTableItem.name, vpcItem, errors); - } - - // Validate LGW route - if (entry.type && entry.type === 'localGateway') { - this.validateLgwRouteEntry(entry, routeTableItem.name, vpcItem, errors); - } - - // Validate target exists - if ( - entry.type && - [ - 'gatewayLoadBalancerEndpoint', - 'natGateway', - 'networkFirewall', - 'networkInterface', - 'transitGateway', - 'vpcPeering', - ].includes(entry.type) - ) { - this.validateRouteEntryTarget(entry, routeTableItem.name, vpcItem, values, helpers, errors); - } - }); - }); - } - - /** - * Validate network firewall route entry - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - * @param networkFirewalls - * @param helpers - * @param errors - */ - private validateNfwRouteEntry( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - networkFirewalls: NfwFirewallConfig[], - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // - // Validate network firewall target exists - if (routeTableEntryItem.type === 'networkFirewall') { - const nfwTarget = networkFirewalls.find(item => item.name === routeTableEntryItem.target); - if (!nfwTarget) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target Network Firewall "${routeTableEntryItem.target}" does not exist in network-config.yaml`, - ); - } else { - if (nfwTarget.vpc !== vpcItem.name) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target Network Firewall "${routeTableEntryItem.target}" must be deployed to the same VPC as the route table. Configured VPC target: ${nfwTarget.vpc}`, - ); - } else { - // - // Validate target AZ exists - this.validateNfwRouteEntryTarget(routeTableEntryItem, routeTableName, vpcItem, nfwTarget, helpers, errors); - } - } - } - } - - /** - * Validate network firewall route entry AZ target - * @param routeTableEntryItem - * @param routeTableName - * @param vpcItem - * @param nfwTarget - * @param helpers - * @param errors - */ - private validateNfwRouteEntryTarget( - routeTableEntryItem: RouteTableEntryConfig, - routeTableName: string, - vpcItem: VpcConfig | VpcTemplatesConfig, - nfwTarget: NfwFirewallConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const endpointAzs: (string | number)[] = []; - // - // Get subnet availability zones - nfwTarget.subnets.forEach(subnetItem => { - const subnet = helpers.getSubnet(vpcItem, subnetItem); - if (subnet && subnet.availabilityZone) { - endpointAzs.push(subnet.availabilityZone); - } - }); - // - // Throw error if network firewall target AZ doesn't exist - if (!routeTableEntryItem.targetAvailabilityZone) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} with type networkFirewall must include targetAvailabilityZone`, - ); - } else { - // - // Validate AZ is correct format - if ( - typeof routeTableEntryItem.targetAvailabilityZone === 'string' && - !helpers.matchesRegex(routeTableEntryItem.targetAvailabilityZone, '^[a-z]{1}$') - ) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target AZ "${routeTableEntryItem.targetAvailabilityZone}" is not in the correct format. AZ must be a single alphanumeric character.`, - ); - } - if ( - typeof routeTableEntryItem.targetAvailabilityZone === 'number' && - !helpers.matchesRegex(routeTableEntryItem.targetAvailabilityZone.toString(), '^[0-9]{1}$') - ) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target AZ "${routeTableEntryItem.targetAvailabilityZone}" is not in the correct format. AZ must be a single alphanumeric character.`, - ); - } - // - // Validate endpoint AZ exists - if (!endpointAzs.includes(routeTableEntryItem.targetAvailabilityZone)) { - errors.push( - `[Route table ${routeTableName} for VPC ${vpcItem.name}]: route entry ${routeTableEntryItem.name} target AZ "${routeTableEntryItem.targetAvailabilityZone}" does not exist for Network Firewall "${routeTableEntryItem.target}". Configured AZs: ${endpointAzs}`, - ); - } - } - } - - /** - * Validate IPAM allocations for a given VPC - * @param vpcItem - * @param values - * @param errors - */ - private validateIpamAllocations(vpcItem: VpcConfig | VpcTemplatesConfig, values: NetworkConfig, errors: string[]) { - const ipams = values.centralNetworkServices?.ipams; - vpcItem.ipamAllocations?.forEach(alloc => { - const ipamPool = ipams?.find(ipam => ipam.pools?.find(pool => pool.name === alloc.ipamPoolName)); - // Check if targeted IPAM exists - if (!ipamPool) { - errors.push(`[VPC ${vpcItem.name}]: target IPAM pool ${alloc.ipamPoolName} is not defined`); - } - // Validate prefix length - if (ipamPool && !this.isValidIpv4PrefixLength(alloc.netmaskLength)) { - errors.push( - `[VPC ${vpcItem.name} allocation ${alloc.ipamPoolName}]: netmaskLength cannot be larger than 16 or smaller than 28`, - ); - } - }); - - vpcItem.subnets?.forEach(subnet => { - // Check if allocation is created for VPC - if ( - subnet.ipamAllocation && - !vpcItem.ipamAllocations?.find(alloc => alloc.ipamPoolName === subnet.ipamAllocation!.ipamPoolName) - ) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: target IPAM pool ${subnet.ipamAllocation.ipamPoolName} is not a source pool of the VPC`, - ); - } - // Check if targeted IPAM pool exists - if ( - subnet.ipamAllocation && - !ipams?.find(ipam => ipam.pools?.find(pool => pool.name === subnet.ipamAllocation!.ipamPoolName)) - ) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: target IPAM pool ${subnet.ipamAllocation.ipamPoolName} is not defined`, - ); - } - // Validate prefix length - if (subnet.ipamAllocation && !this.isValidIpv4PrefixLength(subnet.ipamAllocation.netmaskLength)) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: netmaskLength cannot be larger than 16 or smaller than 28`, - ); - } - }); - } - - /** - * Function to validate conditional dependencies for Outpost and Local Gateway configurations. - * @param values - */ - private validateOutpostConfiguration(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // - // Validate that local gateways do not have the same name across different outposts - // - this.validateLocalGatewayNames(values, helpers, errors); - // - // Validate that all outpost names are unique - // - this.validateOutpostNames(values, helpers, errors); - } - - /** - * Function to validate conditional dependencies for VPC configurations. - * @param values - */ - private validateVpcConfiguration(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - const vpcs = [...values.vpcs, ...(values.vpcTemplates ?? [])] ?? []; - vpcs.forEach(vpcItem => { - // - // Validate VPC structure - // - const allValid = this.validateVpcStructure(vpcItem, errors); - if (allValid) { - // - // Validate VPC CIDRs - // - this.validateVpcCidrs(vpcItem, helpers, errors); - // - // Validate DHCP options - // - this.validateDhcpOptions(values, vpcItem, helpers, errors); - // - // Validate DNS firewall rule groups - // - this.validateDnsFirewallRuleGroups(values, vpcItem, helpers, errors); - // - // Validate endpoint policies - // - this.validateEndpointPolicies(values, vpcItem, errors); - // - // Validate gateway endpoints - // - this.validateGatewayEndpoints(vpcItem, helpers, errors); - // - // Validate interface endpoints - // - this.validateInterfaceEndpoints(vpcItem, helpers, errors); - // - // Validate IPAM allocations - // - this.validateIpamAllocations(vpcItem, values, errors); - // - // Validate NAT gateways - // - this.validateNatGateways(vpcItem, helpers, errors); - // - // Validate NACLs - // - this.validateNacls(vpcItem, helpers, errors); - // - // Validate query logs - // - this.validateQueryLogs(values, vpcItem, helpers, errors); - // - // Validate resolver rules - // - this.validateResolverRules(values, vpcItem, helpers, errors); - // - // Validate route tables - // - this.validateRouteTables(values, vpcItem, helpers, errors); - // - // Validate security groups - // - this.validateSecurityGroups(values, vpcItem, helpers, errors); - // - // Validate subnets - // - this.validateSubnets(vpcItem, helpers, errors); - // - // Validate transit gateway attachments - // - this.validateTgwAttachments(values, vpcItem, helpers, errors); - // - // Validate ACM shared targets - // - this.validateAcmSharesToAlbShares(values, vpcItem, helpers, errors); - } - }); - } - - private isValidIpv4PrefixLength(prefix: number): boolean { - return prefix >= 16 && prefix <= 28; - } - - private isValidIpv6VpcPrefixLength(prefix: number): boolean { - return prefix >= 44 && prefix <= 60 && prefix % 4 === 0; - } - - private isValidIpv6SubnetPrefixLength(prefix: number): boolean { - return prefix >= 44 && prefix <= 64 && prefix % 4 === 0; - } - - /** - * Validate the base structure of the VPC object is correct - * @param vpcItem - * @param errors - * @returns - */ - private validateVpcStructure(vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]): boolean { - let allValid = true; - // Validate the VPC doesn't have a static CIDR and IPAM defined - if (vpcItem.cidrs && vpcItem.ipamAllocations) { - allValid = false; - errors.push(`[VPC ${vpcItem.name}]: Both a CIDR and IPAM allocation are defined. Please choose only one`); - } - // Validate the VPC doesn't have a static CIDR and IPAM defined - if (!vpcItem.cidrs && !vpcItem.ipamAllocations) { - allValid = false; - errors.push(`[VPC ${vpcItem.name}]: Neither a CIDR or IPAM allocation are defined. Please define one property`); - } - // Validate there is at least one IPv4 CIDR assigned to the VPC - if (vpcItem.ipv6Cidrs && !vpcItem.cidrs && !vpcItem.ipamAllocations) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name}]: A VPC must have at least one IPv4 CIDR defined. Please specify either an IPv4 static CIDR or IPAM allocation.`, - ); - } - // Validate that a BYOP pool is defined if using a static IPv6 CIDR - vpcItem.ipv6Cidrs?.forEach(ipv6Cidr => { - if (ipv6Cidr.cidrBlock && !ipv6Cidr.byoipPoolId) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name}]: IPv6 static CIDR block defined without specifying a BYOIP address pool ID. Please specify a pool ID or choose an Amazon-provided IPv6 CIDR instead`, - ); - } - if (ipv6Cidr.amazonProvided && (ipv6Cidr.byoipPoolId || ipv6Cidr.cidrBlock)) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name}]: IPv6 static CIDR block defined with "amazonProvided: true" and other property elements. Please choose either amazonProvided or a static IPv6 CIDR from a BYOIP address pool.`, - ); - } - }); - // If the VPC is using central endpoints, ensure there is a central endpoints VPC in regions - if (vpcItem.useCentralEndpoints && !this.centralEndpointVpcRegions.includes(vpcItem.region)) { - allValid = false; - errors.push(`[VPC ${vpcItem.name}]: useCentralEndpoints is true, but no central endpoint VPC defined in region`); - } - vpcItem.routeTables?.forEach(routeTableItem => { - // Throw error if gateway association exists but no internet gateway - if (routeTableItem.gatewayAssociation === 'internetGateway' && !vpcItem.internetGateway) { - allValid = false; - errors.push( - `[Route table ${routeTableItem.name} for VPC ${vpcItem.name}]: attempting to configure a gateway association with no IGW attached to the VPC!`, - ); - } - if (routeTableItem.gatewayAssociation === 'virtualPrivateGateway' && !vpcItem.virtualPrivateGateway) { - allValid = false; - errors.push( - `[Route table ${routeTableItem.name} for VPC ${vpcItem.name}]: attempting to configure a gateway association with no VGW attached to the VPC!`, - ); - } - }); - return allValid; - } - - /** - * Validate VPC CIDRs - * @param vpcItem - * @param helpers - * @param errors - */ - private validateVpcCidrs( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate IPv4 CIDRs - vpcItem.cidrs?.forEach(cidr => { - if (!helpers.isValidIpv4Cidr(cidr)) { - errors.push(`[VPC ${vpcItem.name}]: IPv4 CIDR "${cidr}" is invalid. Value must be a valid IPv4 CIDR range`); - } - // Validate prefix - const prefix = helpers.isValidIpv4Cidr(cidr) ? cidr.split('/')[1] : undefined; - if (prefix && !this.isValidIpv4PrefixLength(parseInt(prefix))) { - errors.push( - `[VPC ${vpcItem.name}]: IPv4 CIDR "${cidr}" is invalid. CIDR prefix cannot be larger than /16 or smaller than /28`, - ); - } - }); - // Validate IPv6 CIDRs - vpcItem.ipv6Cidrs?.forEach(ipv6Cidr => { - const cidrRange = ipv6Cidr.cidrBlock; - if (cidrRange && !helpers.isValidIpv6Cidr(cidrRange)) { - errors.push( - `[VPC ${vpcItem.name}]: IPv6 CIDR "${ipv6Cidr.cidrBlock}" is invalid. Value must be a valid IPv6 CIDR range`, - ); - } - // Validate prefix - const ipv6Prefix = cidrRange && helpers.isValidIpv6Cidr(cidrRange) ? cidrRange.split('/')[1] : undefined; - if (ipv6Prefix && !this.isValidIpv6VpcPrefixLength(parseInt(ipv6Prefix))) { - errors.push( - `[VPC ${vpcItem.name}]: IPv6 CIDR "${cidrRange}" is invalid. CIDR prefix cannot be larger than /44 or smaller than /60 and must be in an increment of /4`, - ); - } - }); - } - - /** - * Validate DHCP options - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateDhcpOptions( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - if (vpcItem.dhcpOptions) { - const optSet = values.dhcpOptions?.find(item => item.name === vpcItem.dhcpOptions); - const vpcAccountNames = helpers.getVpcAccountNames(vpcItem); - const targetComparison = optSet ? helpers.compareTargetAccounts(vpcAccountNames, optSet.accounts) : []; - - if (!optSet) { - errors.push(`[VPC ${vpcItem.name}]: DHCP options set ${vpcItem.dhcpOptions} does not exist`); - } - // Validate DHCP options set exists in the same account and region - if (optSet && targetComparison.length > 0) { - errors.push( - `[VPC ${vpcItem.name}]: DHCP options set "${vpcItem.dhcpOptions}" is not deployed to one or more VPC deployment target accounts. Missing accounts: ${targetComparison}`, - ); - } - if (optSet && !optSet.regions.includes(vpcItem.region)) { - errors.push( - `[VPC ${vpcItem.name}]: DHCP options set "${vpcItem.dhcpOptions}" is not deployed to VPC region ${vpcItem.region}`, - ); - } - } - } - - /** - * Validate DNS firewall rule groups - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateDnsFirewallRuleGroups( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const groupNames: string[] = []; - const priorities: string[] = []; - vpcItem.dnsFirewallRuleGroups?.forEach(group => { - groupNames.push(group.name); - priorities.push(group.priority.toString()); - }); - - // Validate there are no duplicates - if (helpers.hasDuplicates(groupNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate DNS firewall rule groups defined. Rule groups must be unique. Rule groups configured: ${groupNames}`, - ); - } - if (helpers.hasDuplicates(priorities)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate DNS firewall rule group priorities defined. Priorities must be unique. Priorities configured: ${priorities}`, - ); - } - - // Validate rule groups - const vpcAccountNames = helpers.getVpcAccountNames(vpcItem); - groupNames.forEach(name => { - const group = values.centralNetworkServices?.route53Resolver?.firewallRuleGroups?.find( - item => item.name === name, - ); - if (!group) { - errors.push(`[VPC ${vpcItem.name}]: DNS firewall rule group "${name}" does not exist`); - } else { - // Validate accounts and regions - const groupAccountNames = helpers.getDelegatedAdminShareTargets(group.shareTargets); - const targetComparison = helpers.compareTargetAccounts(vpcAccountNames, groupAccountNames); - if (targetComparison.length > 0) { - errors.push( - `[VPC ${vpcItem.name}]: DNS firewall rule group "${name}" is not shared to one or more VPC deployment target accounts. Missing accounts: ${targetComparison}`, - ); - } - if (!group.regions.includes(vpcItem.region)) { - errors.push( - `[VPC ${vpcItem.name}]: DNS firewall rule group "${name}" is not deployed to VPC region ${vpcItem.region}`, - ); - } - } - }); - } - - /** - * Validate VPC endpoint policies - * @param values - * @param vpcItem - * @param errors - */ - private validateEndpointPolicies(values: NetworkConfig, vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]) { - const policies = values.endpointPolicies.map(policy => { - return policy.name; - }); - const endpoints = [ - ...(vpcItem.gatewayEndpoints?.endpoints ?? []), - ...(vpcItem.interfaceEndpoints?.endpoints ?? []), - ]; - - // Validate default policies - if (vpcItem.gatewayEndpoints && !policies.includes(vpcItem.gatewayEndpoints.defaultPolicy)) { - errors.push( - `[VPC ${vpcItem.name}]: gateway endpoint defaultPolicy "${vpcItem.gatewayEndpoints.defaultPolicy}" does not exist`, - ); - } - if (vpcItem.interfaceEndpoints && !policies.includes(vpcItem.interfaceEndpoints.defaultPolicy)) { - errors.push( - `[VPC ${vpcItem.name}]: interface endpoint defaultPolicy "${vpcItem.interfaceEndpoints.defaultPolicy}" does not exist`, - ); - } - - // Validate per-endpoint policies - endpoints.forEach(endpoint => { - if (endpoint.policy && !policies.includes(endpoint.policy)) { - errors.push( - `[VPC ${vpcItem.name}]: endpoint policy "${endpoint.policy}" for ${endpoint.service} endpoint does not exist`, - ); - } - }); - } - - /** - * Validate gateway endpoints - * @param vpcItem - * @param helpers - * @param errors - */ - private validateGatewayEndpoints( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const endpoints: string[] = []; - vpcItem.gatewayEndpoints?.endpoints.forEach(endpoint => endpoints.push(endpoint.service)); - - if (endpoints.length > 2) { - errors.push(`[VPC ${vpcItem.name}]: no more than two gateway endpoints may be specified`); - } - - if (helpers.hasDuplicates(endpoints)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate gateway endpoint services defined. Services must be unique. Services configured: ${endpoints}`, - ); - } - } - - /** - * Validate interface endpoint configuration - * @param vpcItem - * @param helpers - * @param errors - */ - private validateInterfaceEndpoints( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate endpoint service names - const services: string[] = []; - vpcItem.interfaceEndpoints?.endpoints.forEach(endpoint => services.push(endpoint.service)); - if (helpers.hasDuplicates(services)) { - errors.push( - `[VPC ${vpcItem.name}]: interfaceEndpoints has duplicate service endpoints defined. Services must be unique. Services configured: ${services}`, - ); - } - - // Validate allowed CIDRs - vpcItem.interfaceEndpoints?.allowedCidrs?.forEach(cidr => { - if (!helpers.isValidIpv4Cidr(cidr)) { - errors.push( - `[VPC ${vpcItem.name}]: interface endpoint allowed CIDR "${cidr}" is invalid. Value must be a valid IPv4 CIDR range`, - ); - } - }); - - // Validate there are no duplicate subnet names - if (vpcItem.interfaceEndpoints && helpers.hasDuplicates(vpcItem.interfaceEndpoints.subnets)) { - errors.push( - `[VPC ${vpcItem.name}]: interfaceEndpoints has duplicate target subnets defined. Subnets must be unique. Subnets configured: ${vpcItem.interfaceEndpoints.subnets}`, - ); - } - - // Validate that tags are specified for the central endpoint VPC and not the spoke(s) - if (vpcItem.interfaceEndpoints?.tags && !vpcItem.interfaceEndpoints.central) { - errors.push( - `[VPC ${vpcItem.name}]: has tags set under interfaceEndpoints to tag the Private Hosted Zones, but is not set to the central VPC for interface endpoints`, - ); - } - - // Validate subnets - const azs: (string | number)[] = []; - vpcItem.interfaceEndpoints?.subnets.forEach(subnetName => { - const subnet = helpers.getSubnet(vpcItem, subnetName); - if (!subnet) { - errors.push(`[VPC ${vpcItem.name}]: interfaceEndpoints target subnet "${subnetName}" does not exist in VPC`); - } else { - azs.push(subnet.availabilityZone ? subnet.availabilityZone : ''); - } - }); - // Validate there are no duplicate AZs - if (helpers.hasDuplicates(azs)) { - errors.push( - `[VPC ${vpcItem.name}]: interfaceEndpoints target subnets reside in duplicate AZs. AZs must be unique. AZs configured: ${azs}`, - ); - } - } - - /** - * Validate NAT gateways for a given VPC - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNatGateways( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const natNames: string[] = []; - vpcItem.natGateways?.forEach(nat => { - natNames.push(nat.name); - // Validate subnet exists - if (!helpers.getSubnet(vpcItem, nat.subnet)) { - errors.push(`[VPC ${vpcItem.name} NAT gateway ${nat.name}]: subnet "${nat.subnet}" does not exist in the VPC`); - } - // Validate connectivity type - if (nat.private && nat.allocationId) { - errors.push( - `[VPC ${vpcItem.name} NAT gateway ${nat.name}]: cannot define an allocationId for a private NAT gateway`, - ); - } - }); - - if (helpers.hasDuplicates(natNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate NAT gateway names defined. NAT gateway names must be unique. NAT gateway names configured: ${natNames}`, - ); - } - } - - /** - * Validate NACLs for a given VPC - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNacls(vpcItem: VpcConfig | VpcTemplatesConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - // Validate NACL names - this.validateNaclNames(vpcItem, helpers, errors); - // Validate NACL subnets - this.validateNaclSubnets(vpcItem, helpers, errors); - // Validate rule numbers - this.validateNaclRuleNumbers(vpcItem, helpers, errors); - // Validate ports - this.validateNaclPorts(vpcItem, errors); - // Validate NACL source/destination - this.validateNaclSourceDestinationConfig(vpcItem, helpers, errors); - } - - /** - * Validate there are no duplicate NACL names - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclNames( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const naclNames: string[] = []; - vpcItem.networkAcls?.forEach(nacl => naclNames.push(nacl.name)); - - if (helpers.hasDuplicates(naclNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate NACL names defined. NACL names must be unique. NACL names configured: ${naclNames}`, - ); - } - } - - /** - * Validate NACL subnet associations - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclSubnets( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const subnetNames: string[] = []; - vpcItem.networkAcls?.forEach(nacl => { - nacl.subnetAssociations.forEach(subnet => { - subnetNames.push(subnet); - // Validate subnet exists - if (!helpers.getSubnet(vpcItem, subnet)) { - errors.push(`[VPC ${vpcItem.name} NACL ${nacl.name}]: subnet "${subnet}" does not exist in the VPC`); - } - }); - }); - // Validate there are no duplicates - if (helpers.hasDuplicates(subnetNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate NACL subnet associations defined. Subnet associations must be unique. Associations configured: ${subnetNames}`, - ); - } - } - - /** - * Validate rule numbers for NACLs in a VPC - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclRuleNumbers( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.networkAcls?.forEach(nacl => { - const inboundIds: string[] = []; - const outboundIds: string[] = []; - // Validate inbound rules - nacl.inboundRules?.forEach(inboundRule => { - inboundIds.push(inboundRule.rule.toString()); - - if (inboundRule.rule < 1 || inboundRule.rule > 32766) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name}]: NACL inbound rule "${inboundRule.rule}" is invalid. Rule ID must be an integer in the range 1-32766`, - ); - } - }); - // Validate outbound rules - nacl.outboundRules?.forEach(outboundRule => { - outboundIds.push(outboundRule.rule.toString()); - - if (outboundRule.rule < 1 || outboundRule.rule > 32766) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name}]: NACL outbound rule "${outboundRule.rule}" is invalid. Rule ID must be an integer in the range 1-32766`, - ); - } - }); - - // Validate duplicates - if (helpers.hasDuplicates(inboundIds)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name}]: duplicate inbound rule IDs defined. Rule IDs must be unique. Rule IDs configured: ${inboundIds}`, - ); - } - if (helpers.hasDuplicates(outboundIds)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name}]: duplicate outbound rule IDs defined. Rule IDs must be unique. Rule IDs configured: ${outboundIds}`, - ); - } - }); - } - - /** - * Validate ports defined in VPC NACLs - * @param vpcItem - * @param errors - */ - private validateNaclPorts(vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]) { - vpcItem.networkAcls?.forEach(nacl => { - // Validate inbound ports - nacl.inboundRules?.forEach(inbound => { - const isAllPorts = inbound.fromPort === -1 && inbound.toPort === -1; - const isValidPortRange = inbound.fromPort <= inbound.toPort; - const portRangeString = `fromPort: ${inbound.fromPort}, toPort: ${inbound.toPort}`; - if (!isValidPortRange) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: fromPort must be less than or equal to toPort. Defined port range: ${portRangeString}`, - ); - } else { - if (!isAllPorts && (inbound.fromPort < 0 || inbound.fromPort > 65535)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: when not using -1, fromPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - - if (!isAllPorts && (inbound.toPort < 0 || inbound.toPort > 65535)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: when not using -1, toPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - } - }); - // Validate outbound ports - nacl.outboundRules?.forEach(outbound => { - const isAllPorts = outbound.fromPort === -1 && outbound.toPort === -1; - const isValidPortRange = outbound.fromPort <= outbound.toPort; - const portRangeString = `fromPort: ${outbound.fromPort}, toPort: ${outbound.toPort}`; - if (!isValidPortRange) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: fromPort must be less than or equal to toPort. Defined port range: ${portRangeString}`, - ); - } else { - if (!isAllPorts && (outbound.fromPort < 0 || outbound.fromPort > 65535)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: when not using -1, fromPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - - if (!isAllPorts && (outbound.toPort < 0 || outbound.toPort > 65535)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: when not using -1, toPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - } - }); - }); - } - - /** - * Validate NACL rule source/destination configuration - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclSourceDestinationConfig( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate NACL inbound CIDRs - this.validateNaclCidrs(vpcItem, helpers, errors); - // Validate NACL inbound subnet selection - this.validateNaclInboundSubnetSelections(vpcItem, helpers, errors); - // Validate NACL outbound subnet selection - this.validateNaclOutboundSubnetSelections(vpcItem, helpers, errors); - } - - /** - * Validate NACL CIDR sources/destinations - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclCidrs( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.networkAcls?.forEach(nacl => { - // Validate inbound sources - nacl.inboundRules?.forEach(inbound => { - if (typeof inbound.source === 'string') { - // Validate CIDR source - if (!helpers.isValidIpv4Cidr(inbound.source) && !helpers.isValidIpv6Cidr(inbound.source)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: source "${inbound.source}" is invalid. Source must be a valid IPv4/v6 CIDR or subnet selection`, - ); - } - } - }); - // Validate outbound destinations - nacl.outboundRules?.forEach(outbound => { - if (typeof outbound.destination === 'string') { - // Validate CIDR source - if (!helpers.isValidIpv4Cidr(outbound.destination) && !helpers.isValidIpv6Cidr(outbound.destination)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: destination "${outbound.destination}" is invalid. Destination must be a valid IPv4/v6 CIDR or subnet selection`, - ); - } - } - }); - }); - } - - /** - * Validate NACL inbound subnet selections - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclInboundSubnetSelections( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.networkAcls?.forEach(nacl => { - nacl.inboundRules?.forEach(inbound => { - if (isNetworkType('INetworkAclSubnetSelection', inbound.source)) { - // Validate subnet source - const vpc = helpers.getVpc(inbound.source.vpc); - if (!vpc) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: source VPC "${inbound.source.vpc}" does not exist`, - ); - } else { - // Validate subnet - if (!helpers.getSubnet(vpc, inbound.source.subnet)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: subnet "${inbound.source.subnet}" does not exist in source VPC "${inbound.source.vpc}"`, - ); - } - // Validate account target - const vpcAccountNames = helpers.getVpcAccountNames(vpc); - if (!vpcAccountNames.includes(inbound.source.account)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: source VPC "${inbound.source.vpc}" is not deployed to account "${inbound.source.account}"`, - ); - } - // Validate VPC region - if (inbound.source.region && inbound.source.region !== vpc.region) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: source VPC "${inbound.source.vpc}" does not exist in region "${inbound.source.region}"`, - ); - } - if (!inbound.source.region && vpcItem.region !== vpc.region) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} inbound rule ${inbound.rule}]: source VPC "${inbound.source.vpc}" does not exist in VPC region "${vpcItem.region}." Use region property for subnet selection if targeting a source VPC in a different region`, - ); - } - } - } - }); - }); - } - - /** - * Validate NACL outbound subnet selections - * @param vpcItem - * @param helpers - * @param errors - */ - private validateNaclOutboundSubnetSelections( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.networkAcls?.forEach(nacl => { - nacl.outboundRules?.forEach(outbound => { - if (isNetworkType('INetworkAclSubnetSelection', outbound.destination)) { - // Validate subnet source - const vpc = helpers.getVpc(outbound.destination.vpc); - if (!vpc) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: destination VPC "${outbound.destination.vpc}" does not exist`, - ); - } else { - // Validate subnet - if (!helpers.getSubnet(vpc, outbound.destination.subnet)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: subnet "${outbound.destination.subnet}" does not exist in destination VPC "${outbound.destination.vpc}"`, - ); - } - // Validate account target - const vpcAccountNames = helpers.getVpcAccountNames(vpc); - if (!vpcAccountNames.includes(outbound.destination.account)) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: destination VPC "${outbound.destination.vpc}" is not deployed to account "${outbound.destination.account}"`, - ); - } - // Validate VPC region - if (outbound.destination.region && outbound.destination.region !== vpc.region) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: destination VPC "${outbound.destination.vpc}" does not exist in region "${outbound.destination.region}"`, - ); - } - if (!outbound.destination.region && vpcItem.region !== vpc.region) { - errors.push( - `[VPC ${vpcItem.name} NACL ${nacl.name} outbound rule ${outbound.rule}]: destination VPC "${outbound.destination.vpc}" does not exist in VPC region "${vpcItem.region}." Use region property for subnet selection if targeting a destination VPC in a different region`, - ); - } - } - } - }); - }); - } - - /** - * Validate DNS query logs - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateQueryLogs( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate query log name - const queryLogs = values.centralNetworkServices?.route53Resolver?.queryLogs; - vpcItem.queryLogs?.forEach(name => { - if (!queryLogs) { - errors.push(`[VPC ${vpcItem.name}]: DNS query logs "${name}" does not exist`); - } else { - if (name !== queryLogs.name) { - errors.push(`[VPC ${vpcItem.name}]: DNS query logs "${name}" does not exist`); - } - - // Validate query log share targets - const vpcAccountNames = helpers.getVpcAccountNames(vpcItem); - const queryLogAccountNames = helpers.getDelegatedAdminShareTargets(queryLogs.shareTargets); - const targetComparison = helpers.compareTargetAccounts(vpcAccountNames, queryLogAccountNames); - if (targetComparison.length > 0) { - errors.push( - `[VPC ${vpcItem.name}]: DNS query logging configuration "${name}" is not shared to one or more VPC deployment target accounts. Missing accounts: ${targetComparison}`, - ); - } - } - }); - } - - /** - * Validate resolver rules - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateResolverRules( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Fetch resolver rules - const resolverRules: ResolverRuleConfig[] = []; - values.centralNetworkServices?.route53Resolver?.rules?.forEach(systemRule => resolverRules.push(systemRule)); - values.centralNetworkServices?.route53Resolver?.endpoints?.forEach(endpoint => { - endpoint.rules?.forEach(forwardRule => resolverRules.push(forwardRule)); - }); - - // Validate rule - vpcItem.resolverRules?.forEach(name => { - const rule = resolverRules.find(item => name === item.name); - if (!rule) { - errors.push(`[VPC ${vpcItem.name}]: Resolver rule "${name}" does not exist`); - } else { - // Validate target accounts - const vpcAccountNames = helpers.getVpcAccountNames(vpcItem); - const ruleAccountNames = helpers.getDelegatedAdminShareTargets(rule.shareTargets); - const resolverEndpoint = values.centralNetworkServices?.route53Resolver?.endpoints?.find(endpointItem => - endpointItem.rules?.find(ruleItem => rule.name === ruleItem.name), - ); - const targetComparison = helpers.compareTargetAccounts(vpcAccountNames, ruleAccountNames); - - if (targetComparison.length > 0) { - errors.push( - `[VPC ${vpcItem.name}]: Resolver rule "${name}" is not shared to one or more VPC deployment target accounts. Missing accounts: ${targetComparison}`, - ); - } - // Validate target region - if (rule.excludedRegions && rule.excludedRegions.includes(vpcItem.region)) { - errors.push(`[VPC ${vpcItem.name}]: Resolver rule "${name}" is not deployed to VPC region ${vpcItem.region}`); - } - if (resolverEndpoint) { - const resolverEndpointVpc = helpers.getVpc(resolverEndpoint.vpc); - if (resolverEndpointVpc && resolverEndpointVpc.region !== vpcItem.region) { - errors.push( - `[VPC ${vpcItem.name}]: Resolver rule "${name}" is not deployed to VPC region ${vpcItem.region}`, - ); - } - } - } - }); - } - - /** - * Validate security group sources - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroups( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate group names - this.validateSecurityGroupNames(vpcItem, helpers, errors); - // Validate group structure - const ingressValid = this.validateSecurityGroupIngressStructure(vpcItem, helpers, errors); - const egressValid = this.validateSecurityGroupEgressStructure(vpcItem, helpers, errors); - - if (ingressValid && egressValid) { - // Validate security group sources - this.validateSecurityGroupSources(values, vpcItem, helpers, errors); - // Validate security group ports - this.validateSecurityGroupPorts(vpcItem, errors); - } - } - - /** - * Validate security group names - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupNames( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const groupNames: string[] = []; - vpcItem.securityGroups?.forEach(group => groupNames.push(group.name)); - if (helpers.hasDuplicates(groupNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate security group names defined. Security group names must be unique. Security group names configured: ${groupNames}`, - ); - } - } - - /** - * Returns true if all inbound rules have the correct structure - * @param vpcItem - * @param helpers - * @param errors - * @returns - */ - private validateSecurityGroupIngressStructure( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - const toFromPorts = ['fromPort', 'toPort']; - const tcpUdpPorts = ['tcpPorts', 'udpPorts']; - let allValid = true; - - vpcItem.securityGroups?.forEach(group => { - group.inboundRules.forEach(inbound => { - const keys = helpers.getObjectKeys(inbound); - if (inbound.types) { - allValid = this.securityGroupValidateTypes(group, inbound, keys, vpcItem, errors); - } else { - // Validate to/fromPorts don't exist - if (toFromPorts.some(item => keys.includes(item))) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inboundRules cannot contain ${toFromPorts} properties when types property is undefined`, - ); - } - // Validate tcpPorts/udpPorts exists - if (!inbound.tcpPorts && !inbound.udpPorts && !inbound.ipProtocols) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inboundRules must contain one of ${tcpUdpPorts} properties when both the types and ipProtocols properties are undefined.`, - ); - } - } - if (inbound.ipProtocols) { - allValid = this.securityGroupValidateIpProtocols(group, inbound, vpcItem, errors); - } - }); - }); - return allValid; - } - - /** - * Returns true if all inbound rules have the correct structure - * @param vpcItem - * @param helpers - * @param errors - * @returns - */ - private validateSecurityGroupEgressStructure( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ): boolean { - const toFromPorts = ['fromPort', 'toPort']; - const tcpUdpPorts = ['tcpPorts', 'udpPorts']; - let allValid = true; - - vpcItem.securityGroups?.forEach(group => { - group.outboundRules.forEach(outbound => { - const keys = helpers.getObjectKeys(outbound); - if (outbound.types) { - allValid = this.securityGroupValidateTypes(group, outbound, keys, vpcItem, errors); - } else { - // Validate to/fromPorts don't exist - if (toFromPorts.some(item => keys.includes(item))) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outboundRules cannot contain ${toFromPorts} properties when types property is undefined`, - ); - } - // Validate tcpPorts/udpPorts exists - if (!outbound.tcpPorts && !outbound.udpPorts && !outbound.ipProtocols) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outboundRules must contain one of ${tcpUdpPorts} properties when both the types and ipProtocols properties are undefined.`, - ); - } - } - if (outbound.ipProtocols) { - allValid = this.securityGroupValidateIpProtocols(group, outbound, vpcItem, errors); - } - }); - }); - return allValid; - } - - /** - * Validate security group types - * @param securityGroupItem - * @param vpcItem - * @param helpers - * @param errors - */ - private securityGroupValidateTypes( - group: SecurityGroupConfig, - securityGroupItem: SecurityGroupRuleConfig, - keys: string[], - vpcItem: VpcConfig | VpcTemplatesConfig, - errors: string[], - ): boolean { - let allValid = true; - const toFromPorts = ['fromPort', 'toPort']; - const tcpUdpPorts = ['tcpPorts', 'udpPorts']; - const toFromTypes = ['ICMP', 'TCP', 'UDP']; - const tcpUdpTypes = ['TCP', 'UDP']; - - // Validate types and tcpPorts/udpPorts are not defined in the same rule - if (keys.some(key => tcpUdpPorts.includes(key))) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: Rules cannot contain ${tcpUdpPorts} properties when types property is defined`, - ); - } - // Validate type is correct if using fromPort/toPort - if ( - securityGroupItem.types!.some(type => toFromTypes.includes(type)) && - toFromPorts.some(item => !keys.includes(item)) - ) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: Rules must contain ${toFromPorts} properties when one of the following types is specified: ${toFromTypes}`, - ); - } - if ( - securityGroupItem.types!.some(type => !toFromTypes.includes(type)) && - toFromPorts.some(item => keys.includes(item)) - ) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: Rules may only contain ${toFromPorts} properties when one of the following types is specified: ${toFromTypes}`, - ); - } - // Validate both TCP/UDP and ICMP are not used in the same rule - if ( - securityGroupItem.types!.some(type => tcpUdpTypes.includes(type)) && - securityGroupItem.types!.includes('ICMP') - ) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: Rules cannot contain both ICMP and TCP/UDP types in the same rule`, - ); - } - if (securityGroupItem.ipProtocols) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: Rules cannot contain both types and ipProtocols for the same rule. Create separate rules for both.`, - ); - } - return allValid; - } - - /** - * Validate security group ip protocols - * @param securityGroupItem - * @param vpcItem - * @param helpers - * @param errors - */ - private securityGroupValidateIpProtocols( - group: SecurityGroupConfig, - securityGroupItem: SecurityGroupRuleConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - errors: string[], - ): boolean { - let allValid = true; - const invalidProtocols: string[] = []; - for (const ipProtocolItem of securityGroupItem.ipProtocols ?? []) { - const protocolExists = ipProtocolItem in cdk.aws_ec2.Protocol; - if (!protocolExists) { - if (!invalidProtocols.includes(ipProtocolItem)) { - invalidProtocols.push(ipProtocolItem); - } - } - } - if (invalidProtocols.length > 0) { - allValid = false; - errors.push( - `[VPC ${vpcItem.name} security group ${ - group.name - }]: Is using the following unsupported IP Protocols: [${invalidProtocols.join(', ')}]`, - ); - } - return allValid; - } - - /** - * Validate security group sources - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupSources( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate CIDR sources - this.validateSecurityGroupCidrs(vpcItem, helpers, errors); - // Validate subnet sources - this.validateSecurityGroupIngressSubnetSources(vpcItem, helpers, errors); - this.validateSecurityGroupEgressSubnetSources(vpcItem, helpers, errors); - // Validate SG sources - this.validateSecurityGroupSgSources(vpcItem, errors); - // Validate prefix list sources - this.validateSecurityGroupPrefixListSources(values, vpcItem, helpers, errors); - } - - /** - * Validate security group source CIDRs - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupCidrs( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.securityGroups?.forEach(group => { - group.inboundRules.forEach(inbound => { - // Validate inbound sources - inbound.sources.forEach(inboundSource => { - if (typeof inboundSource === 'string') { - if (!helpers.isValidIpv4Cidr(inboundSource) && !helpers.isValidIpv6Cidr(inboundSource)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inbound rule source "${inboundSource}" is invalid. Value must be a valid IPv4/v6 CIDR, subnet reference, security group reference, or prefix list reference`, - ); - } - } - }); - }); - // Validate outbound sources - group.outboundRules.forEach(outbound => { - outbound.sources.forEach(outboundSource => { - if (typeof outboundSource === 'string') { - if (!helpers.isValidIpv4Cidr(outboundSource) && !helpers.isValidIpv6Cidr(outboundSource)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outbound rule source "${outboundSource}" is invalid. Value must be a valid IPv4/v6 CIDR, subnet reference, security group reference, or prefix list reference`, - ); - } - } - }); - }); - }); - } - - /** - * Validate security group ingress subnet sources - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupIngressSubnetSources( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.securityGroups?.forEach(group => { - group.inboundRules.forEach(inbound => { - inbound.sources.forEach(source => { - if (isNetworkType('ISubnetSourceConfig', source)) { - // Validate subnet source - const vpc = helpers.getVpc(source.vpc); - if (!vpc) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inboundRule source VPC "${source.vpc}" does not exist`, - ); - } else { - // Validate subnets - source.subnets.forEach(subnet => { - const subnetItem = helpers.getSubnet(vpc, subnet); - if (!subnetItem) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: subnet "${subnet}" does not exist in source VPC "${source.vpc}"`, - ); - } else { - // Check cross-account IPAM subnet condition - const sourceVpcAccountNames = helpers.getVpcAccountNames(vpcItem); - if ( - (!sourceVpcAccountNames.includes(source.account) || vpc.region !== vpcItem.region) && - subnetItem.ipamAllocation - ) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: accelerator does not currently support cross-account/cross-region IPAM subnets as security group references (source VPC: ${source.vpc}, source subnet: ${subnet}, source account: ${source.account})`, - ); - } - } - }); - - // Validate account target - const vpcAccountNames = helpers.getVpcAccountNames(vpc); - if (!vpcAccountNames.includes(source.account)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: source VPC "${source.vpc}" is not deployed to account "${source.account}"`, - ); - } - } - } - }); - }); - }); - } - - /** - * Validate security group ingress subnet sources - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupEgressSubnetSources( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.securityGroups?.forEach(group => { - group.outboundRules.forEach(outbound => { - outbound.sources.forEach(source => { - if (isNetworkType('ISubnetSourceConfig', source)) { - // Validate subnet source - const vpc = helpers.getVpc(source.vpc); - if (!vpc) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outboundRule source VPC "${source.vpc}" does not exist`, - ); - } else { - // Validate subnets - source.subnets.forEach(subnet => { - const subnetItem = helpers.getSubnet(vpc, subnet); - if (!subnetItem) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: subnet "${subnet}" does not exist in source VPC "${source.vpc}"`, - ); - } else { - // Check cross-account IPAM subnet condition - const sourceVpcAccountNames = helpers.getVpcAccountNames(vpcItem); - if ( - (!sourceVpcAccountNames.includes(source.account) || vpc.region !== vpcItem.region) && - subnetItem.ipamAllocation - ) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: accelerator does not currently support cross-account/cross-region IPAM subnets as security group references (source VPC: ${source.vpc}, source subnet: ${subnet}, source account: ${source.account})`, - ); - } - } - }); - - // Validate account target - const vpcAccountNames = helpers.getVpcAccountNames(vpc); - if (!vpcAccountNames.includes(source.account)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: source VPC "${source.vpc}" is not deployed to account "${source.account}"`, - ); - } - } - } - }); - }); - }); - } - - /** - * Validate security group sourcing another security group - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupSgSources(vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]) { - const securityGroups: string[] = []; - vpcItem.securityGroups?.forEach(groupItem => securityGroups.push(groupItem.name)); - - vpcItem.securityGroups?.forEach(group => { - group.inboundRules.forEach(inbound => { - // Validate inbound sources - inbound.sources.forEach(inboundSource => { - if (isNetworkType('ISecurityGroupSourceConfig', inboundSource)) { - inboundSource.securityGroups.forEach(sg => { - if (!securityGroups.includes(sg)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inboundRule source security group "${sg}" does not exist in VPC`, - ); - } - }); - } - }); - }); - // Validate outbound sources - group.outboundRules.forEach(outbound => { - outbound.sources.forEach(outboundSource => { - if (isNetworkType('ISecurityGroupSourceConfig', outboundSource)) { - outboundSource.securityGroups.forEach(item => { - if (!securityGroups.includes(item)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outboundRule source security group "${item}" does not exist in VPC`, - ); - } - }); - } - }); - }); - }); - } - - /** - * Validate prefix list sources - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSecurityGroupPrefixListSources( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Get accounts for RAM shared subnets - const sharedAccounts = vpcItem.subnets ? this.getSharedSubnetAccounts(vpcItem.subnets, helpers) : []; - - vpcItem.securityGroups?.forEach(group => { - group.inboundRules.forEach(inbound => { - // Validate inbound rules - inbound.sources.forEach(inboundSource => { - if (isNetworkType('IPrefixListSourceConfig', inboundSource)) { - inboundSource.prefixLists.forEach(listName => { - const prefixList = values.prefixLists?.find(item => item.name === listName); - if (!prefixList) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inboundRule source prefix list "${listName}" does not exist`, - ); - return; - } - if (prefixList.accounts || prefixList.deploymentTargets) { - // Prefix lists must be deployed to all deployment target accounts, including subnet shares - const accounts = []; - if (prefixList.accounts && prefixList.accounts.length > 0) { - accounts.push(...prefixList.accounts); - } - if (prefixList.deploymentTargets) { - accounts.push(...helpers.getAccountNamesFromTarget(prefixList.deploymentTargets)); - } - const vpcAccountNames = [...new Set([...helpers.getVpcAccountNames(vpcItem), ...sharedAccounts])]; - const targetComparison = helpers.compareTargetAccounts(vpcAccountNames, accounts); - if (targetComparison.length > 0) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: inboundRule source prefix list "${listName}" is not deployed to one or more VPC deployment target or subnet share target accounts. Missing accounts: ${targetComparison}`, - ); - } - } - }); - } - }); - }); - // Validate outbound rules - group.outboundRules.forEach(outbound => { - outbound.sources.forEach(outboundSource => { - if (isNetworkType('IPrefixListSourceConfig', outboundSource)) { - outboundSource.prefixLists.forEach(listName => { - const prefixList = values.prefixLists?.find(item => item.name === listName); - if (!prefixList) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outboundRule source prefix list "${listName}" does not exist`, - ); - return; - } - - if (prefixList.accounts || prefixList.deploymentTargets) { - // Prefix lists must be deployed to all deployment target accounts, including subnet shares - const accounts = []; - if (prefixList.accounts && prefixList.accounts.length > 0) { - accounts.push(...prefixList.accounts); - } - - if (prefixList.deploymentTargets) { - accounts.push(...helpers.getAccountNamesFromTarget(prefixList.deploymentTargets)); - } - const vpcAccountNames = [...new Set([...helpers.getVpcAccountNames(vpcItem), ...sharedAccounts])]; - const targetComparison = helpers.compareTargetAccounts(vpcAccountNames, accounts); - if (targetComparison.length > 0) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: outboundRule source prefix list "${listName}" is not deployed to one or more VPC deployment target or subnet share target accounts. Missing accounts: ${targetComparison}`, - ); - } - } - }); - } - }); - }); - }); - } - - /** - * Retrieve shared account names for a subnet's share targets - * @param subnetConfig - * @param helpers - * @returns - */ - private getSharedSubnetAccounts(subnetConfig: SubnetConfig[], helpers: NetworkValidatorFunctions): string[] { - const sharedAccounts: string[] = []; - - for (const subnet of subnetConfig) { - const subnetSharedAccounts = subnet.shareTargets ? helpers.getAccountNamesFromTarget(subnet.shareTargets) : []; - sharedAccounts.push(...subnetSharedAccounts); - } - return [...new Set(sharedAccounts)]; - } - - /** - * Validate security group ports - * @param vpcItem - * @param errors - */ - private validateSecurityGroupPorts(vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]) { - const tcpUdpTypes = ['TCP', 'UDP']; - - vpcItem.securityGroups?.forEach(group => { - // Validate inbound rules - group.inboundRules.forEach(inbound => { - // Validate TCP/UDP ports - if (inbound.types && inbound.types.some(inboundType => tcpUdpTypes.includes(inboundType))) { - this.validateSecurityGroupTcpUdpPorts(vpcItem, group, inbound, 'inbound', errors); - } - // Validate ICMP codes - if (inbound.types && inbound.types.includes('ICMP')) { - this.validateSecurityGroupIcmp(vpcItem, group, inbound, 'inbound', errors); - } - }); - // Validate outbound rules - group.outboundRules.forEach(outbound => { - // Validate TCP/UDP ports - if (outbound.types && outbound.types.some(outboundType => tcpUdpTypes.includes(outboundType))) { - this.validateSecurityGroupTcpUdpPorts(vpcItem, group, outbound, 'outbound', errors); - } - // Validate ICMP codes - if (outbound.types && outbound.types.includes('ICMP')) { - this.validateSecurityGroupIcmp(vpcItem, group, outbound, 'outbound', errors); - } - }); - }); - } - - /** - * Validate security group TCP/UDP ports - * @param vpcItem - * @param group - * @param rule - * @param direction - * @param errors - */ - private validateSecurityGroupTcpUdpPorts( - vpcItem: VpcConfig | VpcTemplatesConfig, - group: SecurityGroupConfig, - rule: SecurityGroupRuleConfig, - direction: string, - errors: string[], - ) { - if (rule.fromPort !== undefined && rule.toPort !== undefined) { - const isValidPortRange = rule.fromPort <= rule.toPort; - const portRangeString = `fromPort: ${rule.fromPort}, toPort: ${rule.toPort}`; - - if (!isValidPortRange) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: ${direction} fromPort must be less than or equal to toPort. Defined port range: ${portRangeString}`, - ); - } - - if (isValidPortRange && (rule.fromPort < 0 || rule.fromPort > 65535)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: ${direction} fromPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - - if (isValidPortRange && (rule.toPort < 0 || rule.toPort > 65535)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: ${direction} toPort value must be between 0 and 65535. Defined port range: ${portRangeString}`, - ); - } - } - } - - /** - * Validate security group ICMP codes - * @param vpcItem - * @param group - * @param rule - * @param direction - * @param errors - */ - private validateSecurityGroupIcmp( - vpcItem: VpcConfig | VpcTemplatesConfig, - group: SecurityGroupConfig, - rule: SecurityGroupRuleConfig, - direction: string, - errors: string[], - ) { - if (rule.fromPort !== undefined && rule.toPort !== undefined) { - if (rule.fromPort === -1) { - if (rule.toPort !== -1) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: ${direction} ICMP rules using -1 as fromPort must also have -1 as toPort`, - ); - } - } else { - if (rule.fromPort < 0 || rule.fromPort > 43) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: if not allowing all ${direction} ICMP types (-1), fromPort must be in range 0-43`, - ); - } - if (rule.toPort !== -1 && (rule.toPort < 0 || rule.toPort > 15)) { - errors.push( - `[VPC ${vpcItem.name} security group ${group.name}]: if not allowing all ${direction} ICMP codes (-1), toPort must be in range 0-15`, - ); - } - } - } - } - - /** - * Validate subnets for a given VPC - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSubnets( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - // Validate subnet names - this.validateSubnetNames(vpcItem, helpers, errors); - // Validate subnet structure - this.validateSubnetStructure(vpcItem, errors); - // Validate subnet CIDR - this.validateSubnetCidrs(vpcItem, helpers, errors); - // Validate subnet route table - this.validateSubnetRouteTables(vpcItem, errors); - // Validate subnet availability zones - this.validateSubnetAvailabilityZones(vpcItem, helpers, errors); - // Validate subnets exist in VPC that are used with Application Load Balancer - this.validateAlbConfigForExistingSubnets(vpcItem, helpers, errors); - // Validate that Application Load Balancer that is using shared targets is using subnets that are using shared target. - this.validateSharedAlbSubnets(vpcItem, helpers, errors); - // Validate subnet share target ou names - this.validateVpcSubnetShareTargetOUs(vpcItem, helpers, errors); - // Validate subnet share target account names - this.validateVpcSubnetShareTargetAccounts(vpcItem, helpers, errors); - } - - /** - * Validate subnet names - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSubnetNames( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const subnetNames: string[] = []; - vpcItem.subnets?.forEach(subnet => subnetNames.push(subnet.name)); - if (helpers.hasDuplicates(subnetNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate subnet names defined. Subnet names must be unique. Subnet names in configuration: ${subnetNames}`, - ); - } - } - - /** - * Validate subnet structure - * @param vpcItem - * @param errors - */ - private validateSubnetStructure(vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]) { - vpcItem.subnets?.forEach(subnet => { - // Validate a CIDR or IPAM allocation is defined - if (subnet.ipv4CidrBlock && subnet.ipamAllocation) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: cannot define both ipv4CidrBlock and ipamAllocation properties`, - ); - } - if (!subnet.ipv6CidrBlock && !subnet.ipv4CidrBlock && !subnet.ipamAllocation) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: must define one of ipv4CidrBlock, ipv6CidrBlock, or ipamAllocation properties`, - ); - } - // Validate IPv6 structure - if (subnet.ipv6CidrBlock && subnet.ipv4CidrBlock && subnet.ipamAllocation) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: ipv4CidrBlock, ipv6CidrBlock, and ipamAllocation properties are all defined. A subnet may only have a maximum of one IPv4 and one IPv6 address.`, - ); - } - // Validate an AZ is assigned - if (subnet.availabilityZone && subnet.outpost) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: cannot define both availabilityZone and outpost properties`, - ); - } - if (!subnet.availabilityZone && !subnet.outpost) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: must define either availabilityZone or outpost property`, - ); - } - }); - } - - /** - * Validate subnet CIDR - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSubnetCidrs( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.subnets?.forEach(subnet => { - // Validate IPv4 subnets - if (subnet.ipv4CidrBlock) { - if (!helpers.isValidIpv4Cidr(subnet.ipv4CidrBlock)) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: CIDR "${subnet.ipv4CidrBlock}" is invalid. Value must be a valid IPv4 CIDR range`, - ); - } - // Validate prefix - const prefix = helpers.isValidIpv4Cidr(subnet.ipv4CidrBlock) ? subnet.ipv4CidrBlock.split('/')[1] : undefined; - if (prefix && !this.isValidIpv4PrefixLength(parseInt(prefix))) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: CIDR "${subnet.ipv4CidrBlock}" is invalid. CIDR prefix cannot be larger than /16 or smaller than /28`, - ); - } - } - // Validate IPv6 subnets - if (subnet.ipv6CidrBlock) { - if (!helpers.isValidIpv6Cidr(subnet.ipv6CidrBlock)) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: CIDR "${subnet.ipv6CidrBlock}" is invalid. Value must be a valid IPv6 CIDR range`, - ); - } - // Validate prefix - const prefix = helpers.isValidIpv6Cidr(subnet.ipv6CidrBlock) ? subnet.ipv6CidrBlock.split('/')[1] : undefined; - if (prefix && !this.isValidIpv6SubnetPrefixLength(parseInt(prefix))) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: CIDR "${subnet.ipv6CidrBlock}" is invalid. CIDR prefix cannot be larger than /44 or smaller than /64 and must be an increment of /4`, - ); - } - } - }); - } - - /** - * Validate subnet route table associations - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSubnetRouteTables(vpcItem: VpcConfig | VpcTemplatesConfig, errors: string[]) { - const tableNames: string[] = []; - vpcItem.routeTables?.forEach(routeTable => tableNames.push(routeTable.name)); - - vpcItem.subnets?.forEach(subnet => { - if (subnet.routeTable && !tableNames.includes(subnet.routeTable)) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: route table "${subnet.routeTable}" does not exist in the VPC`, - ); - } - }); - } - - /** - * Validate subnet availability zones - * @param vpcItem - * @param helps - * @param errors - */ - private validateSubnetAvailabilityZones( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - vpcItem.subnets?.forEach(subnet => { - if (typeof subnet.availabilityZone === 'string') { - if (!helpers.matchesRegex(subnet.availabilityZone, '^[a-z]{1}$')) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: Uses an incorrect value for ${subnet.availabilityZone} as the availabilityZone. Please use a single lowercase letter.`, - ); - } - } - if (typeof subnet.availabilityZone === 'number') { - if (!helpers.matchesRegex(subnet.availabilityZone.toString(), '^[0-9]{1}$')) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnet.name}]: Uses an incorrect value for ${subnet.availabilityZone} as the availabilityZone. Please use a single number.`, - ); - } - } - }); - } - - /** - * Validate that subnet specified in ALB exists - * @param vpcItem - * @param helpers - * @param errors - */ - private validateAlbConfigForExistingSubnets( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers ?? []) { - for (const subnetItem of albItem.subnets) { - if (!helpers.getSubnet(vpcItem, subnetItem)) { - errors.push( - `The Application Load Balancer: ${albItem.name} for VPC ${vpcItem.name} is using subnet ${subnetItem} that doesn't exist`, - ); - } - } - } - } - - /** - * Validate that ALB that is using sharedTargets has subnets that are also using shared targets method. - * @param values - * @param errors - */ - private validateSharedAlbSubnets( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const nonSharedSubnets: string[] = []; - const invalidAlbs: { name: string; subnets: string[] }[] = []; - for (const subnetItem of vpcItem.subnets ?? []) { - if (!subnetItem.shareTargets) { - nonSharedSubnets.push(subnetItem.name); - } - } - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers ?? []) { - if (albItem.shareTargets) { - this.validateSubnetSharesToAlbShares(albItem, vpcItem, helpers, errors); - this.validateTargetGroupSharesToAlbShares(albItem, vpcItem, helpers, errors); - if (albItem.subnets.find(item => nonSharedSubnets.find(nonSharedSubnet => item === nonSharedSubnet))) { - invalidAlbs.push({ name: albItem.name, subnets: albItem.subnets }); - } - } - } - - for (const alb of invalidAlbs) { - errors.push( - `The Application Load Balancer: ${ - alb.name - } is using the sharedTargets method, but at least one of the subnets [${alb.subnets.join( - ',', - )}] in its configuration is not using the sharedTargets method.`, - ); - } - } - - /** - * Validate subnet availability to shared Application Load Balancers - * @param vpcItem - * @param helpers - * @param errors - */ - private validateSubnetSharesToAlbShares( - albItem: ApplicationLoadBalancerConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - let missingAccountIds: string[] = []; - const albAccounts = helpers.getAccountNamesFromTarget(albItem.shareTargets as ShareTargets); - for (const subnetName of albItem.subnets) { - const subnetSharedTarget = this.getSubnetSharedTarget(vpcItem, subnetName); - if (subnetSharedTarget) { - const subnetAccounts = helpers.getAccountNamesFromTarget(subnetSharedTarget); - missingAccountIds = albAccounts.filter(item => !subnetAccounts.includes(item)); - if (missingAccountIds.length > 0) { - errors.push( - `The Application Load Balancer ${albItem.name} is deployed to multiple accounts and using subnets that aren't available. Please make sure your sharedTargets configuration for your subnet makes the subnet available for the ALB.`, - ); - } - } - } - } - - /** - * Validate Subnet share target OU names - * @param vpcItem - * @param helpers - * @param errors - */ - private validateVpcSubnetShareTargetOUs( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const subnetItem of vpcItem.subnets ?? []) { - for (const ou of subnetItem.shareTargets?.organizationalUnits ?? []) { - if (!helpers.ouExists(ou)) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnetItem.name}]: Shared Target OU ${ou} does not exist in organization-config.yaml file.`, - ); - } - } - } - } - - /** - * Validate Subnet share target account names - * @param vpcItem - * @param helpers - * @param errors - */ - private validateVpcSubnetShareTargetAccounts( - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - for (const subnetItem of vpcItem.subnets ?? []) { - for (const account of subnetItem.shareTargets?.accounts ?? []) { - if (!helpers.accountExists(account)) { - errors.push( - `[VPC ${vpcItem.name} subnet ${subnetItem.name}]: Shared Target account ${account} does not exist in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Validate target group availability to shared Application Load Balancers - * @param vpcItem - * @param helpers - * @param errors - */ - private validateTargetGroupSharesToAlbShares( - albItem: ApplicationLoadBalancerConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - let missingAccountIds: string[] = []; - - const albAccounts = helpers.getAccountNamesFromTarget(albItem.shareTargets as ShareTargets); - for (const listenerItem of albItem.listeners ?? []) { - for (const targetGroupItem of vpcItem.targetGroups ?? []) { - if (targetGroupItem.name === listenerItem.targetGroup) { - const targetGroupSharedTarget = this.getTargetGroupSharedTarget(vpcItem, listenerItem.targetGroup); - if (targetGroupSharedTarget) { - const targetGroupAccounts = helpers.getAccountNamesFromTarget(targetGroupSharedTarget); - missingAccountIds = albAccounts.filter(item => !targetGroupAccounts.includes(item)); - if (missingAccountIds.length > 0) { - errors.push( - `The Application Load Balancer ${albItem.name} is deployed to multiple accounts and using target group(s) that are not in the same accounts. Please make sure your sharedTargets configuration for your targetGroup makes the Target Group available for the ALB.`, - ); - } - } - } - } - } - } - - /** - * Validate ACM availability to shared Application Load Balancers - * @param values - * @param vpcItem - * @param helpers - * @param errors - */ - private validateAcmSharesToAlbShares( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const invalidAlbList: string[] = []; - let missingAccountIds: string[] = []; - - for (const acmItem of values.certificates ?? []) { - for (const albItem of vpcItem.loadBalancers?.applicationLoadBalancers ?? []) { - if (albItem.shareTargets) { - const albAccounts = helpers.getAccountNamesFromTarget(albItem.shareTargets as ShareTargets); - for (const listenerItem of albItem.listeners ?? []) { - if (listenerItem.certificate === acmItem.name) { - const acmAccountIds = helpers.getAccountNamesFromTarget(acmItem.deploymentTargets); - missingAccountIds = albAccounts.filter(item => !acmAccountIds.includes(item)); - if (missingAccountIds.length > 0) { - if (!invalidAlbList.includes(albItem.name)) { - invalidAlbList.push(albItem.name); - } - } - } - } - } - } - } - for (const alb of invalidAlbList) { - errors.push( - `The Application Load Balancer ${alb} is deployed to multiple accounts and using ACM certificate(s) that are not in the same accounts. Please make sure your sharedTargets configuration for your ACM certificates makes the Target Group available for the ALB.`, - ); - } - } - - /** - * Returns the shared targets from the input of the subnet name. - * @param vpcItem - * @param subnetName - * @returns - */ - private getSubnetSharedTarget(vpcItem: VpcConfig | VpcTemplatesConfig, subnetName: string): ShareTargets | undefined { - for (const subnetItem of vpcItem.subnets ?? []) { - if (subnetItem.name === subnetName) { - return subnetItem.shareTargets; - } - } - return undefined; - } - - /** - * Returns the shared targets from the input of the target group name. - * @param vpcItem - * @param subnetName - * @returns - */ - private getTargetGroupSharedTarget( - vpcItem: VpcConfig | VpcTemplatesConfig, - targetGroupName: string, - ): ShareTargets | undefined { - for (const targetGroupItem of vpcItem.targetGroups ?? []) { - if (targetGroupItem.name === targetGroupName) { - return targetGroupItem.shareTargets; - } - } - return undefined; - } - - /** - * Returns a transit gateway config based on a given name and account - * @param values - * @param name - * @param account - * @returns - */ - private getTransitGateway(values: NetworkConfig, name: string, account: string): TransitGatewayConfig | undefined { - return values.transitGateways.find(tgw => tgw.name === name && tgw.account === account); - } - - /** - * Validate TGW attachments for a given VPC - * @param values - * @param vpcItem - * @param errors - */ - private validateTgwAttachments( - values: NetworkConfig, - vpcItem: VpcConfig | VpcTemplatesConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const attachNames: string[] = []; - const tgwNames: string[] = []; - - vpcItem.transitGatewayAttachments?.forEach(attach => { - attachNames.push(attach.name); - tgwNames.push(attach.transitGateway.name); - const tgw = this.getTransitGateway(values, attach.transitGateway.name, attach.transitGateway.account); - if (!tgw) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: TGW "${attach.transitGateway.name}" in account "${attach.transitGateway.account}" does not exist`, - ); - } else { - // Validate associations and propagations - this.validateTgwRouteTableAssociations(vpcItem, attach, tgw, errors); - this.validateTgwRouteTablePropagations(vpcItem, attach, tgw, helpers, errors); - // Validate subnets - this.validateTgwAttachmentSubnets(vpcItem, attach, helpers, errors); - // Validate TGW region - if (tgw.region !== vpcItem.region) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: TGW "${attach.transitGateway.name}" is not deployed to the same region as the VPC`, - ); - } - } - }); - - // Check for duplicate attachment names - if (helpers.hasDuplicates(attachNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate TGW attachment names defined. Attachment names must be unique. Attachment names configured: ${attachNames}`, - ); - } - // Check for duplicate TGW attachments - if (helpers.hasDuplicates(tgwNames)) { - errors.push( - `[VPC ${vpcItem.name}]: duplicate TGW attachment targets defined. Target TGWs must be unique. Attachment target TGWs configured: ${tgwNames}`, - ); - } - } - - /** - * Validate TGW route table associations for a given VPC TGW attachment - * @param vpcItem - * @param attach - * @param tgw - * @param errors - */ - private validateTgwRouteTableAssociations( - vpcItem: VpcConfig | VpcTemplatesConfig, - attach: TransitGatewayAttachmentConfig, - tgw: TransitGatewayConfig, - errors: string[], - ) { - // Check number of associations - if (attach.routeTableAssociations && attach.routeTableAssociations.length > 1) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: cannot define more than one TGW route table association`, - ); - } - // Validate route table exists - if ( - attach.routeTableAssociations && - !tgw.routeTables.find(table => table.name === attach.routeTableAssociations![0]) - ) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: route table "${attach.routeTableAssociations[0]}" does not exist on TGW "${tgw.name}"`, - ); - } - } - - /** - * Validate TGW route table propagations for a given VPC TGW attachment - * @param vpcItem - * @param attach - * @param tgw - * @param helpers - * @param errors - */ - private validateTgwRouteTablePropagations( - vpcItem: VpcConfig | VpcTemplatesConfig, - attach: TransitGatewayAttachmentConfig, - tgw: TransitGatewayConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const tableNames: string[] = []; - attach.routeTablePropagations?.forEach(propagation => { - tableNames.push(propagation); - if (!tgw.routeTables.find(table => table.name === propagation)) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: route table "${propagation}" does not exist on TGW "${tgw.name}"`, - ); - } - }); - - // Check for duplicate route table names - if (helpers.hasDuplicates(tableNames)) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: duplicate TGW route table propagations defined. Propagations must be unique. Propagations configured: ${tableNames}`, - ); - } - } - - /** - * Validate TGW attachment target subnets - * @param vpcItem - * @param attach - * @param helpers - * @param errors - */ - private validateTgwAttachmentSubnets( - vpcItem: VpcConfig | VpcTemplatesConfig, - attach: TransitGatewayAttachmentConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const subnetAzs: (string | number)[] = []; - const subnetNames: string[] = []; - - attach.subnets.forEach(subnetName => { - subnetNames.push(subnetName); - const subnet = helpers.getSubnet(vpcItem, subnetName); - if (!subnet) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: target subnet "${subnetName}" does not exist in VPC "${vpcItem.name}"`, - ); - } else { - subnetAzs.push(subnet.availabilityZone ? subnet.availabilityZone : ''); - } - }); - - // Check for duplicate subnet names - if (helpers.hasDuplicates(subnetNames)) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: duplicate TGW attachment subnets defined. Subnets must be unique. Subnets configured: ${subnetNames}`, - ); - } - // Check for duplicate subnet AZs - if (helpers.hasDuplicates(subnetAzs)) { - errors.push( - `[VPC ${vpcItem.name} TGW attachment ${attach.name}]: duplicate TGW attachment subnet AZs defined. Subnet AZs must be unique. AZs configured: ${subnetAzs}`, - ); - } - } - - /** - * Validate Outpost Local Gateway names - * @param values - * @param helpers - * @param errors - */ - private validateLocalGatewayNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.vpcs.forEach(vpcItem => { - const lgwNames = []; - if (vpcItem.outposts) { - for (const outpost of vpcItem.outposts) { - if (outpost.localGateway?.name) { - lgwNames.push(outpost.localGateway?.name); - } - } - - // Validate no VPC names are duplicated - if (helpers.hasDuplicates(lgwNames)) { - errors.push(`Duplicate Local Gateway names exist, LGW names must be unique. LGW names in file: ${lgwNames}`); - } - } - }); - } - - /** - * Validate uniqueness of Outpost names - * @param values - * @param helpers - * @param errors - */ - private validateOutpostNames(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - values.vpcs.forEach(vpcItem => { - const outpostNames = []; - if (vpcItem.outposts) { - for (const outpost of vpcItem.outposts) { - outpostNames.push(outpost.name); - } - - // Validate no VPC names are duplicated - if (helpers.hasDuplicates(outpostNames)) { - errors.push( - `Duplicate Outpost names exist, Outpost names must be unique. Outpost names in file: ${outpostNames}`, - ); - } - } - }); - } - - /** - * Validate VPC peering connections - * @param values - * @param errors - */ - private validateVpcPeeringConfiguration(values: NetworkConfig, errors: string[]) { - const vpcs = [...values.vpcs, ...(values.vpcTemplates ?? [])]; - const vpcTemplates = values.vpcTemplates ?? []; - for (const peering of values.vpcPeering ?? []) { - // Ensure exactly two VPCs are defined - if (peering.vpcs.length < 2 || peering.vpcs.length > 2) { - errors.push( - `[VPC peering connection ${peering.name}]: exactly two VPCs must be defined for a VPC peering connection`, - ); - } - - // Ensure VPCs exist and more than one is not defined - for (const vpc of peering.vpcs) { - if (!vpcs.find(item => item.name === vpc)) { - errors.push(`[VPC peering connection ${peering.name}]: VPC or VPC Template ${vpc} does not exist`); - } - if (vpcs.filter(item => item.name === vpc).length > 1) { - errors.push(`[VPC peering connection ${peering.name}]: more than one VPC or VPC Template named ${vpc}`); - } - } - - // Ensure not both vpcs are from vpcTemplates - if ( - vpcTemplates.find(item => item.name === peering.vpcs[0]) && - vpcTemplates.find(item => item.name === peering.vpcs[1]) - ) { - errors.push( - `[VPC peering connection ${peering.name}]: Both VPCs ${peering.vpcs[0]}, ${peering.vpcs[1]} should not be from vpcTemplates. Only one VPC in a peering connection can be from vpcTemplate configuration`, - ); - } - } - } - - /** - * Validate default VPC configuration - * @param values - * @param helpers - * @param global - * @param errors - */ - private validateDefaultVpcConfiguration(values: NetworkConfig, helpers: NetworkValidatorFunctions, errors: string[]) { - this.validateDefaultVpcRegionConfiguration(values, helpers, errors); - this.validateDefaultVpcAccountConfiguration(values, helpers, errors); - } - - /** - * Validate default VPC region excludes configuration - * @param values - * @param helpers - * @param global - * @param errors - */ - private validateDefaultVpcRegionConfiguration( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const invalidRegions: string[] = []; - for (const region of values.defaultVpc.excludeRegions ?? []) { - const validRegion = helpers.isEnabledRegion(region); - if (!validRegion) { - invalidRegions.push(region); - } - } - if (invalidRegions.length > 0) { - errors.push( - `[Default Vpc Configuration contains the following regions that are not in the enabledRegions: ${invalidRegions.join( - ', ', - )}`, - ); - } - } - - /** - * Validate default VPC account excludes configuration - * @param values - * @param helpers - * @param global - * @param errors - */ - private validateDefaultVpcAccountConfiguration( - values: NetworkConfig, - helpers: NetworkValidatorFunctions, - errors: string[], - ) { - const invalidAccounts: string[] = []; - for (const account of values.defaultVpc.excludeAccounts ?? []) { - if (!helpers.accountExists(account)) { - invalidAccounts.push(account); - } - } - if (invalidAccounts.length > 0) { - errors.push( - `[Default Vpc Configuration contains the following accounts that are not in the accounts config: ${invalidAccounts.join( - ', ', - )}`, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/config/validator/organization-config-validator.ts b/source/packages/@aws-accelerator/config/validator/organization-config-validator.ts deleted file mode 100644 index f9edd5a..0000000 --- a/source/packages/@aws-accelerator/config/validator/organization-config-validator.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import fs from 'fs'; -import path from 'path'; -import { OrganizationConfig } from '../lib/organization-config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { ReplacementsConfig } from '../lib/replacements-config'; -import { CommonValidatorFunctions } from './common/common-validator-functions'; - -export class OrganizationConfigValidator { - constructor(values: OrganizationConfig, replacementsConfig: ReplacementsConfig | undefined, configDir: string) { - const errors: string[] = []; - - const logger = createLogger(['organization-config-validator']); - - logger.info(`${OrganizationConfig.FILENAME} file validation started`); - - // Validate presence of service control policy file - this.validateServiceControlPolicyFile(configDir, values, errors); - - // Validate presence of tagging policy file - this.validateTaggingPolicyFile(configDir, values, errors); - - // Validate presence of backup policy file - this.validateBackupPolicyFile(configDir, values, errors); - - // Validate definition of static parameter in policy file - this.validateSCPParameters(configDir, values, replacementsConfig, errors); - - if (errors.length) { - throw new Error(`${OrganizationConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - - /** - * Function to validate service control policy file existence - * @param configDir - * @param values - */ - private validateServiceControlPolicyFile(configDir: string, values: OrganizationConfig, errors: string[]) { - type validateScpItem = { - orgEntity: string; - orgEntityType: string; - appliedScpName: string[]; - }; - const validateScpCountForOrg: validateScpItem[] = []; - for (const serviceControlPolicy of values.serviceControlPolicies ?? []) { - if (!fs.existsSync(path.join(configDir, serviceControlPolicy.policy))) { - errors.push( - `Invalid policy file ${serviceControlPolicy.policy} for service control policy ${serviceControlPolicy.name} !!!`, - ); - } - - for (const orgUnitScp of serviceControlPolicy.deploymentTargets.organizationalUnits ?? []) { - //check in array to see if OU is already there - const index = validateScpCountForOrg.map(object => object.orgEntity).indexOf(orgUnitScp); - if (index > -1) { - validateScpCountForOrg[index].appliedScpName.push(serviceControlPolicy.name); - } else { - validateScpCountForOrg.push({ - orgEntity: orgUnitScp, - orgEntityType: 'Organization Unit', - appliedScpName: [serviceControlPolicy.name], - }); - } - } - for (const accUnitScp of serviceControlPolicy.deploymentTargets.accounts ?? []) { - //check in array to see if account is already there - const index = validateScpCountForOrg.map(object => object.orgEntity).indexOf(accUnitScp); - if (index > -1) { - validateScpCountForOrg[index].appliedScpName.push(serviceControlPolicy.name); - } else { - validateScpCountForOrg.push({ - orgEntity: accUnitScp, - orgEntityType: 'Account', - appliedScpName: [serviceControlPolicy.name], - }); - } - } - } - for (const validateOrgEntity of validateScpCountForOrg) { - if (validateOrgEntity.appliedScpName.length > 5) { - errors.push( - `${validateOrgEntity.orgEntityType} - ${validateOrgEntity.orgEntity} has ${validateOrgEntity.appliedScpName.length} out of 5 allowed scps`, - ); - } - } - } - - /** - * Function to validate tagging policy file existence - * @param configDir - * @param values - */ - private validateTaggingPolicyFile(configDir: string, values: OrganizationConfig, errors: string[]) { - for (const taggingPolicy of values.taggingPolicies ?? []) { - if (!fs.existsSync(path.join(configDir, taggingPolicy.policy))) { - errors.push(`Invalid policy file ${taggingPolicy.policy} for tagging policy ${taggingPolicy.name} !!!`); - } - } - } - - /** - * Function to validate presence of backup policy file existence - * @param configDir - * @param values - */ - private validateBackupPolicyFile(configDir: string, values: OrganizationConfig, errors: string[]) { - // Validate presence of backup policy file - for (const backupPolicy of values.backupPolicies ?? []) { - if (!fs.existsSync(path.join(configDir, backupPolicy.policy))) { - errors.push(`Invalid policy file ${backupPolicy.policy} for backup policy ${backupPolicy.name} !!!`); - } - } - } - - /** - * Function to validate if static parameter in policy file is defined in replacements config - * @param configDir - * @param organizationConfig - * @param replacementConfig - * @param errors - */ - private validateSCPParameters( - configDir: string, - organizationConfig: OrganizationConfig, - replacementConfig: ReplacementsConfig | undefined, - errors: string[], - ) { - const policyPaths = organizationConfig.serviceControlPolicies.map(scp => scp.policy); - CommonValidatorFunctions.validateStaticParameters(replacementConfig, configDir, policyPaths, new Set(), errors); - } -} diff --git a/source/packages/@aws-accelerator/config/validator/replacements-config-validator.ts b/source/packages/@aws-accelerator/config/validator/replacements-config-validator.ts deleted file mode 100644 index df52af1..0000000 --- a/source/packages/@aws-accelerator/config/validator/replacements-config-validator.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { - ParameterReplacementConfigV2, - ReplacementsConfig, - ParameterReplacementConfig, -} from '../lib/replacements-config'; -import { CustomizationsConfig } from '../lib/customizations-config'; -import { AccountsConfig } from '../lib/accounts-config'; -import { GlobalConfig } from '../lib/global-config'; -import { SecurityConfig } from '../lib/security-config'; -import { OrganizationConfig } from '../lib/organization-config'; -import { NetworkConfig } from '../lib/network-config'; -import { IamConfig } from '../lib/iam-config'; - -const fileNameList = [ - AccountsConfig.FILENAME, - CustomizationsConfig.FILENAME, - GlobalConfig.FILENAME, - IamConfig.FILENAME, - NetworkConfig.FILENAME, - OrganizationConfig.FILENAME, - SecurityConfig.FILENAME, -]; -export class ReplacementsConfigValidator { - constructor(values: ReplacementsConfig, configDir: string) { - const errors: string[] = []; - - const logger = createLogger(['replacement-config-validator']); - - logger.info(`${ReplacementsConfig.FILENAME} file validation started`); - - // - // Validate global replacements - // - this.validateGlobalReplacement(values, errors); - - // - // Validate any instances of double-curly brackets in config files are deliberate - // - this.validateNoUnusedReplacements(values, configDir, errors); - - if (errors.length) { - throw new Error(`${ReplacementsConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - - /** - * Function to validate global replacement - * @param values - */ - private validateGlobalReplacement(values: ReplacementsConfig, errors: string[]) { - if (!values || !values.globalReplacements || values.globalReplacements.length === 0) { - return; - } - - for (const replacement of values.globalReplacements) { - if ((replacement as ParameterReplacementConfigV2).type) { - this.validateParameterReplacementConfigV2(replacement as ParameterReplacementConfigV2, errors); - } else { - this.validateParameterReplacementConfig(replacement, errors); - } - - this.validateReplacementForKeywords(replacement, errors); - } - } - - private validateParameterReplacementConfig(replacement: ParameterReplacementConfig, errors: string[]) { - if (!replacement.path) { - errors.push(`Invalid replacement - no path specified for ${replacement.key}.`); - } - } - - private validateParameterReplacementConfigV2(replacementV2: ParameterReplacementConfigV2, errors: string[]) { - if (replacementV2.type === 'SSM') { - if (!replacementV2.path) { - errors.push(`Invalid replacement - no path specified for SSM replacement: ${replacementV2.key}.`); - } - if (replacementV2.value) { - errors.push(`Invalid replacement - value are not allowed for SSM replacement: ${replacementV2.key}.`); - } - } else if (replacementV2.type === 'String') { - if (replacementV2.path) { - errors.push(`Invalid replacement - path is not allowed for String replacement: ${replacementV2.key}.`); - } else if (!replacementV2.value) { - errors.push(`Invalid replacement - no String value specified for String replacement: ${replacementV2.key}.`); - } else if (typeof replacementV2.value !== 'string') { - errors.push(`Invalid replacement - value type is correct for String replacement: ${replacementV2.key}.`); - } - } else if (replacementV2.type === 'StringList') { - if (replacementV2.path) { - errors.push(`Invalid replacement - path is not allowed for StringList replacement: ${replacementV2.key}.`); - } else if (!replacementV2.value || replacementV2.value.length === 0) { - errors.push( - `Invalid replacement - no StringList value specified for StringList replacement: ${replacementV2.key}.`, - ); - } - } - } - - private validateReplacementForKeywords(replacement: ParameterReplacementConfig, errors: string[]) { - if (replacement.key.toLowerCase().startsWith('resolve')) { - errors.push( - `Invalid replacement ${replacement.key} , replacement key cannot start with keyword "resolve". The keyword "resolve" is reserved for CloudFormation dynamic references.`, - ); - } - } - - /** - * Function to validate all strings in the config files surrounded by double curly braces are using LZA lookups or SSM Dynamic references - * @param values - * @param configDir - * @param errors - */ - private validateNoUnusedReplacements(values: ReplacementsConfig, configDir: string, errors: string[]) { - // Retrieve replacement keys defined explicitly in replacements-config.yaml - const definedReplacementKeys = values.globalReplacements.map(replacement => replacement.key); - - for (const fileName of fileNameList) { - if ( - fileName === CustomizationsConfig.FILENAME && - !fs.existsSync(path.join(configDir, CustomizationsConfig.FILENAME)) - ) { - continue; - } else { - const replacementKeys = this.getReplacementKeysInFile(configDir, fileName); - this.evaluateReplacementKeys(replacementKeys, definedReplacementKeys, errors); - } - } - } - - /** - * Function to evaluate that a replacement key meets one of the 3 criteria to be determined intentional - * @param replacementKeys - * @param definedReplacementKeys - * @param errors - */ - private evaluateReplacementKeys(replacementKeys: string[], definedReplacementKeys: string[], errors: string[]) { - for (const key of replacementKeys) { - if (definedReplacementKeys.includes(key)) { - continue; - } else if (key.startsWith('account')) { - continue; - } else if (key.startsWith('resolve:')) { - continue; - } else { - errors.push( - `Undefined replacement {{${key}}} found. Double-curly brackets are reserved for LZA lookups and SSM dynamic reference parameters.`, - ); - } - } - } - - /** - * Function to find instances of double curly braces in each config file - * @param configDir - * @param fileName - */ - private getReplacementKeysInFile(configDir: string, fileName: string): string[] { - const data = fs.readFileSync(path.join(configDir, fileName), 'utf-8'); - const replacements = data.match(/{{[\w\s\d]*}}/g) ?? []; - const replacementKeys = replacements.map(key => this.trimCurlyBraces(key)); - return replacementKeys; - } - - /** - * Function to remove double curly braces from a string - * @param replacementString - */ - private trimCurlyBraces(replacementString: string) { - return replacementString.replace('{{', '').replace('}}', '').trim(); - } -} diff --git a/source/packages/@aws-accelerator/config/validator/security-config-validator.ts b/source/packages/@aws-accelerator/config/validator/security-config-validator.ts deleted file mode 100644 index 2c0c461..0000000 --- a/source/packages/@aws-accelerator/config/validator/security-config-validator.ts +++ /dev/null @@ -1,1422 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import fs from 'fs'; -import path from 'path'; -import * as t from '../lib/common'; -import { AccountsConfig } from '../lib/accounts-config'; -import { DeploymentTargets } from '../lib/common'; -import { - IAlarmSetConfig, - IAwsConfig, - IAwsConfigRuleSet, - IDocumentConfig, - ISecurityConfig, -} from '../lib/models/security-config'; -import { GlobalConfig } from '../lib/global-config'; -import { OrganizationConfig } from '../lib/organization-config'; -import { ReplacementsConfig } from '../lib/replacements-config'; -import { - AwsConfigRuleSet, - EbsDefaultVolumeEncryptionConfig, - SecurityConfig, - IsPublicSsmDoc, - ConfigRule, - GuardDutyConfig, - SecurityHubConfig, -} from '../lib/security-config'; -import { CommonValidatorFunctions } from './common/common-validator-functions'; - -const RESERVED_STATIC_PARAMETER_FOR_RESOURCE_POLICY = 'ATTACHED_RESOURCE_ARN'; - -export class SecurityConfigValidator { - constructor( - values: SecurityConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - organizationConfig: OrganizationConfig, - replacementsConfig: ReplacementsConfig | undefined, - configDir: string, - ) { - const errors: string[] = []; - const ouIdNames: string[] = ['Root']; - - const logger = createLogger(['security-config-validator']); - - logger.info(`${SecurityConfig.FILENAME} file validation started`); - - // SSM Document validations - const ssmDocuments = this.getSsmDocuments(values); - - // Get list of OU ID names from organization config file - ouIdNames.push(...this.getOuIdNames(organizationConfig)); - - // Get list of Account names from account config file - const accountNames = this.getAccountNames(accountsConfig); - - // Validate SSM document name - this.validateSsmDocumentTargetTypes(ssmDocuments, errors); - - // Validate SSM document name - this.validateSsmDocumentNames(ssmDocuments, errors); - - // Validate presence of SSM document files - this.validateSsmDocumentFiles(configDir, ssmDocuments, errors); - - // Validate KMS key policy files - this.validateKeyPolicyFiles(values, configDir, errors); - - // Create list of custom CMKs, any services to be validated against key list from keyManagementService - const keyNames: string[] = [values.centralSecurityServices.ebsDefaultVolumeEncryption.kmsKey!]; - - // Validate custom CMK names - this.validateCustomKeyName(values, keyNames, errors); - - // Validate EBS default encryption configuration - this.validateEbsEncryptionConfiguration(values.centralSecurityServices.ebsDefaultVolumeEncryption, errors); - - // Validate GuardDuty configuration - this.validateGuardDutyConfiguration(values.centralSecurityServices.guardduty, errors); - // Validate SecurityHub configuration - this.validateSecurityHubConfiguration(values.centralSecurityServices.securityHub, errors); - - // Validate delegated admin account - // Validate deployment targets against organization config file - // validate deployment target OUs for security services - this.validateDelegatedAdminAccount(values, accountNames, errors); - this.validateDeploymentTargetOUs(values, ouIdNames, errors); - this.validateDeploymentTargetAccountNames(values, accountNames, errors); - this.validateConfigRuleDeploymentTargetsInConfigDeploymentTargets(values, accountsConfig, globalConfig, errors); - this.validateConfigDeloymentTargetsInSecurityHubDeploymentTargets(values, accountsConfig, globalConfig, errors); - this.validateSecurityHubStandardDeloymentTargetsInSecurityHubDeloymentTargets( - values, - accountsConfig, - globalConfig, - errors, - ); - this.validateSecurityHubAndConfig(values, errors); - // Validate expiration for Macie and GuardDuty Lifecycle Rules - this.macieLifecycleRules(values, errors); - this.guarddutyLifecycleRules(values, errors); - // Validate Config rule assets - for (const ruleSet of values.awsConfig.ruleSets ?? []) { - this.validateConfigRuleAssets(configDir, ruleSet, errors); - this.validateConfigRuleRemediationAccountNames(ruleSet, accountNames, errors); - this.validateConfigRuleRemediationAssumeRoleFile(configDir, ruleSet, errors); - this.validateConfigRuleRemediationTargetAssets(configDir, ruleSet, ssmDocuments, errors); - } - this.validateConfigRuleNames(values.awsConfig, accountsConfig, globalConfig, errors); - - // Validate SNS Topics for CloudWatch Alarms - const snsTopicNames = this.getSnsTopicNames(globalConfig); - for (const alarm of values.cloudWatch.alarmSets ?? []) { - this.validateSnsTopics(globalConfig, alarm, snsTopicNames, errors); - } - - this.validateSecurityHubNotifications( - snsTopicNames, - values.centralSecurityServices.securityHub.snsTopicName ?? undefined, - values.centralSecurityServices.securityHub.notificationLevel ?? undefined, - errors, - ); - - this.validateAwsConfigAggregation(globalConfig, accountNames, values, errors); - - this.validateAwsCloudWatchLogGroups(values, errors); - this.validateAwsCloudWatchLogGroupsRetention(values, errors); - this.validateResourcePolicyEnforcementConfig(values, ouIdNames, accountNames, errors); - this.validateResourcePolicyParameters(configDir, values, replacementsConfig, errors); - - this.validateConfigRuleCmkDependency(values, globalConfig, accountsConfig, errors); - - if (errors.length) { - throw new Error(`${SecurityConfig.FILENAME} has ${errors.length} issues:\n${errors.join('\n')}`); - } - } - - public hasDuplicates(arr: string[]): boolean { - return new Set(arr).size !== arr.length; - } - - /** - * Prepare list of OU ids from organization config file - * @param organizationConfig - * @returns - */ - private getOuIdNames(organizationConfig: OrganizationConfig): string[] { - const ouIdNames: string[] = []; - if (organizationConfig.enable) { - for (const organizationalUnit of organizationConfig.organizationalUnits ?? []) { - ouIdNames.push(organizationalUnit.name); - } - } - return ouIdNames; - } - - /** - * Prepare list of Account names from account config file - * @param accountsConfig - * @returns - */ - private getAccountNames(accountsConfig: AccountsConfig): string[] { - const accountNames: string[] = []; - - for (const accountItem of [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]) { - accountNames.push(accountItem.name); - } - return accountNames; - } - - /** - * Prepare list of SNS Topic names from the global config file - * @param configDir - */ - private getSnsTopicNames(globalConfig: GlobalConfig): string[] { - return globalConfig.getSnsTopicNames(); - } - - /** - * Validate delegated admin account name - * - * @param values - * @param accountNames - * @param errors - */ - private validateDelegatedAdminAccount(values: SecurityConfig, accountNames: string[], errors: string[]) { - if (!accountNames.includes(values.centralSecurityServices.delegatedAdminAccount)) { - errors.push( - `Delegated admin account ${values.centralSecurityServices.delegatedAdminAccount} does not exist in accounts-config.yaml`, - ); - } - } - - /** - * Validate S3 lifecycle expiration to be smaller than noncurrentVersionExpiration - */ - private macieLifecycleRules(values: ISecurityConfig, errors: string[]) { - for (const lifecycleRule of values.centralSecurityServices?.macie?.lifecycleRules ?? []) { - if (lifecycleRule.expiration && !lifecycleRule.noncurrentVersionExpiration) { - errors.push('You must supply a value for noncurrentVersionExpiration. Macie.'); - } - if (!lifecycleRule.abortIncompleteMultipartUpload) { - errors.push('You must supply a value for abortIncompleteMultipartUpload. Macie'); - } - if (lifecycleRule.expiration && lifecycleRule.expiredObjectDeleteMarker) { - errors.push('You may not configure expiredObjectDeleteMarker with expiration. Macie'); - } - } - } - - /** - * Validate S3 lifecycle expiration to be smaller than noncurrentVersionExpiration - */ - private guarddutyLifecycleRules(values: ISecurityConfig, errors: string[]) { - for (const lifecycleRule of values.centralSecurityServices?.guardduty?.lifecycleRules ?? []) { - if (lifecycleRule.expiration && !lifecycleRule.noncurrentVersionExpiration) { - errors.push('You must supply a value for noncurrentVersionExpiration. GuardDuty'); - } - if (!lifecycleRule.abortIncompleteMultipartUpload) { - errors.push('You must supply a value for abortIncompleteMultipartUpload. GuardDuty'); - } - if (lifecycleRule.expiration && lifecycleRule.expiredObjectDeleteMarker) { - errors.push('You may not configure expiredObjectDeleteMarker with expiration. GuardDuty'); - } - } - } - - /** - * Function to get SSM document names - * @param values - * @returns - */ - private getSsmDocuments( - values: SecurityConfig, - ): { name: string; template: string; targetType: string | undefined }[] { - const ssmDocuments: { name: string; template: string; targetType: string | undefined }[] = []; - - // SSM Document validations - for (const documentSet of values.centralSecurityServices.ssmAutomation.documentSets) { - for (const document of documentSet.documents ?? []) { - ssmDocuments.push(document); - } - } - return ssmDocuments; - } - - /** - * Function to validate SSM document files existence - * @param configDir - */ - private validateSsmDocumentFiles( - configDir: string, - ssmDocuments: { name: string; template: string }[], - errors: string[], - ) { - // Validate presence of SSM document files - for (const ssmDocument of ssmDocuments) { - if (!fs.existsSync(path.join(configDir, ssmDocument.template))) { - errors.push(`SSM document ${ssmDocument.name} template file ${ssmDocument.template} not found !!!`); - } - } - } - - /** - * Function to validate KMS key policy files existence - * @param values - * @param configDir - * @param errors - */ - private validateKeyPolicyFiles(values: SecurityConfig, configDir: string, errors: string[]) { - // Validate presence of KMS policy files - if (!values.keyManagementService) { - return; - } - for (const key of values.keyManagementService.keySets) { - if (key.policy) { - if (!fs.existsSync(path.join(configDir, key.policy))) { - errors.push(`KMS Key ${key.name} policy file ${key.policy} not found !!!`); - } - } - } - } - - /** - * Function to validate custom key existence in key list of keyManagementService - */ - private validateCustomKeyName(values: SecurityConfig, keyNames: string[], errors: string[]) { - // Validate presence of KMS policy files - for (const keyName of keyNames) { - if (keyName) { - if (!values.keyManagementService) { - errors.push(`Custom CMK object keyManagementService not defined, CMK ${keyName} can not be used !!!`); - return; - } - if (!values.keyManagementService.keySets.find(item => item.name === keyName)) { - errors.push( - `Custom CMK ${keyName} is not part of keyManagementService key list [${ - values.keyManagementService.keySets.flatMap(item => item.name) ?? [] - }] !!!`, - ); - } - } - } - } - - /** - * Validate GuardDuty configuration - * @param guardDutyConfig GuardDutyConfig - * @param errors string[] - */ - private validateGuardDutyConfiguration(guardDutyConfig: GuardDutyConfig, errors: string[]) { - if (guardDutyConfig.excludeRegions && guardDutyConfig.deploymentTargets) { - errors.push( - `GuardDuty configuration cannot include both "deploymentTargets" and "excludeRegions" properties. Please use only one.`, - ); - } - - if ( - guardDutyConfig.deploymentTargets && - !guardDutyConfig.deploymentTargets.organizationalUnits?.includes('Root') && - (guardDutyConfig.autoEnableOrgMembers === undefined || guardDutyConfig.autoEnableOrgMembers) - ) { - errors.push( - `"autoEnableOrgMembers" should be set to "false" when using "deploymentTargets" property in guardDuty configuration`, - ); - } - } - - /** - * Validate SecurityHub configuration - * @param securityHubConfig SecurityHubConfig - * @param errors string[] - */ - private validateSecurityHubConfiguration(securityHubConfig: SecurityHubConfig, errors: string[]) { - if (securityHubConfig.excludeRegions && securityHubConfig.deploymentTargets) { - errors.push( - `securityHub configuration cannot include both "deploymentTargets" and "excludeRegions" properties. Please use only one.`, - ); - } - - if ( - securityHubConfig.deploymentTargets && - !securityHubConfig.deploymentTargets.organizationalUnits?.includes('Root') && - (securityHubConfig.autoEnableOrgMembers === undefined || securityHubConfig.autoEnableOrgMembers) - ) { - errors.push( - `"autoEnableOrgMembers" should be set to "false" when using "deploymentTargets" property in securityHub configuration`, - ); - } - } - - /** - * Validate EBS default volume encryption configuration - * @param ebsEncryptionConfig EbsDefaultVolumeEncryptionConfig - * @param errors string[] - */ - private validateEbsEncryptionConfiguration(ebsEncryptionConfig: EbsDefaultVolumeEncryptionConfig, errors: string[]) { - if (ebsEncryptionConfig.excludeRegions && ebsEncryptionConfig.deploymentTargets) { - errors.push( - `EBS default volume configuration cannot include both deploymentTargets and excludeRegions properties. Please use only one.`, - ); - } - } - - /** - * Function to validate AWS CloudWatch Log Groups configuration - */ - private validateAwsCloudWatchLogGroups(values: SecurityConfig, errors: string[]) { - const logGroupNames: string[] = []; - for (const logGroupItem of values.cloudWatch.logGroups ?? []) { - logGroupNames.push(logGroupItem.logGroupName); - const kmsKeyArn = logGroupItem.encryption?.kmsKeyArn; - const kmsKeyName = logGroupItem.encryption?.kmsKeyName; - const lzaKey = logGroupItem.encryption?.useLzaManagedKey; - if ((kmsKeyArn && kmsKeyName) || (kmsKeyArn && lzaKey) || (kmsKeyName && lzaKey)) { - errors.push( - `The Log Group ${logGroupItem.logGroupName} is specifying more than one encryption parameter. Please specify one of kmsKeyArn, kmsKeyName, or lzaKey.`, - ); - } - if (logGroupItem.encryption?.kmsKeyName) { - if (!values.keyManagementService.keySets?.find(item => item.name === logGroupItem.encryption?.kmsKeyName)) { - errors.push( - `The KMS Key Name ${logGroupItem.encryption?.kmsKeyName} provided in the config for ${logGroupItem.logGroupName} does not exist.`, - ); - } - } - } - if (this.hasDuplicates(logGroupNames)) { - errors.push( - `Duplicate CloudWatch Log Groups names exist. Log Group names must be unique. Log Group names in file: ${logGroupNames}`, - ); - } - } - - /** - * Function to validate AWS CloudWatch Log Groups retention values - */ - private validateAwsCloudWatchLogGroupsRetention(values: SecurityConfig, errors: string[]) { - const validRetentionValues = [ - 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653, - ]; - for (const logGroupItem of values.cloudWatch.logGroups ?? []) { - if (validRetentionValues.indexOf(logGroupItem.logRetentionInDays) === -1) { - errors.push( - `${logGroupItem.logGroupName} has a retention value of ${logGroupItem.logRetentionInDays}. Valid values for retention are: ${validRetentionValues}`, - ); - } - } - } - - /** - * Function to validate existence of Config deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateConfigDeploymentTargetAccounts(values: ISecurityConfig, accountNames: string[], errors: string[]) { - for (const account of values.awsConfig.deploymentTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for AWS Config does not exists in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of custom config rule deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateConfigRuleDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const ruleSet of values.awsConfig.ruleSets ?? []) { - for (const account of ruleSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for AWS Config rules does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of Config Recorder and Delivery Channel in accounts where RuleSets are getting deployed - * @param values - * @param accountsConfig - * @param globalConfig - * @param errors - */ - private validateConfigRuleDeploymentTargetsInConfigDeploymentTargets( - values: ISecurityConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - errors: string[], - ) { - const configDeploymentTargets = values.awsConfig.deploymentTargets; - - if (!configDeploymentTargets || configDeploymentTargets?.organizationalUnits?.includes('Root')) return; - - const configAccounts = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - configDeploymentTargets as DeploymentTargets, - ); - const configRegions = CommonValidatorFunctions.getRegionsFromDeploymentTargets( - configDeploymentTargets as DeploymentTargets, - globalConfig, - ); - let configRuleSetAccounts: string[] = []; - let configRuleSetRegions: t.Region[] = []; - - for (const ruleSet of values.awsConfig.ruleSets ?? []) { - configRuleSetAccounts.push( - ...CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - ruleSet.deploymentTargets as DeploymentTargets, - ), - ); - configRuleSetRegions.push( - ...CommonValidatorFunctions.getRegionsFromDeploymentTargets( - ruleSet.deploymentTargets as DeploymentTargets, - globalConfig, - ), - ); - } - - configRuleSetAccounts = [...new Set(configRuleSetAccounts)]; - configRuleSetRegions = [...new Set(configRuleSetRegions)]; - - for (const account of configRuleSetAccounts) { - if (!configAccounts.includes(account)) { - errors.push( - `awsConfig RuleSets deployment target account: "${account}" not present in deployment targets for awsConfig : "${configAccounts}".`, - ); - } - } - - for (const region of configRuleSetRegions) { - if (!configRegions.includes(region)) { - errors.push( - `awsConfig RuleSets deployment target region: "${region}" not present in deployment targets for awsConfig : "${configRegions}".`, - ); - } - } - } - - /** - * Function to validate SecurityHub Standard deploymentTargets in SecurityHub deploymentTargets - * @param values - * @param accountsConfig - * @param globalConfig - * @param errors - */ - private validateSecurityHubStandardDeloymentTargetsInSecurityHubDeloymentTargets( - values: ISecurityConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - errors: string[], - ) { - const securityHubDeploymentTargets = values.centralSecurityServices.securityHub.deploymentTargets; - const securityHubStandards = values.centralSecurityServices.securityHub.standards; - - if (!securityHubDeploymentTargets || securityHubDeploymentTargets?.organizationalUnits?.includes('Root')) return; - - const securityHubAccounts = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - securityHubDeploymentTargets as DeploymentTargets, - ); - const securityHubRegions = CommonValidatorFunctions.getRegionsFromDeploymentTargets( - securityHubDeploymentTargets as DeploymentTargets, - globalConfig, - ); - let securityHubStandardAccounts: string[] = []; - let securityHubStandardRegions: t.Region[] = []; - - for (const securityHubStandard of securityHubStandards ?? []) { - if (securityHubStandard.deploymentTargets) { - securityHubStandardAccounts.push( - ...CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - securityHubStandard.deploymentTargets as DeploymentTargets, - ), - ); - securityHubStandardRegions.push( - ...CommonValidatorFunctions.getRegionsFromDeploymentTargets( - securityHubStandard.deploymentTargets as DeploymentTargets, - globalConfig, - ), - ); - } else if (!securityHubStandard.deploymentTargets) { - errors.push( - `securityHub standard (${securityHubStandard.name}) "deploymentTargets" is required when "deploymentTargets" for securityHub is defined`, - ); - } - } - - securityHubStandardAccounts = [...new Set(securityHubStandardAccounts)]; - securityHubStandardRegions = [...new Set(securityHubStandardRegions)]; - - for (const account of securityHubStandardAccounts) { - if (!securityHubAccounts.includes(account)) { - errors.push( - `securityHub standard deployment target account: "${account}" not present in deployment targets for securityHub : "${securityHubAccounts}".`, - ); - } - } - - for (const region of securityHubStandardRegions) { - if (!securityHubRegions.includes(region)) { - errors.push( - `securityHub standard deployment target region: "${region}" not present in deployment targets for securityHub : "${securityHubRegions}".`, - ); - } - } - } - - /** - * Function to validate securityHub and awsConfig deploymentTargets - * SecurityHub requires awsConfig to be enabled - * @param values - * @param accountsConfig - * @param globalConfig - * @param errors - */ - private validateConfigDeloymentTargetsInSecurityHubDeploymentTargets( - values: ISecurityConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - errors: string[], - ) { - const awsConfig = values.awsConfig; - const securityHub = values.centralSecurityServices.securityHub; - - if ( - !awsConfig.enableConfigurationRecorder || - !awsConfig.deploymentTargets || - awsConfig.deploymentTargets.organizationalUnits?.includes('Root') || - !securityHub.enable - ) - return; - - if (!securityHub.deploymentTargets) { - errors.push( - `Provide securityHub "deploymentTargets" when "deploymentTargets" for awsConfig is provided. awsConfig must be enabled for all "deploymentTargets" utilizing securityHub`, - ); - } else if (securityHub.deploymentTargets) { - const securityHubAccounts = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - securityHub.deploymentTargets as DeploymentTargets, - ); - const securityHubRegions = CommonValidatorFunctions.getRegionsFromDeploymentTargets( - securityHub.deploymentTargets as DeploymentTargets, - globalConfig, - ); - const awsConfigAccounts = CommonValidatorFunctions.getAccountNamesFromDeploymentTargets( - accountsConfig, - awsConfig.deploymentTargets as DeploymentTargets, - ); - const awsConfigRegions = CommonValidatorFunctions.getRegionsFromDeploymentTargets( - awsConfig.deploymentTargets as DeploymentTargets, - globalConfig, - ); - - for (const account of securityHubAccounts) { - if (!awsConfigAccounts.includes(account)) { - errors.push( - `securityHub "deploymentTargets" account: "${account}" not present in "deploymentTargets" for awsConfig : "${awsConfigAccounts}". awsConfig must be enabled for all accounts utilizing securityHub.`, - ); - } - } - - for (const region of securityHubRegions) { - if (!awsConfigRegions.includes(region)) { - errors.push( - `securityHub deploymentTargets region: "${region}" not present in deploymentTargets for awsConfig : "${awsConfigRegions}". awsConfig must be enabled for all regions utilizing securityHub.`, - ); - } - } - } - } - - /** - * Function to validate securityHub and awsConfig - * SecurityHub requires awsConfig to be enabled - * @param values - * @param accountsConfig - * @param globalConfig - * @param errors - */ - private validateSecurityHubAndConfig(values: ISecurityConfig, errors: string[]) { - const awsConfig = values.awsConfig; - const securityHub = values.centralSecurityServices.securityHub; - - if (securityHub.enable && !awsConfig.enableConfigurationRecorder) { - errors.push(`securityHub requires awsConfig to be enabled.`); - } - } - - /** - * Function to validate existence of CloudWatch Metrics deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateCloudWatchMetricsDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const metricSet of values.cloudWatch.metricSets ?? []) { - if (metricSet.deploymentTargets) { - for (const account of metricSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for CloudWatch Metrics does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - } - - /** - * Function to validate existence of CloudWatch Alarms deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateCloudWatchAlarmsDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const alarmSet of values.cloudWatch.alarmSets ?? []) { - for (const account of alarmSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for CloudWatch Alarms does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of CloudWatch LogGroups deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateCloudWatchLogGroupsDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const logGroupSet of values.cloudWatch.logGroups ?? []) { - for (const account of logGroupSet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for CloudWatch LogGroups does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - /** - * Function to validate existence of SSM documents deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateSsmDocumentsDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const documentSet of values.centralSecurityServices.ssmAutomation.documentSets ?? []) { - for (const account of documentSet.shareTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for SSM automation does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - /** - * Function to validate existence of KMS key deployment target Accounts - * Make sure deployment target Accounts are part of account config file - * @param values - */ - private validateKmsKeyConfigDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const keySet of values.keyManagementService?.keySets ?? []) { - for (const account of keySet.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for KMS key ${keySet.name} does not exists in accounts-config.yaml file.`, - ); - } - } - } - } - - /** - * Validate deployment target accounts for EBS default volume encryption - * @param values SecurityConfig - * @param ouIdNames string[] - * @param errors string[] - */ - private validateEbsEncryptionDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const account of values.centralSecurityServices.ebsDefaultVolumeEncryption.deploymentTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for EBS default volume encryption does not exist in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Validate deployment target accounts for GuardDuty - * @param values SecurityConfig - * @param accountNames string[] - * @param errors string[] - */ - private validateGuardDutyDeploymentTargetAccounts(values: ISecurityConfig, accountNames: string[], errors: string[]) { - for (const account of values.centralSecurityServices.guardduty.deploymentTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push(`Deployment target account ${account} for GuardDuty does not exist in accounts-config.yaml file.`); - } - } - } - - /** - * Validate deployment target accounts for SecurityHub - * @param values SecurityConfig - * @param accountNames string[] - * @param errors string[] - */ - private validateSecurityHubDeploymentTargetAccounts( - values: ISecurityConfig, - accountNames: string[], - errors: string[], - ) { - for (const account of values.centralSecurityServices.securityHub.deploymentTargets?.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for securityHub does not exist in accounts-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate Deployment targets account name for security services - * @param values - */ - private validateDeploymentTargetAccountNames(values: ISecurityConfig, accountNames: string[], errors: string[]) { - this.validateConfigDeploymentTargetAccounts(values, accountNames, errors); - this.validateConfigRuleDeploymentTargetAccounts(values, accountNames, errors); - this.validateCloudWatchMetricsDeploymentTargetAccounts(values, accountNames, errors); - this.validateCloudWatchAlarmsDeploymentTargetAccounts(values, accountNames, errors); - this.validateSsmDocumentsDeploymentTargetAccounts(values, accountNames, errors); - this.validateCloudWatchLogGroupsDeploymentTargetAccounts(values, accountNames, errors); - this.validateKmsKeyConfigDeploymentTargetAccounts(values, accountNames, errors); - this.validateEbsEncryptionDeploymentTargetAccounts(values, accountNames, errors); - this.validateGuardDutyDeploymentTargetAccounts(values, accountNames, errors); - this.validateSecurityHubDeploymentTargetAccounts(values, accountNames, errors); - } - - /** - * Function to validate existence of Config deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateConfigDeploymentTargetOUs(values: ISecurityConfig, ouIdNames: string[], errors: string[]) { - for (const ou of values.awsConfig.deploymentTargets?.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for AWS Config rules does not exists in organization-config.yaml file.`, - ); - } - } - } - - /** - * Function to validate existence of custom config rule deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateConfigRuleDeploymentTargetOUs(values: ISecurityConfig, ouIdNames: string[], errors: string[]) { - for (const ruleSet of values.awsConfig.ruleSets ?? []) { - for (const ou of ruleSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for AWS Config rules does not exists in organization-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of CloudWatch Metrics deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateCloudWatchMetricsDeploymentTargetOUs(values: ISecurityConfig, ouIdNames: string[], errors: string[]) { - for (const metricSet of values.cloudWatch.metricSets ?? []) { - if (metricSet.deploymentTargets) { - for (const ou of metricSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for CloudWatch metrics does not exists in organization-config.yaml file.`, - ); - } - } - } - } - } - - /** - * Function to validate existence of CloudWatch Alarms deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateCloudWatchAlarmsDeploymentTargetOUs(values: ISecurityConfig, ouIdNames: string[], errors: string[]) { - for (const alarmSet of values.cloudWatch.alarmSets ?? []) { - for (const ou of alarmSet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for CloudWatch alarms does not exists in organization-config.yaml file.`, - ); - } - } - } - } - - /** - * Function to validate existence of SSM document deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateSsmDocumentDeploymentTargetOUs(values: ISecurityConfig, ouIdNames: string[], errors: string[]) { - for (const documentSet of values.centralSecurityServices.ssmAutomation.documentSets ?? []) { - for (const ou of documentSet.shareTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for SSM documents does not exists in organization-config.yaml file.`); - } - } - } - } - - /** - * Function to validate existence of Key Management Service Config deployment target OUs - * Make sure deployment target OUs are part of Organization config file - * @param values - */ - private validateKmsKeyConfigDeploymentTargetOUs(values: ISecurityConfig, ouIdNames: string[], errors: string[]) { - for (const keySet of values.keyManagementService?.keySets ?? []) { - for (const ou of keySet.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for KMS key ${keySet.name} does not exists in organization-config.yaml file.`, - ); - } - } - } - } - - /** - * Validate deployment target OUs for EBS default volume encryption - * @param values SecurityConfig - * @param ouIdNames string[] - * @param errors string[] - */ - private validateEbsEncryptionDeploymentTargetOUs(values: SecurityConfig, ouIdNames: string[], errors: string[]) { - for (const ou of values.centralSecurityServices.ebsDefaultVolumeEncryption.deploymentTargets?.organizationalUnits ?? - []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for EBS default volume encryption does not exist in organization-config.yaml file.`, - ); - } - } - } - - /** - * Validate deployment target OUs for GuardDuty - * @param values SecurityConfig - * @param ouIdNames string[] - * @param errors string[] - */ - private validateGuardDutyDeploymentTargetOUs(values: SecurityConfig, ouIdNames: string[], errors: string[]) { - for (const ou of values.centralSecurityServices.guardduty.deploymentTargets?.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for GuardDuty does not exist in organization-config.yaml file.`); - } - } - } - - /** - * Validate deployment target OUs for SecurityHub - * @param values SecurityConfig - * @param ouIdNames string[] - * @param errors string[] - */ - private validateSecurityHubDeploymentTargetOUs(values: SecurityConfig, ouIdNames: string[], errors: string[]) { - for (const ou of values.centralSecurityServices.securityHub.deploymentTargets?.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push(`Deployment target OU ${ou} for securityHub does not exist in organization-config.yaml file.`); - } - } - } - - /** - * Function to validate Deployment targets OU name for security services - * @param values - */ - private validateDeploymentTargetOUs(values: SecurityConfig, ouIdNames: string[], errors: string[]) { - this.validateSsmDocumentDeploymentTargetOUs(values, ouIdNames, errors); - this.validateCloudWatchAlarmsDeploymentTargetOUs(values, ouIdNames, errors); - this.validateCloudWatchMetricsDeploymentTargetOUs(values, ouIdNames, errors); - this.validateConfigDeploymentTargetOUs(values, ouIdNames, errors); - this.validateConfigRuleDeploymentTargetOUs(values, ouIdNames, errors); - this.validateKmsKeyConfigDeploymentTargetOUs(values, ouIdNames, errors); - this.validateEbsEncryptionDeploymentTargetOUs(values, ouIdNames, errors); - this.validateGuardDutyDeploymentTargetOUs(values, ouIdNames, errors); - this.validateSecurityHubDeploymentTargetOUs(values, ouIdNames, errors); - } - - /** - * Function to validate existence of custom config rule assets such as lambda zip file and role policy file - * @param configDir - * @param ruleSet - */ - private validateConfigRuleAssets(configDir: string, ruleSet: IAwsConfigRuleSet, errors: string[]) { - for (const rule of ruleSet.rules) { - if (rule.type === 'Custom' && rule.customRule) { - // Validate presence of custom rule lambda function zip file - if (!fs.existsSync(path.join(configDir, rule.customRule.lambda.sourceFilePath))) { - errors.push( - `Custom rule: ${rule.name} lambda function file ${rule.customRule.lambda.sourceFilePath} not found`, - ); - } - // Validate presence of custom rule lambda function role policy file - if (!fs.existsSync(path.join(configDir, rule.customRule.lambda.rolePolicyFile))) { - errors.push( - `Custom rule: ${rule.name} lambda function role policy file ${rule.customRule.lambda.rolePolicyFile} not found`, - ); - } - } - } - } - - /** - * Function to validate if AWS Config Rule names are unique to the environments they're deployed to respectively. - * @param ruleSet - * @param helpers - */ - private validateConfigRuleNames( - configItem: IAwsConfig, - accountsConfig: AccountsConfig, - globalConfig: GlobalConfig, - errors: string[], - ) { - const configRuleMap: { name: string; environments: string[] }[] = []; - const configRuleNames: string[] = []; - const duplicateNames: string[] = []; - - for (const ruleSetItem of configItem.ruleSets ?? []) { - for (const ruleItem of ruleSetItem.rules ?? []) { - configRuleMap.push({ - name: ruleItem.name, - environments: CommonValidatorFunctions.getEnvironmentsFromDeploymentTargets( - accountsConfig, - ruleSetItem.deploymentTargets as DeploymentTargets, - globalConfig, - ), - }); - } - } - - configRuleMap.forEach(configRule => configRuleNames.push(configRule.name)); - for (const ruleName of configRuleNames) { - const deploymentTargetRules = configRuleMap.filter(rule => rule.name === ruleName); - const resultMap = deploymentTargetRules.map(rule => rule.environments); - if (this.hasDuplicates(resultMap.flat())) { - duplicateNames.push(ruleName); - } - } - if (duplicateNames.length > 0) { - errors.push( - `Duplicate AWS Config rules name exist with the same name and must be unique when deployed to the same account and region. Config rules in file: ${configRuleNames}`, - ); - } - } - - /** - * Validate Config rule remediation account name - * @param ruleSet - * @param accountNames - * @param errors - */ - private validateConfigRuleRemediationAccountNames( - ruleSet: AwsConfigRuleSet, - accountNames: string[], - errors: string[], - ) { - for (const rule of ruleSet.rules) { - if (rule.remediation?.targetAccountName && !accountNames.includes(rule.remediation.targetAccountName)) { - errors.push( - `Rule: ${rule.name}, remediation target account ${rule.remediation.targetAccountName} does not exist in accounts-config.yaml`, - ); - } - } - } - - /** - * Function to validate existence of config rule remediation assume role definition file - * @param configDir - * @param ruleSet - */ - private validateConfigRuleRemediationAssumeRoleFile(configDir: string, ruleSet: IAwsConfigRuleSet, errors: string[]) { - for (const rule of ruleSet.rules) { - if (rule.remediation) { - // Validate presence of rule remediation assume role definition file - if (!fs.existsSync(path.join(configDir, rule.remediation.rolePolicyFile))) { - errors.push( - `Rule: ${rule.name}, remediation assume role definition file ${rule.remediation.rolePolicyFile} not found`, - ); - } - } - } - } - - /** - * Function to validate existence of config rule remediation target assets such as SSM document and lambda zip file - * @param configDir - * @param ruleSet - */ - private validateConfigRuleRemediationTargetAssets( - configDir: string, - ruleSet: IAwsConfigRuleSet, - ssmDocuments: { name: string; template: string }[], - errors: string[], - ) { - for (const rule of ruleSet.rules) { - if (rule.remediation) { - if (!IsPublicSsmDoc(rule.remediation.targetId)) { - // Validate presence of SSM document before used as remediation target - if (!ssmDocuments.find(item => item.name === rule.remediation?.targetId)) { - errors.push( - `Rule: ${rule.name}, remediation target SSM document ${rule.remediation?.targetId} not found in ssm automation document lists`, - ); - // Validate presence of custom rule's remediation SSM document invoke lambda function zip file - if (rule.remediation.targetDocumentLambda) { - if (!fs.existsSync(path.join(configDir, rule.remediation.targetDocumentLambda.sourceFilePath))) { - errors.push( - `Rule: ${rule.name}, remediation target SSM document lambda function file ${rule.remediation.targetDocumentLambda.sourceFilePath} not found`, - ); - } - } - } - } - } - } - } - - private validateSsmDocumentTargetTypes(ssmDocuments: IDocumentConfig[], errors: string[]) { - // check if document target type falls under the regex specified by API for SSM CreateDocument - // ref: https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_CreateDocument.html#systemsmanager-CreateDocument-request-TargetType - const ssmDocumentTargetTypeRegex = /^\/[\w.\-:/]*$/; - for (const document of ssmDocuments) { - if (document.targetType) { - if (!ssmDocumentTargetTypeRegex.test(document.targetType)) { - errors.push( - `SSM document: ${document.name} has does not conform with regular expression for TargetType in CreateDocument API call`, - ); - } else { - // check if document target length is over 200 - if (document.targetType.length > 200) { - errors.push( - `SSM document: ${document.name} has TargetType length over 200, please reduce the length of TargetType`, - ); - } - } - } - } - } - /** - * - */ - private validateSsmDocumentNames(ssmDocuments: IDocumentConfig[], errors: string[]) { - // check if document name falls under the regex specified by API for SSM CreateDocument - // ref: https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_CreateDocument.html#systemsmanager-CreateDocument-request-Name - const ssmDocumentNameRegex = /^[a-zA-Z0-9_\-.:/]{3,128}$/; - for (const document of ssmDocuments) { - if (!ssmDocumentNameRegex.test(document.name)) { - errors.push( - `SSM document: ${document.name} has does not conform with regular expression for Name in CreateDocument API call`, - ); - } - } - } - - /** - * Function to validate that sns topic references are correct - * @param globalConfig - * @param alarmSet - * @param snsTopicNames - * @param errors - */ - private validateSnsTopics( - globalConfig: GlobalConfig, - alarmSet: IAlarmSetConfig, - snsTopicNames: string[], - errors: string[], - ) { - for (const alarm of alarmSet.alarms) { - if (alarm.snsTopicName && alarm.snsAlertLevel) { - errors.push(`Alarm: ${alarm.alarmName} is configured for both snsAlertLevel (Deprecated) and snsTopicName`); - } - - if (globalConfig.snsTopics && !alarm.snsTopicName) { - errors.push( - `Alarm: ${alarm.alarmName} does not have the property snsTopicName set and global config has snsTopics configured.`, - ); - } - if (alarm.snsTopicName && !snsTopicNames.find(item => item === alarm.snsTopicName)) { - errors.push( - `Alarm: ${alarm.alarmName} is configured to use snsTopicName ${alarm.snsTopicName} and the topic is not configured in the global config.`, - ); - } - } - } - - private validateSecurityHubNotifications( - snsTopicNames: string[], - snsTopicName: string | undefined, - notificationLevel: string | undefined, - errors: string[], - ) { - if (snsTopicName && !notificationLevel) { - errors.push(`securityHub is configured with a snsTopicName and does not have a notificationLevel`); - } - if (!snsTopicName && notificationLevel) { - errors.push(`securityHub is configured with a notificationLevel and does not have a snsTopicName`); - } - if (notificationLevel) { - switch (notificationLevel) { - case 'CRITICAL': - break; - case 'HIGH': - break; - case 'MEDIUM': - break; - case 'LOW': - break; - case 'INFORMATIONAL': - break; - default: - errors.push( - `securityHub has been configured with a notificationLevel of ${notificationLevel}. This is not a valid value.`, - ); - } - } - // validate topic exists in global config - if (snsTopicName && !snsTopicNames.find(item => item === snsTopicName)) { - errors.push( - `securityHub is configured to use snsTopicName ${snsTopicName} and the topic is not configured in the global config.`, - ); - } - } - - private validateAwsConfigAggregation( - globalConfig: GlobalConfig, - accountNames: string[], - values: ISecurityConfig, - errors: string[], - ) { - if (values.awsConfig.aggregation && globalConfig.controlTower.enable) { - errors.push(`Control Tower is enabled. Config aggregation cannot be managed by AWS LZA`); - } - - if ( - values.awsConfig.aggregation && - values.awsConfig.aggregation.delegatedAdminAccount && - accountNames.indexOf(values.awsConfig.aggregation?.delegatedAdminAccount) === -1 - ) { - errors.push( - `Delegated admin account '${values.awsConfig.aggregation?.delegatedAdminAccount}' provided for config aggregation does not exist in the accounts-config.yaml file.`, - ); - } - } - - private validateResourcePolicyEnforcementConfig( - securityConfig: SecurityConfig, - ouIdNames: string[], - accountNames: string[], - errors: string[], - ) { - if (!securityConfig.resourcePolicyEnforcement) return; - const resourcePolicyEnforcementConfig = securityConfig.resourcePolicyEnforcement; - for (const resourcePolicy of resourcePolicyEnforcementConfig.policySets) { - for (const ou of resourcePolicy.deploymentTargets.organizationalUnits ?? []) { - if (ouIdNames.indexOf(ou) === -1) { - errors.push( - `Deployment target OU ${ou} for Data Perimeter AWS Config rules does not exists in organization-config.yaml file.`, - ); - } - } - - for (const account of resourcePolicy.deploymentTargets.accounts ?? []) { - if (accountNames.indexOf(account) === -1) { - errors.push( - `Deployment target account ${account} for Data Perimeter AWS Config rules does not exists in accounts-config.yaml file.`, - ); - } - } - - if (resourcePolicy.inputParameters?.['SourceAccount']) { - const sourceAccount = resourcePolicy.inputParameters['SourceAccount']; - const accountIdRegex = /^\d{12}$/; - if (sourceAccount !== 'ALL' && sourceAccount.split(',').find(accountId => !accountIdRegex.test(accountId))) { - errors.push( - `parameter 'SourceAccount' in data perimeter can only be 'ALL' or list of valid account ID (12 digits) separated by ','`, - ); - } - } - } - } - - /** - * Function to validate if static parameter in resource policy templates is defined in replacements config - * @param configDir - * @param securityConfig - * @param replacementConfig - * @param errors - */ - private validateResourcePolicyParameters( - configDir: string, - securityConfig: SecurityConfig, - replacementConfig: ReplacementsConfig | undefined, - errors: string[], - ) { - const policyFilePaths = new Set(); - for (const policySet of securityConfig.resourcePolicyEnforcement?.policySets || []) { - policySet.resourcePolicies.map(rp => policyFilePaths.add(rp.document)); - } - CommonValidatorFunctions.validateStaticParameters( - replacementConfig, - configDir, - [...policyFilePaths], - new Set([RESERVED_STATIC_PARAMETER_FOR_RESOURCE_POLICY]), - errors, - ); - } - - /** - * Function to validate AWS Config rules do not use solution defined CMK when global config s3 encryption was disabled. - * @param securityConfig - * @param globalConfig - * @param accountConfig - * @param errors - * @returns - */ - private validateConfigRuleCmkDependency( - securityConfig: SecurityConfig, - globalConfig: GlobalConfig, - accountConfig: AccountsConfig, - errors: string[], - ) { - if (!globalConfig.s3?.encryption?.deploymentTargets) { - if (globalConfig.s3?.encryption?.createCMK === false) { - for (const ruleSet of securityConfig.awsConfig.ruleSets) { - for (const rule of ruleSet.rules) { - if (this.isConfigRuleCmkDependent(rule)) { - errors.push( - `There is a parameter in the security-config.yaml file that refers to the solution created KMS encryption replacement ACCEL_LOOKUP::KMS for the remediation Lambda function for AWS Config rule ${rule.name}, however, global-config.yaml disables the creation of CMK.`, - ); - } - } - } - } - return; - } - - for (const ruleSet of securityConfig.awsConfig.ruleSets) { - for (const rule of ruleSet.rules) { - let remediationDeploymentTargets: DeploymentTargets; - if (this.isConfigRuleCmkDependent(rule)) { - if (rule.remediation.excludeRegions) { - remediationDeploymentTargets = { - accounts: ruleSet.deploymentTargets.accounts ?? [], - organizationalUnits: ruleSet.deploymentTargets.organizationalUnits ?? [], - excludedRegions: rule.remediation.excludeRegions ?? [], - excludedAccounts: ruleSet.deploymentTargets.excludedAccounts ?? [], - }; - } else { - remediationDeploymentTargets = ruleSet.deploymentTargets; - } - const ruleEnvFromDeploymentTarget = CommonValidatorFunctions.getEnvironmentsFromDeploymentTargets( - accountConfig, - remediationDeploymentTargets, - globalConfig, - ); - const s3EncryptionEnvFromDeploymentTarget = CommonValidatorFunctions.getEnvironmentsFromDeploymentTargets( - accountConfig, - globalConfig.s3.encryption.deploymentTargets, - globalConfig, - ); - - const compareDeploymentEnvironments = CommonValidatorFunctions.compareDeploymentEnvironments( - ruleEnvFromDeploymentTarget, - s3EncryptionEnvFromDeploymentTarget, - ); - - if (!compareDeploymentEnvironments.match) { - errors.push( - `There is a parameter in the security-config.yaml file that refers to the solution created KMS encryption replacement ACCEL_LOOKUP::KMS for the remediation Lambda function for AWS Config rule ${rule.name}, however, global-config.yaml disables the creation of CMK.`, - ); - } - } - } - } - } - - /** - * Function to check if given config rule uses solution deployed CMK replacement - * @param rule - * @returns - */ - private isConfigRuleCmkDependent(rule: ConfigRule): ConfigRule | undefined { - for (const parameter of rule.remediation?.parameters ?? []) { - for (const [value] of Object.entries(parameter)) { - if (parameter[value] === '${ACCEL_LOOKUP::KMS}') { - return rule; - } - } - } - - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/config/validator/utils/common-validator-functions.ts b/source/packages/@aws-accelerator/config/validator/utils/common-validator-functions.ts deleted file mode 100644 index 3f5bb88..0000000 --- a/source/packages/@aws-accelerator/config/validator/utils/common-validator-functions.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Returns true if an array contains duplicate values - * @param arr - * @returns - */ -export function hasDuplicates(arr: string[]): boolean { - return new Set(arr).size !== arr.length; -} diff --git a/source/packages/@aws-accelerator/constructs/.gitignore b/source/packages/@aws-accelerator/constructs/.gitignore deleted file mode 100644 index 69d4bb7..0000000 --- a/source/packages/@aws-accelerator/constructs/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.js -*.js.map -!jest.config.js -*.d.ts -node_modules - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-accelerator/constructs/.npmignore b/source/packages/@aws-accelerator/constructs/.npmignore deleted file mode 100644 index c1d6d45..0000000 --- a/source/packages/@aws-accelerator/constructs/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-accelerator/constructs/README.md b/source/packages/@aws-accelerator/constructs/README.md deleted file mode 100644 index 7c6ea17..0000000 --- a/source/packages/@aws-accelerator/constructs/README.md +++ /dev/null @@ -1 +0,0 @@ -# @aws-accelerator/constructs diff --git a/source/packages/@aws-accelerator/constructs/index.ts b/source/packages/@aws-accelerator/constructs/index.ts deleted file mode 100644 index 60be366..0000000 --- a/source/packages/@aws-accelerator/constructs/index.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/aws-accelerator/get-accelerator-metadata'; -export * from './lib/aws-budgets/budget-definition'; -export * from './lib/aws-cloudformation/get-resource-type'; -export * from './lib/aws-cloudwatch-logs/cloudwatch-log-data-protection'; -export * from './lib/aws-cloudwatch-logs/cloudwatch-destination'; -export * from './lib/aws-cloudwatch-logs/cloudwatch-log-group'; -export * from './lib/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter'; -export * from './lib/aws-configservice/config-tags'; -export * from './lib/aws-configservice/config-aggregation'; -export * from './lib/aws-configservice/config-recorder'; -export * from './lib/data-perimeter/detect-resource-policy'; -export * from './lib/data-perimeter/remediate-resource-policy'; -export * from './lib/data-perimeter/remediation-ssm-document'; -export * from './lib/aws-controltower/create-accounts'; -export * from './lib/aws-cur/report-definition'; -export * from './lib/aws-detective/detective-graph-config'; -export * from './lib/aws-detective/detective-members'; -export * from './lib/aws-detective/detective-organization-admin-account'; -export * from './lib/aws-directconnect/direct-connect-gateway'; -export * from './lib/aws-directconnect/gateway-association'; -export * from './lib/aws-directconnect/virtual-interface'; -export * from './lib/aws-directory-service/active-directory'; -export * from './lib/aws-directory-service/active-directory-configuration'; -export * from './lib/aws-directory-service/active-directory-log-subscription'; -export * from './lib/aws-directory-service/active-directory-resolver-rule'; -export * from './lib/aws-directory-service/share-active-directory'; -export * from './lib/aws-ec2/account-warming'; -export * from './lib/aws-ec2/cross-account-route'; -export * from './lib/aws-ec2/customer-gateway'; -export * from './lib/aws-ec2/delete-default-vpc'; -export * from './lib/aws-ec2/dhcp-options'; -export * from './lib/aws-ec2/ebs-encryption'; -export * from './lib/aws-ec2/firewall'; -export * from './lib/aws-ec2/firewall-config-replacements'; -export * from './lib/aws-ec2/firewall-instance'; -export * from './lib/aws-ec2/firewall-asg'; -export * from './lib/aws-ec2/ipam'; -export * from './lib/aws-ec2/ipam-pool'; -export * from './lib/aws-ec2/ipam-organization-admin-account'; -export * from './lib/aws-ec2/ipam-scope'; -export * from './lib/aws-ec2/ipam-subnet'; -export * from './lib/aws-ec2/prefix-list'; -export * from './lib/aws-ec2/prefix-list-route'; -export * from './lib/aws-ec2/route-table'; -export * from './lib/aws-ec2/subnet-id-lookup'; -export * from './lib/aws-ec2/transit-gateway'; -export * from './lib/aws-ec2/transit-gateway-connect'; -export * from './lib/aws-ec2/transit-gateway-peering'; -export * from './lib/aws-ec2/transit-gateway-prefix-list-reference'; -export * from './lib/aws-ec2/transit-gateway-route-table'; -export * from './lib/aws-ec2/transit-gateway-static-route'; -export * from './lib/aws-ec2/vpc'; -export * from './lib/aws-ec2/vpc-endpoint'; -export * from './lib/aws-ec2/vpc-id-lookup'; -export * from './lib/aws-ec2/vpc-peering'; -export * from './lib/aws-ec2/vpn-connection'; -export * from './lib/aws-events/move-account-rule'; -export * from './lib/aws-events/new-cloudwatch-log-event-rule'; -export * from './lib/aws-events/revert-scp-changes'; -export * from './lib/aws-elasticloadbalancingv2/gateway-load-balancer'; -export * from './lib/aws-elasticloadbalancingv2/nlb-addresses'; -export * from './lib/aws-guardduty/guardduty-detector-config'; -export * from './lib/aws-guardduty/guardduty-members'; -export * from './lib/aws-guardduty/guardduty-organization-admin-account'; -export * from './lib/aws-guardduty/guardduty-publishing-destination'; -export * from './lib/aws-auditmanager/auditmanager-organization-admin-account'; -export * from './lib/aws-auditmanager/auditmanager-reports-destination'; -export * from './lib/aws-detective/detective-members'; -export * from './lib/aws-detective/detective-organization-admin-account'; -export * from './lib/aws-detective/detective-graph-config'; -export * from './lib/aws-iam/password-policy'; -export * from './lib/aws-iam/service-linked-role'; -export * from './lib/aws-identity-center/identity-center-instance'; -export * from './lib/aws-identity-center/identity-center-organization-admin-account'; -export * from './lib/aws-identity-center/identity-center-get-permission-set-role-arn'; -export * from './lib/aws-identity-center/identity-center-assignments'; -export * from './lib/aws-firehose/cloudwatch-to-s3-firehose'; -export * from './lib/aws-fms/fms-notification-channel'; -export * from './lib/aws-fms/fms-organization-admin-account'; -export * from './lib/aws-kms/key-encryption'; -export * from './lib/aws-kms/key-lookup'; -export * from './lib/aws-macie/macie-export-config-classification'; -export * from './lib/aws-macie/macie-members'; -export * from './lib/aws-macie/macie-organization-admin-account'; -export * from './lib/aws-macie/macie-session'; -export * from './lib/aws-networkfirewall/firewall'; -export * from './lib/aws-networkfirewall/policy'; -export * from './lib/aws-networkfirewall/rule-group'; -export * from './lib/aws-organizations/account'; -export * from './lib/aws-organizations/create-accounts'; -export * from './lib/aws-organizations/enable-aws-service-access'; -export * from './lib/aws-organizations/enable-policy-type'; -export * from './lib/aws-organizations/move-accounts'; -export * from './lib/aws-organizations/organizational-units'; -export * from './lib/aws-organizations/policy'; -export * from './lib/aws-organizations/policy-attachment'; -export * from './lib/aws-organizations/register-delegated-administrator'; -export * from './lib/aws-organizations/validate-scp-count'; -export * from './lib/aws-ram/enable-sharing-with-aws-organization'; -export * from './lib/aws-ram/resource-share'; -export * from './lib/aws-ram/share-subnet-tags'; -export * from './lib/aws-route-53-resolver/firewall-domain-list'; -export * from './lib/aws-route-53-resolver/firewall-rule-group'; -export * from './lib/aws-route-53-resolver/query-logging-config'; -export * from './lib/aws-route-53-resolver/resolver-endpoint'; -export * from './lib/aws-route-53-resolver/resolver-rule'; -export * from './lib/aws-route-53/associate-hosted-zones'; -export * from './lib/aws-route-53/hosted-zone'; -export * from './lib/aws-route-53/record-set'; -export * from './lib/aws-s3/bucket'; -export * from './lib/aws-s3/bucket-encryption'; -export * from './lib/aws-s3/bucket-replication'; -export * from './lib/aws-s3/bucket-policy'; -export * from './lib/aws-s3/bucket-prefix'; -export * from './lib/aws-s3/central-logs-bucket'; -export * from './lib/aws-s3/public-access-block'; -export * from './lib/aws-s3/validate-bucket'; -export * from './lib/aws-securityhub/securityhub-members'; -export * from './lib/aws-securityhub/securityhub-organization-admin-account'; -export * from './lib/aws-securityhub/securityhub-standards'; -export * from './lib/aws-securityhub/securityhub-region-aggregation'; -export * from './lib/aws-servicecatalog/get-portfolio-id'; -export * from './lib/aws-service-quota/limits-service-quota-definition'; -export * from './lib/aws-servicecatalog/share-portfolio-with-org'; -export * from './lib/aws-servicecatalog/propagate-portfolio-associations'; -export * from './lib/aws-ssm/document'; -export * from './lib/aws-ssm/inventory'; -export * from './lib/aws-ssm/session-manager-settings'; -export * from './lib/aws-ssm/policy-attachment'; -export * from './lib/aws-ssm/put-ssm-parameter'; -export * from './lib/aws-ssm/ssm-parameter-lookup'; -export * from './lib/aws-events/security-hub-events-log'; -export * from './lib/aws-ec2/create-launch-template'; -export * from './lib/aws-autoscaling/create-autoscaling-group'; -export * from './lib/aws-elasticloadbalancingv2/target-group'; -export * from './lib/aws-elasticloadbalancingv2/network-load-balancer'; -export * from './lib/aws-elasticloadbalancingv2/application-load-balancer'; -export * from './lib/aws-certificate-manager/certificate'; -export * from './lib/lza-custom-resource'; -export * from './lib/lza-lambda'; diff --git a/source/packages/@aws-accelerator/constructs/jest.config.js b/source/packages/@aws-accelerator/constructs/jest.config.js deleted file mode 100644 index cd162f9..0000000 --- a/source/packages/@aws-accelerator/constructs/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 76, - functions: 92, - lines: 95, - statements: 95, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata.ts b/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata.ts deleted file mode 100644 index 48c7625..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata.ts +++ /dev/null @@ -1,246 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; -import { pascalCase } from 'change-case'; -const path = require('path'); - -/** - * Initialized AcceleratorMetadataProps properties - */ -export interface AcceleratorMetadataProps { - /** - * Assume Role Name for writing to Accelerator metadata bucket - */ - readonly assumeRole: string; - /** - * Metadata Account Id - */ - readonly loggingAccountId: string; - /** - * Central logging Bucket name - */ - - readonly centralLogBucketName: string; - /** - * ELB log bucket name - */ - readonly elbLogBucketName: string; - /** - * metadata log bucket name - */ - readonly metadataLogBucketName: string; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * Accelerator SSM parameter Prefix - */ - readonly acceleratorSsmParamPrefix: string; - /** - * The Accelerator Organization Id - */ - readonly organizationId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Accelerator Config Repository Name - */ - readonly acceleratorConfigRepositoryName: string; - /** - * Global Region - */ - readonly globalRegion: string; -} - -/** - * Class for FMSOrganizationAdminAccount - */ -export class AcceleratorMetadata extends Construct { - lambdaFunction: { lambda: cdk.aws_lambda.Function; logGroup: cdk.aws_logs.LogGroup }; - role: cdk.aws_iam.Role; - rule: cdk.aws_events.Rule; - constructor(scope: Construct, id: string, props: AcceleratorMetadataProps) { - super(scope, id); - - const stack = cdk.Stack.of(scope); - const account = cdk.Stack.of(this).account; - const region = cdk.Stack.of(this).region; - const functionName = `${props.acceleratorPrefix}-metadata-collection`; - - const lambdaCode = cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'get-accelerator-metadata/dist')); - this.role = this.createLambdaRole(props.acceleratorPrefix, account, region, props.metadataLogBucketName); - this.lambdaFunction = this.createLambdaFunction(functionName, stack, lambdaCode, this.role, props); - this.rule = this.createMetadataCloudwatchRule(stack, props.acceleratorPrefix, this.lambdaFunction.lambda); - this.setCdkNagSuppressions(stack, id, this.role); - } - - private createLambdaRole(acceleratorPrefix: string, account: string, region: string, metadataLogBucketName: string) { - const lambdaRole = new cdk.aws_iam.Role(this, 'MetadataLambda', { - assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), - roleName: `${acceleratorPrefix}-${account}-${region}-metadata-lambda-role`, - }); - console.log(lambdaRole.node.tryGetContext('suffix')); - lambdaRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'codepipeline:GetPipelineExecution', - 'codepipeline:ListPipelineExecutions', - 'codecommit:GetFolder', - 'codecommit:GetFile', - 'kms:DescribeKey', - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - 'kms:ListAliases', - 'logs:CreateLogGroup', - 'logs:CreateLogStream', - 'logs:PutLogEvents', - 'organizations:DescribeOrganizationalUnit', - 'organizations:DescribeAccount', - 'organizations:ListAccounts', - 'organizations:ListChildren', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:ListParents', - 'organizations:ListRoots', - 'ssm:GetParameter', - 'sts:AssumeRole', - ], - resources: [`*`], - }), - ); - lambdaRole.addToPolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:List*', - 's3:DeleteObject*', - 's3:PutObjectAcl', - 's3:PutObject', - 's3:PutObjectLegalHold', - 's3:PutObjectRetention', - 's3:PutObjectTagging', - 's3:PutObjectVersionTagging', - 's3:Abort*', - ], - resources: [ - `arn:${cdk.Stack.of(this).partition}:s3:::${metadataLogBucketName}`, - `arn:${cdk.Stack.of(this).partition}:s3:::${metadataLogBucketName}/*`, - ], - }), - ); - return lambdaRole; - } - private createLambdaFunction( - functionName: string, - stack: cdk.Stack, - code: cdk.aws_lambda.AssetCode, - role: cdk.aws_iam.Role, - props: AcceleratorMetadataProps, - ) { - const logGroup = this.setCloudwatchLogGroup(stack, functionName, props.logRetentionInDays, props.cloudwatchKmsKey); - const lambda = new cdk.aws_lambda.Function(this, functionName, { - functionName, - role, - code, - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - timeout: cdk.Duration.minutes(10), - handler: 'index.handler', - environment: { - CROSS_ACCOUNT_ROLE: props.assumeRole, - LOG_ACCOUNT_ID: props.loggingAccountId, - PARTITION: cdk.Stack.of(this).partition, - CONFIG_REPOSITORY_NAME: props.acceleratorConfigRepositoryName, - ORGANIZATION_ID: props.organizationId, - CENTRAL_LOG_BUCKET: props.centralLogBucketName, - ELB_LOGGING_BUCKET: props.elbLogBucketName, - METADATA_BUCKET: props.metadataLogBucketName, - ACCELERATOR_PREFIX: props.acceleratorPrefix, - GLOBAL_REGION: props.globalRegion, - ACCELERATOR_VERSION_SSM_PATH: `${props.acceleratorSsmParamPrefix}/${props.acceleratorPrefix}-InstallerStack/version`, - }, - }); - - lambda.node.addDependency(logGroup); - return { - lambda, - logGroup, - }; - } - private createMetadataCloudwatchRule( - stack: cdk.Stack, - acceleratorPrefix: string, - targetFunction: cdk.aws_lambda.Function, - ) { - const rule = new cdk.aws_events.Rule(this, pascalCase(`${acceleratorPrefix}MetadataCollectionRule`), { - eventPattern: { - source: ['aws.cloudformation'], - account: [stack.account], - region: [stack.region], - resources: [stack.stackId], - detailType: ['CloudFormation Stack Status Change'], - detail: { - 'stack-id': [stack.stackId], - 'status-details': { - status: ['CREATE_COMPLETE', 'UPDATE_COMPLETE'], - }, - }, - }, - schedule: cdk.aws_events.Schedule.rate(cdk.Duration.days(1)), - ruleName: `${acceleratorPrefix}-metadata-collection-rule`, - }); - - rule.addTarget(new cdk.aws_events_targets.LambdaFunction(targetFunction)); - return rule; - } - - private setCloudwatchLogGroup( - stack: cdk.Stack, - lambdaFunctionName: string, - retention: number, - kmsKey?: cdk.aws_kms.IKey, - ) { - return new cdk.aws_logs.LogGroup(stack, `${lambdaFunctionName}LogGroup`, { - logGroupName: `/aws/lambda/${lambdaFunctionName}`, - retention, - encryptionKey: kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - } - private setCdkNagSuppressions(stack: cdk.Stack, constructId: string, role: cdk.aws_iam.Role) { - // AwsSolutions-IAM5: AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${constructId}/${role.node.id}/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'accelerator metadata collection custom resource', - }, - ], - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/index.ts deleted file mode 100644 index e2269bc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/index.ts +++ /dev/null @@ -1,472 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * get-accelerator-metadata - lambda handler - * - * @param event - * @returns - */ - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export async function handler(_event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const globalRegion = process.env['GLOBAL_REGION']; - const solutionId = process.env['SOLUTION_ID']; - const acceleratorPrefix = process.env['ACCELERATOR_PREFIX']!; - const crossAccountRole = process.env['CROSS_ACCOUNT_ROLE']!; - const logAccountId = process.env['LOG_ACCOUNT_ID']!; - const partition = process.env['PARTITION']!; - const repositoryName = process.env['CONFIG_REPOSITORY_NAME']!; - const ssmAcceleratorVersionPath = process.env['ACCELERATOR_VERSION_SSM_PATH']!; - const organizationId = process.env['ORGANIZATION_ID']!; - const centralLoggingBucket = process.env['CENTRAL_LOG_BUCKET']!; - const elbLogBucket = process.env['ELB_LOGGING_BUCKET']!; - const metadataBucket = process.env['METADATA_BUCKET']!; - const pipelineName = `${acceleratorPrefix}-Pipeline`; - const codeCommitClient = new AWS.CodeCommit({ customUserAgent: solutionId }); - const s3Client = new AWS.S3({ customUserAgent: solutionId }); - const ssmClient = new AWS.SSM({ customUserAgent: solutionId }); - const organizationsClient = new AWS.Organizations({ customUserAgent: solutionId, region: globalRegion }); - const codePipelineClient = new AWS.CodePipeline({ customUserAgent: solutionId }); - const stsClient = new AWS.STS({ customUserAgent: solutionId, region: globalRegion }); - const assumeRoleCredentials = await assumeRole(stsClient, crossAccountRole, logAccountId, partition); - const s3ClientLoggingAccount = new AWS.S3({ credentials: assumeRoleCredentials }); - const kmsClientLoggingAccount = new AWS.KMS({ credentials: assumeRoleCredentials }); - const lastSuccessfulExecution = await getLastSuccessfulPipelineExecution(codePipelineClient, pipelineName); - if (!lastSuccessfulExecution) { - throw new Error('No successful Accelerator CodePipeline executions found. Exiting...'); - } - const pipelineExecutionInfo = await throttlingBackOff(() => - codePipelineClient - .getPipelineExecution({ pipelineExecutionId: lastSuccessfulExecution.pipelineExecutionId!, pipelineName }) - .promise(), - ); - - const sourceConfigArtifact = pipelineExecutionInfo.pipelineExecution?.artifactRevisions - ?.filter(artifactRevision => artifactRevision.name === 'Config') - .pop(); - - if (!sourceConfigArtifact?.revisionId) { - throw new Error('No commitId found for config artifact in CodePipeline stage source. Exiting'); - } - const commitId = sourceConfigArtifact.revisionId; - const codeCommitFiles = await getAllCodeCommitFiles({ codeCommitClient, commitId, repositoryName }); - const version = await getSsmParameterValue(ssmClient, ssmAcceleratorVersionPath); - const ous = await getAllOusWithPaths(organizationsClient); - const accounts = await getAllOrgAccountsWithPaths(organizationsClient, ous); - const logBucket = await getBucketInfo( - s3ClientLoggingAccount, - kmsClientLoggingAccount, - centralLoggingBucket, - 'LogBucket', - ); - const aesLogBucket = await getBucketInfo(s3ClientLoggingAccount, kmsClientLoggingAccount, elbLogBucket, 'AesBucket'); - - const metadata = JSON.stringify( - { - lastSuccessfulExecution, - lastSuccessfulCommitId: commitId, - acceleratorCurrentVersion: version, - logBucket, - aesLogBucket, - organizationId, - accounts, - ous, - }, - null, - 4, - ); - - const bucketItems = await listBucketObjects(s3ClientLoggingAccount, metadataBucket); - - for (const item of bucketItems) { - await s3ClientLoggingAccount.deleteObject({ Bucket: metadataBucket, Key: item.Key! }).promise(); - } - - await s3Client - .putObject({ Bucket: metadataBucket, Key: 'metadata.json', Body: metadata, ACL: 'bucket-owner-full-control' }) - .promise(); - - for (const file of codeCommitFiles) { - await s3Client - .putObject({ - Bucket: metadataBucket, - Key: `config/${file.filePath}`, - Body: file.fileContents, - ACL: 'bucket-owner-full-control', - }) - .promise(); - } - - return; -} - -async function getRepositoryFolder(props: { - codeCommitClient: AWS.CodeCommit; - repositoryName: string; - folderPath: string; - commitId: string | undefined; -}): Promise { - return throttlingBackOff(() => - props.codeCommitClient - .getFolder({ - folderPath: props.folderPath, - repositoryName: props.repositoryName, - commitSpecifier: props.commitId, - }) - .promise(), - ); -} -async function downloadRepositoryFile(props: { - codeCommitClient: AWS.CodeCommit; - repositoryName: string; - commitSpecifier: string; - filePath: string; -}): Promise<{ fileContents: string; filePath: string }> { - const file = await throttlingBackOff(() => - props.codeCommitClient - .getFile({ - filePath: props.filePath, - repositoryName: props.repositoryName, - commitSpecifier: props.commitSpecifier, - }) - .promise(), - ); - return { - fileContents: file.fileContent.toString(), - filePath: file.filePath, - }; -} - -async function getAllCodeCommitFilePaths(props: { - codeCommitClient: AWS.CodeCommit; - repositoryName: string; - commitId: string; -}) { - const folderPaths = ['/']; - const files: AWS.CodeCommit.FileList = []; - - do { - const folderPath = folderPaths.pop(); - if (folderPath) { - const folderContents = await getRepositoryFolder({ - codeCommitClient: props.codeCommitClient, - commitId: props.commitId, - repositoryName: props.repositoryName, - folderPath, - }); - for (const subFolder of folderContents.subFolders ?? []) { - if (subFolder.absolutePath) { - folderPaths.push(subFolder.absolutePath); - } - } - - if (folderContents.files && folderContents.files.length > 0) { - files.push(...folderContents.files); - } - } - } while (folderPaths.length > 0); - - return files; -} - -async function getAllCodeCommitFiles(props: { - codeCommitClient: AWS.CodeCommit; - repositoryName: string; - commitId: string; -}) { - const files = []; - const filePaths = await getAllCodeCommitFilePaths({ - codeCommitClient: props.codeCommitClient, - commitId: props.commitId, - repositoryName: props.repositoryName, - }); - for (const path of filePaths ?? []) { - if (path.absolutePath) { - const file = await downloadRepositoryFile({ - codeCommitClient: props.codeCommitClient, - repositoryName: props.repositoryName, - commitSpecifier: props.commitId, - filePath: path.absolutePath, - }); - - files.push(file); - } - } - - return files; -} - -async function getSsmParameterValue(ssmClient: AWS.SSM, parameterPath: string) { - const getParamResponse = await throttlingBackOff(() => ssmClient.getParameter({ Name: parameterPath }).promise()); - return getParamResponse.Parameter?.Value; -} - -async function listAllOuChildren( - organizationsClient: AWS.Organizations, - parentId: string, - childType: 'ORGANIZATIONAL_UNIT' | 'ACCOUNT', -) { - const children = []; - let nextToken; - do { - const childrenResponse = await throttlingBackOff(() => - organizationsClient.listChildren({ ChildType: childType, ParentId: parentId }).promise(), - ); - nextToken = childrenResponse.NextToken; - if (childrenResponse.Children && childrenResponse.Children.length > 0) { - children.push(...childrenResponse.Children); - } - } while (nextToken); - return children.map(child => { - return { - id: child.Id!, - parentId: parentId, - }; - }); -} - -function getOrgPath( - ous: { - id: string; - parentId: string; - arn: string; - name: string; - }[], - id: string, - path = '', -): string { - const filteredOus = ous.filter(ou => { - return id === ou.id; - }); - - if (id.startsWith('r-')) { - return path.slice(0, -1); - } else { - path = `${filteredOus[0]!.name}/${path}`; - } - - return getOrgPath(ous, filteredOus[0].parentId, path); -} -async function getAllOusWithPaths(organizationsClient: AWS.Organizations) { - const rootResponse = await throttlingBackOff(() => organizationsClient.listRoots({}).promise()); - const ouIdLookups: { id: string; parentId: string }[] = []; - const ouIds: string[] = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const ous: any[] = []; - if (rootResponse.Roots && rootResponse.Roots.length > 0) { - ouIds.push(rootResponse.Roots[0].Id!); - ouIdLookups.push({ id: rootResponse.Roots[0].Id!, parentId: '' }); - ous.push({ - id: rootResponse.Roots[0].Id!, - parentId: '', - arn: rootResponse.Roots[0].Arn!, - name: rootResponse.Roots[0].Name!, - }); - } - - while (ouIds.length > 0) { - const ouId = ouIds.pop()!; - const children = await listAllOuChildren(organizationsClient, ouId, 'ORGANIZATIONAL_UNIT'); - const ids = children.map(child => child.id); - ouIds.push(...ids); - ouIdLookups.push(...children); - } - - for (const ou of ouIdLookups) { - if (!ou.id.startsWith('r-')) { - const ouInfo = await throttlingBackOff(() => - organizationsClient.describeOrganizationalUnit({ OrganizationalUnitId: ou.id }).promise(), - ); - ous.push({ - id: ou.id, - parentId: ou.parentId, - arn: ouInfo.OrganizationalUnit?.Arn, - name: ouInfo.OrganizationalUnit?.Name, - }); - } - } - for (const ou of ous) { - if (ou.id.startsWith('r-')) { - ou['path'] = '/'; - } else { - const path = getOrgPath(ous, ou.id); - ou['path'] = path; - } - } - return ous; -} - -async function getAllOrgAccountsWithPaths( - organizationsClient: AWS.Organizations, - ous: { - id: string; - parentId: string; - arn: string; - name: string; - path: string; - }[], -) { - const accounts = []; - const orgAccounts: { id: string; arn: string; name: string; parentId: string; path?: string }[] = []; - for (const ou of ous) { - const childAccounts = await listAllOuChildren(organizationsClient, ou.id, 'ACCOUNT'); - accounts.push(...childAccounts); - } - - for (const account of accounts) { - const accountInfo = await throttlingBackOff(() => - organizationsClient.describeAccount({ AccountId: account.id }).promise(), - ); - orgAccounts.push({ - id: accountInfo.Account!.Id!, - arn: accountInfo.Account!.Arn!, - name: accountInfo.Account!.Name!, - parentId: account.parentId, - }); - } - - for (const account of orgAccounts) { - const path = getOrgPath([...ous, ...orgAccounts], account.id); - account['path'] = path; - } - - return orgAccounts; -} - -function findSuccessfulPipelineExecution( - executions: AWS.CodePipeline.ListPipelineExecutionsOutput, -): AWS.CodePipeline.PipelineExecutionSummary | undefined { - for (const execution of executions.pipelineExecutionSummaries ?? []) { - if (execution.status === 'Succeeded') { - return execution; - } - } - - return; -} - -async function getLastSuccessfulPipelineExecution(codePipelineClient: AWS.CodePipeline, pipelineName: string) { - let nextToken: string | undefined; - let lastSuccessfulExecution: AWS.CodePipeline.PipelineExecutionSummary | undefined; - do { - const executions = await throttlingBackOff(() => - codePipelineClient.listPipelineExecutions({ pipelineName, nextToken }).promise(), - ); - lastSuccessfulExecution = findSuccessfulPipelineExecution(executions); - nextToken = executions.nextToken; - } while (nextToken || !lastSuccessfulExecution); - - return lastSuccessfulExecution; -} - -async function getBucketInfo( - s3client: AWS.S3, - kmsClient: AWS.KMS, - bucketName: string, - type: 'LogBucket' | 'AesBucket', -) { - const bucketRegion = await throttlingBackOff(() => s3client.getBucketLocation({ Bucket: bucketName }).promise()); - const bucketEncryption = await throttlingBackOff(() => - s3client.getBucketEncryption({ Bucket: bucketName }).promise(), - ); - let encryptionKeyId: string | undefined; - let encryptionKeyArn: string | undefined; - let alias; - for (const rule of bucketEncryption.ServerSideEncryptionConfiguration?.Rules || []) { - if (rule.ApplyServerSideEncryptionByDefault?.KMSMasterKeyID) { - encryptionKeyId = rule.ApplyServerSideEncryptionByDefault.KMSMasterKeyID; - } - } - if (encryptionKeyId) { - const keyInfo = await kmsClient.describeKey({ KeyId: encryptionKeyId }).promise(); - encryptionKeyArn = keyInfo.KeyMetadata?.Arn; - const aliasResponse = await kmsClient.listAliases({ KeyId: encryptionKeyId }).promise(); - if (aliasResponse.Aliases && aliasResponse.Aliases.length > 0) { - alias = aliasResponse.Aliases.pop()?.AliasName; - } - console.log(keyInfo.KeyMetadata); - console.log(alias); - } - const logBucket: { - type: 'LogBucket' | 'AesBucket'; - value: { - bucketArn: string; - bucketName: string; - encryptionKeyArn?: string; - region?: string; - encryptionKeyId?: string; - encryptionKeyName?: string; - }; - } = { - type, - value: { - bucketArn: `arn:aws:s3:::${bucketName}`, - bucketName, - region: bucketRegion.LocationConstraint, - }, - }; - if (encryptionKeyId) { - logBucket.value.encryptionKeyId = encryptionKeyId; - } - if (encryptionKeyArn) { - logBucket.value['encryptionKeyArn'] = encryptionKeyArn; - } - if (alias) { - logBucket.value['encryptionKeyName'] = alias; - } - - return logBucket; -} -async function assumeRole( - stsClient: AWS.STS, - assumeRoleName: string, - accountId: string, - partition: string, -): Promise { - const roleArn = `arn:${partition}:iam::${accountId}:role/${assumeRoleName}`; - const assumeRole = await throttlingBackOff(() => - stsClient.assumeRole({ RoleArn: roleArn, RoleSessionName: 'MetadataAssumeRoleSession' }).promise(), - ); - return new AWS.Credentials({ - accessKeyId: assumeRole.Credentials!.AccessKeyId, - secretAccessKey: assumeRole.Credentials!.SecretAccessKey, - sessionToken: assumeRole.Credentials!.SessionToken, - }); -} - -async function listBucketObjects(s3Client: AWS.S3, bucket: string) { - let nextToken: string | undefined; - const bucketObjects = []; - do { - const listObjectsResponse = await throttlingBackOff(() => - s3Client.listObjectsV2({ Bucket: bucket, ContinuationToken: nextToken }).promise(), - ); - nextToken = listObjectsResponse.ContinuationToken; - if (listObjectsResponse.Contents && listObjectsResponse.Contents.length > 0) { - bucketObjects.push(...listObjectsResponse.Contents); - } - } while (nextToken); - return bucketObjects; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/package.json deleted file mode 100644 index 5f755f7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-accelerator-get-accelerator-metadata", - "version": "1.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-accelerator/get-accelerator-metadata/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-organization-admin-account.ts deleted file mode 100644 index f316428..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-organization-admin-account.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized AuditManagerOrganizationalAdminAccountProps properties - */ -export interface AuditManagerOrganizationalAdminAccountProps { - /** - * Admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class for AuditManagerOrganizationAdminAccount - */ -export class AuditManagerOrganizationAdminAccount extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: AuditManagerOrganizationalAdminAccountProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::AuditManagerEnableOrganizationAdminAccount'; - - const commonPolicyStatements = [ - { - Sid: 'AuditManagerEnableOrganizationAdminAccountTaskOrganizationActions', - Effect: 'Allow', - Action: [ - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:DescribeOrganization', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListAccounts', - 'organizations:ListDelegatedAdministrators', - 'organizations:RegisterDelegatedAdministrator', - ], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:DeregisterDelegatedAdministrator': ['auditmanager.amazonaws.com'], - 'organizations:DescribeOrganization': ['auditmanager.amazonaws.com'], - 'organizations:EnableAWSServiceAccess': ['auditmanager.amazonaws.com'], - 'organizations:ListAWSServiceAccessForOrganization': ['auditmanager.amazonaws.com'], - 'organizations:ListAccounts': ['auditmanager.amazonaws.com'], - 'organizations:ListDelegatedAdministrators': ['auditmanager.amazonaws.com'], - 'organizations:RegisterDelegatedAdministrator': ['auditmanager.amazonaws.com'], - }, - }, - }, - { - Sid: 'AuditManagerEnableOrganizationAdminAccountTaskDetectiveActions', - Effect: 'Allow', - Action: [ - 'auditmanager:RegisterAccount', - 'auditmanager:DeregisterAccount', - 'auditmanager:RegisterOrganizationAdminAccount', - 'auditmanager:DeregisterOrganizationAdminAccount', - 'auditmanager:getOrganizationAdminAccount', - ], - Resource: '*', - }, - { - Sid: 'ServiceLinkedRoleDetective', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: ['*'], - }, - ]; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'enable-organization-admin-account/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: props.kmsKey - ? [ - ...commonPolicyStatements, - { - Sid: 'AuditManagerEnableKmsKeyGrants', - Effect: 'Allow', - Action: 'kms:CreateGrant', - Resource: props.kmsKey.keyArn, - Condition: { - StringLike: { - 'kms:ViaService': 'auditmanager.*.amazonaws.com', - }, - Bool: { - 'kms:GrantIsForAWSResource': 'true', - }, - }, - }, - ] - : [...commonPolicyStatements], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - adminAccountId: props.adminAccountId, - kmsKeyArn: props.kmsKey ? props.kmsKey.keyArn : undefined, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-reports-destination.ts b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-reports-destination.ts deleted file mode 100644 index cc3293f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/auditmanager-reports-destination.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Export config destination types - */ -export enum AuditManagerDefaultReportsDestinationTypes { - S3 = 'S3', -} - -/** - * Initialized AuditManagerDefaultReportDestinationProps properties - */ -export interface AuditManagerDefaultReportsDestinationProps { - /** - * Export destination type - */ - readonly defaultReportsDestinationType: string; - /** - * Publishing destination bucket arn - */ - readonly bucket: string; - /** - * Publishing destination bucket KMS key - */ - readonly bucketKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class - AuditManagerDefaultsReportDestination - */ -export class AuditManagerDefaultReportsDestination extends Construct { - public readonly id: string = ''; - - constructor(scope: Construct, id: string, props: AuditManagerDefaultReportsDestinationProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::AuditManagerCreateDefaultReportsDestination'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'create-reports-destination/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'AuditManagerCreatePublishingDestinationCommandTaskAuditManagerActions', - Effect: 'Allow', - Action: ['auditmanager:UpdateSettings'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - defaultReportsDestinationType: props.defaultReportsDestinationType, - bucket: props.bucket, - kmsKeyArn: props.bucketKmsKey ? props.bucketKmsKey.keyArn : undefined, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/index.ts deleted file mode 100644 index 871a6e8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * create-auditmanager-default-reports-destination - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const defaultReportsDestinationType = event.ResourceProperties['defaultReportsDestinationType']; - const bucket = event.ResourceProperties['bucket']; - const kmsKeyArn: string | undefined = event.ResourceProperties['kmsKeyArn'] ?? undefined; - const solutionId = process.env['SOLUTION_ID']; - - const auditManagerClient = new AWS.AuditManager({ region: region, customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('starting - CreateDefaultLoggingDestination'); - - await throttlingBackOff(() => - auditManagerClient - .updateSettings({ - defaultAssessmentReportsDestination: { - destination: bucket, - destinationType: defaultReportsDestinationType, - }, - kmsKey: kmsKeyArn, - }) - .promise(), - ); - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - console.log('starting - DeleteDefaultReportDestination'); - - return { Status: 'Success', StatusCode: 200 }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/package.json deleted file mode 100644 index 67be9d5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-auditmanager-create-reports-destination", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/create-reports-destination/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/index.ts deleted file mode 100644 index ef0c7cb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * enable-auditmanager - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const adminAccountId = event.ResourceProperties['adminAccountId']; - const kmsKeyArn: string | undefined = event.ResourceProperties['kmsKeyArn'] ?? undefined; - const solutionId = event.ResourceProperties['solutionId']; - - const auditManagerClient = new AWS.AuditManager({ region: region, customUserAgent: solutionId }); - - const auditManagerAdminAccount = await isAuditManagerEnable(auditManagerClient); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (auditManagerAdminAccount.accountId === adminAccountId) { - console.warn( - `GuardDuty admin account ${auditManagerAdminAccount.accountId} is already an admin account, in ${region} region. No action needed`, - ); - return { Status: 'Success', StatusCode: 200 }; - } else { - console.log( - `Started enableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - auditManagerClient.registerAccount({ delegatedAdminAccount: adminAccountId, kmsKey: kmsKeyArn }).promise(), - ); - await throttlingBackOff(() => - auditManagerClient.registerOrganizationAdminAccount({ adminAccountId: adminAccountId }).promise(), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - console.log(auditManagerAdminAccount.accountId); - if (auditManagerAdminAccount.accountId) { - if (auditManagerAdminAccount.accountId === adminAccountId) { - console.log( - `Started disableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - auditManagerClient - .deregisterOrganizationAdminAccount({ - adminAccountId: adminAccountId, - }) - .promise(), - ); - await throttlingBackOff(() => auditManagerClient.deregisterAccount().promise()); - } - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function isAuditManagerEnable(auditManagerClient: AWS.AuditManager): Promise<{ accountId: string | undefined }> { - try { - const response = await throttlingBackOff(() => auditManagerClient.getOrganizationAdminAccount().promise()); - const adminAccount = response.adminAccountId; - - if (adminAccount === undefined) { - return { accountId: undefined }; - } - return { accountId: adminAccount }; - //eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.code === 'AccessDeniedException') { - console.log('Admin account not enabled'); - return { accountId: undefined }; - } else { - throw error; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/package.json deleted file mode 100644 index 130e7f2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-auditmanager-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index db9a8d7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-auditmanager/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} - diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-autoscaling/create-autoscaling-group.ts b/source/packages/@aws-accelerator/constructs/lib/aws-autoscaling/create-autoscaling-group.ts deleted file mode 100644 index de18ce9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-autoscaling/create-autoscaling-group.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { ServiceLinkedRole } from '@aws-accelerator/constructs'; - -export interface IAutoscalingGroupResource extends cdk.IResource { - /** - * The name of the AWS::AutoScaling::AutoScalingGroup resource - */ - readonly autoscalingGroupName: string; -} - -export interface AutoscalingGroupProps { - readonly name: string; - readonly minSize: number; - readonly maxSize: number; - readonly desiredSize: number; - readonly launchTemplateVersion: string; - readonly launchTemplateId: string; - readonly healthCheckGracePeriod?: number; - readonly healthCheckType?: string; - readonly targetGroups?: string[]; - readonly subnets: string[]; - readonly tags?: cdk.CfnTag[]; - readonly lambdaKey?: cdk.aws_kms.IKey; - readonly cloudWatchLogKmsKey?: cdk.aws_kms.IKey; - readonly cloudWatchLogRetentionInDays: number; - readonly maxInstanceLifetime?: number; - /** - * Prefix for nag suppression - */ - readonly nagSuppressionPrefix?: string; -} - -export class AutoscalingGroup extends cdk.Resource implements IAutoscalingGroupResource { - public readonly autoscalingGroupName: string; - - constructor(scope: Construct, id: string, props: AutoscalingGroupProps) { - super(scope, id); - - // Ensure there is service linked role for autoscaling - new ServiceLinkedRole(this, 'AutoScalingServiceLinkedRole', { - environmentEncryptionKmsKey: props.lambdaKey, - cloudWatchLogKmsKey: props.cloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.cloudWatchLogRetentionInDays, - awsServiceName: 'autoscaling.amazonaws.com', - description: - 'Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling', - roleName: 'AWSServiceRoleForAutoScaling', - }); - - const resource = new cdk.aws_autoscaling.CfnAutoScalingGroup(this, 'Resource', { - minSize: props.minSize.toString(), - maxSize: props.maxSize.toString(), - desiredCapacity: props.desiredSize.toString(), - launchTemplate: { - version: props.launchTemplateVersion, - launchTemplateId: props.launchTemplateId, - }, - healthCheckType: props.healthCheckType!, - healthCheckGracePeriod: props.healthCheckGracePeriod, - targetGroupArns: props.targetGroups, - vpcZoneIdentifier: props.subnets, - maxInstanceLifetime: props.maxInstanceLifetime, - tags: props.tags ? this.processTags(props.tags) : undefined, - // autoScalingGroupName: props.name, - }); - this.autoscalingGroupName = resource.ref; - } - - private processTags(tags: cdk.CfnTag[]): cdk.aws_autoscaling.CfnAutoScalingGroup.TagPropertyProperty[] { - return tags.map(tag => { - return { - key: tag.key, - value: tag.value, - propagateAtLaunch: true, - }; - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/budget-definition.ts b/source/packages/@aws-accelerator/constructs/lib/aws-budgets/budget-definition.ts deleted file mode 100644 index 6a4a8c7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/budget-definition.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import path from 'path'; -import { NotificationConfig } from '@aws-accelerator/config'; - -type TimeUnit = 'DAILY' | 'WEEKLY' | 'MONTHLY' | string; - -type Type = 'COST' | 'RI_UTILIZATION' | 'RI_COVERAGE' | 'SAVINGS_PLAN_UTILIZATION' | 'SAVINGS_PLAN_COVERAGE' | string; - -type Unit = 'USD' | string; - -export interface BudgetDefinitionProps { - /** - * The total amount of costs, usage, RI utilization, RI coverage, Savings Plans utilization, or Savings Plans - * coverage that you want to track with your budget. - */ - readonly amount: number; - /** - * Specifies whether a budget includes credits. - */ - readonly includeCredit: boolean; - /** - * Specifies whether a budget includes discounts. - */ - readonly includeDiscount: boolean; - /** - * Specifies whether a budget includes non-RI subscription costs. - */ - readonly includeOtherSubscription: boolean; - /** - * Specifies whether a budget includes recurring fees such as monthly RI fees. - */ - readonly includeRecurring: boolean; - /** - * Specifies whether a budget includes refunds. - */ - readonly includeRefund: boolean; - /** - * Specifies whether a budget includes subscriptions. - */ - readonly includeSubscription: boolean; - /** - * Specifies whether a budget includes support subscription fees. - */ - readonly includeSupport: boolean; - /** - * Specifies whether a budget includes taxes. - */ - readonly includeTax: boolean; - /** - * Specifies whether a budget includes upfront RI costs. - */ - readonly includeUpfront: boolean; - /** - * The name of the budget. - */ - readonly name: string; - /** - * List of notifications. - */ - readonly notifications?: NotificationConfig[]; - /** - * The length of time until a budget resets the actual and forecasted spend. - */ - readonly timeUnit: TimeUnit; - /** - * Specifies whether this budget tracks costs, usage, RI utilization, RI coverage, - * Savings Plans utilization, or Savings Plans coverage. - */ - readonly type: Type; - /** - * Specifies whether a budget uses the amortized rate. - */ - readonly useAmortized: boolean; - /** - * Specifies whether a budget uses a blended rate. - */ - readonly useBlended: boolean; - /** - * The unit of measurement that's used for the budget forecast, actual spend, or budget threshold, such as USD or GBP. - */ - readonly unit: Unit; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export interface SubscribersDefination { - /** - * The total amount of costs, usage, RI utilization, RI coverage, Savings Plans utilization, or Savings Plans - * coverage that you want to track with your budget. - */ - readonly subscriptionType: 'SNS' | 'EMAIL'; - readonly address: string; -} - -export class BudgetDefinition extends cdk.Resource { - readonly id!: string; - constructor(scope: Construct, id: string, props: BudgetDefinitionProps) { - super(scope, id); - const notificationsWithSubscribers = []; - const budget = { - budgetType: props.type, - timeUnit: props.timeUnit, - budgetLimit: { - amount: props.amount, - unit: props.unit, - }, - budgetName: props.name, - costTypes: { - includeCredit: props.includeCredit, - includeDiscount: props.includeDiscount, - includeOtherSubscription: props.includeOtherSubscription, - includeRecurring: props.includeRecurring, - includeRefund: props.includeRefund, - includeSubscription: props.includeSubscription, - includeSupport: props.includeSupport, - includeTax: props.includeTax, - includeUpfront: props.includeUpfront, - useAmortized: props.useAmortized, - useBlended: props.useBlended, - }, - }; - for (const notify of props.notifications ?? []) { - const notificationWithSubscriber = { - notification: { - comparisonOperator: notify.comparisonOperator, - notificationType: notify.type, - threshold: notify.threshold, - thresholdType: notify.thresholdType ?? undefined, - }, - subscribers: this.getRecipients(notify), - }; - notificationsWithSubscribers.push(notificationWithSubscriber); - } - - // List of available endpoints in AWS Budgets - // https://docs.aws.amazon.com/general/latest/gr/billing.html - const awsBudgetsSupportedRegions = [ - 'us-east-1', - 'us-east-2', - 'us-west-1', - 'us-west-2', - 'ap-northeast-2', - 'ap-southeast-1', - 'ap-southeast-2', - 'ap-northeast-1', - 'ca-central-1', - 'eu-central-1', - 'eu-west-1', - 'eu-west-2', - 'eu-west-3', - 'sa-east-1', - ]; - - if (awsBudgetsSupportedRegions.includes(cdk.Stack.of(this).region)) { - new cdk.aws_budgets.CfnBudget(this, `${budget.budgetName}`, { - budget, - notificationsWithSubscribers, - }); - } else { - // Use custom resource - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::CrossRegionBudget', { - codeDirectory: path.join(__dirname, 'cross-region-budget/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['budgets:DeleteBudget', 'budgets:ModifyBudget', 'budgets:PutBudget'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'CrossRegionResource', { - resourceType: 'Custom::CrossRegionBudget', - serviceToken: provider.serviceToken, - properties: { - budgetDefinition: { - amount: props.amount, - includeCredit: props.includeCredit, - includeDiscount: props.includeDiscount, - includeOtherSubscription: props.includeOtherSubscription, - includeRecurring: props.includeRecurring, - includeRefund: props.includeRefund, - includeSubscription: props.includeSubscription, - includeSupport: props.includeSupport, - includeTax: props.includeTax, - includeUpfront: props.includeUpfront, - name: props.name, - notifications: props.notifications, - timeUnit: props.timeUnit, - type: props.type, - useAmortized: props.useAmortized, - useBlended: props.useBlended, - unit: props.unit, - }, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } - } - private getRecipients(notify: NotificationConfig) { - const recipients: SubscribersDefination[] = []; - for (const recipient of notify.recipients ?? []) { - recipients.push({ - subscriptionType: notify.subscriptionType, - address: recipient, - }); - } - if (notify.address) { - recipients.push({ - subscriptionType: notify.subscriptionType, - address: notify.address, - }); - } - return recipients; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/index.ts deleted file mode 100644 index 0a0fb4e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/index.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -AWS.config.logger = console; - -/** - * cross-region-report-definition - lambda handler - * - * @param event - * @returns - */ -interface NotificationConfig { - threshold: string; - thresholdType: 'PERCENTAGE' | 'ABSOLUTE_VALUE'; - type: 'ACTUAL' | 'FORECASTED'; - comparisonOperator: 'GREATER_THAN' | 'LESS_THAN'; - address: string; - recipients: string[]; - subscriptionType: 'EMAIL' | 'SNS'; -} -interface SubscribersDefinition { - readonly SubscriptionType: 'SNS' | 'EMAIL'; - readonly Address: string; -} -interface BudgetDefinition { - amount: number; - includeCredit: string; - includeDiscount: string; - includeOtherSubscription: string; - includeRecurring: string; - includeRefund: string; - includeSubscription: string; - includeSupport: string; - includeTax: string; - includeUpfront: string; - name: string; - notifications?: NotificationConfig[]; - timeUnit: string; - type: string; - useAmortized: string; - useBlended: string; - unit: string; -} - -export async function handler( - event: CloudFormationCustomResourceEvent, - context: { invokedFunctionArn: string }, -): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - const budgetDefinition: BudgetDefinition = event.ResourceProperties['budgetDefinition']; - - if (!budgetDefinition) { - throw new Error('Budget definition is missing from Resource Properties.'); - } - - let budgetClient = new AWS.Budgets(); - const partition = event.ResourceProperties['partition']; - if (partition === 'aws-us-gov') { - console.log(`AWS GovCloud does not have an AWS Budgets endpoints.`); - return; - } else if (partition === 'aws-cn') { - budgetClient = new AWS.Budgets({ region: 'cn-northwest-1' }); - } else { - budgetClient = new AWS.Budgets({ region: 'us-east-1' }); - } - - //AccountId retrieved from context - const awsAccountId = context.invokedFunctionArn.split(':')[4]; - - if (!awsAccountId) { - throw new Error('Account Id is missing from Resource Properties.'); - } - - // cast string to booleans - const includeCredit = JSON.parse(budgetDefinition.includeCredit); - const includeDiscount = JSON.parse(budgetDefinition.includeDiscount); - const includeOtherSubscription = JSON.parse(budgetDefinition.includeOtherSubscription); - const includeRecurring = JSON.parse(budgetDefinition.includeRecurring); - const includeRefund = JSON.parse(budgetDefinition.includeRefund); - const inscludeSubscription = JSON.parse(budgetDefinition.includeCredit); - const includeSupport = JSON.parse(budgetDefinition.includeSupport); - const includeUpfront = JSON.parse(budgetDefinition.includeUpfront); - const includeTax = JSON.parse(budgetDefinition.includeTax); - const useAmortized = JSON.parse(budgetDefinition.useAmortized); - const useBlended = JSON.parse(budgetDefinition.useBlended); - - switch (event.RequestType) { - case 'Create': - // Create new budget definition - console.log(`Creating new budget definition ${budgetDefinition.name}`); - const notifications = []; - for (const budgetNotifications of budgetDefinition.notifications ?? []) { - const budgetNotification = { - Notification: { - ComparisonOperator: `${budgetNotifications.comparisonOperator}`, - NotificationType: `${budgetNotifications.type}`, - Threshold: Number(budgetNotifications.threshold), - ThresholdType: `${budgetNotifications.thresholdType}`, - }, - Subscribers: JSON.parse(JSON.stringify(getRecipients(budgetNotifications))), - }; - notifications.push(budgetNotification); - } - const createParams = { - AccountId: awsAccountId /* required */, - Budget: { - BudgetName: budgetDefinition.name /* required */, - BudgetType: budgetDefinition.type /* required */, - TimeUnit: budgetDefinition.timeUnit /* required */, - BudgetLimit: { - Amount: budgetDefinition.amount.toString() /* required */, - Unit: budgetDefinition.unit /* required */, - }, - CostTypes: { - IncludeCredit: includeCredit, - IncludeDiscount: includeDiscount, - IncludeOtherSubscription: includeOtherSubscription, - IncludeRecurring: includeRecurring, - IncludeRefund: includeRefund, - IncludeSubscription: inscludeSubscription, - IncludeSupport: includeSupport, - IncludeTax: includeTax, - IncludeUpfront: includeUpfront, - UseAmortized: useAmortized, - UseBlended: useBlended, - }, - LastUpdatedTime: new Date(), - }, - NotificationsWithSubscribers: notifications, - }; - - //const createParams = paramsArr[0]; - await throttlingBackOff(() => budgetClient.createBudget(createParams).promise()); - - return { - PhysicalResourceId: budgetDefinition.name, - Status: 'SUCCESS', - }; - - case 'Update': - // Modify budget definition - console.log(`Modifying budget definition ${budgetDefinition.name}`); - const updateParams = { - AccountId: awsAccountId /* required */, - NewBudget: { - /* required */ BudgetName: budgetDefinition.name /* required */, - BudgetType: budgetDefinition.type /* required */, - TimeUnit: budgetDefinition.timeUnit /* required */, - BudgetLimit: { - Amount: budgetDefinition.amount.toString() /* required */, - Unit: budgetDefinition.unit /* required */, - }, - CostTypes: { - IncludeCredit: includeCredit, - IncludeDiscount: includeDiscount, - IncludeOtherSubscription: includeOtherSubscription, - IncludeRecurring: includeRecurring, - IncludeRefund: includeRefund, - IncludeSubscription: inscludeSubscription, - IncludeSupport: includeSupport, - IncludeTax: includeTax, - IncludeUpfront: includeUpfront, - UseAmortized: useAmortized, - UseBlended: useBlended, - }, - LastUpdatedTime: new Date(), - }, - }; - await throttlingBackOff(() => budgetClient.updateBudget(updateParams).promise()); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - // Delete budget definition - console.log(`Deleting budget definition ${event.PhysicalResourceId}`); - const deleteParams = { - AccountId: awsAccountId /* required */, - BudgetName: budgetDefinition.name /* required */, - }; - await throttlingBackOff(() => budgetClient.deleteBudget(deleteParams).promise()); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} -export function getRecipients(notify: NotificationConfig) { - const recipients: SubscribersDefinition[] = []; - for (const recipient of notify.recipients ?? []) { - recipients.push({ - SubscriptionType: notify.subscriptionType, - Address: recipient, - }); - } - if (notify.address) { - recipients.push({ - SubscriptionType: notify.subscriptionType, - Address: notify.address, - }); - } - return recipients; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/package.json deleted file mode 100644 index bb6b9fc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-budgets-cross-region-definition", - "version": "0.0.0", - "private": true, - "description": "A custom resource to create a Budget Defnition in unavailable regions.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-budgets/cross-region-budget/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/certificate.ts b/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/certificate.ts deleted file mode 100644 index 301fa2b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/certificate.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface CertificateProps { - /** - * SSM parameter name for certificate ARN - */ - parameterName: string; - /** - * - * Certificate type - */ - type: string; - /** - * - * The private key that matches the public key in the certificate. - */ - privKey?: string; - /** - * - * The certificate to import. - */ - cert?: string; - /** - * - * PEM encoded certificate chain - */ - chain?: string; - /** - * - * Certificate validation method - */ - validation?: string; - /** - * - * Fully qualified domain name (FQDN), such as www.example.com, that you want to secure with an ACM certificate. - */ - domain?: string; - /** - * Additional FQDNs to be included in the Subject Alternative Name extension of the ACM certificate. For example, add the name www.example.net to a certificate for which the DomainName field is www.example.com if users can reach your site by using either name. - */ - san?: string[]; - /** - * Home region where the asset bucket lives - */ - homeRegion: string; - /** - * Asset Lambda function role name - */ - assetFunctionRoleName: string; - /** - * Asset S3 bucket name - */ - assetBucketName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - cloudWatchLogsKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - logRetentionInDays: number; -} - -/** - * Class to create ACM certificates - */ -export class Certificate extends Construct { - readonly id: string; - constructor(scope: Construct, id: string, props: CertificateProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::CreateAcmCerts'; - - // - // Function definition for the custom resource - // - const providerLambda = new cdk.aws_lambda.Function(this, 'Function', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-certificates/dist')), - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - description: 'Create ACM certificates handler', - role: cdk.aws_iam.Role.fromRoleName(this, 'AssetsFunctionRole', props.assetFunctionRoleName), - }); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudWatchLogsKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'Custom::CreateAcmCerts', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - parameterName: props.parameterName, - type: props.type, - privKey: props.privKey, - cert: props.cert, - chain: props.chain, - validation: props.validation, - domain: props.domain, - san: props.san?.join(','), - homeRegion: props.homeRegion, - assetBucketName: props.assetBucketName, - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/index.ts deleted file mode 100644 index 6e4008c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/index.ts +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; -import { Readable } from 'stream'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - //set variables from event - const parameterName: string = event.ResourceProperties['parameterName']; - const type: string = event.ResourceProperties['type']; - const privKey: string | undefined = event.ResourceProperties['privKey']; - const cert: string | undefined = event.ResourceProperties['cert']; - const chain: string | undefined = event.ResourceProperties['chain']; - const validation: string | undefined = event.ResourceProperties['validation']; - const domain: string | undefined = event.ResourceProperties['domain']!; - const sanString: string | undefined = event.ResourceProperties['san']; - const assetBucketName: string = event.ResourceProperties['assetBucketName']; - const homeRegion: string = event.ResourceProperties['homeRegion']; - - const san = sanString ? sanString.split(',') : undefined; - const solutionId = process.env['SOLUTION_ID']; - - const acmClient = new AWS.ACM({ customUserAgent: solutionId }); - const s3Client = new S3Client({ customUserAgent: solutionId, region: homeRegion }); - const ssmClient = new AWS.SSM({ customUserAgent: solutionId }); - const certArn = await getCertificateArnFromSsm(ssmClient, parameterName); - switch (event.RequestType) { - case 'Create': - if (certArn) { - console.log(`Certificate "${parameterName}" is already imported`); - } else { - await createUpdateEventHandler({ - acmClient, - s3Client, - ssmClient, - parameterName, - type, - assetBucketName, - privKey, - cert, - chain, - domain, - validation, - san, - }); - } - break; - case 'Update': - if (!certArn) { - throw new Error(`SSM Parameter "${parameterName}" for Certificate is not found.`); - } - await createUpdateEventHandler({ - acmClient, - s3Client, - ssmClient, - parameterName, - type, - assetBucketName, - privKey, - cert, - chain, - domain, - validation, - san, - certificateArn: certArn, - }); - break; - case 'Delete': - if (certArn) { - await throttlingBackOff(() => acmClient.deleteCertificate({ CertificateArn: certArn }).promise()); - await throttlingBackOff(() => ssmClient.deleteParameter({ Name: parameterName }).promise()); - } - break; - } - return { Status: 'Success', StatusCode: 200 }; -} - -/** - * Function to handle create or update event for the custom resource lambda function - * @param props - */ -async function createUpdateEventHandler(props: { - acmClient: AWS.ACM; - s3Client: S3Client; - ssmClient: AWS.SSM; - parameterName: string; - type: string; - assetBucketName: string; - certificateArn?: string; - privKey?: string; - cert?: string; - chain?: string; - domain?: string; - validation?: string; - san?: string[]; -}): Promise { - let certificateArn: AWS.ACM.RequestCertificateResponse | AWS.ACM.ImportCertificateResponse | undefined; - switch (props.type) { - case 'import': - const privKeyContent = await getS3FileContents(props.assetBucketName, props.s3Client, props.privKey); - const certContent = await getS3FileContents(props.assetBucketName, props.s3Client, props.cert); - const chainContents = await getS3FileContents(props.assetBucketName, props.s3Client, props.chain); - certificateArn = await createImportCertificate( - props.acmClient, - certContent, - chainContents, - privKeyContent, - props.certificateArn, - ); - break; - case 'request': - certificateArn = await createRequestCertificate(props.acmClient, props.domain, props.validation, props.san); - break; - default: - throw new Error( - `Invalid certificate type ${props.type} !!!. Valid certificate types can be either import or request !!!!`, - ); - } - await createCertificateSsmParameter(props.ssmClient, props.parameterName, certificateArn!); -} - -/** - * Function to get S3 file contents for certificate details - * @param s3BucketName string - * @param s3Client {@link S3Client} - * @param s3Key string - * @returns - */ -async function getS3FileContents( - s3BucketName: string, - s3Client: S3Client, - s3Key?: string, -): Promise { - if (s3Key) { - const response = await throttlingBackOff(() => - s3Client.send(new GetObjectCommand({ Bucket: s3BucketName, Key: s3Key })), - ); - const stream = response.Body as Readable; - return streamToString(stream); - } else { - return undefined; - } -} - -async function streamToString(stream: Readable): Promise { - return new Promise((resolve, reject) => { - const chunks: Uint8Array[] = []; - stream.on('data', chunk => chunks.push(chunk)); - stream.on('error', reject); - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); - }); -} - -/** - * Function to create Import/ReImport Certificate - * ReImport certificate if `certArn` is passed. - * @param acmClient {@link AWS.ACM} - * @param cert string - * @param chain string - * @param privKey string - * @returns certificate {@link AWS.ACM.ImportCertificateResponse} - */ -async function createImportCertificate( - acmClient: AWS.ACM, - cert?: string, - chain?: string, - privKey?: string, - certArn?: string, -): Promise { - if (!cert || !privKey) { - throw new Error('Missing certificate or private key in custom resource event properties'); - } - - return await throttlingBackOff(() => - acmClient - .importCertificate({ Certificate: cert, CertificateChain: chain, PrivateKey: privKey, CertificateArn: certArn }) - .promise(), - ); -} - -/** - * Function to create request certificate - * @param acmClient {@link AWS.ACM} - * @param domain string - * @param validation string - * @param san string - * @returns certificate {@link AWS.ACM.RequestCertificateResponse} - */ -async function createRequestCertificate( - acmClient: AWS.ACM, - domain?: string, - validation?: string, - san?: string[], -): Promise { - if (!domain) { - throw new Error('Missing domain in custom resource event properties'); - } - - return await throttlingBackOff(() => - acmClient - .requestCertificate({ DomainName: domain, ValidationMethod: validation, SubjectAlternativeNames: san }) - .promise(), - ); -} - -/** - * Function to create SSM parameter to store certificate arn value - * @param ssmClient {@link AWS.SSM} - * @param parameterName string - * @param certificateArnItem {@link AWS.ACM.RequestCertificateResponse} | {@link AWS.ACM.ImportCertificateResponse} - */ -async function createCertificateSsmParameter( - ssmClient: AWS.SSM, - parameterName: string, - certificateArnItem: AWS.ACM.RequestCertificateResponse | AWS.ACM.ImportCertificateResponse, -): Promise { - if (!certificateArnItem.CertificateArn) { - throw new Error( - `Missing certificateArn value in ACM client response, unable to create certificate SSM parameter ${parameterName} !!!`, - ); - } - await throttlingBackOff(() => - ssmClient - .putParameter({ - Name: parameterName, - Value: certificateArnItem.CertificateArn!, - Type: 'String', - Overwrite: true, - }) - .promise(), - ); -} - -async function getCertificateArnFromSsm(ssmClient: AWS.SSM, parameterName: string) { - let certificateArn: string | undefined; - try { - const response = await throttlingBackOff(() => ssmClient.getParameter({ Name: parameterName }).promise()); - certificateArn = response.Parameter?.Value; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.code === 'ParameterNotFound') { - console.log(`No Parameter "${parameterName}" found for Certificate ARN`); - } else { - throw e; - } - } - return certificateArn; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/package.json deleted file mode 100644 index 876b255..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-certificate-manager-create-certificate", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-certificate-manager/create-certificates/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type.ts deleted file mode 100644 index ad18392..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -const path = require('path'); - -/** - * Get the ResourceType from a CloudFormation Stack by supplying - * the logicalId - */ -export interface GetCloudFormationResourceTypeProps { - readonly stackName: string; - readonly logicalResourceId: string; - readonly partition: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class GetCloudFormationResourceType extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: GetCloudFormationResourceTypeProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::GetCloudFormationResourceType'; - - const cloudformationPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'cloudformation', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['cloudformation:DescribeStackResource'], - resources: [`arn:${props.partition}:cloudformation:*:${cdk.Stack.of(this).account}`], - }); - - const lambdaFunction = new cdk.aws_lambda.Function(this, 'GetCloudFormationResourceTypeFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'get-resource-type/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(3), - description: 'Get CloudFormation Resources from Stack by LogicalResourceId', - initialPolicy: [cloudformationPolicy], - }); - - const logGroup = new cdk.aws_logs.LogGroup(this, `${lambdaFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${lambdaFunction.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudwatchKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'GetCloudFormationResourceTypeProvider', { - onEventHandler: lambdaFunction, - }); - - const resource = new cdk.CustomResource(this, 'GetCloudFormationResourceTypeResource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - stackName: props.stackName, - logicalResourceId: props.logicalResourceId, - }, - }); - - NagSuppressions.addResourceSuppressions( - lambdaFunction, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'Lambda managed policy', - }, - { - id: 'AwsSolutions-IAM5', - reason: 'Lambda managed policy', - }, - ], - true, - ); - - NagSuppressions.addResourceSuppressions( - provider, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'CDK generated provider', - }, - { - id: 'AwsSolutions-IAM5', - reason: 'CDK generated provider', - }, - ], - true, - ); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/index.ts deleted file mode 100644 index 7b93c12..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/index.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * get-resource-type - lambda handler - * - * @param event - * @returns - */ - -import { - CloudFormationClient, - DescribeStackResourceCommand, - DescribeStackResourceCommandOutput, -} from '@aws-sdk/client-cloudformation'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const stackName = event.ResourceProperties['stackName']; - const logicalResourceId = event.ResourceProperties['logicalResourceId']; - const solutionId = process.env['SOLUTION_ID']; - let resourceDetails: DescribeStackResourceCommandOutput | undefined = undefined; - switch (event.RequestType) { - case 'Create': - case 'Update': - const cloudformationClient = new CloudFormationClient({ customUserAgent: solutionId }); - try { - resourceDetails = await throttlingBackOff(() => - cloudformationClient.send( - new DescribeStackResourceCommand({ StackName: stackName, LogicalResourceId: logicalResourceId }), - ), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e.name === 'ValidationError') { - return { - PhysicalResourceId: undefined, - Status: 'SUCCESS', - }; - } else { - console.log(`Error: ${JSON.stringify(e)}`); - throw new Error(`Error retrieving CloudFormation stack named ${stackName}`); - } - } - if (resourceDetails && resourceDetails.StackResourceDetail?.LogicalResourceId) { - return { - PhysicalResourceId: resourceDetails.StackResourceDetail?.ResourceType, - Status: 'SUCCESS', - }; - } - return { - PhysicalResourceId: undefined, - Status: 'SUCCESS', - }; - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/package.json deleted file mode 100644 index a78da91..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-cloudformation-get-resource-type", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-cloudformation": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudformation/get-resource-type/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-destination.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-destination.ts deleted file mode 100644 index 2be3382..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-destination.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -/** - * Construction properties for CloudWatch Destination for Kinesis Stream. - */ -export interface CloudWatchDestinationProps { - /** - * - * KMS key to encrypt the Kinesis Data Stream - */ - kinesisKmsKey: cdk.aws_kms.IKey; - /** - * - * Kinesis Data Stream for CloudWatch logs - */ - kinesisStream: cdk.aws_kinesis.IStream; - /** - * Organization ID to restrict the usage within specific org - */ - organizationId?: string; - /** - * Partition to determine the IAM condition. - */ - partition: string; - /** - * Account IDs for the IAM condition. - */ - accountIds?: string[]; - /** - * Accelerator Prefix defaults to 'AWSAccelerator'. - */ - acceleratorPrefix: string; - /** - * Use existing IAM roles for deployment. - */ - useExistingRoles: boolean; -} -/** - * Class to configure CloudWatch Destination on logs receiving account - */ -export class CloudWatchDestination extends Construct { - constructor(scope: Construct, id: string, props: CloudWatchDestinationProps) { - super(scope, id); - - let principalOrgIdCondition: object | undefined = undefined; - let accountPrincipals: object | cdk.aws_iam.IPrincipal; - - if (props.partition === 'aws-cn' || !props.organizationId) { - // Only principal block with list of account id is supported. - accountPrincipals = { - AWS: props.accountIds, - }; - } else { - principalOrgIdCondition = { - StringEquals: { - 'aws:PrincipalOrgID': props.organizationId, - }, - }; - accountPrincipals = new cdk.aws_iam.AnyPrincipal(); - } - - const logDestinationPolicy = JSON.stringify({ - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: accountPrincipals, - Action: 'logs:PutSubscriptionFilter', - Resource: `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:destination:${props.acceleratorPrefix}CloudWatchToS3`, - Condition: principalOrgIdCondition, - }, - ], - }); - - new cdk.aws_logs.CfnDestination(this, 'Resource', { - roleArn: this.createKinesisRole( - props.kinesisStream.streamArn, - props.kinesisKmsKey.keyArn, - props.useExistingRoles, - props.acceleratorPrefix, - ), - targetArn: props.kinesisStream.streamArn, - destinationName: `${props.acceleratorPrefix}CloudWatchToS3`, - destinationPolicy: logDestinationPolicy, - }); - } - - private createKinesisRole( - kinesisStreamArn: string, - kinesisKeyArn: string, - useExistingRoles: boolean, - acceleratorPrefix: string, - ) { - if (useExistingRoles) { - return `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${acceleratorPrefix}LogReplicationRole-${cdk.Stack.of(this).region}`; - } - //Create policy for access to Kinesis stream - const kinesisStreamAccess = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: ['kinesis:ListShards', 'kinesis:PutRecord', 'kinesis:PutRecords'], - resources: [kinesisStreamArn], - }), - ], - }); - const kmsKeyAccess = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:ReEncryptTo', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyPair', - 'kms:ReEncryptFrom', - ], - resources: [kinesisKeyArn], - }), - ], - }); - // Create a role for CloudWatch Logs destination - const logsKinesisRole = new cdk.aws_iam.Role(this, 'LogsKinesisRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - // this needs to be inline to ensure role is created with proper access - // if not, CloudWatch destination creation fails with no permission to access Kinesis or KMS (generateDataKey access error) - inlinePolicies: { - KinesisAccess: kinesisStreamAccess, - KmsAccess: kmsKeyAccess, - }, - }); - return logsKinesisRole.roleArn; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-data-protection.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-data-protection.ts deleted file mode 100644 index 1d5b2ce..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-data-protection.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { LzaCustomResource } from '../lza-custom-resource'; -import * as path from 'path'; - -/** - * Construction properties for CloudWatch CloudWatchLogDataProtection. - */ -export interface CloudWatchLogDataProtectionProps { - /** - * - * Central logs bucket name - */ - centralLogBucketName: string; - /** - * - * Data protection identifier names - */ - identifierNames: string[]; - /** - * Indicates whether existing CloudWatch Log data protection policy configuration can be overwritten. - */ - overrideExisting: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; -} -/** - * Class to configure CloudWatch log data protection - */ -export class CloudWatchLogDataProtection extends Construct { - constructor(scope: Construct, id: string, props: CloudWatchLogDataProtectionProps) { - super(scope, id); - - const resourceName = 'CloudWatchDataProtection'; - - new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - properties: [ - { - centralLogBucketName: props.centralLogBucketName, - identifierNames: props.identifierNames, - partition: cdk.Stack.of(this).partition, - overrideExisting: props.overrideExisting, - }, - ], - }, - lambda: { - assetPath: path.join(__dirname, 'put-account-policy/dist'), - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(15), - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudWatchAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'logs:DeleteAccountPolicy', - 'logs:DescribeAccountPolicies', - 'logs:PutAccountPolicy', - 'logs:PutDataProtectionPolicy', - 'logs:DeleteDataProtectionPolicy', - 'logs:CreateLogDelivery', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S3Access', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetBucketPolicy', 's3:PutBucketPolicy'], - resources: [`arn:${cdk.Stack.of(this).partition}:s3:::${props.centralLogBucketName}`], - }), - ], - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-group.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-group.ts deleted file mode 100644 index 051ecf1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-log-group.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import * as path from 'path'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; - -/** - * Construction properties for CloudWatch LogGroups. - */ - -export type LogGroupRetention = - | 1 - | 3 - | 5 - | 7 - | 14 - | 30 - | 60 - | 90 - | 120 - | 150 - | 180 - | 365 - | 400 - | 545 - | 731 - | 1096 - | 1827 - | 2192 - | 2557 - | 2922 - | 3288 - | 3653; - -export interface CloudWatchLogGroupsProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customLambdaLogKmsKey?: cdk.aws_kms.IKey; - /** - * How long, in days, the log contents for the Lambda function - * will be retained. - */ - readonly customLambdaLogRetention: number; - /** - * How long, in days, the log contents will be retained. - * - * To retain all logs, set this value to undefined. - * - */ - readonly logRetentionInDays: LogGroupRetention | number; - /** - * KMS Key Arn to encrypt CloudWatch Logs Group at rest. - */ - readonly keyArn?: string; - /** - * - * Name of the CloudWatch Logs Group - */ - readonly logGroupName?: string; - /** - * For cross-account log groups, the owning account ID - */ - readonly owningAccountId?: string; - /** - * For cross-region log groups, the owning region - */ - readonly owningRegion?: string; - /** - * For cross-account log groups, the IAM role name to assume - */ - readonly roleName?: string; - /** - * - * Determine termination policy on CloudWatch Logs Group - */ - readonly terminationProtected?: boolean; -} - -interface ILogGroup { - /** - * The name of the log group - */ - readonly logGroupName: string; - /** - * The ARN of the log group - */ - readonly logGroupArn: string; -} - -/** - * Class to configure CloudWatch Log Groups - */ -export class CloudWatchLogGroups extends cdk.Resource implements ILogGroup { - public readonly logGroupName: string; - public readonly logGroupArn: string; - - constructor(scope: Construct, id: string, props: CloudWatchLogGroupsProps) { - super(scope, id); - const CLOUD_WATCH_LOG_GROUPS = 'Custom::CreateLogGroup'; - this.logGroupName = props.logGroupName ?? cdk.Names.uniqueResourceName(this, { separator: '-' }); - - // - // Function definition for the custom resource - // - const policyStatements = props.owningAccountId - ? [ - { - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: `arn:${this.stack.partition}:iam::*:role/${props.roleName}`, - }, - ] - : [ - { - Effect: 'Allow', - Action: ['logs:CreateLogGroup', 'logs:DeleteLogGroup', 'logs:PutRetentionPolicy'], - Resource: `arn:${this.stack.partition}:logs:${props.owningRegion ?? this.stack.region}:${ - this.stack.account - }:log-group:${this.logGroupName}:*`, - }, - { - Effect: 'Allow', - Action: ['kms:DescribeKey', 'kms:ListKeys', 'logs:AssociateKmsKey', 'logs:DescribeLogGroups'], - Resource: '*', - }, - ]; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider( - this, - pascalCase(`${this.logGroupName}-${CLOUD_WATCH_LOG_GROUPS}`), - { - codeDirectory: path.join(__dirname, 'create-log-groups/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements, - }, - ); - - const resource = new cdk.CustomResource(this, 'CloudWatchLogsResource', { - resourceType: CLOUD_WATCH_LOG_GROUPS, - serviceToken: provider.serviceToken, - properties: { - logGroupName: this.logGroupName, - retention: props.logRetentionInDays, - keyArn: props.keyArn, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - terminationProtected: props.terminationProtected, - }, - }); - this.logGroupArn = resource.getAttString('LogGroupArn'); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.customLambdaLogRetention, - encryptionKey: props.customLambdaLogKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.ts deleted file mode 100644 index 02765d4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export type cloudwatchExclusionItem = { - account: string; - region: string; - excludeAll?: boolean; - logGroupNames?: string[]; -}; - -/** - * Construction properties for CloudWatch Logs Creating account. - */ - -export interface CloudWatchLogsCreateProps { - /** - * - * Log Destination Arn to which all the logs will get forwarded to - */ - readonly logDestinationArn: string; - /** - * - * KMS key to encrypt the Lambda environment variables, when undefined default AWS managed key will be used - */ - readonly logsKmsKey?: cdk.aws_kms.IKey; - /** - * - * CloudWatch Retention in days from global config - */ - readonly logsRetentionInDays: string; - /** - * - * Subscription Filter Arn - */ - readonly subscriptionFilterRoleArn: string; - /** - * - * LogArchive account Id - */ - readonly logArchiveAccountId: string; - /** - * CloudWatch Logs exclusion options - */ - readonly logExclusionOption?: cloudwatchExclusionItem; - /** - * Existing customer defined log subscription destination arn, which accelerator needs to remove before configuring solution defined subscription destination. - * - */ - readonly replaceLogDestinationArn?: string; - /** - * Accelerator Prefix defaults to 'AWSAccelerator'. - */ - readonly acceleratorPrefix: string; - /** - * Use existing IAM roles for deployment. - */ - readonly useExistingRoles: boolean; -} - -/** - * Class to configure CloudWatch Destination on logs receiving account - */ -export class CloudWatchLogsSubscriptionFilter extends Construct { - readonly id: string; - constructor(scope: Construct, id: string, props: CloudWatchLogsCreateProps) { - super(scope, id); - let acceleratorLogSubscriptionRoleArn: string; - if (props.useExistingRoles) { - acceleratorLogSubscriptionRoleArn = `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${props.acceleratorPrefix}LogReplicationRole-${cdk.Stack.of(this).region}`; - } else { - acceleratorLogSubscriptionRoleArn = props.subscriptionFilterRoleArn; - } - - const UPDATE_SUBSCRIPTION_FILTER = 'Custom::UpdateSubscriptionFilter'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, UPDATE_SUBSCRIPTION_FILTER, { - codeDirectory: path.join(__dirname, 'update-subscription-filter/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'logs:PutRetentionPolicy', - 'logs:AssociateKmsKey', - 'logs:DescribeLogGroups', - 'logs:DescribeSubscriptionFilters', - ], - Resource: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:*`, - ], - }, - - { - Effect: 'Allow', - Action: ['logs:PutSubscriptionFilter', 'logs:DeleteSubscriptionFilter'], - Resource: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:*`, - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - props.logArchiveAccountId - }:destination:*`, - ], - }, - ], - }); - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: UPDATE_SUBSCRIPTION_FILTER, - serviceToken: provider.serviceToken, - properties: { - acceleratorLogRetentionInDays: props.logsRetentionInDays, - acceleratorCreatedLogDestinationArn: props.logDestinationArn, - acceleratorLogSubscriptionRoleArn, - acceleratorLogKmsKeyArn: props.logsKmsKey ? props.logsKmsKey.keyArn : undefined, - logExclusionOption: props.logExclusionOption - ? JSON.stringify(props.logExclusionOption) - : props.logExclusionOption, - replaceLogDestinationArn: props.replaceLogDestinationArn, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: parseInt(props.logsRetentionInDays), - encryptionKey: props.logsKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts deleted file mode 100644 index bed6de8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/index.ts +++ /dev/null @@ -1,285 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { - AssociateKmsKeyCommand, - CloudWatchLogsClient, - CreateLogGroupCommand, - DeleteLogGroupCommand, - DescribeLogGroupsCommand, - PutRetentionPolicyCommand, -} from '@aws-sdk/client-cloudwatch-logs'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * create-log-groups - lambda handler - * - * @param event - * @returns - */ -export async function handler( - event: CloudFormationCustomResourceEvent, -): Promise< - | { PhysicalResourceId: string; Data: { LogGroupArn: string }; Status: string } - | { PhysicalResourceId: string; Status: string } - | undefined -> { - const logGroupName: string = event.ResourceProperties['logGroupName']; - const retention = Number(event.ResourceProperties['retention']); - const terminationProtected: boolean = event.ResourceProperties['terminationProtected'] === 'true'; - const encryptionKey: string | undefined = event.ResourceProperties['keyArn']; - const owningAccountId: string | undefined = event.ResourceProperties['owningAccountId']; - const owningRegion: string | undefined = event.ResourceProperties['owningRegion']; - const roleName: string | undefined = event.ResourceProperties['roleName']; - const invokingAccountId = event.ServiceToken.split(':')[4]; - const invokingRegion = event.ServiceToken.split(':')[3]; - const partition = event.ServiceToken.split(':')[1]; - const solutionId = process.env['SOLUTION_ID']; - // - // Set CloudWatch Logs client - const logClient = await setLogsClient({ - invokingAccountId, - invokingRegion, - partition, - owningAccountId, - owningRegion, - roleName, - solutionId, - }); - // - // Retrieve existing CloudWatch Logs Group - const existingLogGroup = await logGroupExists(logClient, logGroupName); - // - // Set log group ARN - const logGroupArn = setLogGroupArn({ - invokingAccountId, - invokingRegion, - partition, - logGroupName, - owningAccountId, - owningRegion, - }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('Creating or updating log groups'); - // - // Code Block for CloudWatch Log Group that exists - if (existingLogGroup) { - console.warn(`Log Group already exists : ${logGroupName}`); - if (encryptionKey) { - await associateKey(logClient, encryptionKey, logGroupName); - } - } - // - // Code Block for CloudWatch Log Group if it doesn't exist - else { - await createLogGroup(logClient, logGroupName, encryptionKey); - } - // - // Put log group retention policy - await putPolicy(logClient, logGroupName, retention); - - return { - PhysicalResourceId: logGroupName, - Data: { LogGroupArn: logGroupArn }, - Status: 'SUCCESS', - }; - - case 'Delete': - if (!terminationProtected && existingLogGroup) { - await deleteLogGroup(logClient, logGroupName); - } else { - console.log(`The Log Group ${logGroupName} is set to retain or does not exist.`); - } - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Returns a local or cross-account/cross-region CloudWatch Logs client based on input parameters - * @param options - * @returns Promise - */ -async function setLogsClient(options: { - invokingAccountId: string; - invokingRegion: string; - partition: string; - owningAccountId?: string; - owningRegion?: string; - roleName?: string; - solutionId?: string; -}): Promise { - const roleArn = `arn:${options.partition}:iam::${options.owningAccountId}:role/${options.roleName}`; - const stsClient = new STSClient({ - region: options.invokingRegion, - customUserAgent: options.solutionId, - retryStrategy: setRetryStrategy(), - }); - - if (options.owningAccountId && options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account log group required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return logs client - return new CloudWatchLogsClient({ - region: options.owningRegion, - customUserAgent: options.solutionId, - retryStrategy: setRetryStrategy(), - credentials, - }); - } else if (options.owningAccountId && !options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account log group required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return logs client - return new CloudWatchLogsClient({ - region: options.invokingRegion, - customUserAgent: options.solutionId, - retryStrategy: setRetryStrategy(), - credentials, - }); - } else { - return new CloudWatchLogsClient({ - region: options.owningRegion ?? options.invokingRegion, - customUserAgent: options.solutionId, - retryStrategy: setRetryStrategy(), - }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; -} - -/** - * Set the log group ARN - * @param options - * @returns string - */ -function setLogGroupArn(options: { - invokingAccountId: string; - invokingRegion: string; - partition: string; - logGroupName: string; - owningAccountId?: string; - owningRegion?: string; -}): string { - return `arn:${options.partition}:logs:${options.owningRegion ?? options.invokingRegion}:${ - options.owningAccountId ?? options.invokingAccountId - }:log-group:${options.logGroupName}`; -} - -/** - * Determine if there is an existing log group with the given name - * @param logClient CloudWatchLogsClient - * @param logGroupName string - * @returns Promise - */ -async function logGroupExists(logClient: CloudWatchLogsClient, logGroupName: string): Promise { - console.log(`Describing existing log groups...`); - - const response = await throttlingBackOff(() => - logClient.send(new DescribeLogGroupsCommand({ logGroupNamePrefix: logGroupName })), - ); - return response.logGroups?.find(lg => lg.logGroupName === logGroupName) ? true : false; -} - -/** - * Associate KMS key with log group - * @param logClient CloudWatchLogsClient - * @param kmsKeyId string - * @param logGroupName string - */ -async function associateKey(logClient: CloudWatchLogsClient, kmsKeyId: string, logGroupName: string) { - console.log(`Associating KMS key ${kmsKeyId} with log group ${logGroupName}...`); - await throttlingBackOff(() => logClient.send(new AssociateKmsKeyCommand({ kmsKeyId, logGroupName }))); -} - -/** - * Create a CloudWatch Log group - * @param logClient CloudWatchLogsClient - * @param logGroupName string - * @param kmsKeyId string | undefined - */ -async function createLogGroup(logClient: CloudWatchLogsClient, logGroupName: string, kmsKeyId?: string) { - console.log(`Creating log group ${logGroupName}...`); - await throttlingBackOff(() => logClient.send(new CreateLogGroupCommand({ kmsKeyId, logGroupName }))); -} - -/** - * Put retention policy to log group - * @param logClient - * @param logGroupName - * @param retention - */ -async function putPolicy(logClient: CloudWatchLogsClient, logGroupName: string, retentionInDays: number) { - console.log(`Modifying log group ${logGroupName} retention and expiration policy`); - await throttlingBackOff(() => logClient.send(new PutRetentionPolicyCommand({ logGroupName, retentionInDays }))); -} - -/** - * Delete a log group - * @param logClient CloudWatchLogsClient - * @param logGroupName string - */ -async function deleteLogGroup(logClient: CloudWatchLogsClient, logGroupName: string) { - console.log(`The Log Group ${logGroupName} is not set to retain. Deleting log group.`); - await throttlingBackOff(() => logClient.send(new DeleteLogGroupCommand({ logGroupName }))); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/package.json deleted file mode 100644 index dbb4371..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-cloudwatch-logs-create-log-groups", - "version": "0.0.0", - "private": true, - "description": "custom resource to create and manage cloudwatch log groups", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-cloudwatch-logs": "3.410.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "^10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/index.test.ts deleted file mode 100644 index 7124c8c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/index.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { - AssociateKmsKeyCommand, - CloudWatchLogsClient, - CreateLogGroupCommand, - DescribeLogGroupsCommand, - DeleteLogGroupCommand, - PutRetentionPolicyCommand, -} from '@aws-sdk/client-cloudwatch-logs'; - -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; - -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -import { CloudFormationCustomResourceDeleteEvent } from '@aws-accelerator/utils/lib/common-types'; - -const client = AcceleratorMockClient(CloudWatchLogsClient); - -const stsClient = AcceleratorMockClient(STSClient); - -describe('Create Event', () => { - beforeEach(() => { - client.reset(); - }); - test('Log group with encryption created successfully.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - client.on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.newProps.logGroupName }).resolves({ - logGroups: undefined, - }); - - client - .on(CreateLogGroupCommand, { - kmsKeyId: StaticInput.newProps.keyArn, - logGroupName: StaticInput.newProps.logGroupName, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - client - .on(PutRetentionPolicyCommand, { - logGroupName: StaticInput.newProps.logGroupName, - retentionInDays: Number(StaticInput.newProps.retention), - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.createUpdateResponse); - }); - - test('Cross Account -> Log group with encryption created successfully.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.crossAccountNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: StaticInput.assumeRoleCredential.AccessKeyId, - SecretAccessKey: StaticInput.assumeRoleCredential.SecretAccessKey, - SessionToken: StaticInput.assumeRoleCredential.SecretAccessKey, - Expiration: undefined, - }, - }); - - client - .on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.crossAccountNewProps.logGroupName }) - .resolves({ - logGroups: undefined, - }); - - client - .on(CreateLogGroupCommand, { - kmsKeyId: StaticInput.crossAccountNewProps.keyArn, - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - client - .on(PutRetentionPolicyCommand, { - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - retentionInDays: Number(StaticInput.crossAccountNewProps.retention), - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.crossAccountCreateUpdateResponse); - }); - - test('Cross Account -> Missing owner region, Log group with encryption created successfully.', async () => { - const localNewProps = { - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - retention: StaticInput.crossAccountNewProps.retention, - terminationProtected: StaticInput.crossAccountNewProps.terminationProtected, - keyArn: StaticInput.crossAccountNewProps.keyArn, - owningAccountId: StaticInput.crossAccountNewProps.owningAccountId, - owningRegion: undefined, - roleName: StaticInput.crossAccountNewProps.roleName, - }; - - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [localNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: StaticInput.assumeRoleCredential.AccessKeyId, - SecretAccessKey: StaticInput.assumeRoleCredential.SecretAccessKey, - SessionToken: StaticInput.assumeRoleCredential.SecretAccessKey, - Expiration: undefined, - }, - }); - - client.on(DescribeLogGroupsCommand, { logGroupNamePrefix: localNewProps.logGroupName }).resolves({ - logGroups: undefined, - }); - - client - .on(CreateLogGroupCommand, { - kmsKeyId: localNewProps.keyArn, - logGroupName: localNewProps.logGroupName, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - client - .on(PutRetentionPolicyCommand, { - logGroupName: localNewProps.logGroupName, - retentionInDays: Number(localNewProps.retention), - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.crossAccountCreateUpdateResponse); - }); - - test('Cross Account -> Failure role name not provided.', async () => { - const localNewProps = { - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - retention: StaticInput.crossAccountNewProps.retention, - terminationProtected: StaticInput.crossAccountNewProps.terminationProtected, - keyArn: StaticInput.crossAccountNewProps.keyArn, - owningAccountId: StaticInput.crossAccountNewProps.owningAccountId, - owningRegion: StaticInput.crossAccountNewProps.owningRegion, - }; - - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [localNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: StaticInput.assumeRoleCredential.AccessKeyId, - SecretAccessKey: StaticInput.assumeRoleCredential.SecretAccessKey, - SessionToken: StaticInput.assumeRoleCredential.SecretAccessKey, - Expiration: undefined, - }, - }); - - await expect(handler(event)).rejects.toThrow(StaticInput.crossAccountMissingOptionError); - }); - - test('Cross Account -> Failure AssumeRole missing AccessKey.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.crossAccountNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: undefined, - SecretAccessKey: StaticInput.assumeRoleCredential.SecretAccessKey, - SessionToken: StaticInput.assumeRoleCredential.SecretAccessKey, - Expiration: undefined, - }, - }); - - await expect(handler(event)).rejects.toThrow(StaticInput.missingAccessKeyError); - }); - - test('Cross Account -> Failure AssumeRole missing SecretAccessKey.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.crossAccountNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: StaticInput.assumeRoleCredential.AccessKeyId, - SecretAccessKey: undefined, - SessionToken: StaticInput.assumeRoleCredential.SecretAccessKey, - Expiration: undefined, - }, - }); - - await expect(handler(event)).rejects.toThrow(StaticInput.missingSecretAccessKeyError); - }); - - test('Cross Account -> Failure AssumeRole missing SessionToken.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.crossAccountNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: StaticInput.assumeRoleCredential.AccessKeyId, - SecretAccessKey: StaticInput.assumeRoleCredential.SecretAccessKey, - SessionToken: undefined, - Expiration: undefined, - }, - }); - - await expect(handler(event)).rejects.toThrow(StaticInput.missingSessionTokenError); - }); - - test('Cross Account -> Failure owning region and role name not provided.', async () => { - const localNewProps = { - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - retention: StaticInput.crossAccountNewProps.retention, - terminationProtected: StaticInput.crossAccountNewProps.terminationProtected, - keyArn: StaticInput.crossAccountNewProps.keyArn, - owningAccountId: StaticInput.crossAccountNewProps.owningAccountId, - }; - - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [localNewProps] }); - - stsClient - .on(AssumeRoleCommand, { RoleArn: StaticInput.assumeRoleArn, RoleSessionName: 'AcceleratorAssumeRole' }) - .resolves({ - Credentials: { - AccessKeyId: StaticInput.assumeRoleCredential.AccessKeyId, - SecretAccessKey: StaticInput.assumeRoleCredential.SecretAccessKey, - SessionToken: StaticInput.assumeRoleCredential.SecretAccessKey, - Expiration: undefined, - }, - }); - - await expect(handler(event)).rejects.toThrow(StaticInput.crossAccountMissingOptionError); - }); -}); - -describe('Update Event', () => { - beforeEach(() => { - client.reset(); - }); - - test('Add encryption.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.newProps], - old: [StaticInput.oldProps], - }); - client.on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.newProps.logGroupName }).resolves({ - logGroups: [{ logGroupName: StaticInput.newProps.logGroupName, kmsKeyId: undefined }], - }); - - client - .on(AssociateKmsKeyCommand, { - logGroupName: StaticInput.oldProps.logGroupName, - kmsKeyId: StaticInput.newProps.keyArn, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.createUpdateResponse); - }); - - test('Replace encryption.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.newProps], - old: [StaticInput.oldProps], - }); - client.on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.newProps.logGroupName }).resolves({ - logGroups: [ - { - logGroupName: StaticInput.oldProps.logGroupName, - kmsKeyId: StaticInput.oldProps.keyArn, - }, - ], - }); - - client - .on(AssociateKmsKeyCommand, { - logGroupName: StaticInput.newProps.logGroupName, - kmsKeyId: StaticInput.newProps.keyArn, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.createUpdateResponse); - }); - - test('Cross Account -> Add encryption.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.crossAccountNewProps], - old: [StaticInput.crossAccountOldProps], - }); - client - .on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.crossAccountNewProps.logGroupName }) - .resolves({ - logGroups: [{ logGroupName: StaticInput.crossAccountNewProps.logGroupName, kmsKeyId: undefined }], - }); - - client - .on(AssociateKmsKeyCommand, { - logGroupName: StaticInput.crossAccountOldProps.logGroupName, - kmsKeyId: undefined, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.crossAccountCreateUpdateResponse); - }); - - test('Cross Account -> Replace encryption', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.crossAccountNewProps], - old: [StaticInput.crossAccountOldProps], - }); - client - .on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.crossAccountNewProps.logGroupName }) - .resolves({ - logGroups: [ - { - logGroupName: StaticInput.crossAccountOldProps.logGroupName, - kmsKeyId: StaticInput.crossAccountOldProps.keyArn, - }, - ], - }); - - client - .on(AssociateKmsKeyCommand, { - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - kmsKeyId: StaticInput.crossAccountNewProps.keyArn, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.crossAccountCreateUpdateResponse); - }); -}); - -describe('Delete Event', () => { - beforeEach(() => { - client.reset(); - }); - - test('Termination protected no deletion.', async () => { - let event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [StaticInput.newProps], - }); - - // Typecast to Delete Event to access PhysicalResourceId property - event = event as CloudFormationCustomResourceDeleteEvent; - - client.on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.newProps.logGroupName }).resolves({ - logGroups: [ - { - logGroupName: StaticInput.newProps.logGroupName, - kmsKeyId: StaticInput.newProps.keyArn, - }, - ], - }); - - const response = await handler(event); - expect(response).toStrictEqual({ - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }); - }); - - test('Log group deleted successfully.', async () => { - const localNewProps = { - logGroupName: StaticInput.newProps.logGroupName, - retention: StaticInput.newProps.retention, - terminationProtected: 'false', - keyArn: StaticInput.newProps.keyArn, - }; - - let event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [localNewProps], - }); - - // Typecast to Delete Event to access PhysicalResourceId property - event = event as CloudFormationCustomResourceDeleteEvent; - - client.on(DescribeLogGroupsCommand, { logGroupNamePrefix: localNewProps.logGroupName }).resolves({ - logGroups: [ - { - logGroupName: localNewProps.logGroupName, - kmsKeyId: localNewProps.keyArn, - }, - ], - }); - - client - .on(DeleteLogGroupCommand, { - logGroupName: localNewProps.logGroupName, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual({ - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }); - }); - - test('Cross Account -> Termination protected no deletion.', async () => { - let event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [StaticInput.crossAccountNewProps], - }); - - // Typecast to Delete Event to access PhysicalResourceId property - event = event as CloudFormationCustomResourceDeleteEvent; - - client - .on(DescribeLogGroupsCommand, { logGroupNamePrefix: StaticInput.crossAccountNewProps.logGroupName }) - .resolves({ - logGroups: [ - { - logGroupName: StaticInput.crossAccountNewProps.logGroupName, - kmsKeyId: StaticInput.crossAccountNewProps.keyArn, - }, - ], - }); - - const response = await handler(event); - expect(response).toStrictEqual({ - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/static-input.ts deleted file mode 100644 index 856042c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/create-log-groups/test/static-input.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - AccountId, - MakeKeyArn, - MakeLogGroupArn, - MakeRoleArn, - Partition, - Region, -} from '../../../../test/unit-test/common/resources'; - -/** - * CloudWatch Log group property used for Create CloudWatch log group custom resource. - */ -type CloudWatchLogGroupPropertyType = { - logGroupName: string; - retention: string; - terminationProtected: string; - keyArn: string; - owningAccountId?: string; - owningRegion?: string; - roleName?: string; -}; -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly owningAccountId = '222222222222'; - public static readonly owningRegion = 'us-east-1'; - public static readonly roleName = 'MockRoleName'; - public static readonly mockLogGroupName = '/mock/log-group-1'; - public static readonly assumeRoleArn = MakeRoleArn(StaticInput.roleName, Partition, StaticInput.owningAccountId); - - public static readonly newProps: CloudWatchLogGroupPropertyType = { - logGroupName: StaticInput.mockLogGroupName, - retention: '180', - terminationProtected: 'true', - keyArn: MakeKeyArn(StaticInput.mockLogGroupName, Partition, AccountId, Region), - }; - - public static readonly oldProps: CloudWatchLogGroupPropertyType = { - logGroupName: StaticInput.mockLogGroupName, - retention: '60', - terminationProtected: 'true', - keyArn: MakeKeyArn(StaticInput.mockLogGroupName, Partition, AccountId, Region), - }; - - public static readonly crossAccountNewProps = { - logGroupName: StaticInput.mockLogGroupName, - retention: '180', - terminationProtected: 'true', - keyArn: MakeKeyArn(StaticInput.mockLogGroupName, Partition, StaticInput.owningAccountId, StaticInput.owningRegion), - owningAccountId: StaticInput.owningAccountId, - owningRegion: StaticInput.owningRegion, - roleName: StaticInput.roleName, - }; - - public static readonly crossAccountOldProps = { - logGroupName: StaticInput.mockLogGroupName, - retention: '60', - terminationProtected: 'true', - keyArn: MakeKeyArn( - `existing-${StaticInput.mockLogGroupName}`, - Partition, - StaticInput.owningAccountId, - StaticInput.owningRegion, - ), - owningAccountId: StaticInput.owningAccountId, - owningRegion: StaticInput.owningRegion, - roleName: StaticInput.roleName, - }; - - public static readonly createUpdateResponse = { - Data: { - LogGroupArn: MakeLogGroupArn(StaticInput.newProps.logGroupName, Partition, AccountId, Region), - }, - PhysicalResourceId: StaticInput.newProps.logGroupName, - Status: 'SUCCESS', - }; - - public static readonly crossAccountCreateUpdateResponse = { - Data: { - LogGroupArn: MakeLogGroupArn( - StaticInput.newProps.logGroupName, - Partition, - this.owningAccountId, - this.owningRegion, - ), - }, - PhysicalResourceId: StaticInput.newProps.logGroupName, - Status: 'SUCCESS', - }; - - public static readonly crossAccountMissingOptionError = new Error( - `Cross-account log group required but roleName parameter is undefined`, - ); - - public static readonly missingAccessKeyError = new Error(`Access key ID not returned from AssumeRole command`); - - public static readonly missingSecretAccessKeyError = new Error( - `Secret access key not returned from AssumeRole command`, - ); - - public static readonly missingSessionTokenError = new Error(`Session token not returned from AssumeRole command`); - - public static readonly assumeRoleCredential = { - AccessKeyId: 'MockAccessKeyId', - SecretAccessKey: 'MockSecretAccessKey', - SessionToken: 'MockSessionToken', - Expiration: undefined, - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/index.ts deleted file mode 100644 index d7c94ae..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/index.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { - AccountPolicy, - CloudWatchLogsClient, - DeleteAccountPolicyCommand, - DescribeAccountPoliciesCommand, - PolicyType, - PutAccountPolicyCommand, - Scope, -} from '@aws-sdk/client-cloudwatch-logs'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * create-log-groups - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const centralLogBucketName: string = event.ResourceProperties['centralLogBucketName']; - const identifierNames: string[] = event.ResourceProperties['identifierNames']; - const overrideExisting = event.ResourceProperties['overrideExisting'] === 'true' ? true : false; - const partition = event.ResourceProperties['partition']; - - const solutionId = process.env['SOLUTION_ID']; - - const client = new CloudWatchLogsClient({ - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - - const existingPolicies = await getExistingPolicies(client); - - let isPolicyExists = false; - - if (existingPolicies.length === 1) { - isPolicyExists = true; - } - - let policyName = 'ACCELERATOR_ACCOUNT_DATA_PROTECTION_POLICY'; - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (isPolicyExists && !overrideExisting) { - console.warn( - `Existing policy ${existingPolicies[0] - .policyName!} found, and override existing flag is set to false, skip update of policy.`, - ); - return { - Status: 'SUCCESS', - }; - } - - if (isPolicyExists) { - policyName = existingPolicies[0].policyName!; - console.log( - `Existing policy ${existingPolicies[0] - .policyName!} found, and override existing flag is set to true, policy will be updated.`, - ); - } else { - console.log(`No existing policy found, policy ${policyName} will be created.`); - } - - const dataIdentifiers: string[] = identifierNames.map( - item => `arn:${partition}:dataprotection::${partition}:data-identifier/${item}`, - ); - - const policyDocument = { - Name: policyName, - Description: 'Accelerator deployed CloudWatch log data protection policy', - Version: '2021-06-01', - Statement: [ - { - Sid: 'audit-policy', - DataIdentifier: dataIdentifiers, - Operation: { - Audit: { - FindingsDestination: { - S3: { - Bucket: centralLogBucketName, - }, - }, - }, - }, - }, - { - Sid: 'redact-policy', - DataIdentifier: dataIdentifiers, - Operation: { - Deidentify: { - MaskConfig: {}, - }, - }, - }, - ], - }; - - await throttlingBackOff(() => - client.send( - new PutAccountPolicyCommand({ - policyName: policyName, - policyDocument: JSON.stringify(policyDocument), - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - }), - ), - ); - - return { - Status: 'SUCCESS', - }; - - case 'Delete': - if (isPolicyExists) { - // Delete policy deployed by the solution only - if (existingPolicies[0].policyName! === policyName) { - console.log( - `Existing policy ${existingPolicies[0] - .policyName!} found, which is similar to solution deployed policy, policy will be deleted.`, - ); - await throttlingBackOff(() => - client.send( - new DeleteAccountPolicyCommand({ - policyName: policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - }), - ), - ); - } - } - - return { - Status: 'SUCCESS', - }; - } - - /** - * Function to get existing account policy configuration - * @param client {@link CloudWatchLogsClient} - * @returns policyConfiguration {@link AccountPolicy}[] - */ - async function getExistingPolicies(client: CloudWatchLogsClient): Promise { - const response = await throttlingBackOff(() => - client.send( - new DescribeAccountPoliciesCommand({ - policyType: PolicyType.DATA_PROTECTION_POLICY, - }), - ), - ); - - if (!response.accountPolicies) { - throw new Error(`Undefined accountPolicies property received from DescribeAccountPolicies API.`); - } - - return response.accountPolicies; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/jest.config.js deleted file mode 100644 index 0bbae5d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 100, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/package.json deleted file mode 100644 index 629c4b6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-cloudwatch-logs-put-account-policy", - "version": "0.0.0", - "private": true, - "description": "custom resource to create and manage cloudwatch log groups", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 --external:aws-sdk index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-cloudwatch-logs": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "^10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/index.test.ts deleted file mode 100644 index 8efcc62..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/index.test.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CloudWatchLogsClient, - PutAccountPolicyCommand, - DescribeAccountPoliciesCommand, - PolicyType, - Scope, - DeleteAccountPolicyCommand, -} from '@aws-sdk/client-cloudwatch-logs'; - -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const client = AcceleratorMockClient(CloudWatchLogsClient); - -describe('No existing Policy found.', () => { - beforeEach(() => { - client.reset(); - }); - test('Create/Update Success - No Override Existing policy .', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { - new: [StaticInput.createEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [], - }); - - client - .on(PutAccountPolicyCommand, { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - }) - .resolves({ accountPolicy: StaticInput.createOperationOutput }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); - - test('Delete Success - No Override Existing policy.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [StaticInput.createEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [], - }); - - client - .on(DeleteAccountPolicyCommand, { - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); - - test('Create/Update Success - Override Existing policy.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { - new: [StaticInput.overrideExistingCreateEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [], - }); - - client - .on(PutAccountPolicyCommand, { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - }) - .resolves({ accountPolicy: StaticInput.createOperationOutput }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); - - test('Delete Success - Override Existing policy.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [StaticInput.overrideExistingCreateEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [], - }); - - client - .on(DeleteAccountPolicyCommand, { - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); -}); - -describe('Existing Policy found.', () => { - beforeEach(() => { - client.reset(); - }); - - test('Create/Update Success - No Override Existing policy', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { - new: [StaticInput.createEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [ - { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - accountId: StaticInput.accountId, - }, - ], - }); - - client - .on(PutAccountPolicyCommand, { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - }) - .resolves({ accountPolicy: StaticInput.createOperationOutput }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); - - test('Delete Success - No Override Existing policy.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [StaticInput.createEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [ - { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - accountId: StaticInput.accountId, - }, - ], - }); - - client - .on(DeleteAccountPolicyCommand, { - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); - - test('Create/Update Success - Override Existing policy', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.overrideExistingUpdateEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [ - { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - accountId: StaticInput.accountId, - }, - ], - }); - - client - .on(PutAccountPolicyCommand, { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - }) - .resolves({ accountPolicy: StaticInput.createOperationOutput }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); - - test('Delete Success - Override Existing policy.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { - new: [StaticInput.overrideExistingCreateEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: [ - { - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - accountId: StaticInput.accountId, - }, - ], - }); - - client - .on(DeleteAccountPolicyCommand, { - policyName: StaticInput.policyName, - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - const response = await handler(event); - expect(response).toStrictEqual(StaticInput.operationOutput); - }); -}); - -describe('Exception.', () => { - beforeEach(() => { - client.reset(); - }); - - test('DescribeAccountPolicies accountPolicies undefined', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { - new: [StaticInput.createEventProps], - }); - - client - .on(DescribeAccountPoliciesCommand, { - policyType: PolicyType.DATA_PROTECTION_POLICY, - }) - .resolves({ - accountPolicies: undefined, - }); - - await expect(handler(event)).rejects.toThrow(StaticInput.missingAccountPolicies); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/static-input.ts deleted file mode 100644 index 0608908..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/put-account-policy/test/static-input.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { PolicyType, Scope } from '@aws-sdk/client-cloudwatch-logs'; - -/** - * Abstract class to configure static input for put-account-policy custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - private static readonly centralLogBucketName = 'aws-accelerator-central-logs'; - private static readonly createEventPropsIdentifierNames = ['AwsSecretKey', 'EmailAddress']; - private static readonly updateEventPropsIdentifierNames = ['AwsSecretKey', 'EmailAddress', 'BankAccountNumber-US']; - - private static readonly partition = 'aws'; - - public static readonly accountId = '111111111111'; - - public static readonly policyName = 'ACCELERATOR_ACCOUNT_DATA_PROTECTION_POLICY'; - - public static readonly overrideExistingCreateEventProps = { - centralLogBucketName: StaticInput.centralLogBucketName, - identifierNames: StaticInput.createEventPropsIdentifierNames, - solutionId: 'Accelerator-Solution-Id', - overrideExisting: 'true', - }; - - public static readonly overrideExistingUpdateEventProps = { - centralLogBucketName: StaticInput.centralLogBucketName, - identifierNames: StaticInput.updateEventPropsIdentifierNames, - solutionId: 'Accelerator-Solution-Id', - overrideExisting: 'true', - }; - - public static readonly createEventProps = { - centralLogBucketName: StaticInput.centralLogBucketName, - identifierNames: StaticInput.createEventPropsIdentifierNames, - solutionId: 'Accelerator-Solution-Id', - overrideExisting: false, - }; - - public static readonly updateEventProps = { - centralLogBucketName: StaticInput.centralLogBucketName, - identifierNames: StaticInput.updateEventPropsIdentifierNames, - solutionId: 'Accelerator-Solution-Id', - overrideExisting: false, - }; - - public static readonly newDataIdentifiers: string[] = StaticInput.createEventPropsIdentifierNames.map( - item => `arn:${StaticInput.partition}:dataprotection::${StaticInput.partition}:data-identifier/${item}`, - ); - - public static readonly oldDataIdentifiers: string[] = StaticInput.updateEventPropsIdentifierNames.map( - item => `arn:${StaticInput.partition}:dataprotection::${StaticInput.partition}:data-identifier/${item}`, - ); - - public static readonly createEventPolicyDocument = { - Name: 'ACCOUNT_DATA_PROTECTION_POLICY', - Description: '', - Version: '2021-06-01', - Statement: [ - { - Sid: 'audit-policy', - DataIdentifier: StaticInput.newDataIdentifiers, - Operation: { - Audit: { - FindingsDestination: { - S3: { - Bucket: StaticInput.centralLogBucketName, - }, - }, - }, - }, - }, - { - Sid: 'redact-policy', - DataIdentifier: StaticInput.newDataIdentifiers, - Operation: { - Deidentify: { - MaskConfig: {}, - }, - }, - }, - ], - }; - - public static readonly updateEventPolicyDocument = { - Name: 'ACCOUNT_DATA_PROTECTION_POLICY', - Description: '', - Version: '2021-06-01', - Statement: [ - { - Sid: 'audit-policy', - DataIdentifier: StaticInput.oldDataIdentifiers, - Operation: { - Audit: { - FindingsDestination: { - S3: { - Bucket: StaticInput.centralLogBucketName, - }, - }, - }, - }, - }, - { - Sid: 'redact-policy', - DataIdentifier: StaticInput.oldDataIdentifiers, - Operation: { - Deidentify: { - MaskConfig: {}, - }, - }, - }, - ], - }; - - public static operationOutput = { Status: 'SUCCESS' }; - - public static createOperationOutput = { - policyName: StaticInput.policyName, - policyDocument: JSON.stringify(StaticInput.createEventPolicyDocument), - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - accountId: StaticInput.accountId, - }; - - public static updateOperationOutput = { - policyName: StaticInput.policyName, - policyDocument: JSON.stringify(StaticInput.updateEventPolicyDocument), - policyType: PolicyType.DATA_PROTECTION_POLICY, - scope: Scope.ALL, - accountId: StaticInput.accountId, - }; - - public static readonly missingAccountPolicies = new Error( - `Undefined accountPolicies property received from DescribeAccountPolicies API.`, - ); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/index.ts deleted file mode 100644 index 4323760..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/index.ts +++ /dev/null @@ -1,368 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { - CloudWatchLogsClient, - DescribeLogGroupsCommand, - LogGroup, - PutSubscriptionFilterCommand, - SubscriptionFilter, - AssociateKmsKeyCommand, - PutRetentionPolicyCommand, - DescribeSubscriptionFiltersCommandOutput, - DescribeSubscriptionFiltersCommand, - DeleteSubscriptionFilterCommand, -} from '@aws-sdk/client-cloudwatch-logs'; - -import { setRetryStrategy, wildcardMatch } from '@aws-accelerator/utils/lib/common-functions'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -import { CloudFormationCustomResourceEvent } from '../../lza-custom-resource'; - -const solutionId = process.env['SOLUTION_ID'] ?? ''; -const retryStrategy = setRetryStrategy(); - -const logsClient = new CloudWatchLogsClient({ customUserAgent: solutionId, retryStrategy }); - -export type cloudwatchExclusionProcessedItem = { - account: string; - region: string; - excludeAll?: boolean; - logGroupNames?: string[]; -}; -/** - * update-subscription-policy - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const acceleratorLogSubscriptionRoleArn: string = event.ResourceProperties['acceleratorLogSubscriptionRoleArn']; - const acceleratorCreatedLogDestinationArn: string = event.ResourceProperties['acceleratorCreatedLogDestinationArn']; - const acceleratorLogRetentionInDays: string = event.ResourceProperties['acceleratorLogRetentionInDays']; - const acceleratorLogKmsKeyArn: string | undefined = event.ResourceProperties['acceleratorLogKmsKeyArn'] ?? undefined; - - const logExclusionOption: string | undefined = event.ResourceProperties['logExclusionOption']; - const replaceLogDestinationArn: string | undefined = event.ResourceProperties['replaceLogDestinationArn']; - - let logExclusionParse: cloudwatchExclusionProcessedItem | undefined; - if (logExclusionOption) { - logExclusionParse = JSON.parse(logExclusionOption); - } else { - logExclusionParse = undefined; - } - - switch (event.RequestType) { - case 'Create': - case 'Update': - // get all logGroups in the account - const logGroups = await getLogGroups(acceleratorCreatedLogDestinationArn, logExclusionParse); - - // Process retention and encryption setting for ALL log groups - for (const allLogGroup of logGroups.allLogGroups) { - await updateRetentionPolicy(parseInt(acceleratorLogRetentionInDays), allLogGroup); - - await updateLogGroupEncryption(allLogGroup, acceleratorLogKmsKeyArn); - } - - // Process subscription only for included log groups - for (const includedLogGroup of logGroups.includedLogGroups) { - await manageLogSubscriptions( - includedLogGroup.logGroupName!, - acceleratorCreatedLogDestinationArn, - acceleratorLogSubscriptionRoleArn, - replaceLogDestinationArn, - ); - } - break; - case 'Delete': - // Remove the subscription filter created by solution - await deleteAllLogGroupSubscriptions(acceleratorCreatedLogDestinationArn); - break; - } - return { Status: 'SUCCESS' }; -} - -/** - * Function to process log replication exclusion list and return inclusion list of log groups and all log groups list - * @param acceleratorCreatedLogDestinationArn string - * @param logExclusionSetting {@link cloudwatchExclusionProcessedItem} - * @returns - */ -async function getLogGroups( - acceleratorCreatedLogDestinationArn: string, - logExclusionSetting?: cloudwatchExclusionProcessedItem, -): Promise<{ allLogGroups: LogGroup[]; includedLogGroups: LogGroup[] }> { - const allLogGroups: LogGroup[] = []; - const includedLogGroups: LogGroup[] = []; - - let nextToken: string | undefined; - do { - const page = await throttlingBackOff(() => logsClient.send(new DescribeLogGroupsCommand({ nextToken }))); - for (const logGroup of page.logGroups ?? []) { - // control tower log groups are controlled by the service and cannot be modified - if (!logGroup.logGroupName!.includes('aws-controltower')) { - allLogGroups.push(logGroup); - if (isLogGroupExcluded(logGroup.logGroupName!, logExclusionSetting)) { - await deleteSubscription(logGroup.logGroupName!, acceleratorCreatedLogDestinationArn); - } else { - includedLogGroups.push(logGroup); - } - } - } - nextToken = page.nextToken; - } while (nextToken); - - if (logExclusionSetting?.excludeAll) { - await deleteAllLogGroupSubscriptions(acceleratorCreatedLogDestinationArn); - return { allLogGroups: allLogGroups, includedLogGroups: [] }; - } - - return { allLogGroups: allLogGroups, includedLogGroups: includedLogGroups }; -} - -/** - * Function to delete solution configured log subscriptions for every cloud watch log groups - * @param acceleratorCreatedLogDestinationArn string - * - */ -async function deleteAllLogGroupSubscriptions(acceleratorCreatedLogDestinationArn: string) { - let nextToken: string | undefined; - do { - const page = await throttlingBackOff(() => logsClient.send(new DescribeLogGroupsCommand({ nextToken }))); - for (const logGroup of page.logGroups ?? []) { - await deleteSubscription(logGroup.logGroupName!, acceleratorCreatedLogDestinationArn); - } - nextToken = page.nextToken; - } while (nextToken); -} - -/** - * Function to update log retention policy - * @param acceleratorRetentionInDays number - * @param logGroup {@link AWS.CloudWatchLogs.LogGroup} - * @returns - */ -async function updateRetentionPolicy(acceleratorRetentionInDays: number, logGroup: LogGroup) { - const currentRetentionInDays = logGroup.retentionInDays; - if (!currentRetentionInDays) { - return; - } - - if (acceleratorRetentionInDays > currentRetentionInDays) { - await throttlingBackOff(() => - logsClient.send( - new PutRetentionPolicyCommand({ - logGroupName: logGroup.logGroupName!, - retentionInDays: acceleratorRetentionInDays, - }), - ), - ); - } -} - -/** - * Function to manage log subscription filter destinations - * @param logGroupName string - * @param acceleratorCreatedLogDestinationArn string - * @param acceleratorLogSubscriptionRoleArn string - * @param replaceLogDestinationArn string - */ -async function manageLogSubscriptions( - logGroupName: string, - acceleratorCreatedLogDestinationArn: string, - acceleratorLogSubscriptionRoleArn: string, - replaceLogDestinationArn?: string, -): Promise { - let nextToken: string | undefined = undefined; - do { - const page: DescribeSubscriptionFiltersCommandOutput = await throttlingBackOff(() => - logsClient.send(new DescribeSubscriptionFiltersCommand({ logGroupName: logGroupName, nextToken })), - ); - - if (page.subscriptionFilters) { - const subscriptionFilters = page.subscriptionFilters; - - await removeReplaceDestination(logGroupName, subscriptionFilters, replaceLogDestinationArn); - - const acceleratorCreatedSubscriptFilter = subscriptionFilters.find( - item => item.destinationArn === acceleratorCreatedLogDestinationArn, - ); - - const numberOfSubscriptions = subscriptionFilters.length; - - await updateLogSubscription( - logGroupName, - numberOfSubscriptions, - acceleratorCreatedLogDestinationArn, - acceleratorLogSubscriptionRoleArn, - acceleratorCreatedSubscriptFilter, - ); - } - - nextToken = page.nextToken; - } while (nextToken); -} - -/** - * Function to update log subscription filter - * @param logGroupName - * @param numberOfSubscriptions - * @param acceleratorCreatedLogDestinationArn - * @param acceleratorLogSubscriptionRoleArn - * @param acceleratorCreatedSubscriptFilter - * @returns - */ -async function updateLogSubscription( - logGroupName: string, - numberOfSubscriptions: number, - acceleratorCreatedLogDestinationArn: string, - acceleratorLogSubscriptionRoleArn: string, - acceleratorCreatedSubscriptFilter?: SubscriptionFilter, -): Promise { - if (numberOfSubscriptions >= 1 && acceleratorCreatedSubscriptFilter) { - return; - } - - if (numberOfSubscriptions <= 1 && !acceleratorCreatedSubscriptFilter) { - await throttlingBackOff(() => - logsClient.send( - new PutSubscriptionFilterCommand({ - destinationArn: acceleratorCreatedLogDestinationArn, - logGroupName: logGroupName, - roleArn: acceleratorLogSubscriptionRoleArn, - filterName: logGroupName, - filterPattern: '', - }), - ), - ); - } - - if (numberOfSubscriptions === 2 && !acceleratorCreatedSubscriptFilter) { - throw new Error( - `Cloudwatch log group ${logGroupName} have ${numberOfSubscriptions} subscription destinations, can not add accelerator subscription destination!!!! Remove one of the two existing destination and rerun the pipeline for accelerator to add solution defined log destination ${acceleratorCreatedLogDestinationArn}`, - ); - } -} - -/** - * Function to remove given subscription - * @param logGroupName string - * @param subscriptionFilters {@link AWS.CloudWatchLogs.SubscriptionFilters} - * @param replaceLogDestinationArn string | undefined - */ -async function removeReplaceDestination( - logGroupName: string, - subscriptionFilters: SubscriptionFilter[], - replaceLogDestinationArn?: string, -): Promise { - const replaceLogDestinationFilter = subscriptionFilters.find( - item => item.destinationArn === replaceLogDestinationArn, - ); - - if (replaceLogDestinationFilter) { - console.log( - `Removing subscription filter for ${logGroupName} log group, current destination arn is ${replaceLogDestinationFilter.destinationArn}`, - ); - - await throttlingBackOff(() => - logsClient.send( - new DeleteSubscriptionFilterCommand({ - logGroupName: logGroupName, - filterName: replaceLogDestinationFilter.filterName!, - }), - ), - ); - } -} - -/** - * Function to check if log group is part of exclusion list - * @param logGroupName string - * @param logExclusionSetting string - * @returns - */ -function isLogGroupExcluded(logGroupName: string, logExclusionSetting?: cloudwatchExclusionProcessedItem): boolean { - if (logExclusionSetting) { - if (logExclusionSetting.excludeAll) { - return true; - } - - for (const excludeLogGroupName of logExclusionSetting.logGroupNames ?? []) { - if (wildcardMatch(logGroupName, excludeLogGroupName)) { - return true; - } - } - } - - return false; -} - -/** - * Function to delete Accelerator deployed log subscription filter for given log group. - * @param logGroupName string - * @param acceleratorCreatedLogDestinationArn string - */ -async function deleteSubscription(logGroupName: string, acceleratorCreatedLogDestinationArn: string) { - // check subscription on existing logGroup. - let nextToken: string | undefined = undefined; - do { - const page: DescribeSubscriptionFiltersCommandOutput = await throttlingBackOff(() => - logsClient.send(new DescribeSubscriptionFiltersCommand({ logGroupName, nextToken })), - ); - for (const subscriptionFilter of page.subscriptionFilters ?? []) { - // If subscription exists delete it - if ( - subscriptionFilter.filterName === logGroupName && - subscriptionFilter.destinationArn === acceleratorCreatedLogDestinationArn - ) { - console.log( - `Removing subscription filter for ${logGroupName} log group, current destination arn is ${subscriptionFilter.destinationArn}`, - ); - - await throttlingBackOff(() => - logsClient.send( - new DeleteSubscriptionFilterCommand({ - logGroupName: subscriptionFilter.logGroupName!, - filterName: subscriptionFilter.filterName!, - }), - ), - ); - } - } - nextToken = page.nextToken; - } while (nextToken); -} - -/** - * Function to update Log group encryption - * @param logGroup string - * @param acceleratorLogKmsKeyArn string - */ -async function updateLogGroupEncryption(logGroup: LogGroup, acceleratorLogKmsKeyArn?: string) { - if (!logGroup.kmsKeyId && acceleratorLogKmsKeyArn) { - await throttlingBackOff(() => - logsClient.send( - new AssociateKmsKeyCommand({ - logGroupName: logGroup.logGroupName!, - kmsKeyId: acceleratorLogKmsKeyArn, - }), - ), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/package.json deleted file mode 100644 index 9ca015c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-cloudwatch-logs-update-subscription-filter", - "version": "0.0.0", - "private": true, - "description": "custom resource to update CWL subscriptions", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cloudwatch-logs/update-subscription-filter/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-aggregation.ts b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-aggregation.ts deleted file mode 100644 index 1d489a1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-aggregation.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface ConfigAggregationProps { - acceleratorPrefix: string; -} - -export class ConfigAggregation extends Construct { - constructor(scope: Construct, id: string, props: ConfigAggregationProps) { - super(scope, id); - - const configAggregatorRole = new cdk.aws_iam.Role(this, 'ConfigAggregatorRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('config.amazonaws.com'), - description: 'Role used by the AWS Config Service aggregation to use organization resources', - }); - - configAggregatorRole.addManagedPolicy( - cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSConfigRoleForOrganizations'), - ); - - new cdk.aws_config.CfnConfigurationAggregator(this, 'ConfigAggregator', { - configurationAggregatorName: `${props.acceleratorPrefix}-Aggregator`, - organizationAggregationSource: { - roleArn: configAggregatorRole.roleArn, - allAwsRegions: true, - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder.ts b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder.ts deleted file mode 100644 index 7ea8767..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -const path = require('path'); - -export interface ConfigServiceRecorderProps { - /** - * S3 Bucket Name for Delivery Channel - */ - readonly s3BucketName: string; - /** - * S3 Bucket KMS Key - */ - readonly s3BucketKmsKey: cdk.aws_kms.IKey; - /** - * Role for config recorder - */ - configRecorderRoleArn: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly lambdaKmsKey?: cdk.aws_kms.IKey; - /** - * Partition - */ - readonly partition: string; - /** - * Accelerator prefix - */ - readonly acceleratorPrefix: string; -} - -/** - * Class to Create/Update/Delete Config Recorder - */ -export class ConfigServiceRecorder extends Construct { - readonly id: string; - - constructor(scope: Construct, id: string, props: ConfigServiceRecorderProps) { - super(scope, id); - - const CONFIGSERVICE_RECORDER = 'Custom::ConfigServiceRecorder'; - - const configRecorderFunctionPolicies = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'configService', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'config:DeleteDeliveryChannel', - 'config:DescribeConfigurationRecorders', - 'config:DescribeDeliveryChannelStatus', - 'config:PutConfigurationRecorder', - 'config:PutDeliveryChannel', - 'config:StartConfigurationRecorder', - 'config:StopConfigurationRecorder', - 'config:DeleteConfigurationRecorder', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'iam', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['iam:PassRole'], - resources: [props.configRecorderRoleArn], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'sts', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [props.configRecorderRoleArn], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 's3', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutObject*', 's3:GetBucketACL'], - resources: [ - `arn:${props.partition}:s3:::${props.s3BucketName}`, - `arn:${props.partition}:s3:::${props.s3BucketName}/*`, - ], - }), - ], - }); - - const lambdaRole = new cdk.aws_iam.Role(this, 'ConfigServiceRecorderFunctionRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], - inlinePolicies: { configRecorder: configRecorderFunctionPolicies }, - }); - - const lambdaFunction = new cdk.aws_lambda.Function(this, 'ConfigServiceRecorderFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'config-recorder/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(5), - description: 'Create/Update Config Recorder', - environmentEncryption: props.lambdaKmsKey, - role: lambdaRole, - }); - - const logGroup = new cdk.aws_logs.LogGroup(this, `${lambdaFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${lambdaFunction.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudwatchKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'ConfigServiceRecorderProvider', { - onEventHandler: lambdaFunction, - }); - - const resource = new cdk.CustomResource(this, 'ConfigServiceRecorderResource', { - resourceType: CONFIGSERVICE_RECORDER, - serviceToken: provider.serviceToken, - properties: { - s3BucketName: props.s3BucketName, - s3BucketKmsKeyArn: props.s3BucketKmsKey.keyArn, - recorderRoleArn: props.configRecorderRoleArn, - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/index.ts deleted file mode 100644 index 541d669..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/index.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - ConfigServiceClient, - DeleteDeliveryChannelCommand, - DeleteConfigurationRecorderCommand, - DescribeConfigurationRecordersCommand, - DescribeDeliveryChannelStatusCommand, - PutDeliveryChannelCommand, - PutDeliveryChannelCommandInput, - PutConfigurationRecorderCommand, - PutConfigurationRecorderCommandInput, - StartConfigurationRecorderCommand, - StopConfigurationRecorderCommand, -} from '@aws-sdk/client-config-service'; -import { - CloudFormationCustomResourceEvent, - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceUpdateEvent, - CloudFormationCustomResourceDeleteEvent, -} from '@aws-accelerator/utils/lib/common-types'; -import { v4 as uuidv4 } from 'uuid'; -let configClient: ConfigServiceClient; - -/** - * configservice-delivery-channel - lambda handler - * - * @param event - * @returns - */ -export const handler = onEvent; - -export async function onEvent( - event: CloudFormationCustomResourceEvent, -): Promise<{ PhysicalResourceId: string | undefined; StatusCode: number; Status: string }> { - //console.debug(`Event: ${JSON.stringify(event)}`); - const solutionId = process.env['SOLUTION_ID']; - configClient = new ConfigServiceClient({ customUserAgent: solutionId }); - switch (event.RequestType) { - case 'Create': - return onCreate(event); - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event); - } -} - -export async function onCreate(event: CloudFormationCustomResourceCreateEvent) { - const s3BucketName = event.ResourceProperties['s3BucketName']; - const s3BucketKmsKeyArn = event.ResourceProperties['s3BucketKmsKeyArn']; - const recorderRoleArn = event.ResourceProperties['recorderRoleArn']; - - console.log('check config recorders'); - const configRecorders = await throttlingBackOff(() => - configClient.send(new DescribeConfigurationRecordersCommand({})), - ); - console.log(`${JSON.stringify(configRecorders)}`); - - let existingConfigRecorderName: string | undefined = undefined; - if (configRecorders.ConfigurationRecorders?.length === 1) { - existingConfigRecorderName = configRecorders.ConfigurationRecorders[0].name; - } - - if (existingConfigRecorderName) { - console.info('Stopping config recorder'); - await throttlingBackOff(() => - configClient.send( - new StopConfigurationRecorderCommand({ ConfigurationRecorderName: existingConfigRecorderName }), - ), - ); - } - - let configRecorderName = existingConfigRecorderName; - - configRecorderName = await createUpdateRecorder(recorderRoleArn); - - await createUpdateDeliveryChannel(s3BucketName, s3BucketKmsKeyArn); - - console.info('Starting config recorder'); - await throttlingBackOff(() => - configClient.send(new StartConfigurationRecorderCommand({ ConfigurationRecorderName: configRecorderName })), - ); - - return { - PhysicalResourceId: uuidv4(), - StatusCode: 200, - Status: 'SUCCESS', - }; -} - -async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) { - const s3BucketName = event.ResourceProperties['s3BucketName']; - const s3BucketKmsKeyArn = event.ResourceProperties['s3BucketKmsKeyArn']; - const recorderRoleArn = event.ResourceProperties['recorderRoleArn']; - - const configRecorders = await throttlingBackOff(() => - configClient.send(new DescribeConfigurationRecordersCommand({})), - ); - let existingConfigRecorderName: string | undefined = undefined; - - if (configRecorders.ConfigurationRecorders?.length === 1) { - existingConfigRecorderName = configRecorders.ConfigurationRecorders[0].name; - } - - if (existingConfigRecorderName) { - console.info('Stopping config recorder'); - await throttlingBackOff(() => - configClient.send( - new StopConfigurationRecorderCommand({ ConfigurationRecorderName: existingConfigRecorderName }), - ), - ); - } - - let configRecorderName = existingConfigRecorderName; - configRecorderName = await createUpdateRecorder(recorderRoleArn); - - await createUpdateDeliveryChannel(s3BucketName, s3BucketKmsKeyArn); - - console.info('Starting config recorder'); - await throttlingBackOff(() => - configClient.send(new StartConfigurationRecorderCommand({ ConfigurationRecorderName: configRecorderName })), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - StatusCode: 200, - Status: 'SUCCESS', - }; -} - -async function onDelete(event: CloudFormationCustomResourceDeleteEvent) { - const configRecorders = await throttlingBackOff(() => - configClient.send(new DescribeConfigurationRecordersCommand({})), - ); - let existingConfigRecorderName: string | undefined = undefined; - - if (configRecorders.ConfigurationRecorders?.length === 1) { - existingConfigRecorderName = configRecorders.ConfigurationRecorders[0].name; - } - - if (existingConfigRecorderName) { - console.info('Stopping config recorder'); - await throttlingBackOff(() => - configClient.send( - new StopConfigurationRecorderCommand({ ConfigurationRecorderName: existingConfigRecorderName }), - ), - ); - } - - await deleteDeliveryChannel(); - - await deleteConfigRecorder(); - - return { - PhysicalResourceId: event.PhysicalResourceId, - StatusCode: 200, - Status: 'SUCCESS', - }; -} - -async function createUpdateDeliveryChannel(s3BucketName: string, s3BucketKmsKeyArn: string): Promise { - console.log('In create update delivery channel'); - const deliveryChannels = await throttlingBackOff(() => - configClient.send(new DescribeDeliveryChannelStatusCommand({})), - ); - console.info(`Delivery channels: ${JSON.stringify(deliveryChannels)}`); - let existingDeliveryChannelName: string | undefined = undefined; - // should return one or none - if (deliveryChannels.DeliveryChannelsStatus?.length === 1) { - existingDeliveryChannelName = deliveryChannels.DeliveryChannelsStatus[0].name; - } - - console.log(`Existing delivery channel name ${existingDeliveryChannelName}\n`); - - const params: PutDeliveryChannelCommandInput = { - DeliveryChannel: { - name: existingDeliveryChannelName ?? 'default', - s3BucketName, - configSnapshotDeliveryProperties: { - deliveryFrequency: 'One_Hour', - }, - s3KeyPrefix: 'config', - s3KmsKeyArn: s3BucketKmsKeyArn, - }, - }; - - console.info(`Params: ${JSON.stringify(params)}`); - try { - const response = await throttlingBackOff(() => configClient.send(new PutDeliveryChannelCommand(params))); - console.debug(`PutDeliveryChannel Response: ${JSON.stringify(response)}`); - } catch (error) { - console.error(JSON.stringify(error)); - throw new Error('PutDeliveryChannel Failed'); - } -} - -async function deleteConfigRecorder(): Promise { - console.log('In delete config recorder'); - const configRecorders = await throttlingBackOff(() => - configClient.send(new DescribeConfigurationRecordersCommand({})), - ); - let existingConfigRecorderName: string | undefined = undefined; - - if (configRecorders.ConfigurationRecorders?.length === 1) { - existingConfigRecorderName = configRecorders.ConfigurationRecorders[0].name; - } - - if (existingConfigRecorderName) { - try { - const response = await throttlingBackOff(() => - configClient.send( - new DeleteConfigurationRecorderCommand({ ConfigurationRecorderName: existingConfigRecorderName }), - ), - ); - console.debug(`Delete config recorder response: ${JSON.stringify(response)}`); - } catch (error) { - console.error(JSON.stringify(error)); - } - } -} - -async function deleteDeliveryChannel() { - console.log('In delete delivery channel'); - const deliveryChannels = await throttlingBackOff(() => - configClient.send(new DescribeDeliveryChannelStatusCommand({})), - ); - let existingDeliveryChannelName: string | undefined = undefined; - - if (deliveryChannels.DeliveryChannelsStatus?.length === 1) { - existingDeliveryChannelName = deliveryChannels.DeliveryChannelsStatus[0].name; - } - - if (existingDeliveryChannelName) { - try { - const response = await throttlingBackOff(() => - configClient.send(new DeleteDeliveryChannelCommand({ DeliveryChannelName: existingDeliveryChannelName })), - ); - console.debug(`Delete delivery channel response: ${JSON.stringify(response)}`); - } catch (error) { - console.error(JSON.stringify(error)); - } - } -} - -async function createUpdateRecorder(recorderRoleArn: string): Promise { - console.log('In create update recorder'); - const configRecorders = await throttlingBackOff(() => - configClient.send(new DescribeConfigurationRecordersCommand({})), - ); - let existingConfigRecorderName: string | undefined = undefined; - if (configRecorders.ConfigurationRecorders?.length === 1) { - existingConfigRecorderName = configRecorders.ConfigurationRecorders[0].name; - } - - const params: PutConfigurationRecorderCommandInput = { - ConfigurationRecorder: { - name: existingConfigRecorderName ?? 'default', - roleARN: recorderRoleArn, - recordingGroup: { - allSupported: true, - includeGlobalResourceTypes: true, - }, - }, - }; - - console.info(`Recorder Params: ${JSON.stringify(params)}`); - try { - const response = await throttlingBackOff(() => configClient.send(new PutConfigurationRecorderCommand(params))); - console.debug(`PutConfigurationRecorder Response: ${JSON.stringify(response)}`); - } catch (error) { - console.error(JSON.stringify(error)); - throw new Error(`Create/Update Recorder failed: ${JSON.stringify(params)}`); - } - - return params.ConfigurationRecorder!.name!; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/package.json deleted file mode 100644 index e71bf2c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-configservice-config-recorder", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-config-service": "3.410.0" - }, - "devDependencies": { - "@aws-sdk/types": "3.410.0", - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "aws-sdk-client-mock": "2.1.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/test/config-recorder.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/test/config-recorder.test.ts deleted file mode 100644 index de98ed7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/test/config-recorder.test.ts +++ /dev/null @@ -1,442 +0,0 @@ -import { handler } from '../index'; -import { mockClient } from 'aws-sdk-client-mock'; -import { - ConfigServiceClient, - DescribeConfigurationRecordersCommand, - DescribeDeliveryChannelStatusCommand, - PutDeliveryChannelCommand, - DeleteDeliveryChannelCommand, - DeleteConfigurationRecorderCommand, - StopConfigurationRecorderCommand, - PutConfigurationRecorderCommand, -} from '@aws-sdk/client-config-service'; - -const configMock = mockClient(ConfigServiceClient); - -it('create event without existing resources', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [], - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Create', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('create event existing config recorder', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [{ name: 'default' }], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [], - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Create', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('create event existing delivery channel', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [{ name: 'default' }], - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Create', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('create event both recorder and delivery channel exist', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [{ name: 'default' }], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [{ name: 'default' }], - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Create', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('update event both recorder and delivery channel exist', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [{ name: 'default' }], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [{ name: 'default' }], - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Update', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - PhysicalResourceId: 'efffffff-aaaa-bbbb-cccc-dddddddddddddd', - OldResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-orig-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('update event recorder exists', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [{ name: 'default' }], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [], - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Update', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - PhysicalResourceId: 'efffffff-aaaa-bbbb-cccc-dddddddddddddd', - OldResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-orig-us-east-1-123456789012', - }, - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('update event delivery channel exists', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [{ name: 'default' }], - }); - - configMock.on(PutDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(PutConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Update', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - PhysicalResourceId: 'efffffff-aaaa-bbbb-cccc-dddddddddddddd', - OldResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-orig-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('delete event no existing delivery channel', async () => { - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [{ name: 'default' }], - }); - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [], - }); - - configMock.on(DeleteDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Delete', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - PhysicalResourceId: 'efffffff-aaaa-bbbb-cccc-dddddddddddddd', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('delete event both recorder and delivery channel exist', async () => { - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [{ name: 'default' }], - }); - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [{ name: 'default' }], - }); - - configMock.on(StopConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(DeleteConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(DeleteDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Delete', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - PhysicalResourceId: 'efffffff-aaaa-bbbb-cccc-dddddddddddddd', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); - -it('delete event delivery channel exist', async () => { - configMock.on(DescribeDeliveryChannelStatusCommand).resolves({ - DeliveryChannelsStatus: [{ name: 'default' }], - }); - configMock.on(DescribeConfigurationRecordersCommand).resolves({ - ConfigurationRecorders: [], - }); - - configMock.on(StopConfigurationRecorderCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - configMock.on(DeleteDeliveryChannelCommand).resolves({ - $metadata: { - attempts: 1, - httpStatusCode: 200, - }, - }); - - const result = await handler({ - RequestType: 'Delete', - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - ResponseURL: '...', - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/AWSAccelerator-Stack', - RequestId: 'ef092572-5cb7-4a7b-9ca7-dfe495ba877a', - LogicalResourceId: 'LogicalId', - ResourceType: 'Custom::Resource', - PhysicalResourceId: 'efffffff-aaaa-bbbb-cccc-dddddddddddddd', - ResourceProperties: { - ServiceToken: 'arn:aws:lambda:us-east-1:123456789012:function:AWSAccelerator-CustomFunction', - s3BucketName: 'centralLogBucket-us-east-1-123456789012', - s3BucketKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/166692b1-0f01-4261-8dcc-7ff477df12a9', - recorderRoleArn: 'arn:aws:iam::23456789012:role/AWSAccelerator-SecurityRe-ConfigRecorderRoleC4E33A-X0DGQJYIMPU3', - }, - }); - - expect(result.Status).toEqual('SUCCESS'); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-recorder/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-tags.ts b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-tags.ts deleted file mode 100644 index 6625e9e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/config-tags.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { Tag } from '@aws-sdk/client-config-service'; - -const path = require('path'); - -export interface ConfigServiceTagsProps { - readonly partition: string; - readonly accountId: string; - readonly resourceArn: string; - readonly tags: Tag[]; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to tag/untag configservice resources - */ -export class ConfigServiceTags extends Construct { - readonly id: string; - - constructor(scope: Construct, id: string, props: ConfigServiceTagsProps) { - super(scope, id); - - const CONFIGSERVICE_TAGS = 'Custom::ConfigServiceTags'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, CONFIGSERVICE_TAGS, { - codeDirectory: path.join(__dirname, 'update-tags/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['config:TagResource', 'config:UntagResource'], - Resource: `arn:${props.partition}:config:*:${props.accountId}:config-rule/*`, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: CONFIGSERVICE_TAGS, - serviceToken: provider.serviceToken, - properties: { - resourceArn: props.resourceArn, - tags: props.tags, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/index.ts deleted file mode 100644 index 67c0ae3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - ConfigServiceClient, - TagResourceCommand, - UntagResourceCommand, - TagResourceCommandInput, - UntagResourceCommandInput, - Tag, -} from '@aws-sdk/client-config-service'; -import { - CloudFormationCustomResourceEvent, - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceUpdateEvent, - CloudFormationCustomResourceDeleteEvent, -} from '@aws-accelerator/utils/lib/common-types'; -import { v4 as uuidv4 } from 'uuid'; -let configClient: ConfigServiceClient; - -/** - * configservice-update-tags - lambda handler - * - * @param event - * @returns - */ -export const handler = onEvent; - -async function onEvent( - event: CloudFormationCustomResourceEvent, -): Promise<{ PhysicalResourceId: string | undefined; Status: string } | undefined> { - console.log(JSON.stringify(event)); - const solutionId = process.env['SOLUTION_ID']; - configClient = new ConfigServiceClient({ customUserAgent: solutionId }); - switch (event.RequestType) { - case 'Create': - return onCreate(event); - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event); - } -} - -async function onCreate(event: CloudFormationCustomResourceCreateEvent) { - const resourceArn = event.ResourceProperties['resourceArn']; - const acceleratorTags: Tag[] = event.ResourceProperties['tags']; - await tagResource(resourceArn, await acceleratorTags); - return { - PhysicalResourceId: uuidv4(), - Status: 'SUCCESS', - }; -} - -async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) { - const resourceArn = event.ResourceProperties['resourceArn']; - const previousTags: Tag[] = event.OldResourceProperties['tags']; - const currentTags: Tag[] = event.ResourceProperties['tags']; - await unTagResource(resourceArn, previousTags); - if (currentTags.length >>> 0) { - await tagResource(resourceArn, await currentTags); - } - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; -} - -async function onDelete(event: CloudFormationCustomResourceDeleteEvent) { - const resourceArn = event.ResourceProperties['resourceArn']; - const acceleratorTags: Tag[] = event.ResourceProperties['tags']; - await unTagResource(resourceArn, acceleratorTags); - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; -} - -async function tagResource(resourceArn: string, tags: Tag[]): Promise { - console.log(tags); - const params: TagResourceCommandInput = { - ResourceArn: resourceArn, - Tags: tags, - }; - await throttlingBackOff(() => configClient.send(new TagResourceCommand(params))); - return true; -} - -async function unTagResource(resourceArn: string, tags: Tag[]): Promise { - const tagKeys: string[] = []; - for (const tag of tags) { - tagKeys.push(tag.Key!); - } - const params: UntagResourceCommandInput = { - ResourceArn: resourceArn, - TagKeys: tagKeys, - }; - await throttlingBackOff(() => configClient.send(new UntagResourceCommand(params))); - return true; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/package.json deleted file mode 100644 index ffb5141..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-configservice-update-tags", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-config-service": "3.410.0" - }, - "devDependencies": { - "@aws-sdk/types": "3.410.0", - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-configservice/update-tags/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/index.ts deleted file mode 100644 index a0d6bdb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/index.ts +++ /dev/null @@ -1,302 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * aws-controltower-create-accounts-status - lambda handler - * - * @param event - * @returns - */ - -import { DynamoDBDocumentClient, ScanCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - ServiceCatalogClient, - ListProvisioningArtifactsCommand, - SearchProductsCommandInput, - paginateSearchProducts, - ProductViewSummary, - paginateSearchProvisionedProducts, - SearchProvisionedProductsCommandInput, - ProvisionProductOutput, - ProvisionProductCommand, - ProvisionedProductAttribute, -} from '@aws-sdk/client-service-catalog'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { v4 as uuidv4 } from 'uuid'; - -const tableName = process.env['NewAccountsTableName'] ?? ''; -const solutionId = process.env['SOLUTION_ID'] ?? ''; - -const dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); -const documentClient = DynamoDBDocumentClient.from(dynamodbClient); -const serviceCatalogClient = new ServiceCatalogClient({ - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), -}); - -interface AccountConfig { - name: string; - description: string; - email: string; - enableGovCloud?: boolean; - organizationalUnitId?: string; - createRequestId?: string; -} - -type AccountConfigs = Array; - -//eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise< - | { - IsComplete: boolean; - } - | undefined -> { - console.log(event); - // if provisioning is in progress return - // we cannot provision another account while - // an account is being provisioned - const accountsInProcess = await inProgress(); - if (accountsInProcess === 5) { - console.log('Account provisioning in progress continuing to wait'); - return { - IsComplete: false, - }; - } - - try { - //get a single accountConfig from table and attempt to provision - //if no record is returned then all new accounts are provisioned - const accountToAdd: AccountConfigs = await getSingleAccountConfigFromTable(); - if (accountToAdd.length === 0 && accountsInProcess === 0) { - //check if any accounts in error or tainted state - if (await provisionSuccess()) { - console.log('Control Tower account provisioning complete.'); - } else { - console.log('Control Tower account provisioning failed.'); - throw new Error('Accounts failed to enroll in Control Tower. Check Service Catalog Console'); - } - - return { - IsComplete: true, - }; - } - - if (accountToAdd.length > 0) { - const provisionResponse = await provisionAccount(accountToAdd[0]); - console.log(`Provision response: ${JSON.stringify(provisionResponse)}`); - - const deleteResponse = await deleteSingleAccountConfigFromTable(accountToAdd[0].email); - console.log(`Delete response: ${JSON.stringify(deleteResponse)}`); - } - - return { - IsComplete: false, - }; - } catch (e) { - console.log(e); - console.log(`Create accounts failed. Deleting pending account creation records`); - await deleteAllRecordsFromTable(tableName); - throw new Error(`Account creation failed. ${e}`); - } -} - -async function inProgress(): Promise { - const provisionedProductsUnderChange: ProvisionedProductAttribute[] = await getProvisionedProductsWithStatus( - 'UNDER_CHANGE', - ); - let accountsInProcess = 0; - if (provisionedProductsUnderChange.length > 0) { - console.log(`Products that are UNDER_CHANGE ${provisionedProductsUnderChange.length}`); - accountsInProcess = accountsInProcess + provisionedProductsUnderChange.length; - } - - const provisionedProductsPlan: ProvisionedProductAttribute[] = await getProvisionedProductsWithStatus( - 'PLAN_IN_PROGRESS', - ); - if (provisionedProductsPlan.length > 0) { - console.log(`Products that are PLAN_IN_PROGRESS ${provisionedProductsPlan.length}`); - accountsInProcess = accountsInProcess + provisionedProductsPlan.length; - } - - console.log(`Total number of accounts in process ${accountsInProcess}`); - return accountsInProcess; -} - -async function provisionSuccess(): Promise { - const provisionedProductsError: ProvisionedProductAttribute[] = await getProvisionedProductsWithStatus('ERROR'); - if (provisionedProductsError.length > 0) { - console.log(`Provisioning failure error message: ${provisionedProductsError[0].StatusMessage}`); - return false; - } - - const provisionedProductsTainted: ProvisionedProductAttribute[] = await getProvisionedProductsWithStatus('TAINTED'); - if (provisionedProductsTainted.length > 0) { - return false; - } - - return true; -} - -async function getProvisionedProductsWithStatus(searchStatus: string): Promise { - const provisionedProducts: ProvisionedProductAttribute[] = []; - const inputParameters: SearchProvisionedProductsCommandInput = { - Filters: { - SearchQuery: [`status: ${searchStatus}`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }; - for await (const page of paginateSearchProvisionedProducts({ client: serviceCatalogClient }, inputParameters)) { - page?.ProvisionedProducts?.forEach(product => { - if (product.Type === 'CONTROL_TOWER_ACCOUNT') { - provisionedProducts.push(product); - } - }); - } - return provisionedProducts; -} - -async function getSingleAccountConfigFromTable(): Promise { - const accountToAdd: AccountConfigs = []; - const scanParams = { - TableName: tableName, - Limit: 1, - }; - const scanResponse = await throttlingBackOff(() => documentClient.send(new ScanCommand(scanParams))); - - const itemCount = scanResponse.Items?.length ?? 0; - if (itemCount > 0) { - const account: AccountConfig = JSON.parse(scanResponse.Items![0]['accountConfig']); - accountToAdd.push(account); - console.log(`Account to add ${JSON.stringify(accountToAdd)}`); - } - return accountToAdd; -} - -async function deleteSingleAccountConfigFromTable(accountToDeleteEmail: string): Promise { - const deleteParams = { - TableName: tableName, - Key: { - accountEmail: accountToDeleteEmail, - }, - }; - const deleteResponse = await throttlingBackOff(() => documentClient.send(new DeleteCommand(deleteParams))); - return JSON.stringify(deleteResponse); -} - -async function provisionAccount(accountToAdd: AccountConfig): Promise { - const searchProducts: ProductViewSummary[] = []; - const inputParameters: SearchProductsCommandInput = { - Filters: { FullTextSearch: ['AWS Control Tower Account Factory'] }, - }; - - for await (const page of paginateSearchProducts({ client: serviceCatalogClient }, inputParameters)) { - searchProducts.push(...page.ProductViewSummaries!); - } - - let productId: string; - if (searchProducts.length === 0) { - throw new Error('No products were found while searching for AWS Control Tower Account Factory'); - } else if (searchProducts.length > 1) { - throw new Error('Multiple products were found while searching for AWS Control Tower Account Factory'); - } else { - productId = searchProducts[0].ProductId!; - console.log(`Service Catalog ProductId ${productId}`); - } - - // this is non-paginated command - // Ref: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/service-catalog/command/ListProvisioningArtifactsCommand/ - const listProvisioningArtifactsOutput = await throttlingBackOff(() => - serviceCatalogClient.send(new ListProvisioningArtifactsCommand({ ProductId: productId })), - ); - - const provisioningArtifact = listProvisioningArtifactsOutput?.ProvisioningArtifactDetails?.find(a => a.Active); - const provisioningArtifactId = provisioningArtifact?.Id; - console.log(`Service Catalog Provisioning Artifact Id ${provisioningArtifactId}`); - - const provisionInput = { - ProductName: 'AWS Control Tower Account Factory', - ProvisionToken: uuidv4(), - ProvisioningArtifactId: provisioningArtifactId, - ProvisionedProductName: accountToAdd.name, - ProvisioningParameters: [ - { - Key: 'AccountName', - Value: accountToAdd.name, - }, - { - Key: 'AccountEmail', - Value: accountToAdd.email, - }, - { - Key: 'ManagedOrganizationalUnit', - Value: accountToAdd.organizationalUnitId, - }, - { - Key: 'SSOUserEmail', - Value: accountToAdd.email, - }, - { - Key: 'SSOUserFirstName', - Value: accountToAdd.name, - }, - { - Key: 'SSOUserLastName', - Value: accountToAdd.name, - }, - { - Key: 'VPCOptions', - Value: 'No-Primary-VPC', - }, - { - Key: 'VPCRegion', - Value: 'us-east-1', //dummy value, vpc is not created - }, - { - Key: 'VPCCidr', - Value: '10.0.0.0/16', //dummy value, vpc is not created - }, - { - Key: 'PeerVPC', - Value: 'false', //dummy value, vpc is not created - }, - ], - }; - - return throttlingBackOff(() => serviceCatalogClient.send(new ProvisionProductCommand(provisionInput))); -} - -async function deleteAllRecordsFromTable(paramTableName: string) { - const params = { - TableName: paramTableName, - ProjectionExpression: 'accountEmail', - }; - const scanResponse = await throttlingBackOff(() => documentClient.send(new ScanCommand(params))); - if (scanResponse.Items) { - for (const item of scanResponse.Items) { - console.log(item['accountEmail']); - const itemParams = { - TableName: paramTableName, - Key: { - accountEmail: item['accountEmail'], - }, - }; - await throttlingBackOff(() => documentClient.send(new DeleteCommand(itemParams))); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/jest.config.js deleted file mode 100644 index 9e17902..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 85, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/package.json deleted file mode 100644 index 6641923..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-control-tower-create-accounts-status", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-service-catalog": "3.410.0", - "@aws-sdk/client-dynamodb": "3.410.0", - "@aws-sdk/lib-dynamodb": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/index.test.ts deleted file mode 100644 index d61dceb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/index.test.ts +++ /dev/null @@ -1,423 +0,0 @@ -import { DynamoDBDocumentClient, ScanCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; -import { - ServiceCatalogClient, - SearchProvisionedProductsCommand, - SearchProductsCommand, - ListProvisioningArtifactsCommand, - ProvisionProductCommand, -} from '@aws-sdk/client-service-catalog'; - -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -const ddbClient = AcceleratorMockClient(DynamoDBDocumentClient); -const scClient = AcceleratorMockClient(ServiceCatalogClient); - -describe('Any Event', () => { - beforeEach(() => { - ddbClient.reset(); - scClient.reset(); - }); - test('Event - 5 accounts being provisioned', async () => { - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: UNDER_CHANGE`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves(StaticInput.fiveAccountsProvisioning); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: TAINTED`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: ERROR`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: PLAN_IN_PROGRESS`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - const response = await handler({}); - expect(response!.IsComplete).toBeFalsy(); - }); - - test('Event - one account provisioned', async () => { - // account to add - ddbClient.on(ScanCommand, { TableName: '', ProjectionExpression: 'accountEmail' }).resolves({ - Items: [{ accountEmail: 'example@example.com' }], - }); - // get account details - ddbClient.on(ScanCommand, { TableName: '', Limit: 1 }).resolves({ - Items: [ - { - accountEmail: 'example@example.com', - accountConfig: JSON.stringify({ - name: 'name', - description: 'description', - email: 'example@example.com', - enableGovCloud: false, - organizationalUnitId: 'ou-id', - }), - }, - ], - }); - // lookup products on any state is empty. This is like a new account which is running for the first time - scClient.on(SearchProvisionedProductsCommand).resolves({}); - // lookup and get CT product ID - scClient - .on(SearchProductsCommand, { - Filters: { FullTextSearch: ['AWS Control Tower Account Factory'] }, - }) - .resolves({ ProductViewSummaries: [{ ProductId: 'ProductId' }] }); - // get artifact id for that product ID - scClient - .on(ListProvisioningArtifactsCommand, { ProductId: 'ProductId' }) - .resolves({ ProvisioningArtifactDetails: [{ Active: true, Id: 'provisioningArtifactId' }] }); - // this call has a uuid so just mocking the entire call - scClient.on(ProvisionProductCommand).resolves({}); - // delete the record. mocking entire call - ddbClient.on(DeleteCommand, {}).resolves({}); - const response = await handler({}); - expect(response!.IsComplete).toBeFalsy(); - }); - - test('Event - one account completed', async () => { - // new accounts table has nothing - ddbClient.on(ScanCommand).resolves({ - Items: [], - }); - // products do not have error or tainted status - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: ERROR`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: TAINTED`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: UNDER_CHANGE`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: PLAN_IN_PROGRESS`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - const response = await handler({}); - expect(response!.IsComplete).toBeTruthy(); - }); - - test('Event - one account has error', async () => { - // new accounts table has nothing - ddbClient.on(ScanCommand).resolves({ - Items: [], - }); - // products have error or tainted status - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: ERROR`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ - ProvisionedProducts: [ - { - Name: 'account1', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'ERROR', - }, - ], - }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: TAINTED`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: UNDER_CHANGE`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: PLAN_IN_PROGRESS`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - await expect(handler({})).rejects.toThrow( - 'Accounts failed to enroll in Control Tower. Check Service Catalog Console', - ); - }); - - test('Event - one account has been tainted', async () => { - // new accounts table has nothing - ddbClient.on(ScanCommand).resolves({ - Items: [], - }); - // product has tainted status - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: ERROR`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: TAINTED`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ - ProvisionedProducts: [ - { - Name: 'account2', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'TAINTED', - }, - ], - }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: UNDER_CHANGE`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: PLAN_IN_PROGRESS`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - await expect(handler({})).rejects.toThrow( - 'Accounts failed to enroll in Control Tower. Check Service Catalog Console', - ); - }); - - test('Event - one account is being created', async () => { - // new accounts table has nothing - ddbClient.on(ScanCommand).resolves({ - Items: [], - }); - // product has plan_in_progress status - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: ERROR`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: TAINTED`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ - ProvisionedProducts: [], - }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: UNDER_CHANGE`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ ProvisionedProducts: [] }); - scClient - .on(SearchProvisionedProductsCommand, { - Filters: { - SearchQuery: [`status: PLAN_IN_PROGRESS`], - }, - AccessLevelFilter: { - Key: 'Account', - Value: 'self', - }, - }) - .resolves({ - ProvisionedProducts: [ - { - Name: 'account2', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'PLAN_IN_PROGRESS', - }, - ], - }); - const response = await handler({}); - expect(response?.IsComplete).toBeFalsy(); - }); - - test('Event - no Control Tower product', async () => { - // account to add - ddbClient.on(ScanCommand, { TableName: '', ProjectionExpression: 'accountEmail' }).resolves({ - Items: [{ accountEmail: 'example@example.com' }], - }); - // get account details - ddbClient.on(ScanCommand, { TableName: '', Limit: 1 }).resolves({ - Items: [ - { - accountEmail: 'example@example.com', - accountConfig: JSON.stringify({ - name: 'name', - description: 'description', - email: 'example@example.com', - enableGovCloud: false, - organizationalUnitId: 'ou-id', - }), - }, - ], - }); - // lookup products on any state is empty. This is like a new account which is running for the first time - scClient.on(SearchProvisionedProductsCommand).resolves({ ProvisionedProducts: [] }); - // lookup and get CT product ID - scClient - .on(SearchProductsCommand, { - Filters: { FullTextSearch: ['AWS Control Tower Account Factory'] }, - }) - .resolves({ ProductViewSummaries: [] }); - - await expect(handler({})).rejects.toThrow( - 'No products were found while searching for AWS Control Tower Account Factory', - ); - }); - - test('Event - more than 1 Control Tower product', async () => { - // account to add - ddbClient.on(ScanCommand, { TableName: '', ProjectionExpression: 'accountEmail' }).resolves({ - Items: [{ accountEmail: 'example@example.com' }], - }); - // get account details - ddbClient.on(ScanCommand, { TableName: '', Limit: 1 }).resolves({ - Items: [ - { - accountEmail: 'example@example.com', - accountConfig: JSON.stringify({ - name: 'name', - description: 'description', - email: 'example@example.com', - enableGovCloud: false, - organizationalUnitId: 'ou-id', - }), - }, - ], - }); - // lookup products on any state is empty. This is like a new account which is running for the first time - scClient.on(SearchProvisionedProductsCommand).resolves({ ProvisionedProducts: [] }); - // lookup and get CT product ID - scClient - .on(SearchProductsCommand, { - Filters: { FullTextSearch: ['AWS Control Tower Account Factory'] }, - }) - .resolves({ ProductViewSummaries: [{ ProductId: 'ProductId1' }, { ProductId: 'ProductId2' }] }); - await expect(handler({})).rejects.toThrow( - 'Multiple products were found while searching for AWS Control Tower Account Factory', - ); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/static-input.ts deleted file mode 100644 index ba725a2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/test/static-input.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly fiveAccountsProvisioning = { - ProvisionedProducts: [ - { - Name: 'account1', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'UNDER_CHANGE', - }, - { - Name: 'account2', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'UNDER_CHANGE', - }, - { - Name: 'account3', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'UNDER_CHANGE', - }, - { - Name: 'account4', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'UNDER_CHANGE', - }, - { - Name: 'account5', - Type: 'CONTROL_TOWER_ACCOUNT', - Status: 'UNDER_CHANGE', - }, - ], - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts-status/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts.ts b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts.ts deleted file mode 100644 index c93b299..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; - -import path = require('path'); - -/** - * Control create accounts - */ -export interface CreateControlTowerAccountsProps { - readonly table: cdk.aws_dynamodb.ITable; - readonly portfolioId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class Create Accounts when Control Tower Enabled - */ -export class CreateControlTowerAccounts extends Construct { - readonly onEvent: cdk.aws_lambda.IFunction; - readonly isComplete: cdk.aws_lambda.IFunction; - readonly provider: cdk.custom_resources.Provider; - readonly id: string; - - constructor(scope: Construct, id: string, props: CreateControlTowerAccountsProps) { - super(scope, id); - - const CREATE_CONTROL_TOWER_ACCOUNTS = 'Custom::CreateControlTowerAccounts'; - - this.onEvent = new cdk.aws_lambda.Function(this, 'CreateControlTowerAccount', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-accounts/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(1), - description: 'Create Control Tower Account onEvent handler', - environmentEncryption: props.kmsKey, - }); - const onEventLogGroup = new cdk.aws_logs.LogGroup(this, `${this.onEvent.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.onEvent.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const ddbPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'DynamoDb', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:Scan', 'dynamodb:GetItem', 'dynamodb:DeleteItem'], - resources: [props.table.tableArn], - }); - const ddbKmsPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'KMS', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: [props.table.encryptionKey?.keyArn as string], - }); - const scPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'ServiceCatalog', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'servicecatalog:SearchProvisionedProducts', - 'servicecatalog:ProvisionProduct', - 'servicecatalog:DescribeProduct', - 'servicecatalog:ListProvisioningArtifacts', - 'servicecatalog:DescribeProvisionedProduct', - ], - resources: ['*'], - }); - const ctPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'ControlTower', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'controltower:CreateManagedAccount', - 'controltower:SetupLandingZone', - 'controltower:EnableGuardrail', - 'controltower:Describe*', - 'controltower:Get*', - 'controltower:List*', - ], - resources: ['*'], - }); - const ssoPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'SSO', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'sso-directory:DescribeDirectory', - 'sso-directory:CreateUser', - 'sso-directory:SearchUsers', - 'sso-directory:SearchGroups', - 'sso:ListDirectoryAssociations', - 'sso:DescribeRegisteredRegions', - 'sso:ListProfileAssociations', - 'sso:AssociateProfile', - 'sso:GetProfile', - 'sso:CreateProfile', - 'sso:UpdateProfile', - 'sso:GetTrust', - 'sso:CreateTrust', - 'sso:UpdateTrust', - 'sso:GetApplicationInstance', - 'sso:CreateApplicationInstance', - 'sso:ListPermissionSets', - 'sso:GetSSOStatus', - ], - resources: ['*'], - }); - this.isComplete = new cdk.aws_lambda.Function(this, 'CreateControlTowerAccountStatus', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-accounts-status/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(5), - description: 'Create Control Tower Account isComplete handler', - environment: { NewAccountsTableName: props.table.tableName }, - initialPolicy: [ddbPolicy, ddbKmsPolicy, ctPolicy, ssoPolicy], - environmentEncryption: props.kmsKey, - }); - const isCompleteLogGroup = new cdk.aws_logs.LogGroup(this, `${this.isComplete.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.isComplete.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - this.isComplete.role?.addManagedPolicy( - cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AWSServiceCatalogEndUserFullAccess'), - ); - this.isComplete.addToRolePolicy(scPolicy); - - new cdk.aws_servicecatalog.CfnPortfolioPrincipalAssociation(this, 'LambdaPrincipalAssociation', { - portfolioId: props.portfolioId, - principalArn: this.isComplete.role?.roleArn ?? '', - principalType: 'IAM', - }); - - this.provider = new cdk.custom_resources.Provider(this, 'CreateControlTowerAcccountsProvider', { - onEventHandler: this.onEvent, - isCompleteHandler: this.isComplete, - queryInterval: cdk.Duration.seconds(30), - totalTimeout: cdk.Duration.hours(4), - }); - - // - // Custom Resource definition. We want this resource to be evaluated on - // every CloudFormation update, so we generate a new uuid to force - // re-evaluation. - // - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: CREATE_CONTROL_TOWER_ACCOUNTS, - serviceToken: this.provider.serviceToken, - properties: { - uuid: uuidv4(), - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(isCompleteLogGroup); - resource.node.addDependency(onEventLogGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/index.ts deleted file mode 100644 index fc63176..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * aws-controltower-create-accounts - lambda handler - * - * @param event - * @returns - */ -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - IsComplete: boolean; - } - | undefined -> { - switch (event.RequestType) { - case 'Create': - case 'Update': - return { - IsComplete: false, - }; - - case 'Delete': - // Do Nothing - return { - IsComplete: true, - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/package.json deleted file mode 100644 index bba852b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-control-tower-create-accounts", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-controltower/create-accounts/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/index.ts deleted file mode 100644 index 975274d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/index.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent, Context } from '@aws-accelerator/utils/lib/common-types'; - -AWS.config.logger = console; - -/** - * cross-region-report-definition - lambda handler - * - * @param event, context - * @returns - */ - -export async function handler( - event: CloudFormationCustomResourceEvent, - context: Context, -): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - interface ReportDefinition { - ReportName: string; - TimeUnit: string; - Format: string; - Compression: string; - S3Bucket: string; - S3Prefix: string; - S3Region: string; - AdditionalSchemaElements: string[]; - AdditionalArtifacts?: string[]; - RefreshClosedReports?: boolean; - ReportVersioning?: string; - BillingViewArn?: string; - } - - const partition = context.invokedFunctionArn.split(':')[1]; - - let globalRegion = 'us-east-1'; - if (partition === 'aws-cn') { - globalRegion = 'cn-northwest-1'; - } - - const reportDefinition: ReportDefinition = event.ResourceProperties['reportDefinition']; - const solutionId = process.env['SOLUTION_ID']; - const curClient = new AWS.CUR({ region: globalRegion, customUserAgent: solutionId }); - - // Handle case where boolean is passed as string - if (reportDefinition.RefreshClosedReports) { - reportDefinition.RefreshClosedReports = returnBoolean(reportDefinition.RefreshClosedReports.toString()); - } - - switch (event.RequestType) { - case 'Create': - // Create new report definition - console.log(`Creating new report definition ${reportDefinition.ReportName}`); - await throttlingBackOff(() => curClient.putReportDefinition({ ReportDefinition: reportDefinition }).promise()); - - return { - PhysicalResourceId: reportDefinition.ReportName, - Status: 'SUCCESS', - }; - - case 'Update': - // Modify report definition - console.log(`Modifying report definition ${reportDefinition.ReportName}`); - await throttlingBackOff(() => - curClient - .modifyReportDefinition({ ReportName: reportDefinition.ReportName, ReportDefinition: reportDefinition }) - .promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - // Delete report definition - console.log(`Deleting report definition ${event.PhysicalResourceId}`); - await throttlingBackOff(() => - curClient.deleteReportDefinition({ ReportName: event.PhysicalResourceId }).promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -function returnBoolean(input: string): boolean | undefined { - try { - return JSON.parse(input.toLowerCase()); - } catch (e) { - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/package.json deleted file mode 100644 index 5c9ceaf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-cur-cross-region-report-definition", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cur/cross-region-report-definition/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-cur/report-definition.ts b/source/packages/@aws-accelerator/constructs/lib/aws-cur/report-definition.ts deleted file mode 100644 index be4472c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-cur/report-definition.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IReportDefinition extends cdk.IResource { - /** - * The name of the report that you want to create. - * @attribute - */ - readonly reportName: string; -} - -type Compression = 'ZIP' | 'GZIP' | 'Parquet' | string; - -type Format = 'textORcsv' | 'Parquet' | string; - -type ReportVersioning = 'CREATE_NEW_REPORT' | 'OVERWRITE_REPORT' | string; - -type TimeUnit = 'HOURLY' | 'DAILY' | 'MONTHLY' | string; - -type AdditionalArtifacts = 'REDSHIFT' | 'QUICKSIGHT' | 'ATHENA' | string; - -export interface ReportDefinitionProps { - /** - * The compression format that Amazon Web Services uses for the report. - * - */ - readonly compression: Compression; - - /** - * The format that Amazon Web Services saves the report in. - * - */ - readonly format: Format; - - /** - * Whether you want Amazon Web Services to update your reports after they have been finalized if - * Amazon Web Services detects charges related to previous months. - * - */ - readonly refreshClosedReports: boolean | cdk.IResolvable; - - /** - * The name of the report that you want to create. - * - * @default - A CDK generated name - */ - readonly reportName: string; - - /** - * Whether you want Amazon Web Services to overwrite the previous version of each report or to - * deliver the report in addition to the previous versions. - * - */ - readonly reportVersioning: ReportVersioning; - - /** - * The S3 bucket where Amazon Web Services delivers the report. - * - */ - readonly s3Bucket: cdk.aws_s3.IBucket; - - /** - * The prefix that Amazon Web Services adds to the report name when Amazon Web Services delivers the report. - * - */ - readonly s3Prefix: string; - - /** - * The Region of the S3 bucket that Amazon Web Services delivers the report into. - * - */ - readonly s3Region: string; - - /** - * The granularity of the line items in the report. - * - */ - readonly timeUnit: TimeUnit; - - /** - * A list of manifests that you want Amazon Web Services to create for this report. - * - * @default - no additional artifacts - */ - readonly additionalArtifacts?: AdditionalArtifacts[]; - - /** - * A list of strings that indicate additional content that Amazon Web Services includes in - * the report, such as individual resource IDs. - * - * @default - no additional schema elements - */ - readonly additionalSchemaElements?: string[]; - - /** - * The Amazon Resource Name (ARN) of the billing view. - * - * @default - no billing view ARN - */ - readonly billingViewArn?: string; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The partition of this stack. - */ - readonly partition: string; -} - -export class ReportDefinition extends cdk.Resource implements IReportDefinition { - public readonly reportName: string; - private readonly globalRegion: string; - - constructor(scope: Construct, id: string, props: ReportDefinitionProps) { - super(scope, id, { - physicalName: props.reportName, - }); - - this.reportName = this.physicalName; - - if (props.partition === 'aws-cn') { - this.globalRegion = 'cn-northwest-1'; - } else { - this.globalRegion = 'us-east-1'; - } - - // Cfn resource AWS::CUR::ReportDefinition is available in region us-east-1 only. - if (cdk.Stack.of(this).region === 'us-east-1') { - // Use native Cfn construct - new cdk.aws_cur.CfnReportDefinition(this, 'Resource', { - compression: props.compression, - format: props.format, - refreshClosedReports: props.refreshClosedReports, - reportName: props.reportName, - reportVersioning: props.reportVersioning, - s3Bucket: props.s3Bucket.bucketName, - s3Prefix: props.s3Prefix, - s3Region: props.s3Region, - timeUnit: props.timeUnit, - additionalArtifacts: props.additionalArtifacts, - additionalSchemaElements: props.additionalSchemaElements, - billingViewArn: props.billingViewArn, - }); - } else { - // Use custom resource - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::CrossRegionReportDefinition', { - codeDirectory: path.join(__dirname, 'cross-region-report-definition/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['cur:DeleteReportDefinition', 'cur:ModifyReportDefinition', 'cur:PutReportDefinition'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::CrossRegionReportDefinition', - serviceToken: provider.serviceToken, - properties: { - reportDefinition: { - ReportName: props.reportName, - TimeUnit: props.timeUnit, - Format: props.format, - Compression: props.compression, - S3Bucket: props.s3Bucket.bucketName, - S3Prefix: props.s3Prefix, - S3Region: props.s3Region, - AdditionalSchemaElements: props.additionalSchemaElements ?? [], - AdditionalArtifacts: props.additionalArtifacts, - RefreshClosedReports: props.refreshClosedReports, - ReportVersioning: props.reportVersioning, - BillingViewArn: props.billingViewArn, - }, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } - - // Add bucket policy - const policy = this.addBucketPolicy(props.s3Bucket); - this.node.addDependency(policy); - } - - private addBucketPolicy(bucket: cdk.aws_s3.IBucket): cdk.aws_s3.BucketPolicy { - const _stmt1: cdk.aws_iam.PolicyStatement = new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetBucketAcl', 's3:GetBucketPolicy'], - principals: [new cdk.aws_iam.ServicePrincipal('billingreports.amazonaws.com')], - resources: [bucket.bucketArn], - conditions: { - StringEquals: { - 'aws:SourceArn': `arn:${cdk.Aws.PARTITION}:cur:${this.globalRegion}:${cdk.Aws.ACCOUNT_ID}:definition/*`, - 'aws:SourceAccount': `${cdk.Aws.ACCOUNT_ID}`, - }, - }, - }); - - const _stmt2: cdk.aws_iam.PolicyStatement = new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutObject'], - principals: [new cdk.aws_iam.ServicePrincipal('billingreports.amazonaws.com')], - resources: [bucket.arnForObjects('*')], - conditions: { - StringEquals: { - 'aws:SourceArn': `arn:${cdk.Aws.PARTITION}:cur:${this.globalRegion}:${cdk.Aws.ACCOUNT_ID}:definition/*`, - 'aws:SourceAccount': `${cdk.Aws.ACCOUNT_ID}`, - }, - }, - }); - - bucket.addToResourcePolicy(_stmt1); - bucket.addToResourcePolicy(_stmt2); - - return bucket.policy!; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/index.ts deleted file mode 100644 index 008d20f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/index.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { chunkArray } from '@aws-accelerator/utils/lib/common-functions'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * enable-detective - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const partition = event.ResourceProperties['partition']; - const solutionId = process.env['SOLUTION_ID']; - const chunkSize = process.env['CHUNK_SIZE'] ? parseInt(process.env['CHUNK_SIZE']) : 50; - - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1', customUserAgent: solutionId }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - - const detectiveClient = new AWS.Detective({ region: region, customUserAgent: solutionId }); - - const graphArn = await getGraphArn(detectiveClient); - - let nextToken: string | undefined = undefined; - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('starting - CreateMembersCommand'); - const allAccounts: AWS.Detective.Account[] = []; - - do { - const page = await throttlingBackOff(() => - organizationsClient.listAccounts({ NextToken: nextToken }).promise(), - ); - for (const account of page.Accounts ?? []) { - allAccounts.push({ AccountId: account.Id!, EmailAddress: account.Email! }); - } - nextToken = page.NextToken; - } while (nextToken); - - const chunkedAccountsForCreate = chunkArray(allAccounts, chunkSize); - - for (const accounts of chunkedAccountsForCreate) { - console.log(`Initiating createMembers request for ${accounts.length} accounts`); - await throttlingBackOff(() => - detectiveClient.createMembers({ GraphArn: graphArn!, Accounts: accounts }).promise(), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const existingMemberAccountIds: string[] = []; - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - detectiveClient.listMembers({ GraphArn: graphArn!, NextToken: nextToken }).promise(), - ); - for (const member of page.MemberDetails ?? []) { - console.log(member); - existingMemberAccountIds.push(member.AccountId!); - } - nextToken = page.NextToken; - } while (nextToken); - - if (existingMemberAccountIds.length > 0) { - const chunkedAccountsForDelete = chunkArray(existingMemberAccountIds, chunkSize); - - for (const existingMemberAccountIdBatch of chunkedAccountsForDelete) { - console.log(`Initiating deleteMembers request for ${existingMemberAccountIdBatch.length} accounts`); - await throttlingBackOff(() => - detectiveClient.deleteMembers({ AccountIds: existingMemberAccountIdBatch, GraphArn: graphArn! }).promise(), - ); - } - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function getGraphArn(detectiveClient: AWS.Detective): Promise { - const response = await throttlingBackOff(() => detectiveClient.listGraphs({}).promise()); - console.log(response); - if (response.GraphList!.length === 0) { - throw new Error( - 'Could not find graph. It does not look like this account has been set as the delegated administrator for AWS Detective.', - ); - } - return response.GraphList!.length === 1 ? response.GraphList![0].Arn : undefined; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/package.json deleted file mode 100644 index d899ef6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-detective-create-members", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/create-members/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-graph-config.ts b/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-graph-config.ts deleted file mode 100644 index 2a60e4b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-graph-config.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized DetectiveGraphConfigProps properties - */ -export interface DetectiveGraphConfigProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - /** - * Class to Detective Graph Members - */ -export class DetectiveGraphConfig extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: DetectiveGraphConfigProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::DetectiveUpdateGraph'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'update-graph-config/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'DetectiveConfigureOrganizationAdminAccountTaskOrganizationActions', - Effect: 'Allow', - Action: [ - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:DescribeOrganization', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListAccounts', - 'organizations:ListDelegatedAdministrators', - 'organizations:RegisterDelegatedAdministrator', - 'organizations:ServicePrincipal', - 'organizations:UpdateOrganizationConfiguration', - ], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:DeregisterDelegatedAdministrator': ['detective.amazonaws.com'], - 'organizations:DescribeOrganization': ['detective.amazonaws.com'], - 'organizations:EnableAWSServiceAccess': ['detective.amazonaws.com'], - 'organizations:ListAWSServiceAccessForOrganization': ['detective.amazonaws.com'], - 'organizations:ListAccounts': ['detective.amazonaws.com'], - 'organizations:ListDelegatedAdministrators': ['detective.amazonaws.com'], - 'organizations:RegisterDelegatedAdministrator': ['detective.amazonaws.com'], - 'organizations:ServicePrincipal': ['detective.amazonaws.com'], - 'organizations:UpdateOrganizationConfiguration': ['detective.amazonaws.com'], - }, - }, - }, - { - Sid: 'DetectiveUpdateGraphTaskDetectiveActions', - Effect: 'Allow', - Action: ['detective:UpdateOrganizationConfiguration', 'detective:ListGraphs', 'detective:ListMembers'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-members.ts b/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-members.ts deleted file mode 100644 index 0c5960e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-members.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized DetectiveMembersProps properties - */ -export interface DetectiveMembersProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - /** - * Class to Detective Members - */ -export class DetectiveMembers extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: DetectiveMembersProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::DetectiveCreateMembers'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'create-members/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'DetectiveCreateMembersTaskDetectiveActions', - Effect: 'Allow', - Action: [ - 'detective:ListOrganizationAdminAccounts', - 'detective:UpdateOrganizationConfiguration', - 'detective:CreateMembers', - 'detective:DeleteMembers', - 'detective:DisassociateMembership', - 'detective:ListMembers', - 'detective:ListGraphs', - ], - Resource: '*', - }, - { - Sid: 'ServiceLinkedRoleDetective', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: ['*'], - }, - { - Sid: 'OrganisationsListDetective', - Effect: 'Allow', - Action: ['organizations:ListAccounts'], - Resource: ['*'], - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - partition: cdk.Aws.PARTITION, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-organization-admin-account.ts deleted file mode 100644 index 911f989..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/detective-organization-admin-account.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized DetectiveOrganizationalAdminAccountProps properties - */ -export interface DetectiveOrganizationalAdminAccountProps { - /** - * Admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class for DetectiveOrganizationAdminAccount - */ -export class DetectiveOrganizationAdminAccount extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: DetectiveOrganizationalAdminAccountProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::DetectiveEnableOrganizationAdminAccount'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'enable-organization-admin-account/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'DetectiveEnableOrganizationAdminAccountTaskOrganizationActions', - Effect: 'Allow', - Action: [ - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:DescribeOrganization', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListAccounts', - 'organizations:ListDelegatedAdministrators', - 'organizations:RegisterDelegatedAdministrator', - 'organizations:ServicePrincipal', - 'organizations:UpdateOrganizationConfiguration', - ], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:DeregisterDelegatedAdministrator': ['detective.amazonaws.com'], - 'organizations:DescribeOrganization': ['detective.amazonaws.com'], - 'organizations:EnableAWSServiceAccess': ['detective.amazonaws.com'], - 'organizations:ListAWSServiceAccessForOrganization': ['detective.amazonaws.com'], - 'organizations:ListAccounts': ['detective.amazonaws.com'], - 'organizations:ListDelegatedAdministrators': ['detective.amazonaws.com'], - 'organizations:RegisterDelegatedAdministrator': ['detective.amazonaws.com'], - 'organizations:ServicePrincipal': ['detective.amazonaws.com'], - 'organizations:UpdateOrganizationConfiguration': ['detective.amazonaws.com'], - }, - }, - }, - { - Sid: 'DetectiveEnableOrganizationAdminAccountTaskDetectiveActions', - Effect: 'Allow', - Action: [ - 'detective:EnableOrganizationAdminAccount', - 'detective:ListOrganizationAdminAccounts', - 'detective:DisableOrganizationAdminAccount', - 'detective:EnableOrganizationAdminAccount', - 'detective:ListOrganizationAdminAccount', - ], - Resource: '*', - }, - { - Sid: 'ServiceLinkedRoleDetective', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: ['*'], - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - adminAccountId: props.adminAccountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/index.ts deleted file mode 100644 index ca5c311..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - Administrator, - DetectiveClient, - ListOrganizationAdminAccountsCommand, - DisableOrganizationAdminAccountCommand, - EnableOrganizationAdminAccountCommand, -} from '@aws-sdk/client-detective'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -/** - * enable-detective - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const adminAccountId = event.ResourceProperties['adminAccountId']; - const solutionId = process.env['SOLUTION_ID']; - - const detectiveClient = new DetectiveClient({ region: region, customUserAgent: solutionId }); - - const detectiveAdminAccount = await isDetectiveEnable(detectiveClient); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (detectiveAdminAccount.accountId === undefined) { - console.log( - `Started enableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - detectiveClient.send(new EnableOrganizationAdminAccountCommand({ AccountId: adminAccountId })), - ); - return { Status: 'Success', StatusCode: 200 }; - } else { - if (detectiveAdminAccount.accountId === adminAccountId) { - console.warn( - `Detective admin account ${detectiveAdminAccount.accountId} is already an admin account, in ${region} region. No action needed`, - ); - return { Status: 'Success', StatusCode: 200 }; - } - if (detectiveAdminAccount.accountId !== adminAccountId) { - console.warn( - `Detective delegated admin is already set to ${detectiveAdminAccount.accountId} account can not assign another delegated account`, - ); - return { Status: 'Success', StatusCode: 200 }; - } - - return { Status: 'Success', StatusCode: 200 }; - } - - case 'Delete': - if (detectiveAdminAccount.accountId) { - if (detectiveAdminAccount.accountId === adminAccountId) { - console.log( - `Started disableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - detectiveClient.send(new DisableOrganizationAdminAccountCommand({ AccountId: adminAccountId })); - } - } else { - if (detectiveAdminAccount.accountId !== adminAccountId) { - console.warn( - `Detective delegated admin is already set to ${detectiveAdminAccount.accountId} account which differs from the config. Skipping the removal of the delegated admin for AWS Detective.`, - ); - } - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function isDetectiveEnable(detectiveClient: DetectiveClient): Promise<{ accountId: string | undefined }> { - const adminAccounts: Administrator[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - detectiveClient.send(new ListOrganizationAdminAccountsCommand({ NextToken: nextToken })), - ); - for (const account of page.Administrators ?? []) { - console.log(account); - adminAccounts.push(account); - } - nextToken = page.NextToken; - } while (nextToken); - if (adminAccounts.length === 0) { - return { accountId: undefined }; - } - if (adminAccounts.length > 1) { - throw new Error('Multiple admin accounts for Detective in organization'); - } - - return { accountId: adminAccounts[0].AccountId }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/package.json deleted file mode 100644 index cf98f0f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-detective-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-detective": "3.410.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/index.ts deleted file mode 100644 index c56baa6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/index.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { - DetectiveClient, - UpdateOrganizationConfigurationCommand, - ListGraphsCommand, - ListMembersCommand, - ListMembersCommandOutput, -} from '@aws-sdk/client-detective'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -/** - * DetectiveUpdateGraph - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - const detectiveClient = new DetectiveClient({ region: region, customUserAgent: solutionId }); - const graphArn = await getGraphArn(detectiveClient); - let nextToken: string | undefined = undefined; - do { - const page: ListMembersCommandOutput = await detectiveClient.send( - new ListMembersCommand({ GraphArn: graphArn!, NextToken: nextToken }), - ); - nextToken = page.NextToken; - } while (nextToken); - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('starting - CreateMembersCommand'); - await detectiveClient.send(new UpdateOrganizationConfigurationCommand({ AutoEnable: true, GraphArn: graphArn! })); - return { Status: 'Success', StatusCode: 200 }; - case 'Delete': - console.log('deleting - CreateMembersCommand'); - await detectiveClient.send( - new UpdateOrganizationConfigurationCommand({ AutoEnable: false, GraphArn: graphArn! }), - ); - return { Status: 'Success', StatusCode: 200 }; - } -} -async function getGraphArn(detectiveClient: DetectiveClient): Promise { - const response = await detectiveClient.send(new ListGraphsCommand({})); - console.log(response); - if (response.GraphList!.length === 0) { - throw new Error( - 'Could not find graph. It does not look like this account has been set as the delegated administrator for AWS Detective.', - ); - } - return response.GraphList!.length === 1 ? response.GraphList![0].Arn : undefined; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/package.json deleted file mode 100644 index 6c8b259..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-detective-update-graph-config", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-detective": "3.410.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-detective/update-graph-config/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway.ts deleted file mode 100644 index 3f197c2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IDirectConnectGateway extends cdk.IResource { - /** - * The Direct Connect Gateway ID - */ - readonly directConnectGatewayId: string; - /** - * The friendly name of the Direct Connect Gateway - */ - readonly directConnectGatewayName: string; -} - -export interface DirectConnectGatewayProps { - /** - * The friendly name of the Direct Connect Gateway - */ - readonly gatewayName: string; - /** - * The Border Gateway Protocol (BGP) autonomous system number (ASN) - * of the Direct Connect Gateway - */ - readonly asn: number; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class DirectConnectGateway extends cdk.Resource implements IDirectConnectGateway { - public readonly directConnectGatewayId: string; - public readonly directConnectGatewayName: string; - - constructor(scope: Construct, id: string, props: DirectConnectGatewayProps) { - super(scope, id); - - this.directConnectGatewayName = props.gatewayName; - const RESOURCE_TYPE = 'Custom::DirectConnectGateway'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'direct-connect-gateway/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'DirectConnectGatewayCRUD', - Effect: 'Allow', - Action: [ - 'directconnect:CreateDirectConnectGateway', - 'directconnect:DeleteDirectConnectGateway', - 'directconnect:UpdateDirectConnectGateway', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - gatewayName: props.gatewayName, - asn: props.asn, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.directConnectGatewayId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/index.ts deleted file mode 100644 index bf5a34e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * direct-connect-gateway - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - // Set variables - const directConnectGatewayName: string = event.ResourceProperties['gatewayName']; - const amazonSideAsn: number = event.ResourceProperties['asn']; - const solutionId = process.env['SOLUTION_ID']; - - const dx = new AWS.DirectConnect({ customUserAgent: solutionId }); - - // Event handler - switch (event.RequestType) { - case 'Create': - const response = await throttlingBackOff(() => - dx.createDirectConnectGateway({ directConnectGatewayName, amazonSideAsn }).promise(), - ); - - if (!response.directConnectGateway?.directConnectGatewayId) { - throw new Error(`Error creating Direct Connect Gateway; unable to retrieve ID value.`); - } - - return { - PhysicalResourceId: response.directConnectGateway.directConnectGatewayId, - Status: 'SUCCESS', - }; - - case 'Update': - if (event.OldResourceProperties['asn'] !== amazonSideAsn) { - console.warn( - `Cannot update Amazon side ASN for Direct Connect Gateways. Please delete and recreate the gateway instead.`, - ); - } - - if (event.OldResourceProperties['gatewayName'] !== directConnectGatewayName) { - console.log( - `Updating Direct Connect Gateway ${event.PhysicalResourceId} name from ${event.OldResourceProperties['gatewayName']} to ${directConnectGatewayName}`, - ); - await throttlingBackOff(() => - dx - .updateDirectConnectGateway({ - directConnectGatewayId: event.PhysicalResourceId, - newDirectConnectGatewayName: directConnectGatewayName, - }) - .promise(), - ); - } - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - dx.deleteDirectConnectGateway({ directConnectGatewayId: event.PhysicalResourceId }).promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/package.json deleted file mode 100644 index 0254d76..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directconnect-direct-connect-gateway", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/direct-connect-gateway/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/index.ts deleted file mode 100644 index 9b735f7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * direct-connect-gateway-association-proposal - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - // Set variables - const allowedPrefixesInitial: string[] = event.ResourceProperties['allowedPrefixes']; - const directConnectGatewayId: string = event.ResourceProperties['directConnectGatewayId']; - const directConnectGatewayOwnerAccount: string = event.ResourceProperties['directConnectGatewayOwnerAccount']; - const solutionId = process.env['SOLUTION_ID']; - const dx = new AWS.DirectConnect({ customUserAgent: solutionId }); - const gatewayId: string = event.ResourceProperties['gatewayId']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - const allowedPrefixes = allowedPrefixesInitial.map(item => { - return { cidr: item }; - }); - // Create gateway association - const response = await throttlingBackOff(() => - dx - .createDirectConnectGatewayAssociationProposal({ - directConnectGatewayId, - directConnectGatewayOwnerAccount, - addAllowedPrefixesToDirectConnectGateway: allowedPrefixes, - gatewayId, - }) - .promise(), - ); - const associationId = response.directConnectGatewayAssociationProposal?.proposalId; - - // Validate associationId exists - if (!associationId) { - throw new Error( - `Unable to associate Direct Connect Gateway ${directConnectGatewayId} with gateway ${gatewayId}`, - ); - } - - return { - PhysicalResourceId: associationId, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - dx.deleteDirectConnectGatewayAssociationProposal({ proposalId: event.PhysicalResourceId }).promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/package.json deleted file mode 100644 index 79c1c1d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directconnect-gateway-association-proposal", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association-proposal/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association.ts deleted file mode 100644 index 2258448..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IDirectConnectGatewayAssociation extends cdk.IResource { - /** - * The Direct Connect Gateway association ID - */ - readonly associationId: string; - /** - * The transit gateway attachment ID of the gateway association - */ - readonly transitGatewayAttachmentId?: string; -} - -export interface DirectConnectGatewayAssociationProps { - /** - * The Amazon VPC prefixes to advertise to the Direct Connect gateway - */ - readonly allowedPrefixes: string[]; - /** - * The ID of the Direct Connect Gateway - */ - readonly directConnectGatewayId: string; - /** - * The ID of the transit gateway or virtual private gateway - */ - readonly gatewayId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The owner account ID of the Direct Connect Gateway - */ - readonly directConnectGatewayOwnerAccount?: string; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; -} - -export class DirectConnectGatewayAssociation extends cdk.Resource implements IDirectConnectGatewayAssociation { - public readonly associationId: string; - public readonly transitGatewayAttachmentId?: string; - - constructor(scope: Construct, id: string, props: DirectConnectGatewayAssociationProps) { - super(scope, id); - - let codeDirectory: string; - let RESOURCE_TYPE: string; - let policyStatements; - - if (props.directConnectGatewayOwnerAccount) { - codeDirectory = path.join(__dirname, 'gateway-association-proposal/dist'); - RESOURCE_TYPE = 'Custom::DirectConnectGatewayAssociationProposal'; - policyStatements = [ - { - Sid: 'GatewayAssociationProposalCRUD', - Effect: 'Allow', - Action: [ - 'directconnect:CreateDirectConnectGatewayAssociationProposal', - 'directconnect:DeleteDirectConnectGatewayAssociationProposal', - ], - Resource: '*', - }, - ]; - } else { - codeDirectory = path.join(__dirname, 'gateway-association/dist'); - RESOURCE_TYPE = 'Custom::DirectConnectGatewayAssociation'; - policyStatements = [ - { - Sid: 'DirectConnectGatewayCRUD', - Effect: 'Allow', - Action: [ - 'directconnect:CreateDirectConnectGatewayAssociation', - 'directconnect:DeleteDirectConnectGatewayAssociation', - 'directconnect:DescribeDirectConnectGatewayAssociations', - 'directconnect:UpdateDirectConnectGatewayAssociation', - 'ec2:DescribeTransitGatewayAttachments', - ], - Resource: '*', - }, - { - Sid: 'InvokeSelf', - Effect: 'Allow', - Action: ['lambda:InvokeFunction'], - Resource: `arn:${cdk.Aws.PARTITION}:lambda:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:function:${props.acceleratorPrefix}-NetworkAss-CustomDirectConnect*`, - }, - ]; - } - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory, - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements, - timeout: cdk.Duration.minutes(15), - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - allowedPrefixes: props.allowedPrefixes, - directConnectGatewayId: props.directConnectGatewayId, - directConnectGatewayOwnerAccount: props.directConnectGatewayOwnerAccount, - gatewayId: props.gatewayId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.associationId = resource.ref; - if (!props.directConnectGatewayOwnerAccount) { - this.transitGatewayAttachmentId = resource.getAttString('TransitGatewayAttachmentId'); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/index.ts deleted file mode 100644 index 6c4884e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/index.ts +++ /dev/null @@ -1,312 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * direct-connect-gateway-association - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Data: { - TransitGatewayAttachmentId: string; - }; - Status: string; - } - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - // Set variables - const allowedPrefixesInitial: string[] = event.ResourceProperties['allowedPrefixes']; - const directConnectGatewayId: string = event.ResourceProperties['directConnectGatewayId']; - const solutionId = process.env['SOLUTION_ID']; - const dx = new AWS.DirectConnect({ customUserAgent: solutionId }); - const ec2 = new AWS.EC2({ customUserAgent: solutionId }); - const gatewayId: string = event.ResourceProperties['gatewayId']; - const lambdaClient = new AWS.Lambda({ customUserAgent: solutionId }); - let attachmentId: string | undefined = undefined; - - switch (event.RequestType) { - case 'Create': - const allowedPrefixes = allowedPrefixesInitial.map(item => { - return { cidr: item }; - }); - // Create gateway association - const response = await throttlingBackOff(() => - dx - .createDirectConnectGatewayAssociation({ - directConnectGatewayId, - addAllowedPrefixesToDirectConnectGateway: allowedPrefixes, - gatewayId, - }) - .promise(), - ); - const associationId = response.directConnectGatewayAssociation?.associationId; - - // Validate associationId exists - if (!associationId) { - throw new Error( - `Unable to associate Direct Connect Gateway ${directConnectGatewayId} with gateway ${gatewayId}`, - ); - } - - // Validate association state - if (await validateAssociationState(dx, associationId, 'associated')) { - attachmentId = await getDxAttachmentId(ec2, directConnectGatewayId, gatewayId); - return { - PhysicalResourceId: associationId, - Data: { - TransitGatewayAttachmentId: attachmentId, - }, - Status: 'SUCCESS', - }; - } - - // Retry Lambda - await retryLambda(lambdaClient, event); - await sleep(120000); - return; - - case 'Update': - // Update association - if (!(await inProgress(dx, event.PhysicalResourceId))) { - const allowedPrefixesPrevious: string[] = event.OldResourceProperties['allowedPrefixes']; - const [addPrefixes, removePrefixes] = getPrefixUpdates(allowedPrefixesInitial, allowedPrefixesPrevious); - - await throttlingBackOff(() => - dx - .updateDirectConnectGatewayAssociation({ - associationId: event.PhysicalResourceId, - addAllowedPrefixesToDirectConnectGateway: addPrefixes, - removeAllowedPrefixesToDirectConnectGateway: removePrefixes, - }) - .promise(), - ); - } - - if (await validateAssociationState(dx, event.PhysicalResourceId, 'associated')) { - attachmentId = await getDxAttachmentId(ec2, directConnectGatewayId, gatewayId); - return { - PhysicalResourceId: event.PhysicalResourceId, - Data: { - TransitGatewayAttachmentId: attachmentId, - }, - Status: 'SUCCESS', - }; - } - - // Retry Lambda - await retryLambda(lambdaClient, event); - await sleep(120000); - return; - - case 'Delete': - if (!(await inProgress(dx, event.PhysicalResourceId))) { - await throttlingBackOff(() => - dx.deleteDirectConnectGatewayAssociation({ associationId: event.PhysicalResourceId }).promise(), - ); - } - - if (await validateAssociationState(dx, event.PhysicalResourceId, 'disassociated')) { - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } - - // Retry Lambda - await retryLambda(lambdaClient, event); - await sleep(120000); - return; - } -} - -/** - * Validate the gateway association state is `associated` - * or `disassociated` - * @param dx - * @param associationId - * @param expectedState - */ -async function validateAssociationState( - dx: AWS.DirectConnect, - associationId: string, - expectedState: string, -): Promise { - let currentState: string | undefined; - let retries = 0; - - // Describe gateway association until it is in the expected state - do { - const response = await throttlingBackOff(() => - dx.describeDirectConnectGatewayAssociations({ associationId }).promise(), - ); - if (!response.directConnectGatewayAssociations) { - throw new Error(`Unable to retrieve gateway association ${associationId}`); - } - - // Check case where association ID is removed from the list during deletion - if (expectedState === 'disassociated' && response.directConnectGatewayAssociations.length === 0) { - return true; - } - - // Determine association state - currentState = response.directConnectGatewayAssociations[0].associationState; - if (currentState !== expectedState) { - await sleep(60000); - } - - // Increase retry index until timeout nears - retries += 1; - if (retries > 13) { - return false; - } - } while (currentState !== expectedState); - return true; -} - -/** - * Get the transit gateway attachment ID for the Direct Connect Gateway association - * @param directConnectGatewayId - * @returns - */ -async function getDxAttachmentId(ec2: AWS.EC2, directConnectGatewayId: string, gatewayId: string): Promise { - let nextToken: string | undefined = undefined; - let attachmentId: string | undefined = undefined; - - // Get transit gateway attachment ID - do { - const page = await throttlingBackOff(() => - ec2 - .describeTransitGatewayAttachments({ - Filters: [ - { Name: 'resource-id', Values: [directConnectGatewayId] }, - { Name: 'transit-gateway-id', Values: [gatewayId] }, - ], - NextToken: nextToken, - }) - .promise(), - ); - // Set attachment ID - for (const attachment of page.TransitGatewayAttachments ?? []) { - if (attachment.State === 'available') { - attachmentId = attachment.TransitGatewayAttachmentId; - } - } - nextToken = page.NextToken; - } while (nextToken); - - // Validate attachment ID exists - if (!attachmentId) { - throw new Error( - `Unable to retrieve transit gateway attachment ID for Direct Connect Gateway ${directConnectGatewayId}`, - ); - } - return attachmentId; -} - -/** - * Determines the prefixes that need to be added/removed during the update operation. - * @param updatePrefixes - * @param previousPrefixes - */ -function getPrefixUpdates( - updatePrefixes: string[], - previousPrefixes: string[], -): [{ cidr: string }[], { cidr: string }[] | undefined] { - // Determine prefixes that need to be removed - let removePrefixes: { cidr: string }[] | undefined = undefined; - const removePrefixesInitial = previousPrefixes.filter(item => !updatePrefixes.includes(item)); - - if (removePrefixesInitial.length > 0) { - removePrefixes = removePrefixesInitial.map(item => { - return { cidr: item }; - }); - } - - // Convert update prefixes - const addPrefixes = updatePrefixes.map(item => { - return { cidr: item }; - }); - - return [addPrefixes, removePrefixes]; -} - -/** - * Re-invoke the Lambda function if the timeout is close to being reached - * @param lambdaClient - * @param event - */ -async function retryLambda(lambdaClient: AWS.Lambda, event: CloudFormationCustomResourceEvent): Promise { - // Add retry attempt to event - if (!event.ResourceProperties['retryAttempt']) { - event.ResourceProperties['retryAttempt'] = 0; - } - event.ResourceProperties['retryAttempt'] += 1; - - // Throw error for max number of retries - if (event.ResourceProperties['retryAttempt'] > 3) { - throw new Error( - `Exceeded maximum number of retries. Please check the Direct Connect console for the status of your gateway association.`, - ); - } - - // Invoke Lambda - await throttlingBackOff(() => - lambdaClient - .invoke({ FunctionName: event.ServiceToken, InvocationType: 'Event', Payload: JSON.stringify(event) }) - .promise(), - ); -} - -/** - * Check if a mutating action is in progress - * @param dx - * @param associationId - * @returns - */ -async function inProgress(dx: AWS.DirectConnect, associationId: string): Promise { - const response = await throttlingBackOff(() => - dx.describeDirectConnectGatewayAssociations({ associationId }).promise(), - ); - - if (!response.directConnectGatewayAssociations) { - throw new Error(`Unable to retrieve gateway association ${associationId}`); - } - if ( - response.directConnectGatewayAssociations.length === 0 || - ['associated', 'disassociated'].includes(response.directConnectGatewayAssociations[0].associationState!) - ) { - return false; - } - return true; -} - -/** - * Sleep for a specified number of milliseconds - * @param ms - * @returns - */ -async function sleep(ms: number) { - return new Promise(f => setTimeout(f, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/package.json deleted file mode 100644 index 8ad4b63..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directconnect-gateway-association", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/gateway-association/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/attributes.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/attributes.ts deleted file mode 100644 index 2b4bd48..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/attributes.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DirectConnect } from 'aws-sdk'; - -interface VirtualInterfaceAllocationAttributeProps { - /** - * The IP address family - */ - readonly addressFamily: string; - /** - * The BGP ASN of the customer router - */ - readonly asn: number; - /** - * The Direct Connect connection ID - */ - readonly connectionId: string; - /** - * Whether to enable jumbo frames for the virtual interface - */ - readonly jumboFrames: boolean; - /** - * The owner account of the virtual interface - */ - readonly ownerAccount: string; - /** - * Enable SiteLink for the virtual interface - */ - readonly siteLink: boolean; - /** - * The name of the virtual interface - */ - readonly virtualInterfaceName: string; - /** - * The type of the virtual interface - */ - readonly virtualInterfaceType: 'private' | 'transit'; - /** - * The virtual local area network (VLAN) tag - */ - readonly vlan: number; - /** - * The Amazon side peer IP address - */ - readonly amazonAddress?: string; - /** - * The customer side peer IP address - */ - readonly customerAddress?: string; - /** - * Tags for the virtual interface - */ - readonly tags?: DirectConnect.TagList; -} - -export class VirtualInterfaceAllocationAttributes { - public readonly addressFamily: string; - public readonly asn: number; - public readonly connectionId: string; - public readonly mtu: number; - public readonly ownerAccount: string; - public readonly siteLink: boolean; - public readonly virtualInterfaceName: string; - public readonly virtualInterfaceType: 'private' | 'transit'; - public readonly vlan: number; - public readonly amazonAddress?: string; - public readonly customerAddress?: string; - public readonly tags?: DirectConnect.TagList; - constructor(props: VirtualInterfaceAllocationAttributeProps) { - this.addressFamily = props.addressFamily; - this.amazonAddress = props.amazonAddress; - this.asn = props.asn; - this.connectionId = props.connectionId; - this.customerAddress = props.customerAddress; - this.ownerAccount = props.ownerAccount; - this.siteLink = props.siteLink; - this.tags = props.tags; - this.virtualInterfaceName = props.virtualInterfaceName; - this.virtualInterfaceType = props.virtualInterfaceType; - this.vlan = props.vlan; - - // Set MTU - let mtu = 1500; - if (props.jumboFrames) { - if (this.virtualInterfaceType === 'private') { - mtu = 9001; - } - if (this.virtualInterfaceType === 'transit') { - mtu = 8500; - } - } - this.mtu = mtu; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/index.ts deleted file mode 100644 index e308ff5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/index.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - CloudFormationCustomResourceEvent, - CloudFormationCustomResourceUpdateEvent, -} from '@aws-accelerator/utils/lib/common-types'; -import { VirtualInterfaceAllocationAttributes } from './attributes'; - -/** - * direct-connect-virtual-interface - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - // Set variables - const vif = vifInit(event); - const apiProps = setApiProps(vif); - const dx = new AWS.DirectConnect({ - region: event.ResourceProperties['region'], - customUserAgent: process.env['SOLUTION_ID'], - }); - - // Event handler - switch (event.RequestType) { - case 'Create': - let virtualInterfaceId: string | undefined; - // Create allocations depending on interface type - if (vif.virtualInterfaceType === 'private') { - const response = await createPrivateAllocation( - dx, - apiProps as AWS.DirectConnect.AllocatePrivateVirtualInterfaceRequest, - ); - virtualInterfaceId = response.virtualInterfaceId; - } - - if (vif.virtualInterfaceType === 'transit') { - const response = await createTransitAllocation( - dx, - apiProps as AWS.DirectConnect.AllocateTransitVirtualInterfaceRequest, - ); - virtualInterfaceId = response.virtualInterface?.virtualInterfaceId; - } - - if (!virtualInterfaceId) { - throw new Error(`Unable to create virtual interface allocation.`); - } - - return { - PhysicalResourceId: virtualInterfaceId, - Status: 'SUCCESS', - }; - - case 'Update': - // Validate new VIF attributes against existing - const oldVif = oldVifInit(event); - validateUpdateEvent(vif, oldVif); - - // Update attributes - if (vif.mtu !== oldVif.mtu) { - console.log(`Updating ${vif.virtualInterfaceName} MTU from ${oldVif.mtu.toString()} to ${vif.mtu.toString()}`); - await throttlingBackOff(() => - dx - .updateVirtualInterfaceAttributes({ - virtualInterfaceId: event.PhysicalResourceId, - mtu: vif.mtu, - }) - .promise(), - ); - } - if (vif.siteLink !== oldVif.siteLink) { - console.log(`Updating ${vif.virtualInterfaceName} SiteLink from ${oldVif.siteLink} to ${vif.siteLink}`); - await throttlingBackOff(() => - dx - .updateVirtualInterfaceAttributes({ - virtualInterfaceId: event.PhysicalResourceId, - enableSiteLink: vif.siteLink, - }) - .promise(), - ); - } - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - dx.deleteVirtualInterface({ virtualInterfaceId: event.PhysicalResourceId }).promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Initialize the virtual interface attributes object - * @param event - */ -function vifInit(event: CloudFormationCustomResourceEvent): VirtualInterfaceAllocationAttributes { - // Set variables from event - const addressFamily: string = event.ResourceProperties['addressFamily']; - const amazonAddress: string | undefined = event.ResourceProperties['amazonAddress']; - const asn: number = event.ResourceProperties['customerAsn']; - const connectionId: string = event.ResourceProperties['connectionId']; - const customerAddress: string | undefined = event.ResourceProperties['customerAddress']; - const jumboFrames: boolean | undefined = returnBoolean(event.ResourceProperties['jumboFrames']); - const ownerAccount: string = event.ResourceProperties['ownerAccount']; - const siteLink: boolean | undefined = returnBoolean(event.ResourceProperties['enableSiteLink']); - const virtualInterfaceName: string = event.ResourceProperties['interfaceName']; - const virtualInterfaceType: 'private' | 'transit' = event.ResourceProperties['type']; - const vlan: number = event.ResourceProperties['vlan']; - const tags: AWS.DirectConnect.TagList = event.ResourceProperties['tags'] ?? []; - - // Add Name tag - tags.push({ key: 'Name', value: virtualInterfaceName }); - - return new VirtualInterfaceAllocationAttributes({ - addressFamily, - amazonAddress, - asn, - connectionId, - customerAddress, - jumboFrames: jumboFrames ?? false, - ownerAccount, - siteLink: siteLink ?? false, - virtualInterfaceName, - virtualInterfaceType, - vlan, - tags, - }); -} - -/** - * Set API props based on properties passed in to the custom resource - * @param event - */ -function setApiProps( - vif: VirtualInterfaceAllocationAttributes, -): AWS.DirectConnect.AllocatePrivateVirtualInterfaceRequest | AWS.DirectConnect.AllocateTransitVirtualInterfaceRequest { - // Set API props based on virtual interface type - let apiProps: - | AWS.DirectConnect.AllocatePrivateVirtualInterfaceRequest - | AWS.DirectConnect.AllocateTransitVirtualInterfaceRequest; - switch (vif.virtualInterfaceType) { - case 'private': - const newPrivateVirtualInterfaceAllocation = { - asn: vif.asn, - virtualInterfaceName: vif.virtualInterfaceName, - vlan: vif.vlan, - addressFamily: vif.addressFamily, - amazonAddress: vif.amazonAddress, - customerAddress: vif.customerAddress, - mtu: vif.mtu, - tags: vif.tags, - }; - - apiProps = { - connectionId: vif.connectionId, - ownerAccount: vif.ownerAccount, - newPrivateVirtualInterfaceAllocation, - }; - return apiProps; - - case 'transit': - const newTransitVirtualInterfaceAllocation = { - asn: vif.asn, - virtualInterfaceName: vif.virtualInterfaceName, - vlan: vif.vlan, - addressFamily: vif.addressFamily, - amazonAddress: vif.amazonAddress, - customerAddress: vif.customerAddress, - mtu: vif.mtu, - tags: vif.tags, - }; - - apiProps = { - connectionId: vif.connectionId, - ownerAccount: vif.ownerAccount, - newTransitVirtualInterfaceAllocation, - }; - return apiProps; - } -} - -/** - * Initialize the virtual interface attributes object - * @param event - */ -function oldVifInit(event: CloudFormationCustomResourceUpdateEvent): VirtualInterfaceAllocationAttributes { - // Set variables from event - const addressFamily: string = event.OldResourceProperties['addressFamily']; - const amazonAddress: string | undefined = event.OldResourceProperties['amazonAddress']; - const asn: number = event.OldResourceProperties['customerAsn']; - const connectionId: string = event.OldResourceProperties['connectionId']; - const customerAddress: string | undefined = event.OldResourceProperties['customerAddress']; - const jumboFrames: boolean | undefined = returnBoolean(event.OldResourceProperties['jumboFrames']); - const ownerAccount: string = event.OldResourceProperties['ownerAccount']; - const siteLink: boolean | undefined = returnBoolean(event.OldResourceProperties['enableSiteLink']); - const virtualInterfaceName: string = event.OldResourceProperties['interfaceName']; - const virtualInterfaceType: 'private' | 'transit' = event.OldResourceProperties['type']; - const vlan: number = event.OldResourceProperties['vlan']; - const tags: AWS.DirectConnect.TagList = event.OldResourceProperties['tags'] ?? []; - - // Add Name tag - tags.push({ key: 'Name', value: virtualInterfaceName }); - - return new VirtualInterfaceAllocationAttributes({ - addressFamily, - amazonAddress, - asn, - connectionId, - customerAddress, - jumboFrames: jumboFrames ?? false, - ownerAccount, - siteLink: siteLink ?? false, - virtualInterfaceName, - virtualInterfaceType, - vlan, - tags, - }); -} - -/** - * Compare VIF attribute objects and throw errors for invalid update requests - * @param vif - * @param oldVif - */ -function validateUpdateEvent(vif: VirtualInterfaceAllocationAttributes, oldVif: VirtualInterfaceAllocationAttributes) { - // Error validation - if (vif.addressFamily !== oldVif.addressFamily) { - console.warn('Address family cannot be updated. Please delete and recreate the virtual interface instead.'); - } - if (vif.amazonAddress !== oldVif.amazonAddress || vif.customerAddress !== oldVif.customerAddress) { - console.warn('Cannot update peer IP addresses. Please delete and recreate the virtual interface instead.'); - } - if (vif.asn !== oldVif.asn) { - console.warn('Cannot update customer ASN. Please delete and recreate the virtual interface instead.'); - } - if (vif.vlan !== oldVif.vlan) { - console.warn('Cannot update the VLAN tag. Please delete and recreate the virtual interface instead.'); - } -} - -async function createPrivateAllocation( - dx: AWS.DirectConnect, - apiProps: AWS.DirectConnect.AllocatePrivateVirtualInterfaceRequest, -) { - return throttlingBackOff(() => dx.allocatePrivateVirtualInterface(apiProps).promise()); -} - -async function createTransitAllocation( - dx: AWS.DirectConnect, - apiProps: AWS.DirectConnect.AllocateTransitVirtualInterfaceRequest, -) { - return throttlingBackOff(() => dx.allocateTransitVirtualInterface(apiProps).promise()); -} - -function returnBoolean(input: string): boolean | undefined { - try { - return JSON.parse(input.toLowerCase()); - } catch (e) { - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/package.json deleted file mode 100644 index 3955696..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directconnect-virtual-interface-allocation", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface-allocation/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface.ts deleted file mode 100644 index 0ccce26..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IVirtualInterface extends cdk.IResource { - /** - * The Direct Connect virtual interface ID - */ - readonly virtualInterfaceId: string; - /** - * The friendly name of the Direct Connect virtual interface - */ - readonly virtualInterfaceName: string; -} - -export interface VirtualInterfaceProps { - /** - * The Direct Connect connection ID the virtual interface will be created on - */ - readonly connectionId: string; - /** - * The Border Gateway Protocol (BGP) autonomous system number (ASN) - * of the customer router - */ - readonly customerAsn: number; - /** - * The name of the virtual interface. - */ - readonly interfaceName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The region of the virtual interface - */ - readonly region: string; - /** - * The type of Direct Connect virtual interface - */ - readonly type: string; - /** - * The virtual local area network (VLAN) tag of the virtual interface - */ - readonly vlan: number; - /** - * The address family to use for this virtual interface - * - * Default - `'ipv4'` - */ - readonly addressFamily?: string; - /** - * The Amazon side peer IP address to use for this virtual interface - * - */ - readonly amazonAddress?: string; - /** - * The customer side peer IP address to use for this virtual interface - */ - readonly customerAddress?: string; - /** - * The Direct connect Gateway ID to attach the virtual interface to. - */ - readonly directConnectGatewayId?: string; - /** - * Enable SiteLink for this virtual interface. - * - * Default - `false` - */ - readonly enableSiteLink?: boolean; - /** - * Whether to enable jumbo frames for the virtual interface - * - * Default - `false` - */ - readonly jumboFrames?: boolean; - /** - * The owner account of the virtual interface (used for allocations) - */ - readonly ownerAccount?: string; - /** - * An array of tags for the virtual interface - */ - readonly tags?: cdk.CfnTag[]; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; -} - -export class VirtualInterface extends cdk.Resource implements IVirtualInterface { - public readonly virtualInterfaceId: string; - public readonly virtualInterfaceName: string; - - constructor(scope: Construct, id: string, props: VirtualInterfaceProps) { - super(scope, id); - - // Set initial variables - this.virtualInterfaceName = props.interfaceName; - let codeDirectory: string; - let RESOURCE_TYPE: string; - let policyStatements; - - if (props.ownerAccount) { - RESOURCE_TYPE = 'Custom::DirectConnectVirtualInterfaceAllocation'; - codeDirectory = path.join(__dirname, 'virtual-interface-allocation/dist'); - policyStatements = [ - { - Sid: 'DxVirtualInterfaceAllocateCRUD', - Effect: 'Allow', - Action: [ - 'directconnect:AllocatePrivateVirtualInterface', - 'directconnect:AllocateTransitVirtualInterface', - 'directconnect:DeleteVirtualInterface', - 'directconnect:TagResource', - 'directconnect:UpdateVirtualInterfaceAttributes', - ], - Resource: '*', - }, - ]; - } else { - RESOURCE_TYPE = 'Custom::DirectConnectVirtualInterface'; - codeDirectory = path.join(__dirname, 'virtual-interface/dist'); - policyStatements = [ - { - Sid: 'DxVirtualInterfaceCRUD', - Effect: 'Allow', - Action: [ - 'directconnect:CreatePrivateVirtualInterface', - 'directconnect:CreateTransitVirtualInterface', - 'directconnect:DeleteVirtualInterface', - 'directconnect:DescribeVirtualInterfaces', - 'directconnect:TagResource', - 'directconnect:UntagResource', - 'directconnect:UpdateVirtualInterfaceAttributes', - ], - Resource: '*', - }, - { - Sid: 'InvokeSelf', - Effect: 'Allow', - Action: ['lambda:InvokeFunction'], - Resource: `arn:${cdk.Aws.PARTITION}:lambda:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:function:${props.acceleratorPrefix}-NetworkPre-CustomDirectConnect*`, - }, - ]; - } - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory, - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements, - timeout: cdk.Duration.minutes(15), - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - connectionId: props.connectionId, - customerAsn: props.customerAsn, - directConnectGatewayId: props.directConnectGatewayId, - interfaceName: props.interfaceName, - type: props.type, - region: props.region, - vlan: props.vlan, - addressFamily: props.addressFamily ?? 'ipv4', - amazonAddress: props.amazonAddress, - customerAddress: props.customerAddress, - enableSiteLink: props.enableSiteLink ?? false, - jumboFrames: props.jumboFrames ?? false, - ownerAccount: props.ownerAccount, - tags: props.tags, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.virtualInterfaceId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/attributes.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/attributes.ts deleted file mode 100644 index 629093b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/attributes.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { DirectConnect } from 'aws-sdk'; - -interface VirtualInterfaceAttributeProps { - /** - * The IP address family - */ - readonly addressFamily: string; - /** - * The BGP ASN of the customer router - */ - readonly asn: number; - /** - * The Direct Connect connection ID - */ - readonly connectionId: string; - /** - * The Direct Connect Gateway ID - */ - readonly directConnectGatewayId: string; - /** - * Whether to enable jumbo frames for the virtual interface - */ - readonly jumboFrames: boolean; - /** - * Enable SiteLink for the virtual interface - */ - readonly siteLink: boolean; - /** - * The name of the virtual interface - */ - readonly virtualInterfaceName: string; - /** - * The type of the virtual interface - */ - readonly virtualInterfaceType: 'private' | 'transit'; - /** - * The virtual local area network (VLAN) tag - */ - readonly vlan: number; - /** - * The Amazon side peer IP address - */ - readonly amazonAddress?: string; - /** - * The customer side peer IP address - */ - readonly customerAddress?: string; - /** - * Tags for the virtual interface - */ - readonly tags?: DirectConnect.TagList; -} - -export class VirtualInterfaceAttributes { - public readonly addressFamily: string; - public readonly asn: number; - public readonly connectionId: string; - public readonly directConnectGatewayId: string; - public readonly mtu: number; - public readonly siteLink: boolean; - public readonly virtualInterfaceName: string; - public readonly virtualInterfaceType: 'private' | 'transit'; - public readonly vlan: number; - public readonly amazonAddress?: string; - public readonly customerAddress?: string; - public readonly tags?: DirectConnect.TagList; - constructor(props: VirtualInterfaceAttributeProps) { - this.addressFamily = props.addressFamily; - this.amazonAddress = props.amazonAddress; - this.asn = props.asn; - this.connectionId = props.connectionId; - this.customerAddress = props.customerAddress; - this.directConnectGatewayId = props.directConnectGatewayId; - this.siteLink = props.siteLink; - this.tags = props.tags; - this.virtualInterfaceName = props.virtualInterfaceName; - this.virtualInterfaceType = props.virtualInterfaceType; - this.vlan = props.vlan; - - // Set MTU - let mtu = 1500; - if (props.jumboFrames) { - if (this.virtualInterfaceType === 'private') { - mtu = 9001; - } - if (this.virtualInterfaceType === 'transit') { - mtu = 8500; - } - } - this.mtu = mtu; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/index.ts deleted file mode 100644 index 670ded7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/index.ts +++ /dev/null @@ -1,494 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - CloudFormationCustomResourceEvent, - CloudFormationCustomResourceUpdateEvent, -} from '@aws-accelerator/utils/lib/common-types'; -import { VirtualInterfaceAttributes } from './attributes'; - -/** - * direct-connect-virtual-interface - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - // Set variables - const solutionId = process.env['SOLUTION_ID']; - const vif = vifInit(event); - const apiProps = setApiProps(vif); - const dx = new AWS.DirectConnect({ - region: event.ResourceProperties['region'], - customUserAgent: solutionId, - }); - const lambdaClient = new AWS.Lambda({ customUserAgent: solutionId }); - - // Event handler - switch (event.RequestType) { - case 'Create': - let virtualInterfaceId: string | undefined; - // Create interfaces if based on interface type - if (vif.virtualInterfaceType === 'private') { - const response = await createPrivateInterface( - dx, - apiProps as AWS.DirectConnect.CreatePrivateVirtualInterfaceRequest, - ); - virtualInterfaceId = response.virtualInterfaceId; - } - - if (vif.virtualInterfaceType === 'transit') { - const response = await createTransitInterface( - dx, - apiProps as AWS.DirectConnect.CreateTransitVirtualInterfaceRequest, - ); - virtualInterfaceId = response.virtualInterface?.virtualInterfaceId; - } - - if (!virtualInterfaceId) { - throw new Error(`Unable to create virtual interface.`); - } - - if (await validateState(dx, virtualInterfaceId, ['available', 'down'])) { - return { - PhysicalResourceId: virtualInterfaceId, - Status: 'SUCCESS', - }; - } - - // Retry Lambda - await retryLambda(lambdaClient, event); - await sleep(120000); - return; - - case 'Update': - // Validate new VIF attributes against existing - const oldVif = oldVifInit(event); - validateUpdateEvent(vif, oldVif); - - // Determine tag updates - if (!(await inProgress(dx, event.PhysicalResourceId))) { - const vifArn = generateVifArn(event); - await processTagUpdates(dx, vifArn, vif, oldVif); - - // Update attributes if necessary - if (vif.virtualInterfaceName !== oldVif.virtualInterfaceName) { - console.log( - `Updating virtual interface name from ${oldVif.virtualInterfaceName} to ${vif.virtualInterfaceName}`, - ); - await throttlingBackOff(() => - dx - .updateVirtualInterfaceAttributes({ - virtualInterfaceId: event.PhysicalResourceId, - virtualInterfaceName: vif.virtualInterfaceName, - }) - .promise(), - ); - } - if (vif.mtu !== oldVif.mtu) { - console.log( - `Updating ${vif.virtualInterfaceName} MTU from ${oldVif.mtu.toString()} to ${vif.mtu.toString()}`, - ); - await throttlingBackOff(() => - dx - .updateVirtualInterfaceAttributes({ - virtualInterfaceId: event.PhysicalResourceId, - mtu: vif.mtu, - }) - .promise(), - ); - } - if (vif.siteLink !== oldVif.siteLink) { - console.log(`Updating ${vif.virtualInterfaceName} SiteLink from ${oldVif.siteLink} to ${vif.siteLink}`); - await throttlingBackOff(() => - dx - .updateVirtualInterfaceAttributes({ - virtualInterfaceId: event.PhysicalResourceId, - enableSiteLink: vif.siteLink, - }) - .promise(), - ); - } - } - - if (await validateState(dx, event.PhysicalResourceId, ['available', 'down'])) { - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } - - // Retry Lambda - await retryLambda(lambdaClient, event); - await sleep(120000); - return; - - case 'Delete': - if (!(await inProgress(dx, event.PhysicalResourceId))) { - await throttlingBackOff(() => - dx.deleteVirtualInterface({ virtualInterfaceId: event.PhysicalResourceId }).promise(), - ); - } - - if (await validateState(dx, event.PhysicalResourceId, ['deleted'])) { - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } - - // Retry Lambda - await retryLambda(lambdaClient, event); - await sleep(120000); - return; - } -} - -/** - * Initialize the virtual interface attributes object - * @param event - */ -function vifInit(event: CloudFormationCustomResourceEvent): VirtualInterfaceAttributes { - // Set variables from event - const addressFamily: string = event.ResourceProperties['addressFamily']; - const amazonAddress: string | undefined = event.ResourceProperties['amazonAddress']; - const asn: number = event.ResourceProperties['customerAsn']; - const connectionId: string = event.ResourceProperties['connectionId']; - const customerAddress: string | undefined = event.ResourceProperties['customerAddress']; - const directConnectGatewayId: string = event.ResourceProperties['directConnectGatewayId']; - const jumboFrames: boolean | undefined = returnBoolean(event.ResourceProperties['jumboFrames']); - const siteLink: boolean | undefined = returnBoolean(event.ResourceProperties['enableSiteLink']); - const virtualInterfaceName: string = event.ResourceProperties['interfaceName']; - const virtualInterfaceType: 'private' | 'transit' = event.ResourceProperties['type']; - const vlan: number = event.ResourceProperties['vlan']; - const tags: AWS.DirectConnect.TagList = event.ResourceProperties['tags'] ?? []; - - // Add Name tag - tags.push({ key: 'Name', value: virtualInterfaceName }); - - return new VirtualInterfaceAttributes({ - addressFamily, - amazonAddress, - asn, - connectionId, - customerAddress, - directConnectGatewayId, - jumboFrames: jumboFrames ?? false, - siteLink: siteLink ?? false, - virtualInterfaceName, - virtualInterfaceType, - vlan, - tags, - }); -} -/** - * Set API props based on properties passed in to the custom resource - * @param event - */ -function setApiProps( - vif: VirtualInterfaceAttributes, -): AWS.DirectConnect.CreatePrivateVirtualInterfaceRequest | AWS.DirectConnect.CreateTransitVirtualInterfaceRequest { - // Set API props based on virtual interface type - let apiProps: - | AWS.DirectConnect.CreatePrivateVirtualInterfaceRequest - | AWS.DirectConnect.CreateTransitVirtualInterfaceRequest; - const attributes = { - asn: vif.asn, - virtualInterfaceName: vif.virtualInterfaceName, - vlan: vif.vlan, - addressFamily: vif.addressFamily, - amazonAddress: vif.amazonAddress, - customerAddress: vif.customerAddress, - directConnectGatewayId: vif.directConnectGatewayId, - enableSiteLink: vif.siteLink, - mtu: vif.mtu, - tags: vif.tags, - }; - - switch (vif.virtualInterfaceType) { - case 'private': - apiProps = { - connectionId: vif.connectionId, - newPrivateVirtualInterface: attributes, - }; - - return apiProps; - - case 'transit': - apiProps = { - connectionId: vif.connectionId, - newTransitVirtualInterface: attributes, - }; - - return apiProps; - } -} - -/** - * Initialize the old virtual interface attributes object - * @param event - * @returns - */ -function oldVifInit(event: CloudFormationCustomResourceUpdateEvent) { - // Set variables from event - const addressFamily: string = event.OldResourceProperties['addressFamily']; - const amazonAddress: string | undefined = event.OldResourceProperties['amazonAddress']; - const asn: number = event.OldResourceProperties['customerAsn']; - const connectionId: string = event.OldResourceProperties['connectionId']; - const customerAddress: string | undefined = event.OldResourceProperties['customerAddress']; - const directConnectGatewayId: string = event.OldResourceProperties['directConnectGatewayId']; - const jumboFrames: boolean | undefined = returnBoolean(event.OldResourceProperties['jumboFrames']); - const siteLink: boolean | undefined = returnBoolean(event.OldResourceProperties['enableSiteLink']); - const virtualInterfaceName: string = event.OldResourceProperties['interfaceName']; - const virtualInterfaceType: 'private' | 'transit' = event.OldResourceProperties['type']; - const vlan: number = event.OldResourceProperties['vlan']; - const tags: AWS.DirectConnect.TagList = event.OldResourceProperties['tags'] ?? []; - - // Add Name tag - tags.push({ key: 'Name', value: virtualInterfaceName }); - - return new VirtualInterfaceAttributes({ - addressFamily, - amazonAddress, - asn, - connectionId, - customerAddress, - directConnectGatewayId, - jumboFrames: jumboFrames ?? false, - siteLink: siteLink ?? false, - virtualInterfaceName, - virtualInterfaceType, - vlan, - tags, - }); -} - -/** - * Compare VIF attribute objects and throw errors for invalid update requests - * @param vif - * @param oldVif - */ -function validateUpdateEvent(vif: VirtualInterfaceAttributes, oldVif: VirtualInterfaceAttributes) { - // Error validation - if (vif.addressFamily !== oldVif.addressFamily) { - console.warn('Address family cannot be updated. Please delete and recreate the virtual interface instead.'); - } - if (vif.amazonAddress !== oldVif.amazonAddress || vif.customerAddress !== oldVif.customerAddress) { - console.warn('Cannot update peer IP addresses. Please delete and recreate the virtual interface instead.'); - } - if (vif.asn !== oldVif.asn) { - console.warn('Cannot update customer ASN. Please delete and recreate the virtual interface instead.'); - } - if (vif.vlan !== oldVif.vlan) { - console.warn('Cannot update the VLAN tag. Please delete and recreate the virtual interface instead.'); - } -} - -/** - * Create a private virtual interface - * @param dx - * @param apiProps - * @returns - */ -async function createPrivateInterface( - dx: AWS.DirectConnect, - apiProps: AWS.DirectConnect.CreatePrivateVirtualInterfaceRequest, -) { - return throttlingBackOff(() => dx.createPrivateVirtualInterface(apiProps).promise()); -} - -/** - * Create a transit virtual interface - * @param dx - * @param apiProps - * @returns - */ -async function createTransitInterface( - dx: AWS.DirectConnect, - apiProps: AWS.DirectConnect.CreateTransitVirtualInterfaceRequest, -) { - return throttlingBackOff(() => dx.createTransitVirtualInterface(apiProps).promise()); -} - -/** - * Generate the ARN of the virtual interface via the event metadata - * @param event - * @returns - */ -function generateVifArn(event: CloudFormationCustomResourceUpdateEvent): string { - const accountId = event.ServiceToken.split(':')[4]; - const partition = event.ServiceToken.split(':')[1]; - const region = event.ResourceProperties['region']; - const vifId = event.PhysicalResourceId; - - return `arn:${partition}:directconnect:${region}:${accountId}:dxvif/${vifId}`; -} - -/** - * Update resource tags - * @param dx - * @param resourceArn - * @param vif - * @param oldVif - */ -async function processTagUpdates( - dx: AWS.DirectConnect, - resourceArn: string, - vif: VirtualInterfaceAttributes, - oldVif: VirtualInterfaceAttributes, -): Promise { - // Filter tags to remove - let removeTagKeys: AWS.DirectConnect.TagKeyList = []; - if (vif.tags && oldVif.tags) { - const updateTagKeys = vif.tags.map(item => { - return item.key; - }); - const oldTagKeys = oldVif.tags.map(item => { - return item.key; - }); - removeTagKeys = oldTagKeys.filter(item => !updateTagKeys.includes(item)); - } - - // Update tags as necessary - if (vif.tags && vif.tags.length > 0) { - await throttlingBackOff(() => dx.tagResource({ resourceArn, tags: vif.tags! }).promise()); - } - - if (removeTagKeys.length > 0) { - await throttlingBackOff(() => dx.untagResource({ resourceArn, tagKeys: removeTagKeys }).promise()); - } -} - -/** - * Validate the virtual interface reaches - * an expected state - * @param dx - * @param virtualInterfaceId - * @param expectedState - */ -async function validateState( - dx: AWS.DirectConnect, - virtualInterfaceId: string, - expectedState: string[], -): Promise { - let currentState: string | undefined; - let retries = 0; - - // Describe gateway association until it is in the expected state - do { - const response = await throttlingBackOff(() => dx.describeVirtualInterfaces({ virtualInterfaceId }).promise()); - if (!response.virtualInterfaces) { - throw new Error(`Unable to retrieve virtual interface ID ${virtualInterfaceId}`); - } - - // Check case where VIF ID is removed from the list during deletion - if (expectedState.includes('deleted') && response.virtualInterfaces.length === 0) { - return true; - } - - // Determine state - currentState = response.virtualInterfaces[0].virtualInterfaceState; - if (!currentState) { - throw new Error(`Unable to retrieve state of virtual interface ID ${virtualInterfaceId}`); - } - if (!expectedState.includes(currentState)) { - await sleep(60000); - } - - // Increase retry index until timeout nears - retries += 1; - if (retries > 13) { - return false; - } - } while (!expectedState.includes(currentState)); - return true; -} - -/** - * Check if a mutating action is in progress - * @param dx - * @param virtualInterfaceId - * @returns - */ -async function inProgress(dx: AWS.DirectConnect, virtualInterfaceId: string): Promise { - const response = await throttlingBackOff(() => dx.describeVirtualInterfaces({ virtualInterfaceId }).promise()); - - if (!response.virtualInterfaces) { - throw new Error(`Unable to retrieve virtual interface ID ${virtualInterfaceId}`); - } - if ( - response.virtualInterfaces.length === 0 || - ['available', 'down'].includes(response.virtualInterfaces[0].virtualInterfaceState!) - ) { - return false; - } - return true; -} - -/** - * Re-invoke the Lambda function if the timeout is close to being reached - * @param lambdaClient - * @param event - */ -async function retryLambda(lambdaClient: AWS.Lambda, event: CloudFormationCustomResourceEvent): Promise { - // Add retry attempt to event - if (!event.ResourceProperties['retryAttempt']) { - event.ResourceProperties['retryAttempt'] = 0; - } - event.ResourceProperties['retryAttempt'] += 1; - - // Throw error for max number of retries - if (event.ResourceProperties['retryAttempt'] > 3) { - throw new Error( - `Exceeded maximum number of retries. Please check the Direct Connect console for the status of your virtual interface.`, - ); - } - - // Invoke Lambda - await throttlingBackOff(() => - lambdaClient - .invoke({ FunctionName: event.ServiceToken, InvocationType: 'Event', Payload: JSON.stringify(event) }) - .promise(), - ); -} - -/** - * Return a boolean value if JSON is passed in as a string - * @param input - * @returns - */ -function returnBoolean(input: string): boolean | undefined { - try { - return JSON.parse(input.toLowerCase()); - } catch (e) { - return undefined; - } -} - -/** - * Sleep for a specified number of milliseconds - * @param ms - * @returns - */ -async function sleep(ms: number) { - return new Promise(f => setTimeout(f, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/package.json deleted file mode 100644 index 9db2607..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directconnect-virtual-interface", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directconnect/virtual-interface/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-configuration.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-configuration.ts deleted file mode 100644 index fdab7f8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-configuration.ts +++ /dev/null @@ -1,428 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import * as fs from 'fs'; -import * as path from 'path'; - -import { pascalCase } from 'change-case'; - -/** - * UserData scripts type - */ -export type UserDataScriptsType = { - /** - * Friendly name for the script - */ - name: string; - /** - * Relative script path in config repo - */ - path: string; -}; - -/** - * Initialized ActiveDirectoryConfigurationProps properties - */ -export interface ActiveDirectoryConfigurationProps { - /** - * AD configuration EC2 instance type - */ - readonly instanceType: string; - /** - * AD configuration EC2 instance image ssm parameter path - */ - readonly imagePath: string; - /** - * Friendly name for the managed active directory - */ - readonly managedActiveDirectoryName: string; - /** - * Managed active directory secret account id - */ - readonly managedActiveDirectorySecretAccountId: string; - /** - * Managed active directory secret region - */ - readonly managedActiveDirectorySecretRegion: string; - /** - * Managed active directory dns name - */ - readonly dnsName: string; - /** - * Managed active directory netBiosDomainName name - */ - readonly netBiosDomainName: string; - /** - * Managed active directory admin password secret arn - */ - readonly adminPwdSecretArn: string; - /** - * Managed active directory secret ksm key arn - */ - readonly secretKeyArn: string; - - /** - * AD configuration EC2 instance subnet id - */ - readonly subnetId: string; - /** - * AD configuration EC2 instance security group id - */ - readonly securityGroupId: string; - /** - * AD configuration EC2 instance role name - */ - readonly instanceRoleName: string; - /** - * Flag for AD configuration EC2 instance enable api termination protection - */ - readonly enableTerminationProtection: boolean; - /** - * AD configuration EC2 instance user data scripts - */ - readonly userDataScripts: UserDataScriptsType[]; - /** - * Managed active directory user groups - */ - readonly adGroups: string[]; - /** - * Managed active directory groups per account user - */ - readonly adPerAccountGroups: string[]; - /** - * Managed active directory connector group - */ - readonly adConnectorGroup: string; - /** - * Managed active directory user list - */ - readonly adUsers: { name: string; email: string; groups: string[] }[]; - /** - * Accelerator prefix for user secret names - */ - readonly secretPrefix: string; - /** - * Managed active directory user password policy - */ - readonly adPasswordPolicy: { - history: number; - maximumAge: number; - minimumAge: number; - minimumLength: number; - complexity: boolean; - reversible: boolean; - failedAttempts: number; - lockoutDuration: number; - lockoutAttemptsReset: number; - }; - /** - * Managed active directory group account list - */ - readonly accountNames: string[]; -} - -/** - * Managed active directory configuration class. - * This construct creates EC2 instances and executes configuration scripts using user data to configure active directory - * Active directory configuration such as joining domain, ad user, group creation etc. are performed. - */ -export class ActiveDirectoryConfiguration extends Construct { - public readonly activeDirectoryConfigurationProps: ActiveDirectoryConfigurationProps; - - constructor(scope: Construct, id: string, props: ActiveDirectoryConfigurationProps) { - super(scope, id); - - this.activeDirectoryConfigurationProps = props; - - const role = cdk.aws_iam.Role.fromRoleName( - this, - pascalCase(`${props.managedActiveDirectoryName}InstanceRole`), - props.instanceRoleName, - ); - - role.attachInlinePolicy( - new cdk.aws_iam.Policy(this, pascalCase(`${props.managedActiveDirectoryName}KmsPolicy`), { - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: ['kms:Decrypt'], - resources: [props.secretKeyArn], - }), - ], - }), - ); - - // enforce using Instance Metadata Service Version 2 (IMDSv2) - // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html - const launchTemplateId = `${props.managedActiveDirectoryName}LaunchTemplate`; - const launchTemplate = new cdk.aws_ec2.CfnLaunchTemplate(this, pascalCase(launchTemplateId), { - launchTemplateName: launchTemplateId, - launchTemplateData: { - metadataOptions: { - httpTokens: 'required', - httpEndpoint: 'enabled', - }, - }, - }); - - const instance = new cdk.aws_ec2.CfnInstance(this, pascalCase(`${props.managedActiveDirectoryName}Instance`), { - instanceType: props.instanceType, - iamInstanceProfile: role.roleName, - imageId: cdk.aws_ssm.StringParameter.valueForStringParameter(this, props.imagePath), - subnetId: props.subnetId, - securityGroupIds: [props.securityGroupId], - blockDeviceMappings: [ - { - deviceName: '/dev/sda1', - ebs: { - volumeSize: 50, - volumeType: 'gp2', - encrypted: true, - }, - }, - ], - tags: [{ key: 'Name', value: pascalCase(`${props.managedActiveDirectoryName}-ConfiguringInstance`) }], - disableApiTermination: props.enableTerminationProtection, - launchTemplate: { - version: launchTemplate.attrLatestVersionNumber, - launchTemplateName: launchTemplateId, - }, - }); - - instance.cfnOptions.creationPolicy = { resourceSignal: { count: 1, timeout: 'PT30M' } }; - - // Add instance user data - instance.userData = cdk.Fn.base64( - `\n`, - ); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const setupFiles: { [p: string]: { content: any } } = { - 'c:\\cfn\\cfn-hup.conf': { - content: `[main]\n stack=${cdk.Stack.of(this).stackName}\n region=${cdk.Stack.of(this).region}\n`, - }, - 'c:\\cfn\\hooks.d\\cfn-auto-reloader.conf': { - content: `[cfn-auto-reloader-hook]\n triggers=post.update\n path=Resources.${ - instance.logicalId - }.Metadata.AWS::CloudFormation::Init\n action=cfn-init.exe -v -c config -s ${cdk.Stack.of(this).stackId} -r ${ - instance.logicalId - } --region ${cdk.Stack.of(this).region}\n`, - }, - }; - - // Add UserData script files to the setup file list - let joinDomainScriptName = 'Join-Domain.ps1'; - let adGroupSetupScriptName = 'AD-group-setup.ps1'; - let adConnectorPermissionSetupScriptName = 'AD-connector-permissions-setup.ps1'; - let adUserSetupScriptName = 'AD-user-setup.ps1'; - let adUserGroupSetupScriptName = 'AD-user-group-setup.ps1'; - let configurePasswordPolicyScriptName = 'Configure-password-policy.ps1'; - for (const userDataScript of props.userDataScripts ?? []) { - const fileName = path.basename(userDataScript.path); - const fileExtension = path.extname(userDataScript.path); - - let destPath = 'c:\\cfn\\scripts\\'; - if (fileExtension === '.psm1') { - destPath = 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\AWSQuickStart\\'; - } - setupFiles[`${destPath}` + fileName] = { - content: fs.readFileSync(userDataScript.path, 'utf8'), - }; - - switch (userDataScript.name) { - case 'JoinDomain': - joinDomainScriptName = fileName; - break; - case 'ADGroupSetup': - adGroupSetupScriptName = fileName; - break; - case 'ADConnectorPermissionsSetup': - adConnectorPermissionSetupScriptName = fileName; - break; - case 'ADUserSetup': - adUserSetupScriptName = fileName; - break; - case 'ADUserGroupSetup': - adUserGroupSetupScriptName = fileName; - break; - case 'ConfigurePasswordPolicy': - configurePasswordPolicyScriptName = fileName; - break; - } - } - - // Creating AD Users scripts - const adUsersScripts = this.getAdUsersScripts(adUserSetupScriptName, props.secretPrefix); - - const accountNames = props.accountNames; - - const configGroups = props.adGroups.concat(props.adPerAccountGroups).concat(props.adConnectorGroup); - - const adGroups = this.prepareGroups(configGroups, accountNames); - - // Mapping Users to Groups command - const adUserGroups: { user: string; groups: string[] }[] = props.adUsers.map(a => { - const groups = this.prepareGroups(a.groups, accountNames); - return { user: a.name, groups }; - }); - - const adUserGroupsCommand: string[] = adUserGroups.map( - userGroup => - `C:\\cfn\\scripts\\${adUserGroupSetupScriptName} -GroupNames '${userGroup.groups.join(',')}' -UserName ${ - userGroup.user - } -DomainAdminUser ${props.netBiosDomainName}\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${ - props.adminPwdSecretArn - }).SecretString)`, - ); - - instance.addOverride('Metadata.AWS::CloudFormation::Init', { - configSets: { - config: ['setup', 'join', 'installRDS', 'createADConnectorUser', 'configurePasswordPolicy', 'finalize'], - }, - setup: { - files: { ...setupFiles }, - commands: { - 'a-set-execution-policy': { - command: 'powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Force"', - waitAfterCompletion: '0', - }, - 'b-init-quickstart-module': { - command: `powershell.exe -Command "New-AWSQuickStartResourceSignal -Stack ${ - cdk.Stack.of(this).stackName - } -Resource ${instance.logicalId} -Region ${cdk.Stack.of(this).region}"`, - waitAfterCompletion: '0', - }, - }, - services: { - windows: { - 'cfn-hup': { - enabled: 'true', - ensureRunning: 'true', - files: ['c:\\cfn\\cfn-hup.conf', 'c:\\cfn\\hooks.d\\cfn-auto-reloader.conf'], - }, - }, - }, - }, - join: { - commands: { - 'a-join-domain': { - command: `powershell.exe -Command "C:\\cfn\\scripts\\${joinDomainScriptName} -DomainName ${props.dnsName} -UserName ${props.netBiosDomainName}\\admin -Password ((Get-SECSecretValue -SecretId ${props.adminPwdSecretArn}).SecretString)"`, - waitAfterCompletion: 'forever', - }, - }, - }, - installRDS: { - commands: { - 'a-install-rds': { - command: 'powershell.exe -Command "Install-WindowsFeature RSAT-RDS-Gateway,RSAT-AD-Tools"', - waitAfterCompletion: '0', - }, - }, - }, - createADConnectorUser: { - commands: { - 'a-create-ad-users': { - command: `powershell.exe -ExecutionPolicy RemoteSigned ${adUsersScripts.join('; ')}`, - waitAfterCompletion: '0', - }, - 'b-create-ad-groups': { - command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\${adGroupSetupScriptName} -GroupNames '${adGroups.join( - ',', - )}' -DomainAdminUser ${ - props.netBiosDomainName - }\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${props.adminPwdSecretArn}).SecretString)`, - waitAfterCompletion: '0', - }, - 'c-configure-ad-users-groups': { - command: `powershell.exe -ExecutionPolicy RemoteSigned ${adUserGroupsCommand.join('; ')}`, - waitAfterCompletion: '0', - }, - 'd-configure-ad-group-permissions': { - command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\${adConnectorPermissionSetupScriptName} -GroupName ${props.adConnectorGroup} -DomainAdminUser ${props.netBiosDomainName}\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${props.adminPwdSecretArn}).SecretString)`, - waitAfterCompletion: '0', - }, - }, - }, - configurePasswordPolicy: { - commands: { - 'a-set-password-policy': { - command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\${configurePasswordPolicyScriptName} -DomainAdminUser admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${ - props.adminPwdSecretArn - }).SecretString) -ComplexityEnabled:$${pascalCase( - String(props.adPasswordPolicy.complexity), - )} -LockoutDuration 00:${props.adPasswordPolicy.lockoutDuration}:00 -LockoutObservationWindow 00:${ - props.adPasswordPolicy.lockoutAttemptsReset - }:00 -LockoutThreshold ${props.adPasswordPolicy.failedAttempts} -MaxPasswordAge:${ - props.adPasswordPolicy.maximumAge - }.00:00:00 -MinPasswordAge:${props.adPasswordPolicy.minimumAge}.00:00:00 -MinPasswordLength:${ - props.adPasswordPolicy.minimumLength - } -PasswordHistoryCount:${props.adPasswordPolicy.history} -ReversibleEncryptionEnabled:$${ - props.adPasswordPolicy.reversible - }`, - waitAfterCompletion: '0', - }, - }, - }, - finalize: { - commands: { - '1-signal-success': { - command: 'powershell.exe -Command "Write-AWSQuickStartStatus"', - waitAfterCompletion: '0', - }, - }, - }, - }); - } - - private prepareGroups(configGroups: string[], accounts: string[]): string[] { - const groups: string[] = []; - configGroups.forEach(a => { - if (a.startsWith('*')) { - Object.values(accounts).forEach(b => groups.push(`aws-${b}${a.substring(1)}`)); - } else { - groups.push(a); - } - }); - return groups; - } - - /** - * Function to get Ad user creation scripts - */ - private getAdUsersScripts(adUserSetupScriptName: string, secretPrefix: string): string[] { - const adUsersCommand: string[] = []; - for (const adUser of this.activeDirectoryConfigurationProps.adUsers ?? []) { - const secretName = `${secretPrefix}/ad-user/${this.activeDirectoryConfigurationProps.managedActiveDirectoryName}/${adUser.name}`; - const secretArn = `arn:${cdk.Stack.of(this).partition}:secretsmanager:${ - this.activeDirectoryConfigurationProps.managedActiveDirectorySecretRegion - }:${this.activeDirectoryConfigurationProps.managedActiveDirectorySecretAccountId}:secret:${secretName}`; - - adUsersCommand.push( - `C:\\cfn\\scripts\\${adUserSetupScriptName} -UserName ${adUser.name} -Password ((Get-SECSecretValue -SecretId ${secretArn}).SecretString) -DomainAdminUser ${this.activeDirectoryConfigurationProps.netBiosDomainName}\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${this.activeDirectoryConfigurationProps.adminPwdSecretArn}).SecretString) -PasswordNeverExpires Yes -UserEmailAddress ${adUser.email}`, - ); - } - - // Below script to set admin password to never expire - adUsersCommand.push( - `C:\\cfn\\scripts\\${adUserSetupScriptName} -UserName admin -Password ((Get-SECSecretValue -SecretId ${this.activeDirectoryConfigurationProps.adminPwdSecretArn}).SecretString) -DomainAdminUser ${this.activeDirectoryConfigurationProps.netBiosDomainName}\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${this.activeDirectoryConfigurationProps.adminPwdSecretArn}).SecretString) -PasswordNeverExpires Yes`, - ); - - return adUsersCommand; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-log-subscription.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-log-subscription.ts deleted file mode 100644 index 57d6356..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-log-subscription.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import * as path from 'path'; - -/** - * Initialized ActiveDirectoryLogSubscriptionProps properties - */ -export interface ActiveDirectoryLogSubscriptionProps { - /** - * Managed active directory - */ - readonly activeDirectory: cdk.aws_directoryservice.CfnMicrosoftAD; - /** - * Managed active directory log group name, this log group will be created for directory service log subscription - */ - readonly activeDirectoryLogGroupName: string; - /** - * Managed active directory log retention days - */ - readonly activeDirectoryLogRetentionInDays: number; - /** - * Custom resource lambda key to encrypt environment variables, when undefined default AWS managed key will be used - */ - readonly lambdaKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogsKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - /** - * Managed active directory log subscription class - */ -export class ActiveDirectoryLogSubscription extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: ActiveDirectoryLogSubscriptionProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::ActiveDirectoryLogSubscription'; - - // Create directory service log group - const directoryServiceLogGroup = new cdk.aws_logs.LogGroup(this, `${props.activeDirectory.name}LogGroup`, { - logGroupName: props.activeDirectoryLogGroupName, - retention: props.activeDirectoryLogRetentionInDays, - encryptionKey: props.cloudWatchLogsKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - // Allow directory services to write to the log group - directoryServiceLogGroup.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], - principals: [new cdk.aws_iam.ServicePrincipal('ds.amazonaws.com')], - resources: [directoryServiceLogGroup.logGroupArn], - }), - ); - - const providerLambda = new cdk.aws_lambda.Function(this, 'ManageActiveDirectoryLogSubscriptionFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-log-subscription/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.seconds(30), - description: 'Manage active directory log subscription handler', - environmentEncryption: props.lambdaKmsKey, - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'LogSubscription', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ds:ListLogSubscriptions', 'ds:CreateLogSubscription', 'ds:DeleteLogSubscription'], - resources: ['*'], - }), - ); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudWatchLogsKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'ManageActiveDirectoryLogSubscriptionProvider', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - directoryId: props.activeDirectory.ref, - logGroupName: directoryServiceLogGroup.logGroupName, - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-resolver-rule.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-resolver-rule.ts deleted file mode 100644 index b08a2e5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory-resolver-rule.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -import path = require('path'); - -/** - * Initialized ActiveDirectoryResolverRuleProps properties - */ -export interface ActiveDirectoryResolverRuleProps { - readonly route53ResolverRuleName: string; - readonly targetIps: string[]; - readonly roleName: string; - /** - * Custom resource lambda key to encrypt environment variables, when undefined default AWS managed key will be used - */ - readonly lambdaKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogsKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly cloudWatchLogRetentionInDays: number; -} - -/** - * Update resolver group rule with managed active directory dns ips - */ -export class ActiveDirectoryResolverRule extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: ActiveDirectoryResolverRuleProps) { - super(scope, id); - - const providerLambda = new cdk.aws_lambda.Function(this, 'UpdateResolverRuleFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'update-resolver-role/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.seconds(30), - description: 'Update resolver group rule target ips', - environmentEncryption: props.lambdaKmsKey, - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Route53resolver', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['route53resolver:UpdateResolverRule', 'route53resolver:ListResolverRules'], - resources: ['*'], - }), - ); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'StsAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.roleName}`], - }), - ); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.cloudWatchLogRetentionInDays, - encryptionKey: props.cloudWatchLogsKmsKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - const provider = new cdk.custom_resources.Provider(this, 'UpdateResolverRuleProvider', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::UpdateResolverRule', - serviceToken: provider.serviceToken, - properties: { - executingAccountId: cdk.Stack.of(this).account, - partition: cdk.Stack.of(this).partition, - region: cdk.Stack.of(this).region, - roleName: props.roleName, - route53ResolverRuleName: props.route53ResolverRuleName, - targetIps: props.targetIps, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory.ts deleted file mode 100644 index 320cbe8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/active-directory.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { pascalCase } from 'change-case'; -import { ActiveDirectoryLogSubscription } from './active-directory-log-subscription'; - -/** - * Initialized ActiveDirectoryProps properties - */ -export interface ActiveDirectoryProps { - /** - * Friendly name for the managed active directory name - */ - readonly directoryName: string; - /** - * Managed active directory dns name - */ - readonly dnsName: string; - /** - * Managed active directory target vpc id - */ - readonly vpcId: string; - /** - * AD configuration EC2 instance subnet ids - */ - readonly madSubnetIds: string[]; - /** - * Managed active directory admin password secret - */ - readonly adminSecretValue: cdk.SecretValue; - /** - * Managed active directory edition, possible values are Standard or Enterprise - */ - readonly edition: string; - /** - * Managed active directory netBiosDomainName name - */ - readonly netBiosDomainName: string; - /** - * Managed active directory CloudWatch log group name - */ - readonly logGroupName: string; - /** - * Managed active directory CloudWatch log retention in days - */ - readonly logRetentionInDays: number; - /** - * Custom resource lambda key, when undefined default AWS managed key will be used - */ - readonly lambdaKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log retention in days - */ - readonly cloudwatchLogRetentionInDays: number; -} - -/** - * Managed active directory creation class. - */ -export class ActiveDirectory extends Construct { - public readonly id: string; - public readonly dnsIpAddresses: string[]; - - constructor(scope: Construct, id: string, props: ActiveDirectoryProps) { - super(scope, id); - - const activeDirectory = new cdk.aws_directoryservice.CfnMicrosoftAD( - this, - pascalCase(`${props.directoryName}ActiveDirectory`), - { - name: props.dnsName, - password: props.adminSecretValue.toString(), - vpcSettings: { vpcId: props.vpcId, subnetIds: props.madSubnetIds }, - edition: props.edition, - shortName: props.netBiosDomainName, - }, - ); - - // Create log subscription - new ActiveDirectoryLogSubscription(this, pascalCase(`${props.directoryName}LogSubscription`), { - activeDirectory: activeDirectory, - activeDirectoryLogGroupName: props.logGroupName, - activeDirectoryLogRetentionInDays: props.logRetentionInDays, - lambdaKmsKey: props.lambdaKey, - cloudWatchLogsKmsKey: props.cloudwatchKey, - logRetentionInDays: props.cloudwatchLogRetentionInDays, - }); - - this.id = activeDirectory.ref; - this.dnsIpAddresses = activeDirectory.attrDnsIpAddresses; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/index.ts deleted file mode 100644 index c28a126..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/index.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const directoryId = event.ResourceProperties['directoryId']; - const logGroupName = event.ResourceProperties['logGroupName']; - const solutionId = process.env['SOLUTION_ID']; - - const directoryServiceClient = new AWS.DirectoryService({ customUserAgent: solutionId }); - - const existingLogGroups = await getExistingLogGroups(directoryServiceClient, directoryId); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('start createLogSubscription'); - if (existingLogGroups.indexOf(logGroupName) === -1) { - await throttlingBackOff(() => - directoryServiceClient - .createLogSubscription({ - DirectoryId: directoryId, - LogGroupName: logGroupName, - }) - .promise(), - ); - } else { - console.warn(`Log group ${logGroupName} already subscribed for the directory service`); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - console.log('start deleteLogSubscription'); - if (existingLogGroups.indexOf(logGroupName) !== -1) { - await throttlingBackOff(() => - directoryServiceClient - .deleteLogSubscription({ - DirectoryId: directoryId, - }) - .promise(), - ); - } else { - console.warn(`Log group ${logGroupName} subscription not found to delete`); - } - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Function to get existing log group names - * @param directoryServiceClient - * @param directoryId - * @returns - */ -async function getExistingLogGroups( - directoryServiceClient: AWS.DirectoryService, - directoryId: string, -): Promise { - const logGroupNames: string[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - directoryServiceClient - .listLogSubscriptions({ - DirectoryId: directoryId, - NextToken: nextToken, - }) - .promise(), - ); - - for (const LogSubscription of page.LogSubscriptions ?? []) { - if (LogSubscription.LogGroupName) { - logGroupNames.push(LogSubscription.LogGroupName); - } - } - nextToken = page.NextToken; - } while (nextToken); - - return logGroupNames; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/package.json deleted file mode 100644 index 0f5262c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directory-service-create-log-subscription", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/create-log-subscription/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-active-directory.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-active-directory.ts deleted file mode 100644 index dbfdbc6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-active-directory.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; - -/** - * Initialized ShareActiveDirectoryProps properties - */ -export interface ShareActiveDirectoryProps { - /** - * Managed active directory id - */ - readonly directoryId: string; - /** - * Managed active directory share account ids - */ - readonly sharedTargetAccountIds: string[]; - /** - * Accepter account access role name. Custom resource will assume this role to approve share request - */ - readonly accountAccessRoleName: string; - /** - * Custom resource lambda key, when undefined default AWS managed key will be used - */ - readonly lambdaKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log retention in days - */ - readonly cloudwatchLogRetentionInDays: number; -} - -/** - * Managed active directory share class. - */ -export class ShareActiveDirectory extends Construct { - public readonly id: string; - constructor(scope: Construct, id: string, props: ShareActiveDirectoryProps) { - super(scope, id); - - const providerLambda = new cdk.aws_lambda.Function(this, 'ShareManageActiveDirectoryFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'share-directory/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - description: 'Share Manage active directory handler', - environmentEncryption: props.lambdaKey, - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'DirectoryServices', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ds:ShareDirectory', 'ds:UnshareDirectory', 'ds:DescribeSharedDirectories'], - resources: ['*'], - }), - ); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'StsAssumeRoleActions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.accountAccessRoleName}`], - }), - ); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.cloudwatchLogRetentionInDays, - encryptionKey: props.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'ShareManageActiveDirectoryProvider', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::ShareActiveDirectory', - serviceToken: provider.serviceToken, - properties: { - partition: cdk.Stack.of(this).partition, - region: cdk.Stack.of(this).region, - madAccountId: cdk.Stack.of(this).account, - directoryId: props.directoryId, - shareTargetAccountIds: props.sharedTargetAccountIds, - assumeRoleName: props.accountAccessRoleName, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/index.ts deleted file mode 100644 index b79bdb1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/index.ts +++ /dev/null @@ -1,302 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * share directory - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - console.log(event); - const region = event.ResourceProperties['region']; - const partition = event.ResourceProperties['partition']; - const madAccountId = event.ResourceProperties['madAccountId']; - const directoryId = event.ResourceProperties['directoryId']; - const newTargetAccountIds: string[] = event.ResourceProperties['shareTargetAccountIds']; - const assumeRoleName = event.ResourceProperties['assumeRoleName']; - - const solutionId = process.env['SOLUTION_ID']; - - const directoryServiceClient = new AWS.DirectoryService({ region: region, customUserAgent: solutionId }); - - const existingSharedAccountIds = await getExistingSharedAccountIds(directoryServiceClient, directoryId); - - console.log(`Existing shared account is ${existingSharedAccountIds}`); - - const excludeSharedAccountIds: string[] = []; - const includeSharedAccountIds: string[] = []; - - if (existingSharedAccountIds.length > 0) { - excludeSharedAccountIds.push(...existingSharedAccountIds.filter(item => !newTargetAccountIds.includes(item))); - includeSharedAccountIds.push(...newTargetAccountIds.filter(item => !existingSharedAccountIds.includes(item))); - } else { - includeSharedAccountIds.push(...newTargetAccountIds); - } - - console.log(`Include shared account is ${includeSharedAccountIds}`); - console.log(`Exclude shared account is ${excludeSharedAccountIds}`); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('start ShareDirectory'); - - for (const account of includeSharedAccountIds) { - if (madAccountId === account) { - console.warn( - `The Target AWS account ID ${account} you specified cannot be the same as the directory owner AWS account ID ${madAccountId}, skipping share request!!!!`, - ); - continue; - } - console.log(`Start: Directory ${directoryId} share with account ${account}`); - const targetDirectoryId = await shareDirectory(directoryServiceClient, directoryId, account); - await acceptShare( - directoryServiceClient, - region, - partition, - directoryId, - targetDirectoryId, - account, - assumeRoleName, - solutionId, - ); - // } - } - - console.log('start un-share Directory'); - for (const account of excludeSharedAccountIds) { - await unshareDirectory(directoryServiceClient, directoryId, account); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - for (const account of existingSharedAccountIds) { - await unshareDirectory(directoryServiceClient, directoryId, account); - await sleep(30000); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Function to accept directory share request - * @param sourceDirectoryServiceClient - * @param region - * @param sourceDirectoryId - * @param targetDirectoryId - * @param accountId - * @param assumeRoleName - * @param solutionId - */ -async function acceptShare( - sourceDirectoryServiceClient: AWS.DirectoryService, - region: string, - partition: string, - sourceDirectoryId: string, - targetDirectoryId: string, - accountId: string, - assumeRoleName: string, - solutionId?: string, -): Promise { - const shareStatus = await getSharedAccountStatus(sourceDirectoryServiceClient, sourceDirectoryId, accountId); - - if (shareStatus === 'PendingAcceptance') { - const roleArn = `arn:${partition}:iam::${accountId}:role/${assumeRoleName}`; - console.log(`Role arn : ${roleArn}`); - const stsClient = new AWS.STS({ region: region, customUserAgent: solutionId }); - console.log(`Assume role in target account ${accountId}, role arn is ${roleArn}`); - const assumeRoleCredential = await throttlingBackOff(() => - stsClient - .assumeRole({ - RoleArn: roleArn, - RoleSessionName: 'AcceptMadShareSession', - }) - .promise(), - ); - - const targetDirectoryServiceClient = new AWS.DirectoryService({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expireTime: assumeRoleCredential.Credentials!.Expiration, - }, - customUserAgent: solutionId, - }); - - console.log(`Start: Accepting share directory for target account ${accountId}`); - await throttlingBackOff(() => - targetDirectoryServiceClient - .acceptSharedDirectory({ - SharedDirectoryId: targetDirectoryId, - }) - .promise(), - ); - } else { - console.warn(`Share account ${accountId} status is ${shareStatus}, skipping acceptance`); - } - - console.log(`End: Accepting share directory for target account ${accountId}`); -} - -/** - * Function to unshare directory - * @param directoryServiceClient - * @param directoryId - * @param accountId - */ -async function unshareDirectory( - directoryServiceClient: AWS.DirectoryService, - directoryId: string, - accountId: string, -): Promise { - const shareStatus = await getSharedAccountStatus(directoryServiceClient, directoryId, accountId); - - if (shareStatus === 'Shared') { - await throttlingBackOff(() => - directoryServiceClient - .unshareDirectory({ - DirectoryId: directoryId, - UnshareTarget: { Id: accountId, Type: 'ACCOUNT' }, - }) - .promise(), - ); - } else { - throw new Error(`Target account ${accountId} share status is ${shareStatus}, skipped unshare !!!!`); - } -} - -/** - * Function to share directory to target account - * @param directoryServiceClient - * @param directoryId - * @param accountId - * @returns - */ -async function shareDirectory( - directoryServiceClient: AWS.DirectoryService, - directoryId: string, - accountId: string, -): Promise { - const response = await throttlingBackOff(() => - directoryServiceClient - .shareDirectory({ - DirectoryId: directoryId, - ShareNotes: 'Shared by LZA', - ShareMethod: 'HANDSHAKE', - ShareTarget: { Id: accountId, Type: 'ACCOUNT' }, - }) - .promise(), - ); - - let shareStatus = await getSharedAccountStatus(directoryServiceClient, directoryId, accountId); - - do { - console.warn( - `Account ${accountId} share status ${shareStatus} is not PendingAcceptance, sleeping for 10 seconds before rechecking`, - ); - await sleep(10000); - shareStatus = await getSharedAccountStatus(directoryServiceClient, directoryId, accountId); - } while (shareStatus !== 'PendingAcceptance'); - - return response.SharedDirectoryId!; -} - -/** - * Function to get shared account status - * @param directoryServiceClient - * @param directoryId - * @param accountId - * @returns - */ -async function getSharedAccountStatus( - directoryServiceClient: AWS.DirectoryService, - directoryId: string, - accountId: string, -): Promise { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - directoryServiceClient - .describeSharedDirectories({ - OwnerDirectoryId: directoryId, - NextToken: nextToken, - }) - .promise(), - ); - - for (const sharedDirectory of page.SharedDirectories ?? []) { - if (sharedDirectory.SharedAccountId! === accountId) { - return sharedDirectory.ShareStatus!; - } - } - nextToken = page.NextToken; - } while (nextToken); - - throw new Error(`Shared status not found for Directory ${directoryId} with share target account ${accountId}`); -} - -/** - * Function to get existing shared accounts ids - * @param directoryServiceClient - * @param directoryId - * @returns - */ -async function getExistingSharedAccountIds( - directoryServiceClient: AWS.DirectoryService, - directoryId: string, -): Promise { - const sharedAccounts: string[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - directoryServiceClient - .describeSharedDirectories({ - OwnerDirectoryId: directoryId, - NextToken: nextToken, - }) - .promise(), - ); - - for (const sharedDirectory of page.SharedDirectories ?? []) { - sharedAccounts.push(sharedDirectory.SharedAccountId!); - } - nextToken = page.NextToken; - } while (nextToken); - - return sharedAccounts; -} - -/** - * Sleep for a specified number of milliseconds - * @param ms - * @returns - */ -async function sleep(ms: number) { - return new Promise(f => setTimeout(f, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/package.json deleted file mode 100644 index a5d95d4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directory-service-share-directory", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/share-directory/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/index.ts deleted file mode 100644 index 6fb2931..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/index.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const executingAccountId = event.ResourceProperties['executingAccountId']; - const partition = event.ResourceProperties['partition']; - const roleName = event.ResourceProperties['roleName']; - const route53ResolverRuleName = event.ResourceProperties['route53ResolverRuleName']; - const targetIps = event.ResourceProperties['targetIps']; - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - - let route53ResolverClient = new AWS.Route53Resolver({ customUserAgent: solutionId }); - - const ruleOwnerId = await getRuleOwnerId(route53ResolverClient, route53ResolverRuleName); - if (!ruleOwnerId) { - throw new Error(`Resolver rule ${route53ResolverRuleName} owner id not found !!!`); - } - - const stsClient = new AWS.STS({ customUserAgent: solutionId, region: region }); - if (ruleOwnerId !== executingAccountId) { - const assumeRoleResponse = await throttlingBackOff(() => - stsClient - .assumeRole({ - RoleArn: `arn:${partition}:iam::${ruleOwnerId}:role/${roleName}`, - RoleSessionName: 'UpdateRuleAssumeSession', - }) - .promise(), - ); - - route53ResolverClient = new AWS.Route53Resolver({ - credentials: { - accessKeyId: assumeRoleResponse.Credentials?.AccessKeyId ?? '', - secretAccessKey: assumeRoleResponse.Credentials?.SecretAccessKey ?? '', - sessionToken: assumeRoleResponse.Credentials?.SessionToken, - }, - customUserAgent: solutionId, - }); - } - - // const resolverRuleId = await getResolverId(route53ResolverClient, route53ResolverRuleName); - const resolverRuleDetails = await getResolverRuleDetails(route53ResolverClient, route53ResolverRuleName); - if (!resolverRuleDetails.ruleId) { - throw new Error(`Resolver rule ${route53ResolverRuleName} not found !!!`); - } - - if (!resolverRuleDetails.resolverEndpointId) { - throw new Error(`Resolver endpoint not found for rule ${route53ResolverRuleName}!!!`); - } - - const updatedDnsIps: { Ip: string; Port: number | undefined }[] = []; - - for (const targetIp of targetIps) { - updatedDnsIps.push({ Ip: targetIp, Port: resolverRuleDetails.port }); - } - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('start updateResolverRule'); - - await throttlingBackOff(() => - route53ResolverClient - .updateResolverRule({ - ResolverRuleId: resolverRuleDetails.ruleId!, - Config: { - Name: route53ResolverRuleName, - ResolverEndpointId: resolverRuleDetails.resolverEndpointId!, - TargetIps: updatedDnsIps, - }, - }) - .promise(), - ); - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Function to get resolver rule owner Id - * @param route53ResolverClient - * @param route53ResolverRuleName - * @returns - */ -async function getRuleOwnerId( - route53ResolverClient: AWS.Route53Resolver, - route53ResolverRuleName: string, -): Promise { - console.log('Start - getResolverId'); - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - route53ResolverClient - .listResolverRules({ - Filters: [{ Name: 'Name', Values: [route53ResolverRuleName] }], - NextToken: nextToken, - }) - .promise(), - ); - - if (page.ResolverRules && page.ResolverRules.length === 0) { - throw new Error(`Resolver rule ${route53ResolverRuleName} not found !!!`); - } - - for (const resolverRules of page.ResolverRules ?? []) { - return resolverRules.OwnerId; - } - nextToken = page.NextToken; - } while (nextToken); - - return undefined; -} - -/** - * Function to get resolver rule details such as dns port, rule id ad endpoint id - * @param route53ResolverClient - * @param route53ResolverRuleName - * @returns - */ -async function getResolverRuleDetails( - route53ResolverClient: AWS.Route53Resolver, - route53ResolverRuleName: string, -): Promise<{ resolverEndpointId: string | undefined; ruleId: string | undefined; port: number | undefined }> { - console.log('Start - getResolverRuleDetails'); - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - route53ResolverClient - .listResolverRules({ - Filters: [{ Name: 'Name', Values: [route53ResolverRuleName] }], - NextToken: nextToken, - }) - .promise(), - ); - - if (page.ResolverRules && page.ResolverRules.length === 0) { - throw new Error(`Resolver rule ${route53ResolverRuleName} not found !!!`); - } - - for (const resolverRules of page.ResolverRules ?? []) { - for (const targetIp of resolverRules.TargetIps ?? []) { - return { resolverEndpointId: resolverRules.ResolverEndpointId, ruleId: resolverRules.Id, port: targetIp.Port }; - } - } - nextToken = page.NextToken; - } while (nextToken); - - throw new Error(`Resolver rule ${route53ResolverRuleName} not found !!!`); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/package.json deleted file mode 100644 index 110c479..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-directory-service-update-resolver-role", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-directory-service/update-resolver-role/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/index.ts deleted file mode 100644 index 119ebea..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/index.ts +++ /dev/null @@ -1,533 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -/** - * delete-default-vpc - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const requesterAccountId = event.ResourceProperties['requesterAccountId']; - const requesterRegion = event.ResourceProperties['requesterRegion']; - const requesterTransitGatewayRouteTableId = event.ResourceProperties['requesterTransitGatewayRouteTableId']; - const requesterTransitGatewayId = event.ResourceProperties['requesterTransitGatewayId']; - - const accepterAccountId = event.ResourceProperties['accepterAccountId']; - const accepterRegion = event.ResourceProperties['accepterRegion']; - const accepterTransitGatewayId = event.ResourceProperties['accepterTransitGatewayId']; - const accepterTransitGatewayRouteTableId = event.ResourceProperties['accepterTransitGatewayRouteTableId']; - - const autoAccept: boolean = event.ResourceProperties['autoAccept'] === 'true'; - const peeringTags: { Key: string; Value: string }[] | undefined = event.ResourceProperties['peeringTags']; - - const requesterTransitGatewayAttachmentId = event.ResourceProperties['requesterTransitGatewayAttachmentId']; - - const solutionId = process.env['SOLUTION_ID']; - - const requesterEc2Client = new AWS.EC2({ region: requesterRegion }); - - const accepterEc2Client = await getAccepterEc2Client( - accepterRegion, - event.ResourceProperties['accepterRoleArn'], - solutionId, - ); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const accepterTransitGatewayAttachmentId = await getAccepterTransitGatewayAttachmentID( - accepterEc2Client, - accepterTransitGatewayId, - accepterAccountId, - requesterTransitGatewayId, - requesterTransitGatewayAttachmentId, - ); - - if (autoAccept) { - console.log(`Starting - Transit Gateway Peering attachment acceptance`); - - let attachmentStatus = await getAttachmentState(requesterEc2Client, requesterTransitGatewayAttachmentId); - - await checkAttachmentInvalidStateForAcceptance(requesterEc2Client, requesterTransitGatewayAttachmentId); - - while (attachmentStatus !== 'pendingAcceptance' && attachmentStatus !== 'available') { - await delay(1000); - console.log( - `Waiting for transit gateway attachment id ${requesterTransitGatewayAttachmentId} to be in pendingAcceptance or available state in requester account ${requesterAccountId}, current state is : ${attachmentStatus}`, - ); - attachmentStatus = await getAttachmentState(requesterEc2Client, requesterTransitGatewayAttachmentId); - await checkAttachmentInvalidStateForAcceptance(requesterEc2Client, requesterTransitGatewayAttachmentId); - } - - console.log( - `Attachment state is : ${attachmentStatus} in requester account ${requesterAccountId} for transit gateway attachment id ${requesterTransitGatewayAttachmentId}, proceeding with acceptTransitGatewayPeeringAttachment operation in accepter account ${accepterAccountId}`, - ); - - if (!accepterTransitGatewayAttachmentId) { - throw new Error( - `Transit gateway attachment id not found in accepter account ${accepterAccountId} for requester transit gateway attachment id ${requesterTransitGatewayAttachmentId}`, - ); - } - - attachmentStatus = await getAttachmentState(accepterEc2Client, accepterTransitGatewayAttachmentId); - - await checkAttachmentInvalidStateForAcceptance(accepterEc2Client, accepterTransitGatewayAttachmentId); - - while (attachmentStatus !== 'pendingAcceptance' && attachmentStatus !== 'available') { - await delay(1000); - console.log( - `Waiting for transit gateway attachment id ${accepterTransitGatewayAttachmentId} to be in pendingAcceptance or available state in accepter account ${accepterAccountId}, current state is : ${attachmentStatus}`, - ); - attachmentStatus = await getAttachmentState(accepterEc2Client, accepterTransitGatewayAttachmentId); - await checkAttachmentInvalidStateForAcceptance(accepterEc2Client, accepterTransitGatewayAttachmentId); - } - - if (attachmentStatus !== 'available') { - console.log( - `Attachment state is : ${attachmentStatus} in accepter account ${accepterAccountId} for transit gateway attachment id ${accepterTransitGatewayAttachmentId}, proceeding with acceptTransitGatewayPeeringAttachment operation`, - ); - - const response = await throttlingBackOff(() => - accepterEc2Client - .acceptTransitGatewayPeeringAttachment({ TransitGatewayAttachmentId: accepterTransitGatewayAttachmentId }) - .promise(), - ); - - console.log( - `Transit gateway attachment id ${accepterTransitGatewayAttachmentId} acceptTransitGatewayPeeringAttachment operation completed in accepter account ${accepterAccountId}, waiting to be in available state, current state is ${response.TransitGatewayPeeringAttachment?.State}`, - ); - - attachmentStatus = await getAttachmentState(accepterEc2Client, accepterTransitGatewayAttachmentId); - while (attachmentStatus !== 'available') { - await delay(60000); - console.log( - `Waiting for attachment id ${accepterTransitGatewayAttachmentId} in accepter account ${accepterAccountId} to be in available state, current state is : ${attachmentStatus}`, - ); - attachmentStatus = await getAttachmentState(accepterEc2Client, accepterTransitGatewayAttachmentId); - } - } else { - console.log( - `Attachment state is : ${attachmentStatus} in accepter account ${accepterAccountId} for transit gateway attachment id ${accepterTransitGatewayAttachmentId}, acceptTransitGatewayPeeringAttachment operation not needed`, - ); - } - } - console.log(`Starting route table association`); - - // Associate requester attachment to route table - await associateTransitGatewayRouteTable( - requesterAccountId, - requesterTransitGatewayAttachmentId, - requesterTransitGatewayRouteTableId, - requesterEc2Client, - ); - - if (accepterTransitGatewayAttachmentId) { - // Associate accepter attachment to route table - await associateTransitGatewayRouteTable( - accepterAccountId, - accepterTransitGatewayAttachmentId, - accepterTransitGatewayRouteTableId, - accepterEc2Client, - ); - - // Create tags in accepter attachment - await createAccepterAttachmentTags(accepterEc2Client, accepterTransitGatewayAttachmentId, peeringTags); - } - - return { - PhysicalResourceId: requesterTransitGatewayAttachmentId, - Status: 'SUCCESS', - }; - - case 'Delete': - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Function to associate transit gateway route table - * @param account - * @param transitGatewayAttachmentId - * @param transitGatewayRouteTableId - * @param ec2Client - */ -async function associateTransitGatewayRouteTable( - account: string, - transitGatewayAttachmentId: string, - transitGatewayRouteTableId: string, - ec2Client: AWS.EC2, -): Promise { - console.log( - `In Account ${account}: Start of associateTransitGatewayRouteTable, for transitGateway attachment ${transitGatewayAttachmentId} with route table ${transitGatewayRouteTableId}`, - ); - - // Get attachment association information - const attachmentAssociation = await getAttachmentAssociation(ec2Client, transitGatewayAttachmentId, account); - - // - // When NO association found OR given route table disassociated - // Then associate the route table - if ( - !attachmentAssociation || - (attachmentAssociation.State === 'disassociated' && - attachmentAssociation.TransitGatewayRouteTableId === transitGatewayRouteTableId) - ) { - console.log( - `Start: Association of transitGateway attachment ${transitGatewayAttachmentId} to route table ${transitGatewayRouteTableId}, in account ${account}`, - ); - const response = await throttlingBackOff(() => - ec2Client - .associateTransitGatewayRouteTable({ - TransitGatewayAttachmentId: transitGatewayAttachmentId, - TransitGatewayRouteTableId: transitGatewayRouteTableId, - }) - .promise(), - ); - console.log( - `Completed: Association of transitGateway attachment ${transitGatewayAttachmentId} to route table ${transitGatewayRouteTableId}, in account ${account}, association state is ${response.Association?.State}`, - ); - } else if ( - attachmentAssociation.State === 'associated' && - attachmentAssociation.TransitGatewayRouteTableId !== transitGatewayRouteTableId - ) { - // When association found but update requested to change associated route table - // Disassociate current route table and associate to given route table - - console.log( - `Route table ${attachmentAssociation.TransitGatewayRouteTableId} already associated with transit gateway attachment ${transitGatewayAttachmentId} in account ${account}`, - ); - console.log( - `Start: disassociation of route table ${attachmentAssociation.TransitGatewayRouteTableId} from transit gateway attachment ${transitGatewayAttachmentId} in account ${account}`, - ); - const disassociationResponse = await throttlingBackOff(() => - ec2Client - .disassociateTransitGatewayRouteTable({ - TransitGatewayAttachmentId: transitGatewayAttachmentId, - TransitGatewayRouteTableId: attachmentAssociation.TransitGatewayRouteTableId!, - }) - .promise(), - ); - console.log( - `Completed: disassociation of route table ${attachmentAssociation.TransitGatewayRouteTableId} from transit gateway attachment ${transitGatewayAttachmentId} in account ${account}, association state is ${disassociationResponse.Association?.State}`, - ); - - console.log( - `Start: Re-association of transitGateway attachment ${transitGatewayAttachmentId} to route table ${transitGatewayRouteTableId}, in account ${account}`, - ); - - let reAssociationStatus = await reAssociateRouteTable( - ec2Client, - transitGatewayAttachmentId, - transitGatewayRouteTableId, - account, - ); - - // Disassociation takes time so sleep and try to re-associate - while (!reAssociationStatus) { - console.log( - 'Re-association failed because disassociation still in-progress, sleeping 1 minute before starting again', - ); - await delay(60000); - reAssociationStatus = await reAssociateRouteTable( - ec2Client, - transitGatewayAttachmentId, - transitGatewayRouteTableId, - account, - ); - } - } -} - -/** - * Function to re-associate route table - * @param ec2Client - * @param transitGatewayAttachmentId - * @param transitGatewayRouteTableId - * @param account - * @returns - */ -async function reAssociateRouteTable( - ec2Client: AWS.EC2, - transitGatewayAttachmentId: string, - transitGatewayRouteTableId: string, - account: string, -): Promise { - try { - const associateResponse = await throttlingBackOff(() => - ec2Client - .associateTransitGatewayRouteTable({ - TransitGatewayAttachmentId: transitGatewayAttachmentId, - TransitGatewayRouteTableId: transitGatewayRouteTableId, - }) - .promise(), - ); - console.log( - `Completed: Re-association of transitGateway attachment ${transitGatewayAttachmentId} to route table ${transitGatewayRouteTableId}, in account ${account}, association state is ${associateResponse.Association?.State}`, - ); - return true; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'Resource.AlreadyAssociated' || - // SDKv3 Error Structure - e.name === 'Resource.AlreadyAssociated' - ) { - return false; - } - throw new Error(e); - } -} - -/** - * Function to get accepter transit gateway attachment id - * @param ec2Client - * @param accepterTransitGatewayId - * @param accepterAccountId - * @param requesterTransitGatewayId - * @param requesterTransitGatewayAttachmentId - * @returns - */ -async function getAccepterTransitGatewayAttachmentID( - ec2Client: AWS.EC2, - accepterTransitGatewayId: string, - accepterAccountId: string, - requesterTransitGatewayId: string, - requesterTransitGatewayAttachmentId: string, -): Promise { - const response = await throttlingBackOff(() => - ec2Client - .describeTransitGatewayAttachments({ - Filters: [ - { Name: 'resource-type', Values: ['peering'] }, - { Name: 'transit-gateway-id', Values: [accepterTransitGatewayId] }, - { Name: 'resource-id', Values: [requesterTransitGatewayId] }, - { - Name: 'state', - Values: ['pendingAcceptance', 'pending', 'initiatingRequest', 'initiating', 'modifying', 'available'], - }, - ], - }) - .promise(), - ); - if (response.TransitGatewayAttachments) { - if (response.TransitGatewayAttachments.length === 1) { - return response.TransitGatewayAttachments[0].TransitGatewayAttachmentId; - } - - if (response.TransitGatewayAttachments.length === 0) { - throw new Error( - `Transit gateway attachment id not found in accepter account ${accepterAccountId} for requester transit gateway attachment id ${requesterTransitGatewayAttachmentId}`, - ); - } - - if (response.TransitGatewayAttachments.length > 1) { - const id: string[] = []; - for (const item of response.TransitGatewayAttachments) { - id.push(item.TransitGatewayAttachmentId!); - } - throw new Error( - `Multiple transit gateway attachment ids ${id} found in accepter account ${accepterAccountId} for requester transit gateway attachment id ${requesterTransitGatewayAttachmentId}`, - ); - } - } - return undefined; -} - -/** - * Function to get attachment status - * @param transitGatewayAttachmentId - * @param ec2Client - * @returns - */ -async function getAttachmentState( - ec2Client: AWS.EC2, - transitGatewayAttachmentId: string, -): Promise { - const response = await throttlingBackOff(() => - ec2Client - .describeTransitGatewayPeeringAttachments({ - Filters: [{ Name: 'transit-gateway-attachment-id', Values: [transitGatewayAttachmentId] }], - }) - .promise(), - ); - - if (response.TransitGatewayPeeringAttachments?.length === 1) { - console.log( - `Transit gateway attachment ${transitGatewayAttachmentId} state is ${response.TransitGatewayPeeringAttachments[0].State}`, - ); - return response.TransitGatewayPeeringAttachments[0].State; - } - - return undefined; -} - -/** - * Function to check for Invalid attachment state for acceptance - * @param ec2Client - * @param transitGatewayAttachmentId - */ -async function checkAttachmentInvalidStateForAcceptance( - ec2Client: AWS.EC2, - transitGatewayAttachmentId: string, -): Promise { - const response = await throttlingBackOff(() => - ec2Client - .describeTransitGatewayPeeringAttachments({ - Filters: [{ Name: 'transit-gateway-attachment-id', Values: [transitGatewayAttachmentId] }], - }) - .promise(), - ); - - if (response.TransitGatewayPeeringAttachments?.length === 1) { - if ( - response.TransitGatewayPeeringAttachments[0].State === 'failed' || - response.TransitGatewayPeeringAttachments[0].State === 'failing' || - response.TransitGatewayPeeringAttachments[0].State === 'deleted' || - response.TransitGatewayPeeringAttachments[0].State === 'deleting' || - response.TransitGatewayPeeringAttachments[0].State === 'rejected' || - response.TransitGatewayPeeringAttachments[0].State === 'rejecting' || - response.TransitGatewayPeeringAttachments[0].State === 'rollingBack' - ) { - throw new Error( - `Transit gateway attachment ${transitGatewayAttachmentId} in ${response.TransitGatewayPeeringAttachments[0].State}, acceptance of attachment can not be performed !!!`, - ); - } - } -} - -/** - * Function to get transit gateway attachment associated route table id - * @param ec2Client - * @param transitGatewayAttachmentId - * @param account - * @returns - */ -async function getAttachmentAssociation( - ec2Client: AWS.EC2, - transitGatewayAttachmentId: string, - account: string, -): Promise { - const response = await throttlingBackOff(() => - ec2Client - .describeTransitGatewayAttachments({ - Filters: [ - { Name: 'resource-type', Values: ['peering'] }, - { Name: 'transit-gateway-attachment-id', Values: [transitGatewayAttachmentId] }, - ], - }) - .promise(), - ); - - if (response.TransitGatewayAttachments) { - if (response.TransitGatewayAttachments.length === 1) { - return response.TransitGatewayAttachments[0].Association; - } else if (response.TransitGatewayAttachments.length === 0) { - return undefined; - } else { - throw new Error( - `Multiple transit gateway attachments ${transitGatewayAttachmentId} found in account ${account} !!!`, - ); - } - } else { - throw new Error(`Transit gateway attachment ${transitGatewayAttachmentId} not found in account ${account} !!!`); - } -} - -/** - * Function to create tags for accepter transit gateway peering attachment - * @param ec2Client - * @param accepterTransitGatewayAttachmentId - */ -async function createAccepterAttachmentTags( - ec2Client: AWS.EC2, - accepterTransitGatewayAttachmentId: string, - peeringTags: { Key: string; Value: string }[] | undefined, -): Promise { - const tags: { Key: string; Value: string }[] = []; - - if (peeringTags) { - for (const peeringTag of peeringTags ?? []) { - tags.push(peeringTag as { Key: string; Value: string }); - } - - if (tags.length > 0) { - await throttlingBackOff(() => - ec2Client - .createTags({ - Resources: [accepterTransitGatewayAttachmentId], - Tags: tags, - }) - .promise(), - ); - } - } -} - -/** - * Function to create accepter EC2 client - * @param accepterRegion - * @param accepterRoleArn - * @returns - */ -async function getAccepterEc2Client( - accepterRegion: string, - accepterRoleArn: string, - solutionId?: string, -): Promise { - const stsClient = new AWS.STS({ customUserAgent: solutionId, region: accepterRegion }); - - const assumeRoleResponse = await throttlingBackOff(() => - stsClient - .assumeRole({ - RoleArn: accepterRoleArn, - RoleSessionName: 'AccepterTransitGatewayAttachmentSession', - }) - .promise(), - ); - - return new AWS.EC2({ - credentials: { - accessKeyId: assumeRoleResponse.Credentials?.AccessKeyId ?? '', - secretAccessKey: assumeRoleResponse.Credentials?.SecretAccessKey ?? '', - sessionToken: assumeRoleResponse.Credentials?.SessionToken, - }, - region: accepterRegion, - }); -} - -/** - * Function to sleep process - * @param ms - * @returns - */ -function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/package.json deleted file mode 100644 index 0f802ef..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-accept-transit-gateway-peering-attachment", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/accept-transit-gateway-peering-attachment/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/index.ts deleted file mode 100644 index 84caba3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/index.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * aws-ec2-pre-warm-account-status - lambda handler - * - * @param event - * @returns - */ - -import { SSMClient, PutParameterCommand } from '@aws-sdk/client-ssm'; -import { - EC2Client, - DescribeInstancesCommand, - DescribeVpcsCommand, - DescribeSubnetsCommand, - DeleteVpcCommand, - DeleteSubnetCommand, - TerminateInstancesCommand, -} from '@aws-sdk/client-ec2'; -import { AdaptiveRetryStrategy } from '@aws-sdk/util-retry'; -import { delay } from '@aws-accelerator/utils/lib/throttle'; - -const solutionId = process.env['SOLUTION_ID'] ?? ''; -const retryStrategy = new AdaptiveRetryStrategy(() => Promise.resolve(5)); -const ec2Client = new EC2Client({ customUserAgent: solutionId, retryStrategy }); -const ssmClient = new SSMClient({ customUserAgent: solutionId, retryStrategy }); - -type InstanceDetails = { - instanceId: string | undefined; - minutesSinceLaunch: number | undefined; -}; - -//eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise< - | { - IsComplete: boolean; - } - | undefined -> { - console.log(event); - const ssmPrefix: string = event.ResourceProperties['ssmPrefix']; - const instanceDetails = await getInstanceDetails(); - - // if no instance was found, it has been deleted return - if (instanceDetails.instanceId === undefined) { - return { IsComplete: true }; - } - - if (instanceDetails.minutesSinceLaunch! > 15) { - console.log('Account warming time reached'); - await terminateInstance(instanceDetails.instanceId); - await deleteVpc(); - await setSSMParameter(ssmPrefix, 'true'); - return { - IsComplete: true, - }; - } - console.log('Waiting for warmup to complete'); - return { IsComplete: false }; -} - -async function getInstanceDetails(): Promise { - console.log('Getting Instance Id'); - const instanceDetails: InstanceDetails = { instanceId: undefined, minutesSinceLaunch: undefined }; - const ec2Instances = await ec2Client.send( - new DescribeInstancesCommand({ Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }), - ); - - if (ec2Instances.Reservations && ec2Instances.Reservations.length < 1) { - return instanceDetails; - } - - if (ec2Instances.Reservations![0].Instances?.length ?? 0 > 0) { - instanceDetails.instanceId = ec2Instances.Reservations![0].Instances![0].InstanceId; - const launchTime = ec2Instances.Reservations![0].Instances![0].LaunchTime; - - const currentTime = new Date(); - const msElapsed = currentTime.getTime() - launchTime!.getTime(); - instanceDetails.minutesSinceLaunch = msElapsed / 1000 / 60; - } - - return instanceDetails; -} - -async function deleteVpc() { - console.log('Deleting VPC'); - const vpcId = await getVpcId(); - - if (vpcId) { - console.log('Deleting subnets'); - const subnets = await ec2Client.send( - new DescribeSubnetsCommand({ Filters: [{ Name: 'vpc-id', Values: [vpcId] }] }), - ); - for (const subnet of subnets.Subnets) { - await ec2Client.send(new DeleteSubnetCommand({ SubnetId: subnet.SubnetId })); - } - console.log(`Deleting VPC with id: ${vpcId}`); - await ec2Client.send(new DeleteVpcCommand({ VpcId: vpcId })); - } -} - -async function getVpcId(): Promise { - console.log('Getting VPC Id'); - const vpcs = await ec2Client.send( - new DescribeVpcsCommand({ Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }), - ); - - if (vpcs.Vpcs?.length ?? 0 > 0) { - return vpcs.Vpcs![0].VpcId; - } - return undefined; -} - -async function setSSMParameter(ssmPrefix: string, parameterValue: string) { - console.log('Updating SSM Parameter'); - try { - await ssmClient.send( - new PutParameterCommand({ - Name: `${ssmPrefix}/account/pre-warmed`, - Value: parameterValue, - Overwrite: true, - }), - ); - } catch (e) { - console.log(e); - } -} - -async function terminateInstance(instanceId: string): Promise { - console.log(`Terminating EC2 instance: ${instanceId}`); - await ec2Client.send(new TerminateInstancesCommand({ InstanceIds: [instanceId] })); - await waitForTermination(instanceId); -} - -async function waitForTermination(instanceId: string): Promise { - console.log(`Waiting for termination of instanceId: ${instanceId}`); - let ec2Terminated = false; - while (!ec2Terminated) { - const statusResponse = await ec2Client.send(new DescribeInstancesCommand({ InstanceIds: [instanceId] })); - if ( - statusResponse.Reservations[0].Instances[0].State?.Name !== 'terminated' || - statusResponse.Reservations[0].Instances[0].State?.Code !== 48 - ) { - delay(15000); - continue; - } - ec2Terminated = true; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/package.json deleted file mode 100644 index fdc1d3a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-pre-warm-account-status", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/util-retry": "3.374.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming-status/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming.ts deleted file mode 100644 index 5238ecb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import path = require('path'); - -export interface WarmAccountProps { - readonly cloudwatchKmsKey?: cdk.aws_kms.IKey; - readonly logRetentionInDays: number; - readonly ssmPrefix: string; -} - -export class WarmAccount extends Construct { - readonly onEvent: cdk.aws_lambda.IFunction; - readonly isComplete: cdk.aws_lambda.IFunction; - readonly provider: cdk.custom_resources.Provider; - readonly id: string; - - constructor(scope: Construct, id: string, props: WarmAccountProps) { - super(scope, id); - - const WARM_ACCOUNT = 'Custom::WarmAccount'; - - const ec2Policy = new cdk.aws_iam.PolicyStatement({ - sid: 'ec2', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeVpcs', 'ec2:DescribeInstances', 'ec2:DescribeSubnets'], - resources: ['*'], - }); - - const ec2DeletePolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'ec2Delete', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DeleteVpc', 'ec2:DeleteSubnet', 'ec2:TerminateInstances'], - resources: ['*'], - conditions: { - StringEquals: { - 'aws:ResourceTag/Name': 'accelerator-warm', - }, - }, - }); - - const ec2RunPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'ec2Run', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:RunInstances'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:subnet/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:instance/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:volume/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:security-group/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}::image/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}::snapshot/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:network-interface/*`, - ], - }); - - const ec2CreateTags = new cdk.aws_iam.PolicyStatement({ - sid: 'ec2CreateTags', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateTags'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:instance/*`, - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:volume/*`, - ], - conditions: { - StringEquals: { - 'ec2:CreateAction': 'RunInstances', - }, - }, - }); - - const ec2CreateVpcTags = new cdk.aws_iam.PolicyStatement({ - sid: 'ec2CreateVpcTags', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateTags'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:vpc/*`, - ], - conditions: { - StringEquals: { - 'ec2:CreateAction': 'CreateVpc', - }, - }, - }); - - const ec2CreateSubnetTags = new cdk.aws_iam.PolicyStatement({ - sid: 'ec2CreateSubnetTags', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateTags'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ec2:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:subnet/*`, - ], - conditions: { - StringEquals: { - 'ec2:CreateAction': 'CreateSubnet', - }, - }, - }); - - const ec2CreationPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'vpccreation', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateVpc', 'ec2:CreateSubnet'], - resources: ['*'], - }); - - const ssmPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'ssm', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter', 'ssm:PutParameter', 'ssm:DeleteParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter${ - props.ssmPrefix - }/account/pre-warmed`, - ], - }); - - const ssmAmiPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'ssmami', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:${ - cdk.Stack.of(this).region - }::parameter/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2`, - ], - }); - - this.onEvent = new cdk.aws_lambda.Function(this, 'WarmAccountFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'account-warming/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(5), - description: 'Account warming onEvent handler', - initialPolicy: [ - ec2Policy, - ec2DeletePolicy, - ec2CreationPolicy, - ec2RunPolicy, - ec2CreateTags, - ec2CreateVpcTags, - ec2CreateSubnetTags, - ssmPolicy, - ssmAmiPolicy, - ], - }); - - const onEventLogGroup = new cdk.aws_logs.LogGroup(this, `${this.onEvent.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.onEvent.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudwatchKmsKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - this.isComplete = new cdk.aws_lambda.Function(this, 'WarmAccountStatusFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'account-warming-status/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(5), - description: 'Account warming isComplete handler', - initialPolicy: [ec2Policy, ec2DeletePolicy, ssmPolicy], - }); - - const isCompleteLogGroup = new cdk.aws_logs.LogGroup(this, `${this.isComplete.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.isComplete.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudwatchKmsKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - this.provider = new cdk.custom_resources.Provider(this, 'WarmAccountProvider', { - onEventHandler: this.onEvent, - isCompleteHandler: this.isComplete, - queryInterval: cdk.Duration.minutes(2), - totalTimeout: cdk.Duration.hours(1), - }); - - const resource = new cdk.CustomResource(this, 'WarmAccountResource', { - resourceType: WARM_ACCOUNT, - serviceToken: this.provider.serviceToken, - properties: { - ssmPrefix: props.ssmPrefix, - }, - }); - - NagSuppressions.addResourceSuppressions( - this.isComplete, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'Custom resource lambda require access to other services', - }, - { - id: 'AwsSolutions-IAM5', - reason: 'Policy is limited to certain tag properties', - }, - ], - true, - ); - - NagSuppressions.addResourceSuppressions( - this.onEvent, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'Custom resource lambda require access to other services', - }, - { - id: 'AwsSolutions-IAM5', - reason: 'Policy is limited to certain tag properties', - }, - ], - true, - ); - - NagSuppressions.addResourceSuppressions( - this.provider, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'CDK Generated Role', - }, - { - id: 'AwsSolutions-IAM5', - reason: 'CDK Generated Policy', - }, - ], - true, - ); - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(isCompleteLogGroup); - resource.node.addDependency(onEventLogGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/index.ts deleted file mode 100644 index db53ed7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/index.ts +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * aws-ec2-pre-warm-account - lambda handler - * - * @param event - * @returns - */ - -import { - EC2Client, - CreateVpcCommand, - RunInstancesCommand, - CreateSubnetCommand, - DescribeInstancesCommand, - DescribeVpcsCommand, - TerminateInstancesCommand, - DeleteVpcCommand, - DeleteSubnetCommand, - DescribeSubnetsCommand, -} from '@aws-sdk/client-ec2'; -import { - SSMClient, - PutParameterCommand, - GetParameterCommand, - DeleteParameterCommand, - ParameterType, -} from '@aws-sdk/client-ssm'; -import { throttlingBackOff, delay } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { CloudFormationCustomResourceEvent } from '../../lza-custom-resource'; -const solutionId = process.env['SOLUTION_ID'] ?? ''; - -const ec2Client = new EC2Client({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); -const ssmClient = new SSMClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - IsComplete: boolean; - } - | undefined -> { - const ssmPrefix: string = event.ResourceProperties['ssmPrefix']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - const isWarm = await checkWarm(ssmPrefix); - if (isWarm) { - return { - IsComplete: true, - }; - } - await createSsmParameter(ssmPrefix); - await createVpcAndInstance(); - return { - IsComplete: false, - }; - - case 'Delete': - await terminateInstances(); - await deleteVpc(); - await deleteSsmParameter(ssmPrefix); - return { - IsComplete: true, - }; - } -} - -async function checkWarm(ssmPrefix: string): Promise { - console.log('Checking if account has been pre-warmed'); - let warmed = false; - try { - const parameter = await throttlingBackOff(() => - ssmClient.send( - new GetParameterCommand({ - Name: `${ssmPrefix}/account/pre-warmed`, - }), - ), - ); - warmed = (parameter.Parameter?.Value ?? 'false') === 'true'; - } catch (e) { - console.log(`SSM parameter doesn't exist warming account`); - } - return warmed; -} - -async function createSsmParameter(ssmPrefix: string) { - console.log('Creating SSM Parameter'); - try { - await throttlingBackOff(() => - ssmClient.send( - new PutParameterCommand({ - Name: `${ssmPrefix}/account/pre-warmed`, - Value: 'false', - Description: 'Flag for account pre-warming', - Type: ParameterType.STRING, - Overwrite: true, - }), - ), - ); - } catch (e) { - console.log(e); - throw new Error('Failed creating SSM Parameter'); - } -} - -async function createVpcAndInstance() { - console.log('Creating VPC and Subnet'); - const vpcId = await getVpcId(); - const subnetId = await getSubnetId(vpcId); - console.log(`VpcId: ${vpcId}`); - console.log(`SubnetId: ${subnetId}`); - console.log(`SubnetId: ${subnetId}`); - - const instanceId = await getInstanceId(subnetId); - console.log(`Using EC2 Instance Id: ${instanceId}`); -} - -async function deleteSsmParameter(ssmPrefix: string) { - console.log('Deleting SSM Parameter'); - try { - await throttlingBackOff(() => - ssmClient.send( - new DeleteParameterCommand({ - Name: `${ssmPrefix}/account/pre-warmed`, - }), - ), - ); - } catch (e) { - console.log(e); - throw new Error('Failed deleting SSM Parameter'); - } -} - -async function deleteVpc() { - console.log('Deleting VPC'); - const vpcId = await getVpcId(); - - if (vpcId) { - console.log('Deleting subnets'); - const subnets = await throttlingBackOff(() => - ec2Client.send(new DescribeSubnetsCommand({ Filters: [{ Name: 'vpc-id', Values: [vpcId] }] })), - ); - for (const subnet of subnets.Subnets!) { - await throttlingBackOff(() => ec2Client.send(new DeleteSubnetCommand({ SubnetId: subnet.SubnetId }))); - } - console.log(`Deleting VPC with id: ${vpcId}`); - await throttlingBackOff(() => ec2Client.send(new DeleteVpcCommand({ VpcId: vpcId }))); - } -} - -async function getInstanceId(subnetId: string): Promise { - console.log('Getting Instance Id'); - let instanceId: string | undefined; - const ec2Instances = await throttlingBackOff(() => - ec2Client.send(new DescribeInstancesCommand({ Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] })), - ); - if (ec2Instances.Reservations!.length > 0) { - for (const ec2Reservation of ec2Instances.Reservations!) { - for (const ec2Instance of ec2Reservation.Instances!) { - console.log(`Existing EC2 Instance Id, State Code: ${ec2Instance.InstanceId}, ${ec2Instance.State?.Code}`); - if (ec2Instance.State!.Code !== 48 && ec2Instance.State!.Code !== 32) { - instanceId = ec2Instance!.InstanceId!; - } - } - } - } else { - const imageParameter = await throttlingBackOff(() => - ssmClient.send( - new GetParameterCommand({ Name: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' }), - ), - ); - const imageId = imageParameter.Parameter?.Value; - console.log(`AMI Id: ${imageId}`); - - const ec2Instance = await throttlingBackOff(() => - ec2Client.send( - new RunInstancesCommand({ - InstanceType: 't2.micro', - MaxCount: 1, - MinCount: 1, - SubnetId: subnetId, - ImageId: imageId, - TagSpecifications: [{ ResourceType: 'instance', Tags: [{ Key: 'Name', Value: 'accelerator-warm' }] }], - }), - ), - ); - delay(1000); // this delay is needed as program looped and created 2 instances in some cases - console.log(`Created EC2 Instance Id: ${ec2Instance.Instances![0].InstanceId}`); - instanceId = ec2Instance!.Instances![0].InstanceId!; - } - return instanceId!; -} - -async function getVpcId(): Promise { - console.log('Getting VPC Id'); - let vpcId: string; - const vpcs = await throttlingBackOff(() => - ec2Client.send(new DescribeVpcsCommand({ Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] })), - ); - - if (vpcs.Vpcs?.length ?? 0 > 0) { - vpcId = vpcs.Vpcs![0].VpcId!; - } else { - const vpcResponse = await throttlingBackOff(() => - ec2Client.send( - new CreateVpcCommand({ - CidrBlock: '10.10.10.0/24', - TagSpecifications: [{ ResourceType: 'vpc', Tags: [{ Key: 'Name', Value: 'accelerator-warm' }] }], - }), - ), - ); - vpcId = vpcResponse.Vpc!.VpcId!; - } - return vpcId; -} - -async function getSubnetId(vpcId: string): Promise { - console.log('Getting Subnet Id'); - let subnetId: string; - const subnets = await throttlingBackOff(() => - ec2Client.send( - new DescribeSubnetsCommand({ - Filters: [{ Name: 'vpc-id', Values: [vpcId] }], - }), - ), - ); - if (subnets.Subnets!.length > 0) { - subnetId = subnets.Subnets![0].SubnetId!; - } else { - const ec2Subnet = await throttlingBackOff(() => - ec2Client.send( - new CreateSubnetCommand({ - VpcId: vpcId, - CidrBlock: '10.10.10.0/24', - TagSpecifications: [{ ResourceType: 'subnet', Tags: [{ Key: 'Name', Value: 'accelerator-warm' }] }], - }), - ), - ); - subnetId = ec2Subnet.Subnet!.SubnetId!; - } - return subnetId; -} - -async function terminateInstances(): Promise { - console.log('Checking for ec2 instance to terminate'); - const ec2Instances = await throttlingBackOff(() => - ec2Client.send(new DescribeInstancesCommand({ Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] })), - ); - - for (const ec2Reservation of ec2Instances.Reservations!) { - for (const ec2Instance of ec2Reservation.Instances!) { - if (ec2Instance.State?.Name === 'terminated' || ec2Instance.State?.Code === 48) { - continue; - } - if (ec2Instance.State?.Name === 'shutting-down' || ec2Instance.State?.Code === 32) { - await waitForTermination(ec2Instance.InstanceId!); - } - console.log(`Terminating EC2 Instance Id: ${ec2Instance.InstanceId}`); - await throttlingBackOff(() => - ec2Client.send(new TerminateInstancesCommand({ InstanceIds: [ec2Instance.InstanceId!] })), - ); - await waitForTermination(ec2Instance.InstanceId!); - } - } -} - -async function waitForTermination(instanceId: string): Promise { - console.log(`Waiting for termination of instanceId: ${instanceId}`); - let ec2Terminated = false; - while (!ec2Terminated) { - const statusResponse = await throttlingBackOff(() => - ec2Client.send(new DescribeInstancesCommand({ InstanceIds: [instanceId] })), - ); - if ( - statusResponse.Reservations![0].Instances![0].State?.Name !== 'terminated' || - statusResponse.Reservations![0].Instances![0].State?.Code !== 48 - ) { - delay(15000); - continue; - } - ec2Terminated = true; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/package.json deleted file mode 100644 index ec5ff69..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-pre-warm-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/util-retry": "3.374.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/account-warming/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/create-launch-template.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/create-launch-template.ts deleted file mode 100644 index 782a444..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/create-launch-template.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import { NetworkInterfaceItemConfig } from '@aws-accelerator/config'; - -export interface ILaunchTemplateResource extends cdk.IResource { - /** - * The version number. - */ - readonly version: string; - /** - * The ID of the AWS::EC2::LaunchTemplate . You must specify either a LaunchTemplateName or a LaunchTemplateId . - */ - readonly launchTemplateId: string; - /** - * The name of the AWS::EC2::LaunchTemplate . You must specify either a LaunchTemplateName or a LaunchTemplateId . - */ - readonly launchTemplateName: string; -} - -export type EbsProperty = { - deleteOnTermination?: boolean; - encrypted?: boolean; - iops?: number; - kmsKeyId?: string; - snapshotId?: string; - throughput?: number; - volumeSize?: number; - volumeType?: string; -}; - -export type BlockDeviceMappingItem = { - deviceName: string; - ebs?: EbsProperty; -}; - -export interface LaunchTemplateProps { - /* - * Either the path to the user data file or a base-64 encoded userdata string - */ - readonly userData?: string; - /* - * VpcName - */ - readonly vpc: string; - /* - * Name of Launch Template - */ - readonly name: string; - /* - * Name of Application - */ - readonly appName: string; - readonly blockDeviceMappings?: BlockDeviceMappingItem[]; - readonly securityGroups?: string[]; - readonly networkInterfaces?: NetworkInterfaceItemConfig[]; - readonly instanceType: string; - readonly keyPair?: string; - readonly iamInstanceProfile?: string; - readonly imageId: string; - readonly enforceImdsv2?: boolean; -} - -export class LaunchTemplate extends cdk.Resource implements ILaunchTemplateResource { - public readonly version: string; - public readonly launchTemplateId: string; - public readonly launchTemplateName: string; - - constructor(scope: Construct, id: string, props: LaunchTemplateProps) { - super(scope, id); - - let metadataOptions: cdk.aws_ec2.CfnLaunchTemplate.MetadataOptionsProperty; - - if (props.enforceImdsv2 === false) { - metadataOptions = { httpTokens: 'optional' }; - } else { - metadataOptions = { httpTokens: 'required' }; - } - - let networkInterfaceProps: cdk.aws_ec2.CfnLaunchTemplate.NetworkInterfaceProperty[] | undefined = undefined; - if (props.networkInterfaces) { - networkInterfaceProps = this.setNetworkInterfaceProps(props.networkInterfaces); - } - - const launchTemplate = new cdk.aws_ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { - launchTemplateData: { - blockDeviceMappings: props.blockDeviceMappings ?? undefined, - securityGroupIds: props.securityGroups ?? undefined, - networkInterfaces: networkInterfaceProps ?? undefined, - instanceType: props.instanceType, - keyName: props.keyPair ?? undefined, - imageId: props.imageId, - iamInstanceProfile: { name: props.iamInstanceProfile ?? undefined }, - userData: this.processUserData(props.userData), - metadataOptions, - }, - launchTemplateName: props.name, - }); - - this.launchTemplateId = launchTemplate.ref; - this.launchTemplateName = launchTemplate.launchTemplateName!; - this.version = launchTemplate.attrLatestVersionNumber; - } - - private setNetworkInterfaceProps( - interfaces: NetworkInterfaceItemConfig[], - ): cdk.aws_ec2.CfnLaunchTemplate.NetworkInterfaceProperty[] { - const networkInterfaceProps: cdk.aws_ec2.CfnLaunchTemplate.NetworkInterfaceProperty[] = []; - for (const networkInterface of interfaces) { - networkInterfaceProps.push({ - associateCarrierIpAddress: networkInterface.associateCarrierIpAddress, - associatePublicIpAddress: networkInterface.associatePublicIpAddress, - deleteOnTermination: networkInterface.deleteOnTermination, - description: networkInterface.description, - deviceIndex: networkInterface.deviceIndex, - groups: networkInterface.groups, - interfaceType: networkInterface.interfaceType, - networkCardIndex: networkInterface.networkCardIndex, - networkInterfaceId: networkInterface.networkInterfaceId, - privateIpAddress: networkInterface.privateIpAddress, - privateIpAddresses: networkInterface.privateIpAddresses, - secondaryPrivateIpAddressCount: networkInterface.secondaryPrivateIpAddressCount, - subnetId: networkInterface.subnetId, - }); - } - return networkInterfaceProps; - } - - /** - * Process user data file or string - * @param userDataStr string | undefined - * @returns string | undefined - */ - private processUserData(userDataStr?: string): string | undefined { - if (!userDataStr) { - return; - } - // - // If string is a token, return it. It has already been processed upstream - if (cdk.Token.isUnresolved(userDataStr)) { - return userDataStr; - } else { - // - // Else, userDataStr is a file path, return it in base-64 encoding. - return cdk.Fn.base64(fs.readFileSync(userDataStr, 'utf8')); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/index.ts deleted file mode 100644 index bf69638..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/index.ts +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - CreateCustomerGatewayCommand, - CreateTagsCommand, - DeleteCustomerGatewayCommand, - DeleteTagsCommand, - EC2Client, - Tag, -} from '@aws-sdk/client-ec2'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -interface CgwOptions { - /** - * Gateway IP address for customer gateway - */ - readonly ipAddress: string; - /** - * Gateway ASN for customer gateway - */ - readonly bgpAsn: number; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; - /** - * Tags for the customer gateway - */ - readonly tags?: Tag[]; -} - -export async function handler( - event: CloudFormationCustomResourceEvent, -): Promise<{ PhysicalResourceId: string; Status: string } | undefined> { - // - // Set resource properties - const newCgwOptions = setCgwOptions(event.ResourceProperties, event.ServiceToken); - // - // Set EC2 client - const ec2Client = await setEc2Client(newCgwOptions, process.env['SOLUTION_ID']); - // - // Begin custom resource logic - switch (event.RequestType) { - case 'Create': - // - // Create customer gateway - const cgwId = await createCustomerGateway(ec2Client, newCgwOptions); - return { PhysicalResourceId: cgwId, Status: 'SUCCESS' }; - case 'Update': - // - // Set old resource properties - const oldCgwOptions = setCgwOptions(event.OldResourceProperties, event.ServiceToken); - // - // Create new CGW if necessary - if (createNewCgw(oldCgwOptions, newCgwOptions)) { - const newCgwId = await createCustomerGateway(ec2Client, newCgwOptions); - return { PhysicalResourceId: newCgwId, Status: 'SUCCESS' }; - } else { - await updateTags(ec2Client, event.PhysicalResourceId, oldCgwOptions.tags ?? [], newCgwOptions.tags ?? []); - return { PhysicalResourceId: event.PhysicalResourceId, Status: 'SUCCESS' }; - } - case 'Delete': - // - // Delete the CGW - await throttlingBackOff(() => - ec2Client.send(new DeleteCustomerGatewayCommand({ CustomerGatewayId: event.PhysicalResourceId })), - ); - return { PhysicalResourceId: event.PhysicalResourceId, Status: 'SUCCESS' }; - } -} - -/** - * Set CGW options based on event - * @param resourceProperties { [key: string]: any } - * @returns CgwOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setCgwOptions(resourceProperties: { [key: string]: any }, serviceToken: string): CgwOptions { - return { - ipAddress: resourceProperties['ipAddress'] as string, - bgpAsn: resourceProperties['bgpAsn'] as number, - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - partition: serviceToken.split(':')[1], - owningAccountId: (resourceProperties['owningAccountId'] as string) ?? undefined, - owningRegion: (resourceProperties['owningRegion'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - tags: (resourceProperties['tags'] as Tag[]) ?? undefined, - }; -} - -/** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param cgwOptions CgwOptions - * @param solutionId string | undefined - * @returns Promise - */ -async function setEc2Client(cgwOptions: CgwOptions, solutionId?: string): Promise { - const roleArn = `arn:${cgwOptions.partition}:iam::${cgwOptions.owningAccountId}:role/${cgwOptions.roleName}`; - const stsClient = new STSClient({ region: cgwOptions.invokingRegion, customUserAgent: solutionId }); - - if (cgwOptions.owningAccountId && cgwOptions.owningRegion) { - if (!cgwOptions.roleName) { - throw new Error(`Cross-account CGW required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: cgwOptions.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (cgwOptions.owningAccountId && !cgwOptions.owningRegion) { - if (!cgwOptions.roleName) { - throw new Error(`Cross-account CGW required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: cgwOptions.invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: cgwOptions.owningRegion ?? cgwOptions.invokingRegion, - customUserAgent: solutionId, - }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Create a customer gateway - * @param ec2Client EC2Client - * @param cgwOptions CgwOptions - * @returns Promise - */ -async function createCustomerGateway(ec2Client: EC2Client, cgwOptions: CgwOptions): Promise { - console.log(`Creating customer gateway...`); - try { - const response = await throttlingBackOff(() => - ec2Client.send( - new CreateCustomerGatewayCommand({ - BgpAsn: cgwOptions.bgpAsn, - IpAddress: cgwOptions.ipAddress, - Type: 'ipsec.1', - TagSpecifications: [ - { - ResourceType: 'customer-gateway', - Tags: cgwOptions.tags, - }, - ], - }), - ), - ); - // - // Validate response - if (!response.CustomerGateway?.CustomerGatewayId) { - throw new Error(`Customer gateway ID not returned from CreateCustomerGateway command`); - } - return response.CustomerGateway.CustomerGatewayId; - } catch (e) { - throw new Error(`Could not create customer gateway: ${e}`); - } -} - -/** - * Determines if a new CGW must be created - * @param oldCgwOptions CgwOptions - * @param newCgwOptions CgwOptions - * @returns boolean - */ -function createNewCgw(oldCgwOptions: CgwOptions, newCgwOptions: CgwOptions): boolean { - return oldCgwOptions.ipAddress !== newCgwOptions.ipAddress || oldCgwOptions.bgpAsn !== newCgwOptions.bgpAsn; -} - -/** - * Update tags for the customer gateway - * @param ec2Client EC2Client - * @param cgwId string - * @param oldTags Tag[] - * @param newTags Tag[] - */ -async function updateTags(ec2Client: EC2Client, cgwId: string, oldTags: Tag[], newTags: Tag[]): Promise { - const newTagKeys = newTags.map(newTag => newTag.Key); - const removeTags = oldTags.filter(oldTag => !newTagKeys.includes(oldTag.Key)); - - try { - if (removeTags.length > 0) { - console.log(`Removing tag keys [${removeTags.map(tag => tag.Key)}] from CGW ${cgwId}...`); - await throttlingBackOff(() => ec2Client.send(new DeleteTagsCommand({ Resources: [cgwId], Tags: removeTags }))); - } - if (newTags.length > 0) { - console.log(`Creating/updating tag keys [${newTags.map(tag => tag.Key)}] on CGW ${cgwId}...`); - await throttlingBackOff(() => ec2Client.send(new CreateTagsCommand({ Resources: [cgwId], Tags: newTags }))); - } - } catch (e) { - throw new Error(`Error while updating tags: ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/package.json deleted file mode 100644 index fed0adc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-cross-account-customer-gateway", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/tsconfig.json deleted file mode 100644 index 2d6aa8d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-customer-gateway/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} - \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route.ts deleted file mode 100644 index f232f0b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -interface CrossAccountRouteFrameworkProps { - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly logGroupKmsKey?: cdk.aws_kms.IKey; - - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class CrossAccountRouteFramework extends cdk.Resource { - public readonly provider: cdk.custom_resources.Provider; - - constructor(scope: Construct, id: string, props: CrossAccountRouteFrameworkProps) { - super(scope, id); - - const onEventStsPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'StsAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}*`], - }); - - const onEventRoutePolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'EC2RouteCreateDelete', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:CreateRoute', 'ec2:DeleteRoute'], - resources: ['*'], - }); - - const onEvent = new cdk.aws_lambda.Function(this, 'CrossAccountRouteFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'cross-account-route/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.seconds(15), - description: 'Cross account EC2 route OnEvent handler', - initialPolicy: [onEventStsPolicy, onEventRoutePolicy], - }); - new cdk.aws_logs.LogGroup(this, `${onEvent.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${onEvent.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.logGroupKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - this.provider = new cdk.custom_resources.Provider(this, 'CrossAccountRouteProvider', { - onEventHandler: onEvent, - }); - } -} - -interface CrossAccountRouteProps { - /** - * The owner account of the route table - */ - readonly ownerAccount: string; - - /** - * The region of the owner account - */ - readonly ownerRegion: string; - - /** - * The partition of the owner account - */ - readonly partition: string; - - /** - * The custom resource provider - */ - readonly provider: cdk.custom_resources.Provider; - - /** - * The role name to assume - */ - readonly roleName: string; - - /** - * The ID of the route table for the route - */ - readonly routeTableId: string; - - /** - * The ID of a carrier gateway - */ - readonly carrierGatewayId?: string; - - /** - * The destination CIDR - */ - readonly destination?: string; - - /** - * The destination prefix list ID - */ - readonly destinationPrefixListId?: string; - - /** - * The ID of an egress-only Internet gateway - */ - readonly egressOnlyInternetGatewayId?: string; - - /** - * The ID of an Internet Gateway or Virtual Private Gateway - */ - readonly gatewayId?: string; - - /** - * The ID of an instance - */ - readonly instanceId?: string; - - /** - * The destination IPv6 CIDR - */ - readonly ipv6Destination?: string; - - /** - * The ID of a local gateway - */ - readonly localGatewayId?: string; - - /** - * The ID of a NAT gateway - */ - readonly natGatewayId?: string; - - /** - * The ID of a network interface - */ - readonly networkInterfaceId?: string; - - /** - * The ID of a transit gateway - */ - readonly transitGatewayId?: string; - - /** - * The ID of a VPC endpoint - */ - readonly vpcEndpointId?: string; - - /** - * The ID of a VPC peering connection - */ - readonly vpcPeeringConnectionId?: string; -} - -export class CrossAccountRoute extends cdk.Resource { - private roleArn?: string; - - constructor(scope: Construct, id: string, props: CrossAccountRouteProps) { - super(scope, id); - - const CROSS_ACCOUNT_ROUTE_TYPE = 'Custom::CrossAccountRoute'; - this.roleArn = - cdk.Stack.of(this).account !== props.ownerAccount - ? `arn:${props.partition}:iam::${props.ownerAccount}:role/${props.roleName}` - : undefined; - - new cdk.CustomResource(this, 'Resource', { - resourceType: CROSS_ACCOUNT_ROUTE_TYPE, - serviceToken: props.provider.serviceToken, - properties: { - region: props.ownerRegion, - roleArn: this.roleArn, - routeDefinition: { - DestinationCidrBlock: props.destination, - DestinationPrefixListId: props.destinationPrefixListId, - DestinationIpv6CidrBlock: props.ipv6Destination, - RouteTableId: props.routeTableId, - CarrierGatewayId: props.carrierGatewayId, - EgressOnlyInternetGatewayId: props.egressOnlyInternetGatewayId, - GatewayId: props.gatewayId, - InstanceId: props.instanceId, - LocalGatewayId: props.localGatewayId, - NatGatewayId: props.natGatewayId, - NetworkInterfaceId: props.networkInterfaceId, - TransitGatewayId: props.transitGatewayId, - VpcEndpointId: props.vpcEndpointId, - VpcPeeringConnectionId: props.vpcPeeringConnectionId, - }, - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/index.ts deleted file mode 100644 index f19b042..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/index.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * aws-ec2-cross-account-route - lambda handler - * - * @param event - * @returns - */ - -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import { CreateRouteCommand, DeleteRouteCommand, EC2Client } from '@aws-sdk/client-ec2'; -import { STSClient } from '@aws-sdk/client-sts'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy, getStsCredentials } from '@aws-accelerator/utils/lib/common-functions'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string | undefined; - } - | undefined -> { - interface RouteProps { - readonly RouteTableId: string; - readonly CarrierGatewayId?: string; - readonly DestinationCidrBlock?: string; - readonly DestinationPrefixListId?: string; - readonly DestinationIpv6CidrBlock?: string; - readonly EgressOnlyInternetGatewayId?: string; - readonly GatewayId?: string; - readonly InstanceId?: string; - readonly LocalGatewayId?: string; - readonly NatGatewayId?: string; - readonly NetworkInterfaceId?: string; - readonly TransitGatewayId?: string; - readonly VpcEndpointId?: string; - readonly VpcPeeringConnectionId?: string; - } - - const props: RouteProps = event.ResourceProperties['routeDefinition']; - const region: string = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - const roleArn: string | undefined = event.ResourceProperties['roleArn']; - const resourceId = setResourceId(props); - - const ec2Client = await setClient(solutionId, region, roleArn); - - switch (event.RequestType) { - case 'Create': - case 'Update': - await throttlingBackOff(() => ec2Client.send(new CreateRouteCommand(props))); - - return { - PhysicalResourceId: resourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - ec2Client.send( - new DeleteRouteCommand({ - DestinationCidrBlock: props.DestinationCidrBlock, - DestinationPrefixListId: props.DestinationPrefixListId, - DestinationIpv6CidrBlock: props.DestinationIpv6CidrBlock, - RouteTableId: props.RouteTableId, - }), - ), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } - - /** - * Set physical resource ID based on event input - * @param props - * @returns string - */ - function setResourceId(props: RouteProps): string { - if (props.DestinationCidrBlock) { - return `${props.DestinationCidrBlock}${props.RouteTableId}`; - } else if (props.DestinationIpv6CidrBlock) { - return `${props.DestinationIpv6CidrBlock}${props.RouteTableId}`; - } else { - return `${props.DestinationPrefixListId}${props.RouteTableId}`; - } - } - - /** - * Set EC2 client - * @param solutionId string | undefined - * @param region string | undefined - * @param roleArn string | undefined - * @returns Promise - */ - async function setClient(solutionId?: string, region?: string, roleArn?: string): Promise { - if (roleArn) { - const stsClient = new STSClient({ customUserAgent: solutionId, region, retryStrategy: setRetryStrategy() }); - - return new EC2Client({ - customUserAgent: solutionId, - region, - retryStrategy: setRetryStrategy(), - credentials: await getStsCredentials(stsClient, roleArn), - }); - } else { - return new EC2Client({ customUserAgent: solutionId, region, retryStrategy: setRetryStrategy() }); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/package.json deleted file mode 100644 index d0b2d40..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-cross-account-route", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-route/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/index.ts deleted file mode 100644 index 2794c7e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/index.ts +++ /dev/null @@ -1,251 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CreateTransitGatewayRouteCommand, DeleteTransitGatewayRouteCommand, EC2Client } from '@aws-sdk/client-ec2'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -interface TgwStaticRouteOptions { - /** - * API props - */ - readonly props: { - /** - * The CIDR block for the route. - */ - readonly DestinationCidrBlock: string; - /** - * The ID of the transit gateway route table. - */ - readonly TransitGatewayRouteTableId: string; - /** - * Determines if route is blackholed. - */ - readonly Blackhole?: boolean; - /** - * The identifier of the Transit Gateway Attachment - */ - readonly TransitGatewayAttachmentId?: string; - }; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; -} - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - // - // Set TGW route options and EC2 client - const options = setOptions(event.ResourceProperties, event.ServiceToken); - const ec2Client = await setEc2Client(options, process.env['SOLUTION_ID']); - // - // Begin custom resource handler logic - switch (event.RequestType) { - case 'Create': - await createRoute(ec2Client, options); - - return { - Status: 'SUCCESS', - }; - case 'Update': - const oldOptions = setOptions(event.OldResourceProperties, event.ServiceToken); - await deleteRoute(ec2Client, oldOptions); - await createRoute(ec2Client, options); - - return { - Status: 'SUCCESS', - }; - case 'Delete': - await deleteRoute(ec2Client, options); - - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Set TGW static route options based on event - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns TgwStaticRouteOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }, serviceToken: string): TgwStaticRouteOptions { - return { - props: { - TransitGatewayRouteTableId: resourceProperties['transitGatewayRouteTableId'], - Blackhole: resourceProperties['blackhole'] === 'true' ?? undefined, - DestinationCidrBlock: resourceProperties['destinationCidrBlock'], - TransitGatewayAttachmentId: resourceProperties['transitGatewayAttachmentId'], - }, - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - partition: serviceToken.split(':')[1], - owningAccountId: (resourceProperties['owningAccountId'] as string) ?? undefined, - owningRegion: (resourceProperties['owningRegion'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - }; -} - -/** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param options TgwStaticRouteOptions - * @param solutionId string | undefined - * @returns Promise - */ -async function setEc2Client(options: TgwStaticRouteOptions, solutionId?: string): Promise { - const roleArn = `arn:${options.partition}:iam::${options.owningAccountId}:role/${options.roleName}`; - const stsClient = new STSClient({ region: options.invokingRegion, customUserAgent: solutionId }); - - if (options.owningAccountId && options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW static route required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (options.owningAccountId && !options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW static route required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: options.owningRegion ?? options.invokingRegion, - customUserAgent: solutionId, - }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Create TGW static route - * @param ec2Client EC2Client - * @param options TgwStaticRouteOptions - */ -async function createRoute(ec2Client: EC2Client, options: TgwStaticRouteOptions) { - console.log( - `Creating TGW static route for TGW route table ${options.props.TransitGatewayRouteTableId} with destination ${ - options.props.DestinationCidrBlock - } to target ${options.props.TransitGatewayAttachmentId ?? 'blackhole'}...`, - ); - try { - await throttlingBackOff(() => ec2Client.send(new CreateTransitGatewayRouteCommand(options.props))); - } catch (e) { - throw new Error(`Error calling CreateTransitGatewayRoute command: ${e}`); - } -} - -/** - * Delete TGW static route - * @param ec2Client EC2Client - * @param options TgwStaticRouteOptions - */ -async function deleteRoute(ec2Client: EC2Client, options: TgwStaticRouteOptions) { - console.log( - `Removing TGW static route for TGW route table ${options.props.TransitGatewayRouteTableId} with destination ${ - options.props.DestinationCidrBlock - } to target ${options.props.TransitGatewayAttachmentId ?? 'blackhole'}...`, - ); - try { - await throttlingBackOff(() => - ec2Client.send( - new DeleteTransitGatewayRouteCommand({ - DestinationCidrBlock: options.props.DestinationCidrBlock, - TransitGatewayRouteTableId: options.props.TransitGatewayRouteTableId, - }), - ), - ); - } catch (e) { - throw new Error(`Error calling DeleteTransitGatewayRoute command: ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/package.json deleted file mode 100644 index d51be9e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-cross-account-transit-gateway-route", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/tsconfig.json deleted file mode 100644 index 2d6aa8d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/cross-account-transit-gateway-route/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} - \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/index.ts deleted file mode 100644 index 964ab54..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/index.ts +++ /dev/null @@ -1,575 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - CreateTagsCommand, - CreateVpnConnectionCommand, - CreateVpnConnectionCommandInput, - DeleteTagsCommand, - DeleteVpnConnectionCommand, - DescribeVpnConnectionsCommand, - EC2Client, - ModifyVpnConnectionOptionsCommand, - ModifyVpnTunnelOptionsCommand, - Tag, - VpnConnection, - VpnTunnelOptionsSpecification, -} from '@aws-sdk/client-ec2'; -import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { VpnConnectionDiff, VpnOptions, VpnTunnelOptions } from './vpn-types'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string | undefined; - } - | undefined -> { - // - // Set VPN options - const newVpnOptions = setVpnOptions(event.ResourceProperties, event.ServiceToken); - // - // Set up clients - const [ec2Client, secretsClient] = await setAwsClients(newVpnOptions, process.env['SOLUTION_ID']); - // - // Begin custom resource logic - switch (event.RequestType) { - case 'Create': - // - // Create VPN - const vpnConnectionId = await createVpnConnection(ec2Client, await setVpnProps(secretsClient, newVpnOptions)); - // - // Wait for VPN to be in a stable state - await vpnConnectionStatus(ec2Client, vpnConnectionId, 'available'); - - return { - PhysicalResourceId: vpnConnectionId, - Status: 'SUCCESS', - }; - case 'Update': - // - // Set VPN props - const oldVpnOptions = setVpnOptions(event.OldResourceProperties, event.ServiceToken); - // - // Update VPN connection - const updateVpnConnectionId = await updateVpnConnection( - ec2Client, - secretsClient, - event.PhysicalResourceId, - oldVpnOptions, - newVpnOptions, - ); - - return { - PhysicalResourceId: updateVpnConnectionId, - Status: 'SUCCESS', - }; - - case 'Delete': - // - // Delete VPN - await throttlingBackOff(() => - ec2Client.send(new DeleteVpnConnectionCommand({ VpnConnectionId: event.PhysicalResourceId })), - ); - // - // Wait for VPN to be deleted - await vpnConnectionStatus(ec2Client, event.PhysicalResourceId, 'deleted'); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Deserialize the VPN options from the CloudFormation Custom Resource event - * @param resourceProperties { [key: string]: any } - * @returns VpnOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setVpnOptions(resourceProperties: { [key: string]: any }, serviceToken: string): VpnOptions { - return { - customerGatewayId: resourceProperties['customerGatewayId'] as string, - amazonIpv4NetworkCidr: (resourceProperties['amazonIpv4NetworkCidr'] as string) ?? undefined, - customerIpv4NetworkCidr: (resourceProperties['customerIpv4NetworkCidr'] as string) ?? undefined, - enableVpnAcceleration: resourceProperties['enableVpnAcceleration'] === 'true', - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - partition: serviceToken.split(':')[1], - owningAccountId: (resourceProperties['owningAccountId'] as string) ?? undefined, - owningRegion: (resourceProperties['owningRegion'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - staticRoutesOnly: resourceProperties['staticRoutesOnly'] === 'true', - tags: (resourceProperties['tags'] as Tag[]) ?? undefined, - transitGatewayId: (resourceProperties['transitGatewayId'] as string) ?? undefined, - vpnGatewayId: (resourceProperties['vpnGatewayId'] as string) ?? undefined, - vpnTunnelOptions: (resourceProperties['vpnTunnelOptions'] as VpnTunnelOptions[]) ?? undefined, - }; -} - -/** - * Returns local or cross-account/cross-region AWS API clients based on input parameters - * @param vpnOptions VpnOptions - * @param solutionId string | undefined - * @returns Promise<[EC2Client, SecretsManagerClient]> - */ -async function setAwsClients(vpnOptions: VpnOptions, solutionId?: string): Promise<[EC2Client, SecretsManagerClient]> { - const roleArn = `arn:${vpnOptions.partition}:iam::${vpnOptions.owningAccountId}:role/${vpnOptions.roleName}`; - const stsClient = new STSClient({ region: vpnOptions.invokingRegion, customUserAgent: solutionId }); - - if (vpnOptions.owningAccountId && vpnOptions.owningRegion) { - if (!vpnOptions.roleName) { - throw new Error(`Cross-account VPN required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return clients - const clientSettings = { - region: vpnOptions.owningRegion, - customUserAgent: solutionId, - credentials, - }; - return [new EC2Client(clientSettings), new SecretsManagerClient(clientSettings)]; - } else if (vpnOptions.owningAccountId && !vpnOptions.owningRegion) { - if (!vpnOptions.roleName) { - throw new Error(`Cross-account VPN required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return clients - const clientSettings = { - region: vpnOptions.invokingRegion, - customUserAgent: solutionId, - credentials, - }; - return [new EC2Client(clientSettings), new SecretsManagerClient(clientSettings)]; - } else { - const clientSettings = { - region: vpnOptions.owningRegion ?? vpnOptions.invokingRegion, - customUserAgent: solutionId, - }; - return [new EC2Client(clientSettings), new SecretsManagerClient(clientSettings)]; - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Convert the configured VPN options to the CreateVpnConnectionCommandInput format - * @param vpnOptions - * @returns - */ -async function setVpnProps( - secretsClient: SecretsManagerClient, - vpnOptions: VpnOptions, -): Promise { - return { - CustomerGatewayId: vpnOptions.customerGatewayId, - Type: 'ipsec.1', - Options: { - EnableAcceleration: vpnOptions.enableVpnAcceleration, - LocalIpv4NetworkCidr: vpnOptions.customerIpv4NetworkCidr, - RemoteIpv4NetworkCidr: vpnOptions.amazonIpv4NetworkCidr, - StaticRoutesOnly: vpnOptions.staticRoutesOnly, - TunnelOptions: await setVpnTunnelProps(secretsClient, vpnOptions.vpnTunnelOptions), - }, - TransitGatewayId: vpnOptions.transitGatewayId, - VpnGatewayId: vpnOptions.vpnGatewayId, - TagSpecifications: [ - { - ResourceType: 'vpn-connection', - Tags: vpnOptions.tags, - }, - ], - }; -} - -/** - * Convert the configured VPN tunnel options to the VpnTunnelOptionsSpecification format - * @param tunnelOptions VpnTunnelOptions[] - * @returns VpnTunnelOptionsSpecification[] | undefined - */ -async function setVpnTunnelProps( - secretsClient: SecretsManagerClient, - tunnelOptions?: VpnTunnelOptions[], -): Promise { - if (!tunnelOptions) { - return; - } - - const vpnTunnelOptions: VpnTunnelOptionsSpecification[] = []; - - for (const tunnel of tunnelOptions) { - vpnTunnelOptions.push({ - DPDTimeoutAction: tunnel.dpdTimeoutAction, - DPDTimeoutSeconds: tunnel.dpdTimeoutSeconds, - EnableTunnelLifecycleControl: tunnel.tunnelLifecycleControl, - IKEVersions: tunnel.ikeVersions?.map(version => { - return { Value: `ikev${version}` }; - }), - LogOptions: { - CloudWatchLogOptions: { - LogEnabled: tunnel.logging?.enable, - LogGroupArn: tunnel.logging?.logGroupArn, - LogOutputFormat: tunnel.logging?.outputFormat, - }, - }, - Phase1DHGroupNumbers: tunnel.phase1?.dhGroups?.map(p1DhGroup => { - return { Value: p1DhGroup }; - }), - Phase1EncryptionAlgorithms: tunnel.phase1?.encryptionAlgorithms?.map(p1Enc => { - return { Value: p1Enc }; - }), - Phase1IntegrityAlgorithms: tunnel.phase1?.integrityAlgorithms?.map(p1Int => { - return { Value: p1Int }; - }), - Phase1LifetimeSeconds: tunnel.phase1?.lifetimeSeconds, - Phase2DHGroupNumbers: tunnel.phase2?.dhGroups?.map(p2DhGroup => { - return { Value: p2DhGroup }; - }), - Phase2EncryptionAlgorithms: tunnel.phase2?.encryptionAlgorithms?.map(p2Enc => { - return { Value: p2Enc }; - }), - Phase2IntegrityAlgorithms: tunnel.phase2?.integrityAlgorithms?.map(p2Int => { - return { Value: p2Int }; - }), - Phase2LifetimeSeconds: tunnel.phase2?.lifetimeSeconds, - PreSharedKey: tunnel.preSharedKey ? await getSecretValue(secretsClient, tunnel.preSharedKey) : undefined, - RekeyFuzzPercentage: tunnel.rekeyFuzzPercentage, - RekeyMarginTimeSeconds: tunnel.rekeyMarginTimeSeconds, - ReplayWindowSize: tunnel.replayWindowSize, - StartupAction: tunnel.startupAction, - TunnelInsideCidr: tunnel.tunnelInsideCidr, - }); - } - return vpnTunnelOptions; -} - -/** - * Retrieves a pre-shared key value from Secrets Manager - * @param secretsClient SecretsManagerClient - * @param secretName string - * @returns string - */ -async function getSecretValue(secretsClient: SecretsManagerClient, secretName: string): Promise { - console.log(`Retrieving pre-shared key value ${secretName} from Secrets Manager...`); - try { - const response = await throttlingBackOff(() => - secretsClient.send(new GetSecretValueCommand({ SecretId: secretName })), - ); - - if (!response.SecretString) { - throw new Error(`GetSecretValue command did not return a value`); - } - return response.SecretString; - } catch (e) { - throw new Error(`Error while retrieving secret: ${e}`); - } -} - -/** - * Create VPN connection - * @param ec2Client EC2Client - * @param vpnProps CreateVpnConnectionCommandInput - * @returns Promise - */ -async function createVpnConnection(ec2Client: EC2Client, vpnProps: CreateVpnConnectionCommandInput): Promise { - let logMessage = `Creating VPN connection between customer gateway ${vpnProps.CustomerGatewayId}`; - logMessage += vpnProps.TransitGatewayId - ? ` and transit gateway ${vpnProps.TransitGatewayId}...` - : ` and virtual private gateway ${vpnProps.VpnGatewayId}...`; - console.info(logMessage); - - try { - const response = await throttlingBackOff(() => ec2Client.send(new CreateVpnConnectionCommand(vpnProps))); - - if (!response.VpnConnection?.VpnConnectionId) { - throw new Error('VPN connection ID was not returned from CreateVpnConnection command'); - } - return response.VpnConnection.VpnConnectionId; - } catch (e) { - throw new Error(`Error when creating VPN connection: ${e}`); - } -} - -/** - * Checks VPN connection status against a desired state - * @param ec2Client EC2Client - * @param vpnConnectionId string - * @param desiredState string - */ -async function vpnConnectionStatus(ec2Client: EC2Client, vpnConnectionId: string, desiredState: string): Promise { - let currentState: string | undefined = undefined; - let retries = 0; - - console.info(`Awaiting VPN connection ${vpnConnectionId} to be in ${desiredState} state...`); - try { - do { - const vpnDetails = await describeVpnConnection(ec2Client, vpnConnectionId); - // - // Get current VPN connection state - currentState = vpnDetails.State; - - if (!currentState) { - throw new Error('VPN connection state was not returned from DescribeVpnConnections command'); - } - // - // Wait and iterate retries if state is not desired - if (currentState !== desiredState) { - await sleep(30000); - retries += 1; - } - // - // Throw error if state is not desired after maximum retries - if (retries === 28) { - throw new Error('VPN connection state did not stabilize after maximum number of retries'); - } - } while (currentState !== desiredState); - } catch (e) { - throw new Error(`Error while checking VPN connection status: ${e}`); - } -} - -/** - * Returns a VpnConnection object from the EC2 API - * @param ec2Client EC2Client - * @param vpnConnectionId string - * @returns VpnConnection - */ -async function describeVpnConnection(ec2Client: EC2Client, vpnConnectionId: string): Promise { - const response = await throttlingBackOff(() => - ec2Client.send(new DescribeVpnConnectionsCommand({ VpnConnectionIds: [vpnConnectionId] })), - ); - - if (!response.VpnConnections) { - throw new Error('VPN connection details were not returned from DescribeVpnConnections command'); - } - return response.VpnConnections[0]; -} - -/** - * Update VPN connection based on changed values - * @param ec2Client EC2Client - * @param secretsClient SecretsManagerClient - * @param vpnConnectionId string - * @param oldVpnOptions VpnOptions - * @param newVpnOptions VpnOptions - * @returns Promise - */ -async function updateVpnConnection( - ec2Client: EC2Client, - secretsClient: SecretsManagerClient, - vpnConnectionId: string, - oldVpnOptions: VpnOptions, - newVpnOptions: VpnOptions, -): Promise { - const vpnDiff = new VpnConnectionDiff(oldVpnOptions, newVpnOptions); - // - // If CGW, TGW, or VGW changed, create a new connection - if (vpnDiff.createNewVpnConnection) { - const newVpnConnectionId = await createVpnConnection(ec2Client, await setVpnProps(secretsClient, newVpnOptions)); - await vpnConnectionStatus(ec2Client, newVpnConnectionId, 'available'); - return newVpnConnectionId; - } else { - // - // Modify VPN based on diff - if (vpnDiff.vpnConnectionOptionsModified) { - await modifyVpnOptions(ec2Client, newVpnOptions, vpnConnectionId); - } - if (vpnDiff.vpnTunnelOptionsModified.includes(true)) { - await modifyVpnTunnelOptions( - ec2Client, - secretsClient, - newVpnOptions, - vpnDiff.vpnTunnelOptionsModified.indexOf(true), - vpnConnectionId, - ); - } - // - // Update tags - await updateTags(ec2Client, vpnConnectionId, oldVpnOptions.tags ?? [], newVpnOptions.tags ?? []); - - return vpnConnectionId; - } -} - -/** - * Modify VPN connection options - * @param ec2Client EC2Client - * @param vpnOptions VpnOptions - * @param vpnConnectionId string - */ -async function modifyVpnOptions(ec2Client: EC2Client, vpnOptions: VpnOptions, vpnConnectionId: string): Promise { - console.log(`Modifying VPN connection options for ${vpnConnectionId}...`); - try { - const response = await throttlingBackOff(() => - ec2Client.send( - new ModifyVpnConnectionOptionsCommand({ - VpnConnectionId: vpnConnectionId, - LocalIpv4NetworkCidr: vpnOptions.customerIpv4NetworkCidr, - RemoteIpv4NetworkCidr: vpnOptions.amazonIpv4NetworkCidr, - }), - ), - ); - - if (!response.VpnConnection?.VpnConnectionId) { - throw new Error('VPN connection ID was not returned by ModifyVpnConnectionOptions command'); - } - // - // Wait for VPN connection to stabilize - await vpnConnectionStatus(ec2Client, response.VpnConnection.VpnConnectionId, 'available'); - } catch (e) { - throw new Error(`Error while modifying VPN connection options: ${e}`); - } -} - -/** - * Modify VPN tunnel options - * @param ec2Client EC2Client - * @param secretsClient SecretsManagerClient - * @param vpnOptions VpnOptions - * @param vpnTunnelIndex number - * @param vpnConnectionId string - */ -async function modifyVpnTunnelOptions( - ec2Client: EC2Client, - secretsClient: SecretsManagerClient, - vpnOptions: VpnOptions, - vpnTunnelIndex: number, - vpnConnectionId: string, -): Promise { - try { - // - // Retrieve VPN tunnel details - const vpnDetails = await describeVpnConnection(ec2Client, vpnConnectionId); - - if (!vpnDetails.Options?.TunnelOptions) { - throw new Error('VPN tunnel option details were not returned from DescribeVpnConnections command'); - } - // - // Modify VPN tunnel options - const tunnelOutsideIp = vpnDetails.Options.TunnelOptions[vpnTunnelIndex].OutsideIpAddress; - const tunnelProps = await setVpnTunnelProps(secretsClient, vpnOptions.vpnTunnelOptions); - - if (!tunnelOutsideIp) { - throw new Error('VPN tunnel outside IP was not returned from DescribeVpnConnections command'); - } - - const response = await throttlingBackOff(() => - ec2Client.send( - new ModifyVpnTunnelOptionsCommand({ - VpnConnectionId: vpnConnectionId, - VpnTunnelOutsideIpAddress: tunnelOutsideIp, - TunnelOptions: tunnelProps ? tunnelProps[vpnTunnelIndex] : undefined, - }), - ), - ); - - if (!response.VpnConnection?.VpnConnectionId) { - throw new Error('VPN connection ID was not returned by ModifyVpnTunnelOptions command'); - } - // - // Wait for VPN connection to stabilize - await vpnConnectionStatus(ec2Client, response.VpnConnection.VpnConnectionId, 'available'); - } catch (e) { - throw new Error(`Error while modifying VPN tunnel options: ${e}`); - } -} - -/** - * Update tags for the VPN connection - * @param ec2Client EC2Client - * @param vpnConnectionId string - * @param oldTags Tag[] - * @param newTags Tag[] - */ -async function updateTags( - ec2Client: EC2Client, - vpnConnectionId: string, - oldTags: Tag[], - newTags: Tag[], -): Promise { - const newTagKeys = newTags.map(newTag => newTag.Key); - const removeTags = oldTags.filter(oldTag => !newTagKeys.includes(oldTag.Key)); - - try { - if (removeTags.length > 0) { - console.log(`Removing tag keys [${removeTags.map(tag => tag.Key)}] from VPN connection ${vpnConnectionId}...`); - await throttlingBackOff(() => - ec2Client.send(new DeleteTagsCommand({ Resources: [vpnConnectionId], Tags: removeTags })), - ); - } - if (newTags.length > 0) { - console.log( - `Creating/updating tag keys [${newTags.map(tag => tag.Key)}] on VPN connection ${vpnConnectionId}...`, - ); - await throttlingBackOff(() => - ec2Client.send(new CreateTagsCommand({ Resources: [vpnConnectionId], Tags: newTags })), - ); - } - } catch (e) { - throw new Error(`Error while updating tags: ${e}`); - } -} - -/** - * Sleep function - * @param ms number - * @returns Promise - */ -async function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/package.json deleted file mode 100644 index e9a0c95..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-custom-vpn-connection", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-secrets-manager": "3.410.0", - "@aws-sdk/client-sts": "3.410.0", - "@types/diff": "^5.0.3", - "diff": "^5.1.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/tsconfig.json deleted file mode 100644 index 2d6aa8d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} - \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/vpn-types.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/vpn-types.ts deleted file mode 100644 index 2aa314c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/custom-vpn-connection/vpn-types.ts +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Tag } from '@aws-sdk/client-ec2'; -import * as diff from 'diff'; - -export interface VpnTunnelOptions { - /** - * DPD timeout action - */ - readonly dpdTimeoutAction?: string; - /** - * DPD timeout in seconds - */ - readonly dpdTimeoutSeconds?: number; - /** - * IKE versions - */ - readonly ikeVersions?: number[]; - /** - * VPN tunnel logging - */ - readonly logging?: { - readonly enable?: boolean; - readonly logGroupArn?: string; - readonly outputFormat?: string; - }; - /** - * Phase 1 config - */ - readonly phase1?: { - readonly dhGroups?: number[]; - readonly encryptionAlgorithms?: string[]; - readonly integrityAlgorithms?: string[]; - readonly lifetimeSeconds?: number; - }; - /** - * Phase 2 config - */ - readonly phase2?: { - readonly dhGroups?: number[]; - readonly encryptionAlgorithms?: string[]; - readonly integrityAlgorithms?: string[]; - readonly lifetimeSeconds?: number; - }; - /** - * A Secrets Manager secret name - */ - readonly preSharedKey?: string; - /** - * IKE rekey fuzz percentage - */ - readonly rekeyFuzzPercentage?: number; - /** - * IKE rekey margin time in seconds - */ - readonly rekeyMarginTimeSeconds?: number; - /** - * IKE replay window size - */ - readonly replayWindowSize?: number; - /** - * The startup action for the VPN connection - */ - readonly startupAction?: string; - /** - * An IP address that is a size /30 CIDR block from the 169.254.0.0/16. - */ - readonly tunnelInsideCidr?: string; - /** - * Enable tunnel lifecycle control - */ - readonly tunnelLifecycleControl?: boolean; -} - -/** - * Custom deserialization interface for VPN options - */ -export interface VpnOptions { - /** - * Customer Gateway ID - */ - readonly customerGatewayId: string; - /** - * Enable VPN acceleration - */ - readonly enableVpnAcceleration: boolean; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * Static routes only for the VPN connection - */ - readonly staticRoutesOnly: boolean; - /** - * Amazon-side IPv4 CIDR - */ - readonly amazonIpv4NetworkCidr?: string; - /** - * Customer-side IPv4 CIDR - */ - readonly customerIpv4NetworkCidr?: string; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; - /** - * Tags to apply to the VPN connection - */ - readonly tags?: Tag[]; - /** - * The ID of the Transit Gateway to terminate the VPN Connection. - */ - readonly transitGatewayId?: string; - /** - * The ID of the Virtual Private Gateway to terminate the VPN Connection. - */ - readonly vpnGatewayId?: string; - /** - * The advanced tunnel options for the VPN - */ - readonly vpnTunnelOptions?: VpnTunnelOptions[]; -} - -/** - * A helper class that determines the differences between two VPN option configurations - */ -export class VpnConnectionDiff { - /** - * Create a new VPN connection based on options diff - */ - public readonly createNewVpnConnection: boolean; - /** - * Modify VPN connection options based on diff - */ - public readonly vpnConnectionOptionsModified: boolean; - /** - * Modify VPN tunnel options based on diff - */ - public readonly vpnTunnelOptionsModified: boolean[]; - /** - * The previous VPN options configuration - */ - private oldVpnOptions: VpnOptions; - /** - * The new VPN options configuration - */ - private newVpnOptions: VpnOptions; - - constructor(oldVpnOptions: VpnOptions, newVpnOptions: VpnOptions) { - // - // Set initial props - this.oldVpnOptions = oldVpnOptions; - this.newVpnOptions = newVpnOptions; - // - // Determine VPN options diff - this.createNewVpnConnection = this.setCreateConnectionFlag(this.oldVpnOptions, this.newVpnOptions); - this.vpnConnectionOptionsModified = this.setVpnOptionsModifiedFlag(this.oldVpnOptions, this.newVpnOptions); - this.vpnTunnelOptionsModified = this.setTunnelOptionsModifiedFlags(this.oldVpnOptions, this.newVpnOptions); - // - // Validate options to update. Throw errors if more than one mutating option. - this.validateModifications(this.vpnConnectionOptionsModified, this.vpnTunnelOptionsModified); - } - - /** - * Determines if a new VPN connection should be created on update - * @param oldVpnOptions VpnOptions - * @param newVpnOptions VpnOptions - * @returns boolean - */ - private setCreateConnectionFlag(oldVpnOptions: VpnOptions, newVpnOptions: VpnOptions): boolean { - return ( - oldVpnOptions.customerGatewayId !== newVpnOptions.customerGatewayId || - oldVpnOptions.enableVpnAcceleration !== newVpnOptions.enableVpnAcceleration || - oldVpnOptions.staticRoutesOnly !== newVpnOptions.staticRoutesOnly || - oldVpnOptions.transitGatewayId !== newVpnOptions.transitGatewayId || - oldVpnOptions.vpnGatewayId !== newVpnOptions.vpnGatewayId - ); - } - - /** - * Determines if VPN options have been modified - * @param oldVpnOptions VpnOptions - * @param newVpnOptions VpnOptions - * @returns boolean - */ - private setVpnOptionsModifiedFlag(oldVpnOptions: VpnOptions, newVpnOptions: VpnOptions): boolean { - return ( - oldVpnOptions.amazonIpv4NetworkCidr !== newVpnOptions.amazonIpv4NetworkCidr || - oldVpnOptions.customerIpv4NetworkCidr !== newVpnOptions.customerIpv4NetworkCidr - ); - } - - /** - * Returns an array of booleans indicating if VPN tunnel options have been modified for either tunnel - * @param oldVpnOptions VpnOptions - * @param newVpnOptions VpnOptions - * @returns boolean[] - */ - private setTunnelOptionsModifiedFlags(oldVpnOptions: VpnOptions, newVpnOptions: VpnOptions): boolean[] { - const tunnelUpdates: boolean[] = []; - - for (const [index, tunnel] of oldVpnOptions.vpnTunnelOptions?.entries() ?? []) { - const oldJsonOptions = JSON.stringify(tunnel); - const newJsonOptions = JSON.stringify(newVpnOptions.vpnTunnelOptions?.[index] ?? {}); - const tunnelDiff = diff.diffJson(oldJsonOptions, newJsonOptions); - tunnelUpdates.push(this.tunnelOptionsModified(tunnelDiff)); - } - return tunnelUpdates; - } - - /** - * Searches the diff array for any changes to the tunnel options - * @param tunnelDiff diff.Change[] - * @returns boolean - */ - private tunnelOptionsModified(tunnelDiff: diff.Change[]): boolean { - for (const part of tunnelDiff) { - if (part.added || part.removed) { - return true; - } - } - return false; - } - - /** - * Validate there is only one mutating VPN option - * @param vpnOptionsUpdate boolean - * @param vpnTunnelOptionsUpdate boolean[] - */ - private validateModifications(connectionOptionsUpdate: boolean, tunnelOptionsUpdate: boolean[]): void { - if (connectionOptionsUpdate && tunnelOptionsUpdate.includes(true)) { - throw new Error( - ` - VPN connection options and tunnel options cannot both be modified in the same pipeline run. Please revert one change and run the pipeline again. Note: you may need to continue rolling back the CloudFormation stack manually. - See the following reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html`, - ); - } - if (!connectionOptionsUpdate && tunnelOptionsUpdate[0] && tunnelOptionsUpdate[1]) { - throw new Error( - ` - Only one VPN tunnel option can be modified per pipeline run. Please revert one change and run the pipeline again. Note: you may need to continue rolling back the CloudFormation stack manually. - See the following reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html`, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/customer-gateway.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/customer-gateway.ts deleted file mode 100644 index e6d8398..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/customer-gateway.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { LzaCustomResource } from '../lza-custom-resource'; - -export interface CustomerGatewayProps { - /** - * Name of the Customer Gateway - */ - readonly name: string; - /** - * Gateway IP address for customer gateway - */ - readonly ipAddress: string; - /** - * Gateway ASN for customer gateway - */ - readonly bgpAsn: number; - /** - * Custom resource handler for cross-account customer gateways - */ - readonly customResourceHandler?: cdk.aws_lambda.IFunction; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; - /** - * Tags for the customer gateway - */ - readonly tags?: cdk.CfnTag[]; -} - -interface ICustomerGateway extends cdk.IResource { - /** - * The identifier of the customer gateway - * - * @attribute - */ - readonly customerGatewayId: string; - /** - * The BGP ASN of the customer gateway - */ - readonly bgpAsn: number; - /** - * The IP address of the customer gateway - */ - readonly ipAddress: string; -} - -/** - * Class for Customer Gateway - */ -export class CustomerGateway extends cdk.Resource implements ICustomerGateway { - public readonly bgpAsn: number; - public readonly customerGatewayId: string; - public readonly ipAddress: string; - public readonly name: string; - - constructor(scope: Construct, id: string, props: CustomerGatewayProps) { - super(scope, id); - this.name = props.name; - this.bgpAsn = props.bgpAsn; - this.ipAddress = props.ipAddress; - - let resource: cdk.aws_ec2.CfnCustomerGateway | cdk.CustomResource; - - if (!props.customResourceHandler) { - resource = new cdk.aws_ec2.CfnCustomerGateway(this, 'CustomerGateway', { - bgpAsn: props.bgpAsn, - ipAddress: props.ipAddress, - type: 'ipsec.1', - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', this.name); - } else { - // Convert tags to EC2 API format - const tags = - props.tags?.map(tag => { - return { Key: tag.key, Value: tag.value }; - }) ?? []; - tags.push({ Key: 'Name', Value: props.name }); - - resource = new LzaCustomResource(this, 'CustomResource', { - resource: { - name: 'CustomResource', - parentId: id, - properties: [ - { - bgpAsn: props.bgpAsn, - ipAddress: props.ipAddress, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - tags, - }, - ], - onEventHandler: props.customResourceHandler, - }, - }).resource; - } - this.customerGatewayId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/index.ts deleted file mode 100644 index 7dff617..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/index.ts +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * delete-default-security-group-rules - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - // Retrieve operating region that stack is ran - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - const ec2 = new AWS.EC2({ region: region, customUserAgent: solutionId }); - const vpcId = event.ResourceProperties['vpcId']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log(`Starting - Deletion of default security group ingress and egress rules for ${vpcId}`); - const securityGroupParams = { - Filters: [ - { - Name: 'group-name', - Values: ['default'], - }, - { - Name: 'vpc-id', - Values: [vpcId], - }, - ], - }; - // Pull VPC ID from SSM return ID - const securityGroupId = await getDefaultSecurityGroupId(ec2!, securityGroupParams); - console.log(securityGroupId); - if (!securityGroupId) { - throw new Error('Security Group ID not found.'); - } - // Build traffic parameter to pass to ingress and egress removal functions. - const ingresstrafficParams = { - GroupId: securityGroupId, - IpPermissions: [ - { - IpProtocol: '-1', - UserIdGroupPairs: [ - { - GroupId: securityGroupId, - }, - ], - }, - ], - }; - const egresstrafficParams = { - GroupId: securityGroupId, - IpPermissions: [ - { - IpProtocol: '-1', - IpRanges: [ - { - CidrIp: '0.0.0.0/0', - }, - ], - }, - ], - }; - - console.log(`Removing egress rules for ${securityGroupId}`); - await deleteEgressRules(ec2!, egresstrafficParams); - - console.log(`Removing ingress rules for ${securityGroupId}`); - await deleteIngressRules(ec2!, ingresstrafficParams); - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - // Do Nothing - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function getDefaultSecurityGroupId( - ec2: AWS.EC2, - params: { - Filters: { - Name: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Values: any[]; - }[]; - }, -): Promise { - const response = await throttlingBackOff(() => ec2.describeSecurityGroups(params).promise()); - return response.SecurityGroups![0].GroupId; -} - -async function deleteEgressRules( - ec2: AWS.EC2, - params: { - GroupId: string; - IpPermissions: { - IpProtocol: string; - IpRanges: { - CidrIp: string; - }[]; - }[]; - }, -) { - try { - await throttlingBackOff(() => ec2.revokeSecurityGroupEgress(params).promise()); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'InvalidPermission.NotFound' || - // SDKv3 Error Structure - e.name === 'InvalidPermission.NotFound' - ) { - return false; - } - throw new Error(e); - } -} - -async function deleteIngressRules( - ec2: AWS.EC2, - params: { - GroupId: string; - IpPermissions: { - IpProtocol: string; - UserIdGroupPairs: { - GroupId: string; - }[]; - }[]; - }, -) { - try { - await throttlingBackOff(() => ec2.revokeSecurityGroupIngress(params).promise()); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'InvalidPermission.NotFound' || - // SDKv3 Error Structure - e.name === 'InvalidPermission.NotFound' - ) { - return false; - } - throw new Error(e); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/package.json deleted file mode 100644 index 33d852e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-security-group-rules/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-delete-default-security-group-rules", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc.ts deleted file mode 100644 index e5df6ca..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized DeleteDefaultVpcProps properties - */ -export interface DeleteDefaultVpcProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class Delete the Default VPC - */ -export class DeleteDefaultVpc extends Construct { - readonly id: string; - constructor(scope: Construct, id: string, props: DeleteDefaultVpcProps) { - super(scope, id); - - const DELETE_DEFAULT_VPC_TYPE = 'Custom::DeleteDefaultVpc'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, DELETE_DEFAULT_VPC_TYPE, { - codeDirectory: path.join(__dirname, 'delete-default-vpc/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'ec2:DeleteInternetGateway', - 'ec2:DetachInternetGateway', - 'ec2:DeleteNetworkAcl', - 'ec2:DeleteRoute', - 'ec2:DeleteSecurityGroup', - 'ec2:DeleteSubnet', - 'ec2:DeleteVpc', - 'ec2:DescribeInternetGateways', - 'ec2:DescribeNetworkAcls', - 'ec2:DescribeRouteTables', - 'ec2:DescribeSecurityGroups', - 'ec2:DescribeSubnets', - 'ec2:DescribeVpcs', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: DELETE_DEFAULT_VPC_TYPE, - serviceToken: provider.serviceToken, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/index.ts deleted file mode 100644 index 621ba0d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/index.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * delete-default-vpc - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - // Retrieve operating region that stack is ran - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - const ec2Client = new AWS.EC2({ region: region, customUserAgent: solutionId }); - const defaultVpcIds: string[] = []; - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log(`Starting - Deletion of default VPC and associated resources in ${region}`); - - // Retrieve default VPC(s) - let describeVpcsNextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client - .describeVpcs({ - Filters: [{ Name: 'is-default', Values: [`true`] }], - NextToken: describeVpcsNextToken, - }) - .promise(), - ); - - for (const vpc of page.Vpcs ?? []) { - if (vpc.VpcId) { - defaultVpcIds.push(vpc.VpcId); - } - } - describeVpcsNextToken = page.NextToken; - } while (describeVpcsNextToken); - - console.log(`List of VPCs: `, defaultVpcIds); - if (defaultVpcIds.length == 0) { - console.warn('No default VPCs detected'); - return { - PhysicalResourceId: `delete-default-vpc`, - Status: 'SUCCESS', - }; - } else { - console.warn('Default VPC Detected'); - } - - // Retrieve and detach, delete IGWs - for (const vpcId of defaultVpcIds) { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client - .describeInternetGateways({ - Filters: [{ Name: 'attachment.vpc-id', Values: [vpcId] }], - NextToken: nextToken, - }) - .promise(), - ); - - for (const igw of page.InternetGateways ?? []) { - for (const attachment of igw.Attachments ?? []) { - if (attachment.State == 'available') { - console.log(`Detaching ${igw.InternetGatewayId}`); - await throttlingBackOff(() => - ec2Client - .detachInternetGateway({ InternetGatewayId: igw.InternetGatewayId!, VpcId: vpcId }) - .promise(), - ); - } - console.warn(`${igw.InternetGatewayId} is not attached. Proceeding to delete.`); - await throttlingBackOff(() => - ec2Client - .deleteInternetGateway({ - InternetGatewayId: igw.InternetGatewayId!, - }) - .promise(), - ); - } - } - nextToken = page.NextToken; - } while (nextToken); - - // Retrieve Default VPC Subnets - console.log(`Gathering Subnets for VPC ${vpcId}`); - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client - .describeSubnets({ - Filters: [{ Name: 'vpc-id', Values: [vpcId] }], - NextToken: nextToken, - }) - .promise(), - ); - for (const subnet of page.Subnets ?? []) { - console.log(`Delete Subnet ${subnet.SubnetId}`); - await throttlingBackOff(() => - ec2Client - .deleteSubnet({ - SubnetId: subnet.SubnetId!, - }) - .promise(), - ); - } - nextToken = page.NextToken; - } while (nextToken); - - // Delete Routes - console.log(`Gathering list of Route Tables for VPC ${vpcId}`); - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client - .describeRouteTables({ - Filters: [{ Name: 'vpc-id', Values: [vpcId] }], - NextToken: nextToken, - }) - .promise(), - ); - for (const routeTableObject of page.RouteTables ?? []) { - for (const routes of routeTableObject.Routes ?? []) { - if (routes.GatewayId !== 'local') { - console.log(`Removing route ${routes.DestinationCidrBlock} from ${routeTableObject.RouteTableId}`); - await throttlingBackOff(() => - ec2Client - .deleteRoute({ - RouteTableId: routeTableObject.RouteTableId!, - DestinationCidrBlock: routes.DestinationCidrBlock, - }) - .promise(), - ); - } - } - } - nextToken = page.NextToken; - } while (nextToken); - - // List and Delete NACLs - console.log(`Gathering list of NACLs for VPC ${vpcId}`); - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client - .describeNetworkAcls({ - Filters: [{ Name: 'vpc-id', Values: [vpcId] }], - NextToken: nextToken, - }) - .promise(), - ); - for (const networkAclObject of page.NetworkAcls ?? []) { - if (networkAclObject.IsDefault !== true) { - console.log(`Deleting Network ACL ID ${networkAclObject.NetworkAclId}`); - await throttlingBackOff(() => - ec2Client - .deleteNetworkAcl({ - NetworkAclId: networkAclObject.NetworkAclId!, - }) - .promise(), - ); - } else { - console.warn(`${networkAclObject.NetworkAclId} is the default NACL. Ignoring`); - } - } - nextToken = page.NextToken; - } while (nextToken); - - // List and Delete Security Groups - console.log(`Gathering list of Security Groups for VPC ${vpcId}`); - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client - .describeSecurityGroups({ - Filters: [{ Name: 'vpc-id', Values: [vpcId] }], - NextToken: nextToken, - }) - .promise(), - ); - for (const securityGroupObject of page.SecurityGroups ?? []) { - if (securityGroupObject.GroupName == 'default') { - console.warn(`${securityGroupObject.GroupId} is the default SG. Ignoring`); - } else { - console.log(`Deleting Security Group Id ${securityGroupObject.GroupId}`); - await throttlingBackOff(() => - ec2Client - .deleteSecurityGroup({ - GroupId: securityGroupObject.GroupId, - }) - .promise(), - ); - } - } - nextToken = page.NextToken; - } while (nextToken); - - // Once all resources are deleted, delete the VPC. - console.log(`Deleting VPC ${vpcId}`); - await throttlingBackOff(() => ec2Client.deleteVpc({ VpcId: vpcId }).promise()); - } - - return { - PhysicalResourceId: `delete-default-vpc`, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/package.json deleted file mode 100644 index a2e75ee..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-delete-default-vpc", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/delete-default-vpc/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/dhcp-options.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/dhcp-options.ts deleted file mode 100644 index 557e230..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/dhcp-options.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IDhcpOptions extends cdk.IResource { - /** - * The name of the DHCP options set. - */ - readonly name: string; - - /** - * The ID of the DHCP options set. - */ - readonly dhcpOptionsId: string; -} - -export interface DhcpOptionsProps { - /** - * The name of the DHCP options set. - */ - readonly name: string; - - /** - * This value is used to complete unqualified DNS hostnames. - */ - readonly domainName?: string; - - /** - * The IPv4 addresses of up to four domain name servers. - * - * @default -- AmazonProvidedDNS - */ - readonly domainNameServers?: string[]; - - /** - * The IPv4 addresses of up to four NetBIOS name servers. - */ - readonly netbiosNameServers?: string[]; - - /** - * The NetBIOS node type (1, 2, 4, or 8). - */ - readonly netbiosNodeType?: number; - - /** - * The IPv4 addresses of up to four Network Time Protocol (NTP) servers. - */ - readonly ntpServers?: string[]; - - /** - * Any tags assigned to the DHCP options set. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class DhcpOptions extends cdk.Resource implements IDhcpOptions { - public readonly name: string; - public readonly dhcpOptionsId: string; - - constructor(scope: Construct, id: string, props: DhcpOptionsProps) { - super(scope, id); - - this.name = props.name; - - const resource = new cdk.aws_ec2.CfnDHCPOptions(this, 'Resource', { - domainName: props.domainName, - domainNameServers: props.domainNameServers ?? ['AmazonProvidedDNS'], - netbiosNameServers: props.netbiosNameServers, - netbiosNodeType: props.netbiosNodeType, - ntpServers: props.ntpServers, - tags: props.tags, - }); - // Add name tag to tags - cdk.Tags.of(this).add('Name', this.name); - - this.dhcpOptionsId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/index.ts deleted file mode 100644 index ffc5351..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * aws-ec2-enable-ebs-encryption - lambda handler - * - * @param event - * @returns - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { - DisableEbsEncryptionByDefaultCommand, - EC2Client, - EnableEbsEncryptionByDefaultCommand, - ModifyEbsDefaultKmsKeyIdCommand, -} from '@aws-sdk/client-ec2'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const kmsKeyId = event.ResourceProperties['kmsKeyId'] as string; - const solutionId = process.env['SOLUTION_ID']; - - const ec2Client = new EC2Client({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - switch (event.RequestType) { - case 'Create': - case 'Update': - const enableResponse = await throttlingBackOff(() => ec2Client.send(new EnableEbsEncryptionByDefaultCommand({}))); - console.log(`Enable encryption response ${JSON.stringify(enableResponse)}`); - - const response = await throttlingBackOff(() => - ec2Client.send(new ModifyEbsDefaultKmsKeyIdCommand({ KmsKeyId: kmsKeyId })), - ); - console.log(`Modify KMS Key response ${JSON.stringify(response)}`); - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const disableResponse = await throttlingBackOff(() => - ec2Client.send(new DisableEbsEncryptionByDefaultCommand({})), - ); - console.log(`Disable EBS encryption response ${JSON.stringify(disableResponse)}`); - return { Status: 'Success', StatusCode: 200 }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/package.json deleted file mode 100644 index 9b0ae91..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-enable-ebs-encryption", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-default-encryption/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-encryption.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-encryption.ts deleted file mode 100644 index e93dcd3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ebs-encryption.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized EbsVolumeEncryptionProps properties - */ -export interface EbsVolumeEncryptionProps { - /** - * Ebs encryption key - */ - readonly ebsEncryptionKmsKey: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly logGroupKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to Enable Default EBS Volume Encryption - */ -export class EbsDefaultEncryption extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: EbsVolumeEncryptionProps) { - super(scope, id); - - const EBS_ENCRYPTION_TYPE = 'Custom::EbsDefaultVolumeEncryption'; - - const iamPolicy = [ - { - Sid: 'EC2', - Effect: 'Allow', - Action: [ - 'ec2:DisableEbsEncryptionByDefault', - 'ec2:EnableEbsEncryptionByDefault', - 'ec2:ModifyEbsDefaultKmsKeyId', - 'ec2:ResetEbsDefaultKmsKeyId', - ], - Resource: '*', - }, - { - Sid: 'KMS', - Effect: 'Allow', - Action: ['kms:DescribeKey'], - Resource: props.ebsEncryptionKmsKey.keyArn, - }, - ]; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, EBS_ENCRYPTION_TYPE, { - codeDirectory: path.join(__dirname, 'ebs-default-encryption/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: iamPolicy, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: EBS_ENCRYPTION_TYPE, - serviceToken: provider.serviceToken, - properties: { - kmsKeyId: props.ebsEncryptionKmsKey.keyId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.logGroupKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/index.ts deleted file mode 100644 index 0ab9aec..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * enable-ipam-organization-admin-account - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const accountId = event.ResourceProperties['accountId']; - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - const ec2Client = new AWS.EC2({ region: region, customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log(`Enabling IPAM delegated administration for account ${accountId}`); - await throttlingBackOff(() => - ec2Client - .enableIpamOrganizationAdminAccount({ - DelegatedAdminAccountId: accountId, - }) - .promise(), - ); - - return { - PhysicalResourceId: accountId, - Status: 'SUCCESS', - }; - - case 'Delete': - console.log(`Removing IPAM delegated administration from account ${event.PhysicalResourceId}`); - await throttlingBackOff(() => - ec2Client - .disableIpamOrganizationAdminAccount({ - DelegatedAdminAccountId: event.PhysicalResourceId, - }) - .promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/package.json deleted file mode 100644 index 51d2646..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-enable-ipam-organization-admin", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.js", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/enable-ipam-organization-admin/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-asg.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-asg.ts deleted file mode 100644 index 7635c89..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-asg.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AutoScalingConfig } from '@aws-accelerator/config'; -import { Construct } from 'constructs'; -import { AutoscalingGroup } from '../aws-autoscaling/create-autoscaling-group'; -import { Firewall, FirewallProps, IFirewall } from './firewall'; - -export interface IFirewallAutoScalingGroup extends IFirewall { - /** - * The Autoscaling Group ID - */ - readonly groupName: string; -} - -interface FirewallAutoScalingGroupProps extends FirewallProps { - /** - * The Autoscaling Group configuration - */ - readonly autoscaling: AutoScalingConfig; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly lambdaKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly cloudWatchLogRetentionInDays: number; -} - -export class FirewallAutoScalingGroup extends Firewall implements IFirewallAutoScalingGroup { - public readonly groupName: string; - - constructor(scope: Construct, id: string, props: FirewallAutoScalingGroupProps) { - super(scope, id, props); - - const tags = props.tags ? [{ key: 'Name', value: this.name }, ...props.tags] : [{ key: 'Name', value: this.name }]; - - const asg = new AutoscalingGroup(this, 'Resource', { - name: props.autoscaling.name, - minSize: props.autoscaling.minSize, - maxSize: props.autoscaling.maxSize, - desiredSize: props.autoscaling.desiredSize, - launchTemplateId: this.launchTemplate.launchTemplateId, - launchTemplateVersion: this.launchTemplate.version, - healthCheckType: props.autoscaling.healthCheckType, - healthCheckGracePeriod: props.autoscaling.healthCheckGracePeriod, - targetGroups: props.autoscaling.targetGroups, - subnets: props.autoscaling.subnets, - tags, - lambdaKey: props.lambdaKey, - cloudWatchLogKmsKey: props.cloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.cloudWatchLogRetentionInDays, - maxInstanceLifetime: props.autoscaling.maxInstanceLifetime, - nagSuppressionPrefix: `${id}/Resource`, - }); - - this.groupName = asg.autoscalingGroupName; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements.ts deleted file mode 100644 index 601ef8c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import path from 'path'; -import { LzaCustomResource } from '../lza-custom-resource'; - -interface FirewallConfigReplacementProps { - /** - * Custom resource CloudWatch Log encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch Log retention - */ - readonly cloudWatchLogRetentionInDays: number; - /** - * Custom resource environment encryption key, when undefined default AWS managed key will be used - */ - readonly environmentEncryptionKey?: cdk.aws_kms.IKey; - /** - * Custom resource properties - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly properties: { [key: string]: any }[]; - /** - * Custom resource role - */ - readonly role: cdk.aws_iam.IRole; -} - -export class FirewallConfigReplacements extends cdk.Resource { - constructor(scope: Construct, id: string, props: FirewallConfigReplacementProps) { - super(scope, id); - - new LzaCustomResource(this, 'Resource', { - resource: { - name: 'Resource', - parentId: id, - properties: props.properties, - }, - lambda: { - assetPath: path.join(__dirname, 'firewall-config-replacements/dist'), - description: 'Firewall configuration replacement custom resource', - environmentEncryptionKmsKey: props.environmentEncryptionKey, - cloudWatchLogKmsKey: props.cloudWatchLogKey, - cloudWatchLogRetentionInDays: props.cloudWatchLogRetentionInDays, - role: props.role, - timeOut: cdk.Duration.seconds(120), - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/index.ts deleted file mode 100644 index 5c88373..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/index.ts +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { EC2Client } from '@aws-sdk/client-ec2'; -import { - CopyObjectCommand, - CopyObjectCommandOutput, - GetObjectCommand, - ListObjectsV2Command, - PutObjectCommand, - PutObjectCommandOutput, - S3Client, -} from '@aws-sdk/client-s3'; -import { FirewallReplacements, VpnConnectionProps, initReplacements } from './replacements'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export interface IStaticReplacements { - /** - * The key name for the replacement - */ - readonly key: string; - /** - * The value for the replacement - */ - readonly value: string; -} - -export interface FirewallReplacementOptions { - /** - * The AWS Partition - */ - readonly partition: string; - /** - * The name of the S3 asset bucket - */ - readonly assetBucketName: string; - /** - * The name of the S3 config bucket - */ - readonly configBucketName: string; - /** - * The VPC ID where the firewall resides - */ - readonly vpcId: string; - /** - * The key name of the configuration file - */ - readonly configFileKey?: string; - /** - * The directory of the configuration files - */ - readonly configDir?: string; - /** - * The hostname of the firewall instance - */ - readonly firewallName?: string; - /** - * The instance ID of the firewall instance - */ - readonly instanceId?: string; - /** - * The key name of the license file - */ - readonly licenseFileKey?: string; - /** - * The name of the role to assume to retrieve cross-account values - */ - readonly roleName?: string; - /** - * Static replacements - */ - readonly staticReplacements?: IStaticReplacements[]; - /** - * VPN connection details to look up - */ - readonly vpnConnectionProps?: VpnConnectionProps[]; - /** - * Management Account ID used to read Secrets Manager secrets for replacements - */ - readonly managementAccountId?: string; -} - -export async function handler(event: CloudFormationCustomResourceEvent): Promise<{ Status: string } | undefined> { - // - // Set custom resource options - const options = setOptions(event.ResourceProperties); - // - // Set up clients - const ec2Client = new EC2Client({ customUserAgent: process.env['SOLUTION_ID'] }); - const s3Client = new S3Client({ customUserAgent: process.env['SOLUTION_ID'] }); - // - // Begin handler logic - switch (event.RequestType) { - case 'Create': - case 'Update': - // - // Copy license file - await copyLicenseFile(s3Client, options); - // - // Process config file replacements - const replacements = - options.configFileKey || options.configDir - ? await initReplacements(ec2Client, event.ServiceToken, options) - : undefined; - await processConfigFileReplacements(s3Client, options, replacements); - - return { - Status: 'SUCCESS', - }; - - case 'Delete': - // - // Do nothing - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Set firewall replacement options based on event - * @param resourceProperties { [key: string]: any } - * @returns FirewallReplacementOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }): FirewallReplacementOptions { - return { - partition: (resourceProperties['ServiceToken'] as string).split(':')[1], - assetBucketName: resourceProperties['assetBucketName'] as string, - configBucketName: resourceProperties['configBucketName'] as string, - vpcId: resourceProperties['vpcId'] as string, - configFileKey: (resourceProperties['configFile'] as string) ?? undefined, - configDir: (resourceProperties['configDir'] as string) ?? undefined, - firewallName: (resourceProperties['firewallName'] as string) ?? undefined, - instanceId: (resourceProperties['instanceId'] as string) ?? undefined, - licenseFileKey: (resourceProperties['licenseFile'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - staticReplacements: (resourceProperties['staticReplacements'] as IStaticReplacements[]) ?? undefined, - vpnConnectionProps: (resourceProperties['vpnConnections'] as VpnConnectionProps[]) ?? undefined, - managementAccountId: (resourceProperties['managementAccountId'] as string) ?? undefined, - }; -} - -/** - * Copy license file from asset bucket to config bucket - * @param s3Client S3Client - * @param options FirewallReplacementOptions - * @returns Promise - */ -async function copyLicenseFile( - s3Client: S3Client, - options: FirewallReplacementOptions, -): Promise { - if (!options.licenseFileKey) { - return; - } - - console.log( - `Copying license file ${options.licenseFileKey} from bucket s3://${options.assetBucketName} to s3://${options.configBucketName}...`, - ); - try { - const response = await throttlingBackOff(() => - s3Client.send( - new CopyObjectCommand({ - CopySource: `${options.assetBucketName}/${options.licenseFileKey}`, - Bucket: options.configBucketName, - Key: options.licenseFileKey, - }), - ), - ); - - return response; - } catch (e) { - throw new Error(`${e}`); - } -} - -/** - * - * @param s3Client S3Client - * @param options FirewallReplacementOptions - * @param replacements string | undefined - * @returns Promise - */ -async function processConfigFileReplacements( - s3Client: S3Client, - options: FirewallReplacementOptions, - replacements?: FirewallReplacements, -): Promise { - // - // Validate input - if ((!options.configFileKey || !options.configDir) && !replacements) { - return; - } else if ((options.configFileKey || options.configDir) && !replacements) { - throw new Error(`Configuration file/directory S3 key provided but replacements are undefined`); - } else if ((options.configFileKey || options.configDir) && replacements) { - const configFiles = options.configFileKey ? [options.configFileKey] : []; - if (options.configDir) { - const objs = await s3Client.send( - new ListObjectsV2Command({ Bucket: options.assetBucketName, Prefix: options.configDir }), - ); - for (const obj of objs.Contents ?? []) { - // Empty folders are ignored here - if (obj.Key!.endsWith('/')) continue; - configFiles.push(obj.Key!); - } - } - for (const configFileKey of configFiles) { - // - // Get raw config asset - const rawFile = await getRawConfigFile(s3Client, options.assetBucketName, configFileKey); - // - // Process replacements - const transformedFile = await transformConfigFile(replacements, rawFile); - // - // Put transformed config file - await putTransformedConfigFile(s3Client, options.configBucketName, transformedFile, configFileKey); - } - } - return; -} - -/** - * Get raw configuration file from asset bucket - * @param s3Client S3Client - * @param assetBucketName string - * @param configFileKey string - * @returns Promise - */ -async function getRawConfigFile( - s3Client: S3Client, - assetBucketName: string, - configFileKey: string, -): Promise { - console.log(`Retrieving raw configuration file ${configFileKey} from bucket s3://${assetBucketName}...`); - try { - const response = await throttlingBackOff(() => - s3Client.send(new GetObjectCommand({ Bucket: assetBucketName, Key: configFileKey })), - ); - - return await response.Body?.transformToString(); - } catch (e) { - throw new Error(`${e}`); - } -} - -/** - * Transform configuration file with replacements - * @param replacements FirewallReplacements - * @param configFile string | undefined - * @returns string - */ -async function transformConfigFile(replacements: FirewallReplacements, configFile?: string): Promise { - if (!configFile) { - throw new Error(`Encountered an error retrieving configuration file from S3`); - } - // - // Process config file replacements - const lookupRegex = /\${ACCEL_LOOKUP::(EC2|CUSTOM|SECRETS_MANAGER)(:.[^}]+){1,3}}/gi; - const variables = [...new Set(configFile.match(lookupRegex) ?? [])]; - const replacedVariables = await replacements.processReplacements(variables); - // - // Transform variables to regex - const regexVariables = transformVariablesToRegex(variables); - - for (const [index, value] of regexVariables.entries()) { - configFile = configFile.replace(new RegExp(value, 'g'), replacedVariables[index]); - } - return configFile; -} - -/** - * Transform variables to a regex pattern - * @param variables string[] - * @returns string[] - */ -function transformVariablesToRegex(variables: string[]): string[] { - const regexVariables: string[] = []; - - for (const variable of variables) { - let regexVariable = variable; - regexVariable = regexVariable.replace(/\$/, '\\$'); - regexVariable = regexVariable.replace(/\{/, '\\{'); - regexVariable = regexVariable.replace(/\}/, '\\}'); - regexVariables.push(regexVariable); - } - return regexVariables; -} - -/** - * Put transformed config file to config bucket - * @param s3Client - * @param configBucketName - * @param transformedFile - * @param configFileKey - * @returns Promise - */ -async function putTransformedConfigFile( - s3Client: S3Client, - configBucketName: string, - transformedFile: string, - configFileKey: string, -): Promise { - console.log(`Putting transformed configuration file ${configFileKey} to bucket s3://${configBucketName}...`); - try { - return await throttlingBackOff(() => - s3Client.send(new PutObjectCommand({ Bucket: configBucketName, Body: transformedFile, Key: configFileKey })), - ); - } catch (e) { - throw new Error(`${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/package.json deleted file mode 100644 index f69ca25..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-firewall-config-replacements", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-s3": "3.412.0", - "@aws-sdk/client-sts": "3.410.0", - "ip-num": "1.5.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/replacements.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/replacements.ts deleted file mode 100644 index 997a647..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/replacements.ts +++ /dev/null @@ -1,1620 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - DescribeInstancesCommand, - DescribeSubnetsCommand, - DescribeSubnetsCommandOutput, - DescribeVpcsCommand, - DescribeVpnConnectionsCommand, - EC2Client, - InstanceNetworkInterface, - InstancePrivateIpAddress, - TunnelOption, -} from '@aws-sdk/client-ec2'; -import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { IPv4CidrRange } from 'ip-num'; -import { FirewallReplacementOptions, IStaticReplacements } from './index'; - -/** - * Describes a network interface - */ -interface INetworkInterface { - /** - * The device index of the interface - */ - readonly deviceIndex: number; - /** - * The ID of the network interface - */ - readonly interfaceId: string; - /** - * The primary private IP address of the interface - */ - readonly primaryPrivateIp: string; - /** - * The subnet the ENI was deployed to - */ - readonly subnet: ISubnet; - /** - * The primary public IP address of the interface - */ - readonly primaryPublicIp?: string; - /** - * The secondary IP addresses of the interface - * - * @remarks - * For mapping purposes, IPs are stored in the format `privateIp:publicIp` - */ - readonly secondaryIps?: string[]; -} - -/** - * Describes a VPC subnet - */ -interface ISubnet { - /** - * The logical name of the subnet - */ - readonly name: string; - /** - * The network address of the subnet - */ - readonly networkAddress: string; - /** - * The network mask of the subnet - */ - readonly networkMask: string; - /** - * The router IP address of the subnet - */ - readonly routerAddress: string; - /** - * The CIDR of the subnet - */ - readonly subnetCidr: string; - /** - * The ID of the subnet - */ - readonly subnetId: string; -} - -/** - * Describes a VPC CIDR range - */ -interface IVpcCidr { - /** - * The numerical index of the VPC CIDR - */ - readonly index: number; - /** - * The network address of the VPC CIDR - */ - readonly networkAddress: string; - /** - * The network mask of the VPC CIDR - */ - readonly networkMask: string; - /** - * The router IP address of the VPC CIDR - */ - readonly routerAddress: string; - /** - * The CIDR of the VPC - */ - readonly vpcCidr: string; -} - -/** - * Describes a VPN tunnel - */ -interface IVpnTunnel { - /** - * AWS inside IP - */ - readonly awsInsideIp: string; - /** - * AWS outside IP - */ - readonly awsOutsideIp: string; - /** - * Customer gateway inside IP - */ - readonly cgwInsideIp: string; - /** - * The pre-shared key - */ - readonly preSharedKey: string; - /** - * Tunnel inside CIDR - */ - readonly tunnelInsideCidr: string; - /** - * Tunnel inside netmask - */ - readonly tunnelInsideNetmask: string; -} - -/** - * Describes a VPN connection - */ -interface IVpnConnection { - /** - * The name of the VPN connection - */ - readonly name: string; - /** - * AWS BGP ASN - */ - readonly awsBgpAsn: number; - /** - * Customer gateway BGP ASN - */ - readonly cgwBgpAsn: number; - /** - * The customer gateway outside IP address - */ - readonly cgwOutsideIp: string; - /** - * The VPN tunnel details - */ - readonly tunnels: IVpnTunnel[]; -} - -/** - * Describes VPN connection properties - */ -export interface VpnConnectionProps { - /** - * The name of the VPN connection - */ - readonly name: string; - /** - * AWS BGP ASN - */ - readonly awsBgpAsn: number; - /** - * Customer gateway BGP ASN - */ - readonly cgwBgpAsn: number; - /** - * The customer gateway outside IP address - */ - readonly cgwOutsideIp: string; - /** - * The VPN connection ID - */ - readonly id: string; - /** - * The owning account ID, if different than the invoking account - */ - readonly owningAccountId?: string; - /** - * The owning region, if different from the invoking region - */ - readonly owningRegion?: string; -} - -/** - * Describes the regex patterns for replacement variables - */ -interface IReplacementRegex { - /** - * Hostname replacement regex - */ - hostname: RegExp; - /** - * ENI match regex - */ - eni: RegExp; - /** - * ENI private IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:PRIVATEIP_0} - */ - eniPrivateIp: RegExp; - /** - * ENI public IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:PUBLICIP_0} - */ - eniPublicIp: RegExp; - /** - * ENI subnet CIDR replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_CIDR} - */ - eniSubnetCidr: RegExp; - /** - * ENI subnet mask replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_NETMASK} - */ - eniSubnetMask: RegExp; - /** - * ENI subnet network IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_NETWORKIP} - */ - eniSubnetNetIp: RegExp; - /** - * ENI subnet router IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_ROUTERIP} - */ - eniSubnetRouterIp: RegExp; - /** - * Custom static replacement regex - * - * @example - * ${ACCEL_LOOKUP::CUSTOM:KEY} - */ - static: RegExp; - /** - * Subnet match regex - */ - subnet: RegExp; - /** - * Subnet CIDR regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:CIDR:subnetName} - */ - subnetCidr: RegExp; - /** - * Subnet netmask replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:NETMASK:subnetName} - */ - subnetMask: RegExp; - /** - * Subnet network IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:NETWORKIP:subnetName} - */ - subnetNetIp: RegExp; - /** - * Subnet router IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:ROUTERIP:subnetName} - */ - subnetRouterIp: RegExp; - /** - * VPC match regex - */ - vpc: RegExp; - /** - * VPC CIDR replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:CIDR_0} - */ - vpcCidr: RegExp; - /** - * VPC netmask replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:NETMASK_0} - */ - vpcNetmask: RegExp; - /** - * VPC network IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:NETWORKIP_0} - */ - vpcNetIp: RegExp; - /** - * VPC router IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:ROUTERIP_0} - */ - vpcRouterIp: RegExp; - /** - * VPN match regex - */ - vpn: RegExp; - /** - * VPN AWS BGP ASN - * - * @example - * ${ACCEL_LOOKUP::EC2:VPN:AWS_BGPASN:vpnName} - */ - vpnAwsBgpAsn: RegExp; - /** - * VPN tunnel AWS inside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:AWS_INSIDEIP_0:vpnName} - */ - vpnAwsInsideIp: RegExp; - /** - * VPN tunnel AWS outside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:AWS_OUTSIDEIP_0:vpnName} - */ - vpnAwsOutsideIp: RegExp; - /** - * VPN CGW BGP ASN - * - * @example - * ${ACCEL_LOOKUP::EC2:VPN:CGW_BGPASN:vpnName} - */ - vpnCgwBgpAsn: RegExp; - /** - * VPN tunnel CGW inside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:CGW_INSIDEIP_0:vpnName} - */ - vpnCgwInsideIp: RegExp; - /** - * VPN tunnel CGW outside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:CGW_OUTSIDEIP:vpnName} - */ - vpnCgwOutsideIp: RegExp; - /** - * VPN tunnel inside CIDR - * - * @example ${ACCEL_LOOKUP::EC2:VPN:INSIDE_CIDR_0:vpnName} - */ - vpnInsideCidr: RegExp; - /** - * VPN inside netmask - * - * @example ${ACCEL_LOOKUP::EC2:VPN:INSIDE_NETMASK_0:vpnName} - */ - vpnInsideNetmask: RegExp; - /** - * VPN tunnel pre-shared key - * - * @example ${ACCEL_LOOKUP::EC2:VPN:PSK_0:vpnName} - */ - vpnPsk: RegExp; - /** - * Secrets Manager secret match regex - */ - secretsManager: RegExp; -} - -/** - * Describes a VPC with third-party firewall resources - */ -interface IFirewallReplacements { - /** - * The ID of the VPC - */ - readonly vpcId: string; - /** - * The replacement regex patterns - */ - readonly replacementRegex: IReplacementRegex; - /** - * The name of the firewall instance - */ - readonly firewallName?: string; - /** - * The ID of the firewall instance - */ - readonly instanceId?: string; - /** - * The role name used for cross-account VPN lookups - */ - readonly roleName?: string; - /** - * Static key/value pair replacements defined for the firewall - */ - readonly staticReplacements?: IStaticReplacements[]; - /** - * VPN connection properties for VPN connections associated with this firewall - */ - readonly vpnConnectionProps?: VpnConnectionProps[]; -} - -enum FirewallReplacementType { - /** - * ENI match regex - */ - ENI = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:.+}$', - /** - * ENI private IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:PRIVATEIP_0} - */ - ENI_PRIVATEIP = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:PRIVATEIP_\\d}$', - /** - * ENI public IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:PUBLICIP_0} - */ - ENI_PUBLICIP = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:PUBLICIP_\\d}$', - /** - * ENI subnet CIDR replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_CIDR} - */ - ENI_SUBNET_CIDR = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:SUBNET_CIDR}$', - /** - * ENI subnet mask replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_NETMASK} - */ - ENI_SUBNET_MASK = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:SUBNET_NETMASK}$', - /** - * ENI subnet network IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_NETWORKIP} - */ - ENI_SUBNET_NETWORK_IP = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:SUBNET_NETWORKIP}$', - /** - * ENI subnet router IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:ENI_0:SUBNET_ROUTERIP} - */ - ENI_SUBNET_ROUTER_IP = '^\\${ACCEL_LOOKUP::EC2:ENI_\\d:SUBNET_ROUTERIP}$', - /** - * Hostname replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:INSTANCE:HOSTNAME} - */ - HOSTNAME = '^\\${ACCEL_LOOKUP::EC2:INSTANCE:HOSTNAME}$', - /** - * Custom static replacement regex - * - * @example - * ${ACCEL_LOOKUP::CUSTOM:KEY} - */ - STATIC = '^\\${ACCEL_LOOKUP::CUSTOM:.+}$', - /** - * Subnet match regex - */ - SUBNET = '^\\${ACCEL_LOOKUP::EC2:SUBNET:.+}$', - /** - * Subnet CIDR regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:CIDR:subnetName} - */ - SUBNET_CIDR = '^\\${ACCEL_LOOKUP::EC2:SUBNET:CIDR:.+}$', - /** - * Subnet netmask replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:NETMASK:subnetName} - */ - SUBNET_NETMASK = '^\\${ACCEL_LOOKUP::EC2:SUBNET:NETMASK:.+}$', - /** - * Subnet network IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:NETWORKIP:subnetName} - */ - SUBNET_NETWORKIP = '^\\${ACCEL_LOOKUP::EC2:SUBNET:NETWORKIP:.+}$', - /** - * Subnet router IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:SUBNET:ROUTERIP:subnetName} - */ - SUBNET_ROUTERIP = '^\\${ACCEL_LOOKUP::EC2:SUBNET:ROUTERIP:.+}$', - /** - * VPC match regex - */ - VPC = '^\\${ACCEL_LOOKUP::EC2:VPC:.+}$', - /** - * VPC CIDR replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:CIDR_0} - */ - VPC_CIDR = '^\\${ACCEL_LOOKUP::EC2:VPC:CIDR_\\d}$', - /** - * VPC netmask replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:NETMASK_0} - */ - VPC_NETMASK = '^\\${ACCEL_LOOKUP::EC2:VPC:NETMASK_\\d}$', - /** - * VPC network IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:NETWORKIP_0} - */ - VPC_NETWORKIP = '^\\${ACCEL_LOOKUP::EC2:VPC:NETWORKIP_\\d}$', - /** - * VPC router IP replacement regex - * - * @example - * ${ACCEL_LOOKUP::EC2:VPC:ROUTERIP_0} - */ - VPC_ROUTERIP = '^\\${ACCEL_LOOKUP::EC2:VPC:ROUTERIP_\\d}$', - /** - * VPN match regex - */ - VPN = '^\\${ACCEL_LOOKUP::EC2:VPN:.+}$', - /** - * VPN AWS BGP ASN - * - * @example - * ${ACCEL_LOOKUP::EC2:VPN:AWS_BGPASN:vpnName} - */ - VPN_AWS_BGP_ASN = '^\\${ACCEL_LOOKUP::EC2:VPN:AWS_BGPASN:.+}$', - /** - * VPN tunnel AWS inside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:AWS_INSIDEIP_0:vpnName} - */ - VPN_AWS_INSIDE_IP = '^\\${ACCEL_LOOKUP::EC2:VPN:AWS_INSIDEIP_\\d:.+}$', - /** - * VPN tunnel AWS outside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:AWS_OUTSIDEIP_0:vpnName} - */ - VPN_AWS_OUTSIDE_IP = '^\\${ACCEL_LOOKUP::EC2:VPN:AWS_OUTSIDEIP_\\d:.+}$', - /** - * VPN CGW BGP ASN - * - * @example - * ${ACCEL_LOOKUP::EC2:VPN:CGW_BGPASN:vpnName} - */ - VPN_CGW_BGP_ASN = '^\\${ACCEL_LOOKUP::EC2:VPN:CGW_BGPASN:.+}$', - /** - * VPN tunnel CGW inside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:CGW_INSIDEIP_0:vpnName} - */ - VPN_CGW_INSIDE_IP = '^\\${ACCEL_LOOKUP::EC2:VPN:CGW_INSIDEIP_\\d:.+}$', - /** - * VPN tunnel CGW outside IP - * - * @example ${ACCEL_LOOKUP::EC2:VPN:CGW_OUTSIDEIP:vpnName} - */ - VPN_CGW_OUTSIDE_IP = '^\\${ACCEL_LOOKUP::EC2:VPN:CGW_OUTSIDEIP:.+}$', - /** - * VPN tunnel inside CIDR - * - * @example ${ACCEL_LOOKUP::EC2:VPN:INSIDE_CIDR_0:vpnName} - */ - VPN_INSIDE_CIDR = '^\\${ACCEL_LOOKUP::EC2:VPN:INSIDE_CIDR_\\d:.+}$', - /** - * VPN inside netmask - * - * @example ${ACCEL_LOOKUP::EC2:VPN:INSIDE_NETMASK_0:vpnName} - */ - VPN_INSIDE_NETMASK = '^\\${ACCEL_LOOKUP::EC2:VPN:INSIDE_NETMASK_\\d:.+}$', - /** - * VPN tunnel pre-shared key - * - * @example ${ACCEL_LOOKUP::EC2:VPN:PSK_0:vpnName} - */ - VPN_PSK = '^\\${ACCEL_LOOKUP::EC2:VPN:PSK_\\d:.+}$', - /** - * Secrets Manager secret regex match - * - * @example ${ACCEL_LOOKUP::SECRETS_MANAGER:secretName} - */ - SECRETS_MANAGER = '^\\${ACCEL_LOOKUP::SECRETS_MANAGER:.+}$', -} - -/** - * Class describing a VPC with third-party firewall resources - */ -export class FirewallReplacements implements IFirewallReplacements { - private cidrs: VpcCidr[] = []; - private networkInterfaces: NetworkInterface[] = []; - private subnets: Subnet[] = []; - private vpnConnections: VpnConnection[] = []; - private readonly partition: string; - private readonly secretsManagerClient: SecretsManagerClient; - private readonly managementAccountId?: string; - public readonly replacementRegex: IReplacementRegex; - public readonly vpcId: string; - public readonly firewallName?: string; - public readonly instanceId?: string; - public readonly roleName?: string; - public readonly staticReplacements?: IStaticReplacements[]; - public readonly vpnConnectionProps?: VpnConnectionProps[]; - - constructor(options: FirewallReplacementOptions) { - this.replacementRegex = this.setReplacementRegex(); - this.partition = options.partition; - this.vpcId = options.vpcId; - this.firewallName = options.firewallName; - this.instanceId = options.instanceId; - this.roleName = options.roleName; - this.staticReplacements = options.staticReplacements; - this.vpnConnectionProps = options.vpnConnectionProps; - this.secretsManagerClient = new SecretsManagerClient({ customUserAgent: process.env['SOLUTION_ID'] }); - this.managementAccountId = options.managementAccountId; - } - - /** - * Set replacement regex patterns - * @returns IReplacementRegex - */ - private setReplacementRegex(): IReplacementRegex { - return { - eni: new RegExp(FirewallReplacementType.ENI, 'i'), - eniPrivateIp: new RegExp(FirewallReplacementType.ENI_PRIVATEIP, 'i'), - eniPublicIp: new RegExp(FirewallReplacementType.ENI_PUBLICIP, 'i'), - eniSubnetCidr: new RegExp(FirewallReplacementType.ENI_SUBNET_CIDR, 'i'), - eniSubnetMask: new RegExp(FirewallReplacementType.ENI_SUBNET_MASK, 'i'), - eniSubnetNetIp: new RegExp(FirewallReplacementType.ENI_SUBNET_NETWORK_IP, 'i'), - eniSubnetRouterIp: new RegExp(FirewallReplacementType.ENI_SUBNET_ROUTER_IP, 'i'), - hostname: new RegExp(FirewallReplacementType.HOSTNAME, 'i'), - static: new RegExp(FirewallReplacementType.STATIC, 'i'), - subnet: new RegExp(FirewallReplacementType.SUBNET, 'i'), - subnetCidr: new RegExp(FirewallReplacementType.SUBNET_CIDR, 'i'), - subnetMask: new RegExp(FirewallReplacementType.SUBNET_NETMASK, 'i'), - subnetNetIp: new RegExp(FirewallReplacementType.SUBNET_NETWORKIP, 'i'), - subnetRouterIp: new RegExp(FirewallReplacementType.SUBNET_ROUTERIP, 'i'), - vpc: new RegExp(FirewallReplacementType.VPC, 'i'), - vpcCidr: new RegExp(FirewallReplacementType.VPC_CIDR, 'i'), - vpcNetIp: new RegExp(FirewallReplacementType.VPC_NETWORKIP, 'i'), - vpcNetmask: new RegExp(FirewallReplacementType.VPC_NETMASK, 'i'), - vpcRouterIp: new RegExp(FirewallReplacementType.VPC_ROUTERIP, 'i'), - vpn: new RegExp(FirewallReplacementType.VPN, 'i'), - vpnAwsBgpAsn: new RegExp(FirewallReplacementType.VPN_AWS_BGP_ASN, 'i'), - vpnAwsInsideIp: new RegExp(FirewallReplacementType.VPN_AWS_INSIDE_IP, 'i'), - vpnAwsOutsideIp: new RegExp(FirewallReplacementType.VPN_AWS_OUTSIDE_IP, 'i'), - vpnCgwBgpAsn: new RegExp(FirewallReplacementType.VPN_CGW_BGP_ASN, 'i'), - vpnCgwInsideIp: new RegExp(FirewallReplacementType.VPN_CGW_INSIDE_IP, 'i'), - vpnCgwOutsideIp: new RegExp(FirewallReplacementType.VPN_CGW_OUTSIDE_IP, 'i'), - vpnInsideCidr: new RegExp(FirewallReplacementType.VPN_INSIDE_CIDR, 'i'), - vpnInsideNetmask: new RegExp(FirewallReplacementType.VPN_INSIDE_NETMASK, 'i'), - vpnPsk: new RegExp(FirewallReplacementType.VPN_PSK, 'i'), - secretsManager: new RegExp(FirewallReplacementType.SECRETS_MANAGER, 'i'), - }; - } - - /** - * Initialize the VPC replacements object - * @param ec2Client EC2Client - * @param serviceToken string - * @returns Promise - */ - public async init(ec2Client: EC2Client, serviceToken: string): Promise { - // - // Set VPC CIDR details - await this.setVpcCidrDetails(ec2Client); - // - // Set subnet details - await this.setVpcSubnetDetails(ec2Client); - // - // Set network interface details - if (this.instanceId) { - await this.setNetworkInterfaceDetails(ec2Client, this.instanceId); - } - // - // Set up VPN replacements - for (const vpnItem of this.vpnConnectionProps ?? []) { - const vpn = new VpnConnection(vpnItem); - this.vpnConnections.push(await vpn.init(vpnItem, serviceToken, this.roleName)); - } - - return this; - } - - /** - * Set VPC CIDR details - * @param ec2Client EC2Client - */ - private async setVpcCidrDetails(ec2Client: EC2Client): Promise { - let index = 0; - // - // Get VPC details - console.log(`Retrieving VPC CIDR details for VPC ${this.vpcId}...`); - try { - const response = await throttlingBackOff(() => ec2Client.send(new DescribeVpcsCommand({ VpcIds: [this.vpcId] }))); - // - // Add CIDRs to array - if (!response.Vpcs) { - throw new Error(`Unable to retrieve VPC details for VPC ${this.vpcId}`); - } - for (const association of response.Vpcs[0].CidrBlockAssociationSet ?? []) { - if (!association.CidrBlock) { - throw new Error(`Unable to retrieve CIDR block details for VPC ${this.vpcId}`); - } - this.addVpcCidr(index, association.CidrBlock); - index += 1; - } - } catch (e) { - throw new Error(`${e}`); - } - } - - /** - * Get VPC subnet details - * @param ec2Client EC2Client - */ - private async setVpcSubnetDetails(ec2Client: EC2Client): Promise { - let nextToken: string | undefined = undefined; - // - // Get subnet details - console.log(`Retrieving VPC subnet details for VPC ${this.vpcId}...`); - try { - do { - const page = await throttlingBackOff(() => - ec2Client.send( - new DescribeSubnetsCommand({ Filters: [{ Name: 'vpc-id', Values: [this.vpcId] }], NextToken: nextToken }), - ), - ); - // - // Process page response - this.processSubnets(page); - - nextToken = page.NextToken; - } while (nextToken); - } catch (e) { - throw new Error(`${e}`); - } - } - - /** - * Process describe subnets command output and add to array - * @param subnetPage DescribeSubnetsCommandOutput - */ - private processSubnets(subnetPage: DescribeSubnetsCommandOutput): void { - for (const subnet of subnetPage.Subnets ?? []) { - // - // Validate response - if (!subnet.CidrBlock) { - throw new Error(`Unable to retrieve subnet CIDR details for VPC ${this.vpcId}`); - } - if (!subnet.SubnetId) { - throw new Error(`Unable to retrieve subnet ID details for VPC ${this.vpcId}`); - } - const name = subnet.Tags?.find(tag => tag.Key === 'Name')?.Value ?? ''; - // - // Add to subnet details array - this.addSubnet(name, subnet.CidrBlock, subnet.SubnetId); - } - } - - /** - * Set network interface details for the EC2 firewall - * @param ec2Client EC2Client - * @param instanceId string - */ - private async setNetworkInterfaceDetails(ec2Client: EC2Client, instanceId: string): Promise { - // - // Get instance details - console.log(`Retrieving network interface details for firewall instance ${instanceId}...`); - try { - const response = await throttlingBackOff(() => - ec2Client.send(new DescribeInstancesCommand({ InstanceIds: [instanceId] })), - ); - // - // Validate response - if (!response.Reservations) { - throw new Error(`Unable to retrieve instance details for instance ${instanceId}`); - } - if (!response.Reservations[0].Instances) { - throw new Error(`Unable to retrieve instance details for instance ${instanceId}`); - } - // - // Process interface details - for (const eni of response.Reservations[0].Instances[0].NetworkInterfaces ?? []) { - // - // Add network interface - const eniProps = this.processNetworkInterfaceDetails(eni, instanceId); - this.addNetworkInterface(eniProps); - } - } catch (e) { - throw new Error(`${e}`); - } - } - - /** - * Process network interface property details - * @param networkInterface InstanceNetworkInterface - * @param instanceId string - * @returns INetworkInterface - */ - private processNetworkInterfaceDetails( - networkInterface: InstanceNetworkInterface, - instanceId: string, - ): INetworkInterface { - // - // Validate device index - if (networkInterface.Attachment?.DeviceIndex === undefined) { - throw new Error(`Unable to retrieve network interface attachment details for instance ${instanceId}`); - } - // - // Validate ENI ID - if (!networkInterface.NetworkInterfaceId) { - throw new Error(`Unable to retrieve network interface ID details for instance ${instanceId}`); - } - // - // Validate primary private IP addresses - if (!networkInterface.PrivateIpAddress) { - throw new Error(`Unable to retrieve network interface IP details for instance ${instanceId}`); - } - // - // Validate private IP addresses - if (!networkInterface.PrivateIpAddresses) { - throw new Error(`Unable to retrieve network interface IP details for instance ${instanceId}`); - } - // - // Validate subnet ID - if (!networkInterface.SubnetId) { - throw new Error(`Unable to retrieve network interface subnet details for instance ${instanceId}`); - } - // - // Return interface details - return { - deviceIndex: networkInterface.Attachment.DeviceIndex, - interfaceId: networkInterface.NetworkInterfaceId, - primaryPrivateIp: networkInterface.PrivateIpAddress, - subnet: this.getSubnetById(networkInterface.SubnetId), - primaryPublicIp: this.setPrimaryPublicIp(networkInterface.PrivateIpAddresses, instanceId), - secondaryIps: this.setSecondaryIps(networkInterface.PrivateIpAddresses), - }; - } - - /** - * Set primary network interface public IP, if it exists - * @param addresses InstancePrivateIpAddress[] - * @param instanceId string - * @returns string | undefined - */ - private setPrimaryPublicIp(ipAddresses: InstancePrivateIpAddress[], instanceId: string): string | undefined { - const primaryIp = ipAddresses.find(item => item.Primary); - - if (!primaryIp) { - throw new Error(`Unable to retrieve primary network interface details for instance ${instanceId}`); - } - - return primaryIp.Association?.PublicIp; - } - - /** - * Set secondary IP addresses, if they exist - * @param addresses InstancePrivateIpAddress[] - * @returns string[] | undefined - */ - private setSecondaryIps(ipAddresses: InstancePrivateIpAddress[]): string[] | undefined { - const secondaryIps = ipAddresses.filter(item => !item.Primary); - let ipArray: string[] | undefined = undefined; - - if (secondaryIps.length > 0) { - ipArray = []; - for (const ip of secondaryIps) { - ipArray.push(`${ip.PrivateIpAddress}:${ip.Association?.PublicIp}`); - } - } - return ipArray; - } - - /** - * Mutator method to add VPC CIDR details to the VPC object - * @param index number - * @param cidr string - */ - private addVpcCidr(index: number, cidr: string): void { - this.cidrs.push(new VpcCidr(index, cidr)); - } - - /** - * Mutator method to add subnets to the VPC object - * @param subnetCidr string - * @param subnetId string - */ - private addSubnet(name: string, subnetCidr: string, subnetId: string): void { - this.subnets.push(new Subnet(name, subnetCidr, subnetId)); - } - - /** - * Adds a network interface definition to the VPC - * @param eni {@link INetworkInterface} - */ - private addNetworkInterface(eni: INetworkInterface): void { - this.networkInterfaces.push(new NetworkInterface(eni)); - } - - /** - * Accessor method to retrieve a VPC CIDR - * @param index number - * @returns VpcCidr - */ - public getVpcCidr(index: number): VpcCidr { - const vpcCidr = this.cidrs.find(cidrItem => cidrItem.index === index); - - if (!vpcCidr) { - throw new Error(`VPC CIDR index ${index} does not exist in VPC ${this.vpcId}`); - } - - return vpcCidr; - } - - /** - * Accessor method to retrieve a network interface - * @param index number - * @returns VpcCidr - */ - public getNetworkInterface(index: number): NetworkInterface { - const eni = this.networkInterfaces.find(eniItem => eniItem.deviceIndex === index); - - if (!eni) { - throw new Error(`Network interface index ${index} does not exist in VPC ${this.vpcId}`); - } - - return eni; - } - - /** - * Accessor method to retrieve a static replacement value - * @param keyName string - * @returns string - */ - public getStaticReplacementByName(keyName: string): string { - const replacement = this.staticReplacements?.find(staticItem => staticItem.key === keyName); - - if (!replacement) { - throw new Error( - `Static replacement with key name ${keyName} is not defined for this firewall in customizations-config.yaml`, - ); - } - - return replacement.value; - } - - /** - * Accessor method to get a subnet by ID - * @param subnetId string - * @returns Subnet - */ - public getSubnetById(subnetId: string): Subnet { - const subnet = this.subnets.find(subnetItem => subnetItem.subnetId === subnetId); - - if (!subnet) { - throw new Error(`Subnet ID ${subnetId} does not exist in VPC ${this.vpcId}`); - } - - return subnet; - } - - /** - * Accessor method to get a subnet by logical name - * @param name string - * @returns Subnet - */ - public getSubnetByName(name: string): Subnet { - const subnet = this.subnets.find(subnetItem => subnetItem.name === name); - - if (!subnet) { - throw new Error(`Subnet with Name tag ${name} does not exist in VPC ${this.vpcId}`); - } - - return subnet; - } - - /** - * Accessor method to get a VPN connection by logical name - * @param name string - * @returns VpnConnection - */ - public getVpnConnectionByName(name: string): VpnConnection { - const vpn = this.vpnConnections.find(vpnItem => vpnItem.name === name); - - if (!vpn) { - throw new Error(`VPN with Name tag ${name} is not connected to this firewall`); - } - - return vpn; - } - - /** - * Process variable replacements - * @param variables string[] - * @returns string[] - */ - public async processReplacements(variables: string[]): Promise { - const replacements: string[] = []; - - for (const variable of variables) { - const replacement = this.processStaticReplacement(variable) ?? (await this.processDynamicReplacement(variable)); - - if (!replacement) { - throw new Error( - `Unable to parse replacement variable ${variable}. Please verify the variable is using the correct syntax.`, - ); - } - replacements.push(replacement); - } - return replacements; - } - - /** - * Process a static replacement variable, or return undefined if no match - * @param variable string - * @returns string | undefined - */ - private processStaticReplacement(variable: string): string | undefined { - if (this.replacementRegex.hostname.test(variable)) { - return this.firewallName ?? ''; - } else if (this.replacementRegex.static.test(variable)) { - return this.processCustomStaticReplacement(variable); - } else { - return; - } - } - - /** - * Process a custom static replacement variable - * @param variable string - * @returns string - */ - private processCustomStaticReplacement(variable: string): string { - try { - const keyName = variable.split(':')[3].replace('}', ''); - return this.getStaticReplacementByName(keyName); - } catch (e) { - throw new Error(`Unable to process static replacement variable ${variable}. Error message: ${e}`); - } - } - - /** - * Process a dynamic replacement variable, or return undefined if no match - * @param variable string - * @returns string | undefined - */ - private async processDynamicReplacement(variable: string): Promise { - if (this.replacementRegex.vpc.test(variable)) { - return this.processVpcReplacement(variable); - } else if (this.replacementRegex.subnet.test(variable)) { - return this.processSubnetReplacement(variable); - } else if (this.replacementRegex.eni.test(variable)) { - return this.processNetworkInterfaceReplacement(variable); - } else if (this.replacementRegex.vpn.test(variable)) { - return this.processVpnReplacement(variable); - } else if (this.replacementRegex.secretsManager.test(variable)) { - return this.processSecretsManagerReplacement(variable); - } else { - return; - } - } - - private async processSecretsManagerReplacement(variable: string): Promise { - try { - const secretVariable = variable.split(':')[3].replace('}', ''); - const secretArn = this.managementAccountId - ? `arn:${this.partition}:secretsmanager:${process.env['AWS_REGION']}:${this.managementAccountId}:secret:${secretVariable}` - : secretVariable; - const secretResponse = await this.secretsManagerClient.send(new GetSecretValueCommand({ SecretId: secretArn })); - return secretResponse.SecretString!; - } catch (e) { - throw new Error(`Unable to process Secret replacement variable ${variable}. Error message: ${e}`); - } - } - - /** - * Process VPC replacements - * @param variable string - * @returns string - */ - private processVpcReplacement(variable: string): string { - // - // Get VPC CIDR - try { - const index = Number(variable.split(':')[4].split('_')[1].replace('}', '')); - const cidr = this.getVpcCidr(index); - // - // Return replacement - if (this.replacementRegex.vpcCidr.test(variable)) { - return cidr.vpcCidr; - } else if (this.replacementRegex.vpcNetmask.test(variable)) { - return cidr.networkMask; - } else if (this.replacementRegex.vpcNetIp.test(variable)) { - return cidr.networkAddress; - } else if (this.replacementRegex.vpcRouterIp.test(variable)) { - return cidr.routerAddress; - } else { - throw new Error(`Variable does not match accepted patterns. Please ensure it is using the correct syntax.`); - } - } catch (e) { - throw new Error(`Unable to process VPC replacement variable ${variable}. Error message: ${e}`); - } - } - - /** - * Process subnet replacements - * @param variable string - * @returns string - */ - private processSubnetReplacement(variable: string): string { - // - // Get subnet - try { - const subnetName = variable.split(':')[5].replace('}', ''); - const subnet = this.getSubnetByName(subnetName); - // - // Return replacement - if (this.replacementRegex.subnetCidr.test(variable)) { - return subnet.subnetCidr; - } else if (this.replacementRegex.subnetMask.test(variable)) { - return subnet.networkMask; - } else if (this.replacementRegex.subnetNetIp.test(variable)) { - return subnet.networkAddress; - } else if (this.replacementRegex.subnetRouterIp.test(variable)) { - return subnet.routerAddress; - } else { - throw new Error(`Variable does not match accepted patterns. Please ensure it is using the correct syntax.`); - } - } catch (e) { - throw new Error(`Unable to process subnet replacement variable ${variable}. Error message: ${e}`); - } - } - - /** - * Process network interface replacements - * @param variable string - * @returns string - */ - private processNetworkInterfaceReplacement(variable: string): string { - try { - // - // Validate object state - if (!this.instanceId) { - throw new Error(`Network interface replacements are not supported for firewall AutoScaling Groups.`); - } - // - // Get ENI - const deviceIndex = Number(variable.split(':')[3].split('_')[1]); - const eni = this.getNetworkInterface(deviceIndex); - // - // Return replacement - if (this.replacementRegex.eniPrivateIp.test(variable)) { - const addressIndex = Number(variable.split(':')[4].split('_')[1].replace('}', '')); - return eni.getPrivateIpAddress(addressIndex); - } else if (this.replacementRegex.eniPublicIp.test(variable)) { - const addressIndex = Number(variable.split(':')[4].split('_')[1].replace('}', '')); - return eni.getPublicIpAddress(addressIndex); - } else { - return this.processNetworkInterfaceSubnetReplacement(eni, variable); - } - } catch (e) { - throw new Error(`Unable to process network interface replacement variable ${variable}. Error message: ${e}`); - } - } - - /** - * Process ENI subnet replacements - * @param eni {@link NetworkInterface} - * @param variable string - * @returns string - */ - private processNetworkInterfaceSubnetReplacement(eni: NetworkInterface, variable: string): string { - if (this.replacementRegex.eniSubnetCidr.test(variable)) { - return eni.subnet.subnetCidr; - } else if (this.replacementRegex.eniSubnetMask.test(variable)) { - return eni.subnet.networkMask; - } else if (this.replacementRegex.eniSubnetNetIp.test(variable)) { - return eni.subnet.networkAddress; - } else if (this.replacementRegex.eniSubnetRouterIp.test(variable)) { - return eni.subnet.routerAddress; - } else { - throw new Error(`Variable does not match accepted patterns. Please ensure it is using the correct syntax.`); - } - } - - /** - * Process VPN replacements - * @param variable string - * @returns string - */ - private processVpnReplacement(variable: string): string { - try { - // - // Get VPN connection object - const vpnName = variable.split(':')[5].replace('}', ''); - const vpn = this.getVpnConnectionByName(vpnName); - // - // Return replacement - if (this.replacementRegex.vpnAwsBgpAsn.test(variable)) { - return vpn.awsBgpAsn.toString(); - } else if (this.replacementRegex.vpnCgwBgpAsn.test(variable)) { - return vpn.cgwBgpAsn.toString(); - } else if (this.replacementRegex.vpnCgwOutsideIp.test(variable)) { - return vpn.cgwOutsideIp; - } else { - return this.processVpnTunnelReplacement(vpn, variable); - } - } catch (e) { - throw new Error(`Unable to process VPN replacement variable ${variable}. Error message: ${e}`); - } - } - - /** - * Process VPN tunnel replacements - * @param vpn VpnConnection - * @param variable string - * @returns string - */ - private processVpnTunnelReplacement(vpn: VpnConnection, variable: string): string { - const tunnelPskIndex = variable.split(':')[4].split('_')[1]; - const tunnelIpIndex = variable.split(':')[4].split('_')[2]; - - if (this.replacementRegex.vpnAwsInsideIp.test(variable)) { - return vpn.tunnels[Number(tunnelIpIndex)].awsInsideIp; - } else if (this.replacementRegex.vpnAwsOutsideIp.test(variable)) { - return vpn.tunnels[Number(tunnelIpIndex)].awsOutsideIp; - } else if (this.replacementRegex.vpnCgwInsideIp.test(variable)) { - return vpn.tunnels[Number(tunnelIpIndex)].cgwInsideIp; - } else if (this.replacementRegex.vpnInsideCidr.test(variable)) { - return vpn.tunnels[Number(tunnelIpIndex)].tunnelInsideCidr; - } else if (this.replacementRegex.vpnInsideNetmask.test(variable)) { - return vpn.tunnels[Number(tunnelIpIndex)].tunnelInsideNetmask; - } else if (this.replacementRegex.vpnPsk.test(variable)) { - return vpn.tunnels[Number(tunnelPskIndex)].preSharedKey; - } else { - throw new Error(`Variable does not match accepted patterns. Please ensure it is using the correct syntax.`); - } - } -} - -/** - * Class describing a VPC CIDR range - */ -class VpcCidr implements IVpcCidr { - public readonly index: number; - public readonly networkAddress: string; - public readonly networkMask: string; - public readonly routerAddress: string; - public readonly vpcCidr: string; - - constructor(index: number, vpcCidr: string) { - // - // Set initial properties - this.index = index; - this.vpcCidr = vpcCidr; - // - // Set VPC CIDR - const cidrRange = IPv4CidrRange.fromCidr(this.vpcCidr); - // - // Set additional properties - this.networkAddress = cidrRange.getFirst().toString(); - this.networkMask = cidrRange.getPrefix().toMask().toString(); - this.routerAddress = cidrRange.getFirst().nextIPNumber().toString(); - } -} - -/** - * Class describing a VPC subnet - */ -class Subnet implements ISubnet { - public readonly name: string; - public readonly networkAddress: string; - public readonly networkMask: string; - public readonly routerAddress: string; - public readonly subnetCidr: string; - public readonly subnetId: string; - - constructor(name: string, subnetCidr: string, subnetId: string) { - // - // Set initial properties - this.name = name; - this.subnetCidr = subnetCidr; - this.subnetId = subnetId; - // - // Set subnet CIDR - const cidrRange = IPv4CidrRange.fromCidr(this.subnetCidr); - // - // Set additional properties - this.networkAddress = cidrRange.getFirst().toString(); - this.networkMask = cidrRange.getPrefix().toMask().toString(); - this.routerAddress = cidrRange.getFirst().nextIPNumber().toString(); - } -} - -/** - * Class describing an elastic network interface - */ -class NetworkInterface implements INetworkInterface { - public readonly deviceIndex: number; - public readonly interfaceId: string; - public readonly primaryPrivateIp: string; - public readonly subnet: Subnet; - public readonly primaryPublicIp?: string; - public readonly secondaryIps?: string[]; - - constructor(props: INetworkInterface) { - this.deviceIndex = props.deviceIndex; - this.interfaceId = props.interfaceId; - this.primaryPrivateIp = props.primaryPrivateIp; - this.subnet = props.subnet; - this.primaryPublicIp = props.primaryPublicIp; - this.secondaryIps = props.secondaryIps; - } - - /** - * Accessor method to get a private IP address from - * a network interface - * @param index - * @returns string - */ - public getPrivateIpAddress(index: number): string { - if (index === 0) { - return this.primaryPrivateIp; - } else { - if (!this.secondaryIps) { - throw new Error(`Private IP index ${index} does not exist on network interface ${this.interfaceId}`); - } - try { - return this.secondaryIps[index - 1].split(':')[0]; - } catch (e) { - throw new Error(`Private IP index ${index} does not exist on network interface ${this.interfaceId}`); - } - } - } - - /** - * Accessor method to get a public IP address from - * a network interface - * @param index - * @returns string - */ - public getPublicIpAddress(index: number): string { - if (index === 0) { - if (!this.primaryPublicIp) { - throw new Error(`Public IP index ${index} does not exist on network interface ${this.interfaceId}`); - } - return this.primaryPublicIp; - } else { - if (!this.secondaryIps) { - throw new Error(`Public IP index ${index} does not exist on network interface ${this.interfaceId}`); - } - try { - return this.secondaryIps[index - 1].split(':')[1]; - } catch (e) { - throw new Error(`Public IP index ${index} does not exist on network interface ${this.interfaceId}`); - } - } - } -} - -/** - * Class describing a VPN connection - */ -class VpnConnection implements IVpnConnection { - public readonly name: string; - public readonly awsBgpAsn: number; - public readonly cgwBgpAsn: number; - public readonly cgwOutsideIp: string; - public readonly tunnels: IVpnTunnel[] = []; - - constructor(props: VpnConnectionProps) { - // - // Set initial props - this.name = props.name; - this.awsBgpAsn = props.awsBgpAsn; - this.cgwBgpAsn = props.cgwBgpAsn; - this.cgwOutsideIp = props.cgwOutsideIp; - } - - /** - * Initialize VPN tunnel details - * @param props VpnConnectionProps - * @param serviceToken string - * @param roleName string | undefined - * @returns Promise - */ - public async init(props: VpnConnectionProps, serviceToken: string, roleName?: string): Promise { - // - // Set up EC2 client - const invokingRegion = serviceToken.split(':')[3]; - const partition = serviceToken.split(':')[1]; - const ec2Client = await this.setEc2Client(props, invokingRegion, partition, roleName); - // - // Set VPN tunnels - this.tunnels.push(...(await this.setVpnTunnels(ec2Client, props.id))); - - return this; - } - - /** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param props VpnConnectionProps - * @param invokingRegion string - * @param partition string - * @param roleName string | undefined - * @returns Promise - */ - private async setEc2Client( - props: VpnConnectionProps, - invokingRegion: string, - partition: string, - roleName?: string, - ): Promise { - const roleArn = `arn:${partition}:iam::${props.owningAccountId}:role/${roleName}`; - const solutionId = process.env['SOLUTION_ID']; - const stsClient = new STSClient({ region: invokingRegion, customUserAgent: solutionId }); - - if (props.owningAccountId && props.owningRegion) { - if (!roleName) { - throw new Error(`Cross-account VPN required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await this.getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: props.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (props.owningAccountId && !props.owningRegion) { - if (!roleName) { - throw new Error(`Cross-account CGW required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await this.getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: props.owningRegion ?? invokingRegion, - customUserAgent: solutionId, - }); - } - } - - /** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ - private async getStsCredentials( - stsClient: STSClient, - roleArn: string, - ): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } - } - - /** - * Process and return an array of IVpnTunnel objects - * @param ec2Client EC2Client - * @param vpnConnectionId string - * @returns Promise - */ - private async setVpnTunnels(ec2Client: EC2Client, vpnConnectionId: string): Promise { - const tunnels: IVpnTunnel[] = []; - - console.log(`Retrieving VPN connection details for ${this.name} (${vpnConnectionId})...`); - try { - const response = await throttlingBackOff(() => - ec2Client.send(new DescribeVpnConnectionsCommand({ VpnConnectionIds: [vpnConnectionId] })), - ); - - if (!response.VpnConnections) { - throw new Error(`VPN connection details not returned from DescribeVpnConnections command`); - } - if (!response.VpnConnections[0].Options) { - throw new Error(`VPN connection options not returned from DescribeVpnConnections command`); - } - if (!response.VpnConnections[0].Options.TunnelOptions) { - throw new Error(`VPN tunnel options not returned from DescribeVpnConnections command`); - } - // - // Process tunnel options - for (const tunnelItem of response.VpnConnections[0].Options.TunnelOptions) { - tunnels.push(this.processTunnelOptions(tunnelItem)); - } - - return tunnels; - } catch (e) { - throw new Error(`Unable to process VPN connection ${this.name}: ${e}`); - } - } - - /** - * Process tunnel options for a VPN tunnel and return an IVpnTunnel - * @param tunnelOption TunnelOption - * @returns IVpnTunnel - */ - private processTunnelOptions(tunnelOption: TunnelOption): IVpnTunnel { - // - // Validate response object - if (!tunnelOption.TunnelInsideCidr) { - throw new Error(`VPN tunnel inside CIDR not returned from DescribeVpnConnections command`); - } - if (!tunnelOption.OutsideIpAddress) { - throw new Error(`VPN tunnel outside IP not returned from DescribeVpnConnections command`); - } - if (!tunnelOption.PreSharedKey) { - throw new Error(`VPN tunnel PSK not returned from DescribeVpnConnections command`); - } - // - // Set inside IP CIDR and tunnel IPs - const insideCidr = IPv4CidrRange.fromCidr(tunnelOption.TunnelInsideCidr); - const awsInsideIp = insideCidr.getFirst().nextIPNumber(); - const cgwInsideIp = awsInsideIp.nextIPNumber(); - - return { - awsInsideIp: awsInsideIp.toString(), - awsOutsideIp: tunnelOption.OutsideIpAddress, - cgwInsideIp: cgwInsideIp.toString(), - preSharedKey: tunnelOption.PreSharedKey, - tunnelInsideCidr: insideCidr.toCidrString(), - tunnelInsideNetmask: insideCidr.getPrefix().toMask().toString(), - }; - } -} - -/** - * Initialize the firewall replacements object - * @param ec2Client EC2Client - * @param serviceToken string - * @param options FirewallReplacementOptions - * @returns FirewallReplacements - */ -export async function initReplacements( - ec2Client: EC2Client, - serviceToken: string, - options: FirewallReplacementOptions, -): Promise { - const replacements = new FirewallReplacements(options); - await replacements.init(ec2Client, serviceToken); - return replacements; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/tsconfig.json deleted file mode 100644 index 4e33341..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-config-replacements/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-instance.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-instance.ts deleted file mode 100644 index c7f1376..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall-instance.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { Firewall, FirewallProps, IFirewall } from './firewall'; -import { NetworkInterfaceItemConfig } from '@aws-accelerator/config'; - -export interface IFirewallInstance extends IFirewall { - /** - * The underlying EC2 instance for the firewall - */ - readonly ec2Instance: cdk.aws_ec2.CfnInstance; - /** - * The instance ID of the firewall instance - */ - readonly instanceId: string; - /** - * VPN connections associated with this firewall instance - */ - readonly vpnConnections: { name: string; id: string }[]; -} - -interface FirewallInstanceProps extends FirewallProps { - /** - * Enable detailed monitoring for the firewall instance - */ - readonly detailedMonitoring?: boolean; - /** - * Enable termination protection for the firewall instance - */ - readonly terminationProtection?: boolean; -} - -export interface FirewallVpnProps { - /** - * The name of the VPN connection - */ - readonly name: string; - /** - * AWS BGP ASN - */ - readonly awsBgpAsn: number; - /** - * Customer gateway BGP ASN - */ - readonly cgwBgpAsn: number; - /** - * The customer gateway outside IP address - */ - readonly cgwOutsideIp: string; - /** - * The VPN connection ID - */ - readonly id: string; - /** - * The owning account ID, if different than the invoking account - */ - readonly owningAccountId?: string; - /** - * The owning region, if different from the invoking region - */ - readonly owningRegion?: string; -} - -export class FirewallInstance extends Firewall implements IFirewallInstance { - public readonly ec2Instance: cdk.aws_ec2.CfnInstance; - public readonly instanceId: string; - public readonly vpnConnections: FirewallVpnProps[] = []; - constructor(scope: Construct, id: string, props: FirewallInstanceProps) { - super(scope, id, props); - - // Create instance - this.ec2Instance = new cdk.aws_ec2.CfnInstance(this, 'Resource', { - launchTemplate: { - launchTemplateId: this.launchTemplate.launchTemplateId, - version: this.launchTemplate.version, - }, - disableApiTermination: props.terminationProtection, - monitoring: props.detailedMonitoring, - tags: props.tags, - }); - cdk.Tags.of(this.ec2Instance).add('Name', this.name); - - this.instanceId = this.ec2Instance.ref; - } - - /** - * Public accessor method for retrieving the public IP address of a firewall interface - * @param deviceIndex - * @returns - */ - public getPublicIpAddress(deviceIndex: number): string { - const ipAddress = this.publicIpAddresses.get(deviceIndex); - if (!ipAddress) { - throw new Error(`No public IP address for firewall instance ${this.name} device index ${deviceIndex}`); - } - return ipAddress; - } - - /** - * Public accessor method for retrieving the network interfaces of a firewall interface - * @param deviceIndex - * @returns - */ - public getNetworkInterface(deviceIndex: number): NetworkInterfaceItemConfig { - const networkInterface = this.networkInterfaces.find(eni => eni.deviceIndex! === deviceIndex); - if (!networkInterface) { - throw new Error(`No network interface for firewall instance ${this.name} at device index ${deviceIndex}`); - } - return networkInterface; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall.ts deleted file mode 100644 index 134f8c3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/firewall.ts +++ /dev/null @@ -1,243 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { LaunchTemplateConfig, NetworkInterfaceItemConfig } from '@aws-accelerator/config'; -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import fs from 'fs'; -import path from 'path'; -import { LaunchTemplate } from './create-launch-template'; - -export interface IFirewall extends cdk.IResource { - /** - * The friendly name of the firewall instance - */ - readonly name: string; -} - -export interface FirewallProps { - /** - * The friendly name of the firewall instance - */ - readonly name: string; - /** - * The name of the firewall configuration bucket - */ - readonly configBucketName: string; - /** - * The configuration directory path - */ - readonly configDir: string; - /** - * The launch template for the firewall instance - */ - readonly launchTemplate: LaunchTemplateConfig; - /** - * The name of the VPC to deploy the instance to - */ - readonly vpc: string; - /** - * An array of CloudFormation tags - */ - readonly tags?: cdk.CfnTag[]; -} - -export class Firewall extends cdk.Resource implements IFirewall { - public readonly name: string; - protected publicIpAddresses: Map = new Map(); - protected launchTemplate: LaunchTemplate; - protected networkInterfaces: NetworkInterfaceItemConfig[]; - protected props: FirewallProps; - private eipAssociations: cdk.aws_ec2.CfnEIPAssociation[] = []; - - constructor(scope: Construct, id: string, props: FirewallProps) { - super(scope, id); - this.name = props.name; - this.props = props; - - // Create interfaces with elastic IPs, if needed - this.networkInterfaces = this.setNetworkInterfaceProps(); - // Create launch template - this.launchTemplate = this.createLaunchTemplate(); - // Create EIP interface dependencies - this.eipAssociations.forEach(association => this.launchTemplate.node.addDependency(association)); - } - - /** - * Create a launch template for the firewall - * @returns - */ - private createLaunchTemplate(): LaunchTemplate { - return new LaunchTemplate(this, 'LaunchTemplate', { - appName: this.props.name, - name: this.props.launchTemplate.name, - vpc: this.props.vpc, - blockDeviceMappings: this.props.launchTemplate.blockDeviceMappings, - userData: this.processUserData(this.props.launchTemplate.userData), - securityGroups: this.props.launchTemplate.securityGroups, - networkInterfaces: this.networkInterfaces, - instanceType: this.props.launchTemplate.instanceType, - keyPair: this.props.launchTemplate.keyPair, - iamInstanceProfile: this.props.launchTemplate.iamInstanceProfile, - imageId: this.props.launchTemplate.imageId, - enforceImdsv2: this.props.launchTemplate.enforceImdsv2, - }); - } - - /** - * Set network interface properties for the launch template - * @returns - */ - private setNetworkInterfaceProps(): NetworkInterfaceItemConfig[] { - const networkInterfaces: NetworkInterfaceItemConfig[] = []; - let deviceIndex = 0; - for (const networkInterface of this.props.launchTemplate.networkInterfaces ?? []) { - // Create interface with elastic IP - if (networkInterface.associateElasticIp) { - networkInterfaces.push( - this.createEipInterface( - networkInterface, - networkInterface.deviceIndex !== undefined ? networkInterface.deviceIndex : deviceIndex, - ), - ); - deviceIndex += 1; - continue; - } - - // Create interface with source/dest check disabled - if (!networkInterface.associateElasticIp && networkInterface.sourceDestCheck === false) { - networkInterfaces.push( - this.createRouterInterface( - networkInterface, - networkInterface.deviceIndex !== undefined ? networkInterface.deviceIndex : deviceIndex, - ), - ); - deviceIndex += 1; - continue; - } - - networkInterfaces.push({ - associateCarrierIpAddress: networkInterface.associateCarrierIpAddress, - associateElasticIp: undefined, - associatePublicIpAddress: networkInterface.associatePublicIpAddress, - deleteOnTermination: networkInterface.deleteOnTermination, - description: networkInterface.description, - deviceIndex: networkInterface.deviceIndex, - groups: networkInterface.groups, - interfaceType: networkInterface.interfaceType, - networkCardIndex: networkInterface.networkCardIndex, - networkInterfaceId: networkInterface.networkInterfaceId, - privateIpAddress: networkInterface.privateIpAddress, - privateIpAddresses: networkInterface.privateIpAddresses, - secondaryPrivateIpAddressCount: networkInterface.secondaryPrivateIpAddressCount, - sourceDestCheck: undefined, - subnetId: networkInterface.subnetId, - }); - deviceIndex += 1; - } - return networkInterfaces; - } - - /** - * Create and associate an EIP with a network interface - * @param networkInterface - * @param deviceIndex - * @returns - */ - private createEipInterface( - networkInterface: NetworkInterfaceItemConfig, - deviceIndex: number, - ): NetworkInterfaceItemConfig { - // Create EIP - const eip = new cdk.aws_ec2.CfnEIP(this, `ElasticIp${deviceIndex}`, { - domain: 'vpc', - }); - - // Add public IP to map - this.publicIpAddresses.set(deviceIndex, eip.ref); - - // Create interface - const eipInterface = new cdk.aws_ec2.CfnNetworkInterface(this, `NetworkInterface${deviceIndex}`, { - description: networkInterface.description, - groupSet: networkInterface.groups, - interfaceType: networkInterface.interfaceType, - privateIpAddress: networkInterface.privateIpAddress, - privateIpAddresses: networkInterface.privateIpAddresses - ? (networkInterface.privateIpAddresses as cdk.aws_ec2.CfnNetworkInterface.PrivateIpAddressSpecificationProperty[]) - : undefined, - secondaryPrivateIpAddressCount: networkInterface.secondaryPrivateIpAddressCount, - sourceDestCheck: networkInterface.sourceDestCheck, - subnetId: networkInterface.subnetId!, - }); - - // Associate EIP - this.eipAssociations.push( - new cdk.aws_ec2.CfnEIPAssociation(this, `EipAssociation${deviceIndex}`, { - allocationId: eip.attrAllocationId, - networkInterfaceId: eipInterface.ref, - }), - ); - - return { - deviceIndex: deviceIndex, - networkInterfaceId: eipInterface.ref, - } as NetworkInterfaceItemConfig; - } - - /** - * Create a network interface with source/dest checks disabled - * @param networkInterface - * @param deviceIndex - * @returns - */ - private createRouterInterface( - networkInterface: NetworkInterfaceItemConfig, - deviceIndex: number, - ): NetworkInterfaceItemConfig { - // Create interface - const routerInterface = new cdk.aws_ec2.CfnNetworkInterface(this, `NetworkInterface${deviceIndex}`, { - description: networkInterface.description, - groupSet: networkInterface.groups, - interfaceType: networkInterface.interfaceType, - privateIpAddress: networkInterface.privateIpAddress, - privateIpAddresses: networkInterface.privateIpAddresses - ? (networkInterface.privateIpAddresses as cdk.aws_ec2.CfnNetworkInterface.PrivateIpAddressSpecificationProperty[]) - : undefined, - secondaryPrivateIpAddressCount: networkInterface.secondaryPrivateIpAddressCount, - sourceDestCheck: networkInterface.sourceDestCheck, - subnetId: networkInterface.subnetId!, - }); - - return { - deviceIndex: deviceIndex, - networkInterfaceId: routerInterface.ref, - } as NetworkInterfaceItemConfig; - } - - /** - * Returns a base-64 encoded userdata string, replacing the - * firewall config bucket name, if applicable - * @param userdataPath string | undefined - * @returns string | undefined - */ - private processUserData(userdataPath?: string): string | undefined { - if (!userdataPath) { - return; - } - // Replace config bucket name variable - const varRegex = new RegExp('\\$\\{ACCEL_LOOKUP::S3:BUCKET:firewall-config\\}', 'gi'); - const userdata = fs.readFileSync(path.join(this.props.configDir, userdataPath), 'utf-8'); - - return cdk.Fn.base64(userdata.replace(varRegex, this.props.configBucketName)); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts deleted file mode 100644 index a6b8efd..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/index.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; - -/** - * get-ipam-subnet-cidr - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent) { - console.log(event); - const region = event.ResourceProperties['region']; - const ssmSubnetIdPath = event.ResourceProperties['ssmSubnetIdPath']; - //const roleArn = event.ResourceProperties['roleArn']; - const roleArn: string | undefined = event.ResourceProperties['roleArn']; - - let ec2: AWS.EC2 | undefined; - let ssm: AWS.SSM | undefined; - const stsClient = new AWS.STS({ region }); - const assumeRoleCredential = await throttlingBackOff(() => - stsClient - .assumeRole({ - RoleArn: event.ResourceProperties['roleArn'], - RoleSessionName: 'AcceleratorAssumeRoleSession', - }) - .promise(), - ); - if (roleArn) { - ec2 = new AWS.EC2({ - region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expireTime: assumeRoleCredential.Credentials!.Expiration, - }, - }); - - ssm = new AWS.SSM({ - region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expireTime: assumeRoleCredential.Credentials!.Expiration, - }, - }); - } else { - ec2 = new AWS.EC2({ region }); - ssm = new AWS.SSM({ region }); - } - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log(`Starting - Create/Update event for IPAM Nacls ${region}`); - // Pull VPC ID from SSM return ID - const subnetId = await getVpcId(ssm!, ssmSubnetIdPath); - if (!subnetId) { - throw new Error('Subnet ID not found.'); - } - // Describe subnet with VPC ID as a filter - const subnetCidr = await getCidr(ec2!, subnetId); - if (!subnetCidr) { - throw new Error(`Not able to pull the Cidr from parameter store ${ssmSubnetIdPath}!`); - } - - return { - PhysicalResourceId: subnetCidr, - Data: { ipv4CidrBlock: subnetCidr }, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -async function getVpcId(ssm: AWS.SSM, subnet: string): Promise { - const response = await throttlingBackOff(() => ssm.getParameter({ Name: subnet }).promise()); - return response.Parameter?.Value; -} - -async function getCidr(ec2: AWS.EC2, subnet: string): Promise { - const response = await throttlingBackOff(() => - ec2 - .describeSubnets({ - Filters: [ - { - Name: 'subnet-id', - Values: [subnet], - }, - ], - }) - .promise(), - ); - return response.Subnets?.pop()?.CidrBlock; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/package.json deleted file mode 100644 index 73e4271..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-ipam-subnet-cidr/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-get-ipam-subnet-cidr", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/index.ts deleted file mode 100644 index 320143b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(event); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('start GetSubnetId'); - - const vpcId = event.ResourceProperties['vpcId']; - const subnetName = event.ResourceProperties['subnetName']; - const solutionId = process.env['SOLUTION_ID']; - const ec2Client = new AWS.EC2({ customUserAgent: solutionId }); - - let nextToken: string | undefined = undefined; - - do { - const page = await throttlingBackOff(() => - ec2Client - .describeSubnets({ - Filters: [ - { Name: 'vpc-id', Values: [vpcId] }, - { Name: 'tag:Name', Values: [subnetName] }, - ], - NextToken: nextToken, - }) - .promise(), - ); - - for (const subnet of page.Subnets ?? []) { - console.log(`Subnet ${subnetName} id is : ${subnet.SubnetId}`); - return { - PhysicalResourceId: subnet.SubnetId, - Status: 'SUCCESS', - }; - } - - nextToken = page.NextToken; - } while (nextToken); - - throw new Error( - `Subnet ${subnetName} not found, if this is shared subnet, make sure vpc has tag with key Name and value as ${subnetName} `, - ); - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/package.json deleted file mode 100644 index 677f667..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-get-subnet-id", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-subnet-id/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/index.ts deleted file mode 100644 index 9836ed5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/index.ts +++ /dev/null @@ -1,344 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - DescribeTransitGatewayAttachmentsCommand, - DescribeVpnConnectionsCommand, - EC2Client, - TransitGatewayAttachment, -} from '@aws-sdk/client-ec2'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -interface TgwAttachmentOptions { - /** - * The transit gateway attachment type - */ - readonly attachmentType: string; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * The name of the TGW attachment - */ - readonly name: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * The owning account ID if looking up a cross-account VPC attachment - */ - readonly vpcOwningAccountId: string; - /** - * The transit gateway ID - */ - readonly transitGatewayId: string; - /** - * The role ARN to assume if looking up a cross-account VPC attachment - */ - readonly vpcLookupRoleArn?: string; - /** - * Cross-account lookup options - * - * @remarks - * These options should only be used for cross-account VPN attachment - * lookups. Currently the only use case is for dynamic EC2 firewall - * VPN connections - */ - readonly crossAccountVpnOptions?: { - /** - * Owning account ID of the VPN attachment - */ - readonly owningAccountId?: string; - /** - * Owning region of the VPN attachment - */ - readonly owningRegion?: string; - /** - * Role name to assume - */ - readonly roleName?: string; - }; -} - -/** - * get-transit-gateway-attachment - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const options = setOptions(event.ResourceProperties, event.ServiceToken); - const ec2Client = await setEc2Client(options, process.env['SOLUTION_ID']); - - switch (event.RequestType) { - case 'Create': - case 'Update': - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client.send( - new DescribeTransitGatewayAttachmentsCommand({ - Filters: [{ Name: 'resource-type', Values: [options.attachmentType] }], - NextToken: nextToken, - }), - ), - ); - for (const attachment of page.TransitGatewayAttachments ?? []) { - if ( - attachment.TransitGatewayId === options.transitGatewayId && - attachment.State === 'available' && - (await validateAttachment( - attachment, - options.name, - options.attachmentType, - options.transitGatewayId, - ec2Client, - )) - ) { - return { - PhysicalResourceId: attachment.TransitGatewayAttachmentId, - Status: 'SUCCESS', - }; - } - } - nextToken = page.NextToken; - } while (nextToken); - - throw new Error(`Attachment ${options.name} for ${options.transitGatewayId} not found`); - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Validates whether or not the attachment ID is valid based on the lookup request metadata - * @param attachment TransitGatewayAttachment - * @param name string - * @param attachmentType string - * @param tgwId string - * @param ec2Client EC2Client - * @returns Promise - */ -async function validateAttachment( - attachment: TransitGatewayAttachment, - name: string, - attachmentType: string, - tgwId: string, - ec2Client: EC2Client, -): Promise { - switch (attachmentType) { - case 'vpc': - case 'peering': - const nameTag = attachment.Tags?.find(t => t.Key === 'Name'); - if (nameTag && nameTag.Value === name) { - return true; - } - return false; - - case 'vpn': - try { - const vpnResponse = await throttlingBackOff(() => - ec2Client.send( - new DescribeVpnConnectionsCommand({ - Filters: [ - { Name: 'tag:Name', Values: [name] }, - { Name: 'transit-gateway-id', Values: [tgwId] }, - { Name: 'state', Values: ['available'] }, - ], - }), - ), - ); - - if (vpnResponse.VpnConnections) { - if (vpnResponse.VpnConnections.length > 1) { - throw new Error(`Multiple VPN connections found with name ${name} connected to TGW ${tgwId}`); - } - if (vpnResponse.VpnConnections.length === 0) { - throw new Error(`No VPN connections found with name ${name} connected to TGW ${tgwId}`); - } - if (vpnResponse.VpnConnections[0].VpnConnectionId === attachment.ResourceId) { - return true; - } - } - return false; - } catch (e) { - throw new Error(`Unable to find VPN attachment ${name}: ${e}`); - } - } - return false; -} - -/** - * Set TGW attachment lookup options based on event - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns TgwAttachmentOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }, serviceToken: string): TgwAttachmentOptions { - return { - attachmentType: resourceProperties['type'], - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - name: resourceProperties['name'], - partition: serviceToken.split(':')[1], - transitGatewayId: resourceProperties['transitGatewayId'], - vpcOwningAccountId: resourceProperties['owningAccountId'], - vpcLookupRoleArn: resourceProperties['roleArn'], - crossAccountVpnOptions: resourceProperties['crossAccountVpnOptions'], - }; -} - -/** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param options TgwAttachmentOptions - * @param solutionId string | undefined - * @returns Promise - */ -async function setEc2Client(options: TgwAttachmentOptions, solutionId?: string): Promise { - const roleArn = options.vpcLookupRoleArn - ? options.vpcLookupRoleArn - : `arn:${options.partition}:iam::${options.crossAccountVpnOptions?.owningAccountId}:role/${options.crossAccountVpnOptions?.roleName}`; - const stsClient = new STSClient({ region: options.invokingRegion, customUserAgent: solutionId }); - - if (options.vpcLookupRoleArn) { - return await setVpcLookupEc2Client(stsClient, roleArn, solutionId); - } else if (options.crossAccountVpnOptions) { - return await setCrossAccountVpnEc2Client(stsClient, roleArn, options, solutionId); - } else { - return new EC2Client({ customUserAgent: solutionId }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Returns a local or cross-account EC2 client for VPC attachment lookups based on input parameters - * @param stsClient STSClient - * @param roleArn string - * @param solutionId string | undefined - * @returns Promise - */ -async function setVpcLookupEc2Client(stsClient: STSClient, roleArn: string, solutionId?: string): Promise { - const credentials = await getStsCredentials(stsClient, roleArn); - - return new EC2Client({ - customUserAgent: solutionId, - credentials, - }); -} - -/** - * Returns a local or cross-account/cross-region EC2 client for cross-account VPN attachment lookups based on input parameters - * @param stsClient STSClient - * @param roleArn string - * @param options TgwAttachmentOptions - * @param solutionId string | undefined - * @returns Promise - */ -async function setCrossAccountVpnEc2Client( - stsClient: STSClient, - roleArn: string, - options: TgwAttachmentOptions, - solutionId?: string, -): Promise { - if (options.crossAccountVpnOptions?.owningAccountId && options.crossAccountVpnOptions?.owningRegion) { - if (!options.crossAccountVpnOptions?.roleName) { - throw new Error(`Cross-account VPN attachment lookup required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.crossAccountVpnOptions.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (options.crossAccountVpnOptions?.owningAccountId && !options.crossAccountVpnOptions?.owningRegion) { - if (!options.crossAccountVpnOptions?.roleName) { - throw new Error(`Cross-account VPN attachment lookup required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: options.crossAccountVpnOptions?.owningRegion ?? options.invokingRegion, - customUserAgent: solutionId, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/package.json deleted file mode 100644 index 14ac475..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-get-transit-gateway-attachment", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-transit-gateway-attachment/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/index.ts deleted file mode 100644 index 330a6c7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/index.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(event); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('start GetVpcId'); - - const vpcName = event.ResourceProperties['vpcName']; - const solutionId = process.env['SOLUTION_ID']; - const ec2Client = new AWS.EC2({ customUserAgent: solutionId }); - - let nextToken: string | undefined = undefined; - - do { - const page = await throttlingBackOff(() => - ec2Client - .describeVpcs({ - Filters: [{ Name: 'tag:Name', Values: [vpcName] }], - NextToken: nextToken, - }) - .promise(), - ); - - for (const vpc of page.Vpcs ?? []) { - return { - PhysicalResourceId: vpc.VpcId, - Status: 'SUCCESS', - }; - } - - nextToken = page.NextToken; - } while (nextToken); - - throw new Error( - `Vpc ${vpcName} not found, if this is shared vpc, make sure vpc has tag with key Name and value as ${vpcName} `, - ); - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/package.json deleted file mode 100644 index 4559de8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-get-vpc-id", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/get-vpc-id/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-organization-admin-account.ts deleted file mode 100644 index 805edb5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-organization-admin-account.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IEnableIpamOrgAdminAccount extends cdk.IResource { - /** - * The account ID of the delegated administrator. - */ - readonly accountId: string; -} - -export interface EnableIpamOrgAdminAccountProps { - /** - * The account ID to delegate admin privileges. - */ - readonly accountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class IpamOrganizationAdminAccount extends cdk.Resource implements IEnableIpamOrgAdminAccount { - public readonly accountId: string; - - constructor(scope: Construct, id: string, props: EnableIpamOrgAdminAccountProps) { - super(scope, id); - - this.accountId = props.accountId; - - const ENABLE_IPAM_ADMIN = 'Custom::EnableIpamOrganizationAdminAccount'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, ENABLE_IPAM_ADMIN, { - codeDirectory: path.join(__dirname, 'enable-ipam-organization-admin/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ec2:DisableIpamOrganizationAdminAccount', 'ec2:EnableIpamOrganizationAdminAccount'], - Resource: '*', - }, - { - Effect: 'Allow', - Action: [ - 'organizations:DisableAwsServiceAccess', - 'organizations:EnableAwsServiceAccess', - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:RegisterDelegatedAdministrator', - ], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:ServicePrincipal': ['ipam.amazonaws.com'], - }, - }, - }, - { - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole', 'iam:DeleteServiceLinkedRole'], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'iam:AWSServiceName': ['ipam.amazonaws.com'], - }, - }, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: ENABLE_IPAM_ADMIN, - serviceToken: provider.serviceToken, - properties: { - accountId: this.accountId, - region: cdk.Stack.of(this).region, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-pool.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-pool.ts deleted file mode 100644 index e051e19..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-pool.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IIpamPool extends cdk.IResource { - /** - * The ARN of the IPAM pool. - * - * @attribute - */ - readonly ipamPoolArn: string; - - /** - * The identifier of the IPAM pool. - * - * @attribute - */ - readonly ipamPoolId: string; - - /** - * The name of the IPAM pool. - */ - readonly ipamPoolName: string; -} - -type IpVersionEnum = 'ipv4' | 'ipv6'; - -export interface IpamPoolProps { - /** - * The address family of the pool. - */ - readonly addressFamily: IpVersionEnum; - - /** - * The ID of the scope in which you would like to create the IPAM pool. - */ - readonly ipamScopeId: string; - - /** - * The name of the IPAM pool. - */ - readonly name: string; - - /** - * The default netmask length for allocations added to this pool. - */ - readonly allocationDefaultNetmaskLength?: number; - - /** - * The maximum netmask length possible for CIDR allocations in this IPAM pool to be compliant. - */ - readonly allocationMaxNetmaskLength?: number; - - /** - * The minimum netmask length required for CIDR allocations in this IPAM pool to be compliant. - */ - readonly allocationMinNetmaskLength?: number; - - /** - * Tags that are required for resources that use CIDRs from this IPAM pool. - */ - readonly allocationResourceTags?: cdk.CfnTag[]; - - /** - * If selected, IPAM will continuously look for resources within the CIDR range of this pool and automatically - * import them as allocations into your IPAM. - */ - readonly autoImport?: boolean; - - /** - * The description of the IPAM pool. - */ - readonly description?: string; - - /** - * The locale of the IPAM pool. - */ - readonly locale?: string; - - /** - * Information about the CIDRs provisioned to an IPAM pool. - */ - readonly provisionedCidrs?: string[]; - - /** - * Determines if a pool is publicly advertisable. - */ - readonly publiclyAdvertisable?: boolean; - - /** - * The ID of the source IPAM pool. - */ - readonly sourceIpamPoolId?: string; - - /** - * The key/value combination of a tag assigned to the resource. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class IpamPool extends cdk.Resource implements IIpamPool { - public readonly ipamPoolArn: string; - public readonly ipamPoolId: string; - public readonly ipamPoolName: string; - private cidrs?: cdk.aws_ec2.CfnIPAMPool.ProvisionedCidrProperty[]; - - constructor(scope: Construct, id: string, props: IpamPoolProps) { - super(scope, id); - - this.ipamPoolName = props.name; - - // Map provisioned cidrs values - this.cidrs = props.provisionedCidrs?.map(prefix => { - return { cidr: prefix }; - }); - - const resource = new cdk.aws_ec2.CfnIPAMPool(this, 'Resource', { - addressFamily: props.addressFamily, - ipamScopeId: props.ipamScopeId, - allocationDefaultNetmaskLength: props.allocationDefaultNetmaskLength, - allocationMaxNetmaskLength: props.allocationMaxNetmaskLength, - allocationMinNetmaskLength: props.allocationMinNetmaskLength, - allocationResourceTags: props.allocationResourceTags, - autoImport: props.autoImport, - description: props.description, - locale: props.locale, - provisionedCidrs: this.cidrs, - publiclyAdvertisable: props.publiclyAdvertisable, - sourceIpamPoolId: props.sourceIpamPoolId, - tags: props.tags, - }); - - cdk.Tags.of(this).add('Name', this.ipamPoolName); - - this.ipamPoolArn = resource.attrArn; - this.ipamPoolId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-scope.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-scope.ts deleted file mode 100644 index a16c4d9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-scope.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IIpamScope extends cdk.IResource { - /** - * The ARN of the IPAM scope. - * - * @attribute - */ - readonly ipamScopeArn: string; - - /** - * The identifier of the IPAM scope. - * - * @attribute - */ - readonly ipamScopeId: string; - - /** - * The name of the IPAM scope. - */ - readonly ipamScopeName: string; -} - -export interface IpamScopeProps { - /** - * The ID of the IPAM for which you're creating this scope. - */ - readonly ipamId: string; - - /** - * The name of the IPAM scope. - */ - readonly name: string; - - /** - * The description of the IPAM scope. - */ - readonly description?: string; - - /** - * Tags for the IPAM scope. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class IpamScope extends cdk.Resource implements IIpamScope { - public readonly ipamScopeArn: string; - public readonly ipamScopeId: string; - public readonly ipamScopeName: string; - - constructor(scope: Construct, id: string, props: IpamScopeProps) { - super(scope, id); - - this.ipamScopeName = props.name; - - const resource = new cdk.aws_ec2.CfnIPAMScope(this, 'Resource', { - ipamId: props.ipamId, - description: props.description, - tags: props.tags, - }); - - cdk.Tags.of(this).add('Name', this.ipamScopeName); - - this.ipamScopeArn = resource.attrArn; - this.ipamScopeId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet.ts deleted file mode 100644 index e9ea9c8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet.ts +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; - -import { IpamAllocationConfig } from '@aws-accelerator/config'; - -export interface IIpamSubnet extends cdk.IResource { - /** - * The IPv4 CIDR assigned to the subnet - * - * @attribute - */ - readonly ipv4CidrBlock: string; - /** - * The resource ID of the subnet - * - * @attribute - */ - readonly subnetId: string; -} - -export interface IpamSubnetProps { - /** - * The friendly name of the subnet - */ - readonly name: string; - /** - * The availability zone (AZ) of the subnet - */ - readonly availabilityZone?: string; - - /** - * The Physical Availability Zone ID the subnet is located in - * - * @attribute - */ - readonly availabilityZoneId?: string; - - /** - * The base IPAM pool CIDR range the subnet is assigned to - */ - readonly basePool: string[]; - /** - * The IPAM allocation configuration - */ - readonly ipamAllocation: IpamAllocationConfig; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The VPC ID the subnet will be created in - */ - readonly vpcId: string; - /** - * Auto-create public IP addresses on EC2 instance launch - */ - readonly mapPublicIpOnLaunch?: boolean; - /** - * An array of tags for the subnet - */ - readonly tags?: cdk.CfnTag[]; - /** - * The outpost arn for the subnet - */ - readonly outpostArn?: string; -} - -export interface IpamSubnetLookupOptions { - readonly owningAccountId: string; - readonly ssmSubnetIdPath: string; - readonly roleName?: string; - readonly region?: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class IpamSubnet extends cdk.Resource implements IIpamSubnet { - public static fromLookup(scope: Construct, id: string, options: IpamSubnetLookupOptions): IIpamSubnet { - class Import extends cdk.Resource implements IIpamSubnet { - public readonly subnetId: string = options.ssmSubnetIdPath; - public readonly ipv4CidrBlock: string; - - constructor(scope: Construct, id: string) { - super(scope, id); - - const GET_IPAM_SUBNET_CIDR = 'Custom::GetIpamSubnetCidr'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, GET_IPAM_SUBNET_CIDR, { - codeDirectory: path.join(__dirname, 'get-ipam-subnet-cidr/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['ec2:DescribeSubnets', 'ssm:GetParameter'], - Resource: '*', - }, - ], - }); - - // Construct role arn if this is a cross-account lookup - let roleArn: string | undefined = undefined; - if (options.roleName) { - roleArn = cdk.Stack.of(this).formatArn({ - service: 'iam', - region: '', - account: options.owningAccountId, - resource: 'role', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: options.roleName, - }); - } - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: GET_IPAM_SUBNET_CIDR, - serviceToken: provider.serviceToken, - properties: { - ssmSubnetIdPath: options.ssmSubnetIdPath, - region: options.region, - roleArn, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: options.logRetentionInDays, - encryptionKey: options.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.ipv4CidrBlock = resource.getAttString('ipv4CidrBlock'); - } - } - return new Import(scope, id); - } - - public readonly ipv4CidrBlock: string; - public readonly subnetId: string; - private tags: { Key: string; Value: string }[] = []; - - constructor(scope: Construct, id: string, props: IpamSubnetProps) { - super(scope, id); - - const IPAM_SUBNET = 'Custom::IpamSubnet'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, IPAM_SUBNET, { - codeDirectory: path.join(__dirname, 'ipam-subnet/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ec2:CreateTags', 'ec2:DeleteSubnet', 'ec2:DeleteTags', 'ec2:ModifySubnetAttribute'], - Resource: `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:subnet/*`, - }, - { - Effect: 'Allow', - Action: ['ec2:CreateSubnet'], - Resource: [ - `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:subnet/*`, - `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:vpc/*`, - ], - }, - { - Effect: 'Allow', - Action: ['ec2:DescribeVpcs', 'ec2:DescribeSubnets'], - Resource: '*', - }, - ], - }); - - // Convert tag object to expected keys - if (props.tags) { - this.tags = props.tags.map(tag => { - return { - Key: tag.key, - Value: tag.value, - }; - }); - } - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: IPAM_SUBNET, - serviceToken: provider.serviceToken, - properties: { - name: props.name, - availabilityZone: props.availabilityZone, - availabilityZoneId: props.availabilityZoneId, - basePool: props.basePool, - ipamAllocation: props.ipamAllocation, - vpcId: props.vpcId, - mapPublicIpOnLaunch: props.mapPublicIpOnLaunch, - tags: this.tags ?? [], - outpostArn: props.outpostArn, - }, - }); - - this.ipv4CidrBlock = resource.getAttString('ipv4CidrBlock'); - this.subnetId = resource.ref; - - /** - * Single pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/index.ts deleted file mode 100644 index de8110b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/index.ts +++ /dev/null @@ -1,275 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; - -import { - CreateSubnetCommand, - CreateTagsCommand, - DeleteSubnetCommand, - DeleteTagsCommand, - EC2Client, - ModifySubnetAttributeCommand, - Tag, -} from '@aws-sdk/client-ec2'; -import { Vpc, VpcSubnet, vpcInit } from './vpc'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - Data: { - ipv4CidrBlock: string | undefined; - }; - } - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - // Create interfaces - interface IpamAllocation { - readonly ipamPoolName: string; - readonly netmaskLength: number; - } - // - // Set properties - const az: string | undefined = event.ResourceProperties['availabilityZone']; - const azId: string | undefined = event.ResourceProperties['availabilityZoneId']; - const basePool: string[] = event.ResourceProperties['basePool']; - const ipamAllocation: IpamAllocation = event.ResourceProperties['ipamAllocation']; - let mapPublicIpOnLaunch: boolean | undefined = event.ResourceProperties['mapPublicIpOnLaunch'] ?? false; - const subnetName: string = event.ResourceProperties['name']; - const tags: Tag[] = event.ResourceProperties['tags'] ?? []; - const vpcId: string = event.ResourceProperties['vpcId']; - const outpostArn: string = event.ResourceProperties['outpostArn'] ?? undefined; - const solutionId = process.env['SOLUTION_ID']; - let vpc: Vpc; - // - // Initialize EC2 client - const ec2Client = new EC2Client({ customUserAgent: solutionId }); - // - // Set netmask length as BigInt - const netmaskLength = BigInt(ipamAllocation.netmaskLength); - // - // Handle case where boolean is passed as string - if (mapPublicIpOnLaunch) { - mapPublicIpOnLaunch = returnBoolean(mapPublicIpOnLaunch.toString()); - } - // - // Handle the custom resource workflow - switch (event.RequestType) { - case 'Create': - // - // Initialize the VPC - vpc = await vpcInit({ basePool, vpcId }, ec2Client); - // - // Get the subnet CIDR - const subnetCidr = vpc.allocateCidr(netmaskLength); - // - // Create the subnet - const subnetId = await createSubnet(vpc, ec2Client, { - subnetName, - tags, - az, - azId, - mapPublicIpOnLaunch, - outpostArn, - subnetCidr, - }); - - return { - PhysicalResourceId: subnetId, - Status: 'SUCCESS', - Data: { - ipv4CidrBlock: subnetCidr, - }, - }; - - case 'Update': - // - // Initialize the VPC - vpc = await vpcInit({ basePool, vpcId }, ec2Client); - // - // Get existing subnet from subnet array - const subnet = vpc.getSubnetByName(subnetName); - // - // Update the subnet - await updateSubnet( - subnet, - ec2Client, - netmaskLength, - tags, - event.OldResourceProperties['tags'], - mapPublicIpOnLaunch, - ); - - return { - PhysicalResourceId: subnet.subnetId, - Status: 'SUCCESS', - Data: { - ipv4CidrBlock: subnet.allocatedCidr.toCidrString(), - }, - }; - - case 'Delete': - console.log(`Delete subnet ${subnetName} (${event.PhysicalResourceId})`); - try { - await throttlingBackOff(() => ec2Client.send(new DeleteSubnetCommand({ SubnetId: event.PhysicalResourceId }))); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } catch (e) { - throw new Error(`Error while deleting subnet: ${e}`); - } - } -} - -/** - * Create the subnet - * @param vpc Vpc - * @param ec2Client EC2Client - * @param props - * @returns Promise - */ -async function createSubnet( - vpc: Vpc, - ec2Client: EC2Client, - props: { - subnetName: string; - tags: Tag[]; - az?: string; - azId?: string; - mapPublicIpOnLaunch?: boolean; - outpostArn?: string; - subnetCidr?: string; - }, -): Promise { - // - // Set name tag - props.tags.push({ Key: 'Name', Value: props.subnetName }); - // - // Create the subnet - console.log(`Create subnet ${props.subnetName} with CIDR ${props.subnetCidr} in VPC ${vpc.name} (${vpc.vpcId})`); - try { - const response = await throttlingBackOff(() => - ec2Client.send( - new CreateSubnetCommand({ - TagSpecifications: [{ ResourceType: 'subnet', Tags: props.tags }], - AvailabilityZone: props.az, - AvailabilityZoneId: props.azId, - CidrBlock: props.subnetCidr, - VpcId: vpc.vpcId, - OutpostArn: props.outpostArn, - }), - ), - ); - const subnetId = response.Subnet?.SubnetId; - - if (!subnetId) { - throw new Error(`Unable to retrieve subnet ID for newly-created subnet`); - } - - if (props.mapPublicIpOnLaunch) { - await throttlingBackOff(() => - ec2Client.send(new ModifySubnetAttributeCommand({ MapPublicIpOnLaunch: { Value: true }, SubnetId: subnetId })), - ); - } - return subnetId; - } catch (e) { - throw new Error(`Error while creating subnet: ${e}`); - } -} - -/** - * Update subnet tags and attributes - * @param subnet VpcSubnet - * @param ec2Client EC2Client - * @param netmaskLength bigint - * @param tags Tag[] - * @param mapPublicIpOnLaunch boolean - */ -async function updateSubnet( - subnet: VpcSubnet, - ec2Client: EC2Client, - netmaskLength: bigint, - newTags: Tag[], - oldTags: Tag[], - mapPublicIpOnLaunch?: boolean, -): Promise { - try { - // - // Throw error if CIDR prefix value has changed - if (subnet.allocatedCidr.cidrPrefix.getValue() !== netmaskLength) { - throw new Error(`Cannot allocate new CIDR for existing subnet. Please delete and recreate the subnet instead.`); - } - // - // Update subnet tags as needed - await updateTags(subnet.subnetId, ec2Client, newTags, oldTags); - // - // Update subnet attributes as needed - if (subnet.mapPublicIpOnLaunch !== mapPublicIpOnLaunch) { - const value = mapPublicIpOnLaunch ?? false; - - console.log(`Update MapPublicIpOnLaunch property of subnet ${subnet.name} (${subnet.subnetId}) to ${value}`); - await throttlingBackOff(() => - ec2Client.send( - new ModifySubnetAttributeCommand({ MapPublicIpOnLaunch: { Value: value }, SubnetId: subnet.subnetId }), - ), - ); - } - } catch (e) { - throw new Error(`Error while updating subnet ${subnet.name} (${subnet.subnetId}): ${e}`); - } -} - -/** - * Removes and/or modifies subnet tags as needed - * @param subnetId string - * @param ec2Client EC2Client - * @param newTags Tag[] - * @param oldTags Tag[] - */ -async function updateTags(subnetId: string, ec2Client: EC2Client, newTags: Tag[], oldTags: Tag[]) { - const newTagKeys = newTags.map(newTag => newTag.Key); - const removeTags = oldTags.filter(oldTag => !newTagKeys.includes(oldTag.Key)); - - try { - if (removeTags.length > 0) { - console.log(`Removing tag keys [${removeTags.map(tag => tag.Key)}] from subnet ${subnetId}...`); - await throttlingBackOff(() => ec2Client.send(new DeleteTagsCommand({ Resources: [subnetId], Tags: removeTags }))); - } - if (newTags.length > 0) { - console.log(`Creating/updating tag keys [${newTags.map(tag => tag.Key)}] on subnet ${subnetId}...`); - await throttlingBackOff(() => ec2Client.send(new CreateTagsCommand({ Resources: [subnetId], Tags: newTags }))); - } - } catch (e) { - throw new Error(`Error while updating tags: ${e}`); - } -} - -/** - * Returns a boolean value from a string - * @param input - * @returns boolean | undefined - */ -function returnBoolean(input: string): boolean | undefined { - try { - return JSON.parse(input.toLowerCase()); - } catch (e) { - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/package.json deleted file mode 100644 index f73f8bd..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-ipam-subnet", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "ip-num": "1.5.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/vpc.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/vpc.ts deleted file mode 100644 index 7c89732..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam-subnet/vpc.ts +++ /dev/null @@ -1,450 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { IPv4CidrRange, IPv4Prefix, Pool } from 'ip-num'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - DescribeSubnetsCommand, - DescribeSubnetsResult, - DescribeVpcsCommand, - EC2Client, - Subnet, - Tag, - VpcCidrBlockAssociation, -} from '@aws-sdk/client-ec2'; - -interface IVpc { - /** - * The CIDRs of the VPC allocated by IPAM - */ - readonly allocatedCidrs: IAllocatedCidr[]; - /** - * The friendly name of the VPC - */ - readonly name: string; - /** - * The ID of the VPC - */ - readonly vpcId: string; -} - -interface VpcProps { - /** - * The base CIDR pool of the VPC - */ - readonly basePool: string[]; - /** - * The ID of the VPC - */ - readonly vpcId: string; -} - -export class Vpc implements IVpc { - public allocatedCidrs: AllocatedCidr[] = []; - public name = ''; - public readonly vpcId: string; - private readonly baseRanges: IPv4CidrRange[]; - - constructor(props: VpcProps) { - this.vpcId = props.vpcId; - this.baseRanges = props.basePool.map(item => { - return IPv4CidrRange.fromCidr(item); - }); - } - - /** - * Initialize the VPC object - * @param ec2Client EC2Client - * @returns Vpc - */ - public async init(ec2Client: EC2Client): Promise { - await this.vpcDetails(ec2Client); - await this.subnetDetails(ec2Client); - return this; - } - - /** - * Retrieve VPC details - * @param ec2Client EC2Client - */ - private async vpcDetails(ec2Client: EC2Client): Promise { - // - // Get VPC details - console.log(`Retrieving VPC details for VPC ${this.vpcId}...`); - try { - const vpcDetails = await throttlingBackOff(() => - ec2Client.send(new DescribeVpcsCommand({ VpcIds: [this.vpcId] })), - ); - - if (!vpcDetails.Vpcs || !vpcDetails.Vpcs[0].CidrBlockAssociationSet) { - throw new Error(`Unable to retrieve CIDR block details for VPC ${this.vpcId}`); - } - - // Determine CIDRs allocated by IPAM - for (const baseRange of this.baseRanges) { - for (const item of vpcDetails.Vpcs[0].CidrBlockAssociationSet ?? []) { - // Compare VPC CIDR to base CIDR range - const vpcRange = this.validateVpcCidr(item); - if (vpcRange && (vpcRange.inside(baseRange) || vpcRange.isEquals(baseRange))) { - this.setAllocatedCidr(vpcRange); - } - } - } - // - // Get name tag - const nameTag = vpcDetails.Vpcs[0].Tags?.filter(item => item.Key === 'Name')[0].Value; - if (!nameTag) { - throw new Error(`Unable to retrieve name tag for VPC ${this.vpcId}`); - } - // - // Set property values - this.name = nameTag; - } catch (e) { - throw new Error(`Error retrieving VPC details: ${e}`); - } - } - - /** - * Validate CIDR block state and return CIDR range object - * @param cidrItem - * @returns IPv4CidrRange | undefined - */ - private validateVpcCidr(cidrItem: VpcCidrBlockAssociation): IPv4CidrRange | undefined { - if (!cidrItem.CidrBlock) { - throw new Error(`Unable to retrieve CIDR block for VPC ${this.vpcId}`); - } - if (cidrItem.CidrBlockState?.State === 'associated') { - return IPv4CidrRange.fromCidr(cidrItem.CidrBlock); - } - return; - } - - /** - * Validate API response subnet state - * @param subnet - */ - private validateSubnet(subnet: Subnet) { - if (!subnet.SubnetId) { - throw new Error(`Unable to retrieve subnet ID`); - } - if (!subnet.CidrBlock) { - throw new Error(`Unable to retrieve CIDR block for subnet ${subnet.SubnetId}`); - } - } - - /** - * Add subnets to the allocatedCidrs array - * @param subnetList - */ - private setSubnets(subnetList: DescribeSubnetsResult | undefined): void { - for (const subnet of subnetList?.Subnets ?? []) { - this.validateSubnet(subnet); - - // Determine if subnet is in scope of IPAM - const subnetCidr = IPv4CidrRange.fromCidr(subnet.CidrBlock!); - for (const vpcRange of this.allocatedCidrs) { - if (subnetCidr.inside(vpcRange.cidr) || subnetCidr.isEquals(vpcRange.cidr)) { - // Get name tag - const nameTag = subnet.Tags?.filter(item => item.Key === 'Name')[0].Value; - - // Push object to array - vpcRange.addSubnet( - new VpcSubnet({ - allocatedCidr: subnetCidr, - mapPublicIpOnLaunch: subnet.MapPublicIpOnLaunch ?? false, - name: nameTag, - subnetId: subnet.SubnetId!, - tags: subnet.Tags, - }), - ); - } - } - } - } - - /** - * Retrieve subnet details for the VPC - * @param ec2Client EC2Client - */ - private async subnetDetails(ec2Client: EC2Client): Promise { - let nextToken: string | undefined = undefined; - console.log(`Retrieving subnet details for VPC ${this.vpcId}...`); - try { - do { - // - // Get subnet details - const page = await throttlingBackOff(() => - ec2Client.send( - new DescribeSubnetsCommand({ Filters: [{ Name: 'vpc-id', Values: [this.vpcId] }], NextToken: nextToken }), - ), - ); - - this.setSubnets(page); - - nextToken = page.NextToken; - } while (nextToken); - } catch (e) { - throw new Error(`Error retrieving subnet details: ${e}`); - } - } - - /** - * Mutator method to set an allocated CIDR - * @param cidr IPv4CidrRange - */ - private setAllocatedCidr(cidr: IPv4CidrRange) { - this.allocatedCidrs.push(new AllocatedCidr(cidr)); - } - - /** - * Accessor method to return a subnet by name - * @param name - * @returns VpcSubnet - */ - public getSubnetByName(name: string): VpcSubnet { - let subnet: VpcSubnet | undefined = undefined; - - for (const cidr of this.allocatedCidrs) { - subnet = cidr.subnets.find(item => item.name === name); - if (subnet) { - break; - } - } - if (!subnet) { - throw new Error(`Subnet with Name tag "${name}" does not exist in ${this.vpcId}`); - } - return subnet; - } - - /** - * Returns the next CIDR range available in the VPC - * @param netmaskLength bigint - * @param vpc Vpc - * @returns string | undefined - */ - public allocateCidr(netmaskLength: bigint): string | undefined { - // - // Convert netmask to prefix - const requestedPrefix = IPv4Prefix.fromNumber(netmaskLength); - - for (const [index, item] of this.allocatedCidrs.entries()) { - try { - // - // Get next subnet CIDR range from the pool. - // If it doesn't exist, try the next VPC CIDR range. - const nextRange = this.getNextRangeFromPool(item.cidr, requestedPrefix); - if (!nextRange) { - continue; - } - // - // Get next available subnet CIDR for the VPC CIDR range - const subnetCidr = this.getNextAvailableCidr(item, nextRange); - // - // Determine if next CIDR is available. - // If it is, break out of the loop. - if (subnetCidr) { - return subnetCidr; - } else { - if (index + 1 === this.allocatedCidrs.length) { - throw new Error( - `VPC is exhausted of address space. Cannot allocate a CIDR with /${netmaskLength.toString()} prefix.`, - ); - } - } - } catch (e) { - throw new Error(`Error while calculating next subnet CIDR: ${e}`); - } - } - return; - } - - /** - * Creates a pool of CIDR ranges from the given VPC CIDR and returns - * the next CIDR range from the pool, if available - * @param vpcCidr IPv4CidrRange - * @param requestedPrefix IPv4Prefix - * @returns IPv4CidrRange | undefined - */ - private getNextRangeFromPool(vpcCidr: IPv4CidrRange, requestedPrefix: IPv4Prefix): IPv4CidrRange | undefined { - try { - const pool = Pool.fromCidrRanges([vpcCidr]); - return pool.getCidrRange(requestedPrefix); - } catch (e) { - // If the above operation fails, it's due to the requested prefix being too large. - // Return undefined to indicate that no CIDR range could be found. - return; - } - } - - /** - * Returns the next valid CIDR range from the given VPC CIDR if it exists - * @param vpcCidr AllocatedCidr - * @param nextRange IPv4CidrRange - * @returns string | undefined - */ - private getNextAvailableCidr(vpcCidr: AllocatedCidr, nextRange: IPv4CidrRange): string | undefined { - // Determine if the next range is overlapping. - // If it is, try to find one that doesn't overlap. - if (!this.isOverlapping(vpcCidr, nextRange)) { - return nextRange.toCidrString(); - } else { - return this.tryFindNextCidr(vpcCidr, nextRange); - } - } - - /** - * Returns true if the next CIDR range is overlapping with subnets associated with the given VPC CIDR - * @param vpcCidr AllocatedCidr - * @param nextCidr IPv4CidrRange - * @returns boolean - */ - private isOverlapping(vpcCidr: AllocatedCidr, nextCidr: IPv4CidrRange): boolean { - for (const subnet of vpcCidr.subnets) { - if ( - subnet.allocatedCidr.contains(nextCidr) || - subnet.allocatedCidr.isEquals(nextCidr) || - nextCidr.contains(subnet.allocatedCidr) - ) { - return true; - } - } - return false; - } - - /** - * Tries to find the next CIDR range that is not overlapping with the given VPC CIDR - * @param vpcCidr AllocatedCidr - * @param range IPv4CidrRange - * @returns string | undefined - */ - private tryFindNextCidr(vpcCidr: AllocatedCidr, range: IPv4CidrRange): string | undefined { - let nextCidr = range.nextRange(); - let overlappingCidr = false; - if (!nextCidr) { - return; - } - - do { - if (!this.isOverlapping(vpcCidr, nextCidr) && vpcCidr.cidr.getLast().isGreaterThan(nextCidr.getFirst())) { - return nextCidr.toCidrString(); - } else { - overlappingCidr = true; - nextCidr = nextCidr.nextRange(); - if (!nextCidr || vpcCidr.cidr.getLast().isLessThanOrEquals(nextCidr.getFirst())) { - return; - } - } - } while (overlappingCidr); - return; - } -} - -interface IAllocatedCidr { - /** - * The CIDR of the allocated CIDR block - */ - readonly cidr: IPv4CidrRange; - /** - * The subnets associated with the CIDR - */ - readonly subnets: VpcSubnet[]; -} - -export class AllocatedCidr implements IAllocatedCidr { - public readonly cidr: IPv4CidrRange; - public readonly subnets: VpcSubnet[]; - - constructor(cidr: IPv4CidrRange) { - this.cidr = cidr; - this.subnets = []; - } - - /** - * Mutator method to add a subnet to the allocated CIDR - * @param subnet - */ - public addSubnet(subnet: VpcSubnet) { - this.subnets.push(subnet); - } -} - -interface IVpcSubnet { - /** - * The CIDR of the subnet allocated by IPAM - */ - readonly allocatedCidr: IPv4CidrRange; - /** - * The ID of the subnet - */ - readonly subnetId: string; - /** - * The MapPublicIpOnLaunch subnet attribute - */ - readonly mapPublicIpOnLaunch?: boolean; - /** - * The friendly name of the subnet - */ - readonly name?: string; - /** - * Tags associated with the subnet - */ - readonly tags?: Tag[]; -} - -interface VpcSubnetProps { - /** - * The CIDR of the subnet allocated by IPAM - */ - readonly allocatedCidr: IPv4CidrRange; - /** - * The ID of the subnet - */ - readonly subnetId: string; - /** - * The MapPublicIpOnLaunch subnet attribute - */ - readonly mapPublicIpOnLaunch?: boolean; - /** - * The friendly name of the subnet - */ - readonly name?: string; - /** - * Tags associated with the subnet - */ - readonly tags?: Tag[]; -} - -export class VpcSubnet implements IVpcSubnet { - public readonly allocatedCidr: IPv4CidrRange; - public readonly subnetId: string; - public readonly mapPublicIpOnLaunch: boolean | undefined; - public readonly name: string | undefined; - public readonly tags: Tag[] | undefined; - - constructor(props: VpcSubnetProps) { - this.allocatedCidr = props.allocatedCidr; - this.mapPublicIpOnLaunch = props.mapPublicIpOnLaunch; - this.name = props.name; - this.subnetId = props.subnetId; - this.tags = props.tags; - } -} - -export async function vpcInit(props: VpcProps, ec2Client: EC2Client): Promise { - // Set static properties - const vpc = new Vpc(props); - - // Set VPC and subnet properties - await vpc.init(ec2Client); - return vpc; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam.ts deleted file mode 100644 index b1486d7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/ipam.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IIpam extends cdk.IResource { - /** - * The ARN of the IPAM. - * - * @attribute - */ - readonly ipamArn: string; - - /** - * The identifier of the IPAM. - * - * @attribute - */ - readonly ipamId: string; - - /** - * The name of the IPAM. - */ - readonly ipamName: string; - - /** - * The ID of the IPAM's default private scope. - * - * @attribute - */ - readonly privateDefaultScopeId: string; - - /** - * The ID of the IPAM's default public scope. - * - * @attribute - */ - readonly publicDefaultScopeId: string; -} - -export interface IpamProps { - /** - * The name of the IPAM. - */ - readonly name: string; - - /** - * The description for the IPAM. - */ - readonly description?: string; - - /** - * The operating Regions for an IPAM. - */ - readonly operatingRegions?: string[]; - - /** - * The key/value combination of a tag assigned to the resource. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class Ipam extends cdk.Resource implements IIpam { - public readonly ipamArn: string; - public readonly ipamId: string; - public readonly ipamName: string; - public readonly privateDefaultScopeId: string; - public readonly publicDefaultScopeId: string; - private regions?: cdk.aws_ec2.CfnIPAM.IpamOperatingRegionProperty[]; - - constructor(scope: Construct, id: string, props: IpamProps) { - super(scope, id); - - this.ipamName = props.name; - - // Map operating region values - this.regions = props.operatingRegions?.map(region => { - return { regionName: region }; - }); - - const resource = new cdk.aws_ec2.CfnIPAM(this, 'Resource', { - description: props.description, - operatingRegions: this.regions, - tags: props.tags, - }); - - cdk.Tags.of(this).add('Name', this.ipamName); - - this.ipamArn = resource.attrArn; - this.ipamId = resource.ref; - this.privateDefaultScopeId = resource.attrPrivateDefaultScopeId; - this.publicDefaultScopeId = resource.attrPublicDefaultScopeId; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route.ts deleted file mode 100644 index c668a16..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface PrefixListRouteProps { - /** - * The destination prefix list ID of the route - */ - readonly destinationPrefixListId: string; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly logGroupKmsKey?: cdk.aws_kms.IKey; - - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - - /** - * The ID of the route table for the route - */ - readonly routeTableId: string; - - /** - * The ID of a carrier gateway - */ - readonly carrierGatewayId?: string; - - /** - * The ID of an egress-only Internet gateway - */ - readonly egressOnlyInternetGatewayId?: string; - - /** - * The ID of an Internet Gateway or Virtual Private Gateway - */ - readonly gatewayId?: string; - - /** - * The ID of an instance - */ - readonly instanceId?: string; - - /** - * The ID of a local gateway - */ - readonly localGatewayId?: string; - - /** - * The ID of a NAT gateway - */ - readonly natGatewayId?: string; - - /** - * The ID of a network interface - */ - readonly networkInterfaceId?: string; - - /** - * The ID of a transit gateway - */ - readonly transitGatewayId?: string; - - /** - * The ID of a VPC endpoint - */ - readonly vpcEndpointId?: string; - - /** - * The ID of a VPC peering connection - */ - readonly vpcPeeringConnectionId?: string; -} - -export class PrefixListRoute extends cdk.Resource { - public readonly id: string; - - constructor(scope: Construct, id: string, props: PrefixListRouteProps) { - super(scope, id); - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::PrefixListRoute', { - codeDirectory: path.join(__dirname, 'prefix-list-route/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'AllowModifyRoutes', - Effect: 'Allow', - Action: ['ec2:CreateRoute', 'ec2:DeleteRoute'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::PrefixListRoute', - serviceToken: provider.serviceToken, - properties: { - routeDefinition: { - DestinationPrefixListId: props.destinationPrefixListId, - RouteTableId: props.routeTableId, - CarrierGatewayId: props.carrierGatewayId, - EgressOnlyInternetGatewayId: props.egressOnlyInternetGatewayId, - GatewayId: props.gatewayId, - InstanceId: props.instanceId, - LocalGatewayId: props.localGatewayId, - NatGatewayId: props.natGatewayId, - NetworkInterfaceId: props.networkInterfaceId, - TransitGatewayId: props.transitGatewayId, - VpcEndpointId: props.vpcEndpointId, - VpcPeeringConnectionId: props.vpcPeeringConnectionId, - }, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.logGroupKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/index.ts deleted file mode 100644 index 74361d5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * aws-ec2-prefix-list-route - lambda handler - * - * @param event - * @returns - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string | undefined; - } - | undefined -> { - interface RouteProps { - readonly DestinationPrefixListId: string; - readonly RouteTableId: string; - readonly CarrierGatewayId?: string; - readonly EgressOnlyInternetGatewayId?: string; - readonly GatewayId?: string; - readonly InstanceId?: string; - readonly LocalGatewayId?: string; - readonly NatGatewayId?: string; - readonly NetworkInterfaceId?: string; - readonly TransitGatewayId?: string; - readonly VpcEndpointId?: string; - readonly VpcPeeringConnectionId?: string; - } - - const ec2 = new AWS.EC2({ customUserAgent: process.env['SOLUTION_ID'] }); - const props: RouteProps = event.ResourceProperties['routeDefinition']; - const resourceId = `${props.DestinationPrefixListId}${props.RouteTableId}`; - - switch (event.RequestType) { - case 'Create': - case 'Update': - await throttlingBackOff(() => ec2.createRoute(props).promise()); - - return { - PhysicalResourceId: resourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - ec2 - .deleteRoute({ DestinationPrefixListId: props.DestinationPrefixListId, RouteTableId: props.RouteTableId }) - .promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/package.json deleted file mode 100644 index 14f3e4a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-prefix-list-route", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list-route/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list.ts deleted file mode 100644 index dadf98b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/prefix-list.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CfnTag, IResource, Resource } from 'aws-cdk-lib'; -import * as ec2 from 'aws-cdk-lib/aws-ec2'; -import { Construct } from 'constructs'; - -export interface IPrefixList extends IResource { - /** - * The ID of the prefix list. - */ - readonly prefixListId: string; -} - -export interface PrefixListProps { - /** - * The name of the prefix list. - */ - readonly name: string; - - /** - * IP Address Family IPv4 or IPv6. - * @default -- 'IPv4' - */ - readonly addressFamily: string; - - /** - * Maxinum number of CIDR block entries. - * - * @default -- 1 - */ - readonly maxEntries: number; - - /** - * List of CIDR block entries. - * - */ - readonly entries: string[]; - - /** - * Any tags assigned to the prefix list. - */ - readonly tags?: CfnTag[]; -} - -export class PrefixList extends Resource implements IPrefixList { - public readonly prefixListId: string; - - constructor(scope: Construct, id: string, props: PrefixListProps) { - super(scope, id); - - const cidrBlocks: ec2.CfnPrefixList.EntryProperty[] = props.entries.map(item => { - return { cidr: item }; - }); - - const resource = new ec2.CfnPrefixList(this, 'Resource', { - prefixListName: props.name, - addressFamily: props.addressFamily, - maxEntries: props.maxEntries, - entries: cidrBlocks, - tags: props.tags, - }); - - this.prefixListId = resource.attrPrefixListId; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/route-table.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/route-table.ts deleted file mode 100644 index acca0dc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/route-table.ts +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { PrefixListRoute } from './prefix-list-route'; -import { ITransitGatewayAttachment } from './transit-gateway'; -import { Vpc } from './vpc'; - -export interface IRouteTable extends cdk.IResource { - /** - * The identifier of the route table - * - * @attribute - */ - readonly routeTableId: string; - - /** - * The VPC associated with the route table - * - * @attribute - */ - readonly vpc: Vpc; -} - -export interface RouteTableProps { - readonly name: string; - readonly vpc: Vpc; - readonly tags?: cdk.CfnTag[]; -} - -export interface ImportRouteTableProps extends Omit { - routeTableId: string; -} - -export abstract class RouteTableBase extends cdk.Resource implements IRouteTable { - public abstract readonly routeTableId: string; - public abstract readonly vpc: Vpc; - - public addTransitGatewayRoute( - id: string, - transitGatewayId: string, - transitGatewayAttachment: ITransitGatewayAttachment, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): cdk.aws_ec2.CfnRoute | PrefixListRoute { - let route: cdk.aws_ec2.CfnRoute | PrefixListRoute; - - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - - route = new PrefixListRoute(this, id, { - routeTableId: this.routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - transitGatewayId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - route = new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: this.routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - transitGatewayId: transitGatewayId, - }); - } - - transitGatewayAttachment.addDependency(route); - return route; - } - - public addNatGatewayRoute( - id: string, - natGatewayId: string, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): cdk.aws_ec2.CfnRoute | PrefixListRoute { - let route: cdk.aws_ec2.CfnRoute | PrefixListRoute; - - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - - route = new PrefixListRoute(this, id, { - routeTableId: this.routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - natGatewayId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - route = new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: this.routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - natGatewayId: natGatewayId, - }); - } - return route; - } - - public addLocalGatewayRoute( - id: string, - localGatewayId: string, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): cdk.aws_ec2.CfnRoute | PrefixListRoute { - let route: cdk.aws_ec2.CfnRoute | PrefixListRoute; - - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - - route = new PrefixListRoute(this, id, { - routeTableId: this.routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - localGatewayId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - route = new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: this.routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - localGatewayId: localGatewayId, - }); - } - return route; - } - - public addInternetGatewayRoute( - id: string, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): cdk.aws_ec2.CfnRoute | PrefixListRoute { - if (!this.vpc.internetGatewayId) { - throw new Error('Attempting to add Internet Gateway route without an IGW defined.'); - } - - let route: cdk.aws_ec2.CfnRoute | PrefixListRoute; - - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - - route = new PrefixListRoute(this, id, { - routeTableId: this.routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - gatewayId: this.vpc.internetGatewayId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - route = new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: this.routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - gatewayId: this.vpc.internetGatewayId, - }); - } - - // Need to add depends on for the attachment, as IGW needs to be part of - // the network (vpc) - // To avoid explicit dependency setting, create addInternetGatewayRoute in VPC similar to how CDK implements - this.vpc.addInternetGatewayDependent(route); - return route; - } - - public addEgressOnlyIgwRoute( - id: string, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): cdk.aws_ec2.CfnRoute | PrefixListRoute { - if (!this.vpc.egressOnlyIgwId) { - throw new Error('Attempting to add Egress-only Internet Gateway route without an EIGW defined.'); - } - - let route: cdk.aws_ec2.CfnRoute | PrefixListRoute; - - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - - route = new PrefixListRoute(this, id, { - routeTableId: this.routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - egressOnlyInternetGatewayId: this.vpc.egressOnlyIgwId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - route = new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: this.routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - egressOnlyInternetGatewayId: this.vpc.egressOnlyIgwId, - }); - } - return route; - } - - public addVirtualPrivateGatewayRoute( - id: string, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): cdk.aws_ec2.CfnRoute | PrefixListRoute { - if (!this.vpc.virtualPrivateGatewayId) { - throw new Error('Attempting to add Virtual Private Gateway route without an VGW defined.'); - } - let route: cdk.aws_ec2.CfnRoute | PrefixListRoute; - - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - route = new PrefixListRoute(this, id, { - routeTableId: this.routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - gatewayId: this.vpc.virtualPrivateGatewayId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - route = new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: this.routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - gatewayId: this.vpc.virtualPrivateGatewayId, - }); - } - - // Need to add depends on for the attachment, as VGW needs to be part of - // the network (vpc) - // To avoid explicit dependency setting, create addVirtualPrivateGatewayRoute in VPC similar to how CDK implements - this.vpc.addVirtualPrivateGatewayDependent(route); - return route; - } - - public addGatewayAssociation(type: string): void { - if (type === 'internetGateway') { - const association = new cdk.aws_ec2.CfnGatewayRouteTableAssociation(this, 'GatewayAssociation', { - routeTableId: this.routeTableId, - gatewayId: this.vpc.internetGatewayId!, - }); - this.vpc.addInternetGatewayDependent(association); - } - - if (type === 'virtualPrivateGateway') { - const association = new cdk.aws_ec2.CfnGatewayRouteTableAssociation(this, 'VirtualPrivateGatewayAssociation', { - routeTableId: this.routeTableId, - gatewayId: this.vpc.virtualPrivateGatewayId!, - }); - this.vpc.addVirtualPrivateGatewayDependent(association); - } - } -} - -export class ImportedRouteTable extends RouteTableBase { - public readonly routeTableId: string; - public readonly vpc: Vpc; - - constructor(scope: Construct, id: string, props: ImportRouteTableProps) { - super(scope, id); - this.routeTableId = props.routeTableId; - this.vpc = props.vpc; - } -} - -export class RouteTable extends RouteTableBase { - public readonly routeTableId: string; - public readonly vpc: Vpc; - - constructor(scope: Construct, id: string, props: RouteTableProps) { - super(scope, id); - - this.vpc = props.vpc; - - const resource = new cdk.aws_ec2.CfnRouteTable(this, 'Resource', { - vpcId: props.vpc.vpcId, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', props.name); - - this.routeTableId = resource.ref; - } - - static fromRouteTableAttributes(scope: Construct, id: string, props: ImportRouteTableProps) { - return new ImportedRouteTable(scope, id, props); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/subnet-id-lookup.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/subnet-id-lookup.ts deleted file mode 100644 index 7a6f03f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/subnet-id-lookup.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import * as path from 'path'; - -/** - * Initialized SubnetIdLookupProps properties - */ -export interface SubnetIdLookupProps { - /** - * Vpc id for the subnet lookup - */ - readonly vpcId: string; - /** - * Subnet name - */ - readonly subnetName: string; - /** - * Custom resource lambda key, when undefined default AWS managed key will be used - */ - readonly lambdaKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log retention in days - */ - readonly cloudwatchLogRetentionInDays: number; -} - -/** - * Subnet id lookup class. - */ -export class SubnetIdLookup extends Construct { - public readonly subnetId: string; - constructor(scope: Construct, id: string, props: SubnetIdLookupProps) { - super(scope, id); - - const providerLambda = new cdk.aws_lambda.Function(this, 'SubnetIdLookupFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'get-subnet-id/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - description: 'Lookup subnet id from account', - environmentEncryption: props.lambdaKey, - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Ec2Actions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeSubnets'], - resources: ['*'], - }), - ); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.cloudwatchLogRetentionInDays, - encryptionKey: props.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - const provider = new cdk.custom_resources.Provider(this, 'SubnetIdLookupProvider', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::SubnetIdLookup', - serviceToken: provider.serviceToken, - properties: { - vpcId: props.vpcId, - subnetName: props.subnetName, - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.subnetId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/index.ts deleted file mode 100644 index ec0e8bb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/index.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - AssociateTransitGatewayRouteTableCommand, - DisassociateTransitGatewayRouteTableCommand, - EC2Client, -} from '@aws-sdk/client-ec2'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -interface TgwAssociationOptions { - /** - * Transit gateway attachment ID - */ - readonly transitGatewayAttachmentId: string; - /** - * Transit gateway route table ID - */ - readonly transitGatewayRouteTableId: string; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; -} - -export async function handler(event: CloudFormationCustomResourceEvent): Promise<{ Status: string } | undefined> { - // - // Set resource properties - const options = setOptions(event.ResourceProperties, event.ServiceToken); - // - // Set EC2 client - const ec2Client = await setEc2Client(options, process.env['SOLUTION_ID']); - // - // Begin custom resource logic - switch (event.RequestType) { - case 'Create': - await createTgwAssociation(ec2Client, options); - return { - Status: 'SUCCESS', - }; - case 'Update': - const oldOptions = setOptions(event.OldResourceProperties, event.ServiceToken); - await deleteTgwAssociation(ec2Client, oldOptions); - await createTgwAssociation(ec2Client, options); - return { - Status: 'SUCCESS', - }; - case 'Delete': - await deleteTgwAssociation(ec2Client, options); - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Set TGW association options based on event - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns TgwAssociationOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }, serviceToken: string): TgwAssociationOptions { - return { - transitGatewayAttachmentId: resourceProperties['transitGatewayAttachmentId'], - transitGatewayRouteTableId: resourceProperties['transitGatewayRouteTableId'], - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - partition: serviceToken.split(':')[1], - owningAccountId: (resourceProperties['owningAccountId'] as string) ?? undefined, - owningRegion: (resourceProperties['owningRegion'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - }; -} - -/** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param options options - * @param solutionId string | undefined - * @returns Promise - */ -async function setEc2Client(options: TgwAssociationOptions, solutionId?: string): Promise { - const roleArn = `arn:${options.partition}:iam::${options.owningAccountId}:role/${options.roleName}`; - const stsClient = new STSClient({ region: options.invokingRegion, customUserAgent: solutionId }); - - if (options.owningAccountId && options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW association required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (options.owningAccountId && !options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW association required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: options.owningRegion ?? options.invokingRegion, - customUserAgent: solutionId, - }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Create TGW route table association - * @param ec2Client - * @param options - * @returns Promise - */ -async function createTgwAssociation(ec2Client: EC2Client, options: TgwAssociationOptions): Promise { - console.log( - `Associating attachment ${options.transitGatewayAttachmentId} to TGW route table ${options.transitGatewayRouteTableId}...`, - ); - try { - await throttlingBackOff(() => - ec2Client.send( - new AssociateTransitGatewayRouteTableCommand({ - TransitGatewayAttachmentId: options.transitGatewayAttachmentId, - TransitGatewayRouteTableId: options.transitGatewayRouteTableId, - }), - ), - ); - } catch (e) { - throw new Error(`Could not complete AssociateTransitGateway command: ${e}`); - } -} - -/** - * Delete TGW route table association - * @param ec2Client - * @param options - * @returns Promise - */ -async function deleteTgwAssociation(ec2Client: EC2Client, options: TgwAssociationOptions): Promise { - console.log( - `Disassociating attachment ${options.transitGatewayAttachmentId} from TGW route table ${options.transitGatewayRouteTableId}...`, - ); - try { - await throttlingBackOff(() => - ec2Client.send( - new DisassociateTransitGatewayRouteTableCommand({ - TransitGatewayAttachmentId: options.transitGatewayAttachmentId, - TransitGatewayRouteTableId: options.transitGatewayRouteTableId, - }), - ), - ); - } catch (e) { - throw new Error(`Could not complete AssociateTransitGateway command: ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/package.json deleted file mode 100644 index 18c8dbe..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-transit-gateway-association", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/tsconfig.json deleted file mode 100644 index 4e33341..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-association/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-connect.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-connect.ts deleted file mode 100644 index 1e79394..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-connect.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as ec2 from 'aws-cdk-lib/aws-ec2'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; - -export interface TransitGatewayConnectProps { - /** - * The name of the transit gateway connect. Will be assigned to the Name tag - */ - readonly name: string; - /** - * The options for the Transit Gateway Connect - */ - readonly options: ec2.CfnTransitGatewayConnect.TransitGatewayConnectOptionsProperty; - - /** - * The ID of the transit gateway attachment. - */ - readonly transitGatewayAttachmentId: string; - /** - * The tags that will be attached to the transit gateway route table. - */ - readonly tags?: cdk.CfnTag[]; -} - -/** - * Creates a Transit Gateway Route Table - */ -export class TransitGatewayConnect extends Construct { - constructor(scope: Construct, id: string, props: TransitGatewayConnectProps) { - super(scope, id); - - new ec2.CfnTransitGatewayConnect(this, pascalCase(`${props.name}TransitGatewayConnect`), { - transportTransitGatewayAttachmentId: props.transitGatewayAttachmentId, - options: props.options, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', props.name); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-peering.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-peering.ts deleted file mode 100644 index 1e6e08c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-peering.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { pascalCase } from 'change-case'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; - -export interface TransitGatewayPeeringProps { - /** - * Peering accepter properties - */ - readonly accepter: { - /** - * Accepter transit gateway account id - */ - readonly accountId: string; - /** - * Accepter account access role name. Custom resource will assume this role to approve peering request - */ - readonly accountAccessRoleName: string; - /** - * Accepter transit gateway region name - */ - readonly region: string; - /** - * Accepter transit gateway name - */ - readonly transitGatewayName: string; - /** - * Accepter transit gateway ID - */ - readonly transitGatewayId: string; - /** - * Accepter transit gateway region route table name. Peering attachment will be associated to this route table - */ - readonly transitGatewayRouteTableId: string; - /** - * Peering request auto accept flag. - */ - readonly autoAccept: boolean; - /** - * Peering request apply tags flag. Tags provided are applies to requester attachment only. - * When this flag is on, similar tags will be applied to peer or accepter attachment also. - * In peer or accepter attachment existing tags can't be changed, only given tags will be added or modified. - */ - readonly applyTags: boolean; - }; - - /** - * Peering requester properties - */ - readonly requester: { - /** - * Requester Account name. - */ - readonly accountName: string; - /** - * Requester Transit Gateway ID. - */ - readonly transitGatewayId: string; - /** - * Requester Transit Gateway Name. - */ - readonly transitGatewayName: string; - /** - * Requester Transit Gateway route table id. Peering attachment will be associated to this route table - */ - readonly transitGatewayRouteTableId: string; - /** - * The tags for the transit gateway peering attachment, applied to requester attachment only. - */ - readonly tags?: cdk.CfnTag[]; - }; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customLambdaLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to create Transit Gateway peering configuration - */ -export class TransitGatewayPeering extends Construct { - public readonly peeringAttachmentId: string; - constructor(scope: Construct, id: string, props: TransitGatewayPeeringProps) { - super(scope, id); - - const cfnTransitGatewayPeeringAttachment = new cdk.aws_ec2.CfnTransitGatewayPeeringAttachment( - this, - pascalCase(`${props.accepter.transitGatewayName}To${props.requester.transitGatewayName}`), - { - peerAccountId: props.accepter.accountId, - peerRegion: props.accepter.region, - peerTransitGatewayId: props.accepter.transitGatewayId, - transitGatewayId: props.requester.transitGatewayId, - tags: props.requester.tags, - }, - ); - - this.peeringAttachmentId = cfnTransitGatewayPeeringAttachment.attrTransitGatewayAttachmentId; - - const RESOURCE_TYPE = 'Custom::TransitGatewayAcceptPeering'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'accept-transit-gateway-peering-attachment/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'AllowAssumeRole', - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: `arn:${cdk.Stack.of(this).partition}:iam::${props.accepter.accountId}:role/${ - props.accepter.accountAccessRoleName - }`, - }, - { - Sid: 'AllowModifyPeeringReferences', - Effect: 'Allow', - Action: [ - 'ec2:AssociateTransitGatewayRouteTable', - 'ec2:DisassociateTransitGatewayRouteTable', - 'ec2:DescribeTransitGatewayPeeringAttachments', - 'ec2:DescribeTransitGatewayAttachments', - ], - Resource: '*', - }, - ], - }); - - const tags: { Key: string; Value: string }[] = []; - if (props.requester.tags) { - if (props.accepter.applyTags ?? false) { - for (const peeringTag of props.requester.tags) { - tags.push({ Key: peeringTag.key, Value: peeringTag.value }); - } - } - } - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - accepterRegion: props.accepter.region, - accepterAccountId: props.accepter.accountId, - accepterTransitGatewayId: props.accepter.transitGatewayId, - accepterTransitGatewayRouteTableId: props.accepter.transitGatewayRouteTableId, - accepterRoleArn: `arn:${cdk.Stack.of(this).partition}:iam::${props.accepter.accountId}:role/${ - props.accepter.accountAccessRoleName - }`, - requesterAccountId: cdk.Stack.of(this).account, - requesterRegion: cdk.Stack.of(this).region, - requesterTransitGatewayRouteTableId: props.requester.transitGatewayRouteTableId, - requesterTransitGatewayAttachmentId: this.peeringAttachmentId, - requesterTransitGatewayId: props.requester.transitGatewayId, - - autoAccept: props.accepter.autoAccept, - peeringTags: tags.length === 0 ? undefined : tags, - uuid: uuidv4(), - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.customLambdaLogKmsKey, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference.ts deleted file mode 100644 index 8a9d2af..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface TransitGatewayPrefixListReferenceProps { - /** - * The prefix list ID to reference - */ - readonly prefixListId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly logGroupKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The ID of the transit gateway route table. - */ - readonly transitGatewayRouteTableId: string; - /** - * Determines if route is blackholed. - */ - readonly blackhole?: boolean; - /** - * Owning account ID for cross-account TGW associations - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account TGW associations - */ - readonly owningRegion?: string; - /** - * Role name for cross-account TGW associations - */ - readonly roleName?: string; - /** - * The identifier of the Transit Gateway Attachment - * - */ - readonly transitGatewayAttachmentId?: string; -} - -export class TransitGatewayPrefixListReference extends cdk.Resource { - public readonly id: string; - - constructor(scope: Construct, id: string, props: TransitGatewayPrefixListReferenceProps) { - super(scope, id); - - const policyStatements = [ - { - Sid: 'AllowModifyTgwReferences', - Effect: 'Allow', - Action: [ - 'ec2:CreateTransitGatewayPrefixListReference', - 'ec2:ModifyTransitGatewayPrefixListReference', - 'ec2:DeleteTransitGatewayPrefixListReference', - ], - Resource: '*', - }, - ]; - - if (props.roleName) { - policyStatements.push({ - Sid: 'AssumeRole', - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: `arn:${this.stack.partition}:iam::*:role/${props.roleName}`, - }); - } - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::TransitGatewayPrefixListReference', { - codeDirectory: path.join(__dirname, 'transit-gateway-prefix-list-reference/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::TransitGatewayPrefixListReference', - serviceToken: provider.serviceToken, - properties: { - prefixListReference: { - PrefixListId: props.prefixListId, - TransitGatewayRouteTableId: props.transitGatewayRouteTableId, - Blackhole: props.blackhole, - TransitGatewayAttachmentId: props.transitGatewayAttachmentId, - }, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.logGroupKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/index.ts deleted file mode 100644 index eaa35df..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/index.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * aws-ec2-transit-gateway-prefix-list-reference - lambda handler - * - * @param event - * @returns - */ - -import { - CreateTransitGatewayPrefixListReferenceCommand, - DeleteTransitGatewayPrefixListReferenceCommand, - EC2Client, - ModifyTransitGatewayPrefixListReferenceCommand, -} from '@aws-sdk/client-ec2'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -interface ReferenceOptions { - /** - * API props - */ - readonly props: { - readonly PrefixListId: string; - readonly TransitGatewayRouteTableId: string; - readonly Blackhole?: boolean; - readonly TransitGatewayAttachmentId?: string; - }; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; -} - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - } - | undefined -> { - const options = setOptions(event.ResourceProperties, event.ServiceToken); - const ec2Client = await setEc2Client(options, process.env['SOLUTION_ID']); - - switch (event.RequestType) { - case 'Create': - await throttlingBackOff(() => ec2Client.send(new CreateTransitGatewayPrefixListReferenceCommand(options.props))); - - return { - Status: 'SUCCESS', - }; - - case 'Update': - await throttlingBackOff(() => ec2Client.send(new ModifyTransitGatewayPrefixListReferenceCommand(options.props))); - - return { - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - ec2Client.send( - new DeleteTransitGatewayPrefixListReferenceCommand({ - PrefixListId: options.props.PrefixListId, - TransitGatewayRouteTableId: options.props.TransitGatewayRouteTableId, - }), - ), - ); - - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Set TGW prefix list reference options based on event - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns ReferenceOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }, serviceToken: string): ReferenceOptions { - return { - props: { - PrefixListId: resourceProperties['prefixListReference']['PrefixListId'], - TransitGatewayRouteTableId: resourceProperties['prefixListReference']['TransitGatewayRouteTableId'], - Blackhole: resourceProperties['prefixListReference']['Blackhole'] === 'true' ?? undefined, - TransitGatewayAttachmentId: resourceProperties['prefixListReference']['TransitGatewayAttachmentId'], - }, - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - partition: serviceToken.split(':')[1], - owningAccountId: (resourceProperties['owningAccountId'] as string) ?? undefined, - owningRegion: (resourceProperties['owningRegion'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - }; -} - -/** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param options ReferenceOptions - * @param solutionId string | undefined - * @returns Promise - */ -async function setEc2Client(options: ReferenceOptions, solutionId?: string): Promise { - const roleArn = `arn:${options.partition}:iam::${options.owningAccountId}:role/${options.roleName}`; - const stsClient = new STSClient({ region: options.invokingRegion, customUserAgent: solutionId }); - - if (options.owningAccountId && options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW prefix list reference required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (options.owningAccountId && !options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW prefix list reference required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: options.owningRegion ?? options.invokingRegion, - customUserAgent: solutionId, - }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/package.json deleted file mode 100644 index cd2ee25..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-transit-gateway-prefix-list-reference", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-prefix-list-reference/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/index.ts deleted file mode 100644 index edb9136..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/index.ts +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - DisableTransitGatewayRouteTablePropagationCommand, - EC2Client, - EnableTransitGatewayRouteTablePropagationCommand, -} from '@aws-sdk/client-ec2'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -interface TgwPropagationOptions { - /** - * Transit gateway attachment ID - */ - readonly transitGatewayAttachmentId: string; - /** - * Transit gateway route table ID - */ - readonly transitGatewayRouteTableId: string; - /** - * Invoking account ID for the custom resource - */ - readonly invokingAccountId: string; - /** - * Invoking region for the custom resource - */ - readonly invokingRegion: string; - /** - * Custom resource partition - */ - readonly partition: string; - /** - * Owning account ID for cross-account customer gateways - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account customer gateways - */ - readonly owningRegion?: string; - /** - * Role name for cross-account customer gateways - */ - readonly roleName?: string; -} - -export async function handler(event: CloudFormationCustomResourceEvent): Promise<{ Status: string } | undefined> { - // - // Set resource properties - const options = setOptions(event.ResourceProperties, event.ServiceToken); - // - // Set EC2 client - const ec2Client = await setEc2Client(options, process.env['SOLUTION_ID']); - // - // Begin custom resource logic - switch (event.RequestType) { - case 'Create': - await createTgwPropagation(ec2Client, options); - return { - Status: 'SUCCESS', - }; - case 'Update': - const oldOptions = setOptions(event.OldResourceProperties, event.ServiceToken); - await deleteTgwPropagation(ec2Client, oldOptions); - await createTgwPropagation(ec2Client, options); - return { - Status: 'SUCCESS', - }; - case 'Delete': - await deleteTgwPropagation(ec2Client, options); - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Set TGW propagation options based on event - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns TgwPropagationOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }, serviceToken: string): TgwPropagationOptions { - return { - transitGatewayAttachmentId: resourceProperties['transitGatewayAttachmentId'], - transitGatewayRouteTableId: resourceProperties['transitGatewayRouteTableId'], - invokingAccountId: serviceToken.split(':')[4], - invokingRegion: serviceToken.split(':')[3], - partition: serviceToken.split(':')[1], - owningAccountId: (resourceProperties['owningAccountId'] as string) ?? undefined, - owningRegion: (resourceProperties['owningRegion'] as string) ?? undefined, - roleName: (resourceProperties['roleName'] as string) ?? undefined, - }; -} - -/** - * Returns a local or cross-account/cross-region EC2 client based on input parameters - * @param options options - * @param solutionId string | undefined - * @returns Promise - */ -async function setEc2Client(options: TgwPropagationOptions, solutionId?: string): Promise { - const roleArn = `arn:${options.partition}:iam::${options.owningAccountId}:role/${options.roleName}`; - const stsClient = new STSClient({ region: options.invokingRegion, customUserAgent: solutionId }); - - if (options.owningAccountId && options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW propagation required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.owningRegion, - customUserAgent: solutionId, - credentials, - }); - } else if (options.owningAccountId && !options.owningRegion) { - if (!options.roleName) { - throw new Error(`Cross-account TGW propagation required but roleName parameter is undefined`); - } - // - // Assume role via STS - const credentials = await getStsCredentials(stsClient, roleArn); - // - // Return EC2 client - return new EC2Client({ - region: options.invokingRegion, - customUserAgent: solutionId, - credentials, - }); - } else { - return new EC2Client({ - region: options.owningRegion ?? options.invokingRegion, - customUserAgent: solutionId, - }); - } -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Create TGW route table association - * @param ec2Client - * @param options - * @returns Promise - */ -async function createTgwPropagation(ec2Client: EC2Client, options: TgwPropagationOptions): Promise { - console.log( - `Enabling attachment propagation for ${options.transitGatewayAttachmentId} to TGW route table ${options.transitGatewayRouteTableId}...`, - ); - try { - await throttlingBackOff(() => - ec2Client.send( - new EnableTransitGatewayRouteTablePropagationCommand({ - TransitGatewayAttachmentId: options.transitGatewayAttachmentId, - TransitGatewayRouteTableId: options.transitGatewayRouteTableId, - }), - ), - ); - } catch (e) { - throw new Error(`Could not complete AssociateTransitGateway command: ${e}`); - } -} - -/** - * Delete TGW route table association - * @param ec2Client - * @param options - * @returns Promise - */ -async function deleteTgwPropagation(ec2Client: EC2Client, options: TgwPropagationOptions): Promise { - console.log( - `Disabling propagation for attachment ${options.transitGatewayAttachmentId} from TGW route table ${options.transitGatewayRouteTableId}...`, - ); - try { - await throttlingBackOff(() => - ec2Client.send( - new DisableTransitGatewayRouteTablePropagationCommand({ - TransitGatewayAttachmentId: options.transitGatewayAttachmentId, - TransitGatewayRouteTableId: options.transitGatewayRouteTableId, - }), - ), - ); - } catch (e) { - throw new Error(`Could not complete AssociateTransitGateway command: ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/package.json deleted file mode 100644 index cb5248b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ec2-transit-gateway-propagation", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/tsconfig.json deleted file mode 100644 index 4e33341..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-propagation/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-route-table.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-route-table.ts deleted file mode 100644 index 930fc2a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-route-table.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as ec2 from 'aws-cdk-lib/aws-ec2'; -import * as cdk from 'aws-cdk-lib'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; - -export interface TransitGatewayRouteTableProps { - /** - * The name of the transit gateway route table. Will be assigned to the Name tag - */ - readonly name: string; - - /** - * The ID of the transit gateway. - */ - readonly transitGatewayId: string; - - /** - * The tags that will be attached to the transit gateway route table. - */ - readonly tags?: cdk.CfnTag[]; -} - -/** - * Creates a Transit Gateway Route Table - */ -export class TransitGatewayRouteTable extends Construct { - public readonly id: string; - - static fromRouteTableId(scope: Construct, id: string, routeTableId: string) { - class Import extends Construct { - public readonly id = routeTableId; - constructor(scope: Construct, id: string) { - super(scope, id); - } - } - return new Import(scope, id); - } - - constructor(scope: Construct, id: string, props: TransitGatewayRouteTableProps) { - super(scope, id); - - const routeTable = new ec2.CfnTransitGatewayRouteTable(this, pascalCase(`${props.name}TransitGatewayRouteTable`), { - transitGatewayId: props.transitGatewayId, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', props.name); - - this.id = routeTable.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-static-route.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-static-route.ts deleted file mode 100644 index 144cf11..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway-static-route.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { LzaCustomResource } from '../lza-custom-resource'; - -export interface TransitGatewayStaticRouteProps { - /** - * The CIDR block for the route. - */ - readonly destinationCidrBlock: string; - /** - * The ID of the transit gateway route table. - */ - readonly transitGatewayRouteTableId: string; - /** - * Determines if route is blackholed. - */ - readonly blackhole?: boolean; - /** - * Custom resource handler for cross-account TGW associations - */ - readonly customResourceHandler?: cdk.aws_lambda.IFunction; - /** - * Owning account ID for cross-account TGW associations - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account TGW associations - */ - readonly owningRegion?: string; - /** - * Role name for cross-account TGW associations - */ - readonly roleName?: string; - /** - * The identifier of the Transit Gateway Attachment - */ - readonly transitGatewayAttachmentId?: string; -} - -/** - * Creates a Transit Gateway Static Route - */ -export class TransitGatewayStaticRoute extends Construct { - constructor(scope: Construct, id: string, props: TransitGatewayStaticRouteProps) { - super(scope, id); - - if (!props.customResourceHandler) { - new cdk.aws_ec2.CfnTransitGatewayRoute(this, 'StaticRoute', { - transitGatewayRouteTableId: props.transitGatewayRouteTableId, - blackhole: props.blackhole, - destinationCidrBlock: props.destinationCidrBlock, - transitGatewayAttachmentId: props.transitGatewayAttachmentId, - }); - } else { - new LzaCustomResource(this, 'CustomResource', { - resource: { - name: 'CustomResource', - parentId: id, - properties: [ - { - destinationCidrBlock: props.destinationCidrBlock, - transitGatewayRouteTableId: props.transitGatewayRouteTableId, - blackhole: props.blackhole, - transitGatewayAttachmentId: props.transitGatewayAttachmentId, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - }, - ], - onEventHandler: props.customResourceHandler, - }, - }); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway.ts deleted file mode 100644 index 574edd1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/transit-gateway.ts +++ /dev/null @@ -1,530 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -import { TransitGatewayAttachmentOptionsConfig } from '@aws-accelerator/config'; -import { LzaCustomResource } from '../lza-custom-resource'; - -const path = require('path'); - -export interface ITransitGatewayRouteTableAssociation extends cdk.IResource { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayRouteTableId: string; -} - -export interface TransitGatewayRouteTableAssociationProps { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayRouteTableId: string; - /** - * Custom resource handler for cross-account TGW associations - */ - readonly customResourceHandler?: cdk.aws_lambda.IFunction; - /** - * Owning account ID for cross-account TGW associations - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account TGW associations - */ - readonly owningRegion?: string; - /** - * Role name for cross-account TGW associations - */ - readonly roleName?: string; -} - -export class TransitGatewayRouteTableAssociation extends cdk.Resource implements ITransitGatewayRouteTableAssociation { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayRouteTableId: string; - - constructor(scope: Construct, id: string, props: TransitGatewayRouteTableAssociationProps) { - super(scope, id); - - this.transitGatewayAttachmentId = props.transitGatewayAttachmentId; - this.transitGatewayRouteTableId = props.transitGatewayRouteTableId; - - if (!props.customResourceHandler) { - new cdk.aws_ec2.CfnTransitGatewayRouteTableAssociation(this, 'Resource', { - transitGatewayAttachmentId: props.transitGatewayAttachmentId, - transitGatewayRouteTableId: props.transitGatewayRouteTableId, - }); - } else { - new LzaCustomResource(this, 'CustomResource', { - resource: { - name: 'CustomResource', - parentId: id, - properties: [ - { - transitGatewayAttachmentId: props.transitGatewayAttachmentId, - transitGatewayRouteTableId: props.transitGatewayRouteTableId, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - }, - ], - onEventHandler: props.customResourceHandler, - }, - }); - } - } -} - -export interface ITransitGatewayRouteTablePropagation extends cdk.IResource { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayRouteTableId: string; -} - -export interface TransitGatewayRouteTablePropagationProps { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayRouteTableId: string; - /** - * Custom resource handler for cross-account TGW propagations - */ - readonly customResourceHandler?: cdk.aws_lambda.IFunction; - /** - * Owning account ID for cross-account TGW propagations - */ - readonly owningAccountId?: string; - /** - * Owning region for cross-account TGW propagations - */ - readonly owningRegion?: string; - /** - * Role name for cross-account TGW propagations - */ - readonly roleName?: string; -} - -export class TransitGatewayRouteTablePropagation extends cdk.Resource implements ITransitGatewayRouteTablePropagation { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayRouteTableId: string; - - constructor(scope: Construct, id: string, props: TransitGatewayRouteTablePropagationProps) { - super(scope, id); - - this.transitGatewayAttachmentId = props.transitGatewayAttachmentId; - this.transitGatewayRouteTableId = props.transitGatewayRouteTableId; - - if (!props.customResourceHandler) { - new cdk.aws_ec2.CfnTransitGatewayRouteTablePropagation(this, 'Resource', { - transitGatewayAttachmentId: props.transitGatewayAttachmentId, - transitGatewayRouteTableId: props.transitGatewayRouteTableId, - }); - } else { - new LzaCustomResource(this, 'CustomResource', { - resource: { - name: 'CustomResource', - parentId: id, - properties: [ - { - transitGatewayAttachmentId: props.transitGatewayAttachmentId, - transitGatewayRouteTableId: props.transitGatewayRouteTableId, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - }, - ], - onEventHandler: props.customResourceHandler, - }, - }); - } - } -} - -export interface ITransitGatewayAttachment extends cdk.IResource { - readonly transitGatewayAttachmentId: string; - readonly transitGatewayAttachmentName: string; - - addDependency: (dependent: Construct) => void; -} - -export interface TransitGatewayAttachmentProps { - readonly name: string; - readonly partition: string; - readonly transitGatewayId: string; - readonly subnetIds: string[]; - readonly vpcId: string; - readonly options?: TransitGatewayAttachmentOptionsConfig; - readonly tags?: cdk.CfnTag[]; -} - -export enum TransitGatewayAttachmentType { - DXGW = 'direct-connect-gateway', - PEERING = 'peering', - VPC = 'vpc', - VPN = 'vpn', -} - -export interface TransitGatewayAttachmentLookupOptions { - readonly name: string; - readonly owningAccountId: string; - readonly transitGatewayId: string; - readonly type: TransitGatewayAttachmentType; - readonly roleName?: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Cross-account lookup options - * - * @remarks - * These options should only be used for cross-account VPN attachment - * lookups. Currently the only use case is for dynamic EC2 firewall - * VPN connections - */ - readonly crossAccountVpnOptions?: { - /** - * Owning account ID of the VPN attachment - */ - readonly owningAccountId?: string; - /** - * Owning region of the VPN attachment - */ - readonly owningRegion?: string; - /** - * Role name to assume - */ - readonly roleName?: string; - }; -} - -abstract class TransitGatewayAttachmentBase extends cdk.Resource implements ITransitGatewayAttachment { - public abstract readonly transitGatewayAttachmentId: string; - public abstract readonly transitGatewayAttachmentName: string; - - addDependency(dependent: Construct) { - dependent.node.addDependency(this); - } -} - -export class TransitGatewayAttachment extends TransitGatewayAttachmentBase { - public static fromLookup( - scope: Construct, - id: string, - options: TransitGatewayAttachmentLookupOptions, - ): ITransitGatewayAttachment { - class Import extends TransitGatewayAttachmentBase { - public readonly transitGatewayAttachmentId: string; - public readonly transitGatewayAttachmentName = options.name; - - constructor(scope: Construct, id: string) { - super(scope, id); - - const GET_TRANSIT_GATEWAY_ATTACHMENT = 'Custom::GetTransitGatewayAttachment'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, GET_TRANSIT_GATEWAY_ATTACHMENT, { - codeDirectory: path.join(__dirname, 'get-transit-gateway-attachment/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['ec2:DescribeTransitGatewayAttachments', 'ec2:DescribeVpnConnections'], - Resource: '*', - }, - ], - }); - - // Construct role arn if this is a cross-account lookup - let roleArn: string | undefined = undefined; - if (options.roleName) { - roleArn = cdk.Stack.of(this).formatArn({ - service: 'iam', - region: '', - account: options.owningAccountId, - resource: 'role', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: options.roleName, - }); - } - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: GET_TRANSIT_GATEWAY_ATTACHMENT, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - name: options.name, - transitGatewayId: options.transitGatewayId, - type: options.type, - roleArn, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - crossAccountVpnOptions: options.crossAccountVpnOptions, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: options.logRetentionInDays, - encryptionKey: options.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.transitGatewayAttachmentId = resource.ref; - } - } - return new Import(scope, id); - } - - public static fromTransitGatewayAttachmentId( - scope: Construct, - id: string, - options: { - attachmentId: string; - attachmentName: string; - }, - ): ITransitGatewayAttachment { - class Import extends TransitGatewayAttachmentBase { - public readonly transitGatewayAttachmentId: string; - public readonly transitGatewayAttachmentName = options.attachmentName; - constructor(scope: Construct, id: string) { - super(scope, id); - this.transitGatewayAttachmentId = options.attachmentId; - } - } - return new Import(scope, id); - } - - public readonly transitGatewayAttachmentId: string; - public readonly transitGatewayAttachmentName: string; - - constructor(scope: Construct, id: string, props: TransitGatewayAttachmentProps) { - super(scope, id); - - let resource: cdk.aws_ec2.CfnTransitGatewayVpcAttachment | cdk.aws_ec2.CfnTransitGatewayAttachment; - switch (props.partition) { - case 'aws': - resource = new cdk.aws_ec2.CfnTransitGatewayVpcAttachment(this, 'Resource', { - vpcId: props.vpcId, - transitGatewayId: props.transitGatewayId, - subnetIds: props.subnetIds, - options: { - ApplianceModeSupport: props.options?.applianceModeSupport ?? 'disable', - DnsSupport: props.options?.dnsSupport ?? 'enable', - Ipv6Support: props.options?.ipv6Support ?? 'disable', - }, - tags: props.tags, - }); - break; - case 'aws-us-gov': - resource = new cdk.aws_ec2.CfnTransitGatewayAttachment(this, 'Resource', { - vpcId: props.vpcId, - transitGatewayId: props.transitGatewayId, - subnetIds: props.subnetIds, - options: { - ApplianceModeSupport: props.options?.applianceModeSupport ?? 'disable', - DnsSupport: props.options?.dnsSupport ?? 'enable', - Ipv6Support: props.options?.ipv6Support ?? 'disable', - }, - tags: props.tags, - }); - break; - default: - resource = new cdk.aws_ec2.CfnTransitGatewayAttachment(this, 'Resource', { - vpcId: props.vpcId, - transitGatewayId: props.transitGatewayId, - subnetIds: props.subnetIds, - tags: props.tags, - }); - break; - } - // Add name tag - cdk.Tags.of(this).add('Name', props.name); - - this.transitGatewayAttachmentId = resource.ref; - this.transitGatewayAttachmentName = props.name; - } -} - -export interface ITransitGateway extends cdk.IResource { - /** - * The identifier of the transit gateway - * - * @attribute - */ - readonly transitGatewayId: string; - - /** - * The name of the transit gateway - * - * @attribute - */ - readonly transitGatewayName: string; - - /** - * The ARN of the transit gateway - * - * @attribute - */ - readonly transitGatewayArn: string; -} - -export interface TransitGatewayProps { - /** - * The name of the transit gateway. Will be assigned to the Name tag - */ - readonly name: string; - - /** - * A private Autonomous System Number (ASN) for the Amazon side of a BGP session. The range is - * 64512 to 65534 for 16-bit ASNs. The default is 64512. - */ - readonly amazonSideAsn?: number; - - /** - * Enable or disable automatic acceptance of attachment requests. Disabled by default. - */ - readonly autoAcceptSharedAttachments?: string; - - /** - * Enable or disable automatic association with the default association route table. Enabled by - * default. - */ - readonly defaultRouteTableAssociation?: string; - - /** - * Enable or disable automatic propagation of routes to the default propagation route table. - * Enabled by default. - */ - readonly defaultRouteTablePropagation?: string; - - /** - * The description of the transit gateway. - */ - readonly description?: string; - - /** - * Enable or disable DNS support. Enabled by default. - */ - readonly dnsSupport?: string; - - /** - * Indicates whether multicast is enabled on the transit gateway - */ - readonly multicastSupport?: string; - - /** - * Enable or disable Equal Cost Multipath Protocol support. Enabled by default. - */ - readonly vpnEcmpSupport?: string; - - /** - * A list of static CIDRs for the Transit Gateway. - */ - readonly transitGatewayCidrBlocks?: string[]; - /** - * Tags that will be attached to the transit gateway - */ - readonly tags?: cdk.CfnTag[]; -} - -interface TransitGatewayAttributes { - /** - * The ID of the TransitGateway. - */ - transitGatewayId: string; - /** - * The Name of the TransitGateway. - */ - transitGatewayName: string; -} - -abstract class TransitGatewayBase extends cdk.Resource implements ITransitGateway { - public abstract readonly transitGatewayId: string; - public abstract readonly transitGatewayName: string; - public abstract readonly transitGatewayArn: string; -} - -export class ImportedTransitGateway extends TransitGatewayBase { - readonly transitGatewayId: string; - - readonly transitGatewayName: string; - - readonly transitGatewayArn: string; - - constructor(scope: Construct, id: string, props: TransitGatewayAttributes) { - super(scope, id); - - this.transitGatewayId = props.transitGatewayId; - - this.transitGatewayName = props.transitGatewayName; - - this.transitGatewayArn = cdk.Stack.of(this).formatArn({ - service: 'ec2', - resource: 'transit-gateway', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: this.transitGatewayId, - }); - } -} - -/** - * Creates a Transit Gateway - */ -export class TransitGateway extends TransitGatewayBase { - readonly transitGatewayId: string; - - readonly transitGatewayName: string; - - readonly transitGatewayArn: string; - - constructor(scope: Construct, id: string, props: TransitGatewayProps) { - super(scope, id); - - const resource = new cdk.aws_ec2.CfnTransitGateway(this, 'Resource', { - amazonSideAsn: props.amazonSideAsn, - autoAcceptSharedAttachments: props.autoAcceptSharedAttachments, - defaultRouteTableAssociation: props.defaultRouteTableAssociation, - defaultRouteTablePropagation: props.defaultRouteTablePropagation, - dnsSupport: props.dnsSupport, - transitGatewayCidrBlocks: props.transitGatewayCidrBlocks, - vpnEcmpSupport: props.vpnEcmpSupport, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', props.name); - - this.transitGatewayId = resource.ref; - - this.transitGatewayName = props.name; - - this.transitGatewayArn = cdk.Stack.of(this).formatArn({ - service: 'ec2', - resource: 'transit-gateway', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: this.transitGatewayId, - }); - } - - static fromTransitGatewayAttributes(scope: Construct, id: string, attrs: TransitGatewayAttributes) { - return new ImportedTransitGateway(scope, id, attrs); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-endpoint.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-endpoint.ts deleted file mode 100644 index 798b92d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-endpoint.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { ISecurityGroup } from './vpc'; - -export interface IVpcEndpoint extends cdk.IResource { - readonly vpcEndpointId: string; - readonly service: string; - readonly vpcId: string; - readonly dnsName?: string; - readonly hostedZoneId?: string; - - createEndpointRoute: (id: string, routeTableId: string, destination?: string, ipv6Destination?: string) => void; -} - -export enum VpcEndpointType { - INTERFACE = 'Interface', - GATEWAY = 'Gateway', - GWLB = 'GatewayLoadBalancer', -} - -export interface VpcEndpointProps { - readonly vpcEndpointType: VpcEndpointType; - readonly service: string; - readonly vpcId: string; - readonly subnets?: string[]; - readonly securityGroups?: ISecurityGroup[]; - readonly privateDnsEnabled?: boolean; - readonly policyDocument?: cdk.aws_iam.PolicyDocument; - readonly routeTables?: string[]; - readonly partition?: string; - readonly serviceName?: string; -} - -abstract class VpcEndpointBase extends cdk.Resource implements IVpcEndpoint { - public abstract readonly vpcEndpointId: string; - public abstract readonly vpcId: string; - public abstract readonly service: string; - public abstract readonly dnsName?: string; - public abstract readonly hostedZoneId?: string; - - public createEndpointRoute(id: string, routeTableId: string, destination?: string, ipv6Destination?: string): void { - new cdk.aws_ec2.CfnRoute(this, id, { - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - routeTableId, - vpcEndpointId: this.vpcEndpointId, - }); - } -} - -interface VpcEndpointAttributes { - vpcId: string; - vpcEndpointId: string; - service: string; -} - -export class VpcEndpoint extends VpcEndpointBase { - public readonly vpcEndpointId: string; - public readonly vpcId: string; - public readonly service: string; - public readonly dnsName?: string; - public readonly hostedZoneId?: string; - - static fromAttributes(scope: Construct, id: string, attrs: VpcEndpointAttributes): IVpcEndpoint { - class Import extends VpcEndpointBase { - public readonly vpcEndpointId = attrs.vpcEndpointId; - public readonly vpcId = attrs.vpcId; - public readonly service = attrs.service; - public readonly dnsName?: string; - public readonly hostedZoneId?: string; - constructor(scope: Construct, id: string) { - super(scope, id); - } - } - return new Import(scope, id); - } - - constructor(scope: Construct, id: string, props: VpcEndpointProps) { - super(scope, id); - - this.service = props.service; - this.vpcId = props.vpcId; - - // Add constant for sagemaker conditionals - const sagemakerArray = ['notebook', 'studio']; - - if (props.vpcEndpointType === VpcEndpointType.INTERFACE) { - let serviceName = `com.amazonaws.${cdk.Stack.of(this).region}.${props.service}`; - if (sagemakerArray.includes(this.service)) { - serviceName = `aws.sagemaker.${cdk.Stack.of(this).region}.${props.service}`; - } - if (this.service === 's3-global.accesspoint') { - serviceName = `com.amazonaws.${props.service}`; - } - // Add the ability against China region to override serviceName due to the prefix of - // serviceName is inconsistent (com.amazonaws vs cn.com.amazonaws) for VPC interface - // endpoints in that region. - if (props.serviceName) { - serviceName = props.serviceName; - } - const resource = new cdk.aws_ec2.CfnVPCEndpoint(this, 'Resource', { - serviceName, - vpcEndpointType: props.vpcEndpointType, - vpcId: this.vpcId, - subnetIds: props.subnets, - securityGroupIds: props.securityGroups?.map(item => item.securityGroupId), - privateDnsEnabled: props.privateDnsEnabled, - policyDocument: props.policyDocument, - }); - this.vpcEndpointId = resource.ref; - - let dnsEntriesIndex = 0; - if (sagemakerArray.includes(this.service)) { - dnsEntriesIndex = 4; - } - - this.dnsName = cdk.Fn.select(1, cdk.Fn.split(':', cdk.Fn.select(dnsEntriesIndex, resource.attrDnsEntries))); - this.hostedZoneId = cdk.Fn.select(0, cdk.Fn.split(':', cdk.Fn.select(dnsEntriesIndex, resource.attrDnsEntries))); - return; - } else if (props.vpcEndpointType === VpcEndpointType.GATEWAY) { - let serviceName = new cdk.aws_ec2.GatewayVpcEndpointAwsService(props.service).name; - if (props.serviceName) { - serviceName = props.serviceName; - } - const resource = new cdk.aws_ec2.CfnVPCEndpoint(this, 'Resource', { - serviceName, - vpcId: this.vpcId, - routeTableIds: props.routeTables, - policyDocument: props.policyDocument, - }); - this.vpcEndpointId = resource.ref; - return; - } else { - const servicePrefix = props.partition === 'aws-cn' ? 'cn.com.amazonaws' : 'com.amazonaws'; - const serviceName = `${servicePrefix}.vpce.${cdk.Stack.of(this).region}.${props.service}`; - - const resource = new cdk.aws_ec2.CfnVPCEndpoint(this, 'Resource', { - serviceName, - vpcEndpointType: props.vpcEndpointType, - vpcId: this.vpcId, - subnetIds: props.subnets, - }); - this.vpcEndpointId = resource.ref; - return; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-id-lookup.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-id-lookup.ts deleted file mode 100644 index 0ab0770..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-id-lookup.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import * as path from 'path'; - -/** - * Initialized VpcIdLookupProps properties - */ -export interface VpcIdLookupProps { - /** - * Vpc name - */ - readonly vpcName: string; - /** - * Custom resource lambda key, when undefined default AWS managed key will be used - */ - readonly lambdaKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudwatchKey?: cdk.aws_kms.IKey; - /** - * Custom resource CloudWatch log retention in days - */ - readonly cloudwatchLogRetentionInDays: number; -} - -/** - * Vpc id lookup class. - */ -export class VpcIdLookup extends Construct { - public readonly vpcId: string; - constructor(scope: Construct, id: string, props: VpcIdLookupProps) { - super(scope, id); - - const providerLambda = new cdk.aws_lambda.Function(this, 'VpcIdLookupFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'get-vpc-id/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - description: 'Lookup vpc id from account', - environmentEncryption: props.lambdaKey, - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Ec2Actions', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ec2:DescribeVpcs'], - resources: ['*'], - }), - ); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.cloudwatchLogRetentionInDays, - encryptionKey: props.cloudwatchKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'VpcIdLookupProvider', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::VpcIdLookup', - serviceToken: provider.serviceToken, - properties: { - vpcName: props.vpcName, - }, - }); - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.vpcId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-peering.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-peering.ts deleted file mode 100644 index c25fae5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc-peering.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { CrossAccountRoute } from './cross-account-route'; -import { PrefixListRoute } from './prefix-list-route'; - -export interface IVpcPeering extends cdk.IResource { - /** - * The name of the peering connection. - */ - readonly name: string; - - /** - * The ID of the VPC peering connection. - */ - readonly peeringId: string; -} - -export interface VpcPeeringProps { - /** - * The name of the peering connection. - */ - readonly name: string; - /** - * The AWS account ID of the owner of the accepter VPC. - */ - readonly peerOwnerId: string; - - /** - * The Region code for the accepter VPC, if the accepter VPC is - * located in a Region other than the Region in which you make the request. - */ - readonly peerRegion: string; - /** - * The ID of the VPC with which you are creating the VPC peering connection. - */ - readonly peerVpcId: string; - - /** - * The ID of the VPC creating the connection request. - */ - readonly vpcId: string; - - /** - * The name of the VPC peer role for the - * peering connection in another AWS account. - */ - readonly peerRoleName?: string; - - /** - * An optional list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -export interface PeeringAttributes { - /** - * VPC Peering Connection ID - */ - peeringId: string; - /** - * VPC Peering Connection Name - */ - name: string; -} - -abstract class VpcPeeringBase extends cdk.Resource implements IVpcPeering { - public abstract readonly name: string; - public abstract readonly peeringId: string; - - public addPeeringRoute( - id: string, - routeTableId: string, - destination?: string, - destinationPrefixListId?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - logRetentionInDays?: number, - ): void { - if (destinationPrefixListId) { - if (!logRetentionInDays) { - throw new Error('Attempting to add prefix list route without specifying log group retention period'); - } - - new PrefixListRoute(this, id, { - routeTableId, - destinationPrefixListId, - logGroupKmsKey, - logRetentionInDays, - vpcPeeringConnectionId: this.peeringId, - }); - } else { - if (!destination && !ipv6Destination) { - throw new Error('Attempting to add CIDR route without specifying destination'); - } - - new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId: routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - vpcPeeringConnectionId: this.peeringId, - }); - } - } - - public addCrossAcctPeeringRoute(props: { - id: string; - ownerAccount: string; - ownerRegion: string; - partition: string; - provider: cdk.custom_resources.Provider; - roleName: string; - routeTableId: string; - destination?: string; - destinationPrefixListId?: string; - ipv6Destination?: string; - }): void { - new CrossAccountRoute(this, props.id, { - ownerAccount: props.ownerAccount, - ownerRegion: props.ownerRegion, - partition: props.partition, - provider: props.provider, - roleName: props.roleName, - routeTableId: props.routeTableId, - destination: props.destination, - destinationPrefixListId: props.destinationPrefixListId, - ipv6Destination: props.ipv6Destination, - vpcPeeringConnectionId: this.peeringId, - }); - } -} - -export class ImportedVpcPeering extends VpcPeeringBase { - public readonly name: string; - public readonly peeringId: string; - constructor(scope: Construct, id: string, attrs: PeeringAttributes) { - super(scope, id); - this.name = attrs.name; - this.peeringId = attrs.peeringId; - } -} - -export class VpcPeering extends VpcPeeringBase { - public readonly name: string; - public readonly peeringId: string; - - constructor(scope: Construct, id: string, props: VpcPeeringProps) { - super(scope, id); - // Set name tag - this.name = props.name; - props.tags?.push({ key: 'Name', value: this.name }); - - const peerRoleArn = - props.peerRoleName && `arn:${cdk.Stack.of(this).partition}:iam::${props.peerOwnerId}:role/${props.peerRoleName}`; - - const resource = new cdk.aws_ec2.CfnVPCPeeringConnection(this, 'Resource', { - peerOwnerId: props.peerOwnerId, - peerRegion: props.peerRegion, - peerVpcId: props.peerVpcId, - vpcId: props.vpcId, - peerRoleArn, - tags: props.tags, - }); - - this.peeringId = resource.ref; - } - - public static fromPeeringAttributes(scope: Construct, id: string, attrs: PeeringAttributes) { - return new ImportedVpcPeering(scope, id, attrs); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc.ts deleted file mode 100644 index 020c84a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpc.ts +++ /dev/null @@ -1,1065 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; - -import { IpamAllocationConfig, OutpostsConfig, VirtualPrivateGatewayConfig } from '@aws-accelerator/config'; - -import { IpamSubnet } from './ipam-subnet'; -import { IPrefixList } from './prefix-list'; -import { IRouteTable } from './route-table'; -import { VpnConnection } from './vpn-connection'; - -export interface ISubnet extends cdk.IResource { - /** - * The identifier of the subnet - * - * @attribute - */ - readonly subnetId: string; - - /** - * The identifier of the subnet - * - * @attribute - */ - readonly subnetArn: string; - - /** - * The name of the subnet - * - * @attribute - */ - readonly subnetName: string; - - /** - * The CIDR Block of the subnet - */ - readonly ipv4CidrBlock?: string; - - /** - * The Availability Zone the subnet is located in - * - * @attribute - */ - readonly availabilityZone?: string; - - /** - * The Physical Availability Zone ID the subnet is located in - * - * @attribute - */ - readonly availabilityZoneId?: string; - - /** - * The IPV6 CIDR Block of the subnet - */ - readonly ipv6CidrBlock?: string; -} - -interface SubnetPrivateDnsOptions { - readonly enableDnsAAAARecord?: boolean; - readonly enableDnsARecord?: boolean; - readonly hostnameType?: 'ip-name' | 'resource-name'; -} - -export interface SubnetProps { - readonly name: string; - readonly vpc: IVpc; - readonly assignIpv6OnCreation?: boolean; - readonly availabilityZone?: string; - readonly availabilityZoneId?: string; - readonly basePool?: string[]; - readonly enableDns64?: boolean; - readonly ipamAllocation?: IpamAllocationConfig; - readonly ipv4CidrBlock?: string; - readonly ipv6CidrBlock?: string; - readonly kmsKey?: cdk.aws_kms.IKey; - readonly logRetentionInDays?: number; - readonly mapPublicIpOnLaunch?: boolean; - readonly outpost?: OutpostsConfig; - readonly privateDnsOptions?: SubnetPrivateDnsOptions; - readonly routeTable?: IRouteTable; - readonly tags?: cdk.CfnTag[]; -} - -export interface ImportedSubnetProps { - readonly subnetId: string; - readonly name: string; - readonly routeTable?: IRouteTable; - readonly ipv4CidrBlock: string; -} - -abstract class SubnetBase extends cdk.Resource implements ISubnet { - public abstract readonly subnetName: string; - public abstract readonly subnetId: string; - public abstract readonly subnetArn: string; - public readonly availabilityZone?: string; - public readonly availabilityZoneId?: string; - public abstract readonly routeTable?: IRouteTable; -} - -export class ImportedSubnet extends SubnetBase { - public readonly subnetName: string; - public readonly routeTable?: IRouteTable; - public readonly subnetId: string; - public readonly subnetArn: string; - public readonly ipv4CidrBlock?: string; - - constructor(scope: Construct, id: string, props: ImportedSubnetProps) { - super(scope, id); - - this.subnetName = props.name; - this.routeTable = props.routeTable; - this.subnetId = props.subnetId; - this.subnetArn = cdk.Stack.of(this).formatArn({ - service: 'ec2', - resource: 'subnet', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: props.subnetId, - }); - - if (props.ipv4CidrBlock) { - this.ipv4CidrBlock = props.ipv4CidrBlock; - } - - if (props.routeTable) { - // Route Table is not imported, Associating Subnet to new RouteTable - new cdk.aws_ec2.CfnSubnetRouteTableAssociation(this, 'RouteTableAssociation', { - subnetId: this.subnetId, - routeTableId: props.routeTable.routeTableId, - }); - } - } -} -export class Subnet extends SubnetBase { - public readonly subnetArn: string; - public readonly subnetId: string; - public readonly subnetName: string; - public readonly availabilityZone?: string; - public readonly availabilityZoneId?: string; - public readonly ipv4CidrBlock?: string; - public readonly ipv6CidrBlock?: string; - public readonly mapPublicIpOnLaunch?: boolean; - public readonly routeTable?: IRouteTable; - - public readonly outpostArn?: string; - - constructor(scope: Construct, id: string, props: SubnetProps) { - super(scope, id); - - this.subnetName = props.name; - this.availabilityZone = props.availabilityZone; - this.availabilityZoneId = props.availabilityZoneId; - this.mapPublicIpOnLaunch = props.mapPublicIpOnLaunch; - this.routeTable = props.routeTable; - this.outpostArn = props.outpost?.arn; - - // Determine if IPAM subnet or native - let resource: cdk.aws_ec2.CfnSubnet | IpamSubnet; - - if (props.ipv4CidrBlock || props.ipv6CidrBlock) { - this.ipv4CidrBlock = props.ipv4CidrBlock; - this.ipv6CidrBlock = props.ipv6CidrBlock; - - const ipv6Native = this.ipv6CidrBlock !== undefined && !this.ipv4CidrBlock ? true : undefined; - const privateDnsNameOptionsOnLaunch = props.privateDnsOptions - ? { - EnableResourceNameDnsAAAARecord: props.privateDnsOptions?.enableDnsAAAARecord, - EnableResourceNameDnsARecord: props.privateDnsOptions?.enableDnsARecord, - HostnameType: props.privateDnsOptions?.hostnameType, - } - : undefined; - - resource = new cdk.aws_ec2.CfnSubnet(this, 'Resource', { - vpcId: props.vpc.vpcId, - assignIpv6AddressOnCreation: props.assignIpv6OnCreation, - availabilityZone: props.availabilityZone, - availabilityZoneId: props.availabilityZoneId, - cidrBlock: props.ipv4CidrBlock, - enableDns64: props.enableDns64, - ipv6CidrBlock: props.ipv6CidrBlock, - ipv6Native, - mapPublicIpOnLaunch: props.mapPublicIpOnLaunch, - outpostArn: props.outpost?.arn, - privateDnsNameOptionsOnLaunch, - tags: props.tags, - }); - - cdk.Tags.of(this).add('Name', props.name); - this.subnetId = resource.ref; - this.subnetArn = cdk.Stack.of(this).formatArn({ - service: 'ec2', - resource: 'subnet', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: resource.ref, - }); - } else { - if (!props.basePool) { - throw new Error( - `Error creating subnet ${props.name}: must specify basePool property when using ipamAllocation`, - ); - } - if (!props.ipamAllocation) { - throw new Error( - `Error creating subnet ${props.name}: ipamAllocation property must be defined if not specifying ipv4CidrBlock`, - ); - } - if (!props.logRetentionInDays) { - throw new Error( - `Error creating subnet ${props.name}: logRetentionInDays property must be defined if not specifying ipv4CidrBlock`, - ); - } - - resource = new IpamSubnet(this, 'Resource', { - name: props.name, - availabilityZone: props.availabilityZone, - availabilityZoneId: props.availabilityZoneId, - basePool: props.basePool, - ipamAllocation: props.ipamAllocation, - vpcId: props.vpc.vpcId, - mapPublicIpOnLaunch: props.mapPublicIpOnLaunch, - kmsKey: props.kmsKey, - logRetentionInDays: props.logRetentionInDays, - tags: props.tags, - outpostArn: props.outpost?.arn, - }); - - this.ipv4CidrBlock = resource.ipv4CidrBlock; - this.subnetId = resource.subnetId; - this.subnetArn = cdk.Stack.of(this).formatArn({ - service: 'ec2', - resource: 'subnet', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: resource.subnetId, - }); - } - - if (props.routeTable) { - new cdk.aws_ec2.CfnSubnetRouteTableAssociation(this, 'RouteTableAssociation', { - subnetId: this.subnetId, - routeTableId: props.routeTable.routeTableId, - }); - } - } - - static fromSubnetAttributes(scope: Construct, id: string, props: ImportedSubnetProps) { - return new ImportedSubnet(scope, id, props); - } -} - -export interface INatGateway extends cdk.IResource { - /** - * The identifier of the NAT Gateway - * - * @attribute - */ - readonly natGatewayId: string; - - /** - * The name of the NAT Gateway - * - * @attribute - */ - readonly natGatewayName: string; -} - -export interface NatGatewayProps { - readonly name: string; - readonly subnet: ISubnet; - readonly allocationId?: string; - readonly private?: boolean; - readonly tags?: cdk.CfnTag[]; -} - -export class NatGateway extends cdk.Resource implements INatGateway { - public readonly natGatewayId: string; - public readonly natGatewayName: string; - private natGatewayArgs?: cdk.aws_ec2.CfnNatGatewayProps; - - static fromAttributes( - scope: Construct, - id: string, - attrs: { natGatewayId: string; natGatewayName: string }, - ): INatGateway { - class Import extends cdk.Resource implements INatGateway { - public readonly natGatewayId: string = attrs.natGatewayId; - public readonly natGatewayName: string = attrs.natGatewayName; - - constructor(scope: Construct, id: string) { - super(scope, id); - } - } - return new Import(scope, id); - } - - constructor(scope: Construct, id: string, props: NatGatewayProps) { - super(scope, id); - - this.natGatewayName = props.name; - - this.natGatewayArgs = { - subnetId: props.subnet.subnetId, - allocationId: props.private ? undefined : this.getAllocationId(props), - connectivityType: props.private ? 'private' : undefined, - tags: props.tags, - }; - - const resource = new cdk.aws_ec2.CfnNatGateway(this, 'Resource', this.natGatewayArgs); - cdk.Tags.of(this).add('Name', props.name); - this.natGatewayId = resource.ref; - } - - /** - * Return allocation ID for a public NAT gateway - * @param props - * @returns - */ - private getAllocationId(props: NatGatewayProps): string { - return props.allocationId - ? props.allocationId - : new cdk.aws_ec2.CfnEIP(this, 'Eip', { - domain: 'vpc', - }).attrAllocationId; - } -} -export interface ISecurityGroup extends cdk.IResource { - /** - * ID for the current security group - * @attribute - */ - readonly securityGroupId: string; -} - -export interface SecurityGroupProps { - /** - * The name of the security group. For valid values, see the GroupName - * parameter of the CreateSecurityGroup action in the Amazon EC2 API - * Reference. - * - * It is not recommended to use an explicit group name. - * - * @default If you don't specify a GroupName, AWS CloudFormation generates a - * unique physical ID and uses that ID for the group name. - */ - readonly securityGroupName?: string; - - /** - * A description of the security group. - * - * @default The default name will be the construct's CDK path. - */ - readonly description?: string; - - /** - * The outbound rules associated with the security group. - */ - readonly securityGroupEgress?: SecurityGroupEgressRuleProps[]; - - /** - * The inbound rules associated with the security group. - */ - readonly securityGroupIngress?: SecurityGroupIngressRuleProps[]; - - /** - * The tags that will be attached to the security group - */ - readonly tags?: cdk.CfnTag[]; - - /** - * The VPC in which to create the security group. - */ - readonly vpc?: IVpc; - - /** - * The VPC in which to create the security group. - */ - readonly vpcId?: string; -} - -export interface SecurityGroupIngressRuleProps { - readonly ipProtocol: string; - readonly description?: string; - readonly cidrIp?: string; - readonly cidrIpv6?: string; - readonly sourcePrefixList?: IPrefixList; - readonly sourcePrefixListId?: string; - readonly sourceSecurityGroup?: ISecurityGroup; - readonly fromPort?: number; - readonly toPort?: number; -} - -export interface SecurityGroupEgressRuleProps { - readonly ipProtocol: string; - readonly description?: string; - readonly cidrIp?: string; - readonly cidrIpv6?: string; - readonly destinationPrefixList?: IPrefixList; - readonly destinationPrefixListId?: string; - readonly destinationSecurityGroup?: ISecurityGroup; - readonly fromPort?: number; - readonly toPort?: number; -} - -abstract class SecurityGroupBase extends cdk.Resource implements ISecurityGroup { - public abstract readonly securityGroupId: string; - - public addIngressRule(id: string, props: SecurityGroupIngressRuleProps) { - new cdk.aws_ec2.CfnSecurityGroupIngress(this, id, { - groupId: this.securityGroupId, - ipProtocol: props.ipProtocol, - description: props.description, - cidrIp: props.cidrIp, - cidrIpv6: props.cidrIpv6, - sourcePrefixListId: props.sourcePrefixList?.prefixListId, - sourceSecurityGroupId: props.sourceSecurityGroup?.securityGroupId, - fromPort: props.fromPort, - toPort: props.toPort, - }); - } - - public addEgressRule(id: string, props: SecurityGroupEgressRuleProps) { - new cdk.aws_ec2.CfnSecurityGroupEgress(this, id, { - groupId: this.securityGroupId, - ipProtocol: props.ipProtocol, - description: props.description, - cidrIp: props.cidrIp, - cidrIpv6: props.cidrIpv6, - destinationPrefixListId: props.destinationPrefixList?.prefixListId, - destinationSecurityGroupId: props.destinationSecurityGroup?.securityGroupId, - fromPort: props.fromPort, - toPort: props.toPort, - }); - } -} - -export class ImportedSecurityGroup extends SecurityGroupBase { - public readonly securityGroupId: string; - constructor(scope: Construct, id: string) { - super(scope, id); - this.securityGroupId = id; - } -} - -export class SecurityGroup extends SecurityGroupBase { - public readonly securityGroupId: string; - - constructor(scope: Construct, id: string, props: SecurityGroupProps) { - super(scope, id); - - if (!props.vpc?.vpcId && !props.vpcId) { - throw new Error(`A property value for vpc or vpcId must be specified`); - } - - const securityGroup = new cdk.aws_ec2.CfnSecurityGroup(this, 'Resource', { - groupDescription: props.description ?? '', - securityGroupEgress: props.securityGroupEgress, - securityGroupIngress: props.securityGroupIngress, - groupName: props.securityGroupName, - vpcId: props.vpc?.vpcId ?? props.vpcId, - tags: props.tags, - }); - - if (props.securityGroupName) { - cdk.Tags.of(securityGroup).add('Name', props.securityGroupName); - } - - this.securityGroupId = securityGroup.ref; - } - - static fromSecurityGroupId(scope: Construct, id: string) { - return new ImportedSecurityGroup(scope, id); - } -} - -/** - * A NetworkAcl - * - * - */ -export interface INetworkAcl extends cdk.IResource { - /** - * ID for the current Network ACL - * @attribute - */ - readonly networkAclId: string; - - /** - * ID for the current Network ACL - * @attribute - */ - readonly networkAclName: string; -} - -/** - * A NetworkAclBase that is not created in this template - */ -abstract class NetworkAclBase extends cdk.Resource implements INetworkAcl { - public abstract readonly networkAclId: string; - public abstract readonly networkAclName: string; -} - -/** - * Properties to create NetworkAcl - */ -export interface NetworkAclProps { - /** - * The name of the NetworkAcl. - */ - readonly networkAclName: string; - - /** - * The VPC in which to create the NetworkACL. - */ - readonly vpc: IVpc; - - /** - * The tags which will be attached to the NetworkACL. - */ - readonly tags?: cdk.CfnTag[]; -} - -/** - * Define a new custom network ACL - * - * By default, will deny all inbound and outbound traffic unless entries are - * added explicitly allowing it. - */ -export class NetworkAcl extends NetworkAclBase { - /** - * The ID of the NetworkACL - * - * @attribute - */ - public readonly networkAclId: string; - - /** - * The Name of the NetworkACL - * - * @attribute - */ - public readonly networkAclName: string; - - /** - * The VPC ID for this NetworkACL - * - * @attribute - */ - public readonly networkAclVpcId: string; - - constructor(scope: Construct, id: string, props: NetworkAclProps) { - super(scope, id); - - this.networkAclName = props.networkAclName; - - const resource = new cdk.aws_ec2.CfnNetworkAcl(this, 'Resource', { - vpcId: props.vpc.vpcId, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', props.networkAclName); - - this.networkAclId = resource.ref; - this.networkAclVpcId = resource.vpcId; - } - - public associateSubnet(id: string, props: { subnet: ISubnet }) { - new cdk.aws_ec2.CfnSubnetNetworkAclAssociation(this, id, { - networkAclId: this.networkAclId, - subnetId: props.subnet.subnetId, - }); - } - - public addEntry( - id: string, - props: { - ruleNumber: number; - protocol: number; - ruleAction: 'allow' | 'deny'; - egress: boolean; - cidrBlock?: string; - icmp?: { - code?: number; - type?: number; - }; - ipv6CidrBlock?: string; - portRange?: { - from: number; - to: number; - }; - }, - ) { - new cdk.aws_ec2.CfnNetworkAclEntry(this, id, { - networkAclId: this.networkAclId, - ...props, - }); - } -} - -export interface IVpc extends cdk.IResource { - /** - * The identifier of the vpc - * - * @attribute - */ - readonly vpcId: string; - /** - * The Name of the vpc - * - * @attribute - */ - readonly name: string; - /** - * The CIDR Block of the vpc - * @remarks CloudFormation resource attribute is used. - * - * @attribute - */ - readonly cidrBlock: string; - /** - * Additional Cidrs for VPC - */ - readonly cidrs: { ipv4: cdk.aws_ec2.CfnVPCCidrBlock[]; ipv6: cdk.aws_ec2.CfnVPCCidrBlock[] }; - /** - * The EIGW ID assinged to the VPC - */ - egressOnlyIgwId?: string; - /** - * The InternetGatewayId assigned to VPC - */ - internetGatewayId?: string; - /** - * The VirtualPrivateGatewayId assigned to VPC - */ - virtualPrivateGatewayId?: string; -} - -/** - * Construction properties for a VPC object. - */ -export interface VpcProps { - readonly name: string; - readonly dhcpOptions?: string; - readonly enableDnsHostnames?: boolean; - readonly enableDnsSupport?: boolean; - readonly egressOnlyIgw?: boolean; - readonly instanceTenancy?: 'default' | 'dedicated'; - readonly internetGateway?: boolean; - readonly ipv4CidrBlock?: string; - readonly ipv4IpamPoolId?: string; - readonly ipv4NetmaskLength?: number; - readonly tags?: cdk.CfnTag[]; - readonly virtualPrivateGateway?: VirtualPrivateGatewayConfig; -} - -export interface ImportedVpcProps { - readonly name: string; - readonly vpcId: string; - readonly cidrBlock: string; - readonly internetGatewayId?: string; - readonly virtualPrivateGatewayId?: string; -} - -abstract class VpcBase extends cdk.Resource implements IVpc { - public abstract readonly name: string; - public abstract readonly vpcId: string; - public abstract readonly cidrs: { ipv4: cdk.aws_ec2.CfnVPCCidrBlock[]; ipv6: cdk.aws_ec2.CfnVPCCidrBlock[] }; - public egressOnlyIgwId?: string; - public abstract readonly cidrBlock: string; - public internetGatewayId?: string; - public virtualPrivateGatewayId?: string; - protected egressOnlyIgw: cdk.aws_ec2.CfnEgressOnlyInternetGateway | undefined; - protected internetGateway: cdk.aws_ec2.CfnInternetGateway | undefined; - protected internetGatewayAttachment: cdk.aws_ec2.CfnVPCGatewayAttachment | undefined; - protected virtualPrivateGateway: cdk.aws_ec2.VpnGateway | undefined; - protected virtualPrivateGatewayAttachment: cdk.aws_ec2.CfnVPCGatewayAttachment | undefined; - - public addInternetGatewayDependent(dependent: Construct) { - if (this.internetGatewayAttachment) { - dependent.node.addDependency(this.internetGatewayAttachment); - } - } - - public addVirtualPrivateGatewayDependent(dependent: Construct) { - if (this.virtualPrivateGatewayAttachment) { - dependent.node.addDependency(this.virtualPrivateGatewayAttachment); - } - } - - public addFlowLogs(options: { - destinations: ('s3' | 'cloud-watch-logs')[]; - trafficType: 'ALL' | 'REJECT' | 'ACCEPT'; - maxAggregationInterval: number; - logFormat?: string; - logRetentionInDays?: number; - encryptionKey?: cdk.aws_kms.IKey; - bucketArn?: string; - useExistingRoles: boolean; - acceleratorPrefix: string; - overrideS3LogPath?: string; - }) { - // Validate maxAggregationInterval - const maxAggregationInterval = options.maxAggregationInterval; - if (maxAggregationInterval != 60 && maxAggregationInterval != 600) { - throw new Error(`Invalid maxAggregationInterval (${maxAggregationInterval}) - must be 60 or 600 seconds`); - } - - // Destination: CloudWatch Logs - if (options.destinations.includes('cloud-watch-logs')) { - if (!options.logRetentionInDays) { - throw new Error('logRetentionInDays not provided for cwl flow log'); - } - - const logGroup = new cdk.aws_logs.LogGroup(this, 'FlowLogsGroup', { - encryptionKey: options.encryptionKey, - retention: options.logRetentionInDays, - }); - - new cdk.aws_ec2.CfnFlowLog(this, 'CloudWatchFlowLog', { - deliverLogsPermissionArn: this.createVpcFlowLogsRoleCloudWatchLogs( - logGroup.logGroupArn, - options.useExistingRoles, - options.acceleratorPrefix, - ), - logDestinationType: 'cloud-watch-logs', - logDestination: logGroup.logGroupArn, - resourceId: this.vpcId, - resourceType: 'VPC', - trafficType: options.trafficType, - maxAggregationInterval, - logFormat: options.logFormat, - }); - } - - let s3LogDestination = `${options.bucketArn}/vpc-flow-logs/`; - if (options.overrideS3LogPath) { - const replacedS3LogPath = this.replaceVpcFlowLogDestName(options.overrideS3LogPath, this.name, this.env.account); - s3LogDestination = `${options.bucketArn}/${replacedS3LogPath}`; - } - - // Destination: S3 - if (options.destinations.includes('s3')) { - new cdk.aws_ec2.CfnFlowLog(this, 'S3FlowLog', { - logDestinationType: 's3', - logDestination: s3LogDestination, - resourceId: this.vpcId, - resourceType: 'VPC', - trafficType: options.trafficType, - maxAggregationInterval, - logFormat: options.logFormat, - }); - } - } - - /** - * Replaces Lookup Values for VPC Name in string - * Currently supports look ups for VPC_Name and ACCOUNT_ID for VPC Flow Logs Destinations - * @param inputString - * @param replacementValue - * @returns - */ - private replaceVpcFlowLogDestName(inputString: string, replacementValue: string, accountId: string): string { - const replacements = { - '\\${ACCEL_LOOKUP::VPC_NAME}': replacementValue, - '\\${ACCEL_LOOKUP::ACCOUNT_ID}': accountId, - }; - - for (const [key, value] of Object.entries(replacements)) { - inputString = inputString.replace(new RegExp(key, 'g'), value); - } - return inputString; - } - - private createVpcFlowLogsRoleCloudWatchLogs( - logGroupArn: string, - useExistingRoles: boolean, - acceleratorPrefix: string, - ) { - if (useExistingRoles) { - return `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${acceleratorPrefix}VpcFlowLogsRole`; - } - const role = new cdk.aws_iam.Role(this, 'FlowLogsRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('vpc-flow-logs.amazonaws.com'), - }); - - role.addToPrincipalPolicy( - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'logs:CreateLogDelivery', - 'logs:CreateLogGroup', - 'logs:CreateLogStream', - 'logs:DeleteLogDelivery', - 'logs:DescribeLogGroups', - 'logs:DescribeLogStreams', - 'logs:PutLogEvents', - ], - resources: [logGroupArn], - }), - ); - return role.roleArn; - } - - public addIpv4Cidr(options: { cidrBlock?: string; ipv4IpamPoolId?: string; ipv4NetmaskLength?: number }) { - // This block is required for backwards compatibility - // with a previous iteration. It appends a number to the - // logical ID so more than two VPC CIDRs can be defined. - let logicalId = 'VpcCidrBlock'; - if (this.cidrs.ipv4.length > 0) { - logicalId = `VpcCidrBlock${this.cidrs.ipv4.length}`; - } - - // Create a secondary VPC CIDR - this.cidrs.ipv4.push( - new cdk.aws_ec2.CfnVPCCidrBlock(this, logicalId, { - cidrBlock: options.cidrBlock, - ipv4IpamPoolId: options.ipv4IpamPoolId, - ipv4NetmaskLength: options.ipv4NetmaskLength, - vpcId: this.vpcId, - }), - ); - } - - public addIpv6Cidr(options: { - amazonProvidedIpv6CidrBlock?: boolean; - ipv6CidrBlock?: string; - ipv6IpamPoolId?: string; - ipv6NetmaskLength?: number; - ipv6Pool?: string; - }) { - const logicalId = `Ipv6CidrBlock${this.cidrs.ipv6.length}`; - - // Create a secondary VPC CIDR - this.cidrs.ipv6.push( - new cdk.aws_ec2.CfnVPCCidrBlock(this, logicalId, { - amazonProvidedIpv6CidrBlock: options.amazonProvidedIpv6CidrBlock, - ipv6CidrBlock: options.ipv6CidrBlock, - ipv6IpamPoolId: options.ipv6IpamPoolId, - ipv6Pool: options.ipv6Pool, - vpcId: this.vpcId, - }), - ); - } - - addEgressOnlyIgw() { - if (this.egressOnlyIgwId) { - throw new Error(`Egress-Only Internet Gateway is already configured to VPC ${this.name}`); - } - this.egressOnlyIgw = new cdk.aws_ec2.CfnEgressOnlyInternetGateway(this, 'EgressOnlyIgw', { vpcId: this.vpcId }); - this.egressOnlyIgwId = this.egressOnlyIgw.ref; - } - - addInternetGateway() { - if (this.internetGatewayId) { - throw new Error(`Internet Gateway is already configured to VPC ${this.name}`); - } - this.internetGateway = new cdk.aws_ec2.CfnInternetGateway(this, 'InternetGateway', {}); - this.internetGatewayAttachment = new cdk.aws_ec2.CfnVPCGatewayAttachment(this, 'InternetGatewayAttachment', { - internetGatewayId: this.internetGatewayId, - vpcId: this.vpcId, - }); - this.internetGatewayId = this.internetGateway.ref; - } - - addVirtualPrivateGateway(asn: number) { - if (this.virtualPrivateGatewayId) { - throw new Error(`Virtual Private Gateway is already configured to VPC ${this.name}`); - } - this.virtualPrivateGateway = new cdk.aws_ec2.VpnGateway(this, `VirtualPrivateGateway`, { - amazonSideAsn: asn, - type: 'ipsec.1', - }); - this.virtualPrivateGatewayAttachment = new cdk.aws_ec2.CfnVPCGatewayAttachment( - this, - `VirtualPrivateGatewayAttachment`, - { - vpnGatewayId: this.virtualPrivateGateway.gatewayId, - vpcId: this.vpcId, - }, - ); - this.virtualPrivateGatewayId = this.virtualPrivateGateway.gatewayId; - } - - setDhcpOptions(dhcpOptions: string) { - new cdk.aws_ec2.CfnVPCDHCPOptionsAssociation(this, 'DhcpOptionsAssociation', { - dhcpOptionsId: dhcpOptions, - vpcId: this.vpcId, - }); - } -} - -/** - * Defines a Imported VPC object - */ -export class ImportedVpc extends VpcBase { - public readonly name: string; - public readonly vpcId: string; - public readonly cidrs: { ipv4: cdk.aws_ec2.CfnVPCCidrBlock[]; ipv6: cdk.aws_ec2.CfnVPCCidrBlock[] }; - public readonly vpnConnections: VpnConnection[] = []; - public readonly cidrBlock: string; - - constructor(scope: Construct, id: string, props: ImportedVpcProps) { - super(scope, id); - this.name = props.name; - this.vpcId = props.vpcId; - this.cidrBlock = props.cidrBlock; - this.cidrs = { ipv4: [], ipv6: [] }; - this.internetGatewayId = props.internetGatewayId; - this.virtualPrivateGatewayId = props.virtualPrivateGatewayId; - } -} - -/** - * Defines a new VPC object - */ -export class Vpc extends VpcBase { - public readonly name: string; - public readonly vpcId: string; - public readonly cidrs: { ipv4: cdk.aws_ec2.CfnVPCCidrBlock[]; ipv6: cdk.aws_ec2.CfnVPCCidrBlock[] }; - public readonly vpnConnections: VpnConnection[] = []; - public readonly cidrBlock: string; - constructor(scope: Construct, id: string, props: VpcProps) { - super(scope, id); - this.name = props.name; - const resource = new cdk.aws_ec2.CfnVPC(this, 'Resource', { - cidrBlock: props.ipv4CidrBlock, - enableDnsHostnames: props.enableDnsHostnames, - enableDnsSupport: props.enableDnsSupport, - instanceTenancy: props.instanceTenancy, - ipv4IpamPoolId: props.ipv4IpamPoolId, - ipv4NetmaskLength: props.ipv4NetmaskLength, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', props.name); - - this.cidrBlock = resource.attrCidrBlock; - - this.vpcId = resource.ref; - - this.cidrs = { ipv4: [], ipv6: [] }; - - if (props.egressOnlyIgw) { - this.addEgressOnlyIgw(); - } - - if (props.internetGateway) { - this.internetGateway = new cdk.aws_ec2.CfnInternetGateway(this, 'InternetGateway', {}); - this.internetGatewayAttachment = new cdk.aws_ec2.CfnVPCGatewayAttachment(this, 'InternetGatewayAttachment', { - internetGatewayId: this.internetGateway.ref, - vpcId: this.vpcId, - }); - this.internetGatewayId = this.internetGateway.ref; - } - - if (props.virtualPrivateGateway) { - this.virtualPrivateGateway = new cdk.aws_ec2.VpnGateway(this, `VirtualPrivateGateway`, { - amazonSideAsn: props.virtualPrivateGateway.asn, - type: 'ipsec.1', - }); - this.virtualPrivateGatewayAttachment = new cdk.aws_ec2.CfnVPCGatewayAttachment( - this, - `VirtualPrivateGatewayAttachment`, - { - vpnGatewayId: this.virtualPrivateGateway.gatewayId, - vpcId: this.vpcId, - }, - ); - this.virtualPrivateGatewayId = this.virtualPrivateGateway.gatewayId; - } - - if (props.dhcpOptions) { - new cdk.aws_ec2.CfnVPCDHCPOptionsAssociation(this, 'DhcpOptionsAssociation', { - dhcpOptionsId: props.dhcpOptions, - vpcId: this.vpcId, - }); - } - } - - static fromVpcAttributes(scope: Construct, id: string, props: ImportedVpcProps) { - return new ImportedVpc(scope, id, props); - } -} - -/** - * Initialized DeleteDefaultSecurityGroupRules properties - */ -export interface DeleteDefaultSecurityGroupRulesProps { - /** - * Take in Vpc Id as a parameter - */ - readonly vpcId: string; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} -/** - * Class Delete the Default Security Group Rules for the Vpc - */ -export class DeleteDefaultSecurityGroupRules extends Construct { - readonly id: string; - constructor(scope: Construct, id: string, props: DeleteDefaultSecurityGroupRulesProps) { - super(scope, id); - - const DELETE_DEFAULT_SECURITY_GROUP_RULES = 'Custom::DeleteDefaultSecurityGroupRules'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, DELETE_DEFAULT_SECURITY_GROUP_RULES, { - codeDirectory: path.join(__dirname, 'delete-default-security-group-rules/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ec2:DescribeSecurityGroups', 'ec2:RevokeSecurityGroupIngress', 'ec2:RevokeSecurityGroupEgress'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: DELETE_DEFAULT_SECURITY_GROUP_RULES, - serviceToken: provider.serviceToken, - properties: { - vpcId: props.vpcId, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpn-connection.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpn-connection.ts deleted file mode 100644 index 533a7a6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ec2/vpn-connection.ts +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { LzaCustomResource } from '../lza-custom-resource'; - -export interface VpnConnectionProps { - /** - * Name of the VPN Connection - */ - readonly name: string; - /** - * Identifier of the Customer Gateway - */ - readonly customerGatewayId: string; - /** - * Amazon-side IPv4 CIDR - */ - readonly amazonIpv4NetworkCidr?: string; - /** - * Customer-side IPv4 CIDR - */ - readonly customerIpv4NetworkCidr?: string; - /** - * If advanced VPN options are enabled, a custom resource handler to - * maintain the resource - */ - readonly customResourceHandler?: cdk.aws_lambda.IFunction; - /** - * Enable VPN acceleration - */ - readonly enableVpnAcceleration?: boolean; - /** - * The owning account ID, if the VPN tunnel needs to be created cross-account - */ - readonly owningAccountId?: string; - /** - * The owning region, if the VPN tunnel needs to be created cross-region - */ - readonly owningRegion?: string; - /** - * The role name to assume if creating a cross-account VPN - */ - readonly roleName?: string; - /** - * The name of the Transit Gateway to terminate the VPN Connection. - */ - readonly transitGatewayId?: string; - /** - * The name of the Virtual Private Gateway to terminate the VPN Connection. - */ - readonly virtualPrivateGateway?: string; - /** - * Determine if static routes will be used or dynamic for the VPN Connection. - */ - readonly staticRoutesOnly?: boolean; - /** - * The optional configuration of the VPN Tunnels of a VPN Connection - */ - readonly vpnTunnelOptionsSpecifications?: VpnTunnelOptionsSpecifications[]; - /** - * The array of tag values to add onto the VPN Connection. - */ - readonly tags?: cdk.CfnTag[]; -} - -export interface VpnTunnelOptionsSpecifications { - /** - * DPD timeout action - */ - readonly dpdTimeoutAction?: string; - /** - * DPD timeout in seconds - */ - readonly dpdTimeoutSeconds?: number; - /** - * IKE versions - */ - readonly ikeVersions?: number[]; - /** - * VPN tunnel logging - */ - readonly logging?: { - readonly enable?: boolean; - readonly logGroupArn?: string; - readonly outputFormat?: string; - }; - /** - * Phase 1 config - */ - readonly phase1?: { - readonly dhGroups?: number[]; - readonly encryptionAlgorithms?: string[]; - readonly integrityAlgorithms?: string[]; - readonly lifetimeSeconds?: number; - }; - /** - * Phase 2 config - */ - readonly phase2?: { - readonly dhGroups?: number[]; - readonly encryptionAlgorithms?: string[]; - readonly integrityAlgorithms?: string[]; - readonly lifetimeSeconds?: number; - }; - /** - * A Secrets Manager secret name - */ - readonly preSharedKey?: string; - /** - * IKE rekey fuzz percentage - */ - readonly rekeyFuzzPercentage?: number; - /** - * IKE rekey margin time in seconds - */ - readonly rekeyMarginTimeSeconds?: number; - /** - * IKE replay window size - */ - readonly replayWindowSize?: number; - /** - * The startup action for the VPN connection - */ - readonly startupAction?: string; - /** - * An IP address that is a size /30 CIDR block from the 169.254.0.0/16. - */ - readonly tunnelInsideCidr?: string; - /** - * Enable tunnel lifecycle control - */ - readonly tunnelLifecycleControl?: boolean; -} - -interface IVpnConnection extends cdk.IResource { - /** - * The identifier of the VPN Connection - * - * @attribute - */ - readonly vpnConnectionId: string; -} - -/** - * Class for VPN Connection - */ - -export class VpnConnection extends cdk.Resource implements IVpnConnection { - public readonly vpnConnectionId: string; - public readonly name: string; - - constructor(scope: Construct, id: string, props: VpnConnectionProps) { - super(scope, id); - this.name = props.name; - - let resource: cdk.aws_ec2.CfnVPNConnection | cdk.CustomResource; - - if (!props.customResourceHandler) { - resource = new cdk.aws_ec2.CfnVPNConnection(this, 'VpnConnection', { - customerGatewayId: props.customerGatewayId, - type: 'ipsec.1', - staticRoutesOnly: props.staticRoutesOnly, - tags: props.tags, - transitGatewayId: props.transitGatewayId, - vpnGatewayId: props.virtualPrivateGateway, - vpnTunnelOptionsSpecifications: props.vpnTunnelOptionsSpecifications, - }); - cdk.Tags.of(this).add('Name', props.name); - } else { - // Convert tags to EC2 API format - const tags = - props.tags?.map(tag => { - return { Key: tag.key, Value: tag.value }; - }) ?? []; - tags.push({ Key: 'Name', Value: props.name }); - - resource = new LzaCustomResource(this, 'CustomResource', { - resource: { - name: 'CustomResource', - parentId: id, - properties: [ - { - amazonIpv4NetworkCidr: props.amazonIpv4NetworkCidr, - customerIpv4NetworkCidr: props.customerIpv4NetworkCidr, - enableVpnAcceleration: props.enableVpnAcceleration, - customerGatewayId: props.customerGatewayId, - owningAccountId: props.owningAccountId, - owningRegion: props.owningRegion, - roleName: props.roleName, - staticRoutesOnly: props.staticRoutesOnly, - transitGatewayId: props.transitGatewayId, - vpnGatewayId: props.virtualPrivateGateway, - vpnTunnelOptions: props.vpnTunnelOptionsSpecifications, - tags, - }, - ], - onEventHandler: props.customResourceHandler, - }, - }).resource; - } - this.vpnConnectionId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/application-load-balancer.ts b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/application-load-balancer.ts deleted file mode 100644 index fcd7db1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/application-load-balancer.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { pascalCase } from 'change-case'; -import { ApplicationLoadBalancerListenerConfig } from '@aws-accelerator/config'; - -export interface IApplicationLoadBalancerResource extends cdk.IResource { - /** - * The ARN of the ApplicationLoadBalancer. - */ - readonly applicationLoadBalancerArn: string; - /** - * The name of the ApplicationLoadBalancer - */ - readonly applicationLoadBalancerName: string; -} - -export type albAttributesType = { - deletionProtection?: boolean; - idleTimeout?: number; - routingHttpDesyncMitigationMode?: string; - routingHttpDropInvalidHeader?: boolean; - routingHttpXAmznTlsCipherEnable?: boolean; - routingHttpXffClientPort?: boolean; - routingHttpXffHeaderProcessingMode?: 'append' | 'preserve' | 'remove'; - http2Enabled?: boolean; - wafFailOpen?: boolean; -}; - -export type albForwardTargetGroup = { - targetGroupArn: string; -}; - -export type albListenerActionProperty = { - type: string; - fixedResponseConfig?: { - statusCode: string; - - // the properties below are optional - contentType?: string; - messageBody?: string; - }; - forwardConfig?: { - targetGroups: albForwardTargetGroup[]; - targetGroupStickinessConfig?: { - durationSeconds?: number; - enabled?: boolean; - }; - }; - order?: number; - redirectConfig?: { - statusCode?: string; - - // the properties below are optional - host?: string; - path?: string; - port?: string; - protocol?: string; - query?: string; - }; - targetGroupArn: string; -}; - -export interface ApplicationLoadBalancerProps { - /** - * Name for Application Load Balancer. - */ - readonly name: string; - /** - * SSM parameter prefix - */ - readonly ssmPrefix: string; - /** - * Application Load Balancer Subnets (required). - */ - readonly subnets: string[]; - /** - * Application Load Balancer SecurityGroups (required). - */ - readonly securityGroups?: string[]; - /** - * Application Load Balancer scheme. - */ - readonly scheme?: string; - /** - * Application Load Balancer attributes. - */ - readonly attributes?: albAttributesType; - /** - * Listeners for Application Load Balancer. - */ - readonly listeners?: ApplicationLoadBalancerListenerConfig[]; - - /** - * Access logs s3 bucket name. - */ - readonly accessLogsBucket: string; -} - -export class ApplicationLoadBalancer extends cdk.Resource implements IApplicationLoadBalancerResource { - public readonly applicationLoadBalancerArn: string; - public readonly applicationLoadBalancerName: string; - constructor(scope: Construct, id: string, props: ApplicationLoadBalancerProps) { - super(scope, id); - const resource = new cdk.aws_elasticloadbalancingv2.CfnLoadBalancer(this, 'Resource', { - type: 'application', - subnets: props.subnets, - name: props.name, - scheme: props.scheme, - securityGroups: props.securityGroups, - loadBalancerAttributes: this.buildAttributes(props), - }); - - // Add Name tag - cdk.Tags.of(this).add('Name', props.name); - - // Set initial properties - this.applicationLoadBalancerArn = resource.ref; - this.applicationLoadBalancerName = resource.attrLoadBalancerName; - - for (const listener of props.listeners ?? []) { - const listenerAction: cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty = - this.getListenerAction(listener); - new cdk.aws_elasticloadbalancingv2.CfnListener(this, pascalCase(`Listener${listener.name}`), { - defaultActions: [listenerAction], - loadBalancerArn: resource.ref, - certificates: [{ certificateArn: this.getCertificate(props.ssmPrefix, listener.certificate) }], - port: listener.port, - protocol: listener.protocol, - sslPolicy: listener.sslPolicy!, - }); - } - } - private getCertificate(ssmPrefix: string, certificate: string | undefined) { - if (certificate) { - //check if user provided arn. If so do nothing, if not get it from ssm - if (certificate.match('\\arn:*')) { - return certificate; - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter(this, `${ssmPrefix}/acm/${certificate}/arn`); - } - } - return undefined; - } - private getListenerAction( - listener: ApplicationLoadBalancerListenerConfig, - ): cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty { - const listenerTargetGroupArn = listener.targetGroup; - const actionValues: albListenerActionProperty = { - type: listener.type, - order: listener.order, - targetGroupArn: listenerTargetGroupArn, - }; - switch (listener.type) { - case 'forward': - actionValues.forwardConfig = { - targetGroups: [{ targetGroupArn: listener.targetGroup }], - targetGroupStickinessConfig: listener.forwardConfig?.targetGroupStickinessConfig ?? undefined, - }; - break; - case 'redirect': - if (listener.redirectConfig) { - actionValues.redirectConfig = { - host: listener.redirectConfig.host, - path: listener.redirectConfig.path, - port: listener.redirectConfig.port?.toString() ?? undefined, - protocol: listener.redirectConfig.protocol, - query: listener.redirectConfig.query, - statusCode: listener.redirectConfig.statusCode, - }; - } else { - throw new Error(`Listener ${listener.name} is set to redirect but redirectConfig is not defined`); - } - break; - case 'fixed-response': - if (listener.fixedResponseConfig) { - actionValues.fixedResponseConfig = { - contentType: listener.fixedResponseConfig.contentType, - messageBody: listener.fixedResponseConfig.messageBody, - statusCode: listener.fixedResponseConfig.statusCode, - }; - } else { - throw new Error(`Listener ${listener.name} is set to fixed-response but fixedResponseConfig is not defined`); - } - break; - default: - throw new Error(`Listener ${listener.name} is not set to forward, fixed-response or redirect`); - } - - return actionValues as cdk.aws_elasticloadbalancingv2.CfnListener.ActionProperty; - } - - private buildAttributes(props: ApplicationLoadBalancerProps) { - // add elements to the array. - // based on https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-loadbalancer-loadbalancerattributes.html - const albAttributesProperties = [ - { - key: 'access_logs.s3.enabled', - value: 'true', - }, - { - key: 'access_logs.s3.bucket', - value: props.accessLogsBucket, - }, - { - key: 'access_logs.s3.prefix', - value: `${cdk.Stack.of(this).account}/${cdk.Stack.of(this).region}/${props.name}`, - }, - ]; - if (!props.attributes) { - return albAttributesProperties; - } - - if (props.attributes.deletionProtection) { - albAttributesProperties.push({ - key: 'deletion_protection.enabled', - value: props.attributes.deletionProtection.toString(), - }); - } - if (props.attributes.idleTimeout) { - albAttributesProperties.push({ - key: 'idle_timeout.timeout_seconds', - value: props.attributes.idleTimeout.toString(), - }); - } - if (props.attributes.routingHttpDesyncMitigationMode) { - albAttributesProperties.push({ - key: 'routing.http.desync_mitigation_mode', - value: props.attributes.routingHttpDesyncMitigationMode, - }); - } - if (props.attributes.routingHttpDropInvalidHeader) { - albAttributesProperties.push({ - key: 'routing.http.drop_invalid_header_fields.enabled', - value: props.attributes.routingHttpDropInvalidHeader.toString(), - }); - } - if (props.attributes.routingHttpXAmznTlsCipherEnable) { - albAttributesProperties.push({ - key: 'routing.http.x_amzn_tls_version_and_cipher_suite.enabled', - value: props.attributes.routingHttpXAmznTlsCipherEnable.toString(), - }); - } - if (props.attributes.routingHttpXffClientPort) { - albAttributesProperties.push({ - key: 'routing.http.xff_client_port.enabled', - value: props.attributes.routingHttpXffClientPort.toString(), - }); - } - if (props.attributes.routingHttpXffHeaderProcessingMode) { - albAttributesProperties.push({ - key: 'routing.http.xff_header_processing.mode', - value: props.attributes.routingHttpXffHeaderProcessingMode, - }); - } - if (props.attributes.http2Enabled) { - albAttributesProperties.push({ - key: 'routing.http2.enabled', - value: props.attributes.http2Enabled.toString(), - }); - } - if (props.attributes.wafFailOpen) { - albAttributesProperties.push({ - key: 'waf.fail_open.enabled', - value: props.attributes.wafFailOpen.toString(), - }); - } - - return albAttributesProperties; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/gateway-load-balancer.ts b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/gateway-load-balancer.ts deleted file mode 100644 index 9ccfe00..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/gateway-load-balancer.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IGatewayLoadBalancer extends cdk.IResource { - /** - * The ID of the VPC endpoint service associated with this Gateway Load Balancer. - */ - readonly endpointServiceId: string; - /** - * The ARN of the Gateway Load Balancer. - */ - readonly loadBalancerArn: string; - /** - * The name of the Gateway Load Balancer - */ - readonly loadBalancerName: string; -} - -export interface GatewayLoadBalancerProps { - /** - * The name of the Gateway Load Balancer. - */ - readonly name: string; - /** - * An array of account principals allowed to create endpoints for the service. - */ - readonly allowedPrincipals: string[]; - /** - * The subnets the Gateway Load Balancer will be deployed to. - */ - readonly subnets: string[]; - /** - * Whether to enable cross-zone load balancing. - * - * @default 'true' - */ - readonly crossZoneLoadBalancing?: boolean; - /** - * Whether to enable deletion protection. - * - * @default 'false' - */ - readonly deletionProtection?: boolean; - /** - * An array of CloudFormation tags to apply to the Gateway Load Balancer. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class GatewayLoadBalancer extends cdk.Resource implements IGatewayLoadBalancer { - public readonly endpointServiceId: string; - public readonly loadBalancerArn: string; - public readonly loadBalancerName: string; - private allowedPrincipals: string[]; - private endpointService: cdk.aws_ec2.CfnVPCEndpointService; - - constructor(scope: Construct, id: string, props: GatewayLoadBalancerProps) { - super(scope, id); - - const resource = new cdk.aws_elasticloadbalancingv2.CfnLoadBalancer(this, 'Resource', { - loadBalancerAttributes: [ - { - key: 'deletion_protection.enabled', - value: props.deletionProtection ? props.deletionProtection.toString() : 'false', - }, - { - key: 'load_balancing.cross_zone.enabled', - value: props.crossZoneLoadBalancing ? props.crossZoneLoadBalancing.toString() : 'true', - }, - ], - subnets: props.subnets, - tags: props.tags, - type: 'gateway', - }); - // Add Name tag - cdk.Tags.of(this).add('Name', props.name); - - // Set initial properties - this.loadBalancerArn = resource.ref; - this.loadBalancerName = resource.attrLoadBalancerName; - - // Create endpoint service - this.endpointService = this.createEndpointService(); - this.allowedPrincipals = props.allowedPrincipals; - this.endpointServiceId = this.endpointService.ref; - if (this.allowedPrincipals.length > 0) { - this.createEndpointServicePermissions(); - } - } - - /** - * Create endpoint service for the load balancer. - */ - private createEndpointService(): cdk.aws_ec2.CfnVPCEndpointService { - return new cdk.aws_ec2.CfnVPCEndpointService(this, 'EndpointService', { - acceptanceRequired: false, - gatewayLoadBalancerArns: [this.loadBalancerArn], - }); - } - - /** - * Create endpoint service permissions. - */ - private createEndpointServicePermissions(): void { - const principals = this.allowedPrincipals.map(item => { - return `arn:${cdk.Aws.PARTITION}:iam::${item}:root`; - }); - - new cdk.aws_ec2.CfnVPCEndpointServicePermissions(this, 'EndpointServicePermissions', { - serviceId: this.endpointServiceId, - allowedPrincipals: principals, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/network-load-balancer.ts b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/network-load-balancer.ts deleted file mode 100644 index 8502001..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/network-load-balancer.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { pascalCase } from 'change-case'; - -export interface INetworkLoadBalancerResource extends cdk.IResource { - /** - * The ARN of the NetworkLoadBalancer. - */ - readonly networkLoadBalancerArn: string; - /** - * The name of the NetworkLoadBalancer - */ - readonly networkLoadBalancerName: string; -} - -export type NetworkLoadBalancerListener = { - /** - * Name for Listener. - */ - readonly name: string; - /** - * ACM ARN of the certificate to be associated with the listener. - */ - readonly certificate?: string; - /** - * Port where the traffic is directed to. - */ - readonly port?: number; - /** - * Protocol used for the traffic. - */ - readonly protocol?: string; - /** - * Application-Layer Protocol Negotiation (ALPN) policy for TLS encrypted traffic - */ - readonly alpnPolicy?: string; - /** - * SSL policy for TLS encrypted traffic - */ - readonly sslPolicy?: string; - /** - * Target Group to direct the traffic to. - */ - readonly targetGroup: string; -}; -export interface NetworkLoadBalancerProps { - /** - * Name for Network Load Balancer. - */ - readonly name: string; - /** - * SSM prefix - */ - readonly ssmPrefix: string; - /** - * Network Load Balancer Subnets (required). - */ - readonly subnets: string[]; - /** - * Network Load Balancer scheme. - */ - readonly scheme?: string; - /** - * Network Load Balancer deletionProtection - */ - readonly deletionProtection?: boolean; - /** - * Cross Zone load balancing for Network Load Balancer. - */ - readonly crossZoneLoadBalancing?: boolean; - /** - * Listeners for Network Load Balancer. - */ - readonly listeners?: NetworkLoadBalancerListener[]; - /** - * Access logs s3 bucket name. - */ - readonly accessLogsBucket: string; - /** - * VPC Name (required). - */ - readonly vpcName: string; - /** - * App Name (required). - */ - readonly appName: string; -} - -export class NetworkLoadBalancer extends cdk.Resource implements INetworkLoadBalancerResource { - public readonly networkLoadBalancerArn: string; - public readonly networkLoadBalancerName: string; - constructor(scope: Construct, id: string, props: NetworkLoadBalancerProps) { - super(scope, id); - const resource = new cdk.aws_elasticloadbalancingv2.CfnLoadBalancer(this, 'Resource', { - type: 'network', - subnets: props.subnets, - name: props.name, - scheme: props.scheme, - loadBalancerAttributes: [ - { - key: 'deletion_protection.enabled', - value: props.deletionProtection ? props.deletionProtection.toString() : 'false', - }, - { - key: 'load_balancing.cross_zone.enabled', - value: props.crossZoneLoadBalancing ? props.crossZoneLoadBalancing.toString() : 'true', - }, - { - key: 'access_logs.s3.enabled', - value: 'true', - }, - { - key: 'access_logs.s3.bucket', - value: props.accessLogsBucket, - }, - { - key: 'access_logs.s3.prefix', - value: `${cdk.Stack.of(this).account}/${cdk.Stack.of(this).region}/${props.name}`, - }, - ], - }); - - // Add Name tag - cdk.Tags.of(this).add('Name', props.name); - - // Set initial properties - this.networkLoadBalancerArn = resource.ref; - this.networkLoadBalancerName = resource.attrLoadBalancerName; - - for (const listener of props.listeners ?? []) { - const targetGroupArn = this.getTargetGroupArn( - listener.targetGroup, - props.vpcName, - props.appName, - props.ssmPrefix, - ); - new cdk.aws_elasticloadbalancingv2.CfnListener(this, pascalCase(`Listener${listener.name}`), { - defaultActions: [ - { - type: 'forward', - forwardConfig: { - targetGroups: [ - { - targetGroupArn: targetGroupArn, - }, - ], - }, - targetGroupArn: targetGroupArn, - }, - ], - loadBalancerArn: resource.ref, - alpnPolicy: [listener.alpnPolicy!], - certificates: [{ certificateArn: listener.certificate! }], - port: listener.port!, - protocol: listener.protocol!, - sslPolicy: listener.sslPolicy!, - }); - } - } - private getTargetGroupArn(targetGroup: string, vpcName: string, appName: string, ssmPrefix: string): string { - if (targetGroup.match('\\${ACCEL_LOOKUP::TargetGroup:([a-zA-Z0-9-/:]*)}')) { - const targetGroupMatch = targetGroup.match('\\${ACCEL_LOOKUP::TargetGroup:([a-zA-Z0-9-/:]*)}'); - const targetGroupValue = cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${ssmPrefix}/application/targetGroup/${appName}/${vpcName}/${targetGroupMatch![1]}/arn`, - ); - return targetGroupValue; - } else if (targetGroup.match('\\arn:*')) { - return targetGroup; - } else { - return cdk.aws_ssm.StringParameter.valueForStringParameter( - this, - `${ssmPrefix}/application/targetGroup/${appName}/${vpcName}/${targetGroup}/arn`, - ); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-addresses.ts b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-addresses.ts deleted file mode 100644 index f3238ac..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-addresses.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; -import { NlbTargetTypeConfig } from '@aws-accelerator/config'; - -const path = require('path'); - -export interface INLBAddresses extends cdk.IResource { - /** - * The IP addresses of the endpoint. - */ - readonly ipAddresses: cdk.Reference; -} - -export interface NLBAddressesProps { - /** - * The ip and NLB targets - */ - readonly targets: (NlbTargetTypeConfig | string)[]; - /** - * The role to assume to retrieve the ip addresses - */ - readonly assumeRoleName: string; - /** - * The partition - */ - readonly partition: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class for FMSOrganizationAdminAccount - */ -export class NLBAddresses extends cdk.Resource implements INLBAddresses { - public readonly ipAddresses: cdk.Reference; - constructor(scope: Construct, id: string, props: NLBAddressesProps) { - super(scope, id); - const functionId = `${id}ProviderLambda`; - const providerId = `${id}Provider`; - - const providerLambda = new cdk.aws_lambda.Function(this, functionId, { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'nlb-ip-lookup/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - timeout: cdk.Duration.seconds(15), - handler: 'index.handler', - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'StsAssumeRole', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.assumeRoleName}`], - }), - ); - - const provider = new cdk.custom_resources.Provider(this, providerId, { - onEventHandler: providerLambda, - }); - - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const resource = new cdk.CustomResource(this, `Resource`, { - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - targets: props.targets, - assumeRoleName: props.assumeRoleName, - partition: cdk.Stack.of(scope).partition, - }, - }); - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.ipAddresses = resource.getAtt('ipAddresses'); - - const stack = cdk.Stack.of(scope); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${providerId}/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${providerId}/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/index.ts deleted file mode 100644 index 9c9cb83..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/index.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { NlbTargetTypeConfig } from '@aws-accelerator/config'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * get cross account NLB IP Addresses and add static ip addresses in config- lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent) { - const region: string = event.ResourceProperties['region']; - const targets: (string | NlbTargetTypeConfig)[] = event.ResourceProperties['targets']; - const assumeRoleName: string = event.ResourceProperties['assumeRoleName']; - const partition: string = event.ResourceProperties['partition']; - const solutionId = process.env['SOLUTION_ID']; - const stsClient = new AWS.STS({ customUserAgent: solutionId, region: region }); - switch (event.RequestType) { - case 'Create': - case 'Update': - const ec2ClientMap = await setEC2ClientMap({ stsClient, partition, targets, assumeRoleName }); - const nlbIpAddressList = await getEniIpAddresses(ec2ClientMap, targets); - const staticIpAddresses = - targets - .filter(target => typeof target === 'string') - .map(filteredTarget => { - return { Id: filteredTarget }; - }) ?? []; - const ipAddressList = [...nlbIpAddressList, ...staticIpAddresses]; - if (!ipAddressList) { - throw new Error(`Could not get static IP addresses for targets ${JSON.stringify(targets, null, 4)}`); - } - return { Status: 'Success', StatusCode: 200, Data: { ipAddresses: ipAddressList } }; - - case 'Delete': - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function assumeRole(stsClient: AWS.STS, assumeRoleName: string, accountId: string, partition: string) { - const roleArn = `arn:${partition}:iam::${accountId}:role/${assumeRoleName}`; - const assumeRole = await throttlingBackOff(() => - stsClient.assumeRole({ RoleArn: roleArn, RoleSessionName: `fmsDeregisterAdmin` }).promise(), - ); - return new AWS.Credentials({ - accessKeyId: assumeRole.Credentials!.AccessKeyId, - secretAccessKey: assumeRole.Credentials!.SecretAccessKey, - sessionToken: assumeRole.Credentials!.SessionToken, - }); -} - -async function setEC2ClientMap(props: { - stsClient: AWS.STS; - partition: string; - targets: (string | NlbTargetTypeConfig)[]; - assumeRoleName: string; -}) { - const ec2ClientMap = new Map(); - - for (const target of props.targets) { - if (typeof target !== 'string') { - if (!ec2ClientMap.get(`${target.account}${target.region}`)) { - const credentials = await assumeRole(props.stsClient, props.assumeRoleName, target.account, props.partition); - const ec2Client = new AWS.EC2({ credentials, region: target.region }); - ec2ClientMap.set(`${target.account}${target.region}`, ec2Client); - } - } - } - - return ec2ClientMap; -} - -async function getEniIpAddresses(ec2ClientMap: Map, targets: (string | NlbTargetTypeConfig)[]) { - const ipAddresses = []; - for (const target of targets) { - if (typeof target !== 'string') { - const ec2Client = ec2ClientMap.get(`${target.account}${target.region}`); - if (ec2Client) { - const enis = await ec2Client - .describeNetworkInterfaces({ - Filters: [ - { - Name: 'description', - Values: [`ELB net/${target.nlbName}*`], - }, - ], - }) - .promise(); - const ips = - enis.NetworkInterfaces?.map(eni => { - return { Id: eni.PrivateIpAddress, AvailabilityZone: 'all' }; - }) ?? []; - ipAddresses.push(...ips); - } - } - } - return ipAddresses; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/package.json deleted file mode 100644 index 2b3a15e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-elasticloadbalancingv2-nlb-ip-lookup", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/nlb-ip-lookup/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/target-group.ts b/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/target-group.ts deleted file mode 100644 index 54edf61..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-elasticloadbalancingv2/target-group.ts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Reference } from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface ITargetGroupResource extends cdk.IResource { - /** - * The ARN of the TargetGroup. - */ - readonly targetGroupArn: string; - /** - * The name of the TargetGroup - */ - readonly targetGroupName: string; -} - -export interface TargetGroupProps { - /** - * Name of the target group. - */ - readonly name: string; - /** - * port of the target group. - */ - readonly port: number; - /** - * protocol of the target group. - */ - readonly protocol: string; - /** - * protocolVersion of the target group. - */ - readonly protocolVersion?: string; - /** - * Type of the target group. - */ - readonly type: string; - /** - * Target group VPC ID (required). - */ - readonly vpc: string; - /** - * Target group Attributes (optional). - */ - readonly attributes?: TargetGroupAttributesType; - /** - * Target group Attributes (optional). - */ - readonly healthCheck?: TargetGroupHealthCheckType; - /** - * Targets for the target group - */ - readonly targets?: string[] | Reference; - /** - * Target group Attributes (optional). - */ - readonly threshold?: TargetGroupThresholdType; - /** - * Target group Attributes (optional). - */ - readonly matcher?: TargetGroupMatcherType; -} - -export type TargetGroupAttributesType = { - deregistrationDelay?: number; - stickiness?: boolean; - stickinessType?: string; - algorithm?: string; - slowStart?: number; - appCookieName?: string; - appCookieDuration?: number; - lbCookieDuration?: number; - connectionTermination?: boolean; - preserveClientIp?: boolean; - proxyProtocolV2?: boolean; - targetFailover?: string; -}; -export type TargetGroupHealthCheckType = { - enabled?: boolean; - interval?: number; - path?: string; - port?: number; - protocol?: string; - timeout?: number; -}; -export type TargetGroupThresholdType = { - healthy?: number; - unhealthy?: number; -}; -export type TargetGroupMatcherType = { - grpcCode?: string; - httpCode?: string; -}; - -export class TargetGroup extends cdk.Resource implements ITargetGroupResource { - public readonly targetGroupArn: string; - public readonly targetGroupName: string; - constructor(scope: Construct, id: string, props: TargetGroupProps) { - super(scope, id); - - const resource = new cdk.aws_elasticloadbalancingv2.CfnTargetGroup(this, 'Resource', { - healthCheckEnabled: true, - healthCheckIntervalSeconds: props.healthCheck ? props.healthCheck.interval : undefined, - healthCheckPath: props.healthCheck ? props.healthCheck.path : undefined, - healthCheckPort: props.healthCheck ? props.healthCheck.port?.toString() : undefined, - healthCheckProtocol: props.healthCheck ? props.healthCheck.protocol : undefined, - healthCheckTimeoutSeconds: props.healthCheck ? props.healthCheck.timeout : undefined, - healthyThresholdCount: props.threshold ? props.threshold.healthy : undefined, - matcher: { - grpcCode: props.matcher ? props.matcher.grpcCode : undefined, - httpCode: props.matcher ? props.matcher.httpCode : undefined, - }, - name: props.name, - port: props.port, - protocol: props.protocol, - protocolVersion: props.protocolVersion ?? undefined, - targetGroupAttributes: this.buildAttributes(props) ?? undefined, - targets: props.targets ? this.buildTargets(props.targets) : undefined, - targetType: props.type, - unhealthyThresholdCount: props.threshold ? props.threshold.healthy : undefined, - vpcId: props.vpc, - }); - // Add Name tag - cdk.Tags.of(this).add('Name', props.name); - - // Set initial properties - this.targetGroupArn = resource.ref; - this.targetGroupName = resource.attrTargetGroupName; - } - - private buildAttributes(props: TargetGroupProps) { - // add elements to the array. - // based on https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-targetgroup-targetgroupattribute.html - - if (!props.attributes) { - return undefined; - } - - const targetGroupAttributesProperties = []; - - if (props.attributes.deregistrationDelay) { - targetGroupAttributesProperties.push({ - key: 'deregistration_delay.timeout_seconds', - value: props.attributes.deregistrationDelay.toString(), - }); - } - if (props.attributes.stickiness) { - targetGroupAttributesProperties.push({ - key: 'stickiness.enabled', - value: props.attributes.stickiness.toString(), - }); - } - if (props.attributes.stickinessType) { - targetGroupAttributesProperties.push({ - key: 'stickiness.type', - value: props.attributes.stickinessType, - }); - } - if (props.attributes.algorithm) { - targetGroupAttributesProperties.push({ - key: 'load_balancing.algorithm.type', - value: props.attributes.algorithm, - }); - } - if (props.attributes.slowStart) { - targetGroupAttributesProperties.push({ - key: 'slow_start.duration_seconds', - value: props.attributes.slowStart.toString(), - }); - } - if (props.attributes.appCookieName) { - targetGroupAttributesProperties.push({ - key: 'stickiness.app_cookie.cookie_name', - value: props.attributes.appCookieName, - }); - } - if (props.attributes.appCookieDuration) { - targetGroupAttributesProperties.push({ - key: 'stickiness.app_cookie.duration_seconds', - value: props.attributes.appCookieDuration.toString(), - }); - } - if (props.attributes.lbCookieDuration) { - targetGroupAttributesProperties.push({ - key: 'stickiness.lb_cookie.duration_seconds', - value: props.attributes.lbCookieDuration.toString(), - }); - } - if (props.attributes.connectionTermination) { - targetGroupAttributesProperties.push({ - key: 'deregistration_delay.connection_termination.enabled', - value: props.attributes.connectionTermination.toString(), - }); - } - if (props.attributes.preserveClientIp) { - targetGroupAttributesProperties.push({ - key: 'preserve_client_ip.enabled', - value: props.attributes.preserveClientIp.toString(), - }); - } - if (props.attributes.proxyProtocolV2) { - targetGroupAttributesProperties.push({ - key: 'proxy_protocol_v2.enabled', - value: props.attributes.proxyProtocolV2.toString(), - }); - } - if (props.attributes.targetFailover) { - targetGroupAttributesProperties.push({ - key: 'target_failover.on_deregistration', - value: props.attributes.targetFailover, - }); - targetGroupAttributesProperties.push({ - key: 'target_failover.on_unhealthy', - value: props.attributes.targetFailover, - }); - } - - if (targetGroupAttributesProperties.length > 0) { - return targetGroupAttributesProperties; - } - return undefined; - } - - private buildTargets( - targets: string[] | Reference, - ): cdk.aws_elasticloadbalancingv2.CfnTargetGroup.TargetDescriptionProperty[] | Reference { - if (targets instanceof Reference) { - return targets; - } - return targets.map(target => { - return { id: target }; - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account-rule.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account-rule.ts deleted file mode 100644 index c76eb60..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account-rule.ts +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { SsmParameterLookup } from '../aws-ssm/ssm-parameter-lookup'; - -/** - * MoveAccountRuleProps - */ - -export interface MoveAccountRuleProps { - /** - * Global region - */ - readonly globalRegion: string; - /** - * Home region - */ - readonly homeRegion: string; - /** - * Move account role name - */ - readonly moveAccountRoleName: string; - /** - * Commit id - */ - readonly commitId: string; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * Accelerator SSM parameter name where config table name can be found - */ - readonly configTableNameParameterName: string; - /** - * Accelerator SSM parameter name where config table arn can be found - */ - readonly configTableArnParameterName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to configure CloudWatch event to moves an account from its current source ou to destination ou. - */ -export class MoveAccountRule extends Construct { - constructor(scope: Construct, id: string, props: MoveAccountRuleProps) { - super(scope, id); - - let configTableName: string | undefined; - let configTableArn: string | undefined; - - if (props.homeRegion === props.globalRegion) { - configTableName = cdk.aws_ssm.StringParameter.valueForStringParameter(this, props.configTableNameParameterName); - configTableArn = cdk.aws_ssm.StringParameter.valueForStringParameter(this, props.configTableArnParameterName); - } else { - configTableName = new SsmParameterLookup(this, 'AcceleratorConfigTableNameLookup', { - name: props.configTableNameParameterName, - accountId: cdk.Stack.of(this).account, - parameterRegion: props.homeRegion, - roleName: props.moveAccountRoleName, - kmsKey: props.kmsKey, - logRetentionInDays: props.logRetentionInDays, - acceleratorPrefix: props.acceleratorPrefix, - }).value; - configTableArn = new SsmParameterLookup(this, 'AcceleratorConfigTableArnLookup', { - name: props.configTableArnParameterName, - accountId: cdk.Stack.of(this).account, - parameterRegion: props.homeRegion, - roleName: props.moveAccountRoleName, - kmsKey: props.kmsKey, - logRetentionInDays: props.logRetentionInDays, - acceleratorPrefix: props.acceleratorPrefix, - }).value; - } - - // resources for control tower lifecycle events - const moveAccountTargetFunction = new cdk.aws_lambda.Function(this, 'MoveAccountTargetFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'move-account/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to process Organizations MoveAccount event from CloudTrail', - timeout: cdk.Duration.minutes(5), - environment: { - HOME_REGION: props.homeRegion, - GLOBAL_REGION: props.globalRegion, - CONFIG_TABLE_NAME: configTableName, - COMMIT_ID: props.commitId, - STACK_PREFIX: props.acceleratorPrefix, - }, - }); - - moveAccountTargetFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'dynamodbConfigTable', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:Query'], - resources: [configTableArn], - }), - ); - - moveAccountTargetFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'organizations', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'organizations:MoveAccount', - 'organizations:ListParents', - 'organizations:DescribeOrganizationalUnit', - 'organizations:ListRoots', - ], - resources: ['*'], - }), - ); - - new cdk.aws_logs.LogGroup(this, `${moveAccountTargetFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${moveAccountTargetFunction.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const moveAccountRule = new cdk.aws_events.Rule(this, 'MoveAccountRule', { - description: 'CloudWatch Events rule to monitor for Organizations MoveAccount events', - eventPattern: { - source: ['aws.organizations'], - detailType: ['AWS API Call via CloudTrail'], - detail: { - eventName: ['MoveAccount'], - eventSource: ['organizations.amazonaws.com'], - }, - }, - }); - - moveAccountRule.addTarget( - new cdk.aws_events_targets.LambdaFunction(moveAccountTargetFunction, { retryAttempts: 3 }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/index.ts deleted file mode 100644 index f0c8cf1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/index.ts +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - DynamoDBDocumentClient, - QueryCommandInput, - paginateQuery, - DynamoDBDocumentPaginationConfiguration, - QueryCommand, -} from '@aws-sdk/lib-dynamodb'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -type ConfigOrganizationalUnitKeys = { - acceleratorKey: string; - awsKey: string; - registered: boolean | undefined; - ignore: boolean; -}; - -type DDBItem = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -}; -type DDBItems = Array; - -const solutionId: string = process.env['SOLUTION_ID'] ?? ''; -const configTableName = process.env['CONFIG_TABLE_NAME'] ?? ''; -const commitId = process.env['COMMIT_ID'] ?? ''; -const homeRegion: string = process.env['HOME_REGION'] ?? ''; -const globalRegion: string = process.env['GLOBAL_REGION'] ?? ''; -const stackPrefix = process.env['STACK_PREFIX']!; - -const marshallOptions = { - convertEmptyValues: false, - //overriding default value of false - removeUndefinedValues: true, - convertClassInstanceToMap: false, -}; -const unmarshallOptions = { - wrapNumbers: false, -}; -const translateConfig = { marshallOptions, unmarshallOptions }; - -const dynamodbClient = new DynamoDBClient({ region: homeRegion, customUserAgent: solutionId }); -const documentClient = DynamoDBDocumentClient.from(dynamodbClient, translateConfig); -const paginationConfig: DynamoDBDocumentPaginationConfiguration = { - client: documentClient, - pageSize: 100, -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function handler(event: any): Promise { - console.log(JSON.stringify(event)); - - if (event.detailType != 'AWS Service Event via CloudTrail' && event.source != 'aws.organizations') { - console.error('Event was not the proper type.'); - return; - } - - if (event.detail.eventName === 'MoveAccount') { - const accountID: string = event.detail.requestParameters.accountId; - const destinationParentId: string = event.detail.requestParameters.destinationParentId; - const username: string = event.detail.userIdentity.sessionContext.sessionIssuer.userName; - - if (!username.includes(`${stackPrefix}-AccountsSt-`) && !username.includes(`${stackPrefix}-PrepareSta-`)) { - const organizationsClient = new AWS.Organizations({ region: globalRegion, customUserAgent: solutionId }); - - // Get all Ou details - const configAllOuKeys = await getConfigOuKeys(organizationsClient, configTableName, commitId); - - // Get all account details - const accountDetails = await getAccountDetails(configTableName, commitId, configAllOuKeys); - - const destSuspended = await isDestinationParentSuspended(configAllOuKeys, destinationParentId); - - const configTableSourceParent = accountDetails.find(item => item.accountId === accountID); - if (!configTableSourceParent) { - throw new Error(`Account id ${accountID} not found in config table !!!`); - } - - const eventDestParent = configAllOuKeys.find(item => item.awsKey === destinationParentId); - if (!eventDestParent) { - throw new Error(`Event's destination parent id ${destinationParentId} not found in organization !!!`); - } - - if (configTableSourceParent.ouId !== destinationParentId) { - if (destSuspended) { - console.log( - `Account id ${accountID} with email ${configTableSourceParent.email} was moved by non LZA user named ${username}, from source ou named ${configTableSourceParent.ouName} to a SUSPENDED(ignored) destination ou named ${eventDestParent.acceleratorKey}, event time was ${event.detail.eventTime}, assumed principal was ${event.detail.userIdentity.principalId}, change will NOT perform rollback !!!!`, - ); - return; - } - - console.warn( - `Account id ${accountID} with email ${configTableSourceParent.email} was moved by non LZA user named ${username}, from ${configTableSourceParent.ouName} ou to ${eventDestParent.acceleratorKey} ou, event time was ${event.detail.eventTime}, assumed principal was ${event.detail.userIdentity.principalId}, change will be rollback !!!!`, - ); - - console.log( - `Start: moving account id ${accountID} with email ${configTableSourceParent.email} from ${eventDestParent.acceleratorKey} ou to ${configTableSourceParent.ouName} ou`, - ); - try { - await throttlingBackOff(() => - organizationsClient - .moveAccount({ - AccountId: accountID, - DestinationParentId: configTableSourceParent.ouId, - SourceParentId: destinationParentId, - }) - .promise(), - ); - console.log( - `End: Account id ${accountID} with email ${configTableSourceParent.email} successfully moved from ${eventDestParent.acceleratorKey} ou to ${configTableSourceParent.ouName} ou`, - ); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'DuplicateAccountException' || - // SDKv3 Error Structure - e.name === 'DuplicateAccountException' - ) { - console.warn(e.name + ': ' + e.message); - } - } - } - } - } -} - -async function isDestinationParentSuspended( - configAllOuKeys: ConfigOrganizationalUnitKeys[], - destinationParentId: string, -): Promise { - const destParentOu = configAllOuKeys.find(ouItem => ouItem.awsKey === destinationParentId); - - if (!destParentOu) { - throw new Error(`Destination parent ou id ${destinationParentId} not found in organization !!!`); - } - if (destParentOu.ignore) { - return true; - } - return false; -} - -async function getAccountDetailsFromConfigTable( - configTableName: string, - commitId: string, - dataType: string, -): Promise { - const params: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': dataType, - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - }; - - const paginator = paginateQuery(paginationConfig, params); - - const items: DDBItems = []; - - for await (const page of paginator) { - if (page.Items) { - for (const item of page.Items) { - items.push(item); - } - } - } - return items; -} - -async function getAccountDetails( - configTableName: string, - commitId: string, - configAllOuKeys: ConfigOrganizationalUnitKeys[], -): Promise< - { accountType: string; accountId: string; email: string; ouName: string; ouId: string; ouIgnore: boolean }[] -> { - const accounts: { - accountType: string; - accountId: string; - email: string; - ouName: string; - ouId: string; - ouIgnore: boolean; - }[] = []; - const mandatoryAccounts = await getAccountDetailsFromConfigTable(configTableName, commitId, 'mandatoryAccount'); - - for (const mandatoryAccount of mandatoryAccounts ?? []) { - const ouId = configAllOuKeys.find(ouItem => ouItem.acceleratorKey === mandatoryAccount['ouName']); - - if (!ouId) { - throw new Error(`Ou ${mandatoryAccount['ouName']} not found in ${configTableName} !!!`); - } - - accounts.push({ - accountType: 'mandatoryAccount', - accountId: mandatoryAccount['awsKey'], - email: mandatoryAccount['acceleratorKey'], - ouName: mandatoryAccount['ouName'], - ouId: ouId.awsKey, - ouIgnore: ouId.ignore, - }); - } - - const workloadAccounts = await getAccountDetailsFromConfigTable(configTableName, commitId, 'workloadAccount'); - - for (const workloadAccount of workloadAccounts ?? []) { - const ouId = configAllOuKeys.find(ouItem => ouItem.acceleratorKey === workloadAccount['ouName']); - - if (!ouId) { - throw new Error(`Ou ${workloadAccount['ouName']} not found in ${configTableName} !!!`); - } - - accounts.push({ - accountType: 'workloadAccount', - accountId: workloadAccount['awsKey'], - email: workloadAccount['acceleratorKey'], - ouName: workloadAccount['ouName'], - ouId: ouId.awsKey, - ouIgnore: ouId.ignore, - }); - } - - return accounts; -} - -async function getConfigOuKeys( - organizationsClient: AWS.Organizations, - configTableName: string, - commitId: string, -): Promise { - const organizationParams: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'organization', - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - ProjectionExpression: 'acceleratorKey, awsKey, registered, dataBag', - }; - const organizationResponse = await throttlingBackOff(() => documentClient.send(new QueryCommand(organizationParams))); - const ouKeys: ConfigOrganizationalUnitKeys[] = []; - if (organizationResponse.Items) { - for (const item of organizationResponse.Items) { - const ouConfig = JSON.parse(item['dataBag']); - const ignored = ouConfig['ignore'] ?? false; - - ouKeys.push({ - acceleratorKey: item['acceleratorKey'], - awsKey: item['awsKey'], - registered: item['registered'] ?? undefined, - ignore: ignored, - }); - } - } - // get root ou key - const rootId = await getRootId(organizationsClient); - ouKeys.push({ - acceleratorKey: 'Root', - awsKey: rootId, - registered: true, - ignore: false, - }); - return ouKeys; -} - -async function getRootId(organizationsClient: AWS.Organizations): Promise { - // get root ou id - let rootId = ''; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => organizationsClient.listRoots({ NextToken: nextToken }).promise()); - for (const item of page.Roots ?? []) { - if (item.Name === 'Root' && item.Id && item.Arn) { - rootId = item.Id; - } - } - nextToken = page.NextToken; - } while (nextToken); - return rootId; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/package.json deleted file mode 100644 index c48b787..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-events-move-account", - "version": "0.0.0", - "private": true, - "description": "custom resource to move accounts", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/move-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/new-cloudwatch-log-event-rule.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/new-cloudwatch-log-event-rule.ts deleted file mode 100644 index 27f7990..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/new-cloudwatch-log-event-rule.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export type cloudwatchExclusionProcessedItem = { - account: string; - region: string; - excludeAll?: boolean; - logGroupNames?: string[]; -}; - -/** - * Construction properties for CloudWatch Logs Creating account. - */ - -export interface NewCloudWatchLogsEventProps { - /** - * - * Log Destination Arn to which all the logs will get forwarded to - */ - logDestinationArn: string; - /** - * - * KMS key to encrypt the Lambda environment variables, when undefined default AWS managed key will be used - */ - lambdaEnvKey?: cdk.aws_kms.IKey; - /** - * - * KMS key to encrypt the Lambda environment variables, when undefined default AWS managed key will be used - */ - logsKmsKey?: cdk.aws_kms.IKey; - /** - * - * CloudWatch Retention in days from global config - */ - logsRetentionInDaysValue: string; - /** - * - * Subscription Filter Arn - */ - subscriptionFilterRoleArn: string; - /** - * - * AWS Partition where code is being executed - */ - logArchiveAccountId: string; - /** - * Accelerator Prefix defaults to 'AWSAccelerator'. - */ - acceleratorPrefix: string; - /** - * Use existing IAM roles for deployment. - */ - useExistingRoles: boolean; - /** - * CloudWatch Logs exclusion setting - */ - exclusionSetting?: cloudwatchExclusionProcessedItem; -} - -/** - * Class to configure CloudWatch event when new log group is created - */ -export class NewCloudWatchLogEvent extends Construct { - constructor(scope: Construct, id: string, props: NewCloudWatchLogsEventProps) { - super(scope, id); - let LogSubscriptionRole: string; - if (props.useExistingRoles) { - LogSubscriptionRole = `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - props.acceleratorPrefix - }LogReplicationRole-${cdk.Stack.of(this).region}`; - } else { - LogSubscriptionRole = props.subscriptionFilterRoleArn; - } - - const newLogGroupRule = new cdk.aws_events.Rule(this, 'NewLogGroupCreatedRule', { - eventPattern: { - detailType: ['AWS API Call via CloudTrail'], - source: ['aws.logs'], - detail: { - eventSource: ['logs.amazonaws.com'], - eventName: ['CreateLogGroup'], - }, - }, - }); - - // Lambda function that sets expiration and puts subscription filter on - const lambdaEnvironmentList: - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[] = [ - { LogRetention: props.logsRetentionInDaysValue }, - { LogDestination: props.logDestinationArn }, - { LogSubscriptionRole: LogSubscriptionRole }, - { LogExclusion: JSON.stringify(props.exclusionSetting!) }, - ]; - - if (props.logsKmsKey) { - lambdaEnvironmentList.push({ LogKmsKeyArn: props.logsKmsKey.keyArn }); - } - - const lambdaEnvironment: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - } = {}; - - for (const environmentVariable of lambdaEnvironmentList) { - for (const [key, value] of Object.entries(environmentVariable)) { - lambdaEnvironment[key] = value; - } - } - - const setLogRetentionSubscriptionFunction = new cdk.aws_lambda.Function( - this, - 'SetLogRetentionSubscriptionFunction', - { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - timeout: cdk.Duration.minutes(15), - handler: 'index.handler', - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'put-subscription-policy/dist')), - environmentEncryption: props.lambdaEnvKey, - environment: lambdaEnvironment, - initialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'logs:PutRetentionPolicy', - 'logs:AssociateKmsKey', - 'logs:DescribeLogGroups', - 'logs:DescribeSubscriptionFilters', - ], - resources: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:*`, - ], - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['logs:PutSubscriptionFilter', 'logs:DeleteSubscriptionFilter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:*`, - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - props.logArchiveAccountId - }:destination:*`, - ], - }), - ], - }, - ); - // set basic trigger with 5 retries - newLogGroupRule.addTarget( - new cdk.aws_events_targets.LambdaFunction(setLogRetentionSubscriptionFunction, { retryAttempts: 5 }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/index.ts deleted file mode 100644 index 5b01346..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/index.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { wildcardMatch } from '@aws-accelerator/utils/lib/common-functions'; -import { ScheduledEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -export type cloudwatchExclusionProcessedItem = { - account: string; - region: string; - excludeAll?: boolean; - logGroupNames?: string[]; -}; - -const logSubscriptionRoleArn = process.env['LogSubscriptionRole']!; -const logDestinationArn = process.env['LogDestination']!; -const logRetention = process.env['LogRetention']!; -const logKmsKeyArn = process.env['LogKmsKeyArn']; -const logExclusionSetting = process.env['LogExclusion']!; -const solutionId = process.env['SOLUTION_ID']; - -const logsClient = new AWS.CloudWatchLogs({ customUserAgent: solutionId }); -let logExclusionParse: cloudwatchExclusionProcessedItem | undefined; -if (logExclusionSetting) { - logExclusionParse = JSON.parse(logExclusionSetting); -} else { - logExclusionParse = undefined; -} -export async function handler(event: ScheduledEvent) { - const logGroupName = event.detail.requestParameters.logGroupName as string; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - logsClient.describeLogGroups({ nextToken, logGroupNamePrefix: logGroupName }).promise(), - ); - for (const logGroup of page.logGroups ?? []) { - await updateRetentionPolicy(logRetention, logGroup); - await updateSubscriptionPolicy(logGroup, logExclusionParse); - await updateKmsKey(logGroup, logKmsKeyArn); - } - nextToken = page.nextToken; - } while (nextToken); -} - -export async function updateRetentionPolicy(logRetentionValue: string, logGroupValue: AWS.CloudWatchLogs.LogGroup) { - // filter out logGroups that already have retention set - if (logGroupValue.retentionInDays === parseInt(logRetentionValue)) { - console.log('Log Group: ' + logGroupValue.logGroupName! + ' has the right retention period'); - } else if (logGroupValue.logGroupName!.includes('aws-controltower')) { - console.log( - `Log Group: ${logGroupValue.logGroupName} retention cannot be changed as its enforced by AWS Control Tower`, - ); - } else { - console.log(`Setting retention of ${logRetentionValue} for log group ${logGroupValue.logGroupName}`); - await throttlingBackOff(() => - logsClient - .putRetentionPolicy({ - logGroupName: logGroupValue.logGroupName!, - retentionInDays: parseInt(logRetentionValue), - }) - .promise(), - ); - } -} - -export async function updateSubscriptionPolicy( - logGroup: AWS.CloudWatchLogs.LogGroup, - logExclusionSetting: cloudwatchExclusionProcessedItem | undefined, -) { - let isGroupExcluded = false; - if (logExclusionSetting) { - isGroupExcluded = await isLogGroupExcluded(logGroup.logGroupName!, logExclusionSetting); - } - - const subscriptionFilters = await getExistingSubscriptionFilters(logGroup.logGroupName!); - const hasAcceleratorFilter = await hasAcceleratorSubscriptionFilter(subscriptionFilters, logGroup.logGroupName!); - - if (isGroupExcluded && hasAcceleratorFilter) { - // delete accelerator filter if log group is excluded - await deleteSubscription(logGroup.logGroupName!); - } else if (!isGroupExcluded && !hasAcceleratorFilter) { - // create the accelerator subscription filter for this logGroup - await setupSubscription(logGroup.logGroupName!); - } -} - -export async function hasAcceleratorSubscriptionFilter( - filters: AWS.CloudWatchLogs.SubscriptionFilter[], - logGroupName: string, -) { - if (filters.length < 1) { - return false; - } else if (filters.some(filter => filter.filterName === logGroupName)) { - return true; - } - return false; -} - -export async function getExistingSubscriptionFilters(logGroupName: string) { - const subscriptionFilters = []; - - // check subscription on existing logGroup. - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - logsClient.describeSubscriptionFilters({ logGroupName: logGroupName, nextToken }).promise(), - ); - for (const subFilter of page.subscriptionFilters ?? []) { - subscriptionFilters.push(subFilter); - } - - nextToken = page.nextToken; - } while (nextToken); - return subscriptionFilters; -} - -export async function isLogGroupExcluded(logGroupName: string, logExclusionSetting: cloudwatchExclusionProcessedItem) { - if (logExclusionSetting.excludeAll) { - return true; - } else if (logExclusionSetting?.logGroupNames && logExclusionSetting.logGroupNames!.length > 0) { - // check input string and if matched return true - for (const excludeLogs of logExclusionSetting.logGroupNames) { - if (wildcardMatch(logGroupName, excludeLogs)) { - return true; - } - } - } - return false; -} - -export async function deleteSubscription(logGroupName: string) { - console.log(`Deleting subscription for log group ${logGroupName}`); - try { - await throttlingBackOff(() => - logsClient.deleteSubscriptionFilter({ logGroupName: logGroupName, filterName: logGroupName }).promise(), - ); - } catch (e) { - console.warn(`Failed to delete subscription filter ${logGroupName} for log group ${logGroupName}`); - } -} - -export async function setupSubscription(logGroupName: string) { - console.log(`Setting destination ${logDestinationArn} for log group ${logGroupName}`); - await throttlingBackOff(() => - logsClient - .putSubscriptionFilter({ - destinationArn: logDestinationArn, - logGroupName: logGroupName, - roleArn: logSubscriptionRoleArn, - filterName: logGroupName, - filterPattern: '', - }) - .promise(), - ); -} - -export async function updateKmsKey(logGroupValue: AWS.CloudWatchLogs.LogGroup, logKmsKeyArn?: string) { - // check kmsKey on existing logGroup. - if (logGroupValue.kmsKeyId) { - // if there is a KMS do nothing - console.log('Log Group: ' + logGroupValue.logGroupName! + ' has kms set'); - return; - } - if (!logKmsKeyArn) { - // when no Kms Key arn provided - console.log( - `Accelerator KMK key ${logKmsKeyArn} not provided for Log Group ${logGroupValue.logGroupName!}, log group encryption not performed`, - ); - return; - } - // there is no KMS set one - console.log(`Setting KMS for log group ${logGroupValue.logGroupName}`); - await throttlingBackOff(() => - logsClient - .associateKmsKey({ - logGroupName: logGroupValue.logGroupName!, - kmsKeyId: logKmsKeyArn, - }) - .promise(), - ); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/package.json deleted file mode 100644 index 6ad429e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-events-put-subscription-policy", - "version": "0.0.0", - "private": true, - "description": "custom resource to add a subscription policy", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/put-subscription-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes.ts deleted file mode 100644 index 051fa9f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; - -import * as fs from 'fs'; -import * as path from 'path'; - -/** - * Organizations Revert Scp Changes - * This construct creates a Lambda function and eventbridge rule to trigger on - * service control policy (scp) changes as well as attach and detach actions. Upon - * receiving an event, the Lambda function will evaluate the change and revert to the - * state defined by the organization-config file. - */ -export interface RevertScpChangesProps { - /** - * Prefix for accelerator resources - */ - readonly acceleratorPrefix: string; - /** - * Configuration directory path - */ - readonly configDirPath: string; - /** - * Accelerator home region - */ - readonly homeRegion: string; - /** - * Lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKeyCloudWatch?: cdk.aws_kms.IKey; - /** - * Lambda environment variable encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKeyLambda?: cdk.aws_kms.IKey; - /** - * Lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Accelerator SNS topic name Prefix - */ - readonly acceleratorTopicNamePrefix: string; - /** - * SNS Topic Name to publish notifications to - */ - readonly snsTopicName: string | undefined; - /** - * SCP File Paths - */ - readonly scpFilePaths: { name: string; path: string; tempPath: string }[]; - /** - * Single Account mode - */ - readonly singleAccountMode: boolean; - /** - * Organization enabled - */ - readonly organizationEnabled: boolean; -} - -export class RevertScpChanges extends Construct { - constructor(scope: Construct, id: string, props: RevertScpChangesProps) { - super(scope, id); - - this.copyPoliciesToDeploymentPackage(props.scpFilePaths); - this.copyConfigsToDeploymentPackage(['accounts-config.yaml', 'organization-config.yaml'], props.configDirPath); - - const LAMBDA_TIMEOUT_IN_MINUTES = 1; - let snsTopicArn = ''; - - const kmsEncryptMessage = new cdk.aws_iam.PolicyStatement({ - sid: 'kmsEncryptMessage', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Encrypt', 'kms:GenerateDataKey'], - resources: ['*'], - }); - - const orgPolicyUpdate = new cdk.aws_iam.PolicyStatement({ - sid: 'OrgPolicyUpdate', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'organizations:AttachPolicy', - 'organizations:DetachPolicy', - 'organizations:DescribePolicy', - 'organizations:ListAccounts', - 'organizations:ListRoots', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:UpdatePolicy', - ], - resources: ['*'], - }); - - const revertScpChangesPolicyList = [kmsEncryptMessage, orgPolicyUpdate]; - - if (props.snsTopicName) { - snsTopicArn = `arn:${cdk.Stack.of(this).partition}:sns:${props.homeRegion}:${cdk.Stack.of(this).account}:${ - props.acceleratorTopicNamePrefix - }-${props.snsTopicName}`; - revertScpChangesPolicyList.push( - new cdk.aws_iam.PolicyStatement({ - sid: 'snsPublishMessage', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sns:Publish'], - resources: [snsTopicArn], - }), - ); - } - - const revertScpChangesFunction = new cdk.aws_lambda.Function(this, 'RevertScpChangesFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'revert-scp-changes/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to revert changes made to LZA-controlled service control policies', - timeout: cdk.Duration.minutes(LAMBDA_TIMEOUT_IN_MINUTES), - environment: { - ACCELERATOR_PREFIX: props.acceleratorPrefix, - AWS_PARTITION: cdk.Aws.PARTITION, - HOME_REGION: props.homeRegion, - SNS_TOPIC_ARN: snsTopicArn ?? '', - SINGLE_ACCOUNT_MODE: `${props.singleAccountMode}`, - ORGANIZATIONS_ENABLED: `${props.organizationEnabled}`, - }, - environmentEncryption: props.kmsKeyLambda, - initialPolicy: revertScpChangesPolicyList, - }); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressions(revertScpChangesFunction.role!, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressions( - revertScpChangesFunction.role!, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - true, - ); - - const modifyScpRule = new cdk.aws_events.Rule(this, 'ModifyScpRule', { - eventPattern: { - source: ['aws.organizations'], - detailType: ['AWS API Call via CloudTrail'], - detail: { - eventSource: ['organizations.amazonaws.com'], - eventName: ['AttachPolicy', 'DetachPolicy', 'UpdatePolicy'], - }, - }, - description: 'Rule to notify when an LZA-managed SCP is modified or detached.', - }); - - modifyScpRule.addTarget( - new cdk.aws_events_targets.LambdaFunction(revertScpChangesFunction, { - maxEventAge: cdk.Duration.hours(4), - retryAttempts: 2, - }), - ); - - new cdk.aws_logs.LogGroup(this, `${revertScpChangesFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${revertScpChangesFunction.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKeyCloudWatch, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - } - - // Copies Service Control Policy files to the Lambda directory for packaging - private copyPoliciesToDeploymentPackage(filePaths: { name: string; path: string; tempPath: string }[]) { - const deploymentPackagePath = path.join(__dirname, 'revert-scp-changes/dist'); - - // Make policy folder - fs.mkdirSync(path.join(deploymentPackagePath, 'policies'), { recursive: true }); - - for (const policyFilePath of filePaths) { - // Create subdirectories if they don't exist - fs.mkdirSync(path.dirname(path.join(deploymentPackagePath, 'policies', policyFilePath.path)), { - recursive: true, - }); - //copy from generated temp path to original policy path - fs.copyFileSync( - path.join(policyFilePath.tempPath), - path.join(deploymentPackagePath, 'policies', policyFilePath.path), - ); - } - } - - // Copies a list of files from the configuration directory to the Lambda deployment package - private copyConfigsToDeploymentPackage(fileNames: string[], configDirPath: string) { - const deploymentPackagePath = path.join(__dirname, 'revert-scp-changes/dist'); - - // Make config folder - fs.mkdirSync(path.join(deploymentPackagePath, 'config'), { recursive: true }); - - for (const fileName of fileNames) { - fs.copyFileSync(path.join(configDirPath, fileName), path.join(deploymentPackagePath, 'config', fileName)); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/index.ts deleted file mode 100644 index ba2d7a4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/index.ts +++ /dev/null @@ -1,295 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; -import * as path from 'path'; - -import { AccountsConfig } from '@aws-accelerator/config/lib/accounts-config'; -import { OrganizationConfig } from '@aws-accelerator/config/lib/organization-config'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { ScheduledEvent } from '@aws-accelerator/utils/lib/common-types'; - -let organizationsClient: AWS.Organizations; - -const acceleratorRolePrefix = process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'; -const snsTopicArn = process.env['SNS_TOPIC_ARN']; -const partition = process.env['AWS_PARTITION']!; -const homeRegion = process.env['HOME_REGION']!; -const isOrgsEnabled = process.env['ORGANIZATIONS_ENABLED'] === 'true'; -const singleAccountMode = process.env['SINGLE_ACCOUNT_MODE'] === 'true'; - -const snsClient = new AWS.SNS({ region: homeRegion }); - -if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1' }); -} else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1' }); -} else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1' }); -} - -/** - * revert-scp-changes - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: ScheduledEvent): Promise<{ statusCode: number }> { - console.log(`Event: ${JSON.stringify(event, null, 2)}`); - - const eventName: string = event.detail.eventName; - const policyId: string = event.detail.requestParameters.policyId; - const userIdentityArn: string = event.detail.userIdentity.arn; - - // Exits if the scp change was made by the Accelerator - if (await isChangeMadeByAccelerator(userIdentityArn)) { - console.log('SCP modification performed by LZA role, exiting'); - return { - statusCode: 200, - }; - } - - const policyDetail: AWS.Organizations.Policy = await getPolicyDetails(policyId); - const organizationConfig = await getOrganizationConfig(); - const accountsConfig = await getAccountConfig(); - - if (await isControlTowerPolicy(policyDetail)) { - console.log('SCP owned by Control Tower, exiting'); - return { - statusCode: 200, - }; - } - - if (policyDetail?.PolicySummary?.Type && policyDetail?.PolicySummary?.Type !== 'SERVICE_CONTROL_POLICY') { - console.log('Policy is not a service control policy, exiting'); - return { - statusCode: 200, - }; - } - - const policyManagedByAccelerator = await isPolicyManagedByAccelerator( - policyDetail.PolicySummary!.Name!, - organizationConfig, - ); - - if (eventName === 'UpdatePolicy') { - if (policyManagedByAccelerator) { - await revertScpModification(policyDetail, organizationConfig); - } else { - console.log('Updated policy not managed by accelerator, exiting'); - return { - statusCode: 200, - }; - } - } else if (['DetachPolicy', 'AttachPolicy'].includes(eventName)) { - const targetId: string = event.detail.requestParameters.targetId; - const targetManagedByAccelerator: boolean = await isTargetManagedByAccelerator( - targetId, - organizationConfig, - accountsConfig, - ); - if (!policyManagedByAccelerator && !targetManagedByAccelerator) { - console.log(`Policy ${policyId} and target ${targetId} not managed by accelerator, exiting`); - return { - statusCode: 200, - }; - } - - if (eventName === 'DetachPolicy') { - await reattachScp(policyId, targetId); - } else if (eventName === 'AttachPolicy') { - await detachScp(policyId, targetId); - } - } - - return { - statusCode: 200, - }; -} - -async function reattachScp(policyId: string, target: string): Promise { - console.log(`Reattaching policy ${policyId} to target ${target}`); - const attachPolicyParams = { PolicyId: policyId, TargetId: target }; - await throttlingBackOff(() => organizationsClient.attachPolicy(attachPolicyParams).promise()); - await publishSuccessToSns( - `A manual SCP modification was automatically reverted by the Landing Zone Accelerator. Policy ${policyId} was reattached to ${target}.`, - ); -} - -async function detachScp(policyId: string, target: string): Promise { - console.log(`Detaching policy ${policyId} from target ${target}`); - const detachPolicyParams = { PolicyId: policyId, TargetId: target }; - await throttlingBackOff(() => organizationsClient.detachPolicy(detachPolicyParams).promise()); - await publishSuccessToSns( - `A manual SCP modification was automatically reverted by the Landing Zone Accelerator. Policy ${policyId} was detached from ${target}.`, - ); -} - -async function revertScpModification( - policyDetail: AWS.Organizations.Policy, - orgConfig: OrganizationConfig, -): Promise { - const policyName = policyDetail.PolicySummary?.Name; - const policyId = policyDetail.PolicySummary?.Id; - if (!policyName || !policyId) { - await publishErrorToSns(`Error automatically remediating SCP modification. Investigate recent changes to SCPs.`); - throw Error(`SCP ${policyId} not found, exiting`); - } - - const originalPolicy = await getOriginalPolicyDocument(policyName, orgConfig); - console.log(`Original policy document: \n${originalPolicy}`); - console.log('Performing update to revert policy to accelerator configuration'); - const updatePolicyParams = { Content: originalPolicy, PolicyId: policyId }; - await throttlingBackOff(() => organizationsClient.updatePolicy(updatePolicyParams).promise()); - await publishSuccessToSns( - `A manual SCP modification was automatically reverted by the Landing Zone Accelerator. Policy ${policyId} was updated to match the configuration provided in the aws-accelerator-config repository.`, - ); - console.log('Policy update successful'); -} - -async function getPolicyDetails(policyId: string): Promise { - const getPolicyParams = { PolicyId: policyId }; - - try { - const response = await throttlingBackOff(() => organizationsClient.describePolicy(getPolicyParams).promise()); - return response.Policy!; - } catch (e) { - console.error(e); - await publishErrorToSns(`Error automatically remediating SCP modification. Investigate recent changes to SCPs.`); - throw Error(`Error fetching policy details for policy ${policyId} , exiting`); - } -} - -async function isControlTowerPolicy(policyDetail: AWS.Organizations.Policy): Promise { - const policyName = policyDetail?.PolicySummary?.Name; - if (policyName?.startsWith('aws-guardrails-')) { - return true; - } - return false; -} - -async function getOriginalPolicyDocument(policyName: string, orgConfig: OrganizationConfig): Promise { - const policyPath = await getOriginalPolicyDocumentPath(policyName, orgConfig); - return JSON.stringify(require(path.join(__dirname, 'policies', policyPath))); -} - -async function getOrganizationConfig(): Promise { - const organizationConfig = OrganizationConfig.load(path.join(__dirname, '/config')); - await organizationConfig.loadOrganizationalUnitIds(partition); - if (!organizationConfig) { - throw Error('Error parsing organization-config file, object undefined'); - } - return organizationConfig; -} - -async function getAccountConfig(): Promise { - const accountsConfig = AccountsConfig.load(path.join(__dirname, '/config')); - await accountsConfig.loadAccountIds(partition, singleAccountMode, isOrgsEnabled, accountsConfig); - if (!accountsConfig) { - await publishErrorToSns(`Error automatically remediating SCP modification. Investigate recent changes to SCPs.`); - throw Error('Error parsing account-config file, object undefined'); - } - return accountsConfig; -} - -async function getOriginalPolicyDocumentPath(policyName: string, orgConfig: OrganizationConfig): Promise { - for (const policyDetail of orgConfig.serviceControlPolicies) { - if (policyName === policyDetail.name) { - return policyDetail.policy; - } - } - throw Error(`Policy ${policyName} not found in organization-config file`); -} - -async function isChangeMadeByAccelerator(principalArn: string): Promise { - console.log(`SCP modification performed by ${principalArn}`); - const principalArr = principalArn.split(':'); - const principal = principalArr[principalArr.length - 1]; - const roleNameArr = principal.split('/'); - const roleName = roleNameArr[roleNameArr.length - 1]; - - if (roleName?.startsWith(acceleratorRolePrefix)) { - return true; - } else { - return false; - } -} - -async function isPolicyManagedByAccelerator(policyName: string, orgConfig: OrganizationConfig): Promise { - const policyNames = orgConfig.serviceControlPolicies?.map(a => a.name); - if (policyNames && policyNames.includes(policyName)) { - return true; - } - return false; -} - -async function isTargetManagedByAccelerator( - targetId: string, - orgConfig: OrganizationConfig, - acctConfig: AccountsConfig, -): Promise { - if (targetId.startsWith('ou-')) { - const organizationUnitIds = orgConfig.organizationalUnitIds?.map(a => a.id); - if (organizationUnitIds?.includes(targetId)) { - return true; - } else { - return false; - } - } else { - const accountIds = acctConfig.accountIds?.map(a => a.accountId); - if (accountIds?.includes(targetId)) { - return true; - } else { - return false; - } - } -} - -async function publishErrorToSns(errorMessage: string): Promise { - const publishParams = { - Message: errorMessage, - Subject: 'Service Control Policy Remediation Failure', - TopicArn: snsTopicArn, - }; - if (!snsTopicArn) { - console.log('SNS Topic not configured, publishing error message to logs'); - console.log(errorMessage); - return; - } - try { - await throttlingBackOff(() => snsClient.publish(publishParams).promise()); - } catch (e) { - console.error(e); - } - return; -} - -async function publishSuccessToSns(successMessage: string): Promise { - const publishParams = { - Message: successMessage, - Subject: 'Service Control Policy Remediation Success', - TopicArn: snsTopicArn, - }; - if (!snsTopicArn) { - console.log('SNS Topic not configured, publishing success message to logs'); - console.log(successMessage); - return; - } - try { - await throttlingBackOff(() => snsClient.publish(publishParams).promise()); - } catch (e) { - console.error(e); - } - return; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/package.json deleted file mode 100644 index bca7f48..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-events-revert-scp-changes", - "version": "0.0.0", - "private": true, - "description": "Custom resource to revert non-accelerator scp changes.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/revert-scp-changes/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/index.ts deleted file mode 100644 index 63650cf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { - CloudWatchLogsClient, - CreateLogGroupCommand, - PutResourcePolicyCommand, - ResourceAlreadyExistsException, -} from '@aws-sdk/client-cloudwatch-logs'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -const solutionId = process.env['SOLUTION_ID']; -const logsClient = new CloudWatchLogsClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - -export async function handler(event: CloudFormationCustomResourceEvent) { - console.log(event); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const logGroupName = event.ResourceProperties['logGroupName']; - const logGroupArn = event.ResourceProperties['logGroupArn']; - - // Create the log group /AWSAccelerator-SecurityHub (if a specified log group name wasn't provided) if it does not exist. - await createLogGroup(logGroupName); - // Update resource policy - await putLogGroupResourcePolicy(logGroupArn); - return { - PhysicalResourceId: undefined, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -export async function putLogGroupResourcePolicy(logGroupArn: string) { - const policyDocument = await generateResourcePolicy(logGroupArn); - console.log('Attempting to update resource policy on CloudWatch log group'); - try { - await logsClient.send( - new PutResourcePolicyCommand({ - policyDocument, - policyName: 'TrustEventsToStoreLogEvent', - }), - ); - } catch (e: unknown) { - console.log('Encountered an error putting resource policy on log group'); - throw e; - } -} - -export async function generateResourcePolicy(logGroupArn: string) { - return JSON.stringify({ - Statement: [ - { - Action: ['logs:CreateLogStream', 'logs:PutLogEvents'], - Effect: 'Allow', - Principal: { - Service: ['events.amazonaws.com', 'delivery.logs.amazonaws.com'], - }, - Resource: logGroupArn, - Sid: 'TrustEventsToStoreLogs', - }, - ], - Version: '2012-10-17', - }); -} - -export async function createLogGroup(logGroupName: string) { - try { - console.log(`Attempting to create CloudWatch log group ${logGroupName}`); - await logsClient.send(new CreateLogGroupCommand({ logGroupName: logGroupName })); - } catch (e) { - if (e instanceof ResourceAlreadyExistsException) { - console.log(`Found existing CloudWatch log group ${logGroupName}, continuing`); - } else { - throw e; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/package.json deleted file mode 100644 index a0af932..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-events-security-hub-events-log", - "version": "0.0.0", - "private": true, - "description": "Sends all Security Hub Findings to a Lambda that writes to CloudWatch Logs", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-cloudwatch-logs": "3.410.0", - "@aws-sdk/client-sns": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/index.test.ts deleted file mode 100644 index b7a4375..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/index.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - CloudWatchLogsClient, - CreateLogGroupCommand, - ResourceAlreadyExistsException, - PutResourcePolicyCommand, -} from '@aws-sdk/client-cloudwatch-logs'; -import { describe, beforeEach, expect, test } from '@jest/globals'; - -import { generateResourcePolicy, handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; -import { StaticInput } from './static-input'; - -const logsClient = AcceleratorMockClient(CloudWatchLogsClient); - -describe('Create Event, happy path', () => { - beforeEach(() => { - logsClient.reset(); - }); - test('Log group with policy created successfully.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - - logsClient - .on(CreateLogGroupCommand, { - logGroupName: StaticInput.newProps.logGroupName, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const policyDocument = await generateResourcePolicy(StaticInput.newProps.logGroupArn); - logsClient - .on(PutResourcePolicyCommand, { - policyDocument: policyDocument, - policyName: 'TrustEventsToStoreLogEvent', - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual({ PhysicalResourceId: undefined, Status: 'SUCCESS' }); - }); - - test('Create Event, log group already exists.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - - logsClient - .on(CreateLogGroupCommand, { - logGroupName: StaticInput.newProps.logGroupName, - }) - .rejects(new ResourceAlreadyExistsException({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - - const policyDocument = await generateResourcePolicy(StaticInput.newProps.logGroupArn); - logsClient - .on(PutResourcePolicyCommand, { - policyDocument: policyDocument, - policyName: 'TrustEventsToStoreLogEvent', - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual({ PhysicalResourceId: undefined, Status: 'SUCCESS' }); - }); - - test('Create Event, encountered error creating log group.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - - logsClient - .on(CreateLogGroupCommand, { - logGroupName: StaticInput.newProps.logGroupName, - }) - .rejects(); - - await expect(handler(event)).rejects.toThrowError(); - }); - - test('Update event, happy path', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - - logsClient - .on(CreateLogGroupCommand, { - logGroupName: StaticInput.newProps.logGroupName, - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const policyDocument = await generateResourcePolicy(StaticInput.newProps.logGroupArn); - logsClient - .on(PutResourcePolicyCommand, { - policyDocument: policyDocument, - policyName: 'TrustEventsToStoreLogEvent', - }) - .resolves({ - $metadata: { httpStatusCode: 200 }, - }); - - const response = await handler(event); - expect(response).toStrictEqual({ PhysicalResourceId: undefined, Status: 'SUCCESS' }); - }); - - test('Update Event, error updating resource policy.', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - - logsClient - .on(CreateLogGroupCommand, { - logGroupName: StaticInput.newProps.logGroupName, - }) - .rejects(new ResourceAlreadyExistsException({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - - const policyDocument = await generateResourcePolicy(StaticInput.newProps.logGroupArn); - logsClient - .on(PutResourcePolicyCommand, { - policyDocument: policyDocument, - policyName: 'TrustEventsToStoreLogEvent', - }) - .rejects(); - - await expect(handler(event)).rejects.toThrowError(); - }); - - test('Delete event, happy path', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - - const response = await handler(event); - expect(response).toStrictEqual({ PhysicalResourceId: 'PhysicalResourceId', Status: 'SUCCESS' }); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/static-input.ts deleted file mode 100644 index e7eae20..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/test/static-input.ts +++ /dev/null @@ -1,6 +0,0 @@ -export abstract class StaticInput { - public static readonly newProps = { - logGroupName: '/AWSAccelerator-SecurityHub', - logGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/*:*', - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-event-log/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-events-log.ts b/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-events-log.ts deleted file mode 100644 index 81ee61e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-events/security-hub-events-log.ts +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { LzaCustomResource } from '../lza-custom-resource'; - -export interface SecurityHubEventsLogProps { - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * Number of days to retain CloudWatch logs - */ - readonly cloudWatchLogRetentionInDays: number; - /** - * SNS Topic Arn that notification will be delivered to - */ - snsTopicArn?: string; - /** - * SNS KMS Key - */ - snsKmsKey?: cdk.aws_kms.IKey; - /** - * Alert level - */ - notificationLevel?: string; - /** - * Log level - */ - logLevel?: string; - /** - * Account Lambda key for environment encryption - */ - lambdaKey?: cdk.aws_kms.IKey; - /** - * Log Group Name - */ - logGroupName?: string; -} - -/** - * Send all Security Hub events to CloudWatch Logs - */ -export class SecurityHubEventsLog extends Construct { - constructor(scope: Construct, id: string, props: SecurityHubEventsLogProps) { - super(scope, id); - const logGroupName = props.logGroupName ?? `/${props.acceleratorPrefix}-SecurityHub`; - const logGroupArn = `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:/*:*`; - - // Create custom resource Lambda to create CloudWatch Logs Group target and update resource policy - const customResource = new LzaCustomResource(this, 'SecurityHubEventsFunction', { - resource: { - name: 'SecurityHubEventsFunction', - parentId: id, - properties: [{ logGroupName: logGroupName }, { logGroupArn: logGroupArn }], - forceUpdate: true, - }, - lambda: { - assetPath: path.join(__dirname, 'security-hub-event-log/dist'), - description: - 'Creates a CloudWatch Logs Group to store SecurityHub findings and updates CW Log Group resource policy', - timeOut: cdk.Duration.minutes(3), - environmentEncryptionKmsKey: props.lambdaKey, - cloudWatchLogRetentionInDays: props.cloudWatchLogRetentionInDays, - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - actions: ['logs:CreateLogGroup', 'logs:CreateLogStream'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:${logGroupName}*`, - ], - }), - // Describe call needs access to entire region and account - new cdk.aws_iam.PolicyStatement({ - actions: ['logs:DescribeLogGroups', 'logs:PutResourcePolicy'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:log-group:*`, - ], - }), - ], - }, - nagSuppressionPrefix: 'SecurityHubEventsLog', - }); - const logGroup = cdk.aws_logs.LogGroup.fromLogGroupName(this, 'ExistingShLogGroup', logGroupName); - logGroup.node.addDependency(customResource); - - // Create EventBridge rule targeting CloudWatch Log Group - const securityHubEventsRule = new cdk.aws_events.CfnRule(this, 'SecurityHubLogEventsRule', { - description: 'Sends Security Hub Findings above threshold to CloudWatch Logs', - eventPattern: this.getLogRuleEventPattern(props.logLevel), - targets: [{ arn: logGroup.logGroupArn, id: 'CloudWatchLogTarget' }], - }); - securityHubEventsRule.node.addDependency(customResource); - - // Create EventBridge rule targeting SNS Topic - if (props.snsTopicArn && props.notificationLevel) { - new cdk.aws_events.CfnRule(this, 'SecurityHubSnsEventsRule', { - description: 'Sends Security Hub Findings above threshold to SNS', - eventPattern: { - source: ['aws.securityhub'], - 'detail-type': ['Security Hub Findings - Imported'], - detail: { - findings: { - Severity: { - Label: this.getSeverityLevelArray(props.notificationLevel), - }, - }, - }, - }, - targets: [{ arn: props.snsTopicArn, id: 'SnsTarget' }], - }); - } - } - - private getLogRuleEventPattern(logLevel?: string) { - if (logLevel) { - return { - source: ['aws.securityhub'], - 'detail-type': ['Security Hub Findings - Imported'], - detail: { - findings: { - Severity: { - Label: this.getSeverityLevelArray(logLevel), - }, - }, - }, - }; - } else { - return { - source: ['aws.securityhub'], - 'detail-type': ['Security Hub Findings - Imported'], - }; - } - } - - private getSeverityLevelArray(level: string) { - switch (level) { - case 'CRITICAL': - return ['CRITICAL']; - case 'HIGH': - return ['CRITICAL', 'HIGH']; - case 'MEDIUM': - return ['CRITICAL', 'HIGH', 'MEDIUM']; - case 'LOW': - return ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']; - case 'INFORMATIONAL': - default: - return ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFORMATIONAL']; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/cloudwatch-to-s3-firehose.ts b/source/packages/@aws-accelerator/constructs/lib/aws-firehose/cloudwatch-to-s3-firehose.ts deleted file mode 100644 index 9e29d1d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/cloudwatch-to-s3-firehose.ts +++ /dev/null @@ -1,418 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { pascalCase } from 'change-case'; -import { NagSuppressions } from 'cdk-nag'; -import * as path from 'path'; -import * as fs from 'fs'; - -/** - * Construction properties for CloudWatch to S3 replication for Kinesis Stream. - */ -export interface CloudWatchToS3FirehoseProps { - /** - * - * Dynamic partitioning as JSON string array to partition records in firehose - */ - dynamicPartitioningValue?: string; - /** - * - * Source bucket object is must when source bucket name wasn't provided - * This bucket will have all the CloudWatch Logs - */ - bucket?: cdk.aws_s3.IBucket; - /** - * - * Source bucket name is must when source bucket object wasn't provided - * This bucket will have all the CloudWatch Logs - */ - bucketName?: string; - /** - * - * Kinesis Stream which will trigger Firehose - */ - kinesisStream: cdk.aws_kinesis.IStream; - /** - * - * KMS key to encrypt the Firehose - */ - firehoseKmsKey: cdk.aws_kms.IKey; - /** - * - * KMS key to encrypt the Lambda, when undefined default AWS managed key will be used - */ - lambdaKey?: cdk.aws_kms.IKey; - /** - * - * Home region where the log archive bucket is located. - */ - homeRegion: string; - /** - * - * KMS key to encrypt the Lambda - */ - kinesisKmsKey: cdk.aws_kms.IKey; - /** - * - * Config directory path - */ - configDir: string; - /** - * Accelerator Prefix defaults to 'AWSAccelerator'. - */ - acceleratorPrefix: string; - /** - * Use existing IAM roles for deployment. - */ - useExistingRoles: boolean; - /** - * Firehose preprocessor function name - */ - firehoseRecordsProcessorFunctionName: string; - /** - * - * KMS key to encrypt the CloudWatch log group, when undefined default AWS managed key will be used - */ - logsKmsKey?: cdk.aws_kms.IKey; - /** - * - * CloudWatch Logs Retention in days from global config - */ - logsRetentionInDaysValue: string; -} -/** - * Class to configure CloudWatch replication on logs receiving account - */ -export class CloudWatchToS3Firehose extends Construct { - constructor(scope: Construct, id: string, props: CloudWatchToS3FirehoseProps) { - super(scope, id); - - if (props.dynamicPartitioningValue) { - this.packageDynamicPartitionInDeployment(props.configDir, props.dynamicPartitioningValue); - } - - let logsStorageBucket: cdk.aws_s3.IBucket; - - if ((!props.bucket && !props.bucketName) || (props.bucket && props.bucketName)) { - throw new Error( - 'Either source bucket or source bucketName property must be defined. Only one property must be defined.', - ); - } - - if (props.bucketName) { - logsStorageBucket = cdk.aws_s3.Bucket.fromBucketName(this, `${pascalCase(props.bucketName)}`, props.bucketName); - } else { - logsStorageBucket = props.bucket!; - } - - const firehosePrefixProcessingLambda = new cdk.aws_lambda.Function(this, 'FirehosePrefixProcessingLambda', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - functionName: props.firehoseRecordsProcessorFunctionName, - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'firehose-record-processing/dist')), - handler: 'index.handler', - memorySize: 2048, - timeout: cdk.Duration.minutes(5), - environmentEncryption: props.lambdaKey, - environment: { - DynamicS3LogPartitioningMapping: props.dynamicPartitioningValue!, - KinesisStreamArn: props.kinesisStream.streamArn, - }, - }); - // Allow lambda to place records back to kinesis stream if the payload is over 6MB - firehosePrefixProcessingLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - actions: ['kinesis:PutRecords', 'kinesis:PutRecord', 'kinesis:ListShards'], - resources: [props.kinesisStream.streamArn], - }), - ); - // Allow lambda use to kinesis stream kms key - firehosePrefixProcessingLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:ReEncryptTo', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyPair', - 'kms:ReEncryptFrom', - ], - resources: [props.kinesisKmsKey.keyArn], - }), - ); - const firehoseCloudwatchLogs = new cdk.aws_logs.LogGroup(this, 'FirehoseCloudwatchLogs', { - logGroupName: `/accelerator/logs/firehose/${props.kinesisStream.streamName}`, - retention: parseInt(props.logsRetentionInDaysValue), - encryptionKey: props.logsKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const firehoseCloudwatchLogStream = new cdk.aws_logs.LogStream(this, 'FirehoseCloudWatchLogStream', { - logGroup: firehoseCloudwatchLogs, - logStreamName: 'DestinationDeliveryError', - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const firehose = new cdk.aws_kinesisfirehose.CfnDeliveryStream(this, 'FirehoseStream', { - deliveryStreamType: 'KinesisStreamAsSource', - kinesisStreamSourceConfiguration: { - kinesisStreamArn: props.kinesisStream.streamArn, - roleArn: this.createKinesisStreamRole( - props.kinesisStream.streamArn, - props.kinesisKmsKey.keyArn, - props.acceleratorPrefix, - props.useExistingRoles, - ), - }, - extendedS3DestinationConfiguration: { - bucketArn: logsStorageBucket.bucketArn, - bufferingHints: { - intervalInSeconds: 60, - sizeInMBs: 64, // Minimum value that this can take - }, - cloudWatchLoggingOptions: { - enabled: true, - logGroupName: firehoseCloudwatchLogs.logGroupName, - logStreamName: firehoseCloudwatchLogStream.logStreamName, - }, - compressionFormat: 'UNCOMPRESSED', - dataFormatConversionConfiguration: { - enabled: false, - }, - roleArn: this.createFirehoseServiceRole( - props.firehoseKmsKey.keyArn, - props.homeRegion, - logsStorageBucket.bucketArn, - firehosePrefixProcessingLambda.functionArn, - props.acceleratorPrefix, - props.useExistingRoles, - firehoseCloudwatchLogs, - firehoseCloudwatchLogStream.logStreamName, - props.logsKmsKey, - ), - dynamicPartitioningConfiguration: { - enabled: true, - }, - errorOutputPrefix: `CloudWatchLogs/processing-failed`, - encryptionConfiguration: { - kmsEncryptionConfig: { - awskmsKeyArn: props.firehoseKmsKey.keyArn, - }, - }, - prefix: '!{partitionKeyFromLambda:dynamicPrefix}', - processingConfiguration: { - enabled: true, - processors: [ - { - type: 'Lambda', - parameters: [ - { - parameterName: 'LambdaArn', - parameterValue: firehosePrefixProcessingLambda.functionArn, - }, - { - parameterName: 'NumberOfRetries', - parameterValue: '3', - }, - { - // The AWS Lambda function has a 6 MB invocation payload quota. Your data can expand in size after it's processed by the AWS Lambda function. A smaller buffer size allows for more room should the data expand after processing. - // Minimum: 0.2 MB, maximum: 3 MB. - // setting to minimum to allow for large spikes in log traffic to firehose - parameterName: 'BufferSizeInMBs', - parameterValue: '0.2', - }, - { - // The period of time during which Kinesis Data Firehose buffers incoming data before invoking the AWS Lambda function. The AWS Lambda function is invoked once the value of the buffer size or the buffer interval is reached. - // Minimum: 60 seconds, maximum: 900 seconds - // setting minimum so that lambda function is invoked frequently - parameterName: 'BufferIntervalInSeconds', - parameterValue: '60', - }, - ], - }, - ], - }, - }, - }); - - const stack = cdk.Stack.of(scope); - - // FirehosePrefixProcessingLambda/ServiceRole AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${firehosePrefixProcessingLambda.node.path}/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Managed policy for Lambda basic execution attached.', - }, - ], - ); - - // Kinesis-Firehose-Stream-Dynamic-Partitioning AwsSolutions-KDF1: The Kinesis Data Firehose delivery stream does have server-side encryption enabled. - NagSuppressions.addResourceSuppressionsByPath(stack, `${firehose.node.path}`, [ - { - id: 'AwsSolutions-KDF1', - reason: 'Customer managed key is used to encrypt firehose delivery stream.', - }, - ]); - - // FirehoseToS3Setup/FirehoseServiceRole/Resource AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission. - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/FirehoseToS3Setup/FirehoseServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: - 'Bucket permissions are wildcards to abort downloads and clean up objects. KMS permissions are wildcards to re-encrypt entities.', - }, - ], - ); - } - private packageDynamicPartitionInDeployment(configDirPath: string, dynamicPartitionPath: string) { - const deploymentPackagePath = path.join(__dirname, 'firehose-record-processing/dist'); - - // Make deployment folder - fs.mkdirSync(path.join(deploymentPackagePath), { recursive: true }); - // dynamic partition can be in a path. Create the path in deployment package before copying file in. - fs.mkdirSync(path.dirname(path.join(deploymentPackagePath, dynamicPartitionPath)), { recursive: true }); - // Copy file - fs.copyFileSync( - path.join(configDirPath, dynamicPartitionPath), - path.join(deploymentPackagePath, dynamicPartitionPath), - ); - } - private createFirehoseServiceRole( - firehoseKmsKeyArn: string, - homeRegion: string, - logsStorageBucketArn: string, - processingLambdaArn: string, - acceleratorPrefix: string, - useExistingRoles: boolean, - firehoseCloudwatchLogs: cdk.aws_logs.LogGroup, - firehoseCloudwatchLogStream: string, - logsKmsKey?: cdk.aws_kms.IKey, - ) { - if (useExistingRoles) { - return `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${acceleratorPrefix}FirehoseRole`; - } - // Access is based on least privileged apis - // Ref: https://docs.aws.amazon.com/firehose/latest/dev/controlling-access.html#using-iam-s3 - const policyStatements: cdk.aws_iam.PolicyStatement[] = [ - new cdk.aws_iam.PolicyStatement({ - actions: ['kms:Decrypt', 'kms:GenerateDataKey'], - resources: [firehoseKmsKeyArn], - conditions: { - StringEquals: { - 'kms:ViaService': `s3.${homeRegion}.amazonaws.com`, - }, - StringLike: { - 'kms:EncryptionContext:aws:s3:arn': `${logsStorageBucketArn}/*`, - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - actions: [ - 's3:AbortMultipartUpload', - 's3:GetBucketLocation', - 's3:GetObject', - 's3:ListBucket', - 's3:ListBucketMultipartUploads', - 's3:PutObject', - ], - resources: [logsStorageBucketArn, `${logsStorageBucketArn}/*`], - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['lambda:InvokeFunction', 'lambda:GetFunctionConfiguration'], - resources: [`${processingLambdaArn}:*`, `${processingLambdaArn}`], - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['logs:PutLogEvents'], - resources: [`${firehoseCloudwatchLogs.logGroupArn}:logstream:${firehoseCloudwatchLogStream}`], - }), - ]; - - if (logsKmsKey) { - policyStatements.push( - new cdk.aws_iam.PolicyStatement({ - actions: ['kms:Encrypt*', 'kms:Describe*'], - resources: [logsKmsKey.keyArn], - }), - ); - } - - const firehoseAccessS3KmsLambda = new cdk.aws_iam.PolicyDocument({ - statements: policyStatements, - }); - - const firehoseServiceRole = new cdk.aws_iam.Role(this, 'FirehoseServiceRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('firehose.amazonaws.com'), - description: 'Role used by Kinesis Firehose to place Kinesis records in the central bucket.', - // placing inline policy as firehose needs this from get-go or there might be a few initial failures - inlinePolicies: { - AccessS3KmsLambda: firehoseAccessS3KmsLambda, - }, - }); - return firehoseServiceRole.roleArn; - } - private createKinesisStreamRole( - kinesisStreamArn: string, - kinesisStreamKmsArn: string, - acceleratorPrefix: string, - useExistingRoles: boolean, - ) { - if (useExistingRoles) { - return `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${acceleratorPrefix}FirehoseRole`; - } - const firehoseAccessKinesis = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: ['kinesis:DescribeStream', 'kinesis:GetShardIterator', 'kinesis:GetRecords', 'kinesis:ListShards'], - resources: [kinesisStreamArn], - }), - - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'kms:Decrypt', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:ReEncryptTo', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyPair', - 'kms:ReEncryptFrom', - ], - resources: [kinesisStreamKmsArn], - }), - ], - }); - - const kinesisStreamRole = new cdk.aws_iam.Role(this, 'FirehoseKinesisStreamServiceRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('firehose.amazonaws.com'), - description: 'Role used by Kinesis Firehose to get records from Kinesis.', - // placing inline policy as firehose needs this from get-go or there might be a few initial failures - inlinePolicies: { - AccessKinesis: firehoseAccessKinesis, - }, - }); - - return kinesisStreamRole.roleArn; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/index.ts deleted file mode 100644 index 09d6c5d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/index.ts +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import * as uuid from 'uuid'; -import * as zlib from 'zlib'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy, wildcardMatch } from '@aws-accelerator/utils/lib/common-functions'; - -import { PutRecordsCommand, PutRecordsCommandInput, KinesisClient } from '@aws-sdk/client-kinesis'; -import { - FirehoseTransformationEvent, - FirehoseTransformationResult, - FirehoseTransformationResultRecord, - FirehoseTransformationEventRecord, - CloudWatchLogsToFirehoseRecordLogEvents, - CloudWatchLogsToFirehoseRecord, -} from '@aws-accelerator/utils/lib/common-types'; - -/** - * firehose-prefix-processor - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: FirehoseTransformationEvent) { - const firehoseRecordsOutput: FirehoseTransformationResult = { records: [] }; - - // Parse records - for (const firehoseRecordInput of event.records) { - try { - const processedFirehoseRecord = await processFirehoseInputRecord(firehoseRecordInput); - firehoseRecordsOutput.records.push(processedFirehoseRecord); - } catch (err) { - console.warn(err); - const firehoseReturnErrorResult: FirehoseTransformationResultRecord = { - recordId: firehoseRecordInput.recordId, - data: firehoseRecordInput.data, - result: 'ProcessingFailed', - }; - firehoseRecordsOutput.records.push(firehoseReturnErrorResult); - } - } - - return await checkFirehoseRecords(firehoseRecordsOutput); -} - -async function processFirehoseInputRecord(firehoseRecord: FirehoseTransformationEventRecord) { - const payload = Buffer.from(firehoseRecord.data, 'base64'); - const unzippedPayload = zlib.gunzipSync(payload).toString('utf-8'); - const jsonParsedPayload = JSON.parse(unzippedPayload); - - // only process payload that has logGroup prefix - if ('logGroup' in jsonParsedPayload) { - // check for dynamic partition - const serviceName = await checkDynamicPartition(jsonParsedPayload); - - // parse record to have year/month/date prefix - const firehoseTimestamp = new Date(firehoseRecord.approximateArrivalTimestamp); - const prefixes = await getDatePrefix(serviceName, firehoseTimestamp); - - // these are mandatory prefixes for firehose payload - const partitionKeys = { - dynamicPrefix: prefixes, - }; - const firehoseReturnResult: FirehoseTransformationResultRecord = { - recordId: firehoseRecord.recordId, - data: firehoseRecord.data, - result: 'Ok', - }; - - firehoseReturnResult.result = 'Ok'; - firehoseReturnResult.metadata = { - partitionKeys, - }; - return firehoseReturnResult; - } else { - // if there is no logGroup in payload do not process and forward to firehose - // mark processing failed - const firehoseReturnErrorResult: FirehoseTransformationResultRecord = { - recordId: firehoseRecord.recordId, - data: firehoseRecord.data, - result: 'ProcessingFailed', - }; - return firehoseReturnErrorResult; - } -} - -async function checkDynamicPartition(firehoseRecordDynamicPartition: CloudWatchLogsToFirehoseRecord) { - // data pattern for firehose dynamic mapping - type S3LogPartitionType = { - logGroupPattern: string; - s3Prefix: string; - }; - - //Get mapping from environment - let mappings: S3LogPartitionType[] | undefined; - - const dynamicPartitionMapping = process.env['DynamicS3LogPartitioningMapping']!; - - // if there is a mapping proceed to create a mapping - if (dynamicPartitionMapping) { - mappings = JSON.parse(fs.readFileSync(path.join(__dirname, dynamicPartitionMapping), 'utf-8')); - } - - let serviceName = null; - - if (mappings) { - for (const mapping of mappings) { - if (wildcardMatch(firehoseRecordDynamicPartition.logGroup, mapping.logGroupPattern)) { - serviceName = mapping.s3Prefix; - break; // Take the first match - } - } - } - return serviceName; -} - -async function getDatePrefix(serviceName: string | null, inputTimestamp: Date) { - let calculatedPrefix = 'CloudWatchLogs'; - if (serviceName) { - calculatedPrefix += `/${serviceName}`; - } - - calculatedPrefix += `/${inputTimestamp.getFullYear()}`; - calculatedPrefix += `/${(inputTimestamp.getMonth() + 1).toLocaleString('en-US', { - minimumIntegerDigits: 2, - })}`; - calculatedPrefix += `/${inputTimestamp.getDate().toLocaleString('en-US', { minimumIntegerDigits: 2 })}`; - calculatedPrefix += `/${inputTimestamp.getHours().toLocaleString('en-US', { minimumIntegerDigits: 2 })}`; - calculatedPrefix += `/`; - - return calculatedPrefix; -} - -/** - * Check firehose records going out of firehose records processor function. - * If records are over 6MB then check if - * - there are multiple records in event.records (event.records.length > 1), split the event.records into half, post that in kinesis stream and mark as failed - * - there is single record in event.records (events.records.length === 1), uncompress data and check to see logEvents.length - * -- logEvents.length > 1 split logEvents into half, post it in kinesis stream and mark this processing as failed - * -- logEvents.length === 1 this means compressed payload is too large for lambda to send to kinesis. Fail with message that payload is too large with logGroup and logStream name to help troubleshoot further - */ - -async function checkFirehoseRecords(firehoseRecordsOutput: FirehoseTransformationResult) { - /** - * Initial check to see if records are over 6MB. - * 6MB is exactly 6291456 in binary, this code is assuming decimal so cut off will be 6000000 - * Making this a variable incase lambda limits change in the future - */ - const maxOutputPayload = parseInt(process.env['MaxOutputPayload']! ?? 6000000); - if (JSON.stringify(firehoseRecordsOutput).length > maxOutputPayload) { - console.log( - `LARGE_PAYLOAD_FOUND output event recorded a payload over ${maxOutputPayload} bytes. Will try to split the records and place them back in the kinesis stream`, - ); - await checkFirehoseRecordLength(firehoseRecordsOutput); - return await markFirehoseRecordError(firehoseRecordsOutput); - } - - return firehoseRecordsOutput; -} - -async function checkFirehoseRecordLength(firehoseRecordsOutput: FirehoseTransformationResult) { - /** - * Check for record length. If there is only 1 record uncompress the data - * If the record length is greater than 1 then split the records - * If both the conditions are not met then fail the function - */ - if (firehoseRecordsOutput.records.length === 1) { - await checkCloudWatchLog(firehoseRecordsOutput.records[0]); - } else if (firehoseRecordsOutput.records.length > 1) { - await splitFirehoseRecords(firehoseRecordsOutput); - } -} - -async function checkCloudWatchLog(singleRecord: FirehoseTransformationResultRecord) { - const payload = Buffer.from(singleRecord.data, 'base64'); - const unzippedPayload = zlib.gunzipSync(payload).toString('utf-8'); - const jsonParsedPayload: CloudWatchLogsToFirehoseRecord = JSON.parse(unzippedPayload); - if (jsonParsedPayload.logEvents.length === 1) { - // adding a unique prefix to make insights or log analysis easier. The log group and log stream is printed out to help troubleshoot - throw new Error( - `SINGLE_LARGE_FIREHOSE_PAYLOAD LogGroup: ${jsonParsedPayload.logGroup} with LogStream: ${ - jsonParsedPayload.logStream - } has a single record with compressed data over ${parseInt( - process.env['MaxOutputPayload']! ?? 6000000, - ).toString()}`, - ); - } else if (jsonParsedPayload.logEvents.length > 1) { - //split logEvents - const halfSizeLogEvents = Math.floor(jsonParsedPayload.logEvents.length / 2); - const firstHalfLogEventsArray: CloudWatchLogsToFirehoseRecordLogEvents[] = jsonParsedPayload.logEvents.slice( - 0, - halfSizeLogEvents, - ); - const secondHalfLogEventsArray: CloudWatchLogsToFirehoseRecordLogEvents[] = jsonParsedPayload.logEvents.slice( - halfSizeLogEvents, - jsonParsedPayload.logEvents.length, - ); - - // reconstruct the CloudWatch payload in 2 arrays - const firstHalfKinesisData = await encodeZipRecords( - firstHalfLogEventsArray, - jsonParsedPayload.owner, - jsonParsedPayload.logGroup, - jsonParsedPayload.logStream, - jsonParsedPayload.subscriptionFilters, - jsonParsedPayload.messageType, - ); - const secondHalfKinesisData = await encodeZipRecords( - secondHalfLogEventsArray, - jsonParsedPayload.owner, - jsonParsedPayload.logGroup, - jsonParsedPayload.logStream, - jsonParsedPayload.subscriptionFilters, - jsonParsedPayload.messageType, - ); - - await postRecordsToStream({ - Records: [firstHalfKinesisData, secondHalfKinesisData], - StreamARN: process.env['KinesisStreamArn']!, - }); - } -} - -function encodeZipRecords( - records: CloudWatchLogsToFirehoseRecordLogEvents[], - recordOwner: string, - recordLogGroup: string, - recordLogStream: string, - recordSubscriptionFilters: string[], - recordMessageType: string, -) { - const payload: CloudWatchLogsToFirehoseRecord = { - owner: recordOwner, - logGroup: recordLogGroup, - logStream: recordLogStream, - subscriptionFilters: recordSubscriptionFilters, - messageType: recordMessageType, - logEvents: records, - }; - // compression level 6 as per docs: - // https://docs.aws.amazon.com/firehose/latest/dev/writing-with-cloudwatch-logs.html - return { - Data: Buffer.from(zlib.gzipSync(Buffer.from(JSON.stringify(payload)), { level: 6 }).toString('base64')), - PartitionKey: uuid.v4(), - }; -} - -async function postRecordsToStream(recordsInput: PutRecordsCommandInput) { - // take records and post it to stream - // records should be base64 encoded and compressed - // Each shard can support writes up to 1,000 records per second, up to a maximum data write total of 1 MiB per second. - // Note: When invoking this API, it is recommended you use the StreamARN input parameter rather than the StreamName input parameter. - - const solutionId = process.env['SOLUTION_ID']; - const kinesisClient = new KinesisClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - await throttlingBackOff(() => kinesisClient.send(new PutRecordsCommand(recordsInput))); -} - -async function splitFirehoseRecords(records: FirehoseTransformationResult) { - //split logEvents - const halfSizeLogEvents = Math.floor(records.records.length / 2); - const firstHalfFirehoseRecordsArray: FirehoseTransformationResultRecord[] = records.records.slice( - 0, - halfSizeLogEvents, - ); - const secondHalfFirehoseRecordsArray: FirehoseTransformationResultRecord[] = records.records.slice( - halfSizeLogEvents, - records.records.length, - ); - await parseFirehoseRecordsForKinesisStream(firstHalfFirehoseRecordsArray); - await parseFirehoseRecordsForKinesisStream(secondHalfFirehoseRecordsArray); -} - -async function parseFirehoseRecordsForKinesisStream(records: FirehoseTransformationResultRecord[]) { - const kinesisDataPayload = []; - for (const record of records) { - const singleRecord = { Data: Buffer.from(record.data), PartitionKey: uuid.v4() }; - kinesisDataPayload.push(singleRecord); - } - await postRecordsToStream({ Records: kinesisDataPayload, StreamARN: process.env['KinesisStreamArn']! }); -} - -async function markFirehoseRecordError(firehoseRecordsOutput: FirehoseTransformationResult) { - const errorRecords = []; - for (const record of firehoseRecordsOutput.records) { - const firehoseReturnErrorResult: FirehoseTransformationResultRecord = { - recordId: record.recordId, - data: record.data, - result: 'ProcessingFailed', - }; - errorRecords.push(firehoseReturnErrorResult); - } - return { records: errorRecords }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/package.json deleted file mode 100644 index 1a8dec1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-firehose-record-processing", - "version": "0.0.0", - "private": true, - "description": "Firehose record processing custom resource", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-kinesis": "3.410.0" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-firehose/firehose-record-processing/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/index.ts deleted file mode 100644 index 2f2b22d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * enable-fms - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent) { - console.log(JSON.stringify(event, null, 4)); - const fmsClient = new AWS.FMS({ region: 'us-east-1' }); - const organizationsClient = new AWS.Organizations({ region: 'us-east-1' }); - const fmsServicePrincipal = 'fms.amazonaws.com'; - const currentFMSAdminAccount = await throttlingBackOff(() => fmsClient.getAdminAccount({}).promise()).catch(err => { - console.log(err); - return undefined; - }); - const newFMSAdminAccount = event.ResourceProperties['adminAccountId']; - const assumeRoleName = event.ResourceProperties['assumeRoleName']; - const partition = event.ResourceProperties['partition']; - const region = event.ResourceProperties['region']; - - const solutionId = process.env['SOLUTION_ID']; - - console.log(`Current FMS Account: ${currentFMSAdminAccount?.AdminAccount || 'No account found'}`); - console.log(`New FMS Account: ${newFMSAdminAccount}`); - switch (event.RequestType) { - case 'Create': - case 'Update': - if (currentFMSAdminAccount && currentFMSAdminAccount !== newFMSAdminAccount) { - console.log( - 'WARNING: The FMS admin account id cannot be updated. To associate a new FMS Admin account, remove the FMS configuration and re-run the Pipeline. Once the account is fully disassociated, add the FMS configuration with the new account and run once more.', - ); - return { Status: 'Success', StatusCode: 200 }; - } - - if (currentFMSAdminAccount === newFMSAdminAccount) { - console.log('Accounts match. Nothing to do.'); - return { Status: 'Success', StatusCode: 200 }; - } - - console.log('No FMS Admin account detected. Registering new Admin Account'); - console.log('Checking if FMS is enabled in Organizations'); - const fmsEnabled = await isOrganizationServiceEnabled(organizationsClient, fmsServicePrincipal); - if (!fmsEnabled) { - console.log('Enabling FMS service in Organizations'); - await throttlingBackOff(() => - organizationsClient.enableAWSServiceAccess({ ServicePrincipal: fmsServicePrincipal }).promise(), - ); - } - console.log('Getting delegated Administrator for Organizations'); - const delegatedAdmins = await throttlingBackOff(() => - organizationsClient.listDelegatedAdministrators({ ServicePrincipal: fmsServicePrincipal }).promise(), - ); - - let delegatedAdminAccounts: string[] = []; - if (delegatedAdmins.DelegatedAdministrators) { - delegatedAdminAccounts = delegatedAdmins.DelegatedAdministrators.map(delegatedAdmin => { - return delegatedAdmin.Id!; - }); - } - if (delegatedAdminAccounts.length > 0 && !delegatedAdminAccounts.includes(newFMSAdminAccount)) { - console.log(`Deregistering delegatedAdmins for ${fmsServicePrincipal}`); - await deregisterDelegatedAdministrators(organizationsClient, fmsServicePrincipal, delegatedAdmins, [ - newFMSAdminAccount, - ]); - } - console.log('setting delegated administrator for Organizations'); - if (!delegatedAdminAccounts.includes(newFMSAdminAccount)) { - await throttlingBackOff(() => - organizationsClient - .registerDelegatedAdministrator({ AccountId: newFMSAdminAccount, ServicePrincipal: fmsServicePrincipal }) - .promise(), - ); - } - console.log('Enabling FMS'); - await throttlingBackOff(() => fmsClient.associateAdminAccount({ AdminAccount: newFMSAdminAccount }).promise()); - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const adminAccountId = currentFMSAdminAccount?.AdminAccount; - const stsClient = new AWS.STS({ customUserAgent: solutionId, region: region }); - if (adminAccountId) { - const assumeRoleCredentials = await assumeRole(stsClient, assumeRoleName, adminAccountId, partition); - console.log('Deregistering Admin Account'); - const adminFmsClient = new AWS.FMS({ credentials: assumeRoleCredentials, region: 'us-east-1' }); - await throttlingBackOff(() => adminFmsClient.disassociateAdminAccount({}).promise()); - await throttlingBackOff(() => - organizationsClient - .deregisterDelegatedAdministrator({ - AccountId: adminAccountId, - ServicePrincipal: 'fms.amazonaws.com', - }) - .promise(), - ); - } else { - console.log('No FMS Admin Account exists'); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function isOrganizationServiceEnabled(organizationsClient: AWS.Organizations, servicePrincipal: string) { - let nextToken; - const enabledOrgServices = []; - do { - const services = await organizationsClient.listAWSServiceAccessForOrganization({ NextToken: nextToken }).promise(); - if (services.EnabledServicePrincipals) { - enabledOrgServices.push(...services.EnabledServicePrincipals); - } - } while (nextToken); - - const enabledServiceNames = enabledOrgServices.map(service => { - return service.ServicePrincipal; - }); - - return enabledServiceNames.includes(servicePrincipal); -} - -async function deregisterDelegatedAdministrators( - organizationsClient: AWS.Organizations, - servicePrincipal: string, - delegatedAdmins: AWS.Organizations.ListDelegatedAdministratorsResponse, - accountsToExclude: string[] = [], -) { - for (const delegatedAdmin of delegatedAdmins.DelegatedAdministrators || []) { - if (!accountsToExclude.includes(delegatedAdmin.Id!)) { - console.log(`Deregistering delegated Admin Account ${delegatedAdmin.Id}`); - await throttlingBackOff(() => - organizationsClient - .deregisterDelegatedAdministrator({ - AccountId: delegatedAdmin.Id!, - ServicePrincipal: servicePrincipal, - }) - .promise(), - ); - } - } -} - -async function assumeRole(stsClient: AWS.STS, assumeRoleName: string, accountId: string, partition: string) { - const roleArn = `arn:${partition}:iam::${accountId}:role/${assumeRoleName}`; - const assumeRole = await throttlingBackOff(() => - stsClient.assumeRole({ RoleArn: roleArn, RoleSessionName: `fmsDeregisterAdmin` }).promise(), - ); - return new AWS.Credentials({ - accessKeyId: assumeRole.Credentials!.AccessKeyId, - secretAccessKey: assumeRole.Credentials!.SecretAccessKey, - sessionToken: assumeRole.Credentials!.SessionToken, - }); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/package.json deleted file mode 100644 index 4cf8039..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-fms-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-fms/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-notification-channel.ts b/source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-notification-channel.ts deleted file mode 100644 index b384def..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-notification-channel.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -interface IFMSNotificationChannel extends cdk.IResource { - /** - * The SNS role arn for the delivery channel - */ - readonly snsRoleArn: string; - - /** - * The SNS topic arn for the delivery channel - */ - readonly snsTopicArn: string; -} - -interface FMSNotificationChannelProps { - /** - * The SNS role arn for the delivery channel - */ - readonly snsRoleArn: string; - /** - * The SNS topic arn for the delivery channel - */ - readonly snsTopicArn: string; -} - -export class FMSNotificationChannel extends cdk.Resource implements IFMSNotificationChannel { - public readonly snsRoleArn: string; - public readonly snsTopicArn: string; - - constructor(scope: Construct, id: string, props: FMSNotificationChannelProps) { - super(scope, id); - - const resource = new cdk.aws_fms.CfnNotificationChannel(this, 'Resource', { - snsRoleName: props.snsRoleArn, - snsTopicArn: props.snsTopicArn, - }); - - this.snsRoleArn = resource.snsRoleName; - this.snsTopicArn = resource.snsTopicArn; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-organization-admin-account.ts deleted file mode 100644 index 67f9a58..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-fms/fms-organization-admin-account.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -const path = require('path'); - -/** - * Initialized FMSOrganizationalAdminAccountProps properties - */ -export interface FMSOrganizationalAdminAccountProps { - /** - * Assume Role Name for deregistering FMS Admin - */ - readonly assumeRole: string; - /** - * Admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class for FMSOrganizationAdminAccount - */ -export class FMSOrganizationAdminAccount extends Construct { - readonly provider: cdk.custom_resources.Provider; - readonly resource: cdk.Resource; - constructor(scope: Construct, id: string, props: FMSOrganizationalAdminAccountProps) { - super(scope, id); - const functionId = `${id}ProviderLambda`; - const providerLambda = new cdk.aws_lambda.Function(this, functionId, { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'enable-organization-admin-account/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - timeout: cdk.Duration.seconds(180), - initialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'fms:AssociateAdminAccount', - 'fms:DisassociateAdminAccount', - 'fms:GetAdminAccount', - 'organizations:DescribeAccount', - 'organizations:DescribeOrganization', - 'organizations:DescribeOrganizationalUnit', - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:DisableAWSServiceAccess', - 'organizations:EnableAwsServiceAccess', - 'organizations:ListAccounts', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListChildren', - 'organizations:ListDelegatedAdministrators', - 'organizations:ListDelegatedServicesForAccount', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:ListParents', - 'organizations:ListRoots', - 'organizations:RegisterDelegatedAdministrator', - ], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::${props.adminAccountId}:role/${props.assumeRole}`], - }), - ], - handler: 'index.handler', - }); - - this.provider = new cdk.custom_resources.Provider(this, 'Resource', { - onEventHandler: providerLambda, - }); - - this.resource = new cdk.CustomResource(this, `fmsAdmin${props.adminAccountId}`, { - serviceToken: this.provider.serviceToken, - properties: { - adminAccountId: props.adminAccountId, - assumeRoleName: props.assumeRole, - partition: cdk.Stack.of(scope).partition, - region: cdk.Stack.of(this).region, - }, - }); - - const stack = cdk.Stack.of(scope); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/Resource/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/Resource/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/index.ts deleted file mode 100644 index d101628..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/index.ts +++ /dev/null @@ -1,302 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { chunkArray, setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - GuardDutyClient, - AccountDetail, - UpdateOrganizationConfigurationCommand, - CreateMembersCommand, - ListMembersCommand, - DisassociateMembersCommand, - DeleteMembersCommand, - ListDetectorsCommand, - OrganizationFeatureConfiguration, - BadRequestException, -} from '@aws-sdk/client-guardduty'; -import { OrganizationsClient, ListAccountsCommand, ListAccountsCommandOutput } from '@aws-sdk/client-organizations'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * enable-guardduty - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const partition = event.ResourceProperties['partition']; - const guardDutyMemberAccountIds: string[] = event.ResourceProperties['guardDutyMemberAccountIds']; - const enableS3Protection: boolean = event.ResourceProperties['enableS3Protection'] === 'true'; - const enableEksProtection: boolean = event.ResourceProperties['enableEksProtection'] === 'true'; - const autoEnableOrgMembersFlag: boolean = event.ResourceProperties['autoEnableOrgMembers'] === 'true'; - - const solutionId = process.env['SOLUTION_ID']; - const chunkSize = process.env['CHUNK_SIZE'] ? parseInt(process.env['CHUNK_SIZE']) : 50; - - let organizationsClient: OrganizationsClient; - if (partition === 'aws-us-gov') { - organizationsClient = new OrganizationsClient({ - region: 'us-gov-west-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else if (partition === 'aws-cn') { - organizationsClient = new OrganizationsClient({ - region: 'cn-northwest-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else { - organizationsClient = new OrganizationsClient({ - region: 'us-east-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } - - const guardDutyClient = new GuardDutyClient({ - region: region, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - - const detectorId = await getDetectorId(guardDutyClient); - - let nextToken: string | undefined = undefined; - - let existingMemberAccountIds: string[] = []; - - let autoEnableOrgMembers: 'ALL' | 'NEW' | 'NONE' = 'ALL'; - if (!autoEnableOrgMembersFlag) { - autoEnableOrgMembers = 'NONE'; - } - - console.log(`EnableS3Protection: ${enableS3Protection}`); - console.log(`EnableEksProtection: ${enableEksProtection}`); - console.log(`autoEnableOrgMembers: ${autoEnableOrgMembers}`); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const features = getOrganizationFeaturesEnabled(enableS3Protection, enableEksProtection); - - console.log('starting - UpdateOrganizationConfiguration'); - try { - autoEnableOrgMembersFlag - ? await updateOrganizationConfiguration(guardDutyClient, detectorId!, autoEnableOrgMembers, features) - : await updateOrganizationConfiguration(guardDutyClient, detectorId!, autoEnableOrgMembers); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - return { Status: 'Failure', StatusCode: e.statusCode }; - } - - console.log('starting - CreateMembersCommand'); - const allAccounts: AccountDetail[] = []; - - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListAccountsCommand({ NextToken: nextToken })), - ); - - allAccounts.push(...getMembersToCreate(page, guardDutyMemberAccountIds)); - nextToken = page.NextToken; - } while (nextToken); - - const chunkedAccountsForCreate = chunkArray(allAccounts, chunkSize); - - for (const accounts of chunkedAccountsForCreate) { - console.log(`Initiating createMembers request for ${accounts.length} accounts`); - await throttlingBackOff(() => - guardDutyClient.send(new CreateMembersCommand({ DetectorId: detectorId!, AccountDetails: accounts })), - ); - } - - // Cleanup members removed from deploymentTarget - if (guardDutyMemberAccountIds.length > 0) { - console.log('Inititating cleanup of members removed from deploymentTargets'); - existingMemberAccountIds = await getExistingMembers(guardDutyClient, detectorId!); - - const memberAccountIdsToDelete: string[] = []; - for (const accountId of existingMemberAccountIds) { - if (!guardDutyMemberAccountIds.includes(accountId)) { - memberAccountIdsToDelete.push(accountId); - } - } - if (memberAccountIdsToDelete.length > 0) { - const chunkedAccountsForDelete = chunkArray(memberAccountIdsToDelete, chunkSize); - await disassociateAndDeleteMembers(guardDutyClient, detectorId!, chunkedAccountsForDelete); - } - } - - console.log('Returning Success'); - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const disabledFeatures = getOrganizationFeaturesEnabled(false, false); - try { - await updateOrganizationConfiguration(guardDutyClient, detectorId!, 'NONE', disabledFeatures); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - return { Status: 'Failure', StatusCode: e.statusCode }; - } - - existingMemberAccountIds = await getExistingMembers(guardDutyClient, detectorId!); - - if (existingMemberAccountIds.length > 0) { - const chunkedAccountsForDelete = chunkArray(existingMemberAccountIds, chunkSize); - await disassociateAndDeleteMembers(guardDutyClient, detectorId!, chunkedAccountsForDelete); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Get accounts details for the GuardDuty members to create - * @param page - * @param guardDutyMemberAccountIds - * @returns - */ -function getMembersToCreate(page: ListAccountsCommandOutput, guardDutyMemberAccountIds: string[]): AccountDetail[] { - const allAccounts: AccountDetail[] = []; - for (const account of page.Accounts ?? []) { - if (guardDutyMemberAccountIds.length > 0) { - if (guardDutyMemberAccountIds.includes(account.Id!)) { - allAccounts.push({ AccountId: account.Id!, Email: account.Email! }); - } - } else { - allAccounts.push({ AccountId: account.Id!, Email: account.Email! }); - } - } - return allAccounts; -} - -/** - * Function to get existing guardduty members - * @param guardDutyClient - * @param detectorId - * @returns string[] - */ -async function getExistingMembers(guardDutyClient: GuardDutyClient, detectorId: string): Promise { - const existingMemberAccountIds: string[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - guardDutyClient.send(new ListMembersCommand({ DetectorId: detectorId, NextToken: nextToken })), - ); - for (const member of page.Members ?? []) { - existingMemberAccountIds.push(member.AccountId!); - } - nextToken = page.NextToken; - } while (nextToken); - - return existingMemberAccountIds; -} - -/** - * Function to disassociate and delete guardduty members - * @param guardDutyClient - * @param chunkedAccountsForDelete - */ -async function disassociateAndDeleteMembers( - guardDutyClient: GuardDutyClient, - detectorId: string, - chunkedAccountsForDelete: string[][], -) { - for (const existingMemberAccountIdBatch of chunkedAccountsForDelete) { - console.log(`Initiating disassociateMembers request for ${existingMemberAccountIdBatch.length} accounts`); - await throttlingBackOff(() => - guardDutyClient.send( - new DisassociateMembersCommand({ AccountIds: existingMemberAccountIdBatch, DetectorId: detectorId }), - ), - ); - - console.log(`Initiating deleteMembers request for ${existingMemberAccountIdBatch.length} accounts`); - await throttlingBackOff(() => - guardDutyClient.send( - new DeleteMembersCommand({ AccountIds: existingMemberAccountIdBatch, DetectorId: detectorId }), - ), - ); - } -} - -function convertBooleanToGuardDutyFormat(flag: boolean) { - if (flag) { - return 'NEW'; - } else { - return 'NONE'; - } -} - -function getOrganizationFeaturesEnabled(s3DataEvents: boolean, eksAuditLogs: boolean) { - const featureList: OrganizationFeatureConfiguration[] = []; - - featureList.push({ - AutoEnable: convertBooleanToGuardDutyFormat(s3DataEvents), - Name: 'S3_DATA_EVENTS', - }); - - featureList.push({ - AutoEnable: convertBooleanToGuardDutyFormat(eksAuditLogs), - Name: 'EKS_AUDIT_LOGS', - }); - return featureList; -} - -async function getDetectorId(guardDutyClient: GuardDutyClient): Promise { - const response = await throttlingBackOff(() => guardDutyClient.send(new ListDetectorsCommand({}))); - console.log(response); - return response.DetectorIds!.length === 1 ? response.DetectorIds![0] : undefined; -} - -async function updateOrganizationConfiguration( - guardDutyClient: GuardDutyClient, - detectorId: string, - autoEnableOrgMembers: 'ALL' | 'NEW' | 'NONE', - featureList?: OrganizationFeatureConfiguration[], -) { - try { - await guardDutyClient.send( - new UpdateOrganizationConfigurationCommand({ - AutoEnableOrganizationMembers: autoEnableOrgMembers, - DetectorId: detectorId!, - Features: featureList, - }), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e instanceof BadRequestException && featureList) { - console.log('Retrying with only S3 protection'); - const featureListS3Only = featureList.filter(feat => feat.Name === 'S3_DATA_EVENTS'); - await guardDutyClient.send( - new UpdateOrganizationConfigurationCommand({ - AutoEnableOrganizationMembers: autoEnableOrgMembers, - DetectorId: detectorId!, - Features: featureListS3Only, - }), - ); - } else { - console.log(`Error: ${JSON.stringify(e)}`); - throw new Error('Failed to update GuardDuty Organization Configuration, check logs for details'); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/package.json deleted file mode 100644 index d44b37e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-guardduty-create-members", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-guardduty": "3.412.0", - "@aws-sdk/client-organizations": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-members/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/index.ts deleted file mode 100644 index 743d5cf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/index.ts +++ /dev/null @@ -1,284 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - BadRequestException, - CreateDetectorCommand, - CreatePublishingDestinationCommand, - DeletePublishingDestinationCommand, - DescribePublishingDestinationCommand, - GuardDutyClient, - ListDetectorsCommand, - ListPublishingDestinationsCommand, - ListPublishingDestinationsCommandOutput, - UpdatePublishingDestinationCommand, -} from '@aws-sdk/client-guardduty'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -interface PublishingDestinationOptions { - /** - * Publishing destination ARN - */ - readonly destinationArn: string; - /** - * The export destination type - */ - readonly exportDestinationType: string; - /** - * KMS key ARN - */ - readonly kmsKeyArn: string; - /** - * Override existing configuration - */ - readonly overrideExisting: boolean; -} - -/** - * enable-guardduty - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const options = setOptions(event.ResourceProperties); - - const guardDutyClient = new GuardDutyClient({ - retryStrategy: setRetryStrategy(), - customUserAgent: process.env['SOLUTION_ID'], - }); - - let detectorId = await getDetectorId(guardDutyClient); - - switch (event.RequestType) { - case 'Create': - console.log('starting - CreatePublishingDestination'); - if (!detectorId) { - detectorId = await createDetector(guardDutyClient); - } - - if (options.overrideExisting) { - await overrideExistingDestination(guardDutyClient, detectorId, options); - } else { - try { - await throttlingBackOff(() => - guardDutyClient.send( - new CreatePublishingDestinationCommand({ - DetectorId: detectorId, - DestinationType: options.exportDestinationType, - DestinationProperties: { DestinationArn: options.destinationArn, KmsKeyArn: options.kmsKeyArn }, - }), - ), - ); - } catch (err) { - if ( - err instanceof BadRequestException && - err.message.startsWith('The request failed because a publishingDestination already exists') - ) { - console.log('Publishing destination already exists.'); - return { Status: 'Success', StatusCode: 200 }; - } - throw err; - } - } - return { Status: 'Success', StatusCode: 200 }; - - case 'Update': - console.log('starting - UpdatePublishingDestination'); - - // if override is enabled, then follow that workflow - // Note: override will check destinationArn of s3 and kmsKeyArn. If they are the same no change will be done. - if (options.overrideExisting) { - await overrideExistingDestination(guardDutyClient, detectorId!, options); - } else { - const updateDestinationId = await getDestinationId(guardDutyClient, detectorId!); - - if (updateDestinationId) { - await throttlingBackOff(() => - guardDutyClient.send( - new UpdatePublishingDestinationCommand({ - DetectorId: detectorId!, - DestinationId: updateDestinationId, - DestinationProperties: { DestinationArn: options.destinationArn, KmsKeyArn: options.kmsKeyArn }, - }), - ), - ); - } - } - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const deleteDestinationId = await getDestinationId(guardDutyClient, detectorId!); - - if (deleteDestinationId) { - await throttlingBackOff(() => - guardDutyClient.send( - new DeletePublishingDestinationCommand({ - DetectorId: detectorId!, - DestinationId: deleteDestinationId, - }), - ), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Set custom resource options for GuardDuty publishing destination - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns PublishingDestinationOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }): PublishingDestinationOptions { - return { - destinationArn: resourceProperties['destinationArn'], - exportDestinationType: resourceProperties['exportDestinationType'], - kmsKeyArn: resourceProperties['kmsKeyArn'], - overrideExisting: resourceProperties['exportDestinationOverride'] === 'true', - }; -} - -/** - * Returns the detector ID, if one exists - * @param guardDutyClient GuardDutyClient - * @returns Promise - */ -async function getDetectorId(guardDutyClient: GuardDutyClient): Promise { - const response = await throttlingBackOff(() => guardDutyClient.send(new ListDetectorsCommand({}))); - console.log(response); - return response.DetectorIds!.length === 1 ? response.DetectorIds![0] : undefined; -} - -/** - * Create a GuardDuty detector - * @param guardDutyClient GuardDutyClient - * @returns Promise - */ -async function createDetector(guardDutyClient: GuardDutyClient): Promise { - const response = await throttlingBackOff(() => guardDutyClient.send(new CreateDetectorCommand({ Enable: true }))); - - if (!response.DetectorId) { - throw new Error(`Unable to create detector`); - } - return response.DetectorId; -} - -/** - * List GuardDuty publishing destinations - * @param guardDutyClient GuardDutyClient - * @param detectorId string - * @returns Promise - */ -async function getPublishingDestinations( - guardDutyClient: GuardDutyClient, - detectorId: string, -): Promise { - return await throttlingBackOff(() => - guardDutyClient.send(new ListPublishingDestinationsCommand({ DetectorId: detectorId })), - ); -} - -/** - * Get the GuardDuty publishing destination ID - * @param guardDutyClient GuardDutyClient - * @param detectorId string - * @returns Promise - */ -async function getDestinationId(guardDutyClient: GuardDutyClient, detectorId: string): Promise { - try { - const publishingDestinations = await getPublishingDestinations(guardDutyClient, detectorId); - - if (!publishingDestinations.Destinations) { - throw new Error(`API did not return destinations`); - } - - return publishingDestinations.Destinations.length === 1 - ? publishingDestinations.Destinations[0].DestinationId - : undefined; - } catch (e) { - throw new Error(`Unable to retrieve destination ID from ListPublishingDestinations command: ${e}`); - } -} - -/** - * Override existing publishing destination configuration - * @param guardDutyClient GuardDutyClient - * @param detectorId string - * @param options PublishingDestinationOptions - */ -async function overrideExistingDestination( - guardDutyClient: GuardDutyClient, - detectorId: string, - options: PublishingDestinationOptions, -) { - // Check account for destination - const publishingDestinations = await getPublishingDestinations(guardDutyClient, detectorId); - if (publishingDestinations.Destinations && publishingDestinations.Destinations.length > 0) { - for (const pubDestination of publishingDestinations.Destinations) { - // Only s3 is currently possible but leaving this logic in place, in case GuardDuty service adds additional destinations - if (pubDestination.DestinationType == 'S3') { - // Get current destination id and find the destination arn - const pubDestinationResponse = await throttlingBackOff(() => - guardDutyClient.send( - new DescribePublishingDestinationCommand({ - DestinationId: pubDestination.DestinationId, - DetectorId: detectorId, - }), - ), - ); - - if ( - pubDestinationResponse.DestinationProperties && - pubDestinationResponse.DestinationProperties.DestinationArn === options.destinationArn && - pubDestinationResponse.DestinationProperties.KmsKeyArn === options.kmsKeyArn - ) { - // KMS and destination are same as input. So no change is needed. - console.log('No changes are necessary. Destination Arn and KMS key are the same.'); - } else { - // kms and destination are not the same. So update destination - await throttlingBackOff(() => - guardDutyClient.send( - new UpdatePublishingDestinationCommand({ - DetectorId: detectorId, - DestinationId: pubDestination.DestinationId, - DestinationProperties: { DestinationArn: options.destinationArn, KmsKeyArn: options.kmsKeyArn }, - }), - ), - ); - } - } - } - } else { - // There are no destinations. Create one - await throttlingBackOff(() => - guardDutyClient.send( - new CreatePublishingDestinationCommand({ - DetectorId: detectorId, - DestinationType: options.exportDestinationType, - DestinationProperties: { DestinationArn: options.destinationArn, KmsKeyArn: options.kmsKeyArn }, - }), - ), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/package.json deleted file mode 100644 index 00271e2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-guardduty-create-publishing-destination", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-guardduty": "3.412.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/create-publishing-destination/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/index.ts deleted file mode 100644 index cac7883..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { delay, throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * enable-guardduty - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const adminAccountId = event.ResourceProperties['adminAccountId']; - const solutionId = process.env['SOLUTION_ID']; - - const guardDutyClient = new AWS.GuardDuty({ region: region, customUserAgent: solutionId }); - - const guardDutyAdminAccount = await isGuardDutyEnable(guardDutyClient, adminAccountId); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (guardDutyAdminAccount.status) { - if (guardDutyAdminAccount.accountId === adminAccountId) { - console.warn( - `GuardDuty admin account ${guardDutyAdminAccount.accountId} is already an admin account as status is ${guardDutyAdminAccount.status}, in ${region} region. No action needed`, - ); - return { Status: 'Success', StatusCode: 200 }; - } else { - console.warn( - `GuardDuty delegated admin is already set to ${guardDutyAdminAccount.accountId} account can not assign another delegated account`, - ); - } - } else { - console.log( - `Started enableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - - let retries = 0; - while (retries < 10) { - await delay(retries ** 2 * 1000); - console.log('enable'); - try { - await throttlingBackOff(() => - guardDutyClient.enableOrganizationAdminAccount({ AdminAccountId: adminAccountId }).promise(), - ); - console.log('command run'); - break; - } catch (error) { - console.log(error); - retries = retries + 1; - } - } - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - if (guardDutyAdminAccount.accountId) { - if (guardDutyAdminAccount.accountId === adminAccountId) { - console.log( - `Started disableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - guardDutyClient - .disableOrganizationAdminAccount({ - AdminAccountId: adminAccountId, - }) - .promise(), - ); - } - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function isGuardDutyEnable( - guardDutyClient: AWS.GuardDuty, - adminAccountId: string, -): Promise<{ accountId: string | undefined; status: string | undefined }> { - const adminAccounts: AWS.GuardDuty.AdminAccount[] = []; - let nextToken: string | undefined = undefined; - console.log('isenabled'); - do { - const page = await throttlingBackOff(() => - guardDutyClient.listOrganizationAdminAccounts({ NextToken: nextToken }).promise(), - ); - for (const account of page.AdminAccounts ?? []) { - adminAccounts.push(account); - console.log(account); - } - nextToken = page.NextToken; - } while (nextToken); - if (adminAccounts.length === 0) { - return { accountId: undefined, status: undefined }; - } - if (adminAccounts.length > 1) { - throw new Error('Multiple admin accounts for GuardDuty in organization'); - } - - if (adminAccounts[0].AdminAccountId === adminAccountId && adminAccounts[0].AdminStatus === 'DISABLE_IN_PROGRESS') { - throw new Error(`Admin account ${adminAccounts[0].AdminAccountId} is in ${adminAccounts[0].AdminStatus}`); - } - - return { accountId: adminAccounts[0].AdminAccountId, status: adminAccounts[0].AdminStatus }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/package.json deleted file mode 100644 index aea5345..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-guardduty-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-detector-config.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-detector-config.ts deleted file mode 100644 index a572909..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-detector-config.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Export config destination types - */ -export enum GuardDutyExportConfigDestinationTypes { - S3 = 's3', -} - -/** - * Initialized GuardDutyDetectorConfigProps properties - */ -export interface GuardDutyDetectorConfigProps { - /** - * S3 Protection - */ - readonly enableS3Protection: boolean; - /** - * EKS Protection - */ - readonly enableEksProtection: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * FindingPublishingFrequency - */ - readonly exportFrequency?: string; -} - -/** - /** - * Class to GuardDuty Detector Members - */ -export class GuardDutyDetectorConfig extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: GuardDutyDetectorConfigProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::GuardDutyUpdateDetector'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'update-detector-config/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - memorySize: cdk.Size.mebibytes(512), - policyStatements: [ - { - Sid: 'GuardDutyUpdateDetectorTaskGuardDutyActions', - Effect: 'Allow', - Action: [ - 'guardduty:ListDetectors', - 'guardduty:ListMembers', - 'guardduty:UpdateDetector', - 'guardduty:UpdateMemberDetectors', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - exportFrequency: props.exportFrequency, - enableS3Protection: props.enableS3Protection, - enableEksProtection: props.enableEksProtection, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-members.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-members.ts deleted file mode 100644 index 322a32f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-members.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized GuardDutyMembersProps properties - */ -export interface GuardDutyMembersProps { - /** - * S3 Protection enable flag - */ - readonly enableS3Protection: boolean; - /** - * EKS Protection enable flag - */ - readonly enableEksProtection: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * List of GuardDuty member accountIds populated only when deploymentTargets are defined - */ - readonly guardDutyMemberAccountIds: string[]; - /** - * Enable/disable autoEnableOrgMembers - */ - readonly autoEnableOrgMembers: boolean; -} - -/** - /** - * Class to GuardDuty Members - */ -export class GuardDutyMembers extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: GuardDutyMembersProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::GuardDutyCreateMembers'; - - const servicePrincipal = 'guardduty.amazonaws.com'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'create-members/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'GuardDutyCreateMembersTaskOrganizationAction', - Effect: 'Allow', - Action: ['organizations:ListAccounts'], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:ListAccounts': [servicePrincipal], - }, - }, - }, - { - Sid: 'GuardDutyCreateMembersTaskGuardDutyActions', - Effect: 'Allow', - Action: [ - 'guardDuty:ListDetectors', - 'guardDuty:ListOrganizationAdminAccounts', - 'guardDuty:UpdateOrganizationConfiguration', - 'guardduty:CreateMembers', - 'guardduty:DeleteMembers', - 'guardduty:DisassociateMembers', - 'guardduty:ListDetectors', - 'guardduty:ListMembers', - ], - Resource: '*', - }, - { - Sid: 'ServiceLinkedRoleSecurityHub', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - partition: cdk.Aws.PARTITION, - enableS3Protection: props.enableS3Protection, - enableEksProtection: props.enableEksProtection, - guardDutyMemberAccountIds: props.guardDutyMemberAccountIds, - autoEnableOrgMembers: props.autoEnableOrgMembers, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-organization-admin-account.ts deleted file mode 100644 index 476090c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-organization-admin-account.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized GuardDutyOrganizationalAdminAccountProps properties - */ -export interface GuardDutyOrganizationalAdminAccountProps { - /** - * Admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class for GuardDutyOrganizationAdminAccount - */ -export class GuardDutyOrganizationAdminAccount extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: GuardDutyOrganizationalAdminAccountProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::GuardDutyEnableOrganizationAdminAccount'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'enable-organization-admin-account/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - timeout: cdk.Duration.seconds(180), - policyStatements: [ - { - Sid: 'GuardDutyEnableOrganizationAdminAccountTaskOrganizationActions', - Effect: 'Allow', - Action: [ - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:DescribeOrganization', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListAccounts', - 'organizations:ListDelegatedAdministrators', - 'organizations:RegisterDelegatedAdministrator', - 'organizations:ServicePrincipal', - 'organizations:UpdateOrganizationConfiguration', - ], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:DeregisterDelegatedAdministrator': ['guardduty.amazonaws.com'], - 'organizations:DescribeOrganization': ['guardduty.amazonaws.com'], - 'organizations:EnableAWSServiceAccess': ['guardduty.amazonaws.com'], - 'organizations:ListAWSServiceAccessForOrganization': ['guardduty.amazonaws.com'], - 'organizations:ListAccounts': ['guardduty.amazonaws.com'], - 'organizations:ListDelegatedAdministrators': ['guardduty.amazonaws.com'], - 'organizations:RegisterDelegatedAdministrator': ['guardduty.amazonaws.com'], - 'organizations:ServicePrincipal': ['guardduty.amazonaws.com'], - 'organizations:UpdateOrganizationConfiguration': ['guardduty.amazonaws.com'], - }, - }, - }, - { - Sid: 'GuardDutyEnableOrganizationAdminAccountTaskGuardDutyActions', - Effect: 'Allow', - Action: [ - 'GuardDuty:EnableOrganizationAdminAccount', - 'GuardDuty:ListOrganizationAdminAccounts', - 'guardduty:DisableOrganizationAdminAccount', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - adminAccountId: props.adminAccountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-publishing-destination.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-publishing-destination.ts deleted file mode 100644 index d209a45..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/guardduty-publishing-destination.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized GuardDutyPublishingDestinationProps properties - */ -export interface GuardDutyPublishingDestinationProps { - /** - * Export destination type - */ - readonly exportDestinationType: string; - /** - * Export destination type - */ - readonly exportDestinationOverride: boolean; - /** - * Publishing destination arn - */ - readonly destinationArn: string; - /** - * Publishing destination bucket encryption key - */ - readonly destinationKmsKey: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly logKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class - GuardDutyPublishingDestination - */ -export class GuardDutyPublishingDestination extends Construct { - public readonly id: string = ''; - - constructor(scope: Construct, id: string, props: GuardDutyPublishingDestinationProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::GuardDutyCreatePublishingDestinationCommand'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'create-publishing-destination/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'GuardDutyCreatePublishingDestinationCommandTaskGuardDutyActions', - Effect: 'Allow', - Action: [ - 'guardDuty:CreateDetector', - 'guardDuty:CreatePublishingDestination', - 'guardDuty:DeletePublishingDestination', - 'guardDuty:UpdatePublishingDestination', - 'guardDuty:ListDetectors', - 'guardDuty:ListPublishingDestinations', - 'guardduty:DescribePublishingDestination', - 'iam:CreateServiceLinkedRole', - ], - Resource: '*', - }, - { - Sid: 'GuardDutyCreateBucketPrefix', - Effect: 'Allow', - Action: ['s3:ListBucket', 's3:GetObject'], - Resource: [props.destinationArn, `${props.destinationArn}/*`], - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - exportDestinationType: props.exportDestinationType, - exportDestinationOverride: props.exportDestinationOverride, - destinationArn: props.destinationArn, - kmsKeyArn: props.destinationKmsKey.keyArn, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.logKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/index.ts deleted file mode 100644 index 863d7a1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/index.ts +++ /dev/null @@ -1,247 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { - BadRequestException, - DataSourceConfigurations, - GuardDutyClient, - ListDetectorsCommand, - ListMembersCommand, - UpdateDetectorCommand, - UpdateMemberDetectorsCommand, -} from '@aws-sdk/client-guardduty'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -interface UpdateDetectorOptions { - /** - * Enable EKS protection - */ - readonly enableEksProtection: boolean; - /** - * Enable S3 protection - */ - readonly enableS3Protection: boolean; - /** - * Finding export frequency - */ - readonly exportFrequency?: string; -} - -/** - * GuardDutyUpdateDetector - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const options = setOptions(event.ResourceProperties); - - const guardDutyClient = new GuardDutyClient({ - retryStrategy: setRetryStrategy(), - customUserAgent: process.env['SOLUTION_ID'], - }); - const detectorId = await getDetectorId(guardDutyClient); - - const existingMemberAccountIds = await getMemberAccountIds(guardDutyClient, detectorId); - - console.log(`S3 Protection Enable: ${options.enableS3Protection}`); - console.log(`EKS Protection Enable: ${options.enableEksProtection}`); - - switch (event.RequestType) { - case 'Create': - case 'Update': - await updateMemberDetectors(guardDutyClient, detectorId, existingMemberAccountIds, options); - await updateMainDetector(guardDutyClient, detectorId, options); - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - console.log('starting - Delete'); - try { - const dataSourcesToRemove: DataSourceConfigurations = {}; - dataSourcesToRemove.S3Logs = { Enable: false }; - dataSourcesToRemove.Kubernetes = { AuditLogs: { Enable: false } }; - await throttlingBackOff(() => - guardDutyClient.send( - new UpdateDetectorCommand({ - DetectorId: detectorId, - Enable: false, - FindingPublishingFrequency: options.exportFrequency, - DataSources: dataSourcesToRemove, - }), - ), - ); - - await throttlingBackOff(() => - guardDutyClient.send( - new UpdateMemberDetectorsCommand({ - DetectorId: detectorId, - AccountIds: existingMemberAccountIds, - DataSources: dataSourcesToRemove, - }), - ), - ); - } catch (error) { - console.error(error); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Set custom resource options for GuardDuty detector configuration - * @param resourceProperties { [key: string]: any } - * @param serviceToken string - * @returns UpdateDetectorOptions - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function setOptions(resourceProperties: { [key: string]: any }): UpdateDetectorOptions { - return { - enableEksProtection: resourceProperties['enableEksProtection'] === 'true', - enableS3Protection: resourceProperties['enableS3Protection'] === 'true', - exportFrequency: resourceProperties['exportFrequency'], - }; -} - -/** - * Get existing GuardDuty detector ID - * @param guardDutyClient GuardDutyClient - * @returns Promise - */ -async function getDetectorId(guardDutyClient: GuardDutyClient): Promise { - const response = await throttlingBackOff(() => guardDutyClient.send(new ListDetectorsCommand({}))); - console.log(response); - if (response.DetectorIds) { - return response.DetectorIds[0]; - } - throw new Error(`GuardDuty not enabled`); -} - -/** - * Get existing member account IDs - * @param guardDutyClient GuardDutyClient - * @param detectorId string - * @returns Promise - */ -async function getMemberAccountIds(guardDutyClient: GuardDutyClient, detectorId: string): Promise { - const existingMemberAccountIds: string[] = []; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - guardDutyClient.send(new ListMembersCommand({ DetectorId: detectorId, NextToken: nextToken })), - ); - for (const member of page.Members ?? []) { - if (member.AccountId) { - existingMemberAccountIds.push(member.AccountId); - } - } - nextToken = page.NextToken; - } while (nextToken); - - return existingMemberAccountIds; -} - -/** - * Update member account detector configurations - * @param guardDutyClient GuardDutyClient - * @param detectorId string - * @param existingMemberAccountIds string [] - * @param options UpdateDetectorOptions - */ -async function updateMemberDetectors( - guardDutyClient: GuardDutyClient, - detectorId: string, - existingMemberAccountIds: string[], - options: UpdateDetectorOptions, -) { - let dataSourcesToUpdate: DataSourceConfigurations = {}; - dataSourcesToUpdate.S3Logs = { Enable: options.enableS3Protection }; - dataSourcesToUpdate.Kubernetes = { AuditLogs: { Enable: options.enableEksProtection } }; - console.log('starting - UpdateMembersCommand'); - try { - await throttlingBackOff(() => - guardDutyClient.send( - new UpdateMemberDetectorsCommand({ - DetectorId: detectorId, - AccountIds: existingMemberAccountIds, - DataSources: dataSourcesToUpdate, - }), - ), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e instanceof BadRequestException) { - dataSourcesToUpdate = { S3Logs: { Enable: options.enableS3Protection } }; - await throttlingBackOff(() => - guardDutyClient.send( - new UpdateMemberDetectorsCommand({ - DetectorId: detectorId, - AccountIds: existingMemberAccountIds, - DataSources: dataSourcesToUpdate, - }), - ), - ); - } else { - throw new Error(`Unable to complete UpdateMemberDetectors command: ${e}`); - } - } -} - -async function updateMainDetector( - guardDutyClient: GuardDutyClient, - detectorId: string, - options: UpdateDetectorOptions, -) { - let dataSourcesToUpdate: DataSourceConfigurations = {}; - dataSourcesToUpdate.S3Logs = { Enable: options.enableS3Protection }; - dataSourcesToUpdate.Kubernetes = { AuditLogs: { Enable: options.enableEksProtection } }; - console.log('starting - UpdateDetectorCommand'); - try { - await throttlingBackOff(() => - guardDutyClient.send( - new UpdateDetectorCommand({ - DetectorId: detectorId, - Enable: true, - FindingPublishingFrequency: options.exportFrequency, - DataSources: dataSourcesToUpdate, - }), - ), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e instanceof BadRequestException) { - dataSourcesToUpdate = { S3Logs: { Enable: options.enableS3Protection } }; - await throttlingBackOff(() => - guardDutyClient.send( - new UpdateDetectorCommand({ - DetectorId: detectorId, - Enable: true, - FindingPublishingFrequency: options.exportFrequency, - DataSources: dataSourcesToUpdate, - }), - ), - ); - } else { - throw new Error(`Unable to complete UpdateDetector command: ${e}`); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/package.json deleted file mode 100644 index 30bc5e0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-guardduty-update-detector-config", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-guardduty": "3.412.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-guardduty/update-detector-config/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/index.ts deleted file mode 100644 index 5380a7e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/index.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - GetRoleCommand, - IAMClient, - CreateServiceLinkedRoleCommand, - NoSuchEntityException, - InvalidInputException, -} from '@aws-sdk/client-iam'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * create-service-linked-role - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - Data: { roleArn: string; roleName: string } | undefined; - } - | undefined -> { - switch (event.RequestType) { - case 'Create': - case 'Update': - const iamClient = new IAMClient({ customUserAgent: process.env['SOLUTION_ID'] }); - //check if role exists if it does return success - if (!(await isRoleExists(iamClient, event.ResourceProperties['roleName']))) { - // role needs to be created - const createServiceLinkedRoleResponse = await createServiceLinkedRole( - iamClient, - event.ResourceProperties['serviceName'], - event.ResourceProperties['description'] ?? undefined, - ); - - // Delay to allow service linked role to propagate after creation of SLR - await delay(60000); - return createServiceLinkedRoleResponse; - } else { - //role does not need to be created - return { - Status: 'SUCCESS', - Data: undefined, - }; - } - - case 'Delete': - // Do Nothing - return { - Status: 'SUCCESS', - Data: undefined, - }; - } -} - -/** - * Function to create service linked role. - * After the call is made, check is run every 15s to see if the role is present in IAM - */ -export async function createServiceLinkedRole( - iamClient: IAMClient, - serviceName: string, - description: string | undefined, -): Promise< - | { - Status: string; - Data: { roleArn: string; roleName: string } | undefined; - } - | undefined -> { - try { - const command = new CreateServiceLinkedRoleCommand({ AWSServiceName: serviceName, Description: description }); - const resp = await iamClient.send(command); - // wait for role to be created - await delay(15000); - // check if role is created - if (resp.Role) { - // this function will try 10 times for 15 attempts before going to the next step - await checkRoleStatus(iamClient, resp.Role.RoleName!); - - return { - Status: 'SUCCESS', - Data: { roleArn: resp.Role.Arn!, roleName: resp.Role.RoleName! }, - }; - } else { - throw new Error(`Response did not have Role. ${JSON.stringify(resp)}`); - } - } catch (error) { - if (error instanceof InvalidInputException && error.message.includes('has been taken in this account')) { - //role does not need to be created - return { - Status: 'SUCCESS', - Data: undefined, - }; - } - throw new Error(`There was an error in service linked role creation: ${JSON.stringify(error)}`); - } -} - -export async function isRoleExists(iamClient: IAMClient, roleName: string): Promise { - try { - await iamClient.send(new GetRoleCommand({ RoleName: roleName })); - return true; - } catch (error) { - // if role does not exist, create one - if (error instanceof NoSuchEntityException) { - console.log(`Role: ${roleName} does not exist in the account. Will create one.`); - return false; - } else { - // throwing error so it will be sent back to cloudformation stack and message will help troubleshoot - throw new Error( - `Encountered an error when attempting to retrieve the status of service-linked role ${roleName}.`, - ); - } - } -} - -export async function checkRoleStatus(iamClient: IAMClient, roleName: string) { - let attempts = 0; - - while (attempts < 10) { - try { - const command = new GetRoleCommand({ RoleName: roleName }); - // if this api passes, it means role exists - await iamClient.send(command); - break; - } catch (error) { - // this is the only exception to look for. If its not this error then break and throw exception - if (error instanceof NoSuchEntityException) { - attempts = attempts + 1; - } else { - const msg = `There was an error in checking status of role: ${roleName}`; - throw new Error(msg); - } - } - } -} -function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/package.json deleted file mode 100644 index 1448690..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-iam-create-service-linked-role", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-iam": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/create-service-linked-role/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/password-policy.ts b/source/packages/@aws-accelerator/constructs/lib/aws-iam/password-policy.ts deleted file mode 100644 index 28836e1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/password-policy.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * PasswordPolicyProps properties - */ -export interface PasswordPolicyProps { - readonly allowUsersToChangePassword: boolean; - readonly hardExpiry: boolean; - readonly requireUppercaseCharacters: boolean; - readonly requireLowercaseCharacters: boolean; - readonly requireSymbols: boolean; - readonly requireNumbers: boolean; - readonly minimumPasswordLength: number; - readonly passwordReusePrevention: number; - readonly maxPasswordAge: number; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to Update Account Password Policy - */ -export class PasswordPolicy extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: PasswordPolicyProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::IamUpdateAccountPasswordPolicy'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'update-account-password-policy/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['iam:UpdateAccountPasswordPolicy'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - allowUsersToChangePassword: props.allowUsersToChangePassword, - hardExpiry: props.hardExpiry, - requireUppercaseCharacters: props.requireUppercaseCharacters, - requireLowercaseCharacters: props.requireLowercaseCharacters, - requireSymbols: props.requireSymbols, - requireNumbers: props.requireNumbers, - minimumPasswordLength: props.minimumPasswordLength, - passwordReusePrevention: props.passwordReusePrevention, - maxPasswordAge: props.maxPasswordAge, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/service-linked-role.ts b/source/packages/@aws-accelerator/constructs/lib/aws-iam/service-linked-role.ts deleted file mode 100644 index 3b4ccdc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/service-linked-role.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; -/** - * Initialized ServiceLinkedRoleProps properties - */ -export interface ServiceLinkedRoleProps { - /** - * Service linked role service name - */ - readonly awsServiceName: string; - /** - * Service linked role service description - */ - readonly description?: string; - /** - * Service linked role name that should be created when create-service-link role api call is made. - * this allows to look up roles faster, scale better in case naming changes by service. - * @example - * for autoscaling.amazonaws.com roleName would be AWSServiceRoleForAutoScaling - */ - readonly roleName: string; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - * - */ - readonly environmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly cloudWatchLogRetentionInDays: number; -} - -/** - * Class for ServiceLinkedRole - */ -export class ServiceLinkedRole extends Construct { - public readonly resource: cdk.CustomResource; - public readonly roleArn: string; - public readonly roleName: string; - constructor(scope: Construct, id: string, props: ServiceLinkedRoleProps) { - super(scope, id); - - const lambdaFunction = new cdk.aws_lambda.Function(this, 'CreateServiceLinkedRoleFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-service-linked-role/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - memorySize: 512, - description: 'Custom resource provider to create service linked role', - initialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['iam:CreateServiceLinkedRole', 'iam:GetRole'], - resources: ['*'], - }), - ], - environmentEncryption: props.environmentEncryptionKmsKey, - }); - - const logGroup = new cdk.aws_logs.LogGroup(this, `${lambdaFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${lambdaFunction.functionName}`, - retention: props.cloudWatchLogRetentionInDays, - encryptionKey: props.cloudWatchLogKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'CreateServiceLinkedRoleProvider', { - onEventHandler: lambdaFunction, - }); - - this.resource = new cdk.CustomResource(this, 'CreateServiceLinkedRoleResource', { - resourceType: 'Custom::CreateServiceLinkedRole', - serviceToken: provider.serviceToken, - properties: { - serviceName: props.awsServiceName, - description: props.description, - roleName: props.roleName, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - this.resource.node.addDependency(logGroup); - - this.roleArn = this.resource.getAtt('roleArn').toString(); - this.roleName = this.resource.getAtt('roleName').toString(); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/index.ts deleted file mode 100644 index dc3f71d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * update-account-password-policy - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - switch (event.RequestType) { - case 'Create': - case 'Update': - const iamClient = new AWS.IAM({ customUserAgent: process.env['SOLUTION_ID'] }); - await throttlingBackOff(() => - iamClient - .updateAccountPasswordPolicy({ - AllowUsersToChangePassword: event.ResourceProperties['allowUsersToChangePassword'] === 'true', - HardExpiry: event.ResourceProperties['hardExpiry'] === 'true', - RequireUppercaseCharacters: event.ResourceProperties['requireUppercaseCharacters'] === 'true', - RequireLowercaseCharacters: event.ResourceProperties['requireLowercaseCharacters'] === 'true', - RequireSymbols: event.ResourceProperties['requireSymbols'] === 'true', - RequireNumbers: event.ResourceProperties['requireNumbers'] === 'true', - MinimumPasswordLength: event.ResourceProperties['minimumPasswordLength'], - PasswordReusePrevention: event.ResourceProperties['passwordReusePrevention'], - MaxPasswordAge: event.ResourceProperties['maxPasswordAge'], - }) - .promise(), - ); - return { - PhysicalResourceId: 'update-account-password-policy', - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/package.json deleted file mode 100644 index f35dff3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-iam-update-account-password-policy", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-iam/update-account-password-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts deleted file mode 100644 index 0c83acb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/index.ts +++ /dev/null @@ -1,475 +0,0 @@ -/** - * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * Y - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { - SSOAdminClient, - CreateAccountAssignmentRequest, - DeleteAccountAssignmentRequest, - CreateAccountAssignmentCommand, - DeleteAccountAssignmentCommand, - PrincipalType, - CreateAccountAssignmentCommandInput, -} from '@aws-sdk/client-sso-admin'; -import { Group, IdentitystoreClient, ListGroupsCommand, ListUsersCommand, User } from '@aws-sdk/client-identitystore'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * build-identity-center-assignments - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(`${JSON.stringify(event)}`); - const ssoAdminClient = new SSOAdminClient({ - retryStrategy: setRetryStrategy(), - customUserAgent: process.env['SOLUTION_ID'], - }); - const idcClient = new IdentitystoreClient({ - retryStrategy: setRetryStrategy(), - customUserAgent: process.env['SOLUTION_ID'], - }); - - const instanceArn: string = event.ResourceProperties['instanceArn']; - const identityStoreId: string = event.ResourceProperties['identityStoreId']; - const principalType: string | undefined = event.ResourceProperties['principalType']; - const principalId: string | undefined = event.ResourceProperties['principalId']; - const principals: - | [ - { - name: string; - type: string; - }, - ] - | [] = event.ResourceProperties['principals']; - const permissionSetArnValue: string = event.ResourceProperties['permissionSetArn']; - const accountIds: string[] = event.ResourceProperties['accountIds']; - // const assignmentCreationRequests: CreateAccountAssignmentRequest[] = []; - // const assignmentDeletionRequests: DeleteAccountAssignmentRequest[] = []; - - switch (event.RequestType) { - case 'Create': - return onCreate(event); - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event); - } - - async function onCreate(event: AWSLambda.CloudFormationCustomResourceCreateEvent) { - // Build the account assignment creation - const assignmentCreationRequests = await buildCreateAssignmentsList( - accountIds, - principals, - identityStoreId, - instanceArn, - permissionSetArnValue, - principalType as PrincipalType, - principalId!, - idcClient, - ); - - if (assignmentCreationRequests.length > 0) { - await createAssignment(assignmentCreationRequests, ssoAdminClient); - } - return { - PhysicalResourceId: event.LogicalResourceId, - Status: 'SUCCESS', - }; - } - - async function onUpdate(event: AWSLambda.CloudFormationCustomResourceUpdateEvent) { - const previousAccountIdsList: string[] = event.OldResourceProperties['accountIds'] ?? []; - const deletionList = await retrieveAccountDeletions(previousAccountIdsList, accountIds); - // Build the account assignment creation - const assignmentCreationRequests = await buildCreateAssignmentsList( - accountIds, - principals, - identityStoreId, - instanceArn, - permissionSetArnValue, - principalType as PrincipalType, - principalId!, - idcClient, - ); - - if (assignmentCreationRequests.length > 0) { - await createAssignment(assignmentCreationRequests, ssoAdminClient); - } - - const assignmentDeletionRequests = await buildDeleteAssignmentsList( - deletionList, - principals, - identityStoreId, - instanceArn, - permissionSetArnValue, - principalType as PrincipalType, - principalId!, - idcClient, - ); - - // Build the Delete Parameters - if (deletionList.length > 0) { - await deleteAssignment(assignmentDeletionRequests, ssoAdminClient); - } - return { - PhysicalResourceId: event.LogicalResourceId, - Status: 'SUCCESS', - }; - } - - async function onDelete(event: AWSLambda.CloudFormationCustomResourceDeleteEvent) { - const assignmentDeletionRequests = await buildDeleteAssignmentsList( - accountIds, - principals, - identityStoreId, - instanceArn, - permissionSetArnValue, - principalType as PrincipalType, - principalId!, - idcClient, - ); - - // Call the delete account assignments method - await deleteAssignment(assignmentDeletionRequests, ssoAdminClient); - - return { - PhysicalResourceId: event.LogicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Method to create account assignments list - * @param accountIds List of accounts that are in event - * @param principals The principal object that requires a lookup - * @param identityStoreId - * @param instanceArn - * @param permissionSetArnValue - * @param principalType The Principal Type - * @param principalId The Principal ID - * @param idcClient - * @returns string[] - */ -async function buildCreateAssignmentsList( - accountIds: string[], - principals: - | [ - { - name: string; - type: string; - }, - ] - | [], - identityStoreId: string, - instanceArn: string, - permissionSetArnValue: string, - principalType: PrincipalType, - principalId: string, - idcClient: IdentitystoreClient, -): Promise { - const assignmentCreationRequests: CreateAccountAssignmentRequest[] = []; - for (const accountId of accountIds ?? []) { - if (principals) { - for (const principal of principals) { - // If logic to check if the principal type is for USER - if (principal.type === 'USER') { - const principalId = await throttlingBackOff(() => getUserPrincipalId(principal, identityStoreId, idcClient)); - if (principalId) { - assignmentCreationRequests.push({ - InstanceArn: instanceArn, - TargetId: accountId, - TargetType: 'AWS_ACCOUNT', - PermissionSetArn: permissionSetArnValue, - PrincipalType: principal.type, - PrincipalId: principalId, - }); - } - } - // If logic to check if the principal type is for GROUP - else if (principal.type === 'GROUP') { - const principalId = await throttlingBackOff(() => getGroupPrincipalId(principal, identityStoreId, idcClient)); - if (principalId) { - assignmentCreationRequests.push({ - InstanceArn: instanceArn, - TargetId: accountId, - TargetType: 'AWS_ACCOUNT', - PermissionSetArn: permissionSetArnValue, - PrincipalType: principal.type, - PrincipalId: principalId, - }); - } - } - } - } else { - assignmentCreationRequests.push({ - InstanceArn: instanceArn, - TargetId: accountId, - TargetType: 'AWS_ACCOUNT', - PermissionSetArn: permissionSetArnValue, - PrincipalType: principalType as PrincipalType, - PrincipalId: principalId, - }); - } - } - return assignmentCreationRequests; -} - -/** - * Method to create account assignments list - * @param accountIds List of accounts that are in event - * @param principals The principal object that requires a lookup - * @param identityStoreId - * @param instanceArn - * @param permissionSetArnValue - * @param principalType The Principal Type - * @parm principalId The Principal ID - * @param idcClient - * @returns string[] - */ -async function buildDeleteAssignmentsList( - accountIds: string[], - principals: - | [ - { - name: string; - type: string; - }, - ] - | [], - identityStoreId: string, - instanceArn: string, - permissionSetArnValue: string, - principalType: PrincipalType, - principalId: string, - idcClient: IdentitystoreClient, -): Promise { - const assignmentDeletionRequests: DeleteAccountAssignmentRequest[] = []; - for (const accountId of accountIds) { - if (principals) { - for (const principal of principals) { - // If logic to check if the principal type is for USER - if (principal.type === 'USER') { - const principalId = await throttlingBackOff(() => getUserPrincipalId(principal, identityStoreId, idcClient)); - if (principalId) { - assignmentDeletionRequests.push({ - InstanceArn: instanceArn, - TargetId: accountId, - TargetType: 'AWS_ACCOUNT', - PermissionSetArn: permissionSetArnValue, - PrincipalType: principal.type, - PrincipalId: principalId, - }); - } - } - // If logic to check if the principal type is for GROUP - else if (principal.type === 'GROUP') { - const principalId = await throttlingBackOff(() => getGroupPrincipalId(principal, identityStoreId, idcClient)); - if (principalId) { - assignmentDeletionRequests.push({ - InstanceArn: instanceArn, - TargetId: accountId, - TargetType: 'AWS_ACCOUNT', - PermissionSetArn: permissionSetArnValue, - PrincipalType: principal.type, - PrincipalId: principalId, - }); - } - } - } - } else { - assignmentDeletionRequests.push({ - InstanceArn: instanceArn, - TargetId: accountId, - TargetType: 'AWS_ACCOUNT', - PermissionSetArn: permissionSetArnValue, - PrincipalType: principalType as PrincipalType, - PrincipalId: principalId, - }); - } - } - return assignmentDeletionRequests; -} - -/** - * Method processes list of create account Assignments - * @param assignmentCreationRequests - * @returns string[] - */ -async function createAssignment( - assignmentCreationRequests: CreateAccountAssignmentCommandInput[], - ssoAdminClient: SSOAdminClient, -) { - for (const createParameter of assignmentCreationRequests ?? []) { - console.log( - `Creating account assignment for Principal ID ${createParameter.PrincipalId} for ${createParameter.TargetId}`, - ); - const createEvent = await throttlingBackOff(() => - ssoAdminClient.send(new CreateAccountAssignmentCommand(createParameter)), - ); - if (createEvent.AccountAssignmentCreationStatus) { - console.log( - `Request Id ${createEvent.AccountAssignmentCreationStatus.RequestId} for account ${createParameter.TargetId} in status: ${createEvent.AccountAssignmentCreationStatus.Status}`, - ); - } - console.log(`Processing create event: ${JSON.stringify(createEvent, null, 4)}`); - } -} - -/** - * Method processes list of delete account Assignments - * @param assignmentDeletionRequests - * @returns string[] - */ -async function deleteAssignment( - assignmentDeletionRequests: DeleteAccountAssignmentRequest[], - ssoAdminClient: SSOAdminClient, -) { - for (const deleteParameter of assignmentDeletionRequests ?? []) { - console.log( - `Deleting account assignment for Principal ID ${deleteParameter.PrincipalId} for ${deleteParameter.TargetId}`, - ); - const deleteEvent = await throttlingBackOff(() => - ssoAdminClient.send(new DeleteAccountAssignmentCommand(deleteParameter)), - ); - if (deleteEvent.AccountAssignmentDeletionStatus) { - console.log( - `Request Id ${deleteEvent.AccountAssignmentDeletionStatus.RequestId} for account ${deleteParameter.TargetId} in status: ${deleteEvent.AccountAssignmentDeletionStatus.Status}`, - ); - } - } -} - -/** - * Returns the list of accounts IDs that need to have the account assignments deleted for. - * @param previousAccountIdsList List of accounts IDs that Identity Center Assignments were previously created for - * @returns string[] - */ -async function retrieveAccountDeletions(previousAccountIdsList: string[], accountIds: string[]) { - const deletionList: string[] = []; - for (const accountId of previousAccountIdsList) { - if (accountIds.indexOf(accountId) === -1) { - if (!deletionList.includes(accountId)) { - deletionList.push(accountId); - } - } - } - return deletionList; -} - -/** - * Retruns the principal ID of the User. - * @param principal Incoming JSON object containing the name of the user/group as well as its type. - * @param identityStoreId The Identity store id - * @param idcClient - * @returns string - */ -async function getUserPrincipalId( - principal: { name: string; type: string }, - identityStoreId: string, - idcClient: IdentitystoreClient, -) { - let principalId: string | undefined; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - idcClient.send( - new ListUsersCommand({ - IdentityStoreId: identityStoreId, - Filters: [{ AttributePath: 'UserName', AttributeValue: principal.name }], - }), - ), - ); - // Going through results and retrieving the principal ID - for (const user of page.Users ?? []) { - principalId = user.UserId; - } - if (page.Users) { - validateNumberOfPrincipals(page.Users, principal.name, principal.type, identityStoreId, principalId); - } - nextToken = page.NextToken; - } while (nextToken); - return principalId; -} - -/** - * Retruns the principal ID of the Group. - * @param principal Incoming JSON object containing the name of the user/group as well as its type. - * @param identityStoreId The Identity store id - * @param idcClient - * @returns string - */ -async function getGroupPrincipalId( - principal: { name: string; type: string }, - identityStoreId: string, - idcClient: IdentitystoreClient, -) { - let principalId: string | undefined; - let nextToken: string | undefined = undefined; - - do { - const page = await throttlingBackOff(() => - idcClient.send( - new ListGroupsCommand({ - IdentityStoreId: identityStoreId, - Filters: [{ AttributePath: 'DisplayName', AttributeValue: principal.name }], - }), - ), - ); - // Going through results and retrieving the principal ID - for (const group of page.Groups ?? []) { - principalId = group.GroupId; - } - if (page.Groups) { - validateNumberOfPrincipals(page.Groups, principal.name, principal.type, identityStoreId, principalId); - } - nextToken = page.NextToken; - } while (nextToken); - return principalId; -} - -/** - * Principal validation function - * @param principalArray Users and Groups to validate - * @param principalName Name of the principal - * @param principalType Type of the principal (USER/GROUP) - * @param identityStoreId - * @param principalId - * @returns boolean - */ -function validateNumberOfPrincipals( - principalArray: User[] | Group[], - principalName: string, - principalType: string, - identityStoreId: string, - principalId: string | undefined, -): { PhysicalResourceId: undefined; Status: string } | boolean { - if (principalArray.length > 1 && principalId) { - console.error(`Multiple ${principalName} of ${principalType} found in identity store ${identityStoreId}!!!!`); - return { - PhysicalResourceId: undefined, - Status: 'FAILURE', - }; - } - return true; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/package.json deleted file mode 100644 index 9f792cc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-identitystore-build-identity-center-assignments", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5", - "ts-node": "10.4.0" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-sso-admin": "3.410.0", - "@aws-sdk/client-identitystore": "3.410.0" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/build-identity-center-assignments/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/index.ts deleted file mode 100644 index 9a81827..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,373 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -import { PermissionSet } from 'aws-sdk/clients/ssoadmin'; -import { IdentityCenterPermissionSetConfig } from '@aws-accelerator/config/lib/iam-config'; -AWS.config.logger = console; - -/** - * enable-identity-center - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - Reason?: string | undefined; - } - | undefined -> { - console.log(JSON.stringify(event, null, 4)); - let organizationsClient = new AWS.Organizations({ customUserAgent: process.env['SOLUTION_ID'] }); - const identityCenterClient = new AWS.SSOAdmin({ customUserAgent: process.env['SOLUTION_ID'] }); - const identityCenterServicePrincipal = 'sso.amazonaws.com'; - const partition = event.ResourceProperties['partition']; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1' }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1' }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1' }); - } - - const newIdentityCenterDelegatedAdminAccount = event.ResourceProperties['adminAccountId']; - const lzaManagedPermissionSets = event.ResourceProperties['lzaManagedPermissionSets']; - const lzaManagedAssignments = event.ResourceProperties['lzaManagedAssignments']; - - const currentIdentityCenterDelegatedAdmin = await getCurrentDelegatedAdminAccount( - organizationsClient, - identityCenterServicePrincipal, - ); - - console.log( - `Current Identity Center Delegated Admin Account: ${currentIdentityCenterDelegatedAdmin || 'No account found'}`, - ); - console.log(`New Identity Center Delegated Admin Account: ${newIdentityCenterDelegatedAdminAccount}`); - switch (event.RequestType) { - case 'Create': - case 'Update': - if (currentIdentityCenterDelegatedAdmin === newIdentityCenterDelegatedAdminAccount) { - console.log('Accounts match. Nothing to do.'); - return { Status: 'Success', StatusCode: 200 }; - } - - console.log('Checking if Identity Center is enabled in Organizations'); - const identityCenterEnabled = await isOrganizationServiceEnabled( - organizationsClient, - identityCenterServicePrincipal, - ); - if (!identityCenterEnabled) { - console.log('Enabling Identity service in Organizations'); - await throttlingBackOff(() => - organizationsClient.enableAWSServiceAccess({ ServicePrincipal: identityCenterServicePrincipal }).promise(), - ); - } - - let isDelegatedAdminDeregistered = true; - if ( - currentIdentityCenterDelegatedAdmin && - currentIdentityCenterDelegatedAdmin != newIdentityCenterDelegatedAdminAccount - ) { - console.log(`Deregistering Delegated Administrator for ${identityCenterServicePrincipal}`); - isDelegatedAdminDeregistered = await deregisterDelegatedAdministrators( - organizationsClient, - identityCenterClient, - identityCenterServicePrincipal, - currentIdentityCenterDelegatedAdmin, - lzaManagedPermissionSets, - lzaManagedAssignments, - ); - } - - if (isDelegatedAdminDeregistered) { - console.log('Registering Delegated Administrator for Identity Center'); - await throttlingBackOff(() => - organizationsClient - .registerDelegatedAdministrator({ - AccountId: newIdentityCenterDelegatedAdminAccount, - ServicePrincipal: identityCenterServicePrincipal, - }) - .promise(), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const adminAccountId = await getCurrentDelegatedAdminAccount(organizationsClient, identityCenterServicePrincipal); - if (adminAccountId) { - console.log('Deregistering Delegated Admin Account'); - console.log(adminAccountId); - await throttlingBackOff(() => - organizationsClient - .deregisterDelegatedAdministrator({ - AccountId: adminAccountId, - ServicePrincipal: identityCenterServicePrincipal, - }) - .promise(), - ); - } else { - console.log('No Identity Center Admin Account exists'); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function isOrganizationServiceEnabled( - orgnizationsClient: AWS.Organizations, - servicePrincipal: string, -): Promise { - let nextToken; - const enabledOrgServices = []; - do { - const services = await orgnizationsClient.listAWSServiceAccessForOrganization({ NextToken: nextToken }).promise(); - if (services.EnabledServicePrincipals) { - enabledOrgServices.push(...services.EnabledServicePrincipals); - } - } while (nextToken); - - const enabledServiceNames = enabledOrgServices.map(service => { - return service.ServicePrincipal; - }); - - return enabledServiceNames.includes(servicePrincipal); -} - -async function getCurrentDelegatedAdminAccount( - organizationsClient: AWS.Organizations, - identityCenterServicePrincipal: string, -) { - console.log('Getting Delegated Administrator for Identity Center'); - const delegatedAdmins = await throttlingBackOff(() => - organizationsClient.listDelegatedAdministrators({ ServicePrincipal: identityCenterServicePrincipal }).promise(), - ); - - let delegatedAdminAccounts: string[] = []; - if (delegatedAdmins.DelegatedAdministrators) { - delegatedAdminAccounts = delegatedAdmins.DelegatedAdministrators.map(delegatedAdmin => { - return delegatedAdmin.Id!; - }); - } - let delegatedAdmin = ''; - if (delegatedAdminAccounts?.length > 0) { - delegatedAdmin = delegatedAdminAccounts[0]; - console.log(`Current Delegated Admins for Identity Center is account: ${delegatedAdmin}`); - } - - return delegatedAdmin; -} - -function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function deregisterDelegatedAdministrators( - organizationsClient: AWS.Organizations, - identityCenterClient: AWS.SSOAdmin, - servicePrincipal: string, - delegatedAdmin: string, - permissionSetsFromConfig: IdentityCenterPermissionSetConfig[], - assignmentsFromConfig: Map, -): Promise { - const ssoInstanceId = await getSsoInstanceId(identityCenterClient); - if (ssoInstanceId) { - console.log(`Identity Center Instance ID is: ${ssoInstanceId}`); - const isIdentityCenterDeregisterable = await verifyIdentityCenterResourcesBeforeDeletion( - identityCenterClient, - ssoInstanceId, - permissionSetsFromConfig, - assignmentsFromConfig, - ); - - // Only Deregister Account if No Permission Sets or Assignments are present in the account. - if (isIdentityCenterDeregisterable) { - console.log(`Deregistering Delegated Admin Account ${delegatedAdmin}`); - await throttlingBackOff(() => - organizationsClient - .deregisterDelegatedAdministrator({ - AccountId: delegatedAdmin, - ServicePrincipal: servicePrincipal, - }) - .promise(), - ); - console.log('Waiting 5 seconds to allow DelegatedAdmin account to de-register'); - await delay(5000); - return true; - } - } - return false; -} - -async function verifyIdentityCenterResourcesBeforeDeletion( - identityCenterClient: AWS.SSOAdmin, - ssoInstanceId: string, - permissionSetsFromConfig: IdentityCenterPermissionSetConfig[], - assignmentsFromConfig: Map, -): Promise { - const permissionSetList = await getPermissionSetList(identityCenterClient, ssoInstanceId); - const filteredPermissionSetList = await filterPermissionSetList(permissionSetList, permissionSetsFromConfig); - const assignmentList = await getAssignmentsList( - identityCenterClient, - ssoInstanceId, - assignmentsFromConfig, - permissionSetList, - ); - - if ( - (filteredPermissionSetList && filteredPermissionSetList.length > 0) || - (assignmentList && assignmentList.length > 0) - ) { - throw new Error( - `Delegated Admin Identity Center cannot be updated due to existing Permission Sets or Assignments. Remove existing Permission Sets and Assignments from iam-config.yaml and re-run the pipeline. For more error log details, please check the custom resource Lambda logs.`, - ); - } - return true; -} - -async function getPermissionSetList( - identityCenterClient: AWS.SSOAdmin, - ssoInstanceId: string, -): Promise { - let permissionSetArnList: string[] = []; - let permissionSetList: PermissionSet[] = []; - const listPermissionsResponse = await throttlingBackOff(() => - identityCenterClient - .listPermissionSets({ - InstanceArn: ssoInstanceId, - }) - .promise(), - ); - permissionSetArnList = listPermissionsResponse.PermissionSets!; - permissionSetList = await getPermissionSetObject(identityCenterClient, ssoInstanceId, permissionSetArnList); - - return permissionSetList!; -} - -async function filterPermissionSetList( - permissionSetList: PermissionSet[], - permissionSetsFromConfig: IdentityCenterPermissionSetConfig[], -): Promise { - const permissionSetListNames: string[] = []; - - //Filter down permission set object to list of permission set names. - permissionSetList.filter(permissionSet => { - if (permissionSet.Name) { - permissionSetListNames.push(permissionSet.Name); - } - }); - - //Check to see if existing permission sets are in SSO Permission Set Config - const filteredConfigPermissionSetList = permissionSetsFromConfig.filter(permissionSet => - permissionSetListNames.includes(permissionSet.name), - ); - - if (filteredConfigPermissionSetList && filteredConfigPermissionSetList.length > 0) { - console.log( - `Delegated Admin Identity Center cannot be updated due to existing LZA-Managed Permission Sets. Please remove all Permission Sets from the iam-config.yaml file before changing the delegated administrator.`, - ); - console.log(filteredConfigPermissionSetList); - } - return filteredConfigPermissionSetList; -} - -async function getPermissionSetObject( - identityCenterClient: AWS.SSOAdmin, - ssoInstanceId: string, - permissionSetArnList: string[], -): Promise { - const permissionSetList: PermissionSet[] = []; - for (const permissionSetArn of permissionSetArnList) { - const describePermissionSetResponse = await throttlingBackOff(() => - identityCenterClient - .describePermissionSet({ - InstanceArn: ssoInstanceId, - PermissionSetArn: permissionSetArn, - }) - .promise(), - ); - if (describePermissionSetResponse.PermissionSet) { - permissionSetList.push(describePermissionSetResponse.PermissionSet); - } - } - return permissionSetList; -} - -async function getAssignmentsList( - identityCenterClient: AWS.SSOAdmin, - ssoInstanceId: string, - assignmentsFromConfig: Map, - permissionSetList: PermissionSet[], -): Promise { - const accountAssignmentList: string[] = []; - //Iterate through each assignment - for (const assignment of assignmentsFromConfig) { - //Since object is Map, we need to extract permissionSet and deploymentTarget Account - Object.entries(assignment).forEach(async (key: [string, string | string[]]) => { - const permissionSetName = key[0]; - const permissionSet = permissionSetList.filter(permissionSet => { - if (permissionSet.Name === permissionSetName) { - return permissionSet; - } - return undefined; - })[0]; - const deploymentTargetAccounts = key[1]; - //Then we have to call listAccountAssignments on each individual deploymentTargetAccount - if (permissionSet && permissionSet.PermissionSetArn) { - for (const deploymentTargetAccount of deploymentTargetAccounts) { - const listAccountAssignmentsResponse = await throttlingBackOff(() => - identityCenterClient - .listAccountAssignments({ - InstanceArn: ssoInstanceId!, - AccountId: deploymentTargetAccount!, - PermissionSetArn: permissionSet.PermissionSetArn!, - }) - .promise(), - ); - if ( - listAccountAssignmentsResponse.AccountAssignments && - listAccountAssignmentsResponse.AccountAssignments.length > 0 - ) { - accountAssignmentList.push(JSON.stringify(listAccountAssignmentsResponse.AccountAssignments!)); - } - } - } - }); - } - if (accountAssignmentList && accountAssignmentList.length > 0) { - console.log( - `Delegated Admin Identity Center cannot be updated due to existing LZA-Managed Assignments. Please remove all Assignments from the iam-config.yaml file before changing the delegated administrator.`, - ); - console.log(JSON.stringify(accountAssignmentList)); - } - - return accountAssignmentList!; -} - -async function getSsoInstanceId(identityCenterClient: AWS.SSOAdmin): Promise { - console.log('Checking for Identity Center Instance Id...'); - const listInstanceResponse = await throttlingBackOff(() => identityCenterClient.listInstances().promise()); - const identityCenterInstanceIdList = listInstanceResponse.Instances; - let identityCenterInstance; - if (identityCenterInstanceIdList) { - for (const identityCenterInstanceId of identityCenterInstanceIdList) { - identityCenterInstance = identityCenterInstanceId.InstanceArn; - } - } - return identityCenterInstance; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/package.json deleted file mode 100644 index 2e8db85..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-identity-center-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index 4e33341..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/index.ts deleted file mode 100644 index be761ba..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * get-identity-center-instance-id - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - Data: { identityStoreId: string; instanceArn: string } | undefined; - } - | undefined -> { - const identityCenterClient = new AWS.SSOAdmin({ customUserAgent: process.env['SOLUTION_ID'] }); - - let data: { identityStoreId: string; instanceArn: string } | undefined; - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('Checking for IdentityCenter Instance Id...'); - const response = await throttlingBackOff(() => identityCenterClient.listInstances().promise()); - - if (response.Instances && response.Instances.length === 1) { - console.log(`IdentityCenter Instance Store Arn is -> ${response.Instances[0].InstanceArn!}`); - console.log(`IdentityCenter Instance Store Id is -> ${response.Instances[0].IdentityStoreId!}`); - data = { - identityStoreId: response.Instances[0].IdentityStoreId!, - instanceArn: response.Instances[0].InstanceArn!, - }; - return { - Status: 'SUCCESS', - PhysicalResourceId: response.Instances[0].InstanceArn!, - Data: data, - }; - } - - console.log(`IdentityCenter Instance not found, api response is -> ${response}`); - return { PhysicalResourceId: undefined, Status: 'Failure', Data: undefined }; - - case 'Delete': - return { - Status: 'SUCCESS', - PhysicalResourceId: event.PhysicalResourceId, - Data: undefined, - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/package.json deleted file mode 100644 index 07bae75..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-identity-center-get-identity-center-instance-metadata", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/tsconfig.json deleted file mode 100644 index 4e33341..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-identity-center-instance-metadata/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/index.ts deleted file mode 100644 index d1a1c26..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/index.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -const iamClient = new AWS.IAM({ customUserAgent: process.env['SOLUTION_ID'] }); -const ssoRolePrefix = '/aws-reserved/sso.amazonaws.com/'; - -export interface responseData { - roleArn: string | undefined; -} -/** - * get-identity-center-permission-set-role-arn - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - Data?: responseData | undefined; - } - | undefined -> { - console.log(JSON.stringify(event, null, 4)); - const permissionSetName = event.ResourceProperties['permissionSetName']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('Getting Role ARN for Permission Set...'); - const roleArn = await getPermissionSetRoleArn(permissionSetName); - - if (roleArn) { - const responseData = { roleArn: roleArn }; - console.log(responseData); - return { Status: 'Success', Data: responseData, StatusCode: 200 }; - } else { - throw new Error(`No Permission Set with name ${permissionSetName} found`); - } - - case 'Delete': - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function getPermissionSetRoleArn(permissionSetName: string) { - const iamRoleList = await getIamRoleList(); - const roleArn = iamRoleList.find(role => { - const regex = new RegExp(`AWSReservedSSO_${permissionSetName}_([0-9a-fA-F]{16})`); - const match = regex.test(role.RoleName); - console.log(`Test ${role} for pattern ${regex} result: ${match}`); - return match; - })?.Arn; - - if (roleArn) { - console.log(`Found provisioned role for permission set ${permissionSetName} with ARN: ${roleArn}`); - } else { - console.log(`Permission set with name ${permissionSetName} not found`); - } - - return roleArn; -} - -async function getIamRoleList() { - const roleList = []; - let hasNext = true; - let marker: string | undefined = undefined; - - while (hasNext) { - const response = await throttlingBackOff(() => - iamClient.listRoles({ PathPrefix: ssoRolePrefix, Marker: marker }).promise(), - ); - - // Add roles returned in this paged response - roleList.push(...response.Roles); - - marker = response.Marker; - hasNext = !!marker; - } - return roleList; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/package.json deleted file mode 100644 index 22e706d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-identity-center-get-permission-set-role-arn-id", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/tsconfig.json deleted file mode 100644 index 4e33341..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/get-permission-set-role-arn/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-assignments.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-assignments.ts deleted file mode 100644 index 90cf506..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-assignments.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized IdentityCenterAssignmentsProps properties - */ -export interface IdentityCenterAssignmentsProps { - /** - * Identity Store Id - */ - readonly identityStoreId: string; - /** - * Identity Center instance arn - */ - readonly identityCenterInstanceArn: string; - /** - * Identity Center principals - */ - readonly principals?: { type: string; name: string }[]; - /** - * Identity Center principal type - */ - readonly principalType?: string; - /** - * Identity Center principal id - */ - readonly principalId?: string; - /** - * Identity Center permission set arn - */ - readonly permissionSetArnValue: string; - /** - * Target account Ids - */ - readonly accountIds: string[]; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class Identity Center Assignments - */ -export class IdentityCenterAssignments extends Construct { - readonly id: string; - constructor(scope: Construct, id: string, props: IdentityCenterAssignmentsProps) { - super(scope, id); - - const IDENTITY_CENTER_ASSIGNMENT_TYPE = 'Custom::IdentityCenterAssignments'; - const partition = cdk.Stack.of(this).partition; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, IDENTITY_CENTER_ASSIGNMENT_TYPE, { - codeDirectory: path.join(__dirname, 'build-identity-center-assignments/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'iam:ListRoles', - 'iam:ListPolicies', - 'identitystore:ListGroups', - 'identitystore:ListUsers', - 'sso:CreateAccountAssignment', - 'sso:DeleteAccountAssignment', - 'sso:ListAccountAssignments', - ], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['iam:GetSAMLProvider', 'iam:UpdateSAMLProvider'], - Resource: `arn:${partition}:iam::${cdk.Stack.of(this).account}:saml-provider/AWSSSO_*_DO_NOT_DELETE`, - }, - { - Effect: 'Allow', - Action: [ - 'iam:AttachRolePolicy', - 'iam:CreateRole', - 'iam:DeleteRole', - 'iam:DeleteRolePolicy', - 'iam:DetachRolePolicy', - 'iam:GetRole', - 'iam:ListAttachedRolePolicies', - 'iam:ListRolePolicies', - 'iam:PutRolePolicy', - 'iam:UpdateRole', - 'iam:UpdateRoleDescription', - ], - Resource: `arn:${partition}:iam::*:role/aws-reserved/sso.amazonaws.com/*`, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: IDENTITY_CENTER_ASSIGNMENT_TYPE, - serviceToken: provider.serviceToken, - properties: { - identityStoreId: props.identityStoreId, - instanceArn: props.identityCenterInstanceArn, - principals: props.principals, - principalType: props.principalType, - principalId: props.principalId, - permissionSetArn: props.permissionSetArnValue, - accountIds: props.accountIds, - }, - }); - - this.id = resource.ref; - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-get-permission-set-role-arn.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-get-permission-set-role-arn.ts deleted file mode 100644 index 92bec92..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-get-permission-set-role-arn.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; -import { v4 as uuidv4 } from 'uuid'; -import * as path from 'path'; - -/** - * Initialized GetPermissionSetRoleArn properties - */ -export interface GetPermissionSetRoleArnProps { - /** - * Account id where the permission set has been provisioned - */ - readonly accountId: string; - /** - * Custom resource provider (single provider shared by multiple resources) - */ - readonly serviceToken: string; - /** - * The name of the permission set - */ - readonly permissionSetName?: string; - /** - * Custom resource lambda log group encryption key - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays?: number; -} - -/** - * Class for IdentityCenterGetPermissionRoleArnProvider - */ -export class IdentityCenterGetPermissionRoleArnProvider extends Construct { - readonly provider: cdk.custom_resources.Provider; - readonly serviceToken: string; - constructor(scope: Construct, id: string) { - super(scope, id); - const functionId = `${id}ProviderLambda`; - const providerLambda = new cdk.aws_lambda.Function(this, functionId, { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'get-permission-set-role-arn/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - timeout: cdk.Duration.seconds(60), - initialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['iam:ListRoles'], - resources: ['*'], - }), - ], - handler: 'index.handler', - }); - - this.provider = new cdk.custom_resources.Provider(this, 'Resource', { - onEventHandler: providerLambda, - }); - - this.serviceToken = this.provider.serviceToken; - - const stack = cdk.Stack.of(scope); - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/Resource/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/Resource/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - } -} -/** - * Class for IdentityCenterGetPermissionRoleArn - */ -export class IdentityCenterGetPermissionRoleArn extends Construct { - readonly resource: cdk.CustomResource; - readonly roleArn: string; - constructor(scope: Construct, id: string, props: GetPermissionSetRoleArnProps) { - super(scope, id); - this.resource = new cdk.CustomResource(this, `getPermissionSetRoleArn`, { - serviceToken: props.serviceToken, - properties: { - permissionSetName: props.permissionSetName, - uuid: uuidv4(), - }, - }); - - this.roleArn = this.resource.getAtt('roleArn').toString(); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-instance.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-instance.ts deleted file mode 100644 index 92b452a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-instance.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { LzaCustomResource } from '../lza-custom-resource'; - -/** - * Initialized IdentityCenterInstanceProps properties - */ -export interface IdentityCenterInstanceProps { - /** - * Custom resource lambda environment encryption key - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; -} - -/** - * Class for IdentityCenterInstance - */ -export class IdentityCenterInstance extends Construct { - public readonly instanceArn: string; - public readonly instanceStoreId: string; - constructor(scope: Construct, id: string, props: IdentityCenterInstanceProps) { - super(scope, id); - - const resourceName = 'IdentityCenterGetInstanceId'; - - const lzaCustomResource = new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - }, - lambda: { - assetPath: path.join(__dirname, 'get-identity-center-instance-metadata/dist'), - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(15), - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['sso:ListInstances'], - resources: ['*'], - }), - ], - }, - }); - - this.instanceArn = lzaCustomResource.resource.getAtt('instanceArn').toString(); - this.instanceStoreId = lzaCustomResource.resource.getAtt('identityStoreId').toString(); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-organization-admin-account.ts deleted file mode 100644 index 94e08a9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-identity-center/identity-center-organization-admin-account.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; -import * as path from 'path'; -import { IdentityCenterPermissionSetConfig } from '@aws-accelerator/config'; - -/** - * Initialized IdentityCenterOrganizationalAdminAccountProps properties - */ -export interface IdentityCenterOrganizationalAdminAccountProps { - /** - * Delegated Admin Account Id - */ - readonly adminAccountId: string; - /** - * List of LZA Managed Permission Sets from IAM Config - */ - readonly lzaManagedPermissionSets: IdentityCenterPermissionSetConfig[]; - /** - * List of LZA Managed Assignments from IAM Config - */ - readonly lzaManagedAssignments: { [x: string]: string[] }[]; -} - -/** - * Class for IdentityCenterOrganizationAdminAccount - */ -export class IdentityCenterOrganizationAdminAccount extends Construct { - readonly provider: cdk.custom_resources.Provider; - readonly resource: cdk.Resource; - constructor(scope: Construct, id: string, props: IdentityCenterOrganizationalAdminAccountProps) { - super(scope, id); - const functionId = `${id}ProviderLambda`; - const providerLambda = new cdk.aws_lambda.Function(this, functionId, { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'enable-organization-admin-account/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - timeout: cdk.Duration.seconds(160), - initialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'organizations:ListDelegatedAdministrators', - 'organizations:RegisterDelegatedAdministrator', - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:EnableAwsServiceAccess', - 'organizations:DisableAWSServiceAccess', - 'organizations:DescribeAccount', - 'organizations:DescribeOrganization', - 'organizations:DescribeOrganizationalUnit', - 'organizations:ListAccounts', - 'organizations:ListAWSServiceAccessForOrganization', - 'sso:ListInstances', - 'sso:ListPermissionSets', - 'sso:ListAccountAssignments', - 'sso:DescribePermissionSet', - ], - resources: ['*'], - }), - ], - handler: 'index.handler', - }); - - this.provider = new cdk.custom_resources.Provider(this, 'Resource', { - onEventHandler: providerLambda, - }); - - // Adding UUID, we need to force this to run every time in case there is problems with Deregistering. - this.resource = new cdk.CustomResource(this, `identityCenterAdmin`, { - serviceToken: this.provider.serviceToken, - properties: { - adminAccountId: props.adminAccountId, - partition: cdk.Stack.of(scope).partition, - lzaManagedPermissionSets: props.lzaManagedPermissionSets, - lzaManagedAssignments: props.lzaManagedAssignments, - }, - }); - - const stack = cdk.Stack.of(scope); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/${functionId}/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/Resource/framework-onEvent/ServiceRole/Resource`, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ], - ); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${stack.stackName}/${id}/Resource/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-kms/key-encryption.ts b/source/packages/@aws-accelerator/constructs/lib/aws-kms/key-encryption.ts deleted file mode 100644 index f07aa8b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-kms/key-encryption.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import * as fs from 'fs'; -import { LzaCustomResource } from '../lza-custom-resource'; -/** - * Initialized KmsEncryptionProps properties - */ -export interface KmsEncryptionProps { - /** - * Sets the key policy on the specified KMS key arn - */ - readonly kmsArn: string; - /** - * JSON document policy file paths. - */ - readonly policyFilePaths: string[]; - /** - * Organization Id - */ - readonly organizationId?: string; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; -} - -/** - * Class for KmsEncryption - */ -export class KmsEncryption extends Construct { - private assetPath: string; - constructor(scope: Construct, id: string, props: KmsEncryptionProps) { - super(scope, id); - - const resourceName = 'KmsEncryption'; - this.assetPath = path.join(__dirname, 'put-key-policy/dist'); - const policyFolderName = 'kms-policy'; - fs.mkdirSync(path.join(this.assetPath, policyFolderName), { recursive: true }); - - const policyFilePaths: string[] = []; - - for (const policyFilePath of props.policyFilePaths ?? []) { - const policyFileName = path.parse(policyFilePath).base; - fs.copyFileSync(policyFilePath, path.join(this.assetPath, policyFolderName, policyFileName)); - policyFilePaths.push(`${policyFolderName}/${policyFileName}`); - } - - new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - properties: [ - { sourceAccount: cdk.Stack.of(this).account }, - { kmsArn: props.kmsArn }, - { - policyFilePaths: policyFilePaths, - }, - { organizationId: props.organizationId }, - ], - forceUpdate: true, - }, - lambda: { - assetPath: this.assetPath, - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(5), - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:PutKeyPolicy'], - resources: [props.kmsArn], - }), - ], - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-kms/key-lookup.ts b/source/packages/@aws-accelerator/constructs/lib/aws-kms/key-lookup.ts deleted file mode 100644 index 53b6519..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-kms/key-lookup.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { SsmParameterLookup } from '../aws-ssm/ssm-parameter-lookup'; - -/** - * Initialized KeyLookupProps properties - */ -export interface KeyLookupProps { - /** - * SSM parameter name where key arn is stored - */ - readonly keyArnParameterName: string; - /** - * Key account id - */ - readonly accountId: string; - /** - * Optional key region - */ - readonly keyRegion?: string; - /** - * The name of the cross account role to use when accessing - */ - readonly roleName?: string; - /** - * Custom resource lambda log group encryption key - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays?: number; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * KMS Key ARN - */ - readonly kmsKeyArn?: string; -} - -/** - * Aws Key class - */ -export class KeyLookup extends Construct { - public readonly key: cdk.aws_kms.IKey; - - constructor(scope: Construct, id: string, props: KeyLookupProps) { - super(scope, id); - let keyArn: string | undefined; - if ( - cdk.Stack.of(this).account === props.accountId && - cdk.Stack.of(this).region === (props.keyRegion ?? cdk.Stack.of(this).region) - ) { - keyArn = cdk.aws_ssm.StringParameter.valueForStringParameter(this, props.keyArnParameterName); - } else { - keyArn = new SsmParameterLookup(this, 'Lookup', { - name: props.keyArnParameterName, - accountId: props.accountId, - parameterRegion: props.keyRegion ?? cdk.Stack.of(this).region, - roleName: props.roleName, - kmsKey: props.kmsKey, - logRetentionInDays: props.logRetentionInDays, - acceleratorPrefix: props.acceleratorPrefix, - resolvedValue: props.kmsKeyArn, - }).value; - } - - // Accelerator Key - this.key = cdk.aws_kms.Key.fromKeyArn(this, 'Resource', keyArn); - } - - public getKey(): cdk.aws_kms.IKey { - return this.key; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/index.ts deleted file mode 100644 index 9191860..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { PolicyStatementType } from '@aws-accelerator/utils/lib/common-resources'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -import * as path from 'path'; -AWS.config.logger = console; - -/** - * put-bucket-prefix - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string | undefined; - } - | undefined -> { - const kmsArn: string = event.ResourceProperties['kmsArn']; - const policyFilePaths: string[] = event.ResourceProperties['policyFilePaths']; - const organizationId: string | undefined = event.ResourceProperties['organizationId']; - - const solutionId = process.env['SOLUTION_ID']; - const kmsClient = new AWS.KMS({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const generatedPolicyString = generatePolicy(policyFilePaths); - - let replacedPolicyString = generatedPolicyString; - if (organizationId) { - replacedPolicyString = generatedPolicyString.replace(/\${ORG_ID}/g, organizationId); - } - - await throttlingBackOff(() => - kmsClient.putKeyPolicy({ KeyId: kmsArn, PolicyName: 'default', Policy: replacedPolicyString }).promise(), - ); - - return { - PhysicalResourceId: kmsArn, - Status: 'SUCCESS', - }; - - case 'Delete': - // Skip deleting policy - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -function generatePolicy(policyFilePaths: string[]): string { - const policyStatements: PolicyStatementType[] = []; - - for (const bucketPolicyFilePath of policyFilePaths) { - const policyFile = path.join(__dirname, bucketPolicyFilePath); - const policyContent: { Version?: string; Statement: PolicyStatementType[] } = JSON.parse( - JSON.stringify(require(policyFile)), - ); - - for (const statement of policyContent.Statement) { - policyStatements.push(statement); - } - } - - const policyDocument: { Version: string; Statement: PolicyStatementType[] } = { - Version: '2012-10-17', - Statement: policyStatements, - }; - - return JSON.stringify(policyDocument); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/package.json deleted file mode 100644 index bbfd62d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-kms-put-key-policy", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-kms/put-key-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/index.ts deleted file mode 100644 index 168832a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/index.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const partition = event.ResourceProperties['partition']; - const adminAccountId = event.ResourceProperties['adminAccountId']; - const solutionId = process.env['SOLUTION_ID']; - - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1', customUserAgent: solutionId }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1', customUserAgent: solutionId }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - - const macie2Client = new AWS.Macie2({ region: region, customUserAgent: solutionId }); - const allAccounts: AWS.Organizations.Account[] = []; - const existingMembers: AWS.Macie2.Member[] = []; - let isEnabled = false; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => organizationsClient.listAccounts({ NextToken: nextToken }).promise()); - for (const account of page.Accounts ?? []) { - allAccounts.push(account); - } - nextToken = page.NextToken; - } while (nextToken); - - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - macie2Client.listMembers({ nextToken, onlyAssociated: 'false' }).promise(), - ); - for (const member of page.members ?? []) { - existingMembers.push(member); - } - nextToken = page.nextToken; - } while (nextToken); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (!(await isMacieEnable(macie2Client))) { - console.log('start enable of macie'); - await throttlingBackOff(() => - macie2Client - .enableMacie({ - status: 'ENABLED', - }) - .promise(), - ); - } - - for (const account of allAccounts.filter(item => item.Id !== adminAccountId) ?? []) { - const existingMember = existingMembers.find(member => member.accountId !== account.Id); - if (existingMember && existingMember.relationshipStatus === 'Removed') { - console.log( - `OU account - ${account.Id} macie membership status is "Removed", deleting member before adding again`, - ); - await throttlingBackOff(() => macie2Client.deleteMember({ id: account.Id! }).promise()); - } - if (!existingMember || existingMember.relationshipStatus === 'Removed') { - console.log(`OU account - ${account.Id} macie membership status is "not a macie member", adding as a member`); - await throttlingBackOff(() => - macie2Client - .createMember({ - account: { accountId: account.Id!, email: account.Email! }, - }) - .promise(), - ); - } else { - console.warn( - `OU account - ${account.Id} macie membership status is "a macie member", ignoring create member for the account!!`, - ); - } - } - - isEnabled = await isOrganizationAutoEnabled(macie2Client); - - if (!isEnabled) { - await throttlingBackOff(() => macie2Client.updateOrganizationConfiguration({ autoEnable: true }).promise()); - } else { - console.warn('Delegation admin account Auto-Enable is ON, so ignoring'); - } - return { Status: 'Success', StatusCode: 200 }; - case 'Delete': - for (const account of allAccounts.filter(item => item.Id !== adminAccountId) ?? []) { - if (existingMembers.find(member => member.accountId !== account.Id)) { - console.log( - `OU account - ${account.Id} macie membership status is "a macie member", removing from member list`, - ); - await throttlingBackOff(() => macie2Client.disassociateMember({ id: account.Id! }).promise()); - await throttlingBackOff(() => macie2Client.deleteMember({ id: account.Id! }).promise()); - } else { - console.warn( - `OU account - ${account.Id} macie membership status is "not a macie member", ignoring removing member list!!`, - ); - } - } - - isEnabled = await isOrganizationAutoEnabled(macie2Client); - if (isEnabled) { - await throttlingBackOff(() => macie2Client.updateOrganizationConfiguration({ autoEnable: false }).promise()); - } else { - console.warn('Delegation admin account Auto-Enable is OFF, so ignoring'); - } - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Checking is organization auto enabled for new account - * @param macieClient - */ -async function isOrganizationAutoEnabled(macieClient: AWS.Macie2): Promise { - console.log('calling isOrganizationAutoEnabled'); - const response = await throttlingBackOff(() => macieClient.describeOrganizationConfiguration({}).promise()); - return response.autoEnable ?? false; -} - -/** - * Checking Macie is enable or disabled - * @param macie2Client - */ -async function isMacieEnable(macie2Client: AWS.Macie2): Promise { - try { - const response = await throttlingBackOff(() => macie2Client.getMacieSession({}).promise()); - return response.status === 'ENABLED'; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - - // This is required when macie is not enabled AccessDeniedException exception issues - if ( - // SDKv2 Error Structure - e.code === 'AccessDeniedException' || - // SDKv3 Error Structure - e.name === 'AccessDeniedException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - throw new Error(`Macie enable issue error message - ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/package.json deleted file mode 100644 index c1fb7e0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-macie-create-member", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/create-member/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/index.ts deleted file mode 100644 index 1d5e79e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/index.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * add-macie-members - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const findingPublishingFrequency = event.ResourceProperties['findingPublishingFrequency']; - const isSensitiveSh = event.ResourceProperties['isSensitiveSh'] === 'true'; - const solutionId = process.env['SOLUTION_ID']; - - const macie2Client = new AWS.Macie2({ region: region, customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - let macieStatus = await isMacieEnable(macie2Client); - if (!macieStatus) { - console.log('start enable of macie'); - await throttlingBackOff(() => - macie2Client - .enableMacie({ - findingPublishingFrequency: findingPublishingFrequency, - status: 'ENABLED', - }) - .promise(), - ); - } - console.log('start update of macie'); - await throttlingBackOff(() => - macie2Client - .updateMacieSession({ - findingPublishingFrequency: findingPublishingFrequency, - status: 'ENABLED', - }) - .promise(), - ); - - // macie status do not change immediately causing failure to other processes, so wait till macie enabled - while (!macieStatus) { - console.log(`checking macie status ${macieStatus}`); - macieStatus = await isMacieEnable(macie2Client); - } - - await throttlingBackOff(() => - macie2Client - .putFindingsPublicationConfiguration({ - securityHubConfiguration: { - publishClassificationFindings: isSensitiveSh, - publishPolicyFindings: true, - }, - }) - .promise(), - ); - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - if (await isMacieEnable(macie2Client)) { - await throttlingBackOff(() => - macie2Client - .disableMacie({ - findingPublishingFrequency: findingPublishingFrequency, - status: 'ENABLED', - }) - .promise(), - ); - } - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function isMacieEnable(macie2Client: AWS.Macie2): Promise { - try { - const response = await throttlingBackOff(() => macie2Client.getMacieSession({}).promise()); - return response.status === 'ENABLED'; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - - // This is required when macie is not enabled AccessDeniedException exception issues - if ( - // SDKv2 Error Structure - e.code === 'AccessDeniedException' || - // SDKv3 Error Structure - e.name === 'AccessDeniedException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - throw new Error(`Macie enable issue error message - ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/package.json deleted file mode 100644 index 1262831..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-macie-enable-macie", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-macie/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/index.ts deleted file mode 100644 index a02c4e6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { delay, throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * enableOrganizationAdminAccount - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const adminAccountId = event.ResourceProperties['adminAccountId']; - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - - const macie2Client = new AWS.Macie2({ region: region, customUserAgent: solutionId }); - - const macieDelegatedAccount = await getMacieDelegatedAccount(macie2Client, adminAccountId); - - switch (event.RequestType) { - case 'Create': - case 'Update': - // Enable macie in management account required to create delegated admin account - let macieStatus = await isMacieEnable(macie2Client); - if (!macieStatus) { - console.log('start enable of macie'); - await throttlingBackOff(() => - macie2Client - .enableMacie({ - status: 'ENABLED', - }) - .promise(), - ); - } - - // macie status do not change immediately causing failure to other processes, so wait till macie enabled - while (!macieStatus) { - console.log(`checking macie status ${macieStatus}`); - macieStatus = await isMacieEnable(macie2Client); - } - - if (macieDelegatedAccount.status) { - if (macieDelegatedAccount.accountId === adminAccountId) { - console.warn( - `Macie admin account ${macieDelegatedAccount.accountId} is already an admin account as status is ${macieDelegatedAccount.status}, in ${region} region. No action needed`, - ); - return { Status: 'Success', StatusCode: 200 }; - } else { - console.warn( - `Macie delegated admin is already set to ${macieDelegatedAccount.accountId} account can not assign another delegated account`, - ); - } - } else { - console.log( - `Started enableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - let retries = 0; - while (retries < 10) { - await delay(retries ** 2 * 1000); - try { - await throttlingBackOff(() => - macie2Client.enableOrganizationAdminAccount({ adminAccountId: adminAccountId }).promise(), - ); - break; - } catch (error) { - console.log(error); - retries = retries + 1; - } - } - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - if (macieDelegatedAccount.status) { - console.log( - `Started disableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - macie2Client.disableOrganizationAdminAccount({ adminAccountId: macieDelegatedAccount.accountId! }).promise(), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -async function getMacieDelegatedAccount( - macie2Client: AWS.Macie2, - adminAccountId: string, -): Promise<{ accountId: string | undefined; status: boolean }> { - const adminAccounts: AWS.Macie2.AdminAccount[] = []; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => macie2Client.listOrganizationAdminAccounts({ nextToken }).promise()); - for (const account of page.adminAccounts ?? []) { - adminAccounts.push(account); - } - nextToken = page.nextToken; - } while (nextToken); - - if (adminAccounts.length === 0) { - return { accountId: undefined, status: false }; - } - if (adminAccounts.length > 1) { - throw new Error('Multiple admin accounts for Macie in organization'); - } - - if (adminAccounts[0].accountId === adminAccountId && adminAccounts[0].status === 'DISABLING_IN_PROGRESS') { - throw new Error(`Admin account ${adminAccounts[0].accountId} is in ${adminAccounts[0].status}`); - } - - return { accountId: adminAccounts[0].accountId, status: true }; -} - -async function isMacieEnable(macie2Client: AWS.Macie2): Promise { - try { - const response = await throttlingBackOff(() => macie2Client.getMacieSession({}).promise()); - return response.status === 'ENABLED'; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - - // This is required when macie is not enabled AccessDeniedException exception issues - if ( - // SDKv2 Error Structure - e.code === 'AccessDeniedException' || - // SDKv3 Error Structure - e.name === 'AccessDeniedException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - throw new Error(`Macie enable issue error message - ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/package.json deleted file mode 100644 index b66d918..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-macie-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-export-config-classification.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-export-config-classification.ts deleted file mode 100644 index ac3dbda..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-export-config-classification.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized MacieExportConfigClassificationProps properties - */ -export interface MacieExportConfigClassificationProps { - /** - * Macie ExportConfigClassification repository bucket name - */ - readonly bucketName: string; - /** - * Macie ExportConfigClassification repository bucket encryption key - */ - readonly bucketKmsKey: cdk.aws_kms.IKey; - /** - * Bucket key prefix - */ - readonly keyPrefix: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly logKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Aws MacieSession export configuration classification - */ -export class MacieExportConfigClassification extends Construct { - public readonly id: string = ''; - - constructor(scope: Construct, id: string, props: MacieExportConfigClassificationProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::MaciePutClassificationExportConfiguration'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'put-export-config-classification/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'MaciePutClassificationExportConfigurationTaskMacieActions', - Effect: 'Allow', - Action: [ - 'macie2:EnableMacie', - 'macie2:GetClassificationExportConfiguration', - 'macie2:GetMacieSession', - 'macie2:PutClassificationExportConfiguration', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - bucketName: props.bucketName, - keyPrefix: props.keyPrefix, - kmsKeyArn: props.bucketKmsKey.keyArn, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.logKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-members.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-members.ts deleted file mode 100644 index 071e26f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-members.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized AwsMacieMembersProps properties - */ -export interface AwsMacieMembersProps { - /** - * Macie admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - /** - * Class to Aws MacieSession Members - */ -export class MacieMembers extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: AwsMacieMembersProps) { - super(scope, id); - - const MACIE_RESOURCE_TYPE = 'Custom::MacieCreateMember'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, MACIE_RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'create-member/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'MacieCreateMemberTaskOrganizationAction', - Effect: 'Allow', - Action: ['organizations:ListAccounts'], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:ListAccounts': ['macie.amazonaws.com'], - }, - }, - }, - { - Sid: 'MacieCreateMemberTaskMacieActions', - Effect: 'Allow', - Action: [ - 'macie2:CreateMember', - 'macie2:DeleteMember', - 'macie2:DescribeOrganizationConfiguration', - 'macie2:DisassociateMember', - 'macie2:EnableMacie', - 'macie2:GetMacieSession', - 'macie2:ListMembers', - 'macie2:UpdateOrganizationConfiguration', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: MACIE_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - partition: cdk.Stack.of(this).partition, - adminAccountId: props.adminAccountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-organization-admin-account.ts deleted file mode 100644 index f70a7da..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-organization-admin-account.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized MacieOrganizationAdminAccount properties - */ -export interface MacieOrganizationalAdminAccountProps { - /** - * Macie admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Aws MacieSession organizational Admin Account - */ -export class MacieOrganizationAdminAccount extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: MacieOrganizationalAdminAccountProps) { - super(scope, id); - - const MACIE_RESOURCE_TYPE = 'Custom::MacieEnableOrganizationAdminAccount'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, MACIE_RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'enable-organization-admin-account/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - timeout: cdk.Duration.seconds(180), - policyStatements: [ - { - Sid: 'MacieEnableOrganizationAdminAccountTaskOrganizationActions', - Effect: 'Allow', - Action: [ - 'organizations:DeregisterDelegatedAdministrator', - 'organizations:DescribeOrganization', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListAccounts', - 'organizations:ListDelegatedAdministrators', - 'organizations:RegisterDelegatedAdministrator', - 'organizations:ServicePrincipal', - 'organizations:UpdateOrganizationConfiguration', - ], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:DeregisterDelegatedAdministrator': ['macie.amazonaws.com'], - 'organizations:DescribeOrganization': ['macie.amazonaws.com'], - 'organizations:EnableAWSServiceAccess': ['macie.amazonaws.com'], - 'organizations:ListAWSServiceAccessForOrganization': ['macie.amazonaws.com'], - 'organizations:ListAccounts': ['macie.amazonaws.com'], - 'organizations:ListDelegatedAdministrators': ['macie.amazonaws.com'], - 'organizations:RegisterDelegatedAdministrator': ['macie.amazonaws.com'], - 'organizations:ServicePrincipal': ['macie.amazonaws.com'], - 'organizations:UpdateOrganizationConfiguration': ['macie.amazonaws.com'], - }, - }, - }, - { - Sid: 'MacieEnableOrganizationAdminAccountTaskMacieActions', - Effect: 'Allow', - Action: [ - 'macie2:DisableOrganizationAdminAccount', - 'macie2:EnableMacie', - 'macie2:EnableOrganizationAdminAccount', - 'macie2:GetMacieSession', - 'macie2:ListOrganizationAdminAccounts', - 'macie2:DisableOrganizationAdminAccount', - 'macie2:GetMacieSession', - 'macie2:EnableMacie', - ], - Resource: '*', - }, - { - Sid: 'MacieEnableMacieTaskIamAction', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'iam:CreateServiceLinkedRole': ['macie.amazonaws.com'], - }, - }, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: MACIE_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - adminAccountId: props.adminAccountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-session.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-session.ts deleted file mode 100644 index 9e71571..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/macie-session.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized MacieSessionProps properties - */ -export interface MacieSessionProps { - /** - * Findings publishing frequency - */ - readonly findingPublishingFrequency: string; - /** - * Publish sensitive data findings - */ - readonly isSensitiveSh: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Aws MacieSession class - */ -export class MacieSession extends Construct { - public readonly id: string = ''; - - constructor(scope: Construct, id: string, props: MacieSessionProps) { - super(scope, id); - - const MACIE_RESOURCE_TYPE = 'Custom::MacieEnableMacie'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, MACIE_RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'enable-macie/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'MacieEnableMacieTaskMacieActions', - Effect: 'Allow', - Action: [ - 'macie2:DisableMacie', - 'macie2:EnableMacie', - 'macie2:GetMacieSession', - 'macie2:PutFindingsPublicationConfiguration', - 'macie2:UpdateMacieSession', - ], - Resource: '*', - }, - { - Sid: 'MacieEnableMacieTaskIamAction', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'iam:CreateServiceLinkedRole': ['macie.amazonaws.com'], - }, - }, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: MACIE_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - findingPublishingFrequency: props.findingPublishingFrequency, - isSensitiveSh: props.isSensitiveSh, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/index.ts deleted file mode 100644 index b019d2d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * maciePutClassificationExportConfigurationFunction - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const bucketName = event.ResourceProperties['bucketName']; - const keyPrefix = event.ResourceProperties['keyPrefix']; - const kmsKeyArn = event.ResourceProperties['kmsKeyArn']; - const solutionId = process.env['SOLUTION_ID']; - - const macie2Client = new AWS.Macie2({ region: region, customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (!(await isMacieEnable(macie2Client))) { - console.log('start enable of macie'); - await throttlingBackOff(() => macie2Client.enableMacie({ status: 'ENABLED' }).promise()); - } - - await throttlingBackOff(() => - macie2Client - .putClassificationExportConfiguration({ - configuration: { - s3Destination: { - bucketName: bucketName, - keyPrefix: keyPrefix, - kmsKeyArn: kmsKeyArn, - }, - }, - }) - .promise(), - ); - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Function to check if macie is enabled - * @param macie2Client - */ -async function isMacieEnable(macie2Client: AWS.Macie2): Promise { - try { - const response = await throttlingBackOff(() => macie2Client.getMacieSession({}).promise()); - return response.status === 'ENABLED'; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - - // This is required when macie is not enabled AccessDeniedException exception issues - if ( - // SDKv2 Error Structure - e.code === 'AccessDeniedException' || - // SDKv3 Error Structure - e.name === 'AccessDeniedException' - ) { - console.warn(e.name + ': ' + e.message); - return false; - } - - throw new Error(`Macie enable issue error message - ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/package.json deleted file mode 100644 index 9481084..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-macie-put-export-config-classification", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-macie/put-export-config-classification/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/firewall.ts b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/firewall.ts deleted file mode 100644 index 6380f9f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/firewall.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { GetNetworkFirewallEndpoint } from './get-network-firewall-endpoint'; - -export interface INetworkFirewall extends cdk.IResource { - /** - * The unique IDs of the firewall endpoints for all of the subnets that you attached to the firewall. - */ - readonly endpointIds?: string[]; - /** - * The Amazon Resource Name (ARN) of the firewall. - */ - readonly firewallArn: string; - - /** - * The ID of the firewall. - */ - readonly firewallId?: string; - - /** - * The name of the policy. - */ - readonly firewallName: string; - - addLogging: (config: cdk.aws_networkfirewall.CfnLoggingConfiguration.LoggingConfigurationProperty) => void; - - addNetworkFirewallRoute: ( - id: string, - endpointAz: string, - logRetentionInDays: number, - routeTableId: string, - destination?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - ) => void; -} - -interface NetworkFirewallProps { - /** - * The Amazon Resource Name (ARN) of the firewall policy. - */ - readonly firewallPolicyArn: string; - - /** - * The descriptive name of the firewall. - */ - readonly name: string; - - /** - * The subnets that Network Firewall is using for the firewall. - */ - readonly subnets: string[]; - - /** - * The unique identifier of the VPC where the firewall is in use. - */ - readonly vpcId: string; - - /** - * A flag indicating whether it is possible to delete the firewall. - */ - readonly deleteProtection?: boolean; - - /** - * A description of the firewall. - */ - readonly description?: string; - - /** - * A setting indicating whether the firewall is protected against a change to the firewall policy association. - */ - readonly firewallPolicyChangeProtection?: boolean; - - /** - * A setting indicating whether the firewall is protected against changes to the subnet associations. - */ - readonly subnetChangeProtection?: boolean; - - /** - * An optional list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -abstract class NetworkFirewallBase extends cdk.Resource implements INetworkFirewall { - public abstract readonly firewallArn: string; - public abstract readonly firewallName: string; - public abstract readonly firewallId?: string; - public abstract readonly endpointIds?: string[]; - // protected abstract subnetMapping?: cdk.aws_networkfirewall.CfnFirewall.SubnetMappingProperty[]; - - public addLogging(config: cdk.aws_networkfirewall.CfnLoggingConfiguration.LoggingConfigurationProperty) { - new cdk.aws_networkfirewall.CfnLoggingConfiguration(this, 'LoggingConfig', { - firewallArn: this.firewallArn, - loggingConfiguration: config, - }); - } - - public addNetworkFirewallRoute( - id: string, - endpointAz: string, - logRetentionInDays: number, - routeTableId: string, - destination?: string, - ipv6Destination?: string, - logGroupKmsKey?: cdk.aws_kms.IKey, - ): void { - // Get endpoint ID from custom resource - const vpcEndpointId = new GetNetworkFirewallEndpoint(this, `${id}Endpoint`, { - endpointAz: endpointAz, - firewallArn: this.firewallArn, - kmsKey: logGroupKmsKey, - logRetentionInDays: logRetentionInDays, - region: cdk.Stack.of(this).region, - }).endpointId; - - new cdk.aws_ec2.CfnRoute(this, id, { - routeTableId, - destinationCidrBlock: destination, - destinationIpv6CidrBlock: ipv6Destination, - vpcEndpointId, - }); - } -} -export class NetworkFirewall extends NetworkFirewallBase { - public readonly firewallArn: string; - public readonly firewallName: string; - public readonly firewallId?: string; - public readonly endpointIds?: string[]; - protected subnetMapping: cdk.aws_networkfirewall.CfnFirewall.SubnetMappingProperty[]; - - static fromAttributes( - scope: Construct, - id: string, - attrs: { - firewallArn: string; - firewallName: string; - }, - ): INetworkFirewall { - class Import extends NetworkFirewallBase { - public readonly firewallArn: string; - public readonly firewallName: string; - public readonly firewallId?: string; - public readonly endpointIds?: string[]; - constructor(scope: Construct, id: string) { - super(scope, id); - this.firewallArn = attrs.firewallArn; - this.firewallName = attrs.firewallName; - } - } - return new Import(scope, id); - } - - /** - * Returns CfnFirewall by applying updates to included resource - * @param scope Stack in which included Firewall is created/managed - * @param id logicalId of Firewall - * @param attrs - */ - static includedCfnResource(scope: cdk.cloudformation_include.CfnInclude, id: string, props: NetworkFirewallProps) { - // Set initial properties - const subnetMapping = props.subnets.map(item => { - return { subnetId: item }; - }); - const resource = scope.getResource(id) as cdk.aws_networkfirewall.CfnFirewall; - resource.deleteProtection = props.deleteProtection; - resource.subnetChangeProtection = props.subnetChangeProtection; - resource.subnetMappings = subnetMapping; - resource.firewallPolicyArn = props.firewallPolicyArn; - resource.firewallPolicyChangeProtection = props.firewallPolicyChangeProtection; - resource.description = props.description; - return resource; - } - - constructor(scope: Construct, id: string, props: NetworkFirewallProps) { - super(scope, id); - - // Set initial properties - this.firewallName = props.name; - this.subnetMapping = props.subnets.map(item => { - return { subnetId: item }; - }); - - // Set name tag - props.tags?.push({ key: 'Name', value: this.firewallName }); - - const resource = new cdk.aws_networkfirewall.CfnFirewall(this, 'Resource', { - firewallName: this.firewallName, - firewallPolicyArn: props.firewallPolicyArn, - subnetMappings: this.subnetMapping, - vpcId: props.vpcId, - deleteProtection: props.deleteProtection, - description: props.description, - firewallPolicyChangeProtection: props.firewallPolicyChangeProtection, - subnetChangeProtection: props.subnetChangeProtection, - tags: props.tags, - }); - - // Set remaining properties - this.endpointIds = resource.attrEndpointIds; - this.firewallArn = resource.ref; - this.firewallId = resource.attrFirewallId; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint.ts b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint.ts deleted file mode 100644 index cd001c8..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -interface IGetNetworkFirewallEndpoint extends cdk.IResource { - /** - * The ID of the endpoint - */ - readonly endpointId: string; -} - -interface GetNetworkFirewallEndpointProps { - /** - * The AZ the endpoint is located in - */ - readonly endpointAz: string; - - /** - * The ARN of the associated Network Firewall - */ - readonly firewallArn: string; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - - /** - * The region of the Network Firewall - */ - readonly region: string; -} - -export class GetNetworkFirewallEndpoint extends cdk.Resource implements IGetNetworkFirewallEndpoint { - public readonly endpointId: string; - - constructor(scope: Construct, id: string, props: GetNetworkFirewallEndpointProps) { - super(scope, id); - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::GetNetworkFirewallEndpoint', { - codeDirectory: path.join(__dirname, 'get-network-firewall-endpoint/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ec2:DescribeAvailabilityZones', 'network-firewall:DescribeFirewall'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::GetNetworkFirewallEndpoint', - serviceToken: provider.serviceToken, - properties: { - endpointAz: props.endpointAz, - firewallArn: props.firewallArn, - region: props.region, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.endpointId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/index.ts deleted file mode 100644 index d4d5e33..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/index.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DescribeAvailabilityZonesCommand, EC2Client } from '@aws-sdk/client-ec2'; -import { DescribeFirewallCommand, NetworkFirewallClient } from '@aws-sdk/client-network-firewall'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -/** - * get-network-firewall-endpoint - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - // Set variables passed in event - const endpointAz: string = event.ResourceProperties['endpointAz']; - const firewallArn: string = event.ResourceProperties['firewallArn']; - const region: string = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - - const ec2Client = new EC2Client({ customUserAgent: solutionId }); - const nfwClient = new NetworkFirewallClient({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const logicalZoneName = await getAvailabilityZone(ec2Client, endpointAz, region); - let endpointId: string | undefined = undefined; - - try { - const response = await throttlingBackOff(() => - nfwClient.send(new DescribeFirewallCommand({ FirewallArn: firewallArn })), - ); - // - // Check for endpoint in specified AZ - if (response.FirewallStatus?.SyncStates) { - endpointId = response.FirewallStatus.SyncStates[logicalZoneName].Attachment?.EndpointId; - } - // - // Validate endpoint ID - if (!endpointId) { - throw new Error(`Unable to locate Network Firewall endpoint in AZ ${endpointAz}`); - } - - return { - PhysicalResourceId: endpointId, - Status: 'SUCCESS', - }; - } catch (e) { - throw new Error(`Error retrieving Network Firewall endpoint: ${e}`); - } - - case 'Delete': - // Do nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Returns the logical zone name for the specified AZ - * @param ec2Client - * @param endpointAz - * @param region - * @returns - */ -async function getAvailabilityZone(ec2Client: EC2Client, endpointAz: string, region: string): Promise { - if (endpointAz.includes(region)) { - return endpointAz; - } - - try { - const response = await throttlingBackOff(() => - ec2Client.send( - new DescribeAvailabilityZonesCommand({ - ZoneIds: [endpointAz], - }), - ), - ); - // - // Validate response - if (!response.AvailabilityZones) { - throw new Error(`Unable to retrieve details for AZ ${endpointAz}`); - } - if (!response.AvailabilityZones[0].ZoneName) { - throw new Error(`Unable to retrieve logical zone name for AZ ${endpointAz}`); - } - return response.AvailabilityZones[0].ZoneName; - } catch (e) { - throw new Error(`Error retrieving logical zone name: ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/package.json deleted file mode 100644 index ba18c5b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-networkfirewall-get-network-firewall-endpoint", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-network-firewall": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/get-network-firewall-endpoint/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/policy.ts b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/policy.ts deleted file mode 100644 index 7c6f089..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/policy.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { NfwRuleSourceCustomActionConfig } from '@aws-accelerator/config'; -import { transformPolicy } from './utils'; - -interface INetworkFirewallPolicy extends cdk.IResource { - /** - * The Amazon Resource Name (ARN) of the policy. - */ - readonly policyArn: string; - - /** - * The ID of the policy. - */ - readonly policyId: string; - - /** - * The name of the policy. - */ - readonly policyName: string; -} - -interface StatefulRuleGroupReference { - resourceArn: string; - priority?: number; -} - -interface StatelessRuleGroupReference { - priority: number; - resourceArn: string; -} - -export interface FirewallPolicyProperty { - statelessDefaultActions: string[]; - statelessFragmentDefaultActions: string[]; - statefulDefaultActions?: string[]; - statefulEngineOptions?: string; - statefulRuleGroupReferences?: StatefulRuleGroupReference[]; - statelessCustomActions?: NfwRuleSourceCustomActionConfig[]; - statelessRuleGroupReferences?: StatelessRuleGroupReference[]; -} - -interface NetworkFirewallPolicyProps { - /** - * The traffic filtering behavior of a firewall policy, defined in a collection of stateless and stateful rule groups and other settings. - */ - readonly firewallPolicy: FirewallPolicyProperty; - - /** - * The descriptive name of the firewall policy. - */ - readonly name: string; - - /** - * A description of the firewall policy. - */ - readonly description?: string; - - /** - * An optional list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -abstract class NetworkFirewallPolicyBase extends cdk.Resource implements INetworkFirewallPolicy { - public abstract readonly policyArn: string; - public abstract readonly policyId: string; - public abstract readonly policyName: string; -} - -export class NetworkFirewallPolicy extends NetworkFirewallPolicyBase { - public readonly policyArn: string; - public readonly policyId: string; - public readonly policyName: string; - - /** - * Returns CfnFirewallPolicy by applying updates to included resource - * @param scope Stack in which included FirewallPolicy is created/managed - * @param id logicalId of FirewallPolicy - * @param attrs - */ - static includedCfnResource( - scope: cdk.cloudformation_include.CfnInclude, - id: string, - props: NetworkFirewallPolicyProps, - ) { - const resource = scope.getResource(id) as cdk.aws_networkfirewall.CfnFirewallPolicy; - // Transform properties as necessary - resource.firewallPolicy = transformPolicy(props.firewallPolicy); - resource.description = props.description; - return resource; - } - - static fromAttributes( - scope: Construct, - id: string, - attrs: { policyArn: string; policyName: string }, - ): INetworkFirewallPolicy { - class Import extends NetworkFirewallPolicyBase { - public readonly policyArn = attrs.policyArn; - public readonly policyName = attrs.policyName; - // policyId is not used anywhere. Need to store in SSM if needed - public readonly policyId = ''; - - constructor(scope: Construct, id: string) { - super(scope, id); - } - } - return new Import(scope, id); - } - - constructor(scope: Construct, id: string, props: NetworkFirewallPolicyProps) { - super(scope, id); - - // Set initial properties - this.policyName = props.name; - - // Set firewall policy property - const firewallPolicy = transformPolicy(props.firewallPolicy); - - // Set name tag - props.tags?.push({ key: 'Name', value: this.policyName }); - - const resource = new cdk.aws_networkfirewall.CfnFirewallPolicy(this, 'Resource', { - firewallPolicy: firewallPolicy, - firewallPolicyName: this.policyName, - description: props.description, - tags: props.tags, - }); - - this.policyArn = resource.ref; - this.policyId = resource.attrFirewallPolicyId; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/rule-group.ts b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/rule-group.ts deleted file mode 100644 index bb9e3af..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/rule-group.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { NfwRuleGroupRuleConfig } from '@aws-accelerator/config'; -import { transformRuleGroup } from './utils'; - -interface INetworkFirewallRuleGroup extends cdk.IResource { - /** - * The Amazon Resource Name (ARN) of the rule group. - */ - readonly groupArn: string; - - /** - * The ID of the rule group. - */ - readonly groupId: string; - - /** - * The name of the rule group. - */ - readonly groupName: string; -} - -interface NetworkFirewallRuleGroupProps { - /** - * The maximum operating resources that this rule group can use. - */ - readonly capacity: number; - - /** - * The name of the rule group. - */ - readonly name: string; - - /** - * Indicates whether the rule group is stateless or stateful. - */ - readonly type: string; - - /** - * A description of the rule group. - */ - readonly description?: string; - - /** - * An object that defines the rule group rules. - */ - readonly ruleGroup?: NfwRuleGroupRuleConfig; - - /** - * An optional list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class NetworkFirewallRuleGroup extends cdk.Resource implements INetworkFirewallRuleGroup { - public readonly groupArn: string; - public readonly groupId: string; - public readonly groupName: string; - - /** - * Returns CfnRuleGroup by applying updates to included resource - * @param scope Stack in which included RuleGroup is created/managed - * @param id logicalId of RuleGroup - * @param attrs - */ - static includedCfnResource( - scope: cdk.cloudformation_include.CfnInclude, - id: string, - props: NetworkFirewallRuleGroupProps, - ) { - const resource = scope.getResource(id) as cdk.aws_networkfirewall.CfnRuleGroup; - // Transform properties as necessary - if (props.ruleGroup) { - // Set rule group property - resource.ruleGroup = transformRuleGroup(props.ruleGroup); - } else { - // Remove existing rule group property - resource.ruleGroup = undefined; - } - // Updating capacity requires replacement - resource.capacity = props.capacity; - // Updating type requires replacement - resource.type = props.type; - resource.description = props.description; - return resource; - } - - static fromAttributes( - scope: Construct, - id: string, - attrs: { groupArn: string; groupName: string }, - ): INetworkFirewallRuleGroup { - class Import extends cdk.Resource implements INetworkFirewallRuleGroup { - public readonly groupArn = attrs.groupArn; - public readonly groupId = attrs.groupName; - // groupId is not used anywhere. Need to store in SSM if needed - public readonly groupName = ''; - - constructor(scope: Construct, id: string) { - super(scope, id); - } - } - return new Import(scope, id); - } - - constructor(scope: Construct, id: string, props: NetworkFirewallRuleGroupProps) { - super(scope, id); - - // Set initial properties - this.groupName = props.name; - - let ruleGroup: cdk.aws_networkfirewall.CfnRuleGroup.RuleGroupProperty | undefined; - // Transform properties as necessary - if (props.ruleGroup) { - // Set rule group property - ruleGroup = transformRuleGroup(props.ruleGroup); - } - - // Set name tag - props.tags?.push({ key: 'Name', value: this.groupName }); - - const resource = new cdk.aws_networkfirewall.CfnRuleGroup(this, 'Resource', { - capacity: props.capacity, - ruleGroupName: this.groupName, - type: props.type, - description: props.description, - ruleGroup, - tags: props.tags, - }); - - this.groupArn = resource.ref; - this.groupId = resource.attrRuleGroupId; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/utils.ts b/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/utils.ts deleted file mode 100644 index 0eb6cb0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-networkfirewall/utils.ts +++ /dev/null @@ -1,201 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import { NfwRuleGroupRuleConfig, NfwRuleVariableDefinitionConfig } from '@aws-accelerator/config'; -import { FirewallPolicyProperty } from './policy'; - -/** - * Transform stateless and custom rule group policies to conform with L1 construct. - * - * @param props - */ -function transformStatelessCustom(props: NfwRuleGroupRuleConfig) { - const property = props.rulesSource.statelessRulesAndCustomActions; - const statelessRules = []; - const customActions = []; - - if (property) { - // Push stateless rules - for (const rule of property.statelessRules ?? []) { - statelessRules.push({ - priority: rule.priority, - ruleDefinition: { - actions: rule.ruleDefinition.actions, - matchAttributes: { - destinationPorts: rule.ruleDefinition.matchAttributes?.destinationPorts ?? [], - destinations: - rule.ruleDefinition.matchAttributes?.destinations?.map(item => { - return { addressDefinition: item }; - }) ?? [], - protocols: rule.ruleDefinition.matchAttributes?.protocols ?? [], - sourcePorts: rule.ruleDefinition.matchAttributes?.sourcePorts ?? [], - sources: - rule.ruleDefinition.matchAttributes?.sources?.map(item => { - return { addressDefinition: item }; - }) ?? [], - tcpFlags: rule.ruleDefinition.matchAttributes?.tcpFlags, - }, - }, - }); - } - - // Push custom actions - for (const action of property.customActions ?? []) { - customActions.push({ - actionDefinition: { - publishMetricAction: { - dimensions: action.actionDefinition.publishMetricAction.dimensions.map(item => { - return { value: item }; - }), - }, - }, - actionName: action.actionName, - }); - } - - return { - statelessRules, - customActions: customActions.length > 0 ? customActions : undefined, - }; - } - return undefined; -} - -/** - * Transform rule variables to conform with L1 construct. - * - * @param props - */ -function transformRuleVariables(props: NfwRuleGroupRuleConfig) { - const property = props.ruleVariables; - const ipSets: { [key: string]: { definition: string[] } } = {}; - const portSets: { [key: string]: { definition: string[] } } = {}; - - if (property) { - const ipSetDefinitions = getVariableDefinitions(property.ipSets); - const portSetDefinitions = getVariableDefinitions(property.portSets); - - ipSetDefinitions.forEach(ipSet => { - ipSets[ipSet.name] = { definition: ipSet.definition }; - }); - - portSetDefinitions.forEach(portSet => { - portSets[portSet.name] = { definition: portSet.definition }; - }); - - return { - ipSets, - portSets, - }; - } - return undefined; -} - -/** - * Takes in variable definitions as a map or array and transforms them into an array - * @param definition - * @returns - */ -function getVariableDefinitions( - definition: NfwRuleVariableDefinitionConfig | NfwRuleVariableDefinitionConfig[], -): NfwRuleVariableDefinitionConfig[] { - const variableDefinitions: NfwRuleVariableDefinitionConfig[] = []; - - if (Array.isArray(definition)) { - variableDefinitions.push(...definition); - } else { - variableDefinitions.push(definition); - } - - return variableDefinitions; -} - -/** - * Transform rule options to conform with L1 construct. - * - * @param props - */ -function transformRuleOptions(props: NfwRuleGroupRuleConfig) { - const property = props.statefulRuleOptions; - - if (property) { - return { ruleOrder: property }; - } - return undefined; -} - -/** - * Transform rule group to conform with L1 construct. - * @param ruleGroup - * @returns - */ -export function transformRuleGroup(ruleGroup: NfwRuleGroupRuleConfig) { - return { - rulesSource: { - rulesSourceList: ruleGroup.rulesSource.rulesSourceList, - rulesString: ruleGroup.rulesSource.rulesString, - statefulRules: ruleGroup.rulesSource.statefulRules, - statelessRulesAndCustomActions: transformStatelessCustom(ruleGroup), - }, - ruleVariables: transformRuleVariables(ruleGroup), - statefulRuleOptions: transformRuleOptions(ruleGroup), - }; -} - -/** - * Transform custom actions to conform with L1 construct. - * - * @param props - */ -function transformPolicyCustomActions(props: FirewallPolicyProperty) { - const property = props.statelessCustomActions; - const customActions = []; - - for (const action of property ?? []) { - customActions.push({ - actionDefinition: { - publishMetricAction: { - dimensions: action.actionDefinition.publishMetricAction.dimensions.map(item => { - return { value: item }; - }), - }, - }, - actionName: action.actionName, - }); - } - return customActions; -} - -/** - * Transform engine options to conform with L1 construct. - * - * @param props - */ -function transformPolicyEngineOptions(props: FirewallPolicyProperty) { - const property = props.statefulEngineOptions; - if (property) { - return { ruleOrder: property }; - } - return; -} - -export function transformPolicy( - firewallPolicy: FirewallPolicyProperty, -): cdk.aws_networkfirewall.CfnFirewallPolicy.FirewallPolicyProperty { - let customActions: cdk.aws_networkfirewall.CfnFirewallPolicy.CustomActionProperty[] | undefined; - let statefulOptions: cdk.aws_networkfirewall.CfnFirewallPolicy.StatefulEngineOptionsProperty | undefined; - // Transform properties as necessary - if (firewallPolicy.statelessCustomActions) { - customActions = transformPolicyCustomActions(firewallPolicy); - } - if (firewallPolicy.statefulEngineOptions) { - statefulOptions = transformPolicyEngineOptions(firewallPolicy); - } - return { - statelessDefaultActions: firewallPolicy.statelessDefaultActions, - statelessFragmentDefaultActions: firewallPolicy.statelessFragmentDefaultActions, - statefulDefaultActions: firewallPolicy.statefulDefaultActions, - statefulEngineOptions: statefulOptions, - statefulRuleGroupReferences: firewallPolicy.statefulRuleGroupReferences, - statelessCustomActions: customActions, - statelessRuleGroupReferences: firewallPolicy.statelessRuleGroupReferences, - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/account.ts deleted file mode 100644 index 072ec19..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/account.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ITable } from 'aws-cdk-lib/aws-dynamodb'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = require('path'); - -/** - * Account properties - */ -export interface AccountProps { - readonly acceleratorConfigTable: ITable; - readonly commitId: string; - readonly assumeRoleName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to initialize an Organizations Account - */ -export class Account extends cdk.Resource { - constructor(scope: Construct, id: string, props: AccountProps) { - super(scope, id); - - const ENROLL_ACCOUNT_TYPE = 'Custom::InviteAccountsToOrganization'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, ENROLL_ACCOUNT_TYPE, { - codeDirectory: path.join(__dirname, 'invite-account-to-organization/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'organizations:AcceptHandshake', - 'organizations:ListAccounts', - 'organizations:InviteAccountToOrganization', - 'organizations:MoveAccount', - 'organizations:ListRoots', - ], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['dynamodb:Query'], - Resource: [props.acceleratorConfigTable.tableArn], - }, - { - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: [ - cdk.Stack.of(this).formatArn({ - service: 'iam', - region: '', - account: '*', - resource: 'role', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: props.assumeRoleName, - }), - ], - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: ENROLL_ACCOUNT_TYPE, - serviceToken: provider.serviceToken, - properties: { - configTableName: props.acceleratorConfigTable.tableName, - partition: cdk.Aws.PARTITION, - commitId: props.commitId, - assumeRoleName: props.assumeRoleName, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/index.ts deleted file mode 100644 index 9680dde..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/index.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy, getGlobalRegion } from '@aws-accelerator/utils/lib/common-functions'; -import { - OrganizationsClient, - paginateListTagsForResource, - PolicyNotAttachedException, - PolicyNotFoundException, - DetachPolicyCommand, - paginateListPoliciesForTarget, - paginateListPolicies, - AttachPolicyCommand, - DuplicatePolicyAttachmentException, -} from '@aws-sdk/client-organizations'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * attach-policy - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const policyId: string = event.ResourceProperties['policyId']; - const targetId: string = event.ResourceProperties['targetId'] ?? undefined; - const type: string = event.ResourceProperties['type']; - const strategy: string = event.ResourceProperties['strategy']; - const partition: string = event.ResourceProperties['partition']; - const configPolicyNames: string[] = event.ResourceProperties['configPolicyNames']; - const policyTagKey: string = event.ResourceProperties['policyTagKey']; - const globalRegion = getGlobalRegion(partition); - - const solutionId = process.env['SOLUTION_ID']; - const organizationsClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - // Any configured AWS Organization Service Control Policies (SCPs) are also created and attached to configuration-specified deployment targets in accounts stage in global region - - // - // First detach all non config policies from target - // - await detachNonConfigPolicies(organizationsClient, targetId, configPolicyNames, policyTagKey); - - // - // Check if already exists, update and return the ID - // - const attachedPolicies = await getListPoliciesForTarget(organizationsClient, type, targetId); - - // check if policyId exists in attachedPolicies id - const policyAttached = attachedPolicies?.some(p => p.id === policyId); - const fullAwsAccessPolicyAttached = attachedPolicies?.some(p => p.id === 'p-FullAWSAccess'); - - // Attach if not attached already. - if (!policyAttached) { - await attachSpecificPolicy(organizationsClient, policyId, targetId); - } - - // if SCP strategy is allow-list, then FullAWSAccess policy should be detached - if (strategy === 'allow-list' && fullAwsAccessPolicyAttached) { - console.log('detaching FullAWSAccess policy because the strategy is allow-list'); - await detachSpecificPolicy(organizationsClient, 'p-FullAWSAccess', targetId); - } - - // if SCP strategy is changed from allow-list to deny list, then FullAWSAccess policy should be attached - if (strategy === 'deny-list' && !fullAwsAccessPolicyAttached) { - console.log('attaching FullAWSAccess policy because the strategy is deny-list'); - await attachSpecificPolicy(organizationsClient, 'p-FullAWSAccess', targetId); - } - - return { - PhysicalResourceId: `${policyId}_${targetId}`, - Status: 'SUCCESS', - }; - - case 'Delete': - // - // Detach policy, let CDK manage where it's deployed, - // - // do not remove FullAWSAccess and do nothing for NoOperation - if ( - !['p-FullAWSAccess', 'NoOperation'].includes(policyId) && - // check the org to see if the policy is present - // policy id can change due to out of band change - // if no policy is found, no action should be taken - (await isPolicyInOrg(policyId, type, organizationsClient)) - ) { - const attachedPolicies = await getListPoliciesForTarget(organizationsClient, type, targetId); - await detachPolicyFromSpecificTarget(attachedPolicies, targetId, organizationsClient, policyTagKey); - } - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -async function isPolicyInOrg(policyId: string, type: string, organizationsClient: OrganizationsClient) { - for await (const page of paginateListPolicies({ client: organizationsClient }, { Filter: type })) { - for (const policy of page.Policies ?? []) { - if (policy.Id === policyId) { - return true; - } - } - } - // went through all policies and did not find that policy ID, return false - return false; -} - -async function detachSpecificPolicy(organizationsClient: OrganizationsClient, policyId: string, targetId: string) { - try { - await throttlingBackOff(() => - organizationsClient.send(new DetachPolicyCommand({ PolicyId: policyId, TargetId: targetId })), - ); - } catch (error: unknown) { - // Swallow the error if it's PolicyNotAttachedException - // The policy might already be detached by other attach-policy custom resource concurrently. - if (error instanceof PolicyNotAttachedException) { - console.log(`Policy: ${policyId} was not attached. Continuing...`); - } else if (error instanceof PolicyNotFoundException) { - // if policy was recreated outside of accelerator - // incoming policy will have a unique ID which is not the org and throw this exception - // ignore it and proceed with next step - console.log('Policy: ${policyId} was not found. Continuing...'); - } else { - throw new Error(`Error while trying to detach policy: ${policyId}. Error message: ${JSON.stringify(error)}`); - } - } -} -async function attachSpecificPolicy(organizationsClient: OrganizationsClient, policyId: string, targetId: string) { - try { - await throttlingBackOff(() => - organizationsClient.send(new AttachPolicyCommand({ PolicyId: policyId, TargetId: targetId })), - ); - } catch (error: unknown) { - if (error instanceof DuplicatePolicyAttachmentException) { - console.log('Policy already attached. Continuing...'); - } else { - throw new Error(`Error while trying to attach policy: ${policyId}. Error message: ${JSON.stringify(error)}`); - } - } -} - -async function getListPoliciesForTarget(organizationsClient: OrganizationsClient, type: string, targetId: string) { - const attachedPolicies: { name: string; id: string }[] = []; - for await (const page of paginateListPoliciesForTarget( - { client: organizationsClient }, - { Filter: type, TargetId: targetId }, - )) { - attachedPolicies.push(...(page.Policies! ?? []).map(p => ({ name: p.Name!, id: p.Id! }))); - } - return attachedPolicies; -} - -async function detachNonConfigPolicies( - organizationsClient: OrganizationsClient, - targetId: string, - configPolicyNames: string[], - policyTagKey: string, -): Promise { - console.log(`Detaching non config policies from target ${targetId}`); - console.log(`Config policies are ${configPolicyNames.join(',')}`); - const attachedPolicies = await getListPoliciesForTarget(organizationsClient, 'SERVICE_CONTROL_POLICY', targetId); - - const attachedPolicyNames: string[] = []; - for (const attachedPolicy of attachedPolicies) { - attachedPolicyNames.push(attachedPolicy.name); - } - console.log(`Existing attached polices are [${attachedPolicyNames.join(',')}]`); - - const removePolicies = attachedPolicies.filter(item => configPolicyNames.indexOf(item.name) === -1); - - await detachPolicyFromSpecificTarget(removePolicies, targetId, organizationsClient, policyTagKey); -} - -async function detachPolicyFromSpecificTarget( - removeLzaPolicies: { name: string; id: string }[], - targetId: string, - organizationsClient: OrganizationsClient, - policyTagKey: string, -) { - for (const removeLzaPolicy of removeLzaPolicies) { - // only remove policies that are managed by LZA - if (await isLzaManagedPolicy(organizationsClient, removeLzaPolicy.id, policyTagKey)) { - console.log(`Detaching ${removeLzaPolicy.name} policy from ${targetId} target`); - await detachSpecificPolicy(organizationsClient, removeLzaPolicy.id, targetId); - } - } -} - -/** - * Function to check if policy is managed by LZA, this is by checking lzaManaged tag with Yes value - * @param policyId - * @returns - */ -async function isLzaManagedPolicy( - organizationsClient: OrganizationsClient, - policyId: string, - policyTagKey: string, -): Promise { - // keep full access and operations that were imported by createPolicy called NoOperation - if (policyId === 'p-FullAWSAccess' || policyId === 'NoOperation') { - return false; - } - for await (const page of paginateListTagsForResource({ client: organizationsClient }, { ResourceId: policyId })) { - for (const tag of page.Tags ?? []) { - if (tag.Key === policyTagKey && tag.Value === 'Yes') { - return true; - } - } - } - return false; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/jest.config.js deleted file mode 100644 index dba60d3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 90, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/package.json deleted file mode 100644 index 8900249..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-attach-policy", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --silent", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-organizations": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/index.test.ts deleted file mode 100644 index b6f605f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/index.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { - OrganizationsClient, - ListTagsForResourceCommand, - PolicyNotAttachedException, - DetachPolicyCommand, - ListPoliciesForTargetCommand, - AttachPolicyCommand, - MalformedPolicyDocumentException, - DuplicatePolicyAttachmentException, - PolicyNotFoundException, - ListPoliciesCommand, -} from '@aws-sdk/client-organizations'; - -import { describe, beforeEach, afterEach, expect, test, jest } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const orgClient = AcceleratorMockClient(OrganizationsClient); -process.env['SOLUTION_ID'] = 'testLza'; - -describe('Create Event', () => { - const OLD_ENV = process.env; // cache old env - beforeEach(() => { - orgClient.reset(); - jest.resetModules(); // Most important - it clears the cache - process.env = { ...OLD_ENV }; // Make a copy - }); - // process env can change between tests so making this afterEach not afterAll - afterEach(() => { - process.env = OLD_ENV; // Restore old environment - }); - test('Attach a policy - no policies', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({}); - orgClient.on(AttachPolicyCommand).resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Attach a policy - one policy', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient - .on(ListTagsForResourceCommand) - .resolves({ Tags: [{ Key: StaticInput.attachProps.policyTagKey, Value: 'Yes' }] }); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Attach a policy - one policy', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({}); - orgClient.on(AttachPolicyCommand).resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Attach a policy - one policy already exists', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({}); - orgClient - .on(AttachPolicyCommand) - .rejects(new DuplicatePolicyAttachmentException({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Attach a policy - malformed policy', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({}); - orgClient - .on(AttachPolicyCommand) - .rejects(new MalformedPolicyDocumentException({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - await expect(handler(event)).rejects.toThrowError( - `Error while trying to attach policy: ${StaticInput.attachProps.policyId}. Error message: ${StaticInput.malFormedPolicyException}`, - ); - }); - test('Attach a policy - one policy already detached', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient - .on(ListTagsForResourceCommand) - .resolves({ Tags: [{ Key: StaticInput.attachProps.policyTagKey, Value: 'Yes' }] }); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .rejectsOnce( - new PolicyNotAttachedException({ $metadata: { httpStatusCode: 400 }, message: 'Policy not attached' }), - ); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Attach a policy - no tags found', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient.on(ListTagsForResourceCommand).resolves({}); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Attach a policy - detached error', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient - .on(ListTagsForResourceCommand) - .resolves({ Tags: [{ Key: StaticInput.attachProps.policyTagKey, Value: 'Yes' }] }); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .rejectsOnce({}); - await expect(handler(event)).rejects.toThrowError( - `Error while trying to detach policy: configPolicyId2. Error message: {}`, - ); - }); -}); - -describe('Update Event', () => { - const OLD_ENV = process.env; // cache old env - beforeEach(() => { - orgClient.reset(); - jest.resetModules(); // Most important - it clears the cache - process.env = { ...OLD_ENV }; // Make a copy - }); - // process env can change between tests so making this afterEach not afterAll - afterEach(() => { - process.env = OLD_ENV; // Restore old environment - }); - test('Deny-list strategy - attach full aws access', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.denylistProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({}); - orgClient.on(AttachPolicyCommand).resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('allow-list strategy - attach full aws access', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.allowlistProps] }); - orgClient - .on(ListPoliciesForTargetCommand) - .resolves({ Policies: [{ Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }] }); - orgClient.on(DetachPolicyCommand).resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); -}); - -describe('Delete Event', () => { - const OLD_ENV = process.env; // cache old env - beforeEach(() => { - orgClient.reset(); - jest.resetModules(); // Most important - it clears the cache - process.env = { ...OLD_ENV }; // Make a copy - }); - // process env can change between tests so making this afterEach not afterAll - afterEach(() => { - process.env = OLD_ENV; // Restore old environment - }); - test('Delete policy for target', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient.on(ListPoliciesCommand).resolves({ - Policies: [{ Name: 'configPolicyId2', Id: StaticInput.attachProps.policyId }], - }); - orgClient - .on(ListTagsForResourceCommand) - .resolves({ Tags: [{ Key: StaticInput.attachProps.policyTagKey, Value: 'Yes' }] }); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .rejectsOnce( - new PolicyNotAttachedException({ $metadata: { httpStatusCode: 400 }, message: 'Policy not attached' }), - ); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Delete policy for target - policy not in org', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient.on(ListPoliciesCommand).resolves({ - Policies: [], - }); - orgClient - .on(ListTagsForResourceCommand) - .resolves({ Tags: [{ Key: StaticInput.attachProps.policyTagKey, Value: 'Yes' }] }); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .rejectsOnce(new PolicyNotFoundException({ $metadata: { httpStatusCode: 400 }, message: 'Policy not found' })); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Delete policy for target - policy not found on detach', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.attachProps] }); - orgClient.on(ListPoliciesForTargetCommand).resolves({ - Policies: [ - { Name: 'configPolicy1', Id: StaticInput.attachProps.policyId }, - { Name: 'configPolicy2', Id: 'configPolicyId2' }, - { Name: 'FullAWSAccess', Id: 'p-FullAWSAccess' }, - { Name: 'configPolicy3', Id: 'NoOperation' }, - ], - }); - orgClient.on(ListPoliciesCommand).resolves({ - Policies: [{ Name: 'configPolicyId2', Id: StaticInput.attachProps.policyId }], - }); - orgClient - .on(ListTagsForResourceCommand) - .resolves({ Tags: [{ Key: StaticInput.attachProps.policyTagKey, Value: 'Yes' }] }); - orgClient - .on(DetachPolicyCommand, { PolicyId: 'configPolicyId2', TargetId: StaticInput.attachProps.targetId }) - .rejectsOnce(new PolicyNotFoundException({ $metadata: { httpStatusCode: 400 }, message: 'Policy not found' })); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/static-input.ts deleted file mode 100644 index a3bb789..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/test/static-input.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly newProps = { - policyId: 'policyId', - targetId: 'targetId', - type: 'type', - strategy: 'strategy', - partition: 'aws', - configPolicyNames: ['configPolicy1', 'configPolicy2'], - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'homeRegion', - }; - public static readonly attachProps = { - policyId: 'policyId', - targetId: 'targetId', - type: 'type', - strategy: 'strategy', - partition: 'aws', - configPolicyNames: ['configPolicy1'], - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'homeRegion', - }; - public static readonly denylistProps = { - policyId: 'policyId', - targetId: 'targetId', - type: 'type', - strategy: 'deny-list', - partition: 'aws', - configPolicyNames: ['configPolicy1'], - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'homeRegion', - }; - public static readonly allowlistProps = { - policyId: 'policyId', - targetId: 'targetId', - type: 'type', - strategy: 'allow-list', - partition: 'aws', - configPolicyNames: ['configPolicy1'], - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'homeRegion', - }; - public static readonly otherRegionProps = { - policyId: 'policyId', - targetId: 'targetId', - type: 'type', - strategy: 'allow-list', - partition: 'aws', - configPolicyNames: ['configPolicy1'], - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'region', - }; - public static readonly malFormedPolicyException = JSON.stringify({ - name: 'MalformedPolicyDocumentException', - $fault: 'client', - $metadata: { httpStatusCode: 400 }, - }); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/attach-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/index.ts deleted file mode 100644 index b48ea03..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/index.ts +++ /dev/null @@ -1,315 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * aws-organization-create-accounts - lambda handler - * - * @param event - * @returns - */ - -import * as AWS from 'aws-sdk'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CreateAccountResponse } from 'aws-sdk/clients/organizations'; -import { CloudFormationCustomResourceEvent, Context } from '@aws-accelerator/utils/lib/common-types'; -const documentClient = new AWS.DynamoDB.DocumentClient(); -const newOrgAccountsTableName = process.env['NewOrgAccountsTableName'] ?? ''; -const govCloudAccountMappingTableName = process.env['GovCloudAccountMappingTableName'] ?? ''; -const accountRoleName = process.env['AccountRoleName']; -const solutionId = process.env['SOLUTION_ID'] ?? ''; - -interface AccountConfig { - name: string; - description: string; - email: string; - enableGovCloud: string; - organizationalUnitId: string; - createRequestId?: string; -} - -type AccountConfigs = Array; -let organizationsClient: AWS.Organizations; - -export async function handler( - event: CloudFormationCustomResourceEvent, - context: Context, -): Promise< - | { - IsComplete: boolean; - } - | undefined -> { - const partition = context.invokedFunctionArn.split(':')[1]; - - if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1', customUserAgent: solutionId }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - - console.log(event); - // get a single accountConfig from table and attempt to create - // if no record is returned then all new accounts are provisioned - try { - const accountToAdd = await getSingleAccountConfigFromTable(); - if (accountToAdd.length === 0) { - console.log('Finished adding accounts'); - return { - IsComplete: true, - }; - } - - const singleAccountToAdd = accountToAdd[0]; - console.log(`enablegovcloud value: ${singleAccountToAdd.enableGovCloud}`); - let createAccountResponse: CreateAccountResponse; - // if the createRequestId is empty then we need to create the account - if (singleAccountToAdd.createRequestId === '' || singleAccountToAdd.createRequestId === undefined) { - if (singleAccountToAdd.enableGovCloud == 'true' || singleAccountToAdd.enableGovCloud) { - createAccountResponse = await createGovCloudAccount(singleAccountToAdd.email, singleAccountToAdd.name); - } else { - createAccountResponse = await createOrganizationAccount(singleAccountToAdd.email, singleAccountToAdd.name); - } - switch (createAccountResponse.CreateAccountStatus?.State) { - case 'IN_PROGRESS': - console.log(`Initiated account creation for ${accountToAdd[0].email}`); - singleAccountToAdd.createRequestId = createAccountResponse.CreateAccountStatus.Id; - const updateAccountConfigResponse = await updateAccountConfig(singleAccountToAdd); - if (!updateAccountConfigResponse) { - throw new Error('Unable to update DynamoDB account record with request id'); - } else { - return { - IsComplete: false, - }; - } - case 'SUCCEEDED': - if (createAccountResponse.CreateAccountStatus.GovCloudAccountId) { - console.log( - `GovCloud account created with id ${createAccountResponse.CreateAccountStatus.GovCloudAccountId}`, - ); - await saveGovCloudAccountMapping( - createAccountResponse.CreateAccountStatus.AccountId!, - createAccountResponse.CreateAccountStatus.GovCloudAccountId, - createAccountResponse.CreateAccountStatus.AccountName!, - ); - } - console.log( - `Account with id ${createAccountResponse.CreateAccountStatus.AccountId} was created for email ${singleAccountToAdd.email}`, - ); - await moveAccountToOrgIdFromRoot( - createAccountResponse.CreateAccountStatus.AccountId!, - singleAccountToAdd.organizationalUnitId, - ); - await deleteSingleAccountConfigFromTable(singleAccountToAdd.email); - break; - default: - throw new Error( - `Could not create account ${singleAccountToAdd.email}. Response state: ${createAccountResponse.CreateAccountStatus?.State}. Failure reason: ${createAccountResponse.CreateAccountStatus?.FailureReason}`, - ); - } - } else { - // check status of account creation - const createAccountStatusResponse = await getAccountCreationStatus(singleAccountToAdd.createRequestId); - switch (createAccountStatusResponse.CreateAccountStatus?.State) { - case 'IN_PROGRESS': - console.log(`Account is still being created`); - return { - IsComplete: false, - }; - case 'SUCCEEDED': - console.log(`Account with id ${createAccountStatusResponse.CreateAccountStatus?.AccountId} is complete`); - if (createAccountStatusResponse.CreateAccountStatus.GovCloudAccountId) { - console.log(createAccountStatusResponse.CreateAccountStatus.GovCloudAccountId); - await saveGovCloudAccountMapping( - createAccountStatusResponse.CreateAccountStatus.AccountId!, - createAccountStatusResponse.CreateAccountStatus.GovCloudAccountId, - singleAccountToAdd.name, - ); - } - await moveAccountToOrgIdFromRoot( - createAccountStatusResponse.CreateAccountStatus.AccountId!, - singleAccountToAdd.organizationalUnitId, - ); - await deleteSingleAccountConfigFromTable(singleAccountToAdd.email); - break; - default: - throw new Error( - `Could not create account ${singleAccountToAdd.email}. Response state: ${createAccountStatusResponse.CreateAccountStatus?.State}, Failure reason: ${createAccountStatusResponse.CreateAccountStatus?.FailureReason}`, - ); - } - } - return { - IsComplete: false, - }; - } catch (e) { - console.log(e); - console.log(`Create accounts failed. Deleting pending account creation records`); - await deleteAllRecordsFromTable(newOrgAccountsTableName); - throw new Error(`Account creation failed. ${e}`); - } -} - -async function getSingleAccountConfigFromTable(): Promise { - const accountToAdd: AccountConfigs = []; - const scanParams = { - TableName: newOrgAccountsTableName, - Limit: 1, - }; - - const response = await throttlingBackOff(() => documentClient.scan(scanParams).promise()); - - console.log(`getSingleAccount response ${JSON.stringify(response)}`); - const itemCount = response.Items?.length ?? 0; - if (itemCount > 0) { - const account: AccountConfig = JSON.parse(response.Items![0]['accountConfig']); - accountToAdd.push(account); - console.log(`Account to add ${JSON.stringify(accountToAdd)}`); - } - return accountToAdd; -} - -async function deleteSingleAccountConfigFromTable(accountToDeleteEmail: string): Promise { - const deleteParams = { - TableName: newOrgAccountsTableName, - Key: { - accountEmail: accountToDeleteEmail, - }, - }; - const response = await throttlingBackOff(() => documentClient.delete(deleteParams).promise()); - if (response.$response.httpResponse.statusCode === 200) { - return true; - } else { - console.log(response); - return false; - } -} - -async function createOrganizationAccount( - accountEmail: string, - accountName: string, -): Promise { - const createAccountsParams = { - AccountName: accountName, - Email: accountEmail, - RoleName: accountRoleName, - }; - const createAccountResponse = await throttlingBackOff(() => - organizationsClient.createAccount(createAccountsParams).promise(), - ); - console.log(createAccountResponse); - return createAccountResponse; -} - -async function createGovCloudAccount( - accountEmail: string, - accountName: string, -): Promise { - const createAccountsParams = { - AccountName: accountName, - Email: accountEmail, - RoleName: accountRoleName, - }; - const createAccountResponse = await throttlingBackOff(() => - organizationsClient.createGovCloudAccount(createAccountsParams).promise(), - ); - console.log(createAccountResponse); - return createAccountResponse; -} - -async function getAccountCreationStatus( - requestId: string, -): Promise { - return throttlingBackOff(() => - organizationsClient.describeCreateAccountStatus({ CreateAccountRequestId: requestId }).promise(), - ); -} - -async function updateAccountConfig(accountConfig: AccountConfig): Promise { - const params = { - TableName: newOrgAccountsTableName, - Item: { - accountEmail: accountConfig.email, - accountConfig: JSON.stringify(accountConfig), - }, - }; - const response = await throttlingBackOff(() => documentClient.put(params).promise()); - if (response.$response.httpResponse.statusCode === 200) { - return true; - } else { - console.log(response); - return false; - } -} - -async function moveAccountToOrgIdFromRoot(accountId: string, orgId: string): Promise { - const roots = await throttlingBackOff(() => organizationsClient.listRoots({}).promise()); - const rootOrg = roots.Roots?.find(item => item.Name === 'Root'); - const response = await throttlingBackOff(() => - organizationsClient - .moveAccount({ - AccountId: accountId, - DestinationParentId: orgId, - SourceParentId: rootOrg!.Id!, - }) - .promise(), - ); - if (response.$response.httpResponse.statusCode === 200) { - console.log(`Moved account ${accountId} to OU.`); - return true; - } else { - console.log( - `Failed to move account ${accountId} to OU. Move request status: ${response.$response.httpResponse.statusMessage}`, - ); - } - return false; -} - -async function saveGovCloudAccountMapping( - commercialAccountId: string, - govCloudAccountId: string, - accountName: string, -): Promise { - const params = { - TableName: govCloudAccountMappingTableName, - Item: { - commercialAccountId: commercialAccountId, - govCloudAccountId: govCloudAccountId, - accountName: accountName, - }, - }; - const response = await throttlingBackOff(() => documentClient.put(params).promise()); - if (response.$response.httpResponse.statusCode === 200) { - return true; - } else { - console.log(response); - return false; - } -} - -async function deleteAllRecordsFromTable(paramTableName: string) { - const params = { - TableName: paramTableName, - ProjectionExpression: 'accountEmail', - }; - const response = await documentClient.scan(params).promise(); - if (response.Items) { - for (const item of response.Items) { - console.log(item['accountEmail']); - const itemParams = { - TableName: paramTableName, - Key: { - accountEmail: item['accountEmail'], - }, - }; - await documentClient.delete(itemParams).promise(); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/package.json deleted file mode 100644 index f5c61d0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-create-accounts-status", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts-status/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts.ts deleted file mode 100644 index 4588662..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; - -import path = require('path'); - -/** - * Organizations create accounts - */ -export interface CreateOrganizationAccountsProps { - readonly newOrgAccountsTable: cdk.aws_dynamodb.ITable; - readonly govCloudAccountMappingTable: cdk.aws_dynamodb.ITable | undefined; - readonly accountRoleName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class CreateOrganizationAccounts extends Construct { - readonly onEvent: cdk.aws_lambda.IFunction; - readonly isComplete: cdk.aws_lambda.IFunction; - readonly provider: cdk.custom_resources.Provider; - readonly id: string; - - constructor(scope: Construct, id: string, props: CreateOrganizationAccountsProps) { - super(scope, id); - - const CREATE_ORGANIZATION_ACCOUNTS_RESOURCE_TYPE = 'Custom::CreateOrganizationAccounts'; - - this.onEvent = new cdk.aws_lambda.Function(this, 'CreateOrganizationAccounts', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-accounts/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.seconds(30), - description: 'Create Organization Accounts OnEvent handler', - environmentEncryption: props.kmsKey, - }); - const onEventLogGroup = new cdk.aws_logs.LogGroup(this, `${this.onEvent.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.onEvent.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const ddbPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'DynamoDb', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:Scan', 'dynamodb:GetItem', 'dynamodb:DeleteItem', 'dynamodb:PutItem'], - resources: [props.newOrgAccountsTable.tableArn], - }); - const ddbKmsPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'KMS', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: [props.newOrgAccountsTable.encryptionKey?.keyArn as string], - }); - const orgPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'Organizations', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'organizations:CreateAccount', - 'organizations:CreateGovCloudAccount', - 'organizations:DescribeCreateAccountStatus', - 'organizations:ListRoots', - 'organizations:MoveAccount', - ], - resources: ['*'], - }); - - this.isComplete = new cdk.aws_lambda.Function(this, 'CreateOrganizationAccountStatus', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'create-accounts-status/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(5), - description: 'Create Organization Account isComplete handler', - environment: { - NewOrgAccountsTableName: props.newOrgAccountsTable.tableName, - GovCloudAccountMappingTableName: props.govCloudAccountMappingTable?.tableName || '', - AccountRoleName: props.accountRoleName, - }, - initialPolicy: [ddbPolicy, ddbKmsPolicy, orgPolicy], - environmentEncryption: props.kmsKey, - }); - const isCompleteLogGroup = new cdk.aws_logs.LogGroup(this, `${this.isComplete.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.isComplete.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - if (props.govCloudAccountMappingTable) { - const mappingTablePolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'MappingDynamoDb', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:GetItem', 'dynamodb:PutItem'], - resources: [props.govCloudAccountMappingTable.tableArn], - }); - const mappingTableKeyPolicy = new cdk.aws_iam.PolicyStatement({ - sid: 'MappingKMS', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: [props.govCloudAccountMappingTable.encryptionKey?.keyArn as string], - }); - this.isComplete.addToRolePolicy(mappingTablePolicy); - this.isComplete.addToRolePolicy(mappingTableKeyPolicy); - } - - this.provider = new cdk.custom_resources.Provider(this, 'CreateOrganizationAccountsProvider', { - onEventHandler: this.onEvent, - isCompleteHandler: this.isComplete, - queryInterval: cdk.Duration.seconds(15), - totalTimeout: cdk.Duration.hours(2), - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: CREATE_ORGANIZATION_ACCOUNTS_RESOURCE_TYPE, - serviceToken: this.provider.serviceToken, - properties: { - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(isCompleteLogGroup); - resource.node.addDependency(onEventLogGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/index.ts deleted file mode 100644 index 7f41827..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * aws-organization-create-accounts - lambda handler - * - * @param event - * @returns - */ -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - IsComplete: boolean; - } - | undefined -> { - switch (event.RequestType) { - case 'Create': - case 'Update': - return { - IsComplete: false, - }; - - case 'Delete': - // Do Nothing - return { - IsComplete: true, - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/package.json deleted file mode 100644 index 1609de4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-create-accounts", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-accounts/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/index.ts deleted file mode 100644 index 5c07a0a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/index.ts +++ /dev/null @@ -1,266 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - DynamoDBDocumentClient, - UpdateCommand, - UpdateCommandInput, - paginateQuery, - DynamoDBDocumentPaginationConfiguration, -} from '@aws-sdk/lib-dynamodb'; - -import { - OrganizationsClient, - ListOrganizationalUnitsForParentCommand, - ListOrganizationalUnitsForParentCommandOutput, - ListRootsCommand, - ListRootsCommandOutput, - CreateOrganizationalUnitCommand, -} from '@aws-sdk/client-organizations'; -import { ConfiguredRetryStrategy } from '@aws-sdk/util-retry'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; -let organizationsClient: OrganizationsClient; -const marshallOptions = { - convertEmptyValues: false, - //overriding default value of false - removeUndefinedValues: true, - convertClassInstanceToMap: false, -}; -const unmarshallOptions = { - wrapNumbers: false, -}; -const translateConfig = { marshallOptions, unmarshallOptions }; -let paginationConfig: DynamoDBDocumentPaginationConfiguration; -let dynamodbClient: DynamoDBClient; -let documentClient: DynamoDBDocumentClient; - -type OrganizationConfigRecord = { - dataType: string; - acceleratorKey: string; - dataBag: string; - awsKey: string; - commitId: string; -}; -type OrganizationConfigRecords = Array; -/** - * create-organizational-units - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const configTableName = event.ResourceProperties['configTableName']; - const commitId = event.ResourceProperties['commitId']; - const organizationsEnabled = event.ResourceProperties['organizationsEnabled']; - const partition = event.ResourceProperties['partition']; - const organizationalUnitsToCreate: OrganizationConfigRecords = []; - const solutionId = process.env['SOLUTION_ID']; - - dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId }); - documentClient = DynamoDBDocumentClient.from(dynamodbClient, translateConfig); - paginationConfig = { - client: documentClient, - pageSize: 100, - }; - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (organizationsEnabled == 'false') { - console.log('Stopping, Organizations not enabled.'); - return { - Status: 'SUCCESS', - }; - } - if (partition === 'aws-us-gov') { - organizationsClient = new OrganizationsClient({ - retryStrategy: new ConfiguredRetryStrategy(10, (attempt: number) => 100 + attempt * 1000), - region: 'us-gov-west-1', - }); - } else if (partition === 'aws-cn') { - organizationsClient = new OrganizationsClient({ - retryStrategy: new ConfiguredRetryStrategy(10, (attempt: number) => 100 + attempt * 1000), - region: 'cn-northwest-1', - }); - } else { - organizationsClient = new OrganizationsClient({ - retryStrategy: new ConfiguredRetryStrategy(10, (attempt: number) => 100 + attempt * 1000), - region: 'us-east-1', - }); - } - //read config from table - const organizationalUnitList = await getConfigFromTable(configTableName, commitId); - console.log(`Organizational Units retrieved from config table: ${JSON.stringify(organizationalUnitList)}`); - //build list of organizational units that need to be created - if (organizationalUnitList) { - for (const organizationalUnit of organizationalUnitList) { - if (!organizationalUnit.awsKey) { - organizationalUnitsToCreate.push(organizationalUnit); - } - } - } - //get organzational root id - const rootId = await getRootId(); - console.log(`Root OU ID ${rootId}`); - //sort by number of elements in order to - //create parent organizational units first - const sortedOrganizationalUnits = organizationalUnitsToCreate.sort((a, b) => - a['acceleratorKey'].split('/').length > b['acceleratorKey'].split('/').length ? 1 : -1, - ); - console.log(`Sorted list of OU's to create ${JSON.stringify(sortedOrganizationalUnits)}`); - for (const organizationalUnit of sortedOrganizationalUnits) { - console.log(`Creating organizational unit ${organizationalUnit['acceleratorKey']}`); - const createResponse = await createOrganizationalUnitFromPath( - rootId, - organizationalUnit['acceleratorKey'], - configTableName, - ); - if (!createResponse) { - return { - Status: 'FAILURE', - }; - } - } - return { - Status: 'SUCCESS', - }; - case 'Delete': - // Do Nothing - return { - Status: 'SUCCESS', - }; - } -} -async function lookupOrganizationalUnit(name: string, parentId: string): Promise { - let nextToken: string | undefined = undefined; - do { - const page: ListOrganizationalUnitsForParentCommandOutput = await organizationsClient.send( - new ListOrganizationalUnitsForParentCommand({ ParentId: parentId, NextToken: nextToken }), - ); - for (const ou of page.OrganizationalUnits ?? []) { - if (ou.Name == name) { - return ou.Id!; - } - nextToken = page.NextToken; - } - } while (nextToken); - return ''; -} -async function getConfigFromTable(configTableName: string, commitId: string): Promise { - const params = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'organization', - }, - }; - const items: OrganizationConfigRecords = []; - const paginator = paginateQuery(paginationConfig, params); - for await (const page of paginator) { - if (page.Items) { - for (const item of page.Items) { - items.push(item as OrganizationConfigRecord); - } - } - } - const filterCommitIdResults = items.filter(item => item.commitId == commitId); - return filterCommitIdResults; -} -async function getRootId(): Promise { - // get root ou id - let rootId = ''; - let nextToken: string | undefined = undefined; - do { - const page: ListRootsCommandOutput = await organizationsClient.send(new ListRootsCommand({ NextToken: nextToken })); - for (const item of page.Roots ?? []) { - if (item.Name === 'Root' && item.Id && item.Arn) { - rootId = item.Id; - } - } - nextToken = page.NextToken; - } while (nextToken); - return rootId; -} -function getPath(name: string): string { - //get the parent path - const pathIndex = name.lastIndexOf('/'); - const path = name.slice(0, pathIndex + 1).slice(0, -1); - if (path === '') { - return '/'; - } - return '/' + path; -} -function getOuName(name: string): string { - const result = name.split('/').pop(); - if (result === undefined) { - return name; - } - return result; -} -async function createOrganizationalUnitFromPath( - rootId: string, - acceleratorKey: string, - configTableName: string, -): Promise { - let parentId = rootId; - const path = getPath(acceleratorKey); - const name = getOuName(acceleratorKey); - //find parent for ou - for (const parent of path.split('/')) { - if (parent) { - const orgId = await lookupOrganizationalUnit(parent, parentId); - if (orgId !== '') { - console.log(`Found parent ou with id ${orgId}`); - parentId = orgId; - } else { - console.log(`Need to create ou ${parent} for parentId ${parentId} in the organizations config`); - return false; - } - } - } - // Create the OU if not found - try { - const organizationsResponse = await organizationsClient.send( - new CreateOrganizationalUnitCommand({ - Name: name, - ParentId: parentId, - }), - ); - console.log(`Created OU with id: ${organizationsResponse.OrganizationalUnit?.Id}`); - const params: UpdateCommandInput = { - TableName: configTableName, - Key: { - dataType: 'organization', - acceleratorKey: acceleratorKey, - }, - UpdateExpression: 'set #attribute = :x', - ExpressionAttributeNames: { '#attribute': 'awsKey' }, - ExpressionAttributeValues: { ':x': organizationsResponse.OrganizationalUnit?.Id }, - }; - const updateConfigReponse = await throttlingBackOff(() => documentClient.send(new UpdateCommand(params))); - console.log(updateConfigReponse); - return true; - } catch (error) { - console.log(error); - return false; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/package.json deleted file mode 100644 index c40e4bb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-create-organizational-units", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-dynamodb": "3.410.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/lib-dynamodb": "3.410.0", - "@aws-sdk/util-retry": "3.374.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-organizational-units/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/index.ts deleted file mode 100644 index 1c6014e..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/index.ts +++ /dev/null @@ -1,259 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { setRetryStrategy, getGlobalRegion } from '@aws-accelerator/utils/lib/common-functions'; -import { - OrganizationsClient, - Tag, - paginateListPolicies, - DeletePolicyCommand, - paginateListTargetsForPolicy, - DetachPolicyCommand, - PolicyNotAttachedException, - PolicyNotFoundException, - CreatePolicyCommand, - UpdatePolicyCommand, - DuplicatePolicyException, -} from '@aws-sdk/client-organizations'; -import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * create-policy - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const bucket: string = event.ResourceProperties['bucket']; - const key: string = event.ResourceProperties['key']; - const name: string = event.ResourceProperties['name']; - const description = event.ResourceProperties['description'] || ''; - const type: string = event.ResourceProperties['type']; - const tags: Tag[] = event.ResourceProperties['tags'] || []; - const partition: string = event.ResourceProperties['partition']; - const policyTagKey: string = event.ResourceProperties['policyTagKey']; - const globalRegion = getGlobalRegion(partition); - - const solutionId = process.env['SOLUTION_ID']; - const organizationsClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - const s3Client = new S3Client({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - // Any configured AWS Organization Service Control Policies (SCPs) are also created and attached to configuration-specified deployment targets in accounts stage in global region - - // - // Read in the policy content from the specified S3 location - // - const s3Object = await throttlingBackOff(() => s3Client.send(new GetObjectCommand({ Bucket: bucket, Key: key }))); - // as per javascript section in https://docs.aws.amazon.com/AmazonS3/latest/userguide/example_s3_GetObject_section.html - const content = await s3Object.Body!.transformToString(); - - const policyId = await createPolicy( - { name, type, content, description, tags, policyTagKey }, - organizationsClient, - ); - - return { - PhysicalResourceId: policyId, - Status: 'SUCCESS', - }; - - case 'Delete': - const deleteEventPolicyId = event.PhysicalResourceId; - const deletePolicyId = await getPolicyId(organizationsClient, name, type); - - // only detach if policy ID from event matches policy ID from AWS Organizations - if (deletePolicyId === deleteEventPolicyId) { - console.log(`${type} ${name} found for deletion`); - console.log(`Checking if policy ${name} has any attachments`); - await detachPolicyFromAllAttachedTargets(organizationsClient, { name: name, id: deletePolicyId }); - - console.log(`Deleting policy ${name}, policy type is ${type}`); - await deletePolicy(deletePolicyId, organizationsClient); - console.log(`Policy ${name} deleted successfully!`); - } else { - // Policy set for deletion was not found in AWS Organizations. Log message and send success. - console.warn(`Policy: ${name} was not found in AWS Organizations`); - } - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -/** - * Function to delete specific policy - */ -async function deletePolicy(policyId: string, organizationsClient: OrganizationsClient): Promise { - try { - await throttlingBackOff(() => organizationsClient.send(new DeletePolicyCommand({ PolicyId: policyId }))); - } catch (error: unknown) { - if (error instanceof PolicyNotFoundException) { - // if policy was recreated outside of accelerator - // it will have a unique ID which is not the org and throw this exception - // ignore it and proceed with next step - console.log(`Policy: ${policyId} was not found. Continuing...`); - } else { - throw new Error(`Error while trying to delete policy: ${policyId}. Error message: ${JSON.stringify(error)}`); - } - } -} -/** - * Function to get policy Id required for deletion - * @param organizationsClient - * @param policyName - * @param type - */ -async function getPolicyId( - organizationsClient: OrganizationsClient, - policyName: string, - type: string, -): Promise { - for await (const page of paginateListPolicies({ client: organizationsClient }, { Filter: type })) { - for (const policy of page.Policies ?? []) { - if (policy.Name === policyName) { - return policy.Id; - } - } - } - - return undefined; -} - -/** - * Function to detach all targets from given policy, before deleting the policy - * @param organizationsClient - * @param removePolicy - */ -async function detachPolicyFromAllAttachedTargets( - organizationsClient: OrganizationsClient, - removePolicy: { - name: string; - id: string; - }, -): Promise { - const targetIds: string[] = []; - for await (const page of paginateListTargetsForPolicy( - { client: organizationsClient }, - { PolicyId: removePolicy.id }, - )) { - for (const target of page.Targets ?? []) { - targetIds.push(target.TargetId!); - } - } - - const targetDetachPromise = []; - for (const targetId of targetIds) { - console.log(`Started detach of target ${targetId} from policy ${removePolicy.id}`); - targetDetachPromise.push(detachTargetFromSpecificTarget(targetId, organizationsClient, removePolicy)); - } - await Promise.all(targetDetachPromise); -} - -/** - * Function to detach a specific target based on target Id - * @param targetId - * @param organizationsClient - * @param removePolicy - */ -async function detachTargetFromSpecificTarget( - targetId: string, - organizationsClient: OrganizationsClient, - removePolicy: { - name: string; - id: string; - }, -) { - try { - throttlingBackOff(() => - organizationsClient.send(new DetachPolicyCommand({ PolicyId: removePolicy.id, TargetId: targetId })), - ); - } catch (error) { - if (error instanceof PolicyNotAttachedException) { - console.log(`${removePolicy.name} policy not found to detach`); - } else { - throw new Error(`Policy ${removePolicy.name} detach error message - ${JSON.stringify(error)}`); - } - } -} - -/** - * Function to create organization policy - * @param policyMetadata - * @param organizationsClient - * @returns - */ -async function createPolicy( - policyMetadata: { - name: string; - type: string; - content: string; - description: string; - tags: Tag[]; - policyTagKey: string; - }, - organizationsClient: OrganizationsClient, -) { - try { - const createPolicyResponse = await throttlingBackOff(() => - organizationsClient.send( - new CreatePolicyCommand({ - Content: policyMetadata.content, - Description: policyMetadata.description, - Name: policyMetadata.name, - Type: policyMetadata.type, - Tags: [...policyMetadata.tags, { Key: policyMetadata.policyTagKey, Value: 'Yes' }], - }), - ), - ); - - return createPolicyResponse.Policy!.PolicySummary!.Id; // return policy id for create policy response - } catch (error) { - if (error instanceof DuplicatePolicyException) { - const policyId = await getPolicyId(organizationsClient, policyMetadata.name, policyMetadata.type)!; - console.log(`Policy ${policyMetadata.name} already exists, updating`); - await throttlingBackOff(() => - organizationsClient.send( - new UpdatePolicyCommand({ - Content: policyMetadata.content, - Description: policyMetadata.description, - Name: policyMetadata.name, - PolicyId: policyId, - }), - ), - ); - console.log(`Policy ${policyMetadata.name} updated successfully.`); - return await getPolicyId(organizationsClient, policyMetadata.name, policyMetadata.type)!; // return if policy already exists, no need to create it again. - } - throw new Error( - `Error in creating policy ${policyMetadata.name} in AWS Organizations. Exception: ${JSON.stringify(error)}`, - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/jest.config.js deleted file mode 100644 index ff814cc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 75, - functions: 100, - lines: 95, - statements: 95, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/package.json deleted file mode 100644 index 67eb5ec..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-create-policy", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --silent", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/client-s3": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/index.test.ts deleted file mode 100644 index 02da72a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/index.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - OrganizationsClient, - ListPoliciesCommand, - ListTargetsForPolicyCommand, - CreatePolicyCommand, - DuplicatePolicyException, - DetachPolicyCommand, - DeletePolicyCommand, - PolicyNotFoundException, - UpdatePolicyCommand, -} from '@aws-sdk/client-organizations'; - -import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; - -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; -import { sdkStreamMixin } from '@smithy/util-stream'; -import { Readable } from 'stream'; - -const orgClient = AcceleratorMockClient(OrganizationsClient); - -const s3Client = AcceleratorMockClient(S3Client); - -process.env['SOLUTION_ID'] = 'testLza'; -beforeEach(() => { - orgClient.reset(); - s3Client.reset(); -}); - -describe('Create Event', () => { - test('Create a policy that does not exist', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - s3Client - .on(GetObjectCommand, { Bucket: StaticInput.newProps.bucket, Key: StaticInput.newProps.key }) - .resolves({ Body: stringToStream(StaticInput.policyContent) }); - orgClient.on(CreatePolicyCommand).resolves({ Policy: { PolicySummary: { Id: 'Id' } } }); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Create a policy that exists', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - s3Client - .on(GetObjectCommand, { Bucket: StaticInput.newProps.bucket, Key: StaticInput.newProps.key }) - .resolves({ Body: stringToStream(StaticInput.policyContent) }); - orgClient - .on(CreatePolicyCommand) - .rejects(new DuplicatePolicyException({ $metadata: { httpStatusCode: 400 }, message: 'Duplicate policy' })); - orgClient - .on(ListPoliciesCommand, { Filter: StaticInput.newProps.type }) - .resolves({ Policies: [{ Name: StaticInput.newProps.name, Id: 'Id' }] }); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); -}); - -describe('Update Event', () => { - test('Update a policy that exists', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - s3Client - .on(GetObjectCommand, { Bucket: StaticInput.newProps.bucket, Key: StaticInput.newProps.key }) - .resolves({ Body: stringToStream(StaticInput.policyContent) }); - orgClient.on(CreatePolicyCommand).rejects({}); - await expect(handler(event)).rejects.toThrowError( - `Error in creating policy ${StaticInput.newProps.name} in AWS Organizations. Exception: {}`, - ); - orgClient.on(UpdatePolicyCommand).resolves({ Policy: { PolicySummary: { Id: 'Id' } } }); - }); -}); - -describe('Delete Event', () => { - test('Delete policy ideally', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - orgClient - .on(ListPoliciesCommand, { Filter: StaticInput.newProps.type }) - .resolves({ Policies: [{ Name: StaticInput.newProps.name, Id: 'PhysicalResourceId' }] }); - orgClient - .on(ListTargetsForPolicyCommand, { PolicyId: 'PhysicalResourceId' }) - .resolves({ Targets: [{ TargetId: 'targetId' }] }); - orgClient.on(DetachPolicyCommand, { PolicyId: 'PhysicalResourceId', TargetId: 'targetId' }).resolves({}); - orgClient.on(DeletePolicyCommand, { PolicyId: 'PhysicalResourceId' }).resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Delete policy - policy not found', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - orgClient - .on(ListPoliciesCommand, { Filter: StaticInput.newProps.type }) - .resolves({ Policies: [{ Name: StaticInput.newProps.name, Id: 'PhysicalResourceId' }] }); - orgClient - .on(ListTargetsForPolicyCommand, { PolicyId: 'PhysicalResourceId' }) - .resolves({ Targets: [{ TargetId: 'targetId' }] }); - orgClient.on(DetachPolicyCommand, { PolicyId: 'PhysicalResourceId', TargetId: 'targetId' }).resolves({}); - orgClient - .on(DeletePolicyCommand) - .rejects(new PolicyNotFoundException({ $metadata: { httpStatusCode: 400 }, message: 'Policy not found' })); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); - test('Delete policy - policy is in use', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - orgClient - .on(ListPoliciesCommand, { Filter: StaticInput.newProps.type }) - .resolves({ Policies: [{ Name: StaticInput.newProps.name, Id: 'PhysicalResourceId' }] }); - orgClient - .on(ListTargetsForPolicyCommand, { PolicyId: 'PhysicalResourceId' }) - .resolves({ Targets: [{ TargetId: 'targetId' }] }); - orgClient.on(DetachPolicyCommand, { PolicyId: 'PhysicalResourceId', TargetId: 'targetId' }).resolves({}); - orgClient.on(DeletePolicyCommand).rejects(); - await expect(handler(event)).rejects.toThrowError( - `Error while trying to delete policy: PhysicalResourceId. Error message: {}`, - ); - }); - test('Delete policy in homeRegion - policyId not found', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - orgClient.on(ListPoliciesCommand, { Filter: StaticInput.newProps.type }).resolves({}); - const response = await handler(event); - expect(response?.Status).toStrictEqual('SUCCESS'); - }); -}); - -// describe('Delete Event - errors', () => {}); - -function stringToStream(input: string) { - // create Stream from string - const stream = new Readable(); - stream.push(input); - stream.push(null); // end of stream - // wrap the Stream with SDK mixin - return sdkStreamMixin(stream); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/static-input.ts deleted file mode 100644 index d9852dd..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/test/static-input.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly newProps = { - bucket: 'bucket', - key: 'key', - name: 'name', - description: 'description', - type: 'type', - tags: [{ Key: 'key', Value: 'value' }], - partition: 'aws', - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'homeRegion', - }; - public static readonly otherRegionProps = { - bucket: 'bucket', - key: 'key', - name: 'name', - description: 'description', - type: 'type', - tags: [{ Key: 'key', Value: 'value' }], - partition: 'aws', - policyTagKey: 'policyTagKey', - homeRegion: 'homeRegion', - region: 'region', - }; - public static readonly policyContent = 'policyContent'; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/create-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access.ts deleted file mode 100644 index ffd9ddf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * EnableAwsServiceAccessProps properties - */ -export interface EnableAwsServiceAccessProps { - readonly servicePrincipal: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to Enable AWS Service Access - */ -export class EnableAwsServiceAccess extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: EnableAwsServiceAccessProps) { - super(scope, id); - - const provider = cdk.CustomResourceProvider.getOrCreateProvider( - this, - 'Custom::OrganizationsEnableAwsServiceAccess', - { - codeDirectory: path.join(__dirname, 'enable-aws-service-access/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['organizations:DisableAWSServiceAccess', 'organizations:EnableAwsServiceAccess'], - Resource: '*', - }, - ], - }, - ); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::EnableAwsServiceAccess', - serviceToken: provider.serviceToken, - properties: { - partition: cdk.Aws.PARTITION, - servicePrincipal: props.servicePrincipal, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/index.ts deleted file mode 100644 index f5e3c07..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * enable-aws-service-access - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const servicePrincipal: string = event.ResourceProperties['servicePrincipal']; - const partition = event.ResourceProperties['partition']; - const solutionId = process.env['SOLUTION_ID']; - - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1', customUserAgent: solutionId }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1', customUserAgent: solutionId }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - - switch (event.RequestType) { - case 'Create': - case 'Update': - await throttlingBackOff(() => - organizationsClient.enableAWSServiceAccess({ ServicePrincipal: servicePrincipal }).promise(), - ); - - return { - PhysicalResourceId: servicePrincipal, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - organizationsClient.disableAWSServiceAccess({ ServicePrincipal: servicePrincipal }).promise(), - ); - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/package.json deleted file mode 100644 index ed28e70..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-enable-aws-service-access", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-aws-service-access/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type.ts deleted file mode 100644 index 13c59fb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -export enum PolicyTypeEnum { - SERVICE_CONTROL_POLICY = 'SERVICE_CONTROL_POLICY', - TAG_POLICY = 'TAG_POLICY', - BACKUP_POLICY = 'BACKUP_POLICY', - AISERVICES_OPT_OUT_POLICY = 'AISERVICES_OPT_OUT_POLICY', -} - -/** - * Initialized EnablePolicyType properties - */ -export interface EnablePolicyTypeProps { - readonly policyType: PolicyTypeEnum; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to initialize EnablePolicyType - */ -export class EnablePolicyType extends cdk.Resource { - constructor(scope: Construct, id: string, props: EnablePolicyTypeProps) { - super(scope, id); - - const ENABLE_POLICY_TYPE = 'Custom::EnablePolicyType'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, ENABLE_POLICY_TYPE, { - codeDirectory: path.join(__dirname, 'enable-policy-type/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'organizations:DescribeOrganization', - 'organizations:DisablePolicyType', - 'organizations:EnablePolicyType', - 'organizations:ListRoots', - 'organizations:ListPoliciesForTarget', - 'organizations:ListTargetsForPolicy', - 'organizations:DescribeEffectivePolicy', - 'organizations:DescribePolicy', - 'organizations:DisableAWSServiceAccess', - 'organizations:DetachPolicy', - 'organizations:DeletePolicy', - 'organizations:DescribeAccount', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:ListPolicies', - 'organizations:ListAccountsForParent', - 'organizations:ListAccounts', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListCreateAccountStatus', - 'organizations:UpdatePolicy', - 'organizations:DescribeOrganizationalUnit', - 'organizations:AttachPolicy', - 'organizations:ListParents', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:CreatePolicy', - 'organizations:DescribeCreateAccountStatus', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: ENABLE_POLICY_TYPE, - serviceToken: provider.serviceToken, - properties: { - partition: cdk.Aws.PARTITION, - policyType: props.policyType, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/index.ts deleted file mode 100644 index 35ece19..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { setOrganizationsClient } from '@aws-accelerator/utils/lib/set-organizations-client'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { EnablePolicyTypeCommand, ListRootsCommand } from '@aws-sdk/client-organizations'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * enable-policy-type - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - switch (event.RequestType) { - case 'Create': - const policyType = event.ResourceProperties['policyType']; - const partition = event.ResourceProperties['partition']; - const solutionId = process.env['SOLUTION_ID']; - - // - // Obtain an Organizations client - // - const organizationsClient = setOrganizationsClient(partition, solutionId); - - // Verify policy type from the listRoots call - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListRootsCommand({ NextToken: nextToken })), - ); - for (const orgRoot of page.Roots ?? []) { - if (orgRoot.Name === 'Root') { - if (orgRoot.PolicyTypes?.find(item => item.Type === policyType && item.Status === 'ENABLED')) { - return { - PhysicalResourceId: policyType, - Status: 'SUCCESS', - }; - } - - await throttlingBackOff(() => - organizationsClient.send(new EnablePolicyTypeCommand({ PolicyType: policyType, RootId: orgRoot.Id! })), - ); - - return { - PhysicalResourceId: policyType, - Status: 'SUCCESS', - }; - } - } - nextToken = page.NextToken; - } while (nextToken); - - throw new Error(`Error enabling policy type for Root`); - - case 'Update': - case 'Delete': - // Do Nothing, leave Policy Type enabled - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/package.json deleted file mode 100644 index 992dda4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-enable-policy-type", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-organizations": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/enable-policy-type/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/index.ts deleted file mode 100644 index 6bddb2b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/index.ts +++ /dev/null @@ -1,302 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - OrganizationsClient, - ListRootsCommand, - ListRootsCommandOutput, - ListAccountsCommand, - ListAccountsCommandOutput, - InviteAccountToOrganizationCommand, - AcceptHandshakeCommand, - MoveAccountCommand, -} from '@aws-sdk/client-organizations'; -import { DynamoDBDocumentClient, paginateQuery, DynamoDBDocumentPaginationConfiguration } from '@aws-sdk/lib-dynamodb'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -const marshallOptions = { - convertEmptyValues: false, - //overriding default value of false - removeUndefinedValues: true, - convertClassInstanceToMap: false, -}; -const unmarshallOptions = { - wrapNumbers: false, -}; -const translateConfig = { marshallOptions, unmarshallOptions }; -let dynamodbClient: DynamoDBClient; -let documentClient: DynamoDBDocumentClient; -let paginationConfig: DynamoDBDocumentPaginationConfiguration; - -type OrganizationIdentifier = { - acceleratorKey: string; - awsKey: string; -}; -type OrganizationIdentifiers = Array; - -type AccountDetail = { - accountId: string; - ouName: string; -}; -type AccountDetails = Array; - -/** - * invite-account-to-organization - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const configTableName = event.ResourceProperties['configTableName']; - const commitId = event.ResourceProperties['commitId']; - const assumeRoleName = event.ResourceProperties['assumeRoleName']; - const partition = event.ResourceProperties['partition']; - const solutionId = process.env['SOLUTION_ID']; - - dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId }); - documentClient = DynamoDBDocumentClient.from(dynamodbClient, translateConfig); - paginationConfig = { - client: documentClient, - pageSize: 100, - }; - - let organizationsClient: OrganizationsClient; - if (partition === 'aws-us-gov') { - organizationsClient = new OrganizationsClient({ region: 'us-gov-west-1', customUserAgent: solutionId }); - } else if (partition === 'aws-cn') { - organizationsClient = new OrganizationsClient({ region: 'cn-northwest-1', customUserAgent: solutionId }); - } else { - organizationsClient = new OrganizationsClient({ region: 'us-east-1', customUserAgent: solutionId }); - } - - if (partition !== 'aws-us-gov') { - return { - Status: 'SUCCESS', - }; - } - console.log('CommitId: ', commitId); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const rootId = await getRootId(organizationsClient); - console.log('Organizations Root Id: ', rootId); - const accountsInOu = await listAccountsInOrganization(organizationsClient); - const accountsInConfig = await getAccountsFromTable(configTableName, commitId); - const organizationalUnitsInConfig = await getOrganizationsFromTable(configTableName, commitId); - - for (const account of accountsInConfig) { - if (accountsInOu.find(item => item == account.accountId)) { - console.log(`Account ${account.accountId} already added to organization`); - continue; - } - const ouForAccount = organizationalUnitsInConfig.find(ou => ou.acceleratorKey === account.ouName); - if (ouForAccount?.awsKey) { - const roleArn = `arn:${partition}:iam::${account.accountId}:role/${assumeRoleName}`; - await inviteAccountToOu( - organizationsClient, - account.accountId, - roleArn, - partition, - rootId, - ouForAccount?.awsKey, - ); - } else { - return { - Status: 'FAILURE', - }; - } - } - return { - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - Status: 'SUCCESS', - }; - } -} - -async function inviteAccountToOu( - organizationsClient: OrganizationsClient, - accountId: string, - roleArn: string, - partition: string, - rootId: string, - organizationalUnitId: string, -): Promise { - console.log('InviteAccountToOrganizationCommand'); - const invite = await organizationsClient.send( - new InviteAccountToOrganizationCommand({ Target: { Type: 'ACCOUNT', Id: accountId } }), - ); - console.log(`Invite handshake id: ${invite.Handshake?.Id}`); - - const stsClient = new STSClient({}); - - const assumeRoleResponse = await stsClient.send( - new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceptHandshakeSession' }), - ); - - let acceptOrganizationsClient: OrganizationsClient; - if (partition === 'aws-us-gov') { - acceptOrganizationsClient = new OrganizationsClient({ - credentials: { - accessKeyId: assumeRoleResponse.Credentials?.AccessKeyId ?? '', - secretAccessKey: assumeRoleResponse.Credentials?.SecretAccessKey ?? '', - sessionToken: assumeRoleResponse.Credentials?.SessionToken, - }, - region: 'us-gov-west-1', - }); - } else if (partition === 'aws-cn') { - acceptOrganizationsClient = new OrganizationsClient({ - credentials: { - accessKeyId: assumeRoleResponse.Credentials?.AccessKeyId ?? '', - secretAccessKey: assumeRoleResponse.Credentials?.SecretAccessKey ?? '', - sessionToken: assumeRoleResponse.Credentials?.SessionToken, - }, - region: 'cn-northwest-1', - }); - } else { - acceptOrganizationsClient = new OrganizationsClient({ - credentials: { - accessKeyId: assumeRoleResponse.Credentials?.AccessKeyId ?? '', - secretAccessKey: assumeRoleResponse.Credentials?.SecretAccessKey ?? '', - sessionToken: assumeRoleResponse.Credentials?.SessionToken, - }, - region: 'us-east-1', - }); - } - - console.log('AcceptHandshakeCommand'); - const acceptResponse = await acceptOrganizationsClient.send( - new AcceptHandshakeCommand({ HandshakeId: invite.Handshake!.Id! }), - ); - console.log(acceptResponse); - - console.log('Move account to OU'); - const moveResponse = await organizationsClient.send( - new MoveAccountCommand({ - AccountId: accountId, - SourceParentId: rootId, - DestinationParentId: organizationalUnitId, - }), - ); - console.log(moveResponse); - - return true; -} - -async function getRootId(organizationsClient: OrganizationsClient): Promise { - // get root ou id - let rootId = ''; - let nextToken: string | undefined = undefined; - do { - const page: ListRootsCommandOutput = await organizationsClient.send(new ListRootsCommand({ NextToken: nextToken })); - for (const item of page.Roots ?? []) { - if (item.Name === 'Root' && item.Id && item.Arn) { - rootId = item.Id; - } - } - nextToken = page.NextToken; - } while (nextToken); - return rootId; -} - -async function getOrganizationsFromTable(configTableName: string, commitId: string): Promise { - const params = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'organization', - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - }; - - const items: OrganizationIdentifiers = []; - const paginator = paginateQuery(paginationConfig, params); - for await (const page of paginator) { - if (page.Items) { - for (const item of page.Items) { - items.push({ acceleratorKey: item['acceleratorKey'], awsKey: item['awsKey'] }); - } - } - } - return items; -} - -async function getAccountsFromTable(configTableName: string, commitId: string): Promise { - const workloadAccountParams = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'workloadAccount', - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - }; - - const items: AccountDetails = []; - const workloadPaginator = paginateQuery(paginationConfig, workloadAccountParams); - for await (const page of workloadPaginator) { - if (page.Items) { - for (const item of page.Items) { - items.push({ accountId: item['awsKey'], ouName: item['ouName'] }); - } - } - } - - const mandatoryAccountParams = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'mandatoryAccount', - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - }; - - const mandatoryPaginator = paginateQuery(paginationConfig, mandatoryAccountParams); - for await (const page of mandatoryPaginator) { - if (page.Items) { - for (const item of page.Items) { - items.push({ accountId: item['awsKey'], ouName: item['ouName'] }); - } - } - } - return items; -} - -async function listAccountsInOrganization(organizationsClient: OrganizationsClient): Promise { - const accountsInOu: string[] = []; - let nextToken: string | undefined = undefined; - do { - const page: ListAccountsCommandOutput = await organizationsClient.send( - new ListAccountsCommand({ NextToken: nextToken }), - ); - for (const item of page.Accounts ?? []) { - accountsInOu.push(item.Id!); - } - nextToken = page.NextToken; - } while (nextToken); - return accountsInOu; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/package.json deleted file mode 100644 index 0be2dca..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-invite-account-to-organization", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-dynamodb": "3.410.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/client-sts": "3.410.0", - "@aws-sdk/lib-dynamodb": "3.410.0", - "@aws-sdk/smithy-client": "3.374.0", - "@aws-sdk/types": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/invite-account-to-organization/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/index.ts deleted file mode 100644 index be23cbd..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/index.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * list-policy-for-target - lambda handler - * - * @param event - * @returns - */ - -export type accountItem = { - accountId: string; - name: string; -}; -export type orgItem = { - id: string; - name: string; -}; -type validateScpItem = { - orgEntity: string; - orgEntityType: string; - orgEntityId: string; - appliedScpName: string[]; -}; -const errors: string[] = []; -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const partition = event.ResourceProperties['partition']; - - const organizationUnits: orgItem[] = event.ResourceProperties['organizationUnits']; - const accounts: accountItem[] = event.ResourceProperties['accounts']; - const scps: validateScpItem[] = event.ResourceProperties['scps']; - - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1' }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1' }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1' }); - } - - switch (event.RequestType) { - case 'Create': - case 'Update': - await checkOrganizationUnits(organizationUnits, organizationsClient, scps); - await checkAccounts(accounts, organizationsClient, scps); - if (errors.length > 0) { - throw new Error(`Error: ${errors}`); - } - return { Status: 'SUCCESS' }; - - case 'Delete': - return { Status: 'SUCCESS' }; - } -} - -async function checkOrganizationUnits( - organizationUnits: orgItem[], - organizationsClient: AWS.Organizations, - scps: validateScpItem[], -) { - for (const organizationUnit of organizationUnits) { - // get all scps attached to this particular OU - // this cannot be more than 5 so not paginating - const attachedScps: AWS.Organizations.ListPoliciesForTargetResponse = await throttlingBackOff(() => - organizationsClient - .listPoliciesForTarget({ - Filter: 'SERVICE_CONTROL_POLICY', - TargetId: organizationUnit.id, - MaxResults: 10, - }) - .promise(), - ); - if (attachedScps.Policies) { - // Get all scp names attached by solution from the config - // for this particular organization unit - const accelOuScpsFiltered = scps.filter(obj => { - return obj.orgEntityId === organizationUnit.id; - }); - let accelOuScps: string[] = []; - if (accelOuScpsFiltered.length > 0) { - accelOuScps = accelOuScpsFiltered[0].appliedScpName; - } - - // filter out name of accelerator scps. - // Whatever remains is from external resource (control tower or user) - const nonAccelScps = []; - for (const policy of attachedScps.Policies) { - if (!accelOuScps.includes(policy.Name!)) { - nonAccelScps.push(policy.Name); - } - } - if (nonAccelScps.length + accelOuScps.length > 5) { - errors.push( - `Max Allowed SCPs for OU "${organizationUnit.name}" is 5, found already attached scps count ${nonAccelScps.length} and Accelerator OU scps ${accelOuScps.length} => ${accelOuScps}`, - ); - } - } - } -} - -async function checkAccounts(accounts: accountItem[], organizationsClient: AWS.Organizations, scps: validateScpItem[]) { - for (const account of accounts) { - // get all scps attached to this particular account - // this cannot be more than 5 so not paginating - const attachedScps: AWS.Organizations.ListPoliciesForTargetResponse = await throttlingBackOff(() => - organizationsClient - .listPoliciesForTarget({ - Filter: 'SERVICE_CONTROL_POLICY', - TargetId: account.accountId, - MaxResults: 10, - }) - .promise(), - ); - if (attachedScps.Policies) { - // Get all scp names attached by solution from the config - // for this particular account - const accelOuScpsFiltered = scps.filter(obj => { - return obj.orgEntityId === account.accountId; - }); - let accelOuScps: string[] = []; - if (accelOuScpsFiltered.length > 0) { - accelOuScps = accelOuScpsFiltered[0].appliedScpName; - } - - // filter out name of accelerator scps. - // Whatever remains is from external resource (control tower or user) - const nonAccelScps = []; - for (const policy of attachedScps.Policies) { - if (!accelOuScps.includes(policy.Name!)) { - nonAccelScps.push(policy.Name); - } - } - if (nonAccelScps.length + accelOuScps.length > 5) { - errors.push( - `Max Allowed SCPs for Account "${account.name}" is 5, found already attached scps count ${nonAccelScps.length} and Accelerator OU scps ${accelOuScps.length} => ${accelOuScps}`, - ); - } - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/package.json deleted file mode 100644 index 5b49e97..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-list-policy-for-target", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/list-policy-for-target/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/index.ts deleted file mode 100644 index a46539b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/index.ts +++ /dev/null @@ -1,397 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { - DynamoDBDocumentClient, - QueryCommand, - QueryCommandInput, - paginateQuery, - DynamoDBDocumentPaginationConfiguration, -} from '@aws-sdk/lib-dynamodb'; -import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation'; -import { - ListAccountsForParentCommand, - ListOrganizationalUnitsForParentCommand, - ListRootsCommand, - MoveAccountCommand, - OrganizationsClient, -} from '@aws-sdk/client-organizations'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -const marshallOptions = { - convertEmptyValues: false, - //overriding default value of false - removeUndefinedValues: true, - convertClassInstanceToMap: false, -}; -const unmarshallOptions = { - wrapNumbers: false, -}; -const translateConfig = { marshallOptions, unmarshallOptions }; -let paginationConfig: DynamoDBDocumentPaginationConfiguration; -let dynamodbClient: DynamoDBClient; -let documentClient: DynamoDBDocumentClient; -let cloudformationClient: CloudFormationClient; -let organizationsClient: OrganizationsClient; - -type ConfigOrganizationalUnitKeys = { - acceleratorKey: string; - awsKey: string; - registered: boolean | undefined; - ignore: boolean; -}; - -type AwsOrganizationalUnitKeys = { - acceleratorKey: string; - awsKey: string; -}; - -type DDBItem = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -}; -type DDBItems = Array; - -let mandatoryAccounts: DDBItems = []; -let workloadAccounts: DDBItems = []; -const awsOuKeys: AwsOrganizationalUnitKeys[] = []; - -/** - * validate-environment - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string; - } - | undefined -> { - const configTableName = event.ResourceProperties['configTableName']; - const commitId = event.ResourceProperties['commitId']; - const stackName = event.ResourceProperties['stackName']; - const solutionId = process.env['SOLUTION_ID']; - - organizationsClient = new OrganizationsClient({ - region: event.ResourceProperties['globalRegion'], - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - - dynamodbClient = new DynamoDBClient({ customUserAgent: solutionId }); - documentClient = DynamoDBDocumentClient.from(dynamodbClient, translateConfig); - cloudformationClient = new CloudFormationClient({ customUserAgent: solutionId }); - paginationConfig = { - client: documentClient, - pageSize: 100, - }; - - switch (event.RequestType) { - case 'Create': - case 'Update': - // if stack rollback is in progress don't do anything - // the stack may have failed as the results of errors - // from this construct - // when rolling back this construct will execute and - // fail again preventing stack rollback - if (await isStackInRollback(stackName)) { - return { - Status: 'SUCCESS', - }; - } - console.log(`Configuration repository commit id ${commitId}`); - - const configAllOuKeys = await getConfigOuKeys(configTableName, commitId); - - mandatoryAccounts = await getConfigFromTableForCommit(configTableName, 'mandatoryAccount', commitId); - workloadAccounts = await getConfigFromTableForCommit(configTableName, 'workloadAccount', commitId); - - const allAccountsFromConfigTable: DDBItems = mandatoryAccounts; - for (const workloadAccount of workloadAccounts) { - allAccountsFromConfigTable.push(workloadAccount); - } - - await getAwsOrganizationalUnitKeys(await getRootId(), ''); - const allOrganizationAccounts = await getOrganizationAccounts(configAllOuKeys); - - const rootId = await getRootId(); - awsOuKeys.push({ - acceleratorKey: 'Root', - awsKey: rootId, - }); - - await moveAccounts(awsOuKeys, allAccountsFromConfigTable, allOrganizationAccounts); - - return { - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - Status: 'SUCCESS', - }; - } -} - -/** - * Function to prepare list of accounts needs to move between org based on account config change - * @param allAwsOuKeys - * @param allAccountsFromConfigTable - * @param allAccountsFromOrganization - */ -async function moveAccounts( - allAwsOuKeys: AwsOrganizationalUnitKeys[], - allAccountsFromConfigTable: DDBItems, - allAccountsFromOrganization: { ouId: string; accountId: string; status: string }[], -): Promise { - const moveAccountList: { - accountName: string; - accountId: string; - destinationParentName: string; - destinationParentId: string; - sourceParentName: string; - sourceParentId: string; - }[] = []; - - for (const account of allAccountsFromConfigTable) { - // This is for new account yet to be created, in this case awsKey will not be present in config table - if (!account['awsKey']) { - console.warn( - `Found account with email ${account['acceleratorKey']} without account id, account yet to be created !!!, ignoring the account for move accounts `, - ); - continue; - } - - const awsOuKey = allAwsOuKeys.find(ouKeyItem => ouKeyItem.acceleratorKey === account['ouName']); - const awsAccountOuKey = allAccountsFromOrganization.find( - accountOuKeyItem => accountOuKeyItem.accountId === account['awsKey'], - ); - - if (!awsOuKey) { - throw new Error( - `Source Ou ID ${account['ouName']} not found for account with email ${account['acceleratorKey']} to perform move account operation`, - ); - } - - if (!awsAccountOuKey) { - throw new Error( - `Account with email ${account['acceleratorKey']} not found to determine source ou id for move account operation`, - ); - } - - if (awsOuKey.awsKey !== awsAccountOuKey.ouId) { - const sourceAwsOuKey = allAwsOuKeys.find(ouKeyItem => ouKeyItem.awsKey === awsAccountOuKey.ouId); - if (!sourceAwsOuKey) { - throw new Error( - `Target Ou ID ${awsAccountOuKey.ouId} not found for account with email ${account['acceleratorKey']} to perform move accounts operation`, - ); - } - moveAccountList.push({ - accountName: account['acceleratorKey'], - accountId: account['awsKey'], - destinationParentName: awsOuKey.acceleratorKey, - destinationParentId: awsOuKey.awsKey, - sourceParentName: sourceAwsOuKey.acceleratorKey, - sourceParentId: awsAccountOuKey.ouId, - }); - } - } - - if (moveAccountList.length === 0) { - console.log(`There are no accounts to move between ou !!!`); - } else { - for (const account of moveAccountList) { - await moveAccount(account); - } - } -} - -/** - * Function to move account between ou - * @param account - */ -async function moveAccount(account: { - accountName: string; - accountId: string; - destinationParentName: string; - destinationParentId: string; - sourceParentName: string; - sourceParentId: string; -}) { - console.log( - `Moving account with email ${account.accountName} from ${account.sourceParentName} ou to ${account.destinationParentName} ou`, - ); - try { - await throttlingBackOff(() => - organizationsClient.send( - new MoveAccountCommand({ - AccountId: account.accountId, - DestinationParentId: account.destinationParentId, - SourceParentId: account.sourceParentId, - }), - ), - ); - console.log( - `Account with email ${account.accountName} successfully moved from ${account.sourceParentName} ou to ${account.destinationParentName} ou`, - ); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'DuplicateAccountException' || - // SDKv3 Error Structure - e.name === 'DuplicateAccountException' - ) { - console.warn(e.name + ': ' + e.message); - } - } -} - -async function getOrganizationAccounts( - organizationalUnitKeys: ConfigOrganizationalUnitKeys[], -): Promise<{ ouId: string; accountId: string; status: string }[]> { - const organizationAccounts: { ouId: string; accountId: string; accountName: string; status: string }[] = []; - for (const ouKey of organizationalUnitKeys) { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListAccountsForParentCommand({ ParentId: ouKey.awsKey, NextToken: nextToken })), - ); - for (const account of page.Accounts ?? []) { - organizationAccounts.push({ - ouId: ouKey.awsKey, - accountId: account.Id!, - accountName: account.Name!, - status: account.Status!, - }); - } - nextToken = page.NextToken; - } while (nextToken); - } - return organizationAccounts; -} - -async function getConfigFromTableForCommit( - configTableName: string, - dataType: string, - commitId: string, -): Promise { - const params: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': dataType, - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - }; - const items: DDBItems = []; - const paginator = paginateQuery(paginationConfig, params); - for await (const page of paginator) { - if (page.Items) { - for (const item of page.Items) { - items.push(item); - } - } - } - return items; -} - -async function isStackInRollback(stackName: string): Promise { - const response = await throttlingBackOff(() => - cloudformationClient.send(new DescribeStacksCommand({ StackName: stackName })), - ); - if (response.Stacks && response.Stacks[0].StackStatus == 'UPDATE_ROLLBACK_IN_PROGRESS') { - return true; - } - return false; -} - -async function getRootId(): Promise { - // get root ou id - let rootId = ''; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListRootsCommand({ NextToken: nextToken })), - ); - for (const item of page.Roots ?? []) { - if (item.Name === 'Root' && item.Id && item.Arn) { - rootId = item.Id; - } - } - nextToken = page.NextToken; - } while (nextToken); - return rootId; -} - -async function getConfigOuKeys(configTableName: string, commitId: string): Promise { - const organizationParams: QueryCommandInput = { - TableName: configTableName, - KeyConditionExpression: 'dataType = :hkey', - ExpressionAttributeValues: { - ':hkey': 'organization', - ':commitId': commitId, - }, - FilterExpression: 'contains (commitId, :commitId)', - ProjectionExpression: 'acceleratorKey, awsKey, registered, dataBag', - }; - const organizationResponse = await throttlingBackOff(() => documentClient.send(new QueryCommand(organizationParams))); - const ouKeys: ConfigOrganizationalUnitKeys[] = []; - if (organizationResponse.Items) { - for (const item of organizationResponse.Items) { - const ouConfig = JSON.parse(item['dataBag']); - const ignored = ouConfig['ignore'] ?? false; - - if (item['awsKey']) { - ouKeys.push({ - acceleratorKey: item['acceleratorKey'], - awsKey: item['awsKey'], - registered: item['registered'] ?? undefined, - ignore: ignored, - }); - } - } - } - //get root ou key - const rootId = await getRootId(); - ouKeys.push({ - acceleratorKey: 'Root', - awsKey: rootId, - registered: true, - ignore: false, - }); - return ouKeys; -} - -async function getAwsOrganizationalUnitKeys(ouId: string, path: string) { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListOrganizationalUnitsForParentCommand({ ParentId: ouId, NextToken: nextToken })), - ); - for (const ou of page.OrganizationalUnits ?? []) { - awsOuKeys.push({ acceleratorKey: `${path}${ou.Name!}`, awsKey: ou.Id! }); - await getAwsOrganizationalUnitKeys(ou.Id!, `${path}${ou.Name!}/`); - } - nextToken = page.NextToken; - } while (nextToken); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/package.json deleted file mode 100644 index c0c68a3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-move-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-accounts.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-accounts.ts deleted file mode 100644 index 845b285..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/move-accounts.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = require('path'); - -export interface MoveAccountsProps { - /** - * Global region - */ - readonly globalRegion: string; - /** - * Config Table - */ - readonly configTable: cdk.aws_dynamodb.ITable; - /** - * Config commit Id - */ - readonly commitId: string; - /** - * Management Account Id - */ - readonly managementAccountId: string; - /** - * Custom resource lambda key to encrypt environment variables, when undefined default AWS managed key will be used - */ - readonly lambdaKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogsKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly cloudWatchLogRetentionInDays: number; - /** - * Control Tower enabled flag - */ - readonly controlTower: boolean; -} - -/** - * Class to initialize Organization - */ -export class MoveAccounts extends Construct { - public readonly id: string; - - public constructor(scope: Construct, id: string, props: MoveAccountsProps) { - super(scope, id); - - if (props.controlTower) { - this.id = 'NoOpMoveAccountsFunction'; - return; - } - - const providerLambda = new cdk.aws_lambda.Function(this, 'MoveAccountsFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'move-account/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.minutes(15), - description: 'Moves accounts to conform account config', - environmentEncryption: props.lambdaKmsKey, - }); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'OrganizationsAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'organizations:ListAccountsForParent', - 'organizations:ListRoots', - 'organizations:ListAccountsForParent', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:MoveAccount', - ], - resources: ['*'], - }), - ); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'DynamodbTableAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['dynamodb:Query'], - resources: [props.configTable.tableArn], - }), - ); - - providerLambda.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudformationAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['cloudformation:DescribeStacks'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:cloudformation:${cdk.Stack.of(this).region}:${ - props.managementAccountId - }:stack/${cdk.Stack.of(this).stackName}*`, - ], - }), - ); - - // Custom resource lambda log group - const logGroup = new cdk.aws_logs.LogGroup(this, `${providerLambda.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${providerLambda.functionName}`, - retention: props.cloudWatchLogRetentionInDays, - encryptionKey: props.cloudWatchLogsKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - const provider = new cdk.custom_resources.Provider(this, 'MoveAccountsProvider', { - onEventHandler: providerLambda, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::MoveAccounts', - serviceToken: provider.serviceToken, - properties: { - globalRegion: props.globalRegion, - configTableName: props.configTable.tableName, - commitId: props.commitId, - stackName: cdk.Stack.of(this).stackName, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - // Ensure that the LogGroup is created by Cloudformation prior to Lambda execution - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/organizational-units.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/organizational-units.ts deleted file mode 100644 index f22302d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/organizational-units.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = require('path'); - -/** - * Initialized OrganizationalUnit properties - */ -export interface OrganizationalUnitsProps { - readonly acceleratorConfigTable: cdk.aws_dynamodb.Table; - readonly commitId: string; - readonly controlTowerEnabled: boolean; - readonly organizationsEnabled: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to initialize OrganizationalUnits - */ -export class OrganizationalUnits extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: OrganizationalUnitsProps) { - super(scope, id); - - const provider = cdk.CustomResourceProvider.getOrCreateProvider( - this, - 'Custom::OrganizationsCreateOrganizationalUnits', - { - codeDirectory: path.join(__dirname, 'create-organizational-units/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'organizations', - Effect: 'Allow', - Action: [ - 'organizations:CreateOrganizationalUnit', - 'organizations:ListOrganizationalUnitsForParent', - 'organizations:ListRoots', - 'organizations:UpdateOrganizationalUnit', - ], - Resource: '*', - }, - { - Sid: 'dynamodb', - Effect: 'Allow', - Action: ['dynamodb:UpdateItem', 'dynamodb:Query'], - Resource: [props.acceleratorConfigTable.tableArn], - }, - ], - }, - ); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::CreateOrganizationalUnits', - serviceToken: provider.serviceToken, - properties: { - configTableName: props.acceleratorConfigTable.tableName, - commitId: props.commitId, - controlTowerEnabled: props.controlTowerEnabled, - organizationsEnabled: props.organizationsEnabled, - partition: cdk.Aws.PARTITION, - uuid: uuidv4(), - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy-attachment.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy-attachment.ts deleted file mode 100644 index 40bddf2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy-attachment.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { PolicyType } from './policy'; -import { Construct } from 'constructs'; -import { createHash } from 'crypto'; -import { v4 as uuidv4 } from 'uuid'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -const logger = createLogger(['constructs-organization-policy-attachment']); - -const path = require('path'); - -/** - * Initialized Policy properties - */ -export interface PolicyAttachmentProps { - readonly policyId: string; - readonly targetId?: string; - readonly type: PolicyType; - readonly strategy?: string; - readonly configPolicyNames: string[]; - readonly acceleratorPrefix: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to attach a Policy to an Organization Unit or Account - */ -export class PolicyAttachment extends Construct { - public readonly id: string; - public readonly policyId: string; - public readonly targetId: string | undefined; - public readonly type: PolicyType; - public readonly strategy?: string; - - constructor(scope: Construct, id: string, props: PolicyAttachmentProps) { - super(scope, id); - - this.policyId = props.policyId; - this.targetId = props.targetId; - this.type = props.type; - this.strategy = props.strategy; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::OrganizationsAttachPolicy', { - codeDirectory: path.join(__dirname, 'attach-policy/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'organizations:AttachPolicy', - 'organizations:DetachPolicy', - 'organizations:ListPoliciesForTarget', - 'organizations:ListTagsForResource', - ], - Resource: '*', - }, - ], - }); - - let uuid: string; - const attachArray = [props.policyId, props.targetId ?? '', ...props.configPolicyNames].toString(); - const attachHash = createHash('md5').update(attachArray).digest('hex'); - // Boolean to force update - const forceUpdate = process.env['ACCELERATOR_FORCED_UPDATE'] - ? process.env['ACCELERATOR_FORCED_UPDATE'] === 'true' - : false; - - if (forceUpdate) { - logger.warn(`ACCELERATOR_FORCED_UPDATE env variable is set. Forcing an update.`); - uuid = uuidv4(); - } else { - uuid = attachHash; - } - - // - // Custom Resource definition. We want this resource to be evaluated on - // every CloudFormation update, so we generate a new uuid to force - // re-evaluation. - // - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::AttachPolicy', - serviceToken: provider.serviceToken, - properties: { - partition: cdk.Aws.PARTITION, - uuid, - policyId: props.policyId, - targetId: props.targetId, - type: props.type, - strategy: props.strategy, - configPolicyNames: props.configPolicyNames, - policyTagKey: `${props.acceleratorPrefix}Managed`, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy.ts deleted file mode 100644 index f5114cc..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/policy.ts +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as assets from 'aws-cdk-lib/aws-s3-assets'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { createHash } from 'crypto'; -import { readFileSync } from 'fs'; -import { v4 as uuidv4 } from 'uuid'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -const logger = createLogger(['constructs-organization-policy']); - -export enum PolicyType { - AISERVICES_OPT_OUT_POLICY = 'AISERVICES_OPT_OUT_POLICY', - BACKUP_POLICY = 'BACKUP_POLICY', - SERVICE_CONTROL_POLICY = 'SERVICE_CONTROL_POLICY', - TAG_POLICY = 'TAG_POLICY', -} - -export interface Tag { - /** - * The key identifier, or name, of the tag. - */ - Key: string | undefined; - - /** - * The string value that's associated with the key of the tag. You can set the value of a - * tag to an empty string, but you can't set the value of a tag to null. - */ - Value: string | undefined; -} - -/** - * Initialized Policy properties - */ -export interface PolicyProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The friendly name of the policy - */ - readonly name: string; - /** - * The AWS partition the policy will be created in - */ - readonly partition: string; - /** - * The path of the file for the policy - */ - readonly path: string; - /** - * The type of policy to create - */ - readonly type: PolicyType; - /** - * The SCP strategy - "allow-list" or "deny-list"The type of policy to create - */ - readonly strategy?: string; - /** - * Accelerator prefix - */ - readonly acceleratorPrefix: string; - /** - * An optional description of the policy - */ - readonly description?: string; - /** - * An optional list of tags for the policy - */ - readonly tags?: Tag[]; -} - -/** - * Class to initialize Policy - */ -export class Policy extends Construct { - public readonly id: string; - public readonly path: string; - public readonly name: string; - public readonly description?: string; - public readonly type: PolicyType; - public readonly strategy?: string; - public readonly tags?: Tag[]; - - constructor(scope: Construct, id: string, props: PolicyProps) { - super(scope, id); - - this.path = props.path; - this.name = props.name; - this.description = props.description || ''; - this.type = props.type; - this.strategy = props.strategy; - this.tags = props.tags || []; - - // - // Bundle the policy file. This will be available as an asset in S3 - // - const asset = new assets.Asset(this, 'Policy', { - path: props.path, - }); - - let uuid: string; - // generating md5 hash to allow for file changes and only trigger change when file is updated - const fileHash = createHash('md5').update(readFileSync(props.path)).digest('hex'); - - // Boolean to force update - const forceUpdate = process.env['ACCELERATOR_FORCED_UPDATE'] - ? process.env['ACCELERATOR_FORCED_UPDATE'] === 'true' - : false; - - if (forceUpdate) { - logger.warn(`ACCELERATOR_FORCED_UPDATE env variable is set. Forcing an update.`); - uuid = uuidv4(); - } else { - uuid = fileHash; - } - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::OrganizationsCreatePolicy', { - codeDirectory: path.join(__dirname, 'create-policy/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - description: 'Organizations create policy', - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'organizations:CreatePolicy', - 'organizations:DeletePolicy', - 'organizations:DetachPolicy', - 'organizations:ListPolicies', - 'organizations:ListTargetsForPolicy', - 'organizations:UpdatePolicy', - 'organizations:TagResource', - ], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['s3:GetObject'], - Resource: cdk.Stack.of(this).formatArn({ - service: 's3', - region: '', - account: '', - resource: asset.s3BucketName, - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: '*', - }), - }, - ], - }); - - // - // Custom Resource definition. We want this resource to be evaluated on - // every CloudFormation update, so we generate a new uuid to force - // re-evaluation. - // - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::CreatePolicy', - serviceToken: provider.serviceToken, - properties: { - bucket: asset.s3BucketName, - key: asset.s3ObjectKey, - partition: props.partition, - policyTagKey: `${props.acceleratorPrefix}Managed`, - uuid, - name: props.name, - description: props.description, - type: props.type, - strategy: props.strategy, - tags: props.tags, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator.ts deleted file mode 100644 index 4bc1f87..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * RegisterDelegatedAdministratorProps properties - */ -export interface RegisterDelegatedAdministratorProps { - readonly servicePrincipal: string; - readonly accountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to Register a Delegated Administrator - * - * NOTE: This construct should only be used if the native service does not have - * it's own API or method to establish a delegated administrator - */ -export class RegisterDelegatedAdministrator extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: RegisterDelegatedAdministratorProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::OrganizationsRegisterDelegatedAdministrator'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'register-delegated-administrator/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['organizations:DeregisterDelegatedAdministrator', 'organizations:RegisterDelegatedAdministrator'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - partition: cdk.Aws.PARTITION, - servicePrincipal: props.servicePrincipal, - accountId: props.accountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/index.ts deleted file mode 100644 index 33b76b2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * register-delegated-administrator - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const servicePrincipal: string = event.ResourceProperties['servicePrincipal']; - const accountId: string = event.ResourceProperties['accountId']; - const partition = event.ResourceProperties['partition']; - const solutionId = process.env['SOLUTION_ID']; - - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1', customUserAgent: solutionId }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1', customUserAgent: solutionId }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - - switch (event.RequestType) { - case 'Create': - case 'Update': - try { - await throttlingBackOff(() => - organizationsClient - .registerDelegatedAdministrator({ ServicePrincipal: servicePrincipal, AccountId: accountId }) - .promise(), - ); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'AccountAlreadyRegisteredException' || - // SDKv3 Error Structure - e.name === 'AccountAlreadyRegisteredException' - ) { - console.warn(e.name + ': ' + e.message); - return; - } - } - - return { - PhysicalResourceId: servicePrincipal, - Status: 'SUCCESS', - }; - - case 'Delete': - await throttlingBackOff(() => - organizationsClient - .deregisterDelegatedAdministrator({ ServicePrincipal: servicePrincipal, AccountId: accountId }) - .promise(), - ); - - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/package.json deleted file mode 100644 index 37c4fb4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-organizations-register-delegated-administrator", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/register-delegated-administrator/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/validate-scp-count.ts b/source/packages/@aws-accelerator/constructs/lib/aws-organizations/validate-scp-count.ts deleted file mode 100644 index 494f09f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-organizations/validate-scp-count.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -export type accountItem = { - accountId: string; - name: string; -}; -export type orgItem = { - id: string; - name: string; -}; -type validateScpItem = { - orgEntity: string; - orgEntityType: string; - orgEntityId: string; - appliedScpName: string[]; -}; -export interface ValidateScpCountProps { - organizationUnits: orgItem[]; - accounts: accountItem[]; - scps: validateScpItem[]; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class ValidateScpCount extends cdk.Resource { - constructor(scope: Construct, id: string, props: ValidateScpCountProps) { - super(scope, id); - - const VALIDATE_SCP_COUNT = 'Custom::ValidateScpCount'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, VALIDATE_SCP_COUNT, { - codeDirectory: path.join(__dirname, 'list-policy-for-target/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'organizations:DescribeOrganization', - 'organizations:ListRoots', - 'organizations:ListPoliciesForTarget', - 'organizations:ListTargetsForPolicy', - 'organizations:DescribePolicy', - 'organizations:DescribeAccount', - 'organizations:ListPolicies', - 'organizations:ListAccountsForParent', - 'organizations:ListAccounts', - 'organizations:DescribeOrganizationalUnit', - 'organizations:ListParents', - 'organizations:ListOrganizationalUnitsForParent', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: VALIDATE_SCP_COUNT, - serviceToken: provider.serviceToken, - properties: { - organizationUnits: props.organizationUnits, - accounts: props.accounts, - scps: props.scps, - partition: cdk.Stack.of(this).partition, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization.ts deleted file mode 100644 index b2f78a2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -export interface EnableSharingWithAwsOrganizationProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to initialize Policy - */ -export class EnableSharingWithAwsOrganization extends Construct { - readonly id: string; - - constructor(scope: Construct, id: string, props: EnableSharingWithAwsOrganizationProps) { - super(scope, id); - - const ENABLE_SHARING_WITH_AWS_ORGANIZATION_TYPE = 'Custom::EnableSharingWithAwsOrganization'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, ENABLE_SHARING_WITH_AWS_ORGANIZATION_TYPE, { - codeDirectory: path.join(__dirname, 'enable-sharing-with-aws-organization/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'ram:EnableSharingWithAwsOrganization', - 'iam:CreateServiceLinkedRole', - 'organizations:EnableAWSServiceAccess', - 'organizations:ListAWSServiceAccessForOrganization', - 'organizations:DescribeOrganization', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: ENABLE_SHARING_WITH_AWS_ORGANIZATION_TYPE, - serviceToken: provider.serviceToken, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/index.ts deleted file mode 100644 index dc58e8c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; -/** - * enable-sharing-with-organization - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const client = new AWS.RAM({ customUserAgent: process.env['SOLUTION_ID'] }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - await throttlingBackOff(() => client.enableSharingWithAwsOrganization({}).promise()); - return { - PhysicalResourceId: `ram-enable-sharing-with-aws-organization`, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/package.json deleted file mode 100644 index 6c145db..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ram-enable-sharing-with-aws-organization", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/enable-sharing-with-aws-organization/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/index.ts deleted file mode 100644 index 8d88fb2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * get-resource-share-item - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Data: { - arn: string; - }; - Status: string; - } - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const ramClient = new AWS.RAM({ customUserAgent: process.env['SOLUTION_ID'] }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const resourceOwner = event.ResourceProperties['resourceOwner']; - const resourceShareArn = event.ResourceProperties['resourceShareArn']; - const resourceType = event.ResourceProperties['resourceType']; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - ramClient - .listResources({ resourceShareArns: [resourceShareArn], resourceType, resourceOwner, nextToken }) - .promise(), - ); - // Return the first item found with the specified filters - if (page.resources && page.resources.length > 0) { - const item = page.resources[0]; - if (item.arn) { - console.log(item.arn); - return { - PhysicalResourceId: item.arn.split('/')[1], - Data: { - arn: item.arn, - }, - Status: 'SUCCESS', - }; - } - } - nextToken = page.nextToken; - } while (nextToken); - - throw new Error(`Resource share item not found`); - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/package.json deleted file mode 100644 index dfef454..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ram-get-resource-share-item", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share-item/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/index.ts deleted file mode 100644 index 4d12c56..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/index.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * get-resource-share - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const ramClient = new AWS.RAM({ customUserAgent: process.env['SOLUTION_ID'] }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const resourceOwner = event.ResourceProperties['resourceOwner']; - const owningAccountId = event.ResourceProperties['owningAccountId']; - const name = event.ResourceProperties['name']; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => ramClient.getResourceShareInvitations({ nextToken }).promise()); - for (const resourceShareInvitation of page.resourceShareInvitations ?? []) { - if ( - resourceShareInvitation.status == 'PENDING' && - resourceShareInvitation.senderAccountId == owningAccountId && - resourceShareInvitation.resourceShareName === name - ) { - console.log(resourceShareInvitation); - await throttlingBackOff(() => - ramClient - .acceptResourceShareInvitation({ - resourceShareInvitationArn: resourceShareInvitation.resourceShareInvitationArn!, - }) - .promise(), - ); - - const found = await validateResourceShare(ramClient, resourceOwner, owningAccountId, name); - - if (found == false) { - throw new Error(`Resource share ${name} not accepted successfully`); //share not found after multiple attempts - } - } - nextToken = page.nextToken; - } - } while (nextToken); - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - ramClient.getResourceShares({ resourceOwner, nextToken: nextToken }).promise(), - ); - for (const resourceShare of page.resourceShares ?? []) { - if (resourceShare.owningAccountId == owningAccountId && resourceShare.name === name) { - console.log(resourceShare); - if (resourceShare.resourceShareArn) { - return { - PhysicalResourceId: resourceShare.resourceShareArn.split('/')[1], - Status: 'SUCCESS', - }; - } - } - } - nextToken = page.nextToken; - } while (nextToken); - - throw new Error(`Resource share ${name} not found`); - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -async function validateResourceShare( - ramClient: AWS.RAM, - resourceOwner: string, - owningAccountId: string, - name: string, -): Promise { - let found = false; - let counter = 5; - do { - const nextTokenShare: string | undefined = undefined; - const pageResoureShares = await throttlingBackOff(() => - ramClient.getResourceShares({ resourceOwner, nextToken: nextTokenShare }).promise(), - ); - if (pageResoureShares.resourceShares === undefined || pageResoureShares.resourceShares.length == 0) { - await delay(5000); //delay 5 seconds and try again, no shares found - console.log('resource share not found, waiting 5 seconds'); - counter = counter - 1; - continue; - } else { - for (const resourceShare of pageResoureShares.resourceShares ?? []) { - if (resourceShare.owningAccountId == owningAccountId && resourceShare.name === name) { - found = true; - break; - } - } - } - await delay(5000); //delay 5 seconds and try again, share not found - console.log('resource share not found, waiting 5 seconds'); - counter = counter - 1; - } while (found == false && counter >= 0); - - return found; -} - -async function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/package.json deleted file mode 100644 index 812645c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ram-get-resource-share", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/get-resource-share/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/resource-share.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/resource-share.ts deleted file mode 100644 index f77e1d3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/resource-share.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as ram from 'aws-cdk-lib/aws-ram'; -import { pascalCase } from 'change-case'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = require('path'); - -export interface IResourceShare extends cdk.IResource { - /** - * The identifier of the resource share - * - * @attribute - */ - readonly resourceShareId: string; - - /** - * The name of the resource share - * - * @attribute - */ - readonly resourceShareName: string; - - /** - * The ARN of the resource share - * - * @attribute - */ - readonly resourceShareArn: string; - - /** - * The owner type of the resource share - * - * @attribute - */ - readonly resourceShareOwner: ResourceShareOwner; -} - -export enum ResourceShareOwner { - SELF = 'SELF', - OTHER_ACCOUNTS = 'OTHER-ACCOUNTS', -} - -export interface ResourceShareLookupOptions { - readonly resourceShareOwner: ResourceShareOwner; - readonly resourceShareName: string; - readonly owningAccountId?: string; -} - -export interface ResourceShareProps { - readonly name: string; - readonly allowExternalPrincipals?: boolean; - readonly permissionArns?: string[]; - readonly principals?: string[]; - readonly resourceArns?: string[]; -} - -export interface ResourceShareItemLookupOptions { - readonly resourceShare: IResourceShare; - readonly resourceShareItemType: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} -export interface IResourceShareItem extends cdk.IResource { - readonly resourceShareItemArn: string; - - /** - * The identifier of the shared resource item - * - * @attribute - */ - readonly resourceShareItemId: string; -} - -export abstract class ResourceShareItem extends cdk.Resource implements IResourceShareItem { - readonly resourceShareItemArn: string = ''; - readonly resourceShareItemId: string = ''; - readonly resourceShareItemType: string = ''; - - public static fromLookup(scope: Construct, id: string, options: ResourceShareItemLookupOptions): IResourceShareItem { - class Import extends cdk.Resource implements IResourceShareItem { - public readonly resourceShareItemArn: string; - public readonly resourceShareItemId: string; - - constructor(scope: Construct, id: string) { - super(scope, id); - - const GET_RESOURCE_SHARE_ITEM = 'Custom::GetResourceShareItem'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, GET_RESOURCE_SHARE_ITEM, { - codeDirectory: path.join(__dirname, 'get-resource-share-item/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ram:ListResources'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: GET_RESOURCE_SHARE_ITEM, - serviceToken: provider.serviceToken, - properties: { - resourceOwner: options.resourceShare.resourceShareOwner, - resourceShareArn: options.resourceShare.resourceShareArn, - resourceType: options.resourceShareItemType, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: options.logRetentionInDays, - encryptionKey: options.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.resourceShareItemId = resource.ref; - this.resourceShareItemArn = resource.getAtt('arn').toString(); - } - } - return new Import(scope, id); - } -} - -/** - * Creates a Resource Share - */ -export class ResourceShare extends cdk.Resource implements IResourceShare { - public static fromLookup(scope: Construct, id: string, options: ResourceShareLookupOptions): IResourceShare { - class Import extends cdk.Resource implements IResourceShare { - public readonly resourceShareId: string; - public readonly resourceShareName = options.resourceShareName; - public readonly resourceShareOwner = options.resourceShareOwner; - public readonly resourceShareArn; - - constructor(scope: Construct, id: string) { - super(scope, id); - const GET_RESOURCE_SHARE = 'Custom::GetResourceShare'; - - // - // Get the Resource Share Definition (by name) - // - const cr = cdk.CustomResourceProvider.getOrCreateProvider(this, GET_RESOURCE_SHARE, { - codeDirectory: path.join(__dirname, 'get-resource-share/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ram:AcceptResourceShareInvitation', 'ram:GetResourceShareInvitations', 'ram:GetResourceShares'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: GET_RESOURCE_SHARE, - serviceToken: cr.serviceToken, - properties: { - name: options.resourceShareName, - resourceOwner: options.resourceShareOwner, - owningAccountId: options.owningAccountId, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - this.resourceShareId = resource.ref; - - this.resourceShareArn = cdk.Stack.of(this).formatArn({ - service: 'ram', - account: options.owningAccountId, - resource: 'resource-share', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: this.resourceShareId, - }); - } - } - return new Import(scope, id); - } - - public readonly resourceShareId: string; - public readonly resourceShareName: string; - public readonly resourceShareArn: string; - public readonly resourceShareOwner: ResourceShareOwner; - - constructor(scope: Construct, id: string, props: ResourceShareProps) { - super(scope, id); - - const resource = new ram.CfnResourceShare(this, pascalCase(`${props.name}ResourceShare`), { - name: props.name, - allowExternalPrincipals: props.allowExternalPrincipals, - permissionArns: props.permissionArns, - principals: props.principals, - resourceArns: props.resourceArns, - }); - - this.resourceShareId = resource.ref; - this.resourceShareName = props.name; - this.resourceShareArn = resource.attrArn; - this.resourceShareOwner = ResourceShareOwner.SELF; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags.ts deleted file mode 100644 index 967a967..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -export interface ShareSubnetTagsProps { - readonly vpcTags?: cdk.CfnTag[]; - readonly subnetTags: cdk.CfnTag[]; - readonly sharedSubnetId: string; - readonly owningAccountId: string; - /** - * Friendly name for the shared vpc - */ - readonly vpcName: string; - /** - * Friendly name for the shared subnet - */ - readonly subnetName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly resourceLoggingKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Accelerator SSM parameter Prefix - */ - readonly acceleratorSsmParamPrefix: string; -} - -/** - * Class to initialize Policy - */ -export class ShareSubnetTags extends Construct { - readonly id: string; - - constructor(scope: Construct, id: string, props: ShareSubnetTagsProps) { - super(scope, id); - - const SHARE_SUBNET_TAGS_TYPE = 'Custom::ShareSubnetTags'; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, SHARE_SUBNET_TAGS_TYPE, { - codeDirectory: path.join(__dirname, 'share-subnet-tags/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ec2:DeleteTags', 'ec2:CreateTags'], - Resource: [ - `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:*:subnet/*`, - `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:*:vpc/*`, - ], - }, - { - Effect: 'Allow', - Action: ['ec2:DescribeTags', 'ec2:DescribeVpcs', 'ec2:DescribeSubnets'], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['ssm:GetParameter', 'ssm:PutParameter', 'ssm:DeleteParameter'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - properties: { - vpcTags: props.vpcTags, - subnetTags: props.subnetTags, - sharedSubnetId: props.sharedSubnetId, - sharedSubnetName: props.subnetName, - vpcName: props.vpcName, - acceleratorSsmParamPrefix: props.acceleratorSsmParamPrefix, - }, - resourceType: SHARE_SUBNET_TAGS_TYPE, - serviceToken: provider.serviceToken, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.resourceLoggingKmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/index.ts deleted file mode 100644 index 4e407eb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/index.ts +++ /dev/null @@ -1,295 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { - SSMClient, - GetParameterCommand, - DeleteParameterCommand, - PutParameterCommand, - ParameterNotFound, -} from '@aws-sdk/client-ssm'; -import { - EC2Client, - DescribeTagsCommand, - CreateTagsCommand, - DeleteTagsCommand, - DescribeSubnetsCommand, - DeleteTagsCommandInput, - TagDescription, -} from '@aws-sdk/client-ec2'; -import { CloudFormationCustomResourceEvent } from '../../lza-custom-resource'; - -const solutionId = process.env['SOLUTION_ID']; - -const ec2Client = new EC2Client({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); -const ssmClient = new SSMClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - -interface Tag { - readonly Key: string | undefined; - readonly Value: string | undefined; -} - -/** - * share-subnet-tags - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(JSON.stringify(event)); - - const vpcTags = convertTags(event.ResourceProperties['vpcTags'] || []); - const subnetTags = convertTags(event.ResourceProperties['subnetTags'] || []); - const sharedSubnetId: string = event.ResourceProperties['sharedSubnetId']; - const sharedSubnetName: string = event.ResourceProperties['sharedSubnetName']; - const vpcName: string = event.ResourceProperties['vpcName']; - const ssmParamNamePrefix = event.ResourceProperties['acceleratorSsmParamPrefix']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - // Create Shared Subnet SSM parameter - await createParameter( - ssmClient, - `${ssmParamNamePrefix}/shared/network/vpc/${vpcName}/subnet/${sharedSubnetName}/id`, - 'Shared subnet', - sharedSubnetId, - ); - await updateSubnetTags(subnetTags, sharedSubnetId); - await updateVpcTags(vpcTags, sharedSubnetId, ssmClient, vpcName, ssmParamNamePrefix); - return { - PhysicalResourceId: sharedSubnetId, - Status: 'SUCCESS', - }; - - case 'Delete': - // Delete Shared Subnet SSM parameter - await deleteParameter( - ssmClient, - `${ssmParamNamePrefix}/shared/network/vpc/${vpcName}/subnet/${sharedSubnetName}/id`, - ); - await deleteTags(sharedSubnetId); - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -async function parameterExists(ssmClient: SSMClient, name: string): Promise { - console.log(`Checking if parameter ${name} exists`); - try { - await throttlingBackOff(() => ssmClient.send(new GetParameterCommand({ Name: name }))); - console.log(`Parameter ${name} exists`); - return true; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e instanceof ParameterNotFound) { - console.warn(e.name + ': ' + e.message); - return false; - } else { - throw new Error(e.name + ': ' + e.message); - } - } -} - -async function createParameter(ssmClient: SSMClient, name: string, description: string, value: string): Promise { - console.log(`Creating parameter ${name}`); - if (!(await parameterExists(ssmClient, name))) { - await throttlingBackOff(() => - ssmClient.send( - new PutParameterCommand({ - Name: name, - Description: description, - Value: value, - Type: 'String', - Overwrite: true, - }), - ), - ); - } -} - -async function deleteParameter(ssmClient: SSMClient, name: string): Promise { - console.log(`Deleting parameter ${name}`); - if (!(await parameterExists(ssmClient, name))) { - await throttlingBackOff(() => - ssmClient.send( - new DeleteParameterCommand({ - Name: name, - }), - ), - ); - } -} - -async function updateSubnetTags(newSubnetTags: Tag[], subnetId: string) { - console.info('updateSubnetTags'); - const existingSubnetTags = await getResourceTags(subnetId); - const tagsToDelete = getTagsToDelete(existingSubnetTags, newSubnetTags); - const tagsToAdd = getTagsToAdd(existingSubnetTags, newSubnetTags); - const tagsToUpdate = getTagsToUpdate(existingSubnetTags, newSubnetTags); - const tagsToCreateAndUpdate = [...tagsToAdd, ...tagsToUpdate]; - - if (tagsToDelete.length > 0) { - console.info(`Deleting subnet tags for subnet ${subnetId}`); - console.info(JSON.stringify(tagsToDelete, null, 2)); - await deleteTags(subnetId, tagsToDelete); - } - - if (tagsToCreateAndUpdate.length > 0) { - console.info(`Adding and updating subnet tags for subnet ${subnetId}`); - console.info(JSON.stringify(tagsToCreateAndUpdate, null, 2)); - await createTags(subnetId, tagsToCreateAndUpdate); - } -} - -async function updateVpcTags( - newVpcTags: Tag[], - subnetId: string, - ssmClient: SSMClient, - vpcName: string, - ssmParamNamePrefix: string, -) { - console.info('updateVpcTags'); - const describeSubnetsResponse = await throttlingBackOff(() => - ec2Client.send(new DescribeSubnetsCommand({ Filters: [{ Name: 'subnet-id', Values: [subnetId] }] })), - ); - console.debug('describeSubnetsResponse: ', describeSubnetsResponse); - - if (describeSubnetsResponse.Subnets!.length > 0) { - console.log(`Found subnetId: ${subnetId} with vpcId: ${describeSubnetsResponse.Subnets?.[0].VpcId}`); - } else { - console.error(`Could not find shared subnetId ${subnetId}`); - return; - } - - const vpcId = describeSubnetsResponse.Subnets?.[0].VpcId; - if (vpcId) { - console.debug('VPCId: ', vpcId); - // Create Shared VPC SSM parameter - await createParameter(ssmClient, `${ssmParamNamePrefix}/shared/network/vpc/${vpcName}/id`, 'Shared vpc', vpcId); - } else { - console.error(`Could not locate vpc for shared subnetId ${subnetId}`); - return; - } - - const existingVpcTags = await getResourceTags(vpcId); - const tagsToDelete = getTagsToDelete(existingVpcTags, newVpcTags); - const tagsToAdd = getTagsToAdd(existingVpcTags, newVpcTags); - const tagsToUpdate = getTagsToUpdate(existingVpcTags, newVpcTags); - const tagsToCreateAndUpdate = [...tagsToAdd, ...tagsToUpdate]; - - if (tagsToDelete.length > 0) { - console.info(`Deleting vpc tags for vpc ${vpcId}`); - console.info(JSON.stringify(tagsToDelete, null, 2)); - await deleteTags(vpcId, tagsToDelete); - } - - if (tagsToCreateAndUpdate.length > 0) { - console.log(`Adding and updating vpc tags for VPC ${vpcId}`); - console.log(JSON.stringify(tagsToCreateAndUpdate, null, 2)); - await createTags(vpcId, tagsToCreateAndUpdate); - } -} - -async function deleteTags(itemId: string, tagsToDelete?: Tag[]) { - const deleteTagRequest: DeleteTagsCommandInput = { - Resources: [itemId], - Tags: tagsToDelete, - }; - const deleteTagsResponse = await throttlingBackOff(() => ec2Client.send(new DeleteTagsCommand(deleteTagRequest))); - console.debug('Delete Tags response: ', deleteTagsResponse); -} - -async function createTags(itemId: string, tags: Tag[]) { - const createTagsResponse = await throttlingBackOff(() => - ec2Client.send(new CreateTagsCommand({ Resources: [itemId], Tags: tags })), - ); - console.debug('createTagsResponse: ', createTagsResponse); -} - -function convertTags(tags: []): Tag[] { - const convertedTags: Tag[] = []; - for (const tag of tags) { - convertedTags.push({ Key: tag['key'], Value: tag['value'] }); - } - return convertedTags; -} - -async function getResourceTags(resourceId: string): Promise { - const tags: Tag[] = []; - let nextToken: string | undefined = undefined; - do { - const describeTagsResponse = await throttlingBackOff(() => - ec2Client.send( - new DescribeTagsCommand({ Filters: [{ Name: 'resource-id', Values: [resourceId] }], NextToken: nextToken }), - ), - ); - console.log(describeTagsResponse); - if (describeTagsResponse.Tags) { - const tagKeyValueList = describeTagsResponse.Tags.map((tag: TagDescription) => { - return { - Key: tag.Key, - Value: tag.Value, - }; - }); - tags.push(...tagKeyValueList); - } - nextToken = describeTagsResponse.NextToken! ?? undefined; - } while (nextToken); - return tags; -} - -function getTagsToDelete(currentTags: Tag[], newTags: Tag[]): Tag[] { - const tagsToDelete: Tag[] = []; - for (const currentTag of currentTags) { - const foundTag: Tag | undefined = newTags.find(newTag => newTag.Key === currentTag.Key); - if (!foundTag) { - tagsToDelete.push(currentTag); - } - } - return tagsToDelete; -} - -function getTagsToAdd(currentTags: Tag[], newTags: Tag[]): Tag[] { - const tagsToAdd: Tag[] = []; - for (const newTag of newTags) { - const foundTag: Tag | undefined = currentTags.find(currentTag => currentTag.Key === newTag.Key); - if (!foundTag) { - tagsToAdd.push(newTag); - } - } - return tagsToAdd; -} - -function getTagsToUpdate(currentTags: Tag[], newTags: Tag[]): Tag[] { - const tagsToUpdate: Tag[] = []; - for (const newTag of newTags) { - const foundTag = currentTags.find(currentTag => currentTag.Key === newTag.Key); - if (foundTag?.Value !== newTag.Value) { - tagsToUpdate.push(newTag); - } - } - return tagsToUpdate; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/package.json deleted file mode 100644 index 04048aa..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ram-share-subnet-tags", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-sdk/client-ec2": "3.411.0", - "@aws-sdk/client-ssm": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ram/share-subnet-tags/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/endpoint-addresses.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/endpoint-addresses.ts deleted file mode 100644 index 7291b98..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/endpoint-addresses.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IEndpointAddresses extends cdk.IResource { - /** - * The IP addresses of the endpoint. - */ - readonly ipAddresses: cdk.Reference; -} - -export interface EndpointAddressesProps { - /** - * The ID of the Route 53 Resolver endpoint. - */ - readonly endpointId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class EndpointAddresses extends cdk.Resource implements IEndpointAddresses { - public ipAddresses: cdk.Reference; - - constructor(scope: Construct, id: string, props: EndpointAddressesProps) { - super(scope, id); - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::ResolverEndpointAddresses', { - codeDirectory: path.join(__dirname, 'get-endpoint-addresses/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['route53resolver:ListResolverEndpointIpAddresses'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::ResolverEndpointAddresses', - serviceToken: provider.serviceToken, - properties: { - endpointId: props.endpointId, - region: cdk.Stack.of(this).region, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.ipAddresses = resource.getAtt('ipAddresses'); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-domain-list.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-domain-list.ts deleted file mode 100644 index d50789c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-domain-list.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IResolverFirewallDomainList extends cdk.IResource { - /** - * The ID of the domain list. - */ - readonly listId: string; - - /** - * The name of the domain list. - */ - readonly name: string; - - /** - * The Amazon Resource Name (ARN) of the firewall domain list. - */ - readonly listArn?: string; -} - -export enum ResolverFirewallDomainListType { - CUSTOM = 'CUSTOM', - MANAGED = 'MANAGED', -} - -export interface ResolverFirewallDomainListProps { - /** - * The name of the domain list. - */ - readonly name: string; - - /** - * The type of the domain list. - */ - readonly type: ResolverFirewallDomainListType; - - /** - * Path to a file containing a domain list. - */ - readonly path?: string; - - /** - * A list of CloudFormation tags - */ - readonly tags?: cdk.CfnTag[]; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class ResolverFirewallDomainList extends cdk.Resource implements IResolverFirewallDomainList { - public readonly listId: string; - public readonly name: string; - public readonly listArn?: string; - private assetUrl?: string; - - constructor(scope: Construct, id: string, props: ResolverFirewallDomainListProps) { - super(scope, id); - - this.name = props.name; - - if (props.type === ResolverFirewallDomainListType.CUSTOM) { - // Check if path was provided - if (!props.path) { - throw new Error('path property must be specified when creating domain list of type CUSTOM'); - } - this.assetUrl = this.getAssetUrl(props.path); - - props.tags?.push({ key: 'Name', value: this.name }); - - // Create custom domain list with uploaded asset file - const resource = new cdk.aws_route53resolver.CfnFirewallDomainList(this, 'Resource', { - domainFileUrl: this.assetUrl, - tags: props.tags, - }); - - this.listArn = resource.attrArn; - this.listId = resource.attrId; - } else { - // Create custom resource provider - const RESOURCE_TYPE = 'Custom::ResolverManagedDomainList'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'get-domain-lists/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['route53resolver:ListFirewallDomainLists'], - Resource: '*', - }, - ], - }); - - // Get managed domain list ID - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - listName: props.name, - region: cdk.Stack.of(this).region, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.listId = resource.ref; - } - } - - private getAssetUrl(assetUrlPath: string): string { - const asset = new cdk.aws_s3_assets.Asset(this, 'Asset', { - path: assetUrlPath, - }); - return asset.s3ObjectUrl; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-rule-group.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-rule-group.ts deleted file mode 100644 index debcd30..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/firewall-rule-group.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IResolverFirewallRuleGroup extends cdk.IResource { - /** - * The ARN (Amazon Resource Name) of the rule group. - */ - readonly groupArn: string; - - /** - * The ID of the rule group. - */ - readonly groupId: string; - - /** - * The name of the rule group. - */ - readonly name: string; -} - -export interface ResolverFirewallRuleGroupProps { - /** - * A list of the rules that you have defined. - */ - readonly firewallRules: cdk.aws_route53resolver.CfnFirewallRuleGroup.FirewallRuleProperty[]; - - /** - * The name of the rule group. - */ - readonly name: string; - - /** - * A list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class ResolverFirewallRuleGroup extends cdk.Resource implements IResolverFirewallRuleGroup { - public readonly groupArn: string; - public readonly groupId: string; - public readonly name: string; - - constructor(scope: Construct, id: string, props: ResolverFirewallRuleGroupProps) { - super(scope, id); - - this.name = props.name; - props.tags?.push({ key: 'Name', value: this.name }); - - const resource = new cdk.aws_route53resolver.CfnFirewallRuleGroup(this, 'Resource', { - firewallRules: props.firewallRules, - tags: props.tags, - }); - - this.groupArn = resource.attrArn; - this.groupId = resource.ref; - } -} - -export interface ResolverFirewallRuleGroupAssociationProps { - /** - * The unique identifier of the firewall rule group. - */ - readonly firewallRuleGroupId: string; - - /** - * The setting that determines the processing order of the rule group - * among the rule groups that are associated with a single VPC. - */ - readonly priority: number; - - /** - * The unique identifier of the VPC that is associated with the rule group. - */ - readonly vpcId: string; - - /** - * If enabled, this setting disallows modification or removal of the association - */ - readonly mutationProtection?: string; - - /** - * A list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class ResolverFirewallRuleGroupAssociation extends cdk.Resource { - constructor(scope: Construct, id: string, props: ResolverFirewallRuleGroupAssociationProps) { - super(scope, id); - - new cdk.aws_route53resolver.CfnFirewallRuleGroupAssociation(this, 'Resource', { - firewallRuleGroupId: props.firewallRuleGroupId, - priority: props.priority, - vpcId: props.vpcId, - mutationProtection: props.mutationProtection, - tags: props.tags, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/index.ts deleted file mode 100644 index 0c642c6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/index.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * Get Route 53 resolver endpoint details - Lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - const region: string = event.ResourceProperties['region']; - const listName: string = event.ResourceProperties['listName']; - const solutionId = process.env['SOLUTION_ID']; - const resolverClient = new AWS.Route53Resolver({ region: region, customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - let nextToken: string | undefined = undefined; - let resourceId: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - resolverClient.listFirewallDomainLists({ NextToken: nextToken }).promise(), - ); - - // Loop through IP addresses and push to array - for (const item of page.FirewallDomainLists ?? []) { - if (item.Name === listName) { - resourceId = item.Id; - } - } - nextToken = page.NextToken; - } while (nextToken); - - if (!resourceId) { - throw new Error(`Managed domain list ${listName} does not exist.`); - } - - return { - PhysicalResourceId: resourceId, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/package.json deleted file mode 100644 index 30cffec..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-route53-resolver-get-domain-lists", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-domain-lists/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/index.ts deleted file mode 100644 index 862040f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * Get Route 53 resolver endpoint details - Lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Data: { - ipAddresses: { Ip: string }[]; - }; - Status: string; - } - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - const region: string = event.ResourceProperties['region']; - const endpointId: string = event.ResourceProperties['endpointId']; - const solutionId = process.env['SOLUTION_ID']; - const resolverClient = new AWS.Route53Resolver({ region: region, customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const ipArray: { Ip: string }[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - resolverClient - .listResolverEndpointIpAddresses({ ResolverEndpointId: endpointId, NextToken: nextToken }) - .promise(), - ); - - // Loop through IP addresses and push to array - for (const item of page.IpAddresses ?? []) { - if (item.Ip) { - ipArray.push({ Ip: item.Ip }); - } - } - - nextToken = page.NextToken; - } while (nextToken); - - return { - PhysicalResourceId: endpointId, - Data: { - ipAddresses: ipArray, - }, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/package.json deleted file mode 100644 index 5881753..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-route53-resolver-get-endpoint-addresses", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/get-endpoint-addresses/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/index.ts deleted file mode 100644 index e5c92ce..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * log-resource-policy - Lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string; - Status: string; - } - | undefined -> { - interface ResourceLogPolicy { - policyName: string; - policyStatements: []; - } - - const resourceLogPolicy = event.ResourceProperties as unknown as ResourceLogPolicy; - const policyName = resourceLogPolicy.policyName; - const policyDocument = { - Version: '2012-10-17', - Statement: resourceLogPolicy.policyStatements, - }; - console.log(policyDocument); - - const logsClient = new AWS.CloudWatchLogs(); - - switch (event.RequestType) { - case 'Update': - case 'Create': - console.log(`Creating CloudWatch log resource policy.`); - await throttlingBackOff(() => - logsClient - .putResourcePolicy({ - policyName: policyName, - policyDocument: JSON.stringify(policyDocument), - }) - .promise(), - ); - return { - PhysicalResourceId: policyName, - Status: 'SUCCESS', - }; - case 'Delete': - console.log(`Deleting CloudWatch log resource policy.`); - await throttlingBackOff(() => logsClient.deleteResourcePolicy({ policyName: policyName }).promise()); - return { - PhysicalResourceId: policyName, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/package.json deleted file mode 100644 index 0b77e82..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-log-resource-policy", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/log-resource-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/index.ts deleted file mode 100644 index 9f899de..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * query-logging-config-association - Lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - interface ResolverQueryLogConfigAssociation { - ResolverQueryLogConfigId: string; - VpcId: string; - } - - const resolverQueryLogConfigAssociation = event.ResourceProperties as unknown as ResolverQueryLogConfigAssociation; - - const { ResolverQueryLogConfigId, VpcId } = resolverQueryLogConfigAssociation; - const route53ResolverClient = new AWS.Route53Resolver(); - - switch (event.RequestType) { - case 'Update': - case 'Create': - console.log(`Associating Route53 resolver query log config ${ResolverQueryLogConfigId} to VPC ${VpcId}`); - const data = await throttlingBackOff(() => - route53ResolverClient - .associateResolverQueryLogConfig({ - ResolverQueryLogConfigId: ResolverQueryLogConfigId, - ResourceId: VpcId, - }) - .promise(), - ); - return { - PhysicalResourceId: data.ResolverQueryLogConfigAssociation?.Id, - Status: 'SUCCESS', - }; - case 'Delete': - console.log(`Disassociating Route53 resolver query log config ${ResolverQueryLogConfigId} to VPC ${VpcId}`); - await throttlingBackOff(() => - route53ResolverClient - .disassociateResolverQueryLogConfig({ - ResolverQueryLogConfigId: ResolverQueryLogConfigId, - ResourceId: VpcId, - }) - .promise(), - ); - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/package.json deleted file mode 100644 index 0f277c2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-route53-resolver-query-logging-config-association", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config-association/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config.ts deleted file mode 100644 index c424acb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config.ts +++ /dev/null @@ -1,337 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -export interface IQueryLoggingConfig extends cdk.IResource { - /** - * The Amazon Resource Name (ARN) for the query logging configuration. - */ - readonly logArn: string; - - /** - * The ID for the query logging configuration. - */ - readonly logId: string; - - /** - * The name that you assigned to the query logging config. - */ - readonly name: string; -} - -export interface QueryLoggingConfigProps { - /** - * The resource that you want Resolver to send query logs. - */ - readonly destination: cdk.aws_s3.IBucket | cdk.aws_logs.LogGroup; - - /** - * The name of the query logging configuration. - */ - readonly name: string; - - /** - * The partition of this stack. - */ - readonly partition: string; - - /** - * An AWS Organization ID, if the destination is CloudWatch Logs. - */ - readonly organizationId?: string; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class QueryLoggingConfig extends cdk.Resource implements IQueryLoggingConfig { - public readonly logArn: string; - public readonly logId: string; - public readonly name: string; - private destinationArn: string; - private logRetentionInDays: number; - private kmsKey: cdk.aws_kms.IKey | undefined; - - constructor(scope: Construct, id: string, props: QueryLoggingConfigProps) { - super(scope, id); - - this.name = props.name; - this.logRetentionInDays = props.logRetentionInDays; - this.kmsKey = props.kmsKey; - - if (props.destination instanceof cdk.aws_logs.LogGroup) { - if (props.partition !== 'aws-cn' && !props.organizationId) { - throw new Error('organizationId property must be defined when specifying a CloudWatch log group destination'); - } - this.destinationArn = props.destination.logGroupArn; - } else if ('bucketName' in props.destination) { - this.destinationArn = props.destination.bucketArn; - } else { - throw new Error('Invalid resource type specified for destination property'); - } - - if (props.partition === 'aws-cn') { - const customQueryLoggingConfigResource = this.queryLoggingConfig(); - if (props.destination instanceof cdk.aws_logs.LogGroup) { - const customLogResourcePoliyResource = this.logResourcePolicy(props.destination); - customLogResourcePoliyResource.node.addDependency(customQueryLoggingConfigResource); - } - this.logArn = customQueryLoggingConfigResource.getAtt('attrArn').toString(); - this.logId = customQueryLoggingConfigResource.getAtt('attrId').toString(); - } else { - if (props.destination instanceof cdk.aws_logs.LogGroup) { - this.addPermissions(props.destination, props.organizationId!); - } - const resource = new cdk.aws_route53resolver.CfnResolverQueryLoggingConfig(this, 'Resource', { - destinationArn: this.destinationArn, - }); - this.logArn = resource.attrArn; - this.logId = resource.attrId; - } - } - - private addPermissions(logGroup: cdk.aws_logs.LogGroup, orgId: string) { - logGroup.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow log delivery access', - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com')], - actions: ['logs:CreateLogStream', 'logs:PutLogEvents'], - resources: [`${logGroup.logGroupArn}:log-stream:*`], - conditions: { - StringEquals: { - 'aws:PrincipalOrgId': orgId, - }, - }, - }), - ); - } - - private logResourcePolicy(logGroupDest: cdk.aws_logs.LogGroup) { - // Use custom resource - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::LogResourcePolicy', { - codeDirectory: path.join(__dirname, 'log-resource-policy/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['logs:PutResourcePolicy', 'logs:DeleteResourcePolicy'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'LogResourcePolicy', { - resourceType: 'Custom::LogResourcePolicy', - serviceToken: provider.serviceToken, - properties: { - policyName: 'AllowLogDeliveryAccess', - policyStatements: [ - { - Sid: 'Allow log delivery access', - Effect: cdk.aws_iam.Effect.ALLOW, - Principal: { Service: 'delivery.logs.amazonaws.com' }, - Action: ['logs:CreateLogStream', 'logs:PutLogEvents'], - Resource: [`${logGroupDest.logGroupArn}:log-stream:*`], - }, - ], - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(this); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: this.logRetentionInDays, - encryptionKey: this.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - return resource; - } - - private queryLoggingConfig() { - // Use custom resource - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::QueryLoggingConfig', { - codeDirectory: path.join(__dirname, 'query-logging-config/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'route53resolver:ListResolverQueryLogConfigs', - 'route53resolver:CreateResolverQueryLogConfig', - 'route53resolver:DeleteResolverQueryLogConfig', - 'logs:*', - ], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - Condition: { - 'ForAnyValue:StringEquals': { - 'iam:AWSServiceName': ['route53resolver.amazonaws.com'], - }, - }, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'QueryLoggingConfig', { - resourceType: 'Custom::QueryLoggingConfig', - serviceToken: provider.serviceToken, - properties: { - DestinationArn: this.destinationArn, - Name: this.name, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(this); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: this.logRetentionInDays, - encryptionKey: this.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - return resource; - } -} - -export interface QueryLoggingConfigAssociationProps { - /** - * The ID of the query logging configuration that a VPC is associated with. - */ - readonly resolverQueryLogConfigId?: string; - - /** - * The ID of the Amazon VPC that is associated with the query logging configuration. - */ - readonly vpcId?: string; - - /** - * The partition of this stack. - */ - readonly partition: string; - - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class QueryLoggingConfigAssociation extends cdk.Resource { - private vpcId: string | undefined; - private resolverQueryLogConfigId: string | undefined; - private logRetentionInDays: number; - private kmsKey: cdk.aws_kms.IKey | undefined; - - constructor(scope: Construct, id: string, props: QueryLoggingConfigAssociationProps) { - super(scope, id); - this.vpcId = props.vpcId; - this.resolverQueryLogConfigId = props.resolverQueryLogConfigId; - this.logRetentionInDays = props.logRetentionInDays; - this.kmsKey = props.kmsKey; - - if (props.partition === 'aws-cn') { - this.queryLoggingConfigAssociation(); - } else { - new cdk.aws_route53resolver.CfnResolverQueryLoggingConfigAssociation(this, 'Resource', { - resolverQueryLogConfigId: this.resolverQueryLogConfigId, - resourceId: this.vpcId, - }); - } - } - - private queryLoggingConfigAssociation(): void { - // Use custom resource - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::QueryLoggingConfigAssociation', { - codeDirectory: path.join(__dirname, 'query-logging-config-association/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: [ - 'route53resolver:DisassociateResolverQueryLogConfig', - 'route53resolver:AssociateResolverQueryLogConfig', - 'route53resolver:ListResolverQueryLogConfigs', - 'route53resolver:GetResolverQueryLogConfig', - 'route53resolver:ListResolverQueryLogConfigAssociations', - 'ec2:DescribeVpcs', - ], - Resource: '*', - }, - { - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - Condition: { - 'ForAnyValue:StringEquals': { - 'iam:AWSServiceName': ['route53resolver.amazonaws.com'], - }, - }, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'QueryLoggingConfigAssociation', { - resourceType: 'Custom::QueryLoggingConfigAssociation', - serviceToken: provider.serviceToken, - properties: { - ResolverQueryLogConfigId: this.resolverQueryLogConfigId, - VpcId: this.vpcId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(this); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: this.logRetentionInDays, - encryptionKey: this.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/index.ts deleted file mode 100644 index aebcc48..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as AWS from 'aws-sdk'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * query-logging-config - Lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - Data?: { - attrArn: string | undefined; - attrId: string | undefined; - }; - } - | undefined -> { - interface ResolverQueryLogConfig { - Name: string; - DestinationArn: string; - CreatorRequestId?: string; - Tags?: [] | undefined; - } - - const resolverQueryLogConfig = event.ResourceProperties as unknown as ResolverQueryLogConfig; - if (!resolverQueryLogConfig.CreatorRequestId) { - resolverQueryLogConfig.CreatorRequestId = Date.now().toString(); - } - - const { Name, DestinationArn, CreatorRequestId, Tags } = resolverQueryLogConfig; - const route53ResolverClient = new AWS.Route53Resolver(); - - switch (event.RequestType) { - case 'Update': - case 'Create': - console.log(`Creating Route53 resolver query log config ${resolverQueryLogConfig.Name}`); - const data = await throttlingBackOff(() => - route53ResolverClient - .createResolverQueryLogConfig({ - Name: Name, - DestinationArn: DestinationArn, - CreatorRequestId: CreatorRequestId, - Tags: Tags, - }) - .promise(), - ); - console.log(`Route53 resolver query log config created: ${data.ResolverQueryLogConfig?.Id}`); - return { - PhysicalResourceId: data.ResolverQueryLogConfig?.Id, - Status: 'SUCCESS', - Data: { - attrArn: data.ResolverQueryLogConfig?.Arn, - attrId: data.ResolverQueryLogConfig?.Id, - }, - }; - case 'Delete': - console.log(`Deleting Route53 resolver query log config ${event.PhysicalResourceId}`); - await throttlingBackOff(() => - route53ResolverClient - .deleteResolverQueryLogConfig({ ResolverQueryLogConfigId: event.PhysicalResourceId }) - .promise(), - ); - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/package.json deleted file mode 100644 index 19377c5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-route53-resolver-query-logging-config", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/query-logging-config/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-endpoint.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-endpoint.ts deleted file mode 100644 index bfcbc2b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-endpoint.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IResolverEndpoint extends cdk.IResource { - /** - * The Amazon Resource Name (ARN) of the resolver endpoint. - */ - readonly endpointArn: string; - - /** - * The ID of the resolver endpoint. - */ - readonly endpointId: string; - - /** - * The name that you assigned to the resolver endpoint when you created the endpoint. - */ - readonly name: string; -} - -export interface ResolverEndpointProps { - /** - * Indicates whether the Resolver endpoint allows inbound or outbound DNS queries. - */ - readonly direction: string; - - /** - * The subnets and IP addresses in your VPC that DNS queries originate from (for outbound endpoints) - * or that you forward DNS queries to (for inbound endpoints). - */ - readonly ipAddresses: string[]; - - /** - * A friendly name that lets you easily find a configuration in the Resolver dashboard in the Route 53 console. - */ - readonly name: string; - - /** - * The ID of one or more security groups that control access to this endpoint. - */ - readonly securityGroupIds: string[]; - - /** - * A list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; -} - -export class ResolverEndpoint extends cdk.Resource implements IResolverEndpoint { - public readonly endpointArn: string; - public readonly endpointId: string; - public readonly name: string; - private ipAddresses: cdk.aws_route53resolver.CfnResolverEndpoint.IpAddressRequestProperty[]; - - constructor(scope: Construct, id: string, props: ResolverEndpointProps) { - super(scope, id); - - this.name = props.name; - this.ipAddresses = props.ipAddresses.map(item => { - return { subnetId: item }; - }); - - const resource = new cdk.aws_route53resolver.CfnResolverEndpoint(this, 'Resource', { - direction: props.direction, - ipAddresses: this.ipAddresses, - name: props.name, - securityGroupIds: props.securityGroupIds, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', this.name); - - this.endpointArn = resource.attrArn; - this.endpointId = resource.attrResolverEndpointId; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-rule.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-rule.ts deleted file mode 100644 index 7cf6b3c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53-resolver/resolver-rule.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { EndpointAddresses } from './endpoint-addresses'; - -export interface IResolverRule extends cdk.IResource { - /** - * The friendly name of the resolver rule. - */ - readonly name: string; - - /** - * The Amazon Resource Name (ARN) of the resolver rule. - */ - readonly ruleArn: string; - - /** - * The ID that Resolver assigned to the resolver rule when you created it. - */ - readonly ruleId: string; -} - -export interface ResolverRuleProps { - /** - * DNS queries for this domain name are forwarded to the IP addresses that are specified in `TargetIps`. - */ - readonly domainName: string; - - /** - * The name for the Resolver rule. - */ - readonly name: string; - - /** - * The ID of the endpoint that the rule is associated with. - */ - readonly resolverEndpointId?: string; - - /** - * The type of resolver rule: FORWARD, RECURSIVE, or SYSTEM. - * - * @default FORWARD - */ - readonly ruleType?: string; - - /** - * Choose to target an inbound resolver endpoint for name resolution. - */ - readonly targetInbound?: string; - - /** - * An array that contains the IP addresses and ports that an outbound endpoint forwards DNS queries to. - */ - readonly targetIps?: cdk.aws_route53resolver.CfnResolverRule.TargetAddressProperty[]; - - /** - * A list of CloudFormation tags. - */ - readonly tags?: cdk.CfnTag[]; - - /** - * Custom resource lambda log group encryption key - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays?: number; -} - -export class ResolverRule extends cdk.Resource implements IResolverRule { - public readonly name: string; - public readonly ruleArn: string; - public readonly ruleId: string; - private targetIps?: cdk.aws_route53resolver.CfnResolverRule.TargetAddressProperty[] | cdk.Reference; - - constructor(scope: Construct, id: string, props: ResolverRuleProps) { - super(scope, id); - - this.name = props.name; - - if (props.targetInbound) { - if (!props.logRetentionInDays) { - throw new Error(`logRetentionInDays property must be included if targetInbound property is defined.`); - } - this.targetIps = this.lookupInbound(props.targetInbound, props.logRetentionInDays, props.kmsKey); - } else { - this.targetIps = props.targetIps; - } - - const resource = new cdk.aws_route53resolver.CfnResolverRule(this, 'Resource', { - domainName: props.domainName, - resolverEndpointId: props.ruleType === 'SYSTEM' ? undefined : props.resolverEndpointId, - ruleType: props.ruleType ?? 'FORWARD', - name: this.name, - targetIps: this.targetIps, - tags: props.tags, - }); - cdk.Tags.of(this).add('Name', this.name); - - this.ruleArn = resource.attrArn; - this.ruleId = resource.attrResolverRuleId; - } - - private lookupInbound(endpointId: string, logRetentionInDays: number, kmsKey?: cdk.aws_kms.IKey): cdk.Reference { - const lookup = new EndpointAddresses(this, 'LookupInbound', { - endpointId: endpointId, - kmsKey, - logRetentionInDays, - }); - return lookup.ipAddresses; - } -} - -export interface ResolverRuleAssociationProps { - /** - * The ID of the Resolver rule to associate. - */ - readonly resolverRuleId: string; - - /** - * The ID of the VPC to associate. - */ - readonly vpcId: string; -} - -export class ResolverRuleAssociation extends cdk.Resource { - constructor(scope: Construct, id: string, props: ResolverRuleAssociationProps) { - super(scope, id); - - new cdk.aws_route53resolver.CfnResolverRuleAssociation(this, 'Resource', { - resolverRuleId: props.resolverRuleId, - vpcId: props.vpcId, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones.ts deleted file mode 100644 index 63e3fc4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Size } from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = require('path'); - -export interface AssociateHostedZonesProps { - readonly accountIds: string[]; - readonly hostedZoneIds: string[]; - readonly hostedZoneAccountId: string; - readonly roleName: string; - readonly tagFilters: { - key: string; - value: string; - }[]; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class AssociateHostedZones extends cdk.Resource { - public readonly id: string = ''; - - constructor(scope: Construct, id: string, props: AssociateHostedZonesProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::Route53AssociateHostedZones'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'associate-hosted-zones/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - memorySize: Size.mebibytes(512), - policyStatements: [ - { - Sid: 'Route53AssociateHostedZonesActions', - Effect: 'Allow', - Action: [ - 'ec2:DescribeVpcs', - 'route53:AssociateVPCWithHostedZone', - 'route53:CreateVPCAssociationAuthorization', - 'route53:DeleteVPCAssociationAuthorization', - 'route53:GetHostedZone', - 'sts:AssumeRole', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - partition: cdk.Stack.of(this).partition, - region: cdk.Stack.of(this).region, - accountIds: props.accountIds, - hostedZoneIds: props.hostedZoneIds, - hostedZoneAccountId: props.hostedZoneAccountId, - roleName: props.roleName, - tagFilters: props.tagFilters, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/index.ts deleted file mode 100644 index dd45365..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/index.ts +++ /dev/null @@ -1,345 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils'; -import { STSClient, AssumeRoleCommand, AssumeRoleCommandOutput } from '@aws-sdk/client-sts'; -import { DescribeVpcsCommandInput, EC2Client, Filter, paginateDescribeVpcs } from '@aws-sdk/client-ec2'; -import { - AssociateVPCWithHostedZoneCommand, - CreateVPCAssociationAuthorizationCommand, - CreateVPCAssociationAuthorizationRequest, - DeleteVPCAssociationAuthorizationCommand, - GetHostedZoneCommand, - GetHostedZoneResponse, - Route53Client, -} from '@aws-sdk/client-route-53'; - -import type { - AssumeRoleAccountCredentials, - AllAccountsCredentialParams, - AssumeRoleParams, - AWSClients, - DescribeVpcByTagFiltersParams, - VpcItem, - SetClientsParams, - TagFilter, - VpcAssociation, - CfnResponse, - VpcAssociationItem, -} from './interfaces'; - -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * associate-hosted-zones - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise { - const solutionId = process.env['SOLUTION_ID']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - const accountIds: string[] = event.ResourceProperties['accountIds']; - const hostedZoneIds: string[] = event.ResourceProperties['hostedZoneIds']; - const hostedZoneAccountId = event.ResourceProperties['hostedZoneAccountId']; - const partition = event.ResourceProperties['partition']; - const region = event.ResourceProperties['region']; - const roleName = event.ResourceProperties['roleName']; - const roleSessionName = 'AssociateHostedZone'; - const tagFilters: TagFilter[] = event.ResourceProperties['tagFilters']; - - const assumeRoleResponses = await getAllAssumeRoleCredentials({ - accountIds, - roleName, - solutionId, - region, - partition, - roleSessionName, - hostedZoneAccountId, - }); - - const awsClients = setAwsClients({ - assumeRoleCredentials: assumeRoleResponses, - hostedZoneAccountId, - solutionId, - }); - - const hostedZoneRoute53Client = awsClients[hostedZoneAccountId].route53Client; - if (!hostedZoneRoute53Client) { - throw new Error(`Could not get clients for account ${hostedZoneAccountId}`); - } - - const describeVpcsPromises: Promise[] = Object.keys(awsClients).map(account => - describeVpcsByTagFilters({ account, ec2Client: awsClients[account].ec2Client, tagFilters }), - ); - - const hostedZonePromises = hostedZoneIds.map(hostedZoneId => - getHostedZone(hostedZoneRoute53Client, hostedZoneId), - ); - - const vpcsToAssociate = (await Promise.all(describeVpcsPromises)).flat(); - - console.log('Retrieved VPC information for the following vpcs'); - vpcsToAssociate.forEach(vpc => { - console.log(vpc.account, vpc.vpc.VpcId); - }); - - const hostedZoneItems = await Promise.all(hostedZonePromises); - - console.log('Retrieved Information for the following hosted zones:'); - hostedZoneItems.forEach(hostedZoneItem => { - console.log(hostedZoneItem.HostedZone); - }); - - const nonAssociatedVpcs = findNonAssociatedVpcs(vpcsToAssociate, hostedZoneItems, hostedZoneAccountId, region); - - console.log('Vpcs not associated with hosted zones:'); - console.log(JSON.stringify(nonAssociatedVpcs, null, 2)); - - await associateVpcs(nonAssociatedVpcs, awsClients); - - return { - PhysicalResourceId: 'associate-hosted-zones', - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -async function getAllAssumeRoleCredentials( - allAccountsCredentialParams: AllAccountsCredentialParams, -): Promise { - const assumeRoleAccounts = allAccountsCredentialParams.accountIds.filter( - account => account !== allAccountsCredentialParams.hostedZoneAccountId, - ); - const assumeRoleAccountPromises = assumeRoleAccounts.map(accountId => - getAssumeRoleCredentials({ - accountId, - roleName: allAccountsCredentialParams.roleName, - solutionId: allAccountsCredentialParams.solutionId, - region: allAccountsCredentialParams.region, - partition: allAccountsCredentialParams.partition, - roleSessionName: allAccountsCredentialParams.roleSessionName, - }), - ); - - return Promise.all(assumeRoleAccountPromises); -} - -async function getAssumeRoleCredentials(assumeRoleParams: AssumeRoleParams): Promise { - try { - console.log( - `Retrieving Credentials for account ${assumeRoleParams.accountId} with role ${assumeRoleParams.roleName}`, - ); - const stsClient = new STSClient({ - customUserAgent: assumeRoleParams.solutionId, - region: assumeRoleParams.region, - }); - const assumeRoleRequestParams = new AssumeRoleCommand({ - RoleArn: `arn:${assumeRoleParams.partition}:iam::${assumeRoleParams.accountId}:role/${assumeRoleParams.roleName}`, - RoleSessionName: assumeRoleParams.roleSessionName, - }); - - const stsResponse: AssumeRoleCommandOutput = await throttlingBackOff(() => stsClient.send(assumeRoleRequestParams)); - if ( - !stsResponse.Credentials || - !stsResponse.Credentials.AccessKeyId || - !stsResponse.Credentials.SecretAccessKey || - !stsResponse.Credentials.SessionToken - ) { - throw new Error( - `Could Not retrieve credentials for account ${assumeRoleParams.accountId} in region ${assumeRoleParams.region} for role ${assumeRoleParams.roleName}`, - ); - } - return { - account: assumeRoleParams.accountId, - credentials: { - accessKeyId: stsResponse.Credentials.AccessKeyId, - secretAccessKey: stsResponse.Credentials.SecretAccessKey, - sessionToken: stsResponse.Credentials.SessionToken, - }, - }; - } catch (err) { - console.log(err); - throw err; - } -} - -function setAwsClients(setClientsParams: SetClientsParams): AWSClients { - const awsClients: AWSClients = setClientsParams.assumeRoleCredentials.reduce( - (clients: AWSClients, assumeRoleResponse) => { - const config = { - credentials: assumeRoleResponse.credentials, - customUserAgent: setClientsParams.solutionId, - }; - - clients[assumeRoleResponse.account] = { - ec2Client: new EC2Client(config), - route53Client: new Route53Client(config), - }; - - return clients; - }, - {}, - ); - awsClients[setClientsParams.hostedZoneAccountId] = { - ec2Client: new EC2Client(), - route53Client: new Route53Client(), - }; - - return awsClients; -} - -async function describeVpcsByTagFilters( - describeVpcByTagFiltersParams: DescribeVpcByTagFiltersParams, -): Promise { - let filters: Filter[] = []; - if (describeVpcByTagFiltersParams.tagFilters && describeVpcByTagFiltersParams.tagFilters.length > 0) { - filters = describeVpcByTagFiltersParams.tagFilters.map(tagFilter => { - return { - Name: `tag:${tagFilter.key}`, - Values: [tagFilter.value], - }; - }); - } - - const vpcs = await describeVpcsWithFilter(describeVpcByTagFiltersParams.ec2Client, filters); - - return vpcs.map(vpc => { - const hostedZoneAccountTag = vpc.Tags?.find(tag => tag.Key === 'accelerator:central-endpoints-account-id'); - return { - account: describeVpcByTagFiltersParams.account, - hostedZoneAccount: hostedZoneAccountTag?.Value, - vpc, - }; - }); -} - -async function describeVpcsWithFilter(ec2Client: EC2Client, filters: Filter[]) { - const vpcs = []; - const describeVpcsInput: DescribeVpcsCommandInput = { - Filters: filters, - }; - const describeVpcPagination = paginateDescribeVpcs({ client: ec2Client }, describeVpcsInput); - for await (const page of describeVpcPagination) { - if (page.Vpcs) { - vpcs.push(...page.Vpcs); - } - } - - return vpcs; -} - -async function getHostedZone(route53Client: Route53Client, hostedZoneId: string): Promise { - const getHostedZoneCommand = new GetHostedZoneCommand({ - Id: hostedZoneId, - }); - - return throttlingBackOff(() => route53Client.send(getHostedZoneCommand)); -} - -async function associateVpcs(vpcsToAssociate: VpcAssociation, awsClients: AWSClients): Promise { - const hostedZoneAssociationPromises = Object.keys(vpcsToAssociate).map(hostedZoneId => - associateVpcsPerHostedZone(vpcsToAssociate[hostedZoneId], awsClients), - ); - - await Promise.all(hostedZoneAssociationPromises); -} - -async function associateVpcsPerHostedZone( - vpcAssociationItem: VpcAssociationItem[], - clients: AWSClients, -): Promise { - for (const associationItem of vpcAssociationItem) { - const route53Client = clients[associationItem.account].route53Client; - if (associationItem.account === associationItem.hostedZoneAccountId) { - console.log(`Associating VPC: ${JSON.stringify(associationItem, null, 2)}`); - await associateVpc(route53Client, associationItem.hostedZoneParams); - } else { - console.log(`Associating CrossAccount VPC: ${JSON.stringify(associationItem, null, 2)}`); - const hostedZoneRoute53Client = clients[associationItem.hostedZoneAccountId].route53Client; - await associateCrossAccountVpc(hostedZoneRoute53Client, route53Client, associationItem.hostedZoneParams); - } - } -} - -async function associateVpc( - route53Client: Route53Client, - hostedZoneParams: CreateVPCAssociationAuthorizationRequest, -): Promise { - await throttlingBackOff(() => route53Client.send(new AssociateVPCWithHostedZoneCommand(hostedZoneParams))); -} - -async function associateCrossAccountVpc( - hostedZoneRoute53Client: Route53Client, - route53Client: Route53Client, - hostedZoneParams: CreateVPCAssociationAuthorizationRequest, -): Promise { - await throttlingBackOff(() => - hostedZoneRoute53Client.send(new CreateVPCAssociationAuthorizationCommand(hostedZoneParams)), - ); - // associate VPC with Hosted zones - console.log(`Associating hosted zone ${hostedZoneParams.HostedZoneId} with VPC ${hostedZoneParams.VPC?.VPCId}...`); - await throttlingBackOff(() => route53Client.send(new AssociateVPCWithHostedZoneCommand(hostedZoneParams))); - // delete association of VPC with Hosted zones when VPC and Hosted Zones are defined in two different accounts - await throttlingBackOff(() => - hostedZoneRoute53Client.send(new DeleteVPCAssociationAuthorizationCommand(hostedZoneParams)), - ); -} - -function findNonAssociatedVpcs( - vpcs: VpcItem[], - hostedZones: GetHostedZoneResponse[], - hostedZoneAccountId: string, - region: string, -): VpcAssociation { - const nonAssociatedVpcs: VpcAssociation = {}; - for (const hostedZone of hostedZones) { - if (!hostedZone.HostedZone?.Id || !hostedZone.VPCs) { - continue; - } - if (!nonAssociatedVpcs[hostedZone.HostedZone.Id]) { - nonAssociatedVpcs[hostedZone.HostedZone.Id] = []; - } - const hostedZoneVpcIds = hostedZone.VPCs.map(vpc => vpc.VPCId); - const vpcsToAssociate = vpcs.filter(vpc => vpc.vpc.VpcId && !hostedZoneVpcIds.includes(vpc.vpc.VpcId)); - const vpcItemsToAssociate = vpcsToAssociate.map(vpc => { - return { - account: vpc.account, - hostedZoneAccountId, - hostedZoneParams: { - HostedZoneId: hostedZone.HostedZone!.Id!, - VPC: { - VPCId: vpc.vpc.VpcId!, - VPCRegion: region, - }, - }, - }; - }); - if (vpcItemsToAssociate.length > 0) { - nonAssociatedVpcs[hostedZone.HostedZone.Id].push(...vpcItemsToAssociate); - } - } - - return nonAssociatedVpcs; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/interfaces.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/interfaces.ts deleted file mode 100644 index e841b00..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/interfaces.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { EC2Client, Vpc } from '@aws-sdk/client-ec2'; -import { HostedZone, Route53Client } from '@aws-sdk/client-route-53'; - -export interface AssumeRoleAccountCredentials { - account: string; - credentials: { accessKeyId: string; secretAccessKey: string; sessionToken: string }; -} - -export interface AllAccountsCredentialParams { - accountIds: string[]; - roleName: string; - solutionId?: string; - region: string; - partition: string; - roleSessionName: string; - hostedZoneAccountId: string; -} - -export interface AssumeRoleParams { - accountId: string; - roleName: string; - solutionId?: string; - region: string; - partition: string; - roleSessionName: string; -} - -export interface AWSClients { - [key: string]: { - ec2Client: EC2Client; - route53Client: Route53Client; - }; -} - -export interface DescribeVpcByTagFiltersParams { - account: string; - ec2Client: EC2Client; - tagFilters?: TagFilter[]; -} - -export interface VpcItem { - account: string; - hostedZoneAccount: string | undefined; - vpc: Vpc; -} - -export interface HostedZoneItem { - hostedZone: HostedZone | undefined; - vpcId: string | undefined; - region: string | undefined; -} - -export interface SetClientsParams { - assumeRoleCredentials: AssumeRoleAccountCredentials[]; - solutionId?: string; - hostedZoneAccountId: string; -} -export interface TagFilter { - key: string; - value: string; -} - -export interface VpcAssociationItem { - account: string; - hostedZoneAccountId: string; - hostedZoneParams: { - HostedZoneId: string; - VPC: { - VPCId: string; - VPCRegion: string; - }; - }; -} - -export interface VpcAssociation { - [key: string]: VpcAssociationItem[]; -} - -export interface CfnResponse { - PhysicalResourceId: string | undefined; - Status: string; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/package.json deleted file mode 100644 index ed74107..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-route53-associate-hosted-zones", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-sts": "3.410.0", - "@aws-sdk/client-ec2": "3.410.0", - "@aws-sdk/client-route-53": "3.410.0" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/associate-hosted-zones/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/hosted-zone.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/hosted-zone.ts deleted file mode 100644 index fcde906..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/hosted-zone.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface IHostedZone extends cdk.IResource { - readonly hostedZoneId: string; - readonly hostedZoneName: string; - readonly vpcId: string; - readonly tags?: cdk.CfnTag[]; -} - -export interface HostedZoneProps { - readonly hostedZoneName: string; - readonly vpcId: string; - readonly tags?: cdk.CfnTag[]; -} - -export class HostedZone extends cdk.Resource implements IHostedZone { - readonly hostedZoneId: string; - readonly hostedZoneName: string; - readonly vpcId: string; - readonly tags?: cdk.CfnTag[]; - - constructor(scope: Construct, id: string, props: HostedZoneProps) { - super(scope, id); - - this.vpcId = props.vpcId; - this.hostedZoneName = props.hostedZoneName; - - const resource = new cdk.aws_route53.CfnHostedZone(this, 'Resource', { - name: props.hostedZoneName, - vpcs: [ - { - vpcId: this.vpcId, - vpcRegion: cdk.Stack.of(this).region, - }, - ], - hostedZoneTags: this.processTags(props.tags ?? []), - }); - - this.hostedZoneId = resource.ref; - } - - private processTags(tags: cdk.CfnTag[]): cdk.aws_route53.CfnHostedZone.HostedZoneTagProperty[] { - return tags.map(tag => { - return { - key: tag.key, - value: tag.value, - }; - }); - } - - static getHostedZoneNameForService(service: string, region: string): string { - let hostedZoneName = `${service}.${region}.amazonaws.com`; - - if (service.indexOf('.') > 0 && !HostedZone.ignoreServiceEndpoint(service)) { - const tmp = service.split('.').reverse().join('.'); - hostedZoneName = `${tmp}.${region}.amazonaws.com.`; - } - switch (service) { - case 'appstream.api': - hostedZoneName = `appstream2.${region}.amazonaws.com`; - break; - case 'deviceadvisor.iot': - hostedZoneName = `deviceadvisor.iot.${region}.amazonaws.com`; - break; - case 'pinpoint-sms-voice-v2': - hostedZoneName = `sms-voice.${region}.amazonaws.com`; - break; - case 'rum-dataplane': - hostedZoneName = `dataplane.rum.${region}.amazonaws.com`; - break; - case 's3-global.accesspoint': - hostedZoneName = `${service}.amazonaws.com`; - break; - case 'ecs-agent': - hostedZoneName = `ecs-a.${region}.amazonaws.com`; - break; - case 'ecs-telemetry': - hostedZoneName = `ecs-t.${region}.amazonaws.com`; - break; - case 'codeartifact.api': - hostedZoneName = `codeartifact.${region}.amazonaws.com`; - break; - case 'codeartifact.repositories': - hostedZoneName = `d.codeartifact.${region}.amazonaws.com`; - break; - case 'notebook': - hostedZoneName = `${service}.${region}.sagemaker.aws`; - break; - case 'studio': - hostedZoneName = `${service}.${region}.sagemaker.aws`; - break; - } - return hostedZoneName; - } - - static ignoreServiceEndpoint(service: string): boolean { - const ignoreServicesArray = [ - 'appstream.api', - 'deviceadvisor.iot', - 'pinpoint-sms-voice-v2', - 'rum-dataplane', - 's3-global.accesspoint', - 'ecs-agent', - 'ecs-telemetry', - 'notebook', - 'studio', - 'codeartifact.api', - 'codeartifact.repositories', - ]; - return ignoreServicesArray.includes(service); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/record-set.ts b/source/packages/@aws-accelerator/constructs/lib/aws-route-53/record-set.ts deleted file mode 100644 index 8e83ecd..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-route-53/record-set.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { IHostedZone } from './hosted-zone'; - -export interface IRecordSet extends cdk.IResource { - readonly recordSetId: string; -} - -export interface RecordSetProps { - readonly type: string; - readonly name: string; - - readonly hostedZone: IHostedZone; - readonly dnsName?: string; - readonly hostedZoneId?: string; -} - -export class RecordSet extends cdk.Resource implements IRecordSet { - readonly recordSetId: string; - - constructor(scope: Construct, id: string, props: RecordSetProps) { - super(scope, id); - - const resource = new cdk.aws_route53.CfnRecordSet(this, 'Resource', { - type: props.type, - name: props.name, - hostedZoneId: props.hostedZone.hostedZoneId, - aliasTarget: { - dnsName: props.dnsName ?? '', - hostedZoneId: props.hostedZoneId ?? '', - }, - }); - - this.recordSetId = resource.ref; - } - - static getHostedZoneNameFromService(service: string, region: string): string { - let hostedZoneName = `${service}.${region}.amazonaws.com`; - const sagemakerArray = ['notebook', 'studio']; - if (sagemakerArray.includes(service)) { - hostedZoneName = `${service}.${region}.sagemaker.aws`; - } - if (service === 's3-global.accesspoint') { - hostedZoneName = `${service}.aws.com`; - } - return hostedZoneName; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-encryption.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-encryption.ts deleted file mode 100644 index cd37113..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-encryption.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { LzaCustomResource } from '../lza-custom-resource'; - -/** - * Initialized BucketEncryptionProps properties - */ -export interface BucketEncryptionProps { - /** - * Bucket where resource policy will be applied - */ - readonly bucket: cdk.aws_s3.IBucket; - /** - * Bucket Encryption Key - */ - readonly kmsKey: cdk.aws_kms.IKey; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; -} - -/** - * Class for BucketEncryption - */ -export class BucketEncryption extends Construct { - constructor(scope: Construct, id: string, props: BucketEncryptionProps) { - super(scope, id); - - const resourceName = 'BucketEncryption'; - const assetPath = path.join(__dirname, 'put-bucket-encryption/dist'); - - new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - properties: [ - { bucketName: props.bucket.bucketName }, - { - kmsKeyArn: props.kmsKey.keyArn, - }, - ], - }, - lambda: { - assetPath, - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(5), - cloudWatchLogRemovalPolicy: cdk.RemovalPolicy.RETAIN, - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutEncryptionConfiguration'], - resources: [props.bucket.bucketArn], - }), - ], - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-policy.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-policy.ts deleted file mode 100644 index edb7000..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-policy.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import * as fs from 'fs'; -import { LzaCustomResource } from '../lza-custom-resource'; -import { - AcceleratorImportedBucketType, - AwsPrincipalAccessesType, - PrincipalOrgIdConditionType, -} from '@aws-accelerator/utils/lib/common-resources'; - -/** - * Initialized BucketPolicyProps properties - */ -export interface BucketPolicyProps { - /** - * Type of the bucket - */ - readonly bucketType: AcceleratorImportedBucketType; - /** - * Flag indicating is accelerator generated polices should be applied to imported bucket - */ - readonly applyAcceleratorManagedPolicy: boolean; - /** - * The name of the bucket which will be validated - */ - readonly bucket: cdk.aws_s3.IBucket; - /** - * JSON document bucket policy file paths. - */ - readonly bucketPolicyFilePaths: string[]; - /** - * The name of the bucket which will be validated - */ - readonly principalOrgIdCondition?: PrincipalOrgIdConditionType; - /** - * The name of the bucket which will be validated - */ - readonly awsPrincipalAccesses?: AwsPrincipalAccessesType[]; - /** - * Organization Id - */ - readonly organizationId?: string; - /** - * ELB account Id - */ - readonly elbAccountId?: string; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; - /** - * Accelerator Prefix Firewall Roles - */ - readonly firewallRoles?: string[]; -} - -/** - * Class for BucketPolicy - */ -export class BucketPolicy extends Construct { - public bucketKmsArn: string; - private assetPath: string; - constructor(scope: Construct, id: string, props: BucketPolicyProps) { - super(scope, id); - - const resourceName = 'BucketPolicy'; - this.assetPath = path.join(__dirname, 'put-bucket-policy/dist'); - const policyFolderName = 'bucket-policy'; - fs.mkdirSync(path.join(this.assetPath, policyFolderName), { recursive: true }); - - const bucketPolicyFilePaths: string[] = []; - - for (const bucketPolicyFilePath of props.bucketPolicyFilePaths ?? []) { - const policyFileName = path.parse(bucketPolicyFilePath).base; - fs.copyFileSync(bucketPolicyFilePath, path.join(this.assetPath, policyFolderName, policyFileName)); - bucketPolicyFilePaths.push(`${policyFolderName}/${policyFileName}`); - } - - const lzaCustomResource = new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - properties: [ - { sourceAccount: cdk.Stack.of(this).account }, - { bucketType: props.bucketType }, - { bucketName: props.bucket.bucketName }, - { bucketArn: props.bucket.bucketArn }, - { applyAcceleratorManagedPolicy: props.applyAcceleratorManagedPolicy }, - { - bucketPolicyFilePaths: bucketPolicyFilePaths, - }, - { awsPrincipalAccesses: props.awsPrincipalAccesses }, - { principalOrgIdCondition: props.principalOrgIdCondition }, - { organizationId: props.organizationId }, - { elbAccountId: props.elbAccountId }, - { firewallRoles: props.firewallRoles }, - ], - forceUpdate: true, - }, - lambda: { - assetPath: this.assetPath, - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(5), - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutBucketPolicy'], - resources: [props.bucket.bucketArn], - }), - ], - }, - }); - - this.bucketKmsArn = lzaCustomResource.resource.getAtt('bucketKmsArn').toString(); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-prefix.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-prefix.ts deleted file mode 100644 index 0e07599..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-prefix.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { pascalCase } from 'change-case'; -import path from 'path'; -import { LzaCustomResource } from '../lza-custom-resource'; -/** - * Construction properties for an S3 Bucket replication. - */ -export interface BucketPrefixProps { - source?: { - /** - * Source bucket object - * - * Source bucket object is must when source bucket name wasn't provided - */ - bucket?: cdk.aws_s3.IBucket; - /** - * Source bucket name - * - * Source bucket name is must when source bucket object wasn't provided - */ - bucketName?: string; - }; - bucketPrefixes: string[]; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; - /** - * Prefix for nag suppression - */ - readonly nagSuppressionPrefix?: string; -} - -/** - * Class to configure S3 bucket prefix creation - */ -export class BucketPrefix extends Construct { - private readonly sourceBucket: cdk.aws_s3.IBucket; - constructor(scope: Construct, id: string, props: BucketPrefixProps) { - super(scope, id); - - if (props.source!.bucket && props.source!.bucketName) { - throw new Error('Source bucket or source bucketName (only one property) should be defined.'); - } - - if (!props.source!.bucket && !props.source!.bucketName) { - throw new Error('Source bucket or source bucketName property must be defined when creating bucket prefix.'); - } - - if (props.source!.bucketName) { - this.sourceBucket = cdk.aws_s3.Bucket.fromBucketName( - this, - `${pascalCase(props.source!.bucketName)}`, - props.source!.bucketName, - ); - } else { - this.sourceBucket = props.source!.bucket!; - } - - const resourceName = 'S3CreateBucketPrefix'; - - new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - properties: [ - { sourceBucketName: this.sourceBucket.bucketName }, - { sourceBucketKeyArn: this.sourceBucket.encryptionKey?.keyArn }, - { bucketPrefixes: props.bucketPrefixes }, - ], - forceUpdate: true, - }, - lambda: { - assetPath: path.join(__dirname, 'put-bucket-prefix/dist'), - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(5), - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:ListBucket', 's3:GetObject', 's3:PutObject'], - resources: ['*'], - }), - ], - }, - nagSuppressionPrefix: props.nagSuppressionPrefix, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-replication.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-replication.ts deleted file mode 100644 index b34120d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket-replication.ts +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import { pascalCase } from 'change-case'; -import path from 'path'; -/** - * Construction properties for an S3 Bucket replication. - */ -export interface BucketReplicationProps { - source?: { - /** - * Source bucket object - * - * Source bucket object is must when source bucket name wasn't provided - */ - bucket?: cdk.aws_s3.IBucket; - /** - * Source bucket name - * - * Source bucket name is must when source bucket object wasn't provided - */ - bucketName?: string; - /** - * Filter to limit the scope of this rule to a single prefix. - */ - prefix?: string; - }; - destination: { - /** - * Destination bucket name - */ - bucketName: string; - /** - * Destination bucket account Id - */ - accountId: string; - /** - * Destination bucket key arn - */ - keyArn?: string; - }; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Use existing IAM resources - */ - readonly useExistingRoles: boolean; - /** - * Accelerator prefix, defaults to 'AWSAccelerator' - */ - readonly acceleratorPrefix: string; -} - -/** - * Class to configure S3 bucket replication - */ -export class BucketReplication extends Construct { - private readonly sourceBucket: cdk.aws_s3.IBucket; - private readonly destinationBucket: cdk.aws_s3.IBucket; - constructor(scope: Construct, id: string, props: BucketReplicationProps) { - super(scope, id); - - if (props.source!.bucket && props.source!.bucketName) { - throw new Error('Source bucket or source bucketName (only one property) should be defined.'); - } - - if (!props.source!.bucket && !props.source!.bucketName) { - throw new Error('Source bucket or source bucketName property must be defined when using bucket replication.'); - } - - if (props.source!.bucketName) { - this.sourceBucket = cdk.aws_s3.Bucket.fromBucketName( - this, - `${pascalCase(props.source!.bucketName)}`, - props.source!.bucketName, - ); - } else { - this.sourceBucket = props.source!.bucket!; - } - - this.destinationBucket = cdk.aws_s3.Bucket.fromBucketName( - this, - `${pascalCase(props.destination.bucketName)}`, - props.destination.bucketName, - ); - - if (this.sourceBucket.encryptionKey && !props.destination.keyArn) { - throw new Error('Destination bucket key arn property require when source bucket have server side encryption.'); - } - - const RESOURCE_TYPE = 'Custom::S3PutBucketReplication'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'put-bucket-replication/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'S3PutReplicationConfigurationTaskActions', - Effect: 'Allow', - Action: [ - 'iam:PassRole', - 's3:PutLifecycleConfiguration', - 's3:PutReplicationConfiguration', - 's3:PutBucketVersioning', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - replicationRoleArn: this.createReplicationRole( - props.destination.bucketName, - props.destination.keyArn, - props.useExistingRoles, - props.acceleratorPrefix, - ), - sourceBucketName: this.sourceBucket.bucketName, - prefix: props.source!.prefix ?? '', - destinationBucketArn: this.destinationBucket.bucketArn, - destinationBucketKeyArn: props.destination.keyArn ?? '', - destinationAccountId: props.destination.accountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } - private createReplicationRole( - destinationBucketName: string, - destinationKeyArn: string | undefined, - useExistingRoles: boolean, - acceleratorPrefix: string, - ) { - if (useExistingRoles) { - return `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${acceleratorPrefix}S3ReplicationRole`; - } - // - // Create role for replication - const replicationRole = new cdk.aws_iam.Role(this, `${pascalCase(destinationBucketName)}-ReplicationRole`, { - assumedBy: new cdk.aws_iam.ServicePrincipal('s3.amazonaws.com'), - path: '/service-role/', - }); - - const replicationRolePolicies: cdk.aws_iam.PolicyStatement[] = [ - new cdk.aws_iam.PolicyStatement({ - resources: [this.sourceBucket.bucketArn, this.sourceBucket.arnForObjects('*')], - actions: [ - 's3:GetObjectLegalHold', - 's3:GetObjectRetention', - 's3:GetObjectVersion', - 's3:GetObjectVersionAcl', - 's3:GetObjectVersionForReplication', - 's3:GetObjectVersionTagging', - 's3:GetReplicationConfiguration', - 's3:ListBucket', - 's3:ReplicateDelete', - 's3:ReplicateObject', - 's3:ReplicateTags', - ], - }), - new cdk.aws_iam.PolicyStatement({ - resources: [this.destinationBucket.arnForObjects('*')], - actions: [ - 's3:GetBucketVersioning', - 's3:GetObjectVersionTagging', - 's3:ObjectOwnerOverrideToBucketOwner', - 's3:PutBucketVersioning', - 's3:ReplicateDelete', - 's3:ReplicateObject', - 's3:ReplicateTags', - ], - }), - ]; - - if (this.sourceBucket.encryptionKey) { - replicationRolePolicies.push( - new cdk.aws_iam.PolicyStatement({ - resources: [this.sourceBucket.encryptionKey.keyArn], - actions: ['kms:Decrypt'], - }), - ); - } - - if (destinationKeyArn) { - replicationRolePolicies.push( - new cdk.aws_iam.PolicyStatement({ - resources: [destinationKeyArn], - actions: ['kms:Encrypt'], - }), - ); - } - - replicationRolePolicies.forEach(item => { - replicationRole.addToPolicy(item); - }); - - return replicationRole.roleArn; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket.ts deleted file mode 100644 index 2187dad..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/bucket.ts +++ /dev/null @@ -1,386 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as kms from 'aws-cdk-lib/aws-kms'; -import * as s3 from 'aws-cdk-lib/aws-s3'; -import { StorageClass } from '@aws-accelerator/config/lib/common/types'; -import { BucketReplication, BucketReplicationProps } from './bucket-replication'; -import { BucketPrefix, BucketPrefixProps } from './bucket-prefix'; -import { Construct } from 'constructs'; -import { pascalCase } from 'change-case'; -import { BucketAccessType } from '@aws-accelerator/utils/lib/common-resources'; - -export enum BucketEncryptionType { - SSE_S3 = 'sse-s3', - SSE_KMS = 'sse-kms', -} - -interface Transition { - storageClass: StorageClass; - transitionAfter: number; -} - -export interface S3LifeCycleRule { - abortIncompleteMultipartUploadAfter: number; - enabled: boolean; - expiration?: number; - expiredObjectDeleteMarker: boolean; - id: string; - noncurrentVersionExpiration: number; - transitions: Transition[]; - noncurrentVersionTransitions: Transition[]; - prefix?: string; -} - -/** - * Construction properties for an S3 Bucket object. - */ -export interface BucketProps { - /** - * Physical name of this bucket. - * - * @default - Assigned by CloudFormation (recommended). - */ - s3BucketName?: string; - /** - * SSE encryption type for this bucket. - */ - encryptionType: BucketEncryptionType; - /** - * Policy to apply when the bucket is removed from this stack. - * - * @default - The bucket will be orphaned. - */ - s3RemovalPolicy?: cdk.RemovalPolicy; - /** - * The kms key for bucket encryption. - */ - kmsKey?: kms.IKey; - /** - * The name of the alias. - */ - kmsAliasName?: string; - /** - * A description of the key. - * - * Use a description that helps your users decide - * whether the key is appropriate for a particular task. - * - */ - kmsDescription?: string; - - /** - * - */ - serverAccessLogsBucket?: s3.IBucket; - - /** - * - */ - serverAccessLogsBucketName?: string; - - /** - * - */ - s3LifeCycleRules?: S3LifeCycleRule[]; - - /** - * Prefix to use in the target bucket for server access logs. - * - * @default - name of this bucket - */ - serverAccessLogsPrefix?: string; - - /** - * @optional - * A list of AWS principals and access type the bucket to grant - * principal should be a valid AWS resource principal like for AWS Macie it - * should be macie.amazonaws.com accessType should be any of these possible - * values BucketAccessType.READONLY, BucketAccessType.WRITEONLY,BucketAccessType.READWRITE and BucketAccessType.NO_ACCESS - * - */ - awsPrincipalAccesses?: { name: string; principal: string; accessType: string }[]; - - /** - * Optional bucket replication property - */ - replicationProps?: BucketReplicationProps; - - /** - * Optional bucket prefix property - */ - bucketPrefixProps?: BucketPrefixProps; - /** - * Prefix for nag suppression - */ - readonly nagSuppressionPrefix?: string; -} - -/** - * Defines a Secure S3 Bucket object. By default a KMS CMK is generated and - * associated to the bucket. - */ -export class Bucket extends Construct { - private readonly bucket: s3.Bucket; - /** - * Bucket encryption type set to a default value of BucketEncryption.KMS, - * which will be determined later based on other properties - */ - private encryptionType: s3.BucketEncryption = s3.BucketEncryption.KMS; - private cmk?: kms.IKey; - private serverAccessLogsPrefix: string | undefined; - private serverAccessLogBucket: cdk.aws_s3.IBucket | undefined; - private lifecycleRules: cdk.aws_s3.LifecycleRule[] = []; - - private readonly props: BucketProps; - - constructor(scope: Construct, id: string, props: BucketProps) { - super(scope, id); - - this.props = props; - - // - // Determine encryption type - this.setEncryptionType(); - - // - // Set access log bucket properties - this.setAccessLogBucketProperties(); - - // - // set Lifecycle rules - this.setLifeCycleRules(); - - this.bucket = new s3.Bucket(this, 'Resource', { - encryption: this.encryptionType, - encryptionKey: this.cmk, - removalPolicy: cdk.RemovalPolicy.RETAIN, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, - bucketName: props.s3BucketName, - versioned: true, - lifecycleRules: this.lifecycleRules, - objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED, - serverAccessLogsBucket: this.serverAccessLogBucket, - // Trailing slash for folder-like prefix in S3 - serverAccessLogsPrefix: this.serverAccessLogsPrefix?.concat('/'), - }); - // Had to be removed to allow CloudTrail access - // this.bucket.addToResourcePolicy( - // new iam.PolicyStatement({ - // sid: 'deny-non-encrypted-object-uploads', - // effect: iam.Effect.DENY, - // actions: ['s3:PutObject'], - // resources: [this.bucket.arnForObjects('*')], - // principals: [new iam.AnyPrincipal()], - // conditions: { - // StringNotEquals: { - // 's3:x-amz-server-side-encryption': 'aws:kms', - // }, - // }, - // }), - // ); - this.bucket.addToResourcePolicy( - new iam.PolicyStatement({ - sid: 'deny-insecure-connections', - effect: iam.Effect.DENY, - actions: ['s3:*'], - resources: [this.bucket.bucketArn, this.bucket.arnForObjects('*')], - principals: [new iam.AnyPrincipal()], - conditions: { - Bool: { - 'aws:SecureTransport': 'false', - }, - }, - }), - ); - - // Add access policy for input AWS principal to the bucket - props.awsPrincipalAccesses?.forEach(input => { - switch (input.accessType) { - case BucketAccessType.READONLY: - this.bucket.grantRead(new iam.ServicePrincipal(input.principal)); - cdk.Tags.of(this.bucket).add(`aws-cdk:auto-${input.name.toLowerCase()}-access-bucket`, 'true'); - break; - case BucketAccessType.WRITEONLY: - this.bucket.grantWrite(new iam.ServicePrincipal(input.principal)); - cdk.Tags.of(this.bucket).add(`aws-cdk:auto-${input.name.toLowerCase()}-access-bucket`, 'true'); - break; - case BucketAccessType.READWRITE: - this.bucket.grantReadWrite(new iam.ServicePrincipal(input.principal)); - cdk.Tags.of(this.bucket).add(`aws-cdk:auto-${input.name.toLowerCase()}-access-bucket`, 'true'); - break; - default: - throw new Error(`Invalid Access Type ${input.accessType} for ${input.principal} principal.`); - } - }); - - // Configure replication - if (props.replicationProps) { - new BucketReplication(this, id + 'Replication', { - source: { bucket: this.bucket }, - destination: { - bucketName: props.replicationProps.destination.bucketName, - accountId: props.replicationProps.destination.accountId, - keyArn: props.replicationProps.destination.keyArn, - }, - kmsKey: props.replicationProps.kmsKey, - logRetentionInDays: props.replicationProps.logRetentionInDays, - useExistingRoles: props.replicationProps.useExistingRoles, - acceleratorPrefix: props.replicationProps.acceleratorPrefix, - }); - } - - // Configure prefix creation - if (props.bucketPrefixProps) { - new BucketPrefix(this, id + 'Prefix', { - source: { bucket: this.bucket }, - bucketPrefixes: props.bucketPrefixProps.bucketPrefixes, - customResourceLambdaEnvironmentEncryptionKmsKey: - props.bucketPrefixProps.customResourceLambdaEnvironmentEncryptionKmsKey, - customResourceLambdaCloudWatchLogKmsKey: props.bucketPrefixProps.customResourceLambdaCloudWatchLogKmsKey, - customResourceLambdaLogRetentionInDays: props.bucketPrefixProps.customResourceLambdaLogRetentionInDays, - nagSuppressionPrefix: - props.nagSuppressionPrefix !== undefined ? `${props.nagSuppressionPrefix}/${id}Prefix` : undefined, - }); - } - } - - public getS3Bucket(): s3.IBucket { - return this.bucket; - } - - public getKey(): kms.IKey { - if (this.cmk) { - return this.cmk; - } else { - throw new Error(`S3 bucket ${this.bucket.bucketName} has no associated CMK.`); - } - } - - protected addValidation(): string[] { - return []; - } - - /** - * Function to set bucket encryption type - */ - private setEncryptionType() { - // Determine encryption type - if (this.props.encryptionType == BucketEncryptionType.SSE_KMS) { - if (this.props.kmsKey) { - this.cmk = this.props.kmsKey; - } else { - this.cmk = new kms.Key(this, 'Cmk', { - enableKeyRotation: true, - description: this.props.kmsDescription, - }); - if (this.props.kmsAliasName) { - this.cmk.addAlias(this.props.kmsAliasName); - } - } - this.encryptionType = s3.BucketEncryption.KMS; - } else if (this.props.encryptionType == BucketEncryptionType.SSE_S3) { - this.encryptionType = s3.BucketEncryption.S3_MANAGED; - } - } - - /** - * Set Server access log bucket property - */ - private setAccessLogBucketProperties() { - if (this.props.serverAccessLogsBucketName && !this.props.serverAccessLogsBucket) { - this.serverAccessLogBucket = s3.Bucket.fromBucketName( - this, - `${pascalCase(this.props.serverAccessLogsBucketName)}-S3LogsBucket`, - this.props.serverAccessLogsBucketName, - ); - } - if (!this.props.serverAccessLogsBucketName && this.props.serverAccessLogsBucket) { - this.serverAccessLogBucket = this.props.serverAccessLogsBucket; - // Get server access logs prefix - if (!this.props.s3BucketName && !this.props.serverAccessLogsPrefix) { - throw new Error('s3BucketName or serverAccessLogsPrefix property must be defined when using serverAccessLogs.'); - } else { - this.serverAccessLogsPrefix = this.props.serverAccessLogsPrefix ?? this.props.s3BucketName; - } - } - if (this.props.serverAccessLogsBucketName && this.props.serverAccessLogsBucket) { - throw new Error('serverAccessLogsBucketName or serverAccessLogsBucket (only one property) should be defined.'); - } - } - - private setLifeCycleRules() { - if (this.props.s3LifeCycleRules) { - for (const lifecycleRuleConfig of this.props.s3LifeCycleRules) { - const transitions = []; - const noncurrentVersionTransitions = []; - - for (const transition of lifecycleRuleConfig.transitions) { - const transitionConfig = { - storageClass: new cdk.aws_s3.StorageClass(transition.storageClass), - transitionAfter: cdk.Duration.days(transition.transitionAfter), - }; - transitions.push(transitionConfig); - } - - for (const nonCurrentTransition of lifecycleRuleConfig.noncurrentVersionTransitions) { - const noncurrentVersionTransitionsConfig = { - storageClass: new cdk.aws_s3.StorageClass(nonCurrentTransition.storageClass), - transitionAfter: cdk.Duration.days(nonCurrentTransition.transitionAfter), - }; - noncurrentVersionTransitions.push(noncurrentVersionTransitionsConfig); - } - - this.lifecycleRules.push({ - abortIncompleteMultipartUploadAfter: cdk.Duration.days( - lifecycleRuleConfig.abortIncompleteMultipartUploadAfter, - ), - enabled: lifecycleRuleConfig.enabled, - expiration: - lifecycleRuleConfig.expiration !== undefined - ? cdk.Duration.days(lifecycleRuleConfig.expiration) - : undefined, - transitions, - noncurrentVersionTransitions, - noncurrentVersionExpiration: cdk.Duration.days(lifecycleRuleConfig.noncurrentVersionExpiration), - expiredObjectDeleteMarker: lifecycleRuleConfig.expiredObjectDeleteMarker, - id: `LifecycleRule${this.props.s3BucketName}${lifecycleRuleConfig.id}`, - prefix: lifecycleRuleConfig.prefix, - }); - } - } else { - this.lifecycleRules.push({ - abortIncompleteMultipartUploadAfter: cdk.Duration.days(1), - enabled: true, - expiration: cdk.Duration.days(1825), - expiredObjectDeleteMarker: false, - id: `LifecycleRule${this.props.s3BucketName}`, - noncurrentVersionExpiration: cdk.Duration.days(1825), - noncurrentVersionTransitions: [ - { - storageClass: cdk.aws_s3.StorageClass.DEEP_ARCHIVE, - transitionAfter: cdk.Duration.days(366), - }, - ], - transitions: [ - { - storageClass: cdk.aws_s3.StorageClass.DEEP_ARCHIVE, - transitionAfter: cdk.Duration.days(365), - }, - ], - }); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/central-logs-bucket.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/central-logs-bucket.ts deleted file mode 100644 index 229865f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/central-logs-bucket.ts +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { Bucket, BucketEncryptionType } from '@aws-accelerator/constructs'; -import { GlobalConfig } from '@aws-accelerator/config'; -import { S3LifeCycleRule } from './bucket'; -import { BucketPrefixProps } from './bucket-prefix'; -import { AwsPrincipalAccessesType, BucketAccessType } from '@aws-accelerator/utils/lib/common-resources'; - -export interface CentralLogsBucketProps { - s3BucketName: string; - kmsAliasName: string; - kmsDescription: string; - principalOrgIdCondition: { [key: string]: string | string[] }; - orgPrincipals: cdk.aws_iam.IPrincipal; - serverAccessLogsBucket: cdk.aws_s3.IBucket; - s3LifeCycleRules?: S3LifeCycleRule[]; - /** - * @optional - * A list of AWS principals and access type the bucket to grant - * principal should be a valid AWS resource principal like for AWS MacieSession it - * should be macie.amazonaws.com accessType should be any of these possible - * values BucketAccessType.READONLY, BucketAccessType.WRITEONLY, & and - * BucketAccessType.READWRITE - */ - awsPrincipalAccesses?: AwsPrincipalAccessesType[]; - bucketPrefixProps?: BucketPrefixProps; - globalConfig?: GlobalConfig; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * Accelerator central log bucket cross account ssm parameter access role name - */ - readonly crossAccountAccessRoleName: string; - /** - * Accelerator central log bucket cmk arn ssm parameter name - */ - readonly cmkArnSsmParameterName: string; - /** - * Accelerator management account access role. - */ - readonly managementAccountAccessRole: string; -} - -/** - * Class to initialize Policy - */ -export class CentralLogsBucket extends Construct { - private readonly bucket: Bucket; - - constructor(scope: Construct, id: string, props: CentralLogsBucketProps) { - super(scope, id); - - const awsPrincipalAccesses = props.awsPrincipalAccesses ?? []; - - // Create Central Logs Bucket - this.bucket = new Bucket(this, 'Resource', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: props.s3BucketName, - kmsAliasName: props.kmsAliasName, - kmsDescription: props.kmsDescription, - serverAccessLogsBucket: props.serverAccessLogsBucket, - s3LifeCycleRules: props.s3LifeCycleRules, - awsPrincipalAccesses: awsPrincipalAccesses.filter(item => item.accessType !== BucketAccessType.NO_ACCESS), - bucketPrefixProps: props.bucketPrefixProps, - nagSuppressionPrefix: `${id}/Resource`, - }); - - this.bucket.getKey().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Enable IAM User Permissions', - principals: [new cdk.aws_iam.AccountRootPrincipal()], - actions: ['kms:*'], - resources: ['*'], - }), - ); - - this.bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - principals: [ - new cdk.aws_iam.ServicePrincipal('cloudtrail.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('config.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('ssm.amazonaws.com'), - ], - actions: ['s3:PutObject'], - resources: [this.bucket.getS3Bucket().arnForObjects('*')], - conditions: { - StringEquals: { - 's3:x-amz-acl': 'bucket-owner-full-control', - }, - }, - }), - ); - - this.bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - principals: [ - new cdk.aws_iam.ServicePrincipal('cloudtrail.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('config.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com'), - ], - actions: ['s3:GetBucketAcl', 's3:ListBucket'], - resources: [this.bucket.getS3Bucket().bucketArn], - }), - ); - - this.bucket.getS3Bucket().encryptionKey?.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow S3 use of the key', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:GenerateRandom', - 'kms:GetKeyPolicy', - 'kms:GetKeyRotationStatus', - 'kms:ListAliases', - 'kms:ListGrants', - 'kms:ListKeyPolicies', - 'kms:ListKeys', - 'kms:ListResourceTags', - 'kms:ListRetirableGrants', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - ], - principals: [new cdk.aws_iam.ServicePrincipal('s3.amazonaws.com')], - resources: ['*'], - }), - ); - - this.bucket.getS3Bucket().encryptionKey?.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow AWS Services to encrypt and describe logs', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyPair', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - ], - principals: [ - new cdk.aws_iam.ServicePrincipal('config.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('cloudtrail.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('delivery.logs.amazonaws.com'), - new cdk.aws_iam.ServicePrincipal('ssm.amazonaws.com'), - ], - resources: ['*'], - }), - ); - - // Allow bucket encryption key for given aws principals - awsPrincipalAccesses - .filter(item => item.accessType !== BucketAccessType.NO_ACCESS) - .forEach(item => { - this.bucket.getS3Bucket().encryptionKey?.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow ${item.name} service to use the encryption key`, - principals: [new cdk.aws_iam.ServicePrincipal(item.principal)], - actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - resources: ['*'], - }), - ); - }); - - props.awsPrincipalAccesses?.forEach(item => { - if (item.name === 'SessionManager') { - this.bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization principals to put objects', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutObjectAcl', 's3:PutObject'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [`${this.bucket.getS3Bucket().bucketArn}/*`], - conditions: { - StringEquals: { - ...props.principalOrgIdCondition, - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}-*`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/cdk-accel-*`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.managementAccountAccessRole}-*`, - ], - }, - }, - }), - ); - - this.bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization principals to get encryption context and acl', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetEncryptionConfiguration', 's3:GetBucketAcl'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [`${this.bucket.getS3Bucket().bucketArn}`], - conditions: { - StringEquals: { - ...props.principalOrgIdCondition, - }, - }, - }), - ); - } - }); - - // Grant organization principals to use the bucket - this.bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization principals to use the bucket', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetBucketLocation', 's3:GetBucketAcl', 's3:PutObject', 's3:GetObject', 's3:ListBucket'], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [this.bucket.getS3Bucket().bucketArn, `${this.bucket.getS3Bucket().bucketArn}/*`], - conditions: { - StringEquals: { - ...props.principalOrgIdCondition, - }, - ArnLike: { - 'aws:PrincipalARN': [ - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}-*`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/cdk-accel-*`, - `arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.managementAccountAccessRole}-*`, - ], - }, - }, - }), - ); - - // Allow bucket to be used by other buckets in organization for replication - this.bucket.getS3Bucket().addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization use of the bucket for replication', - actions: [ - 's3:List*', - 's3:GetBucketVersioning', - 's3:PutBucketVersioning', - 's3:ReplicateDelete', - 's3:ReplicateObject', - 's3:ObjectOwnerOverrideToBucketOwner', - ], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: [this.bucket.getS3Bucket().bucketArn, this.bucket.getS3Bucket().arnForObjects('*')], - conditions: { - StringEquals: { - ...props.principalOrgIdCondition, - }, - }, - }), - ); - - this.bucket.getS3Bucket().encryptionKey?.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'Allow Organization use of the key', - actions: [ - 'kms:Decrypt', - 'kms:DescribeKey', - 'kms:Encrypt', - 'kms:GenerateDataKey', - 'kms:GenerateDataKeyPair', - 'kms:GenerateDataKeyPairWithoutPlaintext', - 'kms:GenerateDataKeyWithoutPlaintext', - 'kms:ReEncryptFrom', - 'kms:ReEncryptTo', - 'kms:ListAliases', - ], - principals: [new cdk.aws_iam.AnyPrincipal()], - resources: ['*'], - conditions: { - StringEquals: { - ...props.principalOrgIdCondition, - }, - }, - }), - ); - - const centralLogBucketKmsKeyArnSsmParameter = new cdk.aws_ssm.StringParameter( - this, - 'SsmParamCentralAccountBucketKMSArn', - { - parameterName: props.cmkArnSsmParameterName, - stringValue: this.bucket.getKey().keyArn, - }, - ); - - // SSM parameter access IAM Role for - new cdk.aws_iam.Role(this, 'CrossAccountCentralBucketKMSArnSsmParamAccessRole', { - roleName: props.crossAccountAccessRoleName, - assumedBy: props.orgPrincipals, - inlinePolicies: { - default: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter'], - resources: [centralLogBucketKmsKeyArnSsmParameter.parameterArn], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}-*`], - }, - }, - }), - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:DescribeParameters'], - resources: ['*'], - conditions: { - ArnLike: { - 'aws:PrincipalARN': [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}-*`], - }, - }, - }), - ], - }), - }, - }); - } - - public getS3Bucket(): Bucket { - return this.bucket; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/public-access-block.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/public-access-block.ts deleted file mode 100644 index 997d452..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/public-access-block.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -export interface S3PublicAccessBlockProps { - blockPublicAcls: boolean; - blockPublicPolicy: boolean; - ignorePublicAcls: boolean; - restrictPublicBuckets: boolean; - /** - * @default cdk.Aws.ACCOUNT_ID - */ - accountId?: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class to initialize Policy - */ -export class S3PublicAccessBlock extends Construct { - readonly id: string; - - constructor(scope: Construct, id: string, props: S3PublicAccessBlockProps) { - super(scope, id); - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::S3PutPublicAccessBlock', { - codeDirectory: path.join(__dirname, 'put-public-access-block/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['s3:PutAccountPublicAccessBlock'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::PutPublicAccessBlock', - serviceToken: provider.serviceToken, - properties: { - blockPublicAcls: props.blockPublicAcls, - blockPublicPolicy: props.blockPublicPolicy, - ignorePublicAcls: props.ignorePublicAcls, - restrictPublicBuckets: props.restrictPublicBuckets, - accountId: props.accountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/index.ts deleted file mode 100644 index 8f84878..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * put-bucket-prefix - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string | undefined; - } - | undefined -> { - const bucketName: string = event.ResourceProperties['bucketName']; - const kmsKeyArn: string = event.ResourceProperties['kmsKeyArn']; - const solutionId = process.env['SOLUTION_ID']; - const s3Client = new AWS.S3({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - await throttlingBackOff(() => - s3Client - .putBucketEncryption({ - Bucket: bucketName, - ServerSideEncryptionConfiguration: { - Rules: [ - { - BucketKeyEnabled: false, - ApplyServerSideEncryptionByDefault: { - SSEAlgorithm: 'aws:kms', - KMSMasterKeyID: kmsKeyArn, - }, - }, - ], - }, - }) - .promise(), - ); - - return { - PhysicalResourceId: bucketName, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/package.json deleted file mode 100644 index 91126bb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-s3-put-bucket-encryption", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-encryption/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/index.ts deleted file mode 100644 index ae363c4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/index.ts +++ /dev/null @@ -1,393 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - PolicyStatementType, - AcceleratorImportedBucketType, - AwsPrincipalAccessesType, - PrincipalOrgIdConditionType, -} from '@aws-accelerator/utils/lib/common-resources'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -import * as path from 'path'; -AWS.config.logger = console; - -/** - * put-bucket-prefix - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string | undefined; - } - | undefined -> { - const partition = event.ServiceToken.split(':')[1]; - const sourceAccount: string = event.ResourceProperties['sourceAccount']; - const bucketType: AcceleratorImportedBucketType = event.ResourceProperties['bucketType']; - const bucketName: string = event.ResourceProperties['bucketName']; - const bucketArn: string = event.ResourceProperties['bucketArn']; - const applyAcceleratorManagedPolicy: string = event.ResourceProperties['applyAcceleratorManagedPolicy']; - const bucketPolicyFilePaths: string[] = event.ResourceProperties['bucketPolicyFilePaths']; - - const organizationId: string | undefined = event.ResourceProperties['organizationId']; - const awsPrincipalAccesses: AwsPrincipalAccessesType[] | undefined = event.ResourceProperties['awsPrincipalAccesses']; - const principalOrgIdCondition: PrincipalOrgIdConditionType | undefined = - event.ResourceProperties['principalOrgIdCondition']; - - const elbAccountId: string | undefined = event.ResourceProperties['elbAccountId']; - const firewallRoles: string[] = event.ResourceProperties['firewallRoles'] ?? []; - - const solutionId = process.env['SOLUTION_ID']; - const s3Client = new AWS.S3({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (applyAcceleratorManagedPolicy === 'true' || bucketPolicyFilePaths.length > 0) { - const generatedPolicyString = generateBucketPolicy( - firewallRoles, - applyAcceleratorManagedPolicy, - partition, - sourceAccount, - bucketType, - bucketArn, - bucketPolicyFilePaths, - principalOrgIdCondition, - awsPrincipalAccesses, - elbAccountId, - ); - - let replacedPolicyString = generatedPolicyString; - if (organizationId) { - replacedPolicyString = generatedPolicyString.replace(/\${ORG_ID}/g, organizationId); - } - - await throttlingBackOff(() => - s3Client.putBucketPolicy({ Bucket: bucketName, Policy: replacedPolicyString }).promise(), - ); - } - return { - PhysicalResourceId: bucketName, - Status: 'SUCCESS', - }; - - case 'Delete': - // Skip delete bucket policy - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} - -function generateBucketPolicy( - firewallRoles: string[], - applyAcceleratorManagedPolicy: string, - partition: string, - sourceAccount: string, - bucketType: AcceleratorImportedBucketType, - bucketArn: string, - bucketPolicyFilePaths: string[], - principalOrgIdCondition?: PrincipalOrgIdConditionType, - awsPrincipalAccesses?: AwsPrincipalAccessesType[], - elbAccountId?: string, -): string { - const policyStatements: PolicyStatementType[] = []; - - if (applyAcceleratorManagedPolicy === 'true') { - switch (bucketType) { - case AcceleratorImportedBucketType.ASSETS_BUCKET: - policyStatements.push({ - Sid: 'deny-insecure-connections', - Effect: 'Deny', - Principal: { - AWS: '*', - }, - Action: 's3:*', - Resource: [bucketArn, `${bucketArn}/*`], - Condition: { - Bool: { - 'aws:SecureTransport': 'false', - }, - }, - }); - if (firewallRoles.length > 0) { - policyStatements.push({ - Sid: 'Allow Organization principals to use the bucket', - Effect: 'Allow', - Principal: { - AWS: '*', - }, - Action: ['s3:GetObject', 's3:ListBucket'], - Resource: [bucketArn, `${bucketArn}/*`], - Condition: { - StringEquals: { - ...principalOrgIdCondition, - }, - StringLike: { - 'aws:PrincipalARN': firewallRoles, - }, - }, - }); - } - - break; - case AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET: - policyStatements.push({ - Sid: 'deny-insecure-connections', - Effect: 'Deny', - Principal: { - AWS: '*', - }, - Action: 's3:*', - Resource: [bucketArn, `${bucketArn}/*`], - Condition: { - Bool: { - 'aws:SecureTransport': 'false', - }, - }, - }); - - policyStatements.push({ - Effect: 'Allow', - Principal: { - Service: [ - 'cloudtrail.amazonaws.com', - 'config.amazonaws.com', - 'delivery.logs.amazonaws.com', - 'ssm.amazonaws.com', - ], - }, - Action: 's3:PutObject', - Resource: [`${bucketArn}/*`], - Condition: { - StringEquals: { - 's3:x-amz-acl': 'bucket-owner-full-control', - }, - }, - }); - - policyStatements.push({ - Effect: 'Allow', - Principal: { - Service: ['cloudtrail.amazonaws.com', 'config.amazonaws.com', 'delivery.logs.amazonaws.com'], - }, - Action: ['s3:GetBucketAcl', 's3:ListBucket'], - Resource: [bucketArn], - }); - - policyStatements.push({ - Sid: 'Allow Organization principals to use the bucket', - Effect: 'Allow', - Principal: { - AWS: '*', - }, - Action: ['s3:GetBucketLocation', 's3:GetBucketAcl', 's3:PutObject', 's3:GetObject', 's3:ListBucket'], - Resource: [bucketArn, `${bucketArn}/*`], - Condition: { - StringEquals: { - ...principalOrgIdCondition, - }, - }, - }); - - policyStatements.push({ - Sid: 'Allow Organization use of the bucket for replication', - Effect: 'Allow', - Action: [ - 's3:List*', - 's3:GetBucketVersioning', - 's3:PutBucketVersioning', - 's3:ReplicateDelete', - 's3:ReplicateObject', - 's3:ObjectOwnerOverrideToBucketOwner', - ], - Principal: { - AWS: '*', - }, - Resource: [bucketArn, `${bucketArn}/*`], - Condition: { - StringEquals: { - ...principalOrgIdCondition, - }, - }, - }); - - awsPrincipalAccesses?.forEach(item => { - if (item.name === 'SessionManager') { - policyStatements.push({ - Sid: 'Allow Organization principals to put objects', - Effect: 'Allow', - Action: ['s3:PutObjectAcl', 's3:PutObject'], - Principal: { - AWS: '*', - }, - Resource: [`${bucketArn}/*`], - Condition: { - StringEquals: { - ...principalOrgIdCondition, - }, - }, - }); - - policyStatements.push({ - Sid: 'Allow Organization principals to get encryption context and acl', - Effect: 'Allow', - Action: ['s3:GetEncryptionConfiguration', 's3:GetBucketAcl'], - Principal: { - AWS: '*', - }, - Resource: [bucketArn], - Condition: { - StringEquals: { - ...principalOrgIdCondition, - }, - }, - }); - } else { - policyStatements.push({ - Sid: `Allow read write access for ${item.name} service principal`, - Effect: 'Allow', - Action: [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:List*', - 's3:DeleteObject*', - 's3:PutObject', - 's3:PutObjectLegalHold', - 's3:PutObjectRetention', - 's3:PutObjectTagging', - 's3:PutObjectVersionTagging', - 's3:Abort*', - ], - Principal: { - Service: [item.principal], - }, - Resource: [bucketArn, `${bucketArn}/*`], - }); - } - }); - break; - case AcceleratorImportedBucketType.ELB_LOGS_BUCKET: - let elbPrincipal: PrincipalOrgIdConditionType = { - Service: ['logdelivery.elasticloadbalancing.amazonaws.com'], - }; - - if (elbAccountId) { - elbPrincipal = { - AWS: [`arn:${partition}:iam::${sourceAccount}:root`], - }; - } - - policyStatements.push({ - Sid: 'Allow get acl access for SSM principal', - Effect: 'Allow', - Action: ['s3:GetBucketAcl'], - Principal: { - Service: ['ssm.amazonaws.com'], - }, - Resource: [`${bucketArn}`], - }); - - policyStatements.push({ - Sid: 'Allow write access for ELB Account principal', - Effect: 'Allow', - Action: ['s3:PutObject'], - Principal: elbPrincipal, - Resource: [bucketArn, `${bucketArn}/*`], - }); - - policyStatements.push({ - Sid: 'Allow write access for delivery logging service principal', - Effect: 'Allow', - Action: ['s3:PutObject'], - Principal: { - Service: ['delivery.logs.amazonaws.com'], - }, - Resource: [`${bucketArn}/*`], - Condition: { - StringEquals: { - 's3:x-amz-acl': 'bucket-owner-full-control', - }, - }, - }); - - policyStatements.push({ - Sid: 'Allow read bucket ACL access for delivery logging service principal', - Effect: 'Allow', - Action: ['s3:GetBucketAcl'], - Principal: { - Service: ['delivery.logs.amazonaws.com'], - }, - Resource: [`${bucketArn}`], - }); - - policyStatements.push({ - Sid: 'Allow Organization principals to use of the bucket', - Effect: 'Allow', - Action: ['s3:GetBucketLocation', 's3:PutObject'], - Principal: { - AWS: '*', - }, - Resource: [bucketArn, `${bucketArn}/*`], - Condition: { - StringEquals: { - ...principalOrgIdCondition, - }, - }, - }); - break; - case AcceleratorImportedBucketType.SERVER_ACCESS_LOGS_BUCKET: - policyStatements.push({ - Sid: 'Allow write access for logging service principal', - Effect: 'Allow', - Action: ['s3:PutObject'], - Principal: { - Service: ['logging.s3.amazonaws.com'], - }, - Resource: [`${bucketArn}/*`], - Condition: { - StringEquals: { - 'aws:SourceAccount': sourceAccount, - }, - }, - }); - break; - default: - throw new Error(`Invalid bucket type ${bucketType}`); - } - } - - for (const bucketPolicyFilePath of bucketPolicyFilePaths) { - const policyFile = path.join(__dirname, bucketPolicyFilePath); - const policyContent: { Version?: string; Statement: PolicyStatementType[] } = JSON.parse( - JSON.stringify(require(policyFile)), - ); - - for (const statement of policyContent.Statement) { - policyStatements.push(statement); - } - } - - const policyDocument: { Version: string; Statement: PolicyStatementType[] } = { - Version: '2012-10-17', - Statement: policyStatements, - }; - - return JSON.stringify(policyDocument); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/package.json deleted file mode 100644 index 42d874f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-s3-put-bucket-policy", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-policy/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/index.ts deleted file mode 100644 index d75c378..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * put-bucket-prefix - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string | undefined; - } - | undefined -> { - const sourceBucketName: string = event.ResourceProperties['sourceBucketName']; - const sourceBucketKeyArn: string = event.ResourceProperties['sourceBucketKeyArn']; - const bucketPrefixes: string[] = event.ResourceProperties['bucketPrefixes']; - const solutionId = process.env['SOLUTION_ID']; - const s3Client = new AWS.S3({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - for (const prefix of bucketPrefixes) { - console.log(`starting - check bucket prefix for ${prefix}`); - const listObjectsResponse = await throttlingBackOff(() => - s3Client - .listObjectsV2({ - Bucket: sourceBucketName, - Prefix: prefix + '/', - MaxKeys: 1, - Delimiter: '/', - }) - .promise(), - ); - if (!('Contents' in listObjectsResponse) || listObjectsResponse.Contents?.length === 0) { - console.log(`starting - create bucket prefix for ${prefix}`); - await throttlingBackOff(() => - s3Client - .putObject({ - Bucket: sourceBucketName, - Key: prefix + '/', - ServerSideEncryption: 'aws:kms', - SSEKMSKeyId: sourceBucketKeyArn, - }) - .promise(), - ); - } - } - return { - PhysicalResourceId: `s3-prefix-${sourceBucketName}`, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/package.json deleted file mode 100644 index 3af56c9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-s3-create-bucket-prefix", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-prefix/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/index.ts deleted file mode 100644 index 57ee1e3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/index.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -import * as console from 'console'; -AWS.config.logger = console; - -/** - * put-public-access-block - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const sourceBucketName: string = event.ResourceProperties['sourceBucketName']; - const solutionId = process.env['SOLUTION_ID']; - - const s3Client = new AWS.S3({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const replicationRoleArn: string = event.ResourceProperties['replicationRoleArn']; - const prefix: string = event.ResourceProperties['prefix']; - - const destinationBucketArn: string = event.ResourceProperties['destinationBucketArn']; - const destinationBucketKeyArn: string = event.ResourceProperties['destinationBucketKeyArn']; - const destinationAccountId: string = event.ResourceProperties['destinationAccountId']; - - let replicateEncryptedObjectsStatus = 'Disabled'; - let encryptionConfiguration: AWS.S3.EncryptionConfiguration | undefined; - - if (destinationBucketKeyArn) { - replicateEncryptedObjectsStatus = 'Enabled'; - encryptionConfiguration = { - ReplicaKmsKeyID: destinationBucketKeyArn, - }; - } - - const replicationRules: AWS.S3.ReplicationRules = [ - { - ID: `${sourceBucketName}-replication-rule`, - Status: 'Enabled', - Prefix: prefix, - SourceSelectionCriteria: { - SseKmsEncryptedObjects: { - Status: replicateEncryptedObjectsStatus, - }, - }, - Destination: { - Bucket: destinationBucketArn, - Account: destinationAccountId, - EncryptionConfiguration: encryptionConfiguration, - StorageClass: 'STANDARD', - AccessControlTranslation: { - Owner: 'Destination', - }, - }, - }, - ]; - - await throttlingBackOff(() => - s3Client - .putBucketReplication({ - Bucket: sourceBucketName, - ReplicationConfiguration: { Role: replicationRoleArn, Rules: replicationRules }, - }) - .promise(), - ); - return { - PhysicalResourceId: `s3-replication-${sourceBucketName}`, - Status: 'SUCCESS', - }; - - case 'Delete': - try { - await throttlingBackOff(() => - s3Client - .deleteBucketReplication({ - Bucket: sourceBucketName, - }) - .promise(), - ); - } catch (error) { - console.error(JSON.stringify(error)); - } - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/package.json deleted file mode 100644 index 334f473..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-s3-put-bucket-replication", - "version": "0.0.0", - "private": true, - "description": "custom resource to add bucket replication", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-bucket-replication/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/index.ts deleted file mode 100644 index 2ba9df1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * put-public-access-block - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const accountId: string = event.ResourceProperties['accountId']; - const blockPublicAcls: boolean = event.ResourceProperties['blockPublicAcls'] === 'true'; - const blockPublicPolicy: boolean = event.ResourceProperties['blockPublicPolicy'] === 'true'; - const ignorePublicAcls: boolean = event.ResourceProperties['ignorePublicAcls'] === 'true'; - const restrictPublicBuckets: boolean = event.ResourceProperties['restrictPublicBuckets'] === 'true'; - const solutionId = process.env['SOLUTION_ID']; - - const s3ControlClient = new AWS.S3Control({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - await throttlingBackOff(() => - s3ControlClient - .putPublicAccessBlock({ - AccountId: accountId, - PublicAccessBlockConfiguration: { - BlockPublicAcls: blockPublicAcls, - BlockPublicPolicy: blockPublicPolicy, - IgnorePublicAcls: ignorePublicAcls, - RestrictPublicBuckets: restrictPublicBuckets, - }, - }) - .promise(), - ); - return { - PhysicalResourceId: `s3-bpa-${accountId}`, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/package.json deleted file mode 100644 index ae9a2d9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-s3-put-public-access-block", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/put-public-access-block/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/index.ts deleted file mode 100644 index 86f4bb9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/index.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -AWS.config.logger = console; - -/** - * put-bucket-prefix - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string | undefined; - Data: { bucketKmsArn: string } | undefined; - } - | undefined -> { - const bucketName: string = event.ResourceProperties['bucketName']; - const validationCheckList: string[] = event.ResourceProperties['validationCheckList']; - const encryptionType: 'kms' | 's3' = event.ResourceProperties['encryptionType']; - const solutionId = process.env['SOLUTION_ID']; - const s3Client = new AWS.S3({ customUserAgent: solutionId }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - let validationStatus = true; - let bucketKmsArn: string | undefined; - if (validationCheckList.includes('encryption')) { - const response = await validateEncryption(s3Client, bucketName, encryptionType); - validationStatus = response.status; - bucketKmsArn = response.bucketKmsArn; - } - - if (validationStatus) { - return { - PhysicalResourceId: bucketName, - Status: 'SUCCESS', - Data: { bucketKmsArn: bucketKmsArn! }, - }; - } else { - return { - PhysicalResourceId: bucketName, - Status: 'FAILURE', - Data: undefined, - }; - } - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - Data: undefined, - }; - } -} - -/** - * Function to validate bucket encryption - * @param s3Client - {@link AWS.S3} - * @param bucketName - string - * @returns - */ -async function validateEncryption( - s3Client: AWS.S3, - bucketName: string, - encryptionType: 'kms' | 's3', -): Promise<{ bucketKmsArn: string | undefined; status: boolean }> { - const response = await throttlingBackOff(() => s3Client.getBucketEncryption({ Bucket: bucketName }).promise()); - - if (encryptionType === 'kms') { - if ( - response.ServerSideEncryptionConfiguration?.Rules[0].ApplyServerSideEncryptionByDefault?.SSEAlgorithm === - 'aws:kms' && - response.ServerSideEncryptionConfiguration?.Rules[0].ApplyServerSideEncryptionByDefault?.KMSMasterKeyID - ) { - return { - status: true, - bucketKmsArn: - response.ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.KMSMasterKeyID, - }; - } else { - console.warn( - `Existing bucket ${bucketName} must have server-side encryption with AWS Key Management service keys (SSE-KMS)`, - ); - return { - status: true, - bucketKmsArn: undefined, - }; - } - } else { - if ( - response.ServerSideEncryptionConfiguration?.Rules[0].ApplyServerSideEncryptionByDefault?.SSEAlgorithm === 'AES256' - ) { - return { - status: true, - bucketKmsArn: undefined, - }; - } else { - console.warn( - `Existing bucket ${bucketName} must have server-side encryption with Amazon S3 managed keys (SSE-S3).`, - ); - return { - status: true, - bucketKmsArn: undefined, - }; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/package.json deleted file mode 100644 index e99c2f6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-s3-validate-bucket-config", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket-config/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket.ts b/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket.ts deleted file mode 100644 index 4b04d1f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-s3/validate-bucket.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { LzaCustomResource } from '../lza-custom-resource'; - -/** - * Initialized ValidateBucketProps properties - */ -export interface ValidateBucketProps { - /** - * The name of the bucket which will be validated - */ - readonly bucket: cdk.aws_s3.IBucket; - /** - * Bucket validation check list - */ - readonly validationCheckList: string[]; - /** - * Bucket encryption type - */ - readonly encryptionType: 'kms' | 's3'; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaEnvironmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly customResourceLambdaCloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly customResourceLambdaLogRetentionInDays: number; -} - -/** - * Class for ValidateBucket - */ -export class ValidateBucket extends Construct { - public bucketKmsArn: string | undefined; - constructor(scope: Construct, id: string, props: ValidateBucketProps) { - super(scope, id); - - const resourceName = 'ValidateBucket'; - - const lzaCustomResource = new LzaCustomResource(this, resourceName, { - resource: { - name: resourceName, - parentId: id, - properties: [ - { bucketName: props.bucket.bucketName }, - { validationCheckList: props.validationCheckList }, - { encryptionType: props.encryptionType }, - ], - forceUpdate: true, // Force custom resource to execute to check if encryption changed for the bucket from last LZA deployment - }, - lambda: { - assetPath: path.join(__dirname, 'validate-bucket-config/dist'), - environmentEncryptionKmsKey: props.customResourceLambdaEnvironmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.customResourceLambdaCloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.customResourceLambdaLogRetentionInDays, - timeOut: cdk.Duration.minutes(5), - roleInitialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetEncryptionConfiguration'], - resources: [props.bucket.bucketArn], - }), - ], - }, - }); - - if (props.encryptionType === 'kms') { - this.bucketKmsArn = lzaCustomResource.resource.getAtt('bucketKmsArn').toString(); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/index.ts deleted file mode 100644 index 5085111..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/index.ts +++ /dev/null @@ -1,343 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * batch-enable-standards - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - const inputStandards = JSON.parse(JSON.stringify(event.ResourceProperties['standards'])); - - const securityHubClient = new AWS.SecurityHub({ region: region, customUserAgent: solutionId }); - - // Get AWS defined security standards name and ARN - const awsSecurityHubStandards: { [name: string]: string }[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => securityHubClient.describeStandards({ NextToken: nextToken }).promise()); - for (const standard of page.Standards ?? []) { - if (standard.StandardsArn && standard.Name) { - const securityHubStandard: { [name: string]: string } = {}; - securityHubStandard[standard.Name] = standard.StandardsArn; - awsSecurityHubStandards.push(securityHubStandard); - } - } - nextToken = page.NextToken; - } while (nextToken); - - // Enable security hub is admin account before creating delegation admin account, if this wasn't enabled by organization delegation - await enableSecurityHub(securityHubClient); - - const standardsModificationList = await getStandardsModificationList( - securityHubClient, - inputStandards, - awsSecurityHubStandards, - ); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('starting - BatchEnableStandardsCommand'); - - // When there are standards to be enable - if (standardsModificationList.toEnableStandardRequests.length > 0) { - console.log('To enable:'); - console.log(standardsModificationList.toEnableStandardRequests); - await throttlingBackOff(() => - securityHubClient - .batchEnableStandards({ - StandardsSubscriptionRequests: standardsModificationList.toEnableStandardRequests, - }) - .promise(), - ); - } - - // When there are standards to be disable - if (standardsModificationList.toDisableStandardArns!.length > 0) { - console.log(`Disabling standard ${standardsModificationList.toDisableStandardArns!}`); - await throttlingBackOff(() => - securityHubClient - .batchDisableStandards({ - StandardsSubscriptionArns: standardsModificationList.toDisableStandardArns!, - }) - .promise(), - ); - } - - // get list of controls to modify - const controlsToModify = await getControlArnsToModify(securityHubClient, inputStandards, awsSecurityHubStandards); - - // Enable standard controls - for (const controlArnToModify of controlsToModify.disableStandardControlArns) { - await throttlingBackOff(() => - securityHubClient - .updateStandardsControl({ - StandardsControlArn: controlArnToModify, - ControlStatus: 'DISABLED', - DisabledReason: 'Control disabled by Accelerator', - }) - .promise(), - ); - } - - // Disable standard controls - for (const controlArnToModify of controlsToModify.enableStandardControlArns) { - await throttlingBackOff(() => - securityHubClient - .updateStandardsControl({ StandardsControlArn: controlArnToModify, ControlStatus: 'ENABLED' }) - .promise(), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - const existingEnabledStandards = await getExistingEnabledStandards(securityHubClient); - const subscriptionArns: string[] = []; - existingEnabledStandards.forEach(standard => { - subscriptionArns.push(standard.StandardsSubscriptionArn); - }); - - if (subscriptionArns.length > 0) { - console.log('Below listed standards disable during delete'); - console.log(subscriptionArns); - await throttlingBackOff(() => - securityHubClient.batchDisableStandards({ StandardsSubscriptionArns: subscriptionArns }).promise(), - ); - } - - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Enable SecurityHub - * @param securityHubClient - */ -async function enableSecurityHub(securityHubClient: AWS.SecurityHub): Promise { - try { - await throttlingBackOff(() => securityHubClient.enableSecurityHub({ EnableDefaultStandards: false }).promise()); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return; - } - throw new Error(`SecurityHub enable issue error message - ${e}`); - } -} - -/** - * Function to provide existing enabled standards - * @param securityHubClient - */ -async function getExistingEnabledStandards( - securityHubClient: AWS.SecurityHub, -): Promise { - const response = await throttlingBackOff(() => securityHubClient.getEnabledStandards({}).promise()); - - // Get list of existing enabled standards within securityhub - const existingEnabledStandardArns: AWS.SecurityHub.StandardsSubscription[] = []; - response.StandardsSubscriptions!.forEach(item => { - existingEnabledStandardArns.push({ - StandardsArn: item.StandardsArn!, - StandardsInput: item.StandardsInput!, - StandardsStatus: item.StandardsStatus!, - StandardsSubscriptionArn: item.StandardsSubscriptionArn!, - }); - // } - }); - - return existingEnabledStandardArns; -} - -/** - * Function to provide list of control arns for standards to be enable or disable - * @param securityHubClient - * @param inputStandards - * @param awsSecurityHubStandards - */ -async function getControlArnsToModify( - securityHubClient: AWS.SecurityHub, - inputStandards: { name: string; enable: string; controlsToDisable: string[] | undefined }[], - awsSecurityHubStandards: { [name: string]: string }[], -): Promise<{ disableStandardControlArns: string[]; enableStandardControlArns: string[] }> { - const existingEnabledStandards = await getExistingEnabledStandards(securityHubClient); - const disableStandardControls: string[] = []; - const enableStandardControls: string[] = []; - - let nextToken: string | undefined = undefined; - for (const inputStandard of inputStandards) { - console.log(`inputStandard: ${JSON.stringify(inputStandard)}`); - if (inputStandard.enable === 'true') { - for (const awsSecurityHubStandard of awsSecurityHubStandards) { - if (awsSecurityHubStandard[inputStandard.name]) { - console.log(`Standard Name: ${awsSecurityHubStandard[inputStandard.name]}`); - const existingEnabledStandard = existingEnabledStandards.find( - item => item.StandardsArn === awsSecurityHubStandard[inputStandard.name], - ); - if (existingEnabledStandard) { - console.log(`Getting controls for ${existingEnabledStandard?.StandardsSubscriptionArn} subscription`); - - const standardsControl: AWS.SecurityHub.StandardsControl[] = []; - - do { - const page: AWS.SecurityHub.DescribeStandardsControlsResponse = await getDescribeStandardsControls( - securityHubClient, - existingEnabledStandard?.StandardsSubscriptionArn, - nextToken, - ); - for (const control of page.Controls ?? []) { - standardsControl.push(control); - } - nextToken = page.NextToken; - } while (nextToken); - - while (standardsControl.length === 0) { - console.warn( - `Delaying standard control retrieval by 10000 ms for ${existingEnabledStandard?.StandardsSubscriptionArn}`, - ); - await delay(10000); - console.warn(`Rechecking - Getting controls for ${existingEnabledStandard?.StandardsSubscriptionArn}`); - nextToken = undefined; - do { - const page: AWS.SecurityHub.DescribeStandardsControlsResponse = await getDescribeStandardsControls( - securityHubClient, - existingEnabledStandard?.StandardsSubscriptionArn, - nextToken, - ); - for (const control of page.Controls ?? []) { - standardsControl.push(control); - } - nextToken = page.NextToken; - } while (nextToken); - } - - console.log(`When control list available for ${existingEnabledStandard?.StandardsSubscriptionArn}`); - console.log(standardsControl); - - for (const control of standardsControl) { - if (inputStandard.controlsToDisable?.includes(control.ControlId!)) { - console.log(control.ControlId!); - disableStandardControls.push(control.StandardsControlArn!); - } else { - if (control.ControlStatus == 'DISABLED') { - console.log('following is disabled need to be enable now'); - console.log(control.ControlId!); - enableStandardControls.push(control.StandardsControlArn!); - } - } - } - } - } - } - } - } - - return { disableStandardControlArns: disableStandardControls, enableStandardControlArns: enableStandardControls }; -} - -/** - * Function to be executed before event specific action starts, this function makes the list of standards to be enable or disable based on the input - * @param securityHubClient - * @param inputStandards - * @param awsSecurityHubStandards - */ -async function getStandardsModificationList( - securityHubClient: AWS.SecurityHub, - inputStandards: { name: string; enable: string; controlsToDisable: string[] | undefined }[], - awsSecurityHubStandards: { [name: string]: string }[], -): Promise<{ - toEnableStandardRequests: AWS.SecurityHub.StandardsSubscriptionRequests; - toDisableStandardArns: string[] | undefined; -}> { - const existingEnabledStandards = await getExistingEnabledStandards(securityHubClient); - const toEnableStandardRequests: AWS.SecurityHub.StandardsSubscriptionRequests = []; - const toDisableStandardArns: string[] | undefined = []; - - if (!inputStandards || inputStandards.length === 0) { - for (const existingEnabledStandard of existingEnabledStandards) { - toDisableStandardArns.push(existingEnabledStandard?.StandardsSubscriptionArn); - } - } - - for (const inputStandard of inputStandards) { - if (inputStandard.enable === 'true') { - for (const awsSecurityHubStandard of awsSecurityHubStandards) { - if (awsSecurityHubStandard[inputStandard.name]) { - const existingEnabledStandard = existingEnabledStandards.filter( - item => item.StandardsArn === awsSecurityHubStandard[inputStandard.name], - ); - if (existingEnabledStandard.length === 0) { - toEnableStandardRequests.push({ StandardsArn: awsSecurityHubStandard[inputStandard.name] }); - } - } - } - } else { - for (const awsSecurityHubStandard of awsSecurityHubStandards) { - if (awsSecurityHubStandard[inputStandard.name]) { - const existingEnabledStandard = existingEnabledStandards.find( - item => item.StandardsArn === awsSecurityHubStandard[inputStandard.name], - ); - - if (existingEnabledStandard) { - toDisableStandardArns.push(existingEnabledStandard?.StandardsSubscriptionArn); - } - } - } - } - } - - return { toEnableStandardRequests: toEnableStandardRequests, toDisableStandardArns: toDisableStandardArns }; -} - -async function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function getDescribeStandardsControls( - securityHubClient: AWS.SecurityHub, - standardsSubscriptionArn: string, - nextToken?: string, -): Promise { - return throttlingBackOff(() => - securityHubClient - .describeStandardsControls({ - StandardsSubscriptionArn: standardsSubscriptionArn, - NextToken: nextToken, - }) - .promise(), - ); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/package.json deleted file mode 100644 index aa45d72..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-securityhub-batch-enable-standards", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/batch-enable-standards/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/index.ts deleted file mode 100644 index 737be47..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/index.ts +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { OrganizationsClient, ListAccountsCommand, ListAccountsCommandOutput } from '@aws-sdk/client-organizations'; -import { - CreateMembersCommand, - DeleteMembersCommand, - DisassociateMembersCommand, - EnableSecurityHubCommand, - ListMembersCommand, - SecurityHubClient, - UpdateOrganizationConfigurationCommand, - AccountDetails, -} from '@aws-sdk/client-securityhub'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import { chunkArray, setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -/** - * enable-guardduty - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const partition = event.ResourceProperties['partition']; - const securityHubMemberAccountIds: string[] = event.ResourceProperties['securityHubMemberAccountIds']; - const autoEnableOrgMembers: boolean = event.ResourceProperties['autoEnableOrgMembers'] === 'true'; - const solutionId = process.env['SOLUTION_ID']; - const chunkSize = process.env['CHUNK_SIZE'] ? parseInt(process.env['CHUNK_SIZE']) : 50; - - let organizationsClient: OrganizationsClient; - if (partition === 'aws-us-gov') { - organizationsClient = new OrganizationsClient({ - region: 'us-gov-west-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else if (partition === 'aws-cn') { - organizationsClient = new OrganizationsClient({ - region: 'cn-northwest-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else { - organizationsClient = new OrganizationsClient({ - region: 'us-east-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } - const securityHubClient = new SecurityHubClient({ - region: region, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - - let nextToken: string | undefined = undefined; - - let existingMemberAccountIds: string[] = []; - - // Enable security hub is admin account before creating delegation admin account, if this wasn't enabled by organization delegation - await enableSecurityHub(securityHubClient); - - const allAccounts: AccountDetails[] = []; - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListAccountsCommand({ NextToken: nextToken })), - ); - allAccounts.push(...getMembersToCreate(page, securityHubMemberAccountIds)); - nextToken = page.NextToken; - } while (nextToken); - - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log('starting - CreateMembersCommand'); - - let autoEnableStandards: 'NONE' | 'DEFAULT' = 'DEFAULT'; - if (!autoEnableOrgMembers) { - autoEnableStandards = 'NONE'; - } - await throttlingBackOff(() => - securityHubClient.send( - new UpdateOrganizationConfigurationCommand({ - AutoEnable: autoEnableOrgMembers, - AutoEnableStandards: autoEnableStandards, - }), - ), - ); - - const chunkedAccountsForCreate = chunkArray(allAccounts, chunkSize); - - for (const accounts of chunkedAccountsForCreate) { - console.log(`Initiating createMembers request for ${accounts.length} accounts`); - await throttlingBackOff(() => securityHubClient.send(new CreateMembersCommand({ AccountDetails: accounts }))); - } - - // Cleanup members removed from deploymentTarget - if (securityHubMemberAccountIds.length > 0) { - console.log('Initiating cleanup of members removed from deploymentTargets'); - existingMemberAccountIds = await getExistingMembers(securityHubClient); - - const memberAccountIdsToDelete: string[] = []; - for (const accountId of existingMemberAccountIds) { - if (!securityHubMemberAccountIds.includes(accountId)) { - memberAccountIdsToDelete.push(accountId); - } - } - - await disassociateAndDeleteMembers(securityHubClient, memberAccountIdsToDelete, chunkSize); - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - existingMemberAccountIds = await getExistingMembers(securityHubClient); - - await disassociateAndDeleteMembers(securityHubClient, existingMemberAccountIds, chunkSize); - - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Enable SecurityHub - * @param securityHubClient - */ -async function enableSecurityHub(securityHubClient: SecurityHubClient): Promise { - try { - await throttlingBackOff(() => - securityHubClient.send(new EnableSecurityHubCommand({ EnableDefaultStandards: false })), - ); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return; - } - throw new Error(`SecurityHub enable issue error message - ${e}`); - } -} - -/** - * Get accounts details for the SecurityHub members to create - * @param page - * @param securityHubMemberAccountIds - * @returns - */ -function getMembersToCreate(page: ListAccountsCommandOutput, securityHubMemberAccountIds: string[]): AccountDetails[] { - const allAccounts: AccountDetails[] = []; - for (const account of page.Accounts ?? []) { - if (securityHubMemberAccountIds.length > 0) { - if (securityHubMemberAccountIds.includes(account.Id!)) { - allAccounts.push({ AccountId: account.Id!, Email: account.Email! }); - } - } else { - allAccounts.push({ AccountId: account.Id!, Email: account.Email! }); - } - } - return allAccounts; -} - -/** - * Function to get existing securityHub members - * @param securityHubClient - * @returns string[] - */ -async function getExistingMembers(securityHubClient: SecurityHubClient): Promise { - const existingMemberAccountIds: string[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - securityHubClient.send(new ListMembersCommand({ NextToken: nextToken })), - ); - for (const member of page.Members ?? []) { - console.log(member); - existingMemberAccountIds.push(member.AccountId!); - } - nextToken = page.NextToken; - } while (nextToken); - - return existingMemberAccountIds; -} - -/** - * Function to disassociate and delete securityHub members - * @param securityHubClient - * @param memberAccountIdsToDelete - */ -async function disassociateAndDeleteMembers( - securityHubClient: SecurityHubClient, - memberAccountIdsToDelete: string[], - chunkSize: number, -) { - if (memberAccountIdsToDelete.length > 0) { - const chunkedAccountsForDelete = chunkArray(memberAccountIdsToDelete, chunkSize); - - for (const accounts of chunkedAccountsForDelete) { - console.log(`Initiating disassociateMembers request for ${accounts.length} accounts`); - await throttlingBackOff(() => securityHubClient.send(new DisassociateMembersCommand({ AccountIds: accounts }))); - console.log(`Initiating deleteMembers request for ${accounts.length} accounts`); - await throttlingBackOff(() => securityHubClient.send(new DeleteMembersCommand({ AccountIds: accounts }))); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/package.json deleted file mode 100644 index 187faa0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-securityhub-create-members", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/client-securityhub": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/create-members/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/index.ts deleted file mode 100644 index 6fdf268..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/index.ts +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { delay, throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * SecurityHubOrganizationAdminAccount - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const partition = event.ResourceProperties['partition']; - const adminAccountId = event.ResourceProperties['adminAccountId']; - const solutionId = process.env['SOLUTION_ID']; - - let organizationsClient: AWS.Organizations; - if (partition === 'aws-us-gov') { - organizationsClient = new AWS.Organizations({ region: 'us-gov-west-1', customUserAgent: solutionId }); - } else if (partition === 'aws-cn') { - organizationsClient = new AWS.Organizations({ region: 'cn-northwest-1', customUserAgent: solutionId }); - } else { - organizationsClient = new AWS.Organizations({ region: 'us-east-1', customUserAgent: solutionId }); - } - const securityHubClient = new AWS.SecurityHub({ region: region, customUserAgent: solutionId }); - - const securityHubAdminAccount = await getSecurityHubDelegatedAccount(securityHubClient, adminAccountId); - - switch (event.RequestType) { - case 'Create': - case 'Update': - if (securityHubAdminAccount.status) { - if (securityHubAdminAccount.accountId === adminAccountId) { - console.warn( - `SecurityHub admin account ${securityHubAdminAccount.accountId} is already an admin account as status is ${securityHubAdminAccount.status}, in ${region} region. No action needed`, - ); - return { Status: 'Success', StatusCode: 200 }; - } else { - console.warn( - `SecurityHub delegated admin is already set to ${securityHubAdminAccount.accountId} account can not assign another delegated account`, - ); - } - } else { - // Enable security hub in management account before creating delegation admin account - await enableSecurityHub(securityHubClient); - console.log( - `Started enableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - let retries = 0; - while (retries < 10) { - await delay(retries ** 2 * 1000); - try { - await throttlingBackOff(() => - securityHubClient.enableOrganizationAdminAccount({ AdminAccountId: adminAccountId }).promise(), - ); - break; - } catch (error) { - console.log(error); - retries = retries + 1; - } - } - } - - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - if (securityHubAdminAccount.accountId) { - if (securityHubAdminAccount.accountId === adminAccountId) { - console.log( - `Started disableOrganizationAdminAccount function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - securityHubClient.disableOrganizationAdminAccount({ AdminAccountId: adminAccountId }).promise(), - ); - const response = await throttlingBackOff(() => - organizationsClient - .listDelegatedAdministrators({ ServicePrincipal: 'securityhub.amazonaws.com' }) - .promise(), - ); - - if (response.DelegatedAdministrators!.length > 0) { - console.log( - `Started deregisterDelegatedAdministrator function in ${event.ResourceProperties['region']} region for account ${adminAccountId}`, - ); - await throttlingBackOff(() => - organizationsClient - .deregisterDelegatedAdministrator({ - AccountId: adminAccountId, - ServicePrincipal: 'securityhub.amazonaws.com', - }) - .promise(), - ); - } else { - console.warn( - `Account ${securityHubAdminAccount.accountId} is not registered as delegated administrator account`, - ); - } - } - } else { - console.warn( - `SecurityHub delegation is not configured for account ${securityHubAdminAccount.accountId}, no action performed`, - ); - } - return { Status: 'Success', StatusCode: 200 }; - } -} - -/** - * Find SecurityHub delegated account Id - * @param securityHubClient - * @param adminAccountId - */ -async function getSecurityHubDelegatedAccount( - securityHubClient: AWS.SecurityHub, - adminAccountId: string, -): Promise<{ accountId: string | undefined; status: string | undefined }> { - const adminAccounts: AWS.SecurityHub.AdminAccount[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - securityHubClient.listOrganizationAdminAccounts({ NextToken: nextToken }).promise(), - ); - for (const account of page.AdminAccounts ?? []) { - adminAccounts.push(account); - } - nextToken = page.NextToken; - } while (nextToken); - - if (adminAccounts.length === 0) { - return { accountId: undefined, status: undefined }; - } - if (adminAccounts.length > 1) { - throw new Error('Multiple admin accounts for SecurityHub in organization'); - } - - if (adminAccounts[0].AccountId === adminAccountId && adminAccounts[0].Status === 'DISABLE_IN_PROGRESS') { - throw new Error(`Admin account ${adminAccounts[0].AccountId} is in ${adminAccounts[0].Status}`); - } - - return { accountId: adminAccounts[0].AccountId, status: adminAccounts[0].Status }; -} - -/** - * Enable SecurityHub - * @param securityHubClient - */ -async function enableSecurityHub(securityHubClient: AWS.SecurityHub): Promise { - try { - await throttlingBackOff(() => securityHubClient.enableSecurityHub({ EnableDefaultStandards: false }).promise()); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if ( - // SDKv2 Error Structure - e.code === 'ResourceConflictException' || - // SDKv3 Error Structure - e.name === 'ResourceConflictException' - ) { - console.warn(e.name + ': ' + e.message); - return; - } - throw new Error(`SecurityHub enable issue error message - ${e}`); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/package.json deleted file mode 100644 index a1c575d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-securityhub-enable-organization-admin-account", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/enable-organization-admin-account/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/index.ts deleted file mode 100644 index f357905..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * SecurityHubRegionAggregation - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - Status: string | undefined; - StatusCode: number | undefined; - } - | undefined -> { - const region = event.ResourceProperties['region']; - const solutionId = process.env['SOLUTION_ID']; - - const securityHubClient = new AWS.SecurityHub({ region: region, customUserAgent: solutionId }); - - // check if existing finding aggregator exists - const result = await throttlingBackOff(() => securityHubClient.listFindingAggregators({}).promise()); - - let findingAggregatorArn = ''; - if (result['FindingAggregators']!.length > 0) - findingAggregatorArn = result['FindingAggregators']![0]['FindingAggregatorArn']!; - - switch (event.RequestType) { - case 'Create': - //don't try to create finding aggregator if it exists - if (findingAggregatorArn) { - console.log('Existing Finding Aggregator found, skipping creation', findingAggregatorArn); - } else { - console.log('Enable Finding Aggreggation'); - try { - await throttlingBackOff(() => - securityHubClient.createFindingAggregator({ RegionLinkingMode: 'ALL_REGIONS' }).promise(), - ); - } catch (error) { - console.log(error); - return { Status: 'Failure', StatusCode: 400 }; - } - } - return { Status: 'Success', StatusCode: 200 }; - case 'Update': - console.log('Update Finding Aggregator Arn', findingAggregatorArn); - try { - await throttlingBackOff(() => - securityHubClient - .updateFindingAggregator({ - FindingAggregatorArn: findingAggregatorArn, - RegionLinkingMode: 'ALL_REGIONS', - }) - .promise(), - ); - } catch (error) { - console.log(error); - return { Status: 'Failure', StatusCode: 400 }; - } - return { Status: 'Success', StatusCode: 200 }; - - case 'Delete': - console.log('Delete Finding Aggregator Arn', findingAggregatorArn); - try { - await throttlingBackOff(() => - securityHubClient.deleteFindingAggregator({ FindingAggregatorArn: findingAggregatorArn }).promise(), - ); - } catch (error) { - console.log(error); - return { Status: 'Failure', StatusCode: 400 }; - } - return { Status: 'Success', StatusCode: 200 }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/package.json deleted file mode 100644 index fceaffb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-securityhub-region-aggregation", - "version": "0.0.0", - "private": true, - "description": "Add region aggregation for security hub", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/region-aggregation/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-members.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-members.ts deleted file mode 100644 index 17a501a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-members.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized SecurityHubMembersProps properties - */ -export interface SecurityHubMembersProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * List of SecurityHub member accountIds populated only when deploymentTargets are defined - */ - readonly securityHubMemberAccountIds: string[]; - /** - * Enable/disable autoEnableOrgMembers - */ - readonly autoEnableOrgMembers: boolean; -} - -/** - /** - * Class - SecurityHubMembers - */ -export class SecurityHubMembers extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: SecurityHubMembersProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::SecurityHubCreateMembers'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'create-members/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'SecurityHubCreateMembersTaskOrganizationAction', - Effect: 'Allow', - Action: ['organizations:ListAccounts'], - Resource: '*', - Condition: { - StringLikeIfExists: { - 'organizations:ListAccounts': ['securityhub.amazonaws.com'], - }, - }, - }, - { - Sid: 'SecurityHubCreateMembersTaskSecurityHubActions', - Effect: 'Allow', - Action: [ - 'securityhub:CreateMembers', - 'securityhub:DeleteMembers', - 'securityhub:DisassociateMembers', - 'securityhub:EnableSecurityHub', - 'securityhub:ListMembers', - 'securityhub:UpdateOrganizationConfiguration', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - partition: cdk.Aws.PARTITION, - securityHubMemberAccountIds: props.securityHubMemberAccountIds, - autoEnableOrgMembers: props.autoEnableOrgMembers, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-organization-admin-account.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-organization-admin-account.ts deleted file mode 100644 index 9011785..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-organization-admin-account.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized SecurityHubOrganizationalAdminAccountProps properties - */ -export interface SecurityHubOrganizationalAdminAccountProps { - /** - * Admin account id - */ - readonly adminAccountId: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class - SecurityHubOrganizationAdminAccount - */ -export class SecurityHubOrganizationAdminAccount extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: SecurityHubOrganizationalAdminAccountProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::SecurityHubEnableOrganizationAdminAccount'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'enable-organization-admin-account/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - timeout: cdk.Duration.seconds(180), - policyStatements: [ - { - Sid: 'SecurityHubEnableOrganizationAdminAccountTaskOrganizationActions', - Effect: 'Allow', - Action: [ - 'organizations:DescribeOrganization', - 'organizations:ListAccounts', - 'organizations:ListDelegatedAdministrators', - ], - Resource: '*', - }, - { - Effect: 'Allow', - Action: 'organizations:EnableAWSServiceAccess', - Resource: '*', - Condition: { - StringEquals: { - 'organizations:ServicePrincipal': 'securityhub.amazonaws.com', - }, - }, - }, - { - Effect: 'Allow', - Action: ['organizations:RegisterDelegatedAdministrator', 'organizations:DeregisterDelegatedAdministrator'], - Resource: `arn:${cdk.Stack.of(this).partition}:organizations::*:account/o-*/*`, - Condition: { - StringEquals: { - 'organizations:ServicePrincipal': 'securityhub.amazonaws.com', - }, - }, - }, - { - Sid: 'SecurityHubCreateMembersTaskIamAction', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - Condition: { - StringLike: { - 'iam:AWSServiceName': ['securityhub.amazonaws.com'], - }, - }, - }, - { - Sid: 'SecurityHubEnableOrganizationAdminAccountTaskSecurityHubActions', - Effect: 'Allow', - Action: [ - 'securityhub:DisableOrganizationAdminAccount', - 'securityhub:EnableOrganizationAdminAccount', - 'securityhub:EnableSecurityHub', - 'securityhub:ListOrganizationAdminAccounts', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - partition: cdk.Aws.PARTITION, - adminAccountId: props.adminAccountId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-region-aggregation.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-region-aggregation.ts deleted file mode 100644 index 254c4e4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-region-aggregation.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized SecurityHub Region Aggregation properties - */ -export interface SecurityHubRegionAggregationProps { - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - * Class - SecurityHubRegionAggregation - */ -export class SecurityHubRegionAggregation extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: SecurityHubRegionAggregationProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::SecurityHubRegionAggregation'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'region-aggregation/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - timeout: cdk.Duration.seconds(180), - policyStatements: [ - { - Sid: 'SecurityHubModifyRegionAggregation', - Effect: 'Allow', - Action: [ - 'securityhub:CreateFindingAggregator', - 'securityhub:UpdateFindingAggregator', - 'securityhub:DeleteFindingAggregator', - 'securityhub:ListFindingAggregators', - 'securityhub:GetFindingAggregator', - 'securityhub:DescribeHub', - ], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - partition: cdk.Aws.PARTITION, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-standards.ts b/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-standards.ts deleted file mode 100644 index ed503a0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-securityhub/securityhub-standards.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Initialized SecurityHubMembersProps properties - */ -export interface SecurityHubStandardsProps { - /** - * Security hub standards - */ - readonly standards: { name: string; enable: boolean; controlsToDisable: string[] | undefined }[]; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -/** - /** - * Class - SecurityHubMembers - */ -export class SecurityHubStandards extends Construct { - public readonly id: string; - - constructor(scope: Construct, id: string, props: SecurityHubStandardsProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::SecurityHubBatchEnableStandards'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'batch-enable-standards/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'SecurityHubCreateMembersTaskSecurityHubActions', - Effect: 'Allow', - Action: [ - 'securityhub:BatchDisableStandards', - 'securityhub:BatchEnableStandards', - 'securityhub:DescribeStandards', - 'securityhub:DescribeStandardsControls', - 'securityhub:EnableSecurityHub', - 'securityhub:GetEnabledStandards', - 'securityhub:UpdateStandardsControl', - ], - Resource: '*', - }, - { - Sid: 'SecurityHubServiceLinkedRole', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: '*', - Condition: { - StringLike: { - 'iam:AWSServiceName': 'securityhub.amazonaws.com', - }, - }, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: cdk.Stack.of(this).region, - standards: props.standards, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/index.ts deleted file mode 100644 index 448af4c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { - ServiceQuotasClient, - GetServiceQuotaCommand, - RequestServiceQuotaIncreaseCommand, -} from '@aws-sdk/client-service-quotas'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * service-quota-limits - lambda handler - * - * @param event - * @returns - */ - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - switch (event.RequestType) { - case 'Create': - case 'Update': - console.log(event); - - const servicequotas = new ServiceQuotasClient({ - customUserAgent: process.env['SOLUTION_ID'], - retryStrategy: setRetryStrategy(), - }); - const serviceCode = event.ResourceProperties['serviceCode']; - const quotaCode = event.ResourceProperties['quotaCode']; - const desiredValue = Number(event.ResourceProperties['desiredValue']); - const region = process.env['AWS_REGION']; - const accountId = event.StackId.split(':')[4]; - - const serviceQuotaParams = { - ServiceCode: serviceCode /* required */, - QuotaCode: quotaCode, - }; - - try { - const getServiceQuotaResponse = await throttlingBackOff(() => - servicequotas.send(new GetServiceQuotaCommand(serviceQuotaParams)), - ); - const isAdjustable = getServiceQuotaResponse.Quota?.Adjustable ?? false; - const currentValue = getServiceQuotaResponse.Quota?.Value ?? 0; - // check to see if quota is adjustable and current value is less than desired value - if (isAdjustable) { - if (currentValue < desiredValue) { - const increaseLimitParams = { - ServiceCode: serviceCode, - QuotaCode: quotaCode, - DesiredValue: desiredValue, - }; - const quotaIncreaseResponse = await throttlingBackOff(() => - servicequotas.send(new RequestServiceQuotaIncreaseCommand(increaseLimitParams)), - ); - console.log(quotaIncreaseResponse.RequestedQuota); - } - } else { - console.log( - `Service Quota: ${serviceCode} with quota code: ${quotaCode} has adjustable set to ${isAdjustable} and current value set to ${currentValue}, skipping`, - ); - } - } catch (error) { - console.error(error); - throw new Error( - `[service-quota-limits-config] Error increasing service quota ${quotaCode} for service ${serviceCode} in account ${accountId} region ${region}. Error: ${JSON.stringify( - error, - )}`, - ); - } - - return { - PhysicalResourceId: `service-quota-limits`, - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/package.json deleted file mode 100644 index 21dd660..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-servicequota-create-limits", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --silent", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-service-quotas": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/index.test.ts deleted file mode 100644 index b10abe4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/index.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - ServiceQuotasClient, - GetServiceQuotaCommand, - RequestServiceQuotaIncreaseCommand, -} from '@aws-sdk/client-service-quotas'; -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const sqClient = AcceleratorMockClient(ServiceQuotasClient); - -describe('Create Event', () => { - beforeEach(() => { - sqClient.reset(); - }); - test('Create service quota - success', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - - sqClient - .on(GetServiceQuotaCommand, { - ServiceCode: StaticInput.newProps.serviceCode, - QuotaCode: StaticInput.newProps.quotaCode, - }) - .resolves({ Quota: { Adjustable: true, Value: 1 } }); - sqClient - .on(RequestServiceQuotaIncreaseCommand, { - ServiceCode: StaticInput.newProps.serviceCode, - QuotaCode: StaticInput.newProps.quotaCode, - DesiredValue: parseInt(StaticInput.newProps.desiredValue), - }) - .resolves({}); - expect(await handler(event)).toEqual({ PhysicalResourceId: 'service-quota-limits', Status: 'SUCCESS' }); - }); - test('Create service quota - non-adjustable service', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - - sqClient - .on(GetServiceQuotaCommand, { - ServiceCode: StaticInput.newProps.serviceCode, - QuotaCode: StaticInput.newProps.quotaCode, - }) - .resolves({}); - expect(await handler(event)).toEqual({ PhysicalResourceId: 'service-quota-limits', Status: 'SUCCESS' }); - }); -}); -describe('Update Event', () => { - beforeEach(() => { - sqClient.reset(); - }); - - test('Update service quotas - error', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - const serviceCode = event.ResourceProperties['serviceCode']; - const quotaCode = event.ResourceProperties['quotaCode']; - const serviceQuotaParams = { - ServiceCode: serviceCode /* required */, - QuotaCode: quotaCode, - }; - const region = process.env['AWS_REGION']; - const accountId = event.StackId.split(':')[4]; - sqClient.on(GetServiceQuotaCommand, serviceQuotaParams).rejects({}); - await expect(handler(event)).rejects.toThrowError( - `[service-quota-limits-config] Error increasing service quota ${quotaCode} for service ${serviceCode} in account ${accountId} region ${region}. Error: {}`, - ); - }); -}); -describe('Delete Event', () => { - test('Delete service quota - no action', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/static-input.ts deleted file mode 100644 index f9fc310..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/test/static-input.ts +++ /dev/null @@ -1,7 +0,0 @@ -export abstract class StaticInput { - public static readonly newProps = { - serviceCode: 'serviceCode', - quotaCode: 'quotaCode', - desiredValue: '10', - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/tsconfig.json deleted file mode 100644 index 543ed71..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/create-limits/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] - } \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/limits-service-quota-definition.ts b/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/limits-service-quota-definition.ts deleted file mode 100644 index 8db4d17..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-service-quota/limits-service-quota-definition.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -import path from 'path'; - -export interface ServiceQuotaDefinitionProps { - /** - * The service identifier. - */ - readonly serviceCode: string; - /** - * The quota identifier. - */ - readonly quotaCode: string; - /** - * The new, increased value for the quota. - */ - readonly desiredValue: number; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class LimitsDefinition extends Construct { - readonly id: string; - constructor(scope: Construct, id: string, props: ServiceQuotaDefinitionProps) { - super(scope, id); - - const DEFAULT_LIMITS = `Custom::ServiceQuotaLimits`; - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, DEFAULT_LIMITS, { - codeDirectory: path.join(__dirname, 'create-limits/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'OrganizationListActions', - Effect: 'Allow', - Action: [ - 'organizations:DescribeAccount', - 'organizations:DescribeOrganization', - 'organizations:ListAWSServiceAccessForOrganization', - ], - Resource: '*', - }, - { - Sid: 'AutoScalingLimitsAction', - Effect: 'Allow', - Action: ['autoscaling:DescribeAccountLimits'], - Resource: '*', - }, - { - Sid: 'DynamoDBLimitsAction', - Effect: 'Allow', - Action: ['dynamodb:DescribeLimits'], - Resource: '*', - }, - { - Sid: 'KinesisLimitsAction', - Effect: 'Allow', - Action: ['kinesis:DescribeLimits'], - Resource: '*', - }, - { - Sid: 'IAMAccountSummaryAction', - Effect: 'Allow', - Action: ['iam:GetAccountSummary'], - Resource: [`*`], - }, - { - Sid: 'CloudFormationAccountLimitsAction', - Effect: 'Allow', - Action: ['cloudformation:DescribeAccountLimits'], - Resource: [`*`], - }, - { - Sid: 'CloudWatchLimitsActions', - Effect: 'Allow', - Action: [ - 'cloudformation:DescribeAccountLimits', - 'cloudwatch:DescribeAlarmsForMetric', - 'cloudwatch:DescribeAlarms', - 'cloudwatch:GetMetricData', - 'cloudwatch:GetMetricStatistics', - 'cloudwatch:PutMetricAlarm', - ], - Resource: `*`, - }, - { - Sid: 'ElasticLoadBalancingLimitsAction', - Effect: 'Allow', - Action: ['elasticloadbalancing:DescribeAccountLimits'], - Resource: `*`, - }, - { - Sid: 'Route53LimitsAction', - Effect: 'Allow', - Action: ['route53:GetAccountLimit'], - Resource: `*`, - }, - { - Sid: 'RDSLimitsAction', - Effect: 'Allow', - Action: ['rds:DescribeAccountAttributes'], - Resource: `*`, - }, - { - Sid: 'ServiceQuotaLimitsAction', - Effect: 'Allow', - Action: ['servicequotas:*'], - Resource: `*`, - }, - { - Sid: 'TaggingLimitsActions', - Effect: 'Allow', - Action: ['tag:GetTagKeys', 'tag:GetTagValues'], - Resource: `*`, - }, - { - Sid: 'CreateServiceLinkedRole', - Effect: 'Allow', - Action: ['iam:CreateServiceLinkedRole'], - Resource: `*`, - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: DEFAULT_LIMITS, - serviceToken: provider.serviceToken, - properties: { - serviceCode: props.serviceCode, - quotaCode: props.quotaCode, - desiredValue: props.desiredValue, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - - if (props?.serviceCode) { - console.log(`[Service Quota Limits] Limits are being updated for ${props?.serviceCode}`); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id.ts deleted file mode 100644 index d38fd06..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { v4 as uuidv4 } from 'uuid'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * Get the PortfolioId from servicecatalog - */ -export interface GetPortfolioIdProps { - readonly displayName: string; - readonly providerName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class GetPortfolioId extends Construct { - public readonly portfolioId: string; - - constructor(scope: Construct, id: string, props: GetPortfolioIdProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::GetPortfolioId'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'get-portfolio-id/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'ServiceCatalog', - Effect: 'Allow', - Action: ['servicecatalog:ListPortfolios'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - displayName: props.displayName, - providerName: props.providerName, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.portfolioId = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/index.ts deleted file mode 100644 index 5851ef7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/index.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * get-portfolio-id - lambda handler - * - * @param event - * @returns - */ - -import { paginateListPortfolios, ServiceCatalogClient } from '@aws-sdk/client-service-catalog'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -const serviceCatalogClient = new ServiceCatalogClient({ - retryStrategy: setRetryStrategy(), - customUserAgent: process.env['SOLUTION_ID'], -}); -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const displayName = event.ResourceProperties['displayName']; - const providerName = event.ResourceProperties['providerName']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - const portfolioId = await getPortfolioId(serviceCatalogClient, displayName, providerName); - return { - PhysicalResourceId: portfolioId, - Status: 'SUCCESS', - }; - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} -async function getPortfolioId(serviceCatalogClient: ServiceCatalogClient, displayName: string, providerName: string) { - const portfolios = []; - // get all portfolio id for specific provider and display name - for await (const page of paginateListPortfolios({ client: serviceCatalogClient }, {})) { - for (const portfolio of page.PortfolioDetails ?? []) { - if (portfolio.DisplayName === displayName && portfolio.ProviderName === providerName) { - portfolios.push(portfolio.Id); - } - } - } - // there are no portfolios in the account for that specified filter - if (portfolios.length === 0) { - throw new Error(`No portfolio ID was found for ${displayName} ${providerName} in the account`); - } - // this is to handle the case where there are multiple portfolios with the same display name and provider name - if (portfolios.length > 1) { - throw new Error(`Multiple portfolio IDs were found for ${displayName} ${providerName} in the account`); - } - return portfolios[0]; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/package.json deleted file mode 100644 index 85339ad..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-servicecatalog-get-portfolio-id", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-service-catalog": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/index.test.ts deleted file mode 100644 index 1203693..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/index.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ListPortfoliosCommand, ServiceCatalogClient } from '@aws-sdk/client-service-catalog'; -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const scClient = AcceleratorMockClient(ServiceCatalogClient); - -describe('Create Event', () => { - beforeEach(() => { - scClient.reset(); - }); - test('Create get portfolio id - just one id returned', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - scClient.on(ListPortfoliosCommand).resolves({ - PortfolioDetails: [ - { Id: 'Id', DisplayName: StaticInput.newProps.displayName, ProviderName: StaticInput.newProps.providerName }, - ], - }); - const response = await handler(event); - expect(response?.PhysicalResourceId).toBe('Id'); - }); -}); -describe('Update Event', () => { - beforeEach(() => { - scClient.reset(); - }); - - test('Update get portfolio id - no id returned', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - scClient.on(ListPortfoliosCommand).resolves({}); - await expect(handler(event)).rejects.toThrowError(StaticInput.noPortfolioFoundError); - }); - test('Update get portfolio id - multiple ids returned', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - scClient.on(ListPortfoliosCommand).resolves({ - PortfolioDetails: [ - { Id: 'Id1', DisplayName: StaticInput.newProps.displayName, ProviderName: StaticInput.newProps.providerName }, - { Id: 'Id2', DisplayName: StaticInput.newProps.displayName, ProviderName: StaticInput.newProps.providerName }, - ], - }); - await expect(handler(event)).rejects.toThrowError(StaticInput.multiplePortfolioFoundError); - }); -}); -describe('Delete Event', () => { - beforeEach(() => { - scClient.reset(); - }); - test('Delete get portfolio id - no action', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newProps] }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/static-input.ts deleted file mode 100644 index 7208d05..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/test/static-input.ts +++ /dev/null @@ -1,8 +0,0 @@ -export abstract class StaticInput { - public static readonly newProps = { - displayName: 'displayName', - providerName: 'providerName', - }; - public static readonly noPortfolioFoundError = `No portfolio ID was found for ${this.newProps.displayName} ${this.newProps.providerName} in the account`; - public static readonly multiplePortfolioFoundError = `Multiple portfolio IDs were found for ${this.newProps.displayName} ${this.newProps.providerName} in the account`; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/get-portfolio-id/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations.ts deleted file mode 100644 index fcc7705..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { PortfolioConfig } from '@aws-accelerator/config'; -import { v4 as uuidv4 } from 'uuid'; -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * This construct enables the propagation of Service Catalog Portfolio - */ -export interface PropagatePortfolioAssociationsProps { - /** - * A list of account Ids the portfolio is shared with - */ - readonly shareAccountIds: string[]; - /** - * The name of the role to assume from the account containing the portfolio - */ - readonly crossAccountRole: string; - /** - * Id of the portfolio to be shared - */ - readonly portfolioId: string; - /** - * The portfolio definition from the customizations-config.yaml file - */ - readonly portfolioDefinition: PortfolioConfig; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class PropagatePortfolioAssociations extends Construct { - constructor(scope: Construct, id: string, props: PropagatePortfolioAssociationsProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::PropagatePortfolioAssociations'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'propagate-portfolio-associations/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'ServiceCatalog', - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: '*', - }, - ], - }); - - const customResourceObjects = []; - customResourceObjects.push( - new cdk.CustomResource(this, `PropagateAssociations`, { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - crossAccountRole: props.crossAccountRole, - portfolioId: props.portfolioId, - portfolioDefinition: JSON.stringify(props.portfolioDefinition), - shareAccountIds: props.shareAccountIds.join(','), - partition: cdk.Stack.of(this).partition, - uuid: uuidv4(), // Generates a new UUID to force the resource to update - }, - }), - ); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - for (const customResource of customResourceObjects) { - customResource.node.addDependency(logGroup); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/index.ts deleted file mode 100644 index 8447db7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/index.ts +++ /dev/null @@ -1,343 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * propagate-portfolio-associations - lambda handler - * - * @param event - * @returns - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { getCrossAccountCredentials, setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { PortfolioAssociationConfig, PortfolioConfig } from '@aws-accelerator/config'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import { - AcceptPortfolioShareCommand, - AssociatePrincipalWithPortfolioCommand, - DisassociatePrincipalFromPortfolioCommand, - paginateListPrincipalsForPortfolio, - paginateListAcceptedPortfolioShares, - ServiceCatalogClient, -} from '@aws-sdk/client-service-catalog'; -import { IAMClient, paginateListRoles } from '@aws-sdk/client-iam'; -import { AssumeRoleCommandOutput } from '@aws-sdk/client-sts'; - -const ssoRolePrefix = '/aws-reserved/sso.amazonaws.com/'; - -export type CrossAccountClient = { - serviceCatalog: ServiceCatalogClient; - iam: IAMClient; -}; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(event); - const portfolioId = event.ResourceProperties['portfolioId']; - const propagator = new AssociationPropagator(event); - - try { - await propagator.processPropagations(); - } catch (error) { - console.error(`Failed to propagate principal associations for portfolio ${portfolioId}`); - console.error(error); - return { - PhysicalResourceId: 'none', - Status: 'FAILED', - }; - } - - return { - PhysicalResourceId: 'none', - Status: 'SUCCESS', - }; -} - -export class AssociationPropagator { - private portfolioId: string; - private crossAccountRole: string; - private portfolioDefinition: PortfolioConfig; - private shareAccountIds: string[]; - private region: string; - private partition: string; - private associationsToPropagate: PortfolioAssociationConfig[]; - private requestType: string; - - constructor(event: CloudFormationCustomResourceEvent) { - this.portfolioId = event.ResourceProperties['portfolioId']; - this.crossAccountRole = event.ResourceProperties['crossAccountRole']; - this.portfolioDefinition = JSON.parse(event.ResourceProperties['portfolioDefinition']); - this.shareAccountIds = event.ResourceProperties['shareAccountIds'].split(','); - this.region = process.env['AWS_REGION']!; - this.partition = event.ResourceProperties['partition']; - this.requestType = event.RequestType; - this.associationsToPropagate = this.getPrincipalsToPropagate(); - } - - public async processPropagations(): Promise { - for (const accountId of this.shareAccountIds) { - console.log(`Assuming role ${this.crossAccountRole} in account ${accountId}`); - const crossAccountCredential = await getCrossAccountCredentials( - accountId, - this.region, - this.partition, - this.crossAccountRole, - ); - const clients = await this.getCrossAccountClients(crossAccountCredential); - await this.preprocessPortfolio(clients); - const existingPrincipalArns = await this.getAssociatedPortfolioPrincipals(clients); - if (existingPrincipalArns.length > 0) { - console.log( - `Found the following principals already associated to portfolio ${ - this.portfolioDefinition.name - }: ${existingPrincipalArns.join(', ')}`, - ); - } - - switch (this.requestType) { - case 'Create': - case 'Update': - await this.createUpdatePropagations(accountId, clients, existingPrincipalArns); - break; - case 'Delete': - await this.deletePropagations(accountId, clients, existingPrincipalArns); - } - } - } - - private async createUpdatePropagations( - accountId: string, - clients: CrossAccountClient, - existingPrincipalArns: string[], - ): Promise { - for (const principalAssociation of this.associationsToPropagate) { - const principalArn = await this.getPrincipalArnToAssociate(principalAssociation, accountId, clients.iam); - if (!existingPrincipalArns.includes(principalArn)) { - console.log(`Associating principal ${principalArn} with portfolio ${this.portfolioDefinition.name}`); - await this.createPropagatedAssociation(principalArn, clients); - } else { - console.log( - `Principal ${principalAssociation.name} already associated with portfolio ${this.portfolioDefinition.name}, skipping creation.`, - ); - } - } - } - - private async deletePropagations( - accountId: string, - clients: CrossAccountClient, - existingPrincipalArns: string[], - ): Promise { - for (const principalAssociation of this.associationsToPropagate) { - const principalArn = await this.getPrincipalArnToAssociate(principalAssociation, accountId, clients.iam); - console.warn( - `deletePropagations existingPrincipalArns: ${JSON.stringify( - existingPrincipalArns, - )}, principalArn: ${principalArn}`, - ); - if (existingPrincipalArns.includes(principalArn)) { - await this.deletePropagatedAssociation(principalArn, clients); - } else { - console.log( - `Principal ${principalAssociation.name} not associated with portfolio ${this.portfolioDefinition.name}, skipping deletion.`, - ); - } - } - } - - private async preprocessPortfolio(client: CrossAccountClient): Promise { - const portfolioImported = await this.doesPortfolioExist(this.portfolioId, client); - if (!portfolioImported) { - console.log(`Portfolio ${this.portfolioId} not found. Attempting to accept portfolio share.`); - await this.importPortfolio(client); - } - } - - private async importPortfolio(client: CrossAccountClient): Promise { - try { - await throttlingBackOff(() => - client.serviceCatalog.send( - new AcceptPortfolioShareCommand({ - PortfolioId: this.portfolioId, - PortfolioShareType: 'IMPORTED', - }), - ), - ); - } catch (error) { - console.error(error); - console.log('Encountered error attempting to accept portfolio share. Continuing.'); - } - } - - private async doesPortfolioExist(portfolioId: string, client: CrossAccountClient): Promise { - const importedPortfolioIds = await getAcceptedPortfolioIds(client.serviceCatalog); - if (importedPortfolioIds.includes(portfolioId)) { - return true; - } else { - return false; - } - } - - private async createPropagatedAssociation(principalArn: string, client: CrossAccountClient): Promise { - try { - await throttlingBackOff(() => - client.serviceCatalog.send( - new AssociatePrincipalWithPortfolioCommand({ - PortfolioId: this.portfolioId, - PrincipalARN: principalArn, - PrincipalType: 'IAM', - }), - ), - ); - } catch (error) { - console.error(error); - console.log(`Encountered error attempting to associate ${principalArn} with ${this.portfolioId}. Continuing`); - } - } - - private async deletePropagatedAssociation(principalArn: string, client: CrossAccountClient): Promise { - try { - await throttlingBackOff(() => - client.serviceCatalog.send( - new DisassociatePrincipalFromPortfolioCommand({ - PortfolioId: this.portfolioId, - PrincipalARN: principalArn, - }), - ), - ); - } catch (error) { - console.error(error); - console.log(`Encountered error attempting to disassociate ${principalArn} from ${this.portfolioId}. Continuing`); - } - } - - private async getAssociatedPortfolioPrincipals(client: CrossAccountClient): Promise { - const principalList: string[] = []; - for await (const page of paginateListPrincipalsForPortfolio( - { client: client.serviceCatalog }, - { PortfolioId: this.portfolioId }, - )) { - for (const principal of page.Principals ?? []) { - if (principal.PrincipalType === 'IAM') { - principalList.push(principal.PrincipalARN!); - } - } - } - return principalList; - } - - private getPrincipalsToPropagate(): PortfolioAssociationConfig[] { - const principalAssociationList = this.portfolioDefinition.portfolioAssociations.filter( - a => a.propagateAssociation === true, - ); - return principalAssociationList; - } - - private async getPrincipalArnToAssociate( - association: PortfolioAssociationConfig, - accountId: string, - iamClient: IAMClient, - ): Promise { - const associationType = association.type.toLowerCase(); - let roleArn = ''; - - if (associationType === 'permissionset') { - roleArn = await getPermissionSetRoleArn(association.name, accountId, iamClient); - } else { - roleArn = `arn:${this.partition}:iam::${accountId}:${associationType}/${association.name}`; - } - return roleArn; - } - - private async getCrossAccountClients(crossAccountCredentials: AssumeRoleCommandOutput) { - const credentials = { - accessKeyId: crossAccountCredentials.Credentials!.AccessKeyId!, - secretAccessKey: crossAccountCredentials.Credentials!.SecretAccessKey!, - sessionToken: crossAccountCredentials.Credentials!.SessionToken!, - }; - const iamClient = new IAMClient({ - credentials, - retryStrategy: setRetryStrategy(), - }); - - const serviceCatalogClient = new ServiceCatalogClient({ - region: this.region, - credentials, - retryStrategy: setRetryStrategy(), - }); - return { - iam: iamClient, - serviceCatalog: serviceCatalogClient, - }; - } -} - -export async function getPermissionSetRoleArn( - permissionSetName: string, - account: string, - iamClient: IAMClient, -): Promise { - const regex = new RegExp(`AWSReservedSSO_${permissionSetName}_([0-9a-fA-F]{16})`); - const roles = await getIamRoleList(iamClient, ssoRolePrefix); - const foundRole = roles.find(role => { - const match = regex.test(role.RoleName!); - console.log(`Test ${JSON.stringify(role)} for pattern ${regex} result: ${match}`); - return role; - }); - const roleArn = foundRole?.Arn ?? undefined; - - if (roleArn) { - console.log(`Found provisioned role for permission set ${permissionSetName} with ARN: ${roleArn}`); - } else { - throw new Error(`Unable to find provisioned role for permission set ${permissionSetName} in account ${account}`); - } - - return roleArn; -} - -export async function getIamRoleList(iamClient: IAMClient, prefix: string) { - const roleList = []; - for await (const page of paginateListRoles({ client: iamClient }, { PathPrefix: prefix })) { - if (page.Roles) { - roleList.push(...page.Roles); - } - } - return roleList; -} - -export async function getAcceptedPortfolioIds(scClient: ServiceCatalogClient): Promise { - const importedShares = await listAcceptedShares(scClient, 'IMPORTED'); - const organizationShares = await listAcceptedShares(scClient, 'AWS_ORGANIZATIONS'); - - return [...importedShares, ...organizationShares]; -} - -export async function listAcceptedShares( - scClient: ServiceCatalogClient, - shareType: 'IMPORTED' | 'AWS_ORGANIZATIONS', -): Promise { - const shareList: string[] = []; - for await (const page of paginateListAcceptedPortfolioShares( - { client: scClient }, - { PortfolioShareType: shareType }, - )) { - if (page.PortfolioDetails) { - shareList.push(...page.PortfolioDetails.map(a => a.Id!)); - } - } - return shareList; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/package.json deleted file mode 100644 index a306d97..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-servicecatalog-propagate-portfolio-associations", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda to propagate Service Catalog portfolio associations to accounts the portfolio is shared to.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-service-catalog": "3.410.0", - "@aws-sdk/client-iam": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/index.test.ts deleted file mode 100644 index cb706ac..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/index.test.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { - AcceptPortfolioShareCommand, - AssociatePrincipalWithPortfolioCommand, - DisassociatePrincipalFromPortfolioCommand, - ListPrincipalsForPortfolioCommand, - ListAcceptedPortfolioSharesCommand, - ServiceCatalogClient, -} from '@aws-sdk/client-service-catalog'; -import { IAMClient, ListRolesCommand } from '@aws-sdk/client-iam'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { describe, beforeEach, expect, test, jest, afterAll } from '@jest/globals'; -import { handler, getPermissionSetRoleArn } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const iamClient = AcceleratorMockClient(IAMClient); -const scClient = AcceleratorMockClient(ServiceCatalogClient); -const stsClient = AcceleratorMockClient(STSClient); - -describe('Create Event', () => { - const OLD_ENV = process.env; - beforeEach(() => { - iamClient.reset(); - scClient.reset(); - stsClient.reset(); - jest.resetModules(); // it clears the cache for environment - process.env = { ...OLD_ENV }; // Make a copy - }); - afterAll(() => { - process.env = OLD_ENV; // Restore old environment - }); - test('Create propagate portfolio association', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [{ Id: 'importedId' }] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [{ Id: 'awsOrgId' }] }); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({ - Principals: [{ PrincipalType: 'IAM', PrincipalARN: 'PrincipalARN' }], - }); - scClient.on(AssociatePrincipalWithPortfolioCommand).resolves({}); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('Create propagate portfolio association AssociatePrincipalWithPortfolio error', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [{ Id: 'importedId' }] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [{ Id: 'awsOrgId' }] }); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({ - Principals: [{ PrincipalType: 'IAM', PrincipalARN: 'PrincipalARN' }], - }); - scClient.on(AssociatePrincipalWithPortfolioCommand).rejects({}); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('Create propagate portfolio association error on assumeRole', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - stsClient.on(AssumeRoleCommand).rejects({}); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); - test('Create propagate portfolio association role already associated', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [{ Id: 'importedId' }] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [{ Id: 'awsOrgId' }] }); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({ - Principals: [{ PrincipalType: 'IAM', PrincipalARN: 'roleArn' }], - }); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); -}); -describe('Update Event', () => { - const OLD_ENV = process.env; - beforeEach(() => { - iamClient.reset(); - scClient.reset(); - stsClient.reset(); - jest.resetModules(); // it clears the cache for environment - process.env = { ...OLD_ENV }; // Make a copy - }); - afterAll(() => { - process.env = OLD_ENV; // Restore old environment - }); - test('Update propagate portfolio association - error on importPortfolio', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [] }); - scClient.on(AcceptPortfolioShareCommand).rejects({}); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({}); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('Update propagate portfolio association - portfolio exists', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [{ Id: 'portfolioId' }] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [] }); - scClient.on(AcceptPortfolioShareCommand).resolves({}); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({ - Principals: [{ PrincipalType: 'IAM', PrincipalARN: 'PrincipalARN' }], - }); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('getPermissionSetRoleArn - permission set does not exist', async () => { - iamClient.on(ListRolesCommand).resolves({}); - await expect(getPermissionSetRoleArn('permissionSet', 'account', new IAMClient())).rejects.toThrowError( - StaticInput.permissionSetErrorMessage, - ); - }); -}); -describe('Delete Event', () => { - const OLD_ENV = process.env; - beforeEach(() => { - iamClient.reset(); - scClient.reset(); - stsClient.reset(); - jest.resetModules(); // it clears the cache for environment - process.env = { ...OLD_ENV }; // Make a copy - }); - afterAll(() => { - process.env = OLD_ENV; // Restore old environment - }); - test('Delete propagate portfolio association', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.deleteProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [{ Id: 'portfolioId' }] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [] }); - scClient.on(AcceptPortfolioShareCommand).resolves({}); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({ - Principals: [{ PrincipalType: 'IAM', PrincipalARN: StaticInput.existingRoleArn }], - }); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - scClient.on(DisassociatePrincipalFromPortfolioCommand).resolves({}); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('Delete propagate portfolio association - error on disassociation', async () => { - // Set the variables - process.env['AWS_REGION'] = 'us-east-1'; - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.deleteProps] }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn1, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.assumeRoleArn2, - RoleSessionName: 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }) - .resolves({ - Credentials: { - AccessKeyId: 'mockAccessKeyId', - SecretAccessKey: 'mockSecretAccessKey', - SessionToken: 'mockSecretAccessKey', - Expiration: undefined, - }, - }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'IMPORTED' }) - .resolves({ PortfolioDetails: [{ Id: 'portfolioId' }] }); - scClient - .on(ListAcceptedPortfolioSharesCommand, { PortfolioShareType: 'AWS_ORGANIZATIONS' }) - .resolves({ PortfolioDetails: [] }); - scClient.on(AcceptPortfolioShareCommand).resolves({}); - scClient.on(ListPrincipalsForPortfolioCommand, { PortfolioId: 'portfolioId' }).resolves({ - Principals: [{ PrincipalType: 'IAM', PrincipalARN: StaticInput.existingRoleArn }], - }); - iamClient.on(ListRolesCommand).resolves({ - Roles: [ - { - RoleId: 'RoleId', - Path: 'Path', - Arn: 'roleArn', - CreateDate: new Date(), - RoleName: StaticInput.permissionSetName, - }, - ], - }); - scClient.on(DisassociatePrincipalFromPortfolioCommand).rejects({}); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/static-input.ts deleted file mode 100644 index bcffbe5..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/test/static-input.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { MakeRoleArn, Partition } from '../../../../test/unit-test/common/resources'; -import { PortfolioConfig } from '@aws-accelerator/config'; - -/** - * Abstract class to configure static input for propagate-portfolio-associations custom resource AWS Lambda unit testing - */ -const portfolioInput: PortfolioConfig = { - name: 'testPortfolioName', - provider: 'testProvider', - account: 'Management', - regions: ['us-east-1', 'us-east-2'], - portfolioAssociations: [ - { type: 'Group', name: 'AdminGroup', propagateAssociation: true }, - { type: 'Role', name: 'AdminRole', propagateAssociation: true }, - { type: 'User', name: 'testUser', propagateAssociation: false }, - { type: 'PermissionSet', name: 'AdminPermissionSet', propagateAssociation: true }, - ], - products: [ - { - name: 'productName', - description: 'productDescription', - owner: 'productOwner', - distributor: 'productDistributor', - versions: [ - { - name: 'v1', - description: 'product version description', - template: 'productVersion1Template', - }, - ], - support: { - description: 'product support description', - email: 'product support email', - url: 'product support url', - }, - tagOptions: undefined, - constraints: undefined, - }, - ], - shareTagOptions: undefined, - shareTargets: undefined, - tagOptions: undefined, -}; - -export abstract class StaticInput { - public static readonly permissionSetName = 'AWSReservedSSO_AdminPermissionSet_11111111111111111'; - public static readonly assumeRoleArn1 = MakeRoleArn('crossAccountRole', Partition, '000000000000'); - public static readonly assumeRoleArn2 = MakeRoleArn('crossAccountRole', Partition, '111111111111'); - public static readonly existingRoleArn = MakeRoleArn('AdminRole', Partition, '000000000000'); - public static readonly newProps = { - portfolioId: 'portfolioId', - crossAccountRole: 'crossAccountRole', - portfolioDefinition: JSON.stringify(portfolioInput), - shareAccountIds: '000000000000,111111111111', - partition: 'aws', - }; - public static readonly permissionSetErrorMessage = - 'Unable to find provisioned role for permission set permissionSet in account account'; - public static readonly deleteProps = { - portfolioId: 'portfolioId', - crossAccountRole: 'crossAccountRole', - portfolioDefinition: JSON.stringify(portfolioInput), - shareAccountIds: '000000000000', - partition: 'aws', - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/propagate-portfolio-associations/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org.ts deleted file mode 100644 index 1de5646..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -const path = require('path'); - -/** - * This construct enables the sharing of a Service Catalog Portfolio with an organizational unit. - */ -export interface SharePortfolioWithOrgProps { - /** - * Id of the portfolio to be shared - */ - readonly portfolioId: string; - /** - * Organizational Unit Ids to share portfolio with - */ - readonly organizationalUnitIds: string[]; - /** - * Organization Id to share with - */ - readonly organizationId: string; - /** - * Determines if tag sharing is enabled - */ - readonly tagShareOptions: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; -} - -export class SharePortfolioWithOrg extends Construct { - constructor(scope: Construct, id: string, props: SharePortfolioWithOrgProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::SharePortfolioWithOrg'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'share-portfolio-with-org/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'ServiceCatalog', - Effect: 'Allow', - Action: [ - 'servicecatalog:CreatePortfolioShare', - 'servicecatalog:UpdatePortfolioShare', - 'servicecatalog:DeletePortfolioShare', - 'servicecatalog:DescribePortfolioShareStatus', - 'organizations:DescribeOrganization', - 'organizations:ListParents', - 'organizations:ListChildren', - 'organizations:ListAccountsForParent', - 'organizations:ListAccounts', - ], - Resource: '*', - }, - ], - }); - - const customResourceObjects = []; - if (props.organizationId) { - customResourceObjects.push( - new cdk.CustomResource(this, 'PortfolioShare-Root', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - portfolioId: props.portfolioId, - organizationalUnitId: '', - organizationId: props.organizationId, - tagShareOptions: props.tagShareOptions, - }, - }), - ); - } - for (const orgUnit of props.organizationalUnitIds) { - customResourceObjects.push( - new cdk.CustomResource(this, `PortfolioShare-${orgUnit}`, { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - portfolioId: props.portfolioId, - organizationalUnitId: orgUnit, - organizationId: '', - tagShareOptions: props.tagShareOptions, - }, - }), - ); - } - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - - for (const customResource of customResourceObjects) { - customResource.node.addDependency(logGroup); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/index.ts deleted file mode 100644 index 5a753ab..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/index.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -/** - * share-portfolio-with-org - lambda handler - * - * @param event - * @returns - */ - -import { - CreatePortfolioShareCommand, - DeletePortfolioShareCommand, - DescribePortfolioShareStatusCommand, - ServiceCatalogClient, - UpdatePortfolioShareCommand, -} from '@aws-sdk/client-service-catalog'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -const serviceCatalogClient = new ServiceCatalogClient({ - retryStrategy: setRetryStrategy(), - customUserAgent: process.env['SOLUTION_ID'], -}); - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - console.log(event); - const portfolioId = event.ResourceProperties['portfolioId']; - const organizationalUnitId = event.ResourceProperties['organizationalUnitId']; - const organizationId = event.ResourceProperties['organizationId']; - const tagShareOptions = event.ResourceProperties['tagShareOptions'] === 'true'; - - try { - if (organizationId && organizationalUnitId) { - throw new Error('Both organizational unit id and organization id is specified'); - } else if (organizationalUnitId) { - await modifyPortfolioShare( - organizationalUnitId, - event.RequestType, - portfolioId, - tagShareOptions, - 'ORGANIZATIONAL_UNIT', - ); - } else if (organizationId) { - await modifyPortfolioShare(organizationId, event.RequestType, portfolioId, tagShareOptions, 'ORGANIZATION'); - } else { - throw new Error('Either organizational unit id or organization id is required'); - } - await delay(3000); - } catch (error) { - console.error( - `Failed to ${event.RequestType} portfolio share for portfolio ${portfolioId} with organizational unit ${organizationalUnitId}`, - ); - console.error(error); - return { - PhysicalResourceId: 'none', - Status: 'FAILED', - }; - } - - return { - PhysicalResourceId: 'none', - Status: 'SUCCESS', - }; -} - -export async function createPortfolioShare( - orgResourceId: string, - portfolioId: string, - tagShareOptions: boolean, - nodeType: string, -): Promise { - try { - const response = await throttlingBackOff(() => - serviceCatalogClient.send( - new CreatePortfolioShareCommand({ - PortfolioId: portfolioId, - OrganizationNode: { - Type: nodeType, - Value: orgResourceId, - }, - ShareTagOptions: tagShareOptions, - }), - ), - ); - return response.PortfolioShareToken!; - } catch (error) { - console.error(error); - throw new Error( - `Error while trying to create portfolio share with organization resource id: ${orgResourceId} with portfolio id: ${portfolioId}`, - ); - } -} - -export async function updatePortfolioShare( - orgResourceId: string, - portfolioId: string, - tagShareOptions: boolean, - nodeType: string, -): Promise { - try { - const response = await throttlingBackOff(() => - serviceCatalogClient.send( - new UpdatePortfolioShareCommand({ - PortfolioId: portfolioId, - OrganizationNode: { - Type: nodeType, - Value: orgResourceId, - }, - ShareTagOptions: tagShareOptions, - }), - ), - ); - return response.PortfolioShareToken!; - } catch (error) { - throw new Error( - `UpdatePortfolioShare ran into error with portfolio id: ${portfolioId} on organization resource: ${orgResourceId}`, - ); - } -} - -export async function deletePortfolioShare( - orgResourceId: string, - portfolioId: string, - nodeType: string, -): Promise { - try { - const response = await throttlingBackOff(() => - serviceCatalogClient.send( - new DeletePortfolioShareCommand({ - PortfolioId: portfolioId, - OrganizationNode: { - Type: nodeType, - Value: orgResourceId, - }, - }), - ), - ); - return response.PortfolioShareToken!; - } catch (error) { - throw new Error(`Delete Portfolio share failed on portfolio: ${portfolioId}, organization unit: ${orgResourceId}`); - } -} - -export async function checkPortfolioShareTokenStatus( - portfolioShareToken: string, - portfolioId: string, -): Promise { - try { - const response = await throttlingBackOff(() => - serviceCatalogClient.send( - new DescribePortfolioShareStatusCommand({ - PortfolioShareToken: portfolioShareToken, - }), - ), - ); - console.log(response); - return response.Status!; - } catch (error) { - throw new Error(`Error on checking portfolio share status with portfolioId: ${portfolioId}`); - } -} - -export async function retryCheckPortfolioShareTokenStatus( - portfolioShareToken: string, - portfolioId: string, -): Promise { - let portfolioShareStatus = 'NOT_STARTED'; - - do { - await delay(1000); - portfolioShareStatus = await checkPortfolioShareTokenStatus(portfolioShareToken, portfolioId); - } while ( - portfolioShareStatus === 'NOT_STARTED' || - portfolioShareStatus === 'IN_PROGRESS' || - portfolioShareStatus === 'error' - ); - return portfolioShareStatus; -} - -export async function modifyPortfolioShare( - orgResourceId: string, - requestType: string, - portfolioId: string, - tagShareOptions: boolean, - nodeType: string, -) { - // Random delay to reduce the chance to process more than one portfolio share action at the same time which triggers InvalidStateException - await delay(Math.floor(Math.random() * 5) * 5000); - let portfolioShareToken = ''; - let portfolioShareStatus = 'NOT_STARTED'; - switch (requestType) { - case 'Create': - portfolioShareToken = await createPortfolioShare(orgResourceId, portfolioId, tagShareOptions, nodeType); - portfolioShareStatus = await retryCheckPortfolioShareTokenStatus(portfolioShareToken, portfolioId); - console.log( - `Create portfolio share for portfolio ${portfolioId} with organizational resource ${orgResourceId} status is ${portfolioShareStatus} (portfolioShareToken: ${portfolioShareToken}).`, - ); - break; - case 'Update': - portfolioShareToken = await updatePortfolioShare(orgResourceId, portfolioId, tagShareOptions, nodeType); - portfolioShareStatus = await retryCheckPortfolioShareTokenStatus(portfolioShareToken, portfolioId); - console.log( - `Update portfolio share for portfolio ${portfolioId} with organizational resource ${orgResourceId} status is ${portfolioShareStatus} (portfolioShareToken:${portfolioShareToken}.`, - ); - break; - case 'Delete': - portfolioShareToken = await deletePortfolioShare(orgResourceId, portfolioId, nodeType); - portfolioShareStatus = await retryCheckPortfolioShareTokenStatus(portfolioShareToken, portfolioId); - console.log( - `Delete portfolio share for portfolio ${portfolioId} with organizational resource ${orgResourceId} status is ${portfolioShareStatus} (portfolioShareToken:${portfolioShareToken}.`, - ); - break; - } -} - -/** - * Function to sleep process - * @param ms - * @returns - */ -function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/package.json deleted file mode 100644 index 28e8c43..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-servicecatalog-share-portfolio-with-org", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda to share a Service Catalog prtfolio with an organizational unit.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-service-catalog": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/index.test.ts deleted file mode 100644 index 9a74837..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/index.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - CreatePortfolioShareCommand, - DeletePortfolioShareCommand, - DescribePortfolioShareStatusCommand, - ServiceCatalogClient, - UpdatePortfolioShareCommand, -} from '@aws-sdk/client-service-catalog'; -import { describe, beforeEach, expect, test, jest } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; - -import { StaticInput } from './static-input'; - -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const scClient = AcceleratorMockClient(ServiceCatalogClient); - -describe('Create Event', () => { - jest.setTimeout(240000); - beforeEach(() => { - scClient.reset(); - }); - test('Create share portfolio with org', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newPropsOrgShare] }); - scClient - .on(CreatePortfolioShareCommand, { - PortfolioId: StaticInput.newPropsOrgShare.portfolioId, - OrganizationNode: { Type: 'ORGANIZATION', Value: StaticInput.newPropsOrgShare.organizationId }, - ShareTagOptions: StaticInput.newPropsOrgShare.tagShareOptions === 'true', - }) - .resolves({ PortfolioShareToken: 'PortfolioShareToken' }); - scClient - .on(DescribePortfolioShareStatusCommand, { PortfolioShareToken: 'PortfolioShareToken' }) - .resolvesOnce({ Status: 'NOT_STARTED' }) - .resolvesOnce({ Status: 'IN_PROGRESS' }) - .resolvesOnce({ Status: 'COMPLETED' }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('Create share portfolio with org - CreatePortfolioShare API fails', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.newPropsOrgShare] }); - scClient - .on(CreatePortfolioShareCommand, { - PortfolioId: StaticInput.newPropsOrgShare.portfolioId, - OrganizationNode: { Type: 'ORGANIZATION', Value: StaticInput.newPropsOrgShare.organizationId }, - ShareTagOptions: StaticInput.newPropsOrgShare.tagShareOptions === 'true', - }) - .rejects({}); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); -}); -describe('Update Event', () => { - jest.setTimeout(240000); - beforeEach(() => { - scClient.reset(); - }); - - test('Update share portfolio - both orgId and Org Unit specified', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.orgIdOrgError] }); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); - test('Update share portfolio - no orgId and Org Unit specified', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.noOrgIdOrgError] }); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); - test('Update share portfolio - UpdatePortfolioShare error', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newPropsOuShare] }); - scClient.on(UpdatePortfolioShareCommand).rejects({}); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); - test('Update share portfolio - successful update', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.newPropsOuShare] }); - scClient - .on(UpdatePortfolioShareCommand, { - PortfolioId: StaticInput.newPropsOuShare.portfolioId, - OrganizationNode: { Type: 'ORGANIZATIONAL_UNIT', Value: StaticInput.newPropsOuShare.organizationalUnitId }, - ShareTagOptions: StaticInput.newPropsOuShare.tagShareOptions === 'true', - }) - .resolves({ PortfolioShareToken: 'PortfolioShareToken' }); - scClient - .on(DescribePortfolioShareStatusCommand, { PortfolioShareToken: 'PortfolioShareToken' }) - .resolvesOnce({ Status: 'NOT_STARTED' }) - .resolvesOnce({ Status: 'IN_PROGRESS' }) - .resolvesOnce({ Status: 'COMPLETED' }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); -}); -describe('Delete Event', () => { - jest.setTimeout(240000); - beforeEach(() => { - scClient.reset(); - }); - test('Delete share portfolio - DeletePortfolioShare fails', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newPropsOrgShare] }); - scClient.on(DeletePortfolioShareCommand).rejects({}); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); - test('Delete share portfolio - successful delete', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newPropsOrgShare] }); - scClient - .on(DeletePortfolioShareCommand, { - PortfolioId: StaticInput.newPropsOrgShare.portfolioId, - OrganizationNode: { Type: 'ORGANIZATION', Value: StaticInput.newPropsOrgShare.organizationId }, - }) - .resolves({ PortfolioShareToken: 'PortfolioShareToken' }); - scClient - .on(DescribePortfolioShareStatusCommand, { PortfolioShareToken: 'PortfolioShareToken' }) - .resolvesOnce({ Status: 'NOT_STARTED' }) - .resolvesOnce({ Status: 'IN_PROGRESS' }) - .resolvesOnce({ Status: 'COMPLETED' }); - const response = await handler(event); - expect(response?.Status).toBe('SUCCESS'); - }); - test('Delete share portfolio - DescribePortfolioShareStatus fails', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.newPropsOrgShare] }); - scClient - .on(DeletePortfolioShareCommand, { - PortfolioId: StaticInput.newPropsOrgShare.portfolioId, - OrganizationNode: { Type: 'ORGANIZATION', Value: StaticInput.newPropsOrgShare.organizationId }, - }) - .resolves({ PortfolioShareToken: 'PortfolioShareToken' }); - scClient.on(DescribePortfolioShareStatusCommand, { PortfolioShareToken: 'PortfolioShareToken' }).rejects({}); - const response = await handler(event); - expect(response?.Status).toBe('FAILED'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/static-input.ts deleted file mode 100644 index 062ecbe..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/test/static-input.ts +++ /dev/null @@ -1,22 +0,0 @@ -export abstract class StaticInput { - public static readonly newPropsOrgShare = { - portfolioId: 'portfolioId', - organizationId: 'organizationId', - tagShareOptions: 'true', - }; - public static readonly orgIdOrgError = { - portfolioId: 'portfolioId', - organizationId: 'organizationId', - organizationalUnitId: 'organizationalUnitId', - tagShareOptions: 'true', - }; - public static readonly noOrgIdOrgError = { - portfolioId: 'portfolioId', - tagShareOptions: 'true', - }; - public static readonly newPropsOuShare = { - portfolioId: 'portfolioId', - organizationalUnitId: 'organizationalUnitId', - tagShareOptions: 'true', - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-servicecatalog/share-portfolio-with-org/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/document.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/document.ts deleted file mode 100644 index b77aa57..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/document.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -const path = require('path'); - -export interface IDocument extends cdk.IResource { - readonly documentName: string; -} - -export interface DocumentProps { - readonly name: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly content: any | cdk.IResolvable; - readonly documentType: string; - readonly sharedWithAccountIds: string[]; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Target type of the document - */ - readonly targetType: string | undefined; -} - -export class Document extends cdk.Resource implements IDocument { - readonly documentName: string; - - constructor(scope: Construct, id: string, props: DocumentProps) { - super(scope, id); - - const document = new cdk.aws_ssm.CfnDocument(this, 'Resource', { - name: props.name, - content: props.content, - documentType: props.documentType, - updateMethod: 'NewVersion', - targetType: props.targetType, - }); - - this.documentName = document.ref; - - // Also need a custom resource to do the share - if (props.sharedWithAccountIds.length > 0) { - const SHARE_SSM_DOCUMENT = 'Custom::SSMShareDocument'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, SHARE_SSM_DOCUMENT, { - codeDirectory: path.join(__dirname, 'share-document/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'ShareDocumentActions', - Effect: 'Allow', - Action: ['ssm:DescribeDocumentPermission', 'ssm:ModifyDocumentPermission'], - Resource: cdk.Stack.of(this).formatArn({ - service: 'ssm', - resource: 'document', - arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME, - resourceName: '*', - }), - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'ShareDocument', { - resourceType: SHARE_SSM_DOCUMENT, - serviceToken: provider.serviceToken, - properties: { - name: this.documentName, - accountIds: props.sharedWithAccountIds, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/index.ts deleted file mode 100644 index e0522cf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/index.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; -import { STSClient } from '@aws-sdk/client-sts'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy, getStsCredentials } from '@aws-accelerator/utils/lib/common-functions'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -/** - * get ssm parameter custom control - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const parameterRegion = event.ResourceProperties['parameterRegion']; - const invokingAccountID = event.ResourceProperties['invokingAccountID']; - const parameterAccountID = event.ResourceProperties['parameterAccountID']; - const assumeRoleArn = event.ResourceProperties['assumeRoleArn']; - const parameterName = event.ResourceProperties['parameterName']; - const solutionId = process.env['SOLUTION_ID']; - - switch (event.RequestType) { - case 'Create': - case 'Update': - let ssmClient: SSMClient; - if (invokingAccountID !== parameterAccountID) { - const stsClient = new STSClient({ - region: parameterRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - ssmClient = new SSMClient({ - region: parameterRegion, - credentials: await getStsCredentials(stsClient, assumeRoleArn), - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else { - ssmClient = new SSMClient({ - region: parameterRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } - - const response = await throttlingBackOff(() => ssmClient.send(new GetParameterCommand({ Name: parameterName }))); - - return { PhysicalResourceId: response.Parameter!.Value, Status: 'SUCCESS' }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: undefined, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/jest.config.js deleted file mode 100644 index 6b16d50..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 95, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/package.json deleted file mode 100644 index 0eadb76..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ssm-get-param-value", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/index.test.ts deleted file mode 100644 index ddce6e9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/index.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; -import { StaticInput } from './static-input'; -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const ssmClient = AcceleratorMockClient(SSMClient); -const stsClient = AcceleratorMockClient(STSClient); - -describe('Create Event', () => { - beforeEach(() => { - ssmClient.reset(); - stsClient.reset(); - }); - test('Create event - return parameter successfully', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.crossAccountProps] }); - ssmClient.on(GetParameterCommand, { Name: StaticInput.crossAccountProps.parameterName }).resolves({ - Parameter: { - Value: StaticInput.crossAccountProps.parameterName, - }, - }); - stsClient - .on(AssumeRoleCommand, { - RoleArn: StaticInput.crossAccountProps.assumeRoleArn, - RoleSessionName: 'AcceleratorAssumeRole', - }) - .resolves({ - Credentials: { - AccessKeyId: 'AccessKeyId', - SecretAccessKey: 'SecretAccessKey', - SessionToken: 'SessionToken', - Expiration: new Date(), - }, - }); - - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.crossAccountProps.parameterName); - }); -}); - -describe('Update Event', () => { - beforeEach(() => { - ssmClient.reset(); - }); - test('Update event - return parameter successfully', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { new: [StaticInput.sameAccountProps] }); - ssmClient.on(GetParameterCommand, { Name: StaticInput.crossAccountProps.parameterName }).resolves({ - Parameter: { - Value: StaticInput.crossAccountProps.parameterName, - }, - }); - - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.crossAccountProps.parameterName); - }); -}); - -describe('Delete Event', () => { - test('Delete event - run successfully', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.crossAccountProps] }); - const result = await handler(event); - expect(result?.PhysicalResourceId).toBeUndefined(); - expect(result?.Status).toBe('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/static-input.ts deleted file mode 100644 index a4f099c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/test/static-input.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly crossAccountProps = { - parameterRegion: 'parameterRegion', - invokingAccountID: 'invokingAccountID', - parameterAccountID: 'parameterAccountID', - assumeRoleArn: 'assumeRoleArn', - parameterName: 'parameterName', - }; - public static readonly sameAccountProps = { - parameterRegion: 'parameterRegion', - invokingAccountID: 'invokingAccountID', - parameterAccountID: 'invokingAccountID', - assumeRoleArn: 'assumeRoleArn', - parameterName: 'parameterName', - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/get-param-value/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/inventory.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/inventory.ts deleted file mode 100644 index 09b96ed..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/inventory.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface SsmInventoryProps { - bucketName: string; - bucketRegion: string; - accountId: string; - prefix: string; -} - -export class Inventory extends Construct { - constructor(scope: Construct, id: string, props: SsmInventoryProps) { - super(scope, id); - - new cdk.aws_ssm.CfnResourceDataSync(this, 'ResourceDataSync', { - bucketName: props.bucketName, - bucketRegion: props.bucketRegion, - syncName: `${props.prefix}${props.accountId}-Inventory`, - syncFormat: 'JsonSerDe', - bucketPrefix: `ssm-inventory`, - syncType: 'SyncToDestination', - }); - - new cdk.aws_ssm.CfnAssociation(this, 'GatherInventory', { - name: `AWS-GatherSoftwareInventory`, - associationName: `${props.prefix}${props.accountId}-InventoryCollection`, - scheduleExpression: 'rate(12 hours)', - targets: [ - { - key: 'InstanceIds', - values: ['*'], - }, - ], - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/policy-attachment.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/policy-attachment.ts deleted file mode 100644 index dd39aaf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/policy-attachment.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { RoleSetConfig } from '@aws-accelerator/config'; -import { AcceleratorStack } from '../../../accelerator/lib/stacks/accelerator-stack'; -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -/** - * Construction properties Session Manager Policy - */ -export interface SsmSessionManagerPolicyProps { - readonly s3BucketName?: string; - readonly s3BucketKeyArn?: string; - readonly sendToS3: boolean; - readonly sendToCloudWatchLogs: boolean; - readonly homeRegion: string; - readonly roleSets?: RoleSetConfig[]; - readonly attachPolicyToIamRoles?: string[]; - readonly region: string; - readonly enabledRegions: string[]; - readonly cloudWatchLogGroupList: string[] | undefined; - readonly sessionManagerCloudWatchLogGroupList: string[] | undefined; - readonly s3BucketList: string[] | undefined; - readonly prefixes: { accelerator: string; ssmLog: string }; - readonly ssmKeyDetails: { alias: string; description: string }; -} - -export class SsmSessionManagerPolicy extends Construct { - constructor(scope: Construct, id: string, props: SsmSessionManagerPolicyProps) { - super(scope, id); - - //IAM Policy Document - const sessionManagerRegionalEC2PolicyDocument = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ssmmessages:CreateControlChannel', - 'ssmmessages:CreateDataChannel', - 'ssmmessages:OpenControlChannel', - 'ssmmessages:OpenDataChannel', - 'ssm:UpdateInstanceInformation', - ], - resources: ['*'], - }), - ], - }); - - const sessionManagerEC2Policy = new cdk.aws_iam.ManagedPolicy(this, `SessionManagerPolicy`, { - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'ssmmessages:CreateControlChannel', - 'ssmmessages:CreateDataChannel', - 'ssmmessages:OpenControlChannel', - 'ssmmessages:OpenDataChannel', - 'ssm:UpdateInstanceInformation', - ], - resources: ['*'], - }), - ], - }); - - const sessionManagerEC2ManagedPolicy = new cdk.aws_iam.ManagedPolicy(this, 'SessionManagerEC2Policy', { - document: sessionManagerRegionalEC2PolicyDocument, - managedPolicyName: `${props.prefixes.accelerator}-SessionManagerLogging`, - }); - - // Create an EC2 role that can be used for Session Manager - const sessionManagerEC2Role = new cdk.aws_iam.Role(this, 'SessionManagerEC2Role', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`ec2.${cdk.Stack.of(this).urlSuffix}`), - description: 'IAM Role for an EC2 configured for Session Manager Logging', - managedPolicies: [sessionManagerEC2ManagedPolicy], - roleName: `${props.prefixes.accelerator}-SessionManagerEC2Role`, - }); - - // Create an EC2 instance profile - new cdk.aws_iam.CfnInstanceProfile(this, 'SessionManagerEC2InstanceProfile', { - roles: [sessionManagerEC2Role.roleName], - instanceProfileName: `${props.prefixes.accelerator}-SessionManagerEc2Role`, - }); - - if (props.sendToCloudWatchLogs) { - sessionManagerEC2Policy.addStatements( - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudWatchDescribe', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:DescribeLogGroups'], - resources: props.sessionManagerCloudWatchLogGroupList, - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudWatchLogs', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:CreateLogStream', 'logs:PutLogEvents', 'logs:DescribeLogStreams', 'logs:DescribeLogGroups'], - resources: props.cloudWatchLogGroupList, - }), - ); - sessionManagerRegionalEC2PolicyDocument.addStatements( - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudWatchDescribe', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:DescribeLogGroups'], - resources: props.sessionManagerCloudWatchLogGroupList, - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'CloudWatchLogs', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['logs:CreateLogStream', 'logs:PutLogEvents', 'logs:DescribeLogStreams', 'logs:DescribeLogGroups'], - resources: props.cloudWatchLogGroupList, - }), - ); - } - - if (props.sendToS3) { - if (!props.s3BucketKeyArn || !props.s3BucketName) { - throw new Error('Bucket Key Arn and Bucket Name must be provided'); - } else { - // Build Session Manager EC2 IAM Policy Document to allow writing to S3 Central Logs Bucket - sessionManagerEC2Policy.addStatements( - new cdk.aws_iam.PolicyStatement({ - sid: 'S3CentralLogs', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutObject', 's3:PutObjectAcl'], - resources: props.s3BucketList, - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S3CentralLogsEncryptionConfig', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetEncryptionConfiguration'], - resources: [`arn:${cdk.Stack.of(this).partition}:s3:::${props.s3BucketName}`], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S3CentralLogsEncryption', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:GenerateDataKey'], - resources: [props.s3BucketKeyArn], - }), - ); - sessionManagerRegionalEC2PolicyDocument.addStatements( - new cdk.aws_iam.PolicyStatement({ - sid: 'S3CentralLogs', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:PutObject', 's3:PutObjectAcl'], - resources: props.s3BucketList, - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S3CentralLogsEncryptionConfig', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['s3:GetEncryptionConfiguration'], - resources: [`arn:${cdk.Stack.of(this).partition}:s3:::${props.s3BucketName}`], - }), - new cdk.aws_iam.PolicyStatement({ - sid: 'S3CentralLogsEncryption', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:GenerateDataKey'], - resources: [props.s3BucketKeyArn], - }), - ); - } - } - - //Build Session Manager EC2 IAM Policy Document to allow kms action for session key - sessionManagerEC2Policy.addStatements( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:kms:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:key/*`, - ], - conditions: { - Null: { - 'kms:ResourceAliases': 'false', - }, - 'ForAllValues:StringLike': { - 'kms:ResourceAliases': [props.ssmKeyDetails.alias], - }, - }, - }), - ); - - sessionManagerRegionalEC2PolicyDocument.addStatements( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:kms:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:key/*`, - ], - conditions: { - Null: { - 'kms:ResourceAliases': 'false', - }, - 'ForAllValues:StringLike': { - 'kms:ResourceAliases': [props.ssmKeyDetails.alias], - }, - }, - }), - ); - - // Attach policies to configured roles - for (const iamRoleName of props.attachPolicyToIamRoles ?? []) { - if (this.isRoleInAccount(iamRoleName, props.roleSets ?? [], props.homeRegion)) { - const role = cdk.aws_iam.Role.fromRoleArn( - this, - `AcceleratorSessionManager-${iamRoleName}`, - `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${iamRoleName}`, - ); - sessionManagerEC2ManagedPolicy.attachToRole(role); - } - } - } - - isRoleInAccount(roleName: string, roleSets: RoleSetConfig[], homeRegion: string): boolean { - if (!roleSets) { - return false; - } - - const stack: AcceleratorStack = cdk.Stack.of(this) as AcceleratorStack; - - const roleExists = - roleSets?.filter(roleSet => { - const roleNames = roleSet.roles.map(role => role.name); - - return ( - stack.isIncluded(roleSet.deploymentTargets) && - roleNames.includes(roleName) && - cdk.Stack.of(this).region === homeRegion - ); - }) ?? []; - - return roleExists.length > 0; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/index.ts deleted file mode 100644 index 2119c07..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/index.ts +++ /dev/null @@ -1,354 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SSMClient, PutParameterCommand, DeleteParameterCommand } from '@aws-sdk/client-ssm'; -import { STSClient } from '@aws-sdk/client-sts'; -import { setRetryStrategy, getStsCredentials } from '@aws-accelerator/utils/lib/common-functions'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; - -interface SsmParameterProps { - readonly name: string; - readonly value: string; -} - -/** - * Put SSM parameter custom resource - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const region: string = event.ResourceProperties['region']; - const invokingAccountId: string = event.ResourceProperties['invokingAccountId']; - const parameterAccountIds: string[] = event.ResourceProperties['parameterAccountIds']; - const roleName: string = event.ResourceProperties['roleName']; - const parameters: SsmParameterProps[] = event.ResourceProperties['parameters']; - const solutionId = process.env['SOLUTION_ID']; - const partition = event.ServiceToken.split(':')[1]; - - switch (event.RequestType) { - case 'Create': - // Put parameters - await processParameterCreate( - { invokingAccountId, roleName, region, partition, solutionId }, - parameterAccountIds, - parameters, - ); - break; - - case 'Update': - const oldParameters: SsmParameterProps[] = event.OldResourceProperties['parameters'] ?? []; - const oldAccountIds: string[] = event.OldResourceProperties['parameterAccountIds'] ?? []; - const removedAccountIds = oldAccountIds.filter(id => !parameterAccountIds.includes(id)); - const addedAccountIds = parameterAccountIds.filter(id => !oldAccountIds.includes(id)); - - if (removedAccountIds.length > 0) { - console.log(`Accounts ${removedAccountIds.join(',')} removed, deleting all old parameters for these accounts.`); - await deleteParametersFromRemovedAccounts( - { invokingAccountId, partition, region, roleName, solutionId }, - removedAccountIds, - oldParameters, - ); - } - - await processParameterUpdates( - { invokingAccountId, roleName, region, partition, solutionId }, - parameterAccountIds, - addedAccountIds, - parameters, - oldParameters, - ); - break; - - case 'Delete': - // Delete all parameters - await processParameterDelete( - { invokingAccountId, roleName, region, partition, solutionId }, - parameterAccountIds, - parameters, - ); - break; - } - - return { - PhysicalResourceId: parameters[0].name, // required for backwards compatibility - Status: 'SUCCESS', - }; -} - -/** - * Set role ARN for a given partition, account ID and role name - * - * @param partition - * @param parameterAccountId - * @param roleName - * @returns - */ -function setRoleArn(partition: string, parameterAccountId: string, roleName: string): string { - return `arn:${partition}:iam::${parameterAccountId}:role/${roleName}`; -} - -/** - * Returns an SSM client based on the account ID and region - * @param accountId - * @param region - * @returns - */ -async function getSsmClient( - invokingAccountId: string, - parameterAccountId: string, - region: string, - assumeRoleArn: string, - solutionId?: string, -): Promise { - let ssmClient: SSMClient; - if (invokingAccountId !== parameterAccountId) { - const stsClient = new STSClient({ region: region, customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - ssmClient = new SSMClient({ - region: region, - credentials: await getStsCredentials(stsClient, assumeRoleArn), - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else { - ssmClient = new SSMClient({ region: region, customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - } - return ssmClient; -} - -/** - * Function to delete ssm parameters from removed accounts - * @param stsConfig - * @param removedAccountIds string[] - * @param oldParameters {@link SsmParameterProps}[] - */ -async function deleteParametersFromRemovedAccounts( - stsConfig: { - invokingAccountId: string; - partition: string; - region: string; - roleName: string; - solutionId?: string; - }, - removedAccountIds: string[], - oldParameters: SsmParameterProps[], -): Promise { - const oldParameterNames = oldParameters ? oldParameters.map(oldParam => oldParam.name) : []; - - // Remove old parameters from removed accounts - for (const removedAccountId of removedAccountIds) { - const assumeRoleArn = setRoleArn(stsConfig.partition, removedAccountId, stsConfig.roleName); - const ssmClient = await getSsmClient( - stsConfig.invokingAccountId, - removedAccountId, - stsConfig.region, - assumeRoleArn, - stsConfig.solutionId, - ); - // Remove parameters - await deleteParameters(ssmClient, removedAccountId, oldParameterNames); - } -} - -/** - * Process parameter delete - * @param stsClient - * @param parameterAccountIds string[] - * @param parameterProps {@link SsmParameterProps}[] - */ -async function processParameterDelete( - stsClient: { invokingAccountId: string; roleName: string; region: string; partition: string; solutionId?: string }, - parameterAccountIds: string[], - parameterProps: SsmParameterProps[], -): Promise { - for (const parameterAccountId of parameterAccountIds) { - // Get SSM client for the parameter account - const assumeRoleArn = setRoleArn(stsClient.partition, parameterAccountId, stsClient.roleName); - const ssmClient = await getSsmClient( - stsClient.invokingAccountId, - parameterAccountId, - stsClient.region, - assumeRoleArn, - stsClient.solutionId, - ); - - // Delete all parameters - await deleteParameters(ssmClient, parameterAccountId, undefined, parameterProps); - } -} - -/** - * Process parameter create - * @param stsClient - * @param parameterAccountIds string[] - * @param parameters {@link SsmParameterProps}[] - */ -async function processParameterCreate( - stsClient: { invokingAccountId: string; roleName: string; region: string; partition: string; solutionId?: string }, - parameterAccountIds: string[], - parameters: SsmParameterProps[], -): Promise { - for (const parameterAccountId of parameterAccountIds) { - // Get SSM client for the parameter account - const assumeRoleArn = setRoleArn(stsClient.partition, parameterAccountId, stsClient.roleName); - const ssmClient = await getSsmClient( - stsClient.invokingAccountId, - parameterAccountId, - stsClient.region, - assumeRoleArn, - stsClient.solutionId, - ); - - // Put parameters - await createParameters(ssmClient, parameterAccountId, parameters); - } -} - -/** - * Create SSM parameters - * @param ssmClient {@link AWS.SSM} - * @param parameterAccountId string - * @param parameters {@link SsmParameterProps}[] - */ -async function createParameters(ssmClient: SSMClient, parameterAccountId: string, parameters: SsmParameterProps[]) { - // Put parameters - for (const parameter of parameters) { - console.log(`Put SSM parameter ${parameter.name} to account ${parameterAccountId}`); - await throttlingBackOff(() => - ssmClient.send( - new PutParameterCommand({ Name: parameter.name, Value: parameter.value, Type: 'String', Overwrite: true }), - ), - ); - } -} - -/** - * Delete SSM parameters - * @param ssmClient {@link AWS.SSM} - * @param parameterAccountId string - * @param parameterNames string[] - * @param parameterProps SsmParameterProps[] - */ -async function deleteParameters( - ssmClient: SSMClient, - parameterAccountId: string, - parameterNames?: string[], - parameterProps?: SsmParameterProps[], -) { - const deleteParameterNames: string[] = []; - - for (const parameterProp of parameterProps ?? []) { - deleteParameterNames.push(parameterProp.name); - } - - deleteParameterNames.push(...(parameterNames ?? [])); - - // Remove parameters - for (const deleteParameterName of deleteParameterNames) { - console.log(`Delete SSM parameter ${deleteParameterName} from account ${parameterAccountId}`); - await throttlingBackOff(() => ssmClient.send(new DeleteParameterCommand({ Name: deleteParameterName }))); - } -} - -/** - * Function to get Modified parameters - * @param ssmClient - * @param parameterAccountId string - * @param newParameters {@link SsmParameterProps}[] - * @param oldParameters {@link SsmParameterProps}[] - */ -async function modifyParameters( - ssmClient: SSMClient, - parameterAccountId: string, - newParameters: SsmParameterProps[], - oldParameters: SsmParameterProps[], -): Promise { - const modifiedParameters: SsmParameterProps[] = []; - - for (const newParameter of newParameters) { - const filterParameters = oldParameters.filter(item => item.name === newParameter.name); - for (const filterParameter of filterParameters) { - if (filterParameter.value !== newParameter.value) { - modifiedParameters.push(newParameter); - } - } - } - - // Modify existing parameters if their values have changed - for (const parameter of modifiedParameters) { - console.log(`Modify SSM parameter ${parameter.name} in account ${parameterAccountId}`); - await throttlingBackOff(() => - ssmClient.send( - new PutParameterCommand({ Name: parameter.name, Value: parameter.value, Type: 'String', Overwrite: true }), - ), - ); - } -} - -/** - * Process parameter updates - * @param stsClient - * @param parameterAccountIds string[] - * @param addedAccountIds string[] - * @param newParameters {@link SsmParameterProps}[] - * @param oldParameters {@link SsmParameterProps}[] - */ -async function processParameterUpdates( - stsClient: { invokingAccountId: string; roleName: string; region: string; partition: string; solutionId?: string }, - parameterAccountIds: string[], - addedAccountIds: string[], - newParameters: SsmParameterProps[], - oldParameters: SsmParameterProps[], -): Promise { - const existingAccountIds = parameterAccountIds.filter(item => !addedAccountIds.includes(item)); - - if (addedAccountIds.length > 0) { - await processParameterCreate(stsClient, addedAccountIds, newParameters); - } - - for (const parameterAccountId of existingAccountIds) { - // Get SSM client for the parameter account - - const assumeRoleArn = setRoleArn(stsClient.partition, parameterAccountId, stsClient.roleName); - const ssmClient = await getSsmClient( - stsClient.invokingAccountId, - parameterAccountId, - stsClient.region, - assumeRoleArn, - stsClient.solutionId, - ); - - const oldParameterNames = oldParameters ? oldParameters.map(oldParam => oldParam.name) : []; - const newParameterNames = newParameters.map(newParam => newParam.name); - - const removedParameterNames = oldParameterNames.filter(name => !newParameterNames.includes(name)); - const addedParameters = newParameters.filter(param => !oldParameterNames.includes(param.name)); - - // Remove parameters - await deleteParameters(ssmClient, parameterAccountId, removedParameterNames); - - // Create new parameters - // Put parameters - await createParameters(ssmClient, parameterAccountId, addedParameters); - - // Modify existing parameters if their values have changed - await modifyParameters(ssmClient, parameterAccountId, newParameters, oldParameters); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/jest.config.js deleted file mode 100644 index dba60d3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 90, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/package.json deleted file mode 100644 index 929fbf2..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ssm-put-param-value", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/client-sts": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/index.test.ts deleted file mode 100644 index ccbe31d..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/index.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { SSMClient, PutParameterCommand, DeleteParameterCommand } from '@aws-sdk/client-ssm'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; -import { StaticInput } from './static-input'; -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const ssmClient = AcceleratorMockClient(SSMClient); -const stsClient = AcceleratorMockClient(STSClient); - -describe('Create Event', () => { - beforeEach(() => { - ssmClient.reset(); - stsClient.reset(); - }); - test('Create event - put parameter value cross account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.crossAccountProps] }); - ssmClient - .on(PutParameterCommand, { - Name: StaticInput.crossAccountProps.parameters[0].name, - Value: StaticInput.crossAccountProps.parameters[0].value, - Overwrite: true, - Type: 'String', - }) - .resolves({}); - stsClient.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'AccessKeyId', - SecretAccessKey: 'SecretAccessKey', - SessionToken: 'SessionToken', - Expiration: new Date(), - }, - }); - - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.crossAccountProps.parameters[0].name); - }); - test('Create event - put parameter value same account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.sameAccountProps] }); - ssmClient - .on(PutParameterCommand, { - Name: StaticInput.sameAccountProps.parameters[0].name, - Value: StaticInput.sameAccountProps.parameters[0].value, - Overwrite: true, - Type: 'String', - }) - .resolves({}); - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.sameAccountProps.parameters[0].name); - }); -}); - -describe('Update Event', () => { - beforeEach(() => { - ssmClient.reset(); - stsClient.reset(); - }); - test('Update event - cross accounts, accounts added', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.crossAccountAddUpdateNewProps], - old: [StaticInput.crossAccountAddUpdateOldProps], - }); - ssmClient.on(PutParameterCommand, { - Name: StaticInput.crossAccountAddUpdateNewProps.parameters[0].name, - Value: StaticInput.crossAccountAddUpdateNewProps.parameters[0].value, - Overwrite: true, - Type: 'String', - }); - ssmClient - .on(DeleteParameterCommand, { Name: StaticInput.crossAccountAddUpdateOldProps.parameters[0].name }) - .resolves({}); - // since there are 10 assume roles happening here mocking the entire call to return success - stsClient.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'AccessKeyId', - SecretAccessKey: 'SecretAccessKey', - SessionToken: 'SessionToken', - Expiration: new Date(), - }, - }); - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.crossAccountAddUpdateNewProps.parameters[0].name); - }); - test('Update event - cross accounts, accounts removed', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.crossAccountRemoveUpdateNewProps], - old: [StaticInput.crossAccountRemoveUpdateOldProps], - }); - ssmClient.on(PutParameterCommand, { - Name: StaticInput.crossAccountRemoveUpdateNewProps.parameters[0].name, - Value: StaticInput.crossAccountRemoveUpdateNewProps.parameters[0].value, - Overwrite: true, - Type: 'String', - }); - ssmClient.on(DeleteParameterCommand).resolves({}); - // since there are 10 assume roles happening here mocking the entire call to return success - stsClient.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'AccessKeyId', - SecretAccessKey: 'SecretAccessKey', - SessionToken: 'SessionToken', - Expiration: new Date(), - }, - }); - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.crossAccountRemoveUpdateNewProps.parameters[0].name); - }); -}); - -describe('Delete Event', () => { - beforeEach(() => { - ssmClient.reset(); - stsClient.reset(); - }); - test('Delete event - put parameter value cross account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.crossAccountProps] }); - ssmClient.on(DeleteParameterCommand, { Name: StaticInput.crossAccountProps.parameters[0].name }).resolves({}); - stsClient.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'AccessKeyId', - SecretAccessKey: 'SecretAccessKey', - SessionToken: 'SessionToken', - Expiration: new Date(), - }, - }); - - const result = await handler(event); - expect(result?.PhysicalResourceId).toEqual(StaticInput.crossAccountProps.parameters[0].name); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/static-input.ts deleted file mode 100644 index 9cd99a9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/test/static-input.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly crossAccountProps = { - region: 'region', - invokingAccountId: 'invokingAccountId', - parameterAccountIds: genAccArray(1), - roleName: 'roleName', - parameters: [{ name: 'name1', value: 'value1' }], - }; - public static readonly sameAccountProps = { - region: 'region', - invokingAccountId: 'invokingAccountId', - parameterAccountIds: ['invokingAccountId'], - roleName: 'roleName', - parameters: [{ name: 'name1', value: 'value1' }], - }; - public static readonly crossAccountAddUpdateNewProps = { - region: 'region', - invokingAccountId: 'invokingAccountId', - parameterAccountIds: genAccArray(1), - roleName: 'roleName', - parameters: [{ name: 'name1', value: 'value1' }], - }; - public static readonly crossAccountAddUpdateOldProps = { - region: 'region', - invokingAccountId: 'invokingAccountId', - parameterAccountIds: genAccArray(21), - roleName: 'roleName', - parameters: [ - { name: 'name2', value: 'value2' }, - { name: 'name1', value: 'newValue1' }, - { name: 'test', value: 'test' }, - ], - }; - public static readonly crossAccountRemoveUpdateNewProps = { - region: 'region', - invokingAccountId: 'invokingAccountId', - parameterAccountIds: genAccArray(2), - roleName: 'roleName', - parameters: [{ name: 'name1', value: 'value1' }], - }; - public static readonly crossAccountRemoveUpdateOldProps = { - region: 'region', - invokingAccountId: 'invokingAccountId', - roleName: 'roleName', - }; - public static readonly crossAccountAddUpdateOldProps1 = { - region: 'region', - invokingAccountId: 'invokingAccountId', - parameterAccountIds: genAccArray(2), - roleName: 'roleName', - }; -} -// function to generate an array of strings based on length of array as input -function genAccArray(length: number) { - const array: string[] = []; - for (let i = 0; i < length; i++) { - array.push(`acc${i.toString().padStart(3, '0')}`); - } - return array; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/tsconfig.json deleted file mode 100644 index 272282f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-param-value/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-ssm-parameter.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-ssm-parameter.ts deleted file mode 100644 index ef5c385..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/put-ssm-parameter.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { v4 as uuidv4 } from 'uuid'; - -export interface SsmParameterProps { - /** - * Name of the parameter - */ - readonly name: string; - /** - * Value of the parameter - */ - readonly value: string; -} - -/** - * SsmParameterProps - */ -export interface PutSsmParameterProps { - /** - * The AWS account IDs the parameters will be created in - */ - readonly accountIds: string[]; - /** - * The AWS region the parameter will be created in - */ - readonly region: string; - /** - * The role name to assume - */ - readonly roleName: string; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * The parameters to be created - */ - readonly parameters: SsmParameterProps[]; - /** - * The account ID invoking the request - */ - readonly invokingAccountId: string; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; -} - -/** - * SsmParameter class - to get ssm parameter value from other account - */ -export class PutSsmParameter extends Construct { - constructor(scope: Construct, id: string, props: PutSsmParameterProps) { - super(scope, id); - - const policyStatements = []; - const RESOURCE_TYPE = 'Custom::SsmPutParameterValue'; - const codeDirectory = path.join(__dirname, 'put-param-value/dist'); - const description = `Custom resource provider to put cross-account ssm parameter value`; - - // Push policy statement - policyStatements.push({ - Sid: 'SsmPutParameterActions', - Effect: 'Allow', - Action: ['ssm:DeleteParameter', 'ssm:PutParameter'], - Resource: ['*'], - }); - - policyStatements.push({ - Sid: 'StsAssumeRoleActions', - Effect: 'Allow', - Action: ['sts:AssumeRole'], - Resource: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}*`], - }); - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory, - description, - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements, - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - region: props.region, - parameterAccountIds: props.accountIds, - parameters: props.parameters, - roleName: props.roleName, - invokingAccountId: props.invokingAccountId, - uuid: uuidv4(), - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings.ts deleted file mode 100644 index 4f7c73b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -const path = require('path'); - -/** - * Construction properties for an S3 Bucket object. - */ -export interface SsmSessionManagerSettingsProps { - readonly s3BucketName?: string; - readonly s3KeyPrefix?: string; - readonly s3BucketKeyArn?: string; - readonly sendToS3: boolean; - readonly sendToCloudWatchLogs: boolean; - readonly cloudWatchEncryptionEnabled: boolean; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchEncryptionKey?: cdk.aws_kms.IKey; - readonly region: string; - readonly rolesInAccounts?: { account: string; region: string; parametersByPath: { [key: string]: string } }[]; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Accelerator and SSM Log Prefixes - */ - readonly prefixes: { accelerator: string; ssmLog: string }; - /** - * Accelerator CMK Details for SSM - */ - readonly ssmKeyDetails: { alias: string; description: string }; -} - -export class SsmSessionManagerSettings extends Construct { - readonly id: string; - - constructor(scope: Construct, id: string, props: SsmSessionManagerSettingsProps) { - super(scope, id); - - let sessionManagerLogGroupName = ''; - if (props.sendToCloudWatchLogs) { - const logGroupName = `${props.prefixes.accelerator}-sessionmanager-logs`; - const sessionManagerLogGroup = new cdk.aws_logs.LogGroup(this, 'SessionManagerCloudWatchLogGroup', { - retention: props.logRetentionInDays, - logGroupName: logGroupName, - encryptionKey: props.cloudWatchEncryptionKey, - }); - sessionManagerLogGroupName = sessionManagerLogGroup.logGroupName; - } - - let sessionManagerSessionCmk: cdk.aws_kms.Key | undefined = undefined; - sessionManagerSessionCmk = new cdk.aws_kms.Key(this, 'SessionManagerSessionKey', { - enableKeyRotation: true, - description: props.ssmKeyDetails.description, - alias: props.ssmKeyDetails.alias, - }); - - const sessionManagerUserPolicyDocument = new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:GenerateDataKey'], - resources: [sessionManagerSessionCmk.keyArn], - }), - ], - }); - - // Create an IAM Policy for users to be able to use Session Manager with KMS encryption - new cdk.aws_iam.ManagedPolicy(this, 'SessionManagerUserKMSPolicy', { - document: sessionManagerUserPolicyDocument, - managedPolicyName: `${props.prefixes.accelerator}-SessionManagerUserKMS-${props.region}`, - }); - - // - // Function definition for the custom resource - // - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, 'Custom::SessionManagerLogging', { - codeDirectory: path.join(__dirname, 'session-manager-settings/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Effect: 'Allow', - Action: ['ssm:DescribeDocument', 'ssm:CreateDocument', 'ssm:UpdateDocument'], - Resource: '*', - }, - ], - }); - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: 'Custom::SsmSessionManagerSettings', - serviceToken: provider.serviceToken, - properties: { - s3BucketName: props.s3BucketName, - s3KeyPrefix: props.s3KeyPrefix, - s3EncryptionEnabled: props.sendToS3, //set to true if sending to S3 - cloudWatchLogGroupName: sessionManagerLogGroupName, - cloudWatchEncryptionEnabled: props.cloudWatchEncryptionEnabled, - kmsKeyId: sessionManagerSessionCmk.keyId, - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.cloudWatchEncryptionKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.id = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/index.ts deleted file mode 100644 index 5b067f9..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import { CreateDocumentRequest, UpdateDocumentRequest } from 'aws-sdk/clients/ssm'; -import { - SSMClient, - DescribeDocumentCommand, - UpdateDocumentCommand, - CreateDocumentCommand, - DuplicateDocumentContent, - InvalidDocument, -} from '@aws-sdk/client-ssm'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -const documentName = 'SSM-SessionManagerRunShell'; - -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const s3BucketName: string = event.ResourceProperties['s3BucketName']; - const s3KeyPrefix: string = event.ResourceProperties['s3KeyPrefix']; - const s3EncryptionEnabled: boolean = event.ResourceProperties['s3EncryptionEnabled'] === 'true'; - const cloudWatchLogGroupName: string = event.ResourceProperties['cloudWatchLogGroupName']; - const cloudWatchEncryptionEnabled: boolean = event.ResourceProperties['cloudWatchEncryptionEnabled'] === 'true'; - const kmsKeyId: string = event.ResourceProperties['kmsKeyId']; - const solutionId = process.env['SOLUTION_ID']; - - const ssm = new SSMClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - - switch (event.RequestType) { - case 'Create': - case 'Update': - // Based on doc: https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-configure-preferences-cli.html - const settings = { - schemaVersion: '1.0', - description: 'Document to hold regional settings for Session Manager', - sessionType: 'Standard_Stream', - inputs: { - cloudWatchEncryptionEnabled, - cloudWatchLogGroupName, - kmsKeyId, - s3BucketName, - s3EncryptionEnabled, - s3KeyPrefix: s3KeyPrefix ?? '', - runAsEnabled: false, - runAsDefaultUser: '', - }, - }; - try { - await throttlingBackOff(() => ssm.send(new DescribeDocumentCommand({ Name: documentName }))); - const updateDocumentRequest: UpdateDocumentRequest = { - Content: JSON.stringify(settings), - Name: documentName, - DocumentVersion: '$LATEST', - }; - console.log('Update SSM Document Request: ', updateDocumentRequest); - await throttlingBackOff(() => ssm.send(new UpdateDocumentCommand(updateDocumentRequest))); - console.log('Update SSM Document Success'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - if (e instanceof DuplicateDocumentContent) { - console.log(`SSM Document is Already latest :${documentName}`); - } else if (e instanceof InvalidDocument) { - const createDocumentRequest: CreateDocumentRequest = { - Content: JSON.stringify(settings), - Name: documentName, - DocumentType: `Session`, - }; - console.log('Create SSM Document Request: ', createDocumentRequest); - await throttlingBackOff(() => ssm.send(new CreateDocumentCommand(createDocumentRequest))); - console.log('Create SSM Document Success'); - } else { - throw new Error(`Error while updating SSM Document :${documentName}. Received: ${JSON.stringify(e)}`); - } - } - - return { - PhysicalResourceId: 'session-manager-settings', - Status: 'SUCCESS', - }; - - case 'Delete': - // Do Nothing - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/jest.config.js deleted file mode 100644 index dba60d3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 90, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/package.json deleted file mode 100644 index a8e787f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ssm-session-manager-settings", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ssm": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/index.test.ts deleted file mode 100644 index 7b10bae..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/index.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - SSMClient, - DescribeDocumentCommand, - UpdateDocumentCommand, - CreateDocumentCommand, - DuplicateDocumentContent, - InvalidDocument, - MaxDocumentSizeExceeded, -} from '@aws-sdk/client-ssm'; -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; -import { StaticInput } from './static-input'; -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const ssmClient = AcceleratorMockClient(SSMClient); -const documentName = 'SSM-SessionManagerRunShell'; -describe('Create Event', () => { - beforeEach(() => { - ssmClient.reset(); - }); - test('Create event - put parameter value cross account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.createProps] }); - ssmClient.on(DescribeDocumentCommand, { Name: documentName }).resolves({}); - ssmClient - .on(UpdateDocumentCommand, { - Content: JSON.stringify(StaticInput.createPropsSetting), - Name: documentName, - DocumentVersion: '$LATEST', - }) - .resolves({}); - expect(await handler(event)).toEqual({ PhysicalResourceId: 'session-manager-settings', Status: 'SUCCESS' }); - }); -}); - -describe('Update Event', () => { - beforeEach(() => { - ssmClient.reset(); - }); - test('Update event - document already exists', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.createProps], - }); - ssmClient - .on(DescribeDocumentCommand, { Name: documentName }) - .rejects(new DuplicateDocumentContent({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - expect(await handler(event)).toEqual({ PhysicalResourceId: 'session-manager-settings', Status: 'SUCCESS' }); - }); - test('Update event - unknown error MaxDocumentSizeExceeded', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.createProps], - }); - ssmClient - .on(DescribeDocumentCommand, { Name: documentName }) - .rejects(new MaxDocumentSizeExceeded({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - await expect(handler(event)).rejects.toThrowError( - 'Error while updating SSM Document :SSM-SessionManagerRunShell. Received: {"name":"MaxDocumentSizeExceeded","$fault":"client","$metadata":{"httpStatusCode":400}}', - ); - }); - test('Update event - create document on InvalidDocument exception', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.updateProps], - }); - ssmClient - .on(DescribeDocumentCommand, { Name: documentName }) - .rejects(new InvalidDocument({ $metadata: { httpStatusCode: 400 }, message: 'Error' })); - ssmClient - .on(CreateDocumentCommand, { - Content: JSON.stringify(StaticInput.updatePropsSetting), - Name: documentName, - DocumentType: `Session`, - }) - .resolves({}); - expect(await handler(event)).toEqual({ PhysicalResourceId: 'session-manager-settings', Status: 'SUCCESS' }); - }); -}); - -describe('Delete Event', () => { - test('Delete event - put parameter value cross account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.createProps] }); - const result = await handler(event); - expect(result?.Status).toBe('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/static-input.ts deleted file mode 100644 index 1473416..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/test/static-input.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly createProps = { - s3BucketName: 's3BucketName', - s3KeyPrefix: 's3KeyPrefix', - s3EncryptionEnabled: 's3EncryptionEnabled', - cloudWatchLogGroupName: 'cloudWatchLogGroupName', - cloudWatchEncryptionEnabled: 'cloudWatchEncryptionEnabled', - kmsKeyId: 'kmsKeyId', - }; - public static readonly createPropsSetting = { - schemaVersion: '1.0', - description: 'Document to hold regional settings for Session Manager', - sessionType: 'Standard_Stream', - inputs: { - cloudWatchEncryptionEnabled: StaticInput.createProps.cloudWatchEncryptionEnabled === 'true', - cloudWatchLogGroupName: StaticInput.createProps.cloudWatchLogGroupName, - kmsKeyId: StaticInput.createProps.kmsKeyId, - s3BucketName: StaticInput.createProps.s3BucketName, - s3EncryptionEnabled: StaticInput.createProps.s3EncryptionEnabled === 'true', - s3KeyPrefix: StaticInput.createProps.s3KeyPrefix, - runAsEnabled: false, - runAsDefaultUser: '', - }, - }; - public static readonly updateProps = { - s3BucketName: 's3BucketName', - s3EncryptionEnabled: 's3EncryptionEnabled', - cloudWatchLogGroupName: 'cloudWatchLogGroupName', - cloudWatchEncryptionEnabled: 'cloudWatchEncryptionEnabled', - kmsKeyId: 'kmsKeyId', - }; - public static readonly updatePropsSetting = { - schemaVersion: '1.0', - description: 'Document to hold regional settings for Session Manager', - sessionType: 'Standard_Stream', - inputs: { - cloudWatchEncryptionEnabled: StaticInput.updateProps.cloudWatchEncryptionEnabled === 'true', - cloudWatchLogGroupName: StaticInput.updateProps.cloudWatchLogGroupName, - kmsKeyId: StaticInput.updateProps.kmsKeyId, - s3BucketName: StaticInput.updateProps.s3BucketName, - s3EncryptionEnabled: StaticInput.updateProps.s3EncryptionEnabled === 'true', - runAsEnabled: false, - runAsDefaultUser: '', - }, - }; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/session-manager-settings/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/index.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/index.ts deleted file mode 100644 index 19b9323..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/index.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { CloudFormationCustomResourceEvent } from '@aws-accelerator/utils/lib/common-types'; -import { SSMClient, DescribeDocumentPermissionCommand, ModifyDocumentPermissionCommand } from '@aws-sdk/client-ssm'; - -/** - * share-document - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: CloudFormationCustomResourceEvent): Promise< - | { - PhysicalResourceId: string | undefined; - Status: string; - } - | undefined -> { - const name = event.ResourceProperties['name']; - const accountIds: string[] = event.ResourceProperties['accountIds']; - const solutionId = process.env['SOLUTION_ID']; - - const ssmClient = new SSMClient({ customUserAgent: solutionId, retryStrategy: setRetryStrategy() }); - - console.log('DescribeDocumentPermissionCommand:'); - const documentPermission: string[] = []; - let nextToken: string | undefined = undefined; - // there is no paginator for DescribeDocumentPermissionCommand, so we need to loop through all pages. - do { - const page = await throttlingBackOff(() => - ssmClient.send(new DescribeDocumentPermissionCommand({ Name: name, PermissionType: 'Share' })), - ); - - for (const accountId of page.AccountIds ?? []) { - documentPermission.push(accountId); - } - nextToken = page.NextToken; - } while (nextToken); - - console.log(documentPermission); - - switch (event.RequestType) { - case 'Create': - case 'Update': - const accountIdsToAdd: string[] = []; - const accountIdsToRemove: string[] = []; - - // Identify accounts to add - accountIds.forEach(accountId => { - if (!documentPermission.includes(accountId)) { - accountIdsToAdd.push(accountId); - } - }); - - // Identify accounts to remove - documentPermission.forEach(accountId => { - if (!accountIds.includes(accountId)) { - accountIdsToRemove.push(accountId); - } - }); - - console.log(`accountIdsToAdd: ${accountIdsToAdd}`); - console.log(`accountIdsToRemove: ${accountIdsToRemove}`); - - // api call can only process 20 accounts - // in the AccountIdsToAdd and AccountIdsToRemove - // on each call - - let maxItems = accountIdsToAdd.length; - if (accountIdsToRemove.length > maxItems) { - maxItems = accountIdsToRemove.length; - } - let counter = 0; - while (counter <= maxItems) { - const itemsToAdd = accountIdsToAdd.slice(counter, counter + 20); - const itemsToRemove = accountIdsToRemove.slice(counter, counter + 20); - - console.log('ModifyDocumentPermissionCommand:'); - const response = await throttlingBackOff(() => - ssmClient.send( - new ModifyDocumentPermissionCommand({ - Name: name, - PermissionType: 'Share', - AccountIdsToAdd: itemsToAdd, - AccountIdsToRemove: itemsToRemove, - }), - ), - ); - console.log(JSON.stringify(response)); - counter = counter + 20; - } - - return { - PhysicalResourceId: 'share-document', - Status: 'SUCCESS', - }; - - case 'Delete': - console.log('Start un-sharing the document'); - console.log('Following accounts to be un-share'); - console.log(documentPermission); - - let deleteCounter = 0; - while (deleteCounter <= documentPermission.length) { - const itemsToDelete = documentPermission.slice(deleteCounter, deleteCounter + 20); - - // Remove sharing - await throttlingBackOff(() => - ssmClient.send( - new ModifyDocumentPermissionCommand({ - Name: name, - PermissionType: 'Share', - AccountIdsToRemove: itemsToDelete, - }), - ), - ); - deleteCounter = deleteCounter + 20; - } - return { - PhysicalResourceId: event.PhysicalResourceId, - Status: 'SUCCESS', - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/jest.config.js b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/jest.config.js deleted file mode 100644 index dba60d3..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 90, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/package.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/package.json deleted file mode 100644 index 6391801..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-ssm-share-document", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/index.js --platform=node --target=node18 index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-ssm": "3.410.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/index.test.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/index.test.ts deleted file mode 100644 index ef19396..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/index.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { SSMClient, DescribeDocumentPermissionCommand, ModifyDocumentPermissionCommand } from '@aws-sdk/client-ssm'; -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { handler } from '../index'; -import { AcceleratorMockClient, EventType } from '../../../../test/unit-test/common/resources'; -import { StaticInput } from './static-input'; -import { AcceleratorUnitTest } from '../../../../test/unit-test/accelerator-unit-test'; - -const ssmClient = AcceleratorMockClient(SSMClient); - -describe('Create Event', () => { - beforeEach(() => { - ssmClient.reset(); - }); - test('Create event - put parameter value cross account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [StaticInput.createProps] }); - ssmClient.on(DescribeDocumentPermissionCommand).resolves({}); - ssmClient - .on(ModifyDocumentPermissionCommand, { - Name: 'name', - PermissionType: 'Share', - AccountIdsToAdd: ['acc000'], - AccountIdsToRemove: undefined, - }) - .resolves({}); - const response = await handler(event); - expect(response?.PhysicalResourceId).toEqual('share-document'); - }); -}); - -describe('Update Event', () => { - beforeEach(() => { - ssmClient.reset(); - }); - test('Update event - document already exists', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [StaticInput.updatePropsNew], - old: [StaticInput.createProps], - }); - ssmClient.on(DescribeDocumentPermissionCommand).resolves({ - AccountIds: StaticInput.manyAccounts, - }); - - ssmClient.on(ModifyDocumentPermissionCommand).resolves({}); - - const response = await handler(event); - expect(response?.PhysicalResourceId).toEqual('share-document'); - }); -}); - -describe('Delete Event', () => { - beforeEach(() => { - ssmClient.reset(); - }); - test('Delete event - put parameter value cross account', async () => { - const event = AcceleratorUnitTest.getEvent(EventType.DELETE, { new: [StaticInput.updatePropsNew] }); - ssmClient.on(DescribeDocumentPermissionCommand).resolves({ - AccountIds: StaticInput.manyAccounts, - }); - ssmClient.on(ModifyDocumentPermissionCommand).resolves({}); - - const response = await handler(event); - expect(response?.Status).toEqual('SUCCESS'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/static-input.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/static-input.ts deleted file mode 100644 index fcba712..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/test/static-input.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Abstract class to configure static input for create-log-groups custom resource AWS Lambda unit testing - */ -export abstract class StaticInput { - public static readonly createProps = { - name: 'name', - accountIds: genAccArray(1), - }; - public static readonly updatePropsNew = { - name: 'name', - accountIds: genAccArray(21), - }; - public static readonly manyAccounts = genAccArray(100); -} -// function to generate an array of strings based on length of array as input -export function genAccArray(length: number) { - const array: string[] = []; - for (let i = 0; i < length; i++) { - array.push(`acc${i.toString().padStart(3, '0')}`); - } - return array; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/tsconfig.json deleted file mode 100644 index ebcb98a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/share-document/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/ssm-parameter-lookup.ts b/source/packages/@aws-accelerator/constructs/lib/aws-ssm/ssm-parameter-lookup.ts deleted file mode 100644 index 5e175bb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/aws-ssm/ssm-parameter-lookup.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = require('path'); - -/** - * SsmParameterLookupProps - */ -export interface SsmParameterLookupProps { - /** - * Name of the parameter - */ - readonly name: string; - /** - * Parameter account id - */ - readonly accountId: string; - /** - * Parameter region - */ - readonly parameterRegion: string; - /** - * The name of the cross account role to use when accessing - */ - readonly roleName?: string; - /** - * Custom resource lambda log group encryption key - */ - readonly kmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log retention in days - */ - readonly logRetentionInDays?: number; - /** - * Accelerator Prefix - */ - readonly acceleratorPrefix: string; - /** - * Resolved parameter value - */ - readonly resolvedValue?: string; -} - -/** - * SsmParameterLookup class - to get ssm parameter value from other account - */ -export class SsmParameterLookup extends Construct { - public readonly value: string = ''; - - constructor(scope: Construct, id: string, props: SsmParameterLookupProps) { - super(scope, id); - - const RESOURCE_TYPE = 'Custom::SsmGetParameterValue'; - - const provider = cdk.CustomResourceProvider.getOrCreateProvider(this, RESOURCE_TYPE, { - codeDirectory: path.join(__dirname, 'get-param-value/dist'), - runtime: cdk.CustomResourceProviderRuntime.NODEJS_18_X, - policyStatements: [ - { - Sid: 'SsmGetParameterActions', - Effect: cdk.aws_iam.Effect.ALLOW, - Action: ['ssm:GetParameters', 'ssm:GetParameter', 'ssm:DescribeParameters'], - Resource: ['*'], - }, - { - Sid: 'StsAssumeRoleActions', - Effect: cdk.aws_iam.Effect.ALLOW, - Action: ['sts:AssumeRole'], - Resource: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.acceleratorPrefix}*`], - }, - ], - }); - - const roleArn = props.roleName - ? `arn:${cdk.Stack.of(this).partition}:iam::${props.accountId}:role/${props.roleName}` - : ''; - - const resource = new cdk.CustomResource(this, 'Resource', { - resourceType: RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - parameterRegion: props.parameterRegion, - invokingRegion: cdk.Stack.of(this).region, - parameterAccountID: props.accountId, - parameterName: props.name, - assumeRoleArn: roleArn, - invokingAccountID: cdk.Stack.of(this).account, - uuid: props.resolvedValue ?? uuidv4(), - }, - }); - - /** - * Singleton pattern to define the log group for the singleton function - * in the stack - */ - const stack = cdk.Stack.of(scope); - const logGroup = - (stack.node.tryFindChild(`${provider.node.id}LogGroup`) as cdk.aws_logs.LogGroup) ?? - new cdk.aws_logs.LogGroup(stack, `${provider.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${(provider.node.findChild('Handler') as cdk.aws_lambda.CfnFunction).ref}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKey, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - resource.node.addDependency(logGroup); - - this.value = resource.ref; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/common-functions.ts b/source/packages/@aws-accelerator/constructs/lib/common-functions.ts deleted file mode 100644 index 723ebf4..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/common-functions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; - -// Copies Resource Policy files to the Lambda directory for packaging -export function copyPoliciesToDeploymentPackage( - filePaths: { name: string; path: string; tempPath: string }[], - deploymentPackagePath: string, - accountId: string, -) { - // Make policy folder - fs.mkdirSync(path.join(deploymentPackagePath, 'policies', accountId), { recursive: true }); - - for (const policyFilePath of filePaths) { - //copy from generated temp path to original policy path - fs.copyFileSync( - path.join(policyFilePath.tempPath), - path.join(deploymentPackagePath, 'policies', accountId, `${policyFilePath.name.toUpperCase()}.json`), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/attach-resource-based-policy.yaml b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/attach-resource-based-policy.yaml deleted file mode 100644 index 0904511..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/attach-resource-based-policy.yaml +++ /dev/null @@ -1,22 +0,0 @@ -description: Resource-based Policy -schemaVersion: '0.3' -assumeRole: '{{ AutomationAssumeRole }}' -parameters: - ResourceId: - type: String - FunctionName: - type: String - AutomationAssumeRole: - type: String - ConfigRuleName: - type: String -mainSteps: - - name: invokeLambdaFunction - action: 'aws:invokeLambdaFunction' - inputs: - InvocationType: RequestResponse - FunctionName: "{{ FunctionName }}" - InputPayload: - ResourceId: "{{ ResourceId }}" - ConfigRuleName: "{{ ConfigRuleName }}" - \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/detect-resource-policy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/detect-resource-policy.ts deleted file mode 100644 index d334f91..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/detect-resource-policy.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -import { copyPoliciesToDeploymentPackage } from '../common-functions'; - -/** - * Detect Resource Policy - * This construct creates a Lambda function which is triggered by AWS Config Rule and - * detect if a resource policy is compliant to the resource policy template by comparing - * statements in resource policy. - */ -export interface DetectResourcePolicyProps { - /** - * Prefix for accelerator resources - */ - readonly acceleratorPrefix: string; - /** - * Configuration directory path - */ - readonly configDirPath: string; - /** - * Accelerator home region - */ - readonly homeRegion: string; - /** - * Lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKeyCloudWatch?: cdk.aws_kms.IKey; - /** - * Lambda environment variable encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKeyLambda?: cdk.aws_kms.IKey; - /** - * Lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * Resource base policy File Paths - */ - readonly rbpFilePaths: { name: string; path: string; tempPath: string }[]; - /** - * Input parameters as lambda environment variable - */ - readonly inputParameters?: { [key: string]: string }; -} - -export class DetectResourcePolicy extends Construct { - lambdaFunction: cdk.aws_lambda.Function; - - constructor(scope: Construct, id: string, props: DetectResourcePolicyProps) { - super(scope, id); - - const deploymentPackagePath = path.join(__dirname, 'lambda-handler/dist'); - copyPoliciesToDeploymentPackage(props.rbpFilePaths, deploymentPackagePath, cdk.Stack.of(this).account); - - const LAMBDA_TIMEOUT_IN_MINUTES = 1; - - this.lambdaFunction = new cdk.aws_lambda.Function(this, 'DetectResourcePolicyFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'detect-resource-policy.handler', - description: 'Lambda function to detect non-compliant resource policy', - timeout: cdk.Duration.minutes(LAMBDA_TIMEOUT_IN_MINUTES), - environment: { - ...props.inputParameters, - ACCELERATOR_PREFIX: props.acceleratorPrefix, - AWS_PARTITION: cdk.Aws.PARTITION, - HOME_REGION: props.homeRegion, - }, - environmentEncryption: props.kmsKeyLambda, - }); - - const stack = cdk.Stack.of(this); - this.lambdaFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'secretsmanager:GetResourcePolicy', - 'lex:DescribeResourcePolicy', - 'apigateway:GET', - 'lambda:GetPolicy', - 'backup:GetBackupVaultAccessPolicy', - 'codeartifact:GetRepositoryPermissionsPolicy', - 'events:DescribeEventBus', - 'acm-pca:GetPolicy', - ], - resources: [ - `arn:${stack.partition}:secretsmanager:${stack.region}:${stack.account}:*`, // "arn:aws:s3:::*" - `arn:${stack.partition}:lex:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:apigateway:${stack.region}::*`, // Policy doesn't allow account ID in apigateway ARN - `arn:${stack.partition}:lambda:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:backup:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:codeartifact:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:events:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:acm-pca:${stack.region}:${stack.account}:*`, - ], - }), - ); - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressions(this.lambdaFunction.role!, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressions( - this.lambdaFunction.role!, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - true, - ); - - new cdk.aws_logs.LogGroup(this, `${this.lambdaFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.lambdaFunction.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKeyCloudWatch, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/package.json b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/package.json deleted file mode 100644 index cdb3b17..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "@aws-accelerator/constructs-aws-data-perimeter-lambda-handler", - "version": "0.0.0", - "private": true, - "description": "Custom resource Lambda", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "scripts": { - "build": "esbuild --minify --bundle --outfile=./dist/detect-resource-policy.js --platform=node --target=node18 ./src/detect-resource-policy/index.ts && esbuild --minify --bundle --outfile=./dist/remediate-resource-policy.js --platform=node --target=node18 ./src/remediate-resource-policy/index.ts", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "devDependencies": { - "@aws-sdk/client-acm-pca": "3.410.0", - "@aws-sdk/client-api-gateway": "3.410.0", - "@aws-sdk/client-backup": "3.410.0", - "@aws-sdk/client-codeartifact": "3.410.0", - "@aws-sdk/client-config-service": "^3.410.0", - "@aws-sdk/client-ecr": "3.410.0", - "@aws-sdk/client-efs": "3.410.0", - "@aws-sdk/client-eventbridge": "3.410.0", - "@aws-sdk/client-kms": "3.410.0", - "@aws-sdk/client-lambda": "3.410.0", - "@aws-sdk/client-lex-models-v2": "3.410.0", - "@aws-sdk/client-opensearch": "3.410.0", - "@aws-sdk/client-s3": "3.410.0", - "@aws-sdk/client-secrets-manager": "3.410.0", - "@aws-sdk/client-sns": "3.410.0", - "@aws-sdk/client-sqs": "3.410.0", - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "esbuild": "0.17.10", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/aws-resource-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/aws-resource-policy-strategy.ts deleted file mode 100644 index ef552bb..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/aws-resource-policy-strategy.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ConfigurationItem, PolicyDocument } from './common-resources'; - -export interface AwsResourcePolicyStrategy { - /** - * Evaluate if the resource policy of a resource is compliant - * - * @param configurationItem the configuration of the resource provided by AWS Config - * @param expectedPolicy the expected policy of the resource. The parameter will be undefined if it's a Lambda function or PCA - */ - evaluateResourcePolicyCompliance( - configurationItem: ConfigurationItem, - expectedPolicy?: PolicyDocument, - ): Promise<{ - complianceType: string; - annotation?: string; - }>; - - /** - * Update the resource policy of a resource and make it compliant - * @param configurationItem the configuration of the resource provided by AWS Config and SSM Remediation - * @param policy the expected policy of the resource. The parameter will be undefined if it's a Lambda function or PCA - */ - updateResourceBasedPolicy( - configurationItem: { resourceName: string; resourceId: string; resourceType: string }, - policy?: PolicyDocument, - ): Promise; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/common-resources.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/common-resources.ts deleted file mode 100644 index 4c70c92..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/common-resources.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { PolicyStatementType } from '@aws-accelerator/utils/lib/common-resources'; - -/** - * Supported Resource Type for AWS Config Rule - */ -export enum ResourceType { - S3_BUCKET = 'AWS::S3::Bucket', - KMS_KEY = 'AWS::KMS::Key', - IAM_ROLE = 'AWS::IAM::Role', - SECRETS_MANAGER_SECRET = 'AWS::SecretsManager::Secret', - ECR_REPOSITORY = 'AWS::ECR::Repository', - OPENSEARCH_DOMAIN = 'AWS::OpenSearch::Domain', - SNS_TOPIC = 'AWS::SNS::Topic', - SQS_QUEUE = 'AWS::SQS::Queue', - APIGATEWAY_REST_API = 'AWS::ApiGateway::RestApi', - LEX_BOT = 'AWS::Lex::Bot', - EFS_FILE_SYSTEM = 'AWS::EFS::FileSystem', - EVENTBRIDGE_EVENTBUS = 'AWS::Events::EventBus', - BACKUP_VAULT = 'AWS::Backup::BackupVault', - CODEARTIFACT_REPOSITORY = 'AWS::CodeArtifact::Repository', - CERTIFICATE_AUTHORITY = 'AWS::ACMPCA::CertificateAuthority', - LAMBDA_FUNCTION = 'AWS::Lambda::Function', -} - -/** - * Policy Document - */ -export type PolicyDocument = { - Version: string; - Id?: string; - Statement: PolicyStatementType[]; -}; - -/** - * event input in lambda for Custom AWS Config Rule - */ -export type ConfigRuleEvent = { - invokingEvent: string; // This is a stringified JSON. - ruleParameters?: string; - resultToken: string; - executionRoleArn?: string; - configRuleArn: string; - configRuleName: string; - configRuleId: string; - accountId: string; -}; - -/** - * The schema of invokingEvent in ConfigRuleEvent - */ -export type InvokingEvent = { - configurationItem?: ConfigurationItem; - messageType: string; -}; - -export type ConfigurationItem = { - relatedEvents: string[]; - configurationStateId: number; - version: string; - configurationItemCaptureTime: string; - configurationItemStatus: string; - configurationStateMd5Hash: string; - ARN: string; - resourceType: string; - resourceId: string; - resourceName: string; - AWSAccountId: string; - supplementaryConfiguration?: { BucketPolicy?: { policyText: string }; Policy?: string }; - configuration?: { - id?: string; - keyManager?: string; - path?: string; - assumeRolePolicyDocument?: string; - RepositoryPolicyText?: string; - AccessPolicies?: PolicyDocument; - Policy?: string; - DomainName?: string; - RepositoryName?: string; - FileSystemPolicy?: PolicyDocument; - }; -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/allowed-only-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/allowed-only-policy-strategy.ts deleted file mode 100644 index 81bb60a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/allowed-only-policy-strategy.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { FunctionUrlAuthType } from '@aws-sdk/client-lambda'; -import { PolicyStatementType } from '../common-resources'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -const AWS_ARN_REGEX = /^arn:aws[a-z-]*:[a-z0-9-]+:[a-z0-9-]*:[0-9]{12}:[a-zA-Z0-9-_:/]+/; -const AWS_ACCOUNT_NUMBER_REGEX = /^\d{12}$/; - -export const ALLOWED_SOURCE_ACCOUNTS: string | 'ALL' = process.env['SourceAccount'] || ''; - -/** - * The abstract strategy for resource policy which only support Allow statements. - * There is some difference compared to resource policy which support both Allow and Deny: - * 1. DENY statement is not allowed - * 2. The resource is considered as compliant if resource policy is empty - * 3. No statement should be added to Lambda RBP by SSM Remediation. Only policy update is allowed. - * 4. No resource policy template is accepted by customer (in LZA config). - */ -export abstract class AllowedOnlyPolicyStrategy implements AwsResourcePolicyStrategy { - protected readonly ORG_ID: string = process.env['ORG_ID']!; - protected readonly ACCOUNT_ID: string = process.env['ACCOUNT_ID']!; - - abstract evaluateResourcePolicyCompliance( - configurationItem: ConfigurationItem, - expectedPolicy?: PolicyDocument | undefined, - ): Promise<{ complianceType: string; annotation?: string | undefined }>; - - abstract updateResourceBasedPolicy( - configurationItem: { resourceName: string; resourceId: string; resourceType: string }, - policy?: PolicyDocument | undefined, - ): Promise; - - /** - * Get the statement detail from a policy statement. - * 1. accountsInStatement - the accounts number used in a statement - * 2. functionUrlAuthType - the functionUrlAuthType if present - * 3. principalOrgId - value of the condition key 'aws:PrincipalOrgId' if present - * 4. sourceAccount - value of condition key 'aws:SourceAccount' if present - * 5. sourceArn - value of condition key `aws:SourceArn' if present - * @param statement - * @returns - */ - protected getStatementDetail(statement: PolicyStatementType) { - const awsPrincipal: string = (statement.Principal?.['AWS'] as string) || '*'; - - const accountsInStatement: string[] = []; // Combination of #1, 2, 3 below can give us all the accounts ID occurred in the Lambda or PCA policy - const condition = statement.Condition || {}; - const stringEquals = (condition['StringEquals'] as unknown as { [key: string]: string }) || {}; - const arnLike = (condition['ArnLike'] as unknown as { [key: string]: string }) || {}; - const functionUrlAuthType = stringEquals['lambda:FunctionUrlAuthType']; - const principalOrgId = stringEquals['aws:PrincipalOrgID']; - - // 1. Add account IDs from condition key aws:SourceArn - const sourceArn: string[] = []; - if (stringEquals['AWS:SourceArn'] || stringEquals['aws:SourceArn']) { - // https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html - sourceArn.push(stringEquals['AWS:SourceArn'] || stringEquals['aws:SourceArn']); - } - if (arnLike['AWS:SourceArn'] || arnLike['aws:SourceArn']) { - sourceArn.push(arnLike['AWS:SourceArn'] || arnLike['aws:SourceArn']); - } - sourceArn.forEach(arn => accountsInStatement.push(this.getAccountIdFromArn(arn))); - - // 2. Add account IDs from condition key aws:SourceAccount - const sourceAccount: string[] = []; - if (stringEquals['AWS:SourceAccount'] || stringEquals['aws:SourceAccount']) { - sourceAccount.push(stringEquals['AWS:SourceAccount'] || stringEquals['aws:SourceAccount']); - } - accountsInStatement.push(...sourceAccount); - - // 3. Add account ID from AWS Principal - if (AWS_ARN_REGEX.test(awsPrincipal)) { - accountsInStatement.push(this.getAccountIdFromArn(awsPrincipal)); - } else if (AWS_ACCOUNT_NUMBER_REGEX.test(awsPrincipal)) { - accountsInStatement.push(awsPrincipal); - } - - return { - accountsInStatement, - functionUrlAuthType, - principalOrgId, - sourceAccount, - sourceArn, - }; - } - - protected checkStatementCompliance(statement: PolicyStatementType): boolean { - if (ALLOWED_SOURCE_ACCOUNTS === 'ALL') return true; - - const { accountsInStatement, functionUrlAuthType, principalOrgId } = this.getStatementDetail(statement); - const allowedAccounts: Set | 'ALL' = this.getAllowedAccountsInPolicy(); - - // Allow public access if 'ALL' is passed in SourceAccount - if (allowedAccounts === 'ALL') return true; - - if (principalOrgId && principalOrgId === this.ORG_ID) return true; - if (principalOrgId && principalOrgId !== this.ORG_ID) return false; - - if (functionUrlAuthType === FunctionUrlAuthType.NONE) return false; - if (accountsInStatement.length > 0) { - if (!accountsInStatement.every(account => allowedAccounts.has(account))) { - return false; - } - } - - return true; - } - - protected getAllowedAccountsInPolicy(): Set | 'ALL' { - if (ALLOWED_SOURCE_ACCOUNTS === 'ALL') { - return 'ALL'; - } - - const accountIds = ALLOWED_SOURCE_ACCOUNTS.split(',').map(acc => acc.trim()); - const allowedAccounts = new Set(accountIds); - allowedAccounts.add(this.ACCOUNT_ID); - - return allowedAccounts; - } - - private getAccountIdFromArn(arn: string) { - return arn.split(':')[4]; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/apigateway-repository-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/apigateway-repository-policy-strategy.ts deleted file mode 100644 index 17dbd0f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/apigateway-repository-policy-strategy.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { APIGatewayClient, GetRestApiCommand, UpdateRestApiCommand } from '@aws-sdk/client-api-gateway'; - -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; - -export class ApiGatewayPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new APIGatewayClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicy = await this.getResourcePolicyById(configurationItem.configuration?.id); - if (!currPolicy) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(currPolicy, expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string }, - policy: PolicyDocument, - ) { - const parts = configurationItem.resourceId.split('/'); - const apigwIdentifier = parts[parts.length - 1]; - let currPolicyDocument = await this.getResourcePolicyById(apigwIdentifier); - - if (!currPolicyDocument) { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new UpdateRestApiCommand({ - restApiId: apigwIdentifier, - patchOperations: [ - { - op: 'replace', - path: '/policy', - value: JSON.stringify(currPolicyDocument), - }, - ], - }), - ); - } - - private async getResourcePolicyById(id: string | undefined): Promise { - const response = await this.client.send(new GetRestApiCommand({ restApiId: id })); - if (response.policy) { - const unescapedJson = JSON.parse('"' + response.policy + '"'); - return JSON.parse(unescapedJson); - } - - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/backup-valut-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/backup-valut-policy-strategy.ts deleted file mode 100644 index 03543dd..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/backup-valut-policy-strategy.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { - BackupClient, - GetBackupVaultAccessPolicyCommand, - PutBackupVaultAccessPolicyCommand, -} from '@aws-sdk/client-backup'; - -export class BackupVaultPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new BackupClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const backupVaultName = configurationItem.resourceName; - - const currPolicyStr = await this.getResourceByName(backupVaultName); - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceName: string; resourceId: string; resourceType: string }, - policy: PolicyDocument, - ) { - const backupVaultName = configurationItem.resourceName; - const currPolicyStr = await this.getResourceByName(backupVaultName); - - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new PutBackupVaultAccessPolicyCommand({ - BackupVaultName: backupVaultName, - Policy: JSON.stringify(currPolicyDocument), - }), - ); - } - - private async getResourceByName(backupVaultName: string): Promise { - try { - const response = await this.client.send( - new GetBackupVaultAccessPolicyCommand({ BackupVaultName: backupVaultName }), - ); - return response.Policy; - } catch (e: unknown) { - if ((e as { name: string }).name === 'ResourceNotFoundException') { - return ''; - } - throw e; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/code-artifact-repository-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/code-artifact-repository-policy-strategy.ts deleted file mode 100644 index a516cea..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/code-artifact-repository-policy-strategy.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { - CodeartifactClient, - PutRepositoryPermissionsPolicyCommand, - GetRepositoryPermissionsPolicyCommand, -} from '@aws-sdk/client-codeartifact'; - -export class CodeArtifactRepositoryPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new CodeartifactClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const domain = configurationItem.configuration?.DomainName; - const repository = configurationItem.configuration?.RepositoryName; - if (!domain || !repository) { - throw new Error('No domain or repository name available in configurationItem'); - } - - const currPolicyStr = await this.getResourceByDomainNameAndRepoName(domain, repository); - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - private async getResourceByDomainNameAndRepoName(domain: string, repository: string): Promise { - try { - const response = await this.client.send(new GetRepositoryPermissionsPolicyCommand({ domain, repository })); - return response.policy?.document; - } catch (e: unknown) { - if ((e as { name: string }).name === 'ResourceNotFoundException') { - return undefined; - } - throw e; - } - } - - async updateResourceBasedPolicy( - configurationItem: { resourceName: string; resourceId: string; resourceType: string; configuration: string }, - policy: PolicyDocument, - ) { - const configuration = JSON.parse(configurationItem.configuration); - const domain = configuration.DomainName; - const repository = configuration.RepositoryName; - - const currPolicyStr = await this.getResourceByDomainNameAndRepoName(domain, repository); - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new PutRepositoryPermissionsPolicyCommand({ - domain, - repository, - policyDocument: JSON.stringify(currPolicyDocument), - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/ecr-repository-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/ecr-repository-policy-strategy.ts deleted file mode 100644 index 40cb05c..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/ecr-repository-policy-strategy.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { ECRClient, GetRepositoryPolicyCommand, SetRepositoryPolicyCommand } from '@aws-sdk/client-ecr'; - -export class EcrRepositoryPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new ECRClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicyStr = configurationItem.configuration?.RepositoryPolicyText; - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string; resourceName: string }, - policy: PolicyDocument, - ) { - let response; - try { - response = await this.client.send( - new GetRepositoryPolicyCommand({ - repositoryName: configurationItem.resourceName, - }), - ); - } catch (err: unknown) { - if ((err as { name: string }).name !== 'RepositoryPolicyNotFoundException') { - throw err; - } - } - - let currPolicyDocument; - if (response?.policyText) { - currPolicyDocument = JSON.parse(response.policyText); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex((s: { Sid: string }) => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new SetRepositoryPolicyCommand({ - repositoryName: configurationItem.resourceName, - policyText: JSON.stringify(currPolicyDocument), - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/efs-file-system-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/efs-file-system-policy-strategy.ts deleted file mode 100644 index 6da0072..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/efs-file-system-policy-strategy.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { EFSClient, DescribeFileSystemPolicyCommand, PutFileSystemPolicyCommand } from '@aws-sdk/client-efs'; - -export class EfsFileSystemPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new EFSClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicy = configurationItem.configuration?.FileSystemPolicy; - if (!currPolicy) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(currPolicy, expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string }, - policy: PolicyDocument, - ) { - const fileSystemId = configurationItem.resourceId; - const currPolicyStr = await this.getResourceById(fileSystemId); - - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new PutFileSystemPolicyCommand({ - FileSystemId: fileSystemId, - Policy: JSON.stringify(currPolicyDocument), - }), - ); - } - - private async getResourceById(fileSystemId: string): Promise { - try { - const response = await this.client.send(new DescribeFileSystemPolicyCommand({ FileSystemId: fileSystemId })); - return response.Policy; - } catch (e: unknown) { - if ((e as { name: string }).name === 'PolicyNotFound') { - return undefined; - } - throw e; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/eventbridge-eventbus-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/eventbridge-eventbus-policy-strategy.ts deleted file mode 100644 index 33ef0ac..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/eventbridge-eventbus-policy-strategy.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { EventBridgeClient, DescribeEventBusCommand, PutPermissionCommand } from '@aws-sdk/client-eventbridge'; - -export class EventBridgeEventBusPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new EventBridgeClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const eventBusName = configurationItem.resourceName; - const response = await this.client.send(new DescribeEventBusCommand({ Name: eventBusName })); - - const currPolicyStr = response.Policy; - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string; resourceName: string }, - policy: PolicyDocument, - ) { - const eventBusName = configurationItem.resourceName; - const response = await this.client.send(new DescribeEventBusCommand({ Name: eventBusName })); - - const currPolicyStr = response.Policy; - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new PutPermissionCommand({ - EventBusName: eventBusName, - Policy: JSON.stringify(currPolicyDocument), - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/iam-role-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/iam-role-policy-strategy.ts deleted file mode 100644 index c19525f..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/iam-role-policy-strategy.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { IAMClient, GetRoleCommand, UpdateAssumeRolePolicyCommand } from '@aws-sdk/client-iam'; - -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; - -export class IamRolePolicyStrategy implements AwsResourcePolicyStrategy { - private readonly iamClient = new IAMClient(); - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string; resourceName: string }, - policy: PolicyDocument, - ) { - const roleName = configurationItem.resourceName; - const role = await this.iamClient.send(new GetRoleCommand({ RoleName: roleName })); - - let currAssumeRolePolicyDocument; - if (role.Role?.AssumeRolePolicyDocument) { - currAssumeRolePolicyDocument = JSON.parse(decodeURIComponent(role.Role.AssumeRolePolicyDocument)); - } else { - currAssumeRolePolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'DataPerimeterRolePolicy', - }; - } - - const currStatements = currAssumeRolePolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex((s: { Sid: string }) => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.iamClient.send( - new UpdateAssumeRolePolicyCommand({ - RoleName: roleName, - PolicyDocument: JSON.stringify(currAssumeRolePolicyDocument), - }), - ); - } - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - if (configurationItem.configuration?.path?.startsWith('/aws-service-role/')) { - return { - complianceType: 'NOT_APPLICABLE', - annotation: 'resource policy check is not applicable to AWS managed role', - }; - } - if (!configurationItem.configuration?.assumeRolePolicyDocument) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Trusted entity is empty', - }; - } - - // The trust policy (which includes the trust entity) is stored in the 'assumeRolePolicyDocument' field - const currResourcePolicy = JSON.parse(decodeURIComponent(configurationItem.configuration.assumeRolePolicyDocument)); - - return compareResourcePolicies(currResourcePolicy, expectedPolicy); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/kms-key-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/kms-key-policy-strategy.ts deleted file mode 100644 index e7a69e6..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/kms-key-policy-strategy.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { KMSClient, GetKeyPolicyCommand, PutKeyPolicyCommand } from '@aws-sdk/client-kms'; - -export class KmsKeyPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly kmsClient = new KMSClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - if (configurationItem.configuration?.keyManager === 'AWS') { - return { - complianceType: 'NOT_APPLICABLE', - annotation: 'resource policy check is not applicable to AWS managed key', - }; - } - - const currPolicyStr = configurationItem.supplementaryConfiguration?.Policy; - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Key policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string }, - policy: PolicyDocument, - ) { - const keyId = configurationItem.resourceId; - - const getPolicyResponse = await this.kmsClient.send( - new GetKeyPolicyCommand({ - KeyId: keyId, - PolicyName: 'default', - }), - ); - - let currPolicyDocument; - if (getPolicyResponse.Policy) { - currPolicyDocument = JSON.parse(getPolicyResponse.Policy); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex((s: { Sid: string }) => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.kmsClient.send( - new PutKeyPolicyCommand({ - KeyId: keyId, - PolicyName: 'default', - Policy: JSON.stringify(currPolicyDocument), - BypassPolicyLockoutSafetyCheck: false, - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lambda-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lambda-policy-strategy.ts deleted file mode 100644 index 11acf85..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lambda-policy-strategy.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - LambdaClient, - GetPolicyCommand, - RemovePermissionCommand, - AddPermissionCommand, - FunctionUrlAuthType, - AddPermissionCommandInput, -} from '@aws-sdk/client-lambda'; - -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { PolicyStatementType } from '../common-resources'; -import { AllowedOnlyPolicyStrategy } from './allowed-only-policy-strategy'; - -export class LambdaPolicyStrategy extends AllowedOnlyPolicyStrategy { - private readonly client = new LambdaClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem) { - const functionName = configurationItem.resourceName; - const currPolicyStr = await this.getLambdaPolicyByName(functionName); - - if (!currPolicyStr) { - // The resource policy is considered as compliant if no policy found. - // It's because lambda doesn't allow Deny statement and remediation will never add new permission. Only update of existing statement is allowed - return { - complianceType: 'COMPLIANT', - }; - } - - const currPolicyDocument: PolicyDocument = JSON.parse(currPolicyStr); - const noncompliantStms: string[] = []; - for (const stm of currPolicyDocument.Statement) { - if (!this.checkStatementCompliance(stm)) { - noncompliantStms.push(stm.Sid!); - } - } - - if (noncompliantStms.length > 0) { - return { - complianceType: 'NON_COMPLIANT', - annotation: `${noncompliantStms.join(',')} is not compliant`, - }; - } - - return { - complianceType: 'COMPLIANT', - }; - } - - async updateResourceBasedPolicy(configurationItem: { - resourceName: string; - resourceId: string; - resourceType: string; - }) { - const functionName = configurationItem.resourceName; - - const currPolicyStr = await this.getLambdaPolicyByName(functionName); - if (!currPolicyStr) return; // Never add a statement on an empty policy - - const currPolicyDocument: PolicyDocument = JSON.parse(currPolicyStr); - - for (const stm of currPolicyDocument.Statement) { - if (!this.checkStatementCompliance(stm)) { - // There is no update API for Lambda resource policy, hence doing delete + add for update. - await this.removePermission(functionName, stm.Sid!); - - const commandInput = this.getNewStatementInput(functionName, stm); - await this.client.send(new AddPermissionCommand(commandInput)); - } - } - } - - private async getLambdaPolicyByName(functionName: string) { - try { - const response = await this.client.send(new GetPolicyCommand({ FunctionName: functionName })); - return response.Policy; - } catch (e: unknown) { - if ((e as { name: string }).name === 'ResourceNotFoundException') { - return undefined; - } - throw e; - } - } - - private async removePermission(functionName: string, stmId: string) { - return this.client.send(new RemovePermissionCommand({ FunctionName: functionName, StatementId: stmId })); - } - - private getNewStatementInput(functionName: string, statement: PolicyStatementType): AddPermissionCommandInput { - const { accountsInStatement, functionUrlAuthType, principalOrgId, sourceAccount, sourceArn } = - this.getStatementDetail(statement); - const allowedAccounts = this.getAllowedAccountsInPolicy(); - - let updatedPrincipalOrgId: string | undefined = principalOrgId; - if (allowedAccounts instanceof Set && allowedAccounts.size === 0) { - // Always restrict access from Organization by updating `aws:PrincipalOrgId` if no external account is specified - updatedPrincipalOrgId = this.ORG_ID; - } else if (allowedAccounts instanceof Set && accountsInStatement.find(account => !allowedAccounts.has(account))) { - // Restrict access from Organization by updating `aws:PrincipalOrgId` if an account in statement doesn't occur to the allowed list - updatedPrincipalOrgId = this.ORG_ID; - } - - return { - FunctionName: functionName, - StatementId: statement.Sid, - Action: statement.Action as string, - SourceAccount: sourceAccount[0], // Statement in Lambda policy can only be a single string for account and arn - SourceArn: sourceArn[0], - Principal: (statement.Principal?.['AWS'] as string) || (statement.Principal?.['Service'] as string) || undefined, - PrincipalOrgID: updatedPrincipalOrgId, - FunctionUrlAuthType: functionUrlAuthType as FunctionUrlAuthType, - }; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lex-bot-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lex-bot-policy-strategy.ts deleted file mode 100644 index e7277ea..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/lex-bot-policy-strategy.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { - LexModelsV2Client, - DescribeResourcePolicyCommand, - UpdateResourcePolicyCommand, - CreateResourcePolicyCommand, -} from '@aws-sdk/client-lex-models-v2'; - -export class LexBotPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new LexModelsV2Client(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicyStr = await this.getResourcePolicyByArn(configurationItem.ARN); - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceName: string; resourceId: string; resourceType: string; arn: string }, - policy: PolicyDocument, - ) { - const resourceArn = configurationItem.arn; - const currPolicyStr = await this.getResourcePolicyByArn(resourceArn); - let isNewPolicy = false; - - let currPolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - isNewPolicy = true; - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex((s: { Sid: string }) => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - if (isNewPolicy) { - await this.client.send( - new CreateResourcePolicyCommand({ - resourceArn, - policy: JSON.stringify(currPolicyDocument), - }), - ); - } else { - await this.client.send( - new UpdateResourcePolicyCommand({ - resourceArn, - policy: JSON.stringify(currPolicyDocument), - }), - ); - } - } - - private async getResourcePolicyByArn(resourceArn: string): Promise { - try { - const response = await this.client.send(new DescribeResourcePolicyCommand({ resourceArn })); - return response.policy; - } catch (e: unknown) { - if ((e as { name: string }).name === 'ResourceNotFoundException') { - return ''; - } - throw e; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/opensearch-domain-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/opensearch-domain-policy-strategy.ts deleted file mode 100644 index cb7bd20..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/opensearch-domain-policy-strategy.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { OpenSearchClient, UpdateDomainConfigCommand, DescribeDomainConfigCommand } from '@aws-sdk/client-opensearch'; - -export class OpenSearchDomainPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new OpenSearchClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicy = configurationItem.configuration?.AccessPolicies; - if (!currPolicy) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(currPolicy, expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string; resourceName: string }, - policy: PolicyDocument, - ) { - const domainName = configurationItem.resourceName; - const response = await this.client.send(new DescribeDomainConfigCommand({ DomainName: domainName })); - - const currPolicyStr = response.DomainConfig?.AccessPolicies?.Options; - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new UpdateDomainConfigCommand({ - DomainName: domainName, - AccessPolicies: JSON.stringify(currPolicyDocument), - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/pca-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/pca-policy-strategy.ts deleted file mode 100644 index 6cb7af7..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/pca-policy-strategy.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ConfigurationItem, PolicyDocument, PolicyStatementType } from '../common-resources'; - -import { ACMPCAClient, PutPolicyCommand, GetPolicyCommand } from '@aws-sdk/client-acm-pca'; -import { AllowedOnlyPolicyStrategy } from './allowed-only-policy-strategy'; - -export class PcaPolicyStrategy extends AllowedOnlyPolicyStrategy { - private readonly client = new ACMPCAClient(); - - async evaluateResourcePolicyCompliance( - configurationItem: ConfigurationItem, - ): Promise<{ complianceType: string; annotation?: string | undefined }> { - const currPolicyText = await this.getResourcePolicy(configurationItem.ARN); - - if (!currPolicyText) { - // FOR ACM_PCA, we don't add permission because only ALLOW statement is allowed. We don't want to open permission by adding any ALLOW statement - return { - complianceType: 'COMPLIANT', - }; - } - - const currPolicy: PolicyDocument = JSON.parse(currPolicyText); - const noncompliantStms: string[] = []; - for (const stm of currPolicy.Statement) { - if (!this.checkStatementCompliance(stm)) { - noncompliantStms.push(stm.Sid!); - } - } - - if (noncompliantStms.length > 0) { - return { - complianceType: 'NON_COMPLIANT', - annotation: `${noncompliantStms.join(',')} is not compliant`, - }; - } - - return { - complianceType: 'COMPLIANT', - }; - } - - /** - * Remediate the non-compliant resource and make the resource policy compliant - * @param configurationItem - * @returns - */ - async updateResourceBasedPolicy(configurationItem: { - resourceName: string; - resourceId: string; - resourceType: string; - arn: string; - }) { - const arn = configurationItem.arn; - - let policyDocument: PolicyDocument; - const currPolicyText = await this.getResourcePolicy(arn); - if (!currPolicyText) return; // Do nothing if current resource policy is empty - else { - policyDocument = JSON.parse(currPolicyText); - } - - const statements = []; - for (const stm of policyDocument.Statement) { - if (this.checkStatementCompliance(stm)) { - statements.push(stm); - continue; - } - - let condition = stm.Condition; - if (!condition) condition = {}; - if (!condition['StringEquals']) condition['StringEquals'] = {}; - - condition['StringEquals']['aws:PrincipalOrgID'] = this.ORG_ID; - statements.push({ ...stm, Condition: condition } as PolicyStatementType); - } - - await this.client.send( - new PutPolicyCommand({ ResourceArn: arn, Policy: JSON.stringify({ ...policyDocument, Statement: statements }) }), - ); - } - - private async getResourcePolicy(arn: string) { - try { - const data = await this.client.send(new GetPolicyCommand({ ResourceArn: arn })); - return data.Policy; - } catch (e: unknown) { - if ((e as { name: string }).name === 'ResourceNotFoundException') { - return undefined; - } - throw e; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/s3-bucket-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/s3-bucket-policy-strategy.ts deleted file mode 100644 index 7b48319..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/s3-bucket-policy-strategy.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; - -import { S3Client, GetBucketPolicyCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'; - -export class S3BucketPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly s3Client = new S3Client(); - - evaluateResourcePolicyCompliance( - configurationItem: ConfigurationItem, - expectedPolicy: PolicyDocument, - ): Promise<{ complianceType: string; annotation?: string | undefined }> { - const currPolicyText = configurationItem.supplementaryConfiguration?.BucketPolicy?.policyText; - - return compareResourcePolicies(currPolicyText ? JSON.parse(currPolicyText) : undefined, expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string; resourceName: string }, - policy: PolicyDocument, - ) { - const bucketName = configurationItem.resourceName; - let bucketPolicy: PolicyDocument = { - Version: '2012-10-17', - Statement: [], - }; - try { - const data = await this.s3Client.send(new GetBucketPolicyCommand({ Bucket: bucketName })); - bucketPolicy = JSON.parse(data.Policy || ''); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - if (err.name !== 'NoSuchBucketPolicy') { - throw err; - } - } - - const currStatements = bucketPolicy.Statement; - - // Update or append the s3 standard policy customized by user - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - const newStatement = { - ...statement, - Resource: [`arn:aws:s3:::${bucketName}`, `arn:aws:s3:::${bucketName}/*`], - }; - - if (idx >= 0) { - currStatements[idx] = newStatement; - } else { - currStatements.push(newStatement); - } - } - - const params = { - Bucket: bucketName, - Policy: JSON.stringify(bucketPolicy), - }; - - await this.s3Client.send(new PutBucketPolicyCommand(params)); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/secrets-manager-policy-streategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/secrets-manager-policy-streategy.ts deleted file mode 100644 index c461b8a..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/secrets-manager-policy-streategy.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { - SecretsManagerClient, - PutResourcePolicyCommand, - GetResourcePolicyCommand, -} from '@aws-sdk/client-secrets-manager'; - -export class SecretsManagerPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly smClient = new SecretsManagerClient(); - - async evaluateResourcePolicyCompliance( - configurationItem: ConfigurationItem, - expectedPolicy: PolicyDocument, - ): Promise<{ complianceType: string; annotation?: string | undefined }> { - const response = await this.smClient.send(new GetResourcePolicyCommand({ SecretId: configurationItem.resourceId })); - const currPolicyStr = response.ResourcePolicy; - - if (!currPolicyStr) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicyStr), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string }, - policy: PolicyDocument, - ) { - const resourceId = configurationItem.resourceId; - - const getPolicyResponse = await this.smClient.send( - new GetResourcePolicyCommand({ - SecretId: resourceId, - }), - ); - - let currPolicyDocument; - if (getPolicyResponse.ResourcePolicy) { - currPolicyDocument = JSON.parse(getPolicyResponse.ResourcePolicy); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex((s: { Sid: string }) => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.smClient.send( - new PutResourcePolicyCommand({ - SecretId: resourceId, - ResourcePolicy: JSON.stringify(currPolicyDocument), - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sns-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sns-policy-strategy.ts deleted file mode 100644 index eab8b33..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sns-policy-strategy.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { SNSClient, GetTopicAttributesCommand, SetTopicAttributesCommand } from '@aws-sdk/client-sns'; - -export class SnsPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new SNSClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicy = configurationItem.configuration?.Policy; - if (!currPolicy) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicy), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string }, - policy: PolicyDocument, - ) { - const topicArn = configurationItem.resourceId; - const response = await this.client.send(new GetTopicAttributesCommand({ TopicArn: topicArn })); - - const currPolicyStr = (response.Attributes && response.Attributes['Policy']) || ''; - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - await this.client.send( - new SetTopicAttributesCommand({ - TopicArn: topicArn, - AttributeName: 'Policy', - AttributeValue: JSON.stringify(currPolicyDocument), - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sqs-policy-strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sqs-policy-strategy.ts deleted file mode 100644 index ceccb13..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategies/sqs-policy-strategy.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { compareResourcePolicies } from '../utils'; -import { ConfigurationItem, PolicyDocument } from '../common-resources'; - -import { AwsResourcePolicyStrategy } from '../aws-resource-policy-strategy'; -import { - SQSClient, - GetQueueAttributesCommand, - SetQueueAttributesCommand, - QueueAttributeName, -} from '@aws-sdk/client-sqs'; - -export class SqsPolicyStrategy implements AwsResourcePolicyStrategy { - private readonly client = new SQSClient(); - - async evaluateResourcePolicyCompliance(configurationItem: ConfigurationItem, expectedPolicy?: PolicyDocument) { - const currPolicy = configurationItem.configuration?.Policy; - if (!currPolicy) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource Policy is empty', - }; - } - - return compareResourcePolicies(JSON.parse(currPolicy), expectedPolicy); - } - - async updateResourceBasedPolicy( - configurationItem: { resourceId: string; resourceType: string; resourceName: string }, - policy: PolicyDocument, - ) { - const queueUrl = configurationItem.resourceName; - const response = await this.client.send( - new GetQueueAttributesCommand({ QueueUrl: queueUrl, AttributeNames: [QueueAttributeName.Policy] }), - ); - - const currPolicyStr = (response.Attributes && response.Attributes[QueueAttributeName.Policy]) || ''; - let currPolicyDocument: PolicyDocument; - if (currPolicyStr) { - currPolicyDocument = JSON.parse(currPolicyStr); - } else { - currPolicyDocument = { - Version: '2012-10-17', - Statement: [], - Id: 'ResourcePolicyForDataPerimeter', - }; - } - - const currStatements = currPolicyDocument.Statement; - for (const statement of policy?.Statement || []) { - const idx = currStatements.findIndex(s => s.Sid === statement.Sid); - if (idx >= 0) { - currStatements[idx] = statement; - } else { - currStatements.push(statement); - } - } - - const attributes: Record = {}; - attributes[QueueAttributeName.Policy] = JSON.stringify(currPolicyDocument); - await this.client.send( - new SetQueueAttributesCommand({ - QueueUrl: queueUrl, - Attributes: attributes, - }), - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategy.ts deleted file mode 100644 index f38e5bf..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/strategy.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AwsResourcePolicyStrategy as AwsResourcePolicyStrategy } from './aws-resource-policy-strategy'; -import { ApiGatewayPolicyStrategy } from './strategies/apigateway-repository-policy-strategy'; -import { BackupVaultPolicyStrategy } from './strategies/backup-valut-policy-strategy'; -import { CodeArtifactRepositoryPolicyStrategy } from './strategies/code-artifact-repository-policy-strategy'; -import { EcrRepositoryPolicyStrategy } from './strategies/ecr-repository-policy-strategy'; -import { EfsFileSystemPolicyStrategy } from './strategies/efs-file-system-policy-strategy'; -import { EventBridgeEventBusPolicyStrategy } from './strategies/eventbridge-eventbus-policy-strategy'; -import { IamRolePolicyStrategy } from './strategies/iam-role-policy-strategy'; -import { KmsKeyPolicyStrategy } from './strategies/kms-key-policy-strategy'; -import { LexBotPolicyStrategy } from './strategies/lex-bot-policy-strategy'; -import { OpenSearchDomainPolicyStrategy } from './strategies/opensearch-domain-policy-strategy'; -import { PcaPolicyStrategy } from './strategies/pca-policy-strategy'; -import { S3BucketPolicyStrategy } from './strategies/s3-bucket-policy-strategy'; -import { SecretsManagerPolicyStrategy } from './strategies/secrets-manager-policy-streategy'; -import { SnsPolicyStrategy } from './strategies/sns-policy-strategy'; -import { SqsPolicyStrategy } from './strategies/sqs-policy-strategy'; -import { ResourceType } from './common-resources'; -import { LambdaPolicyStrategy } from './strategies/lambda-policy-strategy'; - -let map: Map; - -export function getOrCreateStrategyMap(): Map { - if (map) return map; - - map = new Map(); - - map.set(ResourceType.S3_BUCKET, new S3BucketPolicyStrategy()); - map.set(ResourceType.IAM_ROLE, new IamRolePolicyStrategy()); - map.set(ResourceType.KMS_KEY, new KmsKeyPolicyStrategy()); - map.set(ResourceType.SECRETS_MANAGER_SECRET, new SecretsManagerPolicyStrategy()); - map.set(ResourceType.APIGATEWAY_REST_API, new ApiGatewayPolicyStrategy()); - map.set(ResourceType.ECR_REPOSITORY, new EcrRepositoryPolicyStrategy()); - map.set(ResourceType.LEX_BOT, new LexBotPolicyStrategy()); - map.set(ResourceType.OPENSEARCH_DOMAIN, new OpenSearchDomainPolicyStrategy()); - map.set(ResourceType.SNS_TOPIC, new SnsPolicyStrategy()); - map.set(ResourceType.SQS_QUEUE, new SqsPolicyStrategy()); - map.set(ResourceType.CODEARTIFACT_REPOSITORY, new CodeArtifactRepositoryPolicyStrategy()); - map.set(ResourceType.EFS_FILE_SYSTEM, new EfsFileSystemPolicyStrategy()); - map.set(ResourceType.EVENTBRIDGE_EVENTBUS, new EventBridgeEventBusPolicyStrategy()); - map.set(ResourceType.BACKUP_VAULT, new BackupVaultPolicyStrategy()); - map.set(ResourceType.CERTIFICATE_AUTHORITY, new PcaPolicyStrategy()); - map.set(ResourceType.LAMBDA_FUNCTION, new LambdaPolicyStrategy()); - - return map; -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/utils.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/utils.ts deleted file mode 100644 index 6772c83..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/common/utils.ts +++ /dev/null @@ -1,157 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { PolicyDocument, ResourceType } from './common-resources'; - -export const RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY = [ResourceType.CERTIFICATE_AUTHORITY, ResourceType.LAMBDA_FUNCTION]; -const RESOURCE_POLICY_FILE_DIR = 'policies'; - -/** - * Compare if two JSON objects are deeply equal - * @param {*} obj1 - * @param {*} obj2 - * @returns - */ -export const deepEqual = (obj1: unknown, obj2: unknown) => { - if (obj1 === obj2) return true; - - if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) { - return false; - } - - if (Array.isArray(obj1) && Array.isArray(obj2)) { - if (obj1.length !== obj2.length) return false; - - for (let i = 0; i < obj1.length; i++) { - if (!deepEqual(obj1[i], obj2[i])) return false; - } - return true; - } - - if (Array.isArray(obj1) !== Array.isArray(obj2)) return false; - - const keys1 = Object.keys(obj1); - const keys2 = Object.keys(obj2); - - if (keys1.length !== keys2.length) return false; - - for (const key of keys1) { - if (!keys2.includes(key)) return false; - if (!deepEqual((obj1 as Record)[key], (obj2 as Record)[key])) return false; - } - - return true; -}; - -/** - * Get resource policy templates with placeholders from lambda environment - * - * @returns Map of ResourceType -> string of PolicyDocument - */ -export const getResourcePolicies = (): Map => { - const accountId: string = process.env['ACCOUNT_ID']!; - const resourcePolicyFilesDir = path.join(__dirname, RESOURCE_POLICY_FILE_DIR, accountId); - const files = fs.readdirSync(resourcePolicyFilesDir); - - const map = new Map(); - for (const resourceTypeKey of Object.keys(ResourceType)) { - const resourceType = ResourceType[resourceTypeKey as keyof typeof ResourceType]; - if (RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY.includes(resourceType)) continue; - - const file = files.find(file => path.basename(file) === `${resourceTypeKey}.json`); - if (file) { - map.set(resourceType, fs.readFileSync(path.join(resourcePolicyFilesDir, file), 'utf-8')); - } else { - throw new Error(`Cannot find policy file for ${resourceTypeKey} in account ${accountId}`); - } - } - return map; -}; - -/** - * Replace the placeholder in resource policy template with actual value. - * - * @param {*} policyTemplateMap - * @param {*} params - * @returns Map of file name (string) -> JSON policy object (PolicyDocument) - */ -export const generatePolicyReplacements = ( - policyTemplateMap: Map, - params: { [key: string]: string | string[] }, -): Map => { - const map = new Map(); - for (const [policyKey, policyStr] of policyTemplateMap) { - let jsonStr = policyStr; - for (const paramKey in params || {}) { - const regex = new RegExp('\\$\\{' + paramKey + '\\}', 'g'); - const value = - typeof params[paramKey] === 'string' ? (params[paramKey] as string) : JSON.stringify(params[paramKey]); - - jsonStr = jsonStr.replace(regex, value); - } - - map.set(policyKey, JSON.parse(jsonStr)); - } - - return map; -}; - -export function stringToEnumValue>( - enumObj: T, - stringValue: string, -): T[keyof T] | undefined { - const enumKeys = Object.keys(enumObj) as (keyof T)[]; - for (const key of enumKeys) { - if (enumObj[key] === stringValue) { - return key as T[keyof T]; - } - } - return undefined; // Return undefined if no match is found -} - -/** - * Compare if each statement in {mandatoryPolicy} exists in current {resourcePolicy}. - * - * @param {*} currPolicy - * @param {*} expectedPolicy - * @returns - */ -export const compareResourcePolicies = async ( - currPolicy: PolicyDocument | undefined, - expectedPolicy: PolicyDocument | undefined, -): Promise<{ - complianceType: string; - annotation?: string; -}> => { - if (!currPolicy) - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Resource policy is empty', - }; - if (!expectedPolicy) { - return { - complianceType: 'NON_COMPLIANT', - annotation: 'Mandatory resource policy is empty', - }; - } - - const currStatements = currPolicy.Statement || []; - for (const policy of expectedPolicy.Statement || []) { - const target = currStatements.find(s => s.Sid === policy.Sid); - if (!target) - return { - complianceType: 'NON_COMPLIANT', - annotation: `Policy statement ${policy.Sid} is not found`, - }; - - if (!deepEqual(target, policy)) { - return { - complianceType: 'NON_COMPLIANT', - annotation: `Policy statement ${policy.Sid} is not identical to mandatory resource policy`, - }; - } - } - - return { - complianceType: 'COMPLIANT', - }; -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/detect-resource-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/detect-resource-policy/index.ts deleted file mode 100644 index f9360a1..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/detect-resource-policy/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { ComplianceType, ConfigServiceClient, PutEvaluationsCommand } from '@aws-sdk/client-config-service'; - -import { getResourcePolicies, generatePolicyReplacements, RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY } from '../common/utils'; -import { getOrCreateStrategyMap } from '../common/strategy'; -import { - ConfigRuleEvent, - ConfigurationItem, - PolicyDocument, - InvokingEvent, - ResourceType, -} from '../common/common-resources'; - -const configClient = new ConfigServiceClient(); -const resourcePolicyTemplates = getResourcePolicies(); -const resourcePolicyStrategyMap = getOrCreateStrategyMap(); - -/** - * detect-resource-policy - lambda handler - * - * @param event - * @returns - */ -export async function handler(event: ConfigRuleEvent) { - console.log(`Custom Rule for checking resource based policies`); - console.log(JSON.stringify(event, null, 2)); - const invokingEvent: InvokingEvent = JSON.parse(event.invokingEvent); - const configurationItem = invokingEvent.configurationItem!; - - const paramsReplacement = { - 'ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN': configurationItem.ARN, - }; - const resourcePolicies = generatePolicyReplacements(resourcePolicyTemplates, paramsReplacement); - - const invocationType = invokingEvent.messageType; - if (invocationType === 'ScheduledNotification') { - return; - } - - const evaluation = await evaluateCompliance(configurationItem, resourcePolicies); - - await configClient.send( - new PutEvaluationsCommand({ - ResultToken: event.resultToken, - Evaluations: [ - { - ComplianceResourceId: configurationItem.resourceId, - ComplianceResourceType: configurationItem.resourceType, - ComplianceType: evaluation.complianceType as ComplianceType, - OrderingTimestamp: new Date(configurationItem.configurationItemCaptureTime), - Annotation: evaluation.annotation, - }, - ], - }), - ); -} - -/** - * Evaluate if the current resource policy is compliant or not - * @param {*} props - * @returns - */ -const evaluateCompliance = (configurationItem: ConfigurationItem, policyMap: Map) => { - if ( - !configurationItem.resourceType || - !Object.values(ResourceType).includes(configurationItem.resourceType as ResourceType) - ) { - return { - complianceType: 'NOT_APPLICABLE', - annotation: `The rule doesn't apply to resources of type ${configurationItem.resourceType}`, - }; - } else if (configurationItem.configurationItemStatus === 'ResourceDeleted') { - return { - complianceType: 'NOT_APPLICABLE', - annotation: 'The configuration item was deleted and could not be validated', - }; - } - - const resourceType = configurationItem.resourceType as ResourceType; - const strategy = resourcePolicyStrategyMap.get(resourceType); - if (!strategy) throw new Error(`Strategy for ${resourceType} is not implemented`); - - if (RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY.includes(resourceType)) { - return strategy.evaluateResourcePolicyCompliance(configurationItem); - } else if (!policyMap.has(resourceType)) { - throw Error(`Policy for ${resourceType} is missing`); - } - - return strategy.evaluateResourcePolicyCompliance(configurationItem, policyMap.get(resourceType)!); -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/remediate-resource-policy/index.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/remediate-resource-policy/index.ts deleted file mode 100644 index a41a366..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/src/remediate-resource-policy/index.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { - ConfigServiceClient, - SelectResourceConfigCommand, - BatchGetResourceConfigCommand, - BaseConfigurationItem, -} from '@aws-sdk/client-config-service'; - -import { getResourcePolicies, generatePolicyReplacements, RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY } from '../common/utils'; -import { getOrCreateStrategyMap } from '../common/strategy'; -import { ResourceType } from '../common/common-resources'; - -const config = new ConfigServiceClient(); - -const policyMapTemplate = getResourcePolicies(); -const resourcePolicyStrategyMap = getOrCreateStrategyMap(); - -export const handler = async (event: { ResourceId: string }) => { - console.log(`Custom Rule to remediate resource based policies`); - console.log(JSON.stringify(event, null, 2)); - - // Validate that the event has the required input - if (!event.ResourceId) { - throw new Error('resourceId is not found in event'); - } - - // Retrieve resource type first because we need both resourceId and resourceType to get the detail policy of a resource. - const resourceType = await getResourceTypeById(event.ResourceId); - const configurationItem = await getResourceConfigurationItem(event.ResourceId, resourceType); - - const paramsReplacement = { - 'ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN': (configurationItem as { arn: string }).arn, - }; - const policyMap = generatePolicyReplacements(policyMapTemplate, paramsReplacement); - const strategy = resourcePolicyStrategyMap.get(resourceType); - - if (!strategy) throw new Error(`Strategy for ${resourceType} is not implemented`); - if (RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY.includes(resourceType)) { - return strategy.updateResourceBasedPolicy(configurationItem); - } else if (!policyMap.has(resourceType)) { - throw Error(`Policy for ${resourceType} is missing`); - } - - await strategy.updateResourceBasedPolicy( - configurationItem as { resourceName: string; resourceId: string; resourceType: string }, - policyMap.get(resourceType)!, - ); -}; - -/** - * Get the resource type based on the resourceId from AWS Config - * - * @param {*} resourceId - * @returns - */ -const getResourceTypeById = async (resourceId: string): Promise => { - const queryResult = await config.send( - new SelectResourceConfigCommand({ Expression: `SELECT resourceType WHERE resourceId = '${resourceId}'` }), - ); - - if (!queryResult.Results || queryResult.Results.length === 0) { - throw new Error(`No resource type with resource id ${resourceId} was found`); - } - - return JSON.parse(queryResult.Results[0]).resourceType as ResourceType; -}; - -/** - * Get the AWS Config configuration item for the resource by resourceId and resourceType. - * This will provide useful information such as resourceName, ARN etc. - * - * @param {*} resourceId - * @param {*} resourceType - * @returns - */ -const getResourceConfigurationItem = async ( - resourceId: string, - resourceType: string, -): Promise => { - const response = await config.send( - new BatchGetResourceConfigCommand({ - resourceKeys: [ - { - resourceId, - resourceType, - }, - ], - }), - ); - - if (!response.baseConfigurationItems || response.baseConfigurationItems.length === 0) { - throw new Error(`No configuration found for ${resourceId} with type ${resourceType} in AWS Config`); - } - - return response.baseConfigurationItems[0]; -}; diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/tsconfig.json b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/tsconfig.json deleted file mode 100644 index 084ba02..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/lambda-handler/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src/**/*.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediate-resource-policy.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediate-resource-policy.ts deleted file mode 100644 index b090c93..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediate-resource-policy.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import * as path from 'path'; - -import { copyPoliciesToDeploymentPackage } from '../common-functions'; - -/** - * Remediate resource policy - * This construct creates a Lambda function which will be triggered by SSM Automation and used to - * remediate any non-compliant resource policy detected by ${stack.partition} Config Rule - */ -export interface RemediateResourcePolicyProps { - /** - * Prefix for accelerator resources - */ - readonly acceleratorPrefix: string; - /** - * Configuration directory path - */ - readonly configDirPath: string; - /** - * Accelerator home region - */ - readonly homeRegion: string; - /** - * Lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKeyCloudWatch?: cdk.aws_kms.IKey; - /** - * Lambda environment variable encryption key, when undefined default AWS managed key will be used - */ - readonly kmsKeyLambda?: cdk.aws_kms.IKey; - /** - * Lambda log retention in days - */ - readonly logRetentionInDays: number; - /** - * SCP File Paths - */ - readonly rbpFilePaths: { name: string; path: string; tempPath: string }[]; - /** - * Input parameters as lambda environment variable - */ - readonly inputParameters?: { [key: string]: string }; -} - -export class RemediateResourcePolicy extends Construct { - lambdaFunction: cdk.aws_lambda.Function; - - constructor(scope: Construct, id: string, props: RemediateResourcePolicyProps) { - super(scope, id); - - const deploymentPackagePath = path.join(__dirname, 'lambda-handler/dist'); - copyPoliciesToDeploymentPackage(props.rbpFilePaths, deploymentPackagePath, cdk.Stack.of(this).account); - - const LAMBDA_TIMEOUT_IN_MINUTES = 1; - - this.lambdaFunction = new cdk.aws_lambda.Function(this, 'RemediateResourcePolicyFunction', { - code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler/dist')), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'remediate-resource-policy.handler', - description: 'Lambda function to remediate non-compliant resource based policy', - timeout: cdk.Duration.minutes(LAMBDA_TIMEOUT_IN_MINUTES), - environment: { - ...props.inputParameters, - ACCELERATOR_PREFIX: props.acceleratorPrefix, - AWS_PARTITION: cdk.Aws.PARTITION, - HOME_REGION: props.homeRegion, - }, - environmentEncryption: props.kmsKeyLambda, - }); - - this.addPermissionToLambdaRole(this.lambdaFunction); - - new cdk.aws_logs.LogGroup(this, `${this.lambdaFunction.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.lambdaFunction.functionName}`, - retention: props.logRetentionInDays, - encryptionKey: props.kmsKeyCloudWatch, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - } - - private addPermissionToLambdaRole(lambdaFunction: cdk.aws_lambda.Function) { - const stack = cdk.Stack.of(this); - lambdaFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - 'iam:getRole', - 'iam:updateAssumeRolePolicy', - 's3:GetBucketPolicy', - 's3:PutBucketPolicy', - 'kms:GetKeyPolicy', - 'kms:PutKeyPolicy', - 'kms:DescribeKey', - 'secretsmanager:GetResourcePolicy', - 'secretsmanager:PutResourcePolicy', - 'secretsmanager:ValidateResourcePolicy', - 'lex:DescribeResourcePolicy', - 'lex:UpdateResourcePolicy', - 'lex:CreateResourcePolicy', - 'apigateway:UpdateRestApiPolicy', - 'apigateway:GET', - 'apigateway:PATCH', - 'ecr:GetRepositoryPolicy', - 'ecr:SetRepositoryPolicy', - 'es:ESHttpGet', - 'es:ESHttpPut', - 'es:DescribeDomainConfig', - 'es:UpdateDomainConfig', - 'sns:GetTopicAttributes', - 'sns:SetTopicAttributes', - 'sqs:GetQueueAttributes', - 'sqs:SetQueueAttributes', - 'elasticfilesystem:DescribeFileSystemPolicy', - 'elasticfilesystem:PutFileSystemPolicy', - 'codeartifact:GetDomainPermissionsPolicy', - 'codeartifact:GetRepositoryPermissionsPolicy', - 'codeartifact:PutDomainPermissionsPolicy', - 'codeartifact:PutRepositoryPermissionsPolicy', - 'events:DescribeEventBus', - 'events:PutPermission', - 'backup:GetBackupVaultAccessPolicy', - 'backup:PutBackupVaultAccessPolicy', - 'acm-pca:PutPolicy', - 'acm-pca:GetPolicy', - 'lambda:GetPolicy', - 'lambda:AddPermission', - 'lambda:RemovePermission', - ], - resources: [ - `arn:${stack.partition}:iam::${stack.account}:*`, // Policy doesn't allow region in S3 arn in resource - `arn:${stack.partition}:s3:::*`, // Policy doesn't allow account and region in S3 arn in resource - `arn:${stack.partition}:kms:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:secretsmanager:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:lex:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:apigateway:${stack.region}::*`, // Policy doesn't allow account ID in apigateway ARN - `arn:${stack.partition}:ecr:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:es:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:sns:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:sqs:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:elasticfilesystem:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:codeartifact:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:lambda:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:backup:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:events:${stack.region}:${stack.account}:*`, - `arn:${stack.partition}:acm-pca:${stack.region}:${stack.account}:*`, - ], - }), - ); - lambdaFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['config:BatchGetResourceConfig', 'config:SelectResourceConfig'], - resources: ['*'], - }), - ); - - // AwsSolutions-IAM4: The IAM user, lambdaFunction.addToRolePolicy, or group uses AWS managed policies - NagSuppressions.addResourceSuppressions(this.lambdaFunction.role!, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressions( - this.lambdaFunction.role!, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - true, - ); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediation-ssm-document.ts b/source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediation-ssm-document.ts deleted file mode 100644 index 41cbd05..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/data-perimeter/remediation-ssm-document.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import * as fs from 'fs'; -import { pascalCase } from 'pascal-case'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import * as yaml from 'js-yaml'; - -import { GlobalConfig } from '@aws-accelerator/config'; -import { Document } from '../aws-ssm/document'; - -/** - * Detect Resource Policy - * This construct creates a Lambda function which is triggered by AWS Config Rule and - * detect if a resource policy is compliant to the resource policy template by comparing - * statements in resource policy. - */ -export interface RemediationSsmDocumentProps { - documentName: string; - sharedAccountIds: string[]; - globalConfig: GlobalConfig; - cloudwatchKey?: cdk.aws_kms.IKey; -} - -export class RemediationSsmDocument extends Construct { - private readonly documentPath = path.join(__dirname, 'attach-resource-based-policy.yaml'); - - constructor(scope: Construct, id: string, props: RemediationSsmDocumentProps) { - super(scope, id); - - // Read in the document which should be properly formatted - const buffer = fs.readFileSync(this.documentPath, 'utf8'); - const content = yaml.load(buffer); - - // Create the document - new Document(this, pascalCase(props.documentName), { - name: props.documentName, - content, - documentType: 'Automation', - sharedWithAccountIds: props.sharedAccountIds, - kmsKey: props.cloudwatchKey, - logRetentionInDays: props.globalConfig.cloudwatchLogRetentionInDays, - targetType: undefined, - }); - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/lza-custom-resource.ts b/source/packages/@aws-accelerator/constructs/lib/lza-custom-resource.ts deleted file mode 100644 index 7d2981b..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/lza-custom-resource.ts +++ /dev/null @@ -1,345 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; -import { v4 as uuidv4 } from 'uuid'; -import { pascalCase } from 'change-case'; -import { LzaLambda } from './lza-lambda'; - -/** - * Initialized LzaCustomResourceProps properties - */ -export interface LzaCustomResourceProps { - /** - * Custom resource properties - */ - readonly resource: { - /** - * Logical name for the custom resource - */ - readonly name: string; - /** - * Logical Id for the implementor construct - */ - readonly parentId: string; - /** - * A name value object list for custom resource properties - * - * @example - * [{ solution: 'accelerator', author: 'tsd' }] - */ - readonly properties?: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[]; - /** - * Generates a new UUID to force the resource to update - */ - readonly forceUpdate?: boolean; - /** - * The AWS Lambda function to invoke for all resource lifecycle operations (CREATE/UPDATE/DELETE). - * - * @default - * undefined. - * - * @remarks - * This function is responsible to begin the requested resource operation (CREATE/UPDATE/DELETE). - * When no value provided construct will create lambda function for the custom resource. - */ - readonly onEventHandler?: cdk.aws_lambda.IFunction; - /** - * Debug flag used in custom resource lambda function to output debug logs - @default false - */ - readonly debug?: boolean; - }; - - /** - * Custom resource lambda properties - */ - readonly lambda?: { - /** - * LZA Custom resource lambda asset folder path including the /dist folder - */ - readonly assetPath: string; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly environmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda cloudwatch log retention in days - */ - readonly cloudWatchLogRetentionInDays: number; - /** - * A description of the custom resource lambda function. - */ - readonly description?: string; - /** - * Custom resource lambda execution role. This is the role that will be assumed by the function upon execution. - * @default - * A unique role will be generated for this lambda function. - */ - readonly role?: cdk.aws_iam.Role | cdk.aws_iam.IRole; - /** - * The amount of memory, in MB, that is allocated to custom resource Lambda function. Lambda uses this value to proportionally allocate the amount of CPU power. - * @default 512 - */ - readonly memorySize?: number; - /** - * Custom resource lambda function execution time (in seconds) after which Lambda terminates the function. - * @default 3 seconds - */ - readonly timeOut?: cdk.Duration; - /** - * Initial policy statements to add to the created custom resource Lambda Role. - */ - readonly roleInitialPolicy?: cdk.aws_iam.PolicyStatement[]; - /** - * The name of the method within lambda code that custom resource lambda calls to execute the function. The format includes the file name. - * @default 'index.handler' - */ - readonly handler?: string; - /** - * Determine the removal policy of CloudWatch log group for the lambda. - @default RemovalPolicy.Destroy - */ - readonly cloudWatchLogRemovalPolicy?: cdk.RemovalPolicy; - /** - * A name value object list for lambda environment variables - * - * @example - * [{ solution: 'accelerator', author: 'tsd' }] - */ - readonly environmentVariables?: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[]; - }; - /** - * Prefix for nag suppression - */ - readonly nagSuppressionPrefix?: string; -} - -export type CloudFormationCustomResourceEvent = - | CloudFormationCustomResourceCreateEvent - | CloudFormationCustomResourceUpdateEvent - | CloudFormationCustomResourceDeleteEvent; - -export type CloudFormationCustomResourceResponse = - | CloudFormationCustomResourceSuccessResponse - | CloudFormationCustomResourceFailedResponse; - -/** - * CloudFormation Custom Resource event and response - * http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref.html - */ -export interface CloudFormationCustomResourceEventCommon { - ServiceToken: string; - ResponseURL: string; - StackId: string; - RequestId: string; - LogicalResourceId: string; - ResourceType: string; - ResourceProperties: { - ServiceToken: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Key: string]: any; - }; -} - -export interface CloudFormationCustomResourceCreateEvent extends CloudFormationCustomResourceEventCommon { - RequestType: 'Create'; -} - -export interface CloudFormationCustomResourceUpdateEvent extends CloudFormationCustomResourceEventCommon { - RequestType: 'Update'; - PhysicalResourceId: string; - OldResourceProperties: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Key: string]: any; - }; -} - -export interface CloudFormationCustomResourceDeleteEvent extends CloudFormationCustomResourceEventCommon { - RequestType: 'Delete'; - PhysicalResourceId: string; -} - -export interface CloudFormationCustomResourceResponseCommon { - PhysicalResourceId: string; - StackId: string; - RequestId: string; - LogicalResourceId: string; - Data?: - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Key: string]: any; - } - | undefined; - NoEcho?: boolean | undefined; -} - -export interface CloudFormationCustomResourceSuccessResponse extends CloudFormationCustomResourceResponseCommon { - Status: 'SUCCESS'; - Reason?: string | undefined; -} - -export interface CloudFormationCustomResourceFailedResponse extends CloudFormationCustomResourceResponseCommon { - Status: 'FAILED'; - Reason: string; -} - -/** - * Class for LZA Custom Resource Construct - * This class can create LZA standard custom resource constructs - */ -export class LzaCustomResource extends Construct { - public readonly resource: cdk.CustomResource; - - private readonly provider: cdk.custom_resources.Provider; - - constructor(scope: Construct, id: string, props: LzaCustomResourceProps) { - super(scope, id); - - if (props.resource.onEventHandler && props.lambda) { - throw new Error( - `Custom resource onEventHandler lambda function or construct lambda property can be provided, both can't be provided.`, - ); - } - - if (!props.resource.onEventHandler && !props.lambda) { - throw new Error( - `Either custom resource onEventHandler lambda function or construct lambda property must be provided, both can't be undefined.`, - ); - } - - let providerLambdaFunction = props.resource.onEventHandler; - let lzaLambda: LzaLambda | undefined; - const nagSuppressionPrefix = props.nagSuppressionPrefix - ? `${props.nagSuppressionPrefix}/${props.resource.name}` - : `${props.resource.parentId}/${props.resource.name}`; - - if (props.lambda) { - lzaLambda = new LzaLambda(this, 'Function', { - assetPath: props.lambda.assetPath, - environmentEncryptionKmsKey: props.lambda.environmentEncryptionKmsKey, - cloudWatchLogKmsKey: props.lambda.cloudWatchLogKmsKey, - cloudWatchLogRetentionInDays: props.lambda.cloudWatchLogRetentionInDays, - description: - props.lambda.description ?? `Accelerator deployed ${props.resource.name} custom resource lambda function.`, - role: props.lambda.role, - memorySize: props.lambda.memorySize, - timeOut: props.lambda.timeOut, - roleInitialPolicy: props.lambda.roleInitialPolicy, - handler: props.lambda.handler, - cloudWatchLogRemovalPolicy: props.lambda.cloudWatchLogRemovalPolicy, - environmentVariables: props.lambda.environmentVariables, - nagSuppressionPrefix, - }); - - providerLambdaFunction = lzaLambda.resource; - } - - this.provider = new cdk.custom_resources.Provider(this, 'Resource', { - onEventHandler: providerLambdaFunction!, - }); - - this.resource = new cdk.CustomResource(this, pascalCase(props.resource.name + 'Resource'), { - serviceToken: this.provider.serviceToken, - properties: this.prepareResourceProperties(props), - }); - if (props.lambda && lzaLambda?.logGroup) { - this.resource.node.addDependency(lzaLambda!.logGroup); - } - this.addSuppression(scope, props); - } - - /** - * Function to add NagSuppressions - * @param scope {@link Construct} - * @param nagSuppressionPrefix {@link LzaCustomResourceProps} - */ - private addSuppression(scope: Construct, props: LzaCustomResourceProps) { - const stack = cdk.Stack.of(scope); - - let prefix: string | undefined; - - if (props.nagSuppressionPrefix) { - prefix = `${stack.stackName}/${props.nagSuppressionPrefix}/${props.resource.name}/Resource`; - } else { - prefix = `${stack.stackName}/${props.resource.parentId}/${props.resource.name}/Resource`; - } - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath(stack, `${prefix}/framework-onEvent/ServiceRole/Resource`, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Custom resource provider framework-role created by cdk.', - }, - ]); - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions - NagSuppressions.addResourceSuppressionsByPath( - stack, - `${prefix}/framework-onEvent/ServiceRole/DefaultPolicy/Resource`, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Allows only specific policy.', - }, - ], - ); - } - - /** - * Function to prepare Resource Properties - * @param props {@link LzaCustomResourceProps} - * @returns - */ - private prepareResourceProperties(props: LzaCustomResourceProps): - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - } - | undefined { - const resourcePropertyList: - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[] = props.resource.properties ?? [{ debug: props.resource.debug ?? false }]; - - if (props.resource.forceUpdate) { - resourcePropertyList.push({ uuid: uuidv4() }); - } - - const resourceProperties: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - } = {}; - - for (const resourceProperty of resourcePropertyList) { - for (const [key, value] of Object.entries(resourceProperty)) { - resourceProperties[key] = value; - } - } - - return resourcePropertyList.length > 0 ? resourceProperties : undefined; - } -} diff --git a/source/packages/@aws-accelerator/constructs/lib/lza-lambda.ts b/source/packages/@aws-accelerator/constructs/lib/lza-lambda.ts deleted file mode 100644 index 6ce58f0..0000000 --- a/source/packages/@aws-accelerator/constructs/lib/lza-lambda.ts +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -/** - * LzaLambdaProps properties - */ -export interface LzaLambdaProps { - /** - * A name for the function. - * @default - AWS CloudFormation generates a unique physical ID and uses that - * ID for the function's name. - */ - readonly functionName?: string; - /** - * LZA Custom resource lambda asset folder path including the /dist folder - */ - readonly assetPath: string; - /** - * Custom resource lambda environment encryption key, when undefined default AWS managed key will be used - */ - readonly environmentEncryptionKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda log group encryption key, when undefined default AWS managed key will be used - */ - readonly cloudWatchLogKmsKey?: cdk.aws_kms.IKey; - /** - * Custom resource lambda cloudwatch log retention in days - */ - readonly cloudWatchLogRetentionInDays: number; - /** - * A description of the custom resource lambda function. - */ - readonly description?: string; - /** - * Custom resource lambda execution role. This is the role that will be assumed by the function upon execution. - * @default - * A unique role will be generated for this lambda function. - */ - readonly role?: cdk.aws_iam.Role | cdk.aws_iam.IRole; - /** - * The amount of memory, in MB, that is allocated to custom resource Lambda function. Lambda uses this value to proportionally allocate the amount of CPU power. - * @default 512 - */ - readonly memorySize?: number; - /** - * Custom resource lambda function execution time (in seconds) after which Lambda terminates the function. - * @default 3 seconds - */ - readonly timeOut?: cdk.Duration; - /** - * Initial policy statements to add to the created custom resource Lambda Role. - */ - readonly roleInitialPolicy?: cdk.aws_iam.PolicyStatement[]; - /** - * The name of the method within lambda code that custom resource lambda calls to execute the function. The format includes the file name. - * @default 'index.handler' - */ - readonly handler?: string; - /** - * Determine the removal policy of CloudWatch log group for the lambda. - @default RemovalPolicy.Destroy - */ - readonly cloudWatchLogRemovalPolicy?: cdk.RemovalPolicy; - /** - * A name value object list for lambda environment variables - * - * @example - * [{ solution: 'accelerator', author: 'tsd' }] - */ - readonly environmentVariables?: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[]; - /** - * Prefix for nag suppression - */ - readonly nagSuppressionPrefix: string; -} - -/** - * Class for LZA Lambda Construct - * Class to create LZA standard Lambda function used for custom resource - */ -export class LzaLambda extends Construct { - public readonly resource: cdk.aws_lambda.IFunction; - public readonly logGroup: cdk.aws_logs.LogGroup; - - constructor(scope: Construct, id: string, props: LzaLambdaProps) { - super(scope, id); - - this.resource = new cdk.aws_lambda.Function(this, 'Resource', { - functionName: props.functionName, - description: props.description ?? `Accelerator deployed lambda function.`, - code: cdk.aws_lambda.Code.fromAsset(props.assetPath), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - memorySize: props.memorySize ?? 512, - timeout: props.timeOut, - role: props.role, - initialPolicy: props.roleInitialPolicy, - handler: props.handler ?? 'index.handler', - environmentEncryption: props.environmentEncryptionKmsKey, - environment: this.prepareLambdaEnvironments(props), - }); - - this.logGroup = new cdk.aws_logs.LogGroup(this, `${this.resource.node.id}LogGroup`, { - logGroupName: `/aws/lambda/${this.resource.functionName}`, - retention: props.cloudWatchLogRetentionInDays, - encryptionKey: props.cloudWatchLogKmsKey, - removalPolicy: props.cloudWatchLogRemovalPolicy ?? cdk.RemovalPolicy.DESTROY, - }); - - this.addSuppression(scope, id, props.nagSuppressionPrefix); - } - - /** - * Function to add NagSuppressions - * @param scope {@link Construct} - * @param id string - * @param nagSuppressionPrefix string - */ - private addSuppression(scope: Construct, id: string, nagSuppressionPrefix: string) { - const stack = cdk.Stack.of(scope); - - const prefix = `${stack.stackName}/${nagSuppressionPrefix}/${id}/Resource`; - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies - NagSuppressions.addResourceSuppressionsByPath(stack, `${prefix}/ServiceRole/Resource`, [ - { - id: 'AwsSolutions-IAM4', - reason: 'AWS Lambda needs Managed policy.', - }, - ]); - NagSuppressions.addResourceSuppressionsByPath(stack, `${prefix}/ServiceRole/DefaultPolicy/Resource`, [ - { - id: 'AwsSolutions-IAM5', - reason: 'AWS Lambda needs Managed policy.', - }, - ]); - } - - /** - * Function to prepare Lambda Environment variables - * @param props {@link LzaCustomResourceProps} - * @returns - */ - private prepareLambdaEnvironments(props: LzaLambdaProps): - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - } - | undefined { - const lambdaEnvironmentList: - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[] = props.environmentVariables ?? []; - - const lambdaEnvironment: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - } = {}; - - for (const environmentVariable of lambdaEnvironmentList) { - for (const [key, value] of Object.entries(environmentVariable)) { - lambdaEnvironment[key] = value; - } - } - - return lambdaEnvironmentList.length > 0 ? lambdaEnvironment : undefined; - } -} diff --git a/source/packages/@aws-accelerator/constructs/package.json b/source/packages/@aws-accelerator/constructs/package.json deleted file mode 100644 index 82ab989..0000000 --- a/source/packages/@aws-accelerator/constructs/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@aws-accelerator/constructs", - "version": "0.0.0", - "private": true, - "description": "The cdk constructs library for the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "index.js", - "types": "index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "@aws-accelerator/config": "^0.0.0", - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-config-service": "3.410.0", - "@types/uuid": "9.0.0", - "aws-cdk-lib": "2.93.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/constructs/test/aws-accelerator/__snapshots__/get-accelerator-metadata.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-accelerator/__snapshots__/get-accelerator-metadata.test.ts.snap deleted file mode 100644 index c10c1f0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-accelerator/__snapshots__/get-accelerator-metadata.test.ts.snap +++ /dev/null @@ -1,304 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AcceleratorMetadata Construct(AcceleratorMetadata): Snapshot Test 1`] = ` -{ - "Resources": { - "AWSAcceleratormetadatacollectionLogGroup76F89C67": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": "/aws/lambda/AWSAccelerator-metadata-collection", - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6": { - "DependsOn": [ - "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6", - "AcceleratorMetadataMetadataLambdaC27FFAF6", - "AWSAcceleratormetadatacollectionLogGroup76F89C67", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "ACCELERATOR_VERSION_SSM_PATH": "/accelerator/AWSAccelerator-InstallerStack/version", - "CENTRAL_LOG_BUCKET": "centralLogBucketTest", - "CONFIG_REPOSITORY_NAME": "aws-config-test", - "CROSS_ACCOUNT_ROLE": "testRole", - "ELB_LOGGING_BUCKET": "elbLogBucketNameTest", - "GLOBAL_REGION": "us-east-1", - "LOG_ACCOUNT_ID": "111111111111", - "METADATA_BUCKET": "testMetadataLogBucket", - "ORGANIZATION_ID": "ou-test123", - "PARTITION": { - "Ref": "AWS::Partition", - }, - }, - }, - "FunctionName": "AWSAccelerator-metadata-collection", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AcceleratorMetadataMetadataLambdaC27FFAF6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 600, - }, - "Type": "AWS::Lambda::Function", - }, - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRule1FFE12D2": { - "Properties": { - "EventPattern": { - "account": [ - { - "Ref": "AWS::AccountId", - }, - ], - "detail": { - "stack-id": [ - { - "Ref": "AWS::StackId", - }, - ], - "status-details": { - "status": [ - "CREATE_COMPLETE", - "UPDATE_COMPLETE", - ], - }, - }, - "detail-type": [ - "CloudFormation Stack Status Change", - ], - "region": [ - { - "Ref": "AWS::Region", - }, - ], - "resources": [ - { - "Ref": "AWS::StackId", - }, - ], - "source": [ - "aws.cloudformation", - ], - }, - "Name": "AWSAccelerator-metadata-collection-rule", - "ScheduleExpression": "rate(1 day)", - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6", - "Arn", - ], - }, - "Id": "Target0", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRuleAllowEventRuleAcceleratorMetadataAWSAcceleratormetadatacollection511598FFB2D995AE": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AcceleratorMetadataAWSAcceleratormetadatacollectionEBD85CF6", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "AcceleratorMetadataAwsAcceleratorMetadataCollectionRule1FFE12D2", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "AcceleratorMetadataMetadataLambdaC27FFAF6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "RoleName": { - "Fn::Join": [ - "", - [ - "AWSAccelerator-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "-metadata-lambda-role", - ], - ], - }, - }, - "Type": "AWS::IAM::Role", - }, - "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "accelerator metadata collection custom resource", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipelineExecution", - "codepipeline:ListPipelineExecutions", - "codecommit:GetFolder", - "codecommit:GetFile", - "kms:DescribeKey", - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:ListAliases", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - "organizations:DescribeOrganizationalUnit", - "organizations:DescribeAccount", - "organizations:ListAccounts", - "organizations:ListChildren", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "ssm:GetParameter", - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObjectAcl", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::testMetadataLogBucket", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::testMetadataLogBucket/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AcceleratorMetadataMetadataLambdaDefaultPolicy03C222B6", - "Roles": [ - { - "Ref": "AcceleratorMetadataMetadataLambdaC27FFAF6", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-accelerator/get-accelerator-metadata.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-accelerator/get-accelerator-metadata.test.ts deleted file mode 100644 index 5bae014..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-accelerator/get-accelerator-metadata.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AcceleratorMetadata } from '../../lib/aws-accelerator/get-accelerator-metadata'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(AcceleratorMetadata): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new AcceleratorMetadata(stack, 'AcceleratorMetadata', { - acceleratorConfigRepositoryName: 'aws-config-test', - acceleratorPrefix: 'AWSAccelerator', - assumeRole: 'testRole', - centralLogBucketName: 'centralLogBucketTest', - cloudwatchKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - elbLogBucketName: 'elbLogBucketNameTest', - loggingAccountId: '111111111111', - metadataLogBucketName: 'testMetadataLogBucket', - organizationId: 'ou-test123', - logRetentionInDays: 3653, - acceleratorSsmParamPrefix: '/accelerator', - globalRegion: 'us-east-1', -}); - -/** - * AuditManagerDefaultReportsDestination construct test - */ -describe('AcceleratorMetadata', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-create-reports-destination.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-create-reports-destination.test.ts.snap deleted file mode 100644 index 48185fa..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-create-reports-destination.test.ts.snap +++ /dev/null @@ -1,208 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AuditManagerDefaultReportsDestination Construct(AuditManagerDefaultReportsDestination): Snapshot Test 1`] = ` -{ - "Resources": { - "AuditManagerDefaultReportsDestinationAFD20D60": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderLogGroupF5AC3566", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433", - "Arn", - ], - }, - "bucket": { - "Fn::Join": [ - "", - [ - "s3//aws-accelerator-auditmgr-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "defaultReportsDestinationType": "S3", - "kmsKeyArn": { - "Fn::GetAtt": [ - "BucketKey7092080A", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::AuditManagerCreateDefaultReportsDestination", - "UpdateReplacePolicy": "Delete", - }, - "BucketKey7092080A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433": { - "DependsOn": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderLogGroupF5AC3566": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderHandler6BCBC433", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerCreateDefaultReportsDestinationCustomResourceProviderRoleAEE72AE5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "auditmanager:UpdateSettings", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerCreatePublishingDestinationCommandTaskAuditManagerActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-organization-admin-account.test.ts.snap deleted file mode 100644 index 082d203..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/__snapshots__/auditmanager-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,232 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AuditManagerOrganizationAdminAccount Construct(AuditManagerOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "AuditManagerOrganizationAdminAccount34B8BA90": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - "Arn", - ], - }, - "adminAccountId": { - "Ref": "AWS::AccountId", - }, - "kmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::AuditManagerEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9": { - "DependsOn": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderLogGroup858CB16C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderHandlerCA9379D9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomAuditManagerEnableOrganizationAdminAccountCustomResourceProviderRoleF4A6BEA4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListAccounts": [ - "auditmanager.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "auditmanager.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "auditmanager.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "auditmanager:RegisterAccount", - "auditmanager:DeregisterAccount", - "auditmanager:RegisterOrganizationAdminAccount", - "auditmanager:DeregisterOrganizationAdminAccount", - "auditmanager:getOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AuditManagerEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": "kms:CreateGrant", - "Condition": { - "Bool": { - "kms:GrantIsForAWSResource": "true", - }, - "StringLike": { - "kms:ViaService": "auditmanager.*.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "Sid": "AuditManagerEnableKmsKeyGrants", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-create-reports-destination.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-create-reports-destination.test.ts deleted file mode 100644 index 10ab70f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-create-reports-destination.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AuditManagerDefaultReportsDestination } from '../../lib/aws-auditmanager/auditmanager-reports-destination'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(AuditManagerDefaultReportsDestination): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new AuditManagerDefaultReportsDestination(stack, 'AuditManagerDefaultReportsDestination', { - bucket: `s3//aws-accelerator-auditmgr-${stack.account}-${stack.region}`, - defaultReportsDestinationType: 'S3', - bucketKmsKey: new cdk.aws_kms.Key(stack, 'BucketKey', {}), - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * AuditManagerDefaultReportsDestination construct test - */ -describe('AuditManagerDefaultReportsDestination', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-organization-admin-account.test.ts deleted file mode 100644 index b5e1d9b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-auditmanager/auditmanager-organization-admin-account.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AuditManagerOrganizationAdminAccount } from '../../lib/aws-auditmanager/auditmanager-organization-admin-account'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(AuditManagerOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new AuditManagerOrganizationAdminAccount(stack, 'AuditManagerOrganizationAdminAccount', { - adminAccountId: stack.account, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * AuditManagerOrganizationAdminAccount construct test - */ -describe('AuditManagerOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-autoscaling/__snapshots__/create-autoscaling-group.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-autoscaling/__snapshots__/create-autoscaling-group.test.ts.snap deleted file mode 100644 index 95668e1..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-autoscaling/__snapshots__/create-autoscaling-group.test.ts.snap +++ /dev/null @@ -1,346 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AutoscalingGroup Construct(AutoscalingGroup): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyCloudWatchFB91CD4E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "Test7BFAF513": { - "Properties": { - "DesiredCapacity": "2", - "HealthCheckGracePeriod": 300, - "HealthCheckType": "ELB", - "LaunchTemplate": { - "LaunchTemplateId": "string", - "Version": "string", - }, - "MaxInstanceLifetime": 86400, - "MaxSize": "4", - "MinSize": "1", - "Tags": [ - { - "Key": "key", - "PropagateAtLaunch": true, - "Value": "value", - }, - ], - "TargetGroupARNs": [ - "string", - ], - "VPCZoneIdentifier": [ - "string", - ], - }, - "Type": "AWS::AutoScaling::AutoScalingGroup", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction48483210": { - "DependsOn": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy5556EF30", - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleD427D69E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleD427D69E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup4DBD4D73": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKeyCloudWatchFB91CD4E", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction48483210", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleD427D69E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy5556EF30": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy5556EF30", - "Roles": [ - { - "Ref": "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleD427D69E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent25455835": { - "DependsOn": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy610C6366", - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6926E52E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/Test/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction48483210", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6926E52E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6926E52E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy610C6366": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction48483210", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction48483210", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy610C6366", - "Roles": [ - { - "Ref": "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole6926E52E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleResourceC5B197D4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroup4DBD4D73", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TestAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent25455835", - "Arn", - ], - }, - "description": "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling", - "roleName": "AWSServiceRoleForAutoScaling", - "serviceName": "autoscaling.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-autoscaling/create-autoscaling-group.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-autoscaling/create-autoscaling-group.test.ts deleted file mode 100644 index 9619f55..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-autoscaling/create-autoscaling-group.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AutoscalingGroup } from '../../lib/aws-autoscaling/create-autoscaling-group'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(AutoscalingGroup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new AutoscalingGroup(stack, 'Test', { - name: 'string', - minSize: 1, - maxSize: 4, - desiredSize: 2, - launchTemplateId: 'string', - launchTemplateVersion: 'string', - healthCheckGracePeriod: 300, - healthCheckType: 'ELB', - targetGroups: ['string'], - subnets: ['string'], - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - cloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyCloudWatch', {}), - cloudWatchLogRetentionInDays: 3653, - maxInstanceLifetime: 86400, - tags: [{ key: 'key', value: 'value' }], -}); - -/** - * GWLB construct test - */ -describe('AutoscalingGroup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-budgets/__snapshots__/budget-definition.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-budgets/__snapshots__/budget-definition.test.ts.snap deleted file mode 100644 index 0cf3a22..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-budgets/__snapshots__/budget-definition.test.ts.snap +++ /dev/null @@ -1,278 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BudgetDefinition Construct(BudgetDefinition): Snapshot Test 1`] = ` -{ - "Resources": { - "ManagementKey0813A4D9": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "Test for the overall lambda", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ManagementKeyAlias69A0A38F": { - "Properties": { - "AliasName": "alias/AcceleratorStack/ACCELERATOR_MANAGEMENT_KEY_ALIAS", - "TargetKeyId": { - "Fn::GetAtt": [ - "ManagementKey0813A4D9", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "TestBudgetDefinitionaccelbudgetBE1AA96C": { - "Properties": { - "Budget": { - "BudgetLimit": { - "Amount": 2000, - "Unit": "USD", - }, - "BudgetName": "accel-budget", - "BudgetType": "COST", - "CostTypes": { - "IncludeCredit": false, - "IncludeDiscount": true, - "IncludeOtherSubscription": true, - "IncludeRecurring": true, - "IncludeRefund": false, - "IncludeSubscription": true, - "IncludeSupport": true, - "IncludeTax": true, - "IncludeUpfront": true, - "UseAmortized": false, - "UseBlended": false, - }, - "TimeUnit": "MONTHLY", - }, - "NotificationsWithSubscribers": [ - { - "Notification": { - "ComparisonOperator": "GREATER_THAN", - "NotificationType": "ACTUAL", - "Threshold": 100, - "ThresholdType": "PERCENTAGE", - }, - "Subscribers": [ - { - "Address": "myemail+pa-budg@example.com", - "SubscriptionType": "EMAIL", - }, - { - "Address": "myemail+pa1-budg@example.com", - "SubscriptionType": "EMAIL", - }, - { - "Address": "myemail+pa-budg@example.com", - "SubscriptionType": "EMAIL", - }, - ], - }, - ], - }, - "Type": "AWS::Budgets::Budget", - }, - }, -} -`; - -exports[`BudgetDefinitionCrossRegion Construct(BudgetDefinition): Snapshot Test 1`] = ` -{ - "Resources": { - "CrossRegionManagementKey1B0C6537": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "Test for the overall lambda", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CrossRegionManagementKeyAlias8BEA027D": { - "Properties": { - "AliasName": "alias/AcceleratorStack/ACCELERATOR_MANAGEMENT_KEY_ALIAS", - "TargetKeyId": { - "Fn::GetAtt": [ - "CrossRegionManagementKey1B0C6537", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "CrossRegionTestBudgetDefinitionCrossRegionResource03BC94B7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomCrossRegionBudgetCustomResourceProviderLogGroupC2E71098", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomCrossRegionBudgetCustomResourceProviderHandler1BDD3BC4", - "Arn", - ], - }, - "budgetDefinition": { - "amount": 2000, - "includeCredit": false, - "includeDiscount": true, - "includeOtherSubscription": true, - "includeRecurring": true, - "includeRefund": false, - "includeSubscription": true, - "includeSupport": true, - "includeTax": true, - "includeUpfront": true, - "name": "accel-budget-cross-region", - "timeUnit": "MONTHLY", - "type": "COST", - "unit": "USD", - "useAmortized": false, - "useBlended": false, - }, - }, - "Type": "Custom::CrossRegionBudget", - "UpdateReplacePolicy": "Delete", - }, - "CustomCrossRegionBudgetCustomResourceProviderHandler1BDD3BC4": { - "DependsOn": [ - "CustomCrossRegionBudgetCustomResourceProviderRole08C8F7BD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-111111111111-dummyRegion", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomCrossRegionBudgetCustomResourceProviderRole08C8F7BD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomCrossRegionBudgetCustomResourceProviderLogGroupC2E71098": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CrossRegionManagementKey1B0C6537", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomCrossRegionBudgetCustomResourceProviderHandler1BDD3BC4", - }, - ], - ], - }, - "RetentionInDays": 100, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomCrossRegionBudgetCustomResourceProviderRole08C8F7BD": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "budgets:DeleteBudget", - "budgets:ModifyBudget", - "budgets:PutBudget", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-budgets/budget-definition.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-budgets/budget-definition.test.ts deleted file mode 100644 index 6b57aee..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-budgets/budget-definition.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { BudgetDefinition } from '../../lib/aws-budgets/budget-definition'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(BudgetDefinition): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const nativeEnv = { account: '333333333333', region: 'us-east-1' }; -const nativeStack = new cdk.Stack(app, 'NativeStack', { env: nativeEnv }); -const nativeKey = new cdk.aws_kms.Key(nativeStack, 'ManagementKey', { - alias: 'AcceleratorStack/ACCELERATOR_MANAGEMENT_KEY_ALIAS', - description: 'Test for the overall lambda', - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, -}); - -new BudgetDefinition(nativeStack, 'TestBudgetDefinition', { - name: 'accel-budget', - timeUnit: 'MONTHLY', - type: 'COST', - amount: 2000, - includeUpfront: true, - includeTax: true, - includeSupport: true, - includeSubscription: true, - includeRecurring: true, - includeOtherSubscription: true, - includeDiscount: true, - includeCredit: false, - includeRefund: false, - useBlended: false, - useAmortized: false, - unit: 'USD', - notifications: [ - { - type: 'ACTUAL', - thresholdType: 'PERCENTAGE', - threshold: 100, - comparisonOperator: 'GREATER_THAN', - subscriptionType: 'EMAIL', - address: 'myemail+pa-budg@example.com', - recipients: ['myemail+pa-budg@example.com', 'myemail+pa1-budg@example.com'], - }, - ], - kmsKey: nativeKey, - logRetentionInDays: 100, -}); - -// Create stack for cross region Cfn construct -const crossRegionEnv = { account: '111111111111', region: 'dummyRegion' }; -const crossRegionStack = new cdk.Stack(app, 'CrossRegionStack', { env: crossRegionEnv }); -const crossRegionKey = new cdk.aws_kms.Key(crossRegionStack, 'CrossRegionManagementKey', { - alias: 'AcceleratorStack/ACCELERATOR_MANAGEMENT_KEY_ALIAS', - description: 'Test for the overall lambda', - enableKeyRotation: true, - removalPolicy: cdk.RemovalPolicy.RETAIN, -}); - -new BudgetDefinition(crossRegionStack, 'CrossRegionTestBudgetDefinition', { - name: 'accel-budget-cross-region', - timeUnit: 'MONTHLY', - type: 'COST', - amount: 2000, - includeUpfront: true, - includeTax: true, - includeSupport: true, - includeSubscription: true, - includeRecurring: true, - includeOtherSubscription: true, - includeDiscount: true, - includeCredit: false, - includeRefund: false, - useBlended: false, - useAmortized: false, - unit: 'USD', - kmsKey: crossRegionKey, - logRetentionInDays: 100, -}); - -/** - * BudgetDefinition construct test - */ -describe('BudgetDefinition', () => { - snapShotTest(testNamePrefix, nativeStack); -}); - -/** - * BudgetDefinition Cross region construct test - */ -describe('BudgetDefinitionCrossRegion', () => { - snapShotTest(testNamePrefix, crossRegionStack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/__snapshots__/certificate.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/__snapshots__/certificate.test.ts.snap deleted file mode 100644 index b60c130..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/__snapshots__/certificate.test.ts.snap +++ /dev/null @@ -1,903 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Certificate Construct(Certificate): Snapshot Test 1`] = ` -{ - "Resources": { - "ImportCertificate94B1FDBC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportCertificateFunctionLogGroup3FFC3061", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportCertificateCustomCreateAcmCertsframeworkonEvent5B216036", - "Arn", - ], - }, - "assetBucketName": "aws-accelerator-assets", - "cert": "cert/cert.crt", - "chain": "cert/chain.csr", - "homeRegion": "us-east-1", - "parameterName": "/accelerator/acm/importCert/arn", - "privKey": "cert/privKey.pem", - "type": "import", - }, - "Type": "Custom::CreateAcmCerts", - "UpdateReplacePolicy": "Delete", - }, - "ImportCertificateCloudWatchKey4B69D07C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ImportCertificateCustomCreateAcmCertsframeworkonEvent5B216036": { - "DependsOn": [ - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicyB532806A", - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ImportCertificate/Custom::CreateAcmCerts)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportCertificateFunction55F21D86", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicyB532806A": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportCertificateFunction55F21D86", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportCertificateFunction55F21D86", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicyB532806A", - "Roles": [ - { - "Ref": "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportCertificateFunction55F21D86": { - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create ACM certificates handler", - "Handler": "index.handler", - "Role": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAccelerator-AssetsAccessRole", - ], - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportCertificateFunctionLogGroup3FFC3061": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "ImportCertificateCloudWatchKey4B69D07C", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportCertificateFunction55F21D86", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "RequestCertificate5D48D3DE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "RequestCertificateFunctionLogGroup1921673C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "RequestCertificateCustomCreateAcmCertsframeworkonEvent15AC29F8", - "Arn", - ], - }, - "assetBucketName": "aws-accelerator-assets", - "domain": "*.example.com", - "homeRegion": "us-east-1", - "parameterName": "/accelerator/acm/requestCert/arn", - "san": "e.co,*.example.net", - "type": "request", - "validation": "DNS", - }, - "Type": "Custom::CreateAcmCerts", - "UpdateReplacePolicy": "Delete", - }, - "RequestCertificateCloudWatchKeyDAA2C481": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "RequestCertificateCustomCreateAcmCertsframeworkonEvent15AC29F8": { - "DependsOn": [ - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicy8F0B16E1", - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/RequestCertificate/Custom::CreateAcmCerts)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "RequestCertificateFunction3B1055B9", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicy8F0B16E1": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "RequestCertificateFunction3B1055B9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "RequestCertificateFunction3B1055B9", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicy8F0B16E1", - "Roles": [ - { - "Ref": "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RequestCertificateFunction3B1055B9": { - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create ACM certificates handler", - "Handler": "index.handler", - "Role": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAccelerator-AssetsAccessRole", - ], - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "RequestCertificateFunctionLogGroup1921673C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "RequestCertificateCloudWatchKeyDAA2C481", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "RequestCertificateFunction3B1055B9", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`ImportCertificate Construct(Certificate): Snapshot Test 1`] = ` -{ - "Resources": { - "ImportCertificate94B1FDBC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ImportCertificateFunctionLogGroup3FFC3061", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ImportCertificateCustomCreateAcmCertsframeworkonEvent5B216036", - "Arn", - ], - }, - "assetBucketName": "aws-accelerator-assets", - "cert": "cert/cert.crt", - "chain": "cert/chain.csr", - "homeRegion": "us-east-1", - "parameterName": "/accelerator/acm/importCert/arn", - "privKey": "cert/privKey.pem", - "type": "import", - }, - "Type": "Custom::CreateAcmCerts", - "UpdateReplacePolicy": "Delete", - }, - "ImportCertificateCloudWatchKey4B69D07C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ImportCertificateCustomCreateAcmCertsframeworkonEvent5B216036": { - "DependsOn": [ - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicyB532806A", - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ImportCertificate/Custom::CreateAcmCerts)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ImportCertificateFunction55F21D86", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicyB532806A": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ImportCertificateFunction55F21D86", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ImportCertificateFunction55F21D86", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicyB532806A", - "Roles": [ - { - "Ref": "ImportCertificateCustomCreateAcmCertsframeworkonEventServiceRole87CE2981", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ImportCertificateFunction55F21D86": { - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create ACM certificates handler", - "Handler": "index.handler", - "Role": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAccelerator-AssetsAccessRole", - ], - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ImportCertificateFunctionLogGroup3FFC3061": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "ImportCertificateCloudWatchKey4B69D07C", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ImportCertificateFunction55F21D86", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "RequestCertificate5D48D3DE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "RequestCertificateFunctionLogGroup1921673C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "RequestCertificateCustomCreateAcmCertsframeworkonEvent15AC29F8", - "Arn", - ], - }, - "assetBucketName": "aws-accelerator-assets", - "domain": "*.example.com", - "homeRegion": "us-east-1", - "parameterName": "/accelerator/acm/requestCert/arn", - "san": "e.co,*.example.net", - "type": "request", - "validation": "DNS", - }, - "Type": "Custom::CreateAcmCerts", - "UpdateReplacePolicy": "Delete", - }, - "RequestCertificateCloudWatchKeyDAA2C481": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "RequestCertificateCustomCreateAcmCertsframeworkonEvent15AC29F8": { - "DependsOn": [ - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicy8F0B16E1", - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/RequestCertificate/Custom::CreateAcmCerts)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "RequestCertificateFunction3B1055B9", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicy8F0B16E1": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "RequestCertificateFunction3B1055B9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "RequestCertificateFunction3B1055B9", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleDefaultPolicy8F0B16E1", - "Roles": [ - { - "Ref": "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "RequestCertificateCustomCreateAcmCertsframeworkonEventServiceRoleFFCDD9BD": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RequestCertificateFunction3B1055B9": { - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create ACM certificates handler", - "Handler": "index.handler", - "Role": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAccelerator-AssetsAccessRole", - ], - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "RequestCertificateFunctionLogGroup1921673C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "RequestCertificateCloudWatchKeyDAA2C481", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "RequestCertificateFunction3B1055B9", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/certificate.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/certificate.test.ts deleted file mode 100644 index 1f9c174..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-certificate-manager/certificate.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Certificate } from '../../lib/aws-certificate-manager/certificate'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(Certificate): '; -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -/** - * Certificate construct test - */ -describe('Certificate', () => { - new Certificate(stack, 'RequestCertificate', { - parameterName: '/accelerator/acm/requestCert/arn', - type: 'request', - validation: 'DNS', - domain: '*.example.com', - san: ['e.co', '*.example.net'], - homeRegion: 'us-east-1', - assetBucketName: 'aws-accelerator-assets', - assetFunctionRoleName: 'AWSAccelerator-AssetsAccessRole', - cloudWatchLogsKmsKey: new cdk.aws_kms.Key(stack, 'RequestCertificateCloudWatchKey', {}), - logRetentionInDays: 365, - }); - snapShotTest(testNamePrefix, stack); -}); - -describe('ImportCertificate', () => { - new Certificate(stack, 'ImportCertificate', { - parameterName: '/accelerator/acm/importCert/arn', - type: 'import', - privKey: 'cert/privKey.pem', - cert: 'cert/cert.crt', - chain: 'cert/chain.csr', - homeRegion: 'us-east-1', - assetBucketName: 'aws-accelerator-assets', - assetFunctionRoleName: 'AWSAccelerator-AssetsAccessRole', - cloudWatchLogsKmsKey: new cdk.aws_kms.Key(stack, 'ImportCertificateCloudWatchKey', {}), - logRetentionInDays: 365, - }); - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudformation/__snapshots__/get-resource-type.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-cloudformation/__snapshots__/get-resource-type.test.ts.snap deleted file mode 100644 index 756c302..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudformation/__snapshots__/get-resource-type.test.ts.snap +++ /dev/null @@ -1,348 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GetCloudFormationResourceType Construct(GetCloudFormationResourceType): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionA13B89C3": { - "DependsOn": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRoleDefaultPolicy7A52BF9E", - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRole450819BD", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Lambda managed policy", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Lambda managed policy", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Get CloudFormation Resources from Stack by LogicalResourceId", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRole450819BD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionLogGroup602C5B86": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionA13B89C3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRole450819BD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Lambda managed policy", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Lambda managed policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRoleDefaultPolicy7A52BF9E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Lambda managed policy", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Lambda managed policy", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "cloudformation:DescribeStackResource", - "Effect": "Allow", - "Resource": "arn:aws:cloudformation:*:333333333333", - "Sid": "cloudformation", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRoleDefaultPolicy7A52BF9E", - "Roles": [ - { - "Ref": "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionServiceRole450819BD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEvent83E12901": { - "DependsOn": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleDefaultPolicyB5AC412E", - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleFB2D11F4", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK generated provider", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK generated provider", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Stack/TestGetCloudFormationResourceType/GetCloudFormationResourceTypeProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionA13B89C3", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleFB2D11F4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleDefaultPolicyB5AC412E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK generated provider", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK generated provider", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionA13B89C3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionA13B89C3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleDefaultPolicyB5AC412E", - "Roles": [ - { - "Ref": "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleFB2D11F4", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEventServiceRoleFB2D11F4": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK generated provider", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK generated provider", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeResource41628DFB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeFunctionLogGroup602C5B86", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TestGetCloudFormationResourceTypeGetCloudFormationResourceTypeProviderframeworkonEvent83E12901", - "Arn", - ], - }, - "logicalResourceId": "TestResource", - "stackName": "AWSAccelerator-TestStack-us-east-1", - }, - "Type": "Custom::GetCloudFormationResourceType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudformation/get-resource-type.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-cloudformation/get-resource-type.test.ts deleted file mode 100644 index e09d3e9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudformation/get-resource-type.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GetCloudFormationResourceType } from '../../lib/aws-cloudformation/get-resource-type'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GetCloudFormationResourceType): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -new GetCloudFormationResourceType(stack, 'TestGetCloudFormationResourceType', { - stackName: 'AWSAccelerator-TestStack-us-east-1', - logicalResourceId: 'TestResource', - logRetentionInDays: 3653, - cloudwatchKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - partition: 'aws', -}); - -/** - * ConfigServiceTags construct test - */ -describe('GetCloudFormationResourceType', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-destination.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-destination.test.ts.snap deleted file mode 100644 index d7ebceb..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-destination.test.ts.snap +++ /dev/null @@ -1,512 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CloudWatchDestination Construct(CloudWatchDestination): Snapshot Test 1`] = ` -{ - "Conditions": { - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions": { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-north-1", - ], - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-northwest-1", - ], - }, - ], - }, - }, - "Resources": { - "CloudWatchDestination47668810": { - "Properties": { - "DestinationName": "AWSAcceleratorCloudWatchToS3", - "DestinationPolicy": { - "Fn::Join": [ - "", - [ - "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":"logs:PutSubscriptionFilter","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":destination:AWSAcceleratorCloudWatchToS3","Condition":{"StringEquals":{"aws:PrincipalOrgID":"o-some-org-id"}}}]}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "CloudWatchDestinationLogsKinesisRole54686795", - "Arn", - ], - }, - "TargetArn": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - "Type": "AWS::Logs::Destination", - }, - "CloudWatchDestinationExistingIamE1F9FD4A": { - "Properties": { - "DestinationName": "AWSAcceleratorCloudWatchToS3", - "DestinationPolicy": { - "Fn::Join": [ - "", - [ - "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":"logs:PutSubscriptionFilter","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":destination:AWSAcceleratorCloudWatchToS3","Condition":{"StringEquals":{"aws:PrincipalOrgID":"o-some-org-id"}}}]}", - ], - ], - }, - "RoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorLogReplicationRole-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "TargetArn": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - "Type": "AWS::Logs::Destination", - }, - "CloudWatchDestinationLogsKinesisRole54686795": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:ListShards", - "kinesis:PutRecord", - "kinesis:PutRecords", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KinesisAccess", - }, - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KmsAccess", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyExistingIam1584FEB7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomStreamE8E9158E": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamExistingIamFE5F8092": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - }, -} -`; - -exports[`CloudWatchDestination Construct(CloudWatchDestination): Snapshot Test 2`] = ` -{ - "Conditions": { - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions": { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-north-1", - ], - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-northwest-1", - ], - }, - ], - }, - }, - "Resources": { - "CloudWatchDestination47668810": { - "Properties": { - "DestinationName": "AWSAcceleratorCloudWatchToS3", - "DestinationPolicy": { - "Fn::Join": [ - "", - [ - "{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["111111111111","222222222222"]},"Action":"logs:PutSubscriptionFilter","Resource":"arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":destination:AWSAcceleratorCloudWatchToS3"}]}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "CloudWatchDestinationLogsKinesisRole54686795", - "Arn", - ], - }, - "TargetArn": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - "Type": "AWS::Logs::Destination", - }, - "CloudWatchDestinationLogsKinesisRole54686795": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:ListShards", - "kinesis:PutRecord", - "kinesis:PutRecords", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KinesisAccess", - }, - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KmsAccess", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomStreamE8E9158E": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-data-protection.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-data-protection.test.ts.snap deleted file mode 100644 index 0fab042..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-data-protection.test.ts.snap +++ /dev/null @@ -1,391 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CloudWatchLogDataProtection Construct(CloudWatchLogDataProtection): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchLogDataProtectionCloudWatchDataProtectionCloudWatchDataProtectionResource070294E0": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionResourceLogGroupFE66A616", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEvent60FCE4B2", - "Arn", - ], - }, - "centralLogBucketName": "aws-accelerator-central-logs", - "identifierNames": [ - "AwsSecretKey", - "OpenSshPrivateKey", - "PgpPrivateKey", - "PkcsPrivateKey", - "PuttyPrivateKey", - ], - "overrideExisting": true, - "partition": { - "Ref": "AWS::Partition", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunction45F668F9": { - "DependsOn": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRoleDefaultPolicy4FB3E4E5", - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRole5D3C0EC2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed CloudWatchDataProtection custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRole5D3C0EC2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionResourceLogGroupFE66A616": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchLogKeyF6569487", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CloudWatchLogDataProtectionCloudWatchDataProtectionFunction45F668F9", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRole5D3C0EC2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRoleDefaultPolicy4FB3E4E5": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:DeleteAccountPolicy", - "logs:DescribeAccountPolicies", - "logs:PutAccountPolicy", - "logs:PutDataProtectionPolicy", - "logs:DeleteDataProtectionPolicy", - "logs:CreateLogDelivery", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CloudWatchAccess", - }, - { - "Action": [ - "s3:GetBucketPolicy", - "s3:PutBucketPolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs", - ], - ], - }, - "Sid": "S3Access", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRoleDefaultPolicy4FB3E4E5", - "Roles": [ - { - "Ref": "CloudWatchLogDataProtectionCloudWatchDataProtectionFunctionServiceRole5D3C0EC2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEvent60FCE4B2": { - "DependsOn": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRoleDefaultPolicyB8DFFB44", - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRole055954B7", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/CloudWatchLogDataProtection/CloudWatchDataProtection/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunction45F668F9", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRole055954B7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRole055954B7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRoleDefaultPolicyB8DFFB44": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunction45F668F9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchLogDataProtectionCloudWatchDataProtectionFunction45F668F9", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRoleDefaultPolicyB8DFFB44", - "Roles": [ - { - "Ref": "CloudWatchLogDataProtectionCloudWatchDataProtectionframeworkonEventServiceRole055954B7", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchLogKeyF6569487": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-group.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-group.test.ts.snap deleted file mode 100644 index 660b145..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-log-group.test.ts.snap +++ /dev/null @@ -1,353 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CloudWatchLogGroups Construct(CloudWatchLogGroups): Snapshot Test 1`] = ` -{ - "Resources": { - "AppTest1CustomCreateLogGroupCustomResourceProviderHandler4FFCDD76": { - "DependsOn": [ - "AppTest1CustomCreateLogGroupCustomResourceProviderRole40E7A5EC", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "AppTest1CustomCreateLogGroupCustomResourceProviderRole40E7A5EC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AppTest1CustomCreateLogGroupCustomResourceProviderLogGroupF4CE828F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AppTest1CustomCreateLogGroupCustomResourceProviderHandler4FFCDD76", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AppTest1CustomCreateLogGroupCustomResourceProviderRole40E7A5EC": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/App/Test1:*", - ], - ], - }, - }, - { - "Action": [ - "kms:DescribeKey", - "kms:ListKeys", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AppTest2CustomCreateLogGroupCustomResourceProviderHandler03798759": { - "DependsOn": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderRole24FD6060", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderRole24FD6060", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AppTest2CustomCreateLogGroupCustomResourceProviderLogGroup7FB49646": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKey9B181885", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AppTest2CustomCreateLogGroupCustomResourceProviderHandler03798759", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "AppTest2CustomCreateLogGroupCustomResourceProviderRole24FD6060": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/App/Test2:*", - ], - ], - }, - }, - { - "Action": [ - "kms:DescribeKey", - "kms:ListKeys", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchKey9B181885": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchLogGroupsCloudWatchLogsResourceB533E7A1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AppTest1CustomCreateLogGroupCustomResourceProviderLogGroupF4CE828F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AppTest1CustomCreateLogGroupCustomResourceProviderHandler4FFCDD76", - "Arn", - ], - }, - "keyArn": "REPLACED-UUID", - "logGroupName": "/App/Test1", - "retention": 30, - }, - "Type": "Custom::CreateLogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchLogGroupsNoKeyCloudWatchLogsResource3EFE207D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderLogGroup7FB49646", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AppTest2CustomCreateLogGroupCustomResourceProviderHandler03798759", - "Arn", - ], - }, - "logGroupName": "/App/Test2", - "retention": 180, - }, - "Type": "Custom::CreateLogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-logs-subscription-filter.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-logs-subscription-filter.test.ts.snap deleted file mode 100644 index 374c5b0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/__snapshots__/cloudwatch-logs-subscription-filter.test.ts.snap +++ /dev/null @@ -1,345 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CloudWatchLogsSubscriptionFilter Construct(CloudWatchLogsSubscriptionFilter): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchLogsSubscriptionFilter90533F2E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - "Arn", - ], - }, - "acceleratorCreatedLogDestinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":111111111111:destination:AWSAcceleratorCloudWatchToS3", - ], - ], - }, - "acceleratorLogKmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "acceleratorLogRetentionInDays": "731", - "acceleratorLogSubscriptionRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/AWSAccelerator-LoggingSta-SubscriptionFilterRole", - ], - ], - }, - "replaceLogDestinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":111111111111:destination:ReplaceDestination", - ], - ], - }, - }, - "Type": "Custom::UpdateSubscriptionFilter", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchLogsSubscriptionFilterExistingIam9E6C2804": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - "Arn", - ], - }, - "acceleratorCreatedLogDestinationArn": "LogRetentionArn", - "acceleratorLogKmsKeyArn": { - "Fn::GetAtt": [ - "CustomKeyExistingIam1584FEB7", - "Arn", - ], - }, - "acceleratorLogRetentionInDays": "731", - "acceleratorLogSubscriptionRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorLogReplicationRole-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "Type": "Custom::UpdateSubscriptionFilter", - "UpdateReplacePolicy": "Delete", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyExistingIam1584FEB7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608": { - "DependsOn": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderLogGroup0738E05B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomUpdateSubscriptionFilterCustomResourceProviderHandler1BAA7608", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomUpdateSubscriptionFilterCustomResourceProviderRole81A1751E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - ], - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":some-acc-id:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-destination.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-destination.test.ts deleted file mode 100644 index 428cd70..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-destination.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CloudWatchDestination } from '../../lib/aws-cloudwatch-logs/cloudwatch-destination'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(CloudWatchDestination): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const cnStack = new cdk.Stack(); - -new CloudWatchDestination(stack, 'CloudWatchDestination', { - kinesisKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - kinesisStream: new cdk.aws_kinesis.Stream(stack, 'CustomStream', {}), - organizationId: 'o-some-org-id', - partition: 'aws', - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', -}); - -new CloudWatchDestination(cnStack, 'CloudWatchDestination', { - kinesisKmsKey: new cdk.aws_kms.Key(cnStack, 'CustomKey', {}), - kinesisStream: new cdk.aws_kinesis.Stream(cnStack, 'CustomStream', {}), - organizationId: 'o-some-org-id', - accountIds: ['111111111111', '222222222222'], - partition: 'aws-cn', - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', -}); - -new CloudWatchDestination(stack, 'CloudWatchDestinationExistingIam', { - kinesisKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyExistingIam', {}), - kinesisStream: new cdk.aws_kinesis.Stream(stack, 'CustomStreamExistingIam', {}), - organizationId: 'o-some-org-id', - partition: 'aws', - useExistingRoles: true, - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * CloudWatchDestination construct test - */ -describe('CloudWatchDestination', () => { - snapShotTest(testNamePrefix, stack); - snapShotTest(testNamePrefix, cnStack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-data-protection.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-data-protection.test.ts deleted file mode 100644 index 51f3751..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-data-protection.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CloudWatchLogDataProtection } from '../../lib/aws-cloudwatch-logs/cloudwatch-log-data-protection'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(CloudWatchLogDataProtection): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new CloudWatchLogDataProtection(stack, 'CloudWatchLogDataProtection', { - centralLogBucketName: 'aws-accelerator-central-logs', - identifierNames: ['AwsSecretKey', 'OpenSshPrivateKey', 'PgpPrivateKey', 'PkcsPrivateKey', 'PuttyPrivateKey'], - overrideExisting: true, - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKey', {}), - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchLogKey', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * CloudWatchLogDataProtection construct test - */ -describe('CloudWatchLogDataProtection', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-group.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-group.test.ts deleted file mode 100644 index 2e33d3a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-log-group.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CloudWatchLogGroups } from '../../lib/aws-cloudwatch-logs/cloudwatch-log-group'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(CloudWatchLogGroups): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new CloudWatchLogGroups(stack, 'CloudWatchLogGroups', { - logGroupName: '/App/Test1', - logRetentionInDays: 30, - keyArn: 'arn:aws:kms:us-east-1:111111111111:key/121ac3b6-8d53-4d8a-a05c-7a0012249950', - customLambdaLogKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - customLambdaLogRetention: 30, -}); - -new CloudWatchLogGroups(stack, 'CloudWatchLogGroupsNoKey', { - logGroupName: '/App/Test2', - logRetentionInDays: 180, - customLambdaLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKey', {}), - customLambdaLogRetention: 30, -}); - -/** - * CloudWatchLogGroups construct test - */ -describe('CloudWatchLogGroups', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.test.ts deleted file mode 100644 index 781ecbf..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CloudWatchLogsSubscriptionFilter } from '../../lib/aws-cloudwatch-logs/cloudwatch-logs-subscription-filter'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(CloudWatchLogsSubscriptionFilter): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new CloudWatchLogsSubscriptionFilter(stack, 'CloudWatchLogsSubscriptionFilter', { - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logDestinationArn: `arn:${stack.partition}:logs:${stack.region}:111111111111:destination:AWSAcceleratorCloudWatchToS3`, - logsRetentionInDays: '731', - subscriptionFilterRoleArn: `arn:${stack.partition}:iam::111111111111:role/AWSAccelerator-LoggingSta-SubscriptionFilterRole`, - logArchiveAccountId: 'some-acc-id', - replaceLogDestinationArn: `arn:${stack.partition}:logs:${stack.region}:111111111111:destination:ReplaceDestination`, - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, -}); -new CloudWatchLogsSubscriptionFilter(stack, 'CloudWatchLogsSubscriptionFilterExistingIam', { - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyExistingIam', {}), - logDestinationArn: 'LogRetentionArn', - logsRetentionInDays: '731', - subscriptionFilterRoleArn: 'testString', - logArchiveAccountId: 'some-acc-id', - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: true, -}); - -/** - * CloudWatchDestination construct test - */ -describe('CloudWatchLogsSubscriptionFilter', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-aggregation.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-aggregation.test.ts.snap deleted file mode 100644 index 7fb7697..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-aggregation.test.ts.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfigAggregation Construct(ConfigAggregation): Snapshot Test 1`] = ` -{ - "Resources": { - "TestConfigAggregationConfigAggregatorE086B6C8": { - "Properties": { - "ConfigurationAggregatorName": "AWSAccelerator-Aggregator", - "OrganizationAggregationSource": { - "AllAwsRegions": true, - "RoleArn": { - "Fn::GetAtt": [ - "TestConfigAggregationConfigAggregatorRole43BBA16A", - "Arn", - ], - }, - }, - }, - "Type": "AWS::Config::ConfigurationAggregator", - }, - "TestConfigAggregationConfigAggregatorRole43BBA16A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "config.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by the AWS Config Service aggregation to use organization resources", - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSConfigRoleForOrganizations", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-recorder.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-recorder.test.ts.snap deleted file mode 100644 index e554fff..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-recorder.test.ts.snap +++ /dev/null @@ -1,364 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfigServiceRecorder Construct(ConfigServiceDeliveryChannel): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "S3Key58EFA89C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestConfigServiceRecorderConfigServiceRecorderFunction78E08DB1": { - "DependsOn": [ - "TestConfigServiceRecorderConfigServiceRecorderFunctionRoleAEDBB1F8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create/Update Config Recorder", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "TestConfigServiceRecorderConfigServiceRecorderFunctionRoleAEDBB1F8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "TestConfigServiceRecorderConfigServiceRecorderFunctionLogGroupC69A3948": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "TestConfigServiceRecorderConfigServiceRecorderFunction78E08DB1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "TestConfigServiceRecorderConfigServiceRecorderFunctionRoleAEDBB1F8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "config:DeleteDeliveryChannel", - "config:DescribeConfigurationRecorders", - "config:DescribeDeliveryChannelStatus", - "config:PutConfigurationRecorder", - "config:PutDeliveryChannel", - "config:StartConfigurationRecorder", - "config:StopConfigurationRecorder", - "config:DeleteConfigurationRecorder", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "configService", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": "arn:aws:iam::123456789012:role/TestRole", - "Sid": "iam", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": "arn:aws:iam::123456789012:role/TestRole", - "Sid": "sts", - }, - { - "Action": [ - "s3:PutObject*", - "s3:GetBucketACL", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::accelerator-central-logs", - "arn:aws:s3:::accelerator-central-logs/*", - ], - "Sid": "s3", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "configRecorder", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventDE69D09A": { - "DependsOn": [ - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRoleDefaultPolicy1BE35F7C", - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRole688F5427", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Stack/TestConfigServiceRecorder/ConfigServiceRecorderProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestConfigServiceRecorderConfigServiceRecorderFunction78E08DB1", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRole688F5427", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRole688F5427": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRoleDefaultPolicy1BE35F7C": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestConfigServiceRecorderConfigServiceRecorderFunction78E08DB1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestConfigServiceRecorderConfigServiceRecorderFunction78E08DB1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRoleDefaultPolicy1BE35F7C", - "Roles": [ - { - "Ref": "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventServiceRole688F5427", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestConfigServiceRecorderConfigServiceRecorderResource1FA3006B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "TestConfigServiceRecorderConfigServiceRecorderFunctionLogGroupC69A3948", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TestConfigServiceRecorderConfigServiceRecorderProviderframeworkonEventDE69D09A", - "Arn", - ], - }, - "recorderRoleArn": "arn:aws:iam::123456789012:role/TestRole", - "s3BucketKmsKeyArn": { - "Fn::GetAtt": [ - "S3Key58EFA89C", - "Arn", - ], - }, - "s3BucketName": "accelerator-central-logs", - }, - "Type": "Custom::ConfigServiceRecorder", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-tags.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-tags.test.ts.snap deleted file mode 100644 index 4703da1..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/config-tags.test.ts.snap +++ /dev/null @@ -1,165 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfigServiceTags Construct(ConfigServiceTags): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomConfigServiceTagsCustomResourceProviderHandler159E7D96": { - "DependsOn": [ - "CustomConfigServiceTagsCustomResourceProviderRoleDC1C2A56", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomConfigServiceTagsCustomResourceProviderRoleDC1C2A56", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomConfigServiceTagsCustomResourceProviderLogGroupF7260892": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomConfigServiceTagsCustomResourceProviderHandler159E7D96", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomConfigServiceTagsCustomResourceProviderRoleDC1C2A56": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "config:TagResource", - "config:UntagResource", - ], - "Effect": "Allow", - "Resource": "arn:aws:config:*:333333333333:config-rule/*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestConfigRule7F50797E": { - "Properties": { - "ConfigRuleName": "test-rule-name", - "Description": "test-description", - "Source": { - "Owner": "AWS", - "SourceIdentifier": "test-identifier", - }, - }, - "Type": "AWS::Config::ConfigRule", - }, - "TestConfigServiceTagsCB5621EB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomConfigServiceTagsCustomResourceProviderLogGroupF7260892", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomConfigServiceTagsCustomResourceProviderHandler159E7D96", - "Arn", - ], - }, - "resourceArn": { - "Fn::GetAtt": [ - "TestConfigRule7F50797E", - "Arn", - ], - }, - "tags": [ - { - "Key": "key", - "Value": "value", - }, - ], - }, - "Type": "Custom::ConfigServiceTags", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/detect-resource-policy.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/detect-resource-policy.test.ts.snap deleted file mode 100644 index dc603d4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/detect-resource-policy.test.ts.snap +++ /dev/null @@ -1,385 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RevertScpChanges Construct(DetectResourcePolicy): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomCloudWatchKey720588E1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "DetectResourcePolicyDetectResourcePolicyFunction3130D833": { - "DependsOn": [ - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D", - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to detect non-compliant resource policy", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-west-2", - }, - }, - "Handler": "detect-resource-policy.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionLogGroup68C3F428": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCloudWatchKey720588E1", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "DetectResourcePolicyDetectResourcePolicyFunction3130D833", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "lex:DescribeResourcePolicy", - "apigateway:GET", - "lambda:GetPolicy", - "backup:GetBackupVaultAccessPolicy", - "codeartifact:GetRepositoryPermissionsPolicy", - "events:DescribeEventBus", - "acm-pca:GetPolicy", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lex:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":apigateway:", - { - "Ref": "AWS::Region", - }, - "::*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":backup:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codeartifact:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":events:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":acm-pca:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleDefaultPolicy17595A0D", - "Roles": [ - { - "Ref": "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "DetectResourcePolicyDetectResourcePolicyFunctionServiceRoleE9AC0FD8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/remediate-resource-policy.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/remediate-resource-policy.test.ts.snap deleted file mode 100644 index 24d8d47..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/__snapshots__/remediate-resource-policy.test.ts.snap +++ /dev/null @@ -1,574 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RemediateResourcePolicy Construct(RemediateResourcePolicy): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomCloudWatchKey720588E1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "DetectResourcePolicyRemediateResourcePolicyFunction701E8B9E": { - "DependsOn": [ - "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRoleDefaultPolicy6470C516", - "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRole269D8709", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to remediate non-compliant resource based policy", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-west-2", - }, - }, - "Handler": "remediate-resource-policy.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRole269D8709", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "DetectResourcePolicyRemediateResourcePolicyFunctionLogGroup45217A8F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCloudWatchKey720588E1", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "DetectResourcePolicyRemediateResourcePolicyFunction701E8B9E", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRole269D8709": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRoleDefaultPolicy6470C516": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:getRole", - "iam:updateAssumeRolePolicy", - "s3:GetBucketPolicy", - "s3:PutBucketPolicy", - "kms:GetKeyPolicy", - "kms:PutKeyPolicy", - "kms:DescribeKey", - "secretsmanager:GetResourcePolicy", - "secretsmanager:PutResourcePolicy", - "secretsmanager:ValidateResourcePolicy", - "lex:DescribeResourcePolicy", - "lex:UpdateResourcePolicy", - "lex:CreateResourcePolicy", - "apigateway:UpdateRestApiPolicy", - "apigateway:GET", - "apigateway:PATCH", - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "es:ESHttpGet", - "es:ESHttpPut", - "es:DescribeDomainConfig", - "es:UpdateDomainConfig", - "sns:GetTopicAttributes", - "sns:SetTopicAttributes", - "sqs:GetQueueAttributes", - "sqs:SetQueueAttributes", - "elasticfilesystem:DescribeFileSystemPolicy", - "elasticfilesystem:PutFileSystemPolicy", - "codeartifact:GetDomainPermissionsPolicy", - "codeartifact:GetRepositoryPermissionsPolicy", - "codeartifact:PutDomainPermissionsPolicy", - "codeartifact:PutRepositoryPermissionsPolicy", - "events:DescribeEventBus", - "events:PutPermission", - "backup:GetBackupVaultAccessPolicy", - "backup:PutBackupVaultAccessPolicy", - "acm-pca:PutPolicy", - "acm-pca:GetPolicy", - "lambda:GetPolicy", - "lambda:AddPermission", - "lambda:RemovePermission", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lex:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":apigateway:", - { - "Ref": "AWS::Region", - }, - "::*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ecr:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":es:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sqs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":elasticfilesystem:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codeartifact:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":backup:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":events:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":acm-pca:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "config:BatchGetResourceConfig", - "config:SelectResourceConfig", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRoleDefaultPolicy6470C516", - "Roles": [ - { - "Ref": "DetectResourcePolicyRemediateResourcePolicyFunctionServiceRole269D8709", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-aggregation.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-aggregation.test.ts deleted file mode 100644 index 05f344c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-aggregation.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ConfigAggregation } from '../../lib/aws-configservice/config-aggregation'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ConfigAggregation): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -new ConfigAggregation(stack, 'TestConfigAggregation', { - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * ConfigServiceTags construct test - */ -describe('ConfigAggregation', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-recorder.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-recorder.test.ts deleted file mode 100644 index 84d097f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-recorder.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ConfigServiceRecorder } from '../../lib/aws-configservice/config-recorder'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ConfigServiceDeliveryChannel): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -new ConfigServiceRecorder(stack, 'TestConfigServiceRecorder', { - s3BucketName: 'accelerator-central-logs', - s3BucketKmsKey: new cdk.aws_kms.Key(stack, 'S3Key', {}), - configRecorderRoleArn: 'arn:aws:iam::123456789012:role/TestRole', - logRetentionInDays: 3653, - cloudwatchKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - lambdaKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKey', {}), - partition: 'aws', - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * ConfigServiceTags construct test - */ -describe('ConfigServiceRecorder', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-tags.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-tags.test.ts deleted file mode 100644 index 6f98871..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/config-tags.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ConfigServiceTags } from '../../lib/aws-configservice/config-tags'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ConfigServiceTags): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -const configRule = new cdk.aws_config.ManagedRule(stack, 'TestConfigRule', { - configRuleName: 'test-rule-name', - description: 'test-description', - identifier: 'test-identifier', - // inputParameters: this.getRuleParameters(rule.name, rule.inputParameters), - // ruleScope: { - // resourceTypes, - // }, -}); - -new ConfigServiceTags(stack, 'TestConfigServiceTags', { - resourceArn: configRule.configRuleArn, - tags: [{ Key: 'key', Value: 'value' }], - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - partition: 'aws', - accountId: env.account, -}); - -/** - * ConfigServiceTags construct test - */ -describe('ConfigServiceTags', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/detect-resource-policy.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-configservice/detect-resource-policy.test.ts deleted file mode 100644 index ecd8477..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/detect-resource-policy.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DetectResourcePolicy } from '../../lib/data-perimeter/detect-resource-policy'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DetectResourcePolicy): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const configDirPath = `${__dirname}/../../../accelerator/test/configs/all-enabled`; - -new DetectResourcePolicy(stack, 'DetectResourcePolicy', { - acceleratorPrefix: 'AWSAccelerator', - configDirPath: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - homeRegion: 'us-west-2', - kmsKeyCloudWatch: new cdk.aws_kms.Key(stack, 'CustomCloudWatchKey', {}), - kmsKeyLambda: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - logRetentionInDays: 365, - rbpFilePaths: [ - { - name: 'S3', - path: 'resource-policies/s3.json', - tempPath: `${configDirPath}/resource-policies/s3.json`, - }, - { - name: 'IAM', - path: 'resource-policies/iam.json', - tempPath: `${configDirPath}/resource-policies/iam.json`, - }, - { - name: 'KMS', - path: 'resource-policies/kms.json', - tempPath: `${configDirPath}/resource-policies/kms.json`, - }, - ], -}); - -/** - * RevertScpChanges construct test - */ -describe('RevertScpChanges', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-configservice/remediate-resource-policy.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-configservice/remediate-resource-policy.test.ts deleted file mode 100644 index bf12bb4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-configservice/remediate-resource-policy.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { RemediateResourcePolicy } from '../../lib/data-perimeter/remediate-resource-policy'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(RemediateResourcePolicy): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const configDirPath = `${__dirname}/../../../accelerator/test/configs/all-enabled`; - -new RemediateResourcePolicy(stack, 'DetectResourcePolicy', { - acceleratorPrefix: 'AWSAccelerator', - configDirPath: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - homeRegion: 'us-west-2', - kmsKeyCloudWatch: new cdk.aws_kms.Key(stack, 'CustomCloudWatchKey', {}), - kmsKeyLambda: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - logRetentionInDays: 365, - rbpFilePaths: [ - { - name: 'S3', - path: 'resource-policies/s3.json', - tempPath: `${configDirPath}/resource-policies/s3.json`, - }, - { - name: 'IAM', - path: 'resource-policies/iam.json', - tempPath: `${configDirPath}/resource-policies/iam.json`, - }, - { - name: 'KMS', - path: 'resource-policies/kms.json', - tempPath: `${configDirPath}/resource-policies/kms.json`, - }, - ], -}); - -/** - * RevertScpChanges construct test - */ -describe('RemediateResourcePolicy', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-controltower/__snapshots__/create-accounts.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-controltower/__snapshots__/create-accounts.test.ts.snap deleted file mode 100644 index 128abe0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-controltower/__snapshots__/create-accounts.test.ts.snap +++ /dev/null @@ -1,965 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfigServiceTags Construct(ConfigServiceTags): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "NewCTAccountsE326CD07": { - "DeletionPolicy": "Delete", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "accountEmail", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "accountEmail", - "KeyType": "HASH", - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "TableKeyF581D96F", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Delete", - }, - "TableKeyF581D96F": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete47A55521": { - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleDefaultPolicyD53F8A69", - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRole4FC767AD", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - isComplete (Stack/TestCreateCTAccounts/CreateControlTowerAcccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - }, - }, - "Handler": "framework.isComplete", - "Role": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRole4FC767AD", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRole4FC767AD": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleDefaultPolicyD53F8A69": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRoleDefaultPolicyD53F8A69", - "Roles": [ - { - "Ref": "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisCompleteServiceRole4FC767AD", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventF4BABDE5": { - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleDefaultPolicy2744460D", - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleF177CF40", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Stack/TestCreateCTAccounts/CreateControlTowerAcccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - "WAITER_STATE_MACHINE_ARN": { - "Ref": "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachine4FC44469", - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleF177CF40", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleDefaultPolicy2744460D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "states:StartExecution", - "Effect": "Allow", - "Resource": { - "Ref": "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachine4FC44469", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleDefaultPolicy2744460D", - "Roles": [ - { - "Ref": "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleF177CF40", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventServiceRoleF177CF40": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutCA292AA3": { - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy58DCA1E0", - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8F9F9F14", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onTimeout (Stack/TestCreateCTAccounts/CreateControlTowerAcccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onTimeout", - "Role": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8F9F9F14", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8F9F9F14": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy58DCA1E0": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy58DCA1E0", - "Roles": [ - { - "Ref": "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutServiceRole8F9F9F14", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachine4FC44469": { - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleDefaultPolicy766F8603", - "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleC1F4B90C", - ], - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{"StartAt":"framework-isComplete-task","States":{"framework-isComplete-task":{"End":true,"Retry":[{"ErrorEquals":["States.ALL"],"IntervalSeconds":30,"MaxAttempts":480,"BackoffRate":1}],"Catch":[{"ErrorEquals":["States.ALL"],"Next":"framework-onTimeout-task"}],"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete47A55521", - "Arn", - ], - }, - ""},"framework-onTimeout-task":{"End":true,"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutCA292AA3", - "Arn", - ], - }, - ""}}}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleC1F4B90C", - "Arn", - ], - }, - }, - "Type": "AWS::StepFunctions::StateMachine", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleC1F4B90C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "states.us-east-1.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleDefaultPolicy766F8603": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete47A55521", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkisComplete47A55521", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutCA292AA3", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonTimeoutCA292AA3", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleDefaultPolicy766F8603", - "Roles": [ - { - "Ref": "TestCreateCTAccountsCreateControlTowerAcccountsProviderwaiterstatemachineRoleC1F4B90C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C": { - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAccountServiceRoleCCD1613F", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Control Tower Account onEvent handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountServiceRoleCCD1613F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "TestCreateCTAccountsCreateControlTowerAccountLogGroup92213B11": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "TestCreateCTAccountsCreateControlTowerAccount8CC34F3C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "TestCreateCTAccountsCreateControlTowerAccountServiceRoleCCD1613F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC": { - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRoleDefaultPolicy35D63657", - "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRole8C15A4DF", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Control Tower Account isComplete handler", - "Environment": { - "Variables": { - "NewAccountsTableName": { - "Ref": "NewCTAccountsE326CD07", - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRole8C15A4DF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "TestCreateCTAccountsCreateControlTowerAccountStatusLogGroupE10D1958": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "TestCreateCTAccountsCreateControlTowerAccountStatus08A35AEC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRole8C15A4DF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AWSServiceCatalogEndUserFullAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRoleDefaultPolicy35D63657": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "dynamodb:Scan", - "dynamodb:GetItem", - "dynamodb:DeleteItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NewCTAccountsE326CD07", - "Arn", - ], - }, - "Sid": "DynamoDb", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TableKeyF581D96F", - "Arn", - ], - }, - "Sid": "KMS", - }, - { - "Action": [ - "controltower:CreateManagedAccount", - "controltower:SetupLandingZone", - "controltower:EnableGuardrail", - "controltower:Describe*", - "controltower:Get*", - "controltower:List*", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ControlTower", - }, - { - "Action": [ - "sso-directory:DescribeDirectory", - "sso-directory:CreateUser", - "sso-directory:SearchUsers", - "sso-directory:SearchGroups", - "sso:ListDirectoryAssociations", - "sso:DescribeRegisteredRegions", - "sso:ListProfileAssociations", - "sso:AssociateProfile", - "sso:GetProfile", - "sso:CreateProfile", - "sso:UpdateProfile", - "sso:GetTrust", - "sso:CreateTrust", - "sso:UpdateTrust", - "sso:GetApplicationInstance", - "sso:CreateApplicationInstance", - "sso:ListPermissionSets", - "sso:GetSSOStatus", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SSO", - }, - { - "Action": [ - "servicecatalog:SearchProvisionedProducts", - "servicecatalog:ProvisionProduct", - "servicecatalog:DescribeProduct", - "servicecatalog:ListProvisioningArtifacts", - "servicecatalog:DescribeProvisionedProduct", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRoleDefaultPolicy35D63657", - "Roles": [ - { - "Ref": "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRole8C15A4DF", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestCreateCTAccountsEE75074E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "TestCreateCTAccountsCreateControlTowerAccountLogGroup92213B11", - "TestCreateCTAccountsCreateControlTowerAccountStatusLogGroupE10D1958", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAcccountsProviderframeworkonEventF4BABDE5", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateControlTowerAccounts", - "UpdateReplacePolicy": "Delete", - }, - "TestCreateCTAccountsLambdaPrincipalAssociation53AF43AE": { - "Properties": { - "PortfolioId": "asdf1234", - "PrincipalARN": { - "Fn::GetAtt": [ - "TestCreateCTAccountsCreateControlTowerAccountStatusServiceRole8C15A4DF", - "Arn", - ], - }, - "PrincipalType": "IAM", - }, - "Type": "AWS::ServiceCatalog::PortfolioPrincipalAssociation", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-controltower/create-accounts.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-controltower/create-accounts.test.ts deleted file mode 100644 index acadb46..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-controltower/create-accounts.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CreateControlTowerAccounts } from '../../lib/aws-controltower/create-accounts'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ConfigServiceTags): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -const newCTAccountsTable = new cdk.aws_dynamodb.Table(stack, 'NewCTAccounts', { - partitionKey: { name: 'accountEmail', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: new cdk.aws_kms.Key(stack, 'TableKey', {}), - removalPolicy: cdk.RemovalPolicy.DESTROY, - pointInTimeRecovery: true, -}); - -new CreateControlTowerAccounts(stack, 'TestCreateCTAccounts', { - table: newCTAccountsTable, - portfolioId: 'asdf1234', - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * ConfigServiceTags construct test - */ -describe('ConfigServiceTags', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cur/__snapshots__/report-definition.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-cur/__snapshots__/report-definition.test.ts.snap deleted file mode 100644 index bac49fb..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cur/__snapshots__/report-definition.test.ts.snap +++ /dev/null @@ -1,417 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ReportDefinition Construct(ReportDefinition): Snapshot Test 1`] = ` -{ - "Resources": { - "NativeStack5E60C508": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestBucket560B80BC": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "TestBucketPolicyBA12ED38": { - "Properties": { - "Bucket": { - "Ref": "TestBucket560B80BC", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetBucketAcl", - "s3:GetBucketPolicy", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "TestBucket560B80BC", - "Arn", - ], - }, - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestBucket560B80BC", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "TestReportDefinition9701AAC4": { - "DependsOn": [ - "TestBucketPolicyBA12ED38", - ], - "Properties": { - "Compression": "Parquet", - "Format": "Parquet", - "RefreshClosedReports": true, - "ReportName": "Test", - "ReportVersioning": "OVERWRITE_REPORT", - "S3Bucket": { - "Ref": "TestBucket560B80BC", - }, - "S3Prefix": "test", - "S3Region": "us-east-1", - "TimeUnit": "DAILY", - }, - "Type": "AWS::CUR::ReportDefinition", - }, - }, -} -`; - -exports[`ReportDefinition Construct(ReportDefinition): Snapshot Test 2`] = ` -{ - "Resources": { - "CustomCrossRegionReportDefinitionCustomResourceProviderHandler8E3AEE17": { - "DependsOn": [ - "CustomCrossRegionReportDefinitionCustomResourceProviderRole845A4C3A", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-west-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomCrossRegionReportDefinitionCustomResourceProviderRole845A4C3A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomCrossRegionReportDefinitionCustomResourceProviderLogGroupC64E88D7": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomCrossRegionReportDefinitionCustomResourceProviderHandler8E3AEE17", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomCrossRegionReportDefinitionCustomResourceProviderRole845A4C3A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "cur:DeleteReportDefinition", - "cur:ModifyReportDefinition", - "cur:PutReportDefinition", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestBucket560B80BC": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "TestBucketPolicyBA12ED38": { - "Properties": { - "Bucket": { - "Ref": "TestBucket560B80BC", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetBucketAcl", - "s3:GetBucketPolicy", - ], - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "TestBucket560B80BC", - "Arn", - ], - }, - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "aws:SourceAccount": { - "Ref": "AWS::AccountId", - }, - "aws:SourceArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cur:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":definition/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "billingreports.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestBucket560B80BC", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "TestReportDefinition9701AAC4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomCrossRegionReportDefinitionCustomResourceProviderLogGroupC64E88D7", - "TestBucketPolicyBA12ED38", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomCrossRegionReportDefinitionCustomResourceProviderHandler8E3AEE17", - "Arn", - ], - }, - "reportDefinition": { - "AdditionalSchemaElements": [], - "Compression": "Parquet", - "Format": "Parquet", - "RefreshClosedReports": true, - "ReportName": "Test", - "ReportVersioning": "OVERWRITE_REPORT", - "S3Bucket": { - "Ref": "TestBucket560B80BC", - }, - "S3Prefix": "test", - "S3Region": "us-west-1", - "TimeUnit": "DAILY", - }, - }, - "Type": "Custom::CrossRegionReportDefinition", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-cur/report-definition.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-cur/report-definition.test.ts deleted file mode 100644 index 42b6a4b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-cur/report-definition.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ReportDefinition } from '../../lib/aws-cur/report-definition'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ReportDefinition): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const nativeEnv = { account: '333333333333', region: 'us-east-1' }; -const nativeStack = new cdk.Stack(app, 'NativeStack', { env: nativeEnv }); -const nativeBucket = new cdk.aws_s3.Bucket(nativeStack, 'TestBucket'); -const nativeKey = new cdk.aws_kms.Key(nativeStack, 'NativeStack', {}); - -// Create stack for custom Cfn construct -const customEnv = { account: '333333333333', region: 'us-west-1' }; -const customStack = new cdk.Stack(app, 'CustomStack', { env: customEnv }); -const customBucket = new cdk.aws_s3.Bucket(customStack, 'TestBucket'); -const customKey = new cdk.aws_kms.Key(customStack, 'CustomKey', {}); - -// Create report definitions for each stack -new ReportDefinition(nativeStack, 'TestReportDefinition', { - compression: 'Parquet', - format: 'Parquet', - refreshClosedReports: true, - reportName: 'Test', - reportVersioning: 'OVERWRITE_REPORT', - s3Bucket: nativeBucket, - s3Prefix: 'test', - s3Region: cdk.Stack.of(nativeStack).region, - timeUnit: 'DAILY', - kmsKey: nativeKey, - logRetentionInDays: 3653, - partition: 'aws', -}); - -new ReportDefinition(customStack, 'TestReportDefinition', { - compression: 'Parquet', - format: 'Parquet', - refreshClosedReports: true, - reportName: 'Test', - reportVersioning: 'OVERWRITE_REPORT', - s3Bucket: customBucket, - s3Prefix: 'test', - s3Region: cdk.Stack.of(customStack).region, - timeUnit: 'DAILY', - kmsKey: customKey, - logRetentionInDays: 3653, - partition: 'aws', -}); - -/** - * ReportDefinition construct test - */ -describe('ReportDefinition', () => { - snapShotTest(testNamePrefix, nativeStack); - snapShotTest(testNamePrefix, customStack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-graph-config.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-graph-config.test.ts.snap deleted file mode 100644 index ff82c8e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-graph-config.test.ts.snap +++ /dev/null @@ -1,200 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DetectiveGraphConfig Construct(DetectiveGraphConfig): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1": { - "DependsOn": [ - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderLogGroupDF150426": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomDetectiveUpdateGraphCustomResourceProviderRole54CD7295": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveConfigureOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:UpdateOrganizationConfiguration", - "detective:ListGraphs", - "detective:ListMembers", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveUpdateGraphTaskDetectiveActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "DetectiveGraphConfig248C4B9F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveUpdateGraphCustomResourceProviderLogGroupDF150426", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveUpdateGraphCustomResourceProviderHandlerD4473EC1", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::DetectiveUpdateGraph", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-members.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-members.test.ts.snap deleted file mode 100644 index 88799af..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-members.test.ts.snap +++ /dev/null @@ -1,180 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DetectiveMembers Construct(DetectiveMembers): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D": { - "DependsOn": [ - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveCreateMembersCustomResourceProviderLogGroup8A5563BD": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomDetectiveCreateMembersCustomResourceProviderRole90BCDD0D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "detective:ListOrganizationAdminAccounts", - "detective:UpdateOrganizationConfiguration", - "detective:CreateMembers", - "detective:DeleteMembers", - "detective:DisassociateMembership", - "detective:ListMembers", - "detective:ListGraphs", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveCreateMembersTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - { - "Action": [ - "organizations:ListAccounts", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "OrganisationsListDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "DetectiveMembers42A16137": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveCreateMembersCustomResourceProviderLogGroup8A5563BD", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveCreateMembersCustomResourceProviderHandler0A0D060D", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::DetectiveCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-organization-admin-account.test.ts.snap deleted file mode 100644 index 83bdba6..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-detective/__snapshots__/detective-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,215 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DetectiveOrganizationAdminAccount Construct(DetectiveOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1": { - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderRoleF6060FD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "detective.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "detective.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "detective.amazonaws.com", - ], - "organizations:ListAccounts": [ - "detective.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "detective.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "detective.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "detective.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "detective.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccounts", - "detective:DisableOrganizationAdminAccount", - "detective:EnableOrganizationAdminAccount", - "detective:ListOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DetectiveEnableOrganizationAdminAccountTaskDetectiveActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "ServiceLinkedRoleDetective", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "DetectiveOrganizationAdminAccountD12FBDDC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderLogGroupD963AACC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDetectiveEnableOrganizationAdminAccountCustomResourceProviderHandlerAC80FDA1", - "Arn", - ], - }, - "adminAccountId": { - "Ref": "AWS::AccountId", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::DetectiveEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-graph-config.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-graph-config.test.ts deleted file mode 100644 index dbf17ad..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-graph-config.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DetectiveGraphConfig } from '../../lib/aws-detective/detective-graph-config'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DetectiveGraphConfig): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new DetectiveGraphConfig(stack, 'DetectiveGraphConfig', { - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * DetectiveGraphConfig construct test - */ -describe('DetectiveGraphConfig', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-members.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-members.test.ts deleted file mode 100644 index dd69be4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-members.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DetectiveMembers } from '../../lib/aws-detective/detective-members'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DetectiveMembers): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new DetectiveMembers(stack, 'DetectiveMembers', { - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * DetectiveMembers construct test - */ -describe('DetectiveMembers', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-organization-admin-account.test.ts deleted file mode 100644 index add1e19..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-detective/detective-organization-admin-account.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DetectiveOrganizationAdminAccount } from '../../lib/aws-detective/detective-organization-admin-account'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DetectiveOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new DetectiveOrganizationAdminAccount(stack, 'DetectiveOrganizationAdminAccount', { - adminAccountId: stack.account, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * DetectiveOrganizationAdminAccount construct test - */ -describe('DetectiveOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/direct-connect-gateway.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/direct-connect-gateway.test.ts.snap deleted file mode 100644 index ff35b68..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/direct-connect-gateway.test.ts.snap +++ /dev/null @@ -1,152 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DirectConnectGateway Construct(DirectConnectGateway): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDirectConnectGatewayCustomResourceProviderHandler82F9382C": { - "DependsOn": [ - "CustomDirectConnectGatewayCustomResourceProviderRole60C5A7B2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayCustomResourceProviderRole60C5A7B2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectGatewayCustomResourceProviderLogGroup242F4324": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectGatewayCustomResourceProviderHandler82F9382C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayCustomResourceProviderRole60C5A7B2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreateDirectConnectGateway", - "directconnect:DeleteDirectConnectGateway", - "directconnect:UpdateDirectConnectGateway", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DirectConnectGatewayCRUD", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestDxGatewayD6A07DBD": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectGatewayCustomResourceProviderLogGroup242F4324", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayCustomResourceProviderHandler82F9382C", - "Arn", - ], - }, - "asn": 65000, - "gatewayName": "TestDxGateway", - }, - "Type": "Custom::DirectConnectGateway", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/gateway-association.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/gateway-association.test.ts.snap deleted file mode 100644 index 138194d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/gateway-association.test.ts.snap +++ /dev/null @@ -1,296 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DirectConnectGatewayAssociation Construct(DirectConnectGateway): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92": { - "DependsOn": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderLogGroup966224A3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayAssociationCustomResourceProviderRole7D012188": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreateDirectConnectGatewayAssociation", - "directconnect:DeleteDirectConnectGatewayAssociation", - "directconnect:DescribeDirectConnectGatewayAssociations", - "directconnect:UpdateDirectConnectGatewayAssociation", - "ec2:DescribeTransitGatewayAttachments", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DirectConnectGatewayCRUD", - }, - { - "Action": [ - "lambda:InvokeFunction", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:AWSAccelerator-NetworkAss-CustomDirectConnect*", - ], - ], - }, - "Sid": "InvokeSelf", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderHandlerD4E4D07C": { - "DependsOn": [ - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderRole70304A6E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderRole70304A6E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderLogGroupAB723DE4": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderHandlerD4E4D07C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderRole70304A6E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreateDirectConnectGatewayAssociationProposal", - "directconnect:DeleteDirectConnectGatewayAssociationProposal", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GatewayAssociationProposalCRUD", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestDxGatewayAssociaionProposal7B0D5FAD": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderLogGroupAB723DE4", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationProposalCustomResourceProviderHandlerD4E4D07C", - "Arn", - ], - }, - "allowedPrefixes": [ - "0.0.0.0/0", - ], - "directConnectGatewayId": "test-dxgw-id", - "directConnectGatewayOwnerAccount": "111111111", - "gatewayId": "test-tgw-id", - }, - "Type": "Custom::DirectConnectGatewayAssociationProposal", - "UpdateReplacePolicy": "Delete", - }, - "TestDxGatewayAssociationBCCCDE09": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderLogGroup966224A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectGatewayAssociationCustomResourceProviderHandler3BC99D92", - "Arn", - ], - }, - "allowedPrefixes": [ - "0.0.0.0/0", - ], - "directConnectGatewayId": "test-dxgw-id", - "gatewayId": "test-tgw-id", - }, - "Type": "Custom::DirectConnectGatewayAssociation", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/virtual-interface.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/virtual-interface.test.ts.snap deleted file mode 100644 index ab93ed0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/__snapshots__/virtual-interface.test.ts.snap +++ /dev/null @@ -1,310 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VirtualInterface Construct(VirtualInterface): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderHandler58CF9005": { - "DependsOn": [ - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderRoleBC904CA4", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderRoleBC904CA4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderLogGroup29B00939": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderHandler58CF9005", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderRoleBC904CA4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:AllocatePrivateVirtualInterface", - "directconnect:AllocateTransitVirtualInterface", - "directconnect:DeleteVirtualInterface", - "directconnect:TagResource", - "directconnect:UpdateVirtualInterfaceAttributes", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DxVirtualInterfaceAllocateCRUD", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomDirectConnectVirtualInterfaceCustomResourceProviderHandler147DBFD9": { - "DependsOn": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderRoleD8C9D582", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderRoleD8C9D582", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDirectConnectVirtualInterfaceCustomResourceProviderLogGroup51E4607A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDirectConnectVirtualInterfaceCustomResourceProviderHandler147DBFD9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDirectConnectVirtualInterfaceCustomResourceProviderRoleD8C9D582": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "directconnect:CreatePrivateVirtualInterface", - "directconnect:CreateTransitVirtualInterface", - "directconnect:DeleteVirtualInterface", - "directconnect:DescribeVirtualInterfaces", - "directconnect:TagResource", - "directconnect:UntagResource", - "directconnect:UpdateVirtualInterfaceAttributes", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DxVirtualInterfaceCRUD", - }, - { - "Action": [ - "lambda:InvokeFunction", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:AWSAccelerator-NetworkPre-CustomDirectConnect*", - ], - ], - }, - "Sid": "InvokeSelf", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestVif905D8106": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderLogGroup51E4607A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectVirtualInterfaceCustomResourceProviderHandler147DBFD9", - "Arn", - ], - }, - "addressFamily": "ipv4", - "connectionId": "test-dx-conn-id", - "customerAsn": 65000, - "directConnectGatewayId": "test-dxgw-id", - "enableSiteLink": false, - "interfaceName": "test-vif", - "jumboFrames": false, - "region": "us-east-1", - "type": "transit", - "vlan": 300, - }, - "Type": "Custom::DirectConnectVirtualInterface", - "UpdateReplacePolicy": "Delete", - }, - "TestVifAllocation5BE00E39": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderLogGroup29B00939", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDirectConnectVirtualInterfaceAllocationCustomResourceProviderHandler58CF9005", - "Arn", - ], - }, - "addressFamily": "ipv4", - "connectionId": "test-dx-conn-id", - "customerAsn": 65000, - "enableSiteLink": false, - "interfaceName": "test-vif", - "jumboFrames": false, - "ownerAccount": "111111111", - "region": "us-east-1", - "type": "transit", - "vlan": 300, - }, - "Type": "Custom::DirectConnectVirtualInterfaceAllocation", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/direct-connect-gateway.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directconnect/direct-connect-gateway.test.ts deleted file mode 100644 index cd534a7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/direct-connect-gateway.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { DirectConnectGateway } from '../../lib/aws-directconnect/direct-connect-gateway'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DirectConnectGateway): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new DirectConnectGateway(stack, 'TestDxGateway', { - gatewayName: 'TestDxGateway', - asn: 65000, - kmsKey: new cdk.aws_kms.Key(stack, 'Key'), - logRetentionInDays: 3653, -}); - -/** - * DirectConnectGateway construct test - */ -describe('DirectConnectGateway', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/gateway-association.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directconnect/gateway-association.test.ts deleted file mode 100644 index c519452..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/gateway-association.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { DirectConnectGatewayAssociation } from '../../lib/aws-directconnect/gateway-association'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DirectConnectGateway): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const key = new cdk.aws_kms.Key(stack, 'Key'); - -// Test DX Gateway Association -new DirectConnectGatewayAssociation(stack, 'TestDxGatewayAssociation', { - allowedPrefixes: ['0.0.0.0/0'], - directConnectGatewayId: 'test-dxgw-id', - gatewayId: 'test-tgw-id', - kmsKey: key, - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}); - -// Test DX Gateway Association Proposal -new DirectConnectGatewayAssociation(stack, 'TestDxGatewayAssociaionProposal', { - allowedPrefixes: ['0.0.0.0/0'], - directConnectGatewayId: 'test-dxgw-id', - directConnectGatewayOwnerAccount: '111111111', - gatewayId: 'test-tgw-id', - kmsKey: key, - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * DirectConnectGatewayAssociation construct test - */ -describe('DirectConnectGatewayAssociation', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/virtual-interface.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directconnect/virtual-interface.test.ts deleted file mode 100644 index 34c7bed..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directconnect/virtual-interface.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { VirtualInterface } from '../../lib/aws-directconnect/virtual-interface'; -import { snapShotTest } from '../snapshot-test'; -import { describe, expect } from '@jest/globals'; -import { VirtualInterfaceAttributes } from '../../lib/aws-directconnect/virtual-interface/attributes'; -import { VirtualInterfaceAllocationAttributes } from '../../lib/aws-directconnect/virtual-interface-allocation/attributes'; - -const testNamePrefix = 'Construct(VirtualInterface): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const key = new cdk.aws_kms.Key(stack, 'Key'); - -// Test virtual interface -new VirtualInterface(stack, 'TestVif', { - connectionId: 'test-dx-conn-id', - customerAsn: 65000, - interfaceName: 'test-vif', - region: 'us-east-1', - type: 'transit', - vlan: 300, - directConnectGatewayId: 'test-dxgw-id', - kmsKey: key, - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}); - -// Test virtual interface allocation -new VirtualInterface(stack, 'TestVifAllocation', { - connectionId: 'test-dx-conn-id', - customerAsn: 65000, - interfaceName: 'test-vif', - ownerAccount: '111111111', - region: 'us-east-1', - type: 'transit', - vlan: 300, - kmsKey: key, - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * VirtualInterface construct test - */ -describe('VirtualInterface', () => { - snapShotTest(testNamePrefix, stack); -}); - -/** - * VirtualInterface attribute test - */ -describe('VirtualInterfaceAttributes', () => { - const privAttribute = new VirtualInterfaceAttributes({ - addressFamily: 'addressFamily', - asn: 1234, - connectionId: 'connectionId', - directConnectGatewayId: 'directConnectGatewayId', - jumboFrames: true, - siteLink: true, - virtualInterfaceName: 'virtualInterfaceName', - virtualInterfaceType: 'private', - vlan: 123, - amazonAddress: 'earth', - customerAddress: 'earth', - }); - expect(privAttribute.mtu).toEqual(9001); - const transitAttribute = new VirtualInterfaceAttributes({ - addressFamily: 'addressFamily', - asn: 1234, - connectionId: 'connectionId', - directConnectGatewayId: 'directConnectGatewayId', - jumboFrames: true, - siteLink: true, - virtualInterfaceName: 'virtualInterfaceName', - virtualInterfaceType: 'transit', - vlan: 123, - amazonAddress: 'earth', - customerAddress: 'earth', - }); - expect(transitAttribute.mtu).toEqual(8500); - const noJumboAttribute = new VirtualInterfaceAttributes({ - addressFamily: 'addressFamily', - asn: 1234, - connectionId: 'connectionId', - directConnectGatewayId: 'directConnectGatewayId', - jumboFrames: false, - siteLink: true, - virtualInterfaceName: 'virtualInterfaceName', - virtualInterfaceType: 'transit', - vlan: 123, - amazonAddress: 'earth', - customerAddress: 'earth', - }); - expect(noJumboAttribute.mtu).toEqual(1500); -}); - -describe('VirtualInterfaceAllocationAttributes', () => { - const privAttribute = new VirtualInterfaceAllocationAttributes({ - addressFamily: 'addressFamily', - asn: 1234, - connectionId: 'connectionId', - jumboFrames: true, - ownerAccount: 'ownerAccount', - siteLink: true, - virtualInterfaceName: 'virtualInterfaceName', - virtualInterfaceType: 'private', - vlan: 123, - amazonAddress: 'earth', - customerAddress: 'earth', - }); - expect(privAttribute.mtu).toEqual(9001); - const transitAttribute = new VirtualInterfaceAllocationAttributes({ - addressFamily: 'addressFamily', - asn: 1234, - connectionId: 'connectionId', - ownerAccount: 'ownerAccount', - jumboFrames: true, - siteLink: true, - virtualInterfaceName: 'virtualInterfaceName', - virtualInterfaceType: 'transit', - vlan: 123, - amazonAddress: 'earth', - customerAddress: 'earth', - }); - expect(transitAttribute.mtu).toEqual(8500); - const noJumboAttribute = new VirtualInterfaceAllocationAttributes({ - addressFamily: 'addressFamily', - asn: 1234, - connectionId: 'connectionId', - ownerAccount: 'ownerAccount', - jumboFrames: false, - siteLink: true, - virtualInterfaceName: 'virtualInterfaceName', - virtualInterfaceType: 'transit', - vlan: 123, - amazonAddress: 'earth', - customerAddress: 'earth', - }); - expect(noJumboAttribute.mtu).toEqual(1500); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-configuration.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-configuration.test.ts.snap deleted file mode 100644 index eed678f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-configuration.test.ts.snap +++ /dev/null @@ -1,689 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ActiveDirectoryConfiguration Construct(ActiveDirectoryConfiguration): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueawsserviceamiwindowslatestWindowsServer2016EnglishFullBaseC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryInstanceCDCB7E02": { - "CreationPolicy": { - "ResourceSignal": { - "Count": 1, - "Timeout": "PT30M", - }, - }, - "Metadata": { - "AWS::CloudFormation::Init": { - "configSets": { - "config": [ - "setup", - "join", - "installRDS", - "createADConnectorUser", - "configurePasswordPolicy", - "finalize", - ], - }, - "configurePasswordPolicy": { - "commands": { - "a-set-password-policy": { - "command": "powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\Configure-password-policy.ps1 -DomainAdminUser admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString) -ComplexityEnabled:$True -LockoutDuration 00:30:00 -LockoutObservationWindow 00:30:00 -LockoutThreshold 6 -MaxPasswordAge:90.00:00:00 -MinPasswordAge:1.00:00:00 -MinPasswordLength:14 -PasswordHistoryCount:24 -ReversibleEncryptionEnabled:$false", - "waitAfterCompletion": "0", - }, - }, - }, - "createADConnectorUser": { - "commands": { - "a-create-ad-users": { - "command": { - "Fn::Join": [ - "", - [ - "powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\AD-user-setup.ps1 -UserName user1 -Password ((Get-SECSecretValue -SecretId arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:us-east-1:111111111111:secret:/accelerator/ad-user/AcceleratorManagedActiveDirectory/user1).SecretString) -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString) -PasswordNeverExpires Yes -UserEmailAddress example-user1@example.com; C:\\cfn\\scripts\\AD-user-setup.ps1 -UserName user2 -Password ((Get-SECSecretValue -SecretId arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:us-east-1:111111111111:secret:/accelerator/ad-user/AcceleratorManagedActiveDirectory/user2).SecretString) -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString) -PasswordNeverExpires Yes -UserEmailAddress example-user2@example.com; C:\\cfn\\scripts\\AD-user-setup.ps1 -UserName admin -Password ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString) -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString) -PasswordNeverExpires Yes", - ], - ], - }, - "waitAfterCompletion": "0", - }, - "b-create-ad-groups": { - "command": "powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\AD-group-setup.ps1 -GroupNames 'aws-Provisioning,aws-Billing,aws-Management-Admin,aws-Audit-Admin,aws-LogArchive-Admin,aws-Management-PowerUser,aws-Audit-PowerUser,aws-LogArchive-PowerUser,aws-Management-View,aws-Audit-View,aws-LogArchive-View,ADConnector-grp' -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString)", - "waitAfterCompletion": "0", - }, - "c-configure-ad-users-groups": { - "command": "powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\AD-user-group-setup.ps1 -GroupNames 'aws-Provisioning,aws-Management-PowerUser,aws-Audit-PowerUser,aws-LogArchive-PowerUser' -UserName user1 -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString); C:\\cfn\\scripts\\AD-user-group-setup.ps1 -GroupNames 'aws-Provisioning,aws-Management-PowerUser,aws-Audit-PowerUser,aws-LogArchive-PowerUser,AWS Delegated Administrators' -UserName user2 -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString)", - "waitAfterCompletion": "0", - }, - "d-configure-ad-group-permissions": { - "command": "powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\AD-connector-permissions-setup.ps1 -GroupName ADConnector-grp -DomainAdminUser example\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString)", - "waitAfterCompletion": "0", - }, - }, - }, - "finalize": { - "commands": { - "1-signal-success": { - "command": "powershell.exe -Command "Write-AWSQuickStartStatus"", - "waitAfterCompletion": "0", - }, - }, - }, - "installRDS": { - "commands": { - "a-install-rds": { - "command": "powershell.exe -Command "Install-WindowsFeature RSAT-RDS-Gateway,RSAT-AD-Tools"", - "waitAfterCompletion": "0", - }, - }, - }, - "join": { - "commands": { - "a-join-domain": { - "command": "powershell.exe -Command "C:\\cfn\\scripts\\Join-Domain.ps1 -DomainName example.com -UserName example\\admin -Password ((Get-SECSecretValue -SecretId adminPwdSecretArn).SecretString)"", - "waitAfterCompletion": "forever", - }, - }, - }, - "setup": { - "commands": { - "a-set-execution-policy": { - "command": "powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Force"", - "waitAfterCompletion": "0", - }, - "b-init-quickstart-module": { - "command": { - "Fn::Join": [ - "", - [ - "powershell.exe -Command "New-AWSQuickStartResourceSignal -Stack Default -Resource ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryInstanceCDCB7E02 -Region ", - { - "Ref": "AWS::Region", - }, - """, - ], - ], - }, - "waitAfterCompletion": "0", - }, - }, - "files": { - "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\AWSQuickStart\\AWSQuickStart.psm1": { - "content": "function New-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true)] - [string] - $Handle, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\', - - [Parameter(Mandatory=$false)] - [switch] - $Base64Handle - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - if ($Base64Handle) { - Write-Verbose "Trying to decode handle Base64 string as UTF8 string" - $decodedHandle = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Handle)) - if ($decodedHandle -notlike "http*") { - Write-Verbose "Now trying to decode handle Base64 string as Unicode string" - $decodedHandle = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($Handle)) - } - Write-Verbose "Decoded handle string: $decodedHandle" - $Handle = $decodedHandle - } - - Write-Verbose "Creating Handle Registry Key" - New-ItemProperty -Path $Path -Name Handle -Value $Handle -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function New-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [string] - $Stack, - - [Parameter(Mandatory=$true)] - [string] - $Resource, - - [Parameter(Mandatory=$true)] - [string] - $Region, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Creating $Path" - New-Item $Path -Force - - Write-Verbose "Creating Stack Registry Key" - New-ItemProperty -Path $Path -Name Stack -Value $Stack -Force - - Write-Verbose "Creating Resource Registry Key" - New-ItemProperty -Path $Path -Name Resource -Value $Resource -Force - - Write-Verbose "Creating Region Registry Key" - New-ItemProperty -Path $Path -Name Region -Value $Region -Force - - Write-Verbose "Creating ErrorCount Registry Key" - New-ItemProperty -Path $Path -Name ErrorCount -Value 0 -PropertyType dword -Force - } - catch { - Write-Verbose $_.Exception.Message - } -} - - -function Get-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - process { - try { - Write-Verbose "Getting ErrorCount Registry Key" - Get-ItemProperty -Path $Path -Name ErrorCount -ErrorAction Stop | Select-Object -ExpandProperty ErrorCount - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Set-AWSQuickStartErrorCount { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [int32] - $Count, - - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - process { - try { - $currentCount = Get-AWSQuickStartErrorCount - $currentCount += $Count - - Write-Verbose "Creating ErrorCount Registry Key" - Set-ItemProperty -Path $Path -Name ErrorCount -Value $currentCount -ErrorAction Stop - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty $Path - - return $key.Handle - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Get-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty $Path - $resourceSignal = @{ - Stack = $key.Stack - Resource = $key.Resource - Region = $key.Region - } - $toReturn = New-Object -TypeName PSObject -Property $resourceSignal - - if ($toReturn.Stack -and $toReturn.Resource -and $toReturn.Region) { - return $toReturn - } else { - return $null - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Remove-AWSQuickStartWaitHandle { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, ValueFromPipeline=$true)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - process { - try { - $ErrorActionPreference = "Stop" - - Write-Verbose "Getting Handle key value from $Path" - $key = Get-ItemProperty -Path $Path -Name Handle -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing Handle key value from $Path" - Remove-ItemProperty -Path $Path -Name Handle - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Remove-AWSQuickStartResourceSignal { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false)] - [string] - $Path = 'HKLM:\\SOFTWARE\\AWSQuickStart\\' - ) - - try { - $ErrorActionPreference = "Stop" - - foreach ($keyName in @('Stack','Resource','Region')) { - Write-Verbose "Getting Stack, Resource, and Region key values from $Path" - $key = Get-ItemProperty -Path $Path -Name $keyName -ErrorAction SilentlyContinue - - if ($key) { - Write-Verbose "Removing $keyName key value from $Path" - Remove-ItemProperty -Path $Path -Name $keyName - } - } - } - catch { - Write-Verbose $_.Exception.Message - } -} - -function Write-AWSQuickStartEvent { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipelineByPropertyName=$true)] - [string] - $Message, - - [Parameter(Mandatory=$false)] - [string] - $EntryType = 'Error' - ) - - process { - Write-Verbose "Checking for AWSQuickStart Eventlog Source" - if(![System.Diagnostics.EventLog]::SourceExists('AWSQuickStart')) { - New-EventLog -LogName Application -Source AWSQuickStart -ErrorAction SilentlyContinue - } - else { - Write-Verbose "AWSQuickStart Eventlog Source exists" - } - - Write-Verbose "Writing message to application log" - - try { - Write-EventLog -LogName Application -Source AWSQuickStart -EntryType $EntryType -EventId 1001 -Message $Message - } - catch { - Write-Verbose $_.Exception.Message - } - } -} - -function Write-AWSQuickStartException { - [CmdletBinding()] - Param( - [Parameter(Mandatory, ValueFromPipeline=$true)] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - process { - try { - Write-Verbose "Incrementing error count" - Set-AWSQuickStartErrorCount -Count 1 - - Write-Verbose "Getting total error count" - $errorTotal = Get-AWSQuickStartErrorCount - - $errorMessage = "Command failure in {0} {1} on line {2} \`nException: {3}" -f $ErrorRecord.InvocationInfo.MyCommand.name, - $ErrorRecord.InvocationInfo.ScriptName, $ErrorRecord.InvocationInfo.ScriptLineNumber, $ErrorRecord.Exception.ToString() - - $CmdSafeErrorMessage = $errorMessage -replace '[^a-zA-Z0-9\\s\\.\\[\\]\\-,:_\\\\\\/\\(\\)]', '' - if ($CmdSafeErrorMessage.length -gt 255) { - $CmdSafeErrorMessage = $CmdSafeErrorMessage.substring(0,252) + '...' - } - - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 1 --reason='$CmdSafeErrorMessage' '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 1 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - finally { - Write-AWSQuickStartEvent -Message $errorMessage - # throwing an exception to force cfn-init execution to stop - throw $CmdSafeErrorMessage - } - } -} - -function Write-AWSQuickStartStatus { - [CmdletBinding()] - Param() - - process { - try { - Write-Verbose "Checking error count" - if((Get-AWSQuickStartErrorCount) -eq 0) { - Write-Verbose "Getting Handle" - $handle = Get-AWSQuickStartWaitHandle -ErrorAction SilentlyContinue - if ($handle) { - Invoke-Expression "cfn-signal.exe -e 0 '$handle'" - } else { - $resourceSignal = Get-AWSQuickStartResourceSignal -ErrorAction SilentlyContinue - if ($resourceSignal) { - Invoke-Expression "cfn-signal.exe -e 0 --stack '$($resourceSignal.Stack)' --resource '$($resourceSignal.Resource)' --region '$($resourceSignal.Region)'" - } else { - throw "No handle or stack/resource/region found in registry" - } - } - } - } - catch { - Write-Verbose $_.Exception.Message - } - } -} -", - }, - "c:\\cfn\\cfn-hup.conf": { - "content": { - "Fn::Join": [ - "", - [ - "[main] - stack=Default - region=", - { - "Ref": "AWS::Region", - }, - " -", - ], - ], - }, - }, - "c:\\cfn\\hooks.d\\cfn-auto-reloader.conf": { - "content": { - "Fn::Join": [ - "", - [ - "[cfn-auto-reloader-hook] - triggers=post.update - path=Resources.ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryInstanceCDCB7E02.Metadata.AWS::CloudFormation::Init - action=cfn-init.exe -v -c config -s ", - { - "Ref": "AWS::StackId", - }, - " -r ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryInstanceCDCB7E02 --region ", - { - "Ref": "AWS::Region", - }, - " -", - ], - ], - }, - }, - "c:\\cfn\\scripts\\AD-group-setup.ps1": { - "content": "[CmdletBinding()] -param( - [string] - $GroupNames, - - [string] - $DomainAdminUser, - - [string] - $DomainAdminPassword -) - -# Turned off logging; -# Start-Transcript -Path C:\\cfn\\log\\AD-connector-setup.txt - -#This part of the code gets the domain name and splits it -$fdn=(Get-WmiObject Win32_ComputerSystem).Domain -$dom,$ext=$fdn.split('.') - -$securePassword = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential $DomainAdminUser, $securePassword - -$groupsArray = $GroupNames -split ',' - -for ($i=0; $i -lt $groupsArray.Length; $i++) { - $groupName = $groupsArray[$i] - $groupExists = Get-ADGroup -Filter {Name -eq $groupName} -Credential $credential - if($null -eq $groupExists) { - #Create Group - New-ADGroup -Name $groupName -GroupCategory Security -GroupScope Global -Credential $credential - } -}", - }, - "c:\\cfn\\scripts\\Join-Domain.ps1": { - "content": "[CmdletBinding()] -param( - [string] - $DomainName, - - [string] - $UserName, - - [string] - $Password -) - -try { - $ErrorActionPreference = "Stop" - - $pass = ConvertTo-SecureString $Password -AsPlainText -Force - $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$pass - - Add-Computer -DomainName $DomainName -Credential $cred -ErrorAction Stop - - # Execute restart after script exit and allow time for external services - $shutdown = Start-Process -FilePath "shutdown.exe" -ArgumentList @("/r", "/t 10") -Wait -NoNewWindow -PassThru - if ($shutdown.ExitCode -ne 0) { - throw "[ERROR] shutdown.exe exit code was not 0. It was actually $($shutdown.ExitCode)." - } -} -catch { - $_ | Write-AWSQuickStartException -} -", - }, - }, - "services": { - "windows": { - "cfn-hup": { - "enabled": "true", - "ensureRunning": "true", - "files": [ - "c:\\cfn\\cfn-hup.conf", - "c:\\cfn\\hooks.d\\cfn-auto-reloader.conf", - ], - }, - }, - }, - }, - }, - }, - "Properties": { - "BlockDeviceMappings": [ - { - "DeviceName": "/dev/sda1", - "Ebs": { - "Encrypted": true, - "VolumeSize": 50, - "VolumeType": "gp2", - }, - }, - ], - "DisableApiTermination": false, - "IamInstanceProfile": "instanceRoleName", - "ImageId": { - "Ref": "SsmParameterValueawsserviceamiwindowslatestWindowsServer2016EnglishFullBaseC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "InstanceType": "t3.large", - "LaunchTemplate": { - "LaunchTemplateName": "AcceleratorManagedActiveDirectoryLaunchTemplate", - "Version": { - "Fn::GetAtt": [ - "ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryLaunchTemplate077F7F9E", - "LatestVersionNumber", - ], - }, - }, - "SecurityGroupIds": [ - "securityGroupId", - ], - "SubnetId": "subnetId", - "Tags": [ - { - "Key": "Name", - "Value": "AcceleratorManagedActiveDirectoryConfiguringInstance", - }, - ], - "UserData": { - "Fn::Base64": { - "Fn::Join": [ - "", - [ - " -", - ], - ], - }, - }, - }, - "Type": "AWS::EC2::Instance", - }, - "ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryKmsPolicy4922950D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "secretKeyArn", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryKmsPolicy4922950D", - "Roles": [ - "instanceRoleName", - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ActiveDirectoryConfigurationAcceleratorManagedActiveDirectoryLaunchTemplate077F7F9E": { - "Properties": { - "LaunchTemplateData": { - "MetadataOptions": { - "HttpEndpoint": "enabled", - "HttpTokens": "required", - }, - }, - "LaunchTemplateName": "AcceleratorManagedActiveDirectoryLaunchTemplate", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-log-subscription.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-log-subscription.test.ts.snap deleted file mode 100644 index 0347510..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-log-subscription.test.ts.snap +++ /dev/null @@ -1,373 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ActiveDirectoryLogSubscription Construct(ActiveDirectoryLogSubscription): Snapshot Test 1`] = ` -{ - "Resources": { - "AcceleratorManagedActiveDirectory": { - "Properties": { - "Edition": "Enterprise", - "Name": "AcceleratorManagedActiveDirectory", - "Password": "password", - "ShortName": "example", - "VpcSettings": { - "SubnetIds": [ - "subnet01", - "subnet02", - ], - "VpcId": "vpcId", - }, - }, - "Type": "AWS::DirectoryService::MicrosoftAD", - }, - "ActiveDirectoryLogSubscription1BFCC309": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionLogGroupA268486C", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventE7A9F2C8", - "Arn", - ], - }, - "directoryId": { - "Ref": "AcceleratorManagedActiveDirectory", - }, - "logGroupName": { - "Ref": "ActiveDirectoryLogSubscriptionAcceleratorManagedActiveDirectoryLogGroup4142C133", - }, - }, - "Type": "Custom::ActiveDirectoryLogSubscription", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryLogSubscriptionAcceleratorManagedActiveDirectoryLogGroup4142C133": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": "/aws/directoryservice/AcceleratorManagedActiveDirectory", - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryLogSubscriptionAcceleratorManagedActiveDirectoryLogGroupPolicyResourcePolicy7874D8BA": { - "Properties": { - "PolicyDocument": { - "Fn::Join": [ - "", - [ - "{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Effect":"Allow","Principal":{"Service":"ds.amazonaws.com"},"Resource":"", - { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionAcceleratorManagedActiveDirectoryLogGroup4142C133", - "Arn", - ], - }, - ""}],"Version":"2012-10-17"}", - ], - ], - }, - "PolicyName": "ActiveDirectoryLogSubscriptionAcceleratorManagedActiveDirectoryLogGroupPolicy8BDF34B7", - }, - "Type": "AWS::Logs::ResourcePolicy", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction8B7A6975": { - "DependsOn": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRoleDefaultPolicy64E35F73", - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole8387EE78", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Manage active directory log subscription handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole8387EE78", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionLogGroupA268486C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction8B7A6975", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole8387EE78": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRoleDefaultPolicy64E35F73": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ds:ListLogSubscriptions", - "ds:CreateLogSubscription", - "ds:DeleteLogSubscription", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "LogSubscription", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRoleDefaultPolicy64E35F73", - "Roles": [ - { - "Ref": "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole8387EE78", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventE7A9F2C8": { - "DependsOn": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleDefaultPolicyC4E51E52", - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRole76E6ACBB", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ActiveDirectoryLogSubscription/ManageActiveDirectoryLogSubscriptionProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction8B7A6975", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRole76E6ACBB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRole76E6ACBB": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleDefaultPolicyC4E51E52": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction8B7A6975", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction8B7A6975", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleDefaultPolicyC4E51E52", - "Roles": [ - { - "Ref": "ActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRole76E6ACBB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomCWLKey7119CF89": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-resolver-rule.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-resolver-rule.test.ts.snap deleted file mode 100644 index c1b110b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory-resolver-rule.test.ts.snap +++ /dev/null @@ -1,347 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ActiveDirectoryResolverRule Construct(ActiveDirectoryResolverRule): Snapshot Test 1`] = ` -{ - "Resources": { - "ActiveDirectoryResolverRule420E8011": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionLogGroupDA0FD002", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEvent8A9C3C72", - "Arn", - ], - }, - "executingAccountId": { - "Ref": "AWS::AccountId", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - "roleName": "AWSAccelerator-MAD-example-com-rule", - "route53ResolverRuleName": "example-com-rule", - "targetIps": [ - "10.10.10.10", - "20.20.20.20", - ], - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::UpdateResolverRule", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleFunction94452B23": { - "DependsOn": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRoleDefaultPolicyBD9D1C7C", - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRole99253C93", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Update resolver group rule target ips", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRole99253C93", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionLogGroupDA0FD002": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ActiveDirectoryResolverRuleUpdateResolverRuleFunction94452B23", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRole99253C93": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRoleDefaultPolicyBD9D1C7C": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:UpdateResolverRule", - "route53resolver:ListResolverRules", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Route53resolver", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-MAD-example-com-rule", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRoleDefaultPolicyBD9D1C7C", - "Roles": [ - { - "Ref": "ActiveDirectoryResolverRuleUpdateResolverRuleFunctionServiceRole99253C93", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEvent8A9C3C72": { - "DependsOn": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRoleDefaultPolicy5DEC28B4", - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRole2EC1A40E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ActiveDirectoryResolverRule/UpdateResolverRuleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleFunction94452B23", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRole2EC1A40E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRole2EC1A40E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRoleDefaultPolicy5DEC28B4": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleFunction94452B23", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ActiveDirectoryResolverRuleUpdateResolverRuleFunction94452B23", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRoleDefaultPolicy5DEC28B4", - "Roles": [ - { - "Ref": "ActiveDirectoryResolverRuleUpdateResolverRuleProviderframeworkonEventServiceRole2EC1A40E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomCWLKey7119CF89": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory.test.ts.snap deleted file mode 100644 index 2a27f12..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/active-directory.test.ts.snap +++ /dev/null @@ -1,373 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ActiveDirectory Construct(ActiveDirectory): Snapshot Test 1`] = ` -{ - "Resources": { - "ActiveDirectoryAcceleratorManagedActiveDirectoryActiveDirectory4AAFE9A0": { - "Properties": { - "Edition": "Enterprise", - "Name": "example.com", - "Password": "{{resolve:secretsmanager:adminSecretArn:SecretString:::}}", - "ShortName": "example", - "VpcSettings": { - "SubnetIds": [ - "subnet01", - "subnet02", - ], - "VpcId": "vpcId", - }, - }, - "Type": "AWS::DirectoryService::MicrosoftAD", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscription31CCD066": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionLogGroupCCE84E08", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventD1664733", - "Arn", - ], - }, - "directoryId": { - "Ref": "ActiveDirectoryAcceleratorManagedActiveDirectoryActiveDirectory4AAFE9A0", - }, - "logGroupName": { - "Ref": "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionexamplecomLogGroup262F5889", - }, - }, - "Type": "Custom::ActiveDirectoryLogSubscription", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction68D4A3D7": { - "DependsOn": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRoleDefaultPolicy5A76EC10", - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole2EF3FD57", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Manage active directory log subscription handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole2EF3FD57", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionLogGroupCCE84E08": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction68D4A3D7", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole2EF3FD57": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRoleDefaultPolicy5A76EC10": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ds:ListLogSubscriptions", - "ds:CreateLogSubscription", - "ds:DeleteLogSubscription", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "LogSubscription", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "toryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRoleDefaultPolicy5A76EC10", - "Roles": [ - { - "Ref": "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunctionServiceRole2EF3FD57", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventD1664733": { - "DependsOn": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleDefaultPolicy6B887A7A", - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleC0C4010A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ActiveDirectory/AcceleratorManagedActiveDirectoryLogSubscription/ManageActiveDirectoryLogSubscriptionProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction68D4A3D7", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleC0C4010A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleC0C4010A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleDefaultPolicy6B887A7A": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction68D4A3D7", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionFunction68D4A3D7", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "anagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleDefaultPolicy6B887A7A", - "Roles": [ - { - "Ref": "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionManageActiveDirectoryLogSubscriptionProviderframeworkonEventServiceRoleC0C4010A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionexamplecomLogGroup262F5889": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": "/aws/directoryservice/AcceleratorManagedActiveDirectory", - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionexamplecomLogGroupPolicyResourcePolicy475C321A": { - "Properties": { - "PolicyDocument": { - "Fn::Join": [ - "", - [ - "{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Effect":"Allow","Principal":{"Service":"ds.amazonaws.com"},"Resource":"", - { - "Fn::GetAtt": [ - "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionexamplecomLogGroup262F5889", - "Arn", - ], - }, - ""}],"Version":"2012-10-17"}", - ], - ], - }, - "PolicyName": "ActiveDirectoryAcceleratorManagedActiveDirectoryLogSubscriptionexamplecomLogGroupPolicy7EF88DF6", - }, - "Type": "AWS::Logs::ResourcePolicy", - }, - "CustomCWLKey7119CF89": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/share-active-directory.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/share-active-directory.test.ts.snap deleted file mode 100644 index 5844918..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/__snapshots__/share-active-directory.test.ts.snap +++ /dev/null @@ -1,348 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ShareActiveDirectory Construct(ShareActiveDirectory): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomCWLKey7119CF89": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ShareActiveDirectory09C8BFAC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ShareActiveDirectoryShareManageActiveDirectoryFunctionLogGroupB93888CF", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventFB17B3F7", - "Arn", - ], - }, - "assumeRoleName": "AWSAccelerator-MadAccept-Role", - "directoryId": "directoryId", - "madAccountId": { - "Ref": "AWS::AccountId", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - "shareTargetAccountIds": [ - "111111111111", - "222222222222", - ], - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::ShareActiveDirectory", - "UpdateReplacePolicy": "Delete", - }, - "ShareActiveDirectoryShareManageActiveDirectoryFunction44493028": { - "DependsOn": [ - "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRoleDefaultPolicyA7BEAEFF", - "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRole60F965A9", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Share Manage active directory handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRole60F965A9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ShareActiveDirectoryShareManageActiveDirectoryFunctionLogGroupB93888CF": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ShareActiveDirectoryShareManageActiveDirectoryFunction44493028", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRole60F965A9": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRoleDefaultPolicyA7BEAEFF": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ds:ShareDirectory", - "ds:UnshareDirectory", - "ds:DescribeSharedDirectories", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DirectoryServices", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-MadAccept-Role", - ], - ], - }, - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRoleDefaultPolicyA7BEAEFF", - "Roles": [ - { - "Ref": "ShareActiveDirectoryShareManageActiveDirectoryFunctionServiceRole60F965A9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventFB17B3F7": { - "DependsOn": [ - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRoleDefaultPolicyA9CF197C", - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRole949DC7C2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ShareActiveDirectory/ShareManageActiveDirectoryProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ShareActiveDirectoryShareManageActiveDirectoryFunction44493028", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRole949DC7C2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRole949DC7C2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRoleDefaultPolicyA9CF197C": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ShareActiveDirectoryShareManageActiveDirectoryFunction44493028", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ShareActiveDirectoryShareManageActiveDirectoryFunction44493028", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRoleDefaultPolicyA9CF197C", - "Roles": [ - { - "Ref": "ShareActiveDirectoryShareManageActiveDirectoryProviderframeworkonEventServiceRole949DC7C2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-configuration.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-configuration.test.ts deleted file mode 100644 index 2ade74d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-configuration.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ActiveDirectoryConfiguration } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ActiveDirectoryConfiguration): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ActiveDirectoryConfiguration(stack, 'ActiveDirectoryConfiguration', { - instanceType: 't3.large', - imagePath: '/aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base', - managedActiveDirectoryName: 'AcceleratorManagedActiveDirectory', - managedActiveDirectorySecretAccountId: '111111111111', - managedActiveDirectorySecretRegion: 'us-east-1', - dnsName: 'example.com', - netBiosDomainName: 'example', - adminPwdSecretArn: 'adminPwdSecretArn', - secretKeyArn: 'secretKeyArn', - secretPrefix: '/accelerator', - subnetId: 'subnetId', - securityGroupId: 'securityGroupId', - instanceRoleName: 'instanceRoleName', - enableTerminationProtection: false, - userDataScripts: [ - { - name: 'JoinDomain', - path: `${__dirname}/../../../accelerator/test/configs/snapshot-only/ad-config-scripts/Join-Domain.ps1`, - }, - { - name: 'AWSQuickStart', - path: `${__dirname}/../../../accelerator/test/configs/snapshot-only/ad-config-scripts/AWSQuickStart.psm1`, - }, - { - name: 'ADGroupSetup', - path: `${__dirname}/../../../accelerator/test/configs/snapshot-only/ad-config-scripts/AD-group-setup.ps1`, - }, - ], - adGroups: ['aws-Provisioning', 'aws-Billing'], - adPerAccountGroups: ['*-Admin', '*-PowerUser', '*-View'], - adConnectorGroup: 'ADConnector-grp', - adUsers: [ - { name: 'user1', email: 'example-user1@example.com', groups: ['aws-Provisioning', '*-PowerUser'] }, - { - name: 'user2', - email: 'example-user2@example.com', - groups: ['aws-Provisioning', '*-PowerUser', 'AWS Delegated Administrators'], - }, - ], - adPasswordPolicy: { - history: 24, - maximumAge: 90, - minimumAge: 1, - minimumLength: 14, - complexity: true, - reversible: false, - failedAttempts: 6, - lockoutDuration: 30, - lockoutAttemptsReset: 30, - }, - accountNames: ['Management', 'Audit', 'LogArchive'], -}); -/** - * ActiveDirectoryConfiguration construct test - */ -describe('ActiveDirectoryConfiguration', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-log-subscription.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-log-subscription.test.ts deleted file mode 100644 index 0de4f93..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-log-subscription.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ActiveDirectoryLogSubscription } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ActiveDirectoryLogSubscription): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ActiveDirectoryLogSubscription(stack, 'ActiveDirectoryLogSubscription', { - activeDirectory: new cdk.aws_directoryservice.CfnMicrosoftAD(stack, 'AcceleratorManagedActiveDirectory', { - name: 'AcceleratorManagedActiveDirectory', - password: 'password', - vpcSettings: { vpcId: 'vpcId', subnetIds: ['subnet01', 'subnet02'] }, - edition: 'Enterprise', - shortName: 'example', - }), - activeDirectoryLogGroupName: '/aws/directoryservice/AcceleratorManagedActiveDirectory', - activeDirectoryLogRetentionInDays: 30, - lambdaKmsKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - cloudWatchLogsKmsKey: new cdk.aws_kms.Key(stack, 'CustomCWLKey', {}), - logRetentionInDays: 30, -}); -/** - * ActiveDirectoryLogSubscription construct test - */ -describe('ActiveDirectoryLogSubscription', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-resolver-rule.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-resolver-rule.test.ts deleted file mode 100644 index a944344..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory-resolver-rule.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ActiveDirectoryResolverRule } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ActiveDirectoryResolverRule): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ActiveDirectoryResolverRule(stack, 'ActiveDirectoryResolverRule', { - route53ResolverRuleName: 'example-com-rule', - targetIps: ['10.10.10.10', '20.20.20.20'], - roleName: `AWSAccelerator-MAD-example-com-rule`, - lambdaKmsKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - cloudWatchLogsKmsKey: new cdk.aws_kms.Key(stack, 'CustomCWLKey', {}), - cloudWatchLogRetentionInDays: 30, -}); -/** - * ActiveDirectoryResolverRule construct test - */ -describe('ActiveDirectoryResolverRule', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory.test.ts deleted file mode 100644 index b06617e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/active-directory.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ActiveDirectory } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ActiveDirectory): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ActiveDirectory(stack, 'ActiveDirectory', { - directoryName: 'AcceleratorManagedActiveDirectory', - dnsName: 'example.com', - vpcId: 'vpcId', - madSubnetIds: ['subnet01', 'subnet02'], - adminSecretValue: cdk.SecretValue.secretsManager('adminSecretArn'), - edition: 'Enterprise', - netBiosDomainName: 'example', - logGroupName: '/aws/directoryservice/AcceleratorManagedActiveDirectory', - logRetentionInDays: 30, - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - cloudwatchKey: new cdk.aws_kms.Key(stack, 'CustomCWLKey', {}), - cloudwatchLogRetentionInDays: 30, -}); -/** - * ActiveDirectory construct test - */ -describe('ActiveDirectory', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/share-active-directory.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-directory-service/share-active-directory.test.ts deleted file mode 100644 index c87378e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-directory-service/share-active-directory.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ShareActiveDirectory } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ShareActiveDirectory): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ShareActiveDirectory(stack, 'ShareActiveDirectory', { - directoryId: 'directoryId', - sharedTargetAccountIds: ['111111111111', '222222222222'], - accountAccessRoleName: 'AWSAccelerator-MadAccept-Role', - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - cloudwatchKey: new cdk.aws_kms.Key(stack, 'CustomCWLKey', {}), - cloudwatchLogRetentionInDays: 30, -}); -/** - * ShareActiveDirectory construct test - */ -describe('ShareActiveDirectory', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/account-warming.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/account-warming.test.ts.snap deleted file mode 100644 index 1fae08d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/account-warming.test.ts.snap +++ /dev/null @@ -1,1334 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AccountWarming Construct(WarmAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "AccountWarmingWarmAccountFunctionAD0DA263": { - "DependsOn": [ - "AccountWarmingWarmAccountFunctionServiceRoleDefaultPolicy7A9A020A", - "AccountWarmingWarmAccountFunctionServiceRoleF7AECCB8", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource lambda require access to other services", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Policy is limited to certain tag properties", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Account warming onEvent handler", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionServiceRoleF7AECCB8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "AccountWarmingWarmAccountFunctionLogGroupF216C336": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AccountWarmingWarmAccountFunctionAD0DA263", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "AccountWarmingWarmAccountFunctionServiceRoleDefaultPolicy7A9A020A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource lambda require access to other services", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Policy is limited to certain tag properties", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeVpcs", - "ec2:DescribeInstances", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ec2", - }, - { - "Action": [ - "ec2:DeleteVpc", - "ec2:DeleteSubnet", - "ec2:TerminateInstances", - ], - "Condition": { - "StringEquals": { - "aws:ResourceTag/Name": "accelerator-warm", - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "ec2Delete", - }, - { - "Action": [ - "ec2:CreateVpc", - "ec2:CreateSubnet", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "vpccreation", - }, - { - "Action": "ec2:RunInstances", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:subnet/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:instance/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:volume/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:security-group/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1::image/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1::snapshot/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:network-interface/*", - ], - ], - }, - ], - "Sid": "ec2Run", - }, - { - "Action": "ec2:CreateTags", - "Condition": { - "StringEquals": { - "ec2:CreateAction": "RunInstances", - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:instance/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:volume/*", - ], - ], - }, - ], - "Sid": "ec2CreateTags", - }, - { - "Action": "ec2:CreateTags", - "Condition": { - "StringEquals": { - "ec2:CreateAction": "CreateVpc", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:vpc/*", - ], - ], - }, - "Sid": "ec2CreateVpcTags", - }, - { - "Action": "ec2:CreateTags", - "Condition": { - "StringEquals": { - "ec2:CreateAction": "CreateSubnet", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:us-east-1:333333333333:subnet/*", - ], - ], - }, - "Sid": "ec2CreateSubnetTags", - }, - { - "Action": [ - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:333333333333:parameter/accelerator/account/pre-warmed", - ], - ], - }, - "Sid": "ssm", - }, - { - "Action": "ssm:GetParameter", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1::parameter/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2", - ], - ], - }, - "Sid": "ssmami", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccountWarmingWarmAccountFunctionServiceRoleDefaultPolicy7A9A020A", - "Roles": [ - { - "Ref": "AccountWarmingWarmAccountFunctionServiceRoleF7AECCB8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccountWarmingWarmAccountFunctionServiceRoleF7AECCB8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource lambda require access to other services", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Policy is limited to certain tag properties", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccountWarmingWarmAccountProviderframeworkisComplete33FFB4AE": { - "DependsOn": [ - "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRoleDefaultPolicy6C15320E", - "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRole9A7E21A7", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - isComplete (Stack/AccountWarming/WarmAccountProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - }, - }, - "Handler": "framework.isComplete", - "Role": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRole9A7E21A7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRole9A7E21A7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRoleDefaultPolicy6C15320E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRoleDefaultPolicy6C15320E", - "Roles": [ - { - "Ref": "AccountWarmingWarmAccountProviderframeworkisCompleteServiceRole9A7E21A7", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccountWarmingWarmAccountProviderframeworkonEvent6A404DAF": { - "DependsOn": [ - "AccountWarmingWarmAccountProviderframeworkonEventServiceRoleDefaultPolicy59DF4E2B", - "AccountWarmingWarmAccountProviderframeworkonEventServiceRole29C5E36F", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Stack/AccountWarming/WarmAccountProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - "WAITER_STATE_MACHINE_ARN": { - "Ref": "AccountWarmingWarmAccountProviderwaiterstatemachineAE64331A", - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkonEventServiceRole29C5E36F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AccountWarmingWarmAccountProviderframeworkonEventServiceRole29C5E36F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccountWarmingWarmAccountProviderframeworkonEventServiceRoleDefaultPolicy59DF4E2B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "states:StartExecution", - "Effect": "Allow", - "Resource": { - "Ref": "AccountWarmingWarmAccountProviderwaiterstatemachineAE64331A", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccountWarmingWarmAccountProviderframeworkonEventServiceRoleDefaultPolicy59DF4E2B", - "Roles": [ - { - "Ref": "AccountWarmingWarmAccountProviderframeworkonEventServiceRole29C5E36F", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccountWarmingWarmAccountProviderframeworkonTimeout9C02467A": { - "DependsOn": [ - "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRoleDefaultPolicyE1E67C51", - "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRole43A89D84", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onTimeout (Stack/AccountWarming/WarmAccountProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onTimeout", - "Role": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRole43A89D84", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRole43A89D84": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRoleDefaultPolicyE1E67C51": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountFunctionAD0DA263", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunction25E39087", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRoleDefaultPolicyE1E67C51", - "Roles": [ - { - "Ref": "AccountWarmingWarmAccountProviderframeworkonTimeoutServiceRole43A89D84", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccountWarmingWarmAccountProviderwaiterstatemachineAE64331A": { - "DependsOn": [ - "AccountWarmingWarmAccountProviderwaiterstatemachineRoleDefaultPolicy3DD0E902", - "AccountWarmingWarmAccountProviderwaiterstatemachineRole0437F7B9", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{"StartAt":"framework-isComplete-task","States":{"framework-isComplete-task":{"End":true,"Retry":[{"ErrorEquals":["States.ALL"],"IntervalSeconds":120,"MaxAttempts":30,"BackoffRate":1}],"Catch":[{"ErrorEquals":["States.ALL"],"Next":"framework-onTimeout-task"}],"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkisComplete33FFB4AE", - "Arn", - ], - }, - ""},"framework-onTimeout-task":{"End":true,"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkonTimeout9C02467A", - "Arn", - ], - }, - ""}}}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderwaiterstatemachineRole0437F7B9", - "Arn", - ], - }, - }, - "Type": "AWS::StepFunctions::StateMachine", - }, - "AccountWarmingWarmAccountProviderwaiterstatemachineRole0437F7B9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "states.us-east-1.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "AccountWarmingWarmAccountProviderwaiterstatemachineRoleDefaultPolicy3DD0E902": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "CDK Generated Role", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "CDK Generated Policy", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkisComplete33FFB4AE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkisComplete33FFB4AE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkonTimeout9C02467A", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkonTimeout9C02467A", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccountWarmingWarmAccountProviderwaiterstatemachineRoleDefaultPolicy3DD0E902", - "Roles": [ - { - "Ref": "AccountWarmingWarmAccountProviderwaiterstatemachineRole0437F7B9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "AccountWarmingWarmAccountResourceA5323DBB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "AccountWarmingWarmAccountFunctionLogGroupF216C336", - "AccountWarmingWarmAccountStatusFunctionLogGroupBD74C59F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountProviderframeworkonEvent6A404DAF", - "Arn", - ], - }, - "ssmPrefix": "/accelerator", - }, - "Type": "Custom::WarmAccount", - "UpdateReplacePolicy": "Delete", - }, - "AccountWarmingWarmAccountStatusFunction25E39087": { - "DependsOn": [ - "AccountWarmingWarmAccountStatusFunctionServiceRoleDefaultPolicy9876CE7C", - "AccountWarmingWarmAccountStatusFunctionServiceRole6BC3CAD6", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource lambda require access to other services", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Policy is limited to certain tag properties", - }, - ], - }, - }, - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Account warming isComplete handler", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "AccountWarmingWarmAccountStatusFunctionServiceRole6BC3CAD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "AccountWarmingWarmAccountStatusFunctionLogGroupBD74C59F": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "AccountWarmingWarmAccountStatusFunction25E39087", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "AccountWarmingWarmAccountStatusFunctionServiceRole6BC3CAD6": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource lambda require access to other services", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Policy is limited to certain tag properties", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AccountWarmingWarmAccountStatusFunctionServiceRoleDefaultPolicy9876CE7C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Custom resource lambda require access to other services", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Policy is limited to certain tag properties", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeVpcs", - "ec2:DescribeInstances", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ec2", - }, - { - "Action": [ - "ec2:DeleteVpc", - "ec2:DeleteSubnet", - "ec2:TerminateInstances", - ], - "Condition": { - "StringEquals": { - "aws:ResourceTag/Name": "accelerator-warm", - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "ec2Delete", - }, - { - "Action": [ - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:us-east-1:333333333333:parameter/accelerator/account/pre-warmed", - ], - ], - }, - "Sid": "ssm", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccountWarmingWarmAccountStatusFunctionServiceRoleDefaultPolicy9876CE7C", - "Roles": [ - { - "Ref": "AccountWarmingWarmAccountStatusFunctionServiceRole6BC3CAD6", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/create-launch-template.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/create-launch-template.test.ts.snap deleted file mode 100644 index 0d10178..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/create-launch-template.test.ts.snap +++ /dev/null @@ -1,49 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LaunchTemplate Construct(LaunchTemplate): Snapshot Test 1`] = ` -{ - "Resources": { - "TestLaunchTemplateAC264C67": { - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "test", - "Ebs": { - "DeleteOnTermination": true, - "Encrypted": true, - "Iops": 300, - "KmsKeyId": "test", - "VolumeSize": 10, - }, - }, - ], - "IamInstanceProfile": {}, - "ImageId": "ami-1234", - "InstanceType": "t3.micro", - "MetadataOptions": { - "HttpTokens": "required", - }, - "SecurityGroupIds": [ - "sg-1234", - ], - "UserData": { - "Fn::Base64": "#!/bin/bash -yum update -y -yum install -y httpd -systemctl start httpd -systemctl enable httpd -usermod -a -G apache ec2-user -chown -R ec2-user:apache /var/www -chmod 2775 /var/www -find /var/www -type d -exec chmod 2775 {} \\; -find /var/www -type f -exec chmod 0664 {} \\;", - }, - }, - "LaunchTemplateName": "Test", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/cross-account-route.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/cross-account-route.test.ts.snap deleted file mode 100644 index d270e8d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/cross-account-route.test.ts.snap +++ /dev/null @@ -1,334 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CrossAccountRoute Construct(CrossAccountRoute): Snapshot Test 1`] = ` -{ - "Resources": { - "FrameworkCrossAccountRouteFunction7BE199E2": { - "DependsOn": [ - "FrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicyEFFA4FAA", - "FrameworkCrossAccountRouteFunctionServiceRoleD3FBE736", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Cross account EC2 route OnEvent handler", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteFunctionServiceRoleD3FBE736", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 15, - }, - "Type": "AWS::Lambda::Function", - }, - "FrameworkCrossAccountRouteFunctionLogGroup9B330ABE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "FrameworkCrossAccountRouteFunction7BE199E2", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "FrameworkCrossAccountRouteFunctionServiceRoleD3FBE736": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicyEFFA4FAA": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2RouteCreateDelete", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FrameworkCrossAccountRouteFunctionServiceRoleDefaultPolicyEFFA4FAA", - "Roles": [ - { - "Ref": "FrameworkCrossAccountRouteFunctionServiceRoleD3FBE736", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FrameworkCrossAccountRouteProviderframeworkonEvent1A959FA7": { - "DependsOn": [ - "FrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyAF7F5043", - "FrameworkCrossAccountRouteProviderframeworkonEventServiceRole274CC003", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/Framework/CrossAccountRouteProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteFunction7BE199E2", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteProviderframeworkonEventServiceRole274CC003", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FrameworkCrossAccountRouteProviderframeworkonEventServiceRole274CC003": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyAF7F5043": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteFunction7BE199E2", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteFunction7BE199E2", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FrameworkCrossAccountRouteProviderframeworkonEventServiceRoleDefaultPolicyAF7F5043", - "Roles": [ - { - "Ref": "FrameworkCrossAccountRouteProviderframeworkonEventServiceRole274CC003", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ResourcePl6F481B77": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteProviderframeworkonEvent1A959FA7", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::TestAccount:role/TestRole", - "routeDefinition": { - "DestinationPrefixListId": "pl-test", - "RouteTableId": "rtb-test123", - "VpcPeeringConnectionId": "pcx-test123", - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "Resourcev4C9A541C2": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteProviderframeworkonEvent1A959FA7", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::TestAccount:role/TestRole", - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.0/16", - "RouteTableId": "rtb-test123", - "VpcPeeringConnectionId": "pcx-test123", - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "Resourcev6A6F941A4": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FrameworkCrossAccountRouteProviderframeworkonEvent1A959FA7", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": "arn:aws:iam::TestAccount:role/TestRole", - "routeDefinition": { - "DestinationIpv6CidrBlock": "::/0", - "RouteTableId": "rtb-test123", - "VpcPeeringConnectionId": "pcx-test123", - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/customer-gateway.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/customer-gateway.test.ts.snap deleted file mode 100644 index 3b5bee8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/customer-gateway.test.ts.snap +++ /dev/null @@ -1,212 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CustomerGateway Construct(CustomerGateway): Snapshot Test 1`] = ` -{ - "Resources": { - "TestCgwCustomerGateway795D02C0": { - "Properties": { - "BgpAsn": 65000, - "IpAddress": "1.1.1.1", - "Tags": [ - { - "Key": "Name", - "Value": "Test-CGW", - }, - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::CustomerGateway", - }, - "TestCustomCgwCustomResourceCustomResourceResource6CB0B3D6": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TestCustomCgwCustomResourceframeworkonEvent7AACEE90", - "Arn", - ], - }, - "bgpAsn": 65123, - "ipAddress": "1.2.3.4", - "owningAccountId": "111111111111", - "owningRegion": "us-west-2", - "roleName": "test-role", - "tags": [ - { - "Key": "Name", - "Value": "Custom-CGW", - }, - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TestCustomCgwCustomResourceframeworkonEvent7AACEE90": { - "DependsOn": [ - "TestCustomCgwCustomResourceframeworkonEventServiceRoleDefaultPolicy4524C238", - "TestCustomCgwCustomResourceframeworkonEventServiceRole4AB1225B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TestCustomCgw/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:TestFunction", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TestCustomCgwCustomResourceframeworkonEventServiceRole4AB1225B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestCustomCgwCustomResourceframeworkonEventServiceRole4AB1225B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestCustomCgwCustomResourceframeworkonEventServiceRoleDefaultPolicy4524C238": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:TestFunction", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:TestFunction:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestCustomCgwCustomResourceframeworkonEventServiceRoleDefaultPolicy4524C238", - "Roles": [ - { - "Ref": "TestCustomCgwCustomResourceframeworkonEventServiceRole4AB1225B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-security-group-rules.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-security-group-rules.test.ts.snap deleted file mode 100644 index ce9224f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-security-group-rules.test.ts.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DeleteDefaultSGRules Construct(DeleteDefaultSGRules): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F": { - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testKey1CDDDD5E", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeSecurityGroups", - "ec2:RevokeSecurityGroupIngress", - "ec2:RevokeSecurityGroupEgress", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DeleteDefaultSGRulesD2F7FF67": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - "vpcId": "vpc-123", - }, - "Type": "Custom::DeleteDefaultSecurityGroupRules", - "UpdateReplacePolicy": "Delete", - }, - "testKey1CDDDD5E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-vpc.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-vpc.test.ts.snap deleted file mode 100644 index bf45d53..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/delete-default-vpc.test.ts.snap +++ /dev/null @@ -1,159 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DeleteDefaultVpc Construct(DeleteDefaultVpc): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35": { - "DependsOn": [ - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultVpcCustomResourceProviderLogGroup4113DA48": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultVpcCustomResourceProviderRole80963EEF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DeleteInternetGateway", - "ec2:DetachInternetGateway", - "ec2:DeleteNetworkAcl", - "ec2:DeleteRoute", - "ec2:DeleteSecurityGroup", - "ec2:DeleteSubnet", - "ec2:DeleteVpc", - "ec2:DescribeInternetGateways", - "ec2:DescribeNetworkAcls", - "ec2:DescribeRouteTables", - "ec2:DescribeSecurityGroups", - "ec2:DescribeSubnets", - "ec2:DescribeVpcs", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "DeleteDefaultVpc4DBAE36C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultVpcCustomResourceProviderLogGroup4113DA48", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultVpcCustomResourceProviderHandler87E89F35", - "Arn", - ], - }, - }, - "Type": "Custom::DeleteDefaultVpc", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/dhcp-options.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/dhcp-options.test.ts.snap deleted file mode 100644 index 6733719..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/dhcp-options.test.ts.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DhcpOptions Construct(DhcpOptions): Snapshot Test 1`] = ` -{ - "Resources": { - "TestDhcpOpts22CADF8A": { - "Properties": { - "DomainName": "test.com", - "DomainNameServers": [ - "1.1.1.1", - ], - "NetbiosNameServers": [ - "1.1.1.1", - ], - "NetbiosNodeType": 2, - "NtpServers": [ - "1.1.1.1", - ], - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::DHCPOptions", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ebs-encryption.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ebs-encryption.test.ts.snap deleted file mode 100644 index 897752d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ebs-encryption.test.ts.snap +++ /dev/null @@ -1,192 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ReportDefinition Construct(EbsDefaultEncryption): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B": { - "DependsOn": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderLogGroupB7212213": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderRole7A4FA6A8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableEbsEncryptionByDefault", - "ec2:EnableEbsEncryptionByDefault", - "ec2:ModifyEbsDefaultKmsKeyId", - "ec2:ResetEbsDefaultKmsKeyId", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "EC2", - }, - { - "Action": [ - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "EbsEncryptionKmsKeyFD1E785E", - "Arn", - ], - }, - "Sid": "KMS", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "EbsDefaultVolumeEncryption55012F7A": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderLogGroupB7212213", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEbsDefaultVolumeEncryptionCustomResourceProviderHandler6551B35B", - "Arn", - ], - }, - "kmsKeyId": { - "Ref": "EbsEncryptionKmsKeyFD1E785E", - }, - }, - "Type": "Custom::EbsDefaultVolumeEncryption", - "UpdateReplacePolicy": "Delete", - }, - "EbsEncryptionKmsKeyFD1E785E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-asg.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-asg.test.ts.snap deleted file mode 100644 index 61dd966..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-asg.test.ts.snap +++ /dev/null @@ -1,387 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LaunchTemplate Construct(FirewallAutoScalingGroup): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyCloudWatchFB91CD4E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction4263CA84": { - "DependsOn": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy1510F19B", - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole82A4BC5E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole82A4BC5E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupB0B4539B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKeyCloudWatchFB91CD4E", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction4263CA84", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole82A4BC5E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy1510F19B": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy1510F19B", - "Roles": [ - { - "Ref": "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRole82A4BC5E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent7CB941E7": { - "DependsOn": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy607127A2", - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleADF19690", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TestFirewall/Resource/AutoScalingServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction4263CA84", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleADF19690", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleADF19690": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy607127A2": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction4263CA84", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunction4263CA84", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicy607127A2", - "Roles": [ - { - "Ref": "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleADF19690", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleResource1EB49BEA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupB0B4539B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TestFirewallAutoScalingServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent7CB941E7", - "Arn", - ], - }, - "description": "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling", - "roleName": "AWSServiceRoleForAutoScaling", - "serviceName": "autoscaling.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - "TestFirewallF12A004C": { - "Properties": { - "DesiredCapacity": "2", - "HealthCheckGracePeriod": 300, - "HealthCheckType": "ELB", - "LaunchTemplate": { - "LaunchTemplateId": { - "Ref": "TestFirewallLaunchTemplateFF1988AF", - }, - "Version": { - "Fn::GetAtt": [ - "TestFirewallLaunchTemplateFF1988AF", - "LatestVersionNumber", - ], - }, - }, - "MaxInstanceLifetime": 86400, - "MaxSize": "4", - "MinSize": "1", - "Tags": [ - { - "Key": "Name", - "PropagateAtLaunch": true, - "Value": "Test", - }, - ], - "TargetGroupARNs": [], - "VPCZoneIdentifier": [ - "subnet-123xyz", - "subnet-456abc", - ], - }, - "Type": "AWS::AutoScaling::AutoScalingGroup", - }, - "TestFirewallLaunchTemplateFF1988AF": { - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "dev/xvda", - "Ebs": { - "Encrypted": true, - }, - }, - ], - "IamInstanceProfile": {}, - "ImageId": "ami-123xyz", - "InstanceType": "t3.large", - "MetadataOptions": { - "HttpTokens": "required", - }, - "NetworkInterfaces": [ - { - "DeviceIndex": 0, - "Groups": [ - "Test", - ], - "SubnetId": "subnet-123xyz", - }, - ], - "SecurityGroupIds": [], - "UserData": { - "Fn::Base64": "S3 bucket name: test-bucket", - }, - }, - "LaunchTemplateName": "test-firewall", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-config-replacements.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-config-replacements.test.ts.snap deleted file mode 100644 index e04dee4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-config-replacements.test.ts.snap +++ /dev/null @@ -1,296 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FirewallConfigReplacements Construct(FirewallConfigReplacements): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchKey9B181885": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ConfigReplacementsFunction3B8BED87": { - "DependsOn": [ - "Role1ABCC5F0", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Firewall configuration replacement custom resource", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "Role1ABCC5F0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 120, - }, - "Type": "AWS::Lambda::Function", - }, - "ConfigReplacementsFunctionResourceLogGroupA52DC796": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKey9B181885", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ConfigReplacementsFunction3B8BED87", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ConfigReplacementsResourceResource58F31BA3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ConfigReplacementsFunctionResourceLogGroupA52DC796", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ConfigReplacementsframeworkonEvent7670032B", - "Arn", - ], - }, - "test": "test", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ConfigReplacementsframeworkonEvent7670032B": { - "DependsOn": [ - "ConfigReplacementsframeworkonEventServiceRoleDefaultPolicy8E284567", - "ConfigReplacementsframeworkonEventServiceRole6796BD33", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ConfigReplacements/Resource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ConfigReplacementsFunction3B8BED87", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ConfigReplacementsframeworkonEventServiceRole6796BD33", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ConfigReplacementsframeworkonEventServiceRole6796BD33": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ConfigReplacementsframeworkonEventServiceRoleDefaultPolicy8E284567": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ConfigReplacementsFunction3B8BED87", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ConfigReplacementsFunction3B8BED87", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ConfigReplacementsframeworkonEventServiceRoleDefaultPolicy8E284567", - "Roles": [ - { - "Ref": "ConfigReplacementsframeworkonEventServiceRole6796BD33", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "Role1ABCC5F0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-instance.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-instance.test.ts.snap deleted file mode 100644 index 78b834b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/firewall-instance.test.ts.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LaunchTemplate Construct(FirewallInstance): Snapshot Test 1`] = ` -{ - "Resources": { - "TestFirewallE26FCA5C": { - "Properties": { - "LaunchTemplate": { - "LaunchTemplateId": { - "Ref": "TestFirewallLaunchTemplateFF1988AF", - }, - "Version": { - "Fn::GetAtt": [ - "TestFirewallLaunchTemplateFF1988AF", - "LatestVersionNumber", - ], - }, - }, - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::Instance", - }, - "TestFirewallEipAssociation049F6348D": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "TestFirewallElasticIp0A7A4BF57", - "AllocationId", - ], - }, - "NetworkInterfaceId": { - "Ref": "TestFirewallNetworkInterface045307B6A", - }, - }, - "Type": "AWS::EC2::EIPAssociation", - }, - "TestFirewallElasticIp0A7A4BF57": { - "Properties": { - "Domain": "vpc", - }, - "Type": "AWS::EC2::EIP", - }, - "TestFirewallLaunchTemplateFF1988AF": { - "DependsOn": [ - "TestFirewallEipAssociation049F6348D", - ], - "Properties": { - "LaunchTemplateData": { - "BlockDeviceMappings": [ - { - "DeviceName": "dev/xvda", - "Ebs": { - "Encrypted": true, - }, - }, - ], - "IamInstanceProfile": {}, - "ImageId": "ami-123xyz", - "InstanceType": "t3.large", - "MetadataOptions": { - "HttpTokens": "required", - }, - "NetworkInterfaces": [ - { - "DeviceIndex": 0, - "NetworkInterfaceId": { - "Ref": "TestFirewallNetworkInterface045307B6A", - }, - }, - ], - "SecurityGroupIds": [], - "UserData": { - "Fn::Base64": "S3 bucket name: test-bucket", - }, - }, - "LaunchTemplateName": "test-firewall", - }, - "Type": "AWS::EC2::LaunchTemplate", - }, - "TestFirewallNetworkInterface045307B6A": { - "Properties": { - "GroupSet": [ - "Test", - ], - "SourceDestCheck": false, - "SubnetId": "subnet-123xyz", - }, - "Type": "AWS::EC2::NetworkInterface", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-organization-admin-account.test.ts.snap deleted file mode 100644 index 5841d22..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,184 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IpamOrganizationAdminAccount Construct(IpamOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25": { - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderRoleC4A018D1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DisableIpamOrganizationAdminAccount", - "ec2:EnableIpamOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:DisableAwsServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ServicePrincipal": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:DeleteServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:AWSServiceName": [ - "ipam.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestIpamOrgAdminDAAE6833": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderLogGroupB1C24203", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableIpamOrganizationAdminAccountCustomResourceProviderHandlerA3CAFE25", - "Arn", - ], - }, - "accountId": "TestAccountId", - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::EnableIpamOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-pool.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-pool.test.ts.snap deleted file mode 100644 index 92282a8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-pool.test.ts.snap +++ /dev/null @@ -1,31 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IpamPool Construct(IpamPool): Snapshot Test 1`] = ` -{ - "Resources": { - "TestIpamPool2D962DC3": { - "Properties": { - "AddressFamily": "ipv4", - "Description": "Test IPAM pool", - "IpamScopeId": "test-scope", - "Locale": "us-east-1", - "ProvisionedCidrs": [ - { - "Cidr": "10.0.0.0/8", - }, - { - "Cidr": "192.168.0.0/16", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::IPAMPool", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-scope.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-scope.test.ts.snap deleted file mode 100644 index 5d747e2..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-scope.test.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IpamScope Construct(IpamScope): Snapshot Test 1`] = ` -{ - "Resources": { - "TestIpamScope20AAB890": { - "Properties": { - "Description": "Test IPAM scope", - "IpamId": "test-ipam", - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::IPAMScope", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-subnet.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-subnet.test.ts.snap deleted file mode 100644 index 8f6e4f8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam-subnet.test.ts.snap +++ /dev/null @@ -1,470 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IpamSubnet Construct(IpamSubnet): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5": { - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "KeyForLookup43C142E7", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetIpamSubnetCidrCustomResourceProviderRoleADF93CF4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeSubnets", - "ssm:GetParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A": { - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTags", - "ec2:DeleteSubnet", - "ec2:DeleteTags", - "ec2:ModifySubnetAttribute", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":subnet/*", - ], - ], - }, - }, - { - "Action": [ - "ec2:CreateSubnet", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":subnet/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":vpc/*", - ], - ], - }, - ], - }, - { - "Action": [ - "ec2:DescribeVpcs", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key2071601C6": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "KeyForLookup43C142E7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestIpamSubnet05D29B1E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "us-east-1a", - "basePool": [ - "10.0.0.0/8", - ], - "ipamAllocation": { - "ipamPoolName": "test-pool", - "netmaskLength": 24, - }, - "name": "Test", - "tags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "vpcId": "vpc-test", - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "TestIpamSubnet22B82F212": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZoneId": "use1-az2", - "basePool": [ - "10.0.0.0/8", - ], - "ipamAllocation": { - "ipamPoolName": "test-pool", - "netmaskLength": 24, - }, - "name": "Test2", - "tags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "vpcId": "vpc-test", - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "TestIpamSubnetFromLookup8A88C539": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetIpamSubnetCidrCustomResourceProviderLogGroup655A9FB7", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetIpamSubnetCidrCustomResourceProviderHandler8C11DBB5", - "Arn", - ], - }, - "region": "us-east-1", - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::11111111111:role/testRole", - ], - ], - }, - "ssmSubnetIdPath": "/path/to/ipam/subnet", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetIpamSubnetCidr", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam.test.ts.snap deleted file mode 100644 index ccde821..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/ipam.test.ts.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Ipam Construct(Ipam): Snapshot Test 1`] = ` -{ - "Resources": { - "TestIpamD7083AA5": { - "Properties": { - "Description": "Test IPAM", - "OperatingRegions": [ - { - "RegionName": "us-east-1", - }, - { - "RegionName": "us-west-2", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::IPAM", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list-route.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list-route.test.ts.snap deleted file mode 100644 index d0c9efd..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list-route.test.ts.snap +++ /dev/null @@ -1,154 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PrefixListRoute Construct(PrefixListRoute): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077": { - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TestKms67078DF1", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyRoutes", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestKms67078DF1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestPrefixListRoute212D9279": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-test", - "RouteTableId": "Test", - "TransitGatewayId": "tgw-test", - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list.test.ts.snap deleted file mode 100644 index fe21cc5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/prefix-list.test.ts.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PrefixList Construct(PrefixList): Snapshot Test 1`] = ` -{ - "Resources": { - "TestPrefixListF3A076C9": { - "Properties": { - "AddressFamily": "IPv4", - "Entries": [ - { - "Cidr": "1.1.1.1/32", - }, - ], - "MaxEntries": 1, - "PrefixListName": "Test", - "Tags": [ - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - }, - "Type": "AWS::EC2::PrefixList", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/route-table.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/route-table.test.ts.snap deleted file mode 100644 index 65a27d5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/route-table.test.ts.snap +++ /dev/null @@ -1,585 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RouteTable Construct(RouteTable): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077": { - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testKey1CDDDD5E", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - }, - ], - ], - }, - "RetentionInDays": 10, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyRoutes", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RouteTable82FB8FA6": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "TestRouteTable", - }, - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "RouteTableGatewayAssociation99E18AA3": { - "DependsOn": [ - "TestVpcInternetGatewayAttachment60E451D5", - ], - "Properties": { - "GatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::GatewayRouteTableAssociation", - }, - "RouteTableVirtualPrivateGatewayAssociation6AD07550": { - "DependsOn": [ - "TestVpcVirtualPrivateGatewayAttachment5D655F8D", - ], - "Properties": { - "GatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::GatewayRouteTableAssociation", - }, - "RouteTabletest2NgwRoute7F788B8D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-1234", - "NatGatewayId": { - "Ref": "ngwBD1698D5", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "RouteTabletest3NgwRoute8D00A105": { - "Properties": { - "DestinationIpv6CidrBlock": "::1", - "NatGatewayId": { - "Ref": "ngwBD1698D5", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestEigwRoute12DFE9140": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-1234", - "EgressOnlyInternetGatewayId": { - "Ref": "TestVpcEgressOnlyIgwE0823B1A", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "RouteTabletestEigwRoute29FFB6E44": { - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "EgressOnlyInternetGatewayId": { - "Ref": "TestVpcEgressOnlyIgwE0823B1A", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestEigwRoute3C3EC8E23": { - "Properties": { - "DestinationIpv6CidrBlock": "::1", - "EgressOnlyInternetGatewayId": { - "Ref": "TestVpcEgressOnlyIgwE0823B1A", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestIgwRoute23DD8B83D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - "TestVpcInternetGatewayAttachment60E451D5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-1234", - "GatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "RouteTabletestIgwRoute3141525D4": { - "DependsOn": [ - "TestVpcInternetGatewayAttachment60E451D5", - ], - "Properties": { - "DestinationIpv6CidrBlock": "::1", - "GatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestIgwRoute3732DB68": { - "DependsOn": [ - "TestVpcInternetGatewayAttachment60E451D5", - ], - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestNgwRouteAE321892": { - "Properties": { - "DestinationCidrBlock": "10.0.3.0/24", - "NatGatewayId": { - "Ref": "ngwBD1698D5", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestVgw2RouteDD19A5B1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - "TestVpcVirtualPrivateGatewayAttachment5D655F8D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-1234", - "GatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "RouteTabletestVgw3RouteFE177B5B": { - "DependsOn": [ - "TestVpcVirtualPrivateGatewayAttachment5D655F8D", - ], - "Properties": { - "DestinationIpv6CidrBlock": "::1", - "GatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletestVgwRoute717D63D3": { - "DependsOn": [ - "TestVpcVirtualPrivateGatewayAttachment5D655F8D", - ], - "Properties": { - "DestinationCidrBlock": "10.0.30./24", - "GatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletgCEFADE05": { - "DependsOn": [ - "tgwAttachment1E239374", - ], - "Properties": { - "DestinationCidrBlock": "10.0.5.0/24", - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - "TransitGatewayId": "tg-1234", - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletgipv684D762E9": { - "DependsOn": [ - "tgwAttachment1E239374", - ], - "Properties": { - "DestinationIpv6CidrBlock": "::1", - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - "TransitGatewayId": "tg-1234", - }, - "Type": "AWS::EC2::Route", - }, - "RouteTabletgroute63CDF5B7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - "tgwAttachment1E239374", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-1234", - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - "TransitGatewayId": "tgw-1234", - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "TestVpcE77CE678": { - "Properties": { - "CidrBlock": "10.0.0.0/16", - "EnableDnsHostnames": false, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "TestVpcEgressOnlyIgwE0823B1A": { - "Properties": { - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::EgressOnlyInternetGateway", - }, - "TestVpcInternetGateway01360C82": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - }, - "Type": "AWS::EC2::InternetGateway", - }, - "TestVpcInternetGatewayAttachment60E451D5": { - "Properties": { - "InternetGatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcVirtualPrivateGateway56B5C340": { - "Properties": { - "AmazonSideAsn": 65000, - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNGateway", - }, - "TestVpcVirtualPrivateGatewayAttachment5D655F8D": { - "Properties": { - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - "VpnGatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "ngwBD1698D5": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "ngwEipBD9BC2FE", - "AllocationId", - ], - }, - "SubnetId": { - "Ref": "testsubnetFBD9E7FF", - }, - "Tags": [ - { - "Key": "Name", - "Value": "ngw", - }, - ], - }, - "Type": "AWS::EC2::NatGateway", - }, - "ngwEipBD9BC2FE": { - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "ngw", - }, - ], - }, - "Type": "AWS::EC2::EIP", - }, - "testKey1CDDDD5E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testsubnetFBD9E7FF": { - "Properties": { - "AvailabilityZone": "a", - "CidrBlock": "10.0.2.0/24", - "Tags": [ - { - "Key": "Name", - "Value": "test-subnet", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "testsubnetRouteTableAssociationA7927A11": { - "Properties": { - "RouteTableId": { - "Ref": "RouteTable82FB8FA6", - }, - "SubnetId": { - "Ref": "testsubnetFBD9E7FF", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "tgwAttachment1E239374": { - "Properties": { - "SubnetIds": [ - "subnet-123", - ], - "Tags": [ - { - "Key": "Name", - "Value": "someTgwAttachment", - }, - ], - "TransitGatewayId": "tgw-12324", - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/subnet-id-lookup.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/subnet-id-lookup.test.ts.snap deleted file mode 100644 index a70eb68..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/subnet-id-lookup.test.ts.snap +++ /dev/null @@ -1,313 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SubnetIdLookup Construct(SubnetIdLookup): Snapshot Test 1`] = ` -{ - "Resources": { - "CWKeyF87F989A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SubnetIdLookup5A070671": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "SubnetIdLookupSubnetIdLookupFunctionLogGroup2C86E34D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SubnetIdLookupSubnetIdLookupProviderframeworkonEvent54521939", - "Arn", - ], - }, - "subnetName": "TestSubnet", - "vpcId": "vpce-00000000", - }, - "Type": "Custom::SubnetIdLookup", - "UpdateReplacePolicy": "Delete", - }, - "SubnetIdLookupSubnetIdLookupFunction2C56B17D": { - "DependsOn": [ - "SubnetIdLookupSubnetIdLookupFunctionServiceRoleDefaultPolicy20F5ACD5", - "SubnetIdLookupSubnetIdLookupFunctionServiceRoleD56D09B3", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lookup subnet id from account", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "SubnetIdLookupSubnetIdLookupFunctionServiceRoleD56D09B3", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SubnetIdLookupSubnetIdLookupFunctionLogGroup2C86E34D": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CWKeyF87F989A", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "SubnetIdLookupSubnetIdLookupFunction2C56B17D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "SubnetIdLookupSubnetIdLookupFunctionServiceRoleD56D09B3": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SubnetIdLookupSubnetIdLookupFunctionServiceRoleDefaultPolicy20F5ACD5": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ec2:DescribeSubnets", - "Effect": "Allow", - "Resource": "*", - "Sid": "Ec2Actions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SubnetIdLookupSubnetIdLookupFunctionServiceRoleDefaultPolicy20F5ACD5", - "Roles": [ - { - "Ref": "SubnetIdLookupSubnetIdLookupFunctionServiceRoleD56D09B3", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SubnetIdLookupSubnetIdLookupProviderframeworkonEvent54521939": { - "DependsOn": [ - "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleDefaultPolicy5FAE987E", - "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleC2619D79", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/SubnetIdLookup/SubnetIdLookupProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "SubnetIdLookupSubnetIdLookupFunction2C56B17D", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleC2619D79", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleC2619D79": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleDefaultPolicy5FAE987E": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SubnetIdLookupSubnetIdLookupFunction2C56B17D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SubnetIdLookupSubnetIdLookupFunction2C56B17D", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleDefaultPolicy5FAE987E", - "Roles": [ - { - "Ref": "SubnetIdLookupSubnetIdLookupProviderframeworkonEventServiceRoleC2619D79", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-connect.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-connect.test.ts.snap deleted file mode 100644 index 70b73af..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-connect.test.ts.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TransitGatewayConnectAttachment Construct(TransitGatewayStaticRoute): Snapshot Test 1`] = ` -{ - "Resources": { - "TransitGatewayConnectVpcAttachTestVpcTgwConnectTransitGatewayConnect442AF647": { - "Properties": { - "Options": { - "Protocol": "gre", - }, - "Tags": [ - { - "Key": "Name", - "Value": "TestVpcTgwConnect", - }, - ], - "TransportTransitGatewayAttachmentId": "tgw-attach-0123456789012", - }, - "Type": "AWS::EC2::TransitGatewayConnect", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-peering.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-peering.test.ts.snap deleted file mode 100644 index 71e1311..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-peering.test.ts.snap +++ /dev/null @@ -1,235 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TransitGatewayPeering Construct(TransitGatewayPrefixListReference): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomTransitGatewayAcceptPeeringCustomResourceProviderHandler692671C6": { - "DependsOn": [ - "CustomTransitGatewayAcceptPeeringCustomResourceProviderRole0A88B1D1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomTransitGatewayAcceptPeeringCustomResourceProviderRole0A88B1D1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomTransitGatewayAcceptPeeringCustomResourceProviderLogGroupB843F457": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TestKms67078DF1", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomTransitGatewayAcceptPeeringCustomResourceProviderHandler692671C6", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "CustomTransitGatewayAcceptPeeringCustomResourceProviderRole0A88B1D1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/ABC", - ], - ], - }, - "Sid": "AllowAssumeRole", - }, - { - "Action": [ - "ec2:AssociateTransitGatewayRouteTable", - "ec2:DisassociateTransitGatewayRouteTable", - "ec2:DescribeTransitGatewayPeeringAttachments", - "ec2:DescribeTransitGatewayAttachments", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyPeeringReferences", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestKms67078DF1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TransitGatewayPeering3AEAB3EC": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomTransitGatewayAcceptPeeringCustomResourceProviderLogGroupB843F457", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomTransitGatewayAcceptPeeringCustomResourceProviderHandler692671C6", - "Arn", - ], - }, - "accepterAccountId": { - "Ref": "AWS::AccountId", - }, - "accepterRegion": "us-west-2", - "accepterRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/ABC", - ], - ], - }, - "accepterTransitGatewayId": "tgw-0001", - "accepterTransitGatewayRouteTableId": "rt-001", - "autoAccept": true, - "peeringTags": [ - { - "Key": "Name", - "Value": "SharedServices-And-Network-Main-Peering", - }, - ], - "requesterAccountId": { - "Ref": "AWS::AccountId", - }, - "requesterRegion": { - "Ref": "AWS::Region", - }, - "requesterTransitGatewayAttachmentId": { - "Fn::GetAtt": [ - "TransitGatewayPeeringNetworkMainTgwToSharedServicesTgw8B9A3F31", - "TransitGatewayAttachmentId", - ], - }, - "requesterTransitGatewayId": "tgw-0002", - "requesterTransitGatewayRouteTableId": "tgw-0002", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::TransitGatewayAcceptPeering", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayPeeringNetworkMainTgwToSharedServicesTgw8B9A3F31": { - "Properties": { - "PeerAccountId": { - "Ref": "AWS::AccountId", - }, - "PeerRegion": "us-west-2", - "PeerTransitGatewayId": "tgw-0001", - "Tags": [ - { - "Key": "Name", - "Value": "SharedServices-And-Network-Main-Peering", - }, - ], - "TransitGatewayId": "tgw-0002", - }, - "Type": "AWS::EC2::TransitGatewayPeeringAttachment", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-prefix-list-reference.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-prefix-list-reference.test.ts.snap deleted file mode 100644 index 36f0a49..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-prefix-list-reference.test.ts.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TransitGatewayPrefixListReference Construct(TransitGatewayPrefixListReference): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3": { - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TestKms67078DF1", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderRoleC5D4C080": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTransitGatewayPrefixListReference", - "ec2:ModifyTransitGatewayPrefixListReference", - "ec2:DeleteTransitGatewayPrefixListReference", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyTgwReferences", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestKms67078DF1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestTransitGatewayPrefixListReference11CAF048": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderLogGroupBC9F3669", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomTransitGatewayPrefixListReferenceCustomResourceProviderHandler9BAD63E3", - "Arn", - ], - }, - "prefixListReference": { - "PrefixListId": "pl-test", - "TransitGatewayAttachmentId": "tgw-attach-test", - "TransitGatewayRouteTableId": "Test", - }, - }, - "Type": "Custom::TransitGatewayPrefixListReference", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-route-table.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-route-table.test.ts.snap deleted file mode 100644 index 7bbf8c6..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-route-table.test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TransitGatewayRouteTable Construct(TransitGatewayRouteTable): Snapshot Test 1`] = ` -{ - "Resources": { - "TransitGatewayRouteTableCoreTransitGatewayRouteTableD6BC94E0": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "core", - }, - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - "TransitGatewayId": "tgw0001", - }, - "Type": "AWS::EC2::TransitGatewayRouteTable", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-static-route.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-static-route.test.ts.snap deleted file mode 100644 index d3f4c1e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway-static-route.test.ts.snap +++ /dev/null @@ -1,756 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TransitGatewayStaticRoute Construct(TransitGatewayStaticRoute): Snapshot Test 1`] = ` -{ - "Resources": { - "TransitGatewayStaticRoute761B0FE9": { - "Properties": { - "Blackhole": false, - "DestinationCidrBlock": "10.0.0.0/16", - "TransitGatewayAttachmentId": "tgw-123123", - "TransitGatewayRouteTableId": "1234", - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "TransitGatewayStaticRouteCustomCustomResourceCustomResourceResource413D39B8": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEvent99125A40", - "Arn", - ], - }, - "blackhole": false, - "destinationCidrBlock": "10.0.0.0/16", - "transitGatewayAttachmentId": "tgw-123123", - "transitGatewayRouteTableId": "1234", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEvent99125A40": { - "DependsOn": [ - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleDefaultPolicyC604837D", - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleA41D6437", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayStaticRouteCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleA41D6437", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleA41D6437": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleDefaultPolicyC604837D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleDefaultPolicyC604837D", - "Roles": [ - { - "Ref": "TransitGatewayStaticRouteCustomCustomResourceframeworkonEventServiceRoleA41D6437", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceCustomResourceResourceC91DE620": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventCD703591", - "Arn", - ], - }, - "blackhole": true, - "destinationCidrBlock": "10.0.0.0/16", - "transitGatewayRouteTableId": "1234", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventCD703591": { - "DependsOn": [ - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRoleDefaultPolicy8552F206", - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRole6BB17CC7", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayStaticRouteCustomv4Blackhole/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRole6BB17CC7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRole6BB17CC7": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRoleDefaultPolicy8552F206": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRoleDefaultPolicy8552F206", - "Roles": [ - { - "Ref": "TransitGatewayStaticRouteCustomv4BlackholeCustomResourceframeworkonEventServiceRole6BB17CC7", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceCustomResourceResourceBB1416A4": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventF3B85DC5", - "Arn", - ], - }, - "blackhole": true, - "destinationCidrBlock": "::/0", - "transitGatewayRouteTableId": "1234", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventF3B85DC5": { - "DependsOn": [ - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRoleDefaultPolicy6F28DB62", - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRole08720388", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayStaticRouteCustomv6Blackhole/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRole08720388", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRole08720388": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRoleDefaultPolicy6F28DB62": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRoleDefaultPolicy6F28DB62", - "Roles": [ - { - "Ref": "TransitGatewayStaticRouteCustomv6BlackholeCustomResourceframeworkonEventServiceRole08720388", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayStaticRouteCustomv6CustomResourceCustomResourceResource3E930A24": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEvent8C56C981", - "Arn", - ], - }, - "blackhole": false, - "destinationCidrBlock": "::/0", - "transitGatewayAttachmentId": "tgw-123123", - "transitGatewayRouteTableId": "1234", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEvent8C56C981": { - "DependsOn": [ - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleDefaultPolicyEB667C86", - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleBC865826", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayStaticRouteCustomv6/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleBC865826", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleBC865826": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleDefaultPolicyEB667C86": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleDefaultPolicyEB667C86", - "Roles": [ - { - "Ref": "TransitGatewayStaticRouteCustomv6CustomResourceframeworkonEventServiceRoleBC865826", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayStaticRoutev4BlackholeStaticRoute652C2DB7": { - "Properties": { - "Blackhole": true, - "DestinationCidrBlock": "10.0.0.0/16", - "TransitGatewayRouteTableId": "1234", - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "TransitGatewayStaticRoutev6BlackholeStaticRouteE1EC25D1": { - "Properties": { - "Blackhole": true, - "DestinationCidrBlock": "::/0", - "TransitGatewayRouteTableId": "1234", - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - "TransitGatewayStaticRoutev6StaticRouteBD0D3139": { - "Properties": { - "Blackhole": false, - "DestinationCidrBlock": "::/0", - "TransitGatewayAttachmentId": "tgw-123123", - "TransitGatewayRouteTableId": "1234", - }, - "Type": "AWS::EC2::TransitGatewayRoute", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway.test.ts.snap deleted file mode 100644 index 73ec978..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/transit-gateway.test.ts.snap +++ /dev/null @@ -1,2993 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ImportTransitGateway Construct(ImportTransitGateway): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TgwAttachLookupKms4975C754", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TgwAttachLookupC88D0DA8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "name", - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::owningAccountId:role/roleName", - ], - ], - }, - "transitGatewayId": "transitGatewayId", - "type": "vpc", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "TgwAttachLookupKms4975C754": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TransitGateway888C3350": { - "Properties": { - "AmazonSideAsn": 1234, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "key", - "Value": "value", - }, - { - "Key": "Name", - "Value": "name", - }, - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "TransitGatewayAttachment610A3027": { - "Properties": { - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayAttachmentAwsPartition24B0B378": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "disable", - "Ipv6Support": "enable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentAwsPartitionOptions0EBDAD23": { - "Properties": { - "Options": { - "ApplianceModeSupport": "disable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentGovCloud09E6C5F1": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayRouteTableAssociation19E386E4": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceCustomResourceResource083447EF": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281": { - "DependsOn": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTableAssociationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "Roles": [ - { - "Ref": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationBC0F0909": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceCustomResourceResourceF6D0C8C8": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A": { - "DependsOn": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTablePropagationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "Roles": [ - { - "Ref": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`TransitGateway Construct(TransitGateway): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TgwAttachLookupKms4975C754", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TgwAttachLookupC88D0DA8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "name", - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::owningAccountId:role/roleName", - ], - ], - }, - "transitGatewayId": "transitGatewayId", - "type": "vpc", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "TgwAttachLookupKms4975C754": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TransitGateway888C3350": { - "Properties": { - "AmazonSideAsn": 1234, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "key", - "Value": "value", - }, - { - "Key": "Name", - "Value": "name", - }, - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "TransitGatewayAttachment610A3027": { - "Properties": { - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayAttachmentAwsPartition24B0B378": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "disable", - "Ipv6Support": "enable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentAwsPartitionOptions0EBDAD23": { - "Properties": { - "Options": { - "ApplianceModeSupport": "disable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentGovCloud09E6C5F1": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayRouteTableAssociation19E386E4": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceCustomResourceResource083447EF": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281": { - "DependsOn": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTableAssociationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "Roles": [ - { - "Ref": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationBC0F0909": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceCustomResourceResourceF6D0C8C8": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A": { - "DependsOn": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTablePropagationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "Roles": [ - { - "Ref": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`TransitGatewayAttachment Construct(TransitGatewayAttachment): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TgwAttachLookupKms4975C754", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TgwAttachLookupC88D0DA8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "name", - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::owningAccountId:role/roleName", - ], - ], - }, - "transitGatewayId": "transitGatewayId", - "type": "vpc", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "TgwAttachLookupKms4975C754": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TransitGateway888C3350": { - "Properties": { - "AmazonSideAsn": 1234, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "key", - "Value": "value", - }, - { - "Key": "Name", - "Value": "name", - }, - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "TransitGatewayAttachment610A3027": { - "Properties": { - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayAttachmentAwsPartition24B0B378": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "disable", - "Ipv6Support": "enable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentAwsPartitionOptions0EBDAD23": { - "Properties": { - "Options": { - "ApplianceModeSupport": "disable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentGovCloud09E6C5F1": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayRouteTableAssociation19E386E4": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceCustomResourceResource083447EF": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281": { - "DependsOn": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTableAssociationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "Roles": [ - { - "Ref": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationBC0F0909": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceCustomResourceResourceF6D0C8C8": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A": { - "DependsOn": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTablePropagationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "Roles": [ - { - "Ref": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`TransitGatewayRouteTableAssociation Construct(TransitGatewayRouteTableAssociation): Snapshot Test 1`] = ` -{ - "Resources": { - "TransitGateway888C3350": { - "Properties": { - "AmazonSideAsn": 1234, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "key", - "Value": "value", - }, - { - "Key": "Name", - "Value": "name", - }, - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "TransitGatewayRouteTableAssociation19E386E4": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceCustomResourceResource083447EF": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281": { - "DependsOn": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTableAssociationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "Roles": [ - { - "Ref": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationBC0F0909": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceCustomResourceResourceF6D0C8C8": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A": { - "DependsOn": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTablePropagationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "Roles": [ - { - "Ref": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; - -exports[`TransitGatewayRouteTablePropagation Construct(TransitGatewayRouteTablePropagation): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354": { - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TgwAttachLookupKms4975C754", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetTransitGatewayAttachmentCustomResourceProviderRoleA6A22C3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ec2:DescribeTransitGatewayAttachments", - "ec2:DescribeVpnConnections", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TgwAttachLookupC88D0DA8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderLogGroup41699CF3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetTransitGatewayAttachmentCustomResourceProviderHandler7E079354", - "Arn", - ], - }, - "name": "name", - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::owningAccountId:role/roleName", - ], - ], - }, - "transitGatewayId": "transitGatewayId", - "type": "vpc", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetTransitGatewayAttachment", - "UpdateReplacePolicy": "Delete", - }, - "TgwAttachLookupKms4975C754": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TransitGateway888C3350": { - "Properties": { - "AmazonSideAsn": 1234, - "AutoAcceptSharedAttachments": "enable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "Tags": [ - { - "Key": "key", - "Value": "value", - }, - { - "Key": "Name", - "Value": "name", - }, - ], - "VpnEcmpSupport": "enable", - }, - "Type": "AWS::EC2::TransitGateway", - }, - "TransitGatewayAttachment610A3027": { - "Properties": { - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayAttachmentAwsPartition24B0B378": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "disable", - "Ipv6Support": "enable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentAwsPartitionOptions0EBDAD23": { - "Properties": { - "Options": { - "ApplianceModeSupport": "disable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayVpcAttachment", - }, - "TransitGatewayAttachmentGovCloud09E6C5F1": { - "Properties": { - "Options": { - "ApplianceModeSupport": "enable", - "DnsSupport": "enable", - "Ipv6Support": "disable", - }, - "SubnetIds": [ - "one", - "two", - "three", - ], - "Tags": [ - { - "Key": "Name", - "Value": "name", - }, - ], - "TransitGatewayId": "transitGatewayId", - "VpcId": "vpcId", - }, - "Type": "AWS::EC2::TransitGatewayAttachment", - }, - "TransitGatewayRouteTableAssociation19E386E4": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTableAssociation", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceCustomResourceResource083447EF": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEvent82B9C281": { - "DependsOn": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTableAssociationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy89E4DC84", - "Roles": [ - { - "Ref": "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TransitGatewayRouteTableAssociationCustomCustomResourceframeworkonEventServiceRoleF3DD7C87": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationBC0F0909": { - "Properties": { - "TransitGatewayAttachmentId": "transitGatewayAttachmentId", - "TransitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::EC2::TransitGatewayRouteTablePropagation", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceCustomResourceResourceF6D0C8C8": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A", - "Arn", - ], - }, - "transitGatewayAttachmentId": "transitGatewayAttachmentId", - "transitGatewayRouteTableId": "transitGatewayRouteTableId", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventC125880A": { - "DependsOn": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/TransitGatewayRouteTablePropagationCustom/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:test:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRoleDefaultPolicy48F54E60", - "Roles": [ - { - "Ref": "TransitGatewayRouteTablePropagationCustomCustomResourceframeworkonEventServiceRole483D3ED1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-endpoint.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-endpoint.test.ts.snap deleted file mode 100644 index d6c491c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-endpoint.test.ts.snap +++ /dev/null @@ -1,226 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VpcEndpoint Construct(VpcEndpoint): Snapshot Test 1`] = ` -{ - "Resources": { - "TestSecurityGroupDA4B5F83": { - "Properties": { - "GroupDescription": "AWS Private Endpoint Zone", - "GroupName": "TestSecurityGroup", - "Tags": [ - { - "Key": "Name", - "Value": "TestSecurityGroup", - }, - ], - "VpcId": "Test", - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "VpcEndpoint80208C18": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": [ - "organizationId", - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AccessToTrustedPrincipalsAndResources", - }, - ], - "Version": "2012-10-17", - }, - "RouteTableIds": [ - "Test1", - "Test2", - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".service", - ], - ], - }, - "VpcId": "Test", - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "VpcEndpointInterfaceEc21C025642": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": [ - "organizationId", - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AccessToTrustedPrincipalsAndResources", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": true, - "SecurityGroupIds": [ - { - "Ref": "TestSecurityGroupDA4B5F83", - }, - ], - "ServiceName": "ec2", - "SubnetIds": [ - "Test1", - "Test2", - ], - "VpcEndpointType": "Interface", - "VpcId": "Test", - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "VpcEndpointInterfaceGwlb3F654CE2": { - "Properties": { - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.vpce.", - { - "Ref": "AWS::Region", - }, - ".ec2", - ], - ], - }, - "SubnetIds": [ - "Test1", - "Test2", - ], - "VpcEndpointType": "GatewayLoadBalancer", - "VpcId": "Test", - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "VpcEndpointInterfaceS343EF5B95": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": [ - "organizationId", - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AccessToTrustedPrincipalsAndResources", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": true, - "SecurityGroupIds": [ - { - "Ref": "TestSecurityGroupDA4B5F83", - }, - ], - "ServiceName": "com.amazonaws.s3-global.accesspoint", - "SubnetIds": [ - "Test1", - "Test2", - ], - "VpcEndpointType": "Interface", - "VpcId": "Test", - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "VpcEndpointInterfaceSagemaker70CE2242": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": [ - "organizationId", - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AccessToTrustedPrincipalsAndResources", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": true, - "SecurityGroupIds": [ - { - "Ref": "TestSecurityGroupDA4B5F83", - }, - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "aws.sagemaker.", - { - "Ref": "AWS::Region", - }, - ".notebook", - ], - ], - }, - "SubnetIds": [ - "Test1", - "Test2", - ], - "VpcEndpointType": "Interface", - "VpcId": "Test", - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - "VpcEndpointid71FBEB7E": { - "Properties": { - "DestinationCidrBlock": "10.100.0.0/16", - "RouteTableId": "routeTableId", - "VpcEndpointId": { - "Ref": "VpcEndpoint80208C18", - }, - }, - "Type": "AWS::EC2::Route", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-id-lookup.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-id-lookup.test.ts.snap deleted file mode 100644 index 7978d4f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-id-lookup.test.ts.snap +++ /dev/null @@ -1,312 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VpcIdLookup Construct(VpcIdLookup): Snapshot Test 1`] = ` -{ - "Resources": { - "CWKeyF87F989A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "VpcIdLookupD915DEA4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "VpcIdLookupVpcIdLookupFunctionLogGroup394AB6B3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "VpcIdLookupVpcIdLookupProviderframeworkonEventA6FF7933", - "Arn", - ], - }, - "vpcName": "TestVpc", - }, - "Type": "Custom::VpcIdLookup", - "UpdateReplacePolicy": "Delete", - }, - "VpcIdLookupVpcIdLookupFunctionCE747608": { - "DependsOn": [ - "VpcIdLookupVpcIdLookupFunctionServiceRoleDefaultPolicyE4E2B3D6", - "VpcIdLookupVpcIdLookupFunctionServiceRoleB001E74A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lookup vpc id from account", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "VpcIdLookupVpcIdLookupFunctionServiceRoleB001E74A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "VpcIdLookupVpcIdLookupFunctionLogGroup394AB6B3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CWKeyF87F989A", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "VpcIdLookupVpcIdLookupFunctionCE747608", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "VpcIdLookupVpcIdLookupFunctionServiceRoleB001E74A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "VpcIdLookupVpcIdLookupFunctionServiceRoleDefaultPolicyE4E2B3D6": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ec2:DescribeVpcs", - "Effect": "Allow", - "Resource": "*", - "Sid": "Ec2Actions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "VpcIdLookupVpcIdLookupFunctionServiceRoleDefaultPolicyE4E2B3D6", - "Roles": [ - { - "Ref": "VpcIdLookupVpcIdLookupFunctionServiceRoleB001E74A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "VpcIdLookupVpcIdLookupProviderframeworkonEventA6FF7933": { - "DependsOn": [ - "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRoleDefaultPolicyF53A1EC3", - "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRole5452CF47", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/VpcIdLookup/VpcIdLookupProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "VpcIdLookupVpcIdLookupFunctionCE747608", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRole5452CF47", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRole5452CF47": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRoleDefaultPolicyF53A1EC3": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "VpcIdLookupVpcIdLookupFunctionCE747608", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "VpcIdLookupVpcIdLookupFunctionCE747608", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRoleDefaultPolicyF53A1EC3", - "Roles": [ - { - "Ref": "VpcIdLookupVpcIdLookupProviderframeworkonEventServiceRole5452CF47", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-import.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-import.test.ts.snap deleted file mode 100644 index 9ad639c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-import.test.ts.snap +++ /dev/null @@ -1,504 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VpcImport Construct(Vpc): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F": { - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testKmsTestDeleteDefaultSgRules7B9FDE55", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeSecurityGroups", - "ec2:RevokeSecurityGroupIngress", - "ec2:RevokeSecurityGroupEgress", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ImportedSubnet2RouteTableAssociationC26BEA90": { - "Properties": { - "RouteTableId": "someImportedRouteTableId", - "SubnetId": "someImportedSubnetId2", - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "ImportedSubnetRouteTableAssociation53282B24": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": "someImportedSubnetId", - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "TestDeleteDefaultSgRules8A6777C7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - "vpcId": "someVpcId", - }, - "Type": "Custom::DeleteDefaultSecurityGroupRules", - "UpdateReplacePolicy": "Delete", - }, - "TestVpcCloudWatchFlowLogD5309822": { - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::GetAtt": [ - "TestVpcFlowLogsRole35E8FB64", - "Arn", - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "TestVpcFlowLogsGroup63B469E8", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": "someImportedVpcId", - "ResourceType": "VPC", - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "TestVpcDhcpOptionsAssociationDB23B751": { - "Properties": { - "DhcpOptionsId": "test-dhcp-opts", - "VpcId": "someImportedVpcId", - }, - "Type": "AWS::EC2::VPCDHCPOptionsAssociation", - }, - "TestVpcFlowLogsGroup63B469E8": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testkey2C81DE6D4", - "Arn", - ], - }, - "RetentionInDays": 10, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "TestVpcFlowLogsRole35E8FB64": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "vpc-flow-logs.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "TestVpcFlowLogsRoleDefaultPolicyA03A358B": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogDelivery", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DeleteLogDelivery", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TestVpcFlowLogsGroup63B469E8", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestVpcFlowLogsRoleDefaultPolicyA03A358B", - "Roles": [ - { - "Ref": "TestVpcFlowLogsRole35E8FB64", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestVpcInternetGateway01360C82": { - "Type": "AWS::EC2::InternetGateway", - }, - "TestVpcInternetGatewayAttachment60E451D5": { - "Properties": { - "VpcId": "someImportedVpcId", - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcS3FlowLog86FD93A4": { - "Properties": { - "LogDestination": "arn:aws:s3:::aws-accelerator-test-111111111111-us-east-1/vpc-flow-logs/", - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": "someImportedVpcId", - "ResourceType": "VPC", - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "TestVpcVirtualPrivateGateway56B5C340": { - "Properties": { - "AmazonSideAsn": 65000, - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNGateway", - }, - "TestVpcVirtualPrivateGatewayAttachment5D655F8D": { - "Properties": { - "VpcId": "someImportedVpcId", - "VpnGatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcVpcCidrBlock8C35CE6C": { - "Properties": { - "CidrBlock": "10.2.0.0/16", - "VpcId": "someImportedVpcId", - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "naclTest75916313": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "naclTest", - }, - { - "Key": "test", - "Value": "test2", - }, - ], - "VpcId": "someImportedVpcId", - }, - "Type": "AWS::EC2::NetworkAcl", - }, - "naclTestnaclEntryF5491A57": { - "Properties": { - "CidrBlock": "10.0.0.14/32", - "Egress": true, - "NetworkAclId": { - "Ref": "naclTest75916313", - }, - "Protocol": 443, - "RuleAction": "deny", - "RuleNumber": 2, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "naclTestnaclSubnetAssociation5D7C6E54": { - "Properties": { - "NetworkAclId": { - "Ref": "naclTest75916313", - }, - "SubnetId": "someImportedSubnetId", - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "natGw090A9EE3": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "natGwEip8079CDCC", - "AllocationId", - ], - }, - "SubnetId": "someImportedSubnetId", - "Tags": [ - { - "Key": "Name", - "Value": "ngw", - }, - { - "Key": "test", - "Value": "test2", - }, - ], - }, - "Type": "AWS::EC2::NatGateway", - }, - "natGwEip8079CDCC": { - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "ngw", - }, - ], - }, - "Type": "AWS::EC2::EIP", - }, - "someImportedSecurityGroupIdegressTest101ED950": { - "Properties": { - "CidrIp": "10.0.0.7/32", - "Description": "test description", - "FromPort": 80, - "GroupId": "someImportedSecurityGroupId", - "IpProtocol": "ipv4", - "ToPort": 80, - }, - "Type": "AWS::EC2::SecurityGroupEgress", - }, - "someImportedSecurityGroupIdingressTest4ACAC2C2": { - "Properties": { - "CidrIp": "10.0.0.7/32", - "Description": "test description", - "FromPort": 80, - "GroupId": "someImportedSecurityGroupId", - "IpProtocol": "ipv4", - "ToPort": 80, - }, - "Type": "AWS::EC2::SecurityGroupIngress", - }, - "testKmsTestDeleteDefaultSgRules7B9FDE55": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testkey2C81DE6D4": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testrtEA36A15F": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "test-rt", - }, - ], - "VpcId": "someImportedVpcId", - }, - "Type": "AWS::EC2::RouteTable", - }, - "testrtIgwRoute03C548FC": { - "DependsOn": [ - "TestVpcInternetGatewayAttachment60E451D5", - ], - "Properties": { - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - }, - "Type": "AWS::EC2::Route", - }, - "tetSgC7E79636": { - "Properties": { - "GroupDescription": "test", - "GroupName": "test", - "Tags": [ - { - "Key": "Name", - "Value": "test", - }, - { - "Key": "test", - "Value": "test2", - }, - ], - "VpcId": "someImportedVpcId", - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "tetSgegressTest4FBFADC1": { - "Properties": { - "CidrIp": "10.0.0.7/32", - "Description": "test description", - "FromPort": 80, - "GroupId": { - "Ref": "tetSgC7E79636", - }, - "IpProtocol": "ipv4", - "ToPort": 80, - }, - "Type": "AWS::EC2::SecurityGroupEgress", - }, - "tetSgingressTestFBF41BB4": { - "Properties": { - "CidrIp": "10.0.0.7/32", - "Description": "test description", - "FromPort": 80, - "GroupId": { - "Ref": "tetSgC7E79636", - }, - "IpProtocol": "ipv4", - "ToPort": 80, - }, - "Type": "AWS::EC2::SecurityGroupIngress", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-peering.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-peering.test.ts.snap deleted file mode 100644 index cef76f3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc-peering.test.ts.snap +++ /dev/null @@ -1,598 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VpcPeering Construct(VpcPeering): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077": { - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "kmsKey3BB021CC", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - }, - ], - ], - }, - "RetentionInDays": 10, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPrefixListRouteCustomResourceProviderRoleD08268B5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateRoute", - "ec2:DeleteRoute", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AllowModifyRoutes", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestPeeringF63C5812": { - "Properties": { - "PeerOwnerId": "111111111111", - "PeerRegion": "us-east-1", - "PeerRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/TestRole", - ], - ], - }, - "PeerVpcId": "AccepterVpc", - "Tags": [ - { - "Key": "key", - "Value": "value", - }, - { - "Key": "Name", - "Value": "Test", - }, - ], - "VpcId": "RequesterVpc", - }, - "Type": "AWS::EC2::VPCPeeringConnection", - }, - "TestPeeringcrossAccountPeerRoutepl9DA94FD5": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "myProviderframeworkonEventAB595BBA", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/role", - ], - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-test", - "RouteTableId": "rt-3456", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "TestPeeringcrossAccountPeerRoutev4CAB8D1B0": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "myProviderframeworkonEventAB595BBA", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/role", - ], - ], - }, - "routeDefinition": { - "DestinationCidrBlock": "10.0.0.6/32", - "RouteTableId": "rt-3456", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "TestPeeringcrossAccountPeerRoutev6CB7B447F": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "myProviderframeworkonEventAB595BBA", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - "roleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/role", - ], - ], - }, - "routeDefinition": { - "DestinationIpv6CidrBlock": "fd00::/8", - "RouteTableId": "rt-3456", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - }, - "Type": "Custom::CrossAccountRoute", - "UpdateReplacePolicy": "Delete", - }, - "TestPeeringvpcPeeringRoute313B0AACD": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "destinationRoute", - "RouteTableId": "rt-12345", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "TestPeeringvpcPeeringRoutepl5A3561CF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPrefixListRouteCustomResourceProviderLogGroup68DB81A5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPrefixListRouteCustomResourceProviderHandler5B28D077", - "Arn", - ], - }, - "routeDefinition": { - "DestinationPrefixListId": "pl-test", - "RouteTableId": "rt-12345", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - }, - "Type": "Custom::PrefixListRoute", - "UpdateReplacePolicy": "Delete", - }, - "TestPeeringvpcPeeringRoutev49F442833": { - "Properties": { - "DestinationCidrBlock": "10.0.0.5/32", - "RouteTableId": "rt-12345", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - "Type": "AWS::EC2::Route", - }, - "TestPeeringvpcPeeringRoutev64A0FC377": { - "Properties": { - "DestinationIpv6CidrBlock": "fd00::/8", - "RouteTableId": "rt-12345", - "VpcPeeringConnectionId": { - "Ref": "TestPeeringF63C5812", - }, - }, - "Type": "AWS::EC2::Route", - }, - "kmsKey1A3FA0EFA": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "kmsKey2EF55D91A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "kmsKey3992EC0B2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "kmsKey3BB021CC": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "myProviderframeworkonEventAB595BBA": { - "DependsOn": [ - "myProviderframeworkonEventServiceRoleDefaultPolicy4EAC636C", - "myProviderframeworkonEventServiceRole94BD41C2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/myProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "testAF53AC38", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "myProviderframeworkonEventServiceRole94BD41C2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "myProviderframeworkonEventServiceRole94BD41C2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "myProviderframeworkonEventServiceRoleDefaultPolicy4EAC636C": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "testAF53AC38", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "testAF53AC38", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "myProviderframeworkonEventServiceRoleDefaultPolicy4EAC636C", - "Roles": [ - { - "Ref": "myProviderframeworkonEventServiceRole94BD41C2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "testAF53AC38": { - "DependsOn": [ - "testServiceRole78466FFF", - ], - "Properties": { - "Code": { - "ZipFile": "foo", - }, - "Handler": "handler", - "Role": { - "Fn::GetAtt": [ - "testServiceRole78466FFF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "testServiceRole78466FFF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc.test.ts.snap deleted file mode 100644 index f104c56..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpc.test.ts.snap +++ /dev/null @@ -1,1226 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Vpc Construct(Vpc): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F": { - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testKmsTestDeleteDefaultSgRules7B9FDE55", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderRole7BAE247B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeSecurityGroups", - "ec2:RevokeSecurityGroupIngress", - "ec2:RevokeSecurityGroupEgress", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A": { - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testKms8D3EA89B", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - }, - ], - ], - }, - "RetentionInDays": 10, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIpamSubnetCustomResourceProviderRoleA2FF4E6D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:CreateTags", - "ec2:DeleteSubnet", - "ec2:DeleteTags", - "ec2:ModifySubnetAttribute", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":subnet/*", - ], - ], - }, - }, - { - "Action": [ - "ec2:CreateSubnet", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":subnet/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":vpc/*", - ], - ], - }, - ], - }, - { - "Action": [ - "ec2:DescribeVpcs", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DualStackSubnetC42B1019": { - "Properties": { - "AvailabilityZoneId": "1", - "CidrBlock": "10.0.0.0/24", - "EnableDns64": true, - "Ipv6CidrBlock": "fd00::/58", - "PrivateDnsNameOptionsOnLaunch": { - "EnableResourceNameDnsAAAARecord": true, - "EnableResourceNameDnsARecord": true, - }, - "Tags": [ - { - "Key": "Name", - "Value": "test-dualstack-subnet", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "DualStackSubnetRouteTableAssociationD43A2316": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": { - "Ref": "DualStackSubnetC42B1019", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "Ipv6OnlySubnet53DACA43": { - "Properties": { - "AvailabilityZoneId": "1", - "EnableDns64": true, - "Ipv6CidrBlock": "fd00::/58", - "Ipv6Native": true, - "PrivateDnsNameOptionsOnLaunch": { - "EnableResourceNameDnsAAAARecord": true, - "EnableResourceNameDnsARecord": false, - "HostnameType": "resource-name", - }, - "Tags": [ - { - "Key": "Name", - "Value": "test-ipv6-only-subnet", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "Ipv6OnlySubnetRouteTableAssociation88B099B9": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": { - "Ref": "Ipv6OnlySubnet53DACA43", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "TestDeleteDefaultSgRules8A6777C7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderLogGroup36ABE46B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomDeleteDefaultSecurityGroupRulesCustomResourceProviderHandler0579558F", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - "vpcId": "someVpcId", - }, - "Type": "Custom::DeleteDefaultSecurityGroupRules", - "UpdateReplacePolicy": "Delete", - }, - "TestVpcCloudWatchFlowLogD5309822": { - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::GetAtt": [ - "TestVpcFlowLogsRole35E8FB64", - "Arn", - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "TestVpcFlowLogsGroup63B469E8", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "TestVpcE77CE678", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "TestVpcDhcpOptionsAssociationDB23B751": { - "Properties": { - "DhcpOptionsId": "Test-Options", - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::VPCDHCPOptionsAssociation", - }, - "TestVpcE77CE678": { - "Properties": { - "CidrBlock": "10.0.0.0/16", - "EnableDnsHostnames": false, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "TestVpcExistingIamB9508C93": { - "Properties": { - "CidrBlock": "10.0.0.0/16", - "EnableDnsHostnames": false, - "EnableDnsSupport": true, - "InstanceTenancy": "default", - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - }, - "Type": "AWS::EC2::VPC", - }, - "TestVpcExistingIamCloudWatchFlowLog976A715F": { - "Properties": { - "DeliverLogsPermissionArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorVpcFlowLogsRole", - ], - ], - }, - "LogDestination": { - "Fn::GetAtt": [ - "TestVpcExistingIamFlowLogsGroupD35F5586", - "Arn", - ], - }, - "LogDestinationType": "cloud-watch-logs", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "TestVpcExistingIamB9508C93", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "TestVpcExistingIamDhcpOptionsAssociation92EB66FD": { - "Properties": { - "DhcpOptionsId": "Test-Options", - "VpcId": { - "Ref": "TestVpcExistingIamB9508C93", - }, - }, - "Type": "AWS::EC2::VPCDHCPOptionsAssociation", - }, - "TestVpcExistingIamFlowLogsGroupD35F5586": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testKey2ExistingIam0E728F30", - "Arn", - ], - }, - "RetentionInDays": 10, - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "TestVpcExistingIamInternetGateway812AACEB": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - }, - "Type": "AWS::EC2::InternetGateway", - }, - "TestVpcExistingIamInternetGatewayAttachment2ABE52DD": { - "Properties": { - "InternetGatewayId": { - "Ref": "TestVpcExistingIamInternetGateway812AACEB", - }, - "VpcId": { - "Ref": "TestVpcExistingIamB9508C93", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcExistingIamS3FlowLog15E7512D": { - "Properties": { - "LogDestination": "arn:aws:s3:::aws-accelerator-test-111111111111-us-east-1/vpc-flow-logs/", - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "TestVpcExistingIamB9508C93", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "TestVpcExistingIamVirtualPrivateGateway4A00A9BC": { - "Properties": { - "AmazonSideAsn": 65000, - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNGateway", - }, - "TestVpcExistingIamVirtualPrivateGatewayAttachmentE053A5C5": { - "Properties": { - "VpcId": { - "Ref": "TestVpcExistingIamB9508C93", - }, - "VpnGatewayId": { - "Ref": "TestVpcExistingIamVirtualPrivateGateway4A00A9BC", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcFlowLogsGroup63B469E8": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "testkey2C81DE6D4", - "Arn", - ], - }, - "RetentionInDays": 10, - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "TestVpcFlowLogsRole35E8FB64": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "vpc-flow-logs.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestVpcFlowLogsRoleDefaultPolicyA03A358B": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogDelivery", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DeleteLogDelivery", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TestVpcFlowLogsGroup63B469E8", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "TestVpcFlowLogsRoleDefaultPolicyA03A358B", - "Roles": [ - { - "Ref": "TestVpcFlowLogsRole35E8FB64", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestVpcInternetGateway01360C82": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - }, - "Type": "AWS::EC2::InternetGateway", - }, - "TestVpcInternetGatewayAttachment60E451D5": { - "Properties": { - "InternetGatewayId": { - "Ref": "TestVpcInternetGateway01360C82", - }, - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcIpv6CidrBlock02C3E0D0B": { - "Properties": { - "AmazonProvidedIpv6CidrBlock": true, - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "TestVpcIpv6CidrBlock121C0BB97": { - "Properties": { - "Ipv6CidrBlock": "::1", - "Ipv6Pool": "ipv6Pool-ec2-1234", - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "TestVpcS3FlowLog86FD93A4": { - "Properties": { - "LogDestination": "arn:aws:s3:::aws-accelerator-test-111111111111-us-east-1/vpc-flow-logs/", - "LogDestinationType": "s3", - "MaxAggregationInterval": 60, - "ResourceId": { - "Ref": "TestVpcE77CE678", - }, - "ResourceType": "VPC", - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - "TrafficType": "ALL", - }, - "Type": "AWS::EC2::FlowLog", - }, - "TestVpcVirtualPrivateGateway56B5C340": { - "Properties": { - "AmazonSideAsn": 65000, - "Tags": [ - { - "Key": "Name", - "Value": "Main", - }, - ], - "Type": "ipsec.1", - }, - "Type": "AWS::EC2::VPNGateway", - }, - "TestVpcVirtualPrivateGatewayAttachment5D655F8D": { - "Properties": { - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - "VpnGatewayId": { - "Ref": "TestVpcVirtualPrivateGateway56B5C340", - }, - }, - "Type": "AWS::EC2::VPCGatewayAttachment", - }, - "TestVpcVpcCidrBlock8C35CE6C": { - "Properties": { - "CidrBlock": "10.2.0.0/16", - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::VPCCidrBlock", - }, - "naclTest75916313": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "naclTest", - }, - { - "Key": "test", - "Value": "test2", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::NetworkAcl", - }, - "naclTestnaclEntryF5491A57": { - "Properties": { - "CidrBlock": "10.0.0.14/32", - "Egress": true, - "NetworkAclId": { - "Ref": "naclTest75916313", - }, - "Protocol": 443, - "RuleAction": "deny", - "RuleNumber": 2, - }, - "Type": "AWS::EC2::NetworkAclEntry", - }, - "naclTestnaclSubnetAssociation5D7C6E54": { - "Properties": { - "NetworkAclId": { - "Ref": "naclTest75916313", - }, - "SubnetId": { - "Ref": "testAF53AC38", - }, - }, - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - }, - "natGw090A9EE3": { - "Properties": { - "AllocationId": { - "Fn::GetAtt": [ - "natGwEip8079CDCC", - "AllocationId", - ], - }, - "SubnetId": { - "Ref": "testAF53AC38", - }, - "Tags": [ - { - "Key": "Name", - "Value": "ngw", - }, - { - "Key": "test", - "Value": "test2", - }, - ], - }, - "Type": "AWS::EC2::NatGateway", - }, - "natGwEip8079CDCC": { - "Properties": { - "Domain": "vpc", - "Tags": [ - { - "Key": "Name", - "Value": "ngw", - }, - ], - }, - "Type": "AWS::EC2::EIP", - }, - "testAF53AC38": { - "Properties": { - "AvailabilityZone": "a", - "CidrBlock": "10.0.1.0/24", - "OutpostArn": "", - "Tags": [ - { - "Key": "Name", - "Value": "testSubnetOutpost", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::Subnet", - }, - "testKey2ExistingIam0E728F30": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testKms1A8429BFF": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testKms25B759B9A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testKms8D3EA89B": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testKmsTestDeleteDefaultSgRules7B9FDE55": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testRouteTableAssociation93A5191C": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": { - "Ref": "testAF53AC38", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "testSubnetIpamB3252329": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZone": "b", - "basePool": [ - "myBasePool", - ], - "ipamAllocation": { - "ipamPoolName": "test", - "netmaskLength": 24, - }, - "name": "testSubnet", - "tags": [], - "vpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "testSubnetIpamPhysicalAz1F5518C4E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZoneId": "1", - "basePool": [ - "myBasePool", - ], - "ipamAllocation": { - "ipamPoolName": "test", - "netmaskLength": 24, - }, - "name": "testSubnetPhysicalAz1", - "tags": [], - "vpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "testSubnetIpamPhysicalAz1RouteTableAssociationFEC58856": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": { - "Ref": "testSubnetIpamPhysicalAz1F5518C4E", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "testSubnetIpamRouteTableAssociation3C411F29": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": { - "Ref": "testSubnetIpamB3252329", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "testSubnetPhysicalAz2E088113B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIpamSubnetCustomResourceProviderLogGroup3BB67050", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIpamSubnetCustomResourceProviderHandlerF7AF0D7A", - "Arn", - ], - }, - "availabilityZoneId": "2", - "basePool": [ - "myBasePool", - ], - "ipamAllocation": { - "ipamPoolName": "test", - "netmaskLength": 24, - }, - "name": "testSubnetPhysicalAz2", - "tags": [], - "vpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "Custom::IpamSubnet", - "UpdateReplacePolicy": "Delete", - }, - "testSubnetPhysicalAz2RouteTableAssociation3FE5D9C7": { - "Properties": { - "RouteTableId": { - "Ref": "testrtEA36A15F", - }, - "SubnetId": { - "Ref": "testSubnetPhysicalAz2E088113B", - }, - }, - "Type": "AWS::EC2::SubnetRouteTableAssociation", - }, - "testkey2C81DE6D4": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "testrtEA36A15F": { - "Properties": { - "Tags": [ - { - "Key": "Name", - "Value": "test-rt", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::RouteTable", - }, - "tetSgC7E79636": { - "Properties": { - "GroupDescription": "test", - "GroupName": "test", - "Tags": [ - { - "Key": "Name", - "Value": "test", - }, - { - "Key": "test", - "Value": "test2", - }, - ], - "VpcId": { - "Ref": "TestVpcE77CE678", - }, - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "tetSgegressTest4FBFADC1": { - "Properties": { - "CidrIp": "10.0.0.7/32", - "Description": "test description", - "FromPort": 80, - "GroupId": { - "Ref": "tetSgC7E79636", - }, - "IpProtocol": "ipv4", - "ToPort": 80, - }, - "Type": "AWS::EC2::SecurityGroupEgress", - }, - "tetSgingressTestFBF41BB4": { - "Properties": { - "CidrIp": "10.0.0.7/32", - "Description": "test description", - "FromPort": 80, - "GroupId": { - "Ref": "tetSgC7E79636", - }, - "IpProtocol": "ipv4", - "ToPort": 80, - }, - "Type": "AWS::EC2::SecurityGroupIngress", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpn-connection.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpn-connection.test.ts.snap deleted file mode 100644 index 7bc15b4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/__snapshots__/vpn-connection.test.ts.snap +++ /dev/null @@ -1,270 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VpnConnection Construct(VpnConnection): Snapshot Test 1`] = ` -{ - "Resources": { - "AdvancedVpnCustomResourceCustomResourceResource604EC204": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdvancedVpnCustomResourceframeworkonEventC96A1021", - "Arn", - ], - }, - "amazonIpv4NetworkCidr": "10.0.0.0/16", - "customerGatewayId": "Test-Cgw", - "customerIpv4NetworkCidr": "192.168.0.0/16", - "enableVpnAcceleration": true, - "staticRoutesOnly": true, - "tags": [ - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - { - "Key": "Name", - "Value": "Advanced-Vpn", - }, - ], - "transitGatewayId": "Test-tgw", - "vpnTunnelOptions": [ - { - "dpdTimeoutAction": "restart", - "dpdTimeoutSeconds": 60, - "ikeVersions": [ - 2, - ], - "logging": { - "enable": true, - }, - "phase1": { - "dhGroups": [ - 14, - 20, - ], - "encryptionAlgorithms": [ - "AES256", - ], - "integrityAlgorithms": [ - "SHA2-256", - ], - }, - "phase2": { - "dhGroups": [ - 14, - 20, - ], - "encryptionAlgorithms": [ - "AES256", - ], - "integrityAlgorithms": [ - "SHA2-256", - ], - }, - "preSharedKey": "test-key-1", - "tunnelInsideCidr": "169.254.200.0/30", - }, - { - "preSharedKey": "test-key-1", - "tunnelInsideCidr": "169.254.100.0/30", - }, - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "AdvancedVpnCustomResourceframeworkonEventC96A1021": { - "DependsOn": [ - "AdvancedVpnCustomResourceframeworkonEventServiceRoleDefaultPolicyC5BC0C38", - "AdvancedVpnCustomResourceframeworkonEventServiceRole2EBDA262", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/AdvancedVpn/CustomResource/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:TestFunction", - ], - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "AdvancedVpnCustomResourceframeworkonEventServiceRole2EBDA262", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "AdvancedVpnCustomResourceframeworkonEventServiceRole2EBDA262": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "AdvancedVpnCustomResourceframeworkonEventServiceRoleDefaultPolicyC5BC0C38": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:TestFunction", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":lambda:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":function:TestFunction:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AdvancedVpnCustomResourceframeworkonEventServiceRoleDefaultPolicyC5BC0C38", - "Roles": [ - { - "Ref": "AdvancedVpnCustomResourceframeworkonEventServiceRole2EBDA262", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TestVpnVpnConnection655CF32D": { - "Properties": { - "CustomerGatewayId": "Test-Cgw", - "StaticRoutesOnly": true, - "Tags": [ - { - "Key": "Name", - "Value": "Test-Vpn", - }, - { - "Key": "Test-Key", - "Value": "Test-Value", - }, - ], - "TransitGatewayId": "Test-tgw", - "Type": "ipsec.1", - "VpnTunnelOptionsSpecifications": [ - { - "PreSharedKey": "test-key-1", - "TunnelInsideCidr": "169.254.200.0/30", - }, - { - "PreSharedKey": "test-key-1", - "TunnelInsideCidr": "169.254.100.0/30", - }, - ], - }, - "Type": "AWS::EC2::VPNConnection", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming.test.ts deleted file mode 100644 index 0eb5aa4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { WarmAccount } from '../../lib/aws-ec2/account-warming'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(WarmAccount): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -new WarmAccount(stack, 'AccountWarming', { - cloudwatchKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, - ssmPrefix: '/accelerator', -}); - -/** - * Report Definition construct test - */ -describe('AccountWarming', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming/index.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming/index.test.ts deleted file mode 100644 index a60fd73..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/account-warming/index.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { handler } from '../../../lib/aws-ec2/account-warming/index'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { - EC2Client, - DescribeVpcsCommand, - CreateVpcCommand, - DescribeSubnetsCommand, - DescribeInstancesCommand, - CreateSubnetCommand, - RunInstancesCommand, - TerminateInstancesCommand, - DeleteSubnetCommand, - DeleteVpcCommand, -} from '@aws-sdk/client-ec2'; -import { SSMClient, GetParameterCommand, DeleteParameterCommand } from '@aws-sdk/client-ssm'; -import { - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceDeleteEvent, -} from '../../../lib/lza-custom-resource'; -import { expect, it, beforeEach, afterEach } from '@jest/globals'; - -let ssmMock: AwsClientStub; -let ec2Mock: AwsClientStub; - -beforeEach(() => { - ssmMock = mockClient(SSMClient); - ec2Mock = mockClient(EC2Client); -}); - -afterEach(() => { - ssmMock.restore(); - ec2Mock.restore(); -}); - -// Given -const createEvent: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::AccountWarming', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - ssmPrefix: '/test', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/account-warming create event account warmed', async () => { - // when - account warming is true - ssmMock - .on(GetParameterCommand, { Name: '/test/account/pre-warmed' }) - .resolves({ Parameter: { Name: '/test/account/pre-warmed', Value: 'true' } }); - const response = await handler(createEvent); - // then - response isComplete is true - expect(response?.IsComplete).toBeTruthy(); -}); - -// When -it('@aws-accelerator/constructs/account-warming create event account not warmed pre-existing subnet, vpc, instance', async () => { - // when - account warming is false - ssmMock - .on(GetParameterCommand, { Name: '/test/account/pre-warmed' }) - .resolves({ Parameter: { Name: '/test/account/pre-warmed', Value: 'false' } }); - // when - vpcs found - ec2Mock - .on(DescribeVpcsCommand, { Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }) - .resolves({ Vpcs: [{ VpcId: 'vpc-12345678' }] }); - //when - vpc created, get subnet for vpc - ec2Mock - .on(DescribeSubnetsCommand, { - Filters: [{ Name: 'vpc-id', Values: ['vpc-12345678'] }], - }) - .resolves({ Subnets: [{ SubnetId: 'subnet-123' }] }); - - // when - subnet is found get instance - ec2Mock - .on(DescribeInstancesCommand, { Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }) - .resolves({ Reservations: [{ Instances: [{ InstanceId: 'i-1234', State: { Code: 10, Name: 'running' } }] }] }); - - // when - instance is found - const response = await handler(createEvent); - // then - response isComplete is false - expect(response?.IsComplete).toBeFalsy(); -}); - -// When -it('@aws-accelerator/constructs/account-warming create event account not warmed new subnet, vpc, instance', async () => { - // when - account warming is false - ssmMock - .on(GetParameterCommand, { Name: '/test/account/pre-warmed' }) - .resolves({ Parameter: { Name: '/test/account/pre-warmed', Value: 'false' } }); - // when - no vpcs found - ec2Mock - .on(DescribeVpcsCommand, { Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }) - .resolves({ Vpcs: [] }); - // when - no vpcs found, create vpc - ec2Mock - .on(CreateVpcCommand, { - CidrBlock: '10.10.10.0/24', - TagSpecifications: [{ ResourceType: 'vpc', Tags: [{ Key: 'Name', Value: 'accelerator-warm' }] }], - }) - .resolves({ Vpc: { VpcId: 'vpc-12345678' } }); - //when - vpc created, get subnet for vpc - ec2Mock - .on(DescribeSubnetsCommand, { - Filters: [{ Name: 'vpc-id', Values: ['vpc-12345678'] }], - }) - .resolves({ Subnets: [] }); - // when - no subnet found, create subnet - ec2Mock - .on(CreateSubnetCommand, { - VpcId: 'vpc-12345678', - CidrBlock: '10.10.10.0/24', - TagSpecifications: [{ ResourceType: 'subnet', Tags: [{ Key: 'Name', Value: 'accelerator-warm' }] }], - }) - .resolves({ Subnet: { SubnetId: 'subnet-123' } }); - // when - subnet is found get instance - ec2Mock - .on(DescribeInstancesCommand, { Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }) - .resolves({ Reservations: [] }); - // when - no instance is found, create instance - ssmMock - .on(GetParameterCommand, { Name: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' }) - .resolves({ Parameter: { Value: 'ami-123' } }); - ec2Mock - .on(RunInstancesCommand, { - InstanceType: 't2.micro', - MaxCount: 1, - MinCount: 1, - SubnetId: 'subnet-123', - ImageId: 'ami-123', - TagSpecifications: [{ ResourceType: 'instance', Tags: [{ Key: 'Name', Value: 'accelerator-warm' }] }], - }) - .resolves({ Instances: [{ InstanceId: 'i-1234' }] }); - - // when - instance is found - const response = await handler(createEvent); - // then - response isComplete is false - expect(response?.IsComplete).toBeFalsy(); -}); - -// Given -const deleteEvent: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::AccountWarming', - PhysicalResourceId: 'example-physical-resource-id', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - ssmPrefix: '/test', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/account-warming delete event', async () => { - // get instance id to terminate - // instance is running, shutting down or terminated - ec2Mock.on(DescribeInstancesCommand, { Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }).resolves({ - Reservations: [ - { - Instances: [ - { InstanceId: 'i-1234', State: { Code: 10, Name: 'running' } }, - { InstanceId: 'i-5678', State: { Code: 32, Name: 'shutting-down' } }, - { InstanceId: 'i-9012', State: { Code: 48, Name: 'terminated' } }, - ], - }, - ], - }); - ec2Mock.on(TerminateInstancesCommand, { InstanceIds: ['i-1234'] }).resolves({}); - - //check instance status and return running, shutting down then terminated - ec2Mock - .on(DescribeInstancesCommand, { InstanceIds: ['i-1234'] }) - .resolvesOnce({ Reservations: [{ Instances: [{ InstanceId: 'i-1234', State: { Code: 10, Name: 'running' } }] }] }) - .resolvesOnce({ - Reservations: [{ Instances: [{ InstanceId: 'i-1234', State: { Code: 32, Name: 'shutting-down' } }] }], - }) - .resolves({ - Reservations: [{ Instances: [{ InstanceId: 'i-1234', State: { Code: 48, Name: 'terminated' } }] }], - }); - - ec2Mock - .on(DescribeInstancesCommand, { InstanceIds: ['i-5678'] }) - .resolvesOnce({ Reservations: [{ Instances: [{ InstanceId: 'i-5678', State: { Code: 10, Name: 'running' } }] }] }) - .resolvesOnce({ - Reservations: [{ Instances: [{ InstanceId: 'i-5678', State: { Code: 32, Name: 'shutting-down' } }] }], - }) - .resolves({ - Reservations: [{ Instances: [{ InstanceId: 'i-5678', State: { Code: 48, Name: 'terminated' } }] }], - }); - // when - vpcs found - ec2Mock - .on(DescribeVpcsCommand, { Filters: [{ Name: 'tag:Name', Values: ['accelerator-warm'] }] }) - .resolves({ Vpcs: [{ VpcId: 'vpc-12345678' }] }); - - //when - vpc found, get subnet for vpc - ec2Mock - .on(DescribeSubnetsCommand, { - Filters: [{ Name: 'vpc-id', Values: ['vpc-12345678'] }], - }) - .resolves({ Subnets: [{ SubnetId: 'subnet-123' }] }); - - ec2Mock.on(DeleteSubnetCommand, { SubnetId: 'subnet-123' }).resolves({}); - ec2Mock.on(DeleteVpcCommand, { VpcId: 'vpc-12345678' }).resolves({}); - ec2Mock.on(DeleteParameterCommand, { Name: '/test/account/pre-warmed' }).resolves({}); - - //then - response isComplete is true - const response = await handler(deleteEvent); - expect(response?.IsComplete).toBeTruthy(); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/create-launch-template.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/create-launch-template.test.ts deleted file mode 100644 index 20febca..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/create-launch-template.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { LaunchTemplate } from '../../lib/aws-ec2/create-launch-template'; -import { snapShotTest } from '../snapshot-test'; -import * as path from 'path'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(LaunchTemplate): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new LaunchTemplate(stack, 'Test', { - name: 'Test', - appName: 'appA', - userData: path.join(__dirname, 'launchTemplateFiles/testUserData.sh'), - vpc: 'test', - instanceType: 't3.micro', - imageId: 'ami-1234', - securityGroups: ['sg-1234'], - blockDeviceMappings: [ - { - deviceName: 'test', - ebs: { deleteOnTermination: true, encrypted: true, iops: 300, kmsKeyId: 'test', volumeSize: 10 }, - }, - ], -}); - -/** - * GWLB construct test - */ -describe('LaunchTemplate', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route.test.ts deleted file mode 100644 index 887f4a6..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { CrossAccountRoute, CrossAccountRouteFramework } from '../../lib/aws-ec2/cross-account-route'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(CrossAccountRoute): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const provider = new CrossAccountRouteFramework(stack, 'Framework', { - logGroupKmsKey: new cdk.aws_kms.Key(stack, 'Key'), - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}).provider; - -new CrossAccountRoute(stack, 'Resourcev4', { - ownerAccount: 'TestAccount', - ownerRegion: 'us-east-1', - partition: 'aws', - provider, - roleName: 'TestRole', - routeTableId: 'rtb-test123', - destination: '10.0.0.0/16', - vpcPeeringConnectionId: 'pcx-test123', -}); - -new CrossAccountRoute(stack, 'Resourcev6', { - ownerAccount: 'TestAccount', - ownerRegion: 'us-east-1', - partition: 'aws', - provider, - roleName: 'TestRole', - routeTableId: 'rtb-test123', - ipv6Destination: '::/0', - vpcPeeringConnectionId: 'pcx-test123', -}); - -new CrossAccountRoute(stack, 'ResourcePl', { - ownerAccount: 'TestAccount', - ownerRegion: 'us-east-1', - partition: 'aws', - provider, - roleName: 'TestRole', - routeTableId: 'rtb-test123', - destinationPrefixListId: 'pl-test', - vpcPeeringConnectionId: 'pcx-test123', -}); - -/** - * CrossAccountRoute construct test - */ -describe('CrossAccountRoute', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route/index.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route/index.test.ts deleted file mode 100644 index 8096f21..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/cross-account-route/index.test.ts +++ /dev/null @@ -1,347 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CreateRouteCommand, DeleteRouteCommand, EC2Client } from '@aws-sdk/client-ec2'; -import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts'; -import { afterEach, beforeEach, expect, it } from '@jest/globals'; -import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; -import { handler } from '../../../lib/aws-ec2/cross-account-route/index'; -import { - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceDeleteEvent, - CloudFormationCustomResourceUpdateEvent, -} from '../../../lib/lza-custom-resource'; - -let ec2Mock: AwsClientStub; -let stsMock: AwsClientStub; - -beforeEach(() => { - ec2Mock = mockClient(EC2Client); - stsMock = mockClient(STSClient); - stsMock.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'access-key', - SecretAccessKey: 'secret-key', - SessionToken: 'session', - Expiration: new Date(), - }, - }); -}); - -afterEach(() => { - ec2Mock.restore(); - stsMock.restore(); -}); - -/** - * IPv4 tests - */ -// Given -const createIpv4Event: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationCidrBlock: '10.0.0.0/16', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route create IPv4 route event', async () => { - ec2Mock.on(CreateRouteCommand).resolves({ Return: true }); - const response = await handler(createIpv4Event); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('10.0.0.0/16rtb-1234abc'); -}); - -// Given -const updateIpv4Event: CloudFormationCustomResourceUpdateEvent = { - RequestType: 'Update', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: '10.0.0.0/16rtb-1234abc', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationCidrBlock: '10.0.1.0/16', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, - OldResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationCidrBlock: '10.0.0.0/16', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route update IPv4 route event', async () => { - ec2Mock.on(CreateRouteCommand).resolves({ Return: true }); - const response = await handler(updateIpv4Event); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('10.0.1.0/16rtb-1234abc'); -}); - -// Given -const deleteIpv4Event: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: '10.0.1.0/16rtb-1234abc', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationCidrBlock: '10.0.1.0/16', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route delete IPv4 route event', async () => { - ec2Mock.on(DeleteRouteCommand).resolves({}); - const response = await handler(deleteIpv4Event); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('10.0.1.0/16rtb-1234abc'); -}); - -/** - * IPv6 tests - */ -// Given -const createIpv6Event: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationIpv6CidrBlock: 'fd00::/8', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route create IPv6 route event', async () => { - ec2Mock.on(CreateRouteCommand).resolves({ Return: true }); - const response = await handler(createIpv6Event); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('fd00::/8rtb-1234abc'); -}); - -// Given -const updateIpv6Event: CloudFormationCustomResourceUpdateEvent = { - RequestType: 'Update', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'fd00::/8rtb-1234abc', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationIpv6CidrBlock: 'fd01::/8', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, - OldResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationIpv6CidrBlock: 'fd00::/8rtb-1234abc', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route update IPv6 route event', async () => { - ec2Mock.on(CreateRouteCommand).resolves({ Return: true }); - const response = await handler(updateIpv6Event); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('fd01::/8rtb-1234abc'); -}); - -// Given -const deleteIpv6Event: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'fd01::/8rtb-1234abc', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationIpv6CidrBlock: 'fd01::/8', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route delete IPv6 route event', async () => { - ec2Mock.on(DeleteRouteCommand).resolves({}); - const response = await handler(deleteIpv6Event); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('fd01::/8rtb-1234abc'); -}); - -/** - * Prefix list tests - */ -// Given -const createPlEvent: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationPrefixListId: 'pl-test', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route create prefix list route event', async () => { - ec2Mock.on(CreateRouteCommand).resolves({ Return: true }); - const response = await handler(createPlEvent); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('pl-testrtb-1234abc'); -}); - -// Given -const updatePlEvent: CloudFormationCustomResourceUpdateEvent = { - RequestType: 'Update', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'pl-testrtb-1234abc', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationPrefixListId: 'pl-test123', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, - OldResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationPrefixListId: 'pl-test', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route update prefix list route event', async () => { - ec2Mock.on(CreateRouteCommand).resolves({ Return: true }); - const response = await handler(updatePlEvent); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('pl-test123rtb-1234abc'); -}); - -// Given -const deletePlEvent: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::CrossAccountRoute', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'pl-test123rtb-1234abc', - ResourceProperties: { - region: 'us-east-1', - roleArn: 'test-role-arn', - routeDefinition: { - DestinationPrefixListId: 'pl-test123', - RouteTableId: 'rtb-1234abc', - }, - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/cross-account-route delete prefix list route event', async () => { - ec2Mock.on(DeleteRouteCommand).resolves({}); - const response = await handler(deletePlEvent); - // Then - expect(response?.Status).toEqual('SUCCESS'); - expect(response?.PhysicalResourceId).toEqual('pl-test123rtb-1234abc'); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/customer-gateway.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/customer-gateway.test.ts deleted file mode 100644 index d574403..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/customer-gateway.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { CustomerGateway } from '../../lib/aws-ec2/customer-gateway'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(CustomerGateway): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new CustomerGateway(stack, 'TestCgw', { - name: 'Test-CGW', - bgpAsn: 65000, - ipAddress: '1.1.1.1', - tags: [{ key: 'Test-Key', value: 'Test-Value' }], -}); - -new CustomerGateway(stack, 'TestCustomCgw', { - name: 'Custom-CGW', - bgpAsn: 65123, - ipAddress: '1.2.3.4', - customResourceHandler: cdk.aws_lambda.Function.fromFunctionName(stack, 'Function', 'TestFunction'), - owningAccountId: '111111111111', - owningRegion: 'us-west-2', - roleName: 'test-role', -}); - -/** - * CustomerGateway construct test - */ -describe('CustomerGateway', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-security-group-rules.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-security-group-rules.test.ts deleted file mode 100644 index 630536c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-security-group-rules.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DeleteDefaultSecurityGroupRules } from '../../lib/aws-ec2/vpc'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DeleteDefaultSGRules): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const key = new cdk.aws_kms.Key(stack, 'testKey'); - -new DeleteDefaultSecurityGroupRules(stack, 'DeleteDefaultSGRules', { - vpcId: 'vpc-123', - kmsKey: key, - logRetentionInDays: 3653, -}); - -/** - * DeleteDefaultVpc construct test - */ -describe('DeleteDefaultSGRules', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-vpc.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-vpc.test.ts deleted file mode 100644 index 19c99af..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/delete-default-vpc.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DeleteDefaultVpc } from '../../lib/aws-ec2/delete-default-vpc'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DeleteDefaultVpc): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new DeleteDefaultVpc(stack, 'DeleteDefaultVpc', { - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * DeleteDefaultVpc construct test - */ -describe('DeleteDefaultVpc', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/dhcp-options.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/dhcp-options.test.ts deleted file mode 100644 index 571c582..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/dhcp-options.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { DhcpOptions } from '../../lib/aws-ec2/dhcp-options'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(DhcpOptions): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new DhcpOptions(stack, 'TestDhcpOpts', { - name: 'Test', - domainName: 'test.com', - domainNameServers: ['1.1.1.1'], - netbiosNameServers: ['1.1.1.1'], - netbiosNodeType: 2, - ntpServers: ['1.1.1.1'], - tags: [], -}); - -/** - * DHCP Options construct test - */ -describe('DhcpOptions', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption.test.ts deleted file mode 100644 index 9abd676..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { EbsDefaultEncryption } from '../../lib/aws-ec2/ebs-encryption'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(EbsDefaultEncryption): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -new EbsDefaultEncryption(stack, 'EbsDefaultVolumeEncryption', { - ebsEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'EbsEncryptionKmsKey', {}), - logGroupKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * Report Definition construct test - */ -describe('ReportDefinition', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption/index.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption/index.test.ts deleted file mode 100644 index 31d9266..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ebs-encryption/index.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - DisableEbsEncryptionByDefaultCommand, - EC2Client, - EnableEbsEncryptionByDefaultCommand, - ModifyEbsDefaultKmsKeyIdCommand, -} from '@aws-sdk/client-ec2'; -import { afterEach, beforeEach, expect, it } from '@jest/globals'; -import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; -import { handler } from '../../../lib/aws-ec2/ebs-default-encryption/index'; -import { - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceDeleteEvent, - CloudFormationCustomResourceUpdateEvent, -} from '../../../lib/lza-custom-resource'; - -let ec2Mock: AwsClientStub; - -beforeEach(() => { - ec2Mock = mockClient(EC2Client); -}); - -afterEach(() => { - ec2Mock.restore(); -}); - -// Given -const createEvent: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EbsDefaultVolumeEncryption', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - kmsKeyId: 'test-kms-key-id', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/ebs-default-encryption create event', async () => { - ec2Mock.on(EnableEbsEncryptionByDefaultCommand).resolves({ EbsEncryptionByDefault: true }); - ec2Mock.on(ModifyEbsDefaultKmsKeyIdCommand).resolves({ KmsKeyId: 'test-kms-key-id' }); - const response = await handler(createEvent); - // Then - expect(response?.Status).toEqual('Success'); - expect(response?.StatusCode).toEqual(200); -}); - -// Given -const updateEvent: CloudFormationCustomResourceUpdateEvent = { - RequestType: 'Update', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EbsDefaultVolumeEncryption', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'example-physical-resource-id', - ResourceProperties: { - kmsKeyId: 'test-kms-key-id-2', - ServiceToken: 'example-service-token', - }, - OldResourceProperties: { - kmsKeyId: 'test-kms-key-id', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/ebs-default-encryption update event', async () => { - ec2Mock.on(EnableEbsEncryptionByDefaultCommand).resolves({ EbsEncryptionByDefault: true }); - ec2Mock.on(ModifyEbsDefaultKmsKeyIdCommand).resolves({ KmsKeyId: 'test-kms-key-id-2' }); - const response = await handler(updateEvent); - // Then - expect(response?.Status).toEqual('Success'); - expect(response?.StatusCode).toEqual(200); -}); - -// Given -const deleteEvent: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EbsDefaultVolumeEncryption', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'example-physical-resource-id', - ResourceProperties: { - kmsKeyId: 'test-kms-key-id-2', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-ec2/ebs-default-encryption delete event', async () => { - ec2Mock.on(DisableEbsEncryptionByDefaultCommand).resolves({ EbsEncryptionByDefault: false }); - const response = await handler(deleteEvent); - // Then - expect(response?.Status).toEqual('Success'); - expect(response?.StatusCode).toEqual(200); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-asg.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-asg.test.ts deleted file mode 100644 index 09cbcff..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-asg.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AutoScalingConfig, - EbsItemConfig, - LaunchTemplateConfig, - NetworkInterfaceItemConfig, -} from '@aws-accelerator/config'; -import * as cdk from 'aws-cdk-lib'; -import path from 'path'; -import { FirewallAutoScalingGroup } from '../../lib/aws-ec2/firewall-asg'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(FirewallAutoScalingGroup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const launchTemplate: LaunchTemplateConfig = { - name: 'test-firewall', - blockDeviceMappings: [ - { - deviceName: 'dev/xvda', - ebs: { - encrypted: true, - } as EbsItemConfig, - }, - ], - enforceImdsv2: true, - iamInstanceProfile: undefined, - imageId: 'ami-123xyz', - instanceType: 't3.large', - keyPair: undefined, - networkInterfaces: [ - { - deviceIndex: 0, - groups: ['Test'], - subnetId: 'subnet-123xyz', - } as NetworkInterfaceItemConfig, - ], - securityGroups: [], - userData: 'aws-ec2/launchTemplateFiles/firewallUserData.txt', -}; - -const autoscaling: AutoScalingConfig = { - name: 'TestAsg', - minSize: 1, - maxSize: 4, - desiredSize: 2, - launchTemplate: 'test-firewall', - healthCheckGracePeriod: 300, - healthCheckType: 'ELB', - subnets: ['subnet-123xyz', 'subnet-456abc'], - targetGroups: [], - maxInstanceLifetime: 86400, -}; - -new FirewallAutoScalingGroup(stack, 'TestFirewall', { - name: 'Test', - autoscaling, - configBucketName: 'test-bucket', - configDir: path.dirname(__dirname), - launchTemplate, - vpc: 'TestVpc', - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - cloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyCloudWatch', {}), - cloudWatchLogRetentionInDays: 3653, -}); - -/** - * Firewall ASG construct test - */ -describe('LaunchTemplate', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-config-replacements.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-config-replacements.test.ts deleted file mode 100644 index 6ec30f8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-config-replacements.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { FirewallConfigReplacements } from '../../lib/aws-ec2/firewall-config-replacements'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(FirewallConfigReplacements): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -// Create mock keys and role for custom resource -const cloudWatchKey = new cdk.aws_kms.Key(stack, 'CloudWatchKey'); -const lambdaKey = new cdk.aws_kms.Key(stack, 'LambdaKey'); -const role = new cdk.aws_iam.Role(stack, 'Role', { - assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), -}); - -// Create resource -new FirewallConfigReplacements(stack, 'ConfigReplacements', { - cloudWatchLogKey: cloudWatchKey, - cloudWatchLogRetentionInDays: 3653, - environmentEncryptionKey: lambdaKey, - properties: [{ test: 'test' }], - role, -}); - -describe('FirewallConfigReplacements', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-instance.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-instance.test.ts deleted file mode 100644 index 6ad296c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/firewall-instance.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { EbsItemConfig, LaunchTemplateConfig, NetworkInterfaceItemConfig } from '@aws-accelerator/config'; -import * as cdk from 'aws-cdk-lib'; -import path from 'path'; -import { FirewallInstance } from '../../lib/aws-ec2/firewall-instance'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(FirewallInstance): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const launchTemplate: LaunchTemplateConfig = { - name: 'test-firewall', - blockDeviceMappings: [ - { - deviceName: 'dev/xvda', - ebs: { - encrypted: true, - } as EbsItemConfig, - }, - ], - enforceImdsv2: true, - iamInstanceProfile: undefined, - imageId: 'ami-123xyz', - instanceType: 't3.large', - keyPair: undefined, - networkInterfaces: [ - { - deviceIndex: 0, - associateElasticIp: true, - groups: ['Test'], - sourceDestCheck: false, - subnetId: 'subnet-123xyz', - } as NetworkInterfaceItemConfig, - ], - securityGroups: [], - userData: 'aws-ec2/launchTemplateFiles/firewallUserData.txt', -}; - -new FirewallInstance(stack, 'TestFirewall', { - name: 'Test', - configBucketName: 'test-bucket', - configDir: path.dirname(__dirname), - launchTemplate, - vpc: 'TestVpc', -}); - -/** - * Firewall instance construct test - */ -describe('LaunchTemplate', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-account-warming/integ.account-warming.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-account-warming/integ.account-warming.ts deleted file mode 100644 index 0807b5b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-account-warming/integ.account-warming.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-account-warming --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import { CfnResource, Stack, StackProps, IAspect, Aspects, RemovalPolicy, App, Duration } from 'aws-cdk-lib'; -import { Key } from 'aws-cdk-lib/aws-kms'; -import { PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; -import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; -import { WarmAccount } from '../../../lib/aws-ec2/account-warming'; -import { Construct, IConstruct } from 'constructs'; -import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements IAspect { - public visit(node: IConstruct): void { - if (node instanceof CfnResource) { - node.applyRemovalPolicy(RemovalPolicy.DESTROY); - } - } -} -class LambdaDefaultMemoryAspect implements IAspect { - visit(node: IConstruct): void { - if (node instanceof CfnResource) { - if (node.cfnResourceType === 'AWS::Lambda::Function') { - const cfnProps = (node as CfnFunction)['_cfnProperties']; - let memorySize = cfnProps['MemorySize']?.toString(); - - if (!memorySize) { - memorySize = (node as CfnFunction).memorySize; - } - - if (!memorySize || memorySize < 512) { - node.addPropertyOverride('MemorySize', 512); - } - } - } - } -} -// CDK App for Integration Tests -const app = new App(); - -export class AccountWarmingDemoStack extends Stack { - constructor(scope: Construct, id: string, props: StackProps) { - super(scope, id, props); - const key = new Key(this, 'Key', { removalPolicy: RemovalPolicy.DESTROY }); - // Allow Cloudwatch logs to use the encryption key - key.addToResourcePolicy( - new PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [new ServicePrincipal(`logs.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`)], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${Stack.of(this).partition}:logs:${Stack.of(this).region}:${ - Stack.of(this).account - }:log-group:*`, - }, - }, - }), - ); - new WarmAccount(this, 'AccountWarming', { - cloudwatchKmsKey: key, - logRetentionInDays: 3653, - ssmPrefix: '/test', - }); - Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - Aspects.of(this).add(new LambdaDefaultMemoryAspect()); - } -} - -// Stack under test -const stackUnderTest = new AccountWarmingDemoStack(app, 'AccountWarmingIntegrationTestStack', { - description: 'This stack includes the application’s resources for integration testing.', -}); - -// Initialize Integ Test construct -const integ = new IntegTest(app, 'AccountWarmingTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); -integ.assertions - .awsApiCall('SSM', 'getParameter', { Name: '/test/account/pre-warmed' }) - .expect( - ExpectedResult.objectLike({ - Parameter: { Name: '/test/account/pre-warmed', Value: 'true', Type: 'String' }, - }), - ) - .waitForAssertions({ totalTimeout: Duration.minutes(2) }); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-ebs-encryption/integ.ebs-encryption.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-ebs-encryption/integ.ebs-encryption.ts deleted file mode 100644 index f0d4bf3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-ebs-encryption/integ.ebs-encryption.ts +++ /dev/null @@ -1,210 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-ebs-encryption --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { App, Aspects, CfnResource, IAspect, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; -import { - AccountPrincipal, - AnyPrincipal, - ArnPrincipal, - Effect, - PolicyStatement, - ServicePrincipal, -} from 'aws-cdk-lib/aws-iam'; -import { Key } from 'aws-cdk-lib/aws-kms'; -import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; -import { Construct, IConstruct } from 'constructs'; -import { EbsDefaultEncryption } from '../../../lib/aws-ec2/ebs-encryption'; - -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements IAspect { - public visit(node: IConstruct): void { - if (node instanceof CfnResource) { - node.applyRemovalPolicy(RemovalPolicy.DESTROY); - } - } -} -class LambdaDefaultMemoryAspect implements IAspect { - visit(node: IConstruct): void { - if (node instanceof CfnResource) { - if (node.cfnResourceType === 'AWS::Lambda::Function') { - const cfnProps = (node as CfnFunction)['_cfnProperties']; - let memorySize = cfnProps['MemorySize']?.toString(); - - if (!memorySize) { - memorySize = (node as CfnFunction).memorySize; - } - - if (!memorySize || memorySize < 512) { - node.addPropertyOverride('MemorySize', 512); - } - } - } - } -} -// CDK App for Integration Tests -const app = new App(); - -export class EbsEncryptionDemoStack extends Stack { - /** - * Declare EBS KMS key ID to validate in integration test - */ - public readonly ebsKmsKeyId: string; - constructor(scope: Construct, id: string, props: StackProps) { - super(scope, id, props); - - const logGroupKmsKey = this.createCloudwatchKey(); - const ebsEncryptionKmsKey = this.createEbsKey(); - - new EbsDefaultEncryption(this, 'DefaultEncryptionTest', { - ebsEncryptionKmsKey, - logGroupKmsKey, - logRetentionInDays: 3653, - }); - Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - Aspects.of(this).add(new LambdaDefaultMemoryAspect()); - - this.ebsKmsKeyId = ebsEncryptionKmsKey.keyArn; - } - - /** - * Create KMS key for CloudWatch Logs - * @returns Key - */ - private createCloudwatchKey(): Key { - const cloudWatchKey = new Key(this, 'Key', { removalPolicy: RemovalPolicy.DESTROY }); - // Allow Cloudwatch logs to use the encryption key - cloudWatchKey.addToResourcePolicy( - new PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [new ServicePrincipal(`logs.${this.region}.${this.urlSuffix}`)], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${this.partition}:logs:${this.region}:${this.account}:log-group:*`, - }, - }, - }), - ); - - return cloudWatchKey; - } - - /** - * Create KMS key for EBS default volume encryption - * @returns Key - */ - private createEbsKey(): Key { - const ebsEncryptionKey = new Key(this, 'EbsEncryptionKey', { - removalPolicy: RemovalPolicy.DESTROY, - }); - ebsEncryptionKey.addToResourcePolicy( - new PolicyStatement({ - sid: 'Allow service-linked role use', - effect: Effect.ALLOW, - actions: ['kms:Decrypt', 'kms:DescribeKey', 'kms:Encrypt', 'kms:GenerateDataKey*', 'kms:ReEncrypt*'], - principals: [ - new ArnPrincipal( - `arn:${this.partition}:iam::${this.account}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling`, - ), - ], - resources: ['*'], - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new PolicyStatement({ - sid: 'Allow Autoscaling to create grant', - effect: Effect.ALLOW, - actions: ['kms:CreateGrant'], - principals: [ - new ArnPrincipal( - `arn:${this.partition}:iam::${this.account}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling`, - ), - ], - resources: ['*'], - conditions: { Bool: { 'kms:GrantIsForAWSResource': 'true' } }, - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new PolicyStatement({ - sid: 'Account Access', - effect: Effect.ALLOW, - principals: [new AccountPrincipal(this.account)], - actions: ['kms:*'], - resources: ['*'], - }), - ); - ebsEncryptionKey.addToResourcePolicy( - new PolicyStatement({ - sid: 'ec2', - effect: Effect.ALLOW, - principals: [new AnyPrincipal()], - actions: ['kms:*'], - resources: ['*'], - conditions: { - StringEquals: { - 'kms:CallerAccount': this.account, - 'kms:ViaService': `ec2.${this.region}.${this.urlSuffix}`, - }, - }, - }), - ); - - return ebsEncryptionKey; - } -} - -// Stack under test -const stackUnderTest = new EbsEncryptionDemoStack(app, 'EbsEncryptionTestStack', { - description: 'Stack for EBS default volume encryption integration tests', -}); - -// Initialize Integ Test construct -const integ = new IntegTest(app, 'EbsEncryptionTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); -// -// Check encryption status -integ.assertions - .awsApiCall('EC2', 'getEbsEncryptionByDefault', {}) - .expect(ExpectedResult.objectLike({ EbsEncryptionByDefault: true })); -// -// Check KMS key ID -integ.assertions - .awsApiCall('EC2', 'getEbsDefaultKmsKeyId', {}) - .expect(ExpectedResult.objectLike({ KmsKeyId: stackUnderTest.ebsKmsKeyId })); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-transit-gateway/integ.transit-gateway.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-transit-gateway/integ.transit-gateway.ts deleted file mode 100644 index 12ee59f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/integ-test-transit-gateway/integ.transit-gateway.ts +++ /dev/null @@ -1,267 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct, IConstruct } from 'constructs'; -import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { AcceleratorAspects } from '../../../../accelerator/lib/accelerator-aspects'; -import { TransitGateway } from '@aws-accelerator/constructs'; -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - node.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); - } - } -} - -// CDK App for Integration Tests -const app = new cdk.App(); -export class NewTransitGatewayStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - new TransitGateway(this, 'Tgw', { - name: 'TestTransitGateway', - amazonSideAsn: 65521, - autoAcceptSharedAttachments: 'enable', - defaultRouteTableAssociation: 'disable', - defaultRouteTablePropagation: 'disable', - dnsSupport: 'enable', - transitGatewayCidrBlocks: ['10.0.0.0/20', '10.5.0.0/20', '2001:db8::/64'], - vpnEcmpSupport: 'enable', - tags: [{ key: 'CostCenter', value: 'Test-Value' }], - }); - - new TransitGateway(this, 'SecondaryTgw', { - name: 'TestNoCidrBlocksTransitGateway', - amazonSideAsn: 65522, - autoAcceptSharedAttachments: 'disable', - defaultRouteTableAssociation: 'disable', - defaultRouteTablePropagation: 'disable', - dnsSupport: 'enable', - vpnEcmpSupport: 'enable', - tags: [{ key: 'CostCenter', value: 'Test-Value' }], - }); - - new TransitGateway(this, 'ThirdTgw', { - name: 'TestIpv4BlocksTransitGateway', - amazonSideAsn: 65523, - autoAcceptSharedAttachments: 'enable', - defaultRouteTableAssociation: 'enable', - defaultRouteTablePropagation: 'enable', - transitGatewayCidrBlocks: ['10.0.0.0/20'], - dnsSupport: 'enable', - vpnEcmpSupport: 'enable', - tags: [{ key: 'Test', value: 'Third' }], - }); - - new TransitGateway(this, 'FourthTgw', { - name: 'TestIpv6BlocksTransitGateway', - amazonSideAsn: 65524, - autoAcceptSharedAttachments: 'enable', - defaultRouteTableAssociation: 'enable', - defaultRouteTablePropagation: 'enable', - transitGatewayCidrBlocks: ['5000:aaaa::/64'], - dnsSupport: 'disable', - vpnEcmpSupport: 'disable', - tags: [{ key: 'Test', value: 'Fourth' }], - }); - - cdk.Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - new AcceleratorAspects(app, 'aws', false); - } -} - -// Stack under test -const stackUnderTest = new NewTransitGatewayStack(app, 'NewTransitGatewayIntegrationTestStack', { - description: 'This stack includes the application’s resources for integration testing.', -}); - -/* - * Initialize Integ Test constructs - */ - -// First integration test -const integ = new IntegTest(app, 'NewTransitGatewayTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -integ.assertions - .awsApiCall('TransitGateway', 'describeTransitGateways', { Filter: 'Name=tag:Name, Values:TestTransitGateway' }) - .expect( - ExpectedResult.objectLike({ - TransitGateways: [ - { - Options: { - AmazonSideAsn: 65521, - TransitGatewayCidrBlocks: ['10.0.0.0/20', '10.5.0.0/20', '2001:db8::/64'], - AutoAcceptSharedAttachments: 'enable', - DefaultRouteTableAssociation: 'disable', - DefaultRouteTablePropagation: 'disable', - VpnEcmpSupport: 'enable', - DnsSupport: 'enable', - }, - }, - ], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); - -// Second integration test -const secondaryInteg = new IntegTest(app, 'NewSecondaryTransitGatewayTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -secondaryInteg.assertions - .awsApiCall('TransitGateway', 'describeTransitGateways', { - Filter: 'Name=tag:Name, Values:TestNoCidrBlocksTransitGateway', - }) - .expect( - ExpectedResult.objectLike({ - TransitGateways: [ - { - Options: { - AmazonSideAsn: 65522, - AutoAcceptSharedAttachments: 'disable', - DefaultRouteTableAssociation: 'disable', - DefaultRouteTablePropagation: 'disable', - VpnEcmpSupport: 'enable', - DnsSupport: 'enable', - }, - }, - ], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); - -// Third integration test -const thirdInteg = new IntegTest(app, 'NewThirdTransitGatewayTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -thirdInteg.assertions - .awsApiCall('TransitGateway', 'describeTransitGateways', { - Filter: 'Name=tag:Name, Values:TestIpv4BlocksTransitGateway', - }) - .expect( - ExpectedResult.objectLike({ - TransitGateways: [ - { - Options: { - AmazonSideAsn: 65523, - TransitGatewayCidrBlocks: ['10.0.0.0/20'], - AutoAcceptSharedAttachments: 'enable', - DefaultRouteTableAssociation: 'enable', - DefaultRouteTablePropagation: 'enable', - VpnEcmpSupport: 'enable', - DnsSupport: 'enable', - }, - }, - ], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); - -// Fourth integration test -const fourthInteg = new IntegTest(app, 'NewFourthTransitGatewayTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -fourthInteg.assertions - .awsApiCall('TransitGateway', 'describeTransitGateways', { - Filter: 'Name=tag:Name, Values:TestIpv6BlocksTransitGateway', - }) - .expect( - ExpectedResult.objectLike({ - TransitGateways: [ - { - Options: { - AmazonSideAsn: 65524, - TransitGatewayCidrBlocks: ['5000:aaaa::/64'], - AutoAcceptSharedAttachments: 'enable', - DefaultRouteTableAssociation: 'enable', - DefaultRouteTablePropagation: 'enable', - VpnEcmpSupport: 'disable', - DnsSupport: 'disable', - }, - }, - ], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-organization-admin-account.test.ts deleted file mode 100644 index 41847d5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-organization-admin-account.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IpamOrganizationAdminAccount } from '../../lib/aws-ec2/ipam-organization-admin-account'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(IpamOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IpamOrganizationAdminAccount(stack, 'TestIpamOrgAdmin', { - accountId: 'TestAccountId', - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), - logRetentionInDays: 3653, -}); - -/** - * IPAM organization admin account construct test - */ -describe('IpamOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-pool.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-pool.test.ts deleted file mode 100644 index ef1cbb0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-pool.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IpamPool } from '../../lib/aws-ec2/ipam-pool'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(IpamPool): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IpamPool(stack, 'TestIpamPool', { - name: 'Test', - description: 'Test IPAM pool', - addressFamily: 'ipv4', - ipamScopeId: 'test-scope', - locale: 'us-east-1', - provisionedCidrs: ['10.0.0.0/8', '192.168.0.0/16'], -}); - -/** - * IPAM pool construct test - */ -describe('IpamPool', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-scope.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-scope.test.ts deleted file mode 100644 index dbff225..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-scope.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IpamScope } from '../../lib/aws-ec2/ipam-scope'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(IpamScope): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IpamScope(stack, 'TestIpamScope', { - name: 'Test', - description: 'Test IPAM scope', - ipamId: 'test-ipam', -}); - -/** - * IPAM scope construct test - */ -describe('IpamScope', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-subnet.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-subnet.test.ts deleted file mode 100644 index 11394c6..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam-subnet.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IpamSubnet } from '../../lib/aws-ec2/ipam-subnet'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(IpamSubnet): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IpamSubnet(stack, 'TestIpamSubnet', { - name: 'Test', - availabilityZone: 'us-east-1a', - availabilityZoneId: undefined, - basePool: ['10.0.0.0/8'], - ipamAllocation: { - ipamPoolName: 'test-pool', - netmaskLength: 24, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), - logRetentionInDays: 3653, - vpcId: 'vpc-test', - tags: [{ key: 'key', value: 'value' }], -}); - -new IpamSubnet(stack, 'TestIpamSubnet2', { - name: 'Test2', - availabilityZone: undefined, - availabilityZoneId: 'use1-az2', - basePool: ['10.0.0.0/8'], - ipamAllocation: { - ipamPoolName: 'test-pool', - netmaskLength: 24, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'Key2', {}), - logRetentionInDays: 3653, - vpcId: 'vpc-test', - tags: [{ key: 'key', value: 'value' }], -}); - -IpamSubnet.fromLookup(stack, 'TestIpamSubnetFromLookup', { - owningAccountId: '11111111111', - ssmSubnetIdPath: '/path/to/ipam/subnet', - roleName: 'testRole', - region: 'us-east-1', - kmsKey: new cdk.aws_kms.Key(stack, 'KeyForLookup', {}), - logRetentionInDays: 3653, -}); - -/** - * IPAM subnet construct test - */ -describe('IpamSubnet', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam.test.ts deleted file mode 100644 index 2348b2d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/ipam.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Ipam } from '../../lib/aws-ec2/ipam'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(Ipam): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new Ipam(stack, 'TestIpam', { - name: 'Test', - description: 'Test IPAM', - operatingRegions: ['us-east-1', 'us-west-2'], -}); - -/** - * IPAM construct test - */ -describe('Ipam', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/firewallUserData.txt b/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/firewallUserData.txt deleted file mode 100644 index 4aef748..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/firewallUserData.txt +++ /dev/null @@ -1 +0,0 @@ -S3 bucket name: ${ACCEL_LOOKUP::S3:BUCKET:firewall-config} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/launchTemplate.yaml b/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/launchTemplate.yaml deleted file mode 100644 index d69248c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/launchTemplate.yaml +++ /dev/null @@ -1,160 +0,0 @@ -blockDeviceMappings: - - deviceName: deviceName - ebs: - deleteOnTermination: false - encrypted: false - iops: 123 - kmsKeyId: ${ACCEL_LOOKUP::KMS:abc} - snapshotId: snapshotId - throughput: 123 - volumeSize: 123 - volumeType: volumeType - noDevice: noDevice - virtualName: virtualName -capacityReservationSpecification: - capacityReservationPreference: capacityReservationPreference - capacityReservationTarget: - capacityReservationId: capacityReservationId - capacityReservationResourceGroupArn: capacityReservationResourceGroupArn -cpuOptions: - coreCount: 123 - threadsPerCore: 123 -creditSpecification: - cpuCredits: cpuCredits -disableApiStop: false -disableApiTermination: false -ebsOptimized: false -elasticGpuSpecifications: - - type: type -elasticInferenceAccelerators: - - count: 123 - type: type -enclaveOptions: - enabled: false -hibernationOptions: - configured: false -iamInstanceProfile: - arn: ${ACCEL_LOOKUP::InstanceProfile:profile-name} - name: name -imageId: ${ACCEL_LOOKUP::AmiId:/path/to/ssm/parameter/store} -instanceInitiatedShutdownBehavior: instanceInitiatedShutdownBehavior -instanceMarketOptions: - marketType: marketType - spotOptions: - blockDurationMinutes: 123 - instanceInterruptionBehavior: instanceInterruptionBehavior - maxPrice: maxPrice - spotInstanceType: spotInstanceType - validUntil: validUntil -instanceRequirements: - acceleratorCount: - max: 123 - min: 123 - acceleratorManufacturers: - - acceleratorManufacturers - acceleratorNames: - - acceleratorNames - acceleratorTotalMemoryMiB: - max: 123 - min: 123 - acceleratorTypes: - - acceleratorTypes - bareMetal: bareMetal - baselineEbsBandwidthMbps: - max: 123 - min: 123 - burstablePerformance: burstablePerformance - cpuManufacturers: - - cpuManufacturers - excludedInstanceTypes: - - excludedInstanceTypes - instanceGenerations: - - instanceGenerations - localStorage: localStorage - localStorageTypes: - - localStorageTypes - memoryGiBPerVCpu: - max: 123 - min: 123 - memoryMiB: - max: 123 - min: 123 - networkInterfaceCount: - max: 123 - min: 123 - onDemandMaxPricePercentageOverLowestPrice: 123 - requireHibernateSupport: false - spotMaxPricePercentageOverLowestPrice: 123 - totalLocalStorageGb: - max: 123 - min: 123 - vCpuCount: - max: 123 - min: 123 -instanceType: instanceType -kernelId: kernelId -keyName: keyName -licenseSpecifications: - - licenseConfigurationArn: licenseConfigurationArn -maintenanceOptions: - autoRecovery: autoRecovery -metadataOptions: - httpEndpoint: httpEndpoint - httpProtocolIpv6: httpProtocolIpv6 - httpPutResponseHopLimit: 123 - httpTokens: httpTokens - instanceMetadataTags: instanceMetadataTags -monitoring: - enabled: false -networkInterfaces: - - associateCarrierIpAddress: false - associatePublicIpAddress: false - deleteOnTermination: false - description: description - deviceIndex: 123 - groups: - - ${ACCEL_LOOKUP::SecurityGroupId:Sg1} - - ${ACCEL_LOOKUP::SecurityGroupId:Sg2} - interfaceType: interfaceType - ipv4PrefixCount: 123 - ipv4Prefixes: - - ipv4Prefix: ipv4Prefix - ipv6AddressCount: 123 - ipv6Addresses: - - ipv6Address: ipv6Address - ipv6PrefixCount: 123 - ipv6Prefixes: - - ipv6Prefix: ipv6Prefix - networkCardIndex: 123 - networkInterfaceId: networkInterfaceId - privateIpAddress: privateIpAddress - privateIpAddresses: - - primary: false - privateIpAddress: privateIpAddress - secondaryPrivateIpAddressCount: 123 - subnetId: ${ACCEL_LOOKUP::SubnetId:Sg1} -placement: - affinity: affinity - availabilityZone: availabilityZone - groupName: groupName - hostId: hostId - hostResourceGroupArn: hostResourceGroupArn - partitionNumber: 123 - spreadDomain: spreadDomain - tenancy: tenancy -privateDnsNameOptions: - enableResourceNameDnsAaaaRecord: false - enableResourceNameDnsARecord: false - hostnameType: hostnameType -ramDiskId: ramDiskId -securityGroupIds: - - ${ACCEL_LOOKUP::SecurityGroupId:Sg1} - - ${ACCEL_LOOKUP::SecurityGroupId:Sg2} -securityGroups: - - securityGroups -tagSpecifications: - - resourceType: resourceType - tags: - - key: key - value: value -userData: userData diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/testUserData.sh b/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/testUserData.sh deleted file mode 100644 index fcff492..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/launchTemplateFiles/testUserData.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -yum update -y -yum install -y httpd -systemctl start httpd -systemctl enable httpd -usermod -a -G apache ec2-user -chown -R ec2-user:apache /var/www -chmod 2775 /var/www -find /var/www -type d -exec chmod 2775 {} \; -find /var/www -type f -exec chmod 0664 {} \; \ No newline at end of file diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list-route.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list-route.test.ts deleted file mode 100644 index 76c8e67..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list-route.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { PrefixListRoute } from '../../lib/aws-ec2/prefix-list-route'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(PrefixListRoute): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new PrefixListRoute(stack, 'TestPrefixListRoute', { - destinationPrefixListId: 'pl-test', - logGroupKmsKey: new cdk.aws_kms.Key(stack, 'TestKms', {}), - logRetentionInDays: 3653, - routeTableId: 'Test', - transitGatewayId: 'tgw-test', -}); - -/** - * Prefix list route construct test - */ -describe('PrefixListRoute', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list.test.ts deleted file mode 100644 index 5c32404..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/prefix-list.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { PrefixList } from '../../lib/aws-ec2/prefix-list'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(PrefixList): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new PrefixList(stack, 'TestPrefixList', { - name: 'Test', - addressFamily: 'IPv4', - maxEntries: 1, - entries: ['1.1.1.1/32'], - tags: [{ key: 'Test-Key', value: 'Test-Value' }], -}); - -/** - * Prefix List construct test - */ -describe('PrefixList', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/route-table.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/route-table.test.ts deleted file mode 100644 index edde239..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/route-table.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { RouteTable } from '../../lib/aws-ec2/route-table'; -import { NatGateway, Vpc, Subnet } from '../../lib/aws-ec2/vpc'; -import { snapShotTest } from '../snapshot-test'; -import { describe, it, expect } from '@jest/globals'; -import { TransitGatewayAttachment } from '../../lib/aws-ec2/transit-gateway'; - -const testNamePrefix = 'Construct(RouteTable): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const key = new cdk.aws_kms.Key(stack, 'testKey'); -const vpc = new Vpc(stack, 'TestVpc', { - name: 'Test', - ipv4CidrBlock: '10.0.0.0/16', - internetGateway: true, - enableDnsHostnames: false, - enableDnsSupport: true, - instanceTenancy: 'default', - virtualPrivateGateway: { - asn: 65000, - }, -}); -vpc.addEgressOnlyIgw(); - -const rt = new RouteTable(stack, 'RouteTable', { - name: 'TestRouteTable', - vpc: vpc, - tags: [{ key: 'Test-Key', value: 'Test-Value' }], -}); - -const subnet = new Subnet(stack, 'test-subnet', { - name: 'test-subnet', - routeTable: rt, - vpc, - availabilityZone: 'a', - availabilityZoneId: undefined, - ipv4CidrBlock: '10.0.2.0/24', -}); - -const ngw = new NatGateway(stack, 'ngw', { name: 'ngw', subnet }); - -const tgwAttachment = new TransitGatewayAttachment(stack, 'tgwAttachment', { - name: 'someTgwAttachment', - partition: 'partition', - subnetIds: ['subnet-123'], - transitGatewayId: 'tgw-12324', - vpcId: vpc.vpcId, -}); - -rt.addTransitGatewayRoute('tg-route', 'tgw-1234', tgwAttachment, undefined, 'pl-1234', undefined, key, 10); -rt.addTransitGatewayRoute('tg', 'tg-1234', tgwAttachment, '10.0.5.0/24', undefined, undefined, key, 10); -rt.addTransitGatewayRoute('tg-ipv6', 'tg-1234', tgwAttachment, undefined, undefined, '::1', key, 10); -rt.addNatGatewayRoute('testNgwRoute', ngw.natGatewayId, '10.0.3.0/24', undefined, undefined, key, 10); -rt.addNatGatewayRoute('test2NgwRoute', ngw.natGatewayId, undefined, 'pl-1234', undefined, key, 10); -rt.addNatGatewayRoute('test3NgwRoute', ngw.natGatewayId, undefined, undefined, '::1', key, 10); -rt.addInternetGatewayRoute('testIgwRoute', '0.0.0.0/0', undefined, undefined, key, 10); -rt.addInternetGatewayRoute('testIgwRoute2', undefined, 'pl-1234', undefined, key, 10); -rt.addInternetGatewayRoute('testIgwRoute3', undefined, undefined, '::1', key, 10); -rt.addEgressOnlyIgwRoute('testEigwRoute1', undefined, 'pl-1234', undefined, key, 10); -rt.addEgressOnlyIgwRoute('testEigwRoute2', '0.0.0.0/0', undefined, undefined, key, 10); -rt.addEgressOnlyIgwRoute('testEigwRoute3', undefined, undefined, '::1', key, 10); -rt.addVirtualPrivateGatewayRoute('testVgwRoute', '10.0.30./24', undefined, undefined, key, 10); -rt.addVirtualPrivateGatewayRoute('testVgw2Route', undefined, 'pl-1234', undefined, key, 10); -rt.addVirtualPrivateGatewayRoute('testVgw3Route', undefined, undefined, '::1', key, 10); -rt.addGatewayAssociation('internetGateway'); -rt.addGatewayAssociation('virtualPrivateGateway'); -/** - * RouteTable construct test - */ -describe('RouteTable', () => { - it('addTransitGatewayRoute destinationPrefix list without logRetention throws error', () => { - function noLogRetention() { - rt.addTransitGatewayRoute( - 'testRoute2', - 'tgw-1234', - tgwAttachment, - undefined, - 'pl-1234', - undefined, - key, - undefined, - ); - } - expect(noLogRetention).toThrow( - new Error('Attempting to add prefix list route without specifying log group retention period'), - ); - }); - it('addTransitGatewayRoute no destination throws error', () => { - function noDest() { - rt.addTransitGatewayRoute('testRoute3', 'tgw-1234', tgwAttachment, undefined, undefined, undefined, key, 10); - } - expect(noDest).toThrow(new Error('Attempting to add CIDR route without specifying destination')); - }); - it('addNatGatewayRoute destinationPrefix list without logRetention throws error', () => { - function noLogRetention() { - rt.addNatGatewayRoute( - 'testNgwRoute2', - ngw.natGatewayId, - '10.0.3.0/24', - 'destinationPrefixListId', - undefined, - key, - undefined, - ); - } - expect(noLogRetention).toThrow( - new Error('Attempting to add prefix list route without specifying log group retention period'), - ); - }); - it('addNatGatewayRoute no destination throws error', () => { - function noDest() { - rt.addNatGatewayRoute('testNgwRoute3', ngw.natGatewayId, undefined, undefined, undefined, key, 10); - } - expect(noDest).toThrow(new Error('Attempting to add CIDR route without specifying destination')); - }); - it('addInternetGatewayRoute destinationPrefix list without logRetention throws error', () => { - function noLogRetention() { - rt.addInternetGatewayRoute('testIgwRoute2', '0.0.0.0/0', 'destinationPrefixListId', undefined, key, undefined); - } - expect(noLogRetention).toThrow( - new Error('Attempting to add prefix list route without specifying log group retention period'), - ); - }); - it('addInternetGatewayRoute no destination throws error', () => { - function noDest() { - rt.addInternetGatewayRoute('testIgwRoute3', undefined, undefined, undefined, key, 10); - } - expect(noDest).toThrow(new Error('Attempting to add CIDR route without specifying destination')); - }); - - it('addEgressOnlyIgwRoute destinationPrefix list without logRetention throws error', () => { - function noLogRetention() { - rt.addEgressOnlyIgwRoute('testIgwRoute2', '0.0.0.0/0', 'destinationPrefixListId', undefined, key, undefined); - } - expect(noLogRetention).toThrow( - new Error('Attempting to add prefix list route without specifying log group retention period'), - ); - }); - it('addEgressOnlyIgwRoute no destination throws error', () => { - function noDest() { - rt.addEgressOnlyIgwRoute('testIgwRoute3', undefined, undefined, undefined, key, 10); - } - expect(noDest).toThrow(new Error('Attempting to add CIDR route without specifying destination')); - }); - - it('addVirtualPrivateGatewayRoute destinationPrefix list without logRetention throws error', () => { - function noLogRetention() { - rt.addVirtualPrivateGatewayRoute( - 'testVgwRoute2', - '0.0.0.0/0', - 'destinationPrefixListId', - undefined, - key, - undefined, - ); - } - expect(noLogRetention).toThrow( - new Error('Attempting to add prefix list route without specifying log group retention period'), - ); - }); - it('addVirtualPrivateGatewayRoute no destination throws error', () => { - function noDest() { - rt.addVirtualPrivateGatewayRoute('testVgwRoute3', undefined, undefined, undefined, key, 10); - } - expect(noDest).toThrow(new Error('Attempting to add CIDR route without specifying destination')); - }); - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/subnet-id-lookup.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/subnet-id-lookup.test.ts deleted file mode 100644 index d331f7e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/subnet-id-lookup.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { SubnetIdLookup } from '../../lib/aws-ec2/subnet-id-lookup'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SubnetIdLookup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SubnetIdLookup(stack, 'SubnetIdLookup', { - vpcId: 'vpce-00000000', - subnetName: 'TestSubnet', - lambdaKey: new cdk.aws_kms.Key(stack, 'LambdaKey'), - cloudwatchKey: new cdk.aws_kms.Key(stack, 'CWKey'), - cloudwatchLogRetentionInDays: 3653, -}); - -/** - * SubnetIdLookup construct test - */ -describe('SubnetIdLookup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-connect.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-connect.test.ts deleted file mode 100644 index f445bb9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-connect.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TransitGatewayConnect } from '../../lib/aws-ec2/transit-gateway-connect'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(TransitGatewayStaticRoute): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new TransitGatewayConnect(stack, 'TransitGatewayConnectVpcAttach', { - name: 'TestVpcTgwConnect', - transitGatewayAttachmentId: 'tgw-attach-0123456789012', - options: { - protocol: 'gre', - }, -}); - -/** - * TransitGatewayConnectAttachment construct test - */ -describe('TransitGatewayConnectAttachment', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-peering.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-peering.test.ts deleted file mode 100644 index 8437e5d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-peering.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TransitGatewayPeering } from '../../lib/aws-ec2/transit-gateway-peering'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(TransitGatewayPrefixListReference): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new TransitGatewayPeering(stack, 'TransitGatewayPeering', { - accepter: { - accountId: stack.account, - accountAccessRoleName: 'ABC', - region: 'us-west-2', - transitGatewayName: 'Network-Main-Tgw', - transitGatewayId: 'tgw-0001', - transitGatewayRouteTableId: 'rt-001', - autoAccept: true, - applyTags: true, - }, - requester: { - accountName: 'SharedServices', - transitGatewayId: 'tgw-0002', - transitGatewayName: 'SharedServices-TGW', - transitGatewayRouteTableId: 'tgw-0002', - tags: [{ key: 'Name', value: 'SharedServices-And-Network-Main-Peering' }], - }, - customLambdaLogKmsKey: new cdk.aws_kms.Key(stack, 'TestKms', {}), - logRetentionInDays: 3653, -}); - -/** - * Transit gateway peering configuration construct test - */ -describe('TransitGatewayPeering', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-prefix-list-reference.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-prefix-list-reference.test.ts deleted file mode 100644 index 3ca56e8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-prefix-list-reference.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TransitGatewayPrefixListReference } from '../../lib/aws-ec2/transit-gateway-prefix-list-reference'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(TransitGatewayPrefixListReference): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new TransitGatewayPrefixListReference(stack, 'TestTransitGatewayPrefixListReference', { - prefixListId: 'pl-test', - transitGatewayAttachmentId: 'tgw-attach-test', - transitGatewayRouteTableId: 'Test', - logGroupKmsKey: new cdk.aws_kms.Key(stack, 'TestKms', {}), - logRetentionInDays: 3653, -}); - -/** - * Transit gateway prefix list reference construct test - */ -describe('TransitGatewayPrefixListReference', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-route-table.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-route-table.test.ts deleted file mode 100644 index ed72265..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-route-table.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TransitGatewayRouteTable } from '../../lib/aws-ec2/transit-gateway-route-table'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(TransitGatewayRouteTable): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new TransitGatewayRouteTable(stack, 'TransitGatewayRouteTable', { - name: 'core', - transitGatewayId: 'tgw0001', - tags: [{ key: 'Test-Key', value: 'Test-Value' }], -}); -/** - * TransitGatewayRouteTable construct test - */ -describe('TransitGatewayRouteTable', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-static-route.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-static-route.test.ts deleted file mode 100644 index 5d0ae8b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway-static-route.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TransitGatewayStaticRoute } from '../../lib/aws-ec2/transit-gateway-static-route'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(TransitGatewayStaticRoute): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRoute', { - transitGatewayRouteTableId: '1234', - blackhole: false, - destinationCidrBlock: '10.0.0.0/16', - transitGatewayAttachmentId: 'tgw-123123', -}); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRoutev6', { - transitGatewayRouteTableId: '1234', - blackhole: false, - destinationCidrBlock: '::/0', - transitGatewayAttachmentId: 'tgw-123123', -}); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRoutev4Blackhole', { - transitGatewayRouteTableId: '1234', - blackhole: true, - destinationCidrBlock: '10.0.0.0/16', -}); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRoutev6Blackhole', { - transitGatewayRouteTableId: '1234', - blackhole: true, - destinationCidrBlock: '::/0', -}); - -const customResourceHandler = cdk.aws_lambda.Function.fromFunctionName(stack, 'test', 'test'); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRouteCustom', { - transitGatewayRouteTableId: '1234', - blackhole: false, - destinationCidrBlock: '10.0.0.0/16', - transitGatewayAttachmentId: 'tgw-123123', - customResourceHandler, -}); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRouteCustomv6', { - transitGatewayRouteTableId: '1234', - blackhole: false, - destinationCidrBlock: '::/0', - transitGatewayAttachmentId: 'tgw-123123', - customResourceHandler, -}); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRouteCustomv4Blackhole', { - transitGatewayRouteTableId: '1234', - blackhole: true, - destinationCidrBlock: '10.0.0.0/16', - customResourceHandler, -}); - -new TransitGatewayStaticRoute(stack, 'TransitGatewayStaticRouteCustomv6Blackhole', { - transitGatewayRouteTableId: '1234', - blackhole: true, - destinationCidrBlock: '::/0', - customResourceHandler, -}); - -/** - * TransitGatewayStaticRoute construct test - */ -describe('TransitGatewayStaticRoute', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway.test.ts deleted file mode 100644 index a2aa666..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/transit-gateway.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - TransitGatewayRouteTableAssociation, - TransitGatewayAttachment, - TransitGatewayRouteTablePropagation, - TransitGateway, - TransitGatewayAttachmentType, -} from '../../lib/aws-ec2/transit-gateway'; -import { snapShotTest } from '../snapshot-test'; -import { describe, it } from '@jest/globals'; - -const testNamePrefix = 'Construct(TransitGatewayRouteTableAssociation): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const customResourceHandler = cdk.aws_lambda.Function.fromFunctionName(stack, 'test', 'test'); - -new TransitGatewayRouteTableAssociation(stack, 'TransitGatewayRouteTableAssociation', { - transitGatewayAttachmentId: 'transitGatewayAttachmentId', - transitGatewayRouteTableId: 'transitGatewayRouteTableId', -}); -new TransitGatewayRouteTableAssociation(stack, 'TransitGatewayRouteTableAssociationCustom', { - transitGatewayAttachmentId: 'transitGatewayAttachmentId', - transitGatewayRouteTableId: 'transitGatewayRouteTableId', - customResourceHandler, -}); -/** - * TransitGatewayRouteTableAssociation construct test - */ -describe('TransitGatewayRouteTableAssociation', () => { - snapShotTest(testNamePrefix, stack); -}); - -/** - * TransitGatewayAttachment construct test - */ -describe('TransitGatewayAttachment', () => { - it('default tgw attachment', () => { - new TransitGatewayAttachment(stack, 'TransitGatewayAttachment', { - name: 'name', - partition: 'partition', - transitGatewayId: 'transitGatewayId', - subnetIds: ['one', 'two', 'three'], - vpcId: 'vpcId', - options: { - applianceModeSupport: 'enable', - dnsSupport: 'enable', - ipv6Support: 'disable', - }, - }); - }); - - it('govcloud tgw attachment', () => { - new TransitGatewayAttachment(stack, 'TransitGatewayAttachmentGovCloud', { - name: 'name', - partition: 'aws-us-gov', - transitGatewayId: 'transitGatewayId', - subnetIds: ['one', 'two', 'three'], - vpcId: 'vpcId', - options: { - applianceModeSupport: 'enable', - dnsSupport: 'enable', - ipv6Support: 'disable', - }, - }); - }); - - it('tgw lookup', () => { - TransitGatewayAttachment.fromLookup(stack, 'TgwAttachLookup', { - transitGatewayId: 'transitGatewayId', - name: 'name', - owningAccountId: 'owningAccountId', - type: TransitGatewayAttachmentType.VPC, - roleName: 'roleName', - kmsKey: new cdk.aws_kms.Key(stack, 'TgwAttachLookupKms'), - logRetentionInDays: 7, - }); - }); - it('regular tgw attachment in aws partition', () => { - new TransitGatewayAttachment(stack, 'TransitGatewayAttachmentAwsPartition', { - name: 'name', - partition: 'aws', - transitGatewayId: 'transitGatewayId', - subnetIds: ['one', 'two', 'three'], - vpcId: 'vpcId', - options: { - applianceModeSupport: 'enable', - dnsSupport: 'disable', - ipv6Support: 'enable', - }, - }); - }); - it('regular tgw attachment in aws partition options toggled', () => { - new TransitGatewayAttachment(stack, 'TransitGatewayAttachmentAwsPartitionOptions', { - name: 'name', - partition: 'aws', - transitGatewayId: 'transitGatewayId', - subnetIds: ['one', 'two', 'three'], - vpcId: 'vpcId', - }); - }); - it('tgw from id', () => { - TransitGatewayAttachment.fromTransitGatewayAttachmentId(stack, 'TgwAttachId', { - attachmentId: 'transitGatewayAttachmentId', - attachmentName: 'name', - }); - }); - snapShotTest('Construct(TransitGatewayAttachment): ', stack); -}); - -/** - * TransitGatewayRouteTablePropagation construct test - */ -describe('TransitGatewayRouteTablePropagation', () => { - new TransitGatewayRouteTablePropagation(stack, 'TransitGatewayRouteTablePropagation', { - transitGatewayAttachmentId: 'transitGatewayAttachmentId', - transitGatewayRouteTableId: 'transitGatewayRouteTableId', - }); - new TransitGatewayRouteTablePropagation(stack, 'TransitGatewayRouteTablePropagationCustom', { - transitGatewayAttachmentId: 'transitGatewayAttachmentId', - transitGatewayRouteTableId: 'transitGatewayRouteTableId', - customResourceHandler, - }); - snapShotTest('Construct(TransitGatewayRouteTablePropagation): ', stack); -}); - -/** - * TransitGateway construct test - */ -describe('TransitGateway', () => { - new TransitGateway(stack, 'TransitGateway', { - name: 'name', - amazonSideAsn: 1234, - autoAcceptSharedAttachments: 'enable', - defaultRouteTableAssociation: 'enable', - defaultRouteTablePropagation: 'enable', - description: 'description', - dnsSupport: 'enable', - multicastSupport: 'enable', - vpnEcmpSupport: 'enable', - tags: [{ key: 'key', value: 'value' }], - }); - snapShotTest('Construct(TransitGateway): ', stack); -}); -/** - * Import TransitGateway construct test - */ -describe('ImportTransitGateway', () => { - TransitGateway.fromTransitGatewayAttributes(stack, 'ImportTransitGateway', { - transitGatewayId: 'someTransitGatewayId', - transitGatewayName: 'someTransitGateway', - }); - snapShotTest('Construct(ImportTransitGateway): ', stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-endpoint.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-endpoint.test.ts deleted file mode 100644 index bb313f9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-endpoint.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SecurityGroup } from '../../lib/aws-ec2/vpc'; -import { VpcEndpoint, VpcEndpointType } from '../../lib/aws-ec2/vpc-endpoint'; -import { snapShotTest } from '../snapshot-test'; -import { describe, it, expect } from '@jest/globals'; - -const testNamePrefix = 'Construct(VpcEndpoint): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const securityGroup = new SecurityGroup(stack, 'TestSecurityGroup`', { - securityGroupName: 'TestSecurityGroup', - description: `AWS Private Endpoint Zone`, - vpcId: 'Test', -}); - -/** - * VpcEndpoint construct test - */ -describe('VpcEndpoint', () => { - it('vpc gateway end point type test', () => { - const initialStack = new VpcEndpoint(stack, 'VpcEndpoint', { - vpcId: 'Test', - vpcEndpointType: VpcEndpointType.GATEWAY, - service: 'service', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: true, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), - routeTables: ['Test1', 'Test2'], - }); - expect(typeof initialStack.createEndpointRoute('id', 'routeTableId', '10.100.0.0/16')).toBe('undefined'); - }); - it('vpc interface end point type test with sagemaker', () => { - new VpcEndpoint(stack, 'VpcEndpointInterfaceSagemaker', { - vpcId: 'Test', - vpcEndpointType: VpcEndpointType.INTERFACE, - service: 'notebook', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: true, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), - routeTables: ['Test1', 'Test2'], - }); - }); - it('vpc interface end point type test with s3 global access', () => { - new VpcEndpoint(stack, 'VpcEndpointInterfaceS3', { - vpcId: 'Test', - vpcEndpointType: VpcEndpointType.INTERFACE, - service: 's3-global.accesspoint', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: true, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), - routeTables: ['Test1', 'Test2'], - }); - }); - it('vpc interface end point type test with serviceName', () => { - new VpcEndpoint(stack, 'VpcEndpointInterfaceEc2', { - vpcId: 'Test', - vpcEndpointType: VpcEndpointType.INTERFACE, - serviceName: 'ec2', - service: 'ec2', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: true, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), - routeTables: ['Test1', 'Test2'], - }); - }); - it('vpc interface end point type test with gwlb', () => { - new VpcEndpoint(stack, 'VpcEndpointInterfaceGwlb', { - vpcId: 'Test', - vpcEndpointType: VpcEndpointType.GWLB, - serviceName: 'ec2', - service: 'ec2', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: true, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), - routeTables: ['Test1', 'Test2'], - }); - }); - - it('vpc gateway end point for s3', () => { - VpcEndpoint.fromAttributes(stack, 'ImportedVpcEndpointS3', { - service: 's3', - vpcEndpointId: 'importedEndpointId', - vpcId: 'Test', - }); - }); - it('serviceName override for gatewayEndpoint', () => { - const checkVpcEndpointGatewayEndpointServiceNameStack = new cdk.Stack(); - new VpcEndpoint(checkVpcEndpointGatewayEndpointServiceNameStack, 'VpcEndpointGatewayEndpointServiceName', { - vpcId: 'Test', - vpcEndpointType: VpcEndpointType.GATEWAY, - service: 'service', - serviceName: 'testGatewayEndpointServiceName', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: true, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), - routeTables: ['Test1', 'Test2'], - }); - const checkVpcEndpointGatewayEndpointServiceNameTemplate = cdk.assertions.Template.fromStack( - checkVpcEndpointGatewayEndpointServiceNameStack, - ); - checkVpcEndpointGatewayEndpointServiceNameTemplate.hasResourceProperties('AWS::EC2::VPCEndpoint', { - ServiceName: cdk.assertions.Match.exact('testGatewayEndpointServiceName'), - }); - }); - - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-id-lookup.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-id-lookup.test.ts deleted file mode 100644 index 9ede62f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-id-lookup.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { VpcIdLookup } from '../../lib/aws-ec2/vpc-id-lookup'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(VpcIdLookup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new VpcIdLookup(stack, 'VpcIdLookup', { - vpcName: 'TestVpc', - lambdaKey: new cdk.aws_kms.Key(stack, 'LambdaKey'), - cloudwatchKey: new cdk.aws_kms.Key(stack, 'CWKey'), - cloudwatchLogRetentionInDays: 3653, -}); - -/** - * VpcIdLookup construct test - */ -describe('VpcIdLookup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-import.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-import.test.ts deleted file mode 100644 index 50cd818..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-import.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - Vpc, - Subnet, - NatGateway, - SecurityGroup, - NetworkAcl, - DeleteDefaultSecurityGroupRules, -} from '../../lib/aws-ec2/vpc'; -import { RouteTable } from '../../lib/aws-ec2/route-table'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(Vpc): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const vpc = Vpc.fromVpcAttributes(stack, 'TestVpc', { - name: 'Main', - vpcId: 'someImportedVpcId', - cidrBlock: '10.1.0.0/16', -}); - -vpc.addVirtualPrivateGateway(65000); -vpc.addInternetGateway(); - -vpc.addFlowLogs({ - destinations: ['s3', 'cloud-watch-logs'], - maxAggregationInterval: 60, - trafficType: 'ALL', - bucketArn: 'arn:aws:s3:::aws-accelerator-test-111111111111-us-east-1', - encryptionKey: new cdk.aws_kms.Key(stack, 'test-key2'), - logRetentionInDays: 10, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', -}); - -vpc.addIpv4Cidr({ cidrBlock: '10.2.0.0/16' }); -const rt2 = RouteTable.fromRouteTableAttributes(stack, 'ImportedRouteTable', { - routeTableId: 'someImportedRouteTableId', - vpc, -}); -const rt = new RouteTable(stack, 'test-rt', { name: 'test-rt', vpc }); -const route = rt.addInternetGatewayRoute('IgwRoute', '0.0.0.0/0'); -vpc.addInternetGatewayDependent(route); -vpc.setDhcpOptions('test-dhcp-opts'); -const subnet1 = Subnet.fromSubnetAttributes(stack, 'ImportedSubnet', { - subnetId: 'someImportedSubnetId', - ipv4CidrBlock: '10.2.1.0/24', - name: 'testImportedSubnet', - routeTable: rt, -}); - -Subnet.fromSubnetAttributes(stack, 'ImportedSubnet2', { - subnetId: 'someImportedSubnetId2', - ipv4CidrBlock: '10.2.2.0/24', - name: 'testImportedSubnet2', - routeTable: rt2, -}); - -new NatGateway(stack, 'natGw', { name: 'ngw', subnet: subnet1, tags: [{ key: 'test', value: 'test2' }] }); - -const sg = new SecurityGroup(stack, 'tetSg', { - description: 'test', - securityGroupName: 'test', - vpc, - tags: [{ key: 'test', value: 'test2' }], -}); - -sg.addEgressRule('egressTest', { - ipProtocol: 'ipv4', - cidrIp: '10.0.0.7/32', - description: 'test description', - fromPort: 80, - toPort: 80, -}); - -sg.addIngressRule('ingressTest', { - ipProtocol: 'ipv4', - cidrIp: '10.0.0.7/32', - description: 'test description', - fromPort: 80, - toPort: 80, -}); - -const sg2 = SecurityGroup.fromSecurityGroupId(stack, 'someImportedSecurityGroupId'); - -sg2.addEgressRule('egressTest', { - ipProtocol: 'ipv4', - cidrIp: '10.0.0.7/32', - description: 'test description', - fromPort: 80, - toPort: 80, -}); - -sg2.addIngressRule('ingressTest', { - ipProtocol: 'ipv4', - cidrIp: '10.0.0.7/32', - description: 'test description', - fromPort: 80, - toPort: 80, -}); - -const nacl = new NetworkAcl(stack, 'naclTest', { - networkAclName: 'naclTest', - vpc, - tags: [{ key: 'test', value: 'test2' }], -}); - -nacl.addEntry('naclEntry', { - egress: true, - protocol: 443, - ruleAction: 'deny', - ruleNumber: 2, - cidrBlock: '10.0.0.14/32', -}); - -nacl.associateSubnet('naclSubnetAssociation', { subnet: subnet1 }); - -new DeleteDefaultSecurityGroupRules(stack, 'TestDeleteDefaultSgRules', { - vpcId: 'someVpcId', //intentionally did not use regex of VPC to prevent scan - kmsKey: new cdk.aws_kms.Key(stack, 'testKmsTestDeleteDefaultSgRules'), - logRetentionInDays: 7, -}); - -/** - * Vpc construct test - */ -describe('VpcImport', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-peering.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-peering.test.ts deleted file mode 100644 index ba08d6b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc-peering.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { VpcPeering } from '../../lib/aws-ec2/vpc-peering'; -import { snapShotTest } from '../snapshot-test'; -import { describe, it, expect } from '@jest/globals'; - -const testNamePrefix = 'Construct(VpcPeering): '; - -//Initialize stack for tests -const stack = new cdk.Stack(); - -const vpcPeer = new VpcPeering(stack, 'TestPeering', { - name: 'Test', - peerOwnerId: '111111111111', - peerRegion: 'us-east-1', - peerVpcId: 'AccepterVpc', - vpcId: 'RequesterVpc', - peerRoleName: 'TestRole', - tags: [{ key: 'key', value: 'value' }], -}); - -const key = new cdk.aws_kms.Key(stack, 'kmsKey'); - -vpcPeer.addPeeringRoute('vpcPeeringRoutev4', 'rt-12345', '10.0.0.5/32', undefined, undefined, key, 10); -vpcPeer.addPeeringRoute('vpcPeeringRoutev6', 'rt-12345', undefined, undefined, 'fd00::/8', key, 10); -vpcPeer.addPeeringRoute('vpcPeeringRoutepl', 'rt-12345', undefined, 'pl-test', undefined, key, 10); - -const crLambda = new cdk.aws_lambda.Function(stack, 'test', { - code: new cdk.aws_lambda.InlineCode('foo'), - handler: 'handler', - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, -}); -const crProvider = new cdk.custom_resources.Provider(stack, 'myProvider', { onEventHandler: crLambda }); - -vpcPeer.addCrossAcctPeeringRoute({ - id: 'crossAccountPeerRoutev4', - ownerAccount: '111111111111', - ownerRegion: stack.region, - partition: stack.partition, - provider: crProvider, - roleName: 'role', - routeTableId: 'rt-3456', - destination: '10.0.0.6/32', -}); - -vpcPeer.addCrossAcctPeeringRoute({ - id: 'crossAccountPeerRoutepl', - ownerAccount: '111111111111', - ownerRegion: stack.region, - partition: stack.partition, - provider: crProvider, - roleName: 'role', - routeTableId: 'rt-3456', - destinationPrefixListId: 'pl-test', -}); - -vpcPeer.addCrossAcctPeeringRoute({ - id: 'crossAccountPeerRoutev6', - ownerAccount: '111111111111', - ownerRegion: stack.region, - partition: stack.partition, - provider: crProvider, - roleName: 'role', - routeTableId: 'rt-3456', - ipv6Destination: 'fd00::/8', -}); -/** - * VPC peering construct test - */ -describe('VpcPeering', () => { - it('destinationPrefix list without logRetention throws error', () => { - function noLogRetention() { - vpcPeer.addPeeringRoute( - 'vpcPeeringRoute2', - 'rt-12345', - '10.0.0.5/32', - 'destinationRoute', - undefined, - new cdk.aws_kms.Key(stack, 'kmsKey1'), - undefined, - ); - } - expect(noLogRetention).toThrow( - new Error('Attempting to add prefix list route without specifying log group retention period'), - ); - }); - it('destinationPrefix list test', () => { - vpcPeer.addPeeringRoute( - 'vpcPeeringRoute3', - 'rt-12345', - '10.0.0.5/32', - 'destinationRoute', - undefined, - new cdk.aws_kms.Key(stack, 'kmsKey2'), - 10, - ); - }); - it('no destination throws error', () => { - function noDest() { - vpcPeer.addPeeringRoute( - 'vpcPeeringRoute4', - 'rt-12345', - undefined, - undefined, - undefined, - new cdk.aws_kms.Key(stack, 'kmsKey3'), - 10, - ); - } - expect(noDest).toThrow(new Error('Attempting to add CIDR route without specifying destination')); - }); - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc.test.ts deleted file mode 100644 index 26322fa..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpc.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - Vpc, - Subnet, - NatGateway, - SecurityGroup, - NetworkAcl, - DeleteDefaultSecurityGroupRules, -} from '../../lib/aws-ec2/vpc'; -import { RouteTable } from '../../lib/aws-ec2/route-table'; -import { snapShotTest } from '../snapshot-test'; -import { OutpostsConfig } from '@aws-accelerator/config'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(Vpc): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const vpc = new Vpc(stack, 'TestVpc', { - name: 'Main', - ipv4CidrBlock: '10.0.0.0/16', - dhcpOptions: 'Test-Options', - internetGateway: true, - enableDnsHostnames: false, - enableDnsSupport: true, - instanceTenancy: 'default', - tags: [{ key: 'Test-Key', value: 'Test-Value' }], - virtualPrivateGateway: { - asn: 65000, - }, -}); - -vpc.addFlowLogs({ - destinations: ['s3', 'cloud-watch-logs'], - maxAggregationInterval: 60, - trafficType: 'ALL', - bucketArn: 'arn:aws:s3:::aws-accelerator-test-111111111111-us-east-1', - encryptionKey: new cdk.aws_kms.Key(stack, 'test-key2'), - logRetentionInDays: 10, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', -}); - -const vpcExistingIam = new Vpc(stack, 'TestVpcExistingIam', { - name: 'Main', - ipv4CidrBlock: '10.0.0.0/16', - dhcpOptions: 'Test-Options', - internetGateway: true, - enableDnsHostnames: false, - enableDnsSupport: true, - instanceTenancy: 'default', - tags: [{ key: 'Test-Key', value: 'Test-Value' }], - virtualPrivateGateway: { - asn: 65000, - }, -}); - -vpcExistingIam.addFlowLogs({ - destinations: ['s3', 'cloud-watch-logs'], - maxAggregationInterval: 60, - trafficType: 'ALL', - bucketArn: 'arn:aws:s3:::aws-accelerator-test-111111111111-us-east-1', - encryptionKey: new cdk.aws_kms.Key(stack, 'testKey2ExistingIam'), - logRetentionInDays: 10, - useExistingRoles: true, - acceleratorPrefix: 'AWSAccelerator', -}); - -vpc.addIpv4Cidr({ cidrBlock: '10.2.0.0/16' }); -vpc.addIpv6Cidr({ amazonProvidedIpv6CidrBlock: true }); -vpc.addIpv6Cidr({ ipv6CidrBlock: '::1', ipv6Pool: 'ipv6Pool-ec2-1234' }); - -const outpostConfig = new OutpostsConfig(); -const rt = new RouteTable(stack, 'test-rt', { name: 'test-rt', vpc }); -const subnet1 = new Subnet(stack, 'test', { - availabilityZone: 'a', - vpc, - name: 'testSubnetOutpost', - routeTable: rt, - outpost: outpostConfig, - ipv4CidrBlock: '10.0.1.0/24', - availabilityZoneId: undefined, -}); - -new Subnet(stack, 'testSubnetIpam', { - availabilityZone: 'b', - availabilityZoneId: undefined, - vpc, - name: 'testSubnet', - routeTable: rt, - ipamAllocation: { - ipamPoolName: 'test', - netmaskLength: 24, - }, - basePool: ['myBasePool'], - logRetentionInDays: 10, - kmsKey: new cdk.aws_kms.Key(stack, 'testKms'), -}); - -new Subnet(stack, 'testSubnetIpamPhysicalAz1', { - availabilityZone: undefined, - availabilityZoneId: '1', - vpc, - name: 'testSubnetPhysicalAz1', - routeTable: rt, - ipamAllocation: { - ipamPoolName: 'test', - netmaskLength: 24, - }, - basePool: ['myBasePool'], - logRetentionInDays: 10, - kmsKey: new cdk.aws_kms.Key(stack, 'testKms1'), -}); - -new Subnet(stack, 'testSubnetPhysicalAz2', { - availabilityZone: undefined, - availabilityZoneId: '2', - vpc, - name: 'testSubnetPhysicalAz2', - routeTable: rt, - ipamAllocation: { - ipamPoolName: 'test', - netmaskLength: 24, - }, - basePool: ['myBasePool'], - logRetentionInDays: 10, - kmsKey: new cdk.aws_kms.Key(stack, 'testKms2'), -}); - -new Subnet(stack, 'Ipv6OnlySubnet', { - availabilityZoneId: '1', - vpc, - name: 'test-ipv6-only-subnet', - routeTable: rt, - enableDns64: true, - ipv6CidrBlock: 'fd00::/58', - privateDnsOptions: { - enableDnsAAAARecord: true, - enableDnsARecord: false, - hostnameType: 'resource-name', - }, -}); - -new Subnet(stack, 'DualStackSubnet', { - availabilityZoneId: '1', - vpc, - name: 'test-dualstack-subnet', - routeTable: rt, - enableDns64: true, - ipv4CidrBlock: '10.0.0.0/24', - ipv6CidrBlock: 'fd00::/58', - privateDnsOptions: { - enableDnsAAAARecord: true, - enableDnsARecord: true, - }, -}); - -new NatGateway(stack, 'natGw', { name: 'ngw', subnet: subnet1, tags: [{ key: 'test', value: 'test2' }] }); - -const sg = new SecurityGroup(stack, 'tetSg', { - description: 'test', - securityGroupName: 'test', - vpc, - tags: [{ key: 'test', value: 'test2' }], -}); - -sg.addEgressRule('egressTest', { - ipProtocol: 'ipv4', - cidrIp: '10.0.0.7/32', - description: 'test description', - fromPort: 80, - toPort: 80, -}); - -sg.addIngressRule('ingressTest', { - ipProtocol: 'ipv4', - cidrIp: '10.0.0.7/32', - description: 'test description', - fromPort: 80, - toPort: 80, -}); - -const nacl = new NetworkAcl(stack, 'naclTest', { - networkAclName: 'naclTest', - vpc, - tags: [{ key: 'test', value: 'test2' }], -}); - -nacl.addEntry('naclEntry', { - egress: true, - protocol: 443, - ruleAction: 'deny', - ruleNumber: 2, - cidrBlock: '10.0.0.14/32', -}); - -nacl.associateSubnet('naclSubnetAssociation', { subnet: subnet1 }); - -new DeleteDefaultSecurityGroupRules(stack, 'TestDeleteDefaultSgRules', { - vpcId: 'someVpcId', //intentionally did not use regex of VPC to prevent scan - kmsKey: new cdk.aws_kms.Key(stack, 'testKmsTestDeleteDefaultSgRules'), - logRetentionInDays: 7, -}); - -/** - * Vpc construct test - */ -describe('Vpc', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpn-connection.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpn-connection.test.ts deleted file mode 100644 index adc5079..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ec2/vpn-connection.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { VpnConnection } from '../../lib/aws-ec2/vpn-connection'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(VpnConnection): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new VpnConnection(stack, 'TestVpn', { - name: 'Test-Vpn', - customerGatewayId: 'Test-Cgw', - staticRoutesOnly: true, - transitGatewayId: 'Test-tgw', - vpnTunnelOptionsSpecifications: [ - { - preSharedKey: 'test-key-1', - tunnelInsideCidr: '169.254.200.0/30', - }, - { - preSharedKey: 'test-key-1', - tunnelInsideCidr: '169.254.100.0/30', - }, - ], - tags: [{ key: 'Test-Key', value: 'Test-Value' }], -}); - -new VpnConnection(stack, 'AdvancedVpn', { - name: 'Advanced-Vpn', - amazonIpv4NetworkCidr: '10.0.0.0/16', - customerIpv4NetworkCidr: '192.168.0.0/16', - customResourceHandler: cdk.aws_lambda.Function.fromFunctionName(stack, 'TestFunction', 'TestFunction'), - enableVpnAcceleration: true, - customerGatewayId: 'Test-Cgw', - staticRoutesOnly: true, - transitGatewayId: 'Test-tgw', - vpnTunnelOptionsSpecifications: [ - { - dpdTimeoutAction: 'restart', - dpdTimeoutSeconds: 60, - ikeVersions: [2], - logging: { - enable: true, - }, - phase1: { - dhGroups: [14, 20], - encryptionAlgorithms: ['AES256'], - integrityAlgorithms: ['SHA2-256'], - }, - phase2: { - dhGroups: [14, 20], - encryptionAlgorithms: ['AES256'], - integrityAlgorithms: ['SHA2-256'], - }, - preSharedKey: 'test-key-1', - tunnelInsideCidr: '169.254.200.0/30', - }, - { - preSharedKey: 'test-key-1', - tunnelInsideCidr: '169.254.100.0/30', - }, - ], - tags: [{ key: 'Test-Key', value: 'Test-Value' }], -}); - -/** - * VpnConnection construct test - */ -describe('VpnConnection', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/application-load-balancer.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/application-load-balancer.test.ts.snap deleted file mode 100644 index 16aecab..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/application-load-balancer.test.ts.snap +++ /dev/null @@ -1,183 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ApplicationLoadBalancer Construct(ApplicationLoadBalancer): Snapshot Test 1`] = ` -{ - "Resources": { - "Test7BFAF513": { - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "access_logs.s3.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.bucket", - "Value": "test-bucket", - }, - { - "Key": "access_logs.s3.prefix", - "Value": { - "Fn::Join": [ - "", - [ - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - "/Test", - ], - ], - }, - }, - { - "Key": "deletion_protection.enabled", - "Value": "true", - }, - { - "Key": "idle_timeout.timeout_seconds", - "Value": "60", - }, - { - "Key": "routing.http.drop_invalid_header_fields.enabled", - "Value": "true", - }, - { - "Key": "routing.http.x_amzn_tls_version_and_cipher_suite.enabled", - "Value": "true", - }, - { - "Key": "routing.http.xff_client_port.enabled", - "Value": "true", - }, - { - "Key": "routing.http.xff_header_processing.mode", - "Value": "append", - }, - { - "Key": "routing.http2.enabled", - "Value": "true", - }, - { - "Key": "waf.fail_open.enabled", - "Value": "true", - }, - ], - "Name": "Test", - "Scheme": "internal", - "SecurityGroups": [ - "sg-test123", - "sg-test456", - ], - "Subnets": [ - "subnet-test123", - "subnet-test456", - ], - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - "Type": "application", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "TestListenerstring15C484455": { - "Properties": { - "Certificates": [ - { - "CertificateArn": "fully-qualified-arn-acm", - }, - ], - "DefaultActions": [ - { - "FixedResponseConfig": { - "ContentType": "contentType", - "MessageBody": "messageBody", - "StatusCode": "statusCode", - }, - "Order": 2, - "TargetGroupArn": "target-group-test1", - "Type": "fixed-response", - }, - ], - "LoadBalancerArn": { - "Ref": "Test7BFAF513", - }, - "Port": 81, - "Protocol": "HTTP", - "SslPolicy": "ELBSecurityPolicy-2016-08", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "TestListenerstring20A24AB7B": { - "Properties": { - "Certificates": [ - { - "CertificateArn": "fully-qualified-arn-acm", - }, - ], - "DefaultActions": [ - { - "Order": 3, - "RedirectConfig": { - "Host": "host", - "Path": "path", - "Port": "82", - "Protocol": "protocol", - "Query": "query", - "StatusCode": "statusCode", - }, - "TargetGroupArn": "target-group-test2", - "Type": "redirect", - }, - ], - "LoadBalancerArn": { - "Ref": "Test7BFAF513", - }, - "Port": 82, - "Protocol": "HTTP", - "SslPolicy": "ELBSecurityPolicy-2016-08", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "TestListenerstring7A65A381": { - "Properties": { - "Certificates": [ - { - "CertificateArn": "fully-qualified-arn-acm", - }, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroupStickinessConfig": { - "DurationSeconds": 1000, - "Enabled": true, - }, - "TargetGroups": [ - { - "TargetGroupArn": "\${ACCEL_LOOKUP::TargetGroup:target-group-test}", - }, - ], - }, - "Order": 1, - "TargetGroupArn": "\${ACCEL_LOOKUP::TargetGroup:target-group-test}", - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "Test7BFAF513", - }, - "Port": 80, - "Protocol": "HTTP", - "SslPolicy": "ELBSecurityPolicy-2016-08", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/gateway-load-balancer.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/gateway-load-balancer.test.ts.snap deleted file mode 100644 index 1fd285d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/gateway-load-balancer.test.ts.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GatewayLoadBalancer Construct(GatewayLoadBalancer): Snapshot Test 1`] = ` -{ - "Resources": { - "Test7BFAF513": { - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "deletion_protection.enabled", - "Value": "true", - }, - { - "Key": "load_balancing.cross_zone.enabled", - "Value": "true", - }, - ], - "Subnets": [ - "subnet-test123", - ], - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - "Type": "gateway", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "TestEndpointServiceDD0E3344": { - "Properties": { - "AcceptanceRequired": false, - "GatewayLoadBalancerArns": [ - { - "Ref": "Test7BFAF513", - }, - ], - }, - "Type": "AWS::EC2::VPCEndpointService", - }, - "TestEndpointServicePermissionsC0FA4150": { - "Properties": { - "AllowedPrincipals": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - ], - "ServiceId": { - "Ref": "TestEndpointServiceDD0E3344", - }, - }, - "Type": "AWS::EC2::VPCEndpointServicePermissions", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/network-load-balancer.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/network-load-balancer.test.ts.snap deleted file mode 100644 index e8421f3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/network-load-balancer.test.ts.snap +++ /dev/null @@ -1,144 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworkLoadBalancer Construct(NetworkLoadBalancer): Snapshot Test 1`] = ` -{ - "Parameters": { - "SsmParameterValueacceleratorapplicationtargetGroupappAvpcAtargetgrouptest1arnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/application/targetGroup/appA/vpcA/target-group-test1/arn", - "Type": "AWS::SSM::Parameter::Value", - }, - "SsmParameterValueacceleratorapplicationtargetGroupappAvpcAtargetgrouptestarnC96584B6F00A464EAD1953AFF4B05118Parameter": { - "Default": "/accelerator/application/targetGroup/appA/vpcA/target-group-test/arn", - "Type": "AWS::SSM::Parameter::Value", - }, - }, - "Resources": { - "Test7BFAF513": { - "Properties": { - "LoadBalancerAttributes": [ - { - "Key": "deletion_protection.enabled", - "Value": "true", - }, - { - "Key": "load_balancing.cross_zone.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.enabled", - "Value": "true", - }, - { - "Key": "access_logs.s3.bucket", - "Value": "test-bucket", - }, - { - "Key": "access_logs.s3.prefix", - "Value": { - "Fn::Join": [ - "", - [ - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - "/Test", - ], - ], - }, - }, - ], - "Name": "Test", - "Scheme": "internal", - "Subnets": [ - "subnet-test123", - "subnet-test456", - ], - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - "Type": "network", - }, - "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", - }, - "TestListenerstring15C484455": { - "Properties": { - "AlpnPolicy": [ - "TLS_V1_2_2018", - ], - "Certificates": [ - { - "CertificateArn": "fully-qualified-arn-acm", - }, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "SsmParameterValueacceleratorapplicationtargetGroupappAvpcAtargetgrouptest1arnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "SsmParameterValueacceleratorapplicationtargetGroupappAvpcAtargetgrouptest1arnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "Test7BFAF513", - }, - "Port": 81, - "Protocol": "HTTP", - "SslPolicy": "ELBSecurityPolicy-2016-08", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - "TestListenerstring7A65A381": { - "Properties": { - "AlpnPolicy": [ - "TLS_V1_2_2018", - ], - "Certificates": [ - { - "CertificateArn": "fully-qualified-arn-acm", - }, - ], - "DefaultActions": [ - { - "ForwardConfig": { - "TargetGroups": [ - { - "TargetGroupArn": { - "Ref": "SsmParameterValueacceleratorapplicationtargetGroupappAvpcAtargetgrouptestarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - }, - ], - }, - "TargetGroupArn": { - "Ref": "SsmParameterValueacceleratorapplicationtargetGroupappAvpcAtargetgrouptestarnC96584B6F00A464EAD1953AFF4B05118Parameter", - }, - "Type": "forward", - }, - ], - "LoadBalancerArn": { - "Ref": "Test7BFAF513", - }, - "Port": 80, - "Protocol": "HTTP", - "SslPolicy": "ELBSecurityPolicy-2016-08", - }, - "Type": "AWS::ElasticLoadBalancingV2::Listener", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/nlb-addresses.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/nlb-addresses.test.ts.snap deleted file mode 100644 index 96d7392..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/nlb-addresses.test.ts.snap +++ /dev/null @@ -1,321 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfigServiceTags Construct(NLBAddresses): Snapshot Test 1`] = ` -{ - "Resources": { - "NLBAddresses5DBEA4ED": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "NLBAddressesNLBAddressesProviderLambdaLogGroup90666151", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "NLBAddressesNLBAddressesProviderframeworkonEvent7CE2CE19", - "Arn", - ], - }, - "assumeRoleName": "test123", - "partition": { - "Ref": "AWS::Partition", - }, - "region": "us-east-1", - "targets": [ - "10.0.0.5", - "10.0.0.6", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "NLBAddressesNLBAddressesProviderLambdaA8F7BC9A": { - "DependsOn": [ - "NLBAddressesNLBAddressesProviderLambdaServiceRoleDefaultPolicyFC32AE1C", - "NLBAddressesNLBAddressesProviderLambdaServiceRole7334A8BA", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "NLBAddressesNLBAddressesProviderLambdaServiceRole7334A8BA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 15, - }, - "Type": "AWS::Lambda::Function", - }, - "NLBAddressesNLBAddressesProviderLambdaLogGroup90666151": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TableKeyF581D96F", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "NLBAddressesNLBAddressesProviderLambdaA8F7BC9A", - }, - ], - ], - }, - "RetentionInDays": 30, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "NLBAddressesNLBAddressesProviderLambdaServiceRole7334A8BA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NLBAddressesNLBAddressesProviderLambdaServiceRoleDefaultPolicyFC32AE1C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/test123", - ], - ], - }, - "Sid": "StsAssumeRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NLBAddressesNLBAddressesProviderLambdaServiceRoleDefaultPolicyFC32AE1C", - "Roles": [ - { - "Ref": "NLBAddressesNLBAddressesProviderLambdaServiceRole7334A8BA", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NLBAddressesNLBAddressesProviderframeworkonEvent7CE2CE19": { - "DependsOn": [ - "NLBAddressesNLBAddressesProviderframeworkonEventServiceRoleDefaultPolicy9F18C252", - "NLBAddressesNLBAddressesProviderframeworkonEventServiceRole1B7F82DB", - ], - "Properties": { - "Code": { - "S3Bucket": "cdk-hnb659fds-assets-333333333333-us-east-1", - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Stack/NLBAddresses/NLBAddressesProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "NLBAddressesNLBAddressesProviderLambdaA8F7BC9A", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "NLBAddressesNLBAddressesProviderframeworkonEventServiceRole1B7F82DB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NLBAddressesNLBAddressesProviderframeworkonEventServiceRole1B7F82DB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NLBAddressesNLBAddressesProviderframeworkonEventServiceRoleDefaultPolicy9F18C252": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "NLBAddressesNLBAddressesProviderLambdaA8F7BC9A", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "NLBAddressesNLBAddressesProviderLambdaA8F7BC9A", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NLBAddressesNLBAddressesProviderframeworkonEventServiceRoleDefaultPolicy9F18C252", - "Roles": [ - { - "Ref": "NLBAddressesNLBAddressesProviderframeworkonEventServiceRole1B7F82DB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "TableKeyF581D96F": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::333333333333:root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/target-group.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/target-group.test.ts.snap deleted file mode 100644 index f7c560b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/__snapshots__/target-group.test.ts.snap +++ /dev/null @@ -1,73 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TargetGroup Construct(TargetGroup): Snapshot Test 1`] = ` -{ - "Resources": { - "Test19A2804E9": { - "Properties": { - "HealthCheckEnabled": true, - "Matcher": {}, - "Name": "Test", - "Port": 80, - "Protocol": "HTTP", - "ProtocolVersion": "HTTP1", - "Tags": [ - { - "Key": "Name", - "Value": "Test", - }, - ], - "TargetGroupAttributes": [ - { - "Key": "deregistration_delay.timeout_seconds", - "Value": "123", - }, - { - "Key": "stickiness.enabled", - "Value": "true", - }, - { - "Key": "stickiness.type", - "Value": "stickinessType", - }, - { - "Key": "load_balancing.algorithm.type", - "Value": "algorithm", - }, - { - "Key": "slow_start.duration_seconds", - "Value": "123", - }, - { - "Key": "stickiness.app_cookie.cookie_name", - "Value": "appCookieName", - }, - { - "Key": "stickiness.app_cookie.duration_seconds", - "Value": "123", - }, - { - "Key": "stickiness.lb_cookie.duration_seconds", - "Value": "123", - }, - { - "Key": "deregistration_delay.connection_termination.enabled", - "Value": "true", - }, - { - "Key": "preserve_client_ip.enabled", - "Value": "true", - }, - { - "Key": "proxy_protocol_v2.enabled", - "Value": "true", - }, - ], - "TargetType": "instance", - "VpcId": "test", - }, - "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/application-load-balancer.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/application-load-balancer.test.ts deleted file mode 100644 index c1301d0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/application-load-balancer.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ApplicationLoadBalancer } from '../../lib/aws-elasticloadbalancingv2/application-load-balancer'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(ApplicationLoadBalancer): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ApplicationLoadBalancer(stack, 'Test', { - name: 'Test', - ssmPrefix: '/accelerator', - subnets: ['subnet-test123', 'subnet-test456'], - securityGroups: ['sg-test123', 'sg-test456'], - scheme: 'internal', - accessLogsBucket: 'test-bucket', - attributes: { - deletionProtection: true, - idleTimeout: 60, - routingHttpDropInvalidHeader: true, - routingHttpXAmznTlsCipherEnable: true, - routingHttpXffClientPort: true, - routingHttpXffHeaderProcessingMode: 'append', - http2Enabled: true, - wafFailOpen: true, - }, - listeners: [ - { - name: 'string', - certificate: 'fully-qualified-arn-acm', - port: 80, - protocol: 'HTTP', - type: 'forward', - sslPolicy: 'ELBSecurityPolicy-2016-08', - targetGroup: '${ACCEL_LOOKUP::TargetGroup:target-group-test}', - order: 1, - forwardConfig: { - targetGroupStickinessConfig: { durationSeconds: 1000, enabled: true }, - }, - fixedResponseConfig: undefined, - redirectConfig: undefined, - }, - { - name: 'string1', - certificate: 'fully-qualified-arn-acm', - port: 81, - protocol: 'HTTP', - order: 2, - type: 'fixed-response', - fixedResponseConfig: { - statusCode: 'statusCode', - contentType: 'contentType', - messageBody: 'messageBody', - }, - forwardConfig: undefined, - redirectConfig: undefined, - sslPolicy: 'ELBSecurityPolicy-2016-08', - targetGroup: 'target-group-test1', - }, - { - name: 'string2', - certificate: 'fully-qualified-arn-acm', - port: 82, - protocol: 'HTTP', - type: 'redirect', - order: 3, - redirectConfig: { - statusCode: 'statusCode', - host: 'host', - path: 'path', - port: 82, - protocol: 'protocol', - query: 'query', - }, - forwardConfig: undefined, - fixedResponseConfig: undefined, - sslPolicy: 'ELBSecurityPolicy-2016-08', - targetGroup: 'target-group-test2', - }, - ], -}); - -/** - * NLB construct test - */ -describe('ApplicationLoadBalancer', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/gateway-load-balancer.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/gateway-load-balancer.test.ts deleted file mode 100644 index 2b03b57..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/gateway-load-balancer.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GatewayLoadBalancer } from '../../lib/aws-elasticloadbalancingv2/gateway-load-balancer'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GatewayLoadBalancer): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new GatewayLoadBalancer(stack, 'Test', { - name: 'Test', - allowedPrincipals: ['333333333333'], - subnets: ['subnet-test123'], - deletionProtection: true, -}); - -/** - * GWLB construct test - */ -describe('GatewayLoadBalancer', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/network-load-balancer.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/network-load-balancer.test.ts deleted file mode 100644 index 4686221..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/network-load-balancer.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NetworkLoadBalancer } from '../../lib/aws-elasticloadbalancingv2/network-load-balancer'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(NetworkLoadBalancer): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new NetworkLoadBalancer(stack, 'Test', { - name: 'Test', - ssmPrefix: '/accelerator', - appName: 'appA', - vpcName: 'vpcA', - subnets: ['subnet-test123', 'subnet-test456'], - deletionProtection: true, - scheme: 'internal', - crossZoneLoadBalancing: true, - accessLogsBucket: 'test-bucket', - listeners: [ - { - name: 'string', - certificate: 'fully-qualified-arn-acm', - port: 80, - protocol: 'HTTP', - alpnPolicy: 'TLS_V1_2_2018', - sslPolicy: 'ELBSecurityPolicy-2016-08', - targetGroup: '${ACCEL_LOOKUP::TargetGroup:target-group-test}', - }, - { - name: 'string1', - certificate: 'fully-qualified-arn-acm', - port: 81, - protocol: 'HTTP', - alpnPolicy: 'TLS_V1_2_2018', - sslPolicy: 'ELBSecurityPolicy-2016-08', - targetGroup: 'target-group-test1', - }, - ], -}); - -/** - * NLB construct test - */ -describe('NetworkLoadBalancer', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/nlb-addresses.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/nlb-addresses.test.ts deleted file mode 100644 index 0ae5f07..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/nlb-addresses.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NLBAddresses } from '../../lib/aws-elasticloadbalancingv2/nlb-addresses'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(NLBAddresses): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const env = { account: '333333333333', region: 'us-east-1' }; -const stack = new cdk.Stack(app, 'Stack', { env: env }); - -/** - * ConfigServiceTags construct test - */ - -new NLBAddresses(stack, 'NLBAddresses', { - assumeRoleName: 'test123', - kmsKey: new cdk.aws_kms.Key(stack, 'TableKey', {}), - logRetentionInDays: 30, - partition: cdk.Stack.of(stack).partition, - targets: ['10.0.0.5', '10.0.0.6'], -}); - -describe('ConfigServiceTags', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/target-group.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/target-group.test.ts deleted file mode 100644 index 654c51b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-elasticloadbalancingv2/target-group.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { TargetGroup } from '../../lib/aws-elasticloadbalancingv2/target-group'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(TargetGroup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new TargetGroup(stack, 'Test1', { - name: 'Test', - port: 80, - protocol: 'HTTP', - protocolVersion: 'HTTP1', - type: 'instance', - vpc: 'test', - attributes: { - deregistrationDelay: 123, - stickiness: true, - stickinessType: 'stickinessType', - algorithm: 'algorithm', - slowStart: 123, - appCookieName: 'appCookieName', - appCookieDuration: 123, - lbCookieDuration: 123, - connectionTermination: true, - preserveClientIp: true, - proxyProtocolV2: true, - }, -}); - -/** - * TargetGroup construct test - */ -describe('TargetGroup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/move-account-rule.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/move-account-rule.test.ts.snap deleted file mode 100644 index d7b8570..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/move-account-rule.test.ts.snap +++ /dev/null @@ -1,428 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MoveAccountRule Construct(MoveAccountRule): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "MoveAccountRuleAcceleratorConfigTableArnLookup080331ED": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAccelerator-MoveAccountConfigRule-Role", - ], - ], - }, - "invokingAccountID": { - "Ref": "AWS::AccountId", - }, - "invokingRegion": { - "Ref": "AWS::Region", - }, - "parameterAccountID": { - "Ref": "AWS::AccountId", - }, - "parameterName": "/accelerator/prepare-stack/configTable/arn", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "MoveAccountRuleAcceleratorConfigTableNameLookup17503A0D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAccelerator-MoveAccountConfigRule-Role", - ], - ], - }, - "invokingAccountID": { - "Ref": "AWS::AccountId", - }, - "invokingRegion": { - "Ref": "AWS::Region", - }, - "parameterAccountID": { - "Ref": "AWS::AccountId", - }, - "parameterName": "/accelerator/prepare-stack/configTable/name", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "MoveAccountRuleAllowEventRuleMoveAccountRuleMoveAccountTargetFunction4311F702F21B0CE9": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "MoveAccountRuleMoveAccountTargetFunction0F994976", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "MoveAccountRuleC1A7E471", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "MoveAccountRuleC1A7E471": { - "Properties": { - "Description": "CloudWatch Events rule to monitor for Organizations MoveAccount events", - "EventPattern": { - "detail": { - "eventName": [ - "MoveAccount", - ], - "eventSource": [ - "organizations.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.organizations", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "MoveAccountRuleMoveAccountTargetFunction0F994976", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 3, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "MoveAccountRuleMoveAccountTargetFunction0F994976": { - "DependsOn": [ - "MoveAccountRuleMoveAccountTargetFunctionServiceRoleDefaultPolicy142271D7", - "MoveAccountRuleMoveAccountTargetFunctionServiceRoleF8A8BB3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to process Organizations MoveAccount event from CloudTrail", - "Environment": { - "Variables": { - "COMMIT_ID": "sample-commit-id", - "CONFIG_TABLE_NAME": { - "Ref": "MoveAccountRuleAcceleratorConfigTableNameLookup17503A0D", - }, - "GLOBAL_REGION": "us-west-2", - "HOME_REGION": "us-east-1", - "STACK_PREFIX": "AWSAccelerator", - }, - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MoveAccountRuleMoveAccountTargetFunctionServiceRoleF8A8BB3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "MoveAccountRuleMoveAccountTargetFunctionLogGroup0D1780CE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "MoveAccountRuleMoveAccountTargetFunction0F994976", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "MoveAccountRuleMoveAccountTargetFunctionServiceRoleDefaultPolicy142271D7": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "dynamodb:Query", - "Effect": "Allow", - "Resource": { - "Ref": "MoveAccountRuleAcceleratorConfigTableArnLookup080331ED", - }, - "Sid": "dynamodbConfigTable", - }, - { - "Action": [ - "organizations:MoveAccount", - "organizations:ListParents", - "organizations:DescribeOrganizationalUnit", - "organizations:ListRoots", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "organizations", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "MoveAccountRuleMoveAccountTargetFunctionServiceRoleDefaultPolicy142271D7", - "Roles": [ - { - "Ref": "MoveAccountRuleMoveAccountTargetFunctionServiceRoleF8A8BB3D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "MoveAccountRuleMoveAccountTargetFunctionServiceRoleF8A8BB3D": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/new-cloudwatch-log-event-rule.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/new-cloudwatch-log-event-rule.test.ts.snap deleted file mode 100644 index 8b27e72..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/new-cloudwatch-log-event-rule.test.ts.snap +++ /dev/null @@ -1,604 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NewCloudWatchLogEvent Construct(NewCloudWatchLogEvent): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyExistingIam1584FEB7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyExistingIamC416D475": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "NewCloudWatchLogEventExistingIamNewLogGroupCreatedRuleAllowEventRuleNewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunction19D4F3A241B61069": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionA6186161", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventExistingIamNewLogGroupCreatedRuleED930315", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "NewCloudWatchLogEventExistingIamNewLogGroupCreatedRuleED930315": { - "Properties": { - "EventPattern": { - "detail": { - "eventName": [ - "CreateLogGroup", - ], - "eventSource": [ - "logs.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.logs", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionA6186161", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 5, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionA6186161": { - "DependsOn": [ - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy9013F7C6", - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleF75C2E9E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "LogDestination": "LogRetentionArn", - "LogKmsKeyArn": { - "Fn::GetAtt": [ - "CustomKeyExistingIam1584FEB7", - "Arn", - ], - }, - "LogRetention": "731", - "LogSubscriptionRole": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorLogReplicationRole-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyExistingIamC416D475", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleF75C2E9E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy9013F7C6": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":some-account-id:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy9013F7C6", - "Roles": [ - { - "Ref": "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleF75C2E9E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "NewCloudWatchLogEventExistingIamSetLogRetentionSubscriptionFunctionServiceRoleF75C2E9E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NewCloudWatchLogEventNewLogGroupCreatedRuleAllowEventRuleNewCloudWatchLogEventSetLogRetentionSubscriptionFunctionDC0DB4146E3585DE": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunction0F748D41", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventNewLogGroupCreatedRuleB25856AE", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "NewCloudWatchLogEventNewLogGroupCreatedRuleB25856AE": { - "Properties": { - "EventPattern": { - "detail": { - "eventName": [ - "CreateLogGroup", - ], - "eventSource": [ - "logs.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.logs", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunction0F748D41", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumRetryAttempts": 5, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunction0F748D41": { - "DependsOn": [ - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy99B4E143", - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRole3A975055", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "LogDestination": "LogRetentionArn", - "LogKmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogRetention": "731", - "LogSubscriptionRole": "testString", - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRole3A975055", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRole3A975055": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy99B4E143": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutRetentionPolicy", - "logs:AssociateKmsKey", - "logs:DescribeLogGroups", - "logs:DescribeSubscriptionFilters", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - { - "Action": [ - "logs:PutSubscriptionFilter", - "logs:DeleteSubscriptionFilter", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":some-account-id:destination:*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRoleDefaultPolicy99B4E143", - "Roles": [ - { - "Ref": "NewCloudWatchLogEventSetLogRetentionSubscriptionFunctionServiceRole3A975055", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/revert-scp-changes.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/revert-scp-changes.test.ts.snap deleted file mode 100644 index a7a8723..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/revert-scp-changes.test.ts.snap +++ /dev/null @@ -1,662 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RevertScpChanges Construct(RevertScpChanges): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomCloudWatchKey29CB95E08": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomCloudWatchKey720588E1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2F8916D97": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "RevertScpChanges2ModifyScpRule922447C3": { - "Properties": { - "Description": "Rule to notify when an LZA-managed SCP is modified or detached.", - "EventPattern": { - "detail": { - "eventName": [ - "AttachPolicy", - "DetachPolicy", - "UpdatePolicy", - ], - "eventSource": [ - "organizations.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.organizations", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "RevertScpChanges2RevertScpChangesFunctionB8AB5A00", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "RevertScpChanges2ModifyScpRuleAllowEventRuleRevertScpChanges2RevertScpChangesFunction84CA55797EA87ED3": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "RevertScpChanges2RevertScpChangesFunctionB8AB5A00", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "RevertScpChanges2ModifyScpRule922447C3", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "RevertScpChanges2RevertScpChangesFunctionB8AB5A00": { - "DependsOn": [ - "RevertScpChanges2RevertScpChangesFunctionServiceRoleDefaultPolicy27A2A655", - "RevertScpChanges2RevertScpChangesFunctionServiceRoleD920129A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to revert changes made to LZA-controlled service control policies", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-west-2", - "ORGANIZATIONS_ENABLED": "false", - "SINGLE_ACCOUNT_MODE": "false", - "SNS_TOPIC_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-west-2:", - { - "Ref": "AWS::AccountId", - }, - ":aws-accelerator-Security", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2F8916D97", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "RevertScpChanges2RevertScpChangesFunctionServiceRoleD920129A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "RevertScpChanges2RevertScpChangesFunctionLogGroup6CD05851": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCloudWatchKey29CB95E08", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "RevertScpChanges2RevertScpChangesFunctionB8AB5A00", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "RevertScpChanges2RevertScpChangesFunctionServiceRoleD920129A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RevertScpChanges2RevertScpChangesFunctionServiceRoleDefaultPolicy27A2A655": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Encrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "kmsEncryptMessage", - }, - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:DescribePolicy", - "organizations:ListAccounts", - "organizations:ListRoots", - "organizations:ListOrganizationalUnitsForParent", - "organizations:UpdatePolicy", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrgPolicyUpdate", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-west-2:", - { - "Ref": "AWS::AccountId", - }, - ":aws-accelerator-Security", - ], - ], - }, - "Sid": "snsPublishMessage", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "RevertScpChanges2RevertScpChangesFunctionServiceRoleDefaultPolicy27A2A655", - "Roles": [ - { - "Ref": "RevertScpChanges2RevertScpChangesFunctionServiceRoleD920129A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "RevertScpChangesModifyScpRule4ECCD6B5": { - "Properties": { - "Description": "Rule to notify when an LZA-managed SCP is modified or detached.", - "EventPattern": { - "detail": { - "eventName": [ - "AttachPolicy", - "DetachPolicy", - "UpdatePolicy", - ], - "eventSource": [ - "organizations.amazonaws.com", - ], - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - "source": [ - "aws.organizations", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "RevertScpChangesRevertScpChangesFunction5EF82185", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "RevertScpChangesModifyScpRuleAllowEventRuleRevertScpChangesRevertScpChangesFunction5DC45CB429E5A89B": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "RevertScpChangesRevertScpChangesFunction5EF82185", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "RevertScpChangesModifyScpRule4ECCD6B5", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "RevertScpChangesRevertScpChangesFunction5EF82185": { - "DependsOn": [ - "RevertScpChangesRevertScpChangesFunctionServiceRoleDefaultPolicyF3C3ECD9", - "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Lambda function to revert changes made to LZA-controlled service control policies", - "Environment": { - "Variables": { - "ACCELERATOR_PREFIX": "AWSAccelerator", - "AWS_PARTITION": { - "Ref": "AWS::Partition", - }, - "HOME_REGION": "us-west-2", - "ORGANIZATIONS_ENABLED": "true", - "SINGLE_ACCOUNT_MODE": "true", - "SNS_TOPIC_ARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-west-2:", - { - "Ref": "AWS::AccountId", - }, - ":aws-accelerator-Security", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "RevertScpChangesRevertScpChangesFunctionLogGroup92A9E8AA": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCloudWatchKey720588E1", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "RevertScpChangesRevertScpChangesFunction5EF82185", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RevertScpChangesRevertScpChangesFunctionServiceRoleDefaultPolicyF3C3ECD9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Encrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "kmsEncryptMessage", - }, - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:DescribePolicy", - "organizations:ListAccounts", - "organizations:ListRoots", - "organizations:ListOrganizationalUnitsForParent", - "organizations:UpdatePolicy", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrgPolicyUpdate", - }, - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:us-west-2:", - { - "Ref": "AWS::AccountId", - }, - ":aws-accelerator-Security", - ], - ], - }, - "Sid": "snsPublishMessage", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "RevertScpChangesRevertScpChangesFunctionServiceRoleDefaultPolicyF3C3ECD9", - "Roles": [ - { - "Ref": "RevertScpChangesRevertScpChangesFunctionServiceRole4F571613", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/security-hub-events-log.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/security-hub-events-log.test.ts.snap deleted file mode 100644 index 1344099..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/__snapshots__/security-hub-events-log.test.ts.snap +++ /dev/null @@ -1,498 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityHubEventsLog Construct(SecurityHubEventsLog): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomSnsKey2E607E8C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Creates a CloudWatch Logs Group to store SecurityHub findings and updates CW Log Group resource policy", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionSecurityHubEventsFunctionResourceF6D56745": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9", - "Arn", - ], - }, - "logGroupArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/*:*", - ], - ], - }, - "logGroupName": "/AWSAccelerator-SecurityHub", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/AWSAccelerator-SecurityHub*", - ], - ], - }, - }, - { - "Action": [ - "logs:DescribeLogGroups", - "logs:PutResourcePolicy", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "Roles": [ - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/SecurityHubEventsLog/SecurityHubEventsFunction/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "Roles": [ - { - "Ref": "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecurityHubEventsLogSecurityHubLogEventsRule9AC6959D": { - "DependsOn": [ - "SecurityHubEventsLogSecurityHubEventsFunction6170C5C5", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRoleDefaultPolicy56E1474C", - "SecurityHubEventsLogSecurityHubEventsFunctionServiceRole3A655D2A", - "SecurityHubEventsLogSecurityHubEventsFunctionResourceLogGroupB7A99902", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEvent1729ADC9", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRoleDefaultPolicy4C5BFF85", - "SecurityHubEventsLogSecurityHubEventsFunctionframeworkonEventServiceRole2DBDE006", - "SecurityHubEventsLogSecurityHubEventsFunctionSecurityHubEventsFunctionResourceF6D56745", - ], - "Properties": { - "Description": "Sends Security Hub Findings above threshold to CloudWatch Logs", - "EventPattern": { - "detail-type": [ - "Security Hub Findings - Imported", - ], - "source": [ - "aws.securityhub", - ], - }, - "Targets": [ - { - "Arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/AWSAccelerator-SecurityHub:*", - ], - ], - }, - "Id": "CloudWatchLogTarget", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "SecurityHubEventsLogSecurityHubSnsEventsRule3900A9EA": { - "Properties": { - "Description": "Sends Security Hub Findings above threshold to SNS", - "EventPattern": { - "detail": { - "findings": { - "Severity": { - "Label": [ - "CRITICAL", - "HIGH", - "MEDIUM", - "LOW", - "INFORMATIONAL", - ], - }, - }, - }, - "detail-type": [ - "Security Hub Findings - Imported", - ], - "source": [ - "aws.securityhub", - ], - }, - "Targets": [ - { - "Arn": "arn:aws:us-east-1:999999999999:aws-accelerator-test", - "Id": "SnsTarget", - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/move-account-rule.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-events/move-account-rule.test.ts deleted file mode 100644 index 01b4342..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/move-account-rule.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { MoveAccountRule } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(MoveAccountRule): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new MoveAccountRule(stack, 'MoveAccountRule', { - globalRegion: 'us-west-2', - homeRegion: 'us-east-1', - moveAccountRoleName: 'AWSAccelerator-MoveAccountConfigRule-Role', - commitId: 'sample-commit-id', - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 365, - acceleratorPrefix: 'AWSAccelerator', - configTableArnParameterName: '/accelerator/prepare-stack/configTable/arn', - configTableNameParameterName: '/accelerator/prepare-stack/configTable/name', -}); - -/** - * MoveAccountRule construct test - */ -describe('MoveAccountRule', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event-rule.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event-rule.test.ts deleted file mode 100644 index eeb2595..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event-rule.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NewCloudWatchLogEvent } from '../../lib/aws-events/new-cloudwatch-log-event-rule'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(NewCloudWatchLogEvent): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new NewCloudWatchLogEvent(stack, 'NewCloudWatchLogEvent', { - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - lambdaEnvKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - logDestinationArn: 'LogRetentionArn', - logsRetentionInDaysValue: '731', - subscriptionFilterRoleArn: 'testString', - logArchiveAccountId: 'some-account-id', - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, -}); - -new NewCloudWatchLogEvent(stack, 'NewCloudWatchLogEventExistingIam', { - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyExistingIam', {}), - lambdaEnvKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKeyExistingIam', {}), - logDestinationArn: 'LogRetentionArn', - logsRetentionInDaysValue: '731', - subscriptionFilterRoleArn: 'testString', - logArchiveAccountId: 'some-account-id', - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: true, -}); - -/** - * CloudWatchDestination construct test - */ -describe('NewCloudWatchLogEvent', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event/integ.new-cloudwatch-log-event.ts b/source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event/integ.new-cloudwatch-log-event.ts deleted file mode 100644 index f7f026e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event/integ.new-cloudwatch-log-event.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/constructs/test/aws-events/new-cloudwatch-log-event --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import * as cdk from 'aws-cdk-lib'; -import { Key } from 'aws-cdk-lib/aws-kms'; -import { PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; -import { Construct, IConstruct } from 'constructs'; -import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { AcceleratorAspects } from '../../../../accelerator/lib/accelerator-aspects'; -import { CloudWatchDestination, NewCloudWatchLogEvent } from '@aws-accelerator/constructs'; -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - node.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); - } - } -} - -// CDK App for Integration Tests -const app = new cdk.App(); - -export class NewCloudWatchLogEventDemoStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: cdk.StackProps) { - super(scope, id, props); - const key = new Key(this, 'Key', { removalPolicy: cdk.RemovalPolicy.DESTROY }); - // Allow Cloudwatch logs to use the encryption key - key.addToResourcePolicy( - new PolicyStatement({ - sid: `Allow Cloudwatch logs to use the encryption key`, - principals: [new ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`)], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - conditions: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this).partition}:logs:${ - cdk.Stack.of(this).region - }:${cdk.Stack.of(this).account}:log-group:*`, - }, - }, - }), - ); - - // Create Kinesis Data Stream - // Kinesis Stream - data stream which will get data from CloudWatch logs - const logsKinesisStreamCfn = new cdk.aws_kinesis.CfnStream(this, 'LogsKinesisStreamCfn', { - retentionPeriodHours: 24, - shardCount: 1, - streamEncryption: { - encryptionType: 'KMS', - keyId: key.keyArn, - }, - }); - const logsKinesisStream = cdk.aws_kinesis.Stream.fromStreamArn( - this, - 'LogsKinesisStream', - logsKinesisStreamCfn.attrArn, - ); - - // Cloudwatch logs destination which points to Kinesis Data Stream - new CloudWatchDestination(this, 'LogsDestinationSetup', { - kinesisKmsKey: key, - kinesisStream: logsKinesisStream, - partition: cdk.Stack.of(this).partition, - accountIds: [cdk.Stack.of(this).account], - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, - }); - - const subscriptionFilterRole = new cdk.aws_iam.Role(this, 'SubscriptionFilterRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal(`logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`), - description: 'Role used by Subscription Filter to allow access to CloudWatch Destination', - inlinePolicies: { - accessLogEvents: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - resources: ['*'], - actions: ['logs:PutLogEvents'], - }), - ], - }), - }, - }); - - const accountRegionExclusion = { - account: cdk.Stack.of(this).account, - region: cdk.Stack.of(this).region, - logGroupNames: ['aws-controltower/CloudTrailLogs', 'demoGroup', 'test*'], - }; - const logsDestinationArnValue = `arn:${cdk.Stack.of(this).partition}:logs:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:destination:AWSAcceleratorCloudWatchToS3`; - - const newCwLogEvent = new NewCloudWatchLogEvent(this, 'NewCloudWatchLogEvent', { - logDestinationArn: logsDestinationArnValue, - lambdaEnvKey: key, - logsKmsKey: key, - logArchiveAccountId: cdk.Stack.of(this).account, - logsRetentionInDaysValue: '3653', - subscriptionFilterRoleArn: subscriptionFilterRole.roleArn, - exclusionSetting: accountRegionExclusion!, - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, - }); - - const includedGroup = new cdk.aws_logs.LogGroup(this, 'IncludedGroup', { - logGroupName: 'includedGroup', - }); - - const excludedGroup = new cdk.aws_logs.LogGroup(this, 'ExcludedGroup', { - logGroupName: 'demoGroup', - }); - - const excludedWildcardGroup = new cdk.aws_logs.LogGroup(this, 'ExcludedWildcardGroup', { - logGroupName: 'testerGroup', - }); - - includedGroup.node.addDependency(newCwLogEvent); - excludedGroup.node.addDependency(newCwLogEvent); - excludedWildcardGroup.node.addDependency(newCwLogEvent); - - cdk.Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - new AcceleratorAspects(app, 'aws', false); - } -} - -// Stack under test -const stackUnderTest = new NewCloudWatchLogEventDemoStack(app, 'NewCloudWatchLogEventIntegrationTestStack', { - description: 'This stack includes the application’s resources for integration testing.', -}); - -// Initialize Integ Test construct -const integ = new IntegTest(app, 'NewCloudWatchLogEventTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -integ.assertions - .awsApiCall('CloudWatchLogs', 'describeSubscriptionFilters', { logGroupName: 'includedGroup' }) - .expect( - ExpectedResult.objectLike({ - subscriptionFilters: [{ filterName: 'includedGroup' }], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); - -integ.assertions - .awsApiCall('CloudWatchLogs', 'describeSubscriptionFilters', { logGroupName: 'demoGroup' }) - .expect( - ExpectedResult.objectLike({ - subscriptionFilters: [], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); - -integ.assertions - .awsApiCall('CloudWatchLogs', 'describeSubscriptionFilters', { logGroupName: 'testerGroup' }) - .expect( - ExpectedResult.objectLike({ - subscriptionFilters: [], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(10) }); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/revert-scp-changes.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-events/revert-scp-changes.test.ts deleted file mode 100644 index 40a433c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/revert-scp-changes.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { RevertScpChanges } from '../../lib/aws-events/revert-scp-changes'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(RevertScpChanges): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const configDirPath = `${__dirname}/../../../accelerator/test/configs/all-enabled`; - -new RevertScpChanges(stack, 'RevertScpChanges', { - acceleratorPrefix: 'AWSAccelerator', - configDirPath: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - homeRegion: 'us-west-2', - kmsKeyCloudWatch: new cdk.aws_kms.Key(stack, 'CustomCloudWatchKey', {}), - kmsKeyLambda: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - logRetentionInDays: 365, - acceleratorTopicNamePrefix: 'aws-accelerator', - snsTopicName: 'Security', - scpFilePaths: [ - { - name: 'AcceleratorGuardrails1', - path: 'service-control-policies/guardrails-1.json', - tempPath: `${configDirPath}/service-control-policies/guardrails-1.json`, - }, - { - name: 'AcceleratorGuardrails2', - path: 'service-control-policies/guardrails-2.json', - tempPath: `${configDirPath}/service-control-policies/guardrails-2.json`, - }, - ], - singleAccountMode: true, - organizationEnabled: true, -}); - -new RevertScpChanges(stack, 'RevertScpChanges2', { - acceleratorPrefix: 'AWSAccelerator', - configDirPath: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - homeRegion: 'us-west-2', - kmsKeyCloudWatch: new cdk.aws_kms.Key(stack, 'CustomCloudWatchKey2', {}), - kmsKeyLambda: new cdk.aws_kms.Key(stack, 'CustomLambdaKey2', {}), - logRetentionInDays: 365, - acceleratorTopicNamePrefix: 'aws-accelerator', - snsTopicName: 'Security', - scpFilePaths: [ - { - name: 'AcceleratorGuardrails1', - path: 'service-control-policies/guardrails-1.json', - tempPath: `${configDirPath}/service-control-policies/guardrails-1.json`, - }, - { - name: 'AcceleratorGuardrails2', - path: 'service-control-policies/guardrails-2.json', - tempPath: `${configDirPath}/service-control-policies/guardrails-2.json`, - }, - ], - singleAccountMode: false, - organizationEnabled: false, -}); -/** - * RevertScpChanges construct test - */ -describe('RevertScpChanges', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log.test.ts deleted file mode 100644 index 578c0fd..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SecurityHubEventsLog } from '../../lib/aws-events/security-hub-events-log'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SecurityHubEventsLog): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SecurityHubEventsLog(stack, 'SecurityHubEventsLog', { - snsTopicArn: 'arn:aws:us-east-1:999999999999:aws-accelerator-test', - snsKmsKey: new cdk.aws_kms.Key(stack, 'CustomSnsKey', {}), - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - notificationLevel: 'INFORMATIONAL', - acceleratorPrefix: 'AWSAccelerator', - cloudWatchLogRetentionInDays: 365, -}); - -/** - * SecurityHubEventsLog construct test - */ -describe('SecurityHubEventsLog', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log/integ.security-hub-events-log.ts b/source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log/integ.security-hub-events-log.ts deleted file mode 100644 index d3f39ea..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log/integ.security-hub-events-log.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/constructs/test/aws-events/security-hub-events-log --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct, IConstruct } from 'constructs'; -import { ExpectedResult, IntegTest, Match } from '@aws-cdk/integ-tests-alpha'; -import { AcceleratorAspects } from '../../../../accelerator/lib/accelerator-aspects'; -import { SecurityHubEventsLog } from '@aws-accelerator/constructs'; -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource) { - node.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); - } - } -} - -// CDK App for Integration Tests -const app = new cdk.App(); - -export interface SecurityHubEventsLogDemoStackProps extends cdk.StackProps { - readonly existingLogGroup: boolean; -} - -export class SecurityHubEventsLogDemoStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: SecurityHubEventsLogDemoStackProps) { - super(scope, id, props); - const testingPrefix = 'integTest'; - const lambdaKey = this.createLambdaKey(); - const snsKey = this.createSnsKey(); - const snsTopic = this.createSnsTopic(snsKey); - - if (props.existingLogGroup) { - const existingLogGroupName = `/${testingPrefix}-SecurityHub`; - new cdk.aws_logs.LogGroup(this, 'ExistingLogGroup', { - logGroupName: existingLogGroupName, - }); - } - - new SecurityHubEventsLog(this, 'SecurityHubEventsLog', { - acceleratorPrefix: testingPrefix, - snsTopicArn: snsTopic.topicArn, - snsKmsKey: snsKey, - notificationLevel: 'CRITICAL', - lambdaKey: lambdaKey, - cloudWatchLogRetentionInDays: 365, - logLevel: 'MEDIUM', - }); - - cdk.Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - new AcceleratorAspects(app, 'aws', false); - } - - /** - * Create KMS key for Lambda - * @returns Key - */ - private createLambdaKey(): cdk.aws_kms.Key { - const lambdaKey = new cdk.aws_kms.Key(this, 'LambdaKey', { removalPolicy: cdk.RemovalPolicy.DESTROY }); - // Allow Lambda to use the encryption key - lambdaKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow Lambda to use the encryption key`, - principals: [new cdk.aws_iam.ServicePrincipal(`lambda.${this.urlSuffix}`)], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - }), - ); - - return lambdaKey; - } - - /** - * Create KMS key for SNS - * @returns Key - */ - private createSnsKey(): cdk.aws_kms.Key { - const snsKey = new cdk.aws_kms.Key(this, 'SnsKey', { removalPolicy: cdk.RemovalPolicy.DESTROY }); - // Allow SNS to use the encryption key - snsKey.addToResourcePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: `Allow SNS to use the encryption key`, - principals: [new cdk.aws_iam.ServicePrincipal(`sns.${this.urlSuffix}`)], - actions: ['kms:Encrypt*', 'kms:Decrypt*', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:Describe*'], - resources: ['*'], - }), - ); - - return snsKey; - } - - private createSnsTopic(snsKey: cdk.aws_kms.Key): cdk.aws_sns.Topic { - const topic = new cdk.aws_sns.Topic(this, `SnsTopic`, { - displayName: `integTest-topic-${cdk.Stack.of(this).region}}`, - topicName: `integTest-topic-${cdk.Stack.of(this).region}`, - masterKey: snsKey, - }); - - topic.grantPublish({ - grantPrincipal: new cdk.aws_iam.ServicePrincipal('events.amazonaws.com'), - }); - - return topic; - } -} - -// Stack under test -const stackUnderTest = new SecurityHubEventsLogDemoStack(app, 'SecurityHubEventsLogTestStack', { - description: 'This stack includes the application resources for integration testing.', - existingLogGroup: false, -}); - -// Initialize Integ Test construct -const integ = new IntegTest(app, 'NewSecurityHubEventsLogTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -const testLogGroupName = '/integTest-SecurityHub'; - -integ.assertions - .awsApiCall('CloudWatchLogs', 'describeLogGroups', { logGroupNamePrefix: testLogGroupName }) - .expect( - ExpectedResult.objectLike({ - logGroups: [{ logGroupName: testLogGroupName }], - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(2) }); - -integ.assertions - .awsApiCall('CloudWatchLogs', 'describeResourcePolicies', {}) - .expect( - ExpectedResult.objectLike({ - resourcePolicies: Match.arrayWith([ - Match.objectLike({ - policyName: 'TrustEventsToStoreLogEvent', - }), - ]), - }), - ) - .waitForAssertions({ totalTimeout: cdk.Duration.minutes(2) }); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/__snapshots__/cloudwatch-to-s3-firehose.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-firehose/__snapshots__/cloudwatch-to-s3-firehose.test.ts.snap deleted file mode 100644 index 84ba803..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/__snapshots__/cloudwatch-to-s3-firehose.test.ts.snap +++ /dev/null @@ -1,5604 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CloudWatchToS3Firehose Construct(CloudWatchToS3Firehose): Snapshot Test 1`] = ` -{ - "Conditions": { - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions": { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-north-1", - ], - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-northwest-1", - ], - }, - ], - }, - }, - "Resources": { - "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyBucketName076BAD56", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamBucketName9F2BF3F7", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseKinesisStreamServiceRole9577C2D5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyBucketNameFF6F8D94", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF": { - "DependsOn": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3", - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyBucketNameF1E215CD", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyBucketNameFF6F8D94", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseServiceRole9EBB7E56": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.someregion.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKeyBucketName4DE2ADC8", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomLogsKeyBucketName076BAD56", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseStream3C376F34": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKeyBucketName4DE2ADC8", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseServiceRole9EBB7E56", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseKinesisStreamServiceRole9577C2D5", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseCloudWatchLogStream4369D67C": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyExistingIamA40FEFF2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamExistingIamFE5F8092", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaF852AD4B": { - "DependsOn": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D", - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyExistingIamC416D475", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyExistingIam19145315", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseStream48CF661F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudWatchLogStream4369D67C", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKeyExistingIam1584FEB7", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaF852AD4B", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorFirehoseRole", - ], - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - "RoleARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorFirehoseRole", - ], - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyA36F38C8", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamE8E9158E", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseFirehoseKinesisStreamServiceRole80E25BD8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKey1382C574", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603": { - "DependsOn": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579", - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKey1382C574", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseFirehoseServiceRole4A1CF56B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.someregion.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomLogsKeyA36F38C8", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehoseStream0CBF097E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseServiceRole4A1CF56B", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseKinesisStreamServiceRole80E25BD8", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CustomBucketF81FA714": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyBucketName4DE2ADC8": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyExistingIam1584FEB7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKey1382C574": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyBucketNameFF6F8D94": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyExistingIam19145315": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyBucketNameF1E215CD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyExistingIamC416D475": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyA36F38C8": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyBucketName076BAD56": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyExistingIamA40FEFF2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomStreamBucketName9F2BF3F7": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamE8E9158E": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamExistingIamFE5F8092": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - }, -} -`; - -exports[`CloudWatchToS3FirehoseBucketName Construct(CloudWatchToS3Firehose): Snapshot Test 1`] = ` -{ - "Conditions": { - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions": { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-north-1", - ], - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-northwest-1", - ], - }, - ], - }, - }, - "Resources": { - "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyBucketName076BAD56", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamBucketName9F2BF3F7", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseKinesisStreamServiceRole9577C2D5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyBucketNameFF6F8D94", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF": { - "DependsOn": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3", - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyBucketNameF1E215CD", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyBucketNameFF6F8D94", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseServiceRole9EBB7E56": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.someregion.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKeyBucketName4DE2ADC8", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomLogsKeyBucketName076BAD56", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseStream3C376F34": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKeyBucketName4DE2ADC8", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseServiceRole9EBB7E56", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseKinesisStreamServiceRole9577C2D5", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseCloudWatchLogStream4369D67C": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyExistingIamA40FEFF2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamExistingIamFE5F8092", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaF852AD4B": { - "DependsOn": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D", - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyExistingIamC416D475", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyExistingIam19145315", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseStream48CF661F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudWatchLogStream4369D67C", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKeyExistingIam1584FEB7", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaF852AD4B", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorFirehoseRole", - ], - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - "RoleARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorFirehoseRole", - ], - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyA36F38C8", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamE8E9158E", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseFirehoseKinesisStreamServiceRole80E25BD8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKey1382C574", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603": { - "DependsOn": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579", - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKey1382C574", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseFirehoseServiceRole4A1CF56B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.someregion.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomLogsKeyA36F38C8", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehoseStream0CBF097E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseServiceRole4A1CF56B", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseKinesisStreamServiceRole80E25BD8", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CustomBucketF81FA714": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyBucketName4DE2ADC8": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyExistingIam1584FEB7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKey1382C574": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyBucketNameFF6F8D94": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyExistingIam19145315": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyBucketNameF1E215CD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyExistingIamC416D475": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyA36F38C8": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyBucketName076BAD56": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyExistingIamA40FEFF2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomStreamBucketName9F2BF3F7": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamE8E9158E": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamExistingIamFE5F8092": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - }, -} -`; - -exports[`CloudWatchToS3FirehoseExistingIam Construct(CloudWatchToS3Firehose): Snapshot Test 1`] = ` -{ - "Conditions": { - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions": { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-north-1", - ], - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region", - }, - "cn-northwest-1", - ], - }, - ], - }, - }, - "Resources": { - "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyBucketName076BAD56", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamBucketName9F2BF3F7", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseKinesisStreamServiceRole9577C2D5": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyBucketNameFF6F8D94", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF": { - "DependsOn": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3", - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyBucketNameF1E215CD", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyBucketNameFF6F8D94", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyA2E491F3", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambdaServiceRoleFA79B6BB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseServiceRole9EBB7E56": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.someregion.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKeyBucketName4DE2ADC8", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomLogsKeyBucketName076BAD56", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseBucketNameFirehoseStream3C376F34": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudwatchLogs2559CE32", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseBucketNameFirehoseCloudWatchLogStreamC585C3CD", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKeyBucketName4DE2ADC8", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehosePrefixProcessingLambda2F1022EF", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseServiceRole9EBB7E56", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamBucketName9F2BF3F7", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseBucketNameFirehoseKinesisStreamServiceRole9577C2D5", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseCloudWatchLogStream4369D67C": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyExistingIamA40FEFF2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamExistingIamFE5F8092", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaF852AD4B": { - "DependsOn": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D", - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKeyExistingIamC416D475", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKeyExistingIam19145315", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleDefaultPolicyC384F73D", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaServiceRoleCC09E565", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseExistingIamFirehoseStream48CF661F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::somebucket", - ], - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudwatchLogsE7A42868", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseExistingIamFirehoseCloudWatchLogStream4369D67C", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKeyExistingIam1584FEB7", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseExistingIamFirehosePrefixProcessingLambdaF852AD4B", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorFirehoseRole", - ], - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamExistingIamFE5F8092", - "Arn", - ], - }, - "RoleARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorFirehoseRole", - ], - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - }, - "LogStreamName": "DestinationDeliveryError", - }, - "Type": "AWS::Logs::LogStream", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomLogsKeyA36F38C8", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/accelerator/logs/firehose/", - { - "Ref": "CustomStreamE8E9158E", - }, - ], - ], - }, - "RetentionInDays": 7, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CloudWatchToS3FirehoseFirehoseKinesisStreamServiceRole80E25BD8": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to get records from Kinesis.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetShardIterator", - "kinesis:GetRecords", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKey1382C574", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessKinesis", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603": { - "DependsOn": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579", - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Environment": { - "Variables": { - "DynamicS3LogPartitioningMapping": "dynamic-partitioning/log-filters.json", - "KinesisStreamArn": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - }, - "FunctionName": "test", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "MemorySize": 2048, - "Role": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Managed policy for Lambda basic execution attached.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:PutRecords", - "kinesis:PutRecord", - "kinesis:ListShards", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ReEncryptTo", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyPair", - "kms:ReEncryptFrom", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKinesisKey1382C574", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRoleDefaultPolicy65018579", - "Roles": [ - { - "Ref": "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaServiceRole405E5167", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CloudWatchToS3FirehoseFirehoseServiceRole4A1CF56B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "firehose.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "Role used by Kinesis Firehose to place Kinesis records in the central bucket.", - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": "s3.someregion.amazonaws.com", - }, - "StringLike": { - "kms:EncryptionContext:aws:s3:arn": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:AbortMultipartUpload", - "s3:GetBucketLocation", - "s3:GetObject", - "s3:ListBucket", - "s3:ListBucketMultipartUploads", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "lambda:InvokeFunction", - "lambda:GetFunctionConfiguration", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - ":*", - ], - ], - }, - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - ], - }, - { - "Action": "logs:PutLogEvents", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - "Arn", - ], - }, - ":logstream:", - { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D", - }, - ], - ], - }, - }, - { - "Action": [ - "kms:Encrypt*", - "kms:Describe*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomLogsKeyA36F38C8", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "AccessS3KmsLambda", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CloudWatchToS3FirehoseFirehoseStream0CBF097E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-KDF1", - "reason": "Customer managed key is used to encrypt firehose delivery stream.", - }, - ], - }, - }, - "Properties": { - "DeliveryStreamType": "KinesisStreamAsSource", - "ExtendedS3DestinationConfiguration": { - "BucketARN": { - "Fn::GetAtt": [ - "CustomBucketF81FA714", - "Arn", - ], - }, - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64, - }, - "CloudWatchLoggingOptions": { - "Enabled": true, - "LogGroupName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudwatchLogs25E5D35A", - }, - "LogStreamName": { - "Ref": "CloudWatchToS3FirehoseFirehoseCloudWatchLogStream3886AD4D", - }, - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false, - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - }, - "EncryptionConfiguration": { - "KMSEncryptionConfig": { - "AWSKMSKeyARN": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - }, - "ErrorOutputPrefix": "CloudWatchLogs/processing-failed", - "Prefix": "!{partitionKeyFromLambda:dynamicPrefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "LambdaArn", - "ParameterValue": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehosePrefixProcessingLambdaE5E42603", - "Arn", - ], - }, - }, - { - "ParameterName": "NumberOfRetries", - "ParameterValue": "3", - }, - { - "ParameterName": "BufferSizeInMBs", - "ParameterValue": "0.2", - }, - { - "ParameterName": "BufferIntervalInSeconds", - "ParameterValue": "60", - }, - ], - "Type": "Lambda", - }, - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseServiceRole4A1CF56B", - "Arn", - ], - }, - }, - "KinesisStreamSourceConfiguration": { - "KinesisStreamARN": { - "Fn::GetAtt": [ - "CustomStreamE8E9158E", - "Arn", - ], - }, - "RoleARN": { - "Fn::GetAtt": [ - "CloudWatchToS3FirehoseFirehoseKinesisStreamServiceRole80E25BD8", - "Arn", - ], - }, - }, - }, - "Type": "AWS::KinesisFirehose::DeliveryStream", - }, - "CustomBucketError0C6799CA": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CustomBucketF81FA714": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyBucketErrorNameE8899342": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyBucketName4DE2ADC8": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyExistingIam1584FEB7": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKey1382C574": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyBucketErrorName644A5508": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyBucketNameFF6F8D94": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKinesisKeyExistingIam19145315": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyBucketErrorName34834C98": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyBucketNameF1E215CD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKeyExistingIamC416D475": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyA36F38C8": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyBucketErrorName6DEB7FC4": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyBucketName076BAD56": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLogsKeyExistingIamA40FEFF2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomStreamBucketErrorNameEE021D38": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamBucketName9F2BF3F7": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamE8E9158E": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - "CustomStreamExistingIamFE5F8092": { - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "Fn::If": [ - "AwsCdkKinesisEncryptedStreamsUnsupportedRegions", - { - "Ref": "AWS::NoValue", - }, - { - "EncryptionType": "KMS", - "KeyId": "alias/aws/kinesis", - }, - ], - }, - }, - "Type": "AWS::Kinesis::Stream", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/cloudwatch-to-s3-firehose.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-firehose/cloudwatch-to-s3-firehose.test.ts deleted file mode 100644 index b86dd93..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/cloudwatch-to-s3-firehose.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CloudWatchToS3Firehose } from '../../lib/aws-firehose/cloudwatch-to-s3-firehose'; -import { snapShotTest } from '../snapshot-test'; -import { describe, expect, test } from '@jest/globals'; - -const testNamePrefix = 'Construct(CloudWatchToS3Firehose): '; -const stack = new cdk.Stack(); - -/** - * CloudWatchDestination construct test - */ -describe('CloudWatchToS3Firehose', () => { - //Initialize stack for snapshot test and resource configuration test - - new CloudWatchToS3Firehose(stack, 'CloudWatchToS3Firehose', { - firehoseKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - kinesisStream: new cdk.aws_kinesis.Stream(stack, 'CustomStream', {}), - kinesisKmsKey: new cdk.aws_kms.Key(stack, 'CustomKinesisKey', {}), - bucket: new cdk.aws_s3.Bucket(stack, 'CustomBucket', {}), - dynamicPartitioningValue: 'dynamic-partitioning/log-filters.json', - homeRegion: 'someregion', - configDir: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, - firehoseRecordsProcessorFunctionName: 'test', - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomLogsKey', {}), - logsRetentionInDaysValue: '7', - }); - snapShotTest(testNamePrefix, stack); -}); - -describe('CloudWatchToS3FirehoseBucketName', () => { - new CloudWatchToS3Firehose(stack, 'CloudWatchToS3FirehoseBucketName', { - firehoseKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyBucketName', {}), - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKeyBucketName', {}), - kinesisStream: new cdk.aws_kinesis.Stream(stack, 'CustomStreamBucketName', {}), - kinesisKmsKey: new cdk.aws_kms.Key(stack, 'CustomKinesisKeyBucketName', {}), - bucketName: 'somebucket', - dynamicPartitioningValue: 'dynamic-partitioning/log-filters.json', - homeRegion: 'someregion', - configDir: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, - firehoseRecordsProcessorFunctionName: 'test', - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomLogsKeyBucketName', {}), - logsRetentionInDaysValue: '7', - }); - snapShotTest(testNamePrefix, stack); -}); - -test('should throw an exception for bucket name and bucket are present', () => { - function s3BucketError() { - new CloudWatchToS3Firehose(stack, 'CloudWatchToS3FirehoseBucketError', { - firehoseKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyBucketErrorName', {}), - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKeyBucketErrorName', {}), - kinesisStream: new cdk.aws_kinesis.Stream(stack, 'CustomStreamBucketErrorName', {}), - kinesisKmsKey: new cdk.aws_kms.Key(stack, 'CustomKinesisKeyBucketErrorName', {}), - bucketName: 'somebucket', - bucket: new cdk.aws_s3.Bucket(stack, 'CustomBucketError', {}), - dynamicPartitioningValue: 'dynamic-partitioning/log-filters.json', - homeRegion: 'someregion', - configDir: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: false, - firehoseRecordsProcessorFunctionName: 'test', - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomLogsKeyBucketErrorName', {}), - logsRetentionInDaysValue: '7', - }); - } - - const errMsg = - 'Either source bucket or source bucketName property must be defined. Only one property must be defined.'; - expect(s3BucketError).toThrow(new Error(errMsg)); -}); - -describe('CloudWatchToS3FirehoseExistingIam', () => { - new CloudWatchToS3Firehose(stack, 'CloudWatchToS3FirehoseExistingIam', { - firehoseKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyExistingIam', {}), - lambdaKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKeyExistingIam', {}), - kinesisStream: new cdk.aws_kinesis.Stream(stack, 'CustomStreamExistingIam', {}), - kinesisKmsKey: new cdk.aws_kms.Key(stack, 'CustomKinesisKeyExistingIam', {}), - bucketName: 'somebucket', - dynamicPartitioningValue: 'dynamic-partitioning/log-filters.json', - homeRegion: 'someregion', - configDir: `${__dirname}/../../../accelerator/test/configs/snapshot-only`, - acceleratorPrefix: 'AWSAccelerator', - useExistingRoles: true, - firehoseRecordsProcessorFunctionName: 'test', - logsKmsKey: new cdk.aws_kms.Key(stack, 'CustomLogsKeyExistingIam', {}), - logsRetentionInDaysValue: '7', - }); - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition1.json b/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition1.json deleted file mode 100644 index d8c241a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition1.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "/AWSAccelerator-SecurityHub", "s3Prefix": "security-hub" } -] diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition2.json b/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition2.json deleted file mode 100644 index 04aa677..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition2.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "appA*region", "s3Prefix": "app-a-region" } -] diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition3.json b/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition3.json deleted file mode 100644 index 4ac754a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition3.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "sandbox*", "s3Prefix": "sandbox" } -] diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition4.json b/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition4.json deleted file mode 100644 index fa1f19a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/dynamicPartition4.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - { "logGroupPattern": "AWSAccelerator*VpcFlowLog*region", "s3Prefix": "accelerator-vpc-flow-logs-region" } -] diff --git a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/index.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/index.test.ts deleted file mode 100644 index 8dedaae..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-firehose/firehose-record-processing/index.test.ts +++ /dev/null @@ -1,445 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as zlib from 'zlib'; -import { handler } from '../../../lib/aws-firehose/firehose-record-processing/index'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { expect, it, beforeEach, afterEach, describe } from '@jest/globals'; -import { KinesisClient, PutRecordsCommand } from '@aws-sdk/client-kinesis'; -import { - FirehoseTransformationEvent, - FirehoseTransformationEventRecord, - CloudWatchLogsToFirehoseRecord, -} from '@aws-accelerator/utils/lib/common-types'; -export interface ProcessEnv { - [key: string]: string | undefined; -} -let envCache: ProcessEnv; -let kinesisMock: AwsClientStub; - -beforeEach(() => { - kinesisMock = mockClient(KinesisClient); - envCache = process.env; -}); - -afterEach(() => { - kinesisMock.restore(); - process.env = envCache; -}); - -it('should return a valid response', async () => { - const input: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'testLogGroup', - logStream: 'testLogStream', - subscriptionFilters: ['testLogGroup'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640000, - message: 'test0', - }, - ], - }; - const response = await handler(makeTestInput(input, 1)); - expect(response.records[0].result).toEqual('Ok'); -}); - -it('does not have logGroup in input', async () => { - const input = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logStream: 'testLogStream', - subscriptionFilters: ['testLogGroup'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640000, - message: 'test0', - }, - ], - }; - const response = await handler(makeTestInput(input, 1)); - expect(response.records[0].result).toEqual('ProcessingFailed'); -}); - -describe('test max payload variable', () => { - it('Max payload reached ', async () => { - const input: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'testLogGroup', - logStream: 'testLogStream', - subscriptionFilters: ['testLogGroup'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - { - id: '37255929110829766979968869523131155620888295107628171265', - timestamp: 1670613640000, - message: 'test1', - }, - ], - }; - - process.env['MaxOutputPayload'] = '170'; - const response = await handler(makeTestInput(input, 1)); - expect(response.records[0].result).toEqual('ProcessingFailed'); - }); - it('Single large payload ', async () => { - const input: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'testLogGroup', - logStream: 'testLogStream', - subscriptionFilters: ['testLogGroup'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - - process.env['MaxOutputPayload'] = '170'; - await expect(handler(makeTestInput(input, 1))).rejects.toThrow('SINGLE_LARGE_FIREHOSE_PAYLOAD'); - }); - - it('Put record in kinesis', async () => { - const input: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'testLogGroup', - logStream: 'testLogStream', - subscriptionFilters: ['testLogGroup'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - { - id: '37255929110829766979968869523131155620888295107628171265', - timestamp: 1670613640000, - message: 'test1', - }, - { - id: '37255929110829766979968869523131155620888295107628171265', - timestamp: 1670613640000, - message: 'test2', - }, - { - id: '37255929110829766979968869523131155620888295107628171265', - timestamp: 1670613640000, - message: 'test3', - }, - ], - }; - process.env['MaxOutputPayload'] = '200'; - kinesisMock.on(PutRecordsCommand).resolves({}); - const response = await handler(makeTestInput(input, 10)); - expect(response.records[0].result).toEqual('ProcessingFailed'); - }); -}); - -it('Bad record', async () => { - const response = await handler({ - invocationId: 'invocationId', - deliveryStreamArn: 'deliveryStreamArn', - region: 'region', - records: [{ recordId: 'recordId', data: 'data', approximateArrivalTimestamp: 1670613640000 }], - }); - expect(response.records[0].result).toEqual('ProcessingFailed'); -}); - -it('Dynamic partition perfect match', async () => { - process.env['DynamicS3LogPartitioningMapping'] = - '../../../test/aws-firehose/firehose-record-processing/dynamicPartition1.json'; - process.env['MaxOutputPayload'] = '50000000'; - const input: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: '/AWSAccelerator-SecurityHub', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const response = await handler(makeTestInput(input, 1)); - expect(response.records[0].metadata?.partitionKeys['dynamicPrefix']).toContain('CloudWatchLogs/security-hub'); -}); - -it('Dynamic partition wildcard match pattern appA*region', async () => { - process.env['DynamicS3LogPartitioningMapping'] = - '../../../test/aws-firehose/firehose-record-processing/dynamicPartition2.json'; - process.env['MaxOutputPayload'] = '50000000'; - const input1: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'appAregion', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input2: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'appA-prefix-some-other-Prefix-000000000000-region', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input3: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'appA-some-text-region', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input4: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'test', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const response1 = await handler(makeTestInput(input1, 1)); - const response2 = await handler(makeTestInput(input2, 1)); - const response3 = await handler(makeTestInput(input3, 1)); - const response4 = await handler(makeTestInput(input4, 1)); - const response = { - invocationId: 'invocationId', - deliveryStreamArn: 'deliveryStreamArn', - region: 'region', - records: [...response1.records, ...response2.records, ...response3.records, ...response4.records], - }; - - expect(response.records[0].metadata?.partitionKeys['dynamicPrefix']).toContain('app-a-region'); - expect(response.records[1].metadata?.partitionKeys['dynamicPrefix']).toContain('app-a-region'); - expect(response.records[2].metadata?.partitionKeys['dynamicPrefix']).toContain('app-a-region'); - expect(response.records[3].metadata?.partitionKeys['dynamicPrefix']).not.toContain('app-a-region'); - expect(response.records[3].metadata?.partitionKeys['dynamicPrefix']).toContain('CloudWatchLogs'); -}); - -it('Dynamic partition wildcard match pattern sandbox*', async () => { - process.env['DynamicS3LogPartitioningMapping'] = - '../../../test/aws-firehose/firehose-record-processing/dynamicPartition3.json'; - process.env['MaxOutputPayload'] = '50000000'; - const input1: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'sandbox-region', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input2: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'sandbox-prefix-some-other-Prefix-000000000000-region', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input3: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'sandbox-some-text-region', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input4: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'test', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const response1 = await handler(makeTestInput(input1, 1)); - const response2 = await handler(makeTestInput(input2, 1)); - const response3 = await handler(makeTestInput(input3, 1)); - const response4 = await handler(makeTestInput(input4, 1)); - const response = { - invocationId: 'invocationId', - deliveryStreamArn: 'deliveryStreamArn', - region: 'region', - records: [...response1.records, ...response2.records, ...response3.records, ...response4.records], - }; - - expect(response.records[0].metadata?.partitionKeys['dynamicPrefix']).toContain('sandbox'); - expect(response.records[1].metadata?.partitionKeys['dynamicPrefix']).toContain('sandbox'); - expect(response.records[2].metadata?.partitionKeys['dynamicPrefix']).toContain('sandbox'); - expect(response.records[3].metadata?.partitionKeys['dynamicPrefix']).not.toContain('sandbox'); - expect(response.records[3].metadata?.partitionKeys['dynamicPrefix']).toContain('CloudWatchLogs'); -}); - -it('Dynamic partition wildcard match pattern AWSAccelerator*VpcFlowLog*region', async () => { - process.env['DynamicS3LogPartitioningMapping'] = - '../../../test/aws-firehose/firehose-record-processing/dynamicPartition4.json'; - process.env['MaxOutputPayload'] = '50000000'; - const input1: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'AWSAccelerator-textText-VpcFlowLog-000000000000-region', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input2: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'AWSAcceleratorVpcFlowLogregion', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input3: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'AWSAcceleratorVpcFlowLogregion', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const input4: CloudWatchLogsToFirehoseRecord = { - messageType: 'DATA_MESSAGE', - owner: '000000000000', - logGroup: 'test', - logStream: 'testLogStream', - subscriptionFilters: ['AWSAccelerator-SecurityHub'], - logEvents: [ - { - id: '37255929110829766979968869523131155620888295107628171264', - timestamp: 1670613640001, - message: 'test0', - }, - ], - }; - const response1 = await handler(makeTestInput(input1, 1)); - const response2 = await handler(makeTestInput(input2, 1)); - const response3 = await handler(makeTestInput(input3, 1)); - const response4 = await handler(makeTestInput(input4, 1)); - const response = { - invocationId: 'invocationId', - deliveryStreamArn: 'deliveryStreamArn', - region: 'region', - records: [...response1.records, ...response2.records, ...response3.records, ...response4.records], - }; - - expect(response.records[0].metadata?.partitionKeys['dynamicPrefix']).toContain('accelerator-vpc-flow-logs-region'); - expect(response.records[1].metadata?.partitionKeys['dynamicPrefix']).toContain('accelerator-vpc-flow-logs-region'); - expect(response.records[2].metadata?.partitionKeys['dynamicPrefix']).toContain('accelerator-vpc-flow-logs-region'); - expect(response.records[3].metadata?.partitionKeys['dynamicPrefix']).not.toContain( - 'accelerator-vpc-flow-logs-region', - ); - expect(response.records[3].metadata?.partitionKeys['dynamicPrefix']).toContain('CloudWatchLogs'); -}); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function makeTestInput(inputData: CloudWatchLogsToFirehoseRecord | any, numberOfRecords: number) { - const jsonStringPayload = JSON.stringify(inputData); - const zippedPayload = zlib.gzipSync(jsonStringPayload); - const base64EncodedPayload = Buffer.from(zippedPayload).toString('base64'); - const recordOutput: FirehoseTransformationEventRecord[] = Array(numberOfRecords ?? 1).fill({ - recordId: 'recordId', - approximateArrivalTimestamp: 1670613640000, - data: base64EncodedPayload, - }); - const output: FirehoseTransformationEvent = { - invocationId: 'invocationId', - deliveryStreamArn: 'deliveryStreamArn', - region: 'region', - records: recordOutput, - }; - return output; -} diff --git a/source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-notification-channel.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-notification-channel.test.ts.snap deleted file mode 100644 index a187404..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-notification-channel.test.ts.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FMSNotificationChannel Construct(FMSNotificationChannel): Snapshot Test 1`] = ` -{ - "Resources": { - "FMSNotificationChannel09689B03": { - "Properties": { - "SnsRoleName": { - "Fn::Join": [ - "", - [ - ""arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/AWSAccelerator-SNSRole"", - ], - ], - }, - "SnsTopicArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":sns:", - { - "Ref": "AWS::Region", - }, - ":111111111111:aws-accelerator-Security", - ], - ], - }, - }, - "Type": "AWS::FMS::NotificationChannel", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-organization-admin-account.test.ts.snap deleted file mode 100644 index 339e024..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-fms/__snapshots__/fms-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,323 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FMSOrganizationAdminAccount Construct(FMSOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E": { - "DependsOn": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "fms:AssociateAdminAccount", - "fms:DisassociateAdminAccount", - "fms:GetAdminAccount", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:DeregisterDelegatedAdministrator", - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListChildren", - "organizations:ListDelegatedAdministrators", - "organizations:ListDelegatedServicesForAccount", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListParents", - "organizations:ListRoots", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/testRole", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy83E36C98", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambdaServiceRole28F3C566", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "FMSOrganizationAdminAccountfmsAdmin111111111111F4637515": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEvent17783F2B", - "Arn", - ], - }, - "adminAccountId": "111111111111", - "assumeRoleName": "testRole", - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "FMSOrganizationAdminAccountframeworkonEvent17783F2B": { - "DependsOn": [ - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/FMSOrganizationAdminAccount/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FMSOrganizationAdminAccountFMSOrganizationAdminAccountProviderLambda2D69B84E", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "FMSOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicyD56A7BD5", - "Roles": [ - { - "Ref": "FMSOrganizationAdminAccountframeworkonEventServiceRole6775A101", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "fmsTestKey6EA63218": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-fms/fms-notification-channel.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-fms/fms-notification-channel.test.ts deleted file mode 100644 index 99aad09..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-fms/fms-notification-channel.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { FMSNotificationChannel } from '../../lib/aws-fms/fms-notification-channel'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(FMSNotificationChannel): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const snsTopicArn = `arn:${stack.partition}:sns:${stack.region}:111111111111:aws-accelerator-Security`; -const snsRoleArn = `"arn:${stack.partition}:iam::111111111111:role/AWSAccelerator-SNSRole"`; -new FMSNotificationChannel(stack, 'FMSNotificationChannel', { - snsRoleArn, - snsTopicArn, -}); -/** - * FMSNotificationChannel construct test - */ -describe('FMSNotificationChannel', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-fms/fms-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-fms/fms-organization-admin-account.test.ts deleted file mode 100644 index ee8fb6a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-fms/fms-organization-admin-account.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { FMSOrganizationAdminAccount } from '../../lib/aws-fms/fms-organization-admin-account'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(FMSOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const adminAccountId = '111111111111'; -const assumeRole = 'testRole'; -new FMSOrganizationAdminAccount(stack, 'FMSOrganizationAdminAccount', { - adminAccountId, - assumeRole, - logRetentionInDays: 10, - kmsKey: new cdk.aws_kms.Key(stack, 'fmsTestKey'), -}); -/** - * FMSOrganizationAdminAccount construct test - */ -describe('FMSOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-detector-config.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-detector-config.test.ts.snap deleted file mode 100644 index ca87896..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-detector-config.test.ts.snap +++ /dev/null @@ -1,154 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GuardDutyDetectorConfig Construct(GuardDutyDetectorConfig): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9": { - "DependsOn": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderLogGroup0E4B1900": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyUpdateDetectorCustomResourceProviderRole3014073E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "guardduty:ListDetectors", - "guardduty:ListMembers", - "guardduty:UpdateDetector", - "guardduty:UpdateMemberDetectors", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyUpdateDetectorTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "GuardDutyDetectorConfigDD64B103": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderLogGroup0E4B1900", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyUpdateDetectorCustomResourceProviderHandler78DF0FF9", - "Arn", - ], - }, - "enableEksProtection": true, - "enableS3Protection": true, - "exportFrequency": "FIFTEEN_MINUTES", - }, - "Type": "Custom::GuardDutyUpdateDetector", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-members.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-members.test.ts.snap deleted file mode 100644 index ddce9da..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-members.test.ts.snap +++ /dev/null @@ -1,188 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GuardDutyMembers Construct(GuardDutyMembers): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673": { - "DependsOn": [ - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderLogGroupB5134860": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyCreateMembersCustomResourceProviderRole2D82020E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreateMembersTaskOrganizationAction", - }, - { - "Action": [ - "guardDuty:ListDetectors", - "guardDuty:ListOrganizationAdminAccounts", - "guardDuty:UpdateOrganizationConfiguration", - "guardduty:CreateMembers", - "guardduty:DeleteMembers", - "guardduty:DisassociateMembers", - "guardduty:ListDetectors", - "guardduty:ListMembers", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreateMembersTaskGuardDutyActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceLinkedRoleSecurityHub", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "GuardDutyMembersD34CA003": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyCreateMembersCustomResourceProviderLogGroupB5134860", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyCreateMembersCustomResourceProviderHandler0A16C673", - "Arn", - ], - }, - "autoEnableOrgMembers": true, - "enableEksProtection": true, - "enableS3Protection": true, - "guardDutyMemberAccountIds": [], - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::GuardDutyCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-organization-admin-account.test.ts.snap deleted file mode 100644 index 9827e0c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,203 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GuardDutyOrganizationAdminAccount Construct(GuardDutyOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026": { - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderRole30371E09": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "guardduty.amazonaws.com", - ], - "organizations:ListAccounts": [ - "guardduty.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "guardduty.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "guardduty.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "guardduty.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "guardduty.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "GuardDuty:EnableOrganizationAdminAccount", - "GuardDuty:ListOrganizationAdminAccounts", - "guardduty:DisableOrganizationAdminAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyEnableOrganizationAdminAccountTaskGuardDutyActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "GuardDutyOrganizationAdminAccount457DB4F1": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderLogGroup9562A5A9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyEnableOrganizationAdminAccountCustomResourceProviderHandler1EC01026", - "Arn", - ], - }, - "adminAccountId": { - "Ref": "AWS::AccountId", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::GuardDutyEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-publishing-destination.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-publishing-destination.test.ts.snap deleted file mode 100644 index 9ea3449..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/__snapshots__/guardduty-publishing-destination.test.ts.snap +++ /dev/null @@ -1,266 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GuardDutyPublishingDestination Construct(GuardDutyPublishingDestination): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8": { - "DependsOn": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderLogGroup118A06DB": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "LogKeyE83D0C1B", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderRoleD01DD26B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "guardDuty:CreateDetector", - "guardDuty:CreatePublishingDestination", - "guardDuty:DeletePublishingDestination", - "guardDuty:UpdatePublishingDestination", - "guardDuty:ListDetectors", - "guardDuty:ListPublishingDestinations", - "guardduty:DescribePublishingDestination", - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "GuardDutyCreatePublishingDestinationCommandTaskGuardDutyActions", - }, - { - "Action": [ - "s3:ListBucket", - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-guardduty-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-guardduty-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/*", - ], - ], - }, - ], - "Sid": "GuardDutyCreateBucketPrefix", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "DestinationKeyEE1EE37E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "GuardDutyPublishingDestination52AE4412": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderLogGroup118A06DB", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGuardDutyCreatePublishingDestinationCommandCustomResourceProviderHandlerB3AE4CE8", - "Arn", - ], - }, - "destinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-guardduty-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "exportDestinationOverride": true, - "exportDestinationType": "S3", - "kmsKeyArn": { - "Fn::GetAtt": [ - "DestinationKeyEE1EE37E", - "Arn", - ], - }, - }, - "Type": "Custom::GuardDutyCreatePublishingDestinationCommand", - "UpdateReplacePolicy": "Delete", - }, - "LogKeyE83D0C1B": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-detector-config.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-detector-config.test.ts deleted file mode 100644 index 54949ed..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-detector-config.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GuardDutyDetectorConfig } from '../../lib/aws-guardduty/guardduty-detector-config'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GuardDutyDetectorConfig): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new GuardDutyDetectorConfig(stack, 'GuardDutyDetectorConfig', { - exportFrequency: 'FIFTEEN_MINUTES', - enableS3Protection: true, - enableEksProtection: true, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * GuardDutyDetectorConfig construct test - */ -describe('GuardDutyDetectorConfig', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-members.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-members.test.ts deleted file mode 100644 index 5b95565..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-members.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GuardDutyMembers } from '../../lib/aws-guardduty/guardduty-members'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GuardDutyMembers): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new GuardDutyMembers(stack, 'GuardDutyMembers', { - enableS3Protection: true, - enableEksProtection: true, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, - guardDutyMemberAccountIds: [], - autoEnableOrgMembers: true, -}); - -/** - * GuardDutyMembers construct test - */ -describe('GuardDutyMembers', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-organization-admin-account.test.ts deleted file mode 100644 index 71044f8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-organization-admin-account.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GuardDutyOrganizationAdminAccount } from '../../lib/aws-guardduty/guardduty-organization-admin-account'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GuardDutyOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new GuardDutyOrganizationAdminAccount(stack, 'GuardDutyOrganizationAdminAccount', { - adminAccountId: stack.account, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * GuardDutyOrganizationAdminAccount construct test - */ -describe('GuardDutyOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-publishing-destination.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-publishing-destination.test.ts deleted file mode 100644 index 32a2d46..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-guardduty/guardduty-publishing-destination.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GuardDutyPublishingDestination } from '../../lib/aws-guardduty/guardduty-publishing-destination'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GuardDutyPublishingDestination): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new GuardDutyPublishingDestination(stack, 'GuardDutyPublishingDestination', { - destinationArn: `arn:${stack.partition}:s3:::aws-accelerator-guardduty-${stack.account}-${stack.region}`, - exportDestinationType: 'S3', - exportDestinationOverride: true, - destinationKmsKey: new cdk.aws_kms.Key(stack, 'DestinationKey', {}), - logKmsKey: new cdk.aws_kms.Key(stack, 'LogKey', {}), - logRetentionInDays: 3653, -}); - -/** - * GuardDutyPublishingDestination construct test - */ -describe('GuardDutyPublishingDestination', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/password-policy.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/password-policy.test.ts.snap deleted file mode 100644 index a0ec266..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/password-policy.test.ts.snap +++ /dev/null @@ -1,156 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PasswordPolicy Construct(CentralLogsBucket): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4": { - "DependsOn": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderLogGroup69C2279A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderRoleC4ECAFE0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:UpdateAccountPasswordPolicy", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "PasswordPolicy4B0A08FE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderLogGroup69C2279A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIamUpdateAccountPasswordPolicyCustomResourceProviderHandler63EDC7F4", - "Arn", - ], - }, - "allowUsersToChangePassword": true, - "hardExpiry": true, - "maxPasswordAge": 90, - "minimumPasswordLength": 8, - "passwordReusePrevention": 5, - "requireLowercaseCharacters": true, - "requireNumbers": true, - "requireSymbols": true, - "requireUppercaseCharacters": true, - }, - "Type": "Custom::IamUpdateAccountPasswordPolicy", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/service-linked-role.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/service-linked-role.test.ts.snap deleted file mode 100644 index bb2ae8f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-iam/__snapshots__/service-linked-role.test.ts.snap +++ /dev/null @@ -1,318 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ServiceLinkedRole Construct(ServiceLinkedRole): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyCloudWatchFB91CD4E": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleFunction76F68A8A": { - "DependsOn": [ - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy412C5718", - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleFC9D35BB", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to create service linked role", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleFC9D35BB", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupE011C075": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKeyCloudWatchFB91CD4E", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ServiceLinkedRoleCreateServiceLinkedRoleFunction76F68A8A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy412C5718": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:CreateServiceLinkedRole", - "iam:GetRole", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleDefaultPolicy412C5718", - "Roles": [ - { - "Ref": "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleFC9D35BB", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionServiceRoleFC9D35BB": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent034259E5": { - "DependsOn": [ - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyAB7B0F16", - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole03414527", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ServiceLinkedRole/CreateServiceLinkedRoleProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ServiceLinkedRoleCreateServiceLinkedRoleFunction76F68A8A", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole03414527", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole03414527": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyAB7B0F16": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ServiceLinkedRoleCreateServiceLinkedRoleFunction76F68A8A", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ServiceLinkedRoleCreateServiceLinkedRoleFunction76F68A8A", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRoleDefaultPolicyAB7B0F16", - "Roles": [ - { - "Ref": "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEventServiceRole03414527", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ServiceLinkedRoleCreateServiceLinkedRoleResource8BE1DC5C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ServiceLinkedRoleCreateServiceLinkedRoleFunctionLogGroupE011C075", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ServiceLinkedRoleCreateServiceLinkedRoleProviderframeworkonEvent034259E5", - "Arn", - ], - }, - "description": "some description", - "roleName": "AWSServiceRoleForAwesomeService", - "serviceName": "awesomeService.amazonaws.com", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateServiceLinkedRole", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-iam/password-policy.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-iam/password-policy.test.ts deleted file mode 100644 index 66f608f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-iam/password-policy.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { PasswordPolicy } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(CentralLogsBucket): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new PasswordPolicy(stack, 'PasswordPolicy', { - allowUsersToChangePassword: true, - hardExpiry: true, - requireUppercaseCharacters: true, - requireLowercaseCharacters: true, - requireSymbols: true, - requireNumbers: true, - minimumPasswordLength: 8, - passwordReusePrevention: 5, - maxPasswordAge: 90, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * PasswordPolicy construct test - */ -describe('PasswordPolicy', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-iam/service-linked-role.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-iam/service-linked-role.test.ts deleted file mode 100644 index cc01333..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-iam/service-linked-role.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ServiceLinkedRole } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(ServiceLinkedRole): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ServiceLinkedRole(stack, 'ServiceLinkedRole', { - environmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - cloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyCloudWatch', {}), - cloudWatchLogRetentionInDays: 3653, - awsServiceName: 'awesomeService.amazonaws.com', - description: 'some description', - roleName: 'AWSServiceRoleForAwesomeService', -}); - -/** - * ServiceLinkedRole construct test - */ -describe('ServiceLinkedRole', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-assignment.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-assignment.test.ts.snap deleted file mode 100644 index b0c53f5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-assignment.test.ts.snap +++ /dev/null @@ -1,223 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IdentityCenterAssignments Construct(IdentityCenterGetInstanceId): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchKey9B181885": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850": { - "DependsOn": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderRoleAEF569D6", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderRoleAEF569D6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomIdentityCenterAssignmentsCustomResourceProviderLogGroup0BC21EA5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKey9B181885", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomIdentityCenterAssignmentsCustomResourceProviderRoleAEF569D6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:ListRoles", - "iam:ListPolicies", - "identitystore:ListGroups", - "identitystore:ListUsers", - "sso:CreateAccountAssignment", - "sso:DeleteAccountAssignment", - "sso:ListAccountAssignments", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:GetSAMLProvider", - "iam:UpdateSAMLProvider", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":saml-provider/AWSSSO_*_DO_NOT_DELETE", - ], - ], - }, - }, - { - "Action": [ - "iam:AttachRolePolicy", - "iam:CreateRole", - "iam:DeleteRole", - "iam:DeleteRolePolicy", - "iam:DetachRolePolicy", - "iam:GetRole", - "iam:ListAttachedRolePolicies", - "iam:ListRolePolicies", - "iam:PutRolePolicy", - "iam:UpdateRole", - "iam:UpdateRoleDescription", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/aws-reserved/sso.amazonaws.com/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterAssignments6A1D06ED": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderLogGroup0BC21EA5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomIdentityCenterAssignmentsCustomResourceProviderHandler67BA8850", - "Arn", - ], - }, - "accountIds": [ - "111111111111", - "222222222222", - ], - "identityStoreId": "d-906751796e", - "instanceArn": "arn:aws:sso:::instance/ssoins-123456789210", - "permissionSetArn": "arn:aws:sso:::permissionSet/ssoins-1111111111111111/ps-1111111111111111", - "principalId": "", - "principalType": "GROUP", - "principals": [ - { - "name": "lza-accelerator-user", - "type": "USER", - }, - { - "name": "lza-accelerator-group", - "type": "GROUP", - }, - ], - }, - "Type": "Custom::IdentityCenterAssignments", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-get-permission-set-role-arn.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-get-permission-set-role-arn.test.ts.snap deleted file mode 100644 index 254cb16..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-get-permission-set-role-arn.test.ts.snap +++ /dev/null @@ -1,247 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IdentityCenterGetPermissionRoleArn Construct(IdentityCenterGetPermissionRoleArn): Snapshot Test 1`] = ` -{ - "Resources": { - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambda87CD5EC8": { - "DependsOn": [ - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRoleDefaultPolicy478A7CE9", - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRole603853E2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRole603853E2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRole603853E2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRoleDefaultPolicy478A7CE9": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "iam:ListRoles", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "entityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRoleDefaultPolicy478A7CE9", - "Roles": [ - { - "Ref": "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambdaServiceRole603853E2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventA87093F8": { - "DependsOn": [ - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleDefaultPolicy05245BF3", - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleAA6883AA", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/IdentityCenterGetPermissionRoleArnProvider/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambda87CD5EC8", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleAA6883AA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleAA6883AA": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleDefaultPolicy05245BF3": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambda87CD5EC8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterGetPermissionRoleArnProviderIdentityCenterGetPermissionRoleArnProviderProviderLambda87CD5EC8", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleDefaultPolicy05245BF3", - "Roles": [ - { - "Ref": "IdentityCenterGetPermissionRoleArnProviderframeworkonEventServiceRoleAA6883AA", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterGetPermissionRoleArngetPermissionSetRoleArn5C0A4E89": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterGetPermissionRoleArnProviderframeworkonEventA87093F8", - "Arn", - ], - }, - "permissionSetName": "Test", - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-instance.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-instance.test.ts.snap deleted file mode 100644 index e55272e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-instance.test.ts.snap +++ /dev/null @@ -1,352 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IdentityCenterInstance Construct(IdentityCenterGetInstanceId): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchKey9B181885": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54": { - "DependsOn": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleDefaultPolicyF2FD593D", - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed IdentityCenterGetInstanceId custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionResourceLogGroup043D5A24": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKey9B181885", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleDefaultPolicyF2FD593D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sso:ListInstances", - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleDefaultPolicyF2FD593D", - "Roles": [ - { - "Ref": "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionServiceRoleC3631797", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdIdentityCenterGetInstanceIdResourceE2BD9B5B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionResourceLogGroup043D5A24", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEvent5DB81BA0", - "Arn", - ], - }, - "debug": false, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEvent5DB81BA0": { - "DependsOn": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRoleDefaultPolicyA09146FD", - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/IdentityCenterInstance/IdentityCenterGetInstanceId/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRoleDefaultPolicyA09146FD": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterInstanceIdentityCenterGetInstanceIdFunctionE59FDB54", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRoleDefaultPolicyA09146FD", - "Roles": [ - { - "Ref": "IdentityCenterInstanceIdentityCenterGetInstanceIdframeworkonEventServiceRole870C36F8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-organization-admin-account.test.ts.snap deleted file mode 100644 index fd4aff2..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/__snapshots__/identity-center-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,266 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IdentityCenterOrganizationAdminAccount Construct(IdentityCenterOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaB22B900C": { - "DependsOn": [ - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy302EC61B", - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRole22209E8A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRole22209E8A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 160, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRole22209E8A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy302EC61B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - "organizations:EnableAwsServiceAccess", - "organizations:DisableAWSServiceAccess", - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:DescribeOrganizationalUnit", - "organizations:ListAccounts", - "organizations:ListAWSServiceAccessForOrganization", - "sso:ListInstances", - "sso:ListPermissionSets", - "sso:ListAccountAssignments", - "sso:DescribePermissionSet", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRoleDefaultPolicy302EC61B", - "Roles": [ - { - "Ref": "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaServiceRole22209E8A", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterOrganizationAdminAccountframeworkonEventEC3480A7": { - "DependsOn": [ - "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicy23D13EB2", - "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRole3C202AD2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/IdentityCenterOrganizationAdminAccount/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaB22B900C", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRole3C202AD2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRole3C202AD2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicy23D13EB2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaB22B900C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "IdentityCenterOrganizationAdminAccountIdentityCenterOrganizationAdminAccountProviderLambdaB22B900C", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRoleDefaultPolicy23D13EB2", - "Roles": [ - { - "Ref": "IdentityCenterOrganizationAdminAccountframeworkonEventServiceRole3C202AD2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "IdentityCenterOrganizationAdminAccountidentityCenterAdmin14AEDC37": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "IdentityCenterOrganizationAdminAccountframeworkonEventEC3480A7", - "Arn", - ], - }, - "adminAccountId": "adminAccountId", - "lzaManagedAssignments": [], - "lzaManagedPermissionSets": [], - "partition": { - "Ref": "AWS::Partition", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-assignment.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-assignment.test.ts deleted file mode 100644 index c28a6e2..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-assignment.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; -import { IdentityCenterAssignments } from '../../lib/aws-identity-center/identity-center-assignments'; -const testNamePrefix = 'Construct(IdentityCenterGetInstanceId): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IdentityCenterAssignments(stack, 'IdentityCenterAssignments', { - identityStoreId: 'd-906751796e', - identityCenterInstanceArn: 'arn:aws:sso:::instance/ssoins-123456789210', - principals: [ - { type: 'USER', name: 'lza-accelerator-user' }, - { type: 'GROUP', name: 'lza-accelerator-group' }, - ], - principalType: 'GROUP', - principalId: '', - permissionSetArnValue: 'arn:aws:sso:::permissionSet/ssoins-1111111111111111/ps-1111111111111111', - accountIds: ['111111111111', '222222222222'], - kmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKey', {}), - logRetentionInDays: 365, -}); - -describe('IdentityCenterAssignments', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-get-permission-set-role-arn.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-get-permission-set-role-arn.test.ts deleted file mode 100644 index 7f14c8e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-get-permission-set-role-arn.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - IdentityCenterGetPermissionRoleArn, - IdentityCenterGetPermissionRoleArnProvider, -} from '../../lib/aws-identity-center/identity-center-get-permission-set-role-arn'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; -const testNamePrefix = 'Construct(IdentityCenterGetPermissionRoleArn): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -const provider = new IdentityCenterGetPermissionRoleArnProvider(stack, 'IdentityCenterGetPermissionRoleArnProvider'); -const serviceToken = provider.serviceToken; - -new IdentityCenterGetPermissionRoleArn(stack, 'IdentityCenterGetPermissionRoleArn', { - permissionSetName: 'Test', - accountId: '111111111111', - serviceToken: serviceToken, -}); - -/** - * IdentityCenterOrganizationAdminAccount construct test - */ -describe('IdentityCenterGetPermissionRoleArn', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-instance.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-instance.test.ts deleted file mode 100644 index f368151..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-instance.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IdentityCenterInstance } from '../../lib/aws-identity-center/identity-center-instance'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; -const testNamePrefix = 'Construct(IdentityCenterGetInstanceId): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IdentityCenterInstance(stack, 'IdentityCenterInstance', { - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKey', {}), - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKey', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * IdentityCenterInstance construct test - */ -describe('IdentityCenterInstance', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-organization-admin-account.test.ts deleted file mode 100644 index 99825e2..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-identity-center/identity-center-organization-admin-account.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { IdentityCenterOrganizationAdminAccount } from '../../lib/aws-identity-center/identity-center-organization-admin-account'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; -const testNamePrefix = 'Construct(IdentityCenterOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new IdentityCenterOrganizationAdminAccount(stack, 'IdentityCenterOrganizationAdminAccount', { - adminAccountId: 'adminAccountId', - lzaManagedAssignments: [], - lzaManagedPermissionSets: [], -}); - -/** - * IdentityCenterOrganizationAdminAccount construct test - */ -describe('IdentityCenterOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-encryption.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-encryption.test.ts.snap deleted file mode 100644 index 666a984..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-encryption.test.ts.snap +++ /dev/null @@ -1,360 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`KmsEncryption Construct(KmsEncryption): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchKeyKmsEncryptionEFBC1FE2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "KeyLookupKmsEncryptionFunction076D1396": { - "DependsOn": [ - "KeyLookupKmsEncryptionFunctionServiceRoleDefaultPolicy45F17070", - "KeyLookupKmsEncryptionFunctionServiceRole3CBF5633", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed KmsEncryption custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyKmsEncryption8BC9FA8C", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "KeyLookupKmsEncryptionFunctionServiceRole3CBF5633", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "KeyLookupKmsEncryptionFunctionResourceLogGroupDFD04BBA": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyKmsEncryptionEFBC1FE2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "KeyLookupKmsEncryptionFunction076D1396", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "KeyLookupKmsEncryptionFunctionServiceRole3CBF5633": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "KeyLookupKmsEncryptionFunctionServiceRoleDefaultPolicy45F17070": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "kms:PutKeyPolicy", - "Effect": "Allow", - "Resource": "111111111111", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KeyLookupKmsEncryptionFunctionServiceRoleDefaultPolicy45F17070", - "Roles": [ - { - "Ref": "KeyLookupKmsEncryptionFunctionServiceRole3CBF5633", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "KeyLookupKmsEncryptionKmsEncryptionResourceB1BDD1D0": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "KeyLookupKmsEncryptionFunctionResourceLogGroupDFD04BBA", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "KeyLookupKmsEncryptionframeworkonEvent7F3FE155", - "Arn", - ], - }, - "kmsArn": "111111111111", - "organizationId": "organizationId", - "policyFilePaths": [ - "kms-policy/full-central-logs-bucket-key-policy.json", - ], - "sourceAccount": { - "Ref": "AWS::AccountId", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "KeyLookupKmsEncryptionframeworkonEvent7F3FE155": { - "DependsOn": [ - "KeyLookupKmsEncryptionframeworkonEventServiceRoleDefaultPolicy6D455222", - "KeyLookupKmsEncryptionframeworkonEventServiceRole61BA4F8D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/KeyLookup/KmsEncryption/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "KeyLookupKmsEncryptionFunction076D1396", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "KeyLookupKmsEncryptionframeworkonEventServiceRole61BA4F8D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "KeyLookupKmsEncryptionframeworkonEventServiceRole61BA4F8D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "KeyLookupKmsEncryptionframeworkonEventServiceRoleDefaultPolicy6D455222": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "KeyLookupKmsEncryptionFunction076D1396", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "KeyLookupKmsEncryptionFunction076D1396", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "KeyLookupKmsEncryptionframeworkonEventServiceRoleDefaultPolicy6D455222", - "Roles": [ - { - "Ref": "KeyLookupKmsEncryptionframeworkonEventServiceRole61BA4F8D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "LambdaKeyKmsEncryption8BC9FA8C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-lookup.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-lookup.test.ts.snap deleted file mode 100644 index 17ff437..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-kms/__snapshots__/key-lookup.test.ts.snap +++ /dev/null @@ -1,156 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`KeyLookup Construct(KeyLookup): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "KeyLookup4B8CA43D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/KeyLookupRoleName", - ], - ], - }, - "invokingAccountID": { - "Ref": "AWS::AccountId", - }, - "invokingRegion": { - "Ref": "AWS::Region", - }, - "parameterAccountID": "111111111111", - "parameterName": "KeyArnParameterName", - "parameterRegion": { - "Ref": "AWS::Region", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-kms/key-encryption.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-kms/key-encryption.test.ts deleted file mode 100644 index 0f8e0f4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-kms/key-encryption.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { KmsEncryption } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(KmsEncryption): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new KmsEncryption(stack, 'KeyLookup', { - kmsArn: '111111111111', - policyFilePaths: [ - `${__dirname}/../../../accelerator/test/configs/snapshot-only/kms/full-central-logs-bucket-key-policy.json`, - ], - organizationId: 'organizationId', - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKeyKmsEncryption', {}), - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKeyKmsEncryption', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * KmsEncryption construct test - */ -describe('KmsEncryption', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-kms/key-lookup.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-kms/key-lookup.test.ts deleted file mode 100644 index b05c354..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-kms/key-lookup.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { KeyLookup } from '../../lib/aws-kms/key-lookup'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(KeyLookup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new KeyLookup(stack, 'KeyLookup', { - accountId: '111111111111', - roleName: 'KeyLookupRoleName', - keyArnParameterName: 'KeyArnParameterName', - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}).getKey(); - -/** - * KeyLookup construct test - */ -describe('KeyLookup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-export-config-classification.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-export-config-classification.test.ts.snap deleted file mode 100644 index f46571c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-export-config-classification.test.ts.snap +++ /dev/null @@ -1,197 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MacieExportConfigClassification Construct(MacieExportConfigClassification): Snapshot Test 1`] = ` -{ - "Resources": { - "AwsMacieUpdateExportConfigClassification832781E3": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderLogGroup727354F4", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC", - "Arn", - ], - }, - "bucketName": "bucketName", - "keyPrefix": "111111111111-aws-macie-export-config", - "kmsKeyArn": { - "Fn::GetAtt": [ - "BucketKey7092080A", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::MaciePutClassificationExportConfiguration", - "UpdateReplacePolicy": "Delete", - }, - "BucketKey7092080A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC": { - "DependsOn": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderLogGroup727354F4": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "LogKeyE83D0C1B", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMaciePutClassificationExportConfigurationCustomResourceProviderHandlerC53E2FCC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMaciePutClassificationExportConfigurationCustomResourceProviderRoleEB42D531": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "macie2:EnableMacie", - "macie2:GetClassificationExportConfiguration", - "macie2:GetMacieSession", - "macie2:PutClassificationExportConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MaciePutClassificationExportConfigurationTaskMacieActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "LogKeyE83D0C1B": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-members.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-members.test.ts.snap deleted file mode 100644 index de5a705..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-members.test.ts.snap +++ /dev/null @@ -1,179 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MacieMembers Construct(MacieMembers): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB": { - "DependsOn": [ - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieCreateMemberCustomResourceProviderLogGroupB2DBC335": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieCreateMemberCustomResourceProviderRole3E8977EE": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieCreateMemberTaskOrganizationAction", - }, - { - "Action": [ - "macie2:CreateMember", - "macie2:DeleteMember", - "macie2:DescribeOrganizationConfiguration", - "macie2:DisassociateMember", - "macie2:EnableMacie", - "macie2:GetMacieSession", - "macie2:ListMembers", - "macie2:UpdateOrganizationConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieCreateMemberTaskMacieActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "MacieMembers1B6840B4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieCreateMemberCustomResourceProviderLogGroupB2DBC335", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieCreateMemberCustomResourceProviderHandler913F75DB", - "Arn", - ], - }, - "adminAccountId": { - "Ref": "AWS::AccountId", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::MacieCreateMember", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-organization-admin-account.test.ts.snap deleted file mode 100644 index 8ffb6c7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,223 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MacieOrganizationAdminAccount Construct(MacieOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A": { - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderRoleA386B194": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:DescribeOrganization", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - "organizations:RegisterDelegatedAdministrator", - "organizations:ServicePrincipal", - "organizations:UpdateOrganizationConfiguration", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:DeregisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:DescribeOrganization": [ - "macie.amazonaws.com", - ], - "organizations:EnableAWSServiceAccess": [ - "macie.amazonaws.com", - ], - "organizations:ListAWSServiceAccessForOrganization": [ - "macie.amazonaws.com", - ], - "organizations:ListAccounts": [ - "macie.amazonaws.com", - ], - "organizations:ListDelegatedAdministrators": [ - "macie.amazonaws.com", - ], - "organizations:RegisterDelegatedAdministrator": [ - "macie.amazonaws.com", - ], - "organizations:ServicePrincipal": [ - "macie.amazonaws.com", - ], - "organizations:UpdateOrganizationConfiguration": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": [ - "macie2:DisableOrganizationAdminAccount", - "macie2:EnableMacie", - "macie2:EnableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:ListOrganizationAdminAccounts", - "macie2:DisableOrganizationAdminAccount", - "macie2:GetMacieSession", - "macie2:EnableMacie", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableOrganizationAdminAccountTaskMacieActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:CreateServiceLinkedRole": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableMacieTaskIamAction", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "MacieOrganizationAdminAccount2C23317B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderLogGroupB2A6EB41", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieEnableOrganizationAdminAccountCustomResourceProviderHandlerD7A9976A", - "Arn", - ], - }, - "adminAccountId": { - "Ref": "AWS::AccountId", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::MacieEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-session.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-session.test.ts.snap deleted file mode 100644 index 9781ee3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/__snapshots__/macie-session.test.ts.snap +++ /dev/null @@ -1,172 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MacieSession Construct(MacieSession): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomMacieEnableMacieCustomResourceProviderHandler1B3444A0": { - "DependsOn": [ - "CustomMacieEnableMacieCustomResourceProviderRole2B29C97C", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomMacieEnableMacieCustomResourceProviderRole2B29C97C", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomMacieEnableMacieCustomResourceProviderLogGroup1E65A7D1": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomMacieEnableMacieCustomResourceProviderHandler1B3444A0", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomMacieEnableMacieCustomResourceProviderRole2B29C97C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "macie2:DisableMacie", - "macie2:EnableMacie", - "macie2:GetMacieSession", - "macie2:PutFindingsPublicationConfiguration", - "macie2:UpdateMacieSession", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableMacieTaskMacieActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLikeIfExists": { - "iam:CreateServiceLinkedRole": [ - "macie.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "MacieEnableMacieTaskIamAction", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "MacieSession011BCE74": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomMacieEnableMacieCustomResourceProviderLogGroup1E65A7D1", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomMacieEnableMacieCustomResourceProviderHandler1B3444A0", - "Arn", - ], - }, - "findingPublishingFrequency": "FIFTEEN_MINUTES", - "isSensitiveSh": true, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::MacieEnableMacie", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-export-config-classification.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-export-config-classification.test.ts deleted file mode 100644 index 9f6e4ad..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-export-config-classification.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { MacieExportConfigClassification } from '../../lib/aws-macie/macie-export-config-classification'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(MacieExportConfigClassification): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new MacieExportConfigClassification(stack, 'AwsMacieUpdateExportConfigClassification', { - bucketName: 'bucketName', - keyPrefix: `111111111111-aws-macie-export-config`, - logRetentionInDays: 3653, - bucketKmsKey: new cdk.aws_kms.Key(stack, 'BucketKey', {}), - logKmsKey: new cdk.aws_kms.Key(stack, 'LogKey', {}), -}); - -/** - * MacieExportConfigClassification construct test - */ -describe('MacieExportConfigClassification', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-members.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-members.test.ts deleted file mode 100644 index 2fd7c86..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-members.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { MacieMembers } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(MacieMembers): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new MacieMembers(stack, 'MacieMembers', { - adminAccountId: stack.account, - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); -/** - * MacieMembers construct test - */ -describe('MacieMembers', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-organization-admin-account.test.ts deleted file mode 100644 index e0c9088..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-organization-admin-account.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { MacieOrganizationAdminAccount } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(MacieOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new MacieOrganizationAdminAccount(stack, 'MacieOrganizationAdminAccount', { - adminAccountId: stack.account, - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); -/** - * MacieOrganizationAdminAccount construct test - */ -describe('MacieOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-session.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-session.test.ts deleted file mode 100644 index b5c573c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-macie/macie-session.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { MacieSession } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(MacieSession): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new MacieSession(stack, 'MacieSession', { - isSensitiveSh: true, - findingPublishingFrequency: 'FIFTEEN_MINUTES', - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); -/** - * MacieSession construct test - */ -describe('MacieSession', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/firewall.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/firewall.test.ts.snap deleted file mode 100644 index 8106155..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/firewall.test.ts.snap +++ /dev/null @@ -1,297 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Network Firewall Construct(NetworkFirewall): Snapshot Test 1`] = ` -{ - "Resources": { - "CloudWatchKey9B181885": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1": { - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKey9B181885", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeAvailabilityZones", - "network-firewall:DescribeFirewall", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestFirewallE26FCA5C": { - "Properties": { - "FirewallName": "TestFirewall", - "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:222222222222:firewall-policy/TestPolicy", - "SubnetMappings": [ - { - "SubnetId": "Test-Subnet-1", - }, - { - "SubnetId": "Test-Subnet-2", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "TestFirewall", - }, - ], - "VpcId": "TestVpc", - }, - "Type": "AWS::NetworkFirewall::Firewall", - }, - "TestFirewalltestRouteId1894E055E": { - "Properties": { - "DestinationCidrBlock": "10.0.0.6/32", - "RouteTableId": "routeTableId", - "VpcEndpointId": { - "Ref": "TestFirewalltestRouteId1Endpoint4D98AAD2", - }, - }, - "Type": "AWS::EC2::Route", - }, - "TestFirewalltestRouteId1Endpoint4D98AAD2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "1", - "firewallArn": { - "Ref": "TestFirewallE26FCA5C", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "TestFirewalltestRouteId280883F56": { - "Properties": { - "DestinationIpv6CidrBlock": "::1", - "RouteTableId": "routeTableId", - "VpcEndpointId": { - "Ref": "TestFirewalltestRouteId2Endpoint9D8D500E", - }, - }, - "Type": "AWS::EC2::Route", - }, - "TestFirewalltestRouteId2Endpoint9D8D500E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "1", - "firewallArn": { - "Ref": "TestFirewallE26FCA5C", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - "TestImportFirewallLoggingConfigB02138AE": { - "Properties": { - "FirewallArn": "arn:aws:network-firewall:us-east-1:222222222222:firewall/TestImportedFirewall", - "LoggingConfiguration": { - "LogDestinationConfigs": [ - { - "LogDestination": { - "logGroup": "firewallAlertLogGroupArn", - }, - "LogDestinationType": "CloudWatchLogs", - "LogType": "ALERT", - }, - ], - }, - }, - "Type": "AWS::NetworkFirewall::LoggingConfiguration", - }, - "TestImportFirewallendpointRouteId4582BF89": { - "Properties": { - "DestinationCidrBlock": "10.0.0.6/32", - "RouteTableId": "routeTableId", - "VpcEndpointId": { - "Ref": "TestImportFirewallendpointRouteIdEndpoint3D0FC334", - }, - }, - "Type": "AWS::EC2::Route", - }, - "TestImportFirewallendpointRouteIdEndpoint3D0FC334": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "1", - "firewallArn": "arn:aws:network-firewall:us-east-1:222222222222:firewall/TestImportedFirewall", - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`Network Firewall Construct(NetworkFirewall): Snapshot Test 2`] = ` -{ - "Resources": { - "firewallLogicalId": { - "Properties": { - "FirewallName": "TestFirewall", - "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:222222222222:firewall-policy/TestPolicy", - "SubnetMappings": [ - { - "SubnetId": "Test-Subnet-1", - }, - { - "SubnetId": "Test-Subnet-2", - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "TestFirewall", - }, - ], - "VpcId": "TestVpc", - }, - "Type": "AWS::NetworkFirewall::Firewall", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/get-network-firewall-endpoint.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/get-network-firewall-endpoint.test.ts.snap deleted file mode 100644 index 74123c9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/get-network-firewall-endpoint.test.ts.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Get Network Firewall endpoint Construct(GetNetworkFirewallEndpoint): Snapshot Test 1`] = ` -{ - "Resources": { - "Custom8166710A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1": { - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Custom8166710A", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetNetworkFirewallEndpointCustomResourceProviderRole540B9917": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeAvailabilityZones", - "network-firewall:DescribeFirewall", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestGetEndpoint7804FE92": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderLogGroup98AC3B14", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetNetworkFirewallEndpointCustomResourceProviderHandler2EF030A1", - "Arn", - ], - }, - "endpointAz": "us-east-1a", - "firewallArn": "arn:aws:network-firewall:us-east-1:222222222222:firewall/TestFirewall", - "region": "us-east-1", - }, - "Type": "Custom::GetNetworkFirewallEndpoint", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/policy.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/policy.test.ts.snap deleted file mode 100644 index d01617e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/policy.test.ts.snap +++ /dev/null @@ -1,89 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Network Firewall Policy Construct(NetworkFirewallPolicy): Snapshot Test 1`] = ` -{ - "Resources": { - "TestPolicy14DF854C3": { - "Properties": { - "FirewallPolicy": { - "StatefulDefaultActions": [ - "statefulDefaultActions", - ], - "StatefulEngineOptions": { - "RuleOrder": "statefulEngineOptions", - }, - "StatefulRuleGroupReferences": [ - { - "Priority": 123, - "ResourceArn": "resourceArn", - }, - ], - "StatelessCustomActions": [ - { - "ActionDefinition": { - "PublishMetricAction": { - "Dimensions": [ - { - "Value": "CustomValue", - }, - ], - }, - }, - "ActionName": "actionName", - }, - ], - "StatelessDefaultActions": [ - "statelessDefaultActions", - ], - "StatelessFragmentDefaultActions": [ - "statelessFragmentDefaultActions", - ], - "StatelessRuleGroupReferences": [ - { - "Priority": 123, - "ResourceArn": "resourceArn", - }, - ], - }, - "FirewallPolicyName": "TestFirewallPolicy1", - "Tags": [ - { - "Key": "Name", - "Value": "TestFirewallPolicy1", - }, - ], - }, - "Type": "AWS::NetworkFirewall::FirewallPolicy", - }, - "TestPolicyCC05E598": { - "Properties": { - "FirewallPolicy": { - "StatefulEngineOptions": { - "RuleOrder": "STRICT_ORDER", - }, - "StatefulRuleGroupReferences": [ - { - "Priority": 123, - "ResourceArn": "arn:aws:network-firewall:us-east-1:222222222222:stateful-rulegroup/TestGroup", - }, - ], - "StatelessDefaultActions": [ - "aws:forward_to_sfe", - ], - "StatelessFragmentDefaultActions": [ - "aws:forward_to_sfe", - ], - }, - "FirewallPolicyName": "TestFirewallPolicy", - "Tags": [ - { - "Key": "Name", - "Value": "TestFirewallPolicy", - }, - ], - }, - "Type": "AWS::NetworkFirewall::FirewallPolicy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/rule-group.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/rule-group.test.ts.snap deleted file mode 100644 index f36ad0b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/__snapshots__/rule-group.test.ts.snap +++ /dev/null @@ -1,285 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Network Firewall Rule Group Construct(NetworkFirewallPolicy): Snapshot Test 1`] = ` -{ - "Resources": { - "TestGroupAF88660E": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RulesSource": { - "StatefulRules": [ - { - "Action": "PASS", - "Header": { - "Destination": "10.0.0.0/16", - "DestinationPort": "ANY", - "Direction": "FORWARD", - "Protocol": "IP", - "Source": "10.1.0.0/16", - "SourcePort": "ANY", - }, - "RuleOptions": [ - { - "Keyword": "sid", - "Settings": [ - "100", - ], - }, - ], - }, - ], - }, - "StatefulRuleOptions": { - "RuleOrder": "STRICT_ORDER", - }, - }, - "RuleGroupName": "TestGroup", - "Tags": [ - { - "Key": "Name", - "Value": "TestGroup", - }, - ], - "Type": "STATEFUL", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - "TestGroupStatelessB53F2424": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RuleVariables": { - "IPSets": { - "HOME_NET": { - "Definition": [ - "10.0.0.0/16", - ], - }, - }, - "PortSets": { - "HOME_NET": { - "Definition": [ - "80", - "443", - ], - }, - }, - }, - "RulesSource": { - "StatelessRulesAndCustomActions": { - "CustomActions": [ - { - "ActionDefinition": { - "PublishMetricAction": { - "Dimensions": [ - { - "Value": "CustomValue", - }, - ], - }, - }, - "ActionName": "CustomAction", - }, - ], - "StatelessRules": [ - { - "Priority": 100, - "RuleDefinition": { - "Actions": [ - "aws:pass", - ], - "MatchAttributes": { - "DestinationPorts": [ - { - "FromPort": 22, - "ToPort": 22, - }, - ], - "Destinations": [ - { - "AddressDefinition": "10.0.0.0/16", - }, - ], - "Protocols": [], - "SourcePorts": [ - { - "FromPort": 1024, - "ToPort": 65535, - }, - ], - "Sources": [ - { - "AddressDefinition": "10.1.0.0/16", - }, - ], - }, - }, - }, - ], - }, - }, - "StatefulRuleOptions": { - "RuleOrder": "STRICT_ORDER", - }, - }, - "RuleGroupName": "TestGroupStateless", - "Tags": [ - { - "Key": "Name", - "Value": "TestGroupStateless", - }, - { - "Key": "someKey", - "Value": "someValue", - }, - ], - "Type": "STATELESS", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - }, -} -`; - -exports[`Network Firewall Rule Group Stateless Construct(NetworkFirewallPolicy): Snapshot Test 1`] = ` -{ - "Resources": { - "TestGroupAF88660E": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RulesSource": { - "StatefulRules": [ - { - "Action": "PASS", - "Header": { - "Destination": "10.0.0.0/16", - "DestinationPort": "ANY", - "Direction": "FORWARD", - "Protocol": "IP", - "Source": "10.1.0.0/16", - "SourcePort": "ANY", - }, - "RuleOptions": [ - { - "Keyword": "sid", - "Settings": [ - "100", - ], - }, - ], - }, - ], - }, - "StatefulRuleOptions": { - "RuleOrder": "STRICT_ORDER", - }, - }, - "RuleGroupName": "TestGroup", - "Tags": [ - { - "Key": "Name", - "Value": "TestGroup", - }, - ], - "Type": "STATEFUL", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - "TestGroupStatelessB53F2424": { - "Properties": { - "Capacity": 100, - "RuleGroup": { - "RuleVariables": { - "IPSets": { - "HOME_NET": { - "Definition": [ - "10.0.0.0/16", - ], - }, - }, - "PortSets": { - "HOME_NET": { - "Definition": [ - "80", - "443", - ], - }, - }, - }, - "RulesSource": { - "StatelessRulesAndCustomActions": { - "CustomActions": [ - { - "ActionDefinition": { - "PublishMetricAction": { - "Dimensions": [ - { - "Value": "CustomValue", - }, - ], - }, - }, - "ActionName": "CustomAction", - }, - ], - "StatelessRules": [ - { - "Priority": 100, - "RuleDefinition": { - "Actions": [ - "aws:pass", - ], - "MatchAttributes": { - "DestinationPorts": [ - { - "FromPort": 22, - "ToPort": 22, - }, - ], - "Destinations": [ - { - "AddressDefinition": "10.0.0.0/16", - }, - ], - "Protocols": [], - "SourcePorts": [ - { - "FromPort": 1024, - "ToPort": 65535, - }, - ], - "Sources": [ - { - "AddressDefinition": "10.1.0.0/16", - }, - ], - }, - }, - }, - ], - }, - }, - "StatefulRuleOptions": { - "RuleOrder": "STRICT_ORDER", - }, - }, - "RuleGroupName": "TestGroupStateless", - "Tags": [ - { - "Key": "Name", - "Value": "TestGroupStateless", - }, - { - "Key": "someKey", - "Value": "someValue", - }, - ], - "Type": "STATELESS", - }, - "Type": "AWS::NetworkFirewall::RuleGroup", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/firewall.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/firewall.test.ts deleted file mode 100644 index ea91340..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/firewall.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as path from 'path'; -import * as cdk from 'aws-cdk-lib'; -import { NetworkFirewall } from '../../lib/aws-networkfirewall/firewall'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(NetworkFirewall): '; - -//Initialize stack for resource configuration test -const stack = new cdk.Stack(); - -const firewallPolicyArn = 'arn:aws:network-firewall:us-east-1:222222222222:firewall-policy/TestPolicy'; - -const importedFirewallArn = 'arn:aws:network-firewall:us-east-1:222222222222:firewall/TestImportedFirewall'; - -const testFirewall = new NetworkFirewall(stack, 'TestFirewall', { - firewallPolicyArn: firewallPolicyArn, - name: 'TestFirewall', - subnets: ['Test-Subnet-1', 'Test-Subnet-2'], - vpcId: 'TestVpc', - tags: [], -}); -const key = new cdk.aws_kms.Key(stack, 'CloudWatchKey', {}); - -testFirewall.addNetworkFirewallRoute('testRouteId1', '1', 365, 'routeTableId', '10.0.0.6/32', undefined, key); -testFirewall.addNetworkFirewallRoute('testRouteId2', '1', 365, 'routeTableId', undefined, '::1', key); - -const importedFirewall = NetworkFirewall.fromAttributes(stack, 'TestImportFirewall', { - firewallArn: importedFirewallArn, - firewallName: 'ImportedFirewallName', -}); -importedFirewall.addLogging({ - logDestinationConfigs: [ - { - logDestinationType: 'CloudWatchLogs', - logDestination: { - logGroup: 'firewallAlertLogGroupArn', - }, - logType: 'ALERT', - }, - ], -}); - -importedFirewall.addNetworkFirewallRoute('endpointRouteId', '1', 365, 'routeTableId', '10.0.0.6/32', undefined, key); - -const app = new cdk.App(); -const includedStack = new cdk.Stack(app, `placeHolder`, {}); -const firewallStack = new cdk.cloudformation_include.CfnInclude(includedStack, 'IncludedStack', { - templateFile: path.join(__dirname, 'includedStacks/firewall-stack.json'), -}); - -NetworkFirewall.includedCfnResource(firewallStack, 'firewallLogicalId', { - firewallPolicyArn: firewallPolicyArn, - name: 'TestFirewall', - subnets: ['Test-Subnet-1', 'Test-Subnet-2'], - vpcId: 'TestVpc', - tags: [], -}); - -/** - * Network Firewall construct test - */ -describe('Network Firewall', () => { - snapShotTest(testNamePrefix, stack); - snapShotTest(testNamePrefix, includedStack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/get-network-firewall-endpoint.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/get-network-firewall-endpoint.test.ts deleted file mode 100644 index 36fd546..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/get-network-firewall-endpoint.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GetNetworkFirewallEndpoint } from '../../lib/aws-networkfirewall/get-network-firewall-endpoint'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(GetNetworkFirewallEndpoint): '; - -//Initialize stack for resource configuration test -const stack = new cdk.Stack(); - -const firewallArn = 'arn:aws:network-firewall:us-east-1:222222222222:firewall/TestFirewall'; - -new GetNetworkFirewallEndpoint(stack, 'TestGetEndpoint', { - endpointAz: 'us-east-1a', - firewallArn: firewallArn, - kmsKey: new cdk.aws_kms.Key(stack, 'Custom', {}), - logRetentionInDays: 3653, - region: 'us-east-1', -}); - -/** - * Get Network Firewall endpoint construct test - */ -describe('Get Network Firewall endpoint', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/includedStacks/firewall-stack.json b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/includedStacks/firewall-stack.json deleted file mode 100644 index 908d95a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/includedStacks/firewall-stack.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "Resources": { - "firewallLogicalId": { - "Properties": { - "FirewallName": "TestFirewall", - "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:222222222222:firewall-policy/TestPolicy", - "SubnetMappings": [ - { - "SubnetId": "Test-Subnet-1" - }, - { - "SubnetId": "Test-Subnet-2" - } - ], - "Tags": [ - { - "Key": "Name", - "Value": "TestFirewall" - } - ], - "VpcId": "TestVpc" - }, - "Type": "AWS::NetworkFirewall::Firewall" - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/policy.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/policy.test.ts deleted file mode 100644 index eb0d6b9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/policy.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { FirewallPolicyProperty, NetworkFirewallPolicy } from '../../lib/aws-networkfirewall/policy'; -import { snapShotTest } from '../snapshot-test'; -import { describe, it } from '@jest/globals'; - -const testNamePrefix = 'Construct(NetworkFirewallPolicy): '; - -const importedFirewallPolicyArn = 'arn:aws:network-firewall:us-east-1:222222222222:firewall-policy/TestImportedPolicy'; - -//Initialize stack for resource configuration test -const stack = new cdk.Stack(); - -/** - * Network Firewall construct test - */ -describe('Network Firewall Policy', () => { - it('test stateful engine', () => { - const firewallPolicy: FirewallPolicyProperty = { - statelessDefaultActions: ['aws:forward_to_sfe'], - statelessFragmentDefaultActions: ['aws:forward_to_sfe'], - statefulEngineOptions: 'STRICT_ORDER', - statefulRuleGroupReferences: [ - { - priority: 123, - resourceArn: 'arn:aws:network-firewall:us-east-1:222222222222:stateful-rulegroup/TestGroup', - }, - ], - }; - - new NetworkFirewallPolicy(stack, 'TestPolicy', { - firewallPolicy: firewallPolicy, - name: 'TestFirewallPolicy', - tags: [], - }); - }); - - it('test custom action', () => { - const testFirewallPolicy: FirewallPolicyProperty = { - statelessDefaultActions: ['statelessDefaultActions'], - statelessFragmentDefaultActions: ['statelessFragmentDefaultActions'], - - statefulDefaultActions: ['statefulDefaultActions'], - statefulEngineOptions: 'statefulEngineOptions', - statefulRuleGroupReferences: [ - { - resourceArn: 'resourceArn', - priority: 123, - }, - ], - statelessCustomActions: [ - { - actionDefinition: { - publishMetricAction: { - dimensions: ['CustomValue'], - }, - }, - actionName: 'actionName', - }, - ], - statelessRuleGroupReferences: [ - { - priority: 123, - resourceArn: 'resourceArn', - }, - ], - }; - new NetworkFirewallPolicy(stack, 'TestPolicy1', { - firewallPolicy: testFirewallPolicy, - name: 'TestFirewallPolicy1', - tags: [], - }); - }); - - it('test import policy', () => { - NetworkFirewallPolicy.fromAttributes(stack, 'TestImportPolicy', { - policyName: importedFirewallPolicyArn, - policyArn: 'importedPolicyArn', - }); - }); - - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/rule-group.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/rule-group.test.ts deleted file mode 100644 index 5298eac..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-networkfirewall/rule-group.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NfwRuleGroupRuleConfig } from '@aws-accelerator/config'; -import { NetworkFirewallRuleGroup } from '../../lib/aws-networkfirewall/rule-group'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(NetworkFirewallPolicy): '; - -//Initialize stack for resource configuration test -const stack = new cdk.Stack(); - -const ruleGroup: NfwRuleGroupRuleConfig = { - rulesSource: { - statefulRules: [ - { - action: 'PASS', - header: { - destination: '10.0.0.0/16', - destinationPort: 'ANY', - direction: 'FORWARD', - protocol: 'IP', - source: '10.1.0.0/16', - sourcePort: 'ANY', - }, - ruleOptions: [ - { - keyword: 'sid', - settings: ['100'], - }, - ], - }, - ], - rulesSourceList: undefined, - statelessRulesAndCustomActions: undefined, - rulesString: undefined, - rulesFile: '../../accelerator/test/configs/snapshot-only/firewall-rules/rules.txt', - }, - ruleVariables: undefined, - statefulRuleOptions: 'STRICT_ORDER', -}; - -new NetworkFirewallRuleGroup(stack, 'TestGroup', { - name: 'TestGroup', - capacity: 100, - type: 'STATEFUL', - ruleGroup: ruleGroup, - tags: [], -}); - -/** - * Network Firewall construct test - */ -describe('Network Firewall Rule Group', () => { - snapShotTest(testNamePrefix, stack); -}); - -const statelessRule: NfwRuleGroupRuleConfig = { - rulesSource: { - statelessRulesAndCustomActions: { - statelessRules: [ - { - priority: 100, - ruleDefinition: { - actions: ['aws:pass'], - matchAttributes: { - protocols: undefined, - tcpFlags: undefined, - sources: ['10.1.0.0/16'], - sourcePorts: [ - { - fromPort: 1024, - toPort: 65535, - }, - ], - destinations: ['10.0.0.0/16'], - destinationPorts: [ - { - fromPort: 22, - toPort: 22, - }, - ], - }, - }, - }, - ], - customActions: [ - { - actionDefinition: { - publishMetricAction: { - dimensions: ['CustomValue'], - }, - }, - actionName: 'CustomAction', - }, - ], - }, - rulesSourceList: undefined, - statefulRules: undefined, - rulesString: undefined, - rulesFile: '../../accelerator/test/configs/snapshot-only/firewall-rules/rules.txt', - }, - - ruleVariables: { - ipSets: { - name: 'HOME_NET', - definition: ['10.0.0.0/16'], - }, - portSets: { - name: 'HOME_NET', - definition: ['80', '443'], - }, - }, - - statefulRuleOptions: 'STRICT_ORDER', -}; - -new NetworkFirewallRuleGroup(stack, 'TestGroupStateless', { - name: 'TestGroupStateless', - capacity: 100, - type: 'STATELESS', - ruleGroup: statelessRule, - tags: [{ key: 'someKey', value: 'someValue' }], -}); - -NetworkFirewallRuleGroup.fromAttributes(stack, 'TestImportGroup', { - groupArn: 'importedGroupArn', - groupName: 'importedGroupName', -}); -/* - * Network Firewall construct test - */ -describe('Network Firewall Rule Group Stateless', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/account.test.ts.snap deleted file mode 100644 index 9d2ac4c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/account.test.ts.snap +++ /dev/null @@ -1,217 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Account Construct(Account): Snapshot Test 1`] = ` -{ - "Resources": { - "Account0D856946": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderLogGroup4B13FC0F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderHandlerC9A5BAC1", - "Arn", - ], - }, - "assumeRoleName": "AWSControlTowerExecution", - "commitId": "abcd123456789", - "configTableName": { - "Ref": "ConfigTable5CD72349", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::InviteAccountsToOrganization", - "UpdateReplacePolicy": "Delete", - }, - "ConfigTable5CD72349": { - "DeletionPolicy": "Retain", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "dataType", - "AttributeType": "S", - }, - ], - "KeySchema": [ - { - "AttributeName": "dataType", - "KeyType": "HASH", - }, - ], - "ProvisionedThroughput": { - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5, - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Retain", - }, - "CustomInviteAccountsToOrganizationCustomResourceProviderHandlerC9A5BAC1": { - "DependsOn": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderRole88663193", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomInviteAccountsToOrganizationCustomResourceProviderRole88663193", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomInviteAccountsToOrganizationCustomResourceProviderLogGroup4B13FC0F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomInviteAccountsToOrganizationCustomResourceProviderHandlerC9A5BAC1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomInviteAccountsToOrganizationCustomResourceProviderRole88663193": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AcceptHandshake", - "organizations:ListAccounts", - "organizations:InviteAccountToOrganization", - "organizations:MoveAccount", - "organizations:ListRoots", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "dynamodb:Query", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ConfigTable5CD72349", - "Arn", - ], - }, - ], - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSControlTowerExecution", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/create-accounts.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/create-accounts.test.ts.snap deleted file mode 100644 index ad4c9df..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/create-accounts.test.ts.snap +++ /dev/null @@ -1,1134 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ReportDefinition Construct(ConfigServiceTags): Snapshot Test 1`] = ` -{ - "Mappings": { - "ServiceprincipalMap": { - "af-south-1": { - "states": "states.af-south-1.amazonaws.com", - }, - "ap-east-1": { - "states": "states.ap-east-1.amazonaws.com", - }, - "ap-northeast-1": { - "states": "states.ap-northeast-1.amazonaws.com", - }, - "ap-northeast-2": { - "states": "states.ap-northeast-2.amazonaws.com", - }, - "ap-northeast-3": { - "states": "states.ap-northeast-3.amazonaws.com", - }, - "ap-south-1": { - "states": "states.ap-south-1.amazonaws.com", - }, - "ap-south-2": { - "states": "states.ap-south-2.amazonaws.com", - }, - "ap-southeast-1": { - "states": "states.ap-southeast-1.amazonaws.com", - }, - "ap-southeast-2": { - "states": "states.ap-southeast-2.amazonaws.com", - }, - "ap-southeast-3": { - "states": "states.ap-southeast-3.amazonaws.com", - }, - "ca-central-1": { - "states": "states.ca-central-1.amazonaws.com", - }, - "cn-north-1": { - "states": "states.cn-north-1.amazonaws.com", - }, - "cn-northwest-1": { - "states": "states.cn-northwest-1.amazonaws.com", - }, - "eu-central-1": { - "states": "states.eu-central-1.amazonaws.com", - }, - "eu-central-2": { - "states": "states.eu-central-2.amazonaws.com", - }, - "eu-north-1": { - "states": "states.eu-north-1.amazonaws.com", - }, - "eu-south-1": { - "states": "states.eu-south-1.amazonaws.com", - }, - "eu-south-2": { - "states": "states.eu-south-2.amazonaws.com", - }, - "eu-west-1": { - "states": "states.eu-west-1.amazonaws.com", - }, - "eu-west-2": { - "states": "states.eu-west-2.amazonaws.com", - }, - "eu-west-3": { - "states": "states.eu-west-3.amazonaws.com", - }, - "me-central-1": { - "states": "states.me-central-1.amazonaws.com", - }, - "me-south-1": { - "states": "states.me-south-1.amazonaws.com", - }, - "sa-east-1": { - "states": "states.sa-east-1.amazonaws.com", - }, - "us-east-1": { - "states": "states.us-east-1.amazonaws.com", - }, - "us-east-2": { - "states": "states.us-east-2.amazonaws.com", - }, - "us-gov-east-1": { - "states": "states.us-gov-east-1.amazonaws.com", - }, - "us-gov-west-1": { - "states": "states.us-gov-west-1.amazonaws.com", - }, - "us-iso-east-1": { - "states": "states.amazonaws.com", - }, - "us-iso-west-1": { - "states": "states.amazonaws.com", - }, - "us-isob-east-1": { - "states": "states.amazonaws.com", - }, - "us-west-1": { - "states": "states.us-west-1.amazonaws.com", - }, - "us-west-2": { - "states": "states.us-west-2.amazonaws.com", - }, - }, - }, - "Resources": { - "CreateOrganizationAccounts49A5350C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C", - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A", - "Arn", - ], - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateOrganizationAccounts", - "UpdateReplacePolicy": "Delete", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Organization Account isComplete handler", - "Environment": { - "Variables": { - "AccountRoleName": "managementAccountAccessRole", - "GovCloudAccountMappingTableName": { - "Ref": "govCloudAccountMapping0E3D2AD8", - }, - "NewOrgAccountsTableName": { - "Ref": "NewOrgAccountsE5BA262F", - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusLogGroupD4DD5E40": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "dynamodb:Scan", - "dynamodb:GetItem", - "dynamodb:DeleteItem", - "dynamodb:PutItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "NewOrgAccountsE5BA262F", - "Arn", - ], - }, - "Sid": "DynamoDb", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TableKeyF581D96F", - "Arn", - ], - }, - "Sid": "KMS", - }, - { - "Action": [ - "organizations:CreateAccount", - "organizations:CreateGovCloudAccount", - "organizations:DescribeCreateAccountStatus", - "organizations:ListRoots", - "organizations:MoveAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Organizations", - }, - { - "Action": [ - "dynamodb:GetItem", - "dynamodb:PutItem", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "govCloudAccountMapping0E3D2AD8", - "Arn", - ], - }, - "Sid": "MappingDynamoDb", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GovCloudTableKeyE9635728", - "Arn", - ], - }, - "Sid": "MappingKMS", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRoleDefaultPolicy217D2441", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountStatusServiceRole5944FA8B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsLogGroup00D93B3C": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CreateOrganizationAccountsDDA8AFE1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - isComplete (Stack/CreateOrganizationAccounts/CreateOrganizationAccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - }, - }, - "Handler": "framework.isComplete", - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRoleDefaultPolicy17A8199B", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisCompleteServiceRole19A96F30", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEvent96B71A5A": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Stack/CreateOrganizationAccounts/CreateOrganizationAccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - "WAITER_STATE_MACHINE_ARN": { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "states:StartExecution", - "Effect": "Allow", - "Resource": { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B", - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRoleDefaultPolicy5CD1CC20", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonEventServiceRole66CD01EF", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onTimeout (Stack/CreateOrganizationAccounts/CreateOrganizationAccountsProvider)", - "Environment": { - "Variables": { - "USER_IS_COMPLETE_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onTimeout", - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsDDA8AFE1", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountStatusBBDEFDFE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleDefaultPolicy7473AD40", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutServiceRoleA8ECA8F4", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineC299C25B": { - "DependsOn": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - ], - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{"StartAt":"framework-isComplete-task","States":{"framework-isComplete-task":{"End":true,"Retry":[{"ErrorEquals":["States.ALL"],"IntervalSeconds":15,"MaxAttempts":480,"BackoffRate":1}],"Catch":[{"ErrorEquals":["States.ALL"],"Next":"framework-onTimeout-task"}],"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "Arn", - ], - }, - ""},"framework-onTimeout-task":{"End":true,"Type":"Task","Resource":"", - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "Arn", - ], - }, - ""}}}", - ], - ], - }, - "RoleArn": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - "Arn", - ], - }, - }, - "Type": "AWS::StepFunctions::StateMachine", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::FindInMap": [ - "ServiceprincipalMap", - { - "Ref": "AWS::Region", - }, - "states", - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkisComplete071270AC", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CreateOrganizationAccountsCreateOrganizationAccountsProviderframeworkonTimeoutDD2DB516", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRoleDefaultPolicyA69022C3", - "Roles": [ - { - "Ref": "CreateOrganizationAccountsCreateOrganizationAccountsProviderwaiterstatemachineRole22D85F30", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CreateOrganizationAccountsDDA8AFE1": { - "DependsOn": [ - "CreateOrganizationAccountsServiceRole99CB3720", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Create Organization Accounts OnEvent handler", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "CreateOrganizationAccountsServiceRole99CB3720", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "CreateOrganizationAccountsServiceRole99CB3720": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "GovCloudTableKeyE9635728": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "NewOrgAccountsE5BA262F": { - "DeletionPolicy": "Delete", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "accountEmail", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "accountEmail", - "KeyType": "HASH", - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "TableKeyF581D96F", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Delete", - }, - "TableKeyF581D96F": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "govCloudAccountMapping0E3D2AD8": { - "DeletionPolicy": "Retain", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "commercialAccountId", - "AttributeType": "S", - }, - ], - "BillingMode": "PAY_PER_REQUEST", - "KeySchema": [ - { - "AttributeName": "commercialAccountId", - "KeyType": "HASH", - }, - ], - "PointInTimeRecoverySpecification": { - "PointInTimeRecoveryEnabled": true, - }, - "SSESpecification": { - "KMSMasterKeyId": { - "Fn::GetAtt": [ - "GovCloudTableKeyE9635728", - "Arn", - ], - }, - "SSEEnabled": true, - "SSEType": "KMS", - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-aws-service-access.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-aws-service-access.test.ts.snap deleted file mode 100644 index f11752e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-aws-service-access.test.ts.snap +++ /dev/null @@ -1,152 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EnableAwsServiceAccess Construct(EnableAwsServiceAccess): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71": { - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderRole59F76BA2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DisableAWSServiceAccess", - "organizations:EnableAwsServiceAccess", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "EnableAwsServiceAccessFCD8AE04": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderLogGroupEB99134A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsEnableAwsServiceAccessCustomResourceProviderHandlerDCD56D71", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "s3.amazonaws.com", - }, - "Type": "Custom::EnableAwsServiceAccess", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-policy-type.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-policy-type.test.ts.snap deleted file mode 100644 index be036ee..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/enable-policy-type.test.ts.snap +++ /dev/null @@ -1,175 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EnablePolicyType Construct(EnablePolicyType): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1": { - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnablePolicyTypeCustomResourceProviderRoleAE71B2CA": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:DisablePolicyType", - "organizations:EnablePolicyType", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribeEffectivePolicy", - "organizations:DescribePolicy", - "organizations:DisableAWSServiceAccess", - "organizations:DetachPolicy", - "organizations:DeletePolicy", - "organizations:DescribeAccount", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:EnableAWSServiceAccess", - "organizations:ListCreateAccountStatus", - "organizations:UpdatePolicy", - "organizations:DescribeOrganizationalUnit", - "organizations:AttachPolicy", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - "organizations:CreatePolicy", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "EnablePolicyTypeA517D946": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnablePolicyTypeCustomResourceProviderLogGroup81BE8EF5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnablePolicyTypeCustomResourceProviderHandlerC244F9E1", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "policyType": "SERVICE_CONTROL_POLICY", - }, - "Type": "Custom::EnablePolicyType", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/move-accounts.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/move-accounts.test.ts.snap deleted file mode 100644 index ecd6bc7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/move-accounts.test.ts.snap +++ /dev/null @@ -1,382 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MoveAccounts Construct(MoveAccounts): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomCWLKey7119CF89": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomLambdaKey2287F5A9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomTable90F1C476": { - "DeletionPolicy": "Retain", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "dataType", - "AttributeType": "S", - }, - ], - "KeySchema": [ - { - "AttributeName": "dataType", - "KeyType": "HASH", - }, - ], - "ProvisionedThroughput": { - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5, - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Retain", - }, - "MoveAccounts99C70985": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "MoveAccountsMoveAccountsFunctionLogGroupFAEDB078", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "MoveAccountsMoveAccountsProviderframeworkonEvent5D4E26AA", - "Arn", - ], - }, - "commitId": "sample-commit-id", - "configTableName": { - "Ref": "CustomTable90F1C476", - }, - "globalRegion": "us-west-2", - "stackName": "Default", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::MoveAccounts", - "UpdateReplacePolicy": "Delete", - }, - "MoveAccountsMoveAccountsFunctionD31A3773": { - "DependsOn": [ - "MoveAccountsMoveAccountsFunctionServiceRoleDefaultPolicyB573AE3E", - "MoveAccountsMoveAccountsFunctionServiceRoleADDE3D6B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Moves accounts to conform account config", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "CustomLambdaKey2287F5A9", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "MoveAccountsMoveAccountsFunctionServiceRoleADDE3D6B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "MoveAccountsMoveAccountsFunctionLogGroupFAEDB078": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomCWLKey7119CF89", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "MoveAccountsMoveAccountsFunctionD31A3773", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "MoveAccountsMoveAccountsFunctionServiceRoleADDE3D6B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "MoveAccountsMoveAccountsFunctionServiceRoleDefaultPolicyB573AE3E": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccountsForParent", - "organizations:ListRoots", - "organizations:ListOrganizationalUnitsForParent", - "organizations:MoveAccount", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrganizationsAccess", - }, - { - "Action": "dynamodb:Query", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomTable90F1C476", - "Arn", - ], - }, - "Sid": "DynamodbTableAccess", - }, - { - "Action": "cloudformation:DescribeStacks", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":cloudformation:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":stack/Default*", - ], - ], - }, - "Sid": "CloudformationAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "MoveAccountsMoveAccountsFunctionServiceRoleDefaultPolicyB573AE3E", - "Roles": [ - { - "Ref": "MoveAccountsMoveAccountsFunctionServiceRoleADDE3D6B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "MoveAccountsMoveAccountsProviderframeworkonEvent5D4E26AA": { - "DependsOn": [ - "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleDefaultPolicy04895FAA", - "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleB3998377", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/MoveAccounts/MoveAccountsProvider)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "MoveAccountsMoveAccountsFunctionD31A3773", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleB3998377", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleB3998377": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleDefaultPolicy04895FAA": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "MoveAccountsMoveAccountsFunctionD31A3773", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MoveAccountsMoveAccountsFunctionD31A3773", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleDefaultPolicy04895FAA", - "Roles": [ - { - "Ref": "MoveAccountsMoveAccountsProviderframeworkonEventServiceRoleB3998377", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/organizational-units.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/organizational-units.test.ts.snap deleted file mode 100644 index 1df480d..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/organizational-units.test.ts.snap +++ /dev/null @@ -1,200 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OrganizationalUnits Construct(OrganizationalUnit): Snapshot Test 1`] = ` -{ - "Resources": { - "ConfigTable5CD72349": { - "DeletionPolicy": "Retain", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "dataType", - "AttributeType": "S", - }, - ], - "KeySchema": [ - { - "AttributeName": "dataType", - "KeyType": "HASH", - }, - ], - "ProvisionedThroughput": { - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5, - }, - }, - "Type": "AWS::DynamoDB::Table", - "UpdateReplacePolicy": "Retain", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderHandler4596F0BC": { - "DependsOn": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderRole4B8B81B0", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderRole4B8B81B0", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderLogGroup5A9D7A53": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderHandler4596F0BC", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderRole4B8B81B0": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreateOrganizationalUnit", - "organizations:ListOrganizationalUnitsForParent", - "organizations:ListRoots", - "organizations:UpdateOrganizationalUnit", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "organizations", - }, - { - "Action": [ - "dynamodb:UpdateItem", - "dynamodb:Query", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ConfigTable5CD72349", - "Arn", - ], - }, - ], - "Sid": "dynamodb", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "OrganizationalUnits30245726": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderLogGroup5A9D7A53", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreateOrganizationalUnitsCustomResourceProviderHandler4596F0BC", - "Arn", - ], - }, - "commitId": "bda32a39", - "configTableName": { - "Ref": "ConfigTable5CD72349", - }, - "controlTowerEnabled": true, - "organizationsEnabled": true, - "partition": { - "Ref": "AWS::Partition", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::CreateOrganizationalUnits", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy-attachment.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy-attachment.test.ts.snap deleted file mode 100644 index 4dd15e3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy-attachment.test.ts.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PolicyAttachment Construct(PolicyAttachment): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202": { - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsAttachPolicyCustomResourceProviderRole051E00A6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:AttachPolicy", - "organizations:DetachPolicy", - "organizations:ListPoliciesForTarget", - "organizations:ListTagsForResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "PolicyAttachmentE9E858C2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderLogGroup03FEC039", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsAttachPolicyCustomResourceProviderHandlerB3233202", - "Arn", - ], - }, - "configPolicyNames": [ - "AcceleratorGuardrails1", - "AcceleratorGuardrails2", - ], - "partition": { - "Ref": "AWS::Partition", - }, - "policyId": "policyId", - "policyTagKey": "AWSAcceleratorManaged", - "strategy": "deny-list", - "targetId": "targetId", - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::AttachPolicy", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy.test.ts.snap deleted file mode 100644 index eda675b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/policy.test.ts.snap +++ /dev/null @@ -1,197 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Policy Construct(Policy): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619": { - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Organizations create policy", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsCreatePolicyCustomResourceProviderRoleBA0ADB43": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreatePolicy", - "organizations:DeletePolicy", - "organizations:DetachPolicy", - "organizations:ListPolicies", - "organizations:ListTargetsForPolicy", - "organizations:UpdatePolicy", - "organizations:TagResource", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "s3:GetObject", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::", - { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Policy23B91518": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderLogGroup019B74A9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsCreatePolicyCustomResourceProviderHandler7A188619", - "Arn", - ], - }, - "bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "description": "Testing Policy construct", - "key": "cc6645b789ecec398524e5ade5814359a8403b9c8c2c4b54b7c4e7ba20348a98.ts", - "name": "TestPolicy", - "partition": "aws", - "policyTagKey": "AWSAcceleratorManaged", - "strategy": "deny-list", - "tags": [ - { - "Key": "name", - "Value": "TestPolicy", - }, - { - "Key": "usage", - "Value": "ConstructTest", - }, - ], - "type": "SERVICE_CONTROL_POLICY", - "uuid": "REPLACED-MD5", - }, - "Type": "Custom::CreatePolicy", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/register-delegated-administrator.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/register-delegated-administrator.test.ts.snap deleted file mode 100644 index 93aa97e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/register-delegated-administrator.test.ts.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RegisterDelegatedAdministrator Construct(RegisterDelegatedAdministrator): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C": { - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderRole4B3EAD1B": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DeregisterDelegatedAdministrator", - "organizations:RegisterDelegatedAdministrator", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "RegisterDelegatedAdministratorF9498A1E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderLogGroupE715E766", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomOrganizationsRegisterDelegatedAdministratorCustomResourceProviderHandlerFAEA655C", - "Arn", - ], - }, - "accountId": { - "Ref": "AWS::AccountId", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "servicePrincipal": "macie.amazonaws.com", - }, - "Type": "Custom::OrganizationsRegisterDelegatedAdministrator", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/validate-scp-count.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/validate-scp-count.test.ts.snap deleted file mode 100644 index 07ce563..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/__snapshots__/validate-scp-count.test.ts.snap +++ /dev/null @@ -1,184 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ValidateScpCount Construct(ValidateScpCount): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomValidateScpCountCustomResourceProviderHandlerEE8CD0C0": { - "DependsOn": [ - "CustomValidateScpCountCustomResourceProviderRole6657C548", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomValidateScpCountCustomResourceProviderRole6657C548", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomValidateScpCountCustomResourceProviderLogGroup79E9A7D0": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomValidateScpCountCustomResourceProviderHandlerEE8CD0C0", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomValidateScpCountCustomResourceProviderRole6657C548": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:ListRoots", - "organizations:ListPoliciesForTarget", - "organizations:ListTargetsForPolicy", - "organizations:DescribePolicy", - "organizations:DescribeAccount", - "organizations:ListPolicies", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - "organizations:DescribeOrganizationalUnit", - "organizations:ListParents", - "organizations:ListOrganizationalUnitsForParent", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateScpCount271EA5CB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomValidateScpCountCustomResourceProviderLogGroup79E9A7D0", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomValidateScpCountCustomResourceProviderHandlerEE8CD0C0", - "Arn", - ], - }, - "accounts": [ - { - "accountId": "000000000000", - "name": "Admin", - }, - ], - "organizationUnits": [ - { - "id": "o-1234567", - "name": "batman", - }, - ], - "partition": { - "Ref": "AWS::Partition", - }, - "scps": [ - { - "appliedScpName": [ - "JusticeLeague", - "Gotham", - ], - "orgEntity": "Test", - "orgEntityId": "o-1234567", - "orgEntityType": "OU", - }, - ], - }, - "Type": "Custom::ValidateScpCount", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/account.test.ts deleted file mode 100644 index 0b46a7e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/account.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Account } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(Account): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new Account(stack, 'Account', { - acceleratorConfigTable: new cdk.aws_dynamodb.Table(stack, 'ConfigTable', { - partitionKey: { name: 'dataType', type: cdk.aws_dynamodb.AttributeType.STRING }, - }), - commitId: 'abcd123456789', - assumeRoleName: 'AWSControlTowerExecution', - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * Account construct test - */ -describe('Account', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/create-accounts.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/create-accounts.test.ts deleted file mode 100644 index fff0dec..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/create-accounts.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CreateOrganizationAccounts } from '../../lib/aws-organizations/create-accounts'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ConfigServiceTags): '; - -const app = new cdk.App(); - -// Create stack for native Cfn construct -const stack = new cdk.Stack(app, 'Stack', {}); - -const newOrgAccountsTable = new cdk.aws_dynamodb.Table(stack, 'NewOrgAccounts', { - partitionKey: { name: 'accountEmail', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: new cdk.aws_kms.Key(stack, 'TableKey', {}), - removalPolicy: cdk.RemovalPolicy.DESTROY, - pointInTimeRecovery: true, -}); - -const govCloudAccountMappingTable = new cdk.aws_dynamodb.Table(stack, 'govCloudAccountMapping', { - partitionKey: { name: 'commercialAccountId', type: cdk.aws_dynamodb.AttributeType.STRING }, - billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: cdk.aws_dynamodb.TableEncryption.CUSTOMER_MANAGED, - encryptionKey: new cdk.aws_kms.Key(stack, 'GovCloudTableKey', {}), - pointInTimeRecovery: true, -}); - -new CreateOrganizationAccounts(stack, 'CreateOrganizationAccounts', { - newOrgAccountsTable: newOrgAccountsTable, - govCloudAccountMappingTable: govCloudAccountMappingTable, - accountRoleName: 'managementAccountAccessRole', - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * Report Definition construct test - */ -describe('ReportDefinition', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-aws-service-access.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-aws-service-access.test.ts deleted file mode 100644 index 6a84ed5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-aws-service-access.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { EnableAwsServiceAccess } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(EnableAwsServiceAccess): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new EnableAwsServiceAccess(stack, 'EnableAwsServiceAccess', { - servicePrincipal: 's3.amazonaws.com', - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * EnableAwsServiceAccess construct test - */ -describe('EnableAwsServiceAccess', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type.test.ts deleted file mode 100644 index 15c1be9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { EnablePolicyType, PolicyTypeEnum } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(EnablePolicyType): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new EnablePolicyType(stack, 'EnablePolicyType', { - policyType: PolicyTypeEnum.SERVICE_CONTROL_POLICY, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); -/** - * EnablePolicyType construct test - */ -describe('EnablePolicyType', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type/index.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type/index.test.ts deleted file mode 100644 index 58d7bcb..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/enable-policy-type/index.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { EnablePolicyTypeCommand, ListRootsCommand, OrganizationsClient } from '@aws-sdk/client-organizations'; -import { afterEach, beforeEach, expect, it } from '@jest/globals'; -import { AwsClientStub, mockClient } from 'aws-sdk-client-mock'; -import { handler } from '../../../lib/aws-organizations/enable-policy-type/index'; -import { - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceDeleteEvent, -} from '../../../lib/lza-custom-resource'; - -let orgsMock: AwsClientStub; - -beforeEach(() => { - orgsMock = mockClient(OrganizationsClient); -}); - -afterEach(() => { - orgsMock.restore(); -}); - -// Given -const createEventScp: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EnablePolicyType', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - partition: 'aws', - policyType: 'SERVICE_CONTROL_POLICY', - ServiceToken: 'example-service-token', - }, -}; - -const createEventTag: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EnablePolicyType', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - partition: 'aws', - policyType: 'TAG_POLICY', - ServiceToken: 'example-service-token', - }, -}; - -const createEventBackup: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EnablePolicyType', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - partition: 'aws', - policyType: 'BACKUP_POLICY', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-organizations/enable-policy-type create event -- policies already enabled', async () => { - orgsMock.on(ListRootsCommand).resolves({ - Roots: [ - { - Id: 'r-123456', - Name: 'Root', - PolicyTypes: [ - { Type: 'SERVICE_CONTROL_POLICY', Status: 'ENABLED' }, - { Type: 'TAG_POLICY', Status: 'ENABLED' }, - { Type: 'BACKUP_POLICY', Status: 'ENABLED' }, - ], - }, - ], - }); - const scpResponse = await handler(createEventScp); - const tagResponse = await handler(createEventTag); - const backupResponse = await handler(createEventBackup); - // Then - expect(scpResponse?.Status).toEqual('SUCCESS'); - expect(scpResponse?.PhysicalResourceId).toEqual('SERVICE_CONTROL_POLICY'); - expect(tagResponse?.Status).toEqual('SUCCESS'); - expect(tagResponse?.PhysicalResourceId).toEqual('TAG_POLICY'); - expect(backupResponse?.Status).toEqual('SUCCESS'); - expect(backupResponse?.PhysicalResourceId).toEqual('BACKUP_POLICY'); -}); - -// When -it('@aws-accelerator/constructs/aws-organizations/enable-policy-type create event -- policies not enabled', async () => { - orgsMock.on(ListRootsCommand).resolves({ - Roots: [{ Id: 'r-123456', Name: 'Root' }], - }); - orgsMock.on(EnablePolicyTypeCommand).resolves({ - Root: { - Id: 'r-123456', - Name: 'Root', - PolicyTypes: [ - { Type: 'SERVICE_CONTROL_POLICY', Status: 'ENABLED' }, - { Type: 'TAG_POLICY', Status: 'ENABLED' }, - { Type: 'BACKUP_POLICY', Status: 'ENABLED' }, - ], - }, - }); - const scpResponse = await handler(createEventScp); - const tagResponse = await handler(createEventTag); - const backupResponse = await handler(createEventBackup); - // Then - expect(scpResponse?.Status).toEqual('SUCCESS'); - expect(scpResponse?.PhysicalResourceId).toEqual('SERVICE_CONTROL_POLICY'); - expect(tagResponse?.Status).toEqual('SUCCESS'); - expect(tagResponse?.PhysicalResourceId).toEqual('TAG_POLICY'); - expect(backupResponse?.Status).toEqual('SUCCESS'); - expect(backupResponse?.PhysicalResourceId).toEqual('BACKUP_POLICY'); -}); - -// Given -const deleteEventScp: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EnablePolicyType', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'SERVICE_CONTROL_POLICY', - ResourceProperties: { - partition: 'aws', - policyType: 'SERVICE_CONTROL_POLICY', - ServiceToken: 'example-service-token', - }, -}; - -const deleteEventTag: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EnablePolicyType', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'TAG_POLICY', - ResourceProperties: { - partition: 'aws', - policyType: 'TAG_POLICY', - ServiceToken: 'example-service-token', - }, -}; - -const deleteEventBackup: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::EnablePolicyType', - LogicalResourceId: 'example-logical-resource-id', - PhysicalResourceId: 'BACKUP_POLICY', - ResourceProperties: { - partition: 'aws', - policyType: 'BACKUP_POLICY', - ServiceToken: 'example-service-token', - }, -}; - -// When -it('@aws-accelerator/constructs/aws-organizations/enable-policy-type delete event', async () => { - const scpResponse = await handler(deleteEventScp); - const tagResponse = await handler(deleteEventTag); - const backupResponse = await handler(deleteEventBackup); - // Then - expect(scpResponse?.Status).toEqual('SUCCESS'); - expect(scpResponse?.PhysicalResourceId).toEqual('SERVICE_CONTROL_POLICY'); - expect(tagResponse?.Status).toEqual('SUCCESS'); - expect(tagResponse?.PhysicalResourceId).toEqual('TAG_POLICY'); - expect(backupResponse?.Status).toEqual('SUCCESS'); - expect(backupResponse?.PhysicalResourceId).toEqual('BACKUP_POLICY'); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/integ-test-enable-policy-type/integ.enable-policy-type.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/integ-test-enable-policy-type/integ.enable-policy-type.ts deleted file mode 100644 index 5aac63c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/integ-test-enable-policy-type/integ.enable-policy-type.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Steps to run - * - install cdk in the account and bootstrap it - * - from source run - * yarn integ-runner --update-on-failed --parallel-regions us-east-1 --directory ./packages/@aws-accelerator/constructs/test/aws-organizations/integ-test-enable-policy-type --language typescript --force - * - in the target account it will create a stack, run assertion and delete the stack - */ - -import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { App, Aspects, CfnResource, IAspect, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; -import { Construct, IConstruct } from 'constructs'; -import { EnablePolicyType, PolicyTypeEnum } from '../../../lib/aws-organizations/enable-policy-type'; -import { AcceleratorAspects } from '../../../../accelerator/lib/accelerator-aspects'; - -/** - * Aspect for setting all removal policies to DESTROY - */ -class ApplyDestroyPolicyAspect implements IAspect { - public visit(node: IConstruct): void { - if (node instanceof CfnResource) { - node.applyRemovalPolicy(RemovalPolicy.DESTROY); - } - } -} - -// CDK App for Integration Tests -const app = new App(); -new AcceleratorAspects(app, 'aws', false); - -export class EnablePolicyTypeStack extends Stack { - constructor(scope: Construct, id: string, props: StackProps) { - super(scope, id, props); - - new EnablePolicyType(this, 'EnableScp', { - policyType: PolicyTypeEnum.SERVICE_CONTROL_POLICY, - logRetentionInDays: 3653, - }); - - new EnablePolicyType(this, 'EnableTag', { - policyType: PolicyTypeEnum.TAG_POLICY, - logRetentionInDays: 3653, - }); - - new EnablePolicyType(this, 'EnableBackup', { - policyType: PolicyTypeEnum.BACKUP_POLICY, - logRetentionInDays: 3653, - }); - - new EnablePolicyType(this, 'EnableAi', { - policyType: PolicyTypeEnum.AISERVICES_OPT_OUT_POLICY, - logRetentionInDays: 3653, - }); - - Aspects.of(this).add(new ApplyDestroyPolicyAspect()); - } -} - -// Stack under test -const stackUnderTest = new EnablePolicyTypeStack(app, 'EnablePolicyTypeTestStack', { - description: 'Stack for enable Organizations policy type integration tests', -}); - -// Initialize Integ Test construct -const integ = new IntegTest(app, 'EnablePolicyTypeTest', { - testCases: [stackUnderTest], // Define a list of cases for this test - cdkCommandOptions: { - deploy: { - args: { - rollback: false, - }, - }, - // Customize the integ-runner parameters - destroy: { - args: { - force: true, - }, - }, - }, - regions: [stackUnderTest.region], -}); - -integ.assertions.awsApiCall('Organizations', 'listRoots', {}).expect( - ExpectedResult.objectLike({ - Roots: [ - { - Name: 'Root', - PolicyTypes: [ - { Type: 'AISERVICES_OPT_OUT_POLICY', Status: 'ENABLED' }, - { Type: 'BACKUP_POLICY', Status: 'ENABLED' }, - { Type: 'TAG_POLICY', Status: 'ENABLED' }, - { Type: 'SERVICE_CONTROL_POLICY', Status: 'ENABLED' }, - ], - }, - ], - }), -); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/move-accounts.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/move-accounts.test.ts deleted file mode 100644 index 5bd2021..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/move-accounts.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { MoveAccounts } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(MoveAccounts): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new MoveAccounts(stack, 'MoveAccounts', { - globalRegion: 'us-west-2', - commitId: 'sample-commit-id', - managementAccountId: stack.account, - configTable: new cdk.aws_dynamodb.Table(stack, 'CustomTable', { - partitionKey: { name: 'dataType', type: cdk.aws_dynamodb.AttributeType.STRING }, - }), - lambdaKmsKey: new cdk.aws_kms.Key(stack, 'CustomLambdaKey', {}), - cloudWatchLogsKmsKey: new cdk.aws_kms.Key(stack, 'CustomCWLKey', {}), - cloudWatchLogRetentionInDays: 365, - controlTower: false, -}); -/** - * MoveAccounts construct test - */ -describe('MoveAccounts', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/organizational-units.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/organizational-units.test.ts deleted file mode 100644 index 5b8b7b7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/organizational-units.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { OrganizationalUnits } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(OrganizationalUnit): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new OrganizationalUnits(stack, 'OrganizationalUnits', { - acceleratorConfigTable: new cdk.aws_dynamodb.Table(stack, 'ConfigTable', { - partitionKey: { name: 'dataType', type: cdk.aws_dynamodb.AttributeType.STRING }, - }), - commitId: 'bda32a39', - controlTowerEnabled: true, - organizationsEnabled: true, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 365, -}); - -/** - * OrganizationalUnit construct test - */ -describe('OrganizationalUnits', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/policy-attachment.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/policy-attachment.test.ts deleted file mode 100644 index 8557f4b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/policy-attachment.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Stack } from 'aws-cdk-lib'; -import { Key } from 'aws-cdk-lib/aws-kms'; -import { PolicyAttachment } from '../../lib/aws-organizations/policy-attachment'; -import { PolicyType } from '../../lib/aws-organizations/policy'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(PolicyAttachment): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new Stack(); - -new PolicyAttachment(stack, 'PolicyAttachment', { - policyId: 'policyId', - targetId: 'targetId', - type: PolicyType.SERVICE_CONTROL_POLICY, - strategy: 'deny-list', - configPolicyNames: ['AcceleratorGuardrails1', 'AcceleratorGuardrails2'], - acceleratorPrefix: 'AWSAccelerator', - kmsKey: new Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * PolicyAttachment construct test - */ -describe('PolicyAttachment', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/policy.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/policy.test.ts deleted file mode 100644 index a61023f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/policy.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Stack } from 'aws-cdk-lib'; -import { Key } from 'aws-cdk-lib/aws-kms'; -import { Policy, PolicyType } from '../../lib/aws-organizations/policy'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(Policy): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new Stack(); - -new Policy(stack, 'Policy', { - path: __filename, - name: 'TestPolicy', - partition: 'aws', - description: 'Testing Policy construct', - type: PolicyType.SERVICE_CONTROL_POLICY, - strategy: 'deny-list', - acceleratorPrefix: 'AWSAccelerator', - tags: [ - { Key: 'name', Value: 'TestPolicy' }, - { Key: 'usage', Value: 'ConstructTest' }, - ], - kmsKey: new Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * Policy construct test - */ -describe('Policy', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/register-delegated-administrator.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/register-delegated-administrator.test.ts deleted file mode 100644 index eb967c7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/register-delegated-administrator.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { RegisterDelegatedAdministrator } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(RegisterDelegatedAdministrator): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new RegisterDelegatedAdministrator(stack, 'RegisterDelegatedAdministrator', { - servicePrincipal: 'macie.amazonaws.com', - accountId: stack.account, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * RegisterDelegatedAdministrator construct test - */ -describe('RegisterDelegatedAdministrator', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-organizations/validate-scp-count.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-organizations/validate-scp-count.test.ts deleted file mode 100644 index 2cca8d3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-organizations/validate-scp-count.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ValidateScpCount } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ValidateScpCount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ValidateScpCount(stack, 'ValidateScpCount', { - organizationUnits: [{ id: 'o-1234567', name: 'batman' }], - accounts: [{ accountId: '000000000000', name: 'Admin' }], - scps: [ - { orgEntity: 'Test', orgEntityId: 'o-1234567', orgEntityType: 'OU', appliedScpName: ['JusticeLeague', 'Gotham'] }, - ], - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); -/** - * ValidateScpCount construct test - */ -describe('ValidateScpCount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/enable-sharing-with-aws-organization.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/enable-sharing-with-aws-organization.test.ts.snap deleted file mode 100644 index 48e6c89..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/enable-sharing-with-aws-organization.test.ts.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EnableSharingWithAwsOrganization Construct(EnableSharingWithAwsOrganization): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398": { - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderRole4FE5EBD7": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ram:EnableSharingWithAwsOrganization", - "iam:CreateServiceLinkedRole", - "organizations:EnableAWSServiceAccess", - "organizations:ListAWSServiceAccessForOrganization", - "organizations:DescribeOrganization", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "EnableSharingWithAwsOrganization81D5714F": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderLogGroupDD3A7DB5", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomEnableSharingWithAwsOrganizationCustomResourceProviderHandler405D7398", - "Arn", - ], - }, - }, - "Type": "Custom::EnableSharingWithAwsOrganization", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/resource-share.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/resource-share.test.ts.snap deleted file mode 100644 index e824723..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/resource-share.test.ts.snap +++ /dev/null @@ -1,193 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResourceShare Construct(ResourceShare): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ResourceShareTestResourceShareResourceShare8D7B67C7": { - "Properties": { - "AllowExternalPrincipals": true, - "Name": "TestResourceShare", - "PermissionArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::test-bucket-1-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::test-bucket-2-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - ], - "Principals": [ - "accountID", - "organizationUnitId", - ], - "ResourceArns": [ - "ec2:TransitGateway", - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - }, -} -`; - -exports[`ResourceShareItem Construct(ResourceShare): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ResourceShareTestResourceShareResourceShare8D7B67C7": { - "Properties": { - "AllowExternalPrincipals": true, - "Name": "TestResourceShare", - "PermissionArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::test-bucket-1-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::test-bucket-2-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - ], - "Principals": [ - "accountID", - "organizationUnitId", - ], - "ResourceArns": [ - "ec2:TransitGateway", - ], - }, - "Type": "AWS::RAM::ResourceShare", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/share-subnet-tags.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/share-subnet-tags.test.ts.snap deleted file mode 100644 index f07be7c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/__snapshots__/share-subnet-tags.test.ts.snap +++ /dev/null @@ -1,209 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ShareSubnetTags Construct(ShareSubnetTest): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomShareSubnetTagsCustomResourceProviderHandler4A04C5EC": { - "DependsOn": [ - "CustomShareSubnetTagsCustomResourceProviderRole2582495C", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomShareSubnetTagsCustomResourceProviderRole2582495C", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomShareSubnetTagsCustomResourceProviderLogGroupA1F3F80A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomShareSubnetTagsCustomResourceProviderHandler4A04C5EC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomShareSubnetTagsCustomResourceProviderRole2582495C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DeleteTags", - "ec2:CreateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":*:subnet/*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ec2:", - { - "Ref": "AWS::Region", - }, - ":*:vpc/*", - ], - ], - }, - ], - }, - { - "Action": [ - "ec2:DescribeTags", - "ec2:DescribeVpcs", - "ec2:DescribeSubnets", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ShareSubnetTagsA9A7452E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomShareSubnetTagsCustomResourceProviderLogGroupA1F3F80A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomShareSubnetTagsCustomResourceProviderHandler4A04C5EC", - "Arn", - ], - }, - "acceleratorSsmParamPrefix": "/accelerator", - "sharedSubnetId": "abcdefg123", - "sharedSubnetName": "TestSubnet", - "subnetTags": [ - { - "key": "Key", - "value": "value", - }, - ], - "vpcName": "TestVpc", - }, - "Type": "Custom::ShareSubnetTags", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/enable-sharing-with-aws-organization.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ram/enable-sharing-with-aws-organization.test.ts deleted file mode 100644 index 5eff903..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/enable-sharing-with-aws-organization.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { EnableSharingWithAwsOrganization } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(EnableSharingWithAwsOrganization): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new EnableSharingWithAwsOrganization(stack, 'EnableSharingWithAwsOrganization', { - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * HostedZone construct test - */ -describe('EnableSharingWithAwsOrganization', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/resource-share.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ram/resource-share.test.ts deleted file mode 100644 index 13d2c42..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/resource-share.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ResourceShare, ResourceShareOwner, ResourceShareItem } from '../../lib/aws-ram/resource-share'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(ResourceShare): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ResourceShare(stack, 'ResourceShare', { - name: 'TestResourceShare', - allowExternalPrincipals: true, - permissionArns: [ - `arn:${stack.partition}:s3:::test-bucket-1-${stack.account}-${stack.region}`, - `arn:${stack.partition}:s3:::test-bucket-2-${stack.account}-${stack.region}`, - ], - - principals: ['accountID', 'organizationUnitId'], - resourceArns: ['ec2:TransitGateway'], -}); - -const stackLookup = new cdk.Stack(); - -// Lookup resource share -ResourceShare.fromLookup(stackLookup, 'ResourceShareLookup', { - resourceShareOwner: ResourceShareOwner.OTHER_ACCOUNTS, - resourceShareName: 'ResourceShareName', - owningAccountId: '111111111111', -}); - -/** - * ResourceShare construct test - */ -describe('ResourceShare', () => { - snapShotTest(testNamePrefix, stack); -}); - -//Lookup Resource share item -// const resourceShare: IResourceShare = -ResourceShareItem.fromLookup(stackLookup, 'ResourceShareItem', { - logRetentionInDays: 7, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - resourceShareItemType: 'resourceShareItemType', - resourceShare: ResourceShare.fromLookup(stackLookup, 'ResourceShareItemLookup', { - resourceShareOwner: ResourceShareOwner.OTHER_ACCOUNTS, - resourceShareName: 'ResourceShareName', - owningAccountId: '111111111111', - }), -}); - -/** - * ResourceShare construct test - */ -describe('ResourceShareItem', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags.test.ts deleted file mode 100644 index af6e009..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ShareSubnetTags } from '../../lib/aws-ram/share-subnet-tags'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(ShareSubnetTest): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ShareSubnetTags(stack, 'ShareSubnetTags', { - subnetTags: [{ key: 'Key', value: 'value' }], - sharedSubnetId: 'abcdefg123', - owningAccountId: '9999999999', - vpcName: 'TestVpc', - subnetName: 'TestSubnet', - resourceLoggingKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, - acceleratorSsmParamPrefix: '/accelerator', -}); - -/** - * ShareTransitGatewayTags construct test - */ -describe('ShareSubnetTags', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags/index.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags/index.test.ts deleted file mode 100644 index 1db63db..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ram/share-subnet-tags/index.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { handler } from '../../../lib/aws-ram/share-subnet-tags/index'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { - EC2Client, - DescribeTagsCommand, - CreateTagsCommand, - DescribeSubnetsCommand, - DeleteTagsCommand, -} from '@aws-sdk/client-ec2'; -import { - SSMClient, - GetParameterCommand, - DeleteParameterCommand, - // PutParameterCommand, - // ParameterNotFound, -} from '@aws-sdk/client-ssm'; -import { - CloudFormationCustomResourceCreateEvent, - CloudFormationCustomResourceDeleteEvent, -} from '../../../lib/lza-custom-resource'; -import { expect, it, beforeEach, afterEach } from '@jest/globals'; - -let ssmMock: AwsClientStub; -let ec2Mock: AwsClientStub; - -beforeEach(() => { - ssmMock = mockClient(SSMClient); - ec2Mock = mockClient(EC2Client); -}); - -afterEach(() => { - ssmMock.restore(); - ec2Mock.restore(); -}); - -// Given -const createEvent: CloudFormationCustomResourceCreateEvent = { - RequestType: 'Create', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::ShareSubnetTags', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - ServiceToken: 'example-service-token', - vpcTags: [ - { key: 'subnetKey1', value: 'subnetValue1' }, - { key: 'subnetKey2', value: 'subnetValue2' }, - ], - subnetTags: [ - { key: 'subnetKey1', value: 'subnetValue1' }, - { key: 'subnetKey2', value: 'subnetValue2' }, - ], - sharedSubnetName: 'sharedSubnet', - vpcName: 'vpcName', - acceleratorSsmParamPrefix: '/accel/prefix', - }, -}; -const sharedSubnetSsmPath = `${createEvent.ResourceProperties['acceleratorSsmParamPrefix']}/shared/network/vpc/${createEvent.ResourceProperties['vpcName']}/subnet/${createEvent.ResourceProperties['sharedSubnetName']}/id`; -const vpcSsmPath = `${createEvent.ResourceProperties['acceleratorSsmParamPrefix']}/shared/network/vpc/${createEvent.ResourceProperties['vpcName']}/id`; - -// When -it('@aws-accelerator/constructs-aws-ram-share-subnet-tags create event subnet and vpc needs no update', async () => { - // when - parameter already exists - ssmMock - .on(GetParameterCommand, { Name: sharedSubnetSsmPath }) - .resolves({ Parameter: { Name: sharedSubnetSsmPath, Value: 'subnet-1234' } }); - // when - tags are same - ec2Mock.on(DescribeTagsCommand).resolves({ - Tags: [ - { Key: 'subnetKey1', Value: 'subnetValue1', ResourceId: 'subnet-1234', ResourceType: 'subnet' }, - { Key: 'subnetKey2', Value: 'subnetValue2', ResourceId: 'subnet-1234', ResourceType: 'subnet' }, - ], - }); - // when - subnet is found with exact tags - ec2Mock.on(DescribeSubnetsCommand).resolves({ - Subnets: [ - { - SubnetId: 'subnet-1234', - VpcId: 'vpc-1234', - AvailabilityZone: 'us-east-1a', - AvailabilityZoneId: 'use1-az1', - CidrBlock: '10.10.10.0/24', - MapPublicIpOnLaunch: true, - AvailableIpAddressCount: 253, - DefaultForAz: true, - State: 'available', - OwnerId: '123456789012', - AssignIpv6AddressOnCreation: false, - Ipv6CidrBlockAssociationSet: [], - Tags: [ - { Key: 'subnetKey1', Value: 'subnetValue1' }, - { Key: 'subnetKey2', Value: 'subnetValue2' }, - ], - }, - ], - }); - // when - parameter already exists - ssmMock - .on(GetParameterCommand, { Name: vpcSsmPath }) - .resolves({ Parameter: { Name: vpcSsmPath, Value: 'vpc-1234' } }); - const response = await handler(createEvent); - // then - response Status is SUCCESS - expect(response?.Status).toBe('SUCCESS'); -}); - -it('@aws-accelerator/constructs-aws-ram-share-subnet-tags create event subnet and vpc exist but need update', async () => { - // when - parameter exist - ssmMock - .on(GetParameterCommand, { Name: sharedSubnetSsmPath }) - .resolves({ Parameter: { Name: sharedSubnetSsmPath, Value: 'subnet-1234' } }); - // when - tags are same - ec2Mock.on(DescribeTagsCommand).resolves({ - Tags: [{ Key: 'Key1', Value: 'Value1', ResourceId: 'subnet-1234', ResourceType: 'subnet' }], - }); - // when - tags are same - ec2Mock.on(DescribeTagsCommand).resolves({ - Tags: [{ Key: 'Key1', Value: 'Value1' }], - }); - // when - subnet is found with different tags - ec2Mock.on(DescribeSubnetsCommand).resolves({ - Subnets: [ - { - SubnetId: 'subnet-1234', - VpcId: 'vpc-1234', - AvailabilityZone: 'us-east-1a', - AvailabilityZoneId: 'use1-az1', - CidrBlock: '10.10.10.0/24', - MapPublicIpOnLaunch: true, - AvailableIpAddressCount: 253, - DefaultForAz: true, - State: 'available', - OwnerId: '123456789012', - AssignIpv6AddressOnCreation: false, - Ipv6CidrBlockAssociationSet: [], - Tags: [{ Key: 'Key1', Value: 'Value1' }], - }, - ], - }); - // when - parameter already exists - ssmMock - .on(GetParameterCommand, { Name: vpcSsmPath }) - .resolves({ Parameter: { Name: vpcSsmPath, Value: 'vpc-1234' } }); - ec2Mock.on(DeleteTagsCommand).resolves({}); - ec2Mock.on(CreateTagsCommand).resolves({}); - const response = await handler(createEvent); - // then - response Status is SUCCESS - expect(response?.Status).toBe('SUCCESS'); -}); - -// Given -const deleteEvent: CloudFormationCustomResourceDeleteEvent = { - RequestType: 'Delete', - ResponseURL: 'https://example.com', - ServiceToken: 'example-service-token', - StackId: 'example-stack-id', - RequestId: 'example-create-request-id', - ResourceType: 'Custom::ShareSubnetTags', - PhysicalResourceId: 'example-physical-resource-id', - LogicalResourceId: 'example-logical-resource-id', - ResourceProperties: { - ServiceToken: 'example-service-token', - vpcTags: [ - { key: 'subnetKey1', value: 'subnetValue1' }, - { key: 'subnetKey2', value: 'subnetValue2' }, - ], - subnetTags: [ - { key: 'subnetKey1', value: 'subnetValue1' }, - { key: 'subnetKey2', value: 'subnetValue2' }, - ], - sharedSubnetName: 'sharedSubnet', - vpcName: 'vpcName', - acceleratorSsmParamPrefix: '/accel/prefix', - }, -}; -const sharedSubnetSsmPathDelete = `${deleteEvent.ResourceProperties['acceleratorSsmParamPrefix']}/shared/network/vpc/${deleteEvent.ResourceProperties['vpcName']}/subnet/${deleteEvent.ResourceProperties['sharedSubnetName']}/id`; - -it('@aws-accelerator/constructs-aws-ram-share-subnet-tags delete event', async () => { - // when - ssmMock - .on(GetParameterCommand, { Name: sharedSubnetSsmPath }) - .resolves({ Parameter: { Name: sharedSubnetSsmPath, Value: 'subnet-1234' } }); - ssmMock.on(DeleteParameterCommand, { Name: sharedSubnetSsmPathDelete }).resolves({}); - ec2Mock.on(DeleteTagsCommand).resolves({}); - const response = await handler(deleteEvent); - // then - response Status is SUCCESS - expect(response?.Status).toBe('SUCCESS'); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/endpoint-addresses.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/endpoint-addresses.test.ts.snap deleted file mode 100644 index 209bbd3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/endpoint-addresses.test.ts.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EndpointAddresses Construct(ResolverEndpointAddresses): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomResolverEndpointAddressesCustomResourceProviderHandler09D4123E": { - "DependsOn": [ - "CustomResolverEndpointAddressesCustomResourceProviderRoleA94B4F27", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomResolverEndpointAddressesCustomResourceProviderRoleA94B4F27", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomResolverEndpointAddressesCustomResourceProviderLogGroup70A41B6B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomResolverEndpointAddressesCustomResourceProviderHandler09D4123E", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomResolverEndpointAddressesCustomResourceProviderRoleA94B4F27": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:ListResolverEndpointIpAddresses", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestEndpointAddressesCE4F1BB4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomResolverEndpointAddressesCustomResourceProviderLogGroup70A41B6B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomResolverEndpointAddressesCustomResourceProviderHandler09D4123E", - "Arn", - ], - }, - "endpointId": "TestEndpointId", - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::ResolverEndpointAddresses", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-domain-list.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-domain-list.test.ts.snap deleted file mode 100644 index 2326df5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-domain-list.test.ts.snap +++ /dev/null @@ -1,200 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResolverFirewallDomainList Construct(ResolverFirewallDomainList): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomResolverManagedDomainListCustomResourceProviderHandler9F7C9581": { - "DependsOn": [ - "CustomResolverManagedDomainListCustomResourceProviderRole33DECC65", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomResolverManagedDomainListCustomResourceProviderRole33DECC65", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomResolverManagedDomainListCustomResourceProviderLogGroupAA48B1A0": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "TestManagedDomainListKeyCB86CE9D", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomResolverManagedDomainListCustomResourceProviderHandler9F7C9581", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomResolverManagedDomainListCustomResourceProviderRole33DECC65": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:ListFirewallDomainLists", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "TestDomainList9DC6C806": { - "Properties": { - "DomainFileUrl": { - "Fn::Sub": "REPLACED-GENERATED-NAME.zip", - }, - "Tags": [ - { - "Key": "Name", - "Value": "TestDomainList", - }, - ], - }, - "Type": "AWS::Route53Resolver::FirewallDomainList", - }, - "TestDomainListKey95870FD5": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestManagedDomainListE1CDFDDE": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomResolverManagedDomainListCustomResourceProviderLogGroupAA48B1A0", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomResolverManagedDomainListCustomResourceProviderHandler9F7C9581", - "Arn", - ], - }, - "listName": "TestManagedDomainList", - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::ResolverManagedDomainList", - "UpdateReplacePolicy": "Delete", - }, - "TestManagedDomainListKeyCB86CE9D": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-rule-group.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-rule-group.test.ts.snap deleted file mode 100644 index efc65fe..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/firewall-rule-group.test.ts.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResolverFirewallRuleGroup Construct(ResolverFirewallRuleGroup): Snapshot Test 1`] = ` -{ - "Resources": { - "TestRuleGroup43F9213A": { - "Properties": { - "FirewallRules": [ - { - "Action": "BLOCK", - "BlockResponse": "NXDOMAIN", - "FirewallDomainListId": "TestDomainList", - "Priority": 101, - }, - ], - "Tags": [ - { - "Key": "Name", - "Value": "TestRuleGroup", - }, - ], - }, - "Type": "AWS::Route53Resolver::FirewallRuleGroup", - }, - "TestRuleGroupAssoc48F4678D": { - "Properties": { - "FirewallRuleGroupId": { - "Ref": "TestRuleGroup43F9213A", - }, - "Priority": 101, - "VpcId": "TestVpc", - }, - "Type": "AWS::Route53Resolver::FirewallRuleGroupAssociation", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/query-logging-config.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/query-logging-config.test.ts.snap deleted file mode 100644 index 07e8a18..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/query-logging-config.test.ts.snap +++ /dev/null @@ -1,703 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`QueryLoggingConfig Construct(QueryLoggingConfig): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomQueryLoggingConfigAssociationCustomResourceProviderHandlerFDE723DC": { - "DependsOn": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderRole0317AB9A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderRole0317AB9A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomQueryLoggingConfigAssociationCustomResourceProviderLogGroupD12EADAE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomQueryLoggingConfigAssociationCustomResourceProviderHandlerFDE723DC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomQueryLoggingConfigAssociationCustomResourceProviderRole0317AB9A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:DisassociateResolverQueryLogConfig", - "route53resolver:AssociateResolverQueryLogConfig", - "route53resolver:ListResolverQueryLogConfigs", - "route53resolver:GetResolverQueryLogConfig", - "route53resolver:ListResolverQueryLogConfigAssociations", - "ec2:DescribeVpcs", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "ForAnyValue:StringEquals": { - "iam:AWSServiceName": [ - "route53resolver.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CwlQueryLoggingTest70DD9614": { - "Properties": { - "DestinationArn": { - "Fn::GetAtt": [ - "TestLogGroup4EEF7AD4", - "Arn", - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfig", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "S3QueryLoggingTestA05F494B": { - "Properties": { - "DestinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::testbucket", - ], - ], - }, - }, - "Type": "AWS::Route53Resolver::ResolverQueryLoggingConfig", - }, - "TestLogGroup4EEF7AD4": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "TestLogGroupPolicyResourcePolicyFDE53895": { - "Properties": { - "PolicyDocument": { - "Fn::Join": [ - "", - [ - "{"Statement":[{"Action":["logs:CreateLogStream","logs:PutLogEvents"],"Condition":{"StringEquals":{"aws:PrincipalOrgId":"o-123test"}},"Effect":"Allow","Principal":{"Service":"delivery.logs.amazonaws.com"},"Resource":"", - { - "Fn::GetAtt": [ - "TestLogGroup4EEF7AD4", - "Arn", - ], - }, - ":log-stream:*","Sid":"Allow log delivery access"}],"Version":"2012-10-17"}", - ], - ], - }, - "PolicyName": "TestLogGroupPolicy628CC4FE", - }, - "Type": "AWS::Logs::ResourcePolicy", - }, - "TestQueryLoggingAssocQueryLoggingConfigAssociationE89E2286": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderLogGroupD12EADAE", - ], - "Properties": { - "ResolverQueryLogConfigId": { - "Fn::GetAtt": [ - "S3QueryLoggingTestA05F494B", - "Id", - ], - }, - "ServiceToken": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderHandlerFDE723DC", - "Arn", - ], - }, - "VpcId": "TestVpc", - }, - "Type": "Custom::QueryLoggingConfigAssociation", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; - -exports[`QueryLoggingConfig Construct(QueryLoggingConfig): Snapshot Test 2`] = ` -{ - "Resources": { - "CustomLogResourcePolicyCustomResourceProviderHandlerB58C0C34": { - "DependsOn": [ - "CustomLogResourcePolicyCustomResourceProviderRole83D4C53E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomLogResourcePolicyCustomResourceProviderRole83D4C53E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomLogResourcePolicyCustomResourceProviderLogGroupB4DDD051": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomLogResourcePolicyCustomResourceProviderHandlerB58C0C34", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomLogResourcePolicyCustomResourceProviderRole83D4C53E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:PutResourcePolicy", - "logs:DeleteResourcePolicy", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomQueryLoggingConfigAssociationCustomResourceProviderHandlerFDE723DC": { - "DependsOn": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderRole0317AB9A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderRole0317AB9A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomQueryLoggingConfigAssociationCustomResourceProviderLogGroupD12EADAE": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomQueryLoggingConfigAssociationCustomResourceProviderHandlerFDE723DC", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomQueryLoggingConfigAssociationCustomResourceProviderRole0317AB9A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:DisassociateResolverQueryLogConfig", - "route53resolver:AssociateResolverQueryLogConfig", - "route53resolver:ListResolverQueryLogConfigs", - "route53resolver:GetResolverQueryLogConfig", - "route53resolver:ListResolverQueryLogConfigAssociations", - "ec2:DescribeVpcs", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "ForAnyValue:StringEquals": { - "iam:AWSServiceName": [ - "route53resolver.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CustomQueryLoggingConfigCustomResourceProviderHandler2B99DCC6": { - "DependsOn": [ - "CustomQueryLoggingConfigCustomResourceProviderRole70878758", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigCustomResourceProviderRole70878758", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomQueryLoggingConfigCustomResourceProviderLogGroup58425034": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomQueryLoggingConfigCustomResourceProviderHandler2B99DCC6", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomQueryLoggingConfigCustomResourceProviderRole70878758": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "route53resolver:ListResolverQueryLogConfigs", - "route53resolver:CreateResolverQueryLogConfig", - "route53resolver:DeleteResolverQueryLogConfig", - "logs:*", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "ForAnyValue:StringEquals": { - "iam:AWSServiceName": [ - "route53resolver.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CwlQueryLoggingTestLogResourcePolicy9F3660E7": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomLogResourcePolicyCustomResourceProviderLogGroupB4DDD051", - "CwlQueryLoggingTestQueryLoggingConfig7F7EA5E9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomLogResourcePolicyCustomResourceProviderHandlerB58C0C34", - "Arn", - ], - }, - "policyName": "AllowLogDeliveryAccess", - "policyStatements": [ - { - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Principal": { - "Service": "delivery.logs.amazonaws.com", - }, - "Resource": [ - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "TestLogGroup4EEF7AD4", - "Arn", - ], - }, - ":log-stream:*", - ], - ], - }, - ], - "Sid": "Allow log delivery access", - }, - ], - }, - "Type": "Custom::LogResourcePolicy", - "UpdateReplacePolicy": "Delete", - }, - "CwlQueryLoggingTestQueryLoggingConfig7F7EA5E9": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomQueryLoggingConfigCustomResourceProviderLogGroup58425034", - ], - "Properties": { - "DestinationArn": { - "Fn::GetAtt": [ - "TestLogGroup4EEF7AD4", - "Arn", - ], - }, - "Name": "CwlQueryLoggingTest", - "ServiceToken": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigCustomResourceProviderHandler2B99DCC6", - "Arn", - ], - }, - }, - "Type": "Custom::QueryLoggingConfig", - "UpdateReplacePolicy": "Delete", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "S3QueryLoggingTestQueryLoggingConfig630BFF1D": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomQueryLoggingConfigCustomResourceProviderLogGroup58425034", - ], - "Properties": { - "DestinationArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::testbucket", - ], - ], - }, - "Name": "S3QueryLoggingTest", - "ServiceToken": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigCustomResourceProviderHandler2B99DCC6", - "Arn", - ], - }, - }, - "Type": "Custom::QueryLoggingConfig", - "UpdateReplacePolicy": "Delete", - }, - "TestLogGroup4EEF7AD4": { - "DeletionPolicy": "Retain", - "Properties": { - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "TestQueryLoggingAssocQueryLoggingConfigAssociationE89E2286": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderLogGroupD12EADAE", - ], - "Properties": { - "ResolverQueryLogConfigId": { - "Fn::GetAtt": [ - "S3QueryLoggingTestQueryLoggingConfig630BFF1D", - "attrId", - ], - }, - "ServiceToken": { - "Fn::GetAtt": [ - "CustomQueryLoggingConfigAssociationCustomResourceProviderHandlerFDE723DC", - "Arn", - ], - }, - "VpcId": "TestVpc", - }, - "Type": "Custom::QueryLoggingConfigAssociation", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-endpoint.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-endpoint.test.ts.snap deleted file mode 100644 index c25075e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-endpoint.test.ts.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResolverEndpoint Construct(ResolverEndpoint): Snapshot Test 1`] = ` -{ - "Resources": { - "TestEndpoint4E197ABD": { - "Properties": { - "Direction": "OUTBOUND", - "IpAddresses": [ - { - "SubnetId": "subnet-1", - }, - { - "SubnetId": "subnet-2", - }, - ], - "Name": "TestEndpoint", - "SecurityGroupIds": [ - "sg-123test", - ], - "Tags": [ - { - "Key": "Name", - "Value": "TestEndpoint", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverEndpoint", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-rule.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-rule.test.ts.snap deleted file mode 100644 index 5406480..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/__snapshots__/resolver-rule.test.ts.snap +++ /dev/null @@ -1,154 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResolverRule Construct(ResolverRule): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestResolverRule183FBE0C": { - "Properties": { - "DomainName": "test.com", - "Name": "TestResolverRule", - "ResolverEndpointId": "TestEndpoint", - "RuleType": "FORWARD", - "Tags": [ - { - "Key": "Name", - "Value": "TestResolverRule", - }, - ], - "TargetIps": [ - { - "Ip": "1.1.1.1", - }, - { - "Ip": "2.2.2.2", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverRule", - }, - "TestResolverRuleAssoc7E0DCDC2": { - "Properties": { - "ResolverRuleId": { - "Fn::GetAtt": [ - "TestResolverRule183FBE0C", - "ResolverRuleId", - ], - }, - "VPCId": "TestVpc", - }, - "Type": "AWS::Route53Resolver::ResolverRuleAssociation", - }, - }, -} -`; - -exports[`ResolverRule Construct(ResolverRule): Snapshot Test 2`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "TestResolverRule183FBE0C": { - "Properties": { - "DomainName": "test.com", - "Name": "TestResolverRule", - "RuleType": "SYSTEM", - "Tags": [ - { - "Key": "Name", - "Value": "TestResolverRule", - }, - ], - "TargetIps": [ - { - "Ip": "1.1.1.1", - }, - { - "Ip": "2.2.2.2", - }, - ], - }, - "Type": "AWS::Route53Resolver::ResolverRule", - }, - "TestResolverRuleAssoc7E0DCDC2": { - "Properties": { - "ResolverRuleId": { - "Fn::GetAtt": [ - "TestResolverRule183FBE0C", - "ResolverRuleId", - ], - }, - "VPCId": "TestVpc", - }, - "Type": "AWS::Route53Resolver::ResolverRuleAssociation", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/endpoint-addresses.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/endpoint-addresses.test.ts deleted file mode 100644 index 67ba6ce..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/endpoint-addresses.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { EndpointAddresses } from '../../lib/aws-route-53-resolver/endpoint-addresses'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ResolverEndpointAddresses): '; - -const stack = new cdk.Stack(); - -new EndpointAddresses(stack, 'TestEndpointAddresses', { - endpointId: 'TestEndpointId', - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * Resolver endpoint addresses construct test - */ -describe('EndpointAddresses', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-domain-list.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-domain-list.test.ts deleted file mode 100644 index 4abee27..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-domain-list.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - ResolverFirewallDomainList, - ResolverFirewallDomainListType, -} from '../../lib/aws-route-53-resolver/firewall-domain-list'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ResolverFirewallDomainList): '; - -const stack = new cdk.Stack(); - -// Custom domain list -new ResolverFirewallDomainList(stack, 'TestDomainList', { - name: 'TestDomainList', - path: __dirname, - tags: [], - type: ResolverFirewallDomainListType.CUSTOM, - kmsKey: new cdk.aws_kms.Key(stack, 'TestDomainListKey', {}), - logRetentionInDays: 3653, -}); - -// Managed domain list -new ResolverFirewallDomainList(stack, 'TestManagedDomainList', { - name: 'TestManagedDomainList', - type: ResolverFirewallDomainListType.MANAGED, - kmsKey: new cdk.aws_kms.Key(stack, 'TestManagedDomainListKey', {}), - logRetentionInDays: 3653, -}); - -/** - * DNS firewall domain list construct test - */ -describe('ResolverFirewallDomainList', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-rule-group.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-rule-group.test.ts deleted file mode 100644 index 4e47a76..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/firewall-rule-group.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - ResolverFirewallRuleGroup, - ResolverFirewallRuleGroupAssociation, -} from '../../lib/aws-route-53-resolver/firewall-rule-group'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ResolverFirewallRuleGroup): '; - -const ruleProps = { - action: 'BLOCK', - firewallDomainListId: 'TestDomainList', - priority: 101, - blockResponse: 'NXDOMAIN', -}; - -const stack = new cdk.Stack(); - -const ruleGroup = new ResolverFirewallRuleGroup(stack, 'TestRuleGroup', { - firewallRules: [ruleProps], - name: 'TestRuleGroup', - tags: [], -}); - -new ResolverFirewallRuleGroupAssociation(stack, 'TestRuleGroupAssoc', { - firewallRuleGroupId: ruleGroup.groupId, - priority: 101, - vpcId: 'TestVpc', -}); - -describe('ResolverFirewallRuleGroup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/query-logging-config.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/query-logging-config.test.ts deleted file mode 100644 index dfa36de..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/query-logging-config.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { - QueryLoggingConfig, - QueryLoggingConfigAssociation, -} from '../../lib/aws-route-53-resolver/query-logging-config'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(QueryLoggingConfig): '; - -const stack = new cdk.Stack(); - -// Instantiate resources required for construct -const bucket = cdk.aws_s3.Bucket.fromBucketName(stack, 'TestBucket', 'testbucket'); -const logGroup = new cdk.aws_logs.LogGroup(stack, 'TestLogGroup'); -const kmsKey = new cdk.aws_kms.Key(stack, 'Key', {}); - -// S3 query logging config -const s3Config = new QueryLoggingConfig(stack, 'S3QueryLoggingTest', { - destination: bucket, - kmsKey: kmsKey, - logRetentionInDays: 3653, - name: 'S3QueryLoggingTest', - partition: 'aws', -}); - -// CloudWatch Logs query logging config -new QueryLoggingConfig(stack, 'CwlQueryLoggingTest', { - destination: logGroup, - kmsKey: kmsKey, - logRetentionInDays: 3653, - name: 'CwlQueryLoggingTest', - organizationId: 'o-123test', - partition: 'aws', -}); - -// Config association -new QueryLoggingConfigAssociation(stack, 'TestQueryLoggingAssoc', { - resolverQueryLogConfigId: s3Config.logId, - vpcId: 'TestVpc', - kmsKey: kmsKey, - logRetentionInDays: 3653, - partition: 'aws-cn', -}); - -// Test for China region. -const cnStack = new cdk.Stack(); -const cnBucket = cdk.aws_s3.Bucket.fromBucketName(cnStack, 'TestBucket', 'testbucket'); -const cnLogGroup = new cdk.aws_logs.LogGroup(cnStack, 'TestLogGroup'); -const cnKmsKey = new cdk.aws_kms.Key(cnStack, 'Key', {}); - -const cnS3Config = new QueryLoggingConfig(cnStack, 'S3QueryLoggingTest', { - destination: cnBucket, - kmsKey: cnKmsKey, - logRetentionInDays: 3653, - name: 'S3QueryLoggingTest', - partition: 'aws-cn', -}); - -new QueryLoggingConfig(cnStack, 'CwlQueryLoggingTest', { - destination: cnLogGroup, - kmsKey: cnKmsKey, - logRetentionInDays: 3653, - name: 'CwlQueryLoggingTest', - organizationId: 'o-123test', - partition: 'aws-cn', -}); - -new QueryLoggingConfigAssociation(cnStack, 'TestQueryLoggingAssoc', { - resolverQueryLogConfigId: cnS3Config.logId, - vpcId: 'TestVpc', - kmsKey: cnKmsKey, - logRetentionInDays: 3653, - partition: 'aws-cn', -}); - -describe('QueryLoggingConfig', () => { - snapShotTest(testNamePrefix, stack); - snapShotTest(testNamePrefix, cnStack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-endpoint.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-endpoint.test.ts deleted file mode 100644 index 8420577..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-endpoint.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ResolverEndpoint } from '../../lib/aws-route-53-resolver/resolver-endpoint'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ResolverEndpoint): '; - -const stack = new cdk.Stack(); - -new ResolverEndpoint(stack, 'TestEndpoint', { - direction: 'OUTBOUND', - ipAddresses: ['subnet-1', 'subnet-2'], - name: 'TestEndpoint', - securityGroupIds: ['sg-123test'], - tags: [], -}); - -describe('ResolverEndpoint', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-rule.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-rule.test.ts deleted file mode 100644 index b21328a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53-resolver/resolver-rule.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ResolverRule, ResolverRuleAssociation } from '../../lib/aws-route-53-resolver/resolver-rule'; -import { snapShotTest } from '../snapshot-test'; -import { describe, expect, it } from '@jest/globals'; - -const testNamePrefix = 'Construct(ResolverRule): '; - -const forwardRuleStack = new cdk.Stack(); -const systemRuleStack = new cdk.Stack(); - -const ipAddresses = [{ ip: '1.1.1.1' }, { ip: '2.2.2.2' }]; - -const forwardRule = new ResolverRule(forwardRuleStack, 'TestResolverRule', { - domainName: 'test.com', - name: 'TestResolverRule', - resolverEndpointId: 'TestEndpoint', - targetIps: ipAddresses, - tags: [], - kmsKey: new cdk.aws_kms.Key(forwardRuleStack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -new ResolverRuleAssociation(forwardRuleStack, 'TestResolverRuleAssoc', { - resolverRuleId: forwardRule.ruleId, - vpcId: 'TestVpc', -}); - -const systemRule = new ResolverRule(systemRuleStack, 'TestResolverRule', { - domainName: 'test.com', - name: 'TestResolverRule', - resolverEndpointId: 'TestEndpoint', - targetIps: ipAddresses, - tags: [], - kmsKey: new cdk.aws_kms.Key(systemRuleStack, 'CustomKey', {}), - logRetentionInDays: 3653, - ruleType: 'SYSTEM', -}); - -new ResolverRuleAssociation(systemRuleStack, 'TestResolverRuleAssoc', { - resolverRuleId: systemRule.ruleId, - vpcId: 'TestVpc', -}); - -describe('ResolverRule', () => { - snapShotTest(testNamePrefix, forwardRuleStack); - snapShotTest(testNamePrefix, systemRuleStack); - it('throw error when targetInbound is specified without logRetention', () => { - function targetInboundLogRetentionError() { - new ResolverRule(systemRuleStack, 'TargetInboundLogRetentionError', { - domainName: 'test.com', - name: 'TestResolverRule', - resolverEndpointId: 'TestEndpoint', - targetIps: ipAddresses, - tags: [], - targetInbound: 'targetInbound', - kmsKey: new cdk.aws_kms.Key(systemRuleStack, 'CustomKeyTargetInboundLogRetentionError', {}), - ruleType: 'SYSTEM', - }); - } - expect(targetInboundLogRetentionError).toThrow( - new Error('logRetentionInDays property must be included if targetInbound property is defined.'), - ); - }); - it('test private function lookup inbound', () => { - const testLookupInbound = new ResolverRule(systemRuleStack, 'TestLookupInbound', { - domainName: 'test.com', - name: 'TestResolverRule', - resolverEndpointId: 'TestEndpoint', - targetIps: ipAddresses, - tags: [], - targetInbound: 'targetInbound', - kmsKey: new cdk.aws_kms.Key(systemRuleStack, 'CustomKeyTestLookupInbound', {}), - logRetentionInDays: 3653, - ruleType: 'SYSTEM', - }); - //output of testLookupId ruleId is a cdk token hash so the check here is to make sure its a string - expect(typeof testLookupInbound.ruleId).toBe('string'); - }); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/associate-hosted-zones.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/associate-hosted-zones.test.ts.snap deleted file mode 100644 index 47dbef3..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/associate-hosted-zones.test.ts.snap +++ /dev/null @@ -1,174 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AssociateHostedZones Construct(AssociateHostedZones): Snapshot Test 1`] = ` -{ - "Resources": { - "AssociateHostedZonesF0E2F0DA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderLogGroupDEA7760D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71", - "Arn", - ], - }, - "accountIds": [], - "hostedZoneAccountId": "111111111111", - "hostedZoneIds": [], - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - "roleName": "AWSAccelerator-EnableCentralEndpointsRole-us-east-1", - "tagFilters": [ - { - "key": "accelerator:use-central-endpoints", - "value": "true", - }, - { - "key": "accelerator:central-endpoints-account-id", - "value": "222222222222", - }, - ], - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::Route53AssociateHostedZones", - "UpdateReplacePolicy": "Delete", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71": { - "DependsOn": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderLogGroupDEA7760D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomRoute53AssociateHostedZonesCustomResourceProviderHandler1296DB71", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomRoute53AssociateHostedZonesCustomResourceProviderRole17C82AD6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ec2:DescribeVpcs", - "route53:AssociateVPCWithHostedZone", - "route53:CreateVPCAssociationAuthorization", - "route53:DeleteVPCAssociationAuthorization", - "route53:GetHostedZone", - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Route53AssociateHostedZonesActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/hosted-zone.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/hosted-zone.test.ts.snap deleted file mode 100644 index 47eed7a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/hosted-zone.test.ts.snap +++ /dev/null @@ -1,358 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HostedZone Construct(HostedZone): Snapshot Test 1`] = ` -{ - "Resources": { - "AppstreamApiHostedZoneEC555793": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "Name": { - "Fn::Join": [ - "", - [ - "appstream2.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "DeviceAdvisorIotHostedZoneC6F4CC0C": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "Name": { - "Fn::Join": [ - "", - [ - "deviceadvisor.iot.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "EcrApiHostedZoneD58C89CF": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "Name": { - "Fn::Join": [ - "", - [ - "api.ecr.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com.", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "EcsAgentHostedZone078A81E8": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "ecs-a.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "EcsTelemetryHostedZone654D1D44": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "ecs-t.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "PintpointSmsVoiceV2HostedZone9DE1AD33": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "sms-voice.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "RumDataplaneHostedZone1346DE6C": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "dataplane.rum.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "SageMakerNoteBookHostedZone355C458F": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "notebook.", - { - "Ref": "AWS::Region", - }, - ".sagemaker.aws", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "SageMakerStudioHostedZone98963296": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "studio.", - { - "Ref": "AWS::Region", - }, - ".sagemaker.aws", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "TestHostedZone68F306E4": { - "Properties": { - "Name": "s3-global.accesspoint.amazonaws.com", - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "codeArtifactApiHostedZoneD0D526E8": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "Name": { - "Fn::Join": [ - "", - [ - "codeartifact.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "codeArtifactRepositoriesHostedZoneF088B188": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "Name": { - "Fn::Join": [ - "", - [ - "d.codeartifact.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "s3HostedHostedZoneC29BF087": { - "Properties": { - "HostedZoneTags": [ - { - "Key": "key", - "Value": "value", - }, - ], - "Name": { - "Fn::Join": [ - "", - [ - "s3.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/record-set.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/record-set.test.ts.snap deleted file mode 100644 index d3e2119..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53/__snapshots__/record-set.test.ts.snap +++ /dev/null @@ -1,154 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RecordSet Construct(RecordSet): Snapshot Test 1`] = ` -{ - "Resources": { - "TestHostedZone68F306E4": { - "Properties": { - "Name": "s3-global.accesspoint.amazonaws.com", - "VPCs": [ - { - "VPCId": "Test", - "VPCRegion": { - "Ref": "AWS::Region", - }, - }, - ], - }, - "Type": "AWS::Route53::HostedZone", - }, - "TestRecordSet11EBF57EA": { - "Properties": { - "AliasTarget": { - "DNSName": "", - "HostedZoneId": "", - }, - "HostedZoneId": { - "Ref": "TestHostedZone68F306E4", - }, - "Name": "s3-global.accesspoint.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "TestRecordSetED81F5C1": { - "Properties": { - "AliasTarget": { - "DNSName": { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "TestVpcEndpointF7CADE71", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - "HostedZoneId": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Fn::Select": [ - 0, - { - "Fn::GetAtt": [ - "TestVpcEndpointF7CADE71", - "DnsEntries", - ], - }, - ], - }, - ], - }, - ], - }, - }, - "HostedZoneId": { - "Ref": "TestHostedZone68F306E4", - }, - "Name": "s3-global.accesspoint.amazonaws.com", - "Type": "A", - }, - "Type": "AWS::Route53::RecordSet", - }, - "TestSecurityGroupDA4B5F83": { - "Properties": { - "GroupDescription": "AWS Private Endpoint Zone", - "GroupName": "TestSecurityGroup", - "Tags": [ - { - "Key": "Name", - "Value": "TestSecurityGroup", - }, - ], - "VpcId": "Test", - }, - "Type": "AWS::EC2::SecurityGroup", - }, - "TestVpcEndpointF7CADE71": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": [ - "organizationId", - ], - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "AccessToTrustedPrincipalsAndResources", - }, - ], - "Version": "2012-10-17", - }, - "PrivateDnsEnabled": false, - "SecurityGroupIds": [ - { - "Ref": "TestSecurityGroupDA4B5F83", - }, - ], - "ServiceName": { - "Fn::Join": [ - "", - [ - "com.amazonaws.", - { - "Ref": "AWS::Region", - }, - ".ec2", - ], - ], - }, - "SubnetIds": [ - "Test1", - "Test2", - ], - "VpcEndpointType": "Interface", - "VpcId": "Test", - }, - "Type": "AWS::EC2::VPCEndpoint", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53/associate-hosted-zones.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53/associate-hosted-zones.test.ts deleted file mode 100644 index f8b9796..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53/associate-hosted-zones.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AssociateHostedZones } from '../../lib/aws-route-53/associate-hosted-zones'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(AssociateHostedZones): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new AssociateHostedZones(stack, 'AssociateHostedZones', { - accountIds: [], - hostedZoneIds: [], - hostedZoneAccountId: '111111111111', - roleName: `AWSAccelerator-EnableCentralEndpointsRole-us-east-1`, - tagFilters: [ - { - key: 'accelerator:use-central-endpoints', - value: 'true', - }, - { - key: 'accelerator:central-endpoints-account-id', - value: '222222222222', - }, - ], - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); - -/** - * AssociateHostedZones construct test - */ -describe('AssociateHostedZones', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53/hosted-zone.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53/hosted-zone.test.ts deleted file mode 100644 index 14417d1..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53/hosted-zone.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { HostedZone } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(HostedZone): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const hostedZoneName = HostedZone.getHostedZoneNameForService('s3-global.accesspoint', stack.region); -const ecrApiHostedZoneName = HostedZone.getHostedZoneNameForService('ecr.api', stack.region); -const appstreamApiHostedZoneName = HostedZone.getHostedZoneNameForService('appstream.api', stack.region); -const deviceAdvisorIotHostedZoneName = HostedZone.getHostedZoneNameForService('deviceadvisor.iot', stack.region); -const pintpointSmsVoiceV2HostedZoneName = HostedZone.getHostedZoneNameForService('pinpoint-sms-voice-v2', stack.region); -const rumDataplaneHostedZoneName = HostedZone.getHostedZoneNameForService('rum-dataplane', stack.region); -const ecsAgentHostedZoneName = HostedZone.getHostedZoneNameForService('ecs-agent', stack.region); -const ecsTelemetryHostedZoneName = HostedZone.getHostedZoneNameForService('ecs-telemetry', stack.region); -const sageMakerNotebookHostedZoneName = HostedZone.getHostedZoneNameForService('notebook', stack.region); -const sageMakerStudioHostedZoneName = HostedZone.getHostedZoneNameForService('studio', stack.region); -const s3HostedHostedZoneName = HostedZone.getHostedZoneNameForService('s3', stack.region); -const codeArtifactRepositoriesHostedZoneName = HostedZone.getHostedZoneNameForService( - 'codeartifact.repositories', - stack.region, -); -const codeArtifactApiHostedZoneName = HostedZone.getHostedZoneNameForService('codeartifact.api', stack.region); - -new HostedZone(stack, `TestHostedZone`, { - hostedZoneName, - vpcId: 'Test', -}); - -new HostedZone(stack, `codeArtifactApiHostedZone`, { - hostedZoneName: codeArtifactApiHostedZoneName, - vpcId: 'Test', - tags: [{ key: 'key', value: 'value' }], -}); - -new HostedZone(stack, `codeArtifactRepositoriesHostedZone`, { - hostedZoneName: codeArtifactRepositoriesHostedZoneName, - vpcId: 'Test', - tags: [{ key: 'key', value: 'value' }], -}); - -new HostedZone(stack, `s3HostedHostedZone`, { - hostedZoneName: s3HostedHostedZoneName, - vpcId: 'Test', - tags: [{ key: 'key', value: 'value' }], -}); - -new HostedZone(stack, `EcrApiHostedZone`, { - hostedZoneName: ecrApiHostedZoneName, - vpcId: 'Test', - tags: [{ key: 'key', value: 'value' }], -}); - -new HostedZone(stack, `AppstreamApiHostedZone`, { - hostedZoneName: appstreamApiHostedZoneName, - vpcId: 'Test', - tags: [{ key: 'key', value: 'value' }], -}); - -new HostedZone(stack, `DeviceAdvisorIotHostedZone`, { - hostedZoneName: deviceAdvisorIotHostedZoneName, - vpcId: 'Test', - tags: [{ key: 'key', value: 'value' }], -}); - -new HostedZone(stack, `PintpointSmsVoiceV2HostedZone`, { - hostedZoneName: pintpointSmsVoiceV2HostedZoneName, - vpcId: 'Test', -}); - -new HostedZone(stack, `RumDataplaneHostedZone`, { - hostedZoneName: rumDataplaneHostedZoneName, - vpcId: 'Test', -}); - -new HostedZone(stack, `EcsAgentHostedZone`, { - hostedZoneName: ecsAgentHostedZoneName, - vpcId: 'Test', -}); - -new HostedZone(stack, `EcsTelemetryHostedZone`, { - hostedZoneName: ecsTelemetryHostedZoneName, - vpcId: 'Test', -}); - -new HostedZone(stack, `SageMakerNoteBookHostedZone`, { - hostedZoneName: sageMakerNotebookHostedZoneName, - vpcId: 'Test', -}); - -new HostedZone(stack, `SageMakerStudioHostedZone`, { - hostedZoneName: sageMakerStudioHostedZoneName, - vpcId: 'Test', -}); - -/** - * HostedZone construct test - */ -describe('HostedZone', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-route-53/record-set.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-route-53/record-set.test.ts deleted file mode 100644 index cd9d816..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-route-53/record-set.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { RecordSet } from '../../lib/aws-route-53/record-set'; -import { HostedZone } from '../../lib/aws-route-53/hosted-zone'; -import { VpcEndpoint } from '../../lib/aws-ec2/vpc-endpoint'; -import { SecurityGroup } from '../../lib/aws-ec2/vpc'; -import { snapShotTest } from '../snapshot-test'; -import { describe, expect, it } from '@jest/globals'; - -const testNamePrefix = 'Construct(RecordSet): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const hostedZoneName = HostedZone.getHostedZoneNameForService('s3-global.accesspoint', stack.region); - -const hostedZone = new HostedZone(stack, `TestHostedZone`, { - hostedZoneName, - vpcId: 'Test', -}); - -const securityGroup = new SecurityGroup(stack, 'TestSecurityGroup`', { - securityGroupName: 'TestSecurityGroup', - description: `AWS Private Endpoint Zone`, - vpcId: 'Test', - tags: [], -}); - -// Create the interface endpoint -const endpoint = new VpcEndpoint(stack, `TestVpcEndpoint`, { - vpcId: 'Test', - vpcEndpointType: cdk.aws_ec2.VpcEndpointType.INTERFACE, - service: 'ec2', - subnets: ['Test1', 'Test2'], - securityGroups: [securityGroup], - privateDnsEnabled: false, - policyDocument: new cdk.aws_iam.PolicyDocument({ - statements: [ - new cdk.aws_iam.PolicyStatement({ - sid: 'AccessToTrustedPrincipalsAndResources', - actions: ['*'], - effect: cdk.aws_iam.Effect.ALLOW, - resources: ['*'], - principals: [new cdk.aws_iam.AnyPrincipal()], - conditions: { - StringEquals: { - 'aws:PrincipalOrgID': ['organizationId'], - }, - }, - }), - ], - }), -}); - -/** - * RecordSet construct test - */ -describe('RecordSet', () => { - it('test with hostedZone and dns', () => { - new RecordSet(stack, `TestRecordSet`, { - type: 'A', - name: hostedZoneName, - hostedZone: hostedZone, - dnsName: endpoint.dnsName, - hostedZoneId: endpoint.hostedZoneId, - }); - }); - it('test without hostedZone and dns', () => { - new RecordSet(stack, `TestRecordSet1`, { - type: 'A', - name: hostedZoneName, - hostedZone: hostedZone, - }); - }); - snapShotTest(testNamePrefix, stack); - const sagemakerHostedZone = RecordSet.getHostedZoneNameFromService('notebook', 'us-east-1'); - expect(sagemakerHostedZone).toBe('notebook.us-east-1.sagemaker.aws'); - const s3GlobalEndpoint = RecordSet.getHostedZoneNameFromService('s3-global.accesspoint', 'us-east-1'); - expect(s3GlobalEndpoint).toBe('s3-global.accesspoint.aws.com'); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-encryption.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-encryption.test.ts.snap deleted file mode 100644 index 13d1d47..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-encryption.test.ts.snap +++ /dev/null @@ -1,405 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BucketEncryption Construct(ValidateBucketKmsEncryption): Snapshot Test 1`] = ` -{ - "Resources": { - "Bucket83908E77": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketKey7092080A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchKeyKmsEncryptionEFBC1FE2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKeyKmsEncryption8BC9FA8C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ValidateBucketKmsEncryptionBucketEncryptionBucketEncryptionResource6B4E2114": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateBucketKmsEncryptionBucketEncryptionFunctionResourceLogGroup589E08B9", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEvent27870498", - "Arn", - ], - }, - "bucketName": { - "Ref": "Bucket83908E77", - }, - "kmsKeyArn": { - "Fn::GetAtt": [ - "BucketKey7092080A", - "Arn", - ], - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionBucketEncryptionFunction89C152CA": { - "DependsOn": [ - "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleDefaultPolicy1F2B7300", - "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleC8174847", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketEncryption custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyKmsEncryption8BC9FA8C", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleC8174847", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionBucketEncryptionFunctionResourceLogGroup589E08B9": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyKmsEncryptionEFBC1FE2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateBucketKmsEncryptionBucketEncryptionFunction89C152CA", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleC8174847": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleDefaultPolicy1F2B7300": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "Bucket83908E77", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleDefaultPolicy1F2B7300", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionBucketEncryptionFunctionServiceRoleC8174847", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEvent27870498": { - "DependsOn": [ - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRoleDefaultPolicy442B432F", - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRole7EC6A981", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ValidateBucketKmsEncryption/BucketEncryption/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketEncryptionFunction89C152CA", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRole7EC6A981", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRole7EC6A981": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRoleDefaultPolicy442B432F": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketEncryptionFunction89C152CA", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketEncryptionFunction89C152CA", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRoleDefaultPolicy442B432F", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionBucketEncryptionframeworkonEventServiceRole7EC6A981", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-policy.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-policy.test.ts.snap deleted file mode 100644 index ad1e0bc..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-policy.test.ts.snap +++ /dev/null @@ -1,390 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BucketPolicy Construct(ValidateBucketKmsEncryption): Snapshot Test 1`] = ` -{ - "Resources": { - "Bucket83908E77": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchKeyKmsEncryptionEFBC1FE2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKeyKmsEncryption8BC9FA8C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ValidateBucketKmsEncryptionBucketPolicyBucketPolicyResource6D974B64": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateBucketKmsEncryptionBucketPolicyFunctionResourceLogGroup18E1F873", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventE38E2CFC", - "Arn", - ], - }, - "applyAcceleratorManagedPolicy": true, - "awsPrincipalAccesses": [ - { - "accessType": "RW", - "name": "macie", - "principal": "macie.amazonaws.com", - }, - ], - "bucketArn": { - "Fn::GetAtt": [ - "Bucket83908E77", - "Arn", - ], - }, - "bucketName": { - "Ref": "Bucket83908E77", - }, - "bucketPolicyFilePaths": [ - "bucket-policy/central-log-bucket.json", - ], - "bucketType": "central-logs", - "organizationId": "o-org-id", - "principalOrgIdCondition": { - "Service": "macie.amazonaws.com", - }, - "sourceAccount": { - "Ref": "AWS::AccountId", - }, - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionBucketPolicyFunctionAF7853D6": { - "DependsOn": [ - "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRoleDefaultPolicy5E4996FF", - "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRole16859A00", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed BucketPolicy custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyKmsEncryption8BC9FA8C", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRole16859A00", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionBucketPolicyFunctionResourceLogGroup18E1F873": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyKmsEncryptionEFBC1FE2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateBucketKmsEncryptionBucketPolicyFunctionAF7853D6", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRole16859A00": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRoleDefaultPolicy5E4996FF": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:PutBucketPolicy", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "Bucket83908E77", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRoleDefaultPolicy5E4996FF", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionBucketPolicyFunctionServiceRole16859A00", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventE38E2CFC": { - "DependsOn": [ - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleDefaultPolicyF29C1115", - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleA0E7A727", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ValidateBucketKmsEncryption/BucketPolicy/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketPolicyFunctionAF7853D6", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleA0E7A727", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleA0E7A727": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleDefaultPolicyF29C1115": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketPolicyFunctionAF7853D6", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionBucketPolicyFunctionAF7853D6", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleDefaultPolicyF29C1115", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionBucketPolicyframeworkonEventServiceRoleA0E7A727", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-prefix.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-prefix.test.ts.snap deleted file mode 100644 index d63aefc..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-prefix.test.ts.snap +++ /dev/null @@ -1,334 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BucketPrefix Construct(BucketPrefix): Snapshot Test 1`] = ` -{ - "Resources": { - "BucketPrefixS3CreateBucketPrefixFunction9F1947D5": { - "DependsOn": [ - "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleDefaultPolicyD7D21A5D", - "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleFB8AFED9", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed S3CreateBucketPrefix custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKey984A39D9", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleFB8AFED9", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "BucketPrefixS3CreateBucketPrefixFunctionResourceLogGroup54BAB5A2": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "BucketPrefixS3CreateBucketPrefixFunction9F1947D5", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleDefaultPolicyD7D21A5D": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:ListBucket", - "s3:GetObject", - "s3:PutObject", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleDefaultPolicyD7D21A5D", - "Roles": [ - { - "Ref": "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleFB8AFED9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketPrefixS3CreateBucketPrefixFunctionServiceRoleFB8AFED9": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "BucketPrefixS3CreateBucketPrefixS3CreateBucketPrefixResourceF5F00D3C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "BucketPrefixS3CreateBucketPrefixFunctionResourceLogGroup54BAB5A2", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "BucketPrefixS3CreateBucketPrefixframeworkonEvent26FE41DA", - "Arn", - ], - }, - "bucketPrefixes": [ - "guardduty", - ], - "sourceBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-central-logs-bucket-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "uuid": "REPLACED-UUID", - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "BucketPrefixS3CreateBucketPrefixframeworkonEvent26FE41DA": { - "DependsOn": [ - "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleDefaultPolicy21D6F213", - "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleD3350EF1", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/BucketPrefix/S3CreateBucketPrefix/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "BucketPrefixS3CreateBucketPrefixFunction9F1947D5", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleD3350EF1", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleD3350EF1": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleDefaultPolicy21D6F213": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "BucketPrefixS3CreateBucketPrefixFunction9F1947D5", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketPrefixS3CreateBucketPrefixFunction9F1947D5", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleDefaultPolicy21D6F213", - "Roles": [ - { - "Ref": "BucketPrefixS3CreateBucketPrefixframeworkonEventServiceRoleD3350EF1", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKey984A39D9": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-replication.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-replication.test.ts.snap deleted file mode 100644 index be7c067..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket-replication.test.ts.snap +++ /dev/null @@ -1,486 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BucketReplication Construct(BucketReplication): Snapshot Test 1`] = ` -{ - "Resources": { - "BucketReplicationAwsAcceleratorCentralLogsBucketReplicationRole184AC22C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "BucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy5AD1044F": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy5AD1044F", - "Roles": [ - { - "Ref": "BucketReplicationAwsAcceleratorCentralLogsBucketReplicationRole184AC22C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketReplicationDF143956": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "BucketReplicationAwsAcceleratorCentralLogsBucketReplicationRole184AC22C", - "Arn", - ], - }, - "sourceBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "BucketReplicationExistingIamA4FEEDD2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket-existing-iam", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/AWSAcceleratorS3ReplicationRole", - ], - ], - }, - "sourceBucketName": { - "Ref": "SourceBucketDDD2130A", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "CWLKeyB9201973": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKeyExistingIamDCB4F720": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CWLKeyB9201973", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SourceBucketDDD2130A": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "SourceBucketExistingIam4DBB8957", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SourceBucketExistingIam4DBB8957": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket.test.ts.snap deleted file mode 100644 index a32f9ef..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/bucket.test.ts.snap +++ /dev/null @@ -1,2731 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Bucket Construct(Bucket): Snapshot Test 1`] = ` -{ - "Resources": { - "BucketAwsPrincipalAccess2BA6D9C4": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "CustomKey1EF07F540", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "Tags": [ - { - "Key": "aws-cdk:auto-somename1-access-bucket", - "Value": "true", - }, - { - "Key": "aws-cdk:auto-somename2-access-bucket", - "Value": "true", - }, - { - "Key": "aws-cdk:auto-somename3-access-bucket", - "Value": "true", - }, - ], - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketAwsPrincipalAccessBucketAwsPrincipalAccessReplication7C3AB1E2": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccessBucketAwsPrincipalAccessReplicationAwsAcceleratorCentralLogsBucketReplicationRole1A806901", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "BucketAwsPrincipalAccess2BA6D9C4", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "BucketAwsPrincipalAccessBucketAwsPrincipalAccessReplicationAwsAcceleratorCentralLogsBucketReplicationRole1A806901": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "BucketAwsPrincipalAccessBucketAwsPrincipalAccessReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy8CCC5B66": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1EF07F540", - "Arn", - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketAwsPrincipalAccessBucketAwsPrincipalAccessReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy8CCC5B66", - "Roles": [ - { - "Ref": "BucketAwsPrincipalAccessBucketAwsPrincipalAccessReplicationAwsAcceleratorCentralLogsBucketReplicationRole1A806901", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketAwsPrincipalAccessPolicy62349E94": { - "Properties": { - "Bucket": { - "Ref": "BucketAwsPrincipalAccess2BA6D9C4", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Principal": { - "Service": "principal1.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "Service": "principal2.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Principal": { - "Service": "principal3.amazonaws.com", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketAwsPrincipalAccess2BA6D9C4", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "BucketBucketReplication33A48F83": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "BucketBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleC235BD7C", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "BucketE75EA64C", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "BucketBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleC235BD7C": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "BucketBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy15E626E1": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "BucketE75EA64C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketE75EA64C", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy15E626E1", - "Roles": [ - { - "Ref": "BucketBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleC235BD7C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketE75EA64C": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketLifeCycleProvidedBC4D367D": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "CustomKeyLifeCycleProvided39D4346A", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 24, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "1", - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 12, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "GLACIER", - "TransitionInDays": 365, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "STANDARD_IA", - "TransitionInDays": 7, - }, - ], - }, - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 24, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "2", - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 12, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "GLACIER", - "TransitionInDays": 365, - }, - ], - "Prefix": "PREFIX1", - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "STANDARD_IA", - "TransitionInDays": 7, - }, - ], - }, - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpiredObjectDeleteMarker": true, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "3", - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 12, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "GLACIER", - "TransitionInDays": 365, - }, - ], - "Prefix": "PREFIX2", - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "STANDARD_IA", - "TransitionInDays": 7, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketLifeCycleProvidedPolicy6ED87BA2": { - "Properties": { - "Bucket": { - "Ref": "BucketLifeCycleProvidedBC4D367D", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketLifeCycleProvidedBC4D367D", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketLifeCycleProvidedBC4D367D", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "BucketNoBucketAccessToAwsPrincipal0B01FBF6": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "CustomKeyNoBucketAccessToAwsPrincipal67B73EC6", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketNoBucketAccessToAwsPrincipalPolicy464C23FC": { - "Properties": { - "Bucket": { - "Ref": "BucketNoBucketAccessToAwsPrincipal0B01FBF6", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketNoBucketAccessToAwsPrincipal0B01FBF6", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketNoBucketAccessToAwsPrincipal0B01FBF6", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "BucketPolicy8005AAB0": { - "Properties": { - "Bucket": { - "Ref": "BucketE75EA64C", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketE75EA64C", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketE75EA64C", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "BucketServerAccessBucketBucketServerAccessBucketReplication84477944": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "BucketServerAccessBucketBucketServerAccessBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleB51CBDB4", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "BucketServerAccessBucketFC17BBE8", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "BucketServerAccessBucketBucketServerAccessBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleB51CBDB4": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "BucketServerAccessBucketBucketServerAccessBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy618A8344": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "BucketServerAccessBucketFC17BBE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketServerAccessBucketFC17BBE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketServerAccessBucketBucketServerAccessBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy618A8344", - "Roles": [ - { - "Ref": "BucketServerAccessBucketBucketServerAccessBucketReplicationAwsAcceleratorCentralLogsBucketReplicationRoleB51CBDB4", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketServerAccessBucketFC17BBE8": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "testbucket", - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": "LifecycleRuletestbucket", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "serverAccessLogsBucket384F7E6F", - }, - "LogFilePrefix": "testbucket/", - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketServerAccessBucketPolicyD3D884B3": { - "Properties": { - "Bucket": { - "Ref": "BucketServerAccessBucketFC17BBE8", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketServerAccessBucketFC17BBE8", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketServerAccessBucketFC17BBE8", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "BucketSseNoKms034DA0EB": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "BucketSseNoKmsCmkBDD78E94", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-macie-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketSseNoKmsBucketSseNoKmsReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy67755573": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "BucketSseNoKms034DA0EB", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketSseNoKms034DA0EB", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "BucketSseNoKmsCmkBDD78E94", - "Arn", - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketSseNoKmsBucketSseNoKmsReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicy67755573", - "Roles": [ - { - "Ref": "BucketSseNoKmsBucketSseNoKmsReplicationAwsAcceleratorCentralLogsBucketReplicationRoleE74AE850", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketSseNoKmsBucketSseNoKmsReplicationAwsAcceleratorCentralLogsBucketReplicationRoleE74AE850": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "BucketSseNoKmsBucketSseNoKmsReplicationF3996D6E": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "BucketSseNoKmsBucketSseNoKmsReplicationAwsAcceleratorCentralLogsBucketReplicationRoleE74AE850", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "BucketSseNoKms034DA0EB", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "BucketSseNoKmsCmkAlias910EEA0F": { - "Properties": { - "AliasName": "alias/kmsAliasName", - "TargetKeyId": { - "Fn::GetAtt": [ - "BucketSseNoKmsCmkBDD78E94", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "BucketSseNoKmsCmkBDD78E94": { - "DeletionPolicy": "Retain", - "Properties": { - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "BucketSseNoKmsPolicyBBB4A613": { - "Properties": { - "Bucket": { - "Ref": "BucketSseNoKms034DA0EB", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketSseNoKms034DA0EB", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketSseNoKms034DA0EB", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "BucketSseS3BucketSseS3Replication4E9D4F48": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - "Arn", - ], - }, - "destinationAccountId": { - "Ref": "AWS::AccountId", - }, - "destinationBucketArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket", - ], - ], - }, - "destinationBucketKeyArn": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - "prefix": "", - "replicationRoleArn": { - "Fn::GetAtt": [ - "BucketSseS3BucketSseS3ReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDB03BBA9", - "Arn", - ], - }, - "sourceBucketName": { - "Ref": "BucketSseS3ECFEB958", - }, - }, - "Type": "Custom::S3PutBucketReplication", - "UpdateReplacePolicy": "Delete", - }, - "BucketSseS3BucketSseS3ReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDB03BBA9": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "Path": "/service-role/", - }, - "Type": "AWS::IAM::Role", - }, - "BucketSseS3BucketSseS3ReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicyC0704764": { - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObjectLegalHold", - "s3:GetObjectRetention", - "s3:GetObjectVersion", - "s3:GetObjectVersionAcl", - "s3:GetObjectVersionForReplication", - "s3:GetObjectVersionTagging", - "s3:GetReplicationConfiguration", - "s3:ListBucket", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "BucketSseS3ECFEB958", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketSseS3ECFEB958", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "s3:GetBucketVersioning", - "s3:GetObjectVersionTagging", - "s3:ObjectOwnerOverrideToBucketOwner", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ReplicateTags", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::aws-accelerator-central-logs-bucket/*", - ], - ], - }, - }, - { - "Action": "kms:Encrypt", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:kms:us-east-1:", - { - "Ref": "AWS::AccountId", - }, - ":key/ksm-key-arn", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "BucketSseS3BucketSseS3ReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDefaultPolicyC0704764", - "Roles": [ - { - "Ref": "BucketSseS3BucketSseS3ReplicationAwsAcceleratorCentralLogsBucketReplicationRoleDB03BBA9", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "BucketSseS3ECFEB958": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": "testbucket", - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": "LifecycleRuletestbucket", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketSseS3Policy60624C99": { - "Properties": { - "Bucket": { - "Ref": "BucketSseS3ECFEB958", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "BucketSseS3ECFEB958", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "BucketSseS3ECFEB958", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "CWLKey1A5231300": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKey237475AFB": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKey4F8144065": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKey527D84753": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKey6989841D1": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKeyB9201973": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKeyNoBucketAccessToAwsPrincipal32572324": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CWLKeyTooMuchServerAccessDFA642F4": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKey1EF07F540": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "principal1.amazonaws.com", - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Effect": "Allow", - "Principal": { - "Service": "principal2.amazonaws.com", - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Principal": { - "Service": "principal3.amazonaws.com", - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyLifeCycleProvided39D4346A": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomKeyNoBucketAccessToAwsPrincipal67B73EC6": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C": { - "DependsOn": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutBucketReplicationCustomResourceProviderLogGroup6A67905E": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CWLKeyB9201973", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutBucketReplicationCustomResourceProviderHandler1D75398C", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutBucketReplicationCustomResourceProviderRole1C378488": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "iam:PassRole", - "s3:PutLifecycleConfiguration", - "s3:PutReplicationConfiguration", - "s3:PutBucketVersioning", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "S3PutReplicationConfigurationTaskActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "serverAccessLogsBucket1EADBEB64": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "serverAccessLogsBucket384F7E6F": { - "DeletionPolicy": "Retain", - "Properties": { - "AccessControl": "LogDeliveryWrite", - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "ObjectWriter", - }, - ], - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "serverAccessLogsBucketTooMuchServerAccessD7A30F75": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/central-logs-buckets.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/central-logs-buckets.test.ts.snap deleted file mode 100644 index e34c25f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/central-logs-buckets.test.ts.snap +++ /dev/null @@ -1,749 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CentralLogsBucket Construct(CentralLogsBucket): Snapshot Test 1`] = ` -{ - "Resources": { - "AccessLogsBucketFA218D2A": { - "DeletionPolicy": "Retain", - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-s3-access-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "AccessLogsBucketPolicy00F12803": { - "Properties": { - "Bucket": { - "Ref": "AccessLogsBucketFA218D2A", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "AccessLogsBucketFA218D2A", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "AccessLogsBucketFA218D2A", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "CentralLogsBucket447B5C59": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "CentralLogsBucketCmkBA0AB2FC", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - "aws-accelerator-central-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRuleaws-accelerator-central-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "AccessLogsBucketFA218D2A", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - "aws-accelerator-central-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CentralLogsBucketCmkAlias286EB783": { - "Properties": { - "AliasName": "alias/accelerator/central-logs/s3", - "TargetKeyId": { - "Fn::GetAtt": [ - "CentralLogsBucketCmkBA0AB2FC", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "CentralLogsBucketCmkBA0AB2FC": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Central Logs Bucket CMK", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - "Sid": "Enable IAM User Permissions", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:GenerateRandom", - "kms:GetKeyPolicy", - "kms:GetKeyRotationStatus", - "kms:ListAliases", - "kms:ListGrants", - "kms:ListKeyPolicies", - "kms:ListKeys", - "kms:ListResourceTags", - "kms:ListRetirableGrants", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - ], - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow S3 use of the key", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "config.amazonaws.com", - "cloudtrail.amazonaws.com", - "delivery.logs.amazonaws.com", - "ssm.amazonaws.com", - ], - }, - "Resource": "*", - "Sid": "Allow AWS Services to encrypt and describe logs", - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:GenerateDataKeyPair", - "kms:GenerateDataKeyPairWithoutPlaintext", - "kms:GenerateDataKeyWithoutPlaintext", - "kms:ReEncryptFrom", - "kms:ReEncryptTo", - "kms:ListAliases", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "acceleratorOrg", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Organization use of the key", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CentralLogsBucketCrossAccountCentralBucketKMSArnSsmParamAccessRole83E55C59": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "acceleratorOrg", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - }, - ], - "Version": "2012-10-17", - }, - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter", - { - "Ref": "CentralLogsBucketSsmParamCentralAccountBucketKMSArn57B61EA3", - }, - ], - ], - }, - }, - { - "Action": "ssm:DescribeParameters", - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "default", - }, - ], - "RoleName": "AWSAccelerator-CentralBucket-KeyArnParam-Role", - }, - "Type": "AWS::IAM::Role", - }, - "CentralLogsBucketPolicyC0B90E7C": { - "Properties": { - "Bucket": { - "Ref": "CentralLogsBucket447B5C59", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com", - "config.amazonaws.com", - "delivery.logs.amazonaws.com", - "ssm.amazonaws.com", - ], - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - "/*", - ], - ], - }, - }, - { - "Action": [ - "s3:GetBucketAcl", - "s3:ListBucket", - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com", - "config.amazonaws.com", - "delivery.logs.amazonaws.com", - ], - }, - "Resource": { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:GetBucketAcl", - "s3:PutObject", - "s3:GetObject", - "s3:ListBucket", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator-*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/cdk-accel-*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSControlTowerExecution-*", - ], - ], - }, - ], - }, - "StringEquals": { - "aws:PrincipalOrgID": "acceleratorOrg", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "Allow Organization principals to use the bucket", - }, - { - "Action": [ - "s3:List*", - "s3:GetBucketVersioning", - "s3:PutBucketVersioning", - "s3:ReplicateDelete", - "s3:ReplicateObject", - "s3:ObjectOwnerOverrideToBucketOwner", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalOrgID": "acceleratorOrg", - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "CentralLogsBucket447B5C59", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "Allow Organization use of the bucket for replication", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "CentralLogsBucketSsmParamCentralAccountBucketKMSArn57B61EA3": { - "Properties": { - "Name": "/accelerator/logging/central-bucket/kms/arn", - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "CentralLogsBucketCmkBA0AB2FC", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/public-access-block.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/public-access-block.test.ts.snap deleted file mode 100644 index 21e1be7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/public-access-block.test.ts.snap +++ /dev/null @@ -1,154 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`S3PublicAccessBlock Construct(S3PublicAccessBlock): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B": { - "DependsOn": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderLogGroup354123A8": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomS3PutPublicAccessBlockCustomResourceProviderRole656EB36E": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:PutAccountPublicAccessBlock", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "S3PublicAccessBlock344F906B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderLogGroup354123A8", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomS3PutPublicAccessBlockCustomResourceProviderHandler978E227B", - "Arn", - ], - }, - "accountId": { - "Ref": "AWS::AccountId", - }, - "blockPublicAcls": true, - "blockPublicPolicy": true, - "ignorePublicAcls": true, - "restrictPublicBuckets": true, - }, - "Type": "Custom::PutPublicAccessBlock", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/validate-bucket.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/validate-bucket.test.ts.snap deleted file mode 100644 index b23d070..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/__snapshots__/validate-bucket.test.ts.snap +++ /dev/null @@ -1,1459 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ValidateBucketKmsEncryption Construct(ValidateBucketKmsEncryption): Snapshot Test 1`] = ` -{ - "Resources": { - "BucketKmsEncryption3526A88C": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketS3Encryption33776260": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchKeyKmsEncryptionEFBC1FE2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchKeyS3EncryptionBF53F87D": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKeyKmsEncryption8BC9FA8C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKeyS3EncryptionA5A84879": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216": { - "DependsOn": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRoleDefaultPolicy36876301", - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyKmsEncryption8BC9FA8C", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionResourceLogGroup0C70290F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyKmsEncryptionEFBC1FE2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRoleDefaultPolicy36876301": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "BucketKmsEncryption3526A88C", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRoleDefaultPolicy36876301", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketKmsEncryptionValidateBucketValidateBucketResource2FD53956": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionResourceLogGroup0C70290F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventB0DCF88B", - "Arn", - ], - }, - "bucketName": { - "Ref": "BucketKmsEncryption3526A88C", - }, - "encryptionType": "kms", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventB0DCF88B": { - "DependsOn": [ - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy77EFEE09", - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ValidateBucketKmsEncryption/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy77EFEE09": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy77EFEE09", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE": { - "DependsOn": [ - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRoleDefaultPolicyB6CF9314", - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyS3EncryptionA5A84879", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketS3EncryptionValidateBucketFunctionResourceLogGroup9806F780": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyS3EncryptionBF53F87D", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRoleDefaultPolicyB6CF9314": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "BucketS3Encryption33776260", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketS3EncryptionValidateBucketFunctionServiceRoleDefaultPolicyB6CF9314", - "Roles": [ - { - "Ref": "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketS3EncryptionValidateBucketValidateBucketResource8AFD01C8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateBucketS3EncryptionValidateBucketFunctionResourceLogGroup9806F780", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketframeworkonEvent1C646E76", - "Arn", - ], - }, - "bucketName": { - "Ref": "BucketS3Encryption33776260", - }, - "encryptionType": "kms", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketS3EncryptionValidateBucketframeworkonEvent1C646E76": { - "DependsOn": [ - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy0C181827", - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ValidateBucketS3Encryption/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy0C181827": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy0C181827", - "Roles": [ - { - "Ref": "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; - -exports[`ValidateBucketS3Encryption Construct(ValidateBucketS3Encryption): Snapshot Test 1`] = ` -{ - "Resources": { - "BucketKmsEncryption3526A88C": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "BucketS3Encryption33776260": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchKeyKmsEncryptionEFBC1FE2": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CloudWatchKeyS3EncryptionBF53F87D": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKeyKmsEncryption8BC9FA8C": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "LambdaKeyS3EncryptionA5A84879": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216": { - "DependsOn": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRoleDefaultPolicy36876301", - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyKmsEncryption8BC9FA8C", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionResourceLogGroup0C70290F": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyKmsEncryptionEFBC1FE2", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRoleDefaultPolicy36876301": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "BucketKmsEncryption3526A88C", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRoleDefaultPolicy36876301", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionValidateBucketFunctionServiceRole1493FC5C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketKmsEncryptionValidateBucketValidateBucketResource2FD53956": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionResourceLogGroup0C70290F", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventB0DCF88B", - "Arn", - ], - }, - "bucketName": { - "Ref": "BucketKmsEncryption3526A88C", - }, - "encryptionType": "kms", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventB0DCF88B": { - "DependsOn": [ - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy77EFEE09", - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ValidateBucketKmsEncryption/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy77EFEE09": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateBucketKmsEncryptionValidateBucketFunctionD829E216", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy77EFEE09", - "Roles": [ - { - "Ref": "ValidateBucketKmsEncryptionValidateBucketframeworkonEventServiceRoleC40E1A6E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE": { - "DependsOn": [ - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRoleDefaultPolicyB6CF9314", - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Accelerator deployed ValidateBucket custom resource lambda function.", - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "LambdaKeyS3EncryptionA5A84879", - "Arn", - ], - }, - "MemorySize": 512, - "Role": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 300, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketS3EncryptionValidateBucketFunctionResourceLogGroup9806F780": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CloudWatchKeyS3EncryptionBF53F87D", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - }, - ], - ], - }, - "RetentionInDays": 365, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ValidateBucketS3EncryptionValidateBucketFunctionServiceRoleDefaultPolicyB6CF9314": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "AWS Lambda needs Managed policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "BucketS3Encryption33776260", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketS3EncryptionValidateBucketFunctionServiceRoleDefaultPolicyB6CF9314", - "Roles": [ - { - "Ref": "ValidateBucketS3EncryptionValidateBucketFunctionServiceRole1CBF0D3D", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketS3EncryptionValidateBucketValidateBucketResource8AFD01C8": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "ValidateBucketS3EncryptionValidateBucketFunctionResourceLogGroup9806F780", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketframeworkonEvent1C646E76", - "Arn", - ], - }, - "bucketName": { - "Ref": "BucketS3Encryption33776260", - }, - "encryptionType": "kms", - "uuid": "REPLACED-UUID", - "validationCheckList": [ - "encryption", - ], - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateBucketS3EncryptionValidateBucketframeworkonEvent1C646E76": { - "DependsOn": [ - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy0C181827", - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "AWS CDK resource provider framework - onEvent (Default/ValidateBucketS3Encryption/ValidateBucket/Resource)", - "Environment": { - "Variables": { - "USER_ON_EVENT_FUNCTION_ARN": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - "Arn", - ], - }, - }, - }, - "Handler": "framework.onEvent", - "Role": { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy0C181827": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "Allows only specific policy.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ValidateBucketS3EncryptionValidateBucketFunction1943C2CE", - "Arn", - ], - }, - ":*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleDefaultPolicy0C181827", - "Roles": [ - { - "Ref": "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "ValidateBucketS3EncryptionValidateBucketframeworkonEventServiceRoleE2623E8E": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "AWS Custom resource provider framework-role created by cdk.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-encryption.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-encryption.test.ts deleted file mode 100644 index 1509360..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-encryption.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { BucketEncryption } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(ValidateBucketKmsEncryption): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new BucketEncryption(stack, 'ValidateBucketKmsEncryption', { - bucket: new cdk.aws_s3.Bucket(stack, 'Bucket'), - kmsKey: new cdk.aws_kms.Key(stack, 'BucketKey', {}), - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKeyKmsEncryption', {}), - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKeyKmsEncryption', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * BucketEncryption construct test - */ -describe('BucketEncryption', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-policy.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-policy.test.ts deleted file mode 100644 index 3fd5b49..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-policy.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { BucketPolicy } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; -import { AcceleratorImportedBucketType } from '@aws-accelerator/utils'; - -const testNamePrefix = 'Construct(ValidateBucketKmsEncryption): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new BucketPolicy(stack, 'ValidateBucketKmsEncryption', { - applyAcceleratorManagedPolicy: true, - bucketType: AcceleratorImportedBucketType.CENTRAL_LOGS_BUCKET, - bucket: new cdk.aws_s3.Bucket(stack, 'Bucket'), - bucketPolicyFilePaths: [ - `${__dirname}/../../../accelerator/test/configs/snapshot-only/bucket-policies/central-log-bucket.json`, - ], - principalOrgIdCondition: { - Service: 'macie.amazonaws.com', - }, - awsPrincipalAccesses: [{ name: 'macie', accessType: 'RW', principal: 'macie.amazonaws.com' }], - organizationId: 'o-org-id', - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKeyKmsEncryption', {}), - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKeyKmsEncryption', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * BucketPolicy construct test - */ -describe('BucketPolicy', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-prefix.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-prefix.test.ts deleted file mode 100644 index 92930f5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-prefix.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { BucketPrefix } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(BucketPrefix): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new BucketPrefix(stack, 'BucketPrefix', { - source: { bucketName: `aws-accelerator-central-logs-bucket-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}` }, - bucketPrefixes: ['guardduty'], - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKey', {}), - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - customResourceLambdaLogRetentionInDays: 3653, - nagSuppressionPrefix: 'BucketPrefix/Resource/BucketPrefix', -}); - -/** - * BucketPrefix construct test - */ -describe('BucketPrefix', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-replication.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-replication.test.ts deleted file mode 100644 index a6619f7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket-replication.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { BucketReplication } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; -import { describe, expect, test } from '@jest/globals'; - -const testNamePrefix = 'Construct(BucketReplication): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new BucketReplication(stack, 'BucketReplication', { - source: { - bucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - }, - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', -}); - -new BucketReplication(stack, 'BucketReplicationExistingIam', { - source: { - bucket: new cdk.aws_s3.Bucket(stack, 'SourceBucket', { - encryption: cdk.aws_s3.BucketEncryption.KMS, - encryptionKey: new cdk.aws_kms.Key(stack, 'SourceBucketExistingIam'), - }), - }, - destination: { - bucketName: `aws-accelerator-central-logs-bucket-existing-iam`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKeyExistingIam', {}), - logRetentionInDays: 3653, - useExistingRoles: true, - acceleratorPrefix: 'AWSAccelerator', -}); -/** - * BucketReplication construct test - */ -describe('BucketReplication', () => { - snapShotTest(testNamePrefix, stack); -}); - -test('should throw an exception when source bucket name and bucket are present', () => { - function s3SourceBucketError1() { - new BucketReplication(stack, 'BucketReplicationSourceBucketError1', { - source: { - bucket: new cdk.aws_s3.Bucket(stack, 'SourceBucketError1', {}), - bucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - }, - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKeySourceBucketError1', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }); - } - - const errMsg = 'Source bucket or source bucketName (only one property) should be defined.'; - expect(s3SourceBucketError1).toThrow(new Error(errMsg)); -}); - -test('should throw an exception when source bucket name and bucket are not defined', () => { - function s3SourceBucketError2() { - new BucketReplication(stack, 'BucketReplicationSourceBucketError2', { - source: { - bucket: undefined, - bucketName: undefined, - }, - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKeySourceBucketError2', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }); - } - - const errMsg = 'Source bucket or source bucketName property must be defined when using bucket replication.'; - expect(s3SourceBucketError2).toThrow(new Error(errMsg)); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket.test.ts deleted file mode 100644 index 7079945..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/bucket.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { BucketAccessType } from '@aws-accelerator/utils'; -import { Bucket, BucketEncryptionType } from '../../lib/aws-s3/bucket'; -import { snapShotTest } from '../snapshot-test'; -import { describe, it, expect } from '@jest/globals'; - -const testNamePrefix = 'Construct(Bucket): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -/** - * Bucket construct test - */ -describe('Bucket', () => { - it('test standard snapshot', () => { - const standardTest = new Bucket(stack, 'Bucket', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - nagSuppressionPrefix: 'BucketPrefix/Resource', - }); - // test methods to call bucket and bucket kms key - // the values are cdk tokens so just check if they are string - expect(standardTest.getKey().keyArn).toBeDefined(); - expect(standardTest.getS3Bucket().bucketName).toBeDefined(); - }); - it('test awsPrincipals', () => { - new Bucket(stack, 'BucketAwsPrincipalAccess', { - encryptionType: BucketEncryptionType.SSE_KMS, - awsPrincipalAccesses: [ - { name: 'someName1', principal: 'principal1', accessType: BucketAccessType.READONLY }, - { name: 'someName2', principal: 'principal2', accessType: BucketAccessType.WRITEONLY }, - { name: 'someName3', principal: 'principal3', accessType: BucketAccessType.READWRITE }, - ], - s3BucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey1', {}), - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey1', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - }); - it('test when sse_kms but no kms is provided', () => { - new Bucket(stack, 'BucketSseNoKms', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - kmsAliasName: 'kmsAliasName', - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey2', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - }); - it('test when kms is sse_s3', () => { - new Bucket(stack, 'BucketSseS3', { - encryptionType: BucketEncryptionType.SSE_S3, - s3BucketName: 'testbucket', - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey4', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - }); - it('Should throw exception when no cmk is set but cmk is called', () => { - function callCmkKeyOnBucketWithoutCmk() { - const noKmsBucket = new Bucket(stack, 'BucketSseS3CmkError', { - encryptionType: BucketEncryptionType.SSE_S3, - s3BucketName: 'testbucket', - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey4', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - noKmsBucket.getKey().keyArn; - return noKmsBucket.getS3Bucket().bucketName; - } - - expect(callCmkKeyOnBucketWithoutCmk).toThrow(Error); - }); - - it('server access logs bucket name is not set and server access logs bucket is provided', () => { - new Bucket(stack, 'BucketServerAccessBucket', { - encryptionType: BucketEncryptionType.SSE_S3, - s3BucketName: 'testbucket', - serverAccessLogsBucket: new cdk.aws_s3.Bucket(stack, 'serverAccessLogsBucket'), - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey5', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - }); - it('server access logs bucket name is not set and s3bucket is not set', () => { - function noS3noServerAccess() { - new Bucket(stack, 'BucketNoS3noServerAccess', { - encryptionType: BucketEncryptionType.SSE_S3, - serverAccessLogsBucket: new cdk.aws_s3.Bucket(stack, 'serverAccessLogsBucket1'), - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKey6', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - } - expect(noS3noServerAccess).toThrow( - new Error('s3BucketName or serverAccessLogsPrefix property must be defined when using serverAccessLogs.'), - ); - }); - it('server access logs bucket name and server access logs bucket is provided', () => { - function tooMuchServerAccess() { - new Bucket(stack, 'BucketTooMuchServerAccess', { - encryptionType: BucketEncryptionType.SSE_S3, - serverAccessLogsBucket: new cdk.aws_s3.Bucket(stack, 'serverAccessLogsBucketTooMuchServerAccess'), - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKeyTooMuchServerAccess', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - } - expect(tooMuchServerAccess).toThrow( - new Error('serverAccessLogsBucketName or serverAccessLogsBucket (only one property) should be defined.'), - ); - }); - it('test awsPrincipals with no access', () => { - function noBucketAccessToAwsPrincipal() { - new Bucket(stack, 'BucketNoBucketAccessToAwsPrincipal', { - encryptionType: BucketEncryptionType.SSE_KMS, - awsPrincipalAccesses: [{ name: 'someName1', principal: 'principal1', accessType: BucketAccessType.NO_ACCESS }], - s3BucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyNoBucketAccessToAwsPrincipal', {}), - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - replicationProps: { - destination: { - bucketName: `aws-accelerator-central-logs-bucket`, - accountId: cdk.Aws.ACCOUNT_ID, - keyArn: `arn:aws:kms:us-east-1:${cdk.Aws.ACCOUNT_ID}:key/ksm-key-arn`, - }, - kmsKey: new cdk.aws_kms.Key(stack, 'CWLKeyNoBucketAccessToAwsPrincipal', {}), - logRetentionInDays: 3653, - useExistingRoles: false, - acceleratorPrefix: 'AWSAccelerator', - }, - }); - } - expect(noBucketAccessToAwsPrincipal).toThrow(new Error('Invalid Access Type no_access for principal1 principal.')); - }); - it('test with s3 lifecycle rules provided', () => { - new Bucket(stack, 'BucketLifeCycleProvided', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: `aws-accelerator-macie-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKeyLifeCycleProvided', {}), - serverAccessLogsBucketName: `aws-accelerator-s3-access-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - s3LifeCycleRules: [ - { - id: '1', - abortIncompleteMultipartUploadAfter: 1, - enabled: true, - expiration: 24, - expiredObjectDeleteMarker: false, - noncurrentVersionExpiration: 12, - transitions: [ - { - storageClass: 'STANDARD_IA', - transitionAfter: 7, - }, - ], - noncurrentVersionTransitions: [ - { - storageClass: 'GLACIER', - transitionAfter: 365, - }, - ], - }, - { - id: '2', - abortIncompleteMultipartUploadAfter: 1, - enabled: true, - expiration: 24, - expiredObjectDeleteMarker: false, - noncurrentVersionExpiration: 12, - transitions: [ - { - storageClass: 'STANDARD_IA', - transitionAfter: 7, - }, - ], - noncurrentVersionTransitions: [ - { - storageClass: 'GLACIER', - transitionAfter: 365, - }, - ], - prefix: 'PREFIX1', - }, - { - id: '3', - abortIncompleteMultipartUploadAfter: 1, - enabled: true, - expiredObjectDeleteMarker: true, - noncurrentVersionExpiration: 12, - transitions: [ - { - storageClass: 'STANDARD_IA', - transitionAfter: 7, - }, - ], - noncurrentVersionTransitions: [ - { - storageClass: 'GLACIER', - transitionAfter: 365, - }, - ], - prefix: 'PREFIX2', - }, - ], - }); - }); - - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/central-logs-buckets.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/central-logs-buckets.test.ts deleted file mode 100644 index 5f89c32..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/central-logs-buckets.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Bucket, BucketEncryptionType, CentralLogsBucket } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(CentralLogsBucket): '; -const organizationId = 'acceleratorOrg'; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new CentralLogsBucket(stack, 'CentralLogsBucket', { - s3BucketName: `aws-accelerator-central-logs-${stack.account}-${stack.region}`, - serverAccessLogsBucket: new Bucket(stack, 'AccessLogsBucket', { - encryptionType: BucketEncryptionType.SSE_S3, - s3BucketName: `aws-accelerator-s3-access-logs-${stack.account}-${stack.region}`, - kmsAliasName: 'alias/accelerator/s3-access-logs/s3', - kmsDescription: 'AWS Accelerator S3 Access Logs Bucket CMK', - }).getS3Bucket(), - kmsAliasName: 'alias/accelerator/central-logs/s3', - kmsDescription: 'AWS Accelerator Central Logs Bucket CMK', - principalOrgIdCondition: { 'aws:PrincipalOrgID': organizationId }, - orgPrincipals: new cdk.aws_iam.OrganizationPrincipal(organizationId), - acceleratorPrefix: 'AWSAccelerator', - crossAccountAccessRoleName: 'AWSAccelerator-CentralBucket-KeyArnParam-Role', - cmkArnSsmParameterName: '/accelerator/logging/central-bucket/kms/arn', - managementAccountAccessRole: 'AWSControlTowerExecution', -}); - -/** - * CentralLogsBucket construct test - */ -describe('CentralLogsBucket', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/public-access-block.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/public-access-block.test.ts deleted file mode 100644 index e4b553e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/public-access-block.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { S3PublicAccessBlock } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(S3PublicAccessBlock): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new S3PublicAccessBlock(stack, 'S3PublicAccessBlock', { - blockPublicAcls: true, - blockPublicPolicy: true, - ignorePublicAcls: true, - restrictPublicBuckets: true, - accountId: stack.account, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * CentralLogsBucket construct test - */ -describe('S3PublicAccessBlock', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-s3/validate-bucket.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-s3/validate-bucket.test.ts deleted file mode 100644 index e5bf10c..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-s3/validate-bucket.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { ValidateBucket } from '@aws-accelerator/constructs'; -import { snapShotTest } from '../snapshot-test'; - -let testNamePrefix = 'Construct(ValidateBucketKmsEncryption): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new ValidateBucket(stack, 'ValidateBucketKmsEncryption', { - bucket: new cdk.aws_s3.Bucket(stack, 'BucketKmsEncryption'), - validationCheckList: ['encryption'], - encryptionType: 'kms', - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKeyKmsEncryption', {}), - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKeyKmsEncryption', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * ValidateBucketKmsEncryption construct test - */ -describe('ValidateBucketKmsEncryption', () => { - snapShotTest(testNamePrefix, stack); -}); - -testNamePrefix = 'Construct(ValidateBucketS3Encryption): '; - -new ValidateBucket(stack, 'ValidateBucketS3Encryption', { - bucket: new cdk.aws_s3.Bucket(stack, 'BucketS3Encryption'), - validationCheckList: ['encryption'], - encryptionType: 'kms', - customResourceLambdaCloudWatchLogKmsKey: new cdk.aws_kms.Key(stack, 'CloudWatchKeyS3Encryption', {}), - customResourceLambdaEnvironmentEncryptionKmsKey: new cdk.aws_kms.Key(stack, 'LambdaKeyS3Encryption', {}), - customResourceLambdaLogRetentionInDays: 365, -}); - -/** - * ValidateBucketS3Encryption construct test - */ -describe('ValidateBucketS3Encryption', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-members.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-members.test.ts.snap deleted file mode 100644 index 98db5f6..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-members.test.ts.snap +++ /dev/null @@ -1,176 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityHubMembers Construct(SecurityHubMembers): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3": { - "DependsOn": [ - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderLogGroup51212673": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubCreateMembersCustomResourceProviderRoleFD355CB6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:ListAccounts", - ], - "Condition": { - "StringLikeIfExists": { - "organizations:ListAccounts": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskOrganizationAction", - }, - { - "Action": [ - "securityhub:CreateMembers", - "securityhub:DeleteMembers", - "securityhub:DisassociateMembers", - "securityhub:EnableSecurityHub", - "securityhub:ListMembers", - "securityhub:UpdateOrganizationConfiguration", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubMembers2A2B77C4": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubCreateMembersCustomResourceProviderLogGroup51212673", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubCreateMembersCustomResourceProviderHandler31D82BF3", - "Arn", - ], - }, - "autoEnableOrgMembers": true, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - "securityHubMemberAccountIds": [], - }, - "Type": "Custom::SecurityHubCreateMembers", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-organization-admin-account.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-organization-admin-account.test.ts.snap deleted file mode 100644 index 2569b9b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-organization-admin-account.test.ts.snap +++ /dev/null @@ -1,219 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityHubOrganizationAdminAccount Construct(SecurityHubOrganizationAdminAccount): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9": { - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderRole1CBC866F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeOrganization", - "organizations:ListAccounts", - "organizations:ListDelegatedAdministrators", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskOrganizationActions", - }, - { - "Action": "organizations:EnableAWSServiceAccess", - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "organizations:RegisterDelegatedAdministrator", - "organizations:DeregisterDelegatedAdministrator", - ], - "Condition": { - "StringEquals": { - "organizations:ServicePrincipal": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":organizations::*:account/o-*/*", - ], - ], - }, - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": [ - "securityhub.amazonaws.com", - ], - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskIamAction", - }, - { - "Action": [ - "securityhub:DisableOrganizationAdminAccount", - "securityhub:EnableOrganizationAdminAccount", - "securityhub:EnableSecurityHub", - "securityhub:ListOrganizationAdminAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubEnableOrganizationAdminAccountTaskSecurityHubActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubOrganizationAdminAccount71D5E029": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderLogGroupDB7DE8A3", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubEnableOrganizationAdminAccountCustomResourceProviderHandler194C30B9", - "Arn", - ], - }, - "adminAccountId": { - "Ref": "AWS::AccountId", - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::SecurityHubEnableOrganizationAdminAccount", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-region-aggregation.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-region-aggregation.test.ts.snap deleted file mode 100644 index 9b3a328..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-region-aggregation.test.ts.snap +++ /dev/null @@ -1,159 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityHubRegionAggregation Construct(SecurityHubRegionAggregation): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A": { - "DependsOn": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 180, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderLogGroup241F2158": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubRegionAggregationCustomResourceProviderRole15741044": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "securityhub:CreateFindingAggregator", - "securityhub:UpdateFindingAggregator", - "securityhub:DeleteFindingAggregator", - "securityhub:ListFindingAggregators", - "securityhub:GetFindingAggregator", - "securityhub:DescribeHub", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubModifyRegionAggregation", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubRegionAggregation0CC69E1B": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderLogGroup241F2158", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubRegionAggregationCustomResourceProviderHandler4B24978A", - "Arn", - ], - }, - "partition": { - "Ref": "AWS::Partition", - }, - "region": { - "Ref": "AWS::Region", - }, - }, - "Type": "Custom::SecurityHubRegionAggregation", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-standards.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-standards.test.ts.snap deleted file mode 100644 index a89d6af..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/__snapshots__/securityhub-standards.test.ts.snap +++ /dev/null @@ -1,190 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SecurityHubStandards Construct(SecurityHubStandards): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1": { - "DependsOn": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderLogGroup51D3B0EA": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderRole1ABC8ED2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "securityhub:BatchDisableStandards", - "securityhub:BatchEnableStandards", - "securityhub:DescribeStandards", - "securityhub:DescribeStandardsControls", - "securityhub:EnableSecurityHub", - "securityhub:GetEnabledStandards", - "securityhub:UpdateStandardsControl", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubCreateMembersTaskSecurityHubActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Condition": { - "StringLike": { - "iam:AWSServiceName": "securityhub.amazonaws.com", - }, - }, - "Effect": "Allow", - "Resource": "*", - "Sid": "SecurityHubServiceLinkedRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SecurityHubStandards294083BB": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderLogGroup51D3B0EA", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSecurityHubBatchEnableStandardsCustomResourceProviderHandler4BE622C1", - "Arn", - ], - }, - "region": { - "Ref": "AWS::Region", - }, - "standards": [ - { - "controlsToDisable": [ - "IAM.1", - "EC2.10", - "Lambda.4", - ], - "enable": true, - "name": "AWS Foundational Security Best Practices v1.0.0", - }, - { - "controlsToDisable": [ - "IAM.1", - "EC2.10", - "Lambda.4", - ], - "enable": true, - "name": "PCI DSS v3.2.1", - }, - ], - }, - "Type": "Custom::SecurityHubBatchEnableStandards", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-members.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-members.test.ts deleted file mode 100644 index 00ca97a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-members.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SecurityHubMembers } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SecurityHubMembers): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SecurityHubMembers(stack, 'SecurityHubMembers', { - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, - securityHubMemberAccountIds: [], - autoEnableOrgMembers: true, -}); - -/** - * SecurityHubMembers construct test - */ -describe('SecurityHubMembers', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-organization-admin-account.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-organization-admin-account.test.ts deleted file mode 100644 index 2405f63..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-organization-admin-account.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SecurityHubOrganizationAdminAccount } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SecurityHubOrganizationAdminAccount): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SecurityHubOrganizationAdminAccount(stack, 'SecurityHubOrganizationAdminAccount', { - adminAccountId: stack.account, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * SecurityHubOrganizationAdminAccount construct test - */ -describe('SecurityHubOrganizationAdminAccount', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-region-aggregation.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-region-aggregation.test.ts deleted file mode 100644 index ea6b55f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-region-aggregation.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SecurityHubRegionAggregation } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SecurityHubRegionAggregation): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SecurityHubRegionAggregation(stack, 'SecurityHubRegionAggregation', { - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * SecurityHubRegionAggregation construct test - */ -describe('SecurityHubRegionAggregation', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-standards.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-standards.test.ts deleted file mode 100644 index aac860f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-securityhub/securityhub-standards.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SecurityHubStandards } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SecurityHubStandards): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SecurityHubStandards(stack, 'SecurityHubStandards', { - standards: [ - { - name: 'AWS Foundational Security Best Practices v1.0.0', - enable: true, - controlsToDisable: ['IAM.1', 'EC2.10', 'Lambda.4'], - }, - { - name: 'PCI DSS v3.2.1', - enable: true, - controlsToDisable: ['IAM.1', 'EC2.10', 'Lambda.4'], - }, - ], - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 3653, -}); - -/** - * SecurityHubStandards construct test - */ -describe('SecurityHubStandards', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-service-quota/__snapshots__/limits-service-quota-definition.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-service-quota/__snapshots__/limits-service-quota-definition.test.ts.snap deleted file mode 100644 index e91131f..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-service-quota/__snapshots__/limits-service-quota-definition.test.ts.snap +++ /dev/null @@ -1,259 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LimitsStandards Construct(LimitsDefinition): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomKey1E6D0D07": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF": { - "DependsOn": [ - "CustomServiceQuotaLimitsCustomResourceProviderRole024C3C88", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomServiceQuotaLimitsCustomResourceProviderRole024C3C88", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomServiceQuotaLimitsCustomResourceProviderLogGroupBC81BDCC": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CustomKey1E6D0D07", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF", - }, - ], - ], - }, - "RetentionInDays": 35, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomServiceQuotaLimitsCustomResourceProviderRole024C3C88": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:DescribeAccount", - "organizations:DescribeOrganization", - "organizations:ListAWSServiceAccessForOrganization", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "OrganizationListActions", - }, - { - "Action": [ - "autoscaling:DescribeAccountLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "AutoScalingLimitsAction", - }, - { - "Action": [ - "dynamodb:DescribeLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "DynamoDBLimitsAction", - }, - { - "Action": [ - "kinesis:DescribeLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "KinesisLimitsAction", - }, - { - "Action": [ - "iam:GetAccountSummary", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "IAMAccountSummaryAction", - }, - { - "Action": [ - "cloudformation:DescribeAccountLimits", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "CloudFormationAccountLimitsAction", - }, - { - "Action": [ - "cloudformation:DescribeAccountLimits", - "cloudwatch:DescribeAlarmsForMetric", - "cloudwatch:DescribeAlarms", - "cloudwatch:GetMetricData", - "cloudwatch:GetMetricStatistics", - "cloudwatch:PutMetricAlarm", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CloudWatchLimitsActions", - }, - { - "Action": [ - "elasticloadbalancing:DescribeAccountLimits", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ElasticLoadBalancingLimitsAction", - }, - { - "Action": [ - "route53:GetAccountLimit", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "Route53LimitsAction", - }, - { - "Action": [ - "rds:DescribeAccountAttributes", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "RDSLimitsAction", - }, - { - "Action": [ - "servicequotas:*", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceQuotaLimitsAction", - }, - { - "Action": [ - "tag:GetTagKeys", - "tag:GetTagValues", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "TaggingLimitsActions", - }, - { - "Action": [ - "iam:CreateServiceLinkedRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "CreateServiceLinkedRole", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "LimitsDefinition14BCE9E9": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomServiceQuotaLimitsCustomResourceProviderLogGroupBC81BDCC", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomServiceQuotaLimitsCustomResourceProviderHandler81BAABBF", - "Arn", - ], - }, - "desiredValue": 1000, - "quotaCode": "L-B99A9384", - "serviceCode": "lambda", - }, - "Type": "Custom::ServiceQuotaLimits", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-service-quota/limits-service-quota-definition.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-service-quota/limits-service-quota-definition.test.ts deleted file mode 100644 index 6df0e3a..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-service-quota/limits-service-quota-definition.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { LimitsDefinition } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(LimitsDefinition): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new LimitsDefinition(stack, 'LimitsDefinition', { - serviceCode: 'lambda', - quotaCode: 'L-B99A9384', - desiredValue: 1000, - kmsKey: new cdk.aws_kms.Key(stack, 'CustomKey', {}), - logRetentionInDays: 35, -}); - -/** - * Limits construct test - */ -describe('LimitsStandards', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/get-portfolio-id.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/get-portfolio-id.test.ts.snap deleted file mode 100644 index 3f88460..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/get-portfolio-id.test.ts.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GetPortfolioId Construct(GetPortfolioId): Snapshot Test 1`] = ` -{ - "Resources": { - "AssociateHostedZonesF0E2F0DA": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomGetPortfolioIdCustomResourceProviderLogGroupA4D50C6A", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomGetPortfolioIdCustomResourceProviderHandlerB6C78D44", - "Arn", - ], - }, - "displayName": "AWS Control Tower Account Factory Portfolio", - "providerName": "AWS Control Tower", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::GetPortfolioId", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetPortfolioIdCustomResourceProviderHandlerB6C78D44": { - "DependsOn": [ - "CustomGetPortfolioIdCustomResourceProviderRole698D24D6", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomGetPortfolioIdCustomResourceProviderRole698D24D6", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomGetPortfolioIdCustomResourceProviderLogGroupA4D50C6A": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomGetPortfolioIdCustomResourceProviderHandlerB6C78D44", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomGetPortfolioIdCustomResourceProviderRole698D24D6": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "servicecatalog:ListPortfolios", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/propagate-portfolio-associations.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/propagate-portfolio-associations.test.ts.snap deleted file mode 100644 index 3c2e5d9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/propagate-portfolio-associations.test.ts.snap +++ /dev/null @@ -1,156 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropagatePortfolioAssociations Construct(PropagatePortfolioAssociations): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38": { - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomPropagatePortfolioAssociationsCustomResourceProviderRole4C66847F": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "PropagatePortfolioAssociationsPropagateAssociationsB8799497": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderLogGroup44DE568D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomPropagatePortfolioAssociationsCustomResourceProviderHandler282C1D38", - "Arn", - ], - }, - "crossAccountRole": "AWSAccelerator-CrossAccount-ServiceCatalog-Role", - "partition": { - "Ref": "AWS::Partition", - }, - "portfolioDefinition": "{"name":"","provider":"","account":"","regions":[],"portfolioAssociations":[],"products":[]}", - "portfolioId": "portfolioId", - "shareAccountIds": "222222222222,333333333333", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::PropagatePortfolioAssociations", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/share-portfolio-with-org.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/share-portfolio-with-org.test.ts.snap deleted file mode 100644 index 760cf3e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/__snapshots__/share-portfolio-with-org.test.ts.snap +++ /dev/null @@ -1,200 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SharePortfolioWithOrg Construct(SharePortfolioWithOrg): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D": { - "DependsOn": [ - "CustomSharePortfolioWithOrgCustomResourceProviderRoleD968FEC7", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSharePortfolioWithOrgCustomResourceProviderRoleD968FEC7", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSharePortfolioWithOrgCustomResourceProviderLogGroup3D78DA1D": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSharePortfolioWithOrgCustomResourceProviderRoleD968FEC7": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "servicecatalog:CreatePortfolioShare", - "servicecatalog:UpdatePortfolioShare", - "servicecatalog:DeletePortfolioShare", - "servicecatalog:DescribePortfolioShareStatus", - "organizations:DescribeOrganization", - "organizations:ListParents", - "organizations:ListChildren", - "organizations:ListAccountsForParent", - "organizations:ListAccounts", - ], - "Effect": "Allow", - "Resource": "*", - "Sid": "ServiceCatalog", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SharePortfolioWithOrgPortfolioShareRoot6FBF67ED": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSharePortfolioWithOrgCustomResourceProviderLogGroup3D78DA1D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D", - "Arn", - ], - }, - "organizationId": "organizationId", - "organizationalUnitId": "", - "portfolioId": "portfolioId", - "tagShareOptions": true, - }, - "Type": "Custom::SharePortfolioWithOrg", - "UpdateReplacePolicy": "Delete", - }, - "SharePortfolioWithOrgPortfolioShareorg10340D408": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSharePortfolioWithOrgCustomResourceProviderLogGroup3D78DA1D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D", - "Arn", - ], - }, - "organizationId": "", - "organizationalUnitId": "org1", - "portfolioId": "portfolioId", - "tagShareOptions": true, - }, - "Type": "Custom::SharePortfolioWithOrg", - "UpdateReplacePolicy": "Delete", - }, - "SharePortfolioWithOrgPortfolioShareorg285669DB9": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSharePortfolioWithOrgCustomResourceProviderLogGroup3D78DA1D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSharePortfolioWithOrgCustomResourceProviderHandler4644676D", - "Arn", - ], - }, - "organizationId": "", - "organizationalUnitId": "org2", - "portfolioId": "portfolioId", - "tagShareOptions": true, - }, - "Type": "Custom::SharePortfolioWithOrg", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/get-portfolio-id.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/get-portfolio-id.test.ts deleted file mode 100644 index 3ae61a4..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/get-portfolio-id.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { GetPortfolioId } from '../../lib/aws-servicecatalog/get-portfolio-id'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(GetPortfolioId): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new GetPortfolioId(stack, 'AssociateHostedZones', { - displayName: 'AWS Control Tower Account Factory Portfolio', - providerName: 'AWS Control Tower', - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); - -/** - * GetPortfolioId construct test - */ -describe('GetPortfolioId', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/propagate-portfolio-associations.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/propagate-portfolio-associations.test.ts deleted file mode 100644 index 26592d7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/propagate-portfolio-associations.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { PropagatePortfolioAssociations } from '../../lib/aws-servicecatalog/propagate-portfolio-associations'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; -import { PortfolioConfig } from '@aws-accelerator/config'; - -const testNamePrefix = 'Construct(PropagatePortfolioAssociations): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); -const portfolioItem = new PortfolioConfig(); - -new PropagatePortfolioAssociations(stack, 'PropagatePortfolioAssociations', { - portfolioId: 'portfolioId', - shareAccountIds: ['222222222222', '333333333333'], - crossAccountRole: 'AWSAccelerator-CrossAccount-ServiceCatalog-Role', - portfolioDefinition: portfolioItem, - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); - -/** - * PropagatePortfolioAssociations construct test - */ -describe('PropagatePortfolioAssociations', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/share-portfolio-with-org.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/share-portfolio-with-org.test.ts deleted file mode 100644 index fa51a38..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-servicecatalog/share-portfolio-with-org.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SharePortfolioWithOrg } from '../../lib/aws-servicecatalog/share-portfolio-with-org'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(SharePortfolioWithOrg): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SharePortfolioWithOrg(stack, 'SharePortfolioWithOrg', { - portfolioId: 'portfolioId', - organizationalUnitIds: ['org1', 'org2'], - organizationId: 'organizationId', - tagShareOptions: true, - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), -}); - -/** - * SharePortfolioWithOrg construct test - */ -describe('SharePortfolioWithOrg', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/document.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/document.test.ts.snap deleted file mode 100644 index 0e26916..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/document.test.ts.snap +++ /dev/null @@ -1,250 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Document Construct(Document): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87": { - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "Key961B73FD", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSSMShareDocumentCustomResourceProviderRole53A2A3EC": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DescribeDocumentPermission", - "ssm:ModifyDocumentPermission", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":document/*", - ], - ], - }, - "Sid": "ShareDocumentActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "Document1573EA08D": { - "Properties": { - "Content": {}, - "DocumentType": "Automation", - "Name": "DocumentName1", - "TargetType": "/AWS::EC2::Instance", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "Document1ShareDocument274F97DF": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "accountA", - ], - "name": { - "Ref": "Document1573EA08D", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "Document7E43FA9A": { - "Properties": { - "Content": {}, - "DocumentType": "Automation", - "Name": "DocumentName", - "UpdateMethod": "NewVersion", - }, - "Type": "AWS::SSM::Document", - }, - "DocumentShareDocument572E9653": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSSMShareDocumentCustomResourceProviderLogGroup11AEA42B", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSSMShareDocumentCustomResourceProviderHandler02D98C87", - "Arn", - ], - }, - "accountIds": [ - "accountA", - ], - "name": { - "Ref": "Document7E43FA9A", - }, - }, - "Type": "Custom::SSMShareDocument", - "UpdateReplacePolicy": "Delete", - }, - "Key13EAD1396": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "Key961B73FD": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/inventory.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/inventory.test.ts.snap deleted file mode 100644 index df0320e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/inventory.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Inventory ../snapshot-test Snapshot Test 1`] = ` -{ - "Resources": { - "InventoryGatherInventory21DB8F8C": { - "Properties": { - "AssociationName": "test99999999999-InventoryCollection", - "Name": "AWS-GatherSoftwareInventory", - "ScheduleExpression": "rate(12 hours)", - "Targets": [ - { - "Key": "InstanceIds", - "Values": [ - "*", - ], - }, - ], - }, - "Type": "AWS::SSM::Association", - }, - "InventoryResourceDataSync837B1722": { - "Properties": { - "BucketName": "central-log-bucket", - "BucketPrefix": "ssm-inventory", - "BucketRegion": "us-east-1", - "SyncFormat": "JsonSerDe", - "SyncName": "test99999999999-Inventory", - "SyncType": "SyncToDestination", - }, - "Type": "AWS::SSM::ResourceDataSync", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/policy-attachment.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/policy-attachment.test.ts.snap deleted file mode 100644 index fd41cb8..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/policy-attachment.test.ts.snap +++ /dev/null @@ -1,290 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SsmSessionManagerPolicy Construct(SsmSessionManagerPolicy): Snapshot Test 1`] = ` -{ - "Resources": { - "SsmSessionManagerPolicyE2938AFD": { - "Properties": { - "Description": "", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssmmessages:CreateControlChannel", - "ssmmessages:CreateDataChannel", - "ssmmessages:OpenControlChannel", - "ssmmessages:OpenDataChannel", - "ssm:UpdateInstanceInformation", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "logs:DescribeLogGroups", - "Effect": "Allow", - "Resource": [ - "arn:aws:logs:us-east-1:111111111111:log-group:aws-accelerator-sessionmanager-logs:*", - "arn:aws:logs:us-west-2:111111111111:log-group:aws-accelerator-sessionmanager-logs:*", - ], - "Sid": "CloudWatchDescribe", - }, - { - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents", - "logs:DescribeLogStreams", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:logs:us-east-1:111111111111:log-group:*", - "arn:aws:logs:us-west-2:111111111111:log-group:*", - ], - "Sid": "CloudWatchLogs", - }, - { - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::\${this.centralLogsBucketName}/session/111111111111/us-east-1/*", - "arn:aws:s3:::\${this.centralLogsBucketName}/session/111111111111/us-west-2/*", - ], - "Sid": "S3CentralLogs", - }, - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::bucketName", - ], - ], - }, - "Sid": "S3CentralLogsEncryptionConfig", - }, - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": "arn", - "Sid": "S3CentralLogsEncryption", - }, - { - "Action": "kms:Decrypt", - "Condition": { - "ForAllValues:StringLike": { - "kms:ResourceAliases": [ - "accelerator/sessionmanager-logs/session", - ], - }, - "Null": { - "kms:ResourceAliases": "false", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":key/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "SsmSessionManagerPolicySessionManagerEC2InstanceProfile9A6E976B": { - "Properties": { - "InstanceProfileName": "AWSAccelerator-SessionManagerEc2Role", - "Roles": [ - { - "Ref": "SsmSessionManagerPolicySessionManagerEC2RoleD20A8741", - }, - ], - }, - "Type": "AWS::IAM::InstanceProfile", - }, - "SsmSessionManagerPolicySessionManagerEC2PolicyDEE46013": { - "Properties": { - "Description": "", - "ManagedPolicyName": "AWSAccelerator-SessionManagerLogging", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssmmessages:CreateControlChannel", - "ssmmessages:CreateDataChannel", - "ssmmessages:OpenControlChannel", - "ssmmessages:OpenDataChannel", - "ssm:UpdateInstanceInformation", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "logs:DescribeLogGroups", - "Effect": "Allow", - "Resource": [ - "arn:aws:logs:us-east-1:111111111111:log-group:aws-accelerator-sessionmanager-logs:*", - "arn:aws:logs:us-west-2:111111111111:log-group:aws-accelerator-sessionmanager-logs:*", - ], - "Sid": "CloudWatchDescribe", - }, - { - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents", - "logs:DescribeLogStreams", - "logs:DescribeLogGroups", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:logs:us-east-1:111111111111:log-group:*", - "arn:aws:logs:us-west-2:111111111111:log-group:*", - ], - "Sid": "CloudWatchLogs", - }, - { - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::\${this.centralLogsBucketName}/session/111111111111/us-east-1/*", - "arn:aws:s3:::\${this.centralLogsBucketName}/session/111111111111/us-west-2/*", - ], - "Sid": "S3CentralLogs", - }, - { - "Action": "s3:GetEncryptionConfiguration", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":s3:::bucketName", - ], - ], - }, - "Sid": "S3CentralLogsEncryptionConfig", - }, - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": "arn", - "Sid": "S3CentralLogsEncryption", - }, - { - "Action": "kms:Decrypt", - "Condition": { - "ForAllValues:StringLike": { - "kms:ResourceAliases": [ - "accelerator/sessionmanager-logs/session", - ], - }, - "Null": { - "kms:ResourceAliases": "false", - }, - }, - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":kms:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":key/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - "SsmSessionManagerPolicySessionManagerEC2RoleD20A8741": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "Description": "IAM Role for an EC2 configured for Session Manager Logging", - "ManagedPolicyArns": [ - { - "Ref": "SsmSessionManagerPolicySessionManagerEC2PolicyDEE46013", - }, - ], - "RoleName": "AWSAccelerator-SessionManagerEC2Role", - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/put-ssm-parameter.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/put-ssm-parameter.test.ts.snap deleted file mode 100644 index 59943d6..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/put-ssm-parameter.test.ts.snap +++ /dev/null @@ -1,187 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SsmParameter Construct(SsmParameter): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F": { - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Description": "Custom resource provider to put cross-account ssm parameter value", - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "keyFEDD6EC0", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmPutParameterValueCustomResourceProviderRole9E1F101A": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DeleteParameter", - "ssm:PutParameter", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmPutParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SsmParameter39B3125C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmPutParameterValueCustomResourceProviderLogGroupB0109C68", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmPutParameterValueCustomResourceProviderHandler2A0BDE7F", - "Arn", - ], - }, - "invokingAccountId": "333333333333", - "parameterAccountIds": [ - "111111111111", - "222222222222", - ], - "parameters": [ - { - "name": "/accelerator/network/vpcPeering/name/id", - "value": "vp-123123123", - }, - ], - "region": "us-east-1", - "roleName": "AWSAccelerator-VpcPeeringRole-222222222222", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmPutParameterValue", - "UpdateReplacePolicy": "Delete", - }, - "keyFEDD6EC0": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/session-manager-settings.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/session-manager-settings.test.ts.snap deleted file mode 100644 index c481bef..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/session-manager-settings.test.ts.snap +++ /dev/null @@ -1,249 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SsmSessionManagerSettings Construct(SsmSessionManagerSettings): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699": { - "DependsOn": [ - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSessionManagerLoggingCustomResourceProviderLogGroupF4E32979": { - "DeletionPolicy": "Delete", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CwKeyC5A32F94", - "Arn", - ], - }, - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSessionManagerLoggingCustomResourceProviderRole1D8EE686": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:DescribeDocument", - "ssm:CreateDocument", - "ssm:UpdateDocument", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "CwKeyC5A32F94": { - "DeletionPolicy": "Retain", - "Properties": { - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SsmSessionManagerSettings24721AC9": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSessionManagerLoggingCustomResourceProviderLogGroupF4E32979", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSessionManagerLoggingCustomResourceProviderHandler4FE51699", - "Arn", - ], - }, - "cloudWatchEncryptionEnabled": true, - "cloudWatchLogGroupName": { - "Ref": "SsmSessionManagerSettingsSessionManagerCloudWatchLogGroup15AB5AE0", - }, - "kmsKeyId": { - "Ref": "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - }, - "s3BucketName": "bucketName", - "s3EncryptionEnabled": true, - "s3KeyPrefix": "prefix", - }, - "Type": "Custom::SsmSessionManagerSettings", - "UpdateReplacePolicy": "Delete", - }, - "SsmSessionManagerSettingsSessionManagerCloudWatchLogGroup15AB5AE0": { - "DeletionPolicy": "Retain", - "Properties": { - "KmsKeyId": { - "Fn::GetAtt": [ - "CwKeyC5A32F94", - "Arn", - ], - }, - "LogGroupName": "AWSAccelerator-sessionmanager-logs", - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Retain", - }, - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C": { - "DeletionPolicy": "Retain", - "Properties": { - "Description": "AWS Accelerator Session Manager Session Encryption", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "SsmSessionManagerSettingsSessionManagerSessionKeyAlias59E0224E": { - "Properties": { - "AliasName": "alias/accelerator/sessionmanager-logs/session", - "TargetKeyId": { - "Fn::GetAtt": [ - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "SsmSessionManagerSettingsSessionManagerUserKMSPolicyFB96BB42": { - "Properties": { - "Description": "", - "ManagedPolicyName": "AWSAccelerator-SessionManagerUserKMS-us-east-1", - "Path": "/", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "SsmSessionManagerSettingsSessionManagerSessionKey23B7175C", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::ManagedPolicy", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/ssm-parameter-lookup.test.ts.snap b/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/ssm-parameter-lookup.test.ts.snap deleted file mode 100644 index 7fb60be..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/__snapshots__/ssm-parameter-lookup.test.ts.snap +++ /dev/null @@ -1,154 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SsmParameterLookup Construct(SsmParameterLookup): Snapshot Test 1`] = ` -{ - "Resources": { - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE": { - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - ], - "Properties": { - "Code": { - "S3Bucket": { - "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", - }, - "S3Key": "REPLACED-GENERATED-NAME.zip", - }, - "Handler": "__entrypoint__.handler", - "MemorySize": 128, - "Role": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D": { - "DeletionPolicy": "Delete", - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - }, - ], - ], - }, - "RetentionInDays": 3653, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "CustomSsmGetParameterValueCustomResourceProviderRoleB3AFDDB2": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - }, - ], - "Policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:DescribeParameters", - ], - "Effect": "Allow", - "Resource": [ - "*", - ], - "Sid": "SsmGetParameterActions", - }, - { - "Action": [ - "sts:AssumeRole", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::*:role/AWSAccelerator*", - ], - ], - }, - ], - "Sid": "StsAssumeRoleActions", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "Inline", - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SsmParameter39B3125C": { - "DeletionPolicy": "Delete", - "DependsOn": [ - "CustomSsmGetParameterValueCustomResourceProviderLogGroup780D220D", - ], - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "CustomSsmGetParameterValueCustomResourceProviderHandlerAAD0E7EE", - "Arn", - ], - }, - "assumeRoleArn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::111111111111:role/TestRole", - ], - ], - }, - "invokingAccountID": { - "Ref": "AWS::AccountId", - }, - "invokingRegion": { - "Ref": "AWS::Region", - }, - "parameterAccountID": "111111111111", - "parameterName": "TestParameter", - "parameterRegion": "us-east-1", - "uuid": "REPLACED-UUID", - }, - "Type": "Custom::SsmGetParameterValue", - "UpdateReplacePolicy": "Delete", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/document.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ssm/document.test.ts deleted file mode 100644 index 2f5d6d9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/document.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Document } from '../../lib/aws-ssm/document'; -import { snapShotTest } from '../snapshot-test'; -import { describe } from '@jest/globals'; - -const testNamePrefix = 'Construct(Document): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new Document(stack, 'Document', { - name: 'DocumentName', - content: JSON.parse('{}'), - documentType: 'Automation', - sharedWithAccountIds: ['accountA'], - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key', {}), - targetType: undefined, -}); -new Document(stack, 'Document1', { - name: 'DocumentName1', - content: JSON.parse('{}'), - documentType: 'Automation', - sharedWithAccountIds: ['accountA'], - logRetentionInDays: 3653, - kmsKey: new cdk.aws_kms.Key(stack, 'Key1', {}), - targetType: '/AWS::EC2::Instance', -}); - -/** - * Document construct test - */ -describe('Document', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/inventory.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ssm/inventory.test.ts deleted file mode 100644 index 7af60b7..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/inventory.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Inventory } from '../../lib/aws-ssm/inventory'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = '../snapshot-test'; - -const stack = new cdk.Stack(); - -new Inventory(stack, 'Inventory', { - bucketName: 'central-log-bucket', - bucketRegion: 'us-east-1', - accountId: '99999999999', - prefix: 'test', -}); - -describe('Inventory', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/policy-attachment.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ssm/policy-attachment.test.ts deleted file mode 100644 index 48190a5..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/policy-attachment.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SsmSessionManagerPolicy } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SsmSessionManagerPolicy): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SsmSessionManagerPolicy(stack, 'SsmSessionManagerPolicy', { - s3BucketName: 'bucketName', - s3BucketKeyArn: 'arn', - sendToS3: true, - homeRegion: 'us-east-1', - sendToCloudWatchLogs: true, - attachPolicyToIamRoles: ['Test1', 'Test2'], - region: 'us-east-1', - enabledRegions: ['us-east-1', 'us-west-2'], - prefixes: { accelerator: 'AWSAccelerator', ssmLog: 'aws-accelerator' }, - ssmKeyDetails: { - alias: 'accelerator/sessionmanager-logs/session', - description: 'AWS Accelerator Session Manager Session Encryption', - }, - cloudWatchLogGroupList: [ - 'arn:aws:logs:us-east-1:111111111111:log-group:*', - 'arn:aws:logs:us-west-2:111111111111:log-group:*', - ], - sessionManagerCloudWatchLogGroupList: [ - 'arn:aws:logs:us-east-1:111111111111:log-group:aws-accelerator-sessionmanager-logs:*', - 'arn:aws:logs:us-west-2:111111111111:log-group:aws-accelerator-sessionmanager-logs:*', - ], - s3BucketList: [ - 'arn:aws:s3:::${this.centralLogsBucketName}/session/111111111111/us-east-1/*', - 'arn:aws:s3:::${this.centralLogsBucketName}/session/111111111111/us-west-2/*', - ], -}); - -/** - * SsmSessionManagerPolicy construct test - */ -describe('SsmSessionManagerPolicy', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/put-ssm-parameter.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ssm/put-ssm-parameter.test.ts deleted file mode 100644 index 0f4446e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/put-ssm-parameter.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; - -import { PutSsmParameter } from '../../lib/aws-ssm/put-ssm-parameter'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SsmParameter): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new PutSsmParameter(stack, 'SsmParameter', { - accountIds: ['111111111111', '222222222222'], - region: 'us-east-1', - roleName: `AWSAccelerator-VpcPeeringRole-222222222222`, - parameters: [ - { - name: `/accelerator/network/vpcPeering/name/id`, - value: 'vp-123123123', - }, - ], - kmsKey: new cdk.aws_kms.Key(stack, 'key'), - logRetentionInDays: 3653, - invokingAccountId: '333333333333', - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * SsmParameter construct test - */ -describe('SsmParameter', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/session-manager-settings.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ssm/session-manager-settings.test.ts deleted file mode 100644 index 125e5f0..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/session-manager-settings.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SsmSessionManagerSettings } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -//import { SynthUtils } from '@aws-cdk/assert'; - -const testNamePrefix = 'Construct(SsmSessionManagerSettings): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SsmSessionManagerSettings(stack, 'SsmSessionManagerSettings', { - s3BucketName: 'bucketName', - s3KeyPrefix: 'prefix', - s3BucketKeyArn: 'arn', - sendToS3: true, - sendToCloudWatchLogs: true, - cloudWatchEncryptionEnabled: true, - cloudWatchEncryptionKey: new cdk.aws_kms.Key(stack, 'CwKey', {}), - logRetentionInDays: 3653, - region: 'us-east-1', - prefixes: { accelerator: 'AWSAccelerator', ssmLog: 'aws-accelerator' }, - ssmKeyDetails: { - alias: 'accelerator/sessionmanager-logs/session', - description: 'AWS Accelerator Session Manager Session Encryption', - }, -}); - -/** - * SsmSessionManagerSettings construct test - */ -describe('SsmSessionManagerSettings', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/aws-ssm/ssm-parameter-lookup.test.ts b/source/packages/@aws-accelerator/constructs/test/aws-ssm/ssm-parameter-lookup.test.ts deleted file mode 100644 index 48917a9..0000000 --- a/source/packages/@aws-accelerator/constructs/test/aws-ssm/ssm-parameter-lookup.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { SsmParameterLookup } from '../../index'; -import { snapShotTest } from '../snapshot-test'; - -const testNamePrefix = 'Construct(SsmParameterLookup): '; - -//Initialize stack for snapshot test and resource configuration test -const stack = new cdk.Stack(); - -new SsmParameterLookup(stack, 'SsmParameter', { - name: 'TestParameter', - accountId: '111111111111', - parameterRegion: 'us-east-1', - roleName: 'TestRole', - logRetentionInDays: 3653, - acceleratorPrefix: 'AWSAccelerator', -}); - -/** - * SsmParameterLookup construct test - */ -describe('SsmParameterLookup', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/constructs/test/snapshot-test.ts b/source/packages/@aws-accelerator/constructs/test/snapshot-test.ts deleted file mode 100644 index 09c0808..0000000 --- a/source/packages/@aws-accelerator/constructs/test/snapshot-test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { SynthUtils } from '@aws-cdk/assert'; -import { expect, test } from '@jest/globals'; - -export function snapShotTest(testNamePrefix: string, stack: cdk.Stack) { - test(`${testNamePrefix} Snapshot Test`, () => { - // limited: only match length of generated zip file or UUID spec lengths. - const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/; - const zipRegex = /[0-9a-f]{64}\.zip/; - const md5Regex = /^[0-9a-f]{32}$/; // limited: only match length of md5 hash. - - // test each serialized object - if any part of string matches regex - // replace with value of print() - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(uuidRegex) != null, - print: () => '"REPLACED-UUID"', - }); - - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(zipRegex) != null, - print: () => '"REPLACED-GENERATED-NAME.zip"', - }); - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(md5Regex) != null && !val.startsWith('REPLACED'), - print: () => '"REPLACED-MD5"', - }); - - expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); - }); -} diff --git a/source/packages/@aws-accelerator/constructs/test/unit-test/accelerator-unit-test.ts b/source/packages/@aws-accelerator/constructs/test/unit-test/accelerator-unit-test.ts deleted file mode 100644 index 4de517e..0000000 --- a/source/packages/@aws-accelerator/constructs/test/unit-test/accelerator-unit-test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - CloudFormationCustomResourceDeleteEvent, - CloudFormationCustomResourceUpdateEvent, - CloudFormationCustomResourceCreateEvent, -} from '@aws-accelerator/utils/lib/common-types'; - -import { AccountId, EventType, Partition, Region } from './common/resources'; - -/** - * CloudFormation event resource property type. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PropertyType = { [Key: string]: any }; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type EventPropertiesType = { new?: PropertyType[]; old?: PropertyType[] }; - -/** - * An abstract class for accelerator custom resource AWS Lambda unit test. - * - * @remarks - * The purpose of this class is to create static event data that is required for unit testing. Unit test functions can use this class to create the required event data. - * - * @example - * Here is how you can use this class to obtain event data for testing purposes - * - * An example of create CloudFormation event data without resource properties - * ``` - * const event = AcceleratorUnitTest.getEvent(EventType.CREATE); - * ``` - * - * An example of create CloudFormation event data with NEW resource properties only - * ``` - * const event = AcceleratorUnitTest.getEvent(EventType.CREATE, { new: [{property: value, property: value}] }); - * ``` - * - * An example of create CloudFormation event data with NEW and OLD resource properties - * - * ``` - * const event = AcceleratorUnitTest.getEvent(EventType.UPDATE, { - new: [{property: value, property: value}], - old: [{property: value, property: value}], - }); - * ``` - */ -export abstract class AcceleratorUnitTest { - private static prepareResourceProperties(properties?: PropertyType[]): PropertyType | undefined { - const resourcePropertyList: - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }[] = properties ?? []; - - const resourceProperties: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - } = {}; - - for (const resourceProperty of resourcePropertyList) { - for (const [key, value] of Object.entries(resourceProperty)) { - resourceProperties[key] = value; - } - } - - return resourcePropertyList.length > 0 ? resourceProperties : undefined; - } - - /** - * Function to get CloudFormation event to unit test Custom Resource Lambda - * @param eventType {@link EventType} - * @param properties {@link EventPropertiesType} - * @returns eventData {@link CloudFormationCustomResourceCreateEvent} | {@link CloudFormationCustomResourceDeleteEvent} | {@link CloudFormationCustomResourceUpdateEvent} - */ - public static getEvent( - eventType: EventType, - properties?: EventPropertiesType, - ): - | CloudFormationCustomResourceCreateEvent - | CloudFormationCustomResourceDeleteEvent - | CloudFormationCustomResourceUpdateEvent { - const newProperties = this.prepareResourceProperties(properties?.new); - const oldProperties = this.prepareResourceProperties(properties?.old); - - switch (eventType) { - case EventType.CREATE: - return { - RequestType: eventType, - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ResponseURL: '...', - StackId: `arn:${Partition}:cloudformation:${Region}:${AccountId}:stack/fake-stack-id`, - RequestId: 'fake-request-id', - LogicalResourceId: 'fake-logical-resource-id', - ResourceType: 'Custom::FakeResource', - ResourceProperties: { - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ...newProperties, - }, - }; - case EventType.UPDATE: - return { - RequestType: eventType, - PhysicalResourceId: 'PhysicalResourceId', - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ResponseURL: '...', - StackId: `arn:${Partition}:cloudformation:${Region}:${AccountId}:stack/fake-stack-id`, - RequestId: 'fake-request-id', - LogicalResourceId: 'fake-logical-resource-id', - ResourceType: 'Custom::FakeResource', - ResourceProperties: { - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ...newProperties, - }, - OldResourceProperties: { - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ...oldProperties, - }, - }; - case EventType.DELETE: - return { - RequestType: eventType, - PhysicalResourceId: 'PhysicalResourceId', - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ResponseURL: '...', - StackId: `arn:${Partition}:cloudformation:${Region}:${AccountId}:stack/fake-stack-id`, - RequestId: 'fake-request-id', - LogicalResourceId: 'fake-logical-resource-id', - ResourceType: 'Custom::FakeResource', - ResourceProperties: { - ServiceToken: `arn:${Partition}:lambda:${Region}:${AccountId}:function:fake-function-name`, - ...newProperties, - }, - }; - } - } -} diff --git a/source/packages/@aws-accelerator/constructs/test/unit-test/common/resources.ts b/source/packages/@aws-accelerator/constructs/test/unit-test/common/resources.ts deleted file mode 100644 index 400d29b..0000000 --- a/source/packages/@aws-accelerator/constructs/test/unit-test/common/resources.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { mockClient } from 'aws-sdk-client-mock'; -import path from 'path'; - -/** - * AWS SDK Mock API client - * @param awsClient - * @returns - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const AcceleratorMockClient = (awsClient: any) => mockClient(awsClient); - -/** - * Accelerator all enable config directory path - */ -export const AllEnabledConfigPath = path.join(__dirname, '../../../../accelerator/test/configs/all-enabled'); - -/** - * Partition - */ -export const Partition = 'aws'; - -/** - * AWS Account Id - */ -export const AccountId = '111111111111'; -/** - * Global Region - */ -export const GlobalRegion = 'us-east-1'; - -/** - * AWS Region - */ -export const Region = 'us-east-1'; - -/** - * Solution id - */ -export const SolutionId = ' AwsSolution/SO0199'; - -/** - * CloudFormation event - */ -export enum EventType { - 'CREATE' = 'Create', - 'UPDATE' = 'Update', - 'DELETE' = 'Delete', -} - -/** - * Make static role arn - * @param name string - * @param partition string | undefined - * @param accountId string | undefined - * @returns arn string - */ -export const MakeRoleArn = (name: string, partition?: string, accountId?: string) => - `arn:${partition ?? Partition}:iam::${accountId ?? AccountId}:role/${name}`; - -/** - * Make kms key arn - * @param name string - * @param partition string | undefined - * @param accountId string | undefined - * @param region string | undefined - * @returns arn string - */ -export const MakeKeyArn = (name: string, partition?: string, accountId?: string, region?: string) => - `arn:${partition ?? Partition}:kms:${region ?? Region}:${accountId ?? AccountId}:key/${name}`; - -/** - * Make Log group Arn - * @param name string - * @param partition string | undefined - * @param accountId string | undefined - * @param region string | undefined - * @returns arn string - */ -export const MakeLogGroupArn = (name: string, partition?: string, accountId?: string, region?: string) => - `arn:${partition ?? Partition}:logs:${region ?? Region}:${accountId ?? AccountId}:log-group:${name}`; diff --git a/source/packages/@aws-accelerator/constructs/tsconfig.json b/source/packages/@aws-accelerator/constructs/tsconfig.json deleted file mode 100644 index 7367be0..0000000 --- a/source/packages/@aws-accelerator/constructs/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": {}, - "include": ["lib/*/*.ts", "index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/.gitignore b/source/packages/@aws-accelerator/govcloud-account-vending/.gitignore deleted file mode 100644 index d31be3e..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -*.js -!jest.config.js -*.d.ts -node_modules - -# CDK asset staging directory -.cdk.staging -cdk.out - -!**/create-govcloud-account/index.js \ No newline at end of file diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/.npmignore b/source/packages/@aws-accelerator/govcloud-account-vending/.npmignore deleted file mode 100644 index c1d6d45..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/README.md b/source/packages/@aws-accelerator/govcloud-account-vending/README.md deleted file mode 100644 index 32cf2f5..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Landing Zone Accelerator on AWS - GovCloud Account Vending - -Create GovCloud (US) account using Service Catalog. - -## Pre-requisites -Make sure the workstation has `yarn`, `node`, `cdk` and `awscli`. The account should satisfy all the necessary requirements to run [CreateGovCloudAccount](https://docs.aws.amazon.com/organizations/latest/APIReference/API_CreateGovCloudAccount.html). **An IAM role, user or group arn that will be used as an input to the CloudFormation template.** This arn will have access to deploy the Service Catalog resource. -Only deploy this in standard (commercial) AWS Partition. The solution assumes a role in `us-east-1` region. - -## Steps -1. Clone the repo and run - - `cd source` - - `yarn install` - - `yarn build` - - `cd packages/@aws-accelerator/govcloud-account-vending ` - - `yarn cdk deploy --require-approval never ` - -2. After the stack is deployed, access AWS console and [grant access to user/role/group](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/catalogs_portfolios_users.html). -3. Navigate to service catalog in the region that stack was deployed. Under products, choose *AWS Landing Zone Accelerator - GovCloud Account Vending* and click Launch. -4. Fill out the information in the product - - Product name: Name for the product. The name must start with a letter (A-Z, a-z) or number (0-9). Other valid characters include: hyphen (-), underscore (_), and period (.). - - Account Name: The friendly name of the member account. The account name can consist of only the characters [a-z],[A-Z],[0-9], hyphen (-), or dot (.) You can't separate characters with a dash (–). - - Account Email: Specifies the email address of the owner to assign to the new member account in the commercial Region. This email address must not already be associated with another AWS account. You must use a valid email address to complete account creation. - - Organization Role Name: The name of an IAM role that AWS Organizations automatically preconfigures in the new member accounts in both the AWS GovCloud (US) Region and in the commercial Region. This role trusts the management account, allowing users in the management account to assume the role, as permitted by the management account administrator. The role has administrator permissions in the new member account. Defaults to `OrganizationAccountAccessRole`. - -5. After the product is created, under the Events tab in Output Key and Value there will be account ID for `GovCloudAccountId` & `AccountId`. \ No newline at end of file diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/bin/govcloud-avm.ts b/source/packages/@aws-accelerator/govcloud-account-vending/bin/govcloud-avm.ts deleted file mode 100644 index 5c95443..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/bin/govcloud-avm.ts +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import 'source-map-support/register'; -import { version } from '../../../../package.json'; -import * as cdk from 'aws-cdk-lib'; -import { GovCloudAccountVendingStack } from '../lib/govcloud-avm-stack'; - -// Set accelerator prefix environment variable -const acceleratorPrefix = process.env['ACCELERATOR_PREFIX'] ?? 'AWSAccelerator'; - -const app = new cdk.App(); -new GovCloudAccountVendingStack(app, `${acceleratorPrefix}-GovCloudAccountVending`, { - description: `(SO0199-govcloudavm) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - acceleratorPrefix: acceleratorPrefix, -}); diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/cdk.json b/source/packages/@aws-accelerator/govcloud-account-vending/cdk.json deleted file mode 100644 index 4f0873c..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/cdk.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/govcloud-avm.ts", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, - "@aws-cdk/core:stackRelativeExports": true, - "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, - "@aws-cdk/aws-lambda:recognizeVersionProps": true, - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ] - } -} diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/jest.config.js b/source/packages/@aws-accelerator/govcloud-account-vending/jest.config.js deleted file mode 100644 index 08263b8..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/test'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest' - } -}; diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-product-stack.ts b/source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-product-stack.ts deleted file mode 100644 index 44eca7f..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-product-stack.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; - -export interface GovCloudAccountVendingProductStackProps extends cdk.StackProps { - readonly acceleratorPrefix: string; -} - -export class GovCloudAccountVendingProductStack extends cdk.aws_servicecatalog.ProductStack { - constructor(scope: Construct, id: string, props: GovCloudAccountVendingProductStackProps) { - super(scope, id); - - /** This stack creates service catalog product which can - * create AWS GovCloud (US) accounts using the Organizations API - * "CreateGovCloudAccount". - * - * - * The account where this stack is launched should - * 1. be in commercial region with Organizations enabled - * 2. Have ability to create AWS GovCloud (US) Accounts - * Please read https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/getting-set-up.html - * and the API documentation - * https://docs.aws.amazon.com/organizations/latest/APIReference/API_CreateGovCloudAccount.html - - */ - - // Parameters for account creation - const accountName = new cdk.CfnParameter(this, 'AccountName', { - type: 'String', - description: - "The friendly name of the member account. The account name can consist of only the characters [a-z],[A-Z],[0-9], hyphen (-), or dot (.) You can't separate characters with a dash (–).", - }); - const emailAddress = new cdk.CfnParameter(this, 'AccountEmail', { - type: 'String', - description: - 'Specifies the email address of the owner to assign to the new member account in the commercial Region. This email address must not already be associated with another AWS account. You must use a valid email address to complete account creation.', - }); - const orgAccessRole = new cdk.CfnParameter(this, 'OrgAccessRole', { - type: 'String', - description: - 'The name of an IAM role that AWS Organizations automatically preconfigures in the new member accounts in both the AWS GovCloud (US) Region and in the commercial Region. This role trusts the management account, allowing users in the management account to assume the role, as permitted by the management account administrator. The role has administrator permissions in the new member account.', - default: 'OrganizationAccountAccessRole', - }); - const parameterGroups: { Label: { default: string }; Parameters: string[] }[] = [ - { - Label: { default: 'Account Configuration' }, - Parameters: [accountName.logicalId, emailAddress.logicalId, orgAccessRole.logicalId], - }, - ]; - const GovCloudAccountVendingParameterLabels: { [p: string]: { default: string } } = { - [accountName.logicalId]: { default: 'Account Name' }, - [emailAddress.logicalId]: { default: 'Account Email' }, - [orgAccessRole.logicalId]: { default: 'Organization Role Name' }, - }; - // Parameter Metadata - this.templateOptions.metadata = { - 'AWS::CloudFormation::Interface': { - ParameterGroups: parameterGroups, - ParameterLabels: { ...GovCloudAccountVendingParameterLabels }, - }, - }; - - const accountVendingFunction = cdk.aws_lambda.Function.fromFunctionName( - this, - 'AccountVendingFunction', - `${props.acceleratorPrefix}-GovCloudAccountVending`, - ); - - // - // Custom Resource definition - // - const resource = new cdk.CustomResource(this, 'CustomCreateGovCloudAccount', { - resourceType: 'Custom::CreateGovCloudAccount', - serviceToken: accountVendingFunction.functionArn, - properties: { - accountName: accountName.valueAsString, - emailAddress: emailAddress.valueAsString, - orgAccessRole: orgAccessRole.valueAsString, - }, - }); - - new cdk.CfnOutput(this, 'GovCloudAccountId', { value: resource.getAtt('GovCloudAccountId').toString() }); - new cdk.CfnOutput(this, 'AccountId', { value: resource.getAtt('AccountId').toString() }); - } -} diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-stack.ts b/source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-stack.ts deleted file mode 100644 index 42fdffa..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/lib/govcloud-avm-stack.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as path from 'path'; -import { GovCloudAccountVendingProductStack } from './govcloud-avm-product-stack'; -import * as fs from 'fs'; -import { version } from '../../../../package.json'; - -export interface GovCloudAccountVendingStackProps extends cdk.StackProps { - readonly acceleratorPrefix: string; -} - -export class GovCloudAccountVendingStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: GovCloudAccountVendingStackProps) { - super(scope, id, props); - - /** This stack creates service catalog product which can - * create AWS GovCloud (US) accounts using the Organizations API - * "CreateGovCloudAccount". - * - * - * The account where this stack is launched should - * 1. be in commercial region with Organizations enabled - * 2. Have ability to create AWS GovCloud (US) Accounts - * Please read https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/getting-set-up.html - * and the API documentation - * https://docs.aws.amazon.com/organizations/latest/APIReference/API_CreateGovCloudAccount.html - - */ - - /* - * Parameter for getting IAM Role ARN - * This role will have access to launch service catalog products - */ - - // Create a portfolio - const portfolio = new cdk.aws_servicecatalog.Portfolio(this, 'GovCloudAccountVendingPortfolio', { - displayName: 'Landing Zone Accelerator on AWS', - providerName: 'AWS Solutions', - }); - - // Create a GovCloud Account Vending service catalog product - const product = new cdk.aws_servicecatalog.CloudFormationProduct(this, 'GovCloudAccountVendingProduct', { - productName: 'Landing Zone Accelerator on AWS - AWS GovCloud (US) Account Vending', - owner: 'AWS Solutions', - productVersions: [ - { - cloudFormationTemplate: cdk.aws_servicecatalog.CloudFormationTemplate.fromProductStack( - new GovCloudAccountVendingProductStack(this, 'GovCloudAccountVendingProductStack', { - acceleratorPrefix: props.acceleratorPrefix, - description: `(SO0199-govcloudavmproduct) Landing Zone Accelerator on AWS. Version ${version}.`, - }), - ), - productVersionName: 'v1.0.0', - description: - 'AWS GovCloud (US) Account Vending Product. Create AWS GovCloud (US) accounts. Required inputs are Account name, email and Organization Access Role.', - }, - ], - }); - // Associate product to the portfolio - portfolio.addProduct(product); - - const fileContents = fs.readFileSync(path.join(__dirname, 'lambdas/create-govcloud-account/index.js')); - - // Lambda function to be used in Custom Resource - const accountVendingFunction = new cdk.aws_lambda.Function(this, 'GovCloudAccountVendingFunction', { - code: new cdk.aws_lambda.InlineCode(fileContents.toString()), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - timeout: cdk.Duration.seconds(900), - functionName: `${props.acceleratorPrefix}-GovCloudAccountVending`, - description: 'Create AWS GovCloud (US) Accounts', - initialPolicy: [ - new cdk.aws_iam.PolicyStatement({ - actions: ['organizations:CreateGovCloudAccount', 'organizations:DescribeCreateAccountStatus'], - resources: ['*'], - }), - ], - }); - - // - // cfn-nag suppressions - // - const cfnLambdaFunctionPolicy = accountVendingFunction.node.findChild('ServiceRole').node.findChild('DefaultPolicy') - .node.defaultChild as cdk.aws_iam.CfnPolicy; - cfnLambdaFunctionPolicy.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'W12', - reason: `IAM policy should not allow * resource.`, - }, - ], - }, - }; - - const cfnLambdaFunction = accountVendingFunction.node.defaultChild as cdk.aws_lambda.CfnFunction; - cfnLambdaFunction.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'W58', - reason: `CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole`, - }, - { - id: 'W89', - reason: `This function supports infrastructure deployment and is not deployed inside a VPC.`, - }, - { - id: 'W92', - reason: `This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.`, - }, - ], - }, - }; - } -} diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/lib/lambdas/create-govcloud-account/index.js b/source/packages/@aws-accelerator/govcloud-account-vending/lib/lambdas/create-govcloud-account/index.js deleted file mode 100644 index aeb3496..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/lib/lambdas/create-govcloud-account/index.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { - OrganizationsClient, - CreateGovCloudAccountCommand, - DescribeCreateAccountStatusCommand, -} = require('@aws-sdk/client-organizations'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); -const cfn = require('cfn-response'); - -const org = new OrganizationsClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), - region: 'us-east-1', -}); -/** - * create-govcloud-account - lambda handler - * - * @param event - * @returns cfn-response - */ - -exports.handler = async (event, context) => { - console.log('Received event:\n' + JSON.stringify(event, null, 2)); - try { - var acc = event.ResourceProperties.accountName; - var em = event.ResourceProperties.emailAddress; - var role = event.ResourceProperties.orgAccessRole; - let i = 0; - - if (event.RequestType === 'Create') { - var accResp = await org.send(new CreateGovCloudAccountCommand({ AccountName: acc, Email: em, RoleName: role })); - console.log(JSON.stringify(accResp)); - var car = accResp.CreateAccountStatus.Id; - let accStatR = await org.send(new DescribeCreateAccountStatusCommand({ CreateAccountRequestId: car })); - let accStat = accStatR.CreateAccountStatus.State; - while (accStat === 'IN_PROGRESS' && i < 40) { - await new Promise(resolve => setTimeout(resolve, 15e3)); - accStatR = await org.send(new DescribeCreateAccountStatusCommand({ CreateAccountRequestId: car })); - accStat = accStatR.CreateAccountStatus.State; - i++; - // print responses to help troubleshoot - console.log(`Attempt: ${i} of 40`); - console.log(JSON.stringify(accStatR)); - console.log(accStat); - } - if (i === 40) { - console.log('Timed out'); - return await cfn.send(event, context, 'FAILED'); - } else if (accStat === 'FAILED') { - var physicalResourceId = accStatR.CreateAccountStatus.FailureReason; - return await cfn.send(event, context, 'FAILED', physicalResourceId); - } else if (accStat === 'SUCCEEDED') { - var responseData = { - AccountId: accStatR.CreateAccountStatus.AccountId, - GovCloudAccountId: accStatR.CreateAccountStatus.GovCloudAccountId, - }; - return await cfn.send(event, context, 'SUCCESS', responseData); - } - } else if (event.RequestType === 'Delete' || event.RequestType === 'Update') { - return await cfn.send(event, context, 'SUCCESS'); - } - } catch (err) { - let errMsg = `${err.name}:\n${err.message}`; - let responseData = { Error: errMsg }; - console.log(errMsg); - - return await cfn.send(event, context, 'FAILED', responseData); - } -}; diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/package.json b/source/packages/@aws-accelerator/govcloud-account-vending/package.json deleted file mode 100644 index 16ff31b..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "@aws-accelerator/govcloud-account-vending", - "version": "0.0.0", - "private": true, - "description": "AWS GovCloud (US) account vending", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "bin": { - "govcloud-avm": "bin/govcloud-avm.js" - }, - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "@aws-sdk/client-config-service": "3.410.0", - "@types/js-yaml": "4.0.5", - "@types/semver": "7.5.0", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "change-case": "4.1.2", - "fs-extra": "11.1.0", - "hash-sum": "2.0.0", - "js-yaml": "4.1.0", - "pascal-case": "3.1.2", - "semver": "7.5.2", - "tempy": "3.0.0", - "winston": "3.8.2" - }, - "devDependencies": { - "@aws-cdk/cx-api": "2.93.0", - "@aws-cdk/region-info": "2.93.0", - "@types/fs-extra": "11.0.1", - "@types/jest": "29.4.0", - "@types/mri": "1.1.1", - "@types/node": "18.14.0", - "@types/promptly": "3.0.2", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "cdk-assets": "2.93.0", - "chokidar": "3.5.3", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "mri": "1.2.0", - "prettier": "2.8.4", - "promptly": "3.2.0", - "proxy-agent": "6.3.0", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/test/__snapshots__/govcloud-account-vending.test.ts.snap b/source/packages/@aws-accelerator/govcloud-account-vending/test/__snapshots__/govcloud-account-vending.test.ts.snap deleted file mode 100644 index ba0665f..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/test/__snapshots__/govcloud-account-vending.test.ts.snap +++ /dev/null @@ -1,231 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GovCloudAccountVendingStack Stack(GovCloudAccountVendingStack): Snapshot Test 1`] = ` -{ - "Resources": { - "GovCloudAccountVendingFunctionB10FB00E": { - "DependsOn": [ - "GovCloudAccountVendingFunctionServiceRoleDefaultPolicy02E0FF6C", - "GovCloudAccountVendingFunctionServiceRole3F1EF782", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { - OrganizationsClient, - CreateGovCloudAccountCommand, - DescribeCreateAccountStatusCommand, -} = require('@aws-sdk/client-organizations'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); -const cfn = require('cfn-response'); - -const org = new OrganizationsClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), - region: 'us-east-1', -}); -/** - * create-govcloud-account - lambda handler - * - * @param event - * @returns cfn-response - */ - -exports.handler = async (event, context) => { - console.log('Received event:\\n' + JSON.stringify(event, null, 2)); - try { - var acc = event.ResourceProperties.accountName; - var em = event.ResourceProperties.emailAddress; - var role = event.ResourceProperties.orgAccessRole; - let i = 0; - - if (event.RequestType === 'Create') { - var accResp = await org.send(new CreateGovCloudAccountCommand({ AccountName: acc, Email: em, RoleName: role })); - console.log(JSON.stringify(accResp)); - var car = accResp.CreateAccountStatus.Id; - let accStatR = await org.send(new DescribeCreateAccountStatusCommand({ CreateAccountRequestId: car })); - let accStat = accStatR.CreateAccountStatus.State; - while (accStat === 'IN_PROGRESS' && i < 40) { - await new Promise(resolve => setTimeout(resolve, 15e3)); - accStatR = await org.send(new DescribeCreateAccountStatusCommand({ CreateAccountRequestId: car })); - accStat = accStatR.CreateAccountStatus.State; - i++; - // print responses to help troubleshoot - console.log(\`Attempt: \${i} of 40\`); - console.log(JSON.stringify(accStatR)); - console.log(accStat); - } - if (i === 40) { - console.log('Timed out'); - return await cfn.send(event, context, 'FAILED'); - } else if (accStat === 'FAILED') { - var physicalResourceId = accStatR.CreateAccountStatus.FailureReason; - return await cfn.send(event, context, 'FAILED', physicalResourceId); - } else if (accStat === 'SUCCEEDED') { - var responseData = { - AccountId: accStatR.CreateAccountStatus.AccountId, - GovCloudAccountId: accStatR.CreateAccountStatus.GovCloudAccountId, - }; - return await cfn.send(event, context, 'SUCCESS', responseData); - } - } else if (event.RequestType === 'Delete' || event.RequestType === 'Update') { - return await cfn.send(event, context, 'SUCCESS'); - } - } catch (err) { - let errMsg = \`\${err.name}:\\n\${err.message}\`; - let responseData = { Error: errMsg }; - console.log(errMsg); - - return await cfn.send(event, context, 'FAILED', responseData); - } -}; -", - }, - "Description": "Create AWS GovCloud (US) Accounts", - "FunctionName": "AWSAccelerator-GovCloudAccountVending", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "GovCloudAccountVendingFunctionServiceRole3F1EF782", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 900, - }, - "Type": "AWS::Lambda::Function", - }, - "GovCloudAccountVendingFunctionServiceRole3F1EF782": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "GovCloudAccountVendingFunctionServiceRoleDefaultPolicy02E0FF6C": { - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "organizations:CreateGovCloudAccount", - "organizations:DescribeCreateAccountStatus", - ], - "Effect": "Allow", - "Resource": "*", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GovCloudAccountVendingFunctionServiceRoleDefaultPolicy02E0FF6C", - "Roles": [ - { - "Ref": "GovCloudAccountVendingFunctionServiceRole3F1EF782", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "GovCloudAccountVendingPortfolioEB3BE033": { - "Properties": { - "DisplayName": "Landing Zone Accelerator on AWS", - "ProviderName": "AWS Solutions", - }, - "Type": "AWS::ServiceCatalog::Portfolio", - }, - "GovCloudAccountVendingPortfolioPortfolioProductAssociationf14ebeee1f152D65EDA3": { - "Properties": { - "PortfolioId": { - "Ref": "GovCloudAccountVendingPortfolioEB3BE033", - }, - "ProductId": { - "Ref": "GovCloudAccountVendingProduct2B8769D5", - }, - }, - "Type": "AWS::ServiceCatalog::PortfolioProductAssociation", - }, - "GovCloudAccountVendingProduct2B8769D5": { - "Properties": { - "Name": "Landing Zone Accelerator on AWS - AWS GovCloud (US) Account Vending", - "Owner": "AWS Solutions", - "ProvisioningArtifactParameters": [ - { - "Description": "AWS GovCloud (US) Account Vending Product. Create AWS GovCloud (US) accounts. Required inputs are Account name, email and Organization Access Role.", - "DisableTemplateValidation": false, - "Info": { - "LoadTemplateFromURL": { - "Fn::Sub": "REPLACED-JSON-PATH.json", - }, - }, - "Name": "v1.0.0", - }, - ], - }, - "Type": "AWS::ServiceCatalog::CloudFormationProduct", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/test/govcloud-account-vending.test.ts b/source/packages/@aws-accelerator/govcloud-account-vending/test/govcloud-account-vending.test.ts deleted file mode 100644 index 70cc12d..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/test/govcloud-account-vending.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -// import { Template } from 'aws-cdk-lib/assertions'; -import { GovCloudAccountVendingStack } from '../lib/govcloud-avm-stack'; -import { snapShotTest } from './snapshot-test'; -// Test prefix -const testNamePrefix = 'Stack(GovCloudAccountVendingStack): '; -const stack = new GovCloudAccountVendingStack(new cdk.App(), 'AWSAccelerator-Test-GovCloudAccountVendingStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - acceleratorPrefix: 'AWSAccelerator', -}); -/** - * GovCloudAccountVendingStack construct test - */ -describe('GovCloudAccountVendingStack', () => { - snapShotTest(testNamePrefix, stack); -}); diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/test/snapshot-test.ts b/source/packages/@aws-accelerator/govcloud-account-vending/test/snapshot-test.ts deleted file mode 100644 index a7092ef..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/test/snapshot-test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { SynthUtils } from '@aws-cdk/assert'; -import { expect, test } from '@jest/globals'; - -export function snapShotTest(testNamePrefix: string, stack: cdk.Stack) { - test(`${testNamePrefix} Snapshot Test`, () => { - // greedy implementation: eg, because "/path/home/temp.json" matches on - // temp.json, replace the whole string to "replaced-json-path.json". - const greedyJsonRegex = /[a-z0-9]+.json/; - - // limited: only match length of generated zip file or UUID spec lengths. - const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/; - const zipRegex = /[0-9a-f]{64}\.zip/; - - // test each serialized object - if any part of string matches regex - // replace with value of print() - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(uuidRegex) != null, - print: () => '"REPLACED-UUID"', - }); - - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(zipRegex) != null, - print: () => '"REPLACED-GENERATED-NAME.zip"', - }); - - expect.addSnapshotSerializer({ - test: ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - val: any, - ) => typeof val === 'string' && val.match(greedyJsonRegex) != null, - print: () => '"REPLACED-JSON-PATH.json"', - }); - - expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); - }); -} diff --git a/source/packages/@aws-accelerator/govcloud-account-vending/tsconfig.json b/source/packages/@aws-accelerator/govcloud-account-vending/tsconfig.json deleted file mode 100644 index 067f5b6..0000000 --- a/source/packages/@aws-accelerator/govcloud-account-vending/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "bin/**/*", "cdk.ts", "index.ts"], - "exclude": ["cdk.out/**/*", "test/**/*"] -} diff --git a/source/packages/@aws-accelerator/installer/.npmignore b/source/packages/@aws-accelerator/installer/.npmignore deleted file mode 100644 index c1d6d45..0000000 --- a/source/packages/@aws-accelerator/installer/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-accelerator/installer/README.md b/source/packages/@aws-accelerator/installer/README.md deleted file mode 100644 index ce42ade..0000000 --- a/source/packages/@aws-accelerator/installer/README.md +++ /dev/null @@ -1 +0,0 @@ -# @aws-accelerator/installer diff --git a/source/packages/@aws-accelerator/installer/bin/installer.ts b/source/packages/@aws-accelerator/installer/bin/installer.ts deleted file mode 100644 index b63e16d..0000000 --- a/source/packages/@aws-accelerator/installer/bin/installer.ts +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { AwsSolutionsChecks } from 'cdk-nag'; -import 'source-map-support/register'; -import { version } from '../../../../package.json'; -import * as installer from '../lib/installer-stack'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { IConstruct } from 'constructs'; - -const logger = createLogger(['installer']); - -async function main() { - const app = new cdk.App(); - cdk.Aspects.of(app).add(new AwsSolutionsChecks()); - - const useExternalPipelineAccount = app.node.tryGetContext('use-external-pipeline-account') === 'true'; - const enableTester = app.node.tryGetContext('enable-tester') === 'true'; - const managementCrossAccountRoleName = app.node.tryGetContext('management-cross-account-role-name'); - const enableSingleAccountMode = app.node.tryGetContext('enable-single-account-mode') === 'true'; - const usePermissionBoundary = app.node.tryGetContext('use-permission-boundary') === 'true'; - - if (enableTester && managementCrossAccountRoleName === undefined) { - console.log(`Invalid --management-cross-account-role-name ${managementCrossAccountRoleName}`); - throw new Error( - 'Usage: app.ts [--context use-external-pipeline-account=BOOLEAN] [--context enable-tester=BOOLEAN] [--context management-cross-account-role-name=MANAGEMENT_CROSS_ACCOUNT_ROLE_NAME] [--context use-permission-boundary=BOOLEAN]', - ); - } - - new installer.InstallerStack(app, 'AWSAccelerator-InstallerStack', { - description: `(SO0199) Landing Zone Accelerator on AWS. Version ${version}.`, - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: useExternalPipelineAccount, - enableTester: enableTester, - managementCrossAccountRoleName: managementCrossAccountRoleName, - enableSingleAccountMode, - usePermissionBoundary, - }); - if (usePermissionBoundary) { - cdk.Aspects.of(app).add(new installerPermissionBoundary()); - } -} - -class installerPermissionBoundary implements cdk.IAspect { - public visit(node: IConstruct): void { - if (node instanceof cdk.CfnResource && node.cfnResourceType === 'AWS::IAM::Role') { - node.addPropertyOverride( - 'PermissionsBoundary.Fn::Sub', - 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${PermissionBoundaryPolicyName}', - ); - } - } -} - -(async () => { - try { - await main(); - } catch (err) { - logger.error(err); - throw new Error(`${err}`); - } -})(); diff --git a/source/packages/@aws-accelerator/installer/cdk.json b/source/packages/@aws-accelerator/installer/cdk.json deleted file mode 100644 index a56f18e..0000000 --- a/source/packages/@aws-accelerator/installer/cdk.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/installer.ts", - "versionReporting": false, - "context": {} -} diff --git a/source/packages/@aws-accelerator/installer/index.ts b/source/packages/@aws-accelerator/installer/index.ts deleted file mode 100644 index bcbe933..0000000 --- a/source/packages/@aws-accelerator/installer/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/installer-stack'; diff --git a/source/packages/@aws-accelerator/installer/jest.config.js b/source/packages/@aws-accelerator/installer/jest.config.js deleted file mode 100644 index e7904ef..0000000 --- a/source/packages/@aws-accelerator/installer/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 70, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/installer/lib/installer-stack.ts b/source/packages/@aws-accelerator/installer/lib/installer-stack.ts deleted file mode 100644 index 4515683..0000000 --- a/source/packages/@aws-accelerator/installer/lib/installer-stack.ts +++ /dev/null @@ -1,1160 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { NagSuppressions } from 'cdk-nag'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import * as path from 'path'; - -import { Bucket, BucketEncryptionType } from '@aws-accelerator/constructs'; - -import { version } from '../../../../package.json'; -import { SolutionHelper } from './solutions-helper'; -import { ResourceNamePrefixes } from './resource-name-prefixes'; -import { Validate } from './validate'; - -export enum RepositorySources { - GITHUB = 'github', - CODECOMMIT = 'codecommit', -} - -export interface InstallerStackProps extends cdk.StackProps { - /** - * External Pipeline Account usage flag - */ - readonly useExternalPipelineAccount: boolean; - /** - * Enable tester flag - */ - readonly enableTester: boolean; - - /** - * Management Cross account role name - */ - readonly managementCrossAccountRoleName?: string; - /** - * Single account deployment enable flag - */ - readonly enableSingleAccountMode: boolean; - /** - * Accelerator Permission boundary usage flag - */ - readonly usePermissionBoundary: boolean; -} - -export class InstallerStack extends cdk.Stack { - private readonly repositorySource = new cdk.CfnParameter(this, 'RepositorySource', { - type: 'String', - description: 'Specify the git host', - allowedValues: [RepositorySources.GITHUB, RepositorySources.CODECOMMIT], - default: RepositorySources.GITHUB, - }); - - private readonly repositoryOwner = new cdk.CfnParameter(this, 'RepositoryOwner', { - type: 'String', - description: 'The owner of the repository containing the accelerator code. (GitHub Only)', - default: 'awslabs', - }); - - private readonly repositoryName = new cdk.CfnParameter(this, 'RepositoryName', { - type: 'String', - description: 'The name of the git repository hosting the accelerator code', - default: 'landing-zone-accelerator-on-aws', - }); - - private readonly repositoryBranchName = new cdk.CfnParameter(this, 'RepositoryBranchName', { - type: 'String', - description: - 'The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.', - default: `release/v${version}`, - allowedPattern: '.+', - constraintDescription: 'The repository branch name must not be empty', - }); - - private readonly enableApprovalStage = new cdk.CfnParameter(this, 'EnableApprovalStage', { - type: 'String', - description: 'Select yes to add a Manual Approval stage to accelerator pipeline', - allowedValues: ['Yes', 'No'], - default: 'Yes', - }); - - private readonly approvalStageNotifyEmailList = new cdk.CfnParameter(this, 'ApprovalStageNotifyEmailList', { - type: 'CommaDelimitedList', - description: 'Provide comma(,) separated list of email ids to receive manual approval stage notification email', - }); - - private readonly managementAccountEmail = new cdk.CfnParameter(this, 'ManagementAccountEmail', { - type: 'String', - description: 'The management (primary) account email', - allowedPattern: '[^\\s@]+@[^\\s@]+\\.[^\\s@]+', - constraintDescription: 'Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"', - }); - - private readonly logArchiveAccountEmail = new cdk.CfnParameter(this, 'LogArchiveAccountEmail', { - type: 'String', - description: 'The log archive account email', - allowedPattern: '[^\\s@]+@[^\\s@]+\\.[^\\s@]+', - constraintDescription: 'Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"', - }); - - private readonly auditAccountEmail = new cdk.CfnParameter(this, 'AuditAccountEmail', { - type: 'String', - description: 'The security audit account (also referred to as the audit account)', - allowedPattern: '[^\\s@]+@[^\\s@]+\\.[^\\s@]+', - constraintDescription: 'Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"', - }); - - private readonly controlTowerEnabled = new cdk.CfnParameter(this, 'ControlTowerEnabled', { - type: 'String', - description: 'Select yes if you deploying to a Control Tower environment. Select no if using just Organizations', - allowedValues: ['Yes', 'No'], - default: 'Yes', - }); - - private readonly acceleratorPrefix = new cdk.CfnParameter(this, 'AcceleratorPrefix', { - type: 'String', - description: - 'The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.', - default: 'AWSAccelerator', - allowedPattern: '[A-Za-z0-9-]+', - maxLength: 15, - }); - - /** - * Use existing configuration repository name flag - * @private - */ - private readonly useExistingConfigRepo = new cdk.CfnParameter(this, 'UseExistingConfigRepo', { - type: 'String', - allowedValues: ['Yes', 'No'], - default: 'No', - description: - 'Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.', - }); - - /** - * Existing LZ Accelerator configuration repository name - * @private - */ - private readonly existingConfigRepositoryName = new cdk.CfnParameter(this, 'ExistingConfigRepositoryName', { - type: 'String', - description: 'The name of an existing CodeCommit repository hosting the accelerator configuration.', - default: '', - }); - - /** - * Existing LZ Accelerator configuration repository branch name - * @private - */ - private readonly existingConfigRepositoryBranchName = new cdk.CfnParameter( - this, - 'ExistingConfigRepositoryBranchName', - { - type: 'String', - description: - 'Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.', - default: '', - }, - ); - - /** - * Flag for LZ Accelerator diagnostics pack - * @private - */ - private readonly enableDiagnosticsPack = new cdk.CfnParameter(this, 'EnableDiagnosticsPack', { - type: 'String', - allowedValues: ['Yes', 'No'], - default: 'Yes', - description: - 'Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.', - }); - - /** - * Management Account ID Parameter - * @private - */ - private readonly managementAccountId: cdk.CfnParameter | undefined; - - /** - * Management Account Role Name Parameter - * @private - */ - private readonly managementAccountRoleName: cdk.CfnParameter | undefined; - - /** - * Accelerator Qualifier parameter - * @private - */ - private readonly acceleratorQualifier: cdk.CfnParameter | undefined; - - /** - * Permission boundary SSM Parameter Path - * @private - */ - private readonly acceleratorPermissionBoundary: cdk.CfnParameter | undefined; - - constructor(scope: Construct, id: string, props: InstallerStackProps) { - super(scope, id, props); - - const isCommercialCondition = new cdk.CfnCondition(this, 'IsCommercialCondition', { - expression: cdk.Fn.conditionEquals(cdk.Stack.of(this).partition, 'aws'), - }); - - const globalRegionMap = new cdk.CfnMapping(this, 'GlobalRegionMap', { - mapping: { - aws: { - regionName: 'us-east-1', - }, - 'aws-us-gov': { - regionName: 'us-gov-west-1', - }, - 'aws-iso-b': { - regionName: 'us-isob-east-1', - }, - 'aws-iso': { - regionName: 'us-iso-east-1', - }, - 'aws-cn': { - regionName: 'cn-northwest-1', - }, - }, - }); - - const parameterGroups: { Label: { default: string }; Parameters: string[] }[] = [ - { - Label: { default: 'Git Repository Configuration' }, - Parameters: [ - this.repositorySource.logicalId, - this.repositoryOwner.logicalId, - this.repositoryName.logicalId, - this.repositoryBranchName.logicalId, - ], - }, - { - Label: { default: 'Pipeline Configuration' }, - Parameters: [this.enableApprovalStage.logicalId, this.approvalStageNotifyEmailList.logicalId], - }, - { - Label: { default: 'Mandatory Accounts Configuration' }, - Parameters: [ - this.managementAccountEmail.logicalId, - this.logArchiveAccountEmail.logicalId, - this.auditAccountEmail.logicalId, - ], - }, - { - Label: { default: 'Environment Configuration' }, - Parameters: [ - this.controlTowerEnabled.logicalId, - this.acceleratorPrefix.logicalId, - this.useExistingConfigRepo.logicalId, - this.existingConfigRepositoryName.logicalId, - this.existingConfigRepositoryBranchName.logicalId, - this.enableDiagnosticsPack.logicalId, - ], - }, - ]; - - const repositoryParameterLabels: { [p: string]: { default: string } } = { - [this.repositorySource.logicalId]: { default: 'Source' }, - [this.repositoryOwner.logicalId]: { default: 'Repository Owner' }, - [this.repositoryName.logicalId]: { default: 'Repository Name' }, - [this.repositoryBranchName.logicalId]: { default: 'Branch Name' }, - [this.useExistingConfigRepo.logicalId]: { default: 'Use Existing Config Repository' }, - [this.existingConfigRepositoryName.logicalId]: { default: 'Existing Config Repository Name' }, - [this.existingConfigRepositoryBranchName.logicalId]: { default: 'Existing Config Repository Branch Name' }, - [this.enableDiagnosticsPack.logicalId]: { default: 'Enable Diagnostics Pack' }, - [this.enableApprovalStage.logicalId]: { default: 'Enable Approval Stage' }, - [this.approvalStageNotifyEmailList.logicalId]: { default: 'Manual Approval Stage notification email list' }, - [this.managementAccountEmail.logicalId]: { default: 'Management Account Email' }, - [this.logArchiveAccountEmail.logicalId]: { default: 'Log Archive Account Email' }, - [this.auditAccountEmail.logicalId]: { default: 'Audit Account Email' }, - [this.controlTowerEnabled.logicalId]: { default: 'Control Tower Environment' }, - [this.acceleratorPrefix.logicalId]: { default: 'Accelerator Resource name prefix' }, - }; - - let targetAcceleratorParameterLabels: { [p: string]: { default: string } } = {}; - let targetAcceleratorEnvVariables: { [p: string]: cdk.aws_codebuild.BuildEnvironmentVariable } = {}; - - if (props.usePermissionBoundary) { - this.acceleratorPermissionBoundary = new cdk.CfnParameter(this, 'AcceleratorPermissionBoundary', { - type: 'String', - description: 'Permission boundary Policy Name which is valid only for management account', - }); - - parameterGroups.push({ - Label: { default: 'Permission Boundary Configuration' }, - Parameters: [this.acceleratorPermissionBoundary.logicalId], - }); - - targetAcceleratorEnvVariables = { - ...targetAcceleratorEnvVariables, - ACCELERATOR_PERMISSION_BOUNDARY: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.acceleratorPermissionBoundary.valueAsString, - }, - }; - this.acceleratorPermissionBoundary.overrideLogicalId('PermissionBoundaryPolicyName'); - } - - if (props.useExternalPipelineAccount) { - this.acceleratorQualifier = new cdk.CfnParameter(this, 'AcceleratorQualifier', { - type: 'String', - description: 'Accelerator assets arn qualifier', - allowedPattern: '^[a-z]+[a-z0-9-]{1,61}[a-z0-9]+$', - constraintDescription: 'Qualifier must include lowercase letters and numbers only', - }); - - this.managementAccountId = new cdk.CfnParameter(this, 'ManagementAccountId', { - type: 'String', - description: 'Target management account id', - }); - - this.managementAccountRoleName = new cdk.CfnParameter(this, 'ManagementAccountRoleName', { - type: 'String', - description: 'Target management account role name', - }); - - parameterGroups.push({ - Label: { default: 'Target Environment Configuration' }, - Parameters: [ - this.acceleratorQualifier.logicalId, - this.managementAccountId.logicalId, - this.managementAccountRoleName.logicalId, - ], - }); - - targetAcceleratorParameterLabels = { - [this.acceleratorQualifier.logicalId]: { default: 'Accelerator Qualifier' }, - [this.managementAccountId.logicalId]: { default: 'Management Account ID' }, - [this.managementAccountRoleName.logicalId]: { default: 'Management Account Role Name' }, - }; - - targetAcceleratorEnvVariables = { - MANAGEMENT_ACCOUNT_ID: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.managementAccountId.valueAsString, - }, - MANAGEMENT_ACCOUNT_ROLE_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.managementAccountRoleName.valueAsString, - }, - ACCELERATOR_QUALIFIER: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.acceleratorQualifier.valueAsString, - }, - }; - } - - // Validate Installer Parameters - - const validatorFunction = new Validate(this, 'ValidateInstaller', { - useExistingConfigRepo: this.useExistingConfigRepo.valueAsString, - existingConfigRepositoryName: this.existingConfigRepositoryName.valueAsString, - existingConfigRepositoryBranchName: this.existingConfigRepositoryBranchName.valueAsString, - }); - // cfn-nag suppression - const validatorFunctionResource = validatorFunction.node.findChild('ValidationFunction').node - .defaultChild as cdk.CfnResource; - this.addLambdaNagMetadata(validatorFunctionResource); - - const resourceNamePrefixes = new ResourceNamePrefixes(this, 'ResourceNamePrefixes', { - acceleratorPrefix: this.acceleratorPrefix.valueAsString, - acceleratorQualifier: this.acceleratorQualifier?.valueAsString, - }); - // cfn-nag suppression - const resourceNameFunctionResource = resourceNamePrefixes.node.findChild('ResourceNamePrefixesFunction').node - .defaultChild as cdk.CfnResource; - this.addLambdaNagMetadata(resourceNameFunctionResource); - - const oneWordPrefix = resourceNamePrefixes.oneWordPrefix.endsWith('-') - ? resourceNamePrefixes.oneWordPrefix.slice(0, -1) - : resourceNamePrefixes.oneWordPrefix; - - const lowerCasePrefix = resourceNamePrefixes.lowerCasePrefix.endsWith('-') - ? resourceNamePrefixes.lowerCasePrefix.slice(0, -1) - : resourceNamePrefixes.lowerCasePrefix; - - const acceleratorPrefix = resourceNamePrefixes.acceleratorPrefix.endsWith('-') - ? resourceNamePrefixes.acceleratorPrefix.slice(0, -1) - : resourceNamePrefixes.acceleratorPrefix; - - let stackIdSsmParameterName = `/${oneWordPrefix}/${cdk.Stack.of(this).stackName}/stack-id`; - let acceleratorVersionSsmParameterName = `/${oneWordPrefix}/${cdk.Stack.of(this).stackName}/version`; - let installerKeyAliasName = `alias/${oneWordPrefix}/installer/kms/key`; - let acceleratorManagementKmsArnSsmParameterName = `/${oneWordPrefix}/installer/kms/key-arn`; - let installerAccessLogsBucketName = `${lowerCasePrefix}-s3-logs-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`; - let installerAccessLogsBucketNameSsmParameterName = `/${oneWordPrefix}/installer-access-logs-bucket-name`; - let secureBucketName = `${lowerCasePrefix}-installer-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`; - let acceleratorPipelineName = `${acceleratorPrefix}-Pipeline`; - let installerProjectName = `${acceleratorPrefix}-InstallerProject`; - let installerPipelineName = `${acceleratorPrefix}-Installer`; - - let acceleratorPrincipalArn = `arn:${cdk.Stack.of(this).partition}:iam::${ - cdk.Stack.of(this).account - }:role/${acceleratorPrefix}-*`; - - if (props.useExternalPipelineAccount) { - // - // Change the variable to use qualifier - stackIdSsmParameterName = `/${oneWordPrefix}/${this.acceleratorQualifier!.valueAsString}/${ - cdk.Stack.of(this).stackName - }/stack-id`; - acceleratorVersionSsmParameterName = `/${oneWordPrefix}/${this.acceleratorQualifier!.valueAsString}/${ - cdk.Stack.of(this).stackName - }/version`; - installerKeyAliasName = `alias/${oneWordPrefix}/${this.acceleratorQualifier!.valueAsString}/installer/kms/key`; - acceleratorManagementKmsArnSsmParameterName = `/${oneWordPrefix}/${ - this.acceleratorQualifier!.valueAsString - }/installer/kms/key-arn`; - installerAccessLogsBucketName = `${this.acceleratorQualifier!.valueAsString}-s3-logs-${cdk.Aws.ACCOUNT_ID}-${ - cdk.Aws.REGION - }`; - installerAccessLogsBucketNameSsmParameterName = `/${oneWordPrefix}/${ - this.acceleratorQualifier!.valueAsString - }/installer-access-logs-bucket-name`; - secureBucketName = `${this.acceleratorQualifier!.valueAsString}-installer-${cdk.Aws.ACCOUNT_ID}-${ - cdk.Aws.REGION - }`; - acceleratorPipelineName = `${this.acceleratorQualifier!.valueAsString}-pipeline`; - installerProjectName = `${this.acceleratorQualifier!.valueAsString}-installer-project`; - installerPipelineName = `${this.acceleratorQualifier!.valueAsString}-installer`; - acceleratorPrincipalArn = `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:role/${ - this.acceleratorQualifier!.valueAsString - }-*`; - } - - if (props.enableSingleAccountMode) { - targetAcceleratorEnvVariables['ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE'] = { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: true, - }; - } - - let targetAcceleratorTestEnvVariables: { [p: string]: cdk.aws_codebuild.BuildEnvironmentVariable } | undefined; - if (props.enableTester) { - targetAcceleratorTestEnvVariables = { - ENABLE_TESTER: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.enableTester, - }, - MANAGEMENT_CROSS_ACCOUNT_ROLE_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: props.managementCrossAccountRoleName, - }, - }; - } - - // Parameter Metadata - this.templateOptions.metadata = { - 'AWS::CloudFormation::Interface': { - ParameterGroups: parameterGroups, - ParameterLabels: { ...repositoryParameterLabels, ...targetAcceleratorParameterLabels }, - }, - }; - - new cdk.aws_ssm.StringParameter(this, 'SsmParamStackId', { - parameterName: stackIdSsmParameterName, - stringValue: cdk.Stack.of(this).stackId, - simpleName: false, - }); - - new cdk.aws_ssm.StringParameter(this, 'SsmParamAcceleratorVersion', { - parameterName: acceleratorVersionSsmParameterName, - stringValue: version, - simpleName: false, - }); - - /** - * Solutions Metrics - * We use this data to better understand how customers use this - * solution and related services and products - */ - new SolutionHelper(this, 'SolutionHelper', { - solutionId: 'SO0199', - repositorySource: this.repositorySource, - repositoryOwner: this.repositoryOwner, - repositoryBranchName: this.repositoryBranchName, - repositoryName: this.repositoryName, - }); - - // Create Accelerator Installer KMS Key - const installerKey = new cdk.aws_kms.Key(this, 'InstallerKey', { - alias: installerKeyAliasName, - description: 'AWS Accelerator Management Account Kms Key', - enableKeyRotation: true, - policy: undefined, - }); - - // - // Add conditional policies to Key policy - const cfnKey = installerKey.node.defaultChild as cdk.aws_kms.CfnKey; - cfnKey.keyPolicy = { - Statement: [ - { - Effect: 'Allow', - Principal: { - AWS: `arn:${cdk.Stack.of(this).partition}:iam::${cdk.Stack.of(this).account}:root`, - }, - Action: 'kms:*', - Resource: '*', - }, - { - Sid: 'Allow Accelerator Role to use the encryption key', - Effect: 'Allow', - Principal: { - AWS: '*', - }, - Action: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - Resource: '*', - Condition: { - ArnLike: { - 'aws:PrincipalARN': acceleratorPrincipalArn, - }, - }, - }, - { - Sid: 'Allow SNS service to use the encryption key', - Effect: 'Allow', - Principal: { - Service: 'sns.amazonaws.com', - }, - Action: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - Resource: '*', - }, - { - Sid: 'Allow Cloudwatch Logs service to use the encryption key', - Effect: 'Allow', - Principal: { - Service: `logs.${cdk.Stack.of(this).region}.${cdk.Stack.of(this).urlSuffix}`, - }, - Action: ['kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey'], - Resource: '*', - Condition: { - ArnLike: { - 'kms:EncryptionContext:aws:logs:arn': `arn:${cdk.Stack.of(this).partition}:logs:${ - cdk.Stack.of(this).region - }:${cdk.Stack.of(this).account}:log-group:*`, - }, - }, - }, - cdk.Fn.conditionIf( - isCommercialCondition.logicalId, - { - Sid: 'KMS key access to codestar-notifications', - Effect: 'Allow', - Principal: { - Service: 'codestar-notifications.amazonaws.com', - }, - Action: ['kms:GenerateDataKey*', 'kms:Decrypt'], - Resource: '*', - Condition: { - StringEquals: { - 'kms:ViaService': `sns.${cdk.Stack.of(this).region}.amazonaws.com`, - }, - }, - }, - cdk.Aws.NO_VALUE, - ), - ], - }; - - // cfn_nag suppressions - const cfnInstallerKey = installerKey.node.defaultChild as cdk.aws_kms.CfnKey; - cfnInstallerKey.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'F76', - reason: 'KMS key using * principal with added arn condition', - }, - ], - }, - }; - - // Create SSM parameter for installer key arn for future use - new cdk.aws_ssm.StringParameter(this, 'AcceleratorManagementKmsArnParameter', { - parameterName: acceleratorManagementKmsArnSsmParameterName, - stringValue: installerKey.keyArn, - simpleName: false, - }); - - const installerServerAccessLogsBucket = new Bucket(this, 'InstallerAccessLogsBucket', { - encryptionType: BucketEncryptionType.SSE_S3, // Server access logging does not support SSE-KMS - s3BucketName: installerAccessLogsBucketName, - }); - - // cfn_nag: Suppress warning related to high S3 Bucket should have access logging configured - const cfnInstallerServerAccessLogsBucket = installerServerAccessLogsBucket.getS3Bucket().node - .defaultChild as cdk.aws_s3.CfnBucket; - cfnInstallerServerAccessLogsBucket.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'W35', - reason: 'This is an access logging bucket.', - }, - ], - }, - }; - - // AwsSolutions-S1: The S3 Bucket has server access logs disabled. - NagSuppressions.addResourceSuppressionsByPath( - this, - `${this.stackName}/InstallerAccessLogsBucket/Resource/Resource`, - [ - { - id: 'AwsSolutions-S1', - reason: 'AccessLogsBucket has server access logs disabled till the task for access logging completed.', - }, - ], - ); - - new cdk.aws_ssm.StringParameter(this, 'InstallerAccessLogsBucketName', { - parameterName: installerAccessLogsBucketNameSsmParameterName, - stringValue: installerServerAccessLogsBucket.getS3Bucket().bucketName, - simpleName: false, - }); - - const bucket = new Bucket(this, 'SecureBucket', { - encryptionType: BucketEncryptionType.SSE_KMS, - s3BucketName: secureBucketName, - kmsKey: installerKey, - serverAccessLogsBucket: installerServerAccessLogsBucket.getS3Bucket(), - }); - - const installerRole = new cdk.aws_iam.Role(this, 'InstallerAdminRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('codebuild.amazonaws.com'), - managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], - }); - - const globalRegion = globalRegionMap.findInMap(cdk.Aws.PARTITION, 'regionName'); - - const installerProject = new cdk.aws_codebuild.PipelineProject(this, 'InstallerProject', { - projectName: installerProjectName, - encryptionKey: installerKey, - role: installerRole, - buildSpec: cdk.aws_codebuild.BuildSpec.fromObjectToYaml({ - version: '0.2', - phases: { - install: { - 'runtime-versions': { - nodejs: 18, - }, - }, - pre_build: { - commands: [ - 'ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no"', - 'if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ' + - 'ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; ' + - 'fi', - `set -e && if ! aws cloudformation describe-stacks --stack-name ${acceleratorPrefix}-CDKToolkit --region ${cdk.Aws.REGION}; then ` + - 'BOOTSTRAPPED_HOME="no"; ' + - 'fi', - `set -e && if ! aws cloudformation describe-stacks --stack-name ${acceleratorPrefix}-CDKToolkit --region ${globalRegion}; then ` + - 'BOOTSTRAPPED_GLOBAL="no"; ' + - 'fi', - `ENABLE_DIAGNOSTICS_PACK=${this.enableDiagnosticsPack.valueAsString}`, - ], - }, - build: { - commands: [ - 'cd source', - `if [ "${cdk.Stack.of(this).partition}" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi`, - 'yarn install', - 'yarn build', - 'cd packages/@aws-accelerator/installer', - `set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ${acceleratorPrefix}-CDKToolkit aws://${cdk.Aws.ACCOUNT_ID}/${cdk.Aws.REGION} --qualifier accel; fi`, - `set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ${acceleratorPrefix}-CDKToolkit aws://${cdk.Aws.ACCOUNT_ID}/${globalRegion} --qualifier accel; fi`, - `set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:${ - cdk.Stack.of(this).partition - }:iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ${acceleratorPrefix}-CDKToolkit --region ${ - cdk.Aws.REGION - }; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ${acceleratorPrefix}-CDKToolkit --region ${globalRegion}; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ${acceleratorPrefix}-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/${ - cdk.Aws.REGION - } --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ${acceleratorPrefix}-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/${globalRegion} --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi`, - 'cd ../accelerator', - `aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi;`, - `set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ${cdk.Aws.ACCOUNT_ID} --region ${cdk.Aws.REGION} --partition ${cdk.Aws.PARTITION} - fi`, - `set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ${cdk.Aws.ACCOUNT_ID} --region ${cdk.Aws.REGION} --partition ${cdk.Aws.PARTITION}`, - `set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ${cdk.Aws.ACCOUNT_ID} --region ${cdk.Aws.REGION}; fi`, - ], - }, - post_build: { - commands: [ - `if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "${acceleratorPipelineName}" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "${acceleratorPipelineName}"; - fi - fi`, - ], - }, - }, - }), - environment: { - buildImage: cdk.aws_codebuild.LinuxBuildImage.STANDARD_7_0, - privileged: false, - computeType: cdk.aws_codebuild.ComputeType.MEDIUM, - environmentVariables: { - NODE_OPTIONS: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '--max_old_space_size=4096', - }, - CDK_NEW_BOOTSTRAP: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: '1', - }, - ACCELERATOR_REPOSITORY_SOURCE: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.repositorySource.valueAsString, - }, - ACCELERATOR_REPOSITORY_OWNER: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.repositoryOwner.valueAsString, - }, - ACCELERATOR_REPOSITORY_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.repositoryName.valueAsString, - }, - ACCELERATOR_REPOSITORY_BRANCH_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.repositoryBranchName.valueAsString, - }, - USE_EXISTING_CONFIG_REPO: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.useExistingConfigRepo.valueAsString, - }, - EXISTING_CONFIG_REPOSITORY_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.existingConfigRepositoryName.valueAsString, - }, - EXISTING_CONFIG_REPOSITORY_BRANCH_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.existingConfigRepositoryBranchName.valueAsString, - }, - ACCELERATOR_ENABLE_APPROVAL_STAGE: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.enableApprovalStage.valueAsString, - }, - APPROVAL_STAGE_NOTIFY_EMAIL_LIST: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: cdk.Fn.join(',', this.approvalStageNotifyEmailList.valueAsList), - }, - MANAGEMENT_ACCOUNT_EMAIL: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.managementAccountEmail.valueAsString, - }, - LOG_ARCHIVE_ACCOUNT_EMAIL: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.logArchiveAccountEmail.valueAsString, - }, - AUDIT_ACCOUNT_EMAIL: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.auditAccountEmail.valueAsString, - }, - CONTROL_TOWER_ENABLED: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.controlTowerEnabled.valueAsString, - }, - ACCELERATOR_PREFIX: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: acceleratorPrefix, - }, - INSTALLER_STACK_NAME: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.stackName, - }, - ENABLE_DIAGNOSTICS_PACK: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: this.enableDiagnosticsPack.valueAsString, - }, - PIPELINE_ACCOUNT_ID: { - type: cdk.aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: cdk.Stack.of(this).account, - }, - ...targetAcceleratorEnvVariables, - ...targetAcceleratorTestEnvVariables, - }, - }, - }); - - /** - * Pipeline - */ - const acceleratorRepoArtifact = new cdk.aws_codepipeline.Artifact('Source'); - - /** - * CodeCommit Pipeline - */ - - const codeCommitPipelineRole = new cdk.aws_iam.Role(this, 'CodeCommitPipelineRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('codepipeline.amazonaws.com'), - }); - - const codeCommitPipeline = new cdk.aws_codepipeline.Pipeline(this, 'CodeCommitPipeline', { - pipelineName: installerPipelineName, - artifactBucket: bucket.getS3Bucket(), - restartExecutionOnUpdate: true, - role: codeCommitPipelineRole, - }); - - codeCommitPipeline.addStage({ - stageName: 'Source', - actions: [ - new cdk.aws_codepipeline_actions.CodeCommitSourceAction({ - actionName: 'Source', - repository: cdk.aws_codecommit.Repository.fromRepositoryName( - this, - 'SourceRepo', - this.repositoryName.valueAsString, - ), - branch: this.repositoryBranchName.valueAsString, - output: acceleratorRepoArtifact, - trigger: cdk.aws_codepipeline_actions.CodeCommitTrigger.NONE, - }), - ], - }); - - codeCommitPipeline.addStage({ - stageName: 'Install', - actions: [ - new cdk.aws_codepipeline_actions.CodeBuildAction({ - actionName: 'Install', - project: installerProject, - input: acceleratorRepoArtifact, - role: codeCommitPipelineRole, - }), - ], - }); - - const useCodeCommitCondition = new cdk.CfnCondition(this, 'UseCodeCommitCondition', { - expression: cdk.Fn.conditionEquals(this.repositorySource.valueAsString, RepositorySources.CODECOMMIT), - }); - - const cfnCodeCommitPipelinePolicy = codeCommitPipelineRole.node.findChild('DefaultPolicy').node - .defaultChild as cdk.aws_iam.CfnPolicy; - cfnCodeCommitPipelinePolicy.cfnOptions.condition = useCodeCommitCondition; - - const cfnCodeCommitPipelineRole = codeCommitPipelineRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnCodeCommitPipelineRole.cfnOptions.condition = useCodeCommitCondition; - - const cfnCodeCommitPipeline = codeCommitPipeline.node.defaultChild as cdk.aws_codepipeline.CfnPipeline; - cfnCodeCommitPipeline.cfnOptions.condition = useCodeCommitCondition; - - const cfnCodeCommitPipelineSource = codeCommitPipeline.node - .findChild('Source') - .node.findChild('Source') - .node.findChild('CodePipelineActionRole').node; - (cfnCodeCommitPipelineSource.defaultChild as cdk.CfnResource).cfnOptions.condition = useCodeCommitCondition; - (cfnCodeCommitPipelineSource.findChild('DefaultPolicy').node.defaultChild as cdk.CfnResource).cfnOptions.condition = - useCodeCommitCondition; - - /** - * GitHub Pipeline - */ - const gitHubPipelineRole = new cdk.aws_iam.Role(this, 'GitHubPipelineRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('codepipeline.amazonaws.com'), - }); - - const gitHubPipeline = new cdk.aws_codepipeline.Pipeline(this, 'GitHubPipeline', { - pipelineName: installerPipelineName, - artifactBucket: bucket.getS3Bucket(), - restartExecutionOnUpdate: true, - role: gitHubPipelineRole, - }); - - gitHubPipeline.addStage({ - stageName: 'Source', - actions: [ - new cdk.aws_codepipeline_actions.GitHubSourceAction({ - actionName: 'Source', - owner: this.repositoryOwner.valueAsString, - repo: this.repositoryName.valueAsString, - branch: this.repositoryBranchName.valueAsString, - oauthToken: cdk.SecretValue.secretsManager('accelerator/github-token'), - output: acceleratorRepoArtifact, - trigger: cdk.aws_codepipeline_actions.GitHubTrigger.NONE, - }), - ], - }); - - gitHubPipeline.addStage({ - stageName: 'Install', - actions: [ - new cdk.aws_codepipeline_actions.CodeBuildAction({ - actionName: 'Install', - project: installerProject, - input: acceleratorRepoArtifact, - role: gitHubPipelineRole, - }), - ], - }); - - const useGitHubCondition = new cdk.CfnCondition(this, 'UseGitHubCondition', { - expression: cdk.Fn.conditionEquals(this.repositorySource.valueAsString, RepositorySources.GITHUB), - }); - - const cfnGitHubPipelinePolicy = gitHubPipelineRole.node.findChild('DefaultPolicy').node - .defaultChild as cdk.aws_iam.CfnPolicy; - cfnGitHubPipelinePolicy.cfnOptions.condition = useGitHubCondition; - - const cfnGitHubPipelineRole = gitHubPipelineRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnGitHubPipelineRole.cfnOptions.condition = useGitHubCondition; - - const cfnGitHubPipeline = gitHubPipeline.node.defaultChild as cdk.aws_codepipeline.CfnPipeline; - cfnGitHubPipeline.cfnOptions.condition = useGitHubCondition; - - /** - * Update GitHub Token for Github Pipeline - */ - - const fileContents = fs.readFileSync( - path.join(__dirname, '..', 'lib', 'lambdas/update-pipeline-github-token/index.js'), - ); - - const updatePipelineLambdaRole = new cdk.aws_iam.Role(this, 'UpdatePipelineLambdaRole', { - assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'), - }); - - const secretIdPrefix = `arn:${this.partition}:secretsmanager:${this.region}:${this.account}:secret:accelerator/github-token`; - const installerPipelineArn = `arn:${this.partition}:codepipeline:${this.region}:${this.account}:${installerPipelineName}`; - const acceleratorPipelineArn = `arn:${this.partition}:codepipeline:${this.region}:${this.account}:${acceleratorPipelineName}`; - - const updatePipelineLambdaPolicy = new cdk.aws_iam.Policy(this, 'UpdatePipelineLambdaPolicy', { - statements: [ - new cdk.aws_iam.PolicyStatement({ - actions: ['codepipeline:GetPipeline', 'codepipeline:UpdatePipeline'], - resources: [`${installerPipelineArn}*`, `${acceleratorPipelineArn}*`], - }), - new cdk.aws_iam.PolicyStatement({ - actions: [ - 'secretsmanager:GetResourcePolicy', - 'secretsmanager:GetSecretValue', - 'secretsmanager:DescribeSecret', - 'secretsmanager:ListSecretVersionIds', - ], - resources: [`${secretIdPrefix}*`], - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['kms:Decrypt'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], - resources: ['*'], - }), - new cdk.aws_iam.PolicyStatement({ - actions: ['iam:PassRole'], - resources: [acceleratorPrincipalArn, gitHubPipelineRole.roleArn], - }), - ], - }); - - updatePipelineLambdaRole.attachInlinePolicy(updatePipelineLambdaPolicy); - - const updatePipelineGithubTokenFunction = new cdk.aws_lambda.Function(this, 'UpdatePipelineGithubTokenFunction', { - code: new cdk.aws_lambda.InlineCode(fileContents.toString()), - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'Lambda function to update CodePipeline OAuth Token', - timeout: cdk.Duration.minutes(1), - environment: { - ACCELERATOR_PIPELINE_NAME: acceleratorPipelineName, - INSTALLER_PIPELINE_NAME: installerPipelineName, - }, - environmentEncryption: installerKey, - role: updatePipelineLambdaRole, - }); - - const eventTargetLambdaType = new cdk.aws_events_targets.LambdaFunction(updatePipelineGithubTokenFunction, { - maxEventAge: cdk.Duration.hours(4), - retryAttempts: 2, - }); - - const updatePipelineGithubTokenRule = new cdk.aws_events.Rule(this, 'UpdatePipelineGithubTokenRule', { - eventPattern: { - detailType: ['AWS API Call via CloudTrail'], - detail: { - eventSource: ['secretsmanager.amazonaws.com'], - eventName: ['UpdateSecret', 'PutSecretValue'], - requestParameters: { - secretId: [ - { - prefix: secretIdPrefix, - }, - ], - }, - }, - }, - description: 'Rule to trigger Lambda Function when the Github Accelerator Token has been updated.', - targets: [eventTargetLambdaType], - }); - - const updatePipelineGithubTokenLogGroup = new cdk.aws_logs.LogGroup( - this, - `${updatePipelineGithubTokenFunction.node.id}LogGroup`, - { - logGroupName: `/aws/lambda/${updatePipelineGithubTokenFunction.functionName}`, - - removalPolicy: cdk.RemovalPolicy.DESTROY, - }, - ); - - /** - * Only create GitHub Pipeline Update Resources if it is a GitHub Sourced Pipeline. - * Constructs must be cast down to L1 constructs in order to use conditions. - */ - for (const x of updatePipelineGithubTokenRule.node.findAll()) { - if (x.node.id.includes('UpdatePipelineGithubTokenFunction')) { - const cfnGithubTokenPermission = updatePipelineGithubTokenRule.node.findChild( - x.node.id, - ) as cdk.aws_lambda.CfnPermission; - cfnGithubTokenPermission.cfnOptions.condition = useGitHubCondition; - } - } - - const cfnUpdatePipelineLambdaRole = updatePipelineLambdaRole.node.defaultChild as cdk.aws_iam.CfnRole; - cfnUpdatePipelineLambdaRole.cfnOptions.condition = useGitHubCondition; - - const cfnUpdatePipelineGithubTokenRule = updatePipelineGithubTokenRule.node.defaultChild as cdk.aws_events.CfnRule; - cfnUpdatePipelineGithubTokenRule.cfnOptions.condition = useGitHubCondition; - - const cfnUpdatePipelineGithubTokenLogGroup = updatePipelineGithubTokenLogGroup.node - .defaultChild as cdk.aws_logs.CfnLogGroup; - cfnUpdatePipelineGithubTokenLogGroup.cfnOptions.condition = useGitHubCondition; - - // Suppressing due to missing field in aws-us-gov CFN spec - cfnUpdatePipelineGithubTokenLogGroup.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'W84', - reason: 'CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data', - }, - ], - }, - }; - - // - // cfn-nag suppressions - // - // W12 IAM Policy allows * on KMS decrypt because Secrets Manager key can be encrypted with user selected key. - const cfnLambdaFunctionPolicy = updatePipelineLambdaPolicy.node.defaultChild as cdk.aws_iam.CfnPolicy; - cfnLambdaFunctionPolicy.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'W12', - reason: 'IAM policy should not allow * resource.', - }, - ], - }, - }; - cfnLambdaFunctionPolicy.cfnOptions.condition = useGitHubCondition; - - const cfnLambdaFunction = updatePipelineGithubTokenFunction.node.defaultChild as cdk.CfnResource; - this.addLambdaNagMetadata(cfnLambdaFunction); - - cfnLambdaFunction.cfnOptions.condition = useGitHubCondition; - - // - // cdk-nag suppressions - // - const iam4SuppressionPaths = [ - 'InstallerAdminRole/Resource', - 'InstallerAdminRole/DefaultPolicy/Resource', - 'UpdatePipelineLambdaRole/Resource', - ]; - - const iam5SuppressionPaths = [ - 'InstallerAdminRole/DefaultPolicy/Resource', - 'CodeCommitPipelineRole/DefaultPolicy/Resource', - 'CodeCommitPipeline/Source/Source/CodePipelineActionRole/DefaultPolicy/Resource', - 'UpdatePipelineLambdaPolicy/Resource', - 'GitHubPipelineRole/DefaultPolicy/Resource', - ]; - - const cb3SuppressionPaths = ['InstallerProject/Resource']; - - // AwsSolutions-IAM4: The IAM user, role, or group uses AWS managed policies. - for (const path of iam4SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { id: 'AwsSolutions-IAM4', reason: 'Managed policies required for IAM role.' }, - ]); - } - - // AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission. - for (const path of iam5SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { id: 'AwsSolutions-IAM5', reason: 'IAM role requires wildcard permissions.' }, - ]); - } - - // AwsSolutions-CB3: The CodeBuild project has privileged mode enabled. - for (const path of cb3SuppressionPaths) { - NagSuppressions.addResourceSuppressionsByPath(this, `${this.stackName}/${path}`, [ - { - id: 'AwsSolutions-CB3', - reason: 'Project requires access to the Docker daemon.', - }, - ]); - } - } - - /** - * Adds required metadata to Lambda functions for AWS Solutions security scans - * @param resource - */ - private addLambdaNagMetadata(resource: cdk.CfnResource): void { - resource.addMetadata('cfn_nag', { - rules_to_suppress: [ - { - id: 'W58', - reason: `CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole`, - }, - { - id: 'W89', - reason: `This function supports infrastructure deployment and is not deployed inside a VPC.`, - }, - { - id: 'W92', - reason: `This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.`, - }, - ], - }); - } -} diff --git a/source/packages/@aws-accelerator/installer/lib/lambdas/update-pipeline-github-token/index.js b/source/packages/@aws-accelerator/installer/lib/lambdas/update-pipeline-github-token/index.js deleted file mode 100644 index ed09d5a..0000000 --- a/source/packages/@aws-accelerator/installer/lib/lambdas/update-pipeline-github-token/index.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(`Secret ${secretName} didn't exist.`); - } - console.log(`Retrieved secret: ${secretName}...`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(`Error retrieving secret: ${secretName}.`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(`Retrieving existing pipeline configuration for: ${pipelineName}...`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(`Updating pipeline with new OAuth Token...`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(`Error occurred while updating pipeline ${pipeline}`); - } - } -} diff --git a/source/packages/@aws-accelerator/installer/lib/resource-name-prefixes.ts b/source/packages/@aws-accelerator/installer/lib/resource-name-prefixes.ts deleted file mode 100644 index f559f85..0000000 --- a/source/packages/@aws-accelerator/installer/lib/resource-name-prefixes.ts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -export interface ResourceNamePrefixesProps { - readonly acceleratorPrefix: string; - readonly acceleratorQualifier?: string; -} - -export class ResourceNamePrefixes extends Construct { - public readonly acceleratorPrefix: string = ''; - public readonly lowerCasePrefix: string = ''; - public readonly oneWordPrefix: string = ''; - - constructor(scope: Construct, id: string, props: ResourceNamePrefixesProps) { - super(scope, id); - - const pipelineStackVersionSsmParamName = props.acceleratorQualifier - ? `/accelerator/${props.acceleratorQualifier}-pipeline-stack-${cdk.Stack.of(this).account}-${ - cdk.Stack.of(this).region - }/version` - : `/accelerator/AWSAccelerator-PipelineStack-${cdk.Stack.of(this).account}-${cdk.Stack.of(this).region}/version`; - - const lambdaFunction = new cdk.aws_lambda.Function(this, 'ResourceNamePrefixesFunction', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: - 'This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack', - code: cdk.aws_lambda.Code.fromInline(` - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }`), - }); - - if (props.acceleratorQualifier) { - lambdaFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'SsmReadParameterAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter', 'ssm:PutParameter', 'ssm:DeleteParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:parameter/accelerator/${props.acceleratorQualifier}/lza-prefix`, - `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:parameter${pipelineStackVersionSsmParamName}`, - ], - conditions: { - StringEquals: { - 'aws:PrincipalAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - } else { - lambdaFunction.addToRolePolicy( - new cdk.aws_iam.PolicyStatement({ - sid: 'SsmReadParameterAccess', - effect: cdk.aws_iam.Effect.ALLOW, - actions: ['ssm:GetParameters', 'ssm:GetParameter', 'ssm:PutParameter', 'ssm:DeleteParameter'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:parameter/accelerator/lza-prefix`, - `arn:${cdk.Stack.of(this).partition}:ssm:${cdk.Stack.of(this).region}:${ - cdk.Stack.of(this).account - }:parameter${pipelineStackVersionSsmParamName}`, - ], - conditions: { - StringEquals: { - 'aws:PrincipalAccount': cdk.Stack.of(this).account, - }, - }, - }), - ); - } - - NagSuppressions.addResourceSuppressions( - lambdaFunction, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'Needed to write to CWL group', - }, - ], - true, - ); - - NagSuppressions.addResourceSuppressions( - lambdaFunction, - [ - { - id: 'AwsSolutions-IAM5', - reason: 'Needed to create SSM parameter for prefix', - }, - ], - true, - ); - - const getPrefixResource = new cdk.CustomResource(this, 'GetPrefixResource', { - serviceToken: lambdaFunction.functionArn, - properties: { - prefix: props.acceleratorPrefix, - pipelineStackVersionSsmParamName: pipelineStackVersionSsmParamName, - prefixParameterName: props.acceleratorQualifier - ? `/accelerator/${props.acceleratorQualifier}/lza-prefix` - : '/accelerator/lza-prefix', - }, - resourceType: 'Custom::GetPrefixes', - }); - - this.acceleratorPrefix = getPrefixResource.getAtt('acceleratorPrefix').toString(); - this.lowerCasePrefix = getPrefixResource.getAtt('lowerCasePrefix').toString(); - this.oneWordPrefix = getPrefixResource.getAtt('oneWordPrefix').toString(); - } -} diff --git a/source/packages/@aws-accelerator/installer/lib/solutions-helper.ts b/source/packages/@aws-accelerator/installer/lib/solutions-helper.ts deleted file mode 100644 index 4d7c905..0000000 --- a/source/packages/@aws-accelerator/installer/lib/solutions-helper.ts +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as lambda from 'aws-cdk-lib/aws-lambda'; - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -export interface SolutionHelperProps { - readonly solutionId: string; - readonly repositorySource: cdk.CfnParameter; - readonly repositoryOwner: cdk.CfnParameter; - readonly repositoryBranchName: cdk.CfnParameter; - readonly repositoryName: cdk.CfnParameter; -} - -export class SolutionHelper extends Construct { - constructor(scope: Construct, id: string, props: SolutionHelperProps) { - super(scope, id); - const metricsMapping = new cdk.CfnMapping(this, 'AnonymousData', { - mapping: { - SendAnonymizedData: { - Data: 'Yes', - }, - }, - }); - - const metricsCondition = new cdk.CfnCondition(this, 'AnonymousDataToAWS', { - expression: cdk.Fn.conditionEquals(metricsMapping.findInMap('SendAnonymizedData', 'Data'), 'Yes'), - }); - - const helperFunction = new lambda.Function(this, 'SolutionHelper', { - runtime: lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: - 'This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team', - code: lambda.Code.fromInline(` - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - `), - timeout: cdk.Duration.seconds(30), - }); - - NagSuppressions.addResourceSuppressions( - helperFunction, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'Needed to write to CWL group', - }, - ], - true, - ); - - const cfnLambdaFunction = helperFunction.node.findChild('Resource') as lambda.CfnFunction; - cfnLambdaFunction.cfnOptions.metadata = { - cfn_nag: { - rules_to_suppress: [ - { - id: 'W58', - reason: `CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole`, - }, - { - id: 'W89', - reason: `This function supports infrastructure deployment and is not deployed inside a VPC.`, - }, - { - id: 'W92', - reason: `This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.`, - }, - ], - }, - }; - - const createIdFunction = new cdk.CustomResource(this, 'SolutionCreateUniqueID', { - serviceToken: helperFunction.functionArn, - properties: { - Resource: 'UUID', - }, - resourceType: 'Custom::CreateUUID', - }); - - const sendDataFunction = new cdk.CustomResource(this, 'SolutionSendAnonymousData', { - serviceToken: helperFunction.functionArn, - properties: { - Resource: 'AnonymousMetric', - SolutionId: props.solutionId, - UUID: createIdFunction.getAttString('UUID'), - Region: cdk.Aws.REGION, - BranchName: props.repositoryBranchName.valueAsString, - RepositoryName: props.repositoryName.valueAsString, - RepositoryOwner: props.repositoryOwner.valueAsString, - RepositorySource: props.repositorySource.valueAsString, - }, - resourceType: 'Custom::AnonymousData', - }); - - (helperFunction.node.defaultChild as lambda.CfnFunction).cfnOptions.condition = metricsCondition; - (createIdFunction.node.defaultChild as lambda.CfnFunction).cfnOptions.condition = metricsCondition; - (sendDataFunction.node.defaultChild as lambda.CfnFunction).cfnOptions.condition = metricsCondition; - } -} diff --git a/source/packages/@aws-accelerator/installer/lib/validate.ts b/source/packages/@aws-accelerator/installer/lib/validate.ts deleted file mode 100644 index a62fba8..0000000 --- a/source/packages/@aws-accelerator/installer/lib/validate.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import { NagSuppressions } from 'cdk-nag'; - -export interface ValidateProps { - readonly useExistingConfigRepo: string; - readonly existingConfigRepositoryName?: string; - readonly existingConfigRepositoryBranchName?: string; -} - -export class Validate extends Construct { - public readonly configRepoName: string = ''; - public readonly configRepoBranchName: string = ''; - - constructor(scope: Construct, id: string, props: ValidateProps) { - super(scope, id); - - const lambdaFunction = new cdk.aws_lambda.Function(this, 'ValidationFunction', { - runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - description: 'This function validates installer parameters', - code: cdk.aws_lambda.Code.fromInline(` - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }`), - }); - - NagSuppressions.addResourceSuppressions( - lambdaFunction, - [ - { - id: 'AwsSolutions-IAM4', - reason: 'Needed to write to CWL group', - }, - ], - true, - ); - - new cdk.CustomResource(this, 'ValidateResource', { - serviceToken: lambdaFunction.functionArn, - properties: { - useExistingConfigRepo: props.useExistingConfigRepo, - existingConfigRepositoryName: props.existingConfigRepositoryName, - existingConfigRepositoryBranchName: props.existingConfigRepositoryBranchName, - resourceType: 'Custom::ValidateInstallerStack', - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/installer/package.json b/source/packages/@aws-accelerator/installer/package.json deleted file mode 100644 index a562836..0000000 --- a/source/packages/@aws-accelerator/installer/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "@aws-accelerator/installer", - "version": "0.0.0", - "private": true, - "description": "The installer package for the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cdk": "cdk", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "@aws-accelerator/accelerator": "^0.0.0", - "@aws-cdk-extensions/cdk-extensions": "^0.0.0", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "cdk-nag": "2.5.2" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "lint-staged": "13.1.2", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/installer/test/__snapshots__/installer.test.ts.snap b/source/packages/@aws-accelerator/installer/test/__snapshots__/installer.test.ts.snap deleted file mode 100644 index 9fc6710..0000000 --- a/source/packages/@aws-accelerator/installer/test/__snapshots__/installer.test.ts.snap +++ /dev/null @@ -1,20736 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InstallerStack Stack(installer): Snapshot Test 1`] = ` -{ - "Conditions": { - "IsCommercialCondition": { - "Fn::Equals": [ - { - "Ref": "AWS::Partition", - }, - "aws", - ], - }, - "SolutionHelperAnonymousDataToAWS62E4FDE2": { - "Fn::Equals": [ - { - "Fn::FindInMap": [ - "SolutionHelperAnonymousData14B64A81", - "SendAnonymizedData", - "Data", - ], - }, - "Yes", - ], - }, - "UseCodeCommitCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "codecommit", - ], - }, - "UseGitHubCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "github", - ], - }, - }, - "Mappings": { - "GlobalRegionMap": { - "aws": { - "regionName": "us-east-1", - }, - "aws-cn": { - "regionName": "cn-northwest-1", - }, - "aws-iso": { - "regionName": "us-iso-east-1", - }, - "aws-iso-b": { - "regionName": "us-isob-east-1", - }, - "aws-us-gov": { - "regionName": "us-gov-west-1", - }, - }, - "SolutionHelperAnonymousData14B64A81": { - "SendAnonymizedData": { - "Data": "Yes", - }, - }, - }, - "Metadata": { - "AWS::CloudFormation::Interface": { - "ParameterGroups": [ - { - "Label": { - "default": "Git Repository Configuration", - }, - "Parameters": [ - "RepositorySource", - "RepositoryOwner", - "RepositoryName", - "RepositoryBranchName", - ], - }, - { - "Label": { - "default": "Pipeline Configuration", - }, - "Parameters": [ - "EnableApprovalStage", - "ApprovalStageNotifyEmailList", - ], - }, - { - "Label": { - "default": "Mandatory Accounts Configuration", - }, - "Parameters": [ - "ManagementAccountEmail", - "LogArchiveAccountEmail", - "AuditAccountEmail", - ], - }, - { - "Label": { - "default": "Environment Configuration", - }, - "Parameters": [ - "ControlTowerEnabled", - "AcceleratorPrefix", - "UseExistingConfigRepo", - "ExistingConfigRepositoryName", - "ExistingConfigRepositoryBranchName", - "EnableDiagnosticsPack", - ], - }, - ], - "ParameterLabels": { - "AcceleratorPrefix": { - "default": "Accelerator Resource name prefix", - }, - "ApprovalStageNotifyEmailList": { - "default": "Manual Approval Stage notification email list", - }, - "AuditAccountEmail": { - "default": "Audit Account Email", - }, - "ControlTowerEnabled": { - "default": "Control Tower Environment", - }, - "EnableApprovalStage": { - "default": "Enable Approval Stage", - }, - "EnableDiagnosticsPack": { - "default": "Enable Diagnostics Pack", - }, - "ExistingConfigRepositoryBranchName": { - "default": "Existing Config Repository Branch Name", - }, - "ExistingConfigRepositoryName": { - "default": "Existing Config Repository Name", - }, - "LogArchiveAccountEmail": { - "default": "Log Archive Account Email", - }, - "ManagementAccountEmail": { - "default": "Management Account Email", - }, - "RepositoryBranchName": { - "default": "Branch Name", - }, - "RepositoryName": { - "default": "Repository Name", - }, - "RepositoryOwner": { - "default": "Repository Owner", - }, - "RepositorySource": { - "default": "Source", - }, - "UseExistingConfigRepo": { - "default": "Use Existing Config Repository", - }, - }, - }, - }, - "Parameters": { - "AcceleratorPrefix": { - "AllowedPattern": "[A-Za-z0-9-]+", - "Default": "AWSAccelerator", - "Description": "The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.", - "MaxLength": 15, - "Type": "String", - }, - "ApprovalStageNotifyEmailList": { - "Description": "Provide comma(,) separated list of email ids to receive manual approval stage notification email", - "Type": "CommaDelimitedList", - }, - "AuditAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The security audit account (also referred to as the audit account)", - "Type": "String", - }, - "ControlTowerEnabled": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes if you deploying to a Control Tower environment. Select no if using just Organizations", - "Type": "String", - }, - "EnableApprovalStage": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes to add a Manual Approval stage to accelerator pipeline", - "Type": "String", - }, - "EnableDiagnosticsPack": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.", - "Type": "String", - }, - "ExistingConfigRepositoryBranchName": { - "Default": "", - "Description": "Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.", - "Type": "String", - }, - "ExistingConfigRepositoryName": { - "Default": "", - "Description": "The name of an existing CodeCommit repository hosting the accelerator configuration.", - "Type": "String", - }, - "LogArchiveAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The log archive account email", - "Type": "String", - }, - "ManagementAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The management (primary) account email", - "Type": "String", - }, - "RepositoryBranchName": { - "AllowedPattern": ".+", - "ConstraintDescription": "The repository branch name must not be empty", - "Default": "release/v1.8.1", - "Description": "The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.", - "Type": "String", - }, - "RepositoryName": { - "Default": "landing-zone-accelerator-on-aws", - "Description": "The name of the git repository hosting the accelerator code", - "Type": "String", - }, - "RepositoryOwner": { - "Default": "awslabs", - "Description": "The owner of the repository containing the accelerator code. (GitHub Only)", - "Type": "String", - }, - "RepositorySource": { - "AllowedValues": [ - "github", - "codecommit", - ], - "Default": "github", - "Description": "Specify the git host", - "Type": "String", - }, - "UseExistingConfigRepo": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "No", - "Description": "Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.", - "Type": "String", - }, - }, - "Resources": { - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer/kms/key-arn", - ], - ], - }, - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CodeCommitPipeline2208527B": { - "Condition": "UseCodeCommitCondition", - "DependsOn": [ - "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "CodeCommitPipelineRole5C35E76C", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "PollForSourceChanges": false, - "RepositoryName": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "CodeCommitPipelineRole5C35E76C": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CodeCommitPipelineRoleDefaultPolicyDE8B332B": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "Roles": [ - { - "Ref": "CodeCommitPipelineRole5C35E76C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "RepositoryName", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D", - "Roles": [ - { - "Ref": "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipeline7B79E906": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "GitHubPipelineRoleDefaultPolicyD82457D6", - "GitHubPipelineRole6F4DEF1B", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "ThirdParty", - "Provider": "GitHub", - "Version": "1", - }, - "Configuration": { - "Branch": { - "Ref": "RepositoryBranchName", - }, - "OAuthToken": "{{resolve:secretsmanager:accelerator/github-token:SecretString:::}}", - "Owner": { - "Ref": "RepositoryOwner", - }, - "PollForSourceChanges": false, - "Repo": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "GitHubPipelineRole6F4DEF1B": { - "Condition": "UseGitHubCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipelineRoleDefaultPolicyD82457D6": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GitHubPipelineRoleDefaultPolicyD82457D6", - "Roles": [ - { - "Ref": "GitHubPipelineRole6F4DEF1B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerAccessLogsBucket647700E9": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W35", - "reason": "This is an access logging bucket.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "InstallerAccessLogsBucketName4F700F48": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer-access-logs-bucket-name", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InstallerAccessLogsBucketPolicy20D4E285": { - "Properties": { - "Bucket": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "InstallerAdminRole7DEE4AC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InstallerAdminRoleDefaultPolicy7EEE1AAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "InstallerProject879FF821", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "InstallerAdminRoleDefaultPolicy7EEE1AAB", - "Roles": [ - { - "Ref": "InstallerAdminRole7DEE4AC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerKey2A6A8C6D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "F76", - "reason": "KMS key using * principal with added arn condition", - }, - ], - }, - }, - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow SNS service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch Logs service to use the encryption key", - }, - { - "Fn::If": [ - "IsCommercialCondition", - { - "Action": [ - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "sns.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": "*", - "Sid": "KMS key access to codestar-notifications", - }, - { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "InstallerKeyAliasD5C174F0": { - "Properties": { - "AliasName": { - "Fn::Join": [ - "", - [ - "alias/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer/kms/key", - ], - ], - }, - "TargetKeyId": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "InstallerProject879FF821": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "EncryptionKey": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_REPOSITORY_SOURCE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositorySource", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_OWNER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryOwner", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryName", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryBranchName", - }, - }, - { - "Name": "USE_EXISTING_CONFIG_REPO", - "Type": "PLAINTEXT", - "Value": { - "Ref": "UseExistingConfigRepo", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryName", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_APPROVAL_STAGE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableApprovalStage", - }, - }, - { - "Name": "APPROVAL_STAGE_NOTIFY_EMAIL_LIST", - "Type": "PLAINTEXT", - "Value": { - "Fn::Join": [ - ",", - { - "Ref": "ApprovalStageNotifyEmailList", - }, - ], - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountEmail", - }, - }, - { - "Name": "LOG_ARCHIVE_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "LogArchiveAccountEmail", - }, - }, - { - "Name": "AUDIT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AuditAccountEmail", - }, - }, - { - "Name": "CONTROL_TOWER_ENABLED", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ControlTowerEnabled", - }, - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator-Test-InstallerStack", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableDiagnosticsPack", - }, - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::AccountId", - }, - }, - { - "Name": "ENABLE_TESTER", - "Type": "PLAINTEXT", - "Value": "true", - }, - { - "Name": "MANAGEMENT_CROSS_ACCOUNT_ROLE_NAME", - "Type": "PLAINTEXT", - "Value": "AWSControlTowerExecution", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-InstallerProject", - ], - ], - }, - "ServiceRole": { - "Fn::GetAtt": [ - "InstallerAdminRole7DEE4AC8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - pre_build: - commands: - - ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no" - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then BOOTSTRAPPED_HOME="no"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then BOOTSTRAPPED_GLOBAL="no"; fi - - ENABLE_DIAGNOSTICS_PACK=", - { - "Ref": "EnableDiagnosticsPack", - }, - " - build: - commands: - - cd source - - |- - if [ "", - { - "Ref": "AWS::Partition", - }, - "" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi - - yarn install - - yarn build - - cd packages/@aws-accelerator/installer - - set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi - - set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi - - |- - set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi - - cd ../accelerator - - |- - aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi; - - |- - set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - fi - - set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - - set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - "; fi - post_build: - commands: - - |- - if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline"; - fi - fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "ResourceNamePrefixesGetPrefixResource96A10E6E": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1", - "Arn", - ], - }, - "pipelineStackVersionSsmParamName": { - "Fn::Join": [ - "", - [ - "/accelerator/AWSAccelerator-PipelineStack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - "prefix": { - "Ref": "AcceleratorPrefix", - }, - "prefixParameterName": "/accelerator/lza-prefix", - }, - "Type": "Custom::GetPrefixes", - "UpdateReplacePolicy": "Delete", - }, - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1": { - "DependsOn": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }", - }, - "Description": "This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalAccount": { - "Ref": "AWS::AccountId", - }, - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/lza-prefix", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/AWSAccelerator-PipelineStack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - ], - "Sid": "SsmReadParameterAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "Roles": [ - { - "Ref": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecureBucket747CD8C0": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SecureBucketPolicy6374AC61": { - "Properties": { - "Bucket": { - "Ref": "SecureBucket747CD8C0", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "SolutionHelper4825923B": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DependsOn": [ - "SolutionHelperServiceRoleF70C0E2A", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - ", - }, - "Description": "This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "SolutionHelperServiceRoleF70C0E2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "SolutionHelperServiceRoleF70C0E2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SolutionHelperSolutionCreateUniqueID070ED802": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "Resource": "UUID", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - }, - "Type": "Custom::CreateUUID", - "UpdateReplacePolicy": "Delete", - }, - "SolutionHelperSolutionSendAnonymousData271B3D26": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "Region": { - "Ref": "AWS::Region", - }, - "RepositoryName": { - "Ref": "RepositoryName", - }, - "RepositoryOwner": { - "Ref": "RepositoryOwner", - }, - "RepositorySource": { - "Ref": "RepositorySource", - }, - "Resource": "AnonymousMetric", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - "SolutionId": "SO0199", - "UUID": { - "Fn::GetAtt": [ - "SolutionHelperSolutionCreateUniqueID070ED802", - "UUID", - ], - }, - }, - "Type": "Custom::AnonymousData", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/AWSAccelerator-Test-InstallerStack/version", - ], - ], - }, - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/AWSAccelerator-Test-InstallerStack/stack-id", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "UpdatePipelineGithubTokenFunction29B64F2E": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "UpdatePipelineLambdaRole88CE0535", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(\`Secret \${secretName} didn't exist.\`); - } - console.log(\`Retrieved secret: \${secretName}...\`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(\`Error retrieving secret: \${secretName}.\`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(\`Retrieving existing pipeline configuration for: \${pipelineName}...\`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(\`Updating pipeline with new OAuth Token...\`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(\`Error occurred while updating pipeline \${pipeline}\`); - } - } -} -", - }, - "Description": "Lambda function to update CodePipeline OAuth Token", - "Environment": { - "Variables": { - "ACCELERATOR_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline", - ], - ], - }, - "INSTALLER_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "UpdatePipelineLambdaRole88CE0535", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "UpdatePipelineGithubTokenFunctionLogGroupFCE3723A": { - "Condition": "UseGitHubCondition", - "DeletionPolicy": "Delete", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W84", - "reason": "CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data", - }, - ], - }, - }, - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "UpdatePipelineGithubTokenFunction29B64F2E", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "UpdatePipelineGithubTokenRule79D83132": { - "Condition": "UseGitHubCondition", - "Properties": { - "Description": "Rule to trigger Lambda Function when the Github Accelerator Token has been updated.", - "EventPattern": { - "detail": { - "eventName": [ - "UpdateSecret", - "PutSecretValue", - ], - "eventSource": [ - "secretsmanager.amazonaws.com", - ], - "requestParameters": { - "secretId": [ - { - "prefix": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token", - ], - ], - }, - }, - ], - }, - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "UpdatePipelineGithubTokenRuleAllowEventRuleAWSAcceleratorTestInstallerStackUpdatePipelineGithubTokenFunction78C173C279CEF0A3": { - "Condition": "UseGitHubCondition", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenRule79D83132", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "UpdatePipelineLambdaPolicy284ABC36": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipeline", - "codepipeline:UpdatePipeline", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline*", - ], - ], - }, - ], - }, - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-*", - ], - ], - }, - { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "UpdatePipelineLambdaPolicy284ABC36", - "Roles": [ - { - "Ref": "UpdatePipelineLambdaRole88CE0535", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "UpdatePipelineLambdaRole88CE0535": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "ValidateInstallerValidateResource24181D5D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunction21674768", - "Arn", - ], - }, - "existingConfigRepositoryBranchName": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - "existingConfigRepositoryName": { - "Ref": "ExistingConfigRepositoryName", - }, - "resourceType": "Custom::ValidateInstallerStack", - "useExistingConfigRepo": { - "Ref": "UseExistingConfigRepo", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateInstallerValidationFunction21674768": { - "DependsOn": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }", - }, - "Description": "This function validates installer parameters", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; - -exports[`InstallerStack Stack(installer): Snapshot Test 2`] = ` -{ - "Conditions": { - "IsCommercialCondition": { - "Fn::Equals": [ - { - "Ref": "AWS::Partition", - }, - "aws", - ], - }, - "SolutionHelperAnonymousDataToAWS62E4FDE2": { - "Fn::Equals": [ - { - "Fn::FindInMap": [ - "SolutionHelperAnonymousData14B64A81", - "SendAnonymizedData", - "Data", - ], - }, - "Yes", - ], - }, - "UseCodeCommitCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "codecommit", - ], - }, - "UseGitHubCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "github", - ], - }, - }, - "Mappings": { - "GlobalRegionMap": { - "aws": { - "regionName": "us-east-1", - }, - "aws-cn": { - "regionName": "cn-northwest-1", - }, - "aws-iso": { - "regionName": "us-iso-east-1", - }, - "aws-iso-b": { - "regionName": "us-isob-east-1", - }, - "aws-us-gov": { - "regionName": "us-gov-west-1", - }, - }, - "SolutionHelperAnonymousData14B64A81": { - "SendAnonymizedData": { - "Data": "Yes", - }, - }, - }, - "Metadata": { - "AWS::CloudFormation::Interface": { - "ParameterGroups": [ - { - "Label": { - "default": "Git Repository Configuration", - }, - "Parameters": [ - "RepositorySource", - "RepositoryOwner", - "RepositoryName", - "RepositoryBranchName", - ], - }, - { - "Label": { - "default": "Pipeline Configuration", - }, - "Parameters": [ - "EnableApprovalStage", - "ApprovalStageNotifyEmailList", - ], - }, - { - "Label": { - "default": "Mandatory Accounts Configuration", - }, - "Parameters": [ - "ManagementAccountEmail", - "LogArchiveAccountEmail", - "AuditAccountEmail", - ], - }, - { - "Label": { - "default": "Environment Configuration", - }, - "Parameters": [ - "ControlTowerEnabled", - "AcceleratorPrefix", - "UseExistingConfigRepo", - "ExistingConfigRepositoryName", - "ExistingConfigRepositoryBranchName", - "EnableDiagnosticsPack", - ], - }, - ], - "ParameterLabels": { - "AcceleratorPrefix": { - "default": "Accelerator Resource name prefix", - }, - "ApprovalStageNotifyEmailList": { - "default": "Manual Approval Stage notification email list", - }, - "AuditAccountEmail": { - "default": "Audit Account Email", - }, - "ControlTowerEnabled": { - "default": "Control Tower Environment", - }, - "EnableApprovalStage": { - "default": "Enable Approval Stage", - }, - "EnableDiagnosticsPack": { - "default": "Enable Diagnostics Pack", - }, - "ExistingConfigRepositoryBranchName": { - "default": "Existing Config Repository Branch Name", - }, - "ExistingConfigRepositoryName": { - "default": "Existing Config Repository Name", - }, - "LogArchiveAccountEmail": { - "default": "Log Archive Account Email", - }, - "ManagementAccountEmail": { - "default": "Management Account Email", - }, - "RepositoryBranchName": { - "default": "Branch Name", - }, - "RepositoryName": { - "default": "Repository Name", - }, - "RepositoryOwner": { - "default": "Repository Owner", - }, - "RepositorySource": { - "default": "Source", - }, - "UseExistingConfigRepo": { - "default": "Use Existing Config Repository", - }, - }, - }, - }, - "Parameters": { - "AcceleratorPrefix": { - "AllowedPattern": "[A-Za-z0-9-]+", - "Default": "AWSAccelerator", - "Description": "The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.", - "MaxLength": 15, - "Type": "String", - }, - "ApprovalStageNotifyEmailList": { - "Description": "Provide comma(,) separated list of email ids to receive manual approval stage notification email", - "Type": "CommaDelimitedList", - }, - "AuditAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The security audit account (also referred to as the audit account)", - "Type": "String", - }, - "ControlTowerEnabled": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes if you deploying to a Control Tower environment. Select no if using just Organizations", - "Type": "String", - }, - "EnableApprovalStage": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes to add a Manual Approval stage to accelerator pipeline", - "Type": "String", - }, - "EnableDiagnosticsPack": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.", - "Type": "String", - }, - "ExistingConfigRepositoryBranchName": { - "Default": "", - "Description": "Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.", - "Type": "String", - }, - "ExistingConfigRepositoryName": { - "Default": "", - "Description": "The name of an existing CodeCommit repository hosting the accelerator configuration.", - "Type": "String", - }, - "LogArchiveAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The log archive account email", - "Type": "String", - }, - "ManagementAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The management (primary) account email", - "Type": "String", - }, - "RepositoryBranchName": { - "AllowedPattern": ".+", - "ConstraintDescription": "The repository branch name must not be empty", - "Default": "release/v1.8.1", - "Description": "The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.", - "Type": "String", - }, - "RepositoryName": { - "Default": "landing-zone-accelerator-on-aws", - "Description": "The name of the git repository hosting the accelerator code", - "Type": "String", - }, - "RepositoryOwner": { - "Default": "awslabs", - "Description": "The owner of the repository containing the accelerator code. (GitHub Only)", - "Type": "String", - }, - "RepositorySource": { - "AllowedValues": [ - "github", - "codecommit", - ], - "Default": "github", - "Description": "Specify the git host", - "Type": "String", - }, - "UseExistingConfigRepo": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "No", - "Description": "Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.", - "Type": "String", - }, - }, - "Resources": { - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer/kms/key-arn", - ], - ], - }, - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CodeCommitPipeline2208527B": { - "Condition": "UseCodeCommitCondition", - "DependsOn": [ - "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "CodeCommitPipelineRole5C35E76C", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "PollForSourceChanges": false, - "RepositoryName": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "CodeCommitPipelineRole5C35E76C": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CodeCommitPipelineRoleDefaultPolicyDE8B332B": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "Roles": [ - { - "Ref": "CodeCommitPipelineRole5C35E76C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "RepositoryName", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D", - "Roles": [ - { - "Ref": "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipeline7B79E906": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "GitHubPipelineRoleDefaultPolicyD82457D6", - "GitHubPipelineRole6F4DEF1B", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "ThirdParty", - "Provider": "GitHub", - "Version": "1", - }, - "Configuration": { - "Branch": { - "Ref": "RepositoryBranchName", - }, - "OAuthToken": "{{resolve:secretsmanager:accelerator/github-token:SecretString:::}}", - "Owner": { - "Ref": "RepositoryOwner", - }, - "PollForSourceChanges": false, - "Repo": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "GitHubPipelineRole6F4DEF1B": { - "Condition": "UseGitHubCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipelineRoleDefaultPolicyD82457D6": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GitHubPipelineRoleDefaultPolicyD82457D6", - "Roles": [ - { - "Ref": "GitHubPipelineRole6F4DEF1B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerAccessLogsBucket647700E9": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W35", - "reason": "This is an access logging bucket.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "InstallerAccessLogsBucketName4F700F48": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer-access-logs-bucket-name", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InstallerAccessLogsBucketPolicy20D4E285": { - "Properties": { - "Bucket": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "InstallerAdminRole7DEE4AC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InstallerAdminRoleDefaultPolicy7EEE1AAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "InstallerProject879FF821", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "InstallerAdminRoleDefaultPolicy7EEE1AAB", - "Roles": [ - { - "Ref": "InstallerAdminRole7DEE4AC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerKey2A6A8C6D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "F76", - "reason": "KMS key using * principal with added arn condition", - }, - ], - }, - }, - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow SNS service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch Logs service to use the encryption key", - }, - { - "Fn::If": [ - "IsCommercialCondition", - { - "Action": [ - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "sns.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": "*", - "Sid": "KMS key access to codestar-notifications", - }, - { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "InstallerKeyAliasD5C174F0": { - "Properties": { - "AliasName": { - "Fn::Join": [ - "", - [ - "alias/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer/kms/key", - ], - ], - }, - "TargetKeyId": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "InstallerProject879FF821": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "EncryptionKey": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_REPOSITORY_SOURCE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositorySource", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_OWNER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryOwner", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryName", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryBranchName", - }, - }, - { - "Name": "USE_EXISTING_CONFIG_REPO", - "Type": "PLAINTEXT", - "Value": { - "Ref": "UseExistingConfigRepo", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryName", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_APPROVAL_STAGE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableApprovalStage", - }, - }, - { - "Name": "APPROVAL_STAGE_NOTIFY_EMAIL_LIST", - "Type": "PLAINTEXT", - "Value": { - "Fn::Join": [ - ",", - { - "Ref": "ApprovalStageNotifyEmailList", - }, - ], - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountEmail", - }, - }, - { - "Name": "LOG_ARCHIVE_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "LogArchiveAccountEmail", - }, - }, - { - "Name": "AUDIT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AuditAccountEmail", - }, - }, - { - "Name": "CONTROL_TOWER_ENABLED", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ControlTowerEnabled", - }, - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator-Test-InstallerStack", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableDiagnosticsPack", - }, - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::AccountId", - }, - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-InstallerProject", - ], - ], - }, - "ServiceRole": { - "Fn::GetAtt": [ - "InstallerAdminRole7DEE4AC8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - pre_build: - commands: - - ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no" - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then BOOTSTRAPPED_HOME="no"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then BOOTSTRAPPED_GLOBAL="no"; fi - - ENABLE_DIAGNOSTICS_PACK=", - { - "Ref": "EnableDiagnosticsPack", - }, - " - build: - commands: - - cd source - - |- - if [ "", - { - "Ref": "AWS::Partition", - }, - "" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi - - yarn install - - yarn build - - cd packages/@aws-accelerator/installer - - set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi - - set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi - - |- - set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi - - cd ../accelerator - - |- - aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi; - - |- - set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - fi - - set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - - set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - "; fi - post_build: - commands: - - |- - if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline"; - fi - fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "ResourceNamePrefixesGetPrefixResource96A10E6E": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1", - "Arn", - ], - }, - "pipelineStackVersionSsmParamName": { - "Fn::Join": [ - "", - [ - "/accelerator/AWSAccelerator-PipelineStack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - "prefix": { - "Ref": "AcceleratorPrefix", - }, - "prefixParameterName": "/accelerator/lza-prefix", - }, - "Type": "Custom::GetPrefixes", - "UpdateReplacePolicy": "Delete", - }, - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1": { - "DependsOn": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }", - }, - "Description": "This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalAccount": { - "Ref": "AWS::AccountId", - }, - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/lza-prefix", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/AWSAccelerator-PipelineStack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - ], - "Sid": "SsmReadParameterAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "Roles": [ - { - "Ref": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecureBucket747CD8C0": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SecureBucketPolicy6374AC61": { - "Properties": { - "Bucket": { - "Ref": "SecureBucket747CD8C0", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "SolutionHelper4825923B": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DependsOn": [ - "SolutionHelperServiceRoleF70C0E2A", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - ", - }, - "Description": "This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "SolutionHelperServiceRoleF70C0E2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "SolutionHelperServiceRoleF70C0E2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SolutionHelperSolutionCreateUniqueID070ED802": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "Resource": "UUID", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - }, - "Type": "Custom::CreateUUID", - "UpdateReplacePolicy": "Delete", - }, - "SolutionHelperSolutionSendAnonymousData271B3D26": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "Region": { - "Ref": "AWS::Region", - }, - "RepositoryName": { - "Ref": "RepositoryName", - }, - "RepositoryOwner": { - "Ref": "RepositoryOwner", - }, - "RepositorySource": { - "Ref": "RepositorySource", - }, - "Resource": "AnonymousMetric", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - "SolutionId": "SO0199", - "UUID": { - "Fn::GetAtt": [ - "SolutionHelperSolutionCreateUniqueID070ED802", - "UUID", - ], - }, - }, - "Type": "Custom::AnonymousData", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/AWSAccelerator-Test-InstallerStack/version", - ], - ], - }, - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/AWSAccelerator-Test-InstallerStack/stack-id", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "UpdatePipelineGithubTokenFunction29B64F2E": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "UpdatePipelineLambdaRole88CE0535", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(\`Secret \${secretName} didn't exist.\`); - } - console.log(\`Retrieved secret: \${secretName}...\`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(\`Error retrieving secret: \${secretName}.\`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(\`Retrieving existing pipeline configuration for: \${pipelineName}...\`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(\`Updating pipeline with new OAuth Token...\`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(\`Error occurred while updating pipeline \${pipeline}\`); - } - } -} -", - }, - "Description": "Lambda function to update CodePipeline OAuth Token", - "Environment": { - "Variables": { - "ACCELERATOR_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline", - ], - ], - }, - "INSTALLER_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "UpdatePipelineLambdaRole88CE0535", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "UpdatePipelineGithubTokenFunctionLogGroupFCE3723A": { - "Condition": "UseGitHubCondition", - "DeletionPolicy": "Delete", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W84", - "reason": "CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data", - }, - ], - }, - }, - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "UpdatePipelineGithubTokenFunction29B64F2E", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "UpdatePipelineGithubTokenRule79D83132": { - "Condition": "UseGitHubCondition", - "Properties": { - "Description": "Rule to trigger Lambda Function when the Github Accelerator Token has been updated.", - "EventPattern": { - "detail": { - "eventName": [ - "UpdateSecret", - "PutSecretValue", - ], - "eventSource": [ - "secretsmanager.amazonaws.com", - ], - "requestParameters": { - "secretId": [ - { - "prefix": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token", - ], - ], - }, - }, - ], - }, - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "UpdatePipelineGithubTokenRuleAllowEventRuleAWSAcceleratorTestInstallerStackUpdatePipelineGithubTokenFunction78C173C279CEF0A3": { - "Condition": "UseGitHubCondition", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenRule79D83132", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "UpdatePipelineLambdaPolicy284ABC36": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipeline", - "codepipeline:UpdatePipeline", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline*", - ], - ], - }, - ], - }, - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-*", - ], - ], - }, - { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "UpdatePipelineLambdaPolicy284ABC36", - "Roles": [ - { - "Ref": "UpdatePipelineLambdaRole88CE0535", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "UpdatePipelineLambdaRole88CE0535": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "ValidateInstallerValidateResource24181D5D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunction21674768", - "Arn", - ], - }, - "existingConfigRepositoryBranchName": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - "existingConfigRepositoryName": { - "Ref": "ExistingConfigRepositoryName", - }, - "resourceType": "Custom::ValidateInstallerStack", - "useExistingConfigRepo": { - "Ref": "UseExistingConfigRepo", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateInstallerValidationFunction21674768": { - "DependsOn": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }", - }, - "Description": "This function validates installer parameters", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; - -exports[`InstallerStack Stack(installer): Snapshot Test 3`] = ` -{ - "Conditions": { - "IsCommercialCondition": { - "Fn::Equals": [ - { - "Ref": "AWS::Partition", - }, - "aws", - ], - }, - "SolutionHelperAnonymousDataToAWS62E4FDE2": { - "Fn::Equals": [ - { - "Fn::FindInMap": [ - "SolutionHelperAnonymousData14B64A81", - "SendAnonymizedData", - "Data", - ], - }, - "Yes", - ], - }, - "UseCodeCommitCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "codecommit", - ], - }, - "UseGitHubCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "github", - ], - }, - }, - "Mappings": { - "GlobalRegionMap": { - "aws": { - "regionName": "us-east-1", - }, - "aws-cn": { - "regionName": "cn-northwest-1", - }, - "aws-iso": { - "regionName": "us-iso-east-1", - }, - "aws-iso-b": { - "regionName": "us-isob-east-1", - }, - "aws-us-gov": { - "regionName": "us-gov-west-1", - }, - }, - "SolutionHelperAnonymousData14B64A81": { - "SendAnonymizedData": { - "Data": "Yes", - }, - }, - }, - "Metadata": { - "AWS::CloudFormation::Interface": { - "ParameterGroups": [ - { - "Label": { - "default": "Git Repository Configuration", - }, - "Parameters": [ - "RepositorySource", - "RepositoryOwner", - "RepositoryName", - "RepositoryBranchName", - ], - }, - { - "Label": { - "default": "Pipeline Configuration", - }, - "Parameters": [ - "EnableApprovalStage", - "ApprovalStageNotifyEmailList", - ], - }, - { - "Label": { - "default": "Mandatory Accounts Configuration", - }, - "Parameters": [ - "ManagementAccountEmail", - "LogArchiveAccountEmail", - "AuditAccountEmail", - ], - }, - { - "Label": { - "default": "Environment Configuration", - }, - "Parameters": [ - "ControlTowerEnabled", - "AcceleratorPrefix", - "UseExistingConfigRepo", - "ExistingConfigRepositoryName", - "ExistingConfigRepositoryBranchName", - "EnableDiagnosticsPack", - ], - }, - { - "Label": { - "default": "Target Environment Configuration", - }, - "Parameters": [ - "AcceleratorQualifier", - "ManagementAccountId", - "ManagementAccountRoleName", - ], - }, - ], - "ParameterLabels": { - "AcceleratorPrefix": { - "default": "Accelerator Resource name prefix", - }, - "AcceleratorQualifier": { - "default": "Accelerator Qualifier", - }, - "ApprovalStageNotifyEmailList": { - "default": "Manual Approval Stage notification email list", - }, - "AuditAccountEmail": { - "default": "Audit Account Email", - }, - "ControlTowerEnabled": { - "default": "Control Tower Environment", - }, - "EnableApprovalStage": { - "default": "Enable Approval Stage", - }, - "EnableDiagnosticsPack": { - "default": "Enable Diagnostics Pack", - }, - "ExistingConfigRepositoryBranchName": { - "default": "Existing Config Repository Branch Name", - }, - "ExistingConfigRepositoryName": { - "default": "Existing Config Repository Name", - }, - "LogArchiveAccountEmail": { - "default": "Log Archive Account Email", - }, - "ManagementAccountEmail": { - "default": "Management Account Email", - }, - "ManagementAccountId": { - "default": "Management Account ID", - }, - "ManagementAccountRoleName": { - "default": "Management Account Role Name", - }, - "RepositoryBranchName": { - "default": "Branch Name", - }, - "RepositoryName": { - "default": "Repository Name", - }, - "RepositoryOwner": { - "default": "Repository Owner", - }, - "RepositorySource": { - "default": "Source", - }, - "UseExistingConfigRepo": { - "default": "Use Existing Config Repository", - }, - }, - }, - }, - "Parameters": { - "AcceleratorPrefix": { - "AllowedPattern": "[A-Za-z0-9-]+", - "Default": "AWSAccelerator", - "Description": "The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.", - "MaxLength": 15, - "Type": "String", - }, - "AcceleratorQualifier": { - "AllowedPattern": "^[a-z]+[a-z0-9-]{1,61}[a-z0-9]+$", - "ConstraintDescription": "Qualifier must include lowercase letters and numbers only", - "Description": "Accelerator assets arn qualifier", - "Type": "String", - }, - "ApprovalStageNotifyEmailList": { - "Description": "Provide comma(,) separated list of email ids to receive manual approval stage notification email", - "Type": "CommaDelimitedList", - }, - "AuditAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The security audit account (also referred to as the audit account)", - "Type": "String", - }, - "ControlTowerEnabled": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes if you deploying to a Control Tower environment. Select no if using just Organizations", - "Type": "String", - }, - "EnableApprovalStage": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes to add a Manual Approval stage to accelerator pipeline", - "Type": "String", - }, - "EnableDiagnosticsPack": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.", - "Type": "String", - }, - "ExistingConfigRepositoryBranchName": { - "Default": "", - "Description": "Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.", - "Type": "String", - }, - "ExistingConfigRepositoryName": { - "Default": "", - "Description": "The name of an existing CodeCommit repository hosting the accelerator configuration.", - "Type": "String", - }, - "LogArchiveAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The log archive account email", - "Type": "String", - }, - "ManagementAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The management (primary) account email", - "Type": "String", - }, - "ManagementAccountId": { - "Description": "Target management account id", - "Type": "String", - }, - "ManagementAccountRoleName": { - "Description": "Target management account role name", - "Type": "String", - }, - "RepositoryBranchName": { - "AllowedPattern": ".+", - "ConstraintDescription": "The repository branch name must not be empty", - "Default": "release/v1.8.1", - "Description": "The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.", - "Type": "String", - }, - "RepositoryName": { - "Default": "landing-zone-accelerator-on-aws", - "Description": "The name of the git repository hosting the accelerator code", - "Type": "String", - }, - "RepositoryOwner": { - "Default": "awslabs", - "Description": "The owner of the repository containing the accelerator code. (GitHub Only)", - "Type": "String", - }, - "RepositorySource": { - "AllowedValues": [ - "github", - "codecommit", - ], - "Default": "github", - "Description": "Specify the git host", - "Type": "String", - }, - "UseExistingConfigRepo": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "No", - "Description": "Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.", - "Type": "String", - }, - }, - "Resources": { - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer/kms/key-arn", - ], - ], - }, - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CodeCommitPipeline2208527B": { - "Condition": "UseCodeCommitCondition", - "DependsOn": [ - "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "CodeCommitPipelineRole5C35E76C", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "PollForSourceChanges": false, - "RepositoryName": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "CodeCommitPipelineRole5C35E76C": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CodeCommitPipelineRoleDefaultPolicyDE8B332B": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "Roles": [ - { - "Ref": "CodeCommitPipelineRole5C35E76C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "RepositoryName", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D", - "Roles": [ - { - "Ref": "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipeline7B79E906": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "GitHubPipelineRoleDefaultPolicyD82457D6", - "GitHubPipelineRole6F4DEF1B", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "ThirdParty", - "Provider": "GitHub", - "Version": "1", - }, - "Configuration": { - "Branch": { - "Ref": "RepositoryBranchName", - }, - "OAuthToken": "{{resolve:secretsmanager:accelerator/github-token:SecretString:::}}", - "Owner": { - "Ref": "RepositoryOwner", - }, - "PollForSourceChanges": false, - "Repo": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "GitHubPipelineRole6F4DEF1B": { - "Condition": "UseGitHubCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipelineRoleDefaultPolicyD82457D6": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GitHubPipelineRoleDefaultPolicyD82457D6", - "Roles": [ - { - "Ref": "GitHubPipelineRole6F4DEF1B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerAccessLogsBucket647700E9": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W35", - "reason": "This is an access logging bucket.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Ref": "AcceleratorQualifier", - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "InstallerAccessLogsBucketName4F700F48": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer-access-logs-bucket-name", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InstallerAccessLogsBucketPolicy20D4E285": { - "Properties": { - "Bucket": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "InstallerAdminRole7DEE4AC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InstallerAdminRoleDefaultPolicy7EEE1AAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "InstallerProject879FF821", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "InstallerAdminRoleDefaultPolicy7EEE1AAB", - "Roles": [ - { - "Ref": "InstallerAdminRole7DEE4AC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerKey2A6A8C6D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "F76", - "reason": "KMS key using * principal with added arn condition", - }, - ], - }, - }, - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Ref": "AcceleratorQualifier", - }, - "-*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow SNS service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch Logs service to use the encryption key", - }, - { - "Fn::If": [ - "IsCommercialCondition", - { - "Action": [ - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "sns.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": "*", - "Sid": "KMS key access to codestar-notifications", - }, - { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "InstallerKeyAliasD5C174F0": { - "Properties": { - "AliasName": { - "Fn::Join": [ - "", - [ - "alias/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer/kms/key", - ], - ], - }, - "TargetKeyId": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "InstallerProject879FF821": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "EncryptionKey": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_REPOSITORY_SOURCE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositorySource", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_OWNER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryOwner", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryName", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryBranchName", - }, - }, - { - "Name": "USE_EXISTING_CONFIG_REPO", - "Type": "PLAINTEXT", - "Value": { - "Ref": "UseExistingConfigRepo", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryName", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_APPROVAL_STAGE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableApprovalStage", - }, - }, - { - "Name": "APPROVAL_STAGE_NOTIFY_EMAIL_LIST", - "Type": "PLAINTEXT", - "Value": { - "Fn::Join": [ - ",", - { - "Ref": "ApprovalStageNotifyEmailList", - }, - ], - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountEmail", - }, - }, - { - "Name": "LOG_ARCHIVE_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "LogArchiveAccountEmail", - }, - }, - { - "Name": "AUDIT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AuditAccountEmail", - }, - }, - { - "Name": "CONTROL_TOWER_ENABLED", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ControlTowerEnabled", - }, - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator-Test-InstallerStack", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableDiagnosticsPack", - }, - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::AccountId", - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountId", - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_ROLE_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountRoleName", - }, - }, - { - "Name": "ACCELERATOR_QUALIFIER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AcceleratorQualifier", - }, - }, - { - "Name": "ENABLE_TESTER", - "Type": "PLAINTEXT", - "Value": "true", - }, - { - "Name": "MANAGEMENT_CROSS_ACCOUNT_ROLE_NAME", - "Type": "PLAINTEXT", - "Value": "AWSControlTowerExecution", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-project", - ], - ], - }, - "ServiceRole": { - "Fn::GetAtt": [ - "InstallerAdminRole7DEE4AC8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - pre_build: - commands: - - ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no" - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then BOOTSTRAPPED_HOME="no"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then BOOTSTRAPPED_GLOBAL="no"; fi - - ENABLE_DIAGNOSTICS_PACK=", - { - "Ref": "EnableDiagnosticsPack", - }, - " - build: - commands: - - cd source - - |- - if [ "", - { - "Ref": "AWS::Partition", - }, - "" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi - - yarn install - - yarn build - - cd packages/@aws-accelerator/installer - - set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi - - set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi - - |- - set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi - - cd ../accelerator - - |- - aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi; - - |- - set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - fi - - set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - - set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - "; fi - post_build: - commands: - - |- - if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline"; - fi - fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "ResourceNamePrefixesGetPrefixResource96A10E6E": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1", - "Arn", - ], - }, - "pipelineStackVersionSsmParamName": { - "Fn::Join": [ - "", - [ - "/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline-stack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - "prefix": { - "Ref": "AcceleratorPrefix", - }, - "prefixParameterName": { - "Fn::Join": [ - "", - [ - "/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "/lza-prefix", - ], - ], - }, - }, - "Type": "Custom::GetPrefixes", - "UpdateReplacePolicy": "Delete", - }, - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1": { - "DependsOn": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }", - }, - "Description": "This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalAccount": { - "Ref": "AWS::AccountId", - }, - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "/lza-prefix", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline-stack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - ], - "Sid": "SsmReadParameterAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "Roles": [ - { - "Ref": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecureBucket747CD8C0": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SecureBucketPolicy6374AC61": { - "Properties": { - "Bucket": { - "Ref": "SecureBucket747CD8C0", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "SolutionHelper4825923B": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DependsOn": [ - "SolutionHelperServiceRoleF70C0E2A", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - ", - }, - "Description": "This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "SolutionHelperServiceRoleF70C0E2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "SolutionHelperServiceRoleF70C0E2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SolutionHelperSolutionCreateUniqueID070ED802": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "Resource": "UUID", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - }, - "Type": "Custom::CreateUUID", - "UpdateReplacePolicy": "Delete", - }, - "SolutionHelperSolutionSendAnonymousData271B3D26": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "Region": { - "Ref": "AWS::Region", - }, - "RepositoryName": { - "Ref": "RepositoryName", - }, - "RepositoryOwner": { - "Ref": "RepositoryOwner", - }, - "RepositorySource": { - "Ref": "RepositorySource", - }, - "Resource": "AnonymousMetric", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - "SolutionId": "SO0199", - "UUID": { - "Fn::GetAtt": [ - "SolutionHelperSolutionCreateUniqueID070ED802", - "UUID", - ], - }, - }, - "Type": "Custom::AnonymousData", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/AWSAccelerator-Test-InstallerStack/version", - ], - ], - }, - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/AWSAccelerator-Test-InstallerStack/stack-id", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "UpdatePipelineGithubTokenFunction29B64F2E": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "UpdatePipelineLambdaRole88CE0535", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(\`Secret \${secretName} didn't exist.\`); - } - console.log(\`Retrieved secret: \${secretName}...\`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(\`Error retrieving secret: \${secretName}.\`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(\`Retrieving existing pipeline configuration for: \${pipelineName}...\`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(\`Updating pipeline with new OAuth Token...\`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(\`Error occurred while updating pipeline \${pipeline}\`); - } - } -} -", - }, - "Description": "Lambda function to update CodePipeline OAuth Token", - "Environment": { - "Variables": { - "ACCELERATOR_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline", - ], - ], - }, - "INSTALLER_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "UpdatePipelineLambdaRole88CE0535", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "UpdatePipelineGithubTokenFunctionLogGroupFCE3723A": { - "Condition": "UseGitHubCondition", - "DeletionPolicy": "Delete", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W84", - "reason": "CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data", - }, - ], - }, - }, - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "UpdatePipelineGithubTokenFunction29B64F2E", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "UpdatePipelineGithubTokenRule79D83132": { - "Condition": "UseGitHubCondition", - "Properties": { - "Description": "Rule to trigger Lambda Function when the Github Accelerator Token has been updated.", - "EventPattern": { - "detail": { - "eventName": [ - "UpdateSecret", - "PutSecretValue", - ], - "eventSource": [ - "secretsmanager.amazonaws.com", - ], - "requestParameters": { - "secretId": [ - { - "prefix": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token", - ], - ], - }, - }, - ], - }, - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "UpdatePipelineGithubTokenRuleAllowEventRuleAWSAcceleratorTestInstallerStackUpdatePipelineGithubTokenFunction78C173C279CEF0A3": { - "Condition": "UseGitHubCondition", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenRule79D83132", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "UpdatePipelineLambdaPolicy284ABC36": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipeline", - "codepipeline:UpdatePipeline", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "AcceleratorQualifier", - }, - "-installer*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline*", - ], - ], - }, - ], - }, - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Ref": "AcceleratorQualifier", - }, - "-*", - ], - ], - }, - { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "UpdatePipelineLambdaPolicy284ABC36", - "Roles": [ - { - "Ref": "UpdatePipelineLambdaRole88CE0535", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "UpdatePipelineLambdaRole88CE0535": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "ValidateInstallerValidateResource24181D5D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunction21674768", - "Arn", - ], - }, - "existingConfigRepositoryBranchName": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - "existingConfigRepositoryName": { - "Ref": "ExistingConfigRepositoryName", - }, - "resourceType": "Custom::ValidateInstallerStack", - "useExistingConfigRepo": { - "Ref": "UseExistingConfigRepo", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateInstallerValidationFunction21674768": { - "DependsOn": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }", - }, - "Description": "This function validates installer parameters", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; - -exports[`InstallerStack Stack(installer): Snapshot Test 4`] = ` -{ - "Conditions": { - "IsCommercialCondition": { - "Fn::Equals": [ - { - "Ref": "AWS::Partition", - }, - "aws", - ], - }, - "SolutionHelperAnonymousDataToAWS62E4FDE2": { - "Fn::Equals": [ - { - "Fn::FindInMap": [ - "SolutionHelperAnonymousData14B64A81", - "SendAnonymizedData", - "Data", - ], - }, - "Yes", - ], - }, - "UseCodeCommitCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "codecommit", - ], - }, - "UseGitHubCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "github", - ], - }, - }, - "Mappings": { - "GlobalRegionMap": { - "aws": { - "regionName": "us-east-1", - }, - "aws-cn": { - "regionName": "cn-northwest-1", - }, - "aws-iso": { - "regionName": "us-iso-east-1", - }, - "aws-iso-b": { - "regionName": "us-isob-east-1", - }, - "aws-us-gov": { - "regionName": "us-gov-west-1", - }, - }, - "SolutionHelperAnonymousData14B64A81": { - "SendAnonymizedData": { - "Data": "Yes", - }, - }, - }, - "Metadata": { - "AWS::CloudFormation::Interface": { - "ParameterGroups": [ - { - "Label": { - "default": "Git Repository Configuration", - }, - "Parameters": [ - "RepositorySource", - "RepositoryOwner", - "RepositoryName", - "RepositoryBranchName", - ], - }, - { - "Label": { - "default": "Pipeline Configuration", - }, - "Parameters": [ - "EnableApprovalStage", - "ApprovalStageNotifyEmailList", - ], - }, - { - "Label": { - "default": "Mandatory Accounts Configuration", - }, - "Parameters": [ - "ManagementAccountEmail", - "LogArchiveAccountEmail", - "AuditAccountEmail", - ], - }, - { - "Label": { - "default": "Environment Configuration", - }, - "Parameters": [ - "ControlTowerEnabled", - "AcceleratorPrefix", - "UseExistingConfigRepo", - "ExistingConfigRepositoryName", - "ExistingConfigRepositoryBranchName", - "EnableDiagnosticsPack", - ], - }, - { - "Label": { - "default": "Target Environment Configuration", - }, - "Parameters": [ - "AcceleratorQualifier", - "ManagementAccountId", - "ManagementAccountRoleName", - ], - }, - ], - "ParameterLabels": { - "AcceleratorPrefix": { - "default": "Accelerator Resource name prefix", - }, - "AcceleratorQualifier": { - "default": "Accelerator Qualifier", - }, - "ApprovalStageNotifyEmailList": { - "default": "Manual Approval Stage notification email list", - }, - "AuditAccountEmail": { - "default": "Audit Account Email", - }, - "ControlTowerEnabled": { - "default": "Control Tower Environment", - }, - "EnableApprovalStage": { - "default": "Enable Approval Stage", - }, - "EnableDiagnosticsPack": { - "default": "Enable Diagnostics Pack", - }, - "ExistingConfigRepositoryBranchName": { - "default": "Existing Config Repository Branch Name", - }, - "ExistingConfigRepositoryName": { - "default": "Existing Config Repository Name", - }, - "LogArchiveAccountEmail": { - "default": "Log Archive Account Email", - }, - "ManagementAccountEmail": { - "default": "Management Account Email", - }, - "ManagementAccountId": { - "default": "Management Account ID", - }, - "ManagementAccountRoleName": { - "default": "Management Account Role Name", - }, - "RepositoryBranchName": { - "default": "Branch Name", - }, - "RepositoryName": { - "default": "Repository Name", - }, - "RepositoryOwner": { - "default": "Repository Owner", - }, - "RepositorySource": { - "default": "Source", - }, - "UseExistingConfigRepo": { - "default": "Use Existing Config Repository", - }, - }, - }, - }, - "Parameters": { - "AcceleratorPrefix": { - "AllowedPattern": "[A-Za-z0-9-]+", - "Default": "AWSAccelerator", - "Description": "The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.", - "MaxLength": 15, - "Type": "String", - }, - "AcceleratorQualifier": { - "AllowedPattern": "^[a-z]+[a-z0-9-]{1,61}[a-z0-9]+$", - "ConstraintDescription": "Qualifier must include lowercase letters and numbers only", - "Description": "Accelerator assets arn qualifier", - "Type": "String", - }, - "ApprovalStageNotifyEmailList": { - "Description": "Provide comma(,) separated list of email ids to receive manual approval stage notification email", - "Type": "CommaDelimitedList", - }, - "AuditAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The security audit account (also referred to as the audit account)", - "Type": "String", - }, - "ControlTowerEnabled": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes if you deploying to a Control Tower environment. Select no if using just Organizations", - "Type": "String", - }, - "EnableApprovalStage": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes to add a Manual Approval stage to accelerator pipeline", - "Type": "String", - }, - "EnableDiagnosticsPack": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.", - "Type": "String", - }, - "ExistingConfigRepositoryBranchName": { - "Default": "", - "Description": "Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.", - "Type": "String", - }, - "ExistingConfigRepositoryName": { - "Default": "", - "Description": "The name of an existing CodeCommit repository hosting the accelerator configuration.", - "Type": "String", - }, - "LogArchiveAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The log archive account email", - "Type": "String", - }, - "ManagementAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The management (primary) account email", - "Type": "String", - }, - "ManagementAccountId": { - "Description": "Target management account id", - "Type": "String", - }, - "ManagementAccountRoleName": { - "Description": "Target management account role name", - "Type": "String", - }, - "RepositoryBranchName": { - "AllowedPattern": ".+", - "ConstraintDescription": "The repository branch name must not be empty", - "Default": "release/v1.8.1", - "Description": "The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.", - "Type": "String", - }, - "RepositoryName": { - "Default": "landing-zone-accelerator-on-aws", - "Description": "The name of the git repository hosting the accelerator code", - "Type": "String", - }, - "RepositoryOwner": { - "Default": "awslabs", - "Description": "The owner of the repository containing the accelerator code. (GitHub Only)", - "Type": "String", - }, - "RepositorySource": { - "AllowedValues": [ - "github", - "codecommit", - ], - "Default": "github", - "Description": "Specify the git host", - "Type": "String", - }, - "UseExistingConfigRepo": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "No", - "Description": "Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.", - "Type": "String", - }, - }, - "Resources": { - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer/kms/key-arn", - ], - ], - }, - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CodeCommitPipeline2208527B": { - "Condition": "UseCodeCommitCondition", - "DependsOn": [ - "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "CodeCommitPipelineRole5C35E76C", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "PollForSourceChanges": false, - "RepositoryName": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "CodeCommitPipelineRole5C35E76C": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CodeCommitPipelineRoleDefaultPolicyDE8B332B": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "Roles": [ - { - "Ref": "CodeCommitPipelineRole5C35E76C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "RepositoryName", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D", - "Roles": [ - { - "Ref": "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipeline7B79E906": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "GitHubPipelineRoleDefaultPolicyD82457D6", - "GitHubPipelineRole6F4DEF1B", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "ThirdParty", - "Provider": "GitHub", - "Version": "1", - }, - "Configuration": { - "Branch": { - "Ref": "RepositoryBranchName", - }, - "OAuthToken": "{{resolve:secretsmanager:accelerator/github-token:SecretString:::}}", - "Owner": { - "Ref": "RepositoryOwner", - }, - "PollForSourceChanges": false, - "Repo": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "GitHubPipelineRole6F4DEF1B": { - "Condition": "UseGitHubCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipelineRoleDefaultPolicyD82457D6": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GitHubPipelineRoleDefaultPolicyD82457D6", - "Roles": [ - { - "Ref": "GitHubPipelineRole6F4DEF1B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerAccessLogsBucket647700E9": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W35", - "reason": "This is an access logging bucket.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Ref": "AcceleratorQualifier", - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "InstallerAccessLogsBucketName4F700F48": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer-access-logs-bucket-name", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InstallerAccessLogsBucketPolicy20D4E285": { - "Properties": { - "Bucket": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "InstallerAdminRole7DEE4AC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InstallerAdminRoleDefaultPolicy7EEE1AAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "InstallerProject879FF821", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "InstallerAdminRoleDefaultPolicy7EEE1AAB", - "Roles": [ - { - "Ref": "InstallerAdminRole7DEE4AC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerKey2A6A8C6D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "F76", - "reason": "KMS key using * principal with added arn condition", - }, - ], - }, - }, - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Ref": "AcceleratorQualifier", - }, - "-*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow SNS service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch Logs service to use the encryption key", - }, - { - "Fn::If": [ - "IsCommercialCondition", - { - "Action": [ - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "sns.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": "*", - "Sid": "KMS key access to codestar-notifications", - }, - { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "InstallerKeyAliasD5C174F0": { - "Properties": { - "AliasName": { - "Fn::Join": [ - "", - [ - "alias/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer/kms/key", - ], - ], - }, - "TargetKeyId": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "InstallerProject879FF821": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "EncryptionKey": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_REPOSITORY_SOURCE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositorySource", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_OWNER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryOwner", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryName", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryBranchName", - }, - }, - { - "Name": "USE_EXISTING_CONFIG_REPO", - "Type": "PLAINTEXT", - "Value": { - "Ref": "UseExistingConfigRepo", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryName", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_APPROVAL_STAGE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableApprovalStage", - }, - }, - { - "Name": "APPROVAL_STAGE_NOTIFY_EMAIL_LIST", - "Type": "PLAINTEXT", - "Value": { - "Fn::Join": [ - ",", - { - "Ref": "ApprovalStageNotifyEmailList", - }, - ], - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountEmail", - }, - }, - { - "Name": "LOG_ARCHIVE_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "LogArchiveAccountEmail", - }, - }, - { - "Name": "AUDIT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AuditAccountEmail", - }, - }, - { - "Name": "CONTROL_TOWER_ENABLED", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ControlTowerEnabled", - }, - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator-Test-InstallerStack", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableDiagnosticsPack", - }, - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::AccountId", - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountId", - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_ROLE_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountRoleName", - }, - }, - { - "Name": "ACCELERATOR_QUALIFIER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AcceleratorQualifier", - }, - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-project", - ], - ], - }, - "ServiceRole": { - "Fn::GetAtt": [ - "InstallerAdminRole7DEE4AC8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - pre_build: - commands: - - ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no" - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then BOOTSTRAPPED_HOME="no"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then BOOTSTRAPPED_GLOBAL="no"; fi - - ENABLE_DIAGNOSTICS_PACK=", - { - "Ref": "EnableDiagnosticsPack", - }, - " - build: - commands: - - cd source - - |- - if [ "", - { - "Ref": "AWS::Partition", - }, - "" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi - - yarn install - - yarn build - - cd packages/@aws-accelerator/installer - - set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi - - set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi - - |- - set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi - - cd ../accelerator - - |- - aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi; - - |- - set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - fi - - set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - - set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - "; fi - post_build: - commands: - - |- - if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline"; - fi - fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "ResourceNamePrefixesGetPrefixResource96A10E6E": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1", - "Arn", - ], - }, - "pipelineStackVersionSsmParamName": { - "Fn::Join": [ - "", - [ - "/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline-stack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - "prefix": { - "Ref": "AcceleratorPrefix", - }, - "prefixParameterName": { - "Fn::Join": [ - "", - [ - "/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "/lza-prefix", - ], - ], - }, - }, - "Type": "Custom::GetPrefixes", - "UpdateReplacePolicy": "Delete", - }, - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1": { - "DependsOn": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }", - }, - "Description": "This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalAccount": { - "Ref": "AWS::AccountId", - }, - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "/lza-prefix", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline-stack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - ], - "Sid": "SsmReadParameterAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "Roles": [ - { - "Ref": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecureBucket747CD8C0": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SecureBucketPolicy6374AC61": { - "Properties": { - "Bucket": { - "Ref": "SecureBucket747CD8C0", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "SolutionHelper4825923B": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DependsOn": [ - "SolutionHelperServiceRoleF70C0E2A", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - ", - }, - "Description": "This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "SolutionHelperServiceRoleF70C0E2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "SolutionHelperServiceRoleF70C0E2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SolutionHelperSolutionCreateUniqueID070ED802": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "Resource": "UUID", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - }, - "Type": "Custom::CreateUUID", - "UpdateReplacePolicy": "Delete", - }, - "SolutionHelperSolutionSendAnonymousData271B3D26": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "Region": { - "Ref": "AWS::Region", - }, - "RepositoryName": { - "Ref": "RepositoryName", - }, - "RepositoryOwner": { - "Ref": "RepositoryOwner", - }, - "RepositorySource": { - "Ref": "RepositorySource", - }, - "Resource": "AnonymousMetric", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - "SolutionId": "SO0199", - "UUID": { - "Fn::GetAtt": [ - "SolutionHelperSolutionCreateUniqueID070ED802", - "UUID", - ], - }, - }, - "Type": "Custom::AnonymousData", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/AWSAccelerator-Test-InstallerStack/version", - ], - ], - }, - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/AWSAccelerator-Test-InstallerStack/stack-id", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "UpdatePipelineGithubTokenFunction29B64F2E": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "UpdatePipelineLambdaRole88CE0535", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(\`Secret \${secretName} didn't exist.\`); - } - console.log(\`Retrieved secret: \${secretName}...\`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(\`Error retrieving secret: \${secretName}.\`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(\`Retrieving existing pipeline configuration for: \${pipelineName}...\`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(\`Updating pipeline with new OAuth Token...\`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(\`Error occurred while updating pipeline \${pipeline}\`); - } - } -} -", - }, - "Description": "Lambda function to update CodePipeline OAuth Token", - "Environment": { - "Variables": { - "ACCELERATOR_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline", - ], - ], - }, - "INSTALLER_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "UpdatePipelineLambdaRole88CE0535", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "UpdatePipelineGithubTokenFunctionLogGroupFCE3723A": { - "Condition": "UseGitHubCondition", - "DeletionPolicy": "Delete", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W84", - "reason": "CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data", - }, - ], - }, - }, - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "UpdatePipelineGithubTokenFunction29B64F2E", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "UpdatePipelineGithubTokenRule79D83132": { - "Condition": "UseGitHubCondition", - "Properties": { - "Description": "Rule to trigger Lambda Function when the Github Accelerator Token has been updated.", - "EventPattern": { - "detail": { - "eventName": [ - "UpdateSecret", - "PutSecretValue", - ], - "eventSource": [ - "secretsmanager.amazonaws.com", - ], - "requestParameters": { - "secretId": [ - { - "prefix": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token", - ], - ], - }, - }, - ], - }, - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "UpdatePipelineGithubTokenRuleAllowEventRuleAWSAcceleratorTestInstallerStackUpdatePipelineGithubTokenFunction78C173C279CEF0A3": { - "Condition": "UseGitHubCondition", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenRule79D83132", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "UpdatePipelineLambdaPolicy284ABC36": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipeline", - "codepipeline:UpdatePipeline", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "AcceleratorQualifier", - }, - "-installer*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline*", - ], - ], - }, - ], - }, - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Ref": "AcceleratorQualifier", - }, - "-*", - ], - ], - }, - { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "UpdatePipelineLambdaPolicy284ABC36", - "Roles": [ - { - "Ref": "UpdatePipelineLambdaRole88CE0535", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "UpdatePipelineLambdaRole88CE0535": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "ValidateInstallerValidateResource24181D5D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunction21674768", - "Arn", - ], - }, - "existingConfigRepositoryBranchName": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - "existingConfigRepositoryName": { - "Ref": "ExistingConfigRepositoryName", - }, - "resourceType": "Custom::ValidateInstallerStack", - "useExistingConfigRepo": { - "Ref": "UseExistingConfigRepo", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateInstallerValidationFunction21674768": { - "DependsOn": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }", - }, - "Description": "This function validates installer parameters", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; - -exports[`InstallerStack Stack(installer): Snapshot Test 5`] = ` -{ - "Conditions": { - "IsCommercialCondition": { - "Fn::Equals": [ - { - "Ref": "AWS::Partition", - }, - "aws", - ], - }, - "SolutionHelperAnonymousDataToAWS62E4FDE2": { - "Fn::Equals": [ - { - "Fn::FindInMap": [ - "SolutionHelperAnonymousData14B64A81", - "SendAnonymizedData", - "Data", - ], - }, - "Yes", - ], - }, - "UseCodeCommitCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "codecommit", - ], - }, - "UseGitHubCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "github", - ], - }, - }, - "Mappings": { - "GlobalRegionMap": { - "aws": { - "regionName": "us-east-1", - }, - "aws-cn": { - "regionName": "cn-northwest-1", - }, - "aws-iso": { - "regionName": "us-iso-east-1", - }, - "aws-iso-b": { - "regionName": "us-isob-east-1", - }, - "aws-us-gov": { - "regionName": "us-gov-west-1", - }, - }, - "SolutionHelperAnonymousData14B64A81": { - "SendAnonymizedData": { - "Data": "Yes", - }, - }, - }, - "Metadata": { - "AWS::CloudFormation::Interface": { - "ParameterGroups": [ - { - "Label": { - "default": "Git Repository Configuration", - }, - "Parameters": [ - "RepositorySource", - "RepositoryOwner", - "RepositoryName", - "RepositoryBranchName", - ], - }, - { - "Label": { - "default": "Pipeline Configuration", - }, - "Parameters": [ - "EnableApprovalStage", - "ApprovalStageNotifyEmailList", - ], - }, - { - "Label": { - "default": "Mandatory Accounts Configuration", - }, - "Parameters": [ - "ManagementAccountEmail", - "LogArchiveAccountEmail", - "AuditAccountEmail", - ], - }, - { - "Label": { - "default": "Environment Configuration", - }, - "Parameters": [ - "ControlTowerEnabled", - "AcceleratorPrefix", - "UseExistingConfigRepo", - "ExistingConfigRepositoryName", - "ExistingConfigRepositoryBranchName", - "EnableDiagnosticsPack", - ], - }, - { - "Label": { - "default": "Target Environment Configuration", - }, - "Parameters": [ - "AcceleratorQualifier", - "ManagementAccountId", - "ManagementAccountRoleName", - ], - }, - ], - "ParameterLabels": { - "AcceleratorPrefix": { - "default": "Accelerator Resource name prefix", - }, - "AcceleratorQualifier": { - "default": "Accelerator Qualifier", - }, - "ApprovalStageNotifyEmailList": { - "default": "Manual Approval Stage notification email list", - }, - "AuditAccountEmail": { - "default": "Audit Account Email", - }, - "ControlTowerEnabled": { - "default": "Control Tower Environment", - }, - "EnableApprovalStage": { - "default": "Enable Approval Stage", - }, - "EnableDiagnosticsPack": { - "default": "Enable Diagnostics Pack", - }, - "ExistingConfigRepositoryBranchName": { - "default": "Existing Config Repository Branch Name", - }, - "ExistingConfigRepositoryName": { - "default": "Existing Config Repository Name", - }, - "LogArchiveAccountEmail": { - "default": "Log Archive Account Email", - }, - "ManagementAccountEmail": { - "default": "Management Account Email", - }, - "ManagementAccountId": { - "default": "Management Account ID", - }, - "ManagementAccountRoleName": { - "default": "Management Account Role Name", - }, - "RepositoryBranchName": { - "default": "Branch Name", - }, - "RepositoryName": { - "default": "Repository Name", - }, - "RepositoryOwner": { - "default": "Repository Owner", - }, - "RepositorySource": { - "default": "Source", - }, - "UseExistingConfigRepo": { - "default": "Use Existing Config Repository", - }, - }, - }, - }, - "Parameters": { - "AcceleratorPrefix": { - "AllowedPattern": "[A-Za-z0-9-]+", - "Default": "AWSAccelerator", - "Description": "The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.", - "MaxLength": 15, - "Type": "String", - }, - "AcceleratorQualifier": { - "AllowedPattern": "^[a-z]+[a-z0-9-]{1,61}[a-z0-9]+$", - "ConstraintDescription": "Qualifier must include lowercase letters and numbers only", - "Description": "Accelerator assets arn qualifier", - "Type": "String", - }, - "ApprovalStageNotifyEmailList": { - "Description": "Provide comma(,) separated list of email ids to receive manual approval stage notification email", - "Type": "CommaDelimitedList", - }, - "AuditAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The security audit account (also referred to as the audit account)", - "Type": "String", - }, - "ControlTowerEnabled": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes if you deploying to a Control Tower environment. Select no if using just Organizations", - "Type": "String", - }, - "EnableApprovalStage": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes to add a Manual Approval stage to accelerator pipeline", - "Type": "String", - }, - "EnableDiagnosticsPack": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.", - "Type": "String", - }, - "ExistingConfigRepositoryBranchName": { - "Default": "", - "Description": "Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.", - "Type": "String", - }, - "ExistingConfigRepositoryName": { - "Default": "", - "Description": "The name of an existing CodeCommit repository hosting the accelerator configuration.", - "Type": "String", - }, - "LogArchiveAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The log archive account email", - "Type": "String", - }, - "ManagementAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The management (primary) account email", - "Type": "String", - }, - "ManagementAccountId": { - "Description": "Target management account id", - "Type": "String", - }, - "ManagementAccountRoleName": { - "Description": "Target management account role name", - "Type": "String", - }, - "RepositoryBranchName": { - "AllowedPattern": ".+", - "ConstraintDescription": "The repository branch name must not be empty", - "Default": "release/v1.8.1", - "Description": "The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.", - "Type": "String", - }, - "RepositoryName": { - "Default": "landing-zone-accelerator-on-aws", - "Description": "The name of the git repository hosting the accelerator code", - "Type": "String", - }, - "RepositoryOwner": { - "Default": "awslabs", - "Description": "The owner of the repository containing the accelerator code. (GitHub Only)", - "Type": "String", - }, - "RepositorySource": { - "AllowedValues": [ - "github", - "codecommit", - ], - "Default": "github", - "Description": "Specify the git host", - "Type": "String", - }, - "UseExistingConfigRepo": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "No", - "Description": "Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.", - "Type": "String", - }, - }, - "Resources": { - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer/kms/key-arn", - ], - ], - }, - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CodeCommitPipeline2208527B": { - "Condition": "UseCodeCommitCondition", - "DependsOn": [ - "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "CodeCommitPipelineRole5C35E76C", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "PollForSourceChanges": false, - "RepositoryName": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "CodeCommitPipelineRole5C35E76C": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CodeCommitPipelineRoleDefaultPolicyDE8B332B": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "Roles": [ - { - "Ref": "CodeCommitPipelineRole5C35E76C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "RepositoryName", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D", - "Roles": [ - { - "Ref": "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipeline7B79E906": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "GitHubPipelineRoleDefaultPolicyD82457D6", - "GitHubPipelineRole6F4DEF1B", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "ThirdParty", - "Provider": "GitHub", - "Version": "1", - }, - "Configuration": { - "Branch": { - "Ref": "RepositoryBranchName", - }, - "OAuthToken": "{{resolve:secretsmanager:accelerator/github-token:SecretString:::}}", - "Owner": { - "Ref": "RepositoryOwner", - }, - "PollForSourceChanges": false, - "Repo": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "GitHubPipelineRole6F4DEF1B": { - "Condition": "UseGitHubCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipelineRoleDefaultPolicyD82457D6": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GitHubPipelineRoleDefaultPolicyD82457D6", - "Roles": [ - { - "Ref": "GitHubPipelineRole6F4DEF1B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerAccessLogsBucket647700E9": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W35", - "reason": "This is an access logging bucket.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Ref": "AcceleratorQualifier", - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "InstallerAccessLogsBucketName4F700F48": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer-access-logs-bucket-name", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InstallerAccessLogsBucketPolicy20D4E285": { - "Properties": { - "Bucket": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "InstallerAdminRole7DEE4AC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InstallerAdminRoleDefaultPolicy7EEE1AAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "InstallerProject879FF821", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "InstallerAdminRoleDefaultPolicy7EEE1AAB", - "Roles": [ - { - "Ref": "InstallerAdminRole7DEE4AC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerKey2A6A8C6D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "F76", - "reason": "KMS key using * principal with added arn condition", - }, - ], - }, - }, - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Ref": "AcceleratorQualifier", - }, - "-*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow SNS service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch Logs service to use the encryption key", - }, - { - "Fn::If": [ - "IsCommercialCondition", - { - "Action": [ - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "sns.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": "*", - "Sid": "KMS key access to codestar-notifications", - }, - { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "InstallerKeyAliasD5C174F0": { - "Properties": { - "AliasName": { - "Fn::Join": [ - "", - [ - "alias/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/installer/kms/key", - ], - ], - }, - "TargetKeyId": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "InstallerProject879FF821": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "EncryptionKey": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_REPOSITORY_SOURCE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositorySource", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_OWNER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryOwner", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryName", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryBranchName", - }, - }, - { - "Name": "USE_EXISTING_CONFIG_REPO", - "Type": "PLAINTEXT", - "Value": { - "Ref": "UseExistingConfigRepo", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryName", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_APPROVAL_STAGE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableApprovalStage", - }, - }, - { - "Name": "APPROVAL_STAGE_NOTIFY_EMAIL_LIST", - "Type": "PLAINTEXT", - "Value": { - "Fn::Join": [ - ",", - { - "Ref": "ApprovalStageNotifyEmailList", - }, - ], - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountEmail", - }, - }, - { - "Name": "LOG_ARCHIVE_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "LogArchiveAccountEmail", - }, - }, - { - "Name": "AUDIT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AuditAccountEmail", - }, - }, - { - "Name": "CONTROL_TOWER_ENABLED", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ControlTowerEnabled", - }, - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator-Test-InstallerStack", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableDiagnosticsPack", - }, - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::AccountId", - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountId", - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_ROLE_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountRoleName", - }, - }, - { - "Name": "ACCELERATOR_QUALIFIER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AcceleratorQualifier", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_SINGLE_ACCOUNT_MODE", - "Type": "PLAINTEXT", - "Value": "true", - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-project", - ], - ], - }, - "ServiceRole": { - "Fn::GetAtt": [ - "InstallerAdminRole7DEE4AC8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - pre_build: - commands: - - ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no" - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then BOOTSTRAPPED_HOME="no"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then BOOTSTRAPPED_GLOBAL="no"; fi - - ENABLE_DIAGNOSTICS_PACK=", - { - "Ref": "EnableDiagnosticsPack", - }, - " - build: - commands: - - cd source - - |- - if [ "", - { - "Ref": "AWS::Partition", - }, - "" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi - - yarn install - - yarn build - - cd packages/@aws-accelerator/installer - - set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi - - set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi - - |- - set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi - - cd ../accelerator - - |- - aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi; - - |- - set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - fi - - set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - - set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - "; fi - post_build: - commands: - - |- - if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline"; - fi - fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "ResourceNamePrefixesGetPrefixResource96A10E6E": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1", - "Arn", - ], - }, - "pipelineStackVersionSsmParamName": { - "Fn::Join": [ - "", - [ - "/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline-stack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - "prefix": { - "Ref": "AcceleratorPrefix", - }, - "prefixParameterName": { - "Fn::Join": [ - "", - [ - "/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "/lza-prefix", - ], - ], - }, - }, - "Type": "Custom::GetPrefixes", - "UpdateReplacePolicy": "Delete", - }, - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1": { - "DependsOn": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }", - }, - "Description": "This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalAccount": { - "Ref": "AWS::AccountId", - }, - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "/lza-prefix", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline-stack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - ], - "Sid": "SsmReadParameterAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "Roles": [ - { - "Ref": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecureBucket747CD8C0": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SecureBucketPolicy6374AC61": { - "Properties": { - "Bucket": { - "Ref": "SecureBucket747CD8C0", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "SolutionHelper4825923B": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DependsOn": [ - "SolutionHelperServiceRoleF70C0E2A", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - ", - }, - "Description": "This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "SolutionHelperServiceRoleF70C0E2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "SolutionHelperServiceRoleF70C0E2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SolutionHelperSolutionCreateUniqueID070ED802": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "Resource": "UUID", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - }, - "Type": "Custom::CreateUUID", - "UpdateReplacePolicy": "Delete", - }, - "SolutionHelperSolutionSendAnonymousData271B3D26": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "Region": { - "Ref": "AWS::Region", - }, - "RepositoryName": { - "Ref": "RepositoryName", - }, - "RepositoryOwner": { - "Ref": "RepositoryOwner", - }, - "RepositorySource": { - "Ref": "RepositorySource", - }, - "Resource": "AnonymousMetric", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - "SolutionId": "SO0199", - "UUID": { - "Fn::GetAtt": [ - "SolutionHelperSolutionCreateUniqueID070ED802", - "UUID", - ], - }, - }, - "Type": "Custom::AnonymousData", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/AWSAccelerator-Test-InstallerStack/version", - ], - ], - }, - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/", - { - "Ref": "AcceleratorQualifier", - }, - "/AWSAccelerator-Test-InstallerStack/stack-id", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "UpdatePipelineGithubTokenFunction29B64F2E": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "UpdatePipelineLambdaRole88CE0535", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(\`Secret \${secretName} didn't exist.\`); - } - console.log(\`Retrieved secret: \${secretName}...\`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(\`Error retrieving secret: \${secretName}.\`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(\`Retrieving existing pipeline configuration for: \${pipelineName}...\`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(\`Updating pipeline with new OAuth Token...\`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(\`Error occurred while updating pipeline \${pipeline}\`); - } - } -} -", - }, - "Description": "Lambda function to update CodePipeline OAuth Token", - "Environment": { - "Variables": { - "ACCELERATOR_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline", - ], - ], - }, - "INSTALLER_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Ref": "AcceleratorQualifier", - }, - "-installer", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "UpdatePipelineLambdaRole88CE0535", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "UpdatePipelineGithubTokenFunctionLogGroupFCE3723A": { - "Condition": "UseGitHubCondition", - "DeletionPolicy": "Delete", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W84", - "reason": "CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data", - }, - ], - }, - }, - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "UpdatePipelineGithubTokenFunction29B64F2E", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "UpdatePipelineGithubTokenRule79D83132": { - "Condition": "UseGitHubCondition", - "Properties": { - "Description": "Rule to trigger Lambda Function when the Github Accelerator Token has been updated.", - "EventPattern": { - "detail": { - "eventName": [ - "UpdateSecret", - "PutSecretValue", - ], - "eventSource": [ - "secretsmanager.amazonaws.com", - ], - "requestParameters": { - "secretId": [ - { - "prefix": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token", - ], - ], - }, - }, - ], - }, - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "UpdatePipelineGithubTokenRuleAllowEventRuleAWSAcceleratorTestInstallerStackUpdatePipelineGithubTokenFunction78C173C279CEF0A3": { - "Condition": "UseGitHubCondition", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenRule79D83132", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "UpdatePipelineLambdaPolicy284ABC36": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipeline", - "codepipeline:UpdatePipeline", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "AcceleratorQualifier", - }, - "-installer*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "AcceleratorQualifier", - }, - "-pipeline*", - ], - ], - }, - ], - }, - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Ref": "AcceleratorQualifier", - }, - "-*", - ], - ], - }, - { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "UpdatePipelineLambdaPolicy284ABC36", - "Roles": [ - { - "Ref": "UpdatePipelineLambdaRole88CE0535", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "UpdatePipelineLambdaRole88CE0535": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "ValidateInstallerValidateResource24181D5D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunction21674768", - "Arn", - ], - }, - "existingConfigRepositoryBranchName": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - "existingConfigRepositoryName": { - "Ref": "ExistingConfigRepositoryName", - }, - "resourceType": "Custom::ValidateInstallerStack", - "useExistingConfigRepo": { - "Ref": "UseExistingConfigRepo", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateInstallerValidationFunction21674768": { - "DependsOn": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }", - }, - "Description": "This function validates installer parameters", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; - -exports[`InstallerStack Stack(installer): Snapshot Test 6`] = ` -{ - "Conditions": { - "IsCommercialCondition": { - "Fn::Equals": [ - { - "Ref": "AWS::Partition", - }, - "aws", - ], - }, - "SolutionHelperAnonymousDataToAWS62E4FDE2": { - "Fn::Equals": [ - { - "Fn::FindInMap": [ - "SolutionHelperAnonymousData14B64A81", - "SendAnonymizedData", - "Data", - ], - }, - "Yes", - ], - }, - "UseCodeCommitCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "codecommit", - ], - }, - "UseGitHubCondition": { - "Fn::Equals": [ - { - "Ref": "RepositorySource", - }, - "github", - ], - }, - }, - "Mappings": { - "GlobalRegionMap": { - "aws": { - "regionName": "us-east-1", - }, - "aws-cn": { - "regionName": "cn-northwest-1", - }, - "aws-iso": { - "regionName": "us-iso-east-1", - }, - "aws-iso-b": { - "regionName": "us-isob-east-1", - }, - "aws-us-gov": { - "regionName": "us-gov-west-1", - }, - }, - "SolutionHelperAnonymousData14B64A81": { - "SendAnonymizedData": { - "Data": "Yes", - }, - }, - }, - "Metadata": { - "AWS::CloudFormation::Interface": { - "ParameterGroups": [ - { - "Label": { - "default": "Git Repository Configuration", - }, - "Parameters": [ - "RepositorySource", - "RepositoryOwner", - "RepositoryName", - "RepositoryBranchName", - ], - }, - { - "Label": { - "default": "Pipeline Configuration", - }, - "Parameters": [ - "EnableApprovalStage", - "ApprovalStageNotifyEmailList", - ], - }, - { - "Label": { - "default": "Mandatory Accounts Configuration", - }, - "Parameters": [ - "ManagementAccountEmail", - "LogArchiveAccountEmail", - "AuditAccountEmail", - ], - }, - { - "Label": { - "default": "Environment Configuration", - }, - "Parameters": [ - "ControlTowerEnabled", - "AcceleratorPrefix", - "UseExistingConfigRepo", - "ExistingConfigRepositoryName", - "ExistingConfigRepositoryBranchName", - "EnableDiagnosticsPack", - ], - }, - { - "Label": { - "default": "Permission Boundary Configuration", - }, - "Parameters": [ - "PermissionBoundaryPolicyName", - ], - }, - ], - "ParameterLabels": { - "AcceleratorPrefix": { - "default": "Accelerator Resource name prefix", - }, - "ApprovalStageNotifyEmailList": { - "default": "Manual Approval Stage notification email list", - }, - "AuditAccountEmail": { - "default": "Audit Account Email", - }, - "ControlTowerEnabled": { - "default": "Control Tower Environment", - }, - "EnableApprovalStage": { - "default": "Enable Approval Stage", - }, - "EnableDiagnosticsPack": { - "default": "Enable Diagnostics Pack", - }, - "ExistingConfigRepositoryBranchName": { - "default": "Existing Config Repository Branch Name", - }, - "ExistingConfigRepositoryName": { - "default": "Existing Config Repository Name", - }, - "LogArchiveAccountEmail": { - "default": "Log Archive Account Email", - }, - "ManagementAccountEmail": { - "default": "Management Account Email", - }, - "RepositoryBranchName": { - "default": "Branch Name", - }, - "RepositoryName": { - "default": "Repository Name", - }, - "RepositoryOwner": { - "default": "Repository Owner", - }, - "RepositorySource": { - "default": "Source", - }, - "UseExistingConfigRepo": { - "default": "Use Existing Config Repository", - }, - }, - }, - }, - "Parameters": { - "AcceleratorPrefix": { - "AllowedPattern": "[A-Za-z0-9-]+", - "Default": "AWSAccelerator", - "Description": "The prefix value for accelerator deployed resources. Leave the default value if using solution defined resource name prefix, the solution will use AWSAccelerator as resource name prefix. Note: Updating this value after initial installation will cause stack failure. Non-default value can not start with keyword "aws" or "ssm". Trailing dash (-) in non-default value will be ignored.", - "MaxLength": 15, - "Type": "String", - }, - "ApprovalStageNotifyEmailList": { - "Description": "Provide comma(,) separated list of email ids to receive manual approval stage notification email", - "Type": "CommaDelimitedList", - }, - "AuditAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The security audit account (also referred to as the audit account)", - "Type": "String", - }, - "ControlTowerEnabled": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes if you deploying to a Control Tower environment. Select no if using just Organizations", - "Type": "String", - }, - "EnableApprovalStage": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select yes to add a Manual Approval stage to accelerator pipeline", - "Type": "String", - }, - "EnableDiagnosticsPack": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "Yes", - "Description": "Select Yes if deploying the solution with diagnostics pack enabled. Diagnostics pack enables you to generate root cause reports to potentially diagnose pipeline failures.", - "Type": "String", - }, - "ExistingConfigRepositoryBranchName": { - "Default": "", - "Description": "Specify the branch name of existing CodeCommit repository to pull the accelerator configuration from.", - "Type": "String", - }, - "ExistingConfigRepositoryName": { - "Default": "", - "Description": "The name of an existing CodeCommit repository hosting the accelerator configuration.", - "Type": "String", - }, - "LogArchiveAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The log archive account email", - "Type": "String", - }, - "ManagementAccountEmail": { - "AllowedPattern": "[^\\s@]+@[^\\s@]+\\.[^\\s@]+", - "ConstraintDescription": "Must be a valid email address matching "[^\\s@]+@[^\\s@]+\\.[^\\s@]+"", - "Description": "The management (primary) account email", - "Type": "String", - }, - "PermissionBoundaryPolicyName": { - "Description": "Permission boundary Policy Name which is valid only for management account", - "Type": "String", - }, - "RepositoryBranchName": { - "AllowedPattern": ".+", - "ConstraintDescription": "The repository branch name must not be empty", - "Default": "release/v1.8.1", - "Description": "The name of the git branch to use for installation. To determine the branch name, navigate to the Landing Zone Accelerator GitHub branches page and choose the release branch you would like to deploy. Release branch names will align with the semantic versioning of our GitHub releases. New release branches will be available as the open source project is updated with new features.", - "Type": "String", - }, - "RepositoryName": { - "Default": "landing-zone-accelerator-on-aws", - "Description": "The name of the git repository hosting the accelerator code", - "Type": "String", - }, - "RepositoryOwner": { - "Default": "awslabs", - "Description": "The owner of the repository containing the accelerator code. (GitHub Only)", - "Type": "String", - }, - "RepositorySource": { - "AllowedValues": [ - "github", - "codecommit", - ], - "Default": "github", - "Description": "Specify the git host", - "Type": "String", - }, - "UseExistingConfigRepo": { - "AllowedValues": [ - "Yes", - "No", - ], - "Default": "No", - "Description": "Select Yes if deploying the solution with an existing CodeCommit configuration repository. Leave the default value if using the solution-deployed repository. If the AcceleratorPrefix parameter is set to the default value, the solution will deploy a repository named "aws-accelerator-config." Otherwise, the solution-deployed repository will be named "AcceleratorPrefix-config." Note: Updating this value after initial installation may cause adverse affects.", - "Type": "String", - }, - }, - "Resources": { - "AcceleratorManagementKmsArnParameter1E6975BF": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer/kms/key-arn", - ], - ], - }, - "Type": "String", - "Value": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "CodeCommitPipeline2208527B": { - "Condition": "UseCodeCommitCondition", - "DependsOn": [ - "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "CodeCommitPipelineRole5C35E76C", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "AWS", - "Provider": "CodeCommit", - "Version": "1", - }, - "Configuration": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "PollForSourceChanges": false, - "RepositoryName": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "CodeCommitPipelineRole5C35E76C": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "CodeCommitPipelineRoleDefaultPolicyDE8B332B": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "CodeCommitPipelineRole5C35E76C", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineRoleDefaultPolicyDE8B332B", - "Roles": [ - { - "Ref": "CodeCommitPipelineRole5C35E76C", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D": { - "Condition": "UseCodeCommitCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "codecommit:GetBranch", - "codecommit:GetCommit", - "codecommit:UploadArchive", - "codecommit:GetUploadArchiveStatus", - "codecommit:CancelUploadArchive", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codecommit:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Ref": "RepositoryName", - }, - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "CodeCommitPipelineSourceCodePipelineActionRoleDefaultPolicyF71E0C0D", - "Roles": [ - { - "Ref": "CodeCommitPipelineSourceCodePipelineActionRoleFB176191", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "CodeCommitPipelineSourceCodePipelineActionRoleFB176191": { - "Condition": "UseCodeCommitCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipeline7B79E906": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "GitHubPipelineRoleDefaultPolicyD82457D6", - "GitHubPipelineRole6F4DEF1B", - ], - "Properties": { - "ArtifactStore": { - "EncryptionKey": { - "Id": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Type": "KMS", - }, - "Location": { - "Ref": "SecureBucket747CD8C0", - }, - "Type": "S3", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - "RestartExecutionOnUpdate": true, - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "Stages": [ - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Source", - "Owner": "ThirdParty", - "Provider": "GitHub", - "Version": "1", - }, - "Configuration": { - "Branch": { - "Ref": "RepositoryBranchName", - }, - "OAuthToken": "{{resolve:secretsmanager:accelerator/github-token:SecretString:::}}", - "Owner": { - "Ref": "RepositoryOwner", - }, - "PollForSourceChanges": false, - "Repo": { - "Ref": "RepositoryName", - }, - }, - "Name": "Source", - "OutputArtifacts": [ - { - "Name": "Source", - }, - ], - "RunOrder": 1, - }, - ], - "Name": "Source", - }, - { - "Actions": [ - { - "ActionTypeId": { - "Category": "Build", - "Owner": "AWS", - "Provider": "CodeBuild", - "Version": "1", - }, - "Configuration": { - "ProjectName": { - "Ref": "InstallerProject879FF821", - }, - }, - "InputArtifacts": [ - { - "Name": "Source", - }, - ], - "Name": "Install", - "RoleArn": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - "RunOrder": 1, - }, - ], - "Name": "Install", - }, - ], - }, - "Type": "AWS::CodePipeline::Pipeline", - }, - "GitHubPipelineRole6F4DEF1B": { - "Condition": "UseGitHubCondition", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codepipeline.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "GitHubPipelineRoleDefaultPolicyD82457D6": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - "s3:DeleteObject*", - "s3:PutObject", - "s3:PutObjectLegalHold", - "s3:PutObjectRetention", - "s3:PutObjectTagging", - "s3:PutObjectVersionTagging", - "s3:Abort*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - }, - { - "Action": [ - "codebuild:BatchGetBuilds", - "codebuild:StartBuild", - "codebuild:StopBuild", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerProject879FF821", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "GitHubPipelineRoleDefaultPolicyD82457D6", - "Roles": [ - { - "Ref": "GitHubPipelineRole6F4DEF1B", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerAccessLogsBucket647700E9": { - "DeletionPolicy": "Retain", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-S1", - "reason": "AccessLogsBucket has server access logs disabled till the task for access logging completed.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W35", - "reason": "This is an access logging bucket.", - }, - ], - }, - }, - "Properties": { - "AccessControl": "LogDeliveryWrite", - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-s3-logs-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "InstallerAccessLogsBucketName4F700F48": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer-access-logs-bucket-name", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "InstallerAccessLogsBucketPolicy20D4E285": { - "Properties": { - "Bucket": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "InstallerAccessLogsBucket647700E9", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "InstallerAdminRole7DEE4AC8": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "codebuild.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/AdministratorAccess", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "InstallerAdminRoleDefaultPolicy7EEE1AAB": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:/aws/codebuild/", - { - "Ref": "InstallerProject879FF821", - }, - ":*", - ], - ], - }, - ], - }, - { - "Action": [ - "codebuild:CreateReportGroup", - "codebuild:CreateReport", - "codebuild:UpdateReport", - "codebuild:BatchPutTestCases", - "codebuild:BatchPutCodeCoverages", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codebuild:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":report-group/", - { - "Ref": "InstallerProject879FF821", - }, - "-*", - ], - ], - }, - }, - { - "Action": [ - "kms:Decrypt", - "kms:Encrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "InstallerAdminRoleDefaultPolicy7EEE1AAB", - "Roles": [ - { - "Ref": "InstallerAdminRole7DEE4AC8", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "InstallerKey2A6A8C6D": { - "DeletionPolicy": "Retain", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "F76", - "reason": "KMS key using * principal with added arn condition", - }, - ], - }, - }, - "Properties": { - "Description": "AWS Accelerator Management Account Kms Key", - "EnableKeyRotation": true, - "KeyPolicy": { - "Statement": [ - { - "Action": "kms:*", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":root", - ], - ], - }, - }, - "Resource": "*", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "aws:PrincipalARN": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "AWS": "*", - }, - "Resource": "*", - "Sid": "Allow Accelerator Role to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com", - }, - "Resource": "*", - "Sid": "Allow SNS service to use the encryption key", - }, - { - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:DescribeKey", - ], - "Condition": { - "ArnLike": { - "kms:EncryptionContext:aws:logs:arn": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":logs:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":log-group:*", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "logs.", - { - "Ref": "AWS::Region", - }, - ".", - { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - "Resource": "*", - "Sid": "Allow Cloudwatch Logs service to use the encryption key", - }, - { - "Fn::If": [ - "IsCommercialCondition", - { - "Action": [ - "kms:GenerateDataKey*", - "kms:Decrypt", - ], - "Condition": { - "StringEquals": { - "kms:ViaService": { - "Fn::Join": [ - "", - [ - "sns.", - { - "Ref": "AWS::Region", - }, - ".amazonaws.com", - ], - ], - }, - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "codestar-notifications.amazonaws.com", - }, - "Resource": "*", - "Sid": "KMS key access to codestar-notifications", - }, - { - "Ref": "AWS::NoValue", - }, - ], - }, - ], - }, - }, - "Type": "AWS::KMS::Key", - "UpdateReplacePolicy": "Retain", - }, - "InstallerKeyAliasD5C174F0": { - "Properties": { - "AliasName": { - "Fn::Join": [ - "", - [ - "alias/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/installer/kms/key", - ], - ], - }, - "TargetKeyId": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - }, - "Type": "AWS::KMS::Alias", - }, - "InstallerProject879FF821": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-CB3", - "reason": "Project requires access to the Docker daemon.", - }, - ], - }, - }, - "Properties": { - "Artifacts": { - "Type": "CODEPIPELINE", - }, - "Cache": { - "Type": "NO_CACHE", - }, - "EncryptionKey": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "EnvironmentVariables": [ - { - "Name": "NODE_OPTIONS", - "Type": "PLAINTEXT", - "Value": "--max_old_space_size=4096", - }, - { - "Name": "CDK_NEW_BOOTSTRAP", - "Type": "PLAINTEXT", - "Value": "1", - }, - { - "Name": "ACCELERATOR_REPOSITORY_SOURCE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositorySource", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_OWNER", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryOwner", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryName", - }, - }, - { - "Name": "ACCELERATOR_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "RepositoryBranchName", - }, - }, - { - "Name": "USE_EXISTING_CONFIG_REPO", - "Type": "PLAINTEXT", - "Value": { - "Ref": "UseExistingConfigRepo", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryName", - }, - }, - { - "Name": "EXISTING_CONFIG_REPOSITORY_BRANCH_NAME", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - }, - { - "Name": "ACCELERATOR_ENABLE_APPROVAL_STAGE", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableApprovalStage", - }, - }, - { - "Name": "APPROVAL_STAGE_NOTIFY_EMAIL_LIST", - "Type": "PLAINTEXT", - "Value": { - "Fn::Join": [ - ",", - { - "Ref": "ApprovalStageNotifyEmailList", - }, - ], - }, - }, - { - "Name": "MANAGEMENT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ManagementAccountEmail", - }, - }, - { - "Name": "LOG_ARCHIVE_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "LogArchiveAccountEmail", - }, - }, - { - "Name": "AUDIT_ACCOUNT_EMAIL", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AuditAccountEmail", - }, - }, - { - "Name": "CONTROL_TOWER_ENABLED", - "Type": "PLAINTEXT", - "Value": { - "Ref": "ControlTowerEnabled", - }, - }, - { - "Name": "ACCELERATOR_PREFIX", - "Type": "PLAINTEXT", - "Value": { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - }, - { - "Name": "INSTALLER_STACK_NAME", - "Type": "PLAINTEXT", - "Value": "AWSAccelerator-Test-InstallerStack", - }, - { - "Name": "ENABLE_DIAGNOSTICS_PACK", - "Type": "PLAINTEXT", - "Value": { - "Ref": "EnableDiagnosticsPack", - }, - }, - { - "Name": "PIPELINE_ACCOUNT_ID", - "Type": "PLAINTEXT", - "Value": { - "Ref": "AWS::AccountId", - }, - }, - { - "Name": "ACCELERATOR_PERMISSION_BOUNDARY", - "Type": "PLAINTEXT", - "Value": { - "Ref": "PermissionBoundaryPolicyName", - }, - }, - ], - "Image": "aws/codebuild/standard:7.0", - "ImagePullCredentialsType": "CODEBUILD", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER", - }, - "Name": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-InstallerProject", - ], - ], - }, - "ServiceRole": { - "Fn::GetAtt": [ - "InstallerAdminRole7DEE4AC8", - "Arn", - ], - }, - "Source": { - "BuildSpec": { - "Fn::Join": [ - "", - [ - "version: "0.2" -phases: - install: - runtime-versions: - nodejs: 18 - pre_build: - commands: - - ENABLE_EXTERNAL_PIPELINE_ACCOUNT="no" - - if [ ! -z "$MANAGEMENT_ACCOUNT_ID" ] && [ ! -z "$MANAGEMENT_ACCOUNT_ROLE_NAME" ]; then ENABLE_EXTERNAL_PIPELINE_ACCOUNT="yes"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then BOOTSTRAPPED_HOME="no"; fi - - set -e && if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then BOOTSTRAPPED_GLOBAL="no"; fi - - ENABLE_DIAGNOSTICS_PACK=", - { - "Ref": "EnableDiagnosticsPack", - }, - " - build: - commands: - - cd source - - |- - if [ "", - { - "Ref": "AWS::Partition", - }, - "" = "aws-cn" ]; then - sed -i "s#registry.yarnpkg.com#registry.npmmirror.com#g" yarn.lock; - set -e && yarn config set registry https://registry.npmmirror.com - fi - - yarn install - - yarn build - - cd packages/@aws-accelerator/installer - - set -e && if [ "$BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi - - set -e && if [ "$BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://", - { - "Ref": "AWS::AccountId", - }, - "/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi - - |- - set -e && if [ $ENABLE_EXTERNAL_PIPELINE_ACCOUNT = "yes" ]; then - export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $(aws sts assume-role --role-arn arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::"$MANAGEMENT_ACCOUNT_ID":role/"$MANAGEMENT_ACCOUNT_ROLE_NAME" --role-session-name acceleratorAssumeRoleSession --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text)); - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Ref": "AWS::Region", - }, - "; then MGMT_BOOTSTRAPPED_HOME="no"; fi; - if ! aws cloudformation describe-stacks --stack-name ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit --region ", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - "; then MGMT_BOOTSTRAPPED_GLOBAL="no"; fi; - if [ "$MGMT_BOOTSTRAPPED_HOME" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Ref": "AWS::Region", - }, - " --qualifier accel; fi; - if [ "$MGMT_BOOTSTRAPPED_GLOBAL" = "no" ]; then yarn run cdk bootstrap --toolkitStackName ", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-CDKToolkit aws://$MANAGEMENT_ACCOUNT_ID/", - { - "Fn::FindInMap": [ - "GlobalRegionMap", - { - "Ref": "AWS::Partition", - }, - "regionName", - ], - }, - " --qualifier accel; fi; - unset AWS_ACCESS_KEY_ID; - unset AWS_SECRET_ACCESS_KEY; - unset AWS_SESSION_TOKEN; - fi - - cd ../accelerator - - |- - aws ssm get-parameter --name /accelerator/migration --query "Parameter.Value" 2> /dev/null - status=$? - if [ $status -ne 0 ]; then - echo "No SSM Parameter found, setting ENABLE_ASEA_MIGRATION to false"; - export ENABLE_ASEA_MIGRATION=false - else - echo "SSM Parameter Found, setting ENABLE_ASEA_MIGRATION to true" - export ENABLE_ASEA_MIGRATION=true - fi; - - |- - set -e && if [ $ENABLE_DIAGNOSTICS_PACK = "Yes" ]; then - yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage diagnostics-pack --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - fi - - set -e && yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - " --partition ", - { - "Ref": "AWS::Partition", - }, - " - - set -e && if [ "$ENABLE_TESTER" = "true" ]; then yarn run ts-node --transpile-only cdk.ts deploy --require-approval never --stage tester-pipeline --account ", - { - "Ref": "AWS::AccountId", - }, - " --region ", - { - "Ref": "AWS::Region", - }, - "; fi - post_build: - commands: - - |- - if [ $CODEBUILD_BUILD_SUCCEEDING -eq 1 ]; then - inprogress_status_count=$(aws codepipeline get-pipeline-state --name "", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline" | grep '"status": "InProgress"' | grep -v grep | wc -l) && - if [ $inprogress_status_count -eq 0 ]; then - set -e && aws codepipeline start-pipeline-execution --name "", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline"; - fi - fi -", - ], - ], - }, - "Type": "CODEPIPELINE", - }, - }, - "Type": "AWS::CodeBuild::Project", - }, - "ResourceNamePrefixesGetPrefixResource96A10E6E": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1", - "Arn", - ], - }, - "pipelineStackVersionSsmParamName": { - "Fn::Join": [ - "", - [ - "/accelerator/AWSAccelerator-PipelineStack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - "prefix": { - "Ref": "AcceleratorPrefix", - }, - "prefixParameterName": "/accelerator/lza-prefix", - }, - "Type": "Custom::GetPrefixes", - "UpdateReplacePolicy": "Delete", - }, - "ResourceNamePrefixesResourceNamePrefixesFunction138C66F1": { - "DependsOn": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const { SSMClient, DeleteParameterCommand, GetParameterCommand, ParameterNotFound, PutParameterCommand } = require("@aws-sdk/client-ssm"); - const { ConfiguredRetryStrategy } = require("@aws-sdk/util-retry"); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const prefix=event.ResourceProperties.prefix; - const pipelineStackVersionSsmParamName=event.ResourceProperties.pipelineStackVersionSsmParamName; - const lowerCasePrefix=prefix.toLowerCase(); - - const ssm = new SSMClient({retryStrategy: new ConfiguredRetryStrategy(10, (attempt) => 100 + attempt * 1000)}); - - let data = {}; - - let paramName = event.ResourceProperties.prefixParameterName; - - if (lowerCasePrefix === 'awsaccelerator') { - data['acceleratorPrefix'] = 'AWSAccelerator'; - data['lowerCasePrefix'] = 'aws-accelerator'; - data['oneWordPrefix'] = 'accelerator'; - } else { - data['acceleratorPrefix'] = prefix; - data['lowerCasePrefix'] = lowerCasePrefix; - data['oneWordPrefix'] = prefix; - } - - - if (event.RequestType === 'Update'){ - - var params = { - Name: paramName, - }; - try { - const ssmResponse = await ssm.send(new GetParameterCommand(params)); - // Fail stack if prefix was changed during update - if (ssmResponse.Parameter.Value !== prefix) { - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA does not allow changing AcceleratorPrefix parameter value after initial deploy !!! Existing prefix: ' + event.OldResourceProperties.prefix + ' New prefix: ' + prefix + '.' }, event.PhysicalResourceId); - return; - } - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } catch (error) { - console.log(error); - if (error instanceof ParameterNotFound){ - await response.send(event, context, response.FAILED, {'FailureReason': 'LZA prefix ssm parameter ' + paramName + ' not found!!! Recreate the parameter with existing AcceleratorPrefix parameter value to fix the issue'}, event.PhysicalResourceId); - return; - } - else { - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - } - - if (event.RequestType === 'Create') { - - if (lowerCasePrefix !== 'awsaccelerator') { - // Fail stack if prefix starts with aws or ssm - if (lowerCasePrefix.startsWith('aws') || lowerCasePrefix.startsWith('ssm')) { - await response.send(event, context, response.FAILED, {'FailureReason': 'Accelerator prefix ' + prefix + ' can not be started with aws or ssm !!!'}, event.PhysicalResourceId); - return; - } - - // Check if this is an existing deployment and prefix changed with initial deployment of custom resource - var versionParams = { - Name: pipelineStackVersionSsmParamName, - }; - try { - await ssm.send(new GetParameterCommand(versionParams)); - await response.send(event, context, response.FAILED, {'FailureReason': 'Can not change AcceleratorPrefix parameter for existing deployment, existing prefix value is AWSAccelerator, keep AcceleratorPrefix parameter value to default value for successfully stack update !!!'}, event.PhysicalResourceId); - return; - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while accessing LZA ssm parameter ' + pipelineStackVersionSsmParamName }, event.PhysicalResourceId); - return; - } - } - } - - // Create /accelerator/lza-prefix SSM parameter to store prefix value to protect updating prefix - try { - var newParams = { - Name: paramName, - Value: prefix, - Description: 'LZA created SSM parameter for Accelerator prefix value, DO NOT MODIFY/DELETE this parameter', - Type: 'String', - }; - await ssm.send(new PutParameterCommand(newParams)); - console.log('LZA prefix parameter ' + paramName + ' created successfully.'); - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - catch (error) { - console.log(error); - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while creating LZA prefix ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - if (event.RequestType === 'Delete') { - - var deleteParams = { - Name: paramName, - }; - try { - await ssm.send(new DeleteParameterCommand(deleteParams)); - console.log('LZA prefix parameter ' + paramName + ' deleted successfully.'); - } - catch (error) { - console.log(error); - if (!(error instanceof ParameterNotFound)){ - await response.send(event, context, response.FAILED, {'FailureReason': error.code + ' error occurred while deleting LZA ssm parameter ' + paramName }, event.PhysicalResourceId); - return; - } - } - await response.send(event, context, response.SUCCESS, {'Status': 'Custom resource deleted successfully' }, event.PhysicalResourceId); - } - - return; - }", - }, - "Description": "This function converts accelerator prefix parameter to lower case to name s3 buckets in installer stack", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - { - "id": "AwsSolutions-IAM5", - "reason": "Needed to create SSM parameter for prefix", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ssm:GetParameters", - "ssm:GetParameter", - "ssm:PutParameter", - "ssm:DeleteParameter", - ], - "Condition": { - "StringEquals": { - "aws:PrincipalAccount": { - "Ref": "AWS::AccountId", - }, - }, - }, - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/lza-prefix", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":ssm:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":parameter/accelerator/AWSAccelerator-PipelineStack-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/version", - ], - ], - }, - ], - "Sid": "SsmReadParameterAccess", - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRoleDefaultPolicyDC1CC159", - "Roles": [ - { - "Ref": "ResourceNamePrefixesResourceNamePrefixesFunctionServiceRole17DDBBF2", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "SecureBucket747CD8C0": { - "DeletionPolicy": "Retain", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "SSEAlgorithm": "aws:kms", - }, - }, - ], - }, - "BucketName": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "LifecycleConfiguration": { - "Rules": [ - { - "AbortIncompleteMultipartUpload": { - "DaysAfterInitiation": 1, - }, - "ExpirationInDays": 1825, - "ExpiredObjectDeleteMarker": false, - "Id": { - "Fn::Join": [ - "", - [ - "LifecycleRule", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - ], - ], - }, - "NoncurrentVersionExpiration": { - "NoncurrentDays": 1825, - }, - "NoncurrentVersionTransitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 366, - }, - ], - "Status": "Enabled", - "Transitions": [ - { - "StorageClass": "DEEP_ARCHIVE", - "TransitionInDays": 365, - }, - ], - }, - ], - }, - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "InstallerAccessLogsBucket647700E9", - }, - "LogFilePrefix": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "lowerCasePrefix", - ], - }, - "-installer-", - { - "Ref": "AWS::AccountId", - }, - "-", - { - "Ref": "AWS::Region", - }, - "/", - ], - ], - }, - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "BucketOwnerPreferred", - }, - ], - }, - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true, - }, - "VersioningConfiguration": { - "Status": "Enabled", - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SecureBucketPolicy6374AC61": { - "Properties": { - "Bucket": { - "Ref": "SecureBucket747CD8C0", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SecureBucket747CD8C0", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - "Sid": "deny-insecure-connections", - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - "SolutionHelper4825923B": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DependsOn": [ - "SolutionHelperServiceRoleF70C0E2A", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - const https = require('https'); - - async function post(url, data) { - const dataString = JSON.stringify(data) - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - timeout: 1000, // in ms - } - - return new Promise((resolve, reject) => { - const req = https.request(url, options, (res) => { - if (res.statusCode < 200 || res.statusCode > 299) { - return reject(new Error('HTTP status code: ', res.statusCode)) - } - const body = [] - res.on('data', (chunk) => body.push(chunk)) - res.on('end', () => { - const resString = Buffer.concat(body).toString() - resolve(resString) - }) - }) - req.on('error', (err) => { - reject(err) - }) - req.on('timeout', () => { - req.destroy() - reject(new Error('Request time out')) - }) - req.write(dataString) - req.end() - }) - } - - function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - - function sanitizeData(resourceProperties) { - const keysToExclude = ['ServiceToken', 'Resource', 'SolutionId', 'UUID']; - return Object.keys(resourceProperties).reduce((sanitizedData, key) => { - if (!keysToExclude.includes(key)) { - sanitizedData[key] = resourceProperties[key]; - } - return sanitizedData; - }, {}) - } - - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - const requestType = event.RequestType; - const resourceProperties = event.ResourceProperties; - const resource = resourceProperties.Resource; - let data = {}; - try { - if (resource === 'UUID' && requestType === 'Create') { - data['UUID'] = uuidv4(); - } - if (resource === 'AnonymousMetric') { - const currentDate = new Date() - data = sanitizeData(resourceProperties); - data['RequestType'] = requestType; - const payload = { - Solution: resourceProperties.SolutionId, - UUID: resourceProperties.UUID, - TimeStamp: currentDate.toISOString(), - Data: data - } - - console.log('Sending metrics data: ', JSON.stringify(payload, null, 2)); - await post('https://metrics.awssolutionsbuilder.com/generic', payload); - console.log('Sent Data'); - } - } catch (error) { - console.log(error); - } - - if (requestType === 'Create') { - await response.send(event, context, response.SUCCESS, data); - } - else { - await response.send(event, context, response.SUCCESS, data, event.PhysicalResourceId); - } - return; - } - ", - }, - "Description": "This function generates UUID for each deployment and sends anonymous data to the AWS Solutions team", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "SolutionHelperServiceRoleF70C0E2A", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 30, - }, - "Type": "AWS::Lambda::Function", - }, - "SolutionHelperServiceRoleF70C0E2A": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SolutionHelperSolutionCreateUniqueID070ED802": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "Resource": "UUID", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - }, - "Type": "Custom::CreateUUID", - "UpdateReplacePolicy": "Delete", - }, - "SolutionHelperSolutionSendAnonymousData271B3D26": { - "Condition": "SolutionHelperAnonymousDataToAWS62E4FDE2", - "DeletionPolicy": "Delete", - "Properties": { - "BranchName": { - "Ref": "RepositoryBranchName", - }, - "Region": { - "Ref": "AWS::Region", - }, - "RepositoryName": { - "Ref": "RepositoryName", - }, - "RepositoryOwner": { - "Ref": "RepositoryOwner", - }, - "RepositorySource": { - "Ref": "RepositorySource", - }, - "Resource": "AnonymousMetric", - "ServiceToken": { - "Fn::GetAtt": [ - "SolutionHelper4825923B", - "Arn", - ], - }, - "SolutionId": "SO0199", - "UUID": { - "Fn::GetAtt": [ - "SolutionHelperSolutionCreateUniqueID070ED802", - "UUID", - ], - }, - }, - "Type": "Custom::AnonymousData", - "UpdateReplacePolicy": "Delete", - }, - "SsmParamAcceleratorVersionFF83282D": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/AWSAccelerator-Test-InstallerStack/version", - ], - ], - }, - "Type": "String", - "Value": "1.8.1", - }, - "Type": "AWS::SSM::Parameter", - }, - "SsmParamStackId521A78D3": { - "Properties": { - "Name": { - "Fn::Join": [ - "", - [ - "/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "oneWordPrefix", - ], - }, - "/AWSAccelerator-Test-InstallerStack/stack-id", - ], - ], - }, - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - "Type": "AWS::SSM::Parameter", - }, - "UpdatePipelineGithubTokenFunction29B64F2E": { - "Condition": "UseGitHubCondition", - "DependsOn": [ - "UpdatePipelineLambdaRole88CE0535", - ], - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": "/** - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager'); -const { CodePipelineClient, GetPipelineCommand, UpdatePipelineCommand } = require('@aws-sdk/client-codepipeline'); -const { ConfiguredRetryStrategy } = require('@aws-sdk/util-retry'); - -const secretsManager = new SecretsManagerClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const codePipeline = new CodePipelineClient({ - retryStrategy: new ConfiguredRetryStrategy(10, attempt => 100 + attempt * 1000), -}); -const installerPipelineName = process.env['INSTALLER_PIPELINE_NAME'] ?? ''; -const acceleratorPipelineName = process.env['ACCELERATOR_PIPELINE_NAME'] ?? ''; -const pipelineArray = [installerPipelineName, acceleratorPipelineName]; - -/** - * update-pipeline-github-token - lambda handler - * - * @param event - * @returns - */ - -exports.handler = async (event, context) => { - const secretDetails = event.detail.requestParameters; - const secretArn = secretDetails.secretId; - const secretValue = await getSecretValue(secretArn); - await updatePipelineDetailsForBothPipelines(secretValue); - return { - statusCode: 200, - }; -}; - -async function getSecretValue(secretName) { - try { - const data = await secretsManager.send( - new GetSecretValueCommand({ - SecretId: secretName, - }), - ); - if (!data || !data.SecretString) { - throw new Error(\`Secret \${secretName} didn't exist.\`); - } - console.log(\`Retrieved secret: \${secretName}...\`); - return data.SecretString; - } catch (error) { - console.log(error); - throw new Error(\`Error retrieving secret: \${secretName}.\`); - } -} - -async function updateCodePipelineSourceStage(pipelineDetails, secretValue) { - const pipelineStages = pipelineDetails.pipeline.stages; - const sourceStage = pipelineStages.find(o => o.name == 'Source'); - const sourceAction = sourceStage.actions.find(a => a.name == 'Source'); - if (sourceAction.actionTypeId.provider !== 'GitHub') { - console.log('Pipeline source is not GitHub, no action will be taken.'); - return; - } - sourceAction.configuration.OAuthToken = secretValue; - - return pipelineDetails; -} - -async function getPipelineDetails(pipelineName) { - //This function retrieves the original Code Pipeline structure, so we can update it. - const getPipelineParams = { - name: pipelineName, - }; - console.log(\`Retrieving existing pipeline configuration for: \${pipelineName}...\`); - const pipelineObject = await codePipeline.send(new GetPipelineCommand(getPipelineParams)); - console.log(JSON.stringify(pipelineObject)); - return pipelineObject; -} - -async function updatePipeline(updatedPipelineDetails) { - //Remove metadata from getPipelineOutput to use as updatePipelineInput - delete updatedPipelineDetails.metadata; - console.log(\`Updating pipeline with new OAuth Token...\`); - return codePipeline.send(new UpdatePipelineCommand(updatedPipelineDetails)); -} - -async function updatePipelineDetailsForBothPipelines(secretValue) { - for (const pipeline of pipelineArray) { - try { - const pipelineDetails = await getPipelineDetails(pipeline); - const updatedPipelineDetails = await updateCodePipelineSourceStage(pipelineDetails, secretValue); - if (updatedPipelineDetails) { - await updatePipeline(updatedPipelineDetails); - } - } catch (error) { - console.error(error); - throw new Error(\`Error occurred while updating pipeline \${pipeline}\`); - } - } -} -", - }, - "Description": "Lambda function to update CodePipeline OAuth Token", - "Environment": { - "Variables": { - "ACCELERATOR_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline", - ], - ], - }, - "INSTALLER_PIPELINE_NAME": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer", - ], - ], - }, - }, - }, - "Handler": "index.handler", - "KmsKeyArn": { - "Fn::GetAtt": [ - "InstallerKey2A6A8C6D", - "Arn", - ], - }, - "Role": { - "Fn::GetAtt": [ - "UpdatePipelineLambdaRole88CE0535", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - "Timeout": 60, - }, - "Type": "AWS::Lambda::Function", - }, - "UpdatePipelineGithubTokenFunctionLogGroupFCE3723A": { - "Condition": "UseGitHubCondition", - "DeletionPolicy": "Delete", - "Metadata": { - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W84", - "reason": "CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data", - }, - ], - }, - }, - "Properties": { - "LogGroupName": { - "Fn::Join": [ - "", - [ - "/aws/lambda/", - { - "Ref": "UpdatePipelineGithubTokenFunction29B64F2E", - }, - ], - ], - }, - "RetentionInDays": 731, - }, - "Type": "AWS::Logs::LogGroup", - "UpdateReplacePolicy": "Delete", - }, - "UpdatePipelineGithubTokenRule79D83132": { - "Condition": "UseGitHubCondition", - "Properties": { - "Description": "Rule to trigger Lambda Function when the Github Accelerator Token has been updated.", - "EventPattern": { - "detail": { - "eventName": [ - "UpdateSecret", - "PutSecretValue", - ], - "eventSource": [ - "secretsmanager.amazonaws.com", - ], - "requestParameters": { - "secretId": [ - { - "prefix": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token", - ], - ], - }, - }, - ], - }, - }, - "detail-type": [ - "AWS API Call via CloudTrail", - ], - }, - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Id": "Target0", - "RetryPolicy": { - "MaximumEventAgeInSeconds": 14400, - "MaximumRetryAttempts": 2, - }, - }, - ], - }, - "Type": "AWS::Events::Rule", - }, - "UpdatePipelineGithubTokenRuleAllowEventRuleAWSAcceleratorTestInstallerStackUpdatePipelineGithubTokenFunction78C173C279CEF0A3": { - "Condition": "UseGitHubCondition", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenFunction29B64F2E", - "Arn", - ], - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "UpdatePipelineGithubTokenRule79D83132", - "Arn", - ], - }, - }, - "Type": "AWS::Lambda::Permission", - }, - "UpdatePipelineLambdaPolicy284ABC36": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM5", - "reason": "IAM role requires wildcard permissions.", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W12", - "reason": "IAM policy should not allow * resource.", - }, - ], - }, - }, - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "codepipeline:GetPipeline", - "codepipeline:UpdatePipeline", - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Installer*", - ], - ], - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":codepipeline:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-Pipeline*", - ], - ], - }, - ], - }, - { - "Action": [ - "secretsmanager:GetResourcePolicy", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:ListSecretVersionIds", - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":secretsmanager:", - { - "Ref": "AWS::Region", - }, - ":", - { - "Ref": "AWS::AccountId", - }, - ":secret:accelerator/github-token*", - ], - ], - }, - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - ], - "Effect": "Allow", - "Resource": "*", - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::", - { - "Ref": "AWS::AccountId", - }, - ":role/", - { - "Fn::GetAtt": [ - "ResourceNamePrefixesGetPrefixResource96A10E6E", - "acceleratorPrefix", - ], - }, - "-*", - ], - ], - }, - { - "Fn::GetAtt": [ - "GitHubPipelineRole6F4DEF1B", - "Arn", - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - "PolicyName": "UpdatePipelineLambdaPolicy284ABC36", - "Roles": [ - { - "Ref": "UpdatePipelineLambdaRole88CE0535", - }, - ], - }, - "Type": "AWS::IAM::Policy", - }, - "UpdatePipelineLambdaRole88CE0535": { - "Condition": "UseGitHubCondition", - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Managed policies required for IAM role.", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::IAM::Role", - }, - "ValidateInstallerValidateResource24181D5D": { - "DeletionPolicy": "Delete", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunction21674768", - "Arn", - ], - }, - "existingConfigRepositoryBranchName": { - "Ref": "ExistingConfigRepositoryBranchName", - }, - "existingConfigRepositoryName": { - "Ref": "ExistingConfigRepositoryName", - }, - "resourceType": "Custom::ValidateInstallerStack", - "useExistingConfigRepo": { - "Ref": "UseExistingConfigRepo", - }, - }, - "Type": "AWS::CloudFormation::CustomResource", - "UpdateReplacePolicy": "Delete", - }, - "ValidateInstallerValidationFunction21674768": { - "DependsOn": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - ], - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - "cfn_nag": { - "rules_to_suppress": [ - { - "id": "W58", - "reason": "CloudWatch Logs are enabled in AWSLambdaBasicExecutionRole", - }, - { - "id": "W89", - "reason": "This function supports infrastructure deployment and is not deployed inside a VPC.", - }, - { - "id": "W92", - "reason": "This function supports infrastructure deployment and does not require setting ReservedConcurrentExecutions.", - }, - ], - }, - }, - "Properties": { - "Code": { - "ZipFile": " - const response = require('cfn-response'); - exports.handler = async function (event, context) { - console.log(JSON.stringify(event, null, 4)); - - const useExistingConfigRepo=event.ResourceProperties.useExistingConfigRepo; - const existingConfigRepositoryName=event.ResourceProperties.existingConfigRepositoryName; - const existingConfigRepositoryBranchName=event.ResourceProperties.existingConfigRepositoryBranchName; - - if (useExistingConfigRepo === 'Yes') { - if (existingConfigRepositoryName === '' || existingConfigRepositoryBranchName === ''){ - await response.send(event, context, response.FAILED, {'FailureReason': 'UseExistingConfigRepo parameter set to Yes, but ExistingConfigRepositoryName or ExistingConfigRepositoryBranchName parameter value missing!!!'}, event.PhysicalResourceId); - return; - } - } - - // End of Validation - await response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId); - return; - }", - }, - "Description": "This function validates installer parameters", - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B", - "Arn", - ], - }, - "Runtime": "nodejs18.x", - }, - "Type": "AWS::Lambda::Function", - }, - "ValidateInstallerValidationFunctionServiceRoleF5BE8F9B": { - "Metadata": { - "cdk_nag": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-IAM4", - "reason": "Needed to write to CWL group", - }, - ], - }, - }, - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - }, -} -`; diff --git a/source/packages/@aws-accelerator/installer/test/installer.test.ts b/source/packages/@aws-accelerator/installer/test/installer.test.ts deleted file mode 100644 index ef8e80b..0000000 --- a/source/packages/@aws-accelerator/installer/test/installer.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { InstallerStack } from '../lib/installer-stack'; -import { SynthUtils } from '@aws-cdk/assert'; -import { expect, test, describe } from '@jest/globals'; - -// Test prefix -const testNamePrefix = 'Stack(installer): '; - -const stacks: InstallerStack[] = [ - //Initialize stack from management account with tester pipeline - new InstallerStack(new cdk.App(), 'AWSAccelerator-Test-InstallerStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: false, - enableTester: true, - managementCrossAccountRoleName: 'AWSControlTowerExecution', - enableSingleAccountMode: false, - usePermissionBoundary: false, - }), - // Initialize stack from management account without tester pipeline - new InstallerStack(new cdk.App(), 'AWSAccelerator-Test-InstallerStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: false, - enableTester: false, - enableSingleAccountMode: false, - usePermissionBoundary: false, - }), - //Initialize stack from external pipeline account with tester pipeline - new InstallerStack(new cdk.App(), 'AWSAccelerator-Test-InstallerStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: true, - enableTester: true, - managementCrossAccountRoleName: 'AWSControlTowerExecution', - enableSingleAccountMode: false, - usePermissionBoundary: false, - }), - //Initialize stack from external pipeline account without tester pipeline - new InstallerStack(new cdk.App(), 'AWSAccelerator-Test-InstallerStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: true, - enableTester: false, - enableSingleAccountMode: false, - usePermissionBoundary: false, - }), - //Initialize stack from external pipeline account without tester pipeline - new InstallerStack(new cdk.App(), 'AWSAccelerator-Test-InstallerStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: true, - enableTester: false, - enableSingleAccountMode: true, - usePermissionBoundary: false, - }), - //Initialize stack from management account with permission boundary - new InstallerStack(new cdk.App(), 'AWSAccelerator-Test-InstallerStack', { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - useExternalPipelineAccount: false, - enableTester: false, - enableSingleAccountMode: false, - usePermissionBoundary: true, - }), -]; - -/** - * InstallerStack construct test - */ -describe('InstallerStack', () => { - test(`${testNamePrefix} Snapshot Test`, () => { - stacks.forEach(item => expect(SynthUtils.toCloudFormation(item)).toMatchSnapshot()); - }); -}); diff --git a/source/packages/@aws-accelerator/installer/tsconfig.json b/source/packages/@aws-accelerator/installer/tsconfig.json deleted file mode 100644 index d2979ce..0000000 --- a/source/packages/@aws-accelerator/installer/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "bin/**/*", "index.ts"], - "exclude": ["cdk.out/**/*", "test/**/*"] -} diff --git a/source/packages/@aws-accelerator/modules/README.md b/source/packages/@aws-accelerator/modules/README.md deleted file mode 100644 index d6bd235..0000000 --- a/source/packages/@aws-accelerator/modules/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Landing Zone Accelerator on AWS - Modules - -This package manages the following modules of the LandingZone Accelerator. - -## AWS Control Tower - -Using the Landing Zone Accelerator on AWS (LZA) solution will allow customers to create, update, or reset an AWS Control Tower Landing Zone. The LZA solution takes care of the prerequisites for AWS Control Tower Landing Zone. - -## AWS Organizations - -The Landing Zone Accelerator supports the registration of AWS Organizations organizational units with the AWS Control Tower. - diff --git a/source/packages/@aws-accelerator/modules/bin/runner.ts b/source/packages/@aws-accelerator/modules/bin/runner.ts deleted file mode 100644 index 8c1665d..0000000 --- a/source/packages/@aws-accelerator/modules/bin/runner.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import yargs from 'yargs'; -import path from 'path'; -import { version } from '../../../../package.json'; -import { ModuleRunnerParametersType } from '../common/resources'; -import { ModuleRunner } from '../lib/module-runner'; - -import { createLogger } from '@aws-accelerator/utils'; - -const logger = createLogger([path.parse(path.basename(__filename)).name]); - -/** - * Function to validate and get module runner parameters - * @returns - */ -function validateAndGetRunnerParameters(): ModuleRunnerParametersType { - /** - * Module runner command with option to execute the command. - */ - const scriptUsage = - 'Usage: yarn run ts-node packages/@aws-accelerator/modules/bin/runner.ts --module --partition --account-id --region --use-existing-role --config-dir '; - - const argv = yargs(process.argv.slice(2)) - .options({ - module: { type: 'string', default: undefined }, - 'config-dir': { type: 'string', default: undefined }, - partition: { type: 'string', default: undefined }, - 'use-existing-role': { type: 'string', default: undefined }, - }) - .parseSync(); - - if (!argv.module || !argv['config-dir'] || !argv.partition || !argv['use-existing-role']) { - throw new Error(`Missing required parameters for module ${argv.module} \n ** Script Usage ** ${scriptUsage}`); - } - - return { - module: argv.module, - options: { - configDirPath: argv['config-dir'], - partition: argv.partition, - useExistingRole: argv['use-existing-role'].toLocaleLowerCase() === 'yes', - solutionId: `AwsSolution/SO0199/${version}`, - }, - }; -} - -process.on('unhandledRejection', reason => { - console.error(reason); - // eslint-disable-next-line no-process-exit - process.exit(1); -}); - -/** - * Main function to invoke accelerator runner - * @returns status string - */ -async function main(): Promise { - //validate and get runner parameters - const runnerParams = validateAndGetRunnerParameters(); - - return await ModuleRunner.execute(runnerParams); -} - -/** - * Call Main function - */ -(async () => { - try { - const status = await main(); - logger.info(status); - } catch (err) { - logger.error(err); - throw err; - } -})(); diff --git a/source/packages/@aws-accelerator/modules/common/accelerator-config-loader.ts b/source/packages/@aws-accelerator/modules/common/accelerator-config-loader.ts deleted file mode 100644 index ee7ae8f..0000000 --- a/source/packages/@aws-accelerator/modules/common/accelerator-config-loader.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AccountsConfig, GlobalConfig, OrganizationConfig, SecurityConfig } from '@aws-accelerator/config'; -import { AssumeRoleCredentialType } from './resources'; - -/** - * Accelerator all configuration types - */ -export type AllConfigType = { - globalConfig: GlobalConfig; - organizationConfig: OrganizationConfig; - accountsConfig: AccountsConfig; - securityConfig: SecurityConfig; -}; - -/** - * AcceleratorConfigLoader an abstract class to load all configuration for module operation - */ -export abstract class AcceleratorConfigLoader { - /** - * Function to load accounts config with account Ids - * @param configDirPath - * @param partition - * @param orgsEnabled - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns accountConfig {@link AccountsConfig} - */ - public static async getAccountsConfigWithAccountIds( - configDirPath: string, - partition: string, - orgsEnabled: boolean, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const accountsConfig = AccountsConfig.load(configDirPath); - await accountsConfig.loadAccountIds( - partition, - false, - orgsEnabled, - accountsConfig, - managementAccountCredentials as AWS.Credentials, - ); - - return accountsConfig; - } - - /** - * Function to get all configurations - * @param configDirPath string - * @param partition string - * @returns configs {@link AllConfigType} - */ - public static async getAllConfig(configDirPath: string, partition: string): Promise { - const orgsEnabled = OrganizationConfig.loadRawOrganizationsConfig(configDirPath).enable; - return { - globalConfig: GlobalConfig.load(configDirPath), - organizationConfig: OrganizationConfig.load(configDirPath), - accountsConfig: await AcceleratorConfigLoader.getAccountsConfigWithAccountIds( - configDirPath, - partition, - orgsEnabled, - ), - securityConfig: SecurityConfig.load(configDirPath), - }; - } -} diff --git a/source/packages/@aws-accelerator/modules/common/functions.ts b/source/packages/@aws-accelerator/modules/common/functions.ts deleted file mode 100644 index b4b6ed9..0000000 --- a/source/packages/@aws-accelerator/modules/common/functions.ts +++ /dev/null @@ -1,504 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - ChildType, - ListChildrenCommand, - ListRootsCommand, - OrganizationalUnit, - OrganizationsClient, - Root, - paginateListOrganizationalUnitsForParent, -} from '@aws-sdk/client-organizations'; -import { ControlTowerClient, GetLandingZoneCommand, ListLandingZonesCommand } from '@aws-sdk/client-controltower'; -import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts'; - -import * as winston from 'winston'; -import path from 'path'; - -import { OrganizationConfig } from '@aws-accelerator/config'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { - AssumeRoleCredentialType, - ControlTowerLandingZoneDetailsType, - OrganizationalUnitDetailsType, -} from './resources'; - -/** - * Logger - */ -const logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - -/** - * Function to get AWS Organizations Root details - * - * @param client {@link OrganizationsClient} - * @returns organizationRoot {@link Root} - */ -export async function getOrganizationsRoot(client: OrganizationsClient): Promise { - const response = await throttlingBackOff(() => client.send(new ListRootsCommand({}))); - if (!response.Roots) { - throw new Error(`AWS Organizations root undefined !!!`); - } - if (response.Roots.length === 0) { - throw new Error(`AWS Organizations root not found !!!`); - } - - if (response.Roots.length > 1) { - throw new Error(`Multiple AWS Organizations root found !!!`); - } - - if (!response.Roots[0].Arn) { - throw new Error(`AWS Organizations root arn undefined !!!`); - } - - if (!response.Roots[0].Id) { - throw new Error(`AWS Organizations root id undefined !!!`); - } - - if (!response.Roots[0].Name) { - throw new Error(`AWS Organizations root name undefined !!!`); - } - - return response.Roots[0]; -} - -/** - * Function to retrieve every AWS organizations OUs with parent information - * @param client {@link OrganizationsClient} - * @returns accounts {@link Account}[] - */ -export async function getAllOusInOrganization(client: OrganizationsClient): Promise { - const organizationUnits: OrganizationalUnitDetailsType[] = []; - - const organizationRoot = await getOrganizationsRoot(client); - const rootChildren = await getOrganizationalUnitsForParent(client, organizationRoot.Id!); - - for (const rootChild of rootChildren) { - organizationUnits.push({ - name: rootChild.Name!, - id: rootChild.Id!, - arn: rootChild.Arn!, - level: 1, - parentName: organizationRoot.Name!, - parentId: organizationRoot.Id!, - }); - - await getAllLevelChildrenOus(client, rootChild, organizationUnits); - } - - // sort by level - return organizationUnits.sort((item1, item2) => item1.level - item2.level); -} - -/** - * Function to get every level of ou details - * @param client {@link OrganizationsClient} - * @param parentOu {@link OrganizationalUnit} - * @param organizationUnits {@link OrganizationalUnitDetailsType}[] - */ -async function getAllLevelChildrenOus( - client: OrganizationsClient, - parentOu: OrganizationalUnit, - organizationUnits: OrganizationalUnitDetailsType[], -) { - const level2Children = await getOrganizationalUnitsForParent(client, parentOu.Id!); - - for (const level2Child of level2Children) { - organizationUnits.push({ - name: level2Child.Name!, - id: level2Child.Id!, - arn: level2Child.Arn!, - level: 2, - parentName: parentOu.Name!, - parentId: parentOu.Id!, - }); - - await getLevel3ChildrenOus(client, level2Child, organizationUnits); - } -} - -/** - * Function to get level 3 onwards ou details - * @param client {@link OrganizationsClient} - * @param parentOu {@link OrganizationalUnit} - * @param organizationUnits {@link OrganizationalUnitDetailsType}[] - */ -async function getLevel3ChildrenOus( - client: OrganizationsClient, - parentOu: OrganizationalUnit, - organizationUnits: OrganizationalUnitDetailsType[], -) { - const level3Children = await getOrganizationalUnitsForParent(client, parentOu.Id!); - - for (const level3Child of level3Children) { - organizationUnits.push({ - name: level3Child.Name!, - id: level3Child.Id!, - arn: level3Child.Arn!, - level: 3, - parentName: parentOu.Name!, - parentId: parentOu.Id!, - }); - - await getLevel4ChildrenOus(client, level3Child, organizationUnits); - } -} - -/** - * Function to get level 4 onwards ou details - * @param client {@link OrganizationsClient} - * @param parentOu {@link OrganizationalUnit} - * @param organizationUnits {@link OrganizationalUnitDetailsType}[] - */ -async function getLevel4ChildrenOus( - client: OrganizationsClient, - parentOu: OrganizationalUnit, - organizationUnits: OrganizationalUnitDetailsType[], -) { - const level4Children = await getOrganizationalUnitsForParent(client, parentOu.Id!); - - for (const level4Child of level4Children) { - organizationUnits.push({ - name: level4Child.Name!, - id: level4Child.Id!, - arn: level4Child.Arn!, - level: 4, - parentName: parentOu.Name!, - parentId: parentOu.Id!, - }); - - await getLevel5ChildrenOus(client, level4Child, organizationUnits); - } -} - -/** - * Function to get level 5 ou details - * @param client {@link OrganizationsClient} - * @param parentOu {@link OrganizationalUnit} - * @param organizationUnits {@link OrganizationalUnitDetailsType}[] - */ -async function getLevel5ChildrenOus( - client: OrganizationsClient, - parentOu: OrganizationalUnit, - organizationUnits: OrganizationalUnitDetailsType[], -) { - const children = await getOrganizationalUnitsForParent(client, parentOu.Id!); - - for (const child of children) { - organizationUnits.push({ - name: child.Name!, - id: child.Id!, - arn: child.Arn!, - level: 5, - parentName: parentOu.Name!, - parentId: parentOu.Id!, - }); - } -} - -export async function hasChildOus(client: OrganizationsClient, parentId: string): Promise { - const response = await throttlingBackOff(() => - client.send(new ListChildrenCommand({ ParentId: parentId, ChildType: ChildType.ORGANIZATIONAL_UNIT })), - ); - - if (!response.Children) { - throw new Error(`AWS Organizations children for parent ${parentId} undefined`); - } - - if (response.Children.length > 0) { - return true; - } - - return false; -} - -/** - * Function to get list of organization for given parent - * @param client {@link OrganizationsClient} - * @param parentId string - * @returns organizationalUnits {@link OrganizationalUnit}[] - */ -export async function getOrganizationalUnitsForParent( - client: OrganizationsClient, - parentId: string, -): Promise { - const organizationalUnits: OrganizationalUnit[] = []; - - const paginator = paginateListOrganizationalUnitsForParent({ client }, { ParentId: parentId }); - for await (const page of paginator) { - for (const organizationalUnit of page.OrganizationalUnits ?? []) { - organizationalUnits.push(organizationalUnit); - } - } - return organizationalUnits; -} - -/** - * Function to get the landing zone identifier. - * - * @remarks - * Function returns undefined when there is no landing zone configured, otherwise returns arn for the landing zone. - * If there are multiple landing zone deployment found, function will return error. - * @returns landingZoneIdentifier string | undefined - * - * @param client {@link ControlTowerClient} - * @returns landingZoneIdentifier string | undefined - */ -export async function getLandingZoneIdentifier(client: ControlTowerClient): Promise { - const response = await throttlingBackOff(() => client.send(new ListLandingZonesCommand({}))); - - if (response.landingZones!.length! > 1) { - throw new Error( - `Multiple AWS Control Tower Landing Zone configuration found, list of Landing Zone arns are - ${response.landingZones?.join( - ',', - )}`, - ); - } - - if (response.landingZones?.length === 1 && response.landingZones[0].arn) { - return response.landingZones[0].arn; - } - - return undefined; -} - -/** - * Function to get the landing zone details - * @param client {@link ControlTowerClient} - * @param region string - * @param landingZoneIdentifier string| undefined - * @returns landingZoneDetails {@link ControlTowerLandingZoneDetailsType} | undefined - */ -export async function getLandingZoneDetails( - client: ControlTowerClient, - region: string, - landingZoneIdentifier?: string, -): Promise { - if (!landingZoneIdentifier) { - return undefined; - } - - const landingZoneDetails: ControlTowerLandingZoneDetailsType = { landingZoneIdentifier: landingZoneIdentifier }; - - try { - const response = await throttlingBackOff(() => - client.send(new GetLandingZoneCommand({ landingZoneIdentifier: landingZoneIdentifier })), - ); - - if (response.landingZone) { - for (const [key, value] of Object.entries(response.landingZone.manifest!)) { - switch (key) { - case 'governedRegions': - landingZoneDetails.governedRegions = value; - break; - case 'accessManagement': - landingZoneDetails.enableIdentityCenterAccess = value['enabled']; - break; - case 'organizationStructure': - landingZoneDetails.securityOuName = value['security']['name']; - if (value['sandbox']) { - landingZoneDetails.sandboxOuName = value['sandbox']['name']; - } - break; - case 'centralizedLogging': - landingZoneDetails.loggingBucketRetentionDays = value['configurations']['loggingBucket']['retentionDays']; - landingZoneDetails.accessLoggingBucketRetentionDays = - value['configurations']['accessLoggingBucket']['retentionDays']; - landingZoneDetails.kmsKeyArn = value['configurations']['kmsKeyArn']; - break; - } - } - landingZoneDetails.landingZoneIdentifier = response.landingZone.arn!; - landingZoneDetails.status = response.landingZone.status!; - landingZoneDetails.version = response.landingZone.version!; - landingZoneDetails.latestAvailableVersion = response.landingZone.latestAvailableVersion!; - landingZoneDetails.driftStatus = response.landingZone.driftStatus!.status!; - } - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e.name === 'ResourceNotFoundException' && landingZoneIdentifier) { - throw new Error( - `Existing AWS Control Tower Landing Zone home region differs from the executing environment region ${region}. Existing Landing Zone identifier is ${landingZoneIdentifier}`, - ); - } - throw e; - } - - return landingZoneDetails; -} - -/** - * Type to define OU relation - */ -export type OuRelationType = { - level: number; - name: string; - parentName?: string; - completePath: string; - isIgnored: boolean; -}; - -/** - * Function to get Ou relation from config - * @param organizationConfig {@link OrganizationConfig} - * @returns ouRelations {@link OuRelationType}[] - */ -export function getOuRelationsFromConfig(organizationConfig: OrganizationConfig): OuRelationType[] { - const ouRelations: OuRelationType[] = []; - for (const organizationalUnit of organizationConfig.organizationalUnits) { - const isIgnored = organizationalUnit.ignore ?? false; - - const isParentChildPath = organizationalUnit.name.split('/'); - const pathLength = isParentChildPath.length; - - if (pathLength === 1) { - ouRelations.push({ - level: pathLength, - name: isParentChildPath[0], - completePath: isParentChildPath[0], - isIgnored, - }); - } else { - ouRelations.push({ - level: pathLength, - name: isParentChildPath[pathLength - 1], - parentName: isParentChildPath[pathLength - 2], - completePath: organizationalUnit.name, - isIgnored, - }); - } - } - - // sort by level - return ouRelations.sort((item1, item2) => item1.level - item2.level); -} - -/** - * Function to sleep process - * @param ms - * @returns - */ -export function delay(minutes: number) { - return new Promise(resolve => setTimeout(resolve, minutes * 60000)); -} - -/** - * Function to get cross account assume role credential - * @param options - * @returns credentials {@link Credentials} - */ -export async function getCredentials(options: { - accountId: string; - region: string; - solutionId: string; - partition?: string; - assumeRoleName?: string; - assumeRoleArn?: string; - sessionName?: string; - credentials?: AssumeRoleCredentialType; -}): Promise { - if (options.assumeRoleName && options.assumeRoleArn) { - throw new Error(`Either assumeRoleName or assumeRoleArn can be provided not both`); - } - - if (!options.assumeRoleName && !options.assumeRoleArn) { - throw new Error(`Either assumeRoleName or assumeRoleArn must provided`); - } - - if (options.assumeRoleName && !options.partition) { - throw new Error(`When assumeRoleName provided partition must be provided`); - } - - const roleArn = - options.assumeRoleArn ?? `arn:${options.partition}:iam::${options.accountId}:role/${options.assumeRoleName}`; - - const client: STSClient = new STSClient({ - region: options.region, - customUserAgent: options.solutionId, - retryStrategy: setRetryStrategy(), - credentials: options.credentials, - }); - - const currentSessionResponse = await throttlingBackOff(() => client.send(new GetCallerIdentityCommand({}))); - - if (currentSessionResponse.Arn === roleArn) { - logger.info(`Already in target environment assume role credential not required`); - return undefined; - } - - const response = await throttlingBackOff(() => - client.send( - new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: options.sessionName ?? 'AcceleratorAssumeRole' }), - ), - ); - - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - expiration: response.Credentials.Expiration, - }; -} - -/** - * Function to get management account credential. - * - * @remarks - * When solution deployed from external account management account credential will be provided - * @param partition string - * @param region string - * @param solutionId string - * @returns credential {@AssumeRoleCredentialType} | undefined - */ -export async function getManagementAccountCredentials( - partition: string, - region: string, - solutionId: string, -): Promise { - if (process.env['MANAGEMENT_ACCOUNT_ID'] && process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']) { - logger.info('set management account credentials'); - logger.info(`managementAccountId => ${process.env['MANAGEMENT_ACCOUNT_ID']}`); - logger.info(`management account role name => ${process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']}`); - - const assumeRoleArn = `arn:${partition}:iam::${process.env['MANAGEMENT_ACCOUNT_ID']}:role/${process.env['MANAGEMENT_ACCOUNT_ROLE_NAME']}`; - - return getCredentials({ - accountId: process.env['MANAGEMENT_ACCOUNT_ID'], - region, - solutionId, - assumeRoleArn, - sessionName: 'ManagementAccountCredentials', - }); - } - - return undefined; -} diff --git a/source/packages/@aws-accelerator/modules/common/resources.ts b/source/packages/@aws-accelerator/modules/common/resources.ts deleted file mode 100644 index 42a2af0..0000000 --- a/source/packages/@aws-accelerator/modules/common/resources.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -/** - * Accelerator solution supported module names - */ -export enum AcceleratorModuleName { - /** - * AWS Organizations module - */ - AWS_ORGANIZATIONS = 'aws-organizations', - /** - * ControlTower module - */ - CONTROL_TOWER = 'control-tower', -} - -/** - * Accelerator Module runner parameter type - */ -export type ModuleRunnerParametersType = { - /** - * Name of the accelerator module. - * - * @see {@link AcceleratorModules} - */ - module: string; - /** - * Accelerator module runner options - * - * @see {@link ModuleOptionsType} - * - */ - options: ModuleOptionsType; -}; - -/** - * Accelerator module option type - */ -export type ModuleOptionsType = { - /** - * LandingZone Accelerator configuration directly path - */ - configDirPath: string; - /** - * AWS partition - * - */ - partition: string; - /** - * Flag indicating existing role - */ - readonly useExistingRole: boolean; - /** - * Solution Id - */ - readonly solutionId: string; -}; - -/** - * Type for organizational unit details - */ -export type OrganizationalUnitDetailsType = { - name: string; - id: string; - arn: string; - level: number; - parentName?: string | undefined; - parentId?: string | undefined; -}; - -/** - * Cross Account assume role credential type - */ -export type AssumeRoleCredentialType = { - accessKeyId: string; - secretAccessKey: string; - sessionToken: string; - expiration?: Date; -}; - -/** - * AWS Control Tower Landing Zone details type. - */ -export type ControlTowerLandingZoneDetailsType = { - /** - * AWS Control Tower Landing Zone identifier - * - * @remarks - * AWS Control Tower Landing Zone arn - * - * @remarks - * AWS Control Tower Landing Zone arn - */ - landingZoneIdentifier: string; - /** - * AWS Control Tower Landing Zone deployment status. - * - * @remarks - * ACTIVE or FAILED or PROCESSING - */ - status?: string; - /** - * AWS Control Tower Landing Zone version - */ - version?: string; - /** - * The latest available version of AWS Control Tower Landing Zone. - */ - latestAvailableVersion?: string; - /** - * The drift status of AWS Control Tower Landing Zone. - * - * @remarks - * DRIFTED or IN_SYNC - */ - driftStatus?: string; - /** - * List of AWS Regions governed by AWS Control Tower Landing Zone - */ - governedRegions?: string[]; - /** - * The name of Security organization unit (OU) - */ - securityOuName?: string; - /** - * The name of Sandbox organization unit (OU) - */ - sandboxOuName?: string; - /** - * Flag indicating weather AWS Control Tower sets up AWS account access with IAM Identity Center or not - */ - enableIdentityCenterAccess?: boolean; - /** - * AWS Control Tower Landing Zone central logging bucket retention in days - */ - loggingBucketRetentionDays?: number; - /** - * AWS Control Tower Landing Zone access logging bucket retention in days - */ - accessLoggingBucketRetentionDays?: number; - /** - * AWS KMS CMK arn to encrypt AWS Control Tower Landing Zone resources - */ - kmsKeyArn?: string; -}; diff --git a/source/packages/@aws-accelerator/modules/jest.config.js b/source/packages/@aws-accelerator/modules/jest.config.js deleted file mode 100644 index c6cb0be..0000000 --- a/source/packages/@aws-accelerator/modules/jest.config.js +++ /dev/null @@ -1,81 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: [ - '/node_modules/', - './lib/control-tower/utils/resources.ts', - './lib/control-tower/index.ts', - './common/functions.ts', - ], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 70, - functions: 92, - lines: 85, - statements: 85, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/modules/lib/accelerator-module.ts b/source/packages/@aws-accelerator/modules/lib/accelerator-module.ts deleted file mode 100644 index 67dc0a3..0000000 --- a/source/packages/@aws-accelerator/modules/lib/accelerator-module.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { ModuleOptionsType } from '../common/resources'; -/** - * Accelerator Module interface - */ -export interface AcceleratorModule { - /** - * Handler function to manage Accelerator Modules - * - * @param module string - * @param props {@link ModuleOptionsType} - * @returns status string - * - */ - handler(module: string, props: ModuleOptionsType): Promise; -} diff --git a/source/packages/@aws-accelerator/modules/lib/aws-organization/index.ts b/source/packages/@aws-accelerator/modules/lib/aws-organization/index.ts deleted file mode 100644 index 38e4bcf..0000000 --- a/source/packages/@aws-accelerator/modules/lib/aws-organization/index.ts +++ /dev/null @@ -1,1247 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AcceptHandshakeCommand, - Account, - CancelHandshakeCommand, - CreateOrganizationalUnitCommand, - DuplicateOrganizationalUnitException, - HandshakePartyType, - HandshakeState, - InviteAccountToOrganizationCommand, - ListHandshakesForAccountCommand, - ListHandshakesForOrganizationCommand, - MoveAccountCommand, - OrganizationsClient, - Root, - paginateListAccounts, -} from '@aws-sdk/client-organizations'; -import { - BaselineOperationStatus, - BaselineSummary, - ControlTowerClient, - EnableBaselineCommand, - EnabledBaselineParameter, - EnabledBaselineSummary, - EnablementStatus, - GetBaselineOperationCommand, - paginateListBaselines, - paginateListEnabledBaselines, -} from '@aws-sdk/client-controltower'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { getGlobalRegion } from '@aws-accelerator/utils/lib/common-functions'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import * as winston from 'winston'; - -import path from 'path'; - -import { AccountIdConfig, AccountsConfig, GlobalConfig, OrganizationConfig } from '@aws-accelerator/config'; - -import { - OuRelationType, - delay, - getAllOusInOrganization, - getCredentials, - getLandingZoneDetails, - getLandingZoneIdentifier, - getManagementAccountCredentials, - getOrganizationsRoot, - getOuRelationsFromConfig, -} from '../../common/functions'; -import { - AssumeRoleCredentialType, - ControlTowerLandingZoneDetailsType, - ModuleOptionsType, - OrganizationalUnitDetailsType, -} from '../../common/resources'; -import { AcceleratorModule } from '../accelerator-module'; -import { setRetryStrategy } from '@aws-accelerator/utils/dist/lib/common-functions'; - -import { getBaselineVersion } from '@aws-accelerator/utils/lib/control-tower'; - -/** - * Type for invite account and organization details type - */ -type InviteAccountOrgDetailsType = { accountItem: AccountIdConfig; accountInOrganization: boolean }; - -/** - * Type for config invite account and organization details type - */ -type ConfigInviteAccountDetailsType = { ouConfigItem: OuRelationType; inviteAccountDetails: InviteAccountDetailsType }; -/** - * Organizational unit details type - */ -type OuDetailsType = { - /** - * The flag indicates whether an organizational unit exists within AWS Organizations. - */ - isExistsInOrg: boolean; - /** - * The flag indicates whether the organizational unit has registered with the AWS Control Tower. - */ - isRegisteredInCt: boolean; - /** - * The flag indicates whether any AWS accounts will be invited to the AWS Organizations organization unit. - */ - hasAccountsToInvite: boolean; - /** - * List of accounts to invite - */ - accountsToInvite: AccountIdConfig[]; - /** - * AWS Organizations organization unit configuration - */ - ou: OuRelationType; - /** - * AWS Organizations organization unit details - */ - existingOu?: OrganizationalUnitDetailsType; -}; - -/** - * Invite account details type - */ -type InviteAccountDetailsType = { hasAccountsToInvite: boolean; accountsToInvite: AccountIdConfig[] }; -/** - * AWSOrganization class to manage AWS Organizations operation. - */ -export class AWSOrganization implements AcceleratorModule { - private logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - /** - * Handler function to manage AWS Organizations - * - * @remarks - * The following activities are performed by this function - * - * - Create AWS Organizations organizational unit - * - Register the organizational unit into the AWS Control Tower - * - If any updates are available, update the baseline of the organizational unit - * - Invite accounts to the AWS Organizations - * - Move accounts to desired organizational unit - * - * @param module string - * @param props {@link ModuleOptionsType} - * @returns status string - */ - public async handler(module: string, props: ModuleOptionsType): Promise { - const statuses: string[] = []; - const globalConfig = GlobalConfig.load(props.configDirPath); - - // - // Get Management account credentials - // - const managementAccountCredentials = await getManagementAccountCredentials( - props.partition, - globalConfig.homeRegion, - props.solutionId, - ); - - const organizationConfig = OrganizationConfig.load(props.configDirPath); - - if (!organizationConfig.enable) { - return `AWS Organizations not enabled in organization config, module "${module}" execution skipped`; - } - - const ouRelationsFromConfig = getOuRelationsFromConfig(organizationConfig); - - const globalRegion = getGlobalRegion(props.partition); - - const controlTowerClient = new ControlTowerClient({ - region: globalConfig.homeRegion, - customUserAgent: props.solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - const organizationsClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: props.solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - const organizationRoot = await getOrganizationsRoot(organizationsClient); - - const landingZoneDetails = await this.getLandingZoneDetails( - controlTowerClient, - globalConfig.homeRegion, - globalConfig.controlTower.enable, - ); - - const enabledBaselines: EnabledBaselineSummary[] = []; - - if (landingZoneDetails) { - enabledBaselines.push(...(await this.getEnabledBaselines(controlTowerClient))); - } - - const orgAccounts = await this.getOrganizationAccounts(organizationsClient); - - const ouItems = await this.prepareOuList( - props, - organizationsClient, - ouRelationsFromConfig, - enabledBaselines, - orgAccounts, - landingZoneDetails, - ); - - for (const ouItem of ouItems) { - // If applicable create the AWS organizational units - await this.manageOuCreation(organizationsClient, ouItem, organizationRoot, statuses); - - // OU baseline only when CT is enabled and OU is not ignored - if (globalConfig.controlTower.enable && !ouItem.ou.isIgnored) { - // If applicable, enable or update baseline for the AWS organizational unit - await this.manageOuRegistration(controlTowerClient, organizationsClient, ouItem, statuses, landingZoneDetails); - } - - // If applicable invite any AWS Accounts for the AWS organizational unit - // Only for OU not marked as ignored. - if (!ouItem.ou.isIgnored) { - await this.inviteAccountsToOrganization( - props, - organizationsClient, - organizationRoot, - ouItem, - globalRegion, - globalConfig.managementAccountAccessRole, - orgAccounts, - statuses, - managementAccountCredentials, - ); - } - } - - return `Module "${module}" completed with following status.\n ${statuses.join('\n')}`; - } - - /** - * Function to manage the organizational unit registration to AWS Control Tower - * @param controlTowerClient {@link ControlTowerClient} - * @param organizationsClient {@link OrganizationsClient} - * @param ouItem {@link OuDetailsType} - * @param statuses string[] - * @param landingZoneDetails {@link ControlTowerLandingZoneDetailsType} | undefined - */ - private async manageOuRegistration( - controlTowerClient: ControlTowerClient, - organizationsClient: OrganizationsClient, - ouItem: OuDetailsType, - statuses: string[], - landingZoneDetails?: ControlTowerLandingZoneDetailsType, - ): Promise { - if (!landingZoneDetails) { - throw new Error( - `AWS Control Tower Landing Zone details undefined, can not perform organizational unit registration.`, - ); - } - const enabledBaselines = await this.getEnabledBaselines(controlTowerClient); - - const baselineVersion = getBaselineVersion(landingZoneDetails.version!); - - const availableControlTowerBaselines = await this.getAvailableControlTowerBaselines(controlTowerClient); - - const awsControlTowerBaselineIdentifier = availableControlTowerBaselines.find( - item => item.name?.toLowerCase() === 'AWSControlTowerBaseline'.toLowerCase(), - )?.arn; - - if (!awsControlTowerBaselineIdentifier) { - throw new Error(`AWSControlTowerBaseline identifier not found in available Control Tower baselines.`); - } - - const identityCenterBaselineIdentifier = await this.getIdentityCenterBaselineIdentifier( - availableControlTowerBaselines, - enabledBaselines, - ); - - if (landingZoneDetails.enableIdentityCenterAccess && !identityCenterBaselineIdentifier) { - throw new Error( - `AWS Control Tower Landing Zone is configured with IAM Identity Center, but IdentityCenterBaseline not found in enabled baselines.`, - ); - } - - // Check for already registered ou status and re-register if registration was failed - if (ouItem.isRegisteredInCt) { - const baselineStatus = this.getOuBaselineStatus(ouItem.existingOu!.arn, enabledBaselines); - - if (baselineStatus === EnablementStatus.FAILED) { - this.logger.info( - `The organizational unit "${ouItem.ou.completePath}" baseline status is "${baselineStatus}", update baseline for the organizational unit wil be performed.`, - ); - - statuses.push( - await this.updateExistingRegistration( - controlTowerClient, - organizationsClient, - ouItem, - baselineVersion, - awsControlTowerBaselineIdentifier, - identityCenterBaselineIdentifier, - ), - ); - } else { - this.logger.info( - `The organizational unit "${ouItem.ou.completePath}" baseline status is "${baselineStatus}", update baseline skipped.`, - ); - } - } else { - this.logger.info( - `The organizational unit "${ouItem.ou.completePath}" is not registered into AWS Control Tower, it will be registered now.`, - ); - - statuses.push( - await this.registerOrganizationalUnit( - controlTowerClient, - organizationsClient, - ouItem.ou, - awsControlTowerBaselineIdentifier, - baselineVersion, - identityCenterBaselineIdentifier, - ), - ); - } - } - - /** - * Function to manage AWS Organizations organizational unit creation process - * @param client {@link OrganizationsClient} - * @param ouItem {@link OuDetailsType} - * @param organizationRoot {@link Root} - * @param statuses string[] - */ - private async manageOuCreation( - client: OrganizationsClient, - ouItem: OuDetailsType, - organizationRoot: Root, - statuses: string[], - ): Promise { - if (!ouItem.isExistsInOrg) { - let parentId = organizationRoot.Id!; - if (ouItem.ou.parentName) { - // reload organization unit details to reflect newly created ou - const existingOusInOrganization = await getAllOusInOrganization(client); - const parentDetails = existingOusInOrganization.find( - item => item.name.toLowerCase() === ouItem.ou.parentName?.toLowerCase() && item.level === ouItem.ou.level - 1, - ); - - if (!parentDetails) { - throw new Error( - `Parent organizational unit "${ouItem.ou.parentName}" not found for organizational unit "${ouItem.ou.completePath}" in AWS Organizations.`, - ); - } - - this.logger.info( - `The organizational unit "${ouItem.ou.completePath}" not found in AWS Organizations. It will be created and register if not ignored.`, - ); - - parentId = parentDetails.id; - } - - statuses.push(await this.createOrganizationUnit(client, ouItem.ou, parentId)); - } else { - this.logger.info( - `The organization unit "${ouItem.ou.completePath}" already exists in AWS Organizations, create organizational operation skipped.`, - ); - } - } - - /** - * Function to update organizational unit registration - * @param controlTowerClient {@link ControlTowerClient} - * @param organizationsClient {@link OrganizationsClient} - * @param ouItem {@link OuDetailsType} - * @param baselineVersion string - * @param awsControlTowerBaselineIdentifier string - * @param identityCenterBaselineIdentifier string | undefiled - * @returns - */ - private async updateExistingRegistration( - controlTowerClient: ControlTowerClient, - organizationsClient: OrganizationsClient, - ouItem: OuDetailsType, - baselineVersion: string, - awsControlTowerBaselineIdentifier: string, - identityCenterBaselineIdentifier?: string, - ): Promise { - return this.registerOrganizationalUnit( - controlTowerClient, - organizationsClient, - ouItem.ou, - awsControlTowerBaselineIdentifier, - baselineVersion, - identityCenterBaselineIdentifier, - ); - } - - /** - * Function to check if given organizational unit has any account to be invited - * @param configDirPath string - * @param ouConfigItem {@link OuRelationType} - * @param orgAccounts {@link Account}[] - * @returns status {@link InviteAccountDetailsType} - */ - private async getInviteAccountDetails( - configDirPath: string, - ouConfigItem: OuRelationType, - orgAccounts: Account[], - ): Promise { - let hasAccountsToInvite = false; - const accountsConfig = AccountsConfig.load(configDirPath); - const accountsToInvite: AccountIdConfig[] = []; - - if (accountsConfig.accountIds?.length === 0) { - return { hasAccountsToInvite, accountsToInvite: [] }; - } - const configAccounts = [...accountsConfig.mandatoryAccounts, ...accountsConfig.workloadAccounts]; - - const promises: Promise[] = []; - - for (const accountItem of accountsConfig.accountIds ?? []) { - promises.push(this.getInviteAccountOrgDetails(accountItem, orgAccounts)); - } - - const inviteAccountsOrgDetails = await Promise.all(promises); - - for (const inviteAccountOrgDetails of inviteAccountsOrgDetails) { - const configAccount = configAccounts.find( - item => - item.email === inviteAccountOrgDetails.accountItem.email && - item.organizationalUnit === ouConfigItem.completePath, - ); - - if (configAccount && !inviteAccountOrgDetails.accountInOrganization) { - hasAccountsToInvite = true; - accountsToInvite.push(inviteAccountOrgDetails.accountItem); - } - } - return { hasAccountsToInvite, accountsToInvite: [...accountsToInvite] }; - } - - /** - * Function to check invite account presents in org - * @param accountItem {@link AccountIdConfig} - * @param orgAccounts {@link Account}[] - * @returns - */ - private async getInviteAccountOrgDetails( - accountItem: AccountIdConfig, - orgAccounts: Account[], - ): Promise { - return { - accountItem: accountItem, - accountInOrganization: await this.isAccountInOrganization(accountItem.accountId, orgAccounts), - }; - } - - /** - * Function to prepare the organizational unit list - * @param props {@link ModuleOptionsType} - * @param client {@link OrganizationsClient} - * @param ouRelationsFromConfig {@link OuRelationType}[] - * @param enabledBaselines {@link EnabledBaselineSummary}[] - * @param landingZoneDetails {@link ControlTowerLandingZoneDetailsType} - * @returns ous {@link OuDetailsType}[] - */ - private async prepareOuList( - props: ModuleOptionsType, - client: OrganizationsClient, - ouRelationsFromConfig: OuRelationType[], - enabledBaselines: EnabledBaselineSummary[], - orgAccounts: Account[], - landingZoneDetails?: ControlTowerLandingZoneDetailsType, - ): Promise { - const ouItems: OuDetailsType[] = []; - - const existingOusInOrganization = await getAllOusInOrganization(client); - - let filteredOuRelationsFromConfig = ouRelationsFromConfig; - - if (landingZoneDetails) { - filteredOuRelationsFromConfig = ouRelationsFromConfig.filter( - item => item.name !== landingZoneDetails.securityOuName, - ); - } - - const promises: Promise[] = []; - - for (const ouConfigItem of filteredOuRelationsFromConfig) { - promises.push(this.getConfigInviteAccountDetails(props.configDirPath, ouConfigItem, orgAccounts)); - } - - const configInviteAccountsDetails = await Promise.all(promises); - - for (const configInviteAccountDetails of configInviteAccountsDetails) { - const existingOu = existingOusInOrganization.find( - item => - item.name.toLowerCase() === configInviteAccountDetails.ouConfigItem.name.toLowerCase() && - item.level === configInviteAccountDetails.ouConfigItem.level, - ); - - let isRegisteredInCt = false; - let isExistsInOrg = false; - - if (existingOu) { - isExistsInOrg = true; - if (landingZoneDetails) { - isRegisteredInCt = this.isOuRegisteredInControlTower(existingOu.arn, enabledBaselines); - } - } - - ouItems.push({ - isExistsInOrg, - isRegisteredInCt, - hasAccountsToInvite: configInviteAccountDetails.inviteAccountDetails.hasAccountsToInvite, - accountsToInvite: configInviteAccountDetails.inviteAccountDetails.accountsToInvite, - ou: configInviteAccountDetails.ouConfigItem, - existingOu, - }); - } - - return ouItems; - } - - /** - * Function to get config invite account org details - * @param configDirPath string - * @param ouConfigItem {@link OuRelationType} - * @param orgAccounts {@link Account}[] - * @returns - */ - private async getConfigInviteAccountDetails( - configDirPath: string, - ouConfigItem: OuRelationType, - orgAccounts: Account[], - ): Promise { - return { - ouConfigItem, - inviteAccountDetails: await this.getInviteAccountDetails(configDirPath, ouConfigItem, orgAccounts), - }; - } - - /** - * Function to create the given AWS Organizations organizational unit - * @param client {@link OrganizationsClient} - * @param ouItem {@link OuRelationType} - * @param parentId string - * @returns status string - */ - private async createOrganizationUnit( - client: OrganizationsClient, - ouItem: OuRelationType, - parentId: string, - ): Promise { - this.logger.info(`Creating Organizational unit ${ouItem.completePath}`); - try { - const response = await throttlingBackOff(() => - client.send(new CreateOrganizationalUnitCommand({ Name: ouItem.name, ParentId: parentId })), - ); - - if (!response.OrganizationalUnit) { - throw new Error( - `The organization unit "${ouItem.completePath}", create organizational unit operation didn't return output.`, - ); - } - - this.logger.info(`AWS Organizations organizational unit "${ouItem.completePath}" created successfully.`); - - return `AWS Organizations organizational unit "${ouItem.completePath}" created successfully.`; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e instanceof DuplicateOrganizationalUnitException) { - this.logger.warn( - `DuplicateOrganizationalUnitException exception occurred while creating ou ${ouItem.completePath}`, - ); - } - throw e; - } - } - - /** - * Function to register the AWS Organizations organizational unit into AWS Control Tower - * @param controlTowerClient {@link ControlTowerClient} - * @param organizationsClient {@link OrganizationsClient} - * @param ouItem {@link OuRelationType} - * @param baselineIdentifier string - * @param baselineVersion string - * @param identityCenterEnabledBaselineArn string | undefined - * @returns status string - */ - private async registerOrganizationalUnit( - controlTowerClient: ControlTowerClient, - organizationsClient: OrganizationsClient, - ouItem: OuRelationType, - baselineIdentifier: string, - baselineVersion: string, - identityCenterEnabledBaselineArn?: string, - ): Promise { - // reload organization unit details to reflect newly created ou - const existingOusInOrganization = await getAllOusInOrganization(organizationsClient); - - const targetOuItem = existingOusInOrganization.find( - item => item.name === ouItem.name && item.level === ouItem.level, - ); - - if (!targetOuItem) { - throw new Error( - `Organizational unit "${ouItem.completePath}" not found in existing organization list failed to register the organization unit.`, - ); - } - - const parameters: EnabledBaselineParameter[] = []; - - if (identityCenterEnabledBaselineArn) { - parameters.push({ - key: 'IdentityCenterEnabledBaselineArn', - value: identityCenterEnabledBaselineArn, - }); - } - - return this.enableBaseline( - controlTowerClient, - 'Organizational Unit', - targetOuItem, - baselineIdentifier, - baselineVersion, - parameters, - ); - } - - /** - * Function to enable baseline for the target organization unit - * @param client {@link ControlTowerClient} - * @param itemType 'Organizational Unit' | 'AWS Account' - * @param targetOuItem {@link OrganizationalUnitDetailsType} - * @param baselineIdentifier string - * @param baselineVersion string - * @param targetIdentifier string - * @param parameters {@link EnabledBaselineParameter}[] - * @returns status string - */ - private async enableBaseline( - client: ControlTowerClient, - itemType: 'Organizational Unit' | 'AWS Account', - targetOuItem: OrganizationalUnitDetailsType, - baselineIdentifier: string, - baselineVersion: string, - parameters?: EnabledBaselineParameter[], - ): Promise { - this.logger.info( - `Enabling baseline for "${itemType} "${targetOuItem.name}" with id "${targetOuItem.id}" for parent "${targetOuItem.parentName}". Baseline version is "${baselineVersion}" and baseline identifier is "${baselineIdentifier}".`, - ); - const response = await throttlingBackOff(() => - client.send( - new EnableBaselineCommand({ - baselineIdentifier, - baselineVersion, - targetIdentifier: targetOuItem.arn, - parameters, - }), - ), - ); - - if (!response.operationIdentifier) { - throw new Error( - `The "${itemType} "${targetOuItem.name}" with id "${targetOuItem.id}" for parent "${targetOuItem.parentName}" enable base line api didn't return operation identifier.`, - ); - } - - await this.waitTillBaselineCompletes(client, targetOuItem, response.operationIdentifier); - - return `The "${itemType} "${targetOuItem.name}" with id "${targetOuItem.id}" for parent "${targetOuItem.parentName}" registered successfully into AWS Control Tower.`; - } - - /** - * Function to check and wait till the AWS Organizations organizational unit registration completion. - * @param client {@link ControlTowerClient} - * @param targetOuItem {@link OrganizationalUnitDetailsType} - * @param operationIdentifier string - */ - private async waitTillBaselineCompletes( - client: ControlTowerClient, - targetOuItem: OrganizationalUnitDetailsType, - operationIdentifier: string, - ): Promise { - const queryIntervalInMinutes = 2; - const timeoutInMinutes = 60; - let elapsedInMinutes = 0; - let status = await this.getBaselineOperationStatus(client, targetOuItem.name, operationIdentifier); - - while (status !== BaselineOperationStatus.SUCCEEDED) { - await delay(queryIntervalInMinutes); - status = await this.getBaselineOperationStatus(client, targetOuItem.name, operationIdentifier); - elapsedInMinutes = elapsedInMinutes + queryIntervalInMinutes; - if (elapsedInMinutes >= timeoutInMinutes) { - throw new Error( - `The organizational unit "${targetOuItem.name}" baseline operation took more than ${timeoutInMinutes} minutes. Pipeline aborted, please review AWS Control Tower console to make sure organization unit registration completes.`, - ); - } - this.logger.info( - `The organizational unit "${targetOuItem.name}" with id "${targetOuItem.id}" for parent "${targetOuItem.parentName}" baseline operation with identifier "${operationIdentifier}" is currently in "${status}" state. After ${queryIntervalInMinutes} minutes delay, the status will be rechecked. Elapsed time ${elapsedInMinutes} minutes.`, - ); - } - } - - /** - * Function to move AWS Account to target organizational unit - * @param client {@link OrganizationsClient} - * @param accountToInvite {@link AccountIdConfig} - * @param sourceParentName string - * @param sourceParentId string - * @param destinationOuItem ${@link OuRelationType} - * @param existingOusInOrganization {@link OrganizationalUnitDetailsType}[] - * @returns status string - */ - private async moveAccountToOu( - client: OrganizationsClient, - accountToInvite: AccountIdConfig, - sourceParentName: string, - sourceParentId: string, - destinationOuItem: OuRelationType, - existingOusInOrganization: OrganizationalUnitDetailsType[], - ): Promise { - const destinationOu = existingOusInOrganization.find( - item => item.name === destinationOuItem.name && item.level === destinationOuItem.level, - ); - - if (!destinationOu) { - throw new Error( - `Destination organizational unit id for ou "${destinationOuItem.name}" not found in existing organization list failed to move account with email "${accountToInvite.email}".`, - ); - } - - await throttlingBackOff(() => - client.send( - new MoveAccountCommand({ - AccountId: accountToInvite.accountId, - SourceParentId: sourceParentId, - DestinationParentId: destinationOu.id, - }), - ), - ); - - return `Account with email "${accountToInvite.email}" moved from "${sourceParentName}" to "${destinationOuItem.name}" within the AWS Organizations.`; - } - - /** - * Function to invite AWS Accounts to AWS organizations - * @param props {@link ModuleOptionsType} - * @param client {@link OrganizationsClient} - * @param organizationRoot {@link Root} - * @param ouItem {@link OuDetailsType} - * @param globalRegion string - * @param assumeRoleName string - * @param orgAccounts {@link Account}[] - * @param statuses string[] - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - */ - private async inviteAccountsToOrganization( - props: ModuleOptionsType, - client: OrganizationsClient, - organizationRoot: Root, - ouItem: OuDetailsType, - globalRegion: string, - assumeRoleName: string, - orgAccounts: Account[], - statuses: string[], - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const localStatuses: string[] = []; - if (ouItem.hasAccountsToInvite) { - this.logger.info(`The organizational unit "${ouItem.ou.name}" has accounts to invite.`); - - const existingOusInOrganization = await getAllOusInOrganization(client); - - const promises: Promise[] = []; - - for (const accountToInvite of ouItem.accountsToInvite) { - promises.push(this.getInviteAccountOrgDetails(accountToInvite, orgAccounts)); - } - - const inviteAccountsOrgDetails = await Promise.all(promises); - - for (const inviteAccountOrgDetails of inviteAccountsOrgDetails) { - if (!inviteAccountOrgDetails.accountInOrganization) { - this.logger.info( - `AWS Account with email "${inviteAccountOrgDetails.accountItem.email}" invited to the AWS Organizations.`, - ); - const handshakeId = await this.inviteAccountToOrganization(client, inviteAccountOrgDetails.accountItem); - - localStatuses.push( - await this.acceptAccountInvitationToOrganization( - client, - props.partition, - globalRegion, - props.solutionId, - assumeRoleName, - handshakeId, - inviteAccountOrgDetails.accountItem, - managementAccountCredentials, - ), - ); - - localStatuses.push( - await this.moveAccountToOu( - client, - inviteAccountOrgDetails.accountItem, - organizationRoot.Name!, - organizationRoot.Id!, - ouItem.ou, - existingOusInOrganization, - ), - ); - - // AWS Accounts enrolled into existing OU can't be registered into Control Tower - // Only OU is supported for base line The ARN of the target on which the baseline will be enabled. Only OUs are supported as targets.) - // https://docs.aws.amazon.com/controltower/latest/APIReference/API_EnableBaseline.html - if (ouItem.isExistsInOrg) { - this.logger.info( - `AWS Account with email ${inviteAccountOrgDetails.accountItem.email} moved to existing organizational unit ${ouItem.ou.name}, account will be enrolled by the prepare stack.`, - ); - } - } - } - } - - if (localStatuses.length === 0) { - this.logger.info( - `No accounts found for organizational unit "${ouItem.ou.name}" to be invited to the AWS Organizations.`, - ); - } - - statuses.push(...localStatuses); - } - - /** - * Function to invite given account into AWS Organizations - * @param client {@link OrganizationsClient} - * @param accountToInvite {@link AccountIdConfig} - * @returns handshakeId string - */ - private async inviteAccountToOrganization( - client: OrganizationsClient, - accountToInvite: AccountIdConfig, - ): Promise { - try { - const response = await throttlingBackOff(() => - client.send( - new InviteAccountToOrganizationCommand({ Target: { Type: 'ACCOUNT', Id: accountToInvite.accountId } }), - ), - ); - - if (!response.Handshake?.Id) { - throw new Error( - `Account "${accountToInvite.email}" invitation api didn't return handshake id, please manually invite and accept this account into the AWS Organizations.`, - ); - } - - return response.Handshake.Id; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - this.logger.warn( - `There was an "${e.name}" error when inviting account with email address "${accountToInvite.email}". AWS Organizations will cancel the invitation.`, - ); - await this.cancelInvitationHandshake(client, accountToInvite); - throw e; - } - } - - /** - * Function to get the opened invitation handshake id for the given account. - * @param client {@link OrganizationsClient} - * @param accountId string - * @returns handshakeId string | undefined - */ - private async getOpenedInvitationHandshakeIdForAccount( - client: OrganizationsClient, - accountId: string, - ): Promise { - const response = await throttlingBackOff(() => client.send(new ListHandshakesForOrganizationCommand({}))); - - for (const handshake of response.Handshakes ?? []) { - if (handshake.State === HandshakeState.OPEN) { - for (const party of handshake.Parties ?? []) { - if (party.Type === HandshakePartyType.ACCOUNT && party.Id === accountId) { - return handshake.Id!; - } - } - } - } - - return undefined; - } - - /** - * Function to cancel AWS Account invitation to the AWS Organizations - * @param client {@link OrganizationsClient} - * @param accountItem {@link AccountIdConfig} - */ - private async cancelInvitationHandshake(client: OrganizationsClient, accountItem: AccountIdConfig): Promise { - const handshakeId = await this.getOpenedInvitationHandshakeIdForAccount(client, accountItem.accountId); - - if (handshakeId) { - this.logger.warn(`Cancelling invitation for account with email ${accountItem.email}`); - await throttlingBackOff(() => client.send(new CancelHandshakeCommand({ HandshakeId: handshakeId }))); - } - } - - /** - * Function to accept invitation to AWS Organizations - * @param managementAccountClient {@link OrganizationsClient} - * @param partition string - * @param globalRegion string - * @param solutionId string - * @param assumeRoleName string - * @param handshakeId string - * @param accountItem {@link AccountIdConfig} - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns status string - */ - private async acceptAccountInvitationToOrganization( - managementAccountClient: OrganizationsClient, - partition: string, - globalRegion: string, - solutionId: string, - assumeRoleName: string, - handshakeId: string, - accountItem: AccountIdConfig, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - let credentials: AssumeRoleCredentialType | undefined; - try { - credentials = await getCredentials({ - accountId: accountItem.accountId, - region: globalRegion, - solutionId, - partition, - assumeRoleName, - sessionName: 'AcceleratorAcceptInviteAssumeRole', - credentials: managementAccountCredentials, - }); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - this.logger.warn( - `When assuming role "${assumeRoleName}" to accept invitation for an account with the email address "${accountItem.email}", an "${e.name}" error occurred. Make sure the role is present in the account, or that the account id provided is valid. AWS Organizations will cancel the invitation.`, - ); - await this.cancelInvitationHandshake(managementAccountClient, accountItem); - throw e; - } - const accepterClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials, - }); - - this.logger.info(`Accepting AWS Account with email "${accountItem.email}" invite to the AWS Organizations.`); - const response = await throttlingBackOff(() => - accepterClient.send(new AcceptHandshakeCommand({ HandshakeId: handshakeId })), - ); - - if (response.Handshake?.State === HandshakeState.ACCEPTED) { - return `Invitation to AWS Organizations for AWS Account with email "${accountItem.email}" completed successfully.`; - } - - if (!response.Handshake) { - throw new Error( - `AWS Account with email "${accountItem.email}" accept handshakes api didn't return any handshake response, please investigate the account invitation. `, - ); - } - - if (!response.Handshake.Id) { - throw new Error( - `AWS Account with email "${accountItem.email}" accept handshakes api didn't return any handshake identifier, please investigate the account invitation. `, - ); - } - - return this.waitUntilAccountInvitationAccepted(accepterClient, response.Handshake.Id, accountItem.email); - } - - /** - * Function to check and wait till the AWS Account invitation accepted. - * @param client {@link OrganizationsClient} - * @param handshakeId string - * @param accountEmail string - * @returns status string - */ - private async waitUntilAccountInvitationAccepted( - client: OrganizationsClient, - handshakeId: string, - accountEmail: string, - ): Promise { - const queryIntervalInMinutes = 1; - const timeoutInMinutes = 10; - let elapsedInMinutes = 0; - let status = await this.getAccountHandshakeStatus(client, handshakeId, accountEmail); - - while (status !== HandshakeState.ACCEPTED) { - await delay(queryIntervalInMinutes); - status = await this.getAccountHandshakeStatus(client, handshakeId, accountEmail); - - if ( - status === HandshakeState.CANCELED || - status === HandshakeState.DECLINED || - status === HandshakeState.EXPIRED - ) { - throw new Error( - `AWS Account with email "${accountEmail}" invitation status is "${status}", please investigate the account invitation. `, - ); - } - - elapsedInMinutes = elapsedInMinutes + queryIntervalInMinutes; - if (elapsedInMinutes >= timeoutInMinutes) { - throw new Error( - `AWS Account with email "${accountEmail}" invitation acceptance operation took more than ${timeoutInMinutes} minutes. Pipeline aborted, please review AWS Account with email "${accountEmail}" and complete acceptance of invitation.`, - ); - } - this.logger.info( - `The AWS Account with email "${accountEmail}" invite acceptance with handshake identifier "${handshakeId}" is currently in "${status}" state. After ${queryIntervalInMinutes} minutes delay, the status will be rechecked. Elapsed time ${elapsedInMinutes} minutes.`, - ); - } - - return `Invitation to AWS Organizations for AWS Account with email "${accountEmail}" completed successfully.`; - } - - /** - * Function to find AWS Account invitation handshake status - * @param client {@link OrganizationsClient} - * @param handshakeId string - * @param accountEmail string - * @returns status string - */ - private async getAccountHandshakeStatus( - client: OrganizationsClient, - handshakeId: string, - accountEmail: string, - ): Promise { - const response = await throttlingBackOff(() => - client.send(new ListHandshakesForAccountCommand({ Filter: { ActionType: 'INVITE' } })), - ); - - if (!response.Handshakes) { - throw new Error( - `AWS Account with email "${accountEmail}" list handshakes api didn't return any response, please investigate the account invitation. `, - ); - } - - const handshakeResponse = response.Handshakes.find(item => item.Id === handshakeId); - - if (!handshakeResponse) { - throw new Error( - `AWS Account with email "${accountEmail}" list handshakes api couldn't find handshake information with handshake id "${handshakeId}", please investigate the account invitation. `, - ); - } - - if (!handshakeResponse.State) { - throw new Error( - `AWS Account with email "${accountEmail}" list handshakes api couldn't find handshake status with handshake id "${handshakeId}", please investigate the account invitation. `, - ); - } - - return handshakeResponse.State; - } - - /** - * Function to check if the given function is part of AWS Organizations - * @param accountId string - * @param orgAccounts {@link Account}[] - * @returns status boolean - */ - private async isAccountInOrganization(accountId: string, orgAccounts: Account[]): Promise { - const accountFound = orgAccounts.find(item => item.Id === accountId); - - if (accountFound) { - return true; - } - - return false; - } - - /** - * Function to get AWS Organizations accounts - * @param client {@link OrganizationsClient} - * @returns accounts {@link Account}[] - */ - private async getOrganizationAccounts(client: OrganizationsClient): Promise { - const accounts: Account[] = []; - const paginator = paginateListAccounts({ client }, {}); - for await (const page of paginator) { - for (const account of page.Accounts ?? []) { - accounts.push(account); - } - } - - return accounts; - } - - /** - * Function to get available AWS Control Tower baselines - * @param client {@link ControlTowerClient} - * @returns baselines {@link BaselineSummary}[] - */ - private async getAvailableControlTowerBaselines(client: ControlTowerClient): Promise { - const baselines: BaselineSummary[] = []; - const paginator = paginateListBaselines({ client }, {}); - for await (const page of paginator) { - for (const baseline of page.baselines ?? []) { - baselines.push(baseline); - } - } - - return baselines; - } - - /** - * Function to get IdentityCenterBaselineIdentifier - * @param baselines {@link BaselineSummary}[] - * @param enabledBaselines {@link EnabledBaselineSummary}[] - * @returns identifier string | undefined - */ - private async getIdentityCenterBaselineIdentifier( - baselines: BaselineSummary[], - enabledBaselines: EnabledBaselineSummary[], - ): Promise { - const baseline = baselines.find(item => item.name?.toLowerCase() === 'IdentityCenterBaseline'.toLowerCase()); - - if (baseline) { - const enabledBaseline = enabledBaselines.find(item => item.baselineIdentifier === baseline.arn); - - if (enabledBaseline) { - return enabledBaseline.arn!; - } - } - - return undefined; - } - - /** - * Function to check if the given AWS Organizations organizational unit is registered into the AWS Control Tower - * @param ouArn string - * @param existingEnabledBaselines {@link EnabledBaselineSummary}[] - * @returns status boolean - */ - private isOuRegisteredInControlTower(ouArn: string, existingEnabledBaselines: EnabledBaselineSummary[]): boolean { - const isOutFound = existingEnabledBaselines.find(item => item.targetIdentifier === ouArn); - if (isOutFound) { - return true; - } - return false; - } - - /** - * Function to get the AWS Organizations organizational unit baseline status - * @param ouArn string - * @param existingEnabledBaselines {@link EnabledBaselineSummary}[] - * @returns status {@link EnablementStatus} | undefined - */ - private getOuBaselineStatus( - ouArn: string, - existingEnabledBaselines: EnabledBaselineSummary[], - ): EnablementStatus | undefined { - const isOutFound = existingEnabledBaselines.find(item => item.targetIdentifier === ouArn); - if (isOutFound) { - return isOutFound.statusSummary?.status; - } - return undefined; - } - - /** - * Function to get the AWS Organizations organizational unit baseline operation status - * - * @param client {@link ControlTowerClient} - * @param ouName string - * @param operationIdentifier string - * @returns operationStatus {@link BaselineOperationStatus} - */ - private async getBaselineOperationStatus( - client: ControlTowerClient, - ouName: string, - operationIdentifier: string, - ): Promise { - const response = await throttlingBackOff(() => - client.send(new GetBaselineOperationCommand({ operationIdentifier })), - ); - - const operationStatus = response.baselineOperation?.status; - - if (!operationStatus) { - throw new Error( - `AWS Control Tower Landing Zone get baseline operation api didn't return operation status. API returned "${operationStatus}" for operation status.`, - ); - } - - if (operationStatus === BaselineOperationStatus.FAILED) { - throw new Error( - `The organizational unit "${ouName}" baseline operation with identifier "${operationIdentifier}" in "${response.baselineOperation?.status}" state. Please investigate baseline operation before executing pipeline.`, - ); - } - - return operationStatus; - } - - /** - * Function to get enabled baselines - * @param client {@link ControlTowerClient} - * @returns enabledBaselines {@link EnabledBaselineSummary}[] - */ - private async getEnabledBaselines(client: ControlTowerClient): Promise { - const enabledBaselines: EnabledBaselineSummary[] = []; - const paginator = paginateListEnabledBaselines({ client }, {}); - for await (const page of paginator) { - for (const enabledBaseline of page.enabledBaselines ?? []) { - enabledBaselines.push(enabledBaseline); - } - } - - return enabledBaselines; - } - - /** - * Function to get AWS Control Tower Landing Zone details - * @param client {@link ControlTowerClient} - * @param homeRegion string - * @param isControlTowerEnabled boolean - * @returns - */ - private async getLandingZoneDetails( - client: ControlTowerClient, - homeRegion: string, - isControlTowerEnabled: boolean, - ): Promise { - let landingZoneDetails: ControlTowerLandingZoneDetailsType | undefined; - - if (!isControlTowerEnabled) { - this.logger.info( - `AWS Control Tower Landing Zone not configured for the environment. The organizational unit registration will be skipped.`, - ); - } else { - const landingZoneIdentifier = await getLandingZoneIdentifier(client); - if (!landingZoneIdentifier) { - throw new Error(`AWS Control Tower Landing Zone not configured for the environment.`); - } - landingZoneDetails = await getLandingZoneDetails(client, homeRegion, landingZoneIdentifier); - if (!landingZoneDetails) { - throw new Error(`AWS Control Tower Landing Zone details undefined.`); - } - } - return landingZoneDetails; - } -} diff --git a/source/packages/@aws-accelerator/modules/lib/control-tower/index.ts b/source/packages/@aws-accelerator/modules/lib/control-tower/index.ts deleted file mode 100644 index 2ac353f..0000000 --- a/source/packages/@aws-accelerator/modules/lib/control-tower/index.ts +++ /dev/null @@ -1,490 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { - ControlTowerClient, - CreateLandingZoneCommand, - CreateLandingZoneCommandInput, - GetLandingZoneOperationCommand, - LandingZoneOperationStatus, - LandingZoneStatus, - ResetLandingZoneCommand, - UpdateLandingZoneCommand, -} from '@aws-sdk/client-controltower'; -import { AccountsConfig, GlobalConfig } from '@aws-accelerator/config'; - -import * as winston from 'winston'; -import { - ControlTowerLandingZoneConfigType, - ControlTowerLandingZoneDetailsType, - delay, - isLandingZoneUpdateOrResetRequired, - makeManifestDocument, -} from './utils/resources'; -import { IamRole } from './prerequisites/iam-role'; -import { KmsKey } from './prerequisites/kms-key'; -import path from 'path'; -import { Organization } from './prerequisites/organization'; -import { SharedAccount } from './prerequisites/shared-account'; -import { AssumeRoleCredentialType, ModuleOptionsType } from '../../common/resources'; -import { AcceleratorModule } from '../accelerator-module'; -import { AcceleratorConfigLoader } from '../../common/accelerator-config-loader'; -import { - getLandingZoneDetails, - getLandingZoneIdentifier, - getManagementAccountCredentials, -} from '../../common/functions'; -import { getGlobalRegion } from '@aws-accelerator/utils/lib/common-functions'; - -const logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - -/** - * ControlTowerLandingZone class to manage AWS Control Tower Landing Zone operation. - */ -export class ControlTowerLandingZone implements AcceleratorModule { - /** - * Function to get the landing zone operation status - * - * @param client {@link ControlTowerClient} - * @param operationIdentifier string - * @returns landingZoneOperationStatus string - */ - private async getLandingZoneOperationStatus( - client: ControlTowerClient, - operationIdentifier: string, - ): Promise { - const response = await throttlingBackOff(() => - client.send(new GetLandingZoneOperationCommand({ operationIdentifier })), - ); - - const operationStatus = response.operationDetails?.status; - - if (!operationStatus) { - logger.warn( - `AWS Control Tower Landing Zone get landing zone operation api didn't return operation status. API returned ${operationStatus} for operation status.`, - ); - throw new Error( - `AWS Control Tower Landing Zone get landing zone operation api didn't return operation status. Solution cannot verify successful completion of Landing Zone operation.`, - ); - } - - if (operationStatus === LandingZoneOperationStatus.FAILED) { - logger.warn( - `AWS Control Tower Landing Zone operation with identifier ${operationIdentifier} in ${response.operationDetails?.status} state !!!!. Please investigate CT operation before executing pipeline`, - ); - throw new Error( - `AWS Control Tower Landing Zone operation with identifier ${operationIdentifier} in ${response.operationDetails?.status} state !!!!. Please investigate CT operation before executing pipeline`, - ); - } - - return operationStatus; - } - - /** - * Function to get AWS Control Tower Landing Zone configuration - * @param globalConfig {@link GlobalConfig} - * @param accountsConfig {@link AccountsConfig} - * @returns config {@link ControlTowerLandingZoneConfigType} - */ - private getControlTowerLandingZoneConfig( - globalConfig: GlobalConfig, - accountsConfig: AccountsConfig, - globalRegion: string, - ): ControlTowerLandingZoneConfigType { - const landingZoneConfig = globalConfig.controlTower.landingZone!; - - const governedRegions: string[] = globalConfig.enabledRegions; - - if (!governedRegions.includes(globalRegion)) { - governedRegions.push(globalRegion); - } - - return { - version: landingZoneConfig.version, - governedRegions, - logArchiveAccountId: accountsConfig.getLogArchiveAccountId(), - auditAccountId: accountsConfig.getAuditAccountId(), - enableIdentityCenterAccess: landingZoneConfig.security.enableIdentityCenterAccess, - loggingBucketRetentionDays: landingZoneConfig.logging.loggingBucketRetentionDays, - accessLoggingBucketRetentionDays: landingZoneConfig.logging.accessLoggingBucketRetentionDays, - enableOrganizationTrail: landingZoneConfig.logging.organizationTrail, - }; - } - - /** - * Function to check and wait till the landing zone operation completion. - * @param client {@link ControlTowerClient} - * @param operationIdentifier string - * @param region string - */ - private async waitTillOperationCompletes(client: ControlTowerClient, operationIdentifier: string): Promise { - const queryIntervalInMinutes = 5; - let status = await this.getLandingZoneOperationStatus(client, operationIdentifier); - - while (status !== LandingZoneOperationStatus.SUCCEEDED) { - logger.info( - `The AWS Control Tower Landing Zone operation with identifier ${operationIdentifier} is currently in ${status} state. After ${queryIntervalInMinutes} minutes delay, the status will be rechecked.`, - ); - - await delay(queryIntervalInMinutes); - status = await this.getLandingZoneOperationStatus(client, operationIdentifier); - } - } - - /** - * Module manager function - * @param module string - * @param props {@link ModuleOptionsType} - * @returns status string - */ - private async manageModule(module: string, props: ModuleOptionsType): Promise { - const globalConfig = GlobalConfig.load(props.configDirPath); - const accountsConfig = AccountsConfig.load(props.configDirPath); - const globalRegion = getGlobalRegion(props.partition); - - if (!globalConfig.controlTower.landingZone) { - return `The global-config.yaml file did not contain any configuration for AWS Control Tower Landing Zone, no activities for module ${module}.`; - } - - // - // Get Management account credentials - // - const managementAccountCredentials = await getManagementAccountCredentials( - props.partition, - globalConfig.homeRegion, - props.solutionId, - ); - - const client: ControlTowerClient = new ControlTowerClient({ - region: globalConfig.homeRegion, - customUserAgent: props.solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - const landingZoneIdentifier = await getLandingZoneIdentifier(client); - - const preRequisitesResources = await ControlTowerPreRequisites.completePreRequisites( - props, - globalConfig.homeRegion, - globalRegion, - accountsConfig, - landingZoneIdentifier, - managementAccountCredentials, - ); - - const accountsConfigWithAccountIds = await AcceleratorConfigLoader.getAccountsConfigWithAccountIds( - props.configDirPath, - props.partition, - true, - managementAccountCredentials, - ); - - const landingZoneConfiguration = this.getControlTowerLandingZoneConfig( - globalConfig, - accountsConfigWithAccountIds, - globalRegion, - ); - - const landingZoneDetails = await getLandingZoneDetails(client, globalConfig.homeRegion, landingZoneIdentifier); - - if (landingZoneDetails?.status === LandingZoneStatus.PROCESSING) { - throw new Error( - `Module - ${module} The Landing Zone update operation failed with error - ConflictException - AWS Control Tower cannot begin landing zone setup while another execution is in progress.`, - ); - } - - if (landingZoneDetails) { - const landingZoneUpdateOrResetStatus = isLandingZoneUpdateOrResetRequired( - landingZoneConfiguration, - landingZoneDetails, - ); - - if (landingZoneUpdateOrResetStatus.updateRequired) { - const operationIdentifier = await LandingZoneOperation.updateLandingZone( - client, - landingZoneUpdateOrResetStatus.targetVersion, - landingZoneUpdateOrResetStatus.reason, - landingZoneConfiguration, - landingZoneDetails, - ); - await this.waitTillOperationCompletes(client, operationIdentifier); - - return `Module - ${module} The Landing Zone update operation completed successfully.`; - } - - if (landingZoneUpdateOrResetStatus.resetRequired) { - const operationIdentifier = await LandingZoneOperation.resetLandingZone( - client, - landingZoneDetails.landingZoneIdentifier, - landingZoneUpdateOrResetStatus.reason, - ); - await this.waitTillOperationCompletes(client, operationIdentifier); - - return `Module - ${module} The Landing Zone reset operation completed successfully.`; - } - - // When no changes required - return landingZoneUpdateOrResetStatus.reason; - } else { - const operationIdentifier = await LandingZoneOperation.createLandingZone( - client, - landingZoneConfiguration, - preRequisitesResources!.kmsKeyArn, - ); - await this.waitTillOperationCompletes(client, operationIdentifier); - - return `Module - ${module} The Landing Zone deployed successfully.`; - } - } - - /** - * Handler function to manage AWS Control Tower Landing Zone - * - * @remarks - * When AWS Control Tower Landing Zone is not configured this function will perform complete pre-requisites and create then landing zone. - * When AWS Control Tower Landing Zone is configured, based ```controlTower.landingZone``` configuration in global config file, function will update the landing zone. - * When existing AWS Control Tower Landing Zone is drifted, function will reset the landing zone. - * @param module string - * @param props {@link ModuleOptionsType} - * @returns status string - */ - public async handler(module: string, props: ModuleOptionsType): Promise { - return await this.manageModule(module, props); - } -} -/** - * LandingZoneOperation an abstract class to perform following AWS Control Tower operation - * - * - Create AWS Control Tower Landing Zone - * - Reset AWS Control Tower Landing Zone - * - Update AWS Control Tower Landing Zone - */ -abstract class LandingZoneOperation { - /** - * Function to deploy the landing zone - * @param client {@link ControlTowerClient} - * @param landingZoneConfiguration {@link ControlTowerLandingZoneConfigType} - * @param kmsKeyArn string - * @returns operationIdentifier string - */ - public static async createLandingZone( - client: ControlTowerClient, - landingZoneConfiguration: ControlTowerLandingZoneConfigType, - kmsKeyArn: string, - ): Promise { - const manifestDocument = makeManifestDocument(landingZoneConfiguration, 'CREATE', kmsKeyArn); - const param: CreateLandingZoneCommandInput = { - version: landingZoneConfiguration.version, - manifest: manifestDocument, - }; - - const response = await throttlingBackOff(() => client.send(new CreateLandingZoneCommand(param))); - - const operationIdentifier = response.operationIdentifier; - - if (!operationIdentifier) { - logger.warn( - `AWS Control Tower Landing Zone create operation api didn't return operation identifier. API return ${operationIdentifier} for operation identifier`, - ); - throw new Error( - `AWS Control Tower Landing Zone create operation api didn't return operation identifier. Solution cannot verify successful completion of AWS Control Tower Landing Zone operation.`, - ); - } - - logger.info( - `The Landing Zone deployment operation successfully started, operation identifier is - ${operationIdentifier}`, - ); - - return operationIdentifier; - } - - /** - * Function to reset the landing zone - * @param client {@link ControlTowerClient} - * @param landingZoneIdentifier string - * @param reason string - * @returns operationIdentifier string - */ - public static async resetLandingZone( - client: ControlTowerClient, - landingZoneIdentifier: string, - reason: string, - ): Promise { - logger.info(`The Landing Zone reset operation will begin, because "${reason}"`); - const response = await throttlingBackOff(() => client.send(new ResetLandingZoneCommand({ landingZoneIdentifier }))); - - const operationIdentifier = response.operationIdentifier; - - if (!operationIdentifier) { - logger.warn( - `AWS Control Tower Landing Zone reset operation api didn't return operation identifier. API return ${operationIdentifier} for operation identifier`, - ); - throw new Error( - `AWS Control Tower Landing Zone reset operation api didn't return operation identifier. Solution cannot verify successful completion of AWS Control Tower Landing Zone operation.`, - ); - } - - logger.info( - `The Landing Zone reset operation successfully started, operation identifier is - ${operationIdentifier}`, - ); - - return operationIdentifier; - } - - /** - * Function to update the landing zone - * - * @param client {@link ControlTowerClient} - * @param targetVersion string - * @param reason string - * @param landingZoneConfiguration {@link ControlTowerLandingZoneConfigType} - * @param landingZoneDetails {@link ControlTowerLandingZoneDetailsType} - * @returns operationIdentifier string - */ - public static async updateLandingZone( - client: ControlTowerClient, - targetVersion: string, - reason: string, - landingZoneConfiguration: ControlTowerLandingZoneConfigType, - landingZoneDetails: ControlTowerLandingZoneDetailsType, - ): Promise { - logger.info(`The Landing Zone update operation will begin, because "${reason}"`); - const manifestDocument = makeManifestDocument( - landingZoneConfiguration, - 'UPDATE', - landingZoneDetails.kmsKeyArn, - landingZoneDetails.sandboxOuName, - ); - - const response = await throttlingBackOff(() => - client.send( - new UpdateLandingZoneCommand({ - version: targetVersion, - landingZoneIdentifier: landingZoneDetails.landingZoneIdentifier, - manifest: manifestDocument, - }), - ), - ); - - const operationIdentifier = response.operationIdentifier; - - if (!operationIdentifier) { - logger.warn( - `AWS Control Tower Landing Zone update operation api didn't return operation identifier. API return ${operationIdentifier} for operation identifier`, - ); - throw new Error( - `AWS Control Tower Landing Zone update operation api didn't return operation identifier. Solution cannot verify successful completion of AWS Control Tower Landing Zone operation.`, - ); - } - - logger.info( - `The Landing Zone update operation successfully started, operation identifier is - ${operationIdentifier}`, - ); - - return operationIdentifier; - } -} - -/** - * ControlTowerPreRequisites an abstract class to perform AWS Control Tower pre-requisites - * - * @remarks - * The following activities are performed by this class - * - * - Validate AWS Organizations - * - Create AWS Control Tower Roles - * - Create AWS KMS CMK to encrypt AWS Control Tower resources - * - Create the shared accounts (LogArchive and Audit) - * - */ -abstract class ControlTowerPreRequisites { - /** - * Function to complete AWS Control Tower Landing Zone pre-requisites - * - * @remarks - * The following activities are performed by this function - * - * - Validate AWS Organizations - * - Create AWS Control Tower Roles - * - Create AWS KMS CMK to encrypt AWS Control Tower resources - * - Create the shared accounts (LogArchive and Audit) - * - * @param props {@link ModuleOptionsType} - * @param region string - * @param globalRegion string - * @param accountsConfig {@link AccountsConfig} - * @param landingZoneIdentifier string | undefined - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns metadata { kmsKeyArn: string } | undefined - */ - public static async completePreRequisites( - props: ModuleOptionsType, - region: string, - globalRegion: string, - accountsConfig: AccountsConfig, - landingZoneIdentifier?: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise<{ kmsKeyArn: string } | undefined> { - if (!landingZoneIdentifier) { - await Organization.ValidateOrganization( - globalRegion, - region, - props.solutionId, - props.partition, - { logArchive: accountsConfig.getLogArchiveAccount().email, audit: accountsConfig.getAuditAccount().email }, - managementAccountCredentials, - ); - - const managementAccountId = await Organization.getManagementAccountId( - globalRegion, - props.solutionId, - accountsConfig.getManagementAccount().email, - managementAccountCredentials, - ); - - if (!props.useExistingRole) { - await IamRole.createControlTowerRoles(props.partition, region, props.solutionId, managementAccountCredentials); - // giving time to complete Role creation completes, otherwise ValidationException - CUSTOMER_ASSUME_ROLE_FAILED error occurs - logger.info(`Created AWS Control Tower roles, sleeping for 5 minutes for role creations to complete.`); - await delay(5); - } - - // - // Do not create accounts for US GovCloud - // - if (props.partition !== 'aws-us-gov') { - await SharedAccount.createAccounts( - props.configDirPath, - globalRegion, - props.solutionId, - managementAccountCredentials, - ); - } - - const kmsKeyArn = await KmsKey.createControlTowerKey( - props.partition, - managementAccountId, - region, - props.solutionId, - managementAccountCredentials, - ); - return { kmsKeyArn }; - } - - return undefined; - } -} diff --git a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/iam-role.ts b/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/iam-role.ts deleted file mode 100644 index 014b0b1..0000000 --- a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/iam-role.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AttachRolePolicyCommand, - IAMClient, - CreateRoleCommand, - PutRolePolicyCommand, - waitUntilRoleExists, - GetRoleCommand, - NoSuchEntityException, -} from '@aws-sdk/client-iam'; - -import path from 'path'; -import * as winston from 'winston'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { AssumeRoleCredentialType } from '../../../common/resources'; - -/** - * IamRole abstract class to create AWS Control Tower Landing Zone IAM roles. - * - * @remarks - * If the following IAM roles do not exist, they will be created. If these are roles are present, solution will delete these roles and re-create as per AWS Control Tower Landing Zone requirement. - * - * - AWSControlTowerAdmin - * - AWSControlTowerCloudTrailRole - * - AWSControlTowerStackSetRole - * - AWSControlTowerConfigAggregatorRoleForOrganizations - * - * Please review the [document](https://docs.aws.amazon.com/controltower/latest/userguide/lz-api-prereques.html) for more information. - */ -export abstract class IamRole { - private static logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - - /** - * List of required AWS Control Tower Landing Zone service roles - */ - private static requiredControlTowerRoleNames = [ - 'AWSControlTowerAdmin', - 'AWSControlTowerCloudTrailRole', - 'AWSControlTowerStackSetRole', - 'AWSControlTowerConfigAggregatorRoleForOrganizations', - ]; - - /** - * Function to check if given role exists - * @param client {@link IAMClient} - * @param roleName string - * @returns status boolean - */ - private static async isRoleExists(client: IAMClient, roleName: string): Promise { - try { - const response = await throttlingBackOff(() => - client.send( - new GetRoleCommand({ - RoleName: roleName, - }), - ), - ); - - if (response.Role?.RoleName === roleName) { - return true; - } - return false; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e instanceof NoSuchEntityException) { - return false; - } - throw e; - } - } - - /** - * Function to check if AWS Control Tower Landing Zone roles exists - * @param client {@link IAMClient} - */ - private static async areControlTowerRoleExists(client: IAMClient): Promise<{ status: boolean; message: string[] }> { - const roleNames: string[] = []; - - for (const roleName of IamRole.requiredControlTowerRoleNames) { - if (await IamRole.isRoleExists(client, roleName)) { - roleNames.push(roleName); - } - } - - if (roleNames.length > 0) { - return { status: true, message: roleNames }; - } - - return { status: false, message: [] }; - } - - /** - * Function to create IAM Role - * @param client {@link IAMClient} - * @param roleName string - * @param assumeRolePrincipal string - */ - private static async createRole(client: IAMClient, roleName: string, assumeRolePrincipal: string): Promise { - IamRole.logger.info(`Creating AWS Control Tower Landing Zone role ${roleName}.`); - await throttlingBackOff(() => - client.send( - new CreateRoleCommand({ - RoleName: roleName, - Path: '/service-role/', - AssumeRolePolicyDocument: `{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Principal": {"Service": [ "${assumeRolePrincipal}"]}, "Action": "sts:AssumeRole"}]}`, - }), - ), - ); - const waiterState = await waitUntilRoleExists({ client, maxWaitTime: 300 }, { RoleName: roleName }); - if (waiterState.state !== 'SUCCESS') { - throw new Error(`AWS Control Tower Landing Zone role ${roleName} creation not completed!!`); - } - } - - /** - * Function to create given AWS Control Tower Landing Zone IAM role and set policy according to AWS Control Tower Landing Zone requirement. - * @param client {@link IAMClient} - * @param partition string - * @param roleName string - */ - private static async createControlTowerRole(client: IAMClient, partition: string, roleName: string): Promise { - switch (roleName) { - case 'AWSControlTowerAdmin': - await IamRole.createRole(client, roleName, 'controltower.amazonaws.com'); - await throttlingBackOff(() => - client.send( - new PutRolePolicyCommand({ - RoleName: roleName, - PolicyName: 'AWSControlTowerAdminPolicy', - PolicyDocument: - '{"Version": "2012-10-17","Statement": [{"Action": "ec2:DescribeAvailabilityZones","Resource": "*","Effect": "Allow"}]}', - }), - ), - ); - await throttlingBackOff(() => - client.send( - new AttachRolePolicyCommand({ - RoleName: roleName, - PolicyArn: `arn:${partition}:iam::aws:policy/service-role/AWSControlTowerServiceRolePolicy`, - }), - ), - ); - break; - case 'AWSControlTowerCloudTrailRole': - await IamRole.createRole(client, roleName, 'cloudtrail.amazonaws.com'); - await throttlingBackOff(() => - client.send( - new PutRolePolicyCommand({ - RoleName: roleName, - PolicyName: 'AWSControlTowerCloudTrailRolePolicy', - PolicyDocument: `{"Version": "2012-10-17","Statement": [{"Action": "logs:CreateLogStream","Resource": "arn:${partition}:logs:*:*:log-group:aws-controltower/CloudTrailLogs:*","Effect": "Allow"},{"Action": "logs:PutLogEvents","Resource": "arn:${partition}:logs:*:*:log-group:aws-controltower/CloudTrailLogs:*","Effect": "Allow"}]}`, - }), - ), - ); - break; - case 'AWSControlTowerStackSetRole': - await IamRole.createRole(client, roleName, 'cloudformation.amazonaws.com'); - await throttlingBackOff(() => - client.send( - new PutRolePolicyCommand({ - RoleName: roleName, - PolicyName: 'AWSControlTowerStackSetRolePolicy', - PolicyDocument: `{"Version": "2012-10-17","Statement": [{"Action": ["sts:AssumeRole"],"Resource": ["arn:${partition}:iam::*:role/AWSControlTowerExecution"],"Effect": "Allow"}]}`, - }), - ), - ); - break; - case 'AWSControlTowerConfigAggregatorRoleForOrganizations': - await IamRole.createRole(client, roleName, 'config.amazonaws.com'); - await throttlingBackOff(() => - client.send( - new AttachRolePolicyCommand({ - RoleName: roleName, - PolicyArn: `arn:${partition}:iam::aws:policy/service-role/AWSConfigRoleForOrganizations`, - }), - ), - ); - break; - } - - IamRole.logger.info(`AWS Control Tower Landing Zone role ${roleName} created successfully.`); - } - - /** - * Function to create AWS Control Tower Landing Zone roles - * @param partition string - * @param region string - * @param solutionId string - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - */ - public static async createControlTowerRoles( - partition: string, - region: string, - solutionId: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const client: IAMClient = new IAMClient({ - region: region, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - const existingRoles = await IamRole.areControlTowerRoleExists(client); - - if (existingRoles.status && existingRoles.message.length > 0) { - throw new Error( - `There are existing AWS Control Tower Landing Zone roles "${existingRoles.message.join( - ',', - )}", the solution cannot deploy AWS Control Tower Landing Zone`, - ); - } - - for (const roleName of IamRole.requiredControlTowerRoleNames) { - await IamRole.createControlTowerRole(client, partition, roleName); - } - } -} diff --git a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/kms-key.ts b/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/kms-key.ts deleted file mode 100644 index 12f5c0f..0000000 --- a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/kms-key.ts +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - AliasListEntry, - KMSClient, - CreateAliasCommand, - CreateKeyCommand, - paginateListAliases, - PutKeyPolicyCommand, -} from '@aws-sdk/client-kms'; - -import path from 'path'; -import * as winston from 'winston'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { PolicyDocument } from '../utils/resources'; -import { AssumeRoleCredentialType } from '../../../common/resources'; - -/** - * KmsKey abstract class to create AWS Control Tower Landing Zone AWS KMS CMK to encrypt AWS Control Tower Landing Zone resources. - * - * @remarks - * If it does not already exist, an AWS KMS CMK with the alias ```alias/aws-controltower/key``` will be created. - * - */ -export abstract class KmsKey { - private static logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - - /** - * Function to get CMK aliases - * @param client {@link KMSClient} - * @returns aliases {@link AliasListEntry}[] - */ - private static async getAliases(client: KMSClient): Promise { - const aliases: AliasListEntry[] = []; - const paginator = paginateListAliases({ client: client }, {}); - for await (const page of paginator) { - for (const alias of page.Aliases ?? []) { - aliases.push(alias); - } - } - return aliases; - } - /** - * Function to check AWS KMS CMK alias ```alias/aws-controltower/key``` is already used. This alias is reserved for AWS Control Tower Landing Zone CMK created the solution. - * @param client {@link KMSClient} - * @param keyAlias string - * @returns status boolean - */ - private static async isKeyAliasExists(client: KMSClient, keyAlias: string): Promise { - const aliasItems = await KmsKey.getAliases(client); - for (const aliasItem of aliasItems) { - if (aliasItem.AliasName === keyAlias) { - return true; - } - } - return false; - } - - /** - * Function to create AWS Control Tower Landing Zone AWS KMS CMK - * @param partition string - * @param accountId string - * @param region string - * @returns keyArn string - * @param solutionId string - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns keyArn string - */ - public static async createControlTowerKey( - partition: string, - accountId: string, - region: string, - solutionId: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const keyAlias = `alias/aws-controltower/key`; - const client: KMSClient = new KMSClient({ - region, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - if (await KmsKey.isKeyAliasExists(client, keyAlias)) { - throw new Error( - `There is already an AWS Control Tower Landing Zone KMS CMK alias named ${keyAlias}. The alias ${keyAlias} is reserved for AWS Control Tower Landing Zone CMK created by the solution, the solution cannot deploy AWS Control Tower Landing Zone.`, - ); - } - - KmsKey.logger.info(`AWS Control Tower Landing Zone encryption key creation started`); - const response = await throttlingBackOff(() => - client.send( - new CreateKeyCommand({ - Description: 'AWS Control Tower Landing Zone encryption key', - KeyUsage: 'ENCRYPT_DECRYPT', - KeySpec: 'SYMMETRIC_DEFAULT', - }), - ), - ); - - const keyId = response.KeyMetadata!.KeyId!; - const keyArn = response.KeyMetadata!.Arn!; - - KmsKey.logger.info(`AWS Control Tower Landing Zone encryption key creation completed, key arn is ${keyArn}`); - - const keyPolicyJson: PolicyDocument = { - Version: '2012-10-17', - Id: 'AWSControlTowerPolicy', - Statement: [ - { - Sid: 'Enable IAM User Permissions', - Effect: 'Allow', - Principal: { - AWS: `arn:${partition}:iam::${accountId}:root`, - }, - Action: 'kms:*', - Resource: '*', - }, - { - Sid: 'Allow CloudTrail to encrypt/decrypt logs', - Effect: 'Allow', - Principal: { - Service: 'cloudtrail.amazonaws.com', - }, - Action: ['kms:GenerateDataKey*', 'kms:Decrypt'], - Resource: `${keyArn}`, - Condition: { - StringEquals: { - 'AWS:SourceArn': `arn:${partition}:cloudtrail:${region}:${accountId}:trail/aws-controltower-BaselineCloudTrail`, - }, - StringLike: { - 'kms:EncryptionContext:aws:cloudtrail:arn': `arn:${partition}:cloudtrail:*:${accountId}:trail/*`, - }, - }, - }, - { - Sid: 'Allow AWS Config to encrypt/decrypt logs', - Effect: 'Allow', - Principal: { - Service: 'config.amazonaws.com', - }, - Action: ['kms:GenerateDataKey', 'kms:Decrypt'], - Resource: `${keyArn}`, - }, - ], - }; - - await throttlingBackOff(() => - client.send( - new PutKeyPolicyCommand({ - KeyId: keyId, - PolicyName: 'default', - Policy: JSON.stringify(keyPolicyJson), - }), - ), - ); - - await throttlingBackOff(() => - client.send( - new CreateAliasCommand({ - AliasName: keyAlias, - TargetKeyId: response.KeyMetadata!.KeyId!, - }), - ), - ); - - KmsKey.logger.info( - `AWS Control Tower Landing Zone encryption key creation completed successfully. Key arn is ${keyArn}`, - ); - - return keyArn; - } -} diff --git a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/organization.ts b/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/organization.ts deleted file mode 100644 index 62df8f9..0000000 --- a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/organization.ts +++ /dev/null @@ -1,377 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - DescribeOrganizationCommand, - ListRootsCommand, - OrganizationsClient, - OrganizationFeatureSet, - OrganizationalUnit, - paginateListAccounts, - EnableAllFeaturesCommand, - Account, - AWSOrganizationsNotInUseException, - paginateListAWSServiceAccessForOrganization, - EnabledServicePrincipal, -} from '@aws-sdk/client-organizations'; -import { InstanceMetadata, paginateListInstances, SSOAdminClient } from '@aws-sdk/client-sso-admin'; - -import path from 'path'; -import * as winston from 'winston'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; - -import { OrganizationRootType } from '../utils/resources'; -import { getOrganizationalUnitsForParent } from '../../../common/functions'; -import { AssumeRoleCredentialType } from '../../../common/resources'; - -/** - * Organization abstract class to get AWS Organizations details and create AWS Organizations if not exists - */ -export abstract class Organization { - private static logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - - /** - * Function to check if AWS Organizations is configured - * @param client {@link OrganizationsClient} - * @returns status boolean - */ - private static async isOrganizationNotConfigured(client: OrganizationsClient): Promise { - try { - const response = await throttlingBackOff(() => client.send(new DescribeOrganizationCommand({}))); - - if (response.Organization?.Id) { - Organization.logger.info(`AWS Organizations already configured`); - return false; - } - return true; - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e instanceof AWSOrganizationsNotInUseException) { - return true; - } - throw e; - } - } - /** - * Function to get list of services enabled in AWS Organizations - * @param client {@link OrganizationsClient} - * @returns enabledServicePrincipals {@link EnabledServicePrincipal}[] - */ - private static async getOrganizationEnabledServices(client: OrganizationsClient): Promise { - const enabledServicePrincipals: EnabledServicePrincipal[] = []; - - const paginator = paginateListAWSServiceAccessForOrganization({ client }, {}); - for await (const page of paginator) { - for (const enabledServicePrincipal of page.EnabledServicePrincipals ?? []) { - enabledServicePrincipals.push(enabledServicePrincipal); - } - } - return enabledServicePrincipals; - } - - /** - * Function to check if any services are enabled in AWS Organizations - * @param client {@link OrganizationsClient} - * @returns status boolean - */ - private static async isAnyOrganizationServiceEnabled(client: OrganizationsClient): Promise { - const enabledServicePrincipals = await this.getOrganizationEnabledServices(client); - if (enabledServicePrincipals.length > 0) { - Organization.logger.warn( - `AWS Organizations have multiple services enabled "${enabledServicePrincipals - .map(item => item.ServicePrincipal) - .join(',')}", the solution cannot deploy AWS Control Tower Landing Zone.`, - ); - return true; - } - - return false; - } - - /** - * Function to check if AWS Organizations have any organizational units - * @param client {@link OrganizationsClient} - * @returns status boolean - */ - private static async isOrganizationsHaveOrganizationalUnits(client: OrganizationsClient): Promise { - const organizationalUnitsForRoot = await Organization.getOrganizationalUnitsForRoot(client); - - if (organizationalUnitsForRoot.length !== 0) { - Organization.logger.warn( - `AWS Organizations have multiple organization units "${organizationalUnitsForRoot - .map(item => item.Name) - .join(',')}", the solution cannot deploy AWS Control Tower Landing Zone.`, - ); - return true; - } - - return false; - } - - /** - * Function to check if AWS Organizations have any accounts other than management account. - * - * @remarks - * GovCloud (US) will have shared accounts within the AWS Organizations, for AWS standard partition these accounts will be created by the solution - * @param client {@link OrganizationsClient} - * @param partition string - * @param sharedAccountEmail - * @returns status boolean - */ - private static async isOrganizationHaveAdditionalAccounts( - client: OrganizationsClient, - partition: string, - sharedAccountEmail: { logArchive: string; audit: string }, - ): Promise { - const accounts = await Organization.getOrganizationAccounts(client); - - switch (partition) { - case 'aws-us-gov': - const logArchiveAccount = accounts.find(item => item.Email === sharedAccountEmail.logArchive); - const auditAccount = accounts.find(item => item.Email === sharedAccountEmail.audit); - - if (accounts.length === 3 && logArchiveAccount && auditAccount) { - return false; - } else { - Organization.logger.warn( - `Either AWS Organizations does not have required shared accounts (LogArchive and Audit) or have other accounts. Existing AWS Organizations accounts are - "${accounts - .map(account => account.Name + ' -> ' + account.Email) - .join(',')}", the solution cannot deploy AWS Control Tower Landing Zone.`, - ); - return true; - } - default: - if (accounts.length > 1) { - Organization.logger.warn( - `AWS Organizations have multiple accounts "${accounts - .map(account => account.Name + ' -> ' + account.Email) - .join(',')}", the solution cannot deploy AWS Control Tower Landing Zone.`, - ); - return true; - } else { - return false; - } - } - } - - /** - * Function to get list of the AWS IAM Identity Center instances - * @param region string - * @param solutionId string - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns instances {@link InstanceMetadata}[] - */ - private static async getIdentityCenterInstances( - region: string, - solutionId: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const client = new SSOAdminClient({ - region, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - const instances: InstanceMetadata[] = []; - - const paginator = paginateListInstances({ client }, {}); - for await (const page of paginator) { - for (const instance of page.Instances ?? []) { - instances.push(instance); - } - } - return instances; - } - - /** - * Function to check if IAM Identity Center is enabled - * @param region string - * @param solutionId string - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns status boolean - */ - private static async isIdentityCenterEnabled( - region: string, - solutionId: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const instances = await Organization.getIdentityCenterInstances(region, solutionId, managementAccountCredentials); - if (instances.length > 0) { - Organization.logger.warn( - `AWS Organizations have IAM Identity Center enabled "${instances - .map(instance => instance.IdentityStoreId) - .join(',')}", the solution cannot deploy AWS Control Tower Landing Zone.`, - ); - return true; - } - return false; - } - - /** - * Function to get Organizational units for root - * - * @param client {@link OrganizationsClient} - */ - private static async getOrganizationalUnitsForRoot( - client: OrganizationsClient, - rootId?: string, - ): Promise { - const parentId = rootId ?? (await Organization.getOrganizationsRoot(client)).Id; - return getOrganizationalUnitsForParent(client, parentId); - } - - /** - * Function to get AWS Organizations Root details - * - * @param client {@link OrganizationsClient} - * @returns organizationRoot {@link OrganizationRootType} - */ - public static async getOrganizationsRoot(client: OrganizationsClient): Promise { - const response = await throttlingBackOff(() => client.send(new ListRootsCommand({}))); - return { Name: response.Roots![0].Name!, Id: response.Roots![0].Id! }; - } - - /** - * Function to enable all features for AWS Organization if not enabled already. - * @param client {@link OrganizationsClient} - */ - private static async enableOrganizationsAllFeature(client: OrganizationsClient): Promise { - const response = await throttlingBackOff(() => client.send(new DescribeOrganizationCommand({}))); - if (response.Organization?.FeatureSet !== OrganizationFeatureSet.ALL) { - Organization.logger.warn( - `The existing AWS Organization ${response.Organization?.Id} does not have all features enabled. The solution will update your organization so that all features are enabled.`, - ); - await throttlingBackOff(() => client.send(new EnableAllFeaturesCommand({}))); - } - } - - /** - * Function to retrieve AWS organizations accounts - * @param client {@link OrganizationsClient} - * @returns accounts {@link Account}[] - */ - public static async getOrganizationAccounts(client: OrganizationsClient): Promise { - const organizationAccounts: Account[] = []; - const paginator = paginateListAccounts({ client }, {}); - for await (const page of paginator) { - for (const account of page.Accounts ?? []) { - organizationAccounts.push(account); - } - } - return organizationAccounts; - } - - /** - * Function to get management account id - * @param globalRegion string - * @parm solutionId string - * @parm email string - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - * @returns accountId string - */ - public static async getManagementAccountId( - globalRegion: string, - solutionId: string, - email: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const client: OrganizationsClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - const accounts = await Organization.getOrganizationAccounts(client); - - for (const account of accounts) { - if (account.Id && account.Email === email) { - return account.Id; - } - } - throw new Error(`Management account with email ${email} not found`); - } - - /** - * Function to validate AWS Organizations - * - * @param globalRegion string - * @param region string - * @param solutionId string - * @param sharedAccountEmail - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - */ - public static async ValidateOrganization( - globalRegion: string, - region: string, - solutionId: string, - partition: string, - sharedAccountEmail: { logArchive: string; audit: string }, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const client: OrganizationsClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - const validationErrors: string[] = []; - - if (await Organization.isIdentityCenterEnabled(region, solutionId, managementAccountCredentials)) { - validationErrors.push(`AWS Control Tower Landing Zone cannot deploy because IAM Identity Center is configured.`); - } - - if (await Organization.isOrganizationNotConfigured(client)) { - validationErrors.push( - `AWS Control Tower Landing Zone cannot deploy because AWS Organizations have not been configured for the environment.`, - ); - } else { - if (await Organization.isAnyOrganizationServiceEnabled(client)) { - validationErrors.push( - `AWS Control Tower Landing Zone cannot deploy because AWS Organizations have services enabled.`, - ); - } - - if (await Organization.isOrganizationsHaveOrganizationalUnits(client)) { - validationErrors.push( - `AWS Control Tower Landing Zone cannot deploy because there are multiple organizational units in AWS Organizations.`, - ); - } - - if (await Organization.isOrganizationHaveAdditionalAccounts(client, partition, sharedAccountEmail)) { - if (partition === 'aws-us-gov') { - validationErrors.push( - `Either AWS Organizations does not have required shared accounts (LogArchive and Audit) or have other accounts.`, - ); - } else { - validationErrors.push( - `AWS Control Tower Landing Zone cannot deploy because there are multiple accounts in AWS Organizations.`, - ); - } - } - } - - if (validationErrors.length > 0) { - throw new Error( - `AWS Organization validation has ${validationErrors.length} issue(s):\n${validationErrors.join('\n')}`, - ); - } - - await Organization.enableOrganizationsAllFeature(client); - } -} diff --git a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/shared-account.ts b/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/shared-account.ts deleted file mode 100644 index 01b6e75..0000000 --- a/source/packages/@aws-accelerator/modules/lib/control-tower/prerequisites/shared-account.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - CreateAccountCommand, - CreateAccountStatus, - CreateAccountState, - DescribeCreateAccountStatusCommand, - OrganizationsClient, -} from '@aws-sdk/client-organizations'; - -import path from 'path'; -import * as winston from 'winston'; - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { setRetryStrategy } from '@aws-accelerator/utils/lib/common-functions'; -import { AccountsConfig } from '@aws-accelerator/config'; - -import { delay } from '../utils/resources'; -import { AssumeRoleCredentialType } from '../../../common/resources'; - -type AccountDetailsType = { name: string; email: string }; - -type AccountCreationStatusType = { name: string; status: string; reason: string; id?: string }; - -/** - * SharedAccount abstract class to create AWS Control Tower Landing Zone shared accounts - * - * @remarks - * This class will create shared AWS accounts. - * - */ -export abstract class SharedAccount { - private static logger: winston.Logger = createLogger([path.parse(path.basename(__filename)).name]); - - /** - * Function to get shared account details form account configuration file - * @param configDirPath string - * @returns accounts {@link AccountDetailsType}[] - */ - private static getSharedAccountsDetails(configDirPath: string): AccountDetailsType[] { - const accountsConfig = AccountsConfig.load(configDirPath); - const sharedAccounts: AccountDetailsType[] = []; - - for (const mandatoryAccount of accountsConfig.mandatoryAccounts) { - if (mandatoryAccount.name !== 'Management') { - sharedAccounts.push({ name: mandatoryAccount.name, email: mandatoryAccount.email }); - } - } - - if (sharedAccounts.length !== 2) { - throw new Error(`accounts-config.yaml file do not have both shared account (LogArchive and Audit) details.`); - } - - return sharedAccounts; - } - - /** - * Function to create shared account - * @param client {@link OrganizationsClient} - * @param accountDetails {@link AccountDetailsType} - * @returns status {@link AccountCreationStatusType} - */ - private static async createAccount( - client: OrganizationsClient, - accountDetails: AccountDetailsType, - ): Promise { - const response = await throttlingBackOff(() => - client.send( - new CreateAccountCommand({ - Email: accountDetails.email, - AccountName: accountDetails.name, - }), - ), - ); - - SharedAccount.logger.info( - `Shared account ${accountDetails.name} creation started, request id is ${response.CreateAccountStatus!.Id}.`, - ); - - if (response.CreateAccountStatus?.State === CreateAccountState.FAILED) { - return { - name: response.CreateAccountStatus.AccountName!, - status: response.CreateAccountStatus.State!, - id: response.CreateAccountStatus.AccountId!, - reason: `${response.CreateAccountStatus.AccountName!} creation is currently in ${response.CreateAccountStatus - .State!} state with ${response.CreateAccountStatus.FailureReason} error`, - }; - } - - return await SharedAccount.waitUntilAccountCreationCompletes(client, response.CreateAccountStatus!); - } - - /** - * Function to check account creation completion and wait till account creation completes - * @param client {@link CreateAccountStatus} - * @param createAccountStatus {@link CreateAccountStatus} - * @returns creationStatus {@link SharedAccountCreationStatusType} - */ - private static async waitUntilAccountCreationCompletes( - client: OrganizationsClient, - createAccountStatus: CreateAccountStatus, - ): Promise { - let createAccountState = createAccountStatus.State!; - let createAccountRequestId = createAccountStatus.Id!; - const queryIntervalInMinutes = 1; - - while (createAccountState === CreateAccountState.IN_PROGRESS) { - SharedAccount.logger.warn( - `Shared account ${createAccountStatus.AccountName} creation is currently in ${createAccountState}.state. After ${queryIntervalInMinutes} minutes delay, the status will be rechecked.`, - ); - await delay(queryIntervalInMinutes); - const response = await throttlingBackOff(() => - client.send( - new DescribeCreateAccountStatusCommand({ - CreateAccountRequestId: createAccountRequestId, - }), - ), - ); - createAccountRequestId = createAccountStatus.Id!; - createAccountState = response.CreateAccountStatus!.State!; - - if (createAccountState === CreateAccountState.FAILED) { - return { - name: createAccountStatus.AccountName!, - status: createAccountState, - id: response.CreateAccountStatus!.AccountId!, - reason: `${createAccountStatus.AccountName} creation is currently in ${createAccountState} state with ${response.CreateAccountStatus?.FailureReason} error`, - }; - } - } - - SharedAccount.logger.info(`Shared account ${createAccountStatus.AccountName} creation completed successfully.`); - return { - name: createAccountStatus.AccountName!, - status: createAccountState, - id: createAccountStatus.AccountId!, - reason: `${createAccountStatus.AccountName} creation successful`, - }; - } - - /** - * Function to create AWS Control Tower Landing Zone shared accounts (LogArchive and Audit) - * @param configDirPath string - * @param globalRegion string - * @param solutionId string - * @param managementAccountCredentials {@link AssumeRoleCredentialType} | undefined - */ - public static async createAccounts( - configDirPath: string, - globalRegion: string, - solutionId: string, - managementAccountCredentials?: AssumeRoleCredentialType, - ): Promise { - const client: OrganizationsClient = new OrganizationsClient({ - region: globalRegion, - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - credentials: managementAccountCredentials, - }); - - const accountCreationStatuses: AccountCreationStatusType[] = []; - const errors: string[] = []; - - const sharedAccountsDetails = SharedAccount.getSharedAccountsDetails(configDirPath); - - for (const sharedAccountsDetail of sharedAccountsDetails) { - const accountCreationStatus = await SharedAccount.createAccount(client, sharedAccountsDetail); - accountCreationStatuses.push(accountCreationStatus); - if (accountCreationStatus.status === CreateAccountState.FAILED) { - errors.push(accountCreationStatus.reason); - } - } - - if (errors.length > 0) { - throw new Error(`Shared account creation failure !!! ${errors.join('. ')}`); - } - } -} diff --git a/source/packages/@aws-accelerator/modules/lib/control-tower/utils/resources.ts b/source/packages/@aws-accelerator/modules/lib/control-tower/utils/resources.ts deleted file mode 100644 index a312d2f..0000000 --- a/source/packages/@aws-accelerator/modules/lib/control-tower/utils/resources.ts +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { PolicyStatementType } from '@aws-accelerator/utils'; -import { LandingZoneDriftStatus } from '@aws-sdk/client-controltower'; - -/** - * AWS Organization Root config type - */ -export type OrganizationRootType = { - Name: string; - Id: string; -}; - -/** - * Policy Document - */ -export type PolicyDocument = { - Version: string; - Id?: string; - Statement: PolicyStatementType[]; -}; - -export type LandingZoneUpdateOrResetRequiredType = { - updateRequired: boolean; - targetVersion: string; - resetRequired: boolean; - reason: string; -}; - -/** - * ControlTowerLandingZoneProps - */ -export interface ControlTowerLandingZoneConfigType { - /** - * AWS Control Tower Landing Zone version, this must be latest version for creating any new AWS Control Tower Landing Zone - * - * @remarks - * AWS Control Tower Landing Zone version value must be latest version of the AWS Control Tower Landing Zone. - * You can refer [AWS Control Tower release notes](https://docs.aws.amazon.com/controltower/latest/userguide/release-notes.html) for more information. - */ - readonly version: string; - /** - * List of AWS Regions governed by the AWS Control Tower Landing Zone - */ - readonly governedRegions: string[]; - /** - * Log archive AWS account ID - */ - readonly logArchiveAccountId: string; - /** - * Audit AWS account ID - */ - readonly auditAccountId: string; - /** - * Flag indicating weather AWS Control Tower sets up AWS account access with IAM Identity Center or not - */ - readonly enableIdentityCenterAccess: boolean; - /** - * AWS Control Tower Landing Zone central logging bucket retention in days - */ - readonly loggingBucketRetentionDays: number; - /** - * AWS Control Tower Landing Zone access logging bucket retention in days - */ - readonly accessLoggingBucketRetentionDays: number; - /** - * Flag indicating Organization level CloudTrail is enable or not. - */ - readonly enableOrganizationTrail: boolean; -} - -/** - * AWS Control Tower Landing Zone details type. - */ -export type ControlTowerLandingZoneDetailsType = { - /** - * AWS Control Tower Landing Zone identifier - * - * @remarks - * AWS Control Tower Landing Zone arn - * - * @remarks - * AWS Control Tower Landing Zone arn - */ - landingZoneIdentifier: string; - /** - * AWS Control Tower Landing Zone deployment status. - * - * @remarks - * ACTIVE or FAILED or PROCESSING - */ - status?: string; - /** - * AWS Control Tower Landing Zone version - */ - version?: string; - /** - * The latest available version of AWS Control Tower Landing Zone. - */ - latestAvailableVersion?: string; - /** - * The drift status of AWS Control Tower Landing Zone. - * - * @remarks - * DRIFTED or IN_SYNC - */ - driftStatus?: string; - /** - * List of AWS Regions governed by AWS Control Tower Landing Zone - */ - governedRegions?: string[]; - /** - * The name of Security organization unit (OU) - */ - securityOuName?: string; - /** - * The name of Sandbox organization unit (OU) - */ - sandboxOuName?: string; - /** - * Flag indicating weather AWS Control Tower sets up AWS account access with IAM Identity Center or not - */ - enableIdentityCenterAccess?: boolean; - /** - * AWS Control Tower Landing Zone central logging bucket retention in days - */ - loggingBucketRetentionDays?: number; - /** - * AWS Control Tower Landing Zone access logging bucket retention in days - */ - accessLoggingBucketRetentionDays?: number; - /** - * AWS KMS CMK arn to encrypt AWS Control Tower Landing Zone resources - */ - kmsKeyArn?: string; -}; - -/** - * Function to make manifest document for CT API - * @param landingZoneConfiguration ${@link LandingZoneConfigType} - * @param event string 'CREATE' | 'UPDATE' - * @param sandboxOuName string - * @returns - */ -export function makeManifestDocument( - landingZoneConfiguration: ControlTowerLandingZoneConfigType, - event: 'CREATE' | 'UPDATE', - kmsKeyArn?: string, - sandboxOuName?: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): any { - let organizationStructure = {}; - - if (event === 'CREATE') { - organizationStructure = { - security: { - name: 'Security', - }, - sandbox: { - name: 'Infrastructure', - }, - }; - } - - if (event === 'UPDATE') { - if (sandboxOuName) { - organizationStructure = { - security: { - name: 'Security', - }, - sandbox: { - name: sandboxOuName, - }, - }; - } else { - organizationStructure = { - security: { - name: 'Security', - }, - }; - } - } - const manifestJsonDocument = { - governedRegions: landingZoneConfiguration.governedRegions, - organizationStructure, - centralizedLogging: { - accountId: landingZoneConfiguration.logArchiveAccountId, - configurations: { - loggingBucket: { - retentionDays: landingZoneConfiguration.loggingBucketRetentionDays, - }, - accessLoggingBucket: { - retentionDays: landingZoneConfiguration.accessLoggingBucketRetentionDays, - }, - kmsKeyArn, - }, - enabled: landingZoneConfiguration.enableOrganizationTrail, - }, - securityRoles: { - accountId: landingZoneConfiguration.auditAccountId, - }, - accessManagement: { - enabled: landingZoneConfiguration.enableIdentityCenterAccess, - }, - }; - - return manifestJsonDocument; -} - -/** - * This function compares the AWS Region list from configuration with the existing AWS Control Tower Landing Zone govern region list - * @param existingRegions string[] - * @param configRegions string[] - * @returns status boolean - */ -export function isGovernedRegionsChanged(existingRegions: string[], configRegions: string[]): boolean { - return !( - existingRegions.length === configRegions.length && existingRegions.every(region => configRegions.includes(region)) - ); -} - -/** - * Function to validate AWS Control Tower Landing Zone organization version provided in global config file is latest version - * @param configVersion string - * @param latestVersion string - * @param reason string | undefined - * @param operationType string | undefined - */ -function validateLandingZoneVersion( - configVersion: string, - latestVersion: string, - reason?: string, - operationType?: string, -): void { - if (latestVersion !== configVersion) { - if (reason && operationType) { - throw new Error( - `It is necessary to ${operationType} the AWS Control Tower Landing Zone because "${reason}". AWS Control Tower Landing Zone's most recent version is ${latestVersion}, which is different from the version ${configVersion} specified in global-config.yaml file. AWS Control Tower Landing Zone can be ${ - operationType === 'update' ? 'updated' : operationType - } when you specify the latest version in the configuration.`, - ); - } else { - throw new Error( - `AWS Control Tower Landing Zone's most recent version is ${latestVersion}, which is different from the version ${configVersion} specified in global-config.yaml file, execution terminated.`, - ); - } - } -} -/** - * Function to check if the AWS Control Tower Landing Zone is required to update or reset - * @param landingZoneConfiguration ${@link LandingZoneConfigType} - * @param landingZoneDetails ${@link ControlTowerLandingZoneDetailsType} - * @returns landingZoneUpdateOrResetRequired {@link LandingZoneUpdateOrResetRequiredType} - */ -export function isLandingZoneUpdateOrResetRequired( - landingZoneConfiguration: ControlTowerLandingZoneConfigType, - landingZoneDetails: ControlTowerLandingZoneDetailsType, -): LandingZoneUpdateOrResetRequiredType { - // validate landing zone version listed in global config - validateLandingZoneVersion(landingZoneConfiguration.version, landingZoneDetails.latestAvailableVersion!); - - //when drifted - if (landingZoneDetails.driftStatus === LandingZoneDriftStatus.DRIFTED) { - const reason = 'The Landing Zone has drifted'; - validateLandingZoneVersion( - landingZoneConfiguration.version, - landingZoneDetails.latestAvailableVersion!, - reason, - 'reset', - ); - - return { - updateRequired: false, - targetVersion: landingZoneDetails.latestAvailableVersion!, - resetRequired: true, - reason, - }; - } - - // Changes in the AWS Control Tower Landing Zone configuration force an update of the AWS Control Tower Landing Zone, which will update the AWS Control Tower Landing Zone to the latest version if it is available - // find reasons to update - const reasons: string[] = []; - if ( - landingZoneDetails.accessLoggingBucketRetentionDays !== landingZoneConfiguration.accessLoggingBucketRetentionDays - ) { - reasons.push( - `Changes made in AccessLoggingBucketRetentionDays from ${landingZoneDetails.accessLoggingBucketRetentionDays} to ${landingZoneConfiguration.accessLoggingBucketRetentionDays}`, - ); - } - if (landingZoneDetails.loggingBucketRetentionDays !== landingZoneConfiguration.loggingBucketRetentionDays) { - reasons.push( - `Changes made in LoggingBucketRetentionDays from ${landingZoneDetails.loggingBucketRetentionDays} to ${landingZoneConfiguration.loggingBucketRetentionDays}`, - ); - } - if (landingZoneDetails.enableIdentityCenterAccess !== landingZoneConfiguration.enableIdentityCenterAccess) { - reasons.push( - `Changes made in EnableIdentityCenterAccess from ${landingZoneDetails.enableIdentityCenterAccess} to ${landingZoneConfiguration.enableIdentityCenterAccess}`, - ); - } - - if (isGovernedRegionsChanged(landingZoneDetails.governedRegions ?? [], landingZoneConfiguration.governedRegions)) { - reasons.push( - `Changes made in governed regions from [${landingZoneDetails.governedRegions?.join( - ',', - )}] to [${landingZoneConfiguration.governedRegions.join(',')}]`, - ); - } - - if (reasons.length > 0) { - validateLandingZoneVersion( - landingZoneConfiguration.version, - landingZoneDetails.latestAvailableVersion!, - reasons.join('. '), - 'update', - ); - - return { - updateRequired: true, - targetVersion: landingZoneDetails.latestAvailableVersion!, - resetRequired: false, - reason: `${reasons.join('. ')}`, - }; - } - - return { - updateRequired: false, - targetVersion: landingZoneDetails.latestAvailableVersion!, - resetRequired: false, - reason: 'There were no changes found to update or reset the Landing Zone.', - }; -} - -/** - * Function to sleep process - * @param ms - * @returns - */ -export function delay(minutes: number) { - return new Promise(resolve => setTimeout(resolve, minutes * 60000)); -} diff --git a/source/packages/@aws-accelerator/modules/lib/module-runner.ts b/source/packages/@aws-accelerator/modules/lib/module-runner.ts deleted file mode 100644 index 286576a..0000000 --- a/source/packages/@aws-accelerator/modules/lib/module-runner.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AcceleratorModuleName, ModuleRunnerParametersType } from '../common/resources'; -import { AWSOrganization } from './aws-organization'; -import { ControlTowerLandingZone } from './control-tower/index'; - -/** - * ModuleRunner abstract class to execute accelerator modules. - */ -export abstract class ModuleRunner { - /** - * Function to execute module specific handler - * @param runnerParams {@link ModuleRunnerParametersType} - * @returns status string - */ - public static async execute(runnerParams: ModuleRunnerParametersType): Promise { - switch (runnerParams.module) { - case AcceleratorModuleName.CONTROL_TOWER: - return new ControlTowerLandingZone().handler(runnerParams.module, runnerParams.options); - case AcceleratorModuleName.AWS_ORGANIZATIONS: - return new AWSOrganization().handler(runnerParams.module, runnerParams.options); - default: - throw new Error(`Invalid module name "${runnerParams.module}".`); - } - } -} diff --git a/source/packages/@aws-accelerator/modules/package.json b/source/packages/@aws-accelerator/modules/package.json deleted file mode 100644 index 8e231d3..0000000 --- a/source/packages/@aws-accelerator/modules/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "@aws-accelerator/modules", - "version": "0.0.0", - "description": "The core of the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/packages/@aws-accelerator/modules/bin/runner.js", - "types": "dist/packages/@aws-accelerator/modules/bin/runner.d.ts", - "private": true, - "scripts": { - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist", - "cleanup:tsc": "tsc --build ./ --clean", - "build": "tsc", - "watch": "tsc -w", - "test": "jest --coverage --ci", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'" - }, - "dependencies": { - "@aws-accelerator/config": "^0.0.0", - "@aws-accelerator/utils": "^0.0.0", - "@aws-sdk/client-controltower":"3.556.0", - "@aws-sdk/client-iam": "3.410.0", - "@aws-sdk/client-sso-admin":"3.410.0", - "@aws-sdk/client-kms": "3.410.0", - "@aws-sdk/client-ssm": "3.410.0", - "@aws-sdk/client-organizations": "3.410.0", - "@types/js-yaml": "4.0.5", - "@types/semver": "7.5.0", - "aws-lambda": "1.0.7", - "change-case": "4.1.2", - "fp-ts": "2.13.1", - "fs-extra": "11.1.0", - "hash-sum": "2.0.0", - "io-ts": "2.2.20", - "io-ts-types": "0.5.19", - "ip-num": "1.5.0", - "js-yaml": "4.1.0", - "monocle-ts": "2.3.13", - "newtype-ts": "0.3.5", - "pascal-case": "3.1.2", - "semver": "7.5.2", - "tempy": "3.0.0", - "winston": "3.8.2", - "yargs": "17.7.1" - }, - "devDependencies": { - "aws-sdk-client-mock":"3.0.1", - "@types/fs-extra": "11.0.1", - "@types/jest": "^29.4.0", - "@types/mri": "1.1.1", - "@types/node": "18.14.0", - "@types/promptly": "3.0.2", - "chokidar": "3.5.3", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-n": "15.6.1", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "eslint-plugin-promise": "6.1.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "mri": "1.2.0", - "prettier": "2.8.4", - "promptly": "3.2.0", - "proxy-agent": "6.3.0", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/modules/test/aws-organization/index.test.ts b/source/packages/@aws-accelerator/modules/test/aws-organization/index.test.ts deleted file mode 100644 index 8a0fe0c..0000000 --- a/source/packages/@aws-accelerator/modules/test/aws-organization/index.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, test } from '@jest/globals'; - -describe('Success', () => { - test('Mock Test', async () => { - // Control Tower API mock test not available currently - }); -}); diff --git a/source/packages/@aws-accelerator/modules/test/control-tower/iam-role.test.ts b/source/packages/@aws-accelerator/modules/test/control-tower/iam-role.test.ts deleted file mode 100644 index 1d2e99c..0000000 --- a/source/packages/@aws-accelerator/modules/test/control-tower/iam-role.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, beforeEach, expect, test } from '@jest/globals'; - -import { IamRole } from '../../lib/control-tower/prerequisites/iam-role'; -import { - IAMClient, - GetRoleCommand, - PutRolePolicyCommand, - AttachRolePolicyCommand, - CreateRoleCommand, - NoSuchEntityException, -} from '@aws-sdk/client-iam'; - -import { - AWSControlTowerAdmin, - AWSControlTowerRolePolicyDocument, - AcceleratorMockClient, - ExistingRoleFoundError, - MockInternalError, - Partition, - Region, - SolutionId, -} from '../utils/test-resources'; - -const client = AcceleratorMockClient(IAMClient); - -describe('Success', () => { - beforeEach(() => { - client.reset(); - }); - - test('Success - Create Role', async () => { - client.on(GetRoleCommand, {}).resolves({ - Role: undefined, - }); - - client - .on(CreateRoleCommand, { - RoleName: AWSControlTowerAdmin.RoleName, - Path: AWSControlTowerAdmin.Path, - AssumeRolePolicyDocument: AWSControlTowerRolePolicyDocument, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - client - .on(PutRolePolicyCommand, { - RoleName: AWSControlTowerAdmin.RoleName, - PolicyName: `${AWSControlTowerAdmin}Policy`, - PolicyDocument: AWSControlTowerRolePolicyDocument, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - client - .on(AttachRolePolicyCommand, { - RoleName: AWSControlTowerAdmin.RoleName, - PolicyArn: `${AWSControlTowerAdmin}Arn`, - }) - .resolves({}); - - expect(await IamRole.createControlTowerRoles(Partition, Region, SolutionId)).toBeUndefined(); - }); -}); - -describe('Failure', () => { - beforeEach(() => { - client.reset(); - }); - test('NoSuchEntityException', async () => { - client - .on(GetRoleCommand) - .rejectsOnce( - new NoSuchEntityException({ - $metadata: {}, - message: '', - }), - ) - .resolves({ - Role: undefined, - }); - - client - .on(CreateRoleCommand, { - RoleName: AWSControlTowerAdmin.RoleName, - Path: AWSControlTowerAdmin.Path, - AssumeRolePolicyDocument: AWSControlTowerRolePolicyDocument, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - client - .on(PutRolePolicyCommand, { - RoleName: AWSControlTowerAdmin.RoleName, - PolicyName: `${AWSControlTowerAdmin}Policy`, - PolicyDocument: AWSControlTowerRolePolicyDocument, - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - - client - .on(AttachRolePolicyCommand, { - RoleName: AWSControlTowerAdmin.RoleName, - PolicyArn: `${AWSControlTowerAdmin}Arn`, - }) - .resolves({}); - - expect(await IamRole.createControlTowerRoles(Partition, Region, SolutionId)).toBeUndefined(); - }); - - test('Existing Role found', async () => { - client.on(GetRoleCommand, {}).resolves({ - Role: AWSControlTowerAdmin, - }); - - await expect(IamRole.createControlTowerRoles(Partition, Region, SolutionId)).rejects.toThrow( - ExistingRoleFoundError([AWSControlTowerAdmin.RoleName]), - ); - }); - - test('Get role internal error', async () => { - client.on(GetRoleCommand).rejectsOnce(MockInternalError).resolves({ - Role: undefined, - }); - - await expect(IamRole.createControlTowerRoles(Partition, Region, SolutionId)).rejects.toThrow(MockInternalError); - }); -}); diff --git a/source/packages/@aws-accelerator/modules/test/control-tower/kms-key.test.ts b/source/packages/@aws-accelerator/modules/test/control-tower/kms-key.test.ts deleted file mode 100644 index 9ff179e..0000000 --- a/source/packages/@aws-accelerator/modules/test/control-tower/kms-key.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, beforeEach, expect, test } from '@jest/globals'; - -import { KmsKey } from '../../lib/control-tower/prerequisites/kms-key'; -import { KMSClient, CreateKeyCommand, paginateListAliases, ListAliasesCommand } from '@aws-sdk/client-kms'; -import { - AcceleratorKeyAlias, - AcceleratorMockClient, - AccountId, - AliasFoundError, - ControlTowerKeyAlias, - CreateKeyParams, - InstallerKeyAlias, - Partition, - Region, - SolutionId, -} from '../utils/test-resources'; - -const client = AcceleratorMockClient(KMSClient); -describe('Success', () => { - beforeEach(() => { - client.reset(); - }); - - test('Create CMK', async () => { - client.on(ListAliasesCommand, {}).resolves({ - Aliases: [AcceleratorKeyAlias, InstallerKeyAlias], - }); - const aliases: string[] = []; - const paginator = paginateListAliases({ client: new KMSClient({}), pageSize: 1 }, {}); - for await (const page of paginator) { - for (const alias of page.Aliases ?? []) { - aliases.push(alias.AliasName!); - } - } - client.on(CreateKeyCommand, CreateKeyParams).resolves({ - KeyMetadata: { KeyId: 'key-id', Arn: `arn:${Partition}:kms:${Region}:${AccountId}:key/key-id` }, - }); - - const keyArn = await KmsKey.createControlTowerKey(Partition, AccountId, Region, SolutionId); - - expect(keyArn).toStrictEqual(`arn:${Partition}:kms:${Region}:${AccountId}:key/key-id`); - }); -}); - -describe('Failure', () => { - beforeEach(() => { - client.reset(); - }); - - test('Existing alias found', async () => { - client.on(ListAliasesCommand, {}).resolves({ - Aliases: [AcceleratorKeyAlias, InstallerKeyAlias, ControlTowerKeyAlias], - }); - const aliases: string[] = []; - const paginator = paginateListAliases({ client: new KMSClient({}), pageSize: 1 }, {}); - for await (const page of paginator) { - for (const alias of page.Aliases ?? []) { - aliases.push(alias.AliasName!); - } - } - - await expect(KmsKey.createControlTowerKey(Partition, AccountId, Region, SolutionId)).rejects.toThrow( - AliasFoundError, - ); - }); -}); diff --git a/source/packages/@aws-accelerator/modules/test/control-tower/organization.test.ts b/source/packages/@aws-accelerator/modules/test/control-tower/organization.test.ts deleted file mode 100644 index 8f49a0b..0000000 --- a/source/packages/@aws-accelerator/modules/test/control-tower/organization.test.ts +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, beforeEach, expect, test } from '@jest/globals'; - -import { Organization } from '../../lib/control-tower/prerequisites/organization'; -import { - DescribeOrganizationCommand, - ListRootsCommand, - OrganizationsClient, - ListOrganizationalUnitsForParentCommand, - ListAccountsCommand, - AWSOrganizationsNotInUseException, - EnableAllFeaturesCommand, - ListAWSServiceAccessForOrganizationCommand, - EnabledServicePrincipal, - paginateListAWSServiceAccessForOrganization, -} from '@aws-sdk/client-organizations'; - -import { ListInstancesCommand, SSOAdminClient } from '@aws-sdk/client-sso-admin'; -import { - AcceleratorMockClient, - AllFeatureEnabledOrganizationConfig, - GlobalRegion, - ValidateOrganizationError, - RootOrganization, - SolutionId, - OrganizationValidationError, - AuditAccount, - LogArchiveAccount, - MockInternalError, - SecurityOuConfig, - BillingEnabledOrganizationConfig, - Region, - ManagementAccount, - ManagementAccountNotFoundError, - Partition, - GovCloud_US, -} from '../utils/test-resources'; - -const client = AcceleratorMockClient(OrganizationsClient); -const ssoAdminClient = AcceleratorMockClient(SSOAdminClient); - -describe('Success', () => { - beforeEach(() => { - client.reset(); - client.on(ListAWSServiceAccessForOrganizationCommand, {}).resolves({ EnabledServicePrincipals: [] }); - client.on(ListRootsCommand, {}).resolves({ Roots: [RootOrganization] }); - - ssoAdminClient.on(ListInstancesCommand, {}).resolves({ Instances: [] }); - - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [] }); - }); - - test('Valid Organization Configuration ', async () => { - expect( - await Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).toBeUndefined(); - }); - - test('Get management account ID ', async () => { - client.on(ListAccountsCommand, {}).resolves({ Accounts: [ManagementAccount] }); - - const accountId = await Organization.getManagementAccountId(GlobalRegion, SolutionId, ManagementAccount.Email); - expect(accountId).toEqual(ManagementAccount.Id); - }); - - test('GovCloud Required Accounts Found', async () => { - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [ManagementAccount, LogArchiveAccount, AuditAccount] }); - - expect( - await Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, GovCloud_US, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).toBeUndefined(); - }); -}); - -describe('Failure', () => { - beforeEach(() => { - client.reset(); - client.on(ListRootsCommand, {}).resolves({ Roots: [RootOrganization] }); - client.on(ListAWSServiceAccessForOrganizationCommand, {}).resolves({ EnabledServicePrincipals: [] }); - ssoAdminClient.on(ListInstancesCommand, {}).resolves({ Instances: [] }); - }); - - test('Organization Not Enabled', async () => { - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: undefined }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.ORG_NOT_FOUND])); - }); - - test('Organization All Feature Not Enabled', async () => { - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: BillingEnabledOrganizationConfig }); - - client.on(EnableAllFeaturesCommand, {}).resolves({ $metadata: { httpStatusCode: 200 } }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [] }); - - expect( - await Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).toBeUndefined(); - }); - - test('Additional OU Found', async () => { - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [SecurityOuConfig] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [] }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.OU_FOUND])); - }); - - test('Additional Accounts Found', async () => { - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [AuditAccount, LogArchiveAccount] }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.ACCOUNT_FOUND])); - }); - - test('GovCloud Required Accounts Not Found', async () => { - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [ManagementAccount] }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, GovCloud_US, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.GOV_CLOUD_ACCOUNT_NOT_FOUND])); - }); - - test('Organizations have services enabled', async () => { - client.on(ListAWSServiceAccessForOrganizationCommand, {}).resolves({ - EnabledServicePrincipals: [ - { - ServicePrincipal: 'securityhub.amazonaws.com', - }, - { - ServicePrincipal: 'sso.amazonaws.com', - }, - ], - }); - - const enabledServicePrincipals: EnabledServicePrincipal[] = []; - const paginator = paginateListAWSServiceAccessForOrganization( - { client: new OrganizationsClient({}), pageSize: 1 }, - {}, - ); - for await (const page of paginator) { - for (const enabledServicePrincipal of page.EnabledServicePrincipals ?? []) { - enabledServicePrincipals.push(enabledServicePrincipal); - } - } - - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [] }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.SERVICE_ENABLED])); - }); - - test('IAM Identity Center enabled', async () => { - ssoAdminClient.on(ListInstancesCommand, {}).resolves({ Instances: [{ IdentityStoreId: 'd-906751796e' }] }); - - client.on(DescribeOrganizationCommand, {}).resolves({ Organization: AllFeatureEnabledOrganizationConfig }); - - client.on(ListOrganizationalUnitsForParentCommand, {}).resolves({ OrganizationalUnits: [] }); - - client.on(ListAccountsCommand, {}).resolves({ Accounts: [] }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.IDENTITY_CENTER_ENABLED])); - }); - - test('AWSOrganizationsNotInUseException', async () => { - client - .on(DescribeOrganizationCommand, {}) - .rejectsOnce( - new AWSOrganizationsNotInUseException({ - $metadata: {}, - message: '', - }), - ) - .resolves({ - Organization: undefined, - }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(ValidateOrganizationError([OrganizationValidationError.ORG_NOT_FOUND])); - }); - - test('Describe Organization internal error', async () => { - client.on(DescribeOrganizationCommand, {}).rejectsOnce(MockInternalError).resolves({ - Organization: undefined, - }); - - await expect( - Organization.ValidateOrganization(GlobalRegion, Region, SolutionId, Partition, { - logArchive: LogArchiveAccount.Email, - audit: AuditAccount.Email, - }), - ).rejects.toThrow(MockInternalError); - }); - - test('Management account not found ', async () => { - client.on(ListAccountsCommand, {}).resolves({ Accounts: [] }); - - await expect( - Organization.getManagementAccountId(GlobalRegion, SolutionId, ManagementAccount.Email), - ).rejects.toThrow(ManagementAccountNotFoundError(ManagementAccount.Email)); - }); -}); diff --git a/source/packages/@aws-accelerator/modules/test/control-tower/shared-account.test.ts b/source/packages/@aws-accelerator/modules/test/control-tower/shared-account.test.ts deleted file mode 100644 index 09ed159..0000000 --- a/source/packages/@aws-accelerator/modules/test/control-tower/shared-account.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { describe, beforeEach, expect, test } from '@jest/globals'; -import { - AcceleratorMockClient, - AccountCreationInternalFailureError, - AuditAccount, - ConfigPath, - GlobalRegion, - LogArchiveAccount, - SolutionId, -} from '../utils/test-resources'; -import { SharedAccount } from '../../lib/control-tower/prerequisites/shared-account'; -import { - CreateAccountCommand, - CreateAccountState, - DescribeCreateAccountStatusCommand, - OrganizationsClient, -} from '@aws-sdk/client-organizations'; -import * as CommonResources from '../../lib/control-tower/utils/resources'; - -const client = AcceleratorMockClient(OrganizationsClient); -const mockDelay = jest.spyOn(CommonResources, 'delay'); - -describe('Success', () => { - beforeEach(() => { - client.reset(); - mockDelay.mockImplementation(() => Promise.resolve()); - }); - - test('Create both the Shared Accounts', async () => { - client - .on(CreateAccountCommand, { - Email: AuditAccount.Email, - AccountName: AuditAccount.Name, - }) - .resolves({ - CreateAccountStatus: { - Id: `${AuditAccount.Name}CreateAccountRequestId`, - AccountName: AuditAccount.Name, - State: CreateAccountState.IN_PROGRESS, - }, - }); - - client - .on(DescribeCreateAccountStatusCommand, { CreateAccountRequestId: `${AuditAccount.Name}CreateAccountRequestId` }) - .resolves({ - CreateAccountStatus: { - AccountName: AuditAccount.Name, - State: CreateAccountState.SUCCEEDED, - AccountId: AuditAccount.Id, - }, - }); - - client - .on(CreateAccountCommand, { - Email: LogArchiveAccount.Email, - AccountName: LogArchiveAccount.Name, - }) - .resolves({ - CreateAccountStatus: { - Id: `${LogArchiveAccount.Name}CreateAccountRequestId`, - AccountName: LogArchiveAccount.Name, - State: CreateAccountState.IN_PROGRESS, - }, - }); - - client - .on(DescribeCreateAccountStatusCommand, { - CreateAccountRequestId: `${LogArchiveAccount.Name}CreateAccountRequestId`, - }) - .resolves({ - CreateAccountStatus: { - AccountName: LogArchiveAccount.Name, - State: CreateAccountState.SUCCEEDED, - AccountId: LogArchiveAccount.Id, - }, - }); - - expect(await SharedAccount.createAccounts(ConfigPath, GlobalRegion, SolutionId)).toBeUndefined(); - }); -}); - -describe('Failure', () => { - beforeEach(() => { - client.reset(); - mockDelay.mockImplementation(() => Promise.resolve()); - }); - - test('Initial Account Creation failed - Internal Error', async () => { - client - .on(CreateAccountCommand, { - Email: AuditAccount.Email, - AccountName: AuditAccount.Name, - }) - .resolves({ - CreateAccountStatus: { - Id: `${AuditAccount.Name}CreateAccountRequestId`, - AccountName: AuditAccount.Name, - State: CreateAccountState.FAILED, - }, - }); - - client - .on(CreateAccountCommand, { - Email: LogArchiveAccount.Email, - AccountName: LogArchiveAccount.Name, - }) - .resolves({ - CreateAccountStatus: { - Id: `${LogArchiveAccount.Name}CreateAccountRequestId`, - AccountName: LogArchiveAccount.Name, - State: CreateAccountState.FAILED, - }, - }); - - await expect(SharedAccount.createAccounts(ConfigPath, GlobalRegion, SolutionId)).rejects.toThrow( - AccountCreationInternalFailureError([ - `${LogArchiveAccount.Name} creation is currently in FAILED state with undefined error`, - `${AuditAccount.Name} creation is currently in FAILED state with undefined error`, - ]), - ); - }); - - test('Account Creation failed during recheck status - Internal Error', async () => { - client - .on(CreateAccountCommand, { - Email: AuditAccount.Email, - AccountName: AuditAccount.Name, - }) - .resolves({ - CreateAccountStatus: { - Id: `${AuditAccount.Name}CreateAccountRequestId`, - AccountName: AuditAccount.Name, - State: CreateAccountState.IN_PROGRESS, - }, - }); - - client - .on(DescribeCreateAccountStatusCommand, { CreateAccountRequestId: `${AuditAccount.Name}CreateAccountRequestId` }) - .resolves({ - CreateAccountStatus: { - AccountName: AuditAccount.Name, - State: CreateAccountState.FAILED, - AccountId: AuditAccount.Id, - }, - }); - - client - .on(CreateAccountCommand, { - Email: LogArchiveAccount.Email, - AccountName: LogArchiveAccount.Name, - }) - .resolves({ - CreateAccountStatus: { - Id: `${LogArchiveAccount.Name}CreateAccountRequestId`, - AccountName: LogArchiveAccount.Name, - State: CreateAccountState.IN_PROGRESS, - }, - }); - - client - .on(DescribeCreateAccountStatusCommand, { - CreateAccountRequestId: `${LogArchiveAccount.Name}CreateAccountRequestId`, - }) - .resolves({ - CreateAccountStatus: { - AccountName: LogArchiveAccount.Name, - State: CreateAccountState.FAILED, - AccountId: LogArchiveAccount.Id, - }, - }); - - await expect(SharedAccount.createAccounts(ConfigPath, GlobalRegion, SolutionId)).rejects.toThrow( - AccountCreationInternalFailureError([ - `${LogArchiveAccount.Name} creation is currently in FAILED state with undefined error`, - `${AuditAccount.Name} creation is currently in FAILED state with undefined error`, - ]), - ); - }); -}); diff --git a/source/packages/@aws-accelerator/modules/test/utils/test-resources.ts b/source/packages/@aws-accelerator/modules/test/utils/test-resources.ts deleted file mode 100644 index a2a3301..0000000 --- a/source/packages/@aws-accelerator/modules/test/utils/test-resources.ts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { OrganizationFeatureSet } from '@aws-sdk/client-organizations'; - -import { mockClient } from 'aws-sdk-client-mock'; -import path from 'path'; - -/** - * AWS SDK Mock API client - * @param awsClient - * @returns - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const AcceleratorMockClient = (awsClient: any) => mockClient(awsClient); - -/** - * Accelerator all enable config directory path - */ -export const ConfigPath = path.join(__dirname, '../../../accelerator/test/configs/all-enabled'); - -/** - * Partition - */ -export const Partition = 'aws'; - -/** - * GovCloud US Partition - */ -export const GovCloud_US = 'aws-us-gov'; - -/** - * AWS Account Id - */ -export const AccountId = '111111111111'; -/** - * Global Region - */ -export const GlobalRegion = 'us-east-1'; - -/** - * AWS Region - */ -export const Region = 'us-east-1'; - -/** - * Solution id - */ -export const SolutionId = ' AwsSolution/SO0199'; - -/** - * AWS KMS CMK creation parameter - */ -export const CreateKeyParams = { - Description: 'AWS Control Tower Landing Zone encryption key', - KeyUsage: 'ENCRYPT_DECRYPT', - KeySpec: 'SYMMETRIC_DEFAULT', -}; - -/** - * Accelerator CMK alias mock config - */ -export const AcceleratorKeyAlias = { AliasArn: 'AliasArn1', AliasName: 'alias/accelerator/key' }; - -/** - * Installer CMK alias mock config - */ -export const InstallerKeyAlias = { AliasArn: 'AliasArn2', AliasName: 'alias/installer/key' }; - -/** - * AWS Control Tower Landing Zone CMK alias mock config - */ -export const ControlTowerKeyAlias = { AliasArn: 'AliasArn3', AliasName: 'alias/aws-controltower/key' }; - -/** - * All Feature enabled mock Organization config - */ -export const AllFeatureEnabledOrganizationConfig = { - Id: 'OrgId', - Arn: 'OrgArn', - FeatureSet: OrganizationFeatureSet.ALL, -}; - -/** - * Billing enabled mock Organization config - */ -export const BillingEnabledOrganizationConfig = { - Id: 'OrgId', - Arn: 'OrgArn', - FeatureSet: OrganizationFeatureSet.CONSOLIDATED_BILLING, -}; - -/** - * AWS Organization Root mock configuration - */ -export const RootOrganization = { Id: 'r-001', Name: 'Root', Arn: 'RootArn' }; - -/** - * AWS Organization Security OU mock configuration - */ -export const SecurityOuConfig = { Id: 'ou-001', Name: 'Security', Arn: 'SecurityArn' }; - -/** - * Existing fake Management account mock configuration - */ -export const ManagementAccount = { - Id: 'ManagementAccountId', - Arn: 'ManagementAccount-Arn', - Email: 'all-enabled-management-account@example.com', - Name: 'Management', - Status: 'Active', -}; - -/** - * Existing fake Audit account mock configuration - */ -export const AuditAccount = { - Id: 'AuditAccountId', - Arn: 'AuditAccount-Arn', - Email: 'all-enabled-audit-account@example.com', - Name: 'Audit', - Status: 'Active', -}; - -/** - * Existing fake LogArchive account mock configuration - */ -export const LogArchiveAccount = { - Id: 'LogArchiveAccountId', - Arn: 'LogArchiveAccount-Arn', - Email: 'all-enabled-logarchive-account@example.com', - Name: 'LogArchive', - Status: 'Active', -}; - -/** - * AWSControlTowerAdmin Role mock Configuration - */ -export const AWSControlTowerAdmin = { - RoleName: 'AWSControlTowerAdmin', - RoleId: 'Role1', - CreateDate: new Date(), - Arn: 'Role1Arn', - Path: '/service-role/', -}; - -/** - * Fake Policy document for AWS Control Tower Landing Zone role. - */ -export const AWSControlTowerRolePolicyDocument = - '{"Version": "2012-10-17","Statement": [{"Action": "ec2:DescribeAvailabilityZones","Resource": "*","Effect": "Allow"}]}'; - -/** - * Mock internal error - */ -export const MockInternalError = new Error('An AWS internal error'); - -/** - * AWS Account creation internal errors - * @param errors string[] - * @returns - */ - -export const AccountCreationInternalFailureError = (errors: string[]) => - new Error(`Shared account creation failure !!! ${errors.join('. ')}`); - -/** - * Expected error when there is an existing Security OU under Root of Organizations - */ -export const ExistingRoleFoundError = (roleNames: string[]) => - new Error( - `There are existing AWS Control Tower Landing Zone roles "${roleNames.join( - ',', - )}", the solution cannot deploy AWS Control Tower Landing Zone`, - ); - -/** - * Possible Organization validation error - */ -export enum OrganizationValidationError { - ORG_NOT_FOUND = `AWS Control Tower Landing Zone cannot deploy because AWS Organizations have not been configured for the environment.`, - SERVICE_ENABLED = `AWS Control Tower Landing Zone cannot deploy because AWS Organizations have services enabled.`, - OU_FOUND = `AWS Control Tower Landing Zone cannot deploy because there are multiple organizational units in AWS Organizations.`, - ACCOUNT_FOUND = `AWS Control Tower Landing Zone cannot deploy because there are multiple accounts in AWS Organizations.`, - GOV_CLOUD_ACCOUNT_NOT_FOUND = `Either AWS Organizations does not have required shared accounts (LogArchive and Audit) or have other accounts.`, - IDENTITY_CENTER_ENABLED = `AWS Control Tower Landing Zone cannot deploy because IAM Identity Center is configured.`, -} - -/** - * Organization validation error - * @param validationErrors string[] - * @returns - */ -export const ValidateOrganizationError = (validationErrors: string[]) => - new Error(`AWS Organization validation has ${validationErrors.length} issue(s):\n${validationErrors.join('\n')}`); - -/** - * Expected error when there is an existing CMK alias for AWS Control Tower Landing Zone CMK - */ -export const AliasFoundError = new Error( - `There is already an AWS Control Tower Landing Zone KMS CMK alias named ${ControlTowerKeyAlias.AliasName}. The alias ${ControlTowerKeyAlias.AliasName} is reserved for AWS Control Tower Landing Zone CMK created by the solution, the solution cannot deploy AWS Control Tower Landing Zone.`, -); - -/** - * Management account not found error - * @param email string - * @returns - */ -export const ManagementAccountNotFoundError = (email: string) => - new Error(`Management account with email ${email} not found`); diff --git a/source/packages/@aws-accelerator/modules/tsconfig.json b/source/packages/@aws-accelerator/modules/tsconfig.json deleted file mode 100644 index 6747743..0000000 --- a/source/packages/@aws-accelerator/modules/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "allowJs": true - }, - "include": ["bin/**/*", "common/**/*", "index.ts", "lib/**/*"], - "exclude": [ "test/**/*"] -} diff --git a/source/packages/@aws-accelerator/tester/.npmignore b/source/packages/@aws-accelerator/tester/.npmignore deleted file mode 100644 index c1d6d45..0000000 --- a/source/packages/@aws-accelerator/tester/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-accelerator/tester/README.md b/source/packages/@aws-accelerator/tester/README.md deleted file mode 100644 index ee3a2ad..0000000 --- a/source/packages/@aws-accelerator/tester/README.md +++ /dev/null @@ -1 +0,0 @@ -# @aws-accelerator/tester diff --git a/source/packages/@aws-accelerator/tester/bin/app.ts b/source/packages/@aws-accelerator/tester/bin/app.ts deleted file mode 100644 index 05a61e7..0000000 --- a/source/packages/@aws-accelerator/tester/bin/app.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import * as yaml from 'js-yaml'; -import fs from 'fs'; -import path from 'path'; -import { TesterStack, CONFIG_FILE_NAME, CONFIG_FILE_CONTENT_TYPE } from '../lib/tester-stack'; - -/** - * Test Accelerator CDK App - */ -async function main() { - const usage = - 'Usage: app.ts --context account=ACCOUNT --context region=REGION --context management-cross-account-role-name=MANAGEMENT_CROSS_ACCOUNT_ROLE_NAME --context config-dir=CONFIG_DIRECTORY [--context qualifier=QUALIFIER] [--context management-account-id=MANAGEMENT_ACCOUNT_ID] [--context management-account-role-name=MANAGEMENT_ACCOUNT_ROLE_NAME]'; - const app = new cdk.App(); - - const acceleratorPrefix = app.node.tryGetContext('acceleratorPrefix'); - const account = app.node.tryGetContext('account'); - const region = app.node.tryGetContext('region'); - const qualifier = app.node.tryGetContext('qualifier'); - const managementCrossAccountRoleName = app.node.tryGetContext('management-cross-account-role-name'); - const configDirPath = app.node.tryGetContext('config-dir'); - - if (account === undefined) { - console.warn(`[tester-app] Invalid --account ${account}`); - throw new Error(usage); - } - - if (region === undefined) { - console.warn(`[tester-app] Invalid --region ${region}`); - throw new Error(usage); - } - - if (managementCrossAccountRoleName === undefined) { - console.warn(`[tester-app] Invalid --management-cross-account-role-name ${managementCrossAccountRoleName}`); - throw new Error(usage); - } - - if (configDirPath === undefined || !fs.existsSync(configDirPath)) { - console.warn(`[tester-app] Invalid --config-dir ${configDirPath}`); - throw new Error(usage); - } - - const configFilePath = path.join(configDirPath, CONFIG_FILE_NAME); - if (!fs.existsSync(configFilePath)) { - throw new Error(`[tester-app] Config file not found ${configFilePath}`); - } - - const configFileContent = yaml.load(fs.readFileSync(configFilePath, 'utf8')) as CONFIG_FILE_CONTENT_TYPE; - - new TesterStack( - app, - qualifier === undefined - ? `${acceleratorPrefix}-TesterStack-${account}-${region}` - : `${qualifier}-tester-stack-${account}-${region}`, - { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - managementCrossAccountRoleName: managementCrossAccountRoleName, - configFileContent: configFileContent, - qualifier: qualifier === undefined ? 'aws-accelerator' : qualifier, - managementAccountId: app.node.tryGetContext('management-account-id'), - managementAccountRoleName: app.node.tryGetContext('management-account-role-name'), - }, - ); -} - -//call the main function -main(); diff --git a/source/packages/@aws-accelerator/tester/cdk.json b/source/packages/@aws-accelerator/tester/cdk.json deleted file mode 100644 index 41d719a..0000000 --- a/source/packages/@aws-accelerator/tester/cdk.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/app.ts", - "versionReporting": false, - "context": { - "@aws-cdk/core:bootstrapQualifier": "accel" - } -} diff --git a/source/packages/@aws-accelerator/tester/index.ts b/source/packages/@aws-accelerator/tester/index.ts deleted file mode 100644 index 33efdcf..0000000 --- a/source/packages/@aws-accelerator/tester/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/tester-stack'; diff --git a/source/packages/@aws-accelerator/tester/jest.config.js b/source/packages/@aws-accelerator/tester/jest.config.js deleted file mode 100644 index c78b6c1..0000000 --- a/source/packages/@aws-accelerator/tester/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 87, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/tester/lambdas/index.ts b/source/packages/@aws-accelerator/tester/lambdas/index.ts deleted file mode 100644 index 7e64fca..0000000 --- a/source/packages/@aws-accelerator/tester/lambdas/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -import { validateTransitGateway } from './test-target-functions/validate-transit-gateway'; -AWS.config.logger = console; - -/** - * AWS Config custom config lambda handler - * - * @param event - * @returns - */ -export async function handler(event): Promise<{ - Status: string | undefined; - StatusCode: number | undefined; -}> { - console.log(event); - const resultToken = event['resultToken']; - - const ruleParameters = JSON.parse(event['ruleParameters']); - - const configRegion = ruleParameters['awsConfigRegion']; - const test = ruleParameters['test']; - - const managementCrossAccountRoleName = ruleParameters['managementAccount']['crossAccountRoleName']; - const partition = ruleParameters['managementAccount']['partition']; - const managementAccountId = ruleParameters['managementAccount']['id']; - const managementAccountRoleName = ruleParameters['managementAccount']['roleName']; - - const invokingEvent = JSON.parse(event['invokingEvent']); - const invokingAwsAccountId = invokingEvent['awsAccountId']; - - const stsClient = new AWS.STS({}); - let managementAccountCredential: AWS.STS.Credentials; - - // Create management account credential when invoking account is not management account - if (invokingAwsAccountId !== managementAccountId) { - const roleArn = `arn:${partition}:iam::${managementAccountId}:role/${managementAccountRoleName}`; - const assumeRoleResponse = await throttlingBackOff(() => - stsClient.assumeRole({ RoleArn: roleArn, RoleSessionName: 'acceleratorAssumeRoleSession' }).promise(), - ); - managementAccountCredential = assumeRoleResponse.Credentials!; - } else { - const credentials = stsClient.config.credentials as AWS.Credentials; - managementAccountCredential = { - AccessKeyId: credentials.accessKeyId, - SecretAccessKey: credentials.secretAccessKey, - SessionToken: credentials.sessionToken, - Expiration: credentials.expireTime, - }; - } - - let response; - - if (test['suite'] === 'network') { - if (test['testTarget'] === 'validateTransitGateway') { - response = await validateTransitGateway( - configRegion, - { - partition: partition, - id: managementAccountId, - crossAccountRoleName: managementCrossAccountRoleName, - credential: managementAccountCredential, - }, - test['parameters'], - ); - } - } - - if (response) { - await putEvaluations(configRegion, resultToken, response); - } - return { Status: 'Success', StatusCode: 200 }; -} - -/** - * Function to config custom rule put evaluation - * @param configRegion - * @param resultToken - * @param result - */ -async function putEvaluations( - // configServiceClient: ConfigServiceClient, - configRegion: string, - resultToken: string, - result: { complianceResourceType: string; complianceResourceId: string; complianceType: string }, -): Promise { - //Put Evaluation - const configServiceClient = new AWS.ConfigService({ region: configRegion }); - await throttlingBackOff(() => - configServiceClient - .putEvaluations({ - Evaluations: [ - { - Annotation: 'Verified by custom lambda function', - ComplianceResourceId: result.complianceResourceId, - ComplianceResourceType: result.complianceResourceType, - ComplianceType: result.complianceType, - OrderingTimestamp: new Date(), - }, - ], - ResultToken: resultToken, - }) - .promise(), - ); -} diff --git a/source/packages/@aws-accelerator/tester/lambdas/package.json b/source/packages/@aws-accelerator/tester/lambdas/package.json deleted file mode 100644 index 71a9ff8..0000000 --- a/source/packages/@aws-accelerator/tester/lambdas/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@aws-accelerator/tester-lambdas", - "version": "0.0.0", - "private": true, - "description": "Tester Lambdas", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "esbuild --minify --bundle --sourcemap --platform=node --target=node18 --outfile=./dist/index.js index.ts", - "cleanup": "tsc --build --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \\\\\\\"*.d.ts\\\\\\\" ", - "precommit": "eslint --max-warnings 0 -c ../../../../.eslintrc.json '**/*.{ts,tsx}' --ignore-pattern \\\\\\\"*.d.ts\\\\\\\" ", - "test": "jest --coverage --ci --passWithNoTests", - "testreport": "" - }, - "dependencies": { - "@aws-accelerator/utils": "^0.0.0", - "aws-sdk": "2.1379.0" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - } -} diff --git a/source/packages/@aws-accelerator/tester/lambdas/test-target-functions/validate-transit-gateway.ts b/source/packages/@aws-accelerator/tester/lambdas/test-target-functions/validate-transit-gateway.ts deleted file mode 100644 index 82e9a00..0000000 --- a/source/packages/@aws-accelerator/tester/lambdas/test-target-functions/validate-transit-gateway.ts +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import * as AWS from 'aws-sdk'; -AWS.config.logger = console; - -/** - * Config rule compliance value enum - */ -const enum ComplianceType { - Compliant = 'COMPLIANT', - Non_Compliant = 'NON_COMPLIANT', -} - -/** - * "validateTransitGateway" test target. Function to validate Transit Gateway. - * Validates following: - *
      - *
    • Transit gateway exists - *
    • Transit gateway has valid route tables - *
    • Transit gateway attachment accounts are valid - *
    - * @param configRegion {string} - * @param managementAccount {Object} - * @param parameters {string} - */ -export async function validateTransitGateway( - configRegion: string, - managementAccount: { partition: string; id: string; crossAccountRoleName: string; credential: AWS.STS.Credentials }, - parameters: string, -): Promise<{ complianceResourceType: string; complianceResourceId: string; complianceType: string }> { - const transitGatewayName = parameters['name']; - const transitGatewayAccountId = parameters['accountId']; - const transitGatewayRegion = parameters['region']; - const amazonSideAsn = parameters['amazonSideAsn']; - const dnsSupport = parameters['dnsSupport']; - const vpnEcmpSupport = parameters['vpnEcmpSupport']; - const autoAcceptSharingAttachments = parameters['autoAcceptSharingAttachments']; - const defaultRouteTableAssociation = parameters['defaultRouteTableAssociation']; - const defaultRouteTablePropagation = parameters['defaultRouteTablePropagation']; - const routeTableNames = parameters['routeTableNames']; - const shareTargetAccountIds = parameters['shareTargetAccountIds']; - - let ec2Client: AWS.EC2; - - // Assume role when transit gateway to be evaluated in other account than config account - if (managementAccount.id !== transitGatewayAccountId) { - const roleArn = `arn:${managementAccount.partition}:iam::${transitGatewayAccountId}:role/${managementAccount.crossAccountRoleName}`; - const stsClient = new AWS.STS({ - region: configRegion, - credentials: { - accessKeyId: managementAccount.credential.AccessKeyId, - secretAccessKey: managementAccount.credential.SecretAccessKey, - sessionToken: managementAccount.credential.SessionToken, - expireTime: managementAccount.credential.Expiration, - }, - }); - - const response = await throttlingBackOff(() => - stsClient.assumeRole({ RoleArn: roleArn, RoleSessionName: 'acceleratorAssumeRoleSession' }).promise(), - ); - - ec2Client = new AWS.EC2({ - region: transitGatewayRegion, - credentials: { - accessKeyId: response.Credentials!.AccessKeyId!, - secretAccessKey: response.Credentials!.SecretAccessKey!, - sessionToken: response.Credentials!.SessionToken, - expireTime: response.Credentials!.Expiration, - }, - }); - } else { - ec2Client = new AWS.EC2({ - region: transitGatewayRegion, - credentials: { - accessKeyId: managementAccount.credential.AccessKeyId, - secretAccessKey: managementAccount.credential.SecretAccessKey, - sessionToken: managementAccount.credential.SessionToken, - expireTime: managementAccount.credential.Expiration, - }, - }); - } - - const complianceResourceType = 'AWS::EC2::TransitGateway'; - let complianceResourceId = transitGatewayAccountId; - let complianceType = ComplianceType.Non_Compliant; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => ec2Client.describeTransitGateways({ NextToken: nextToken }).promise()); - for (const transitGateway of page.TransitGateways ?? []) { - if ( - await transitGatewayExists(transitGateway, transitGatewayName, { - amazonSideAsn: parseInt(amazonSideAsn ?? 0), - dnsSupport, - vpnEcmpSupport, - autoAcceptSharedAttachments: autoAcceptSharingAttachments, - defaultRouteTableAssociation, - defaultRouteTablePropagation, - }) - ) { - complianceResourceId = transitGateway.TransitGatewayId!; - if ( - (await isRouteTablesValid( - ec2Client, - transitGateway.TransitGatewayId!, - defaultRouteTableAssociation, - defaultRouteTablePropagation, - routeTableNames ?? [], - )) && - (await isTransitGatewayAttachmentsValid( - ec2Client, - transitGateway.TransitGatewayId!, - shareTargetAccountIds ?? [], - )) - ) { - // set compliance - complianceType = ComplianceType.Compliant; - } - } - } - nextToken = page.NextToken; - } while (nextToken); - return { - complianceResourceType: complianceResourceType, - complianceResourceId: complianceResourceId, - complianceType: complianceType, - }; -} - -/** - * Function to check if two arrays are exactly same with number of item and the position - * @param first - * @param second - */ -function areArraysEqual(first: string[], second: string[]) { - return ( - Array.isArray(first) && - Array.isArray(second) && - first.length === second.length && - first.every((val, index) => val === second[index]) - ); -} - -/** - * Function to check if transit gateway exists - * @param transitGateway - * @param validatingTransitGatewayName - * @param amazonSideAsn - * @param dnsSupport - * @param vpnEcmpSupport - * @param autoAcceptSharedAttachments - * @param defaultRouteTableAssociation - * @param defaultRouteTablePropagation - */ -async function transitGatewayExists( - transitGateway: AWS.EC2.TransitGateway, - validatingTransitGatewayName: string, - props: { - amazonSideAsn: number; - dnsSupport: string | undefined; - vpnEcmpSupport: string | undefined; - autoAcceptSharedAttachments: string | undefined; - defaultRouteTableAssociation: string | undefined; - defaultRouteTablePropagation: string | undefined; - }, -): Promise { - if ( - transitGateway.Options!.AmazonSideAsn === - (props.amazonSideAsn !== 0 ? props.amazonSideAsn : transitGateway.Options!.AmazonSideAsn) && - transitGateway.Options!.DnsSupport === (props.dnsSupport ?? transitGateway.Options!.DnsSupport) && - transitGateway.Options!.VpnEcmpSupport === (props.vpnEcmpSupport ?? transitGateway.Options!.VpnEcmpSupport) && - transitGateway.Options!.AutoAcceptSharedAttachments === - (props.autoAcceptSharedAttachments ?? transitGateway.Options!.AutoAcceptSharedAttachments) && - transitGateway.Options!.DefaultRouteTableAssociation === - (props.defaultRouteTableAssociation ?? transitGateway.Options!.DefaultRouteTableAssociation) && - transitGateway.Options!.DefaultRouteTablePropagation === - (props.defaultRouteTablePropagation ?? transitGateway.Options!.DefaultRouteTablePropagation) - ) { - for (const tag of transitGateway.Tags ?? []) { - if (tag.Key === 'Name' && tag.Value === validatingTransitGatewayName) { - return true; - } - } - } - return false; -} - -/** - * Function to validate transit gateway route tables - * @param ec2Client - * @param transitGatewayId - * @param defaultRouteTableAssociation - * @param defaultRouteTablePropagation - * @param validatingRouteTableNames - */ -async function isRouteTablesValid( - ec2Client: AWS.EC2, - transitGatewayId: string, - defaultRouteTableAssociation: string | undefined, - defaultRouteTablePropagation: string | undefined, - validatingRouteTableNames: string[], -): Promise { - const presentRouteTableNames: string[] = []; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client.describeTransitGatewayRouteTables({ NextToken: nextToken }).promise(), - ); - for (const transitGatewayRouteTable of page.TransitGatewayRouteTables ?? []) { - if ( - transitGatewayRouteTable.TransitGatewayId === transitGatewayId && - transitGatewayRouteTable.State === 'available' && - transitGatewayRouteTable.DefaultAssociationRouteTable === - ((defaultRouteTableAssociation ?? transitGatewayRouteTable.DefaultAssociationRouteTable) === 'enable') && - transitGatewayRouteTable.DefaultPropagationRouteTable === - ((defaultRouteTablePropagation ?? transitGatewayRouteTable.DefaultPropagationRouteTable) === 'enable') - ) { - for (const tag of transitGatewayRouteTable.Tags ?? []) { - if (tag.Key === 'Name') { - presentRouteTableNames.push(tag.Value!); - } - } - } - } - nextToken = page.NextToken; - } while (nextToken); - - validatingRouteTableNames.sort(); - presentRouteTableNames.sort(); - return validatingRouteTableNames.length === 0 - ? true - : areArraysEqual(validatingRouteTableNames, presentRouteTableNames); -} - -/** - * Function to validate transit gateway attachments - * @param ec2Client - * @param transitGatewayId - * @param shareTargetAccountIds - */ -async function isTransitGatewayAttachmentsValid( - ec2Client: AWS.EC2, - transitGatewayId: string, - shareTargetAccountIds: string[], -): Promise { - const resourceOwnerIds: string[] = []; - - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - ec2Client.describeTransitGatewayAttachments({ NextToken: nextToken }).promise(), - ); - for (const transitGatewayAttachment of page.TransitGatewayAttachments ?? []) { - if ( - transitGatewayAttachment.TransitGatewayId === transitGatewayId && - transitGatewayAttachment.State === 'available' - ) { - resourceOwnerIds.push(transitGatewayAttachment.ResourceOwnerId!); - } - } - nextToken = page.NextToken; - } while (nextToken); - - shareTargetAccountIds.sort(); - resourceOwnerIds.sort(); - return shareTargetAccountIds.length === 0 ? true : areArraysEqual(shareTargetAccountIds, resourceOwnerIds); -} diff --git a/source/packages/@aws-accelerator/tester/lambdas/tsconfig.json b/source/packages/@aws-accelerator/tester/lambdas/tsconfig.json deleted file mode 100644 index 5fafbed..0000000 --- a/source/packages/@aws-accelerator/tester/lambdas/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "noImplicitAny": false - }, - "include": [ - "index.ts" - ], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-accelerator/tester/lib/tester-stack.ts b/source/packages/@aws-accelerator/tester/lib/tester-stack.ts deleted file mode 100644 index 6882af7..0000000 --- a/source/packages/@aws-accelerator/tester/lib/tester-stack.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cdk from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as lambda from 'aws-cdk-lib/aws-lambda'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as config from 'aws-cdk-lib/aws-config'; -import path from 'path'; - -/** - * Test config file name - */ -export const CONFIG_FILE_NAME = 'config.yaml'; - -/** - * Test config structure type - */ -export type CONFIG_FILE_CONTENT_TYPE = { - /** - * List of test cases - */ - tests: [ - { - /** - * Unique test name - */ - name: string; - /** - * Test case description - */ - description: string; - /** - * Test suite - */ - suite: string; - /** - * Test target identifier - */ - testTarget: string; - /** - * Expected test result - PASS/FAIL - */ - expect: string; - /** - * List of test case input parameters - */ - parameters: Record[]; - }, - ]; -}; - -/** - * TesterStackPops - */ -export interface TesterStackPops extends cdk.StackProps { - readonly qualifier: string; - readonly configFileContent: CONFIG_FILE_CONTENT_TYPE; - readonly managementCrossAccountRoleName: string; - readonly managementAccountId?: string; - readonly managementAccountRoleName?: string; -} - -/** - * TesterStack class - */ -export class TesterStack extends cdk.Stack { - constructor(scope: Construct, id: string, props: TesterStackPops) { - super(scope, id, props); - - /** - * Custom policy statements - */ - const policyStatements: iam.PolicyStatement[] = []; - - if (props.managementAccountId && props.managementAccountRoleName) { - policyStatements.push( - new iam.PolicyStatement({ - sid: 'LambdaSTSActions', - effect: iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [ - `arn:${cdk.Stack.of(this).partition}:iam::${props.managementAccountId}:role/${ - props.managementAccountRoleName - }`, - ], - }), - ); - } else { - policyStatements.push( - new iam.PolicyStatement({ - sid: 'LambdaSTSActions', - effect: iam.Effect.ALLOW, - actions: ['sts:AssumeRole'], - resources: [`arn:${cdk.Stack.of(this).partition}:iam::*:role/${props.managementCrossAccountRoleName}`], - }), - ); - } - - for (const test of props.configFileContent.tests) { - const testName = test.name.replace(/[^a-zA-Z0-9-]/g, '-'); - - /** - * Lambda function for config custom role - * Single lambda function can not be used for multiple config custom role, there is a pending issue with CDK team on this - * https://github.com/aws/aws-cdk/issues/17582 - */ - const lambdaFunction = new lambda.Function(this, `${props.qualifier}-${testName}Function`, { - runtime: lambda.Runtime.NODEJS_18_X, - handler: 'index.handler', - code: lambda.Code.fromAsset(path.join(__dirname, '../lambdas/dist')), - description: `AWS Config custom rule function used for test case "${test.name}"`, - timeout: cdk.Duration.minutes(30), - }); - - lambdaFunction.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); - - policyStatements.forEach(policyStatement => { - lambdaFunction?.addToRolePolicy(policyStatement); - }); - - new config.CustomRule(this, `${props.qualifier}-${testName}CustomRule`, { - configRuleName: `${props.qualifier}-${testName}`, - lambdaFunction: lambdaFunction, - periodic: true, - inputParameters: { - ['awsConfigRegion']: cdk.Stack.of(this).region, - ['managementAccount']: { - partition: cdk.Stack.of(this).partition, - id: props.managementAccountId ?? cdk.Stack.of(this).account, - crossAccountRoleName: props.managementCrossAccountRoleName, - roleName: props.managementAccountRoleName, - }, - ['test']: test, - }, - description: `${test.description}`, - maximumExecutionFrequency: config.MaximumExecutionFrequency.SIX_HOURS, // default is 24 hours - }); - } - } -} diff --git a/source/packages/@aws-accelerator/tester/package.json b/source/packages/@aws-accelerator/tester/package.json deleted file mode 100644 index eeaf31b..0000000 --- a/source/packages/@aws-accelerator/tester/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@aws-accelerator/tester", - "version": "0.0.0", - "private": true, - "description": "The test package for the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ../tests --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ../tests --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' --ignore-pattern \\\\\\\"*.d.ts\\\\\\\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' --ignore-pattern \\\\\\\"*.d.ts\\\\\\\" ", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "@types/fs-extra": "11.0.1", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "ts-node": "10.9.1" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "@typescript-eslint/eslint-plugin": "5.53.0", - "@typescript-eslint/parser": "5.53.0", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "fs-extra": "11.1.0", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/tester/test/configs/config.yaml b/source/packages/@aws-accelerator/tester/test/configs/config.yaml deleted file mode 100644 index a7bc21a..0000000 --- a/source/packages/@aws-accelerator/tester/test/configs/config.yaml +++ /dev/null @@ -1,22 +0,0 @@ -tests: - - name: validate main transit gateway - description: Validate Main Transit Gateway - suite: network - testTarget: validateTransitGateway - expect: PASS - parameters: - name: Main - accountId: '333333333333' - region: us-east-1 - amazonSideAsn: '65521' - dnsSupport: enable - vpnEcmpSupport: enable - defaultRouteTableAssociation: disable - defaultRouteTablePropagation: disable - autoAcceptSharingAttachments: enable - routeTableNames: - - core - - segregated - - shared - - standalone - shareTargetAccountIds: ['111111111111','222222222222'] \ No newline at end of file diff --git a/source/packages/@aws-accelerator/tester/test/external-pipeline-account-tester-stack.test.ts b/source/packages/@aws-accelerator/tester/test/external-pipeline-account-tester-stack.test.ts deleted file mode 100644 index 1ce9c65..0000000 --- a/source/packages/@aws-accelerator/tester/test/external-pipeline-account-tester-stack.test.ts +++ /dev/null @@ -1,332 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CONFIG_FILE_CONTENT_TYPE, CONFIG_FILE_NAME, TesterStack } from '../lib/tester-stack'; -import * as yaml from 'js-yaml'; -import * as fs from 'fs'; -import * as path from 'path'; -import { pascalCase } from 'pascal-case'; - -const testNamePrefix = 'Construct(TesterStack): '; - -/** - * External pipeline account TesterStack - */ -const app = new cdk.App({ - context: { - account: '333333333333', - region: 'us-east-1', - 'management-cross-account-role': 'AWSControlTowerExecution', - 'config-dir': path.join(__dirname, 'configs'), - qualifier: 'aws-accelerator', - 'management-account-id': '111111111111', - 'management-account-role-name': 'AcceleratorAccountAccessRole', - }, -}); - -const account = app.node.tryGetContext('account'); -const region = app.node.tryGetContext('region'); -const qualifier = app.node.tryGetContext('qualifier') ?? 'aws-accelerator'; -const managementCrossAccountRoleName = app.node.tryGetContext('management-cross-account-role-name'); -const configDirPath = app.node.tryGetContext('config-dir'); - -const configFilePath = path.join(configDirPath, CONFIG_FILE_NAME); -const configFileContent = yaml.load(fs.readFileSync(configFilePath, 'utf8')) as CONFIG_FILE_CONTENT_TYPE; - -const qualifierInPascalCase = pascalCase(qualifier) - .split('_') - .join('-') - .replace(/AwsAccelerator/gi, 'AWSAccelerator'); - -const stack = new TesterStack(app, `${qualifierInPascalCase}-TesterStack-${account}-${region}`, { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - managementCrossAccountRoleName: managementCrossAccountRoleName, - configFileContent: configFileContent, - qualifier: qualifier, - managementAccountId: app.node.tryGetContext('management-account-id'), - managementAccountRoleName: app.node.tryGetContext('management-account-role-name'), -}); - -/** - * ExternalPipelineAccount-TesterStack construct test - */ -describe('ExternalPipelineAccount-TesterStack', () => { - /** - * Number of ConfigRule resource test - */ - test(`${testNamePrefix} ConfigRule resource count test`, () => { - cdk.assertions.Template.fromStack(stack).resourceCountIs('AWS::Config::ConfigRule', 1); - }); - - /** - * Number of Lambda function resource test - */ - test(`${testNamePrefix} Lambda function resource count test`, () => { - cdk.assertions.Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 1); - }); - - /** - * Number of Lambda permission resource test - */ - test(`${testNamePrefix} Lambda permission resource count test`, () => { - cdk.assertions.Template.fromStack(stack).resourceCountIs('AWS::Lambda::Permission', 1); - }); - - /** - * Number of Lambda IAM role resource test - */ - test(`${testNamePrefix} Lambda IAM role resource count test`, () => { - cdk.assertions.Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 1); - }); - - /** - * Number of Lambda IAM policy resource test - */ - test(`${testNamePrefix} Lambda IAM policy resource count test`, () => { - cdk.assertions.Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 1); - }); - - /** - * ConfigRule awsacceleratorvalidatemaintransitgatewayCustomRule resource configuration test - */ - test(`${testNamePrefix} ConfigRule awsacceleratorvalidatemaintransitgatewayCustomRule resource configuration test`, () => { - cdk.assertions.Template.fromStack(stack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayCustomRuleB70A49C4: { - Type: 'AWS::Config::ConfigRule', - DependsOn: [ - 'awsacceleratorvalidatemaintransitgatewayFunctionCustomRulePermissionbM1jVaicvRO9SDCiAbsQcYrOlESEtMwrrF9ZQQRvd5QD9F10DC6', - 'awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F', - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1', - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', - ], - Properties: { - ConfigRuleName: 'aws-accelerator-validate-main-transit-gateway', - Description: 'Validate Main Transit Gateway', - InputParameters: { - awsConfigRegion: { - Ref: 'AWS::Region', - }, - managementAccount: { - id: '111111111111', - partition: { - Ref: 'AWS::Partition', - }, - roleName: 'AcceleratorAccountAccessRole', - }, - test: { - description: 'Validate Main Transit Gateway', - expect: 'PASS', - name: 'validate main transit gateway', - parameters: { - accountId: '333333333333', - amazonSideAsn: '65521', - autoAcceptSharingAttachments: 'enable', - defaultRouteTableAssociation: 'disable', - defaultRouteTablePropagation: 'disable', - dnsSupport: 'enable', - name: 'Main', - region: 'us-east-1', - routeTableNames: ['core', 'segregated', 'shared', 'standalone'], - shareTargetAccountIds: ['111111111111', '222222222222'], - vpnEcmpSupport: 'enable', - }, - suite: 'network', - testTarget: 'validateTransitGateway', - }, - }, - MaximumExecutionFrequency: 'Six_Hours', - Source: { - Owner: 'CUSTOM_LAMBDA', - SourceDetails: [ - { - EventSource: 'aws.config', - MaximumExecutionFrequency: 'Six_Hours', - MessageType: 'ScheduledNotification', - }, - ], - SourceIdentifier: { - 'Fn::GetAtt': ['awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F', 'Arn'], - }, - }, - }, - }, - }, - }); - }); - - /** - * Lambda function awsacceleratorvalidatemaintransitgatewayFunction resource configuration test - */ - test(`${testNamePrefix} Lambda function awsacceleratorvalidatemaintransitgatewayFunction resource configuration test`, () => { - cdk.assertions.Template.fromStack(stack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F: { - Type: 'AWS::Lambda::Function', - DependsOn: [ - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1', - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', - ], - Properties: { - Code: { - S3Bucket: { - 'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', - }, - }, - Description: 'AWS Config custom rule function used for test case "validate main transit gateway"', - Handler: 'index.handler', - Role: { - 'Fn::GetAtt': ['awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', 'Arn'], - }, - Runtime: 'nodejs18.x', - }, - }, - }, - }); - }); - - /** - * Lambda permission awsacceleratorvalidatemaintransitgatewayFunctionPermission resource configuration test - */ - test(`${testNamePrefix} Lambda permission awsacceleratorvalidatemaintransitgatewayFunctionPermission resource configuration test`, () => { - cdk.assertions.Template.fromStack(stack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunctionCustomRulePermissionbM1jVaicvRO9SDCiAbsQcYrOlESEtMwrrF9ZQQRvd5QD9F10DC6: - { - Type: 'AWS::Lambda::Permission', - Properties: { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': ['awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F', 'Arn'], - }, - Principal: 'config.amazonaws.com', - SourceAccount: { - Ref: 'AWS::AccountId', - }, - }, - }, - }, - }); - }); - - /** - * IAM role awsacceleratorvalidatemaintransitgatewayFunctionServiceRole resource configuration test - */ - test(`${testNamePrefix} IAM role awsacceleratorvalidatemaintransitgatewayFunctionServiceRole resource configuration test`, () => { - cdk.assertions.Template.fromStack(stack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38: { - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - Service: 'lambda.amazonaws.com', - }, - }, - ], - Version: '2012-10-17', - }, - ManagedPolicyArns: [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::aws:policy/ReadOnlyAccess', - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSConfigRulesExecutionRole', - ], - ], - }, - ], - }, - }, - }, - }); - }); - - /** - * IAM policy awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy resource configuration test - */ - test(`${testNamePrefix} IAM policy awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy resource configuration test`, () => { - cdk.assertions.Template.fromStack(stack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1: { - Type: 'AWS::IAM::Policy', - Properties: { - PolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::111111111111:role/AcceleratorAccountAccessRole', - ], - ], - }, - Sid: 'LambdaSTSActions', - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1', - Roles: [ - { - Ref: 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', - }, - ], - }, - }, - }, - }); - }); -}); diff --git a/source/packages/@aws-accelerator/tester/test/tester-stack.test.ts b/source/packages/@aws-accelerator/tester/test/tester-stack.test.ts deleted file mode 100644 index 58c6e94..0000000 --- a/source/packages/@aws-accelerator/tester/test/tester-stack.test.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cdk from 'aws-cdk-lib'; -import { CONFIG_FILE_CONTENT_TYPE, CONFIG_FILE_NAME, TesterStack } from '../lib/tester-stack'; -import * as yaml from 'js-yaml'; -import * as fs from 'fs'; -import * as path from 'path'; -import { pascalCase } from 'pascal-case'; - -const testNamePrefix = 'Construct(TesterStack): '; - -/** - * External pipeline account TesterStack - */ -const app = new cdk.App({ - context: { - account: '333333333333', - region: 'us-east-1', - 'management-cross-account-role': 'AWSControlTowerExecution', - 'config-dir': path.join(__dirname, 'configs'), - }, -}); - -const account = app.node.tryGetContext('account'); -const region = app.node.tryGetContext('region'); -const qualifier = app.node.tryGetContext('qualifier') ?? 'aws-accelerator'; -const managementCrossAccountRoleName = app.node.tryGetContext('management-cross-account-role-name'); -const configDirPath = app.node.tryGetContext('config-dir'); - -const configFilePath = path.join(configDirPath, CONFIG_FILE_NAME); -const configFileContent = yaml.load(fs.readFileSync(configFilePath, 'utf8')) as CONFIG_FILE_CONTENT_TYPE; - -const qualifierInPascalCase = pascalCase(qualifier) - .split('_') - .join('-') - .replace(/AwsAccelerator/gi, 'AWSAccelerator'); - -const externalAccountTesterStack = new TesterStack(app, `${qualifierInPascalCase}-TesterStack-${account}-${region}`, { - synthesizer: new cdk.DefaultStackSynthesizer({ - generateBootstrapVersionRule: false, - }), - managementCrossAccountRoleName: managementCrossAccountRoleName, - configFileContent: configFileContent, - qualifier: qualifier, - managementAccountId: app.node.tryGetContext('management-account-id'), - managementAccountRoleName: app.node.tryGetContext('management-account-role-name'), -}); - -/** - * TesterStack construct test - */ -describe('TesterStack', () => { - /** - * Number of ConfigRule resource test - */ - test(`${testNamePrefix} ConfigRule resource count test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).resourceCountIs('AWS::Config::ConfigRule', 1); - }); - - /** - * Number of Lambda function resource test - */ - test(`${testNamePrefix} Lambda function resource count test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).resourceCountIs('AWS::Lambda::Function', 1); - }); - - /** - * Number of Lambda permission resource test - */ - test(`${testNamePrefix} Lambda permission resource count test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).resourceCountIs('AWS::Lambda::Permission', 1); - }); - - /** - * Number of Lambda IAM role resource test - */ - test(`${testNamePrefix} Lambda IAM role resource count test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).resourceCountIs('AWS::IAM::Role', 1); - }); - - /** - * Number of Lambda IAM policy resource test - */ - test(`${testNamePrefix} Lambda IAM policy resource count test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).resourceCountIs('AWS::IAM::Policy', 1); - }); - - /** - * ConfigRule awsacceleratorvalidatemaintransitgatewayCustomRule resource configuration test - */ - test(`${testNamePrefix} ConfigRule awsacceleratorvalidatemaintransitgatewayCustomRule resource configuration test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayCustomRuleB70A49C4: { - Type: 'AWS::Config::ConfigRule', - DependsOn: [ - 'awsacceleratorvalidatemaintransitgatewayFunctionCustomRulePermissionbM1jVaicvRO9SDCiAbsQcYrOlESEtMwrrF9ZQQRvd5QD9F10DC6', - 'awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F', - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1', - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', - ], - Properties: { - ConfigRuleName: 'aws-accelerator-validate-main-transit-gateway', - Description: 'Validate Main Transit Gateway', - InputParameters: { - awsConfigRegion: { - Ref: 'AWS::Region', - }, - managementAccount: { - id: { - Ref: 'AWS::AccountId', - }, - partition: { - Ref: 'AWS::Partition', - }, - }, - test: { - description: 'Validate Main Transit Gateway', - expect: 'PASS', - name: 'validate main transit gateway', - parameters: { - accountId: '333333333333', - amazonSideAsn: '65521', - autoAcceptSharingAttachments: 'enable', - defaultRouteTableAssociation: 'disable', - defaultRouteTablePropagation: 'disable', - dnsSupport: 'enable', - name: 'Main', - region: 'us-east-1', - routeTableNames: ['core', 'segregated', 'shared', 'standalone'], - shareTargetAccountIds: ['111111111111', '222222222222'], - vpnEcmpSupport: 'enable', - }, - suite: 'network', - testTarget: 'validateTransitGateway', - }, - }, - MaximumExecutionFrequency: 'Six_Hours', - Source: { - Owner: 'CUSTOM_LAMBDA', - SourceDetails: [ - { - EventSource: 'aws.config', - MaximumExecutionFrequency: 'Six_Hours', - MessageType: 'ScheduledNotification', - }, - ], - SourceIdentifier: { - 'Fn::GetAtt': ['awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F', 'Arn'], - }, - }, - }, - }, - }, - }); - }); - - /** - * Lambda function awsacceleratorvalidatemaintransitgatewayFunction resource configuration test - */ - test(`${testNamePrefix} Lambda function awsacceleratorvalidatemaintransitgatewayFunction resource configuration test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F: { - Type: 'AWS::Lambda::Function', - DependsOn: [ - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1', - 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', - ], - Properties: { - Code: { - S3Bucket: { - 'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', - }, - }, - Description: 'AWS Config custom rule function used for test case "validate main transit gateway"', - Handler: 'index.handler', - Role: { - 'Fn::GetAtt': ['awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', 'Arn'], - }, - Runtime: 'nodejs18.x', - }, - }, - }, - }); - }); - - /** - * Lambda permission awsacceleratorvalidatemaintransitgatewayFunctionPermission resource configuration test - */ - test(`${testNamePrefix} Lambda permission awsacceleratorvalidatemaintransitgatewayFunctionPermission resource configuration test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunctionCustomRulePermissionbM1jVaicvRO9SDCiAbsQcYrOlESEtMwrrF9ZQQRvd5QD9F10DC6: - { - Type: 'AWS::Lambda::Permission', - Properties: { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': ['awsacceleratorvalidatemaintransitgatewayFunction5DC44F8F', 'Arn'], - }, - Principal: 'config.amazonaws.com', - SourceAccount: { - Ref: 'AWS::AccountId', - }, - }, - }, - }, - }); - }); - - /** - * IAM role awsacceleratorvalidatemaintransitgatewayFunctionServiceRole resource configuration test - */ - test(`${testNamePrefix} IAM role awsacceleratorvalidatemaintransitgatewayFunctionServiceRole resource configuration test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38: { - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - Service: 'lambda.amazonaws.com', - }, - }, - ], - Version: '2012-10-17', - }, - ManagedPolicyArns: [ - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::aws:policy/ReadOnlyAccess', - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::aws:policy/service-role/AWSConfigRulesExecutionRole', - ], - ], - }, - ], - }, - }, - }, - }); - }); - - /** - * IAM policy awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy resource configuration test - */ - test(`${testNamePrefix} IAM policy awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy resource configuration test`, () => { - cdk.assertions.Template.fromStack(externalAccountTesterStack).templateMatches({ - Resources: { - awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1: { - Type: 'AWS::IAM::Policy', - Properties: { - PolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':iam::*:role/undefined', - ], - ], - }, - Sid: 'LambdaSTSActions', - }, - ], - Version: '2012-10-17', - }, - PolicyName: 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleDefaultPolicy21AEB3E1', - Roles: [ - { - Ref: 'awsacceleratorvalidatemaintransitgatewayFunctionServiceRoleB1766D38', - }, - ], - }, - }, - }, - }); - }); -}); diff --git a/source/packages/@aws-accelerator/tester/tsconfig.json b/source/packages/@aws-accelerator/tester/tsconfig.json deleted file mode 100644 index 5304426..0000000 --- a/source/packages/@aws-accelerator/tester/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - }, - "include": ["lib/*.ts", - "bin/**/*","index.ts" - ], - "exclude": ["cdk.out/**/*", "test/**/*"] -} diff --git a/source/packages/@aws-accelerator/tools/index.ts b/source/packages/@aws-accelerator/tools/index.ts deleted file mode 100644 index 37b9398..0000000 --- a/source/packages/@aws-accelerator/tools/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/classes/accelerator-tool'; -export * from './uninstaller'; diff --git a/source/packages/@aws-accelerator/tools/jest.config.js b/source/packages/@aws-accelerator/tools/jest.config.js deleted file mode 100644 index 19fd33b..0000000 --- a/source/packages/@aws-accelerator/tools/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 100, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts b/source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts deleted file mode 100644 index bba4df8..0000000 --- a/source/packages/@aws-accelerator/tools/lib/classes/accelerator-tool.ts +++ /dev/null @@ -1,2102 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as winston from 'winston'; - -import { GlobalConfig } from '@aws-accelerator/config'; -import { createLogger } from '@aws-accelerator/utils/lib/logger'; -import { throttlingBackOff } from '@aws-accelerator/utils/lib/throttle'; -import { getGlobalRegion } from '@aws-accelerator/utils/lib/common-functions'; -import { BackupClient, DeleteBackupVaultCommand } from '@aws-sdk/client-backup'; -import { - CloudFormationClient, - DeleteStackCommand, - DescribeStacksCommand, - ListStackResourcesCommand, - Stack, - StackStatus, - UpdateTerminationProtectionCommand, -} from '@aws-sdk/client-cloudformation'; -import { CloudWatchLogsClient, DeleteLogGroupCommand, DescribeLogGroupsCommand } from '@aws-sdk/client-cloudwatch-logs'; -import { - BatchDeleteBuildsCommand, - BatchGetProjectsCommand, - CodeBuildClient, - ListBuildsForProjectCommand, -} from '@aws-sdk/client-codebuild'; -import { CodeCommitClient, DeleteRepositoryCommand, GetFileCommand } from '@aws-sdk/client-codecommit'; -import { CodePipelineClient, GetPipelineCommand, StageDeclaration } from '@aws-sdk/client-codepipeline'; -import { DeleteRepositoryCommand as DeleteEcr, DescribeRepositoriesCommand, ECRClient } from '@aws-sdk/client-ecr'; -import { - DetachRolePolicyCommand, - IAMClient, - ListAttachedRolePoliciesCommand, - ListEntitiesForPolicyCommand, -} from '@aws-sdk/client-iam'; -import { - DescribeKeyCommand, - DisableKeyCommand, - KeyState, - KMSClient, - ScheduleKeyDeletionCommand, -} from '@aws-sdk/client-kms'; -import { ListAccountsCommand, OrganizationsClient } from '@aws-sdk/client-organizations'; -import { - DeleteBucketCommand, - DeleteObjectsCommand, - ListBucketsCommand, - ListObjectVersionsCommand, - ListObjectVersionsCommandOutput, - S3Client, -} from '@aws-sdk/client-s3'; -import { - AssumeRoleCommand, - AssumeRoleCommandOutput, - Credentials, - GetCallerIdentityCommand, - STSClient, -} from '@aws-sdk/client-sts'; -import { ConfigServiceClient, DescribeConfigRulesCommand } from '@aws-sdk/client-config-service'; - -/** - * Type for pipeline stage action information with order and action name - */ -type stageActionType = { order: number; name: string; stackPrefix: string }; - -/** - * Pipeline Stack Type - */ -type pipelineStackType = { - stageOrder: number; - order: number; - stackName: string; -}; - -type deleteStacksType = { - clients: { - cloudFormation: CloudFormationClient; - cloudWatchLogs: CloudWatchLogsClient; - s3: S3Client; - backup: BackupClient; - iam: IAMClient; - kms: KMSClient; - }; - stackName: string; - accountID: string; - region: string; -}; - -/** - * Pipeline Management Account Type, with account ID, role name to assume and sts credential - */ -type ManagementAccountType = - | { - accountId: string; - assumeRoleName: string | undefined; - credentials: Credentials | undefined; - } - | undefined; - -/** - * Accelerator AcceleratorToolProps - */ -export interface AcceleratorToolProps { - readonly installerStackName: string; - readonly partition: string; - readonly fullDestroy: boolean; - readonly deleteAccelerator: boolean; - readonly keepBootstraps: boolean; - readonly keepData: boolean; - readonly keepPipelineAndConfig: boolean; - readonly stageName: string; - readonly actionName: string; - readonly debug: boolean; - readonly ignoreTerminationProtection: boolean; -} - -/** - * AcceleratorTool Class - */ -export class AcceleratorTool { - /** - * Executing Account ID - * @private - */ - private executingAccountId: string | undefined; - - /** - * Pipeline Global Config - * @private - */ - private globalConfig: GlobalConfig | undefined; - - /** - * globalRegion - * @private - */ - private globalRegion = 'us-east-1'; - - /** - * acceleratorToolProps - * @private - */ - private readonly acceleratorToolProps: AcceleratorToolProps; - - /** - * Pipeline Source Config repository details - * @private - */ - private pipelineConfigSourceRepo: { repositoryName: string; branch: string; provider: string } | undefined; - - /** - * bootstrapBuildEnvironmentVariables - * @private - */ - private bootstrapBuildEnvironmentVariables: { name: string; value: string }[] | undefined; - - /** - * organizationAccounts - for list of accounts in organization - * @private - */ - private organizationAccounts: { - accountName: string; - accountId: string; - }[] = []; - - /** - * - */ - private deleteStackLists: deleteStacksType[] = []; - - /** - * pipelineStageActions - * List Accelerator stacks in delete order - * Any changes in stacks creation in accelerator needs will need changes of this field - * @private - */ - private pipelineStageActions: { - stage: string; - order: number; - actions: stageActionType[]; - }[] = [ - { - stage: 'Deploy', - order: 7, - actions: [ - { order: 7, name: 'Finalize', stackPrefix: '-FinalizeStack' }, - { order: 6, name: 'Customizations', stackPrefix: '-CustomizationsStack' }, - { order: 5, name: 'Network_Associations', stackPrefix: '-NetworkAssociationsStack' }, - { order: 5, name: 'Network_Associations', stackPrefix: '-NetworkAssociationsGwlbStack' }, - { order: 2, name: 'Security_Resources', stackPrefix: '-SecurityResourcesStack' }, - { order: 4, name: 'Network_VPCs', stackPrefix: '-NetworkVpcDnsStack' }, - { order: 3, name: 'Network_VPCs', stackPrefix: '-NetworkVpcEndpointsStack' }, - { order: 2, name: 'Network_VPCs', stackPrefix: '-NetworkVpcStack' }, - { order: 1, name: 'Operations', stackPrefix: '-OperationsStack' }, - { order: 1, name: 'Security', stackPrefix: '-SecurityStack' }, - { order: 1, name: 'Network_Prepare', stackPrefix: '-NetworkPrepStack' }, - ], - }, - { - stage: 'SecurityAudit', - order: 6, - actions: [{ order: 1, name: 'SecurityAudit', stackPrefix: '-SecurityAuditStack' }], - }, - { - stage: 'Organization', - order: 5, - actions: [{ order: 1, name: 'Organizations', stackPrefix: '-OrganizationsStack' }], - }, - { - stage: 'Logging', - order: 4, - actions: [ - { order: 2, name: 'Logging', stackPrefix: '-LoggingStack' }, - { order: 1, name: 'Key', stackPrefix: '-KeyStack' }, - { order: 1, name: 'Key', stackPrefix: '-DependenciesStack' }, - ], - }, - { - stage: 'Accounts', - order: 3, - actions: [{ order: 1, name: 'Accounts', stackPrefix: '-AccountsStack' }], - }, - { - stage: 'Prepare', - order: 2, - actions: [{ order: 1, name: 'Prepare', stackPrefix: '-PrepareStack' }], - }, - { - stage: 'Bootstrap', - order: 1, - actions: [{ order: 1, name: 'Bootstrap', stackPrefix: '-CDKToolkit' }], - }, - ]; - - /** - * List of pipeline stage names - */ - private pipelineStageNames: string[] = []; - - /** - * List of pipeline action names - */ - private pipelineActionNames: string[] = []; - - /** - * List of Kms key will be used to delete post stack deletion - */ - private kmsKeys: { client: KMSClient; stackName: string; key: string }[] = []; - - /** - * List of backup vaults will be used to delete post stack deletion - */ - private backupVaults: { client: BackupClient; stackName: string; backup: string }[] = []; - - /** - * List of log groups will be used to delete post stack deletion - */ - private logGroups: { - client: CloudWatchLogsClient; - stackName: string; - logGroup: string; - }[] = []; - /** - * List of buckets will be used to delete post stack deletion - */ - private buckets: { client: S3Client; stackName: string; bucket: string }[] = []; - - /** - * List of IAM roles to have policies detached prior to stack deletion - */ - private iamRoles: { client: IAMClient; stackName: string; roleName: string }[] = []; - - /** - * List of IAM policies to be detached prior to stack deletion - */ - private iamPolicies: { client: IAMClient; stackName: string; policyName: string }[] = []; - - /** - * pipelineManagementAccount - * @private - */ - private pipelineManagementAccount: ManagementAccountType = undefined; - - /** - * externalPipelineAccount object - * @private - */ - private externalPipelineAccount: { isUsed: boolean; accountId: string | undefined } = { - isUsed: false, - accountId: undefined, - }; - - /** - * acceleratorCloudFormationStacks - * @private - */ - private acceleratorCloudFormationStacks: pipelineStackType[] = []; - - private acceleratorCodeBuildProjects: string[] = []; - - private logger: winston.Logger; - - constructor(props: AcceleratorToolProps) { - this.acceleratorToolProps = props; - this.logger = createLogger(['accelerator-tool']); - } - - /** - * Function to uninstall accelerator. It is expected to completely rollback installer. - * Uninstaller can rollback following resources created by accelerator. - *
      - *
    • CloudFormation Stacks - *
    • S3 Buckets - *
    • Cloudwatch Log Groups - *
    • Codebuild Projects - *
    • CodeCommit Repository - *
    • CodePipeline - *
    - * @param installerStackName - * The name of the installer cloudformation stack - */ - public async uninstallAccelerator(installerStackName: string): Promise { - // Set global region - this.globalRegion = getGlobalRegion(this.acceleratorToolProps.partition); - - // Get executing account ID - const response = await throttlingBackOff(() => new STSClient({}).send(new GetCallerIdentityCommand({}))); - this.executingAccountId = response.Account; - - // Get installer pipeline - const installerPipeline = await AcceleratorTool.getPipelineNameFromCloudFormationStack(installerStackName); - if (!installerPipeline.status) { - this.debugLog(`${installerPipeline.pipelineName}`, 'info'); - return false; - } - - const getPipelineNameResponse = await throttlingBackOff(() => - new CodePipelineClient({}).send(new GetPipelineCommand({ name: installerPipeline.pipelineName })), - ); - - const installerCodeBuildProjectName = - getPipelineNameResponse.pipeline!.stages![1].actions![0].configuration!['ProjectName']; - - const batchGetProjectsCommandResponse = await throttlingBackOff(() => - new CodeBuildClient({}).send(new BatchGetProjectsCommand({ names: [installerCodeBuildProjectName] })), - ); - - let isQualifierUsed = false; - let acceleratorQualifier = 'AWSAccelerator'; - let acceleratorPrefix = 'AWSAccelerator'; - let repoNamePrefix = 'aws-accelerator'; - - for (const envVariable of batchGetProjectsCommandResponse.projects![0].environment!.environmentVariables!) { - if (envVariable.name === 'ACCELERATOR_QUALIFIER') { - acceleratorQualifier = envVariable.value!; - isQualifierUsed = true; - } - if (envVariable.name === 'ACCELERATOR_PREFIX') { - acceleratorPrefix = envVariable.value!; - repoNamePrefix = envVariable.value!; - } - } - - // Default assignments with prefix when no qualifier present - let testerPipelineConfigRepositoryName = `${repoNamePrefix}-test-config`; - let acceleratorPipelineStackNamePrefix = `${acceleratorPrefix}-PipelineStack`; - let acceleratorPipelineName = `${acceleratorPrefix}-Pipeline`; - // Accelerator tester configuration - let testerPipelineStackNamePrefix = `${acceleratorPrefix}-TesterPipelineStack`; - let testerStackNamePrefix = `${acceleratorPrefix}-TesterStack`; - let diagnosticsPackStackNamePrefix = `${acceleratorPrefix}-DiagnosticsPackStack`; - - // Name resources based on qualifier - if (isQualifierUsed) { - acceleratorPipelineStackNamePrefix = `${acceleratorQualifier}-pipeline-stack`; - acceleratorPipelineName = `${acceleratorQualifier}-pipeline`; - testerStackNamePrefix = `${acceleratorQualifier}-tester-stack`; - testerPipelineStackNamePrefix = `${acceleratorQualifier}-tester-pipeline-stack`; - diagnosticsPackStackNamePrefix = `${acceleratorQualifier}-DiagnosticsPackStack`; - testerPipelineConfigRepositoryName = `${acceleratorQualifier}-test-config`; - } - - //Delete accelerator target cloudformation stacks - await this.deletePipelineCloudFormationStacks(acceleratorPrefix, acceleratorPipelineName); - - // remaining cleanup is required when fullDestroy or deleteAccelerator option used - if (this.acceleratorToolProps.fullDestroy || this.acceleratorToolProps.deleteAccelerator) { - // Installer and Tester stack resource cleanup takes place in pipeline or management account, so reset the credential settings - AcceleratorTool.resetCredentialEnvironment(); - - // Delete tester stack - await this.deletePipelineAccountStack(testerStackNamePrefix); - - // Delete Diagnostics pack stack - await this.deletePipelineAccountStack(diagnosticsPackStackNamePrefix); - - // Delete tester pipeline stack when keepPipelineAndConfig not used - if (!this.acceleratorToolProps.keepPipelineAndConfig) { - await this.deleteTesterPipelineStack(testerPipelineStackNamePrefix, testerPipelineConfigRepositoryName); - } - - // Delete Accelerator Pipeline stack when keepPipelineAndConfig not used - if (!this.acceleratorToolProps.keepPipelineAndConfig) { - await this.deleteAcceleratorPipelineStack(acceleratorPipelineStackNamePrefix); - } - - // - // Start of installer cleanup only when fullDestroy used - - //Delete Installer Stack when fullDestroy used - if (this.acceleratorToolProps.fullDestroy) { - await this.deleteAcceleratorInstallerStack(installerStackName); - - // Delete bootstrap stack only when pipeline not executed from external account - if (!this.externalPipelineAccount.isUsed) { - // AcceleratorTool.resetCredentialEnvironment(); - await this.deleteStack(new CloudFormationClient({}), `${acceleratorPrefix}-CDKToolkit`); - } - //start final resource cleanup, CWL logs are re-created post CFN stack deletion so these needs to be clean - await this.finalCleanup(acceleratorPrefix, acceleratorQualifier); - } - } - return true; - } - - /** - * - * @param stsClient Function to assume role - * @param roleArn - * @returns - */ - private async assumeRole(stsClient: STSClient, roleArn: string): Promise { - return throttlingBackOff(() => - stsClient.send( - new AssumeRoleCommand({ - RoleArn: roleArn, - RoleSessionName: 'acceleratorAssumeRoleSession', - DurationSeconds: 3600, - }), - ), - ); - } - - /** - * Clears credential environment variables - * @private - */ - private static resetCredentialEnvironment() { - //reset credential variables - delete process.env['AWS_ACCESS_KEY_ID']; - delete process.env['AWS_ACCESS_KEY']; - delete process.env['AWS_SECRET_KEY']; - delete process.env['AWS_SECRET_ACCESS_KEY']; - delete process.env['AWS_SESSION_TOKEN']; - } - - /** - * Delete installer stack and resources like installer stack, installer pipeline code build projects etc. - * @param installerStackName - * @private - */ - private async deleteAcceleratorInstallerStack(installerStackName: string): Promise { - if (this.acceleratorToolProps.keepPipelineAndConfig) { - return; - } - - const installerPipeline = await AcceleratorTool.getPipelineNameFromCloudFormationStack(installerStackName); - - if (installerPipeline.status) { - const codeBuildClient = new CodeBuildClient({}); - const response = await throttlingBackOff(() => - codeBuildClient.send(new BatchGetProjectsCommand({ names: [installerPipeline.pipelineName] })), - ); - for (const project of response.projects ?? []) { - await this.deleteCodeBuilds(codeBuildClient, project.name!); - } - } - await this.deleteStack(new CloudFormationClient({}), installerStackName); - } - - /** - * Function to delete cloudformation stacks created by accelerator pipeline - * @param pipelineName - * @private - */ - private async deletePipelineCloudFormationStacks(acceleratorPrefix: string, pipelineName: string): Promise { - await this.initPipeline(acceleratorPrefix, pipelineName); - - for (const stack of this.acceleratorCloudFormationStacks) { - await this.deleteStacks(acceleratorPrefix, stack.stackName); - } - } - - /** - * Private async function to initialize required properties to perform accelerator cleanup - * @private - */ - private async initPipeline(acceleratorPrefix: string, pipelineName: string): Promise { - try { - const response = await throttlingBackOff(() => - new CodePipelineClient({}).send(new GetPipelineCommand({ name: pipelineName })), - ); - - for (const stage of response.pipeline!.stages ?? []) { - // Create list of stage names to be used for stage filter when input stage name is given - this.pipelineStageNames.push(stage.name!.toLowerCase()); - - // maintain list of code build project for the accelerator pipeline - this.getCodebuildProjects(stage); - - // Get the source repo names - this.getPipelineRepos(stage); - - // Get pipeline action names - this.getPipelineActionNames(stage); - - // Get bootstrap environment variables - await this.getBootstrapEnvVariables(stage); - } - - // - // Filter the stages to be destroy based on input stage name - try { - this.filterPipelineStages(); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - throw new Error(e); - } - - // Get Pipeline Global config - this.globalConfig = await this.getGlobalConfig(); - - // Set pipeline management account details - this.pipelineManagementAccount = await this.getPipelineManagementAccount(); - - // Get List of Accounts within organization - this.organizationAccounts = await this.getOrganizationAccountList(); - - // Order the stacks in delete order - this.acceleratorCloudFormationStacks = this.getPipelineCloudFormationStacks(acceleratorPrefix); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e.name === 'PipelineNotFoundException') { - throw new Error(`Pipeline ${pipelineName} not found!!!`); - } else { - throw new Error(e); - } - } - } - - /** - * Function to delete accelerator pipeline stack and it's resources - * @param acceleratorPipelineStackNamePrefix - * @private - */ - private async deleteAcceleratorPipelineStack(acceleratorPipelineStackNamePrefix: string): Promise { - const acceleratorPipelineStackName = `${acceleratorPipelineStackNamePrefix}-${ - this.externalPipelineAccount.isUsed - ? this.externalPipelineAccount.accountId! - : this.pipelineManagementAccount!.accountId - }-${this.globalConfig?.homeRegion}`; - - const acceleratorPipeline = await AcceleratorTool.getPipelineNameFromCloudFormationStack( - acceleratorPipelineStackName, - ); - - if ( - acceleratorPipeline.status && - (await AcceleratorTool.isConfigRepositoryCreatedByAccelerator(acceleratorPipelineStackName)) - ) { - await this.deleteCodecommitRepository( - new CodeCommitClient({}), - `${this.pipelineConfigSourceRepo?.repositoryName}`, - ); - - const codeBuildClient = new CodeBuildClient({}); - const response = await throttlingBackOff(() => - codeBuildClient.send(new BatchGetProjectsCommand({ names: [acceleratorPipeline.pipelineName] })), - ); - for (const project of response.projects ?? []) { - await this.deleteCodeBuilds(codeBuildClient, project.name!); - } - } - - await this.deleteStack(new CloudFormationClient({}), acceleratorPipelineStackName); - } - - /** - * Function to get code build environment variables - * @param buildProjectName - * @private - */ - private async getCodeBuildEnvironmentVariables( - buildProjectName: string, - ): Promise<{ name: string; value: string }[] | undefined> { - const buildEnvironmentVariables: { name: string; value: string }[] = []; - const codeBuildClient = new CodeBuildClient({}); - const response = await throttlingBackOff(() => - codeBuildClient.send(new BatchGetProjectsCommand({ names: [buildProjectName] })), - ); - - for (const envVariable of response.projects![0].environment!.environmentVariables!) { - buildEnvironmentVariables.push({ name: envVariable.name!, value: envVariable.value! }); - } - - return buildEnvironmentVariables; - } - - /** - * Get pipeline management account details - * @private - */ - private async getPipelineManagementAccount(): Promise { - let managementAccountId: string | undefined; - let managementAccountRoleName: string | undefined; - let managementAccountCredentials: Credentials | undefined; - - for (const envVariable of this.bootstrapBuildEnvironmentVariables!) { - if (envVariable.name === 'MANAGEMENT_ACCOUNT_ID') { - managementAccountId = envVariable.value; - } - if (envVariable.name === 'MANAGEMENT_ACCOUNT_ROLE_NAME') { - managementAccountRoleName = envVariable.value; - } - } - - if (this.executingAccountId !== managementAccountId && managementAccountId && managementAccountRoleName) { - managementAccountCredentials = await this.getManagementAccountCredentials( - managementAccountId, - managementAccountRoleName, - ); - this.externalPipelineAccount = { isUsed: true, accountId: this.executingAccountId! }; - return { - accountId: managementAccountId, - assumeRoleName: managementAccountRoleName, - credentials: managementAccountCredentials, - }; - } else { - this.externalPipelineAccount = { isUsed: false, accountId: managementAccountId }; - return { - accountId: this.executingAccountId!, - assumeRoleName: undefined, - credentials: undefined, - }; - } - } - - /** - * Function to get account list from organization - * @private - */ - private async getOrganizationAccountList(): Promise<{ accountName: string; accountId: string }[]> { - let organizationsClient: OrganizationsClient; - if (this.pipelineManagementAccount!.credentials) { - organizationsClient = new OrganizationsClient({ - credentials: { - secretAccessKey: this.pipelineManagementAccount!.credentials.SecretAccessKey!, - accessKeyId: this.pipelineManagementAccount!.credentials.AccessKeyId!, - sessionToken: this.pipelineManagementAccount!.credentials.SessionToken!, - expiration: this.pipelineManagementAccount!.credentials.Expiration!, - }, - }); - } else { - organizationsClient = new OrganizationsClient({}); - } - - const accountIds: { accountName: string; accountId: string }[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - organizationsClient.send(new ListAccountsCommand({ NextToken: nextToken })), - ); - for (const account of page.Accounts ?? []) { - if (account.Id && account.Name) { - accountIds.push({ accountName: account.Name, accountId: account.Id }); - } - } - nextToken = page.NextToken; - } while (nextToken); - return accountIds; - } - - /** - * Function to get cloudformation stacks created by pipeline stage actions - * @private - */ - private getPipelineCloudFormationStacks(acceleratorPrefix: string): pipelineStackType[] { - const pipelineCloudFormationStacks: pipelineStackType[] = []; - - for (const stage of this.pipelineStageActions) { - for (const action of stage.actions) { - // for (const account of this.organizationAccounts) { - pipelineCloudFormationStacks.push({ - stageOrder: stage.order, - order: action.order, - stackName: `${acceleratorPrefix}${action.stackPrefix}`, - }); - - // } - } - } - return pipelineCloudFormationStacks; - } - - private async getListObjectVersions( - s3Client: S3Client, - bucketName: string, - ): Promise { - return throttlingBackOff(() => - s3Client.send( - new ListObjectVersionsCommand({ - Bucket: bucketName, - }), - ), - ); - } - - /** - * Function to get management account credentials - * @param managementAccountId - * @param managementAccountRoleName - * @private - */ - private async getManagementAccountCredentials( - managementAccountId: string, - managementAccountRoleName: string | undefined, - ): Promise { - if (!managementAccountId && !managementAccountRoleName) { - return undefined; - } - - const roleArn = `arn:${this.acceleratorToolProps.partition}:iam::${managementAccountId}:role/${managementAccountRoleName}`; - - const stsClient = new STSClient({}); - - const assumeRoleCredential = await this.assumeRole(stsClient, roleArn); - - process.env['AWS_ACCESS_KEY_ID'] = assumeRoleCredential.Credentials!.AccessKeyId!; - process.env['AWS_ACCESS_KEY'] = assumeRoleCredential.Credentials!.AccessKeyId!; - - process.env['AWS_SECRET_KEY'] = assumeRoleCredential.Credentials!.SecretAccessKey!; - process.env['AWS_SECRET_ACCESS_KEY'] = assumeRoleCredential.Credentials!.SecretAccessKey!; - - process.env['AWS_SESSION_TOKEN'] = assumeRoleCredential.Credentials!.SessionToken; - - return assumeRoleCredential.Credentials; - } - - /** - * Function to get GlobalConfig object from the repo content - * @private - */ - private async getGlobalConfig(): Promise { - const codeCommitClient = new CodeCommitClient({}); - const response = await throttlingBackOff(() => - codeCommitClient.send( - new GetFileCommand({ - repositoryName: this.pipelineConfigSourceRepo!.repositoryName, - filePath: 'global-config.yaml', - }), - ), - ); - - const tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), 'accel-config')); - fs.writeFileSync(path.join(tempDirPath, 'global-config.yaml'), response.fileContent!, 'utf8'); - return GlobalConfig.load(tempDirPath); - } - - /** - * Function to get list of managed policies which are assigned to IAM roles by SSM automation accelerator-ec2-instance-profile-permission - * @returns - */ - private async getSsmManagedPolicies(): Promise { - //Get the actual values from the Config rule (not the securityConfig) to make sure we are getting the resolved ${ACCEL_LOOKUP} values. - const policies: string[] = []; - const configClient = new ConfigServiceClient({}); - - try { - const response = await throttlingBackOff(() => - configClient.send( - new DescribeConfigRulesCommand({ - ConfigRuleNames: ['accelerator-ec2-instance-profile-permission'], - }), - ), - ); - - //If rule not implemented, return empty array. - if (typeof response.ConfigRules![0] == 'undefined') { - return []; - } - - //else, get inputParameters from the Config rule - const configRule = response.ConfigRules![0]; - const inputParameters = configRule.InputParameters; - if (typeof inputParameters == 'undefined') { - return []; - } - - for (const [key, value] of Object.entries(JSON.parse(inputParameters))) { - if (key === 'AWSManagedPolicies' || key === 'CustomerManagedPolicies') { - policies.push(...(value as string).split(',')); - } - } - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - this.debugLog(e, 'error'); - } - - return policies; - } - - /** - * Function to delete bucket once all objects are deleted - * @param s3Client - * @param stackName - * @param bucketName - * @private - */ - private async deleteBucket(s3Client: S3Client, stackName: string, bucketName: string): Promise { - // List object and Delete objects - try { - let listObjectVersionsResponse = await this.getListObjectVersions(s3Client, bucketName); - let contents = [ - ...(listObjectVersionsResponse.Versions ?? []), - ...(listObjectVersionsResponse.DeleteMarkers ?? []), - ]; - while (contents.length > 0) { - this.debugLog(`Number of objects in ${bucketName} is ${contents.length} will be deleted`, 'info'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const records = contents.map((record: any) => ({ - Key: record.Key, - VersionId: record.VersionId, - })); - this.debugLog(`Deleting objects from bucket ${bucketName} from ${stackName} stack`, 'info'); - await throttlingBackOff(() => - s3Client.send( - new DeleteObjectsCommand({ - Bucket: bucketName, - Delete: { Objects: records }, - }), - ), - ); - - // Wait before checking again for data - await this.delay(1000); - - listObjectVersionsResponse = await this.getListObjectVersions(s3Client, bucketName); - - contents = [ - ...(listObjectVersionsResponse.Versions ?? []), - ...(listObjectVersionsResponse.DeleteMarkers ?? []), - ]; - - this.debugLog(`Number of objects in ${bucketName} is ${contents.length}, remaining to delete`, 'info'); - } - - // Delete the empty Bucket - this.debugLog(`Deleting empty bucket ${bucketName} from ${stackName} stack`, 'info'); - await throttlingBackOff(() => - s3Client.send( - new DeleteBucketCommand({ - Bucket: bucketName, - }), - ), - ); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e.name === 'NoSuchBucket') { - return true; - } - } - return true; - } - - /** - * Function to delete Cloudwatch log group - * @param cloudWatchLogsClient - * @param stackName - * @param logGroupName - * @private - */ - private async deleteCloudWatchLogs( - cloudWatchLogsClient: CloudWatchLogsClient, - stackName: string, - logGroupName: string, - ): Promise { - this.debugLog(`Deleting Cloudwatch Log group ${logGroupName} from ${stackName} stack`, 'info'); - try { - await throttlingBackOff(() => - cloudWatchLogsClient.send(new DeleteLogGroupCommand({ logGroupName: logGroupName })), - ); - } catch (ResourceNotFoundException) { - this.debugLog( - `Cloudwatch Log group delete Error Log Group NOT FOUND ${logGroupName} from ${stackName} stack`, - 'info', - ); - } - return true; - } - - private async removeSsmManagedPolicies(): Promise { - // get SsmManagedPolicies onces - const ssmManagedPolicies = await this.getSsmManagedPolicies(); - - for (const item of this.iamRoles) { - this.debugLog(`Deleting IAM Role ${item.roleName} from ${item.stackName} stack`, 'info'); - try { - // Get managed policies - const listAttachedRolePoliciesResponse = await throttlingBackOff(() => - item.client.send(new ListAttachedRolePoliciesCommand({ RoleName: item.roleName })), - ); - - //Delete managed policies - for (const policy of listAttachedRolePoliciesResponse.AttachedPolicies!) { - if (ssmManagedPolicies.indexOf(policy.PolicyName!) !== -1) { - this.debugLog( - `Managed policy ${policy.PolicyName} detached from IAM Role ${item.roleName} from ${item.stackName}`, - 'info', - ); - await throttlingBackOff(() => - item.client.send(new DetachRolePolicyCommand({ RoleName: item.roleName, PolicyArn: policy.PolicyArn })), - ); - } - } - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e.name === 'NoSuchEntity') { - this.debugLog(`IAM Role ${item.roleName} from ${item.stackName} stack not found !!`, 'info'); - } - } - } - this.iamRoles = []; - } - - private async detachPoliciesFromRole(): Promise { - for (const item of this.iamPolicies) { - this.debugLog(`Detaching IAM policy ${item.policyName} from ${item.stackName} stack`, 'info'); - - try { - // Get roles with this policy attached - const listEntitiesForPolicyResponse = await throttlingBackOff(() => - item.client.send(new ListEntitiesForPolicyCommand({ PolicyArn: item.policyName })), - ); - //Detach policy from roles - for (const role of listEntitiesForPolicyResponse.PolicyRoles!) { - this.debugLog( - `Detaching policy ${item.policyName} from role ${role.RoleName} from ${item.stackName} stack`, - 'info', - ); - await throttlingBackOff(() => - item.client.send(new DetachRolePolicyCommand({ RoleName: role.RoleName, PolicyArn: item.policyName })), - ); - } - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e.name === 'NoSuchEntity') { - this.debugLog(`IAM Policy ${item.policyName} from ${item.stackName} stack not found !!`, 'info'); - } - } - } - this.iamPolicies = []; - } - - /** - * Function to delete Cloudwatch log group - * @param backupClient - * @param stackName - * @param backupVaultName - * @private - */ - private async deleteBackupVault( - backupClient: BackupClient, - stackName: string, - backupVaultName: string, - ): Promise { - this.debugLog(`Deleting BackupVault ${backupVaultName} from ${stackName} stack`, 'info'); - - try { - await throttlingBackOff(() => - backupClient.send(new DeleteBackupVaultCommand({ BackupVaultName: backupVaultName })), - ); - } catch (ResourceNotFoundException) { - this.debugLog(`AWS BackupVault NOT FOUND ${backupVaultName} from ${stackName} stack`, 'debug'); - } - return true; - } - - /** - * Function to schedule Key deletion - * @param kMSClient - * @param stackName - * @param kmsKeyId - * @private - */ - private async scheduleKeyDeletion(kMSClient: KMSClient, stackName: string, kmsKeyId: string): Promise { - this.debugLog(`Disabling KMS Key ${kmsKeyId} from ${stackName} stack`, 'info'); - const keyStatus = await throttlingBackOff(() => kMSClient.send(new DescribeKeyCommand({ KeyId: kmsKeyId }))); - if (keyStatus.KeyMetadata?.KeyState === KeyState.Enabled) { - try { - await throttlingBackOff(() => kMSClient.send(new DisableKeyCommand({ KeyId: kmsKeyId }))); - this.debugLog(`Schedule KMS Key deletion ${kmsKeyId} from ${stackName} stack`, 'info'); - await throttlingBackOff(() => - kMSClient.send( - new ScheduleKeyDeletionCommand({ - KeyId: kmsKeyId, - PendingWindowInDays: 7, - }), - ), - ); - } catch ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - e: any - ) { - if (e.name === 'KMSInvalidStateException') { - // This is needed because session manager key is deleted with stack - this.debugLog( - '`KMS Key ${kmsKeyId} from ${stackName} stack in ${keyStatus.KeyMetadata?.KeyState} status, can not schedule deletion`', - 'info', - ); - return true; - } - } - } else { - this.debugLog( - `KMS Key ${kmsKeyId} from ${stackName} stack in ${keyStatus.KeyMetadata?.KeyState} status, can not schedule deletion`, - 'info', - ); - } - return true; - } - - /** - * Function to get pipeline name from given cloudformation stack - * @param stackName - * @private - */ - private static async getPipelineNameFromCloudFormationStack( - stackName: string, - ): Promise<{ status: boolean; pipelineName: string }> { - try { - const cloudformationClient = new CloudFormationClient({}); - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - cloudformationClient.send(new ListStackResourcesCommand({ StackName: stackName, NextToken: nextToken })), - ); - for (const stackResourceSummary of page.StackResourceSummaries ?? []) { - if (stackResourceSummary.ResourceType === 'AWS::CodePipeline::Pipeline') { - return { status: true, pipelineName: stackResourceSummary.PhysicalResourceId! }; - } - } - nextToken = page.NextToken; - } while (nextToken); - return { status: false, pipelineName: `No pipeline found in stack ${stackName}` }; - } catch (error) { - return { status: false, pipelineName: `${error}` }; - } - } - - /** - * Function to check weather accelerator pipeline created code commit repository - * @param stackName - * @private - */ - private static async isConfigRepositoryCreatedByAccelerator(stackName: string): Promise { - const cloudFormationClient = new CloudFormationClient({}); - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - cloudFormationClient.send(new ListStackResourcesCommand({ StackName: stackName, NextToken: nextToken })), - ); - for (const stackResourceSummary of page.StackResourceSummaries ?? []) { - if (stackResourceSummary.ResourceType === 'AWS::CodeCommit::Repository') { - return true; - } - } - nextToken = page.NextToken; - } while (nextToken); - - return false; - } - - /** - * Function to delete stack's resources like S3/Cloudwatch logs, KMS key - * @param accountId - * @param stackName - * @param cloudFormationClient - * @param cloudWatchLogsClient - * @param s3Client - * @param backupClient - * @param iamClient - * @param kMSClient - * @private - */ - private async prepareStackResourcesForDelete( - stackName: string, - cloudFormationClient: CloudFormationClient, - cloudWatchLogsClient: CloudWatchLogsClient, - s3Client: S3Client, - backupClient: BackupClient, - kMSClient: KMSClient, - iamClient: IAMClient, - ): Promise { - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - cloudFormationClient.send(new ListStackResourcesCommand({ StackName: stackName, NextToken: nextToken })), - ); - for (const stackResourceSummary of page.StackResourceSummaries ?? []) { - switch (stackResourceSummary.ResourceType) { - case 'AWS::KMS::Key': - this.kmsKeys.push({ - client: kMSClient, - stackName: stackName, - key: stackResourceSummary.PhysicalResourceId!, - }); - break; - case 'AWS::Backup::BackupVault': - this.backupVaults.push({ - client: backupClient, - stackName: stackName, - backup: stackResourceSummary.PhysicalResourceId!, - }); - break; - case 'AWS::Logs::LogGroup': - this.logGroups.push({ - client: cloudWatchLogsClient, - stackName: stackName, - logGroup: stackResourceSummary.PhysicalResourceId!, - }); - break; - case 'AWS::S3::Bucket': - const listBucketResponse = await throttlingBackOff(() => s3Client.send(new ListBucketsCommand({}))); - for (const bucket of listBucketResponse.Buckets!) { - if (bucket.Name === stackResourceSummary.PhysicalResourceId) { - this.buckets.push({ - client: s3Client, - stackName: stackName, - bucket: stackResourceSummary.PhysicalResourceId!, - }); - } - } - break; - case 'AWS::IAM::Role': - // This is needed because roles may have policies added from the 'accelerator-ec2-instance-profile-permission' - // config rule which will cause stack deletion to fail - this.iamRoles.push({ - client: iamClient, - stackName: stackName, - roleName: stackResourceSummary.PhysicalResourceId!, - }); - break; - case 'AWS::IAM::ManagedPolicy': - // This is needed because the SecurityResources stack creates a policy that can be attached to roles for SSM logging. - // We have to detach the policy from any roles or the stack will fail to delete the policy. - this.iamPolicies.push({ - client: iamClient, - stackName: stackName, - policyName: stackResourceSummary.PhysicalResourceId!, - }); - break; - } - } - nextToken = page.NextToken; - } while (nextToken); - } - - /** - * Function to check if given stack deletion completed. - * @param cloudFormationClient - * @param stackName - * @private - */ - private async isStackDeletionCompleted( - cloudFormationClient: CloudFormationClient, - stackName: string, - retryAttempt: number, - maxRetry: number, - ): Promise<'FAILED' | 'COMPLETE' | 'IN_PROGRESS'> { - if (retryAttempt === 1) { - await this.delay(30000); - } - try { - const response = await throttlingBackOff(() => - cloudFormationClient.send(new DescribeStacksCommand({ StackName: stackName })), - ); - if (response.Stacks![0].StackStatus === StackStatus.DELETE_FAILED) { - if (retryAttempt > maxRetry) { - throw Error( - `Stack ${stackName} failed to delete, ${retryAttempt} times delete attempted, investigate and fix the issue and rerun uninstaller`, - ); - } else { - return 'FAILED'; - } - } - if (response.Stacks![0].StackStatus === StackStatus.DELETE_IN_PROGRESS) { - if (retryAttempt > maxRetry) { - throw Error( - `Stack ${stackName} failed to delete, stack is in ${ - response.Stacks![0].StackStatus - } status for more than 10 minutes, uninstaller exited!!!`, - ); - } - this.debugLog(`Stack ${stackName} deletion in-progress.....`, 'display'); - return 'IN_PROGRESS'; - } - if (response.Stacks![0].StackStatus === StackStatus.DELETE_COMPLETE) { - return 'COMPLETE'; - } - } catch (error) { - if (`${error}`.includes(`Stack ${stackName} does not exist`)) { - return 'COMPLETE'; - } - } - - return 'COMPLETE'; - } - - /** - * Function to delete cloudformation stack - * @param stackName - * @private - */ - private async deleteStacks(acceleratorPrefix: string, stackName: string): Promise { - // const promises: Promise[] = []; - // const deleteStackStartedPromises: Promise[] = []; - // const deleteStackCompletedPromises: Promise[] = []; - const assumeRoleName = this.globalConfig?.managementAccountAccessRole || 'AWSControlTowerExecution'; - let cloudFormationClient: CloudFormationClient; - let s3Client: S3Client; - let kMSClient: KMSClient; - let cloudWatchLogsClient: CloudWatchLogsClient; - let backupClient: BackupClient; - let iamClient: IAMClient; - - // Make list of regions for cleanup of stacks, this is required because some stack goes to global region - const cleanupRegions: string[] = []; - for (const region of this.globalConfig?.enabledRegions || []) { - cleanupRegions.push(region); - } - - if (cleanupRegions.indexOf(this.globalRegion) === -1) { - cleanupRegions.push(this.globalRegion); - } - - // let cloudFormationStack: Stack | undefined; - //Use a loop for all regions - for (const region of cleanupRegions) { - for (const account of this.organizationAccounts) { - if (account.accountId !== this.pipelineManagementAccount?.accountId) { - const roleArn = `arn:${this.acceleratorToolProps.partition}:iam::${account.accountId}:role/${assumeRoleName}`; - const stsClient = new STSClient({ region: region }); - const assumeRoleCredential = await this.assumeRole(stsClient, roleArn); - cloudFormationClient = new CloudFormationClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - s3Client = new S3Client({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - - cloudWatchLogsClient = new CloudWatchLogsClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - backupClient = new BackupClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - iamClient = new IAMClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - kMSClient = new KMSClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - } else { - cloudFormationClient = new CloudFormationClient({ region: region }); - s3Client = new S3Client({ region: region }); - cloudWatchLogsClient = new CloudWatchLogsClient({ region: region }); - backupClient = new BackupClient({ region: region }); - iamClient = new IAMClient({ region: region }); - kMSClient = new KMSClient({ region: region }); - } - - // Exclude management account home region bootstrap deletion before pipeline stack and installer stacks are deleted, conditions are - // 1. When pipeline account is not used - // 2. When stack is for home region - // 3. When it is bootstrap stack - // 4. When stack is part of management account - // 5. When keepBootstraps flag is OFF - if ( - // cloudFormationStack && - !this.externalPipelineAccount.isUsed && - this.globalConfig?.homeRegion === region && - stackName === `${acceleratorPrefix}-CDKToolkit` && - this.pipelineManagementAccount!.accountId === account.accountId && - !this.acceleratorToolProps.keepBootstraps - ) { - this.debugLog(`Management account home region bootstrap stack deletion excluded`, 'info'); - this.debugLog( - `${stackName} stack region is ${region} and home region is ${this.globalConfig?.homeRegion}`, - 'info', - ); - } else { - this.deleteStackLists.push({ - clients: { - cloudFormation: cloudFormationClient, - cloudWatchLogs: cloudWatchLogsClient, - s3: s3Client, - backup: backupClient, - iam: iamClient, - kms: kMSClient, - }, - stackName: stackName!, - accountID: account.accountId, - region: region, - }); - } - } - } - await this.completeStacksDeletion(acceleratorPrefix); - } - - /** - * Function to see is cloudformation stack is deletable - * If stack termination protection is ON and uninstaller flag to ignore termination protection is on then it is considered to be deletable - * @param cloudFormationClient - * @param stackName - * @param accountId - * @param region - * @private - */ - private async disableStackTermination( - cloudFormationClient: CloudFormationClient, - stackName: string, - accountId?: string, - region?: string, - ): Promise { - let enableTerminationProtection = false; - const response = await throttlingBackOff(() => - cloudFormationClient.send(new DescribeStacksCommand({ StackName: stackName })), - ); - enableTerminationProtection = response.Stacks![0].EnableTerminationProtection ?? false; - if (enableTerminationProtection) { - if (this.acceleratorToolProps.ignoreTerminationProtection) { - this.debugLog( - `Stack ${stackName} termination protection is enabled, disabling the termination protection"`, - 'info', - ); - await throttlingBackOff(() => - cloudFormationClient.send( - new UpdateTerminationProtectionCommand({ - StackName: stackName, - EnableTerminationProtection: false, - }), - ), - ); - this.debugLog(`Waiting stack ${stackName} update completion"`, 'info'); - - await this.delay(1000); - - return true; - } else { - if (stackName && accountId) { - this.debugLog( - `Due to termination protection enable skipping deletion of CloudFormation stack ${stackName} in ${accountId} account from ${region} region`, - 'warn', - ); - } else { - this.debugLog( - `Due to termination protection enable skipping deletion of CloudFormation stack ${stackName}`, - 'warn', - ); - } - this.debugLog(`Un-installation STOPPED, due to termination protection of stack ${stackName}!!!"`, 'warn'); - process.abort(); - } - } else { - return true; - } - } - - /** - * Function to delete stacks in pipeline account accelerator tester stack - * @param stackNamePrefix - * @private - */ - private async deletePipelineAccountStack(stackNamePrefix: string): Promise { - const cloudFormationClient = new CloudFormationClient({}); - const testerStackName = `${stackNamePrefix}-${ - this.externalPipelineAccount.isUsed - ? this.externalPipelineAccount.accountId! - : this.pipelineManagementAccount!.accountId - }-${this.globalConfig?.homeRegion}`; - - await this.deleteStack(cloudFormationClient, testerStackName); - } - - /** - * Function to delete accelerator tester pipeline stack - * @param testerPipelineStackNamePrefix - * @param testerPipelineConfigRepositoryName - * @private - */ - private async deleteTesterPipelineStack( - testerPipelineStackNamePrefix: string, - testerPipelineConfigRepositoryName: string, - ): Promise { - const cloudFormationClient = new CloudFormationClient({}); - - if (this.acceleratorToolProps.keepPipelineAndConfig) { - return; - } - - const testerPipelineStackName = `${testerPipelineStackNamePrefix}-${ - this.externalPipelineAccount.isUsed - ? this.externalPipelineAccount.accountId! - : this.pipelineManagementAccount!.accountId - }-${this.globalConfig?.homeRegion}`; - - if (!this.acceleratorToolProps.keepPipelineAndConfig) { - await this.deleteCodecommitRepository(new CodeCommitClient({}), testerPipelineConfigRepositoryName); - } - - const testerPipeline = await AcceleratorTool.getPipelineNameFromCloudFormationStack(testerPipelineStackName); - - if (testerPipeline.status) { - const codeBuildClient = new CodeBuildClient({}); - const response = await throttlingBackOff(() => - codeBuildClient.send(new BatchGetProjectsCommand({ names: [testerPipeline.pipelineName] })), - ); - for (const project of response.projects ?? []) { - await this.deleteCodeBuilds(codeBuildClient, project.name!); - } - } - - await this.deleteStack(cloudFormationClient, testerPipelineStackName); - } - - /** - * Function to delete cloudformation stack - * @param cloudFormationClient - * @param stackName - * @private - */ - private async deleteStack(cloudFormationClient: CloudFormationClient, stackName: string): Promise { - try { - await throttlingBackOff(() => cloudFormationClient.send(new DescribeStacksCommand({ StackName: stackName }))); - // cloudFormationStack = response.Stacks![0].StackName; - } catch (error) { - if (`${error}`.includes(`Stack with id ${stackName} does not exist`)) { - this.debugLog(`Stack ${stackName} does not exist`, 'warn'); - return; - } - } - - if (!(await this.disableStackTermination(cloudFormationClient, stackName))) { - return; - } - - // Prepare list of resources to be deleted before and after stack deletion - await this.prepareStackResourcesForDelete( - stackName, - cloudFormationClient, - new CloudWatchLogsClient({}), - new S3Client({}), - new BackupClient({}), - new KMSClient({}), - new IAMClient({}), - ); - - // Delete resource before stack deletion - await this.deletePreStackDeleteResources(); - - await this.completeStackDeletion(cloudFormationClient, stackName); - - // Delete resource after stack deletion - await this.deletePostStackDeleteResources(); - } - - /** - * Function to delete code commit repository - * @param codeCommitClient - * @param repositoryName - * @private - */ - private async deleteCodecommitRepository(codeCommitClient: CodeCommitClient, repositoryName: string): Promise { - //Delete config repository - this.debugLog(`CodeCommit repository ${repositoryName} deletion started`, 'display'); - await throttlingBackOff(() => - codeCommitClient.send(new DeleteRepositoryCommand({ repositoryName: repositoryName })), - ); - this.debugLog(`CodeCommit repository ${repositoryName} deletion completed`, 'display'); - } - - /** - * Function to delete build ids for code build project - * @param codeBuildClient - * @param buildProjectName - * @private - */ - private async deleteCodeBuilds(codeBuildClient: CodeBuildClient, buildProjectName: string): Promise { - //delete code build projects before deleting pipeline - const buildIds: string[] = []; - let nextToken: string | undefined = undefined; - do { - const page = await throttlingBackOff(() => - codeBuildClient.send(new ListBuildsForProjectCommand({ projectName: buildProjectName, nextToken })), - ); - for (const id of page.ids!) { - buildIds.push(id); - } - nextToken = page.nextToken; - } while (nextToken); - this.debugLog(`Deleting build ids for the project ${buildProjectName}`, 'info'); - await throttlingBackOff(() => codeBuildClient.send(new BatchDeleteBuildsCommand({ ids: buildIds }))); - } - - /** - * - * @returns Function to filter stages to be deleted based on input stage name - */ - private filterPipelineStages() { - const requiredStages: { - stage: string; - order: number; - actions: stageActionType[]; - }[] = []; - let stageOrder = 0; - let actionOrder = 0; - - // filter based on stage name - - if (this.acceleratorToolProps.stageName !== 'all') { - if (this.pipelineStageNames.indexOf(this.acceleratorToolProps.stageName.toLowerCase()) === -1) { - throw new Error(`Invalid pipeline stage name ${this.acceleratorToolProps.stageName}`); - } - - for (const stage of this.pipelineStageActions) { - if (stage.stage.toLowerCase() === this.acceleratorToolProps.stageName.toLowerCase()) { - stageOrder = stage.order; - } - } - this.pipelineStageActions = this.pipelineStageActions.filter(item => item.order >= stageOrder); - } - - // Exclude bootstrap stacks when keepBootstraps flag is on - if (this.acceleratorToolProps.keepBootstraps) { - this.pipelineStageActions = this.pipelineStageActions.filter(item => item.stage.toLowerCase() !== 'bootstrap'); - } - - // - // Filter based on action name - stageOrder = 0; - if (this.acceleratorToolProps.actionName !== 'all') { - if (this.pipelineActionNames.indexOf(this.acceleratorToolProps.actionName.toLowerCase()) === -1) { - throw new Error(`Invalid pipeline action name ${this.acceleratorToolProps.actionName}`); - } - - for (const stage of this.pipelineStageActions) { - for (const action of stage.actions) { - if (action.name.toLowerCase() === this.acceleratorToolProps.actionName.toLowerCase()) { - actionOrder = action.order; - stageOrder = stage.order; - } - } - } - } - this.pipelineStageActions = this.pipelineStageActions.filter(item => item.order >= stageOrder); - - for (const stage of this.pipelineStageActions) { - for (const action of stage.actions!.filter(item => item.order >= actionOrder)) { - requiredStages.push({ stage: stage.stage, order: stage.order, actions: [action] }); - } - } - - this.pipelineStageActions = requiredStages; - } - - /** - * Function to get pipeline repository names - * @param stage - */ - private getPipelineRepos(stage: StageDeclaration) { - if (stage.name === 'Source') { - for (const action of stage.actions!) { - if (action.name! === 'Configuration') { - this.pipelineConfigSourceRepo = { - repositoryName: action.configuration!['RepositoryName'], - branch: action.configuration!['BranchName'], - provider: action.actionTypeId!.provider!, - }; - } - } - } - } - - /** - * Function to get pipeline action names - * @param stage - */ - private getPipelineActionNames(stage: StageDeclaration) { - if (stage.name !== 'Source' && stage.name !== 'Build' && stage.name !== 'Review') { - for (const action of stage.actions!) { - this.pipelineActionNames.push(action.name!.toLowerCase()); - } - } - } - - /** - * Function to get pipeline build projects - */ - private getCodebuildProjects(stage: StageDeclaration) { - for (const action of stage.actions ?? []) { - if ( - action.configuration!['ProjectName'] && - !this.acceleratorCodeBuildProjects.find(item => item === action.configuration!['ProjectName']) - ) { - this.acceleratorCodeBuildProjects.push(action.configuration!['ProjectName']); - } - } - } - - /** - * Function to get pipeline bootstrap stage environment variables - * @param stage - */ - private async getBootstrapEnvVariables(stage: StageDeclaration) { - if (stage.name === 'Bootstrap') { - this.bootstrapBuildEnvironmentVariables = await this.getCodeBuildEnvironmentVariables( - stage.actions![0].configuration!['ProjectName'], - ); - } - } - - /** - * Function to schedule deletion of ksm keys post stack deletion - */ - private async deleteKmsKeys() { - if (!this.acceleratorToolProps.keepData) { - for (const item of this.kmsKeys) { - await this.scheduleKeyDeletion(item.client, item.stackName, item.key); - } - } - this.kmsKeys = []; - } - - /** - * Function to delete backup vaults - */ - private async deleteBackupVaults() { - if (!this.acceleratorToolProps.keepData) { - for (const item of this.backupVaults) { - await this.deleteBackupVault(item.client, item.stackName, item.backup); - } - } - this.backupVaults = []; - } - - /** - * Function to delete log groups - */ - private async deleteLogGroups() { - if (!this.acceleratorToolProps.keepData) { - for (const item of this.logGroups) { - await this.deleteCloudWatchLogs(item.client, item.stackName, item.logGroup); - } - } - this.logGroups = []; - } - - /** - * Function to delete buckets post stack deletion, if buckets deleted before stack replication custom resource will fail - */ - private async deleteBuckets() { - if (!this.acceleratorToolProps.keepData) { - for (const item of this.buckets) { - await this.deleteBucket(item.client, item.stackName, item.bucket); - } - } - this.buckets = []; - } - - /** - * Function to check if stack exists - * @param cloudFormationClient - * @param stackName - * @param accountId - * @param region - * @returns - */ - private async validateStackExistence( - cloudFormationClient: CloudFormationClient, - stackName: string, - accountId: string, - region: string, - ): Promise { - let cloudFormationStack: Stack | undefined; - try { - const response = await throttlingBackOff(() => - cloudFormationClient.send(new DescribeStacksCommand({ StackName: stackName })), - ); - cloudFormationStack = response.Stacks![0]; - } catch (error) { - if (`${error}`.includes(`Stack with id ${stackName} does not exist`)) { - this.debugLog(`Stack ${stackName} does not exist in ${accountId} account in ${region} region`, 'info'); - cloudFormationStack = undefined; - } - } - - return cloudFormationStack; - } - - private async completeStacksDeletion(acceleratorPrefix: string) { - const promises: Promise[] = []; - let stackName = ''; - for (const item of this.deleteStackLists) { - stackName = item.stackName; - const fullyQualifiedStackName = - item.stackName === `${acceleratorPrefix}-CDKToolkit` - ? `${acceleratorPrefix}-CDKToolkit` - : `${item.stackName}-${item.accountID}-${item.region}`; - - const stackExists = await this.validateStackExistence( - item.clients.cloudFormation, - fullyQualifiedStackName, - item.accountID, - item.region, - ); - - if (stackExists) { - await this.disableStackTermination( - item.clients.cloudFormation, - fullyQualifiedStackName, - item.accountID, - item.region, - ); - - // Prepare list of resources to be deleted before and after stack deletion - await this.prepareStackResourcesForDelete( - fullyQualifiedStackName, - item.clients.cloudFormation, - item.clients.cloudWatchLogs, - item.clients.s3, - item.clients.backup, - item.clients.kms, - item.clients.iam, - ); - - promises.push(this.cleanupStack(item.clients.cloudFormation, fullyQualifiedStackName)); - } - } - - if (promises.length >= 0) { - await Promise.all(promises); - } - - await Promise.all(promises); - - // - // Network vpcs stack takes time to deallocate IPAM - if (promises.length > 0 && stackName === `${acceleratorPrefix}-NetworkVpcStack`) { - await this.delay(360000); - } - this.deleteStackLists = []; - } - - /** - * Function to clean stack and it's resources - * @param cloudFormationClient - * @param stackName - */ - private async cleanupStack(cloudFormationClient: CloudFormationClient, stackName: string) { - await this.deletePreStackDeleteResources(); - await this.completeStackDeletion(cloudFormationClient, stackName); - await this.deletePostStackDeleteResources(); - } - - /** - * Function to delete resources before stack deletion - */ - private async deletePreStackDeleteResources(): Promise { - // 1. For roles in this stack, remove SsmManagedPolicies that would prevent role deletion - await this.removeSsmManagedPolicies(); - // 2. For policies in this stack, detach from any roles that would prevent policy deletion - await this.detachPoliciesFromRole(); - } - - /** - * Function to delete resources after stack deletion - */ - private async deletePostStackDeleteResources(): Promise { - // Delete cloudwatch log groups - await this.deleteLogGroups(); - - // Delete backup vaults - await this.deleteBackupVaults(); - - // Delete buckets - await this.deleteBuckets(); - - // Delete KMS keys - await this.deleteKmsKeys(); - } - - private async completeStackDeletion(cloudFormationClient: CloudFormationClient, stackName: string): Promise { - let retryAttempt = 1; - this.debugLog(`Stack ${stackName} deletion started.`, 'display'); - await throttlingBackOff(() => cloudFormationClient.send(new DeleteStackCommand({ StackName: stackName }))); - - let stackDeleteStatus = await this.isStackDeletionCompleted(cloudFormationClient, stackName, retryAttempt, 2); - - while (stackDeleteStatus !== 'COMPLETE') { - // Wait before retry - await this.delay(60000); - if (stackDeleteStatus === 'FAILED') { - retryAttempt = retryAttempt + 1; - this.debugLog( - `Stack ${stackName} deletion status ${stackDeleteStatus}, retry deletion, retry count ${retryAttempt}`, - 'info', - ); - - await throttlingBackOff(() => cloudFormationClient.send(new DeleteStackCommand({ StackName: stackName }))); - stackDeleteStatus = await this.isStackDeletionCompleted(cloudFormationClient, stackName, retryAttempt, 3); - } else { - stackDeleteStatus = await this.isStackDeletionCompleted(cloudFormationClient, stackName, retryAttempt, 10); - } - } - - this.debugLog(`Stack ${stackName} deletion completed.`, 'display'); - } - - private delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - private debugLog(message: string, messageType: string) { - switch (messageType) { - case 'warn': - this.logger.warn(message); - break; - case 'info': - if (this.acceleratorToolProps.debug) { - this.logger.warn(message); - } - break; - case 'debug': - if (this.acceleratorToolProps.debug) { - this.logger.warn(message); - } - break; - case 'display': - this.logger.info(message); - break; - } - } - - private async finalCleanup(acceleratorPrefix: string, acceleratorQualifier: string): Promise { - //cleanup CWL logs - await this.deleteAllRemainingCloudWatchLogGroups(acceleratorPrefix, acceleratorQualifier); - - await this.deleteAllEcrs(); - return; - } - - private async deleteAllEcrs(): Promise { - const assumeRoleName = this.globalConfig?.managementAccountAccessRole || 'AWSControlTowerExecution'; - let ecrClient: ECRClient; - - // Make list of regions for cleanup of stacks, this is required because some stack goes to global region - const cleanupRegions: string[] = []; - for (const region of this.globalConfig?.enabledRegions || []) { - cleanupRegions.push(region); - } - - if (cleanupRegions.indexOf(this.globalRegion) === -1) { - cleanupRegions.push(this.globalRegion); - } - - //Use a loop for all regions - for (const region of cleanupRegions) { - for (const account of this.organizationAccounts) { - if (account.accountId !== this.pipelineManagementAccount?.accountId) { - const roleArn = `arn:${this.acceleratorToolProps.partition}:iam::${account.accountId}:role/${assumeRoleName}`; - const stsClient = new STSClient({ region: region }); - const assumeRoleCredential = await this.assumeRole(stsClient, roleArn); - - ecrClient = new ECRClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - } else { - ecrClient = new ECRClient({ region: region }); - } - - this.debugLog(`Final Ecrs cleanup started in region ${region} of account ${account.accountName}`, 'display'); - let nextToken: string | undefined = undefined; - const repositories: string[] = []; - try { - do { - const page = await throttlingBackOff(() => - ecrClient.send( - new DescribeRepositoriesCommand({ - repositoryNames: [`cdk-accel-container-assets-${account.accountId}-${region}`], - nextToken: nextToken, - }), - ), - ); - for (const repository of page.repositories ?? []) { - repositories.push(repository.repositoryName!); - } - nextToken = page.nextToken; - } while (nextToken); - } catch (RepositoryNotFoundException) { - this.debugLog( - `Ecr delete Error, repository NOT FOUND cdk-accel-container-assets-${account.accountId}-${region} in region ${region} of account ${account.accountName}`, - 'info', - ); - } - - for (const repository of repositories) { - this.debugLog(`Deleting Ecr ${repository} in region ${region} of account ${account.accountName}`, 'info'); - try { - await throttlingBackOff(() => ecrClient.send(new DeleteEcr({ repositoryName: repository }))); - this.debugLog( - `Ecr ${repository} in region ${region} of account ${account.accountName} deleted successfully`, - 'info', - ); - } catch (RepositoryNotFoundException) { - this.debugLog( - `Ecr delete Error, repository NOT FOUND ${repository} in region ${region} of account ${account.accountName}`, - 'info', - ); - } - } - } - } - } - - private async deleteAllRemainingCloudWatchLogGroups( - acceleratorPrefix: string, - acceleratorQualifier: string, - ): Promise { - const assumeRoleName = this.globalConfig?.managementAccountAccessRole || 'AWSControlTowerExecution'; - let cloudWatchLogsClient: CloudWatchLogsClient; - - // Make list of regions for cleanup of stacks, this is required because some stack goes to global region - const cleanupRegions: string[] = []; - for (const region of this.globalConfig?.enabledRegions || []) { - cleanupRegions.push(region); - } - - if (cleanupRegions.indexOf(this.globalRegion) === -1) { - cleanupRegions.push(this.globalRegion); - } - - // let cloudFormationStack: Stack | undefined; - //Use a loop for all regions - for (const region of cleanupRegions) { - for (const account of this.organizationAccounts) { - if (account.accountId !== this.pipelineManagementAccount?.accountId) { - const roleArn = `arn:${this.acceleratorToolProps.partition}:iam::${account.accountId}:role/${assumeRoleName}`; - const stsClient = new STSClient({ region: region }); - const assumeRoleCredential = await this.assumeRole(stsClient, roleArn); - - cloudWatchLogsClient = new CloudWatchLogsClient({ - region: region, - credentials: { - accessKeyId: assumeRoleCredential.Credentials!.AccessKeyId!, - secretAccessKey: assumeRoleCredential.Credentials!.SecretAccessKey!, - sessionToken: assumeRoleCredential.Credentials!.SessionToken, - expiration: assumeRoleCredential.Credentials!.Expiration, - }, - }); - } else { - cloudWatchLogsClient = new CloudWatchLogsClient({ region: region }); - } - - this.debugLog(`Final log groups cleanup started in region ${region} of account ${account}`, 'display'); - - let nextToken: string | undefined = undefined; - const logGroupNames: string[] = []; - do { - const page = await throttlingBackOff(() => - cloudWatchLogsClient.send( - new DescribeLogGroupsCommand({ - logGroupNamePrefix: `/aws/codebuild/${acceleratorQualifier}`, - nextToken: nextToken, - }), - ), - ); - for (const logGroup of page.logGroups ?? []) { - logGroupNames.push(logGroup.logGroupName!); - } - nextToken = page.nextToken; - } while (nextToken); - - nextToken = undefined; - do { - const page = await throttlingBackOff(() => - cloudWatchLogsClient.send( - new DescribeLogGroupsCommand({ - logGroupNamePrefix: `/aws/lambda/${acceleratorQualifier}`, - nextToken: nextToken, - }), - ), - ); - for (const logGroup of page.logGroups ?? []) { - logGroupNames.push(logGroup.logGroupName!); - } - nextToken = page.nextToken; - } while (nextToken); - - // Add /AWSAccelerator-SecurityHub log groups for deletion - logGroupNames.push(`/${acceleratorPrefix}-SecurityHub`); - - for (const logGroupName of logGroupNames) { - this.debugLog( - `Deleting Cloudwatch Log group ${logGroupName} in region ${region} of account ${account}`, - 'info', - ); - try { - await throttlingBackOff(() => - cloudWatchLogsClient.send(new DeleteLogGroupCommand({ logGroupName: logGroupName })), - ); - } catch (ResourceNotFoundException) { - this.debugLog( - `Cloudwatch Log group delete Error Log Group NOT FOUND ${logGroupName} in region ${region} of account ${account}`, - 'info', - ); - } - } - - // cleanup the - logGroupNames.length = 0; - this.debugLog(`Log groups cleanup completed in region ${region} of account ${account}`, 'display'); - } - } - } -} diff --git a/source/packages/@aws-accelerator/tools/package.json b/source/packages/@aws-accelerator/tools/package.json deleted file mode 100644 index 21f35ee..0000000 --- a/source/packages/@aws-accelerator/tools/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "@aws-accelerator/tools", - "version": "0.0.0", - "private": true, - "description": "The tools package for the accelerator solution.", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "@aws-accelerator/config": "^0.0.0", - "@aws-accelerator/utils": "^0.0.0", - "@aws-cdk/cx-api": "2.93.0", - "@aws-sdk/client-backup": "3.410.0", - "@aws-sdk/client-cloudformation": "3.410.0", - "@aws-sdk/client-cloudwatch-logs": "3.410.0", - "@aws-sdk/client-codebuild": "3.410.0", - "@aws-sdk/client-codecommit": "3.410.0", - "@aws-sdk/client-codepipeline": "3.410.0", - "@aws-sdk/client-ecr": "3.410.0", - "@aws-sdk/client-iam": "3.410.0", - "@aws-sdk/client-kms": "3.410.0", - "@aws-sdk/client-organizations": "3.410.0", - "@aws-sdk/client-s3": "3.412.0", - "yargs": "17.7.1" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "@typescript-eslint/eslint-plugin": "5.53.0", - "@typescript-eslint/parser": "5.53.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "ts-node": "10.9.1", - "typescript": "4.9.5" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-accelerator/tools/test/uninstaller.test.ts b/source/packages/@aws-accelerator/tools/test/uninstaller.test.ts deleted file mode 100644 index 12cfeee..0000000 --- a/source/packages/@aws-accelerator/tools/test/uninstaller.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -test('Uninstaller test', () => { - // // const app = new cdk.App(); - // // // WHEN - // // const stack = new Installer.InstallerStack(app, 'MyTestStack'); - // // // THEN - // // // expectCDK(stack).to(matchTemplate({ - // // // "Resources": {} - // // // }, MatchStyle.EXACT)) -}); diff --git a/source/packages/@aws-accelerator/tools/tsconfig.json b/source/packages/@aws-accelerator/tools/tsconfig.json deleted file mode 100644 index cdfe738..0000000 --- a/source/packages/@aws-accelerator/tools/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "lib": ["dom"] - }, - "include": ["lib/**/*", "index.ts", "uninstaller.ts"] -} \ No newline at end of file diff --git a/source/packages/@aws-accelerator/tools/uninstaller.ts b/source/packages/@aws-accelerator/tools/uninstaller.ts deleted file mode 100644 index 6c3cb0a..0000000 --- a/source/packages/@aws-accelerator/tools/uninstaller.ts +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import yargs from 'yargs'; - -import { createLogger } from '@aws-accelerator/utils/lib/logger'; - -import { AcceleratorTool } from './lib/classes/accelerator-tool'; - -/** - * AWS Accelerator Uninstaller tool entry point. - * Script Options: - *
      - *
    • --installer-stack-name The name of the installer cloudformation stack - *
    • --partition AWS partition - *
    • --debug Display debug logs - *
    • --full-destroy When used, uninstaller will delete everything, including installer stack and pipeline and LZA pipeline stack and pipeline. - * Any other flags used with full-destroy will be disregarded. - *
    • --delete-accelerator - * a) When this flag is set to true delete every CFN stacks (override termination protection) deployed by LZA pipeline. - * b) Delete every resources deployed by LZA pipeline stacks, if keep-data flag is used S3 and CW logs will not be deleted. - * c) Bootstrap stacks will be deleted unless keep-bootstraps flag is used - * d) LZA pipeline and config repo will be deleted unless keep-pipeline flag is used - * e) stage-name and action-name flags will be disregarded - *
        - *
      • --keep-pipeline This flag is used along with delete-accelerator flag to reduce scope of uninstaller. When used, LZA pipeline and config repo will not be deleted - *
      • --keep-data This flag is used along with delete-accelerator flag to reduce scope of uninstaller.When used, S3 and CW logs will not be deleted. - * This flag is not applicable to bootstrap stack buckets, bootstrap stacks bucket deletion depends on keep-bootstraps flag. - *
      • -- This flag is used along with delete-accelerator flag to reduce scope of uninstaller. When used, bootstrap stacks will not be deleted - *
      - *
    • --stage-name When used, delete every CFN stacks and resources from the pipeline stage name to the end of the pipeline. If keep-data is used S3 and CW logs will not be deleted. action-name flag will be disregarded. - *
    • --action-name When used, delete every CFN stacks and resources from the pipeline stage action to the end of the pipeline. If keep-data is used S3 and CW logs will not be deleted. - *
    - * @example - * ts-node uninstaller.ts --installer-stack-name --partition --full-destroy - */ - -const logger = createLogger(['uninstaller']); -const scriptUsage = - 'Usage: yarn run ts-node --transpile-only uninstaller.ts --installer-stack-name --partition [--debug] [--full-destroy] [--delete-accelerator] [--keep-pipeline] [--keep-data] [--keep-bootstraps] [--stage-name] [--action-name] '; -async function main(): Promise { - const usage = `** Script Usage ** ${scriptUsage}`; - - const argv = yargs(process.argv.slice(2)) - .options({ - installerStackName: { type: 'string', default: 'AWSAccelerator-InstallerStack' }, - partition: { type: 'string', default: 'aws' }, - debug: { type: 'boolean', default: false }, - fullDestroy: { type: 'boolean', default: false }, - deleteAccelerator: { type: 'boolean', default: false }, - stageName: { type: 'string', default: 'all' }, - actionName: { type: 'string', default: 'all' }, - }) - .parseSync(); - - const installerStackName = argv.installerStackName; - - const partition = argv.partition; - const debug = argv.debug; - const ignoreTerminationProtection = true; - - const fullDestroy = argv.fullDestroy; - const deleteAccelerator = argv.deleteAccelerator; - - let stageName = argv.stageName; - let actionName = argv.actionName; - - let keepPipelineAndConfig = false; - let keepData = false; - let keepBootstraps = false; - - // - // Validate parameters - const errorMessage = validateDeleteOptions(installerStackName, fullDestroy, deleteAccelerator, stageName, actionName); - - if (errorMessage) { - throw new Error(errorMessage); - } - - // When full destroy option provided - if (fullDestroy) { - stageName = 'all'; - actionName = 'all'; - - return await uninstaller( - installerStackName, - partition, - debug, - ignoreTerminationProtection, - fullDestroy, - deleteAccelerator, - keepPipelineAndConfig, - keepData, - keepBootstraps, - stageName, - actionName, - ); - } - - // When delete accelerator option provided - if (deleteAccelerator) { - keepPipelineAndConfig = (argv['keepPipelineAndConfig'] as boolean) ?? false; - keepData = (argv['keepData'] as boolean) ?? false; - keepBootstraps = (argv['keepBootstraps'] as boolean) ?? false; - return await uninstaller( - installerStackName, - partition, - debug, - ignoreTerminationProtection, - fullDestroy, - deleteAccelerator, - keepPipelineAndConfig, - keepData, - keepBootstraps, - stageName, - actionName, - ); - } - - // When specific stage name was provided - if (stageName !== 'all') { - keepData = (argv['keepData'] as boolean) ?? false; - keepBootstraps = (argv['keepBootstraps'] as boolean) ?? false; - actionName = 'all'; - return await uninstaller( - installerStackName, - partition, - debug, - ignoreTerminationProtection, - fullDestroy, - deleteAccelerator, - keepPipelineAndConfig, - keepData, - keepBootstraps, - stageName, - actionName, - ); - } - - // When specific action name was provided - if (actionName !== 'all') { - keepData = (argv['keepData'] as boolean) ?? false; - return await uninstaller( - installerStackName, - partition, - debug, - ignoreTerminationProtection, - fullDestroy, - deleteAccelerator, - keepPipelineAndConfig, - keepData, - keepBootstraps, - stageName, - actionName, - ); - } - - logger.warn(`[uninstaller] Uninstaller didn't execute for unknown reason`); - throw new Error(usage); -} - -process.on('unhandledRejection', reason => { - console.error(reason); - // eslint-disable-next-line no-process-exit - process.exit(1); -}); - -/** - * Function to validate delete option for the uninstaller - * @param installerStackName - * @param fullDestroy - * @param deleteAccelerator - * @param stageName - * @param actionName - * @returns - */ -function validateDeleteOptions( - installerStackName: string, - fullDestroy: boolean, - deleteAccelerator: boolean, - stageName: string, - actionName: string, -): string | undefined { - if (installerStackName === undefined) { - return `[uninstaller] Invalid --installerStackName ${installerStackName}`; - } - - const errorMessage = `[uninstaller] Invalid options !! Only one delete option (fullDestroy, deleteAccelerator, stageName and actionName) can be used `; - // One of the option is must from fullDestroy, deleteAccelerator, stageName and actionName - if (!fullDestroy && !deleteAccelerator && stageName === 'all' && actionName === 'all') { - return `[uninstaller] Invalid options !! One of the option is must from fullDestroy, deleteAccelerator, stageName and actionName`; - } - - // When fullDestroy option specified, then deleteAccelerator, stageName and actionName can't be specified - if (fullDestroy && (deleteAccelerator || stageName !== 'all' || actionName !== 'all')) { - return errorMessage; - } - - // When deleteAccelerator option specified, then fullDestroy, stageName and actionName can't be specified - if (deleteAccelerator && (fullDestroy || stageName !== 'all' || actionName !== 'all')) { - return errorMessage; - } - - // When stageName option specified, then fullDestroy, deleteAccelerator and actionName can't be specified - if (stageName !== 'all' && (fullDestroy || deleteAccelerator || actionName !== 'all')) { - return errorMessage; - } - - // When actionName option specified, then fullDestroy, deleteAccelerator and stageName can't be specified - if (actionName !== 'all' && (fullDestroy || deleteAccelerator || stageName !== 'all')) { - return errorMessage; - } - - return undefined; -} - -async function uninstaller( - installerStackName: string, - partition: string, - debug: boolean, - ignoreTerminationProtection: boolean, - fullDestroy: boolean, - deleteAccelerator: boolean, - keepPipelineAndConfig: boolean, - keepData: boolean, - keepBootstraps: boolean, - stageName: string, - actionName: string, -): Promise { - const start = new Date().getTime(); - - const acceleratorTool = new AcceleratorTool({ - installerStackName, - partition, - fullDestroy, - deleteAccelerator, - keepBootstraps, - keepData, - keepPipelineAndConfig, - stageName, - actionName, - debug, - ignoreTerminationProtection, - }); - - const status = await acceleratorTool.uninstallAccelerator(installerStackName); - const elapsed = Math.round((new Date().getTime() - start) / 60000); - - return status - ? `[uninstaller] Un-installation completed successfully for installer stack "${installerStackName}". Elapsed time ~${elapsed} minutes` - : `[uninstaller] Un-installation failed for installer stack "${installerStackName}". Elapsed time ~${elapsed} minutes`; -} - -/** - * Call Main function - */ -main().then(data => { - logger.info(data); -}); diff --git a/source/packages/@aws-accelerator/utils/README.md b/source/packages/@aws-accelerator/utils/README.md deleted file mode 100644 index 8ead5bd..0000000 --- a/source/packages/@aws-accelerator/utils/README.md +++ /dev/null @@ -1 +0,0 @@ -# @aws-accelerator/utils diff --git a/source/packages/@aws-accelerator/utils/index.ts b/source/packages/@aws-accelerator/utils/index.ts deleted file mode 100644 index 9562551..0000000 --- a/source/packages/@aws-accelerator/utils/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/common-resources'; -export * from './lib/common-functions'; -export * from './lib/logger'; -export * from './lib/policy-replacements'; -export * from './lib/set-organizations-client'; -export * from './lib/ssm-parameter-path'; -export * from './lib/throttle'; -export * from './lib/load-organization-config'; -export * from './lib/get-template'; -export * from './lib/diff-stack'; -export * from './lib/regions'; -export * from './lib/set-token-preferences'; -export * from './lib/evaluate-limits'; -export * from './lib/common-types'; -export * from './lib/control-tower'; diff --git a/source/packages/@aws-accelerator/utils/jest.config.js b/source/packages/@aws-accelerator/utils/jest.config.js deleted file mode 100644 index f172f6a..0000000 --- a/source/packages/@aws-accelerator/utils/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 10, - functions: 40, - lines: 50, - statements: 50, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-accelerator/utils/lib/common-functions.ts b/source/packages/@aws-accelerator/utils/lib/common-functions.ts deleted file mode 100644 index ef27a8d..0000000 --- a/source/packages/@aws-accelerator/utils/lib/common-functions.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { - STSClient, - AssumeRoleCommand, - AssumeRoleCommandInput, - AssumeRoleCommandOutput, - GetCallerIdentityCommand, -} from '@aws-sdk/client-sts'; -import { throttlingBackOff } from './throttle'; -import { ConfiguredRetryStrategy } from '@aws-sdk/util-retry'; -import { createLogger } from './logger'; -import { glob } from 'glob'; -import { dirname } from 'path'; -import * as fs from 'fs'; -const logger = createLogger(['utils-common-functions']); - -export function chunkArray(array: Type[], chunkSize: number): Type[][] { - const chunkedArray: Type[][] = []; - let index = 0; - - while (index < array.length) { - chunkedArray.push(array.slice(index, index + chunkSize)); - index += chunkSize; - } - return chunkedArray; -} - -export async function getCrossAccountCredentials( - accountId: string, - region: string, - partition: string, - managementAccountAccessRole: string, - sessionName?: string, -) { - const stsClient = new STSClient({ - region: region, - retryStrategy: setRetryStrategy(), - endpoint: getStsEndpoint(partition, region), - }); - const stsParams: AssumeRoleCommandInput = { - RoleArn: `arn:${partition}:iam::${accountId}:role/${managementAccountAccessRole}`, - RoleSessionName: sessionName ?? 'acceleratorBootstrapCheck', - DurationSeconds: 3600, - }; - let assumeRoleCredential: AssumeRoleCommandOutput | undefined = undefined; - try { - assumeRoleCredential = await throttlingBackOff(() => stsClient.send(new AssumeRoleCommand(stsParams))); - if (assumeRoleCredential) { - return assumeRoleCredential; - } else { - throw new Error(`Error assuming role ${managementAccountAccessRole} in account ${accountId} ${sessionName}`); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(JSON.stringify(e)); - throw new Error(e.message); - } -} - -export async function getCurrentAccountId(partition: string, region: string): Promise { - logger.debug( - `Sts endpoint for partition ${partition} and region ${region} is : ${getStsEndpoint(partition, region)}`, - ); - const stsClient = new STSClient({ - region, - retryStrategy: setRetryStrategy(), - endpoint: getStsEndpoint(partition, region), - }); - try { - const response = await throttlingBackOff(() => stsClient.send(new GetCallerIdentityCommand({}))); - logger.debug(`Current account id is ${response.Account!}`); - return response.Account!; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - logger.error(`Error to assume role while trying to get current account id ${JSON.stringify(error)}`); - throw new Error(error.message); - } -} - -export function setRetryStrategy() { - const numberOfRetries = Number(process.env['ACCELERATOR_SDK_MAX_ATTEMPTS'] ?? 800); - return new ConfiguredRetryStrategy(numberOfRetries, (attempt: number) => 100 + attempt * 1000); -} - -export function getStsEndpoint(partition: string, region: string): string { - if (partition === 'aws-iso') { - return `https://sts.${region}.c2s.ic.gov`; - } else if (partition === 'aws-iso-b') { - return `https://sts.${region}.sc2s.sgov.gov`; - } else if (partition === 'aws-cn') { - return `https://sts.${region}.amazonaws.com.cn`; - } - // both commercial and govCloud return this pattern - return `https://sts.${region}.amazonaws.com`; -} - -// Converts strings with wildcards (*) to regular expressions to determine if log group name matches exclusion pattern -export function wildcardMatch(text: string, pattern: string): boolean { - const regexPattern = new RegExp('^' + pattern.replace(/\?/g, '.').replace(/\*/g, '.*') + '$'); - logger.debug(`Converted wildcard pattern ${pattern} to regex pattern ${regexPattern}`); - const patternMatch = regexPattern.test(text); - logger.info(`Checking if input string ${text} matches pattern ${pattern} provided. Result: ${patternMatch}`); - return patternMatch; -} - -/** - * Returns STS credentials for a given role ARN - * @param stsClient STSClient - * @param roleArn string - * @returns `Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }>` - */ -export async function getStsCredentials( - stsClient: STSClient, - roleArn: string, -): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> { - console.log(`Assuming role ${roleArn}...`); - try { - const response = await throttlingBackOff(() => - stsClient.send(new AssumeRoleCommand({ RoleArn: roleArn, RoleSessionName: 'AcceleratorAssumeRole' })), - ); - // - // Validate response - if (!response.Credentials?.AccessKeyId) { - throw new Error(`Access key ID not returned from AssumeRole command`); - } - if (!response.Credentials.SecretAccessKey) { - throw new Error(`Secret access key not returned from AssumeRole command`); - } - if (!response.Credentials.SessionToken) { - throw new Error(`Session token not returned from AssumeRole command`); - } - - return { - accessKeyId: response.Credentials.AccessKeyId, - secretAccessKey: response.Credentials.SecretAccessKey, - sessionToken: response.Credentials.SessionToken, - }; - } catch (e) { - throw new Error(`Could not assume role: ${e}`); - } -} - -/** - * Function getAllFilesInPattern - returns all file names in a given directory that match a pattern - * its recursive so top level files will be returned as well as files in sub directories - * Only file name will be returned. For example, if file is testName.extension then return will be testName - */ -export async function getAllFilesInPattern(dir: string, pattern: string, fullPath?: boolean): Promise { - const files = await glob(`${dir}/**/*${pattern}`, { - ignore: ['**/node_modules/**'], - }); - // logger.debug(`Found ${JSON.stringify(files)} files matching pattern ${pattern}`); - const parsedFiles = files.map(file => { - return file.replace(dirname(file), '').replace('/', '').replace(pattern, ''); - }); - // logger.debug(`Parsed files ${JSON.stringify(parsedFiles)}`); - if (fullPath) { - return files; - } - return parsedFiles; -} - -export async function checkDiffFiles(dir: string, templatePattern: string, diffPattern: string) { - const templateFiles = await getAllFilesInPattern(dir, templatePattern); - const diffFiles = await getAllFilesInPattern(dir, diffPattern); - if (templateFiles.length !== diffFiles.length) { - throw new Error( - `Number of template files ${templateFiles.length} does not match number of diff files ${diffFiles.length} in directory ${dir}`, - ); - } -} - -/** - * Sets the global region for API calls based on the given partition - * @param partition - * @returns region - */ -export function getGlobalRegion(partition: string): string { - switch (partition) { - case 'aws-us-gov': - return 'us-gov-west-1'; - case 'aws-iso-b': - return 'us-isob-east-1'; - case 'aws-iso': - return 'us-iso-east-1'; - case 'aws-cn': - return 'cn-northwest-1'; - default: - return 'us-east-1'; - } -} - -export async function fileExists(filePath: string): Promise { - try { - await fs.promises.stat(filePath); - return true; - } catch (err) { - return false; - } -} - -export async function directoryExists(directory: string): Promise { - try { - const stats = await fs.promises.stat(directory); - return stats.isDirectory(); - } catch (err) { - return false; - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/common-resources.ts b/source/packages/@aws-accelerator/utils/lib/common-resources.ts deleted file mode 100644 index aed7f3a..0000000 --- a/source/packages/@aws-accelerator/utils/lib/common-resources.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Accelerator imported bucket type enum - */ -export enum AcceleratorImportedBucketType { - /** - * Server access logs Bucket - */ - SERVER_ACCESS_LOGS_BUCKET = 'access-logs', - /** - * Central logs Bucket - */ - CENTRAL_LOGS_BUCKET = 'central-logs', - /** - * ELB logs Bucket - */ - ELB_LOGS_BUCKET = 'elb-logs', - /** - * ASSETS Bucket - */ - ASSETS_BUCKET = 'assets', -} - -/** - * Principal Org id condition for policy - */ -export type PrincipalOrgIdConditionType = { - [key: string]: string | string[]; -}; - -/** - * AWS principal access types used for CentralLogsBucket access policy configuration - */ -export type AwsPrincipalAccessesType = { name: string; principal: string; accessType: string }; - -/** - * IAM policy statement type used in custom resource to update policy of existing resources - */ -export type PolicyStatementType = { - /** - * The Sid (statement ID) is an optional identifier that you provide for the - * policy statement. You can assign a Sid value to each statement in a - * statement array. In services that let you specify an ID element, such as - * SQS and SNS, the Sid value is just a sub-ID of the policy document's ID. In - * IAM, the Sid value must be unique within a JSON policy. - * - * @default - no sid - */ - readonly Sid?: string; - /** - * List of actions to add to the statement - * - * @default - no actions - */ - readonly Action: string | string[]; - /** - * List of not actions to add to the statement - * - * @default - no not-actions - */ - readonly NotActions?: string[]; - /** - * Principal to add to the statement - * - * @default - no principal - */ - readonly Principal?: PrincipalOrgIdConditionType; - /** - * Principal to add to the statement - * - * @default - no not principal - */ - readonly NotPrincipal?: PrincipalOrgIdConditionType; - /** - * Resource ARNs to add to the statement - * - * @default - no resource - */ - readonly Resource?: string | string[]; - /** - * NotResource ARNs to add to the statement - * - * @default - no not-resources - */ - readonly NotResource?: string[]; - /** - * Condition to add to the statement - * - * @default - no condition - */ - readonly Condition?: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }; - /** - * Whether to allow or deny the actions in this statement - * - * @default Effect.ALLOW - */ - readonly Effect?: 'Allow' | 'Deny'; -}; - -/** - * Bucket access type - */ -export enum BucketAccessType { - /** - * When service need read only access to bucket and CMK - */ - READONLY = 'readonly', - /** - * When service need write only access to bucket and CMK - */ - WRITEONLY = 'writeonly', - /** - * When service need read write access to bucket and CMK - */ - READWRITE = 'readwrite', - /** - * When service need no access like SessionManager, but the service name required for other logical changes in bucket or CMK policy - */ - NO_ACCESS = 'no_access', -} - -/** - * Supported Resource Type for AWS Config Rule - */ -export enum ResourceType { - S3_BUCKET = 'AWS::S3::Bucket', - KMS_KEY = 'AWS::KMS::Key', - IAM_ROLE = 'AWS::IAM::Role', - SECRETS_MANAGER_SECRET = 'AWS::SecretsManager::Secret', - ECR_REPOSITORY = 'AWS::ECR::Repository', - OPENSEARCH_DOMAIN = 'AWS::OpenSearch::Domain', - SNS_TOPIC = 'AWS::SNS::Topic', - SQS_QUEUE = 'AWS::SQS::Queue', - APIGATEWAY_REST_API = 'AWS::ApiGateway::RestApi', - LEX_BOT = 'AWS::Lex::Bot', - EFS_FILE_SYSTEM = 'AWS::EFS::FileSystem', - EVENTBRIDGE_EVENTBUS = 'AWS::Events::EventBus', - BACKUP_VAULT = 'AWS::Backup::BackupVault', - CODEARTIFACT_REPOSITORY = 'AWS::CodeArtifact::Repository', - CERTIFICATE_AUTHORITY = 'AWS::ACMPCA::CertificateAuthority', - LAMBDA_FUNCTION = 'AWS::Lambda::Function', -} - -export const RESOURCE_TYPE_WITH_ALLOW_ONLY_POLICY = [ResourceType.CERTIFICATE_AUTHORITY, ResourceType.LAMBDA_FUNCTION]; diff --git a/source/packages/@aws-accelerator/utils/lib/common-types.ts b/source/packages/@aws-accelerator/utils/lib/common-types.ts deleted file mode 100644 index dfc84a2..0000000 --- a/source/packages/@aws-accelerator/utils/lib/common-types.ts +++ /dev/null @@ -1,304 +0,0 @@ -/** - * The interface that AWS Lambda will invoke your handler with. - * There are more specialized types for many cases where AWS services - * invoke your lambda, but you can directly use this type for when you are invoking - * your lambda directly. - * - */ - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Handler = ( - event: TEvent, - context: Context, - callback: Callback, -) => void | Promise; - -/** - * {@link Handler} context parameter. - * See {@link https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html AWS documentation}. - */ -export interface Context { - callbackWaitsForEmptyEventLoop: boolean; - functionName: string; - functionVersion: string; - invokedFunctionArn: string; - memoryLimitInMB: string; - awsRequestId: string; - logGroupName: string; - logStreamName: string; - identity?: CognitoIdentity | undefined; - clientContext?: ClientContext | undefined; - - getRemainingTimeInMillis(): number; - - // Functions for compatibility with earlier Node.js Runtime v0.10.42 - // No longer documented, so they are deprecated, but they still work - // as of the 12.x runtime, so they are not removed from the types. - - /** @deprecated Use handler callback or promise result */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - done(error?: Error, result?: any): void; - /** @deprecated Use handler callback with first argument or reject a promise result */ - fail(error: Error | string): void; - /** @deprecated Use handler callback with second argument or resolve a promise result */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - succeed(messageOrObject: any): void; - // Unclear what behavior this is supposed to have, I couldn't find any still extant reference, - // and it behaves like the above, ignoring the object parameter. - /** @deprecated Use handler callback or promise result */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - succeed(message: string, object: any): void; -} - -export interface CognitoIdentity { - cognitoIdentityId: string; - cognitoIdentityPoolId: string; -} - -export interface ClientContext { - client: ClientContextClient; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Custom?: any; - env: ClientContextEnv; -} - -export interface ClientContextClient { - installationId: string; - appTitle: string; - appVersionName: string; - appVersionCode: string; - appPackageName: string; -} - -export interface ClientContextEnv { - platformVersion: string; - platform: string; - make: string; - model: string; - locale: string; -} - -/** - * NodeJS-style callback parameter for the {@link Handler} type. - * Can be used instead of returning a promise, see the - * {@link https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html AWS documentation} - * for the handler programming model. - * - * @param error - * Parameter to use to provide the error payload for a failed lambda execution. - * See {@link https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-mode-exceptions.html AWS documentation} - * for error handling. - * If an Error instance is passed, the error payload uses the `name` property as the `errorType`, - * the `message` property as the `errorMessage`, and parses the `stack` property string into - * the `trace` array. - * For other values, the `errorType` is `typeof value`, the `errorMessage` is `String(value)`, and - * `trace` is an empty array. - * - * @param result - * Parameter to use to provide the result payload for a successful lambda execution. - * Pass `null` or `undefined` for the `error` parameter to use this parameter. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Callback = (error?: Error | string | null, result?: TResult) => void; - -export type SNSHandler = Handler; - -// SNS "event" -export interface SNSMessageAttribute { - Type: string; - Value: string; -} - -export interface SNSMessageAttributes { - [name: string]: SNSMessageAttribute; -} - -export interface SNSMessage { - SignatureVersion: string; - Timestamp: string; - Signature: string; - SigningCertUrl: string; - MessageId: string; - Message: string; - MessageAttributes: SNSMessageAttributes; - Type: string; - UnsubscribeUrl: string; - TopicArn: string; - Subject: string; - Token?: string; -} - -export interface SNSEventRecord { - EventVersion: string; - EventSubscriptionArn: string; - EventSource: string; - Sns: SNSMessage; -} - -export interface SNSEvent { - Records: SNSEventRecord[]; -} - -// Note, responses are *not* lambda results, they are sent to the event ResponseURL. -export type CloudFormationCustomResourceHandler = Handler; - -export type CloudFormationCustomResourceEvent = - | CloudFormationCustomResourceCreateEvent - | CloudFormationCustomResourceUpdateEvent - | CloudFormationCustomResourceDeleteEvent; - -export type CloudFormationCustomResourceResponse = - | CloudFormationCustomResourceSuccessResponse - | CloudFormationCustomResourceFailedResponse; - -/** - * CloudFormation Custom Resource event and response - * http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref.html - */ -export interface CloudFormationCustomResourceEventCommon { - ServiceToken: string; - ResponseURL: string; - StackId: string; - RequestId: string; - LogicalResourceId: string; - ResourceType: string; - ResourceProperties: { - ServiceToken: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Key: string]: any; - }; -} - -export interface CloudFormationCustomResourceCreateEvent extends CloudFormationCustomResourceEventCommon { - RequestType: 'Create'; -} - -export interface CloudFormationCustomResourceUpdateEvent extends CloudFormationCustomResourceEventCommon { - RequestType: 'Update'; - PhysicalResourceId: string; - OldResourceProperties: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Key: string]: any; - }; -} - -export interface CloudFormationCustomResourceDeleteEvent extends CloudFormationCustomResourceEventCommon { - RequestType: 'Delete'; - PhysicalResourceId: string; -} - -export interface CloudFormationCustomResourceResponseCommon { - PhysicalResourceId: string; - StackId: string; - RequestId: string; - LogicalResourceId: string; - Data?: - | { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Key: string]: any; - } - | undefined; - NoEcho?: boolean | undefined; -} - -export interface CloudFormationCustomResourceSuccessResponse extends CloudFormationCustomResourceResponseCommon { - Status: 'SUCCESS'; - Reason?: string | undefined; -} - -export interface CloudFormationCustomResourceFailedResponse extends CloudFormationCustomResourceResponseCommon { - Status: 'FAILED'; - Reason: string; -} - -export type FirehoseTransformationHandler = Handler; -export type FirehoseTransformationCallback = Callback; - -// Kinesis Data Firehose Event -// https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-kinesis-firehose -// https://docs.aws.amazon.com/firehose/latest/dev/data-transformation.html -// https://aws.amazon.com/blogs/compute/amazon-kinesis-firehose-data-transformation-with-aws-lambda/ -// Examples in the lambda blueprints -export interface FirehoseTransformationEvent { - invocationId: string; - deliveryStreamArn: string; - sourceKinesisStreamArn?: string | undefined; - region: string; - records: FirehoseTransformationEventRecord[]; -} - -export interface FirehoseTransformationEventRecord { - recordId: string; - approximateArrivalTimestamp: number; - /** Base64 encoded */ - data: string; - kinesisRecordMetadata?: FirehoseRecordMetadata | undefined; -} - -export interface FirehoseRecordMetadata { - shardId: string; - partitionKey: string; - approximateArrivalTimestamp: number; - sequenceNumber: string; - subsequenceNumber: string; -} - -export type FirehoseRecordTransformationStatus = 'Ok' | 'Dropped' | 'ProcessingFailed'; - -export interface FirehoseTransformationMetadata { - partitionKeys: { [name: string]: string }; -} - -export interface FirehoseTransformationResultRecord { - recordId: string; - result: FirehoseRecordTransformationStatus; - /** Encode in Base64 */ - data: string; - metadata?: FirehoseTransformationMetadata; -} - -export interface FirehoseTransformationResult { - records: FirehoseTransformationResultRecord[]; -} - -// based on https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/ValidateLogEventFlow.html - -export interface CloudWatchLogsToFirehoseRecordLogEvents { - id: string; - timestamp: number; - message: string; -} -export interface CloudWatchLogsToFirehoseRecord { - owner: string; - logGroup: string; - logStream: string; - subscriptionFilters: string[]; - messageType: string; - logEvents: CloudWatchLogsToFirehoseRecordLogEvents[]; -} - -export type EventBridgeHandler = Handler< - EventBridgeEvent, - TResult ->; - -export interface EventBridgeEvent { - id: string; - version: string; - account: string; - time: string; - region: string; - resources: string[]; - source: string; - 'detail-type': TDetailType; - detail: TDetail; - 'replay-name'?: string; -} -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ScheduledHandler = EventBridgeHandler<'Scheduled Event', TDetail, void>; - -/** - * https://docs.aws.amazon.com/lambda/latest/dg/with-scheduled-events.html - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ScheduledEvent = EventBridgeEvent<'Scheduled Event', TDetail>; diff --git a/source/packages/@aws-accelerator/utils/lib/control-tower.ts b/source/packages/@aws-accelerator/utils/lib/control-tower.ts deleted file mode 100644 index cc323d1..0000000 --- a/source/packages/@aws-accelerator/utils/lib/control-tower.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * AWS Control Tower Landing Zone latest version. - * - * @remarks - * Once Control Tower API support available for landing zone version, this hard coded constant will be removed. - * When Control Tower Landing Zone gets new version, we need to update this constant. - */ -export const CONTROL_TOWER_LANDING_ZONE_VERSION = '3.3'; - -/** - * Function to get baseline version based on AWS Control Tower Landing Zone version - * - * @remarks - * Baseline version compatibility information can be found [here](https://docs.aws.amazon.com/controltower/latest/userguide/table-of-baselines.html) - * @param landingZoneVersion string - * @returns baselineVersion string - */ -export function getBaselineVersion(landingZoneVersion: string): string { - // base line version compatibility metrics can be found here https://docs.aws.amazon.com/controltower/latest/userguide/table-of-baselines.html - const landingZoneVersionSet1 = ['2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7']; - const landingZoneVersionSet2 = ['2.8', '2.9']; - const landingZoneVersionSet3 = ['3.0', '3.1']; - - const baselineVersion = '4.0'; - - if (landingZoneVersionSet1.includes(landingZoneVersion)) { - return '1.0'; - } - if (landingZoneVersionSet2.includes(landingZoneVersion)) { - return '2.0'; - } - if (landingZoneVersionSet3.includes(landingZoneVersion)) { - return '3.0'; - } - - return baselineVersion; -} diff --git a/source/packages/@aws-accelerator/utils/lib/diff-stack.ts b/source/packages/@aws-accelerator/utils/lib/diff-stack.ts deleted file mode 100644 index ff5ac2c..0000000 --- a/source/packages/@aws-accelerator/utils/lib/diff-stack.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import * as cfnDiff from '@aws-cdk/cloudformation-diff'; -import * as fs from 'fs'; -import { createLogger } from './logger'; - -const logger = createLogger(['diff']); -/** - * Pretty-prints the differences between two template states to the console. - * - * @param oldTemplate the old/current state of the stack. - * @param newTemplate the new/target state of the stack. - * @param strict do not filter out AWS::CDK::Metadata - * @param context lines of context to use in arbitrary JSON diff - * @param quiet silences \'There were no differences\' messages - * - * @returns the count of differences that were rendered. - */ -export function printStackDiff( - oldTemplate: string, - newTemplate: string, - strict: boolean, - context: number, - quiet: boolean, - stream?: cfnDiff.FormatStream, -): number { - let diff = cfnDiff.diffTemplate(readTemplate(oldTemplate), readTemplate(newTemplate)); - - // detect and filter out mangled characters from the diff - let filteredChangesCount = 0; - if (diff.differenceCount && !strict) { - const mangledNewTemplate = JSON.stringify(readTemplate(newTemplate)).replace(/[\u{80}-\u{10ffff}]/gu, '?'); - const mangledDiff = cfnDiff.diffTemplate(readTemplate(oldTemplate), JSON.parse(mangledNewTemplate)); - filteredChangesCount = Math.max(0, diff.differenceCount - mangledDiff.differenceCount); - if (filteredChangesCount > 0) { - diff = mangledDiff; - } - } - - // filter out 'AWS::CDK::Metadata' resources from the template - if (diff.resources && !strict) { - diff.resources = diff.resources.filter(change => { - if (!change) { - return true; - } - if (change.newResourceType === 'AWS::CDK::Metadata') { - return false; - } - if (change.oldResourceType === 'AWS::CDK::Metadata') { - return false; - } - return true; - }); - } - - if (!diff.isEmpty) { - cfnDiff.formatDifferences( - stream || process.stderr, - diff, - { - ...logicalIdMapFromTemplate(oldTemplate), - ...logicalIdMapFromTemplate(newTemplate), - }, - context, - ); - } else if (!quiet) { - stream?.write('There were no differences'); - } - // - - return diff.differenceCount; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function logicalIdMapFromTemplate(template: any) { - const ret: Record = {}; - - for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const path = (resource as any)?.Metadata?.['aws:cdk:path']; - if (path) { - ret[logicalId] = path; - } - } - return ret; -} - -function readTemplate(input: string) { - try { - return JSON.parse(fs.readFileSync(input, { encoding: 'utf-8' })); - } catch (e) { - logger.error(`Error reading template: ${input}`); - const fileContents = fs.readFileSync(input, { encoding: 'utf-8' }); - logger.error(`File Content: \n\n${fileContents}\n\n Exception: ${e}\n`); - - throw e; - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/evaluate-limits.ts b/source/packages/@aws-accelerator/utils/lib/evaluate-limits.ts deleted file mode 100644 index 29a3af2..0000000 --- a/source/packages/@aws-accelerator/utils/lib/evaluate-limits.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { GetServiceQuotaCommand, ServiceQuotasClient } from '@aws-sdk/client-service-quotas'; -import { getCrossAccountCredentials, setRetryStrategy } from './common-functions'; -import { throttlingBackOff } from './throttle'; -import { createLogger } from './logger'; -const logger = createLogger(['utils-evaluate-limits']); - -export async function evaluateLimits( - region: string, - accountId: string, - partition: string, - roleName: string, - currentAccountId: string, -) { - const codebuildParallelLimit = await getLimits('L-2DC20C30', 'codebuild', { - region, - accountId, - partition, - roleName, - currentAccountId, - }); - const lambdaConcurrencyLimit = await getLimits('L-B99A9384', 'lambda', { - region, - accountId, - partition, - roleName, - currentAccountId, - }); - logger.debug(`CodeBuild limit in account ${accountId} region ${region} is ${codebuildParallelLimit}`); - logger.debug(`Lambda limit in account ${accountId} region ${region} is ${lambdaConcurrencyLimit}`); - - // setting this from environment variables to make changes in runtime easier - const acceleratorCodebuildParallelBuildLimit = process.env['ACCELERATOR_CODEBUILD_PARALLEL_LIMIT'] ?? '3'; - const acceleratorLambdaConcurrencyLimit = process.env['ACCELERATOR_LAMBDA_CONCURRENCY_LIMIT'] ?? '1000'; - - if ( - codebuildParallelLimit < parseInt(acceleratorCodebuildParallelBuildLimit) || - lambdaConcurrencyLimit < parseInt(acceleratorLambdaConcurrencyLimit) - ) { - const errMsg = `CodeBuild limit in account ${accountId} region ${region} is ${codebuildParallelLimit} and Lambda limit in account ${accountId} region ${region} is ${lambdaConcurrencyLimit}`; - logger.error(errMsg); - throw new Error(errMsg); - } -} - -async function getLimits( - quotaCode: string, - serviceName: string, - accountMetadata: { region: string; accountId: string; partition: string; roleName: string; currentAccountId: string }, -) { - const serviceQuotasClient = await getServiceQuotasClient(accountMetadata); - try { - const result = await throttlingBackOff(() => - serviceQuotasClient.send(new GetServiceQuotaCommand({ QuotaCode: quotaCode, ServiceCode: serviceName })), - ); - return result.Quota?.Value ?? 0; - } catch (error) { - const errMsg = `Encountered an error in getting service ${serviceName} limit for quota ${quotaCode}. Error: ${JSON.stringify( - error, - )}`; - logger.error(errMsg); - throw new Error(errMsg); - } -} - -async function getServiceQuotasClient(accountMetadata: { - region: string; - accountId: string; - partition: string; - roleName: string; - currentAccountId: string; -}): Promise { - if ( - accountMetadata.currentAccountId === accountMetadata.accountId || - accountMetadata.currentAccountId === process.env['MANAGEMENT_ACCOUNT_ID'] - ) { - return new ServiceQuotasClient({ retryStrategy: setRetryStrategy(), region: accountMetadata.region }); - } else { - const crossAccountCredentials = await getCrossAccountCredentials( - accountMetadata.accountId, - accountMetadata.region, - accountMetadata.partition, - accountMetadata.roleName, - ); - return new ServiceQuotasClient({ - retryStrategy: setRetryStrategy(), - region: accountMetadata.region, - credentials: { - accessKeyId: crossAccountCredentials.Credentials!.AccessKeyId!, - secretAccessKey: crossAccountCredentials.Credentials!.SecretAccessKey!, - sessionToken: crossAccountCredentials.Credentials!.SessionToken!, - }, - }); - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/get-template.ts b/source/packages/@aws-accelerator/utils/lib/get-template.ts deleted file mode 100644 index b703ed5..0000000 --- a/source/packages/@aws-accelerator/utils/lib/get-template.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - CloudFormationClient, - GetTemplateCommand, - GetTemplateCommandInput, - CloudFormationServiceException, -} from '@aws-sdk/client-cloudformation'; -import { ConfiguredRetryStrategy } from '@aws-sdk/util-retry'; -import { getCrossAccountCredentials, getCurrentAccountId, setRetryStrategy } from './common-functions'; -import { throttlingBackOff } from './throttle'; -import * as fs from 'fs'; -import * as path from 'path'; -import { createLogger } from './logger'; -const logger = createLogger(['utils-get-template']); - -export async function getCloudFormationTemplate( - accountId: string, - region: string, - partition: string, - stage: string | undefined, - stackName: string, - savePath: string, - roleName: string, -) { - try { - const currentAccountId = await getCurrentAccountId(partition, region); - const client = await getCloudFormationClient( - setRetryStrategy(), - region, - accountId, - partition, - roleName, - currentAccountId, - ); - - let template = await getTemplate(client, stackName); - - if (stage === 'customizations' && template === '{}') { - logger.warn(`template is empty for ${stackName}. Trying to retrieve stack by customizations stack name`); - // Removes account and region to find possible customizations stack name - const stackNameArr = stackName.split('-'); - for (let i = 0; i < 4; i++) { - stackNameArr.pop(); - } - const customizationsStackName = stackNameArr.join('-'); - logger.info(`Possible customizations stack name is ${customizationsStackName}`); - template = await getTemplate(client, customizationsStackName, 'Original'); - } - fs.writeFileSync(path.join(savePath, `${stackName}.json`), template, 'utf-8'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(e); - logger.error( - `Error trying to get template for account ${accountId}, region: ${region}, stack: ${stackName} using role ${roleName}`, - ); - } -} -async function getCloudFormationClient( - retryStrategy: ConfiguredRetryStrategy, - region: string, - accountId: string, - partition: string, - roleName: string, - currentAccountId: string, -): Promise { - if (currentAccountId === accountId || currentAccountId === process.env['MANAGEMENT_ACCOUNT_ID']) { - return new CloudFormationClient({ retryStrategy, region }); - } else { - const crossAccountCredentials = await getCrossAccountCredentials(accountId, region, partition, roleName); - return new CloudFormationClient({ - retryStrategy, - region, - credentials: { - accessKeyId: crossAccountCredentials.Credentials!.AccessKeyId!, - secretAccessKey: crossAccountCredentials.Credentials!.SecretAccessKey!, - sessionToken: crossAccountCredentials.Credentials!.SessionToken!, - }, - }); - } -} - -async function getTemplate(client: CloudFormationClient, stackName: string, templateStage?: string): Promise { - try { - if (!templateStage) { - templateStage = 'Processed'; - } - const input: GetTemplateCommandInput = { - StackName: stackName, - TemplateStage: templateStage, - }; - const command = new GetTemplateCommand(input); - const response = await throttlingBackOff(() => client.send(command)); - const templateBody = isValidJsonObject(response.TemplateBody ?? ''); - return templateBody; - } catch (e) { - if (e instanceof CloudFormationServiceException) { - if (e.message === `Stack with id ${stackName} does not exist`) { - // write an empty json since stack does not exist - return '{}'; - } - } else { - logger.error(JSON.stringify(e)); - } - } - return '{}'; -} - -/** - * Checks if the given string is a valid JSON object. - * - * @param {string} str - The input string to be checked. - * @returns {string} If the input string is a valid JSON object, it returns the original input string. - * If the input string is not a valid JSON object, it returns an empty JSON object string '{}'. - */ -function isValidJsonObject(str: string): string { - try { - // Attempt to parse the input string as JSON - const parsedJson = JSON.parse(str); - - // Check if the parsed value is an object and not null - if (typeof parsedJson === 'object' && parsedJson !== null) { - // If it's a valid JSON object, return the original input string - return str; - } else { - // If it's not a valid JSON object, return an empty JSON object string - return '{}'; - } - } catch (error) { - // If there's an error during parsing (e.g., the input string is not valid JSON), - // return an empty JSON object string - return '{}'; - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/load-organization-config.ts b/source/packages/@aws-accelerator/utils/lib/load-organization-config.ts deleted file mode 100644 index 6c3b9b6..0000000 --- a/source/packages/@aws-accelerator/utils/lib/load-organization-config.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { - OrganizationsClient, - ListOrganizationalUnitsForParentCommand, - ListRootsCommand, - ListOrganizationalUnitsForParentCommandOutput, -} from '@aws-sdk/client-organizations'; -import { setRetryStrategy } from './common-functions'; - -type OrgUnit = { - Id: string; - ParentId: string; - Name: string; - Arn: string; -}; - -type OrgUnits = OrgUnit[]; - -type AcceleratorOu = { - name: string; - id: string; - arn: string; -}; -type OrganizationConfigArray = { - name: string; - ignore: boolean | undefined; -}; - -export async function loadOrganizationalUnits( - partition: string, - arrayFromConfig: OrganizationConfigArray[], -): Promise { - const client = new OrganizationsClient({ - retryStrategy: setRetryStrategy(), - region: await getRegion(partition), - }); - const acceleratorOrganizationalUnit: AcceleratorOu[] = []; - const rootResults = await client.send(new ListRootsCommand({})); - - let rootId: string; - if (rootResults.Roots) { - rootId = rootResults.Roots[0].Id!; - acceleratorOrganizationalUnit.push({ - name: rootResults.Roots[0].Name!, - arn: rootResults.Roots[0].Arn!, - id: rootId, - }); - } - const level0 = await getChildrenForParent(rootId!, undefined, client); - const level1 = await processLevel(level0, client); - const level2 = await processLevel(level1, client); - const level3 = await processLevel(level2, client); - const level4 = await processLevel(level3, client); - acceleratorOrganizationalUnit.push(...(await parseArray(level0))); - acceleratorOrganizationalUnit.push(...(await parseArray(level1))); - acceleratorOrganizationalUnit.push(...(await parseArray(level2))); - acceleratorOrganizationalUnit.push(...(await parseArray(level3))); - acceleratorOrganizationalUnit.push(...(await parseArray(level4))); - - const filteredArray = acceleratorOrganizationalUnit.filter(obj => { - return arrayFromConfig.filter(value => { - return value.name === obj.name; - }); - }); - return filteredArray; -} - -async function getRegion(partition: string): Promise { - let region: string; - if (partition === 'aws-us-gov') { - region = 'us-gov-west-1'; - } else if (partition === 'aws-cn') { - region = 'cn-northwest-1'; - } else { - region = 'us-east-1'; - } - return region; -} - -async function parseArray(levelArray: OrgUnits): Promise { - const output: AcceleratorOu[] = []; - for (const item of levelArray) { - output.push({ - name: item.Name, - id: item.Id, - arn: item.Arn, - }); - } - return output; -} - -async function processLevel(levelArray: OrgUnits, client: OrganizationsClient): Promise { - const output: OrgUnits = []; - for (const ou of levelArray) { - const results = await getChildrenForParent(ou.Id!, ou.Name!, client); - output.push(...results); - } - return output; -} - -async function getChildrenForParent( - parentId: string, - parentName: string | undefined, - client: OrganizationsClient, -): Promise { - const orgUnits: OrgUnits = []; - let nextToken: string | undefined = undefined; - do { - const results: ListOrganizationalUnitsForParentCommandOutput = await client.send( - new ListOrganizationalUnitsForParentCommand({ - ParentId: parentId, - NextToken: nextToken, - }), - ); - nextToken = results.NextToken; - if (results.OrganizationalUnits) { - for (const item of results.OrganizationalUnits) { - // if parentName is defined, prefix parent name or else just return item.Name - const itemName = parentName ? `${parentName}/${item.Name!}` : item.Name!; - orgUnits.push({ - Id: item.Id!, - ParentId: parentId, - Name: itemName, - Arn: item.Arn!, - }); - } - } - } while (nextToken); - - return orgUnits; -} diff --git a/source/packages/@aws-accelerator/utils/lib/logger.ts b/source/packages/@aws-accelerator/utils/lib/logger.ts deleted file mode 100644 index 2b6c539..0000000 --- a/source/packages/@aws-accelerator/utils/lib/logger.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as winston from 'winston'; - -const Logger = winston.createLogger({ - defaultMeta: { mainLabel: 'accelerator' }, - level: process.env['LOG_LEVEL'] ?? 'info', - format: winston.format.combine( - winston.format.colorize(), - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), - winston.format.printf(({ message, timestamp, level, mainLabel, childLabel }) => { - return `${timestamp} | ${level} | ${childLabel || mainLabel} | ${message}`; - }), - winston.format.align(), - ), - transports: [new winston.transports.Console()], -}); - -winston.add(Logger); - -export const createLogger = (logInfo: string[]) => { - const logInfoString = logInfo.join(' | '); - return Logger.child({ childLabel: logInfoString }); -}; diff --git a/source/packages/@aws-accelerator/utils/lib/policy-replacements.ts b/source/packages/@aws-accelerator/utils/lib/policy-replacements.ts deleted file mode 100644 index e479c95..0000000 --- a/source/packages/@aws-accelerator/utils/lib/policy-replacements.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { AccountConfig, AccountsConfig, GovCloudAccountConfig, NetworkConfig } from '@aws-accelerator/config'; - -// ACCEL_LOOKUP::VPC_ID:ACCOUNT|OU|ORG:{accountId}|{ouId} -export enum POLICY_LOOKUP_TYPE { - VPC_ID = 'VPC_ID', - VPCE_ID = 'VPCE_ID', - CUSTOM = 'CUSTOM', -} - -export enum POLICY_LOOKUP_SCOPE { - ACCOUNT = 'ACCOUNT', - OU = 'OU', - ORG = 'ORG', -} - -export const ACCEL_POLICY_LOOKUP_REGEX = /\${ACCEL_LOOKUP::([a-zA-Z0-9-:_]*)}/g; - -export function policyReplacements(props: { - content: string; - acceleratorPrefix: string; - managementAccountAccessRole: string; - partition: string; - additionalReplacements: { [key: string]: string | string[] }; - acceleratorName: string; - networkConfig?: NetworkConfig; - accountsConfig?: AccountsConfig; -}): string { - const { - acceleratorPrefix, - additionalReplacements, - managementAccountAccessRole, - partition, - networkConfig, - accountsConfig, - } = props; - let { content } = props; - - for (const [key, value] of Object.entries(additionalReplacements)) { - const normalizedValue = normalize(value); - content = content.replace( - new RegExp(key, 'g'), - typeof normalizedValue === 'string' ? normalizedValue : JSON.stringify(normalizedValue), - ); - } - const replacements = { - '\\${MANAGEMENT_ACCOUNT_ACCESS_ROLE}': managementAccountAccessRole, - '\\${ACCELERATOR_NAME}': props.acceleratorName, - '\\${ACCELERATOR_PREFIX}': acceleratorPrefix, - '\\${PARTITION}': partition, - }; - - for (const [key, value] of Object.entries(replacements)) { - content = content.replace(new RegExp(key, 'g'), value); - } - - const matches = content.match(ACCEL_POLICY_LOOKUP_REGEX); - const uniqueLookup = [...new Set(matches)]; - - for (const lookupPattern of uniqueLookup) { - const value = getPolicyReplaceValue(lookupPattern, networkConfig, accountsConfig); - const stringifiedValue = typeof value === 'string' ? value : value.map(v => `"${v}"`).join(','); - if (!stringifiedValue) { - // If the value of the parameter is empty, we need to remove any heading comma with space if there is any - content = content.replace(new RegExp(`,?\\s*${escapeRegExp(lookupPattern)}`, 'g'), stringifiedValue); - } else { - content = content.replace(lookupPattern, stringifiedValue); - } - } - - return content; -} - -function escapeRegExp(str: string) { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -function getPolicyReplaceValue( - key: string, - networkConfig?: NetworkConfig, - accountsConfig?: AccountsConfig, -): string[] | string { - ACCEL_POLICY_LOOKUP_REGEX.lastIndex = 0; - const parameterReplacementNeeded = ACCEL_POLICY_LOOKUP_REGEX.exec(key); - if (parameterReplacementNeeded) { - return getPolicyReplacementValue(parameterReplacementNeeded, networkConfig, accountsConfig); - } - - return key; -} - -function getPolicyReplacementValue( - replacement: RegExpMatchArray, - networkConfig?: NetworkConfig, - accountsConfig?: AccountsConfig, -): string | string[] { - const replacementArray = replacement[1].split(':'); - if (replacementArray.length < 2) { - throw new Error(`Invalid POLICY_LOOKUP_VALUE: ${replacement[1]}`); - } - - const lookupType = replacementArray[0]; - const lookupScope = replacementArray[1]; - - let returnValue: string | string[]; - - // Validate lookup data - validatePolicyLookupData(lookupType, lookupScope, replacementArray); - - switch (lookupType) { - case POLICY_LOOKUP_TYPE.VPC_ID: - if (!networkConfig || !accountsConfig) { - throw new Error('Missing networkConfig and accountConfig for policy statement with VPC parameters'); - } - returnValue = getScopeVpcIds(networkConfig, accountsConfig, lookupScope, replacementArray); - break; - case POLICY_LOOKUP_TYPE.VPCE_ID: - if (!networkConfig || !accountsConfig) { - throw new Error('Missing networkConfig and accountConfig for policy statement with VPCE parameters'); - } - returnValue = getScopeVpcEndpointIds(networkConfig, accountsConfig, lookupScope, replacementArray); - break; - case POLICY_LOOKUP_TYPE.CUSTOM: - // Return the parameter itself for custom parameter. The parameter will be replacement with value from replacement config - // The only exception will be ACCEL_LOOKUP::CUSTOM:ATTACHED_RESOURCE_ARN whose value will only be available during lambda runtime - returnValue = replacement[0]; - break; - default: - throw new Error(`Invalid POLICY_LOOKUP type: ${lookupType}`); - } - - return returnValue; -} - -/** - * Validate lookup data - * @param lookupType string - * @param replacementArray string[] - */ -function validatePolicyLookupData(lookupType: string, lookupScope: string, replacementArray: string[]) { - let isError = false; - if (lookupType === POLICY_LOOKUP_TYPE.VPC_ID || lookupType === POLICY_LOOKUP_TYPE.VPCE_ID) { - if (lookupScope === POLICY_LOOKUP_SCOPE.ACCOUNT && replacementArray.length !== 3) { - // VPC_ID:ACCOUNT:{accountId} - isError = true; - } else if (lookupScope === POLICY_LOOKUP_SCOPE.OU && replacementArray.length !== 3) { - // VPC_ID:OU:{ouId} - isError = true; - } else if (lookupScope === POLICY_LOOKUP_SCOPE.ORG && replacementArray.length !== 2) { - // VPC_ID:ORG - isError = true; - } - } - - if (isError) { - throw new Error(`Invalid replacement options ${replacementArray}`); - } -} - -function getAccountsForOrgUnit( - accountsConfig: AccountsConfig, - organizationUnit: string, -): (AccountConfig | GovCloudAccountConfig)[] { - const accounts = accountsConfig.getAccounts(false); - return accounts.filter(account => account.organizationalUnit === organizationUnit); -} - -function getScopeVpcIds( - networkConfig: NetworkConfig, - accountsConfig: AccountsConfig, - lookupScope: string, - replacementArray: string[], -): string[] { - if (!networkConfig.accountVpcIds) return []; - - const accountVpcIdMap = networkConfig.accountVpcIds; - if (lookupScope === POLICY_LOOKUP_SCOPE.ORG) { - return Object.values(accountVpcIdMap).reduce((acc, arr) => { - return acc.concat(arr); - }, []); - } else if (lookupScope === POLICY_LOOKUP_SCOPE.ACCOUNT) { - const accountName = replacementArray[2]; - const accountId = accountsConfig.getAccountId(accountName); - return accountVpcIdMap[accountId] || []; - } else if (lookupScope === POLICY_LOOKUP_SCOPE.OU) { - const organizationUnit = replacementArray[2]; - const accounts = getAccountsForOrgUnit(accountsConfig, organizationUnit); - return accounts - .map(account => accountVpcIdMap[accountsConfig.getAccountId(account.name)] || []) - .reduce((acc, arr) => { - return acc.concat(arr); - }, []); - } - - return []; -} - -function getScopeVpcEndpointIds( - networkConfig: NetworkConfig, - accountsConfig: AccountsConfig, - lookupScope: string, - replacementArray: string[], -): string[] { - if (!networkConfig.accountVpcEndpointIds) return []; - - const accountVpcEndpointIdMap = networkConfig.accountVpcEndpointIds; - if (lookupScope === POLICY_LOOKUP_SCOPE.ORG) { - return Object.values(accountVpcEndpointIdMap).reduce((acc, arr) => { - return acc.concat(arr); - }, []); - } else if (lookupScope === POLICY_LOOKUP_SCOPE.ACCOUNT) { - const accountName = replacementArray[2]; - const accountId = accountsConfig.getAccountId(accountName); - return accountVpcEndpointIdMap[accountId] || []; - } else if (lookupScope === POLICY_LOOKUP_SCOPE.OU) { - const organizationUnit = replacementArray[2]; - const accounts = getAccountsForOrgUnit(accountsConfig, organizationUnit); - return accounts - .map(account => accountVpcEndpointIdMap[accountsConfig.getAccountId(account.name)] || []) - .reduce((acc, arr) => { - return acc.concat(arr); - }, []); - } - - return []; -} - -/** - * When using some APIs to update resource policy, e.g. IAM:UpdateAssumeRolePolicy, - * an array with single string value will be automatically converted to string in the JSON policy. - * We explicitly do the conversion here to avoid policy mismatch while checking resource policy compliance. - * - * @param value - * @returns - */ -function normalize(value: string | string[]) { - if (typeof value === 'string') return value; - if (Array.isArray(value) && value.length === 1) return `"${value[0]}"`; - return value; -} diff --git a/source/packages/@aws-accelerator/utils/lib/regions.ts b/source/packages/@aws-accelerator/utils/lib/regions.ts deleted file mode 100644 index 5ca4b79..0000000 --- a/source/packages/@aws-accelerator/utils/lib/regions.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { createLogger } from './logger'; - -const logger = createLogger(['regions']); - -type RegionInfo = { - azId: string | undefined; - elbAccount: string | undefined; - optIn: boolean; -}; - -export enum RegionName { - 'af-south-1', - 'ap-east-1', - 'ap-northeast-1', - 'ap-northeast-2', - 'ap-northeast-3', - 'ap-south-1', - 'ap-south-2', - 'ap-southeast-1', - 'ap-southeast-2', - 'ap-southeast-3', - 'ap-southeast-4', - 'ca-central-1', - 'ca-west-1', - 'cn-north-1', - 'cn-northwest-1', - 'eu-central-1', - 'eu-central-2', - 'eu-north-1', - 'eu-south-1', - 'eu-south-2', - 'eu-west-1', - 'eu-west-2', - 'eu-west-3', - 'il-central-1', - 'me-central-1', - 'me-south-1', - 'sa-east-1', - 'us-east-1', - 'us-east-2', - 'us-gov-west-1', - 'us-gov-east-1', - 'us-iso-east-1', - 'us-isob-east-1', - 'us-iso-west-1', - 'us-west-1', - 'us-west-2', -} - -const regionsInfo: Record = { - 'af-south-1': { azId: 'afs1-az', elbAccount: '098369216593', optIn: true }, - 'ap-east-1': { azId: 'ape1-az', elbAccount: '754344448648', optIn: true }, - 'ap-northeast-1': { azId: 'apne1-az', elbAccount: '582318560864', optIn: false }, - 'ap-northeast-2': { azId: 'apne2-az', elbAccount: '600734575887', optIn: false }, - 'ap-northeast-3': { azId: 'apne3-az', elbAccount: '383597477331', optIn: false }, - 'ap-south-1': { azId: 'aps1-az', elbAccount: '718504428378', optIn: false }, - 'ap-south-2': { azId: 'aps2-az', elbAccount: undefined, optIn: true }, - 'ap-southeast-1': { azId: 'apse1-az', elbAccount: '114774131450', optIn: false }, - 'ap-southeast-2': { azId: 'apse2-az', elbAccount: '783225319266', optIn: false }, - 'ap-southeast-3': { azId: 'apse3-az', elbAccount: '589379963580', optIn: true }, - 'ap-southeast-4': { azId: 'apse4-az', elbAccount: undefined, optIn: true }, - 'ca-central-1': { azId: 'cac1-az', elbAccount: '985666609251', optIn: false }, - 'ca-west-1': { azId: 'caw1-az', elbAccount: undefined, optIn: true }, - 'cn-north-1': { azId: undefined, elbAccount: '638102146993', optIn: false }, - 'cn-northwest-1': { azId: undefined, elbAccount: '037604701340', optIn: false }, - 'eu-central-1': { azId: 'euc1-az', elbAccount: '054676820928', optIn: false }, - 'eu-central-2': { azId: 'euc2-az', elbAccount: undefined, optIn: true }, - 'eu-north-1': { azId: 'eun1-az', elbAccount: '897822967062', optIn: false }, - 'eu-south-1': { azId: 'eus1-az', elbAccount: '635631232127', optIn: true }, - 'eu-south-2': { azId: 'eus2-az', elbAccount: undefined, optIn: true }, - 'eu-west-1': { azId: 'euw1-az', elbAccount: '156460612806', optIn: false }, - 'eu-west-2': { azId: 'euw2-az', elbAccount: '652711504416', optIn: false }, - 'eu-west-3': { azId: 'euw3-az', elbAccount: '009996457667', optIn: false }, - 'il-central-1': { azId: 'ilc1-az', elbAccount: undefined, optIn: true }, - 'me-central-1': { azId: 'mec1-az', elbAccount: undefined, optIn: true }, - 'me-south-1': { azId: 'mes1-az', elbAccount: '076674570225', optIn: true }, - 'sa-east-1': { azId: 'sae1-az', elbAccount: '507241528517', optIn: false }, - 'us-east-1': { azId: 'use1-az', elbAccount: '127311923021', optIn: false }, - 'us-east-2': { azId: 'use2-az', elbAccount: '033677994240', optIn: false }, - 'us-gov-west-1': { azId: 'usgw1-az', elbAccount: '048591011584', optIn: false }, - 'us-gov-east-1': { azId: 'usge1-az', elbAccount: '190560391635', optIn: false }, - 'us-iso-east-1': { azId: undefined, elbAccount: undefined, optIn: false }, - 'us-isob-east-1': { azId: undefined, elbAccount: undefined, optIn: false }, - 'us-iso-west-1': { azId: undefined, elbAccount: undefined, optIn: false }, - 'us-west-1': { azId: 'usw1-az', elbAccount: '027434742980', optIn: false }, - 'us-west-2': { azId: 'usw2-az', elbAccount: '797873946194', optIn: false }, -}; - -export const AcceleratorElbRootAccounts = new Map( - Object.entries(regionsInfo) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .filter(([_, info]) => info.elbAccount !== undefined) - .map(([region, info]) => [region, info.elbAccount ?? '']), -); - -export const OptInRegions = Object.entries(regionsInfo) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .filter(([_, info]) => info.optIn === true) - .map(([region]) => region); - -export const Regions = Object.keys(regionsInfo); - -export function getAvailabilityZoneMap(region: string) { - const availabilityZoneIdMap = new Map( - Object.entries(regionsInfo) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .filter(([_, info]) => info.azId !== undefined) - .map(([region, info]) => [region, info.azId ?? '']), - ); - - const availabilityZoneId = availabilityZoneIdMap.get(region); - if (!availabilityZoneIdMap.get(region)) { - logger.error(`The ${region} provided does not support Physical AZ IDs.`); - throw new Error(`Configuration validation failed at runtime.`); - } - return availabilityZoneId; -} diff --git a/source/packages/@aws-accelerator/utils/lib/set-organizations-client.ts b/source/packages/@aws-accelerator/utils/lib/set-organizations-client.ts deleted file mode 100644 index 514791f..0000000 --- a/source/packages/@aws-accelerator/utils/lib/set-organizations-client.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { setRetryStrategy } from './common-functions'; -import { OrganizationsClient } from '@aws-sdk/client-organizations'; - -/** - * Sets an SDKv3 Organizations client based on partition - * @param partition string - * @param solutionId string | undefined - * @returns OrganizationsClient - */ -export function setOrganizationsClient(partition: string, solutionId?: string): OrganizationsClient { - if (partition === 'aws-us-gov') { - return new OrganizationsClient({ - region: 'us-gov-west-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else if (partition === 'aws-cn') { - return new OrganizationsClient({ - region: 'cn-northwest-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } else { - return new OrganizationsClient({ - region: 'us-east-1', - customUserAgent: solutionId, - retryStrategy: setRetryStrategy(), - }); - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/set-token-preferences.ts b/source/packages/@aws-accelerator/utils/lib/set-token-preferences.ts deleted file mode 100644 index 450e88b..0000000 --- a/source/packages/@aws-accelerator/utils/lib/set-token-preferences.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { IAMClient, SetSecurityTokenServicePreferencesCommand, GetAccountSummaryCommand } from '@aws-sdk/client-iam'; -import { throttlingBackOff } from './throttle'; -import { setRetryStrategy } from './common-functions'; -import { createLogger } from './logger'; -const logger = createLogger(['utils-set-token-preferences']); - -export async function setStsTokenPreferences(account: string, globalRegion: string) { - const iamClient = new IAMClient({ retryStrategy: setRetryStrategy(), region: globalRegion }); - try { - const getAccountSummary = await throttlingBackOff(() => iamClient.send(new GetAccountSummaryCommand({}))); - if (getAccountSummary.SummaryMap!['GlobalEndpointTokenVersion'] !== 2) { - logger.debug(`Setting the account ${account} to have STS version 2 token`); - await setTokenVersion(iamClient, account); - } - logger.debug(`Account ${account} has STS version 2 token. No action will be taken`); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - const errMsg = `There was an error getting account summary for account ${account}. Error: ${JSON.stringify(e)}`; - logger.error(errMsg); - throw new Error(errMsg); - } -} - -export async function setTokenVersion(iamClient: IAMClient, account: string) { - try { - await throttlingBackOff(() => - iamClient.send( - new SetSecurityTokenServicePreferencesCommand({ - GlobalEndpointTokenVersion: 'v2Token', - }), - ), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - const errMsg = `There was an error setting token version to v2 for account ${account}. Error: ${JSON.stringify(e)}`; - logger.error(errMsg); - throw new Error(errMsg); - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/ssm-parameter-path.ts b/source/packages/@aws-accelerator/utils/lib/ssm-parameter-path.ts deleted file mode 100644 index 24d1f08..0000000 --- a/source/packages/@aws-accelerator/utils/lib/ssm-parameter-path.ts +++ /dev/null @@ -1,463 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export enum SsmResourceType { - /** - * Accelerator parameters - */ - /** - * Accelerator stack ID - * - * `${0}` is replaced with the stack name - */ - STACK_ID = '/${0}/stack-id', - /** - * Accelerator version - * - * `${0}` is replaced with the stack name - */ - VERSION = '/${0}/version', - /** - * Global network resources - */ - /** - * ACM certificate ARN - * - * `${0}` is replaced with the certificate name - */ - ACM_CERT = '/acm/${0}/arn', - /** - * Customer gateway ID - * - * `${0}` is replaced with the customer gateway name - */ - CGW = '/network/customerGateways/${0}/id', - /** - * Direct Connect Gateway ID - * - * `${0}` is replaced with the direct connect gateway name - */ - DXGW = '/network/directConnectGateways/${0}/id', - /** - * Direct Connect virtual interface ID - * - * `${0}` is replaced with the direct connect gateway name - * - * `${1}` is replaced with the virtual interface name - */ - DXVIF = '/network/directConnectGateways/${0}/virtualInterfaces/${1}/id', - /** - * Accelerator SCP ID - * - * `${0}` is replaced with the scp name - */ - SCP = '/organizations/scp/${0}/id', - /** - * Transit Gateway ID - * - * `${0}` is replaced with the transit gateway name - */ - TGW = '/network/transitGateways/${0}/id', - /** - * Transit Gateway peering ID - * - * `${0}` is replaced with the transit gateway name for either the requester or accepter TGW - * (depending on account we're putting the parameter to) - * - * `${1}` is replaced with the transit gateway peering name - */ - TGW_PEERING = '/network/transitGateways/${0}/peering/${1}/id', - /** - * Transit Gateway route table ID - * - * `${0}` is replaced with the transit gateway name - * - * `${1}` is replaced with the route table name - */ - TGW_ROUTE_TABLE = '/network/transitGateways/${0}/routeTables/${1}/id', - /** - * Transit Gateway VPN attachment ID - * - * `${0}` is replaced with the VPN connection name - */ - TGW_VPN = '/network/vpnConnection/${0}/id', - /** - * Prefix list ID - * - * `${0}` is replaced with the prefix list name - */ - PREFIX_LIST = '/network/prefixList/${0}/id', - /** - * VPC Resources - */ - /** - * VPC ID - * - * `${0}` is replaced with the VPC name - */ - VPC = '/network/vpc/${0}/id', - /** - * VPC IPV4 CIDR Block - * - * `${0}` is replaced with the VPC Primary CIDR Block - */ - VPC_IPV4_CIDR_BLOCK = '/network/vpc/${0}/cidr/ipv4', - /** - * VPC Endpoint ID - * - * `${0}` is replaced with the VPC name - * - * `${1} is replaced with the service name - */ - VPC_ENDPOINT = '/network/vpc/${0}/endpoint/${1}/id', - /** - * VPC peering connection ID - * - * `${0}` is replaced with the VPC peering name - * - */ - VPC_PEERING = '/network/vpcPeering/${0}/id', - /** - * Internet gateway ID - * - * `${0}` is replaced with the VPC name - * - */ - IGW = '/network/vpc/${0}/internetGateway/id', - /** - * Virtual Private gateway ID - * - * `${0}` is replaced with the VPC name - * - */ - VPN_GW = '/network/vpc/${0}/virtualPrivateGateway/id', - /** - * Subnet ID - * - * `${0}` is replaced with the VPC name - * - * `${1} is replaced with the subnet name - */ - SUBNET = '/network/vpc/${0}/subnet/${1}/id', - /** - * Subnet IPV4 CIDR Block - * - * `${0}` is replaced with the VPC name - * - * `${1} is replaced with the subnet name - */ - SUBNET_IPV4_CIDR_BLOCK = '/network/vpc/${0}/subnet/${1}/cidr/ipv4', - /** - * Route table ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the route table name - */ - ROUTE_TABLE = '/network/vpc/${0}/routeTable/${1}/id', - /** - * Security group ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the security group name - */ - SECURITY_GROUP = '/network/vpc/${0}/securityGroup/${1}/id', - /** - * Network ACL ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the network ACL name - */ - NACL = '/network/vpc/${0}/networkAcl/${1}/id', - /** - * Network ACL Subnet Association ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the network ACL name - * * - * `${2}` is replaced with the subnet name - */ - NETWORK_ACL_SUBNET_ASSOCIATION = '/network/vpc/${0}/networkAcl/${1}/subnet/${2}/id', - /** - * NAT gateway ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the NAT gateway name - */ - NAT_GW = '/network/vpc/${0}/natGateway/${1}/id', - /** - * Transit gateway VPC attachment ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the transit gateway attachment name - */ - TGW_ATTACHMENT = '/network/vpc/${0}/transitGatewayAttachment/${1}/id', - /** - * Route53 resources - */ - /** - * Route 53 DNS firewall rule group ID - * - * `${0}` is replaced with the DNS firewall rule group name - */ - DNS_RULE_GROUP = '/network/route53Resolver/firewall/ruleGroups/${0}/id', - /** - * Interface endpoint DNS name - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the interface endpoint service name - */ - ENDPOINT_DNS = '/network/vpc/${0}/endpoints/${1}/dns', - /** - * Interface endpoint hosted zone ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the interface endpoint service name - */ - ENDPOINT_ZONE_ID = '/network/vpc/${0}/endpoints/${1}/hostedZoneId', - /** - * Private hosted zone ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the interface endpoint service name - */ - PHZ_ID = '/network/vpc/${0}/route53/hostedZone/${1}/id', - /** - * Route 53 query logs configuration ID - * - * `${0}` is replaced with the query logs configuration name - */ - QUERY_LOGS = '/network/route53Resolver/queryLogConfigs/${0}/id', - /** - * Route 53 query logs association ID - * - * `${0}` is replaced with the name of the VPC - */ - QUERY_LOGS_ASSOCIATION = '/network/vpc/${0}/route53/queryLogAssociation/id', - /** - * Route 53 resolver endpoint ID - * - * `${0} is replaced with the resolver endpoint name - */ - RESOLVER_ENDPOINT = '/network/route53Resolver/endpoints/${0}/id', - /** - * Route 53 resolver rule ID - * - * `${0}` is replaced with the resolver rule name - */ - RESOLVER_RULE = '/network/route53Resolver/rules/${0}/id', - /** - * Central Network Services - */ - /** - * VPC IPAM ID - * - * `${0}` is replaced with the IPAM name - */ - IPAM = '/network/ipam/${0}/id', - /** - * VPC IPAM pool ID - * - * `${0}` is replaced with the IPAM pool name - */ - IPAM_POOL = '/network/ipam/pools/${0}/id', - /** - * VPC IPAM scope ID - * - * `${0}` is replaced with the IPAM scope name - */ - IPAM_SCOPE = '/network/ipam/scopes/${0}/id', - /** - * Network firewall ARN - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the network firewall name - */ - NFW = '/network/vpc/${0}/networkFirewall/${1}/arn', - /** - * Network firewall policy ARN - * - * `${0}` is replaced with the network firewall policy name - */ - NFW_POLICY = '/network/networkFirewall/policies/${0}/arn', - /** - * Network firewall rule group ARN - * - * `${0}` is replaced with the rule group name - */ - NFW_RULE_GROUP = '/network/networkFirewall/ruleGroups/${0}/arn', - /** - * Load balancers - */ - /** - * Application load balancer ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the application load balancer name - */ - ALB = '/network/vpc/${0}/alb/${1}/id', - /** - * Network load balancer ID - * - * `${0}` is replaced with the VPC name - * - * `${1}` is replaced with the network load balancer name - */ - NLB = '/network/vpc/${0}/nlb/${1}/id', - /** - * Gateway load balancer ARN - * - * `${0}` is replaced with the gateway load balancer name - */ - GWLB_ARN = '/network/gwlb/${0}/arn', - /** - * Gateway load balancer endpoint service ID - * - * `${0}` is replaced with the gateway load balancer name - */ - GWLB_SERVICE = '/network/gwlb/${0}/endpointService/id', - /** - * Applications - */ - /** - * Target group - * - * `${0}` is replaced with the application name - * - * `${1}` is replaced with the VPC name - * - * `${2}` is replaced with the target group name - */ - TARGET_GROUP = '/application/targetGroup/${0}/${1}/${2}/arn', - /** - * IAM - */ - /** - * IAM Role Arn - * - * `${0}` is replaced with the IAM Role name - */ - IAM_ROLE = '/iam/role/${0}/arn', - /** - * IAM Managed Policy Arn - * - * `${0}` is replaced with the IAM Managed Policy name - */ - IAM_POLICY = '/iam/policy/${0}/arn', - /** - * IAM Group Arn - * - * `${0}` is replaced with the IAM Group name - */ - IAM_GROUP = '/iam/group/${0}/arn', - /** - * IAM User Arn - * - * `${0}` is replaced with the IAM Username - */ - IAM_USER = '/iam/user/${0}/arn', - /** - * EC2 firewall dependency resources - */ - /** - * Prefix list ID (for cross-account EC2 firewall CGWs) - * `${0}` is replaced with the CGW name - * `${1}` is replaced with the prefix list name - */ - CROSS_ACCOUNT_PREFIX_LIST = '/network/customerGateways/${0}/prefixList/${1}/id', - /** - * Cross-account TGW ID (for cross-account EC2 firewall CGWs) - * - * `${0}` is replaced with the CGW name - * `${1}` is replaced with the TGW name - */ - CROSS_ACCOUNT_TGW = '/network/customerGateways/${0}/transitGateways/${1}/id', - /** - * Cross-account TGW route table ID (for cross-account EC2 firewall CGWs) - * - * `${0}` is replaced with the CGW name - * `${1}` is replaced with the TGW name - * `${2}` is replaced with the TGW route table name - */ - CROSS_ACCOUNT_TGW_ROUTE_TABLE = '/network/customerGateways/${0}/transitGateways/${1}/routeTables/${2}/id', - /** - * Cross-account VGW ID (for cross-account EC2 firewall CGWs) - * - * `${0}` is replaced with the CGW name - * `${1}` is replaced with the VPC name - */ - CROSS_ACCOUNT_VGW = '/network/customerGateways/${0}/virtualPrivateGateway/${1}/id', - /** - * SSM Resource Data Sync Name - * - * `${0}` is replaced with the Resource Data Sync Name - */ - RESOURCE_DATA_SYNC = '/ssm/resourceDataSync/${0}', - /** - * SSM Association Name - * `${0}` is replaced with the Resource Data Sync Name - */ - ASSOCIATION = '/ssm/association/${0}', - /** - * Firewall Instance ID - * `${0}` is replaced with the Firewall Instance Name - */ - FIREWALL_INSTANCE = '/network/ec2/firewall/instanceName/${0}/instanceId', - /** - * Managed AD Directory Id - * `${0}` is replaced with the Directory Friendly Name - */ - MANAGED_AD = '/managed_ad/directory/name/${0}/directoryId', - /** - * Managed AD Domain Name - * `${0}` is replaced with the Directory Friendly Name - */ - MANAGED_AD_DNS_NAME = '/managed_ad/directory/name/${0}/dnsName', -} - -export class SsmParameterPath { - public readonly parameterPath: string; - - constructor(ssmPrefix: string, resourceType: SsmResourceType, replacements: string[]) { - // Transform SSM path using replacement strings - this.parameterPath = this.transformPath(ssmPrefix, resourceType, replacements); - } - - /** - * Transforms SSM path based on given prefix and replacement values - * @param prefix - * @param rawPath - * @param replacements - * @returns - */ - private transformPath(prefix: string, rawPath: string, replacements: string[]): string { - let param = rawPath; - let i = 0; - - for (const value of replacements) { - param = param.replace(new RegExp(`\\$\\{${i}\\}`, 'g'), value); - i += 1; - } - return prefix + param; - } -} diff --git a/source/packages/@aws-accelerator/utils/lib/throttle.ts b/source/packages/@aws-accelerator/utils/lib/throttle.ts deleted file mode 100644 index 3e465fe..0000000 --- a/source/packages/@aws-accelerator/utils/lib/throttle.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { backOff, IBackOffOptions } from 'exponential-backoff'; - -/** - * Auxiliary function to retry AWS SDK calls when a throttling error occurs. - */ -export function throttlingBackOff( - request: () => Promise, - options?: Partial>, -): Promise { - return backOff(request, { - startingDelay: 150, - numOfAttempts: 20, - jitter: 'full', - retry: isThrottlingError, - ...options, - }); -} - -export const isThrottlingError = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types - e: any, -): boolean => - e.retryable === true || - // SDKv2 Error Structure - e.code === 'ConcurrentModificationException' || // Retry for AWS Organizations - e.code === 'InsufficientDeliveryPolicyException' || // Retry for ConfigService - e.code === 'NoAvailableDeliveryChannelException' || // Retry for ConfigService - e.code === 'ConcurrentModifications' || // Retry for AssociateHostedZone - e.code === 'LimitExceededException' || // Retry for SecurityHub - e.code === 'OperationNotPermittedException' || // Retry for RAM - e.code === 'InvalidStateException' || //retry for ServiceCatalog - e.code === 'TooManyRequestsException' || - e.code === 'TooManyUpdates' || - e.code === 'Throttling' || - e.code === 'ThrottlingException' || - e.code === 'InternalErrorException' || - e.code === 'InternalException' || - e.code === 'ECONNRESET' || - e.code === 'ENOTFOUND' || - e.code === 'EPIPE' || - e.code === 'ETIMEDOUT' || - // SDKv3 Error Structure - e.name === 'ConcurrentModificationException' || // Retry for AWS Organizations - e.name === 'InsufficientDeliveryPolicyException' || // Retry for ConfigService - e.name === 'NoAvailableDeliveryChannelException' || // Retry for ConfigService - e.name === 'ConcurrentModifications' || // Retry for AssociateHostedZone - e.name === 'LimitExceededException' || // Retry for SecurityHub - e.name === 'OperationNotPermittedException' || // Retry for RAM - e.name === 'CredentialsProviderError' || // Retry for STS - e.name === 'TooManyRequestsException' || - e.name === 'TooManyUpdates' || - e.name === 'Throttling' || - e.name === 'ThrottlingException' || - e.name === 'InternalErrorException' || - e.name === 'InternalException' || - e.name === 'ECONNRESET' || - e.name === 'EPIPE' || - e.name === 'ENOTFOUND' || - e.name === 'ETIMEDOUT'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/source/packages/@aws-accelerator/utils/package.json b/source/packages/@aws-accelerator/utils/package.json deleted file mode 100644 index 3094eeb..0000000 --- a/source/packages/@aws-accelerator/utils/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@aws-accelerator/utils", - "version": "0.0.0", - "private": true, - "description": "Accelerator utilities", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "exponential-backoff": "3.1.1" - }, - "devDependencies": { - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "glob": "10.3.10", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.9.5", - "winston": "3.8.2" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - }, - "nohoist": [ - "**/exponential-backoff", - "**/exponential-backoff/**" - ] -} diff --git a/source/packages/@aws-accelerator/utils/test/evaluate-limits.test.ts b/source/packages/@aws-accelerator/utils/test/evaluate-limits.test.ts deleted file mode 100644 index 7af3e99..0000000 --- a/source/packages/@aws-accelerator/utils/test/evaluate-limits.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { test, beforeEach, expect, afterEach } from '@jest/globals'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { STSClient, AssumeRoleCommand, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; -import { GetServiceQuotaCommand, ServiceQuotasClient } from '@aws-sdk/client-service-quotas'; -import { evaluateLimits } from '../lib/evaluate-limits'; - -let stsMock: AwsClientStub; -let serviceQuotasMock: AwsClientStub; - -beforeEach(() => { - stsMock = mockClient(STSClient); - serviceQuotasMock = mockClient(ServiceQuotasClient); -}); -afterEach(() => { - stsMock.reset(); - serviceQuotasMock.reset(); -}); - -test('evaluateLimits everything works in same account', async () => { - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).resolves({ - Quota: { Value: 10 }, - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 1000 }, - }); - const result = await evaluateLimits('us-east-1', '111111111111', 'aws', 'test', '111111111111'); - expect(result).toBeUndefined(); -}); - -test('evaluateLimits low limits in same account', async () => { - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).resolves({}); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 10 }, - }); - await expect(evaluateLimits('us-east-1', '111111111111', 'aws', 'test', '111111111111')).rejects.toThrowError(); -}); - -test('evaluateLimits serviceQuota api error in same account', async () => { - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).rejects(); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 10 }, - }); - await expect(evaluateLimits('us-east-1', '111111111111', 'aws', 'test', '111111111111')).rejects.toThrowError(); -}); - -test('evaluateLimits everything works in cross account', async () => { - stsMock.on(GetCallerIdentityCommand).resolves({ - Account: '111111111111', - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-2DC20C30', ServiceCode: 'codebuild' }).resolves({ - Quota: { Value: 10 }, - }); - serviceQuotasMock.on(GetServiceQuotaCommand, { QuotaCode: 'L-B99A9384', ServiceCode: 'lambda' }).resolves({ - Quota: { Value: 1000 }, - }); - stsMock.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'fake-access-key', - SecretAccessKey: 'fake-secret-key', - SessionToken: 'fake-session-token', - Expiration: new Date(Date.now() + 3600 * 1000), - }, - }); - const result = await evaluateLimits('us-east-1', '222222222222', 'aws', 'test', '111111111111'); - expect(result).toBeUndefined(); -}); diff --git a/source/packages/@aws-accelerator/utils/test/get-template.test.ts b/source/packages/@aws-accelerator/utils/test/get-template.test.ts deleted file mode 100644 index 31f41ec..0000000 --- a/source/packages/@aws-accelerator/utils/test/get-template.test.ts +++ /dev/null @@ -1,251 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { getCloudFormationTemplate } from '../lib/get-template'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { - CloudFormationClient, - GetTemplateCommand, - CloudFormationServiceException, - ChangeSetNotFoundException, -} from '@aws-sdk/client-cloudformation'; -import { STSClient, AssumeRoleCommand, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; -import { expect, it, beforeEach, afterEach } from '@jest/globals'; -import * as path from 'path'; -import * as fs from 'fs'; - -let cfnMock: AwsClientStub; -let stsMock: AwsClientStub; -beforeEach(() => { - cfnMock = mockClient(CloudFormationClient); - stsMock = mockClient(STSClient); -}); -afterEach(() => { - cfnMock.reset(); - stsMock.reset(); -}); - -it('same account, template is valid', async () => { - //Given - const currentAccountId = '123456789012'; - const stackName = 'stack1'; - const template = '{"a":"b"}'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: currentAccountId }); - stsMock.on(AssumeRoleCommand).resolves({}); - cfnMock.on(GetTemplateCommand).resolves({ TemplateBody: template }); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - undefined, - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe(template); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); - -it('cross account, template is valid', async () => { - //Given - const currentAccountId = '012345678910'; - const crossAccountId = '123456789012'; - const stackName = 'stack2'; - const template = '{"a":"b"}'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: crossAccountId }); - stsMock.on(AssumeRoleCommand).resolves({ - Credentials: { - AccessKeyId: 'ASIAIOSFODNN7EXAMPLE', - SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - SessionToken: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - Expiration: undefined, - }, - }); - cfnMock.on(GetTemplateCommand).resolves({ TemplateBody: template }); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - undefined, - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe(template); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); - -it('same account - custom stack, template is valid', async () => { - //Given - const currentAccountId = '123456789012'; - const stackName = 'customStack1-custom-stack-account-region'; - const customStackName = 'customStack1'; - const template = '{"a":"b"}'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: currentAccountId }); - stsMock.on(AssumeRoleCommand).resolves({}); - cfnMock.on(GetTemplateCommand, { StackName: stackName, TemplateStage: 'Processed' }).rejects(); - cfnMock - .on(GetTemplateCommand, { StackName: customStackName, TemplateStage: 'Original' }) - .resolves({ TemplateBody: template }); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - 'customizations', - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe(template); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); - -it('same account, cfn error 1', async () => { - //Given - const currentAccountId = '123456789012'; - const stackName = 'stack1CfnError1'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: currentAccountId }); - stsMock.on(AssumeRoleCommand).resolves({}); - cfnMock.on(GetTemplateCommand).rejects( - new CloudFormationServiceException({ - $metadata: { httpStatusCode: 400 }, - name: 'name', - $fault: 'server', - message: `Stack with id ${stackName} does not exist`, - }), - ); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - undefined, - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe('{}'); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); - -it('same account, cfn gives string', async () => { - //Given - const currentAccountId = '123456789012'; - const stackName = 'stack1CfnError2'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: currentAccountId }); - stsMock.on(AssumeRoleCommand).resolves({}); - cfnMock.on(GetTemplateCommand).resolves( - new ChangeSetNotFoundException({ - $metadata: { httpStatusCode: 400 }, - message: 'message', - }), - ); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - undefined, - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe('{}'); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); - -it('same account, cfn responds with non json string', async () => { - //Given - const currentAccountId = '123456789012'; - const stackName = 'stackErr1'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: currentAccountId }); - stsMock.on(AssumeRoleCommand).resolves({}); - cfnMock.on(GetTemplateCommand).resolves({ TemplateBody: 'string' }); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - undefined, - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe('{}'); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); - -it('same account, cfn responds with non json undefined', async () => { - //Given - const currentAccountId = '123456789012'; - const stackName = 'stackErr2'; - stsMock.on(GetCallerIdentityCommand).resolves({ Account: currentAccountId }); - stsMock.on(AssumeRoleCommand).resolves({}); - cfnMock.on(GetTemplateCommand).resolves({ TemplateBody: undefined }); - - //when - await getCloudFormationTemplate( - currentAccountId, - 'region', - 'aws', - undefined, - stackName, - path.resolve(__dirname), - 'roleNames', - ); - const content = fs.readFileSync(path.join(__dirname, `${stackName}.json`), 'utf-8'); - - // then - expect(content.toString()).toBe('{}'); - - //clean up - fs.rmSync(path.join(__dirname, `${stackName}.json`)); -}); diff --git a/source/packages/@aws-accelerator/utils/test/integration/set-token-preferences.integ.ts b/source/packages/@aws-accelerator/utils/test/integration/set-token-preferences.integ.ts deleted file mode 100644 index 93bcc29..0000000 --- a/source/packages/@aws-accelerator/utils/test/integration/set-token-preferences.integ.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { setStsTokenPreferences } from '../../lib/set-token-preferences'; -import { IAMClient, SetSecurityTokenServicePreferencesCommand, GetAccountSummaryCommand } from '@aws-sdk/client-iam'; -import { throttlingBackOff } from '../../lib/throttle'; -import { setRetryStrategy } from '../../lib/common-functions'; -import { expect, it } from '@jest/globals'; - -/** - * Run the test to see if the function is working in a single account. - * This code will find out what version of token is being used and change it to version 1 if necessary - * Then it will run function to see if the token has changed to version 2. - * Put the account back in original state. - * To run this test, run the command below: - */ -// yarn jest --testMatch [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test|integ).[jt]s?(x)" ] - -const iamClient = new IAMClient({ retryStrategy: setRetryStrategy() }); - -//get current token version -async function getTokenVersion() { - const response = await throttlingBackOff(() => iamClient.send(new GetAccountSummaryCommand({}))); - return response.SummaryMap!['GlobalEndpointTokenVersion']; -} - -// set token version to 1 -async function setTokenVersion(version: number) { - const knownVersions = [1, 2]; - if (knownVersions.includes(version)) { - await throttlingBackOff(() => - iamClient.send( - new SetSecurityTokenServicePreferencesCommand({ - GlobalEndpointTokenVersion: `v${version}Token`, - }), - ), - ); - } else { - throw new Error(`Unknown token version: ${version}`); - } -} - -it('set token preferences from 1 to 2', async () => { - // Given - const originalTokenVersion = await getTokenVersion(); - console.log(`Found original version to be: ${originalTokenVersion}`); - if (originalTokenVersion !== 1) { - await setTokenVersion(1); - } - await setStsTokenPreferences('testAccount', 'region'); - // When - const testVersion = await getTokenVersion(); - console.log(`Found test version to be: ${testVersion}`); - - // Then - expect(testVersion).toBe(2); - - //Cleanup - //revert the token to original - await setTokenVersion(originalTokenVersion); -}); - -it('set token preferences from 2 to 2', async () => { - // Given - const originalTokenVersion = await getTokenVersion(); - console.log(`Found original version to be: ${originalTokenVersion}`); - if (originalTokenVersion !== 2) { - await setTokenVersion(2); - } - await setStsTokenPreferences('testAccount', 'region'); - // When - const testVersion = await getTokenVersion(); - console.log(`Found test version to be: ${testVersion}`); - - // Then - expect(testVersion).toBe(2); - - //Cleanup - //revert the token to original - await setTokenVersion(originalTokenVersion); -}); diff --git a/source/packages/@aws-accelerator/utils/test/set-token-preferences.test.ts b/source/packages/@aws-accelerator/utils/test/set-token-preferences.test.ts deleted file mode 100644 index 99b0b20..0000000 --- a/source/packages/@aws-accelerator/utils/test/set-token-preferences.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -import { setStsTokenPreferences, setTokenVersion } from '../lib/set-token-preferences'; -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; -import { IAMClient, SetSecurityTokenServicePreferencesCommand, GetAccountSummaryCommand } from '@aws-sdk/client-iam'; -import { expect, it, beforeEach, afterEach, test } from '@jest/globals'; - -let iamMock: AwsClientStub; -beforeEach(() => { - iamMock = mockClient(IAMClient); -}); -afterEach(() => { - iamMock.reset(); -}); - -it('does not set the token preferences', async () => { - //Given - iamMock.on(GetAccountSummaryCommand).resolves({ - SummaryMap: { - AccessKeysPerUserQuota: 2, - AccountAccessKeysPresent: 1, - AccountMFAEnabled: 0, - AccountSigningCertificatesPresent: 0, - AttachedPoliciesPerGroupQuota: 10, - AttachedPoliciesPerRoleQuota: 10, - AttachedPoliciesPerUserQuota: 10, - GlobalEndpointTokenVersion: 2, - GroupPolicySizeQuota: 5120, - Groups: 15, - GroupsPerUserQuota: 10, - GroupsQuota: 100, - MFADevices: 6, - MFADevicesInUse: 3, - Policies: 8, - PoliciesQuota: 1000, - PolicySizeQuota: 5120, - PolicyVersionsInUse: 22, - PolicyVersionsInUseQuota: 10000, - ServerCertificates: 1, - ServerCertificatesQuota: 20, - SigningCertificatesPerUserQuota: 2, - UserPolicySizeQuota: 2048, - Users: 27, - UsersQuota: 5000, - VersionsPerPolicyQuota: 5, - }, - }); - iamMock.on(SetSecurityTokenServicePreferencesCommand).resolves({}); - - //when - const response = await setStsTokenPreferences('123456789012', 'region'); - - // then - no response from SetSecurityTokenServicePreferences API - expect(response).toBeUndefined(); -}); - -it('sets the token preferences', async () => { - //Given - iamMock.on(GetAccountSummaryCommand).resolves({ - SummaryMap: { - AccessKeysPerUserQuota: 2, - AccountAccessKeysPresent: 1, - AccountMFAEnabled: 0, - AccountSigningCertificatesPresent: 0, - AttachedPoliciesPerGroupQuota: 10, - AttachedPoliciesPerRoleQuota: 10, - AttachedPoliciesPerUserQuota: 10, - GlobalEndpointTokenVersion: 1, - GroupPolicySizeQuota: 5120, - Groups: 15, - GroupsPerUserQuota: 10, - GroupsQuota: 100, - MFADevices: 6, - MFADevicesInUse: 3, - Policies: 8, - PoliciesQuota: 1000, - PolicySizeQuota: 5120, - PolicyVersionsInUse: 22, - PolicyVersionsInUseQuota: 10000, - ServerCertificates: 1, - ServerCertificatesQuota: 20, - SigningCertificatesPerUserQuota: 2, - UserPolicySizeQuota: 2048, - Users: 27, - UsersQuota: 5000, - VersionsPerPolicyQuota: 5, - }, - }); - iamMock.on(SetSecurityTokenServicePreferencesCommand).resolves({}); - - //when - const response = await setStsTokenPreferences('123456789012', 'region'); - - // then - no response from SetSecurityTokenServicePreferences API - expect(response).toBeUndefined(); -}); - -test('throws error on GetAccountSummary', async () => { - iamMock.on(GetAccountSummaryCommand).rejects(); - await expect(setStsTokenPreferences('123456789012', 'region')).rejects.toThrowError('{}'); -}); - -test('throws error on SetSecurityTokenServicePreferencesCommand', async () => { - // given - iamMock - .on(SetSecurityTokenServicePreferencesCommand, { - GlobalEndpointTokenVersion: 'v2Token', - }) - .rejects(); - // when and then - await expect(setTokenVersion(new IAMClient(), '123456789012')).rejects.toThrowError('{}'); -}); diff --git a/source/packages/@aws-accelerator/utils/test/throttle.test.ts b/source/packages/@aws-accelerator/utils/test/throttle.test.ts deleted file mode 100644 index aa1106f..0000000 --- a/source/packages/@aws-accelerator/utils/test/throttle.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -// import { countResources, expect as expectCDK } from '@aws-cdk/assert'; -// import * as cdk from 'aws-cdk-lib'; - -test('Throttle test', () => { - // // const app = new cdk.App(); - // // // WHEN - // // const stack = new Installer.InstallerStack(app, 'MyTestStack'); - // // // THEN - // // // expectCDK(stack).to(matchTemplate({ - // // // "Resources": {} - // // // }, MatchStyle.EXACT)) -}); diff --git a/source/packages/@aws-accelerator/utils/tsconfig.json b/source/packages/@aws-accelerator/utils/tsconfig.json deleted file mode 100644 index fc62a1e..0000000 --- a/source/packages/@aws-accelerator/utils/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/.gitignore b/source/packages/@aws-cdk-extensions/cdk-extensions/.gitignore deleted file mode 100644 index f60797b..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.js -!jest.config.js -*.d.ts -node_modules - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/.npmignore b/source/packages/@aws-cdk-extensions/cdk-extensions/.npmignore deleted file mode 100644 index c1d6d45..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/README.md b/source/packages/@aws-cdk-extensions/cdk-extensions/README.md deleted file mode 100644 index e09375b..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/README.md +++ /dev/null @@ -1 +0,0 @@ -# @aws-cdk-extensions/cdk-extensions diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/index.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/index.ts deleted file mode 100644 index 204bbfa..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/repository'; -export * from './lib/trail'; diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/jest.config.js b/source/packages/@aws-cdk-extensions/cdk-extensions/jest.config.js deleted file mode 100644 index ffbe26f..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/jest.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const packageJson = require('./package.json'); -module.exports = { - // Automatically clear mock calls and instances between every test - clearMocks: false, - - // Explicitly disable the watch mode - watchAll: false, - - // Force Jest to exit after all tests have completed running. This is useful when resources set up by test code cannot be adequately cleaned up. - forceExit: true, - - // Attempt to collect and print open handles preventing Jest from exiting cleanly. - // Considered using this option to detect async operations that kept running after all tests finished - detectOpenHandles: true, - - // The directory where Jest should output its coverage files - coverageDirectory: 'coverage', - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/'], - - // An object that configures minimum threshold enforcement for coverage results - coverageThreshold: { - global: { - branches: 75, - functions: 100, - lines: 100, - statements: 100, - }, - }, - - // An array of directory names to be searched recursively up from the requiring module's location - moduleDirectories: ['node_modules'], - - // An array of file extensions your modules use - moduleFileExtensions: ['ts', 'json', 'jsx', 'js', 'tsx', 'node'], - - // Automatically reset mock state between every test - resetMocks: true, - - // The glob patterns Jest uses to detect test files - testMatch: ['**/*.test.ts'], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ['/node_modules/'], - - // A map from regular expressions to paths to transformers - transform: { - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - coverageReporters: ['text', ['lcov', { projectRoot: '../../' }]], - - // This option allows the use of a custom results processor. - testResultsProcessor: 'jest-sonar-reporter', - - // Run tests with jest-junit reporters. - reporters: [ - 'default', - [ - 'jest-junit', - { - suiteName: packageJson.name, - outputDirectory: '../../../test-reports', - uniqueOutputName: 'true', - addFileAttribute: 'true', - suiteNameTemplate: '{filename}', - classNameTemplate: packageJson.name, - titleTemplate: '{title}', - }, - ], - ], -}; diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/lib/repository.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/lib/repository.ts deleted file mode 100644 index 9ec547a..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/lib/repository.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Construct } from 'constructs'; -import * as codecommit from 'aws-cdk-lib/aws-codecommit'; - -/** - * Initialized Repository properties - */ -export interface RepositoryProps extends codecommit.RepositoryProps { - /** - * Name of the repository. - * - * This property contains s3 bucket name to initialize CodeCommit repositories. - * - * - */ - readonly s3BucketName: string; - /** - * Name of the repository. - * - * This property contains s3 object key for initializing CodeCommit repositories. - * - * - */ - readonly s3key: string; - /** - * A branch name of the repository to be initialized. - * - * This is an optional property - * - * @default - main - */ - readonly repositoryBranchName?: string; -} - -/** - * Class to initialize repository - */ -export class Repository extends codecommit.Repository { - constructor(scope: Construct, id: string, props: RepositoryProps) { - super(scope, id, props); - - const cfnRepository = this.node.defaultChild as codecommit.CfnRepository; - - cfnRepository.code = { - branchName: props.repositoryBranchName ? props.repositoryBranchName : 'main', - s3: { - bucket: props.s3BucketName, - key: props.s3key, - }, - }; - } -} diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/lib/trail.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/lib/trail.ts deleted file mode 100644 index 8fae73c..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/lib/trail.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail'; -import { IResolvable } from 'aws-cdk-lib/core'; -import { Construct } from 'constructs'; - -export interface TrailProps extends cloudtrail.TrailProps { - readonly isOrganizationTrail: boolean; - readonly apiCallRateInsight: boolean; - readonly apiErrorRateInsight: boolean; -} - -export class Trail extends cloudtrail.Trail { - constructor(scope: Construct, id: string, props: TrailProps) { - super(scope, id, props); - - const insights: IResolvable | (IResolvable | cloudtrail.CfnTrail.InsightSelectorProperty)[] | undefined = []; - - if (props.apiCallRateInsight) { - insights.push({ insightType: 'ApiCallRateInsight' }); - } - - if (props.apiErrorRateInsight) { - insights.push({ insightType: 'ApiErrorRateInsight' }); - } - - const cfnRepository = this.node.defaultChild as cloudtrail.CfnTrail; - cfnRepository.isOrganizationTrail = props.isOrganizationTrail; - cfnRepository.insightSelectors = insights; - } -} diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/package.json b/source/packages/@aws-cdk-extensions/cdk-extensions/package.json deleted file mode 100644 index 09e2136..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "@aws-cdk-extensions/cdk-extensions", - "version": "0.0.0", - "private": true, - "description": "cdk extensions", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist && rm -rf cdk.out", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}' 'test/**/*.{ts,tsx}' --ignore-pattern \"*.d.ts\" ", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "aws-cdk-lib": "2.93.0" - }, - "devDependencies": { - "@aws-cdk/assert": "2.68.0", - "@types/jest": "29.4.0", - "@types/node": "18.14.0", - "aws-cdk-lib": "2.93.0", - "constructs": "10.0.12", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-import-resolver-node": "0.3.7", - "eslint-import-resolver-typescript": "3.5.3", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-license-header": "0.6.0", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.3", - "jest-sonar-reporter": "2.0.0", - "prettier": "2.8.4", - "ts-jest": "29.0.5", - "typescript": "4.5.2" - }, - "jestSonar": { - "reportPath": "coverage", - "reportFile": "test-report.xml", - "indent": 4 - } -} diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository-snapshot.test.ts.snap b/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository-snapshot.test.ts.snap deleted file mode 100644 index 1b8c149..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository-snapshot.test.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Initialized CodeCommit Repository Snapshot Test 1`] = ` -{ - "Resources": { - "SnapshotTest4B1F0CC8": { - "Properties": { - "Code": { - "BranchName": "main", - "S3": { - "Bucket": "Testbucket", - "Key": "testkey", - }, - }, - "RepositoryName": "AWS-accelerator", - }, - "Type": "AWS::CodeCommit::Repository", - }, - }, -} -`; diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository.test.ts.snap b/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository.test.ts.snap deleted file mode 100644 index 1b8c149..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/repository.test.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Initialized CodeCommit Repository Snapshot Test 1`] = ` -{ - "Resources": { - "SnapshotTest4B1F0CC8": { - "Properties": { - "Code": { - "BranchName": "main", - "S3": { - "Bucket": "Testbucket", - "Key": "testkey", - }, - }, - "RepositoryName": "AWS-accelerator", - }, - "Type": "AWS::CodeCommit::Repository", - }, - }, -} -`; diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/trail-snapshot.test.ts.snap b/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/trail-snapshot.test.ts.snap deleted file mode 100644 index 36f524f..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/__snapshots__/trail-snapshot.test.ts.snap +++ /dev/null @@ -1,128 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CloudTrailExtension Snapshot Test 1`] = ` -{ - "Resources": { - "SnapshotTest4B1F0CC8": { - "DependsOn": [ - "SnapshotTestS3Policy98BCFC00", - ], - "Properties": { - "EnableLogFileValidation": true, - "EventSelectors": [], - "IncludeGlobalServiceEvents": true, - "InsightSelectors": [ - { - "InsightType": "ApiCallRateInsight", - }, - { - "InsightType": "ApiErrorRateInsight", - }, - ], - "IsLogging": true, - "IsMultiRegionTrail": true, - "IsOrganizationTrail": true, - "S3BucketName": { - "Ref": "SnapshotTestS30107570F", - }, - }, - "Type": "AWS::CloudTrail::Trail", - }, - "SnapshotTestS30107570F": { - "DeletionPolicy": "Retain", - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "SnapshotTestS3Policy98BCFC00": { - "Properties": { - "Bucket": { - "Ref": "SnapshotTestS30107570F", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "SnapshotTestS30107570F", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SnapshotTestS30107570F", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - { - "Action": "s3:GetBucketAcl", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com", - }, - "Resource": { - "Fn::GetAtt": [ - "SnapshotTestS30107570F", - "Arn", - ], - }, - }, - { - "Action": "s3:PutObject", - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control", - }, - }, - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com", - }, - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "SnapshotTestS30107570F", - "Arn", - ], - }, - "/AWSLogs/", - { - "Ref": "AWS::AccountId", - }, - "/*", - ], - ], - }, - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, - }, -} -`; diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-fine-grained.test.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-fine-grained.test.ts deleted file mode 100644 index ffb13b1..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-fine-grained.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { expect as expectCDK, haveResourceLike } from '@aws-cdk/assert'; -import { test, describe } from '@jest/globals'; -import * as TestConfig from './test-config'; -import * as CdkExtensions from '../index'; - -describe('Initialized CodeCommit Repository', () => { - test('Initialization Properties Test', () => { - new CdkExtensions.Repository(TestConfig.stack, 'SnapshotTest', TestConfig.repositoryProps); - - expectCDK(TestConfig.stack).to( - haveResourceLike('AWS::CodeCommit::Repository', { - RepositoryName: TestConfig.repositoryProps.repositoryName, - Code: { - BranchName: TestConfig.repositoryProps.repositoryBranchName, - S3: { - Bucket: TestConfig.repositoryProps.s3BucketName, - Key: TestConfig.repositoryProps.s3key, - }, - }, - }), - ); - }); -}); diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-snapshot.test.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-snapshot.test.ts deleted file mode 100644 index e64f33f..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository-snapshot.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SynthUtils } from '@aws-cdk/assert'; -import { test, describe, expect } from '@jest/globals'; -import * as CdkExtensions from '../index'; -import * as TestConfig from './test-config'; - -describe('Initialized CodeCommit Repository', () => { - /** - * Snapshot Test - Initialzed Repository - */ - test('Snapshot Test', () => { - new CdkExtensions.Repository(TestConfig.stack, 'SnapshotTest', TestConfig.repositoryProps); - expect(SynthUtils.toCloudFormation(TestConfig.stack)).toMatchSnapshot(); - }); -}); diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository.test.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository.test.ts deleted file mode 100644 index a5f7375..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/repository.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { expect as expectCDK, haveResource, SynthUtils } from '@aws-cdk/assert'; -import * as cdk from 'aws-cdk-lib'; -import * as CdkExtensions from '../index'; -import { test, describe, expect } from '@jest/globals'; - -describe('Initialized CodeCommit Repository', () => { - const props: CdkExtensions.RepositoryProps = { - repositoryName: 'AWS-accelerator', - repositoryBranchName: 'main', - s3BucketName: 'Testbucket', - s3key: 'testkey', - }; - - test('Initialization Properties Test', () => { - const stack = new cdk.Stack(); - - new CdkExtensions.Repository(stack, 'SnapshotTest', props); - - expectCDK(stack).to( - haveResource('AWS::CodeCommit::Repository', { - RepositoryName: props.repositoryName, - Code: { - BranchName: props.repositoryBranchName, - S3: { - Bucket: props.s3BucketName, - Key: props.s3key, - }, - }, - }), - ); - }); - - /** - * Snapshot Test - Initialzed Repository - */ - test('Snapshot Test', () => { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'TestStack'); - - new CdkExtensions.Repository(stack, 'SnapshotTest', { - repositoryBranchName: props.repositoryBranchName, - repositoryName: props.repositoryName, - s3BucketName: props.s3BucketName, - s3key: props.s3key, - }); - - expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); - }); -}); diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/test-config.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/test/test-config.ts deleted file mode 100644 index cb49546..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/test-config.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Stack } from 'aws-cdk-lib'; -import * as CdkExtensions from '../index'; - -/** - * Stack Initialization - */ -export const stack = new Stack(); -/** - * Accelerator Pipeline Secure Bucket Properties - */ -export const repositoryProps: CdkExtensions.RepositoryProps = { - repositoryName: 'AWS-accelerator', - repositoryBranchName: 'main', - s3BucketName: 'Testbucket', - s3key: 'testkey', -}; - -export const trailProps: CdkExtensions.TrailProps = { - isOrganizationTrail: true, - apiCallRateInsight: true, - apiErrorRateInsight: true, -}; diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/test/trail-snapshot.test.ts b/source/packages/@aws-cdk-extensions/cdk-extensions/test/trail-snapshot.test.ts deleted file mode 100644 index d5a6aef..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/test/trail-snapshot.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { SynthUtils } from '@aws-cdk/assert'; -import { test, describe, expect } from '@jest/globals'; -import * as CdkExtensions from '../index'; -import * as TestConfig from './test-config'; - -describe('CloudTrailExtension', () => { - /** - * Snapshot Test - CloudTrail - */ - test('Snapshot Test', () => { - new CdkExtensions.Trail(TestConfig.stack, 'SnapshotTest', TestConfig.trailProps); - expect(SynthUtils.toCloudFormation(TestConfig.stack)).toMatchSnapshot(); - }); -}); diff --git a/source/packages/@aws-cdk-extensions/cdk-extensions/tsconfig.json b/source/packages/@aws-cdk-extensions/cdk-extensions/tsconfig.json deleted file mode 100644 index fc62a1e..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-extensions/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/.gitignore b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/.gitignore deleted file mode 100644 index 53c37a1..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist \ No newline at end of file diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/index.ts b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/index.ts deleted file mode 100644 index e20b99c..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -export * from './lib/assume-role-plugin'; -export * from './lib/assume-role-provider-source'; diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-plugin.ts b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-plugin.ts deleted file mode 100644 index e09e273..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-plugin.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { Plugin, PluginHost } from 'aws-cdk/lib/api/plugin'; -import * as AWS from 'aws-sdk'; - -import { AssumeRoleProviderSource } from './assume-role-provider-source'; - -export class AssumeProfilePlugin implements Plugin { - readonly version = '1'; - - constructor( - private readonly props: { - region?: string; - assumeRoleName?: string; - assumeRoleDuration?: number; - credentials?: AWS.STS.Credentials; - partition?: string; - caBundlePath?: string; - } = {}, - ) {} - - init(host: PluginHost): void { - const source = new AssumeRoleProviderSource({ - name: 'cdk-assume-role-plugin', - assumeRoleName: this.props.assumeRoleName ?? AssumeProfilePlugin.getDefaultAssumeRoleName(), - assumeRoleDuration: this.props.assumeRoleDuration ?? AssumeProfilePlugin.getDefaultAssumeRoleDuration(), - region: this.props.region!, - credentials: this.props.credentials, - partition: this.props.partition, - caBundlePath: this.props.caBundlePath, - }); - host.registerCredentialProviderSource(source); - } - - static getDefaultAssumeRoleName(): string { - return process.env['CDK_PLUGIN_ASSUME_ROLE_NAME']!; - } - - static getDefaultAssumeRoleDuration(): number { - if (process.env['CDK_PLUGIN_ASSUME_ROLE_DURATION']) { - return +process.env['CDK_PLUGIN_ASSUME_ROLE_DURATION']; - } - return 3600; - } -} diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-provider-source.ts b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-provider-source.ts deleted file mode 100644 index 381a32f..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/assume-role-provider-source.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { CredentialProviderSource } from 'aws-cdk/lib/api/plugin'; -import * as AWS from 'aws-sdk'; -import { green } from 'colors/safe'; - -import { throttlingBackOff } from './backoff'; -import fs from 'fs'; -import https from 'https'; - -export interface AssumeRoleProviderSourceProps { - name: string; - assumeRoleName: string; - assumeRoleDuration: number; - region: string; - credentials?: AWS.STS.Credentials; - partition?: string; - caBundlePath?: string; -} - -export class AssumeRoleProviderSource implements CredentialProviderSource { - readonly name = this.props.name; - private readonly cache: { [accountId: string]: AWS.Credentials } = {}; - private readonly cacheExpiration: { [accountId: string]: Date } = {}; - - constructor(private readonly props: AssumeRoleProviderSourceProps) {} - - async isAvailable(): Promise { - return true; - } - - async canProvideCredentials(): Promise { - return true; - } - - async getProvider(accountId: string): Promise { - if (this.cache[accountId] && new Date() < this.cacheExpiration[accountId]) { - return this.cache[accountId]; - } - - let assumeRole; - try { - // Try to assume the role with the given duration - assumeRole = await this.assumeRole(accountId, this.props.assumeRoleDuration); - } catch (e) { - console.warn(`Cannot assume role for ${this.props.assumeRoleDuration} seconds: ${e}`); - - // If that fails, than try to assume the role for one hour - assumeRole = await this.assumeRole(accountId, 3600); - } - - const credentials = assumeRole.Credentials!; - this.cache[accountId] = new AWS.Credentials({ - accessKeyId: credentials.AccessKeyId, - secretAccessKey: credentials.SecretAccessKey, - sessionToken: credentials.SessionToken, - }); - this.cacheExpiration[accountId] = new Date(+new Date() + 60000 * 30); - return this.cache[accountId]; - } - - protected async assumeRole(accountId: string, duration: number): Promise { - const roleArn = `arn:${this.props.partition ?? 'aws'}:iam::${accountId}:role/${this.props.assumeRoleName}`; - console.log(`Assuming role ${green(roleArn)} for ${duration} seconds`); - let httpOptions: AWS.HTTPOptions | undefined = undefined; - if (this.props.caBundlePath) { - const certs = [fs.readFileSync(this.props.caBundlePath)]; - httpOptions = { - agent: new https.Agent({ - rejectUnauthorized: true, - ca: certs, - }), - }; - } - let sts: AWS.STS; - if (this.props.credentials) { - sts = new AWS.STS({ - region: this.props.region, - accessKeyId: this.props.credentials.AccessKeyId, - secretAccessKey: this.props.credentials.SecretAccessKey, - sessionToken: this.props.credentials.SessionToken, - httpOptions, - }); - } else { - sts = new AWS.STS({ region: this.props.region, httpOptions }); - } - - return throttlingBackOff(() => - sts - .assumeRole({ - RoleArn: roleArn, - RoleSessionName: this.name, - DurationSeconds: duration, - }) - .promise(), - ); - } -} diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/backoff.ts b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/backoff.ts deleted file mode 100644 index dcf1c6a..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/lib/backoff.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import { backOff, IBackOffOptions } from 'exponential-backoff'; - -/** - * Auxiliary function to retry AWS SDK calls when a throttling error occurs. - */ -export function throttlingBackOff( - request: () => Promise, - options?: Partial>, -): Promise { - return backOff(request, { - startingDelay: 500, - jitter: 'full', - retry: isThrottlingError, - ...options, - }); -} - -export const isThrottlingError = ( - e: any, // eslint-disable-line -): boolean => - e.retryable === true || - // SDKv2 Error Structure - e.code === 'TooManyRequestsException' || - e.code === 'Throttling' || - e.code === 'ThrottlingException' || - e.code === 'InternalException' || - // SDKv3 Error Structure - e.name === 'TooManyRequestsException' || - e.name === 'Throttling' || - e.name === 'ThrottlingException' || - e.name === 'InternalException'; diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/package.json b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/package.json deleted file mode 100644 index 2aa8be7..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@aws-cdk-extensions/cdk-plugin-assume-role", - "version": "0.0.0", - "private": true, - "description": "Assume role plugin", - "license": "Apache-2.0", - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com/solutions" - }, - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "cleanup": "tsc --build ./ --clean && rm -rf node_modules && rm -rf dist", - "cleanup:tsc": "tsc --build ./ --clean", - "lint": "eslint --fix --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "precommit": "eslint --max-warnings 0 -c ../../../.eslintrc.json 'lib/**/*.{ts,tsx}'", - "test": "jest --coverage --ci --passWithNoTests", - "watch": "tsc -w" - }, - "dependencies": { - "@typescript-eslint/eslint-plugin": "5.53.0", - "@typescript-eslint/parser": "5.53.0", - "aws-cdk": "2.93.0", - "aws-cdk-lib": "2.93.0", - "aws-sdk": "2.1379.0", - "colors": "1.4.0", - "exponential-backoff": "3.1.1" - }, - "devDependencies": { - "@types/node": "18.14.0", - "eslint": "8.34.0", - "eslint-config-prettier": "8.6.0", - "eslint-config-standard": "17.0.0", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "4.2.1", - "prettier": "2.8.4", - "typescript": "4.9.5" - }, - "nohoist": [ - "**/exponential-backoff", - "**/exponential-backoff/**" - ] -} diff --git a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/tsconfig.json b/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/tsconfig.json deleted file mode 100644 index fc62a1e..0000000 --- a/source/packages/@aws-cdk-extensions/cdk-plugin-assume-role/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["lib/**/*", "index.ts"], - "exclude": ["test/**/*"] -} diff --git a/source/run-all-tests.sh b/source/run-all-tests.sh deleted file mode 100755 index 2e9dc5a..0000000 --- a/source/run-all-tests.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/bash -# -# This script runs all tests for the root CDK project, as well as any microservices, Lambda functions, or dependency -# source code packages. These include unit tests, integration tests, and snapshot tests. -# -# This script is called by the ../initialize-repo.sh file and the buildspec.yml file. It is important that this script -# be tested and validated to ensure that all available test fixtures are run. -# -# The if/then blocks are for error handling. They will cause the script to stop executing if an error is thrown from the -# node process running the test case(s). Removing them or not using them for additional calls with result in the -# script continuing to execute despite an error being thrown. - -[ "$DEBUG" == 'true' ] && set -x -set -e - -setup_python_env() { - if [ -d "./.venv-test" ]; then - echo "Reusing already setup python venv in ./.venv-test. Delete ./.venv-test if you want a fresh one created." - return - fi - - echo "Setting up python venv" - python3 -m venv .venv-test - echo "Initiating virtual environment" - source .venv-test/bin/activate - - echo "Installing python packages" - # install test dependencies in the python virtual environment - pip3 install -r requirements-test.txt - pip3 install -r requirements.txt --target . - - echo "deactivate virtual environment" - deactivate -} - -run_python_test() { - local component_path=$1 - local component_name=$2 - - echo "------------------------------------------------------------------------------" - echo "[Test] Run python unit test with coverage for $component_path $component_name" - echo "------------------------------------------------------------------------------" - cd $component_path - - if [ "${CLEAN:-true}" = "true" ]; then - rm -fr .venv-test - fi - - setup_python_env - - echo "Initiating virtual environment" - source .venv-test/bin/activate - - # setup coverage report path - mkdir -p $source_dir/test/coverage-reports - coverage_report_path=$source_dir/test/coverage-reports/$component_name.coverage.xml - echo "coverage report path set to $coverage_report_path" - - # Use -vv for debugging - python3 -m pytest --cov --cov-report=term-missing --cov-report "xml:$coverage_report_path" - - # The pytest --cov with its parameters and .coveragerc generates a xml cov-report with `coverage/sources` list - # with absolute path for the source directories. To avoid dependencies of tools (such as SonarQube) on different - # absolute paths for source directories, this substitution is used to convert each absolute source directory - # path to the corresponding project relative path. The $source_dir holds the absolute path for source directory. - sed -i -e "s,$source_dir,source,g" $coverage_report_path - - echo "deactivate virtual environment" - deactivate - - if [ "${CLEAN:-true}" = "true" ]; then - rm -fr .venv-test - rm .coverage - rm -fr .pytest_cache - rm -fr __pycache__ test/__pycache__ - fi -} - -prepare_jest_coverage_report() { - local component_name=$1 - - if [ ! -d "coverage" ]; then - echo "ValidationError: Missing required directory coverage after running unit tests" - exit 129 - fi - - # prepare coverage reports - rm -fr coverage/lcov-report - mkdir -p $coverage_reports_top_path/jest - coverage_report_path=$coverage_reports_top_path/jest/$component_name - rm -fr $coverage_report_path - mv coverage $coverage_report_path -} - -run_javascript_test() { - local component_path=$1 - local component_name=$2 - - echo "------------------------------------------------------------------------------" - echo "[Test] Run javascript unit test with coverage for $component_path $component_name" - echo "------------------------------------------------------------------------------" - echo "cd $component_path" - cd $component_path - - # install and build for unit testing - npm install - - # run unit tests - npm run test - - # prepare coverage reports - prepare_jest_coverage_report $component_name -} - -run_cdk_project_test() { - local component_path=$1 - local component_name=solutions-constructs - - echo "------------------------------------------------------------------------------" - echo "[Test] $component_name" - echo "------------------------------------------------------------------------------" - cd $component_path - - # Clean up cache prior to install - yarn cache clean - - # install and build for unit testing - yarn install - yarn build - - ## Option to suppress the Override Warning messages while synthesizing using CDK - # export overrideWarningsEnabled=false - - # run unit tests - yarn test - - # prepare coverage reports - # prepare_jest_coverage_report $component_name -} - -# Run unit tests -echo "Running unit tests" - -# Get reference for source folder -source_dir="$(cd $PWD/../source; pwd -P)" -coverage_reports_top_path=$source_dir/test/coverage-reports - -# Test the CDK project -run_cdk_project_test $source_dir - -# run_javascript_test $source_dir/lambda/example-function-js example-function-js - -# Return to the source/ level -cd $source_dir diff --git a/source/tsconfig.json b/source/tsconfig.json deleted file mode 100644 index baad467..0000000 --- a/source/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "alwaysStrict": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noPropertyAccessFromIndexSignature": true, - "noUnusedParameters": true, - "noUnusedLocals": true, - "resolveJsonModule": true, - "strict": true, - "module": "commonjs", - "declaration": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "preserveSymlinks": true, - "sourceMap": true, - "lib": ["ES2019", "ES2020.Promise"], - "target": "ES2019", - "composite": false - } -} diff --git a/source/yarn.lock b/source/yarn.lock deleted file mode 100644 index 2857c28..0000000 --- a/source/yarn.lock +++ /dev/null @@ -1,13491 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@aws-cdk/assert@2.68.0": - version "2.68.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/assert/-/assert-2.68.0.tgz#d83b44ba278d3e3a32c13f372c697f26c6061b64" - integrity sha512-bEztvoYdVp17I/ClYRGZa4wlEP/qNNq4Q+Z7EKwRL0cLDmvq4EI1m1N8LhUPAH7B6YXp5d1164gC6Nr0lV8bbA== - dependencies: - "@aws-cdk/cloudformation-diff" "2.68.0" - -"@aws-cdk/asset-awscli-v1@^2.2.200": - version "2.2.200" - resolved "https://registry.yarnpkg.com/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.200.tgz#6ead533f73f705ad7350eb46955e2538e50cd013" - integrity sha512-Kf5J8DfJK4wZFWT2Myca0lhwke7LwHcHBo+4TvWOGJrFVVKVuuiLCkzPPRBQQVDj0Vtn2NBokZAz8pfMpAqAKg== - -"@aws-cdk/asset-kubectl-v20@^2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz#d8e20b5f5dc20128ea2000dc479ca3c7ddc27248" - integrity sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg== - -"@aws-cdk/asset-node-proxy-agent-v6@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz#6dc9b7cdb22ff622a7176141197962360c33e9ac" - integrity sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg== - -"@aws-cdk/cfnspec@2.68.0": - version "2.68.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/cfnspec/-/cfnspec-2.68.0.tgz#e678c62d92ca76f8513a23c3c78f00ae49a7ab2e" - integrity sha512-g062ljKOvMaeEgp2GR2ewoF3BzeGYpu+AA7UvN/SN+2S0detSwU+qHlxSFeTe0DLyCFaMttNEh81VmYCfiHtpg== - dependencies: - fs-extra "^9.1.0" - md5 "^2.3.0" - -"@aws-cdk/cloud-assembly-schema@2.93.0": - version "2.93.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-2.93.0.tgz#65e59d7781043dc35b557b049dbde79a807651c2" - integrity sha512-73knSgz+gytsbo0ucl0OAZR5hA33RgI2vjIUEG+mFHwsKSJyaLHRdhBCvWCZhPDA5VeYSbscFezgfpX4UAuqfg== - dependencies: - jsonschema "^1.4.1" - semver "^7.5.4" - -"@aws-cdk/cloudformation-diff@2.68.0": - version "2.68.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/cloudformation-diff/-/cloudformation-diff-2.68.0.tgz#e5c69c9419472e3dd56bde418b3fabbca85d4005" - integrity sha512-JnX0sygxNHWU3aKdzSus25B1TuKYWDwnNL2tw3svZvfHcw3Nwz857JTOn/yNOJxT7cZbCbOqNPrOT6Xv+LrxTQ== - dependencies: - "@aws-cdk/cfnspec" "2.68.0" - chalk "^4" - diff "^5.1.0" - fast-deep-equal "^3.1.3" - string-width "^4.2.3" - table "^6.8.1" - -"@aws-cdk/cx-api@2.93.0": - version "2.93.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/cx-api/-/cx-api-2.93.0.tgz#1552efd873af502e0d349c97a50fff027848eccc" - integrity sha512-o0ZkMXeccRpzrlebIaoVU2BQCCavySmeOIUdjorNo6QjonZ3x1cTvQw8HA2MDNWK5J4P6sPCtgHBbWrI/Inrqg== - dependencies: - "@aws-cdk/cloud-assembly-schema" "2.93.0" - semver "^7.5.4" - -"@aws-cdk/integ-runner@2.93.0-alpha.0": - version "2.93.0-alpha.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/integ-runner/-/integ-runner-2.93.0-alpha.0.tgz#9c2070b0e4bda5976bbc5f30e11a583ca07e697a" - integrity sha512-myPYSLlNnOnSVn4eiUHr6uRpBQl5f0XIaMRHOlKE0PHL01EWrwrSxAAJOPmr2GEf6EtfSUpHY4hvuFFCCN/EZA== - dependencies: - aws-cdk "2.93.0" - optionalDependencies: - fsevents "2.3.2" - -"@aws-cdk/integ-tests-alpha@2.93.0-alpha.0": - version "2.93.0-alpha.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/integ-tests-alpha/-/integ-tests-alpha-2.93.0-alpha.0.tgz#9c27365a5ca62e66800b210645e4170c1817676b" - integrity sha512-EQFmCKhLHeXDne/3s3TQSOF86U+YAzSSDr13T+8sX8CY951vu6hERrz4c7FKMtMLkB6mZy8DJcDFalq4n07bKA== - -"@aws-cdk/region-info@2.93.0": - version "2.93.0" - resolved "https://registry.yarnpkg.com/@aws-cdk/region-info/-/region-info-2.93.0.tgz#6940b56ba6badb25decac067b418bca0718112b4" - integrity sha512-RYgRqDIBvX7MIchvwHuZ01s7p2wYi9ynK1ZQ8PuRLOyB2dZ36ITJM94D9QX5JEwRo38ulVVw6kQA2HK83mUOPg== - -"@aws-crypto/crc32@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa" - integrity sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA== - dependencies: - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - tslib "^1.11.1" - -"@aws-crypto/crc32c@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz#016c92da559ef638a84a245eecb75c3e97cb664f" - integrity sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w== - dependencies: - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - tslib "^1.11.1" - -"@aws-crypto/ie11-detection@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz#640ae66b4ec3395cee6a8e94ebcd9f80c24cd688" - integrity sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/sha1-browser@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz#f9083c00782b24714f528b1a1fef2174002266a3" - integrity sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw== - dependencies: - "@aws-crypto/ie11-detection" "^3.0.0" - "@aws-crypto/supports-web-crypto" "^3.0.0" - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-browser@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz#05f160138ab893f1c6ba5be57cfd108f05827766" - integrity sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ== - dependencies: - "@aws-crypto/ie11-detection" "^3.0.0" - "@aws-crypto/sha256-js" "^3.0.0" - "@aws-crypto/supports-web-crypto" "^3.0.0" - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-js@3.0.0", "@aws-crypto/sha256-js@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz#f06b84d550d25521e60d2a0e2a90139341e007c2" - integrity sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ== - dependencies: - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - tslib "^1.11.1" - -"@aws-crypto/supports-web-crypto@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz#5d1bf825afa8072af2717c3e455f35cda0103ec2" - integrity sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/util@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-3.0.0.tgz#1c7ca90c29293f0883468ad48117937f0fe5bfb0" - integrity sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w== - dependencies: - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-sdk/client-acm-pca@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-acm-pca/-/client-acm-pca-3.410.0.tgz#9176dbbff946165d56b6d801dd7479c4939ad610" - integrity sha512-16GpTridjrZHkZbrHlVhnBpM1ViSuREFol6QdXZQLkokAs4TY6m5/8uhiBOCP7+c4QKPYHk6iMZnGE0Aj1NvQw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/client-api-gateway@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-api-gateway/-/client-api-gateway-3.410.0.tgz#a224568646b76dd1669528e307e0fc3ca1e61025" - integrity sha512-eoZH2DM997PdtrVrER2E+VJ8Hq4aPa2wUmoDlQDF2CVlvkSEKRMf11KrQcovpApwu7DMVD0EIbwHSxEl2Rn/fA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-api-gateway" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-stream" "^2.0.9" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-backup@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-backup/-/client-backup-3.410.0.tgz#45a22f5e286f2bf6a35c36ca80f0e9b99657180d" - integrity sha512-ig8ISuoiGTAHc3X//u30Q2dpXGoA1iDEscKZeb5iaQY3/Jpp3qU92WEJKgWYrepGkAJ/Ym9+IYA7DYPkXcI0EQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-cloudformation@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cloudformation/-/client-cloudformation-3.410.0.tgz#43b3db40a0ab10859036a16f8a52408f87ad3a4e" - integrity sha512-iyk+jB2kTLKaJVVYzhimg8THl537tEwpW118snFCEGn8j4+LEHccvdVAsV7J6mojXHQYjVH9bY76OATPm/JU5g== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-cloudwatch-logs@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.410.0.tgz#21360bd2b2ee1db7adcff5b78a8e52fdf48d58e2" - integrity sha512-lUYxWi+WRuBS9rsX4wfXQEiuzzAhCsisElrPNSHaVzmS7HzoPYpanjq1tfSH2slqhD2fbiA09Da0pvCdTGKFIw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-codeartifact@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-codeartifact/-/client-codeartifact-3.410.0.tgz#1875d42ea1d4aa5ebe814d539c4663d43a2d93c3" - integrity sha512-csjrxLo3QmRTkVwcigSNudaPPnJ+H+jTh7Ne8kPD1/Vdt1a0uLx8ZL2undGZ0lAcvkHpp6aqcYgoXg43Q0nTzg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-stream" "^2.0.9" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-codebuild@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-codebuild/-/client-codebuild-3.410.0.tgz#dfd7e51d2358a466134f4c7b2120e5f864937473" - integrity sha512-jCrYrvgk9/sBLxVq4f8SqkCnzTofkLHgaFOT7fcJX494/TSIpB1jahRF8TLNqBHD+KvRZ2lNSe1pCUg4r372sQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-codecommit@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-codecommit/-/client-codecommit-3.410.0.tgz#3fdf301ec14e92954b913756194cccd1bc5719f7" - integrity sha512-aO9QyARmqOvczUd8qE0aW/DymXjURVTKJzqGMRIR/ho9+UswAeESwJjVVpwuFKg9SLWUKw1SvPQBhkD+LBygmg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-codepipeline@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-codepipeline/-/client-codepipeline-3.410.0.tgz#396daf75e8eef3dd353b8543b03f84994023b5be" - integrity sha512-i/vnAC7ObZM4lQ7X3alBaeEHXRvbM1VR7u52xUWV6Q12WD6bnk20Nt2HobExuQkIfuX6hM7pTUmlSmYwtEtltg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-config-service@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-config-service/-/client-config-service-3.410.0.tgz#8f6464f7bb89e6c4248386471814bab53663b77c" - integrity sha512-+hXW60W7vsNHryBmZUpGIye3cGf2PIlSWvEYlPiRGzFuPcQ0t5AwSgPGyjZmGdsVbW3U1id1X0utMeqGsqWHiA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-config-service@^3.410.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-config-service/-/client-config-service-3.451.0.tgz#e58760300c76826c9dabf8f1e28614c715e9c9cb" - integrity sha512-Wyi+S0ovf8iZmIXA2+tvp9JO3N3aqyIO/0Eik2tTZd4Fuqo8FeWD+CIN0de3bdp2aXVTUiMWjWAtfv9XW6nSig== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.451.0" - "@aws-sdk/core" "3.451.0" - "@aws-sdk/credential-provider-node" "3.451.0" - "@aws-sdk/middleware-host-header" "3.451.0" - "@aws-sdk/middleware-logger" "3.451.0" - "@aws-sdk/middleware-recursion-detection" "3.451.0" - "@aws-sdk/middleware-signing" "3.451.0" - "@aws-sdk/middleware-user-agent" "3.451.0" - "@aws-sdk/region-config-resolver" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@aws-sdk/util-endpoints" "3.451.0" - "@aws-sdk/util-user-agent-browser" "3.451.0" - "@aws-sdk/util-user-agent-node" "3.451.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/protocol-http" "^3.0.9" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" - "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" - "@smithy/util-utf8" "^2.0.2" - tslib "^2.5.0" - -"@aws-sdk/client-controltower@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-controltower/-/client-controltower-3.556.0.tgz#f0f548a40ff44b8c27934b2621685458a9db39b1" - integrity sha512-cGzCwFqJYmruvT/gU32AOGK4xxpVEvZ8QXCKVDmTq/QerqW6uG5aRYbs5OX4anj+gHlz4M9eney73zFPGX+XQQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/core" "3.556.0" - "@aws-sdk/credential-provider-node" "3.556.0" - "@aws-sdk/middleware-host-header" "3.535.0" - "@aws-sdk/middleware-logger" "3.535.0" - "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" - "@aws-sdk/region-config-resolver" "3.535.0" - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" - "@aws-sdk/util-user-agent-browser" "3.535.0" - "@aws-sdk/util-user-agent-node" "3.535.0" - "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/hash-node" "^2.2.0" - "@smithy/invalid-dependency" "^2.2.0" - "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" - "@smithy/middleware-serde" "^2.3.0" - "@smithy/middleware-stack" "^2.2.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/url-parser" "^2.2.0" - "@smithy/util-base64" "^2.3.0" - "@smithy/util-body-length-browser" "^2.2.0" - "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" - "@smithy/util-endpoints" "^1.2.0" - "@smithy/util-middleware" "^2.2.0" - "@smithy/util-retry" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@aws-sdk/client-detective@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-detective/-/client-detective-3.410.0.tgz#a48069a024f32b913b6c08223675122d05f462da" - integrity sha512-yi7hpZmmUuQqzDb5Vt7eU2+L+pO9brk/DTquKj9UAH9rTHFHxG9AdmiAWCh69NToG4HszTTXSE4nifHXil5Icg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-dynamodb@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-dynamodb/-/client-dynamodb-3.410.0.tgz#2ede24144119f3b9424ee5659cfd9ee1cb57b8a2" - integrity sha512-Uq5+ePGx+SnUMuCKMqhZ8q6hSHiWoBNpJXYr9fpL9zcZQ63chCBIOqOYw71OqKH8D89aRa8Y6G6dlt7/tW8CXQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-endpoint-discovery" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-ec2@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-ec2/-/client-ec2-3.410.0.tgz#03ba20d3b6d5b54abcffe6e4f67e6856a7bb5cef" - integrity sha512-wdOo/tv5txQ/DGLi4TflREZbGU+7oWM8PO1yUWHVhedo5aSdD/nWBMVZYeFCLhMHw8krAYFnNzc0YheQLlYEbw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-ec2" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-ec2@3.411.0": - version "3.411.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-ec2/-/client-ec2-3.411.0.tgz#966ad5aea14bcbe2b493cb0123957eb485d1fc3b" - integrity sha512-m7Elz+isEEkV/7f5iQILzObNhajCrQMSmzmAdgWBbBA8qEpUSHgOFi9M3FO6UuzIDcnqWAWp+Tav9kMAVOGkHw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-ec2" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-ecr@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-ecr/-/client-ecr-3.410.0.tgz#3297ce20d4382a78b346a6e15b1ac6d53005e418" - integrity sha512-JgLE2nvHxtydG6ieEZOgDek/LKZn0SEIU9ZgbMrFVb504gzzIriCB3wWY/4z1p7NEMuQ6YlyP+hAGGF61AE4UA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/client-efs@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-efs/-/client-efs-3.410.0.tgz#c76f5e11fe0628e0c2e549b8c94b87d5fc2c3742" - integrity sha512-tszdLbY6KZHTQ0XOcqwQWdgGCdVjpWHdLpWcYfUCnitKlI2uOh5wcyGA/vOm0eWP0bRnFp+3xIUc8+E8uEr3kg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-eventbridge@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-eventbridge/-/client-eventbridge-3.410.0.tgz#65eeaa7b538f1df24b671439688d10f5cc82de4d" - integrity sha512-fTEgOvFiiLEJXhcaFQxgjaG37szEDaewFBXxkklcwWZrI308mutYf9hYrbKBDo3SuO/pVzzJPETbWyD0mjMYSg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/signature-v4-multi-region" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-guardduty@3.412.0": - version "3.412.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-guardduty/-/client-guardduty-3.412.0.tgz#3ca6cd682ec0987721a1e3bb276a220989b97e4a" - integrity sha512-URvikip9nm6ViKkmIcQ1Y9+eCDygqC4Zitv4k6Q7jRA3bP6iorVeI6sT2D5eRqBZLTrsRMpEMftolGLtQRsK9Q== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-iam@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-iam/-/client-iam-3.410.0.tgz#62b8966b0b2098c47f9a6c7a97e01ed6ee6c0893" - integrity sha512-sifIwjiUIOlq+hjbv6ZNXCC7eclOIeUiyqQFFLQlFDacCrssmCvXBsNoamhf9oxWUx42Y62DxAFuLvV2WIm09g== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-identitystore@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-identitystore/-/client-identitystore-3.410.0.tgz#458a3223c98ae3d3ae9c87e1effba03b9cc402b8" - integrity sha512-buLMcAmtQyL2SowHyX3Re+rLrR9SQ7qNwJpnaAvzjezAvzhCw4j3khnRljap3bqJQfZdgHjzDQYuPI21pz+PaQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-kinesis@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-kinesis/-/client-kinesis-3.410.0.tgz#bcac766f66de12b185c2cd290f75a30fbb238c96" - integrity sha512-RhTxeiax89ti/7oNFe2lUhX51mWExCHxRcMJWkqW4sz0prf5oCSWuVylE71Z+lGSP3tTjdk83cjLSFPS28/nVw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/eventstream-serde-browser" "^2.0.6" - "@smithy/eventstream-serde-config-resolver" "^2.0.6" - "@smithy/eventstream-serde-node" "^2.0.6" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/client-kms@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-kms/-/client-kms-3.410.0.tgz#20f8afee9fc149c97d08a0ee8c49fea8eb33131a" - integrity sha512-HiNJStZd9EFXPt70Z/3011RmLlE+FRvRVNDCoBRuLSgvxt51fLcUY0iDpteg46mvKZT+ABj09Cxxxf9Zk3gJrA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-lambda@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-lambda/-/client-lambda-3.410.0.tgz#965ad98c3be91688f3ecc6d14bfa4f374073cf30" - integrity sha512-zbcdA++4zE1ODRCmKkbZT4N0f4I5pVXLEP5uBtE0Bxgs1UyiHlqHFfjQprdW9CeaMmtTBHEBB6dDcKlGGOSU/w== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/eventstream-serde-browser" "^2.0.6" - "@smithy/eventstream-serde-config-resolver" "^2.0.6" - "@smithy/eventstream-serde-node" "^2.0.6" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-stream" "^2.0.9" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/client-lex-models-v2@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-lex-models-v2/-/client-lex-models-v2-3.410.0.tgz#0f96f244f3a17a969b9e6cead1cce23039dbb4da" - integrity sha512-7EVfX6OgTLoLopUsY5I5L5GWeW7HvnMVPW6W50NUBLMQnNWqOqBKW/oR2ZlfQSfZMMmRbKqVArP28TZQyAf6rQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/client-network-firewall@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-network-firewall/-/client-network-firewall-3.410.0.tgz#648ad1425d11eb4a70adf97780383e4ac9d94a59" - integrity sha512-v/g449YVuDRzmzblvUTphFt31zxUGlfvf5BAcWGSDkcPa+60Dwo2GXCvCR3upFdYeW3mzhOO/HGmnfrtgijEWQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-opensearch@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-opensearch/-/client-opensearch-3.410.0.tgz#dadce0b847a9732e4e721ba1412b8606600577f1" - integrity sha512-h1wdv82KOHYgL7R3bfyqcx+SvYFelz/hns3NCM26FzVLxW/briEQqt86CMAvQO5CrpURE2zXk6Cjl7eUVWlACg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-organizations@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-organizations/-/client-organizations-3.410.0.tgz#36473eb3cb12b1c767df806fcad1236ca5b2dd03" - integrity sha512-cjsCUx6TkKimBZ0+wNirl0MmYKsacwKY8aMqBQbbmoem7qSTjpainr9Y3rnorecdxLQuImm5r6CqPdlIROCg4A== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-route-53@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-route-53/-/client-route-53-3.410.0.tgz#25ff69c9635638ee6206feb7d7c80cff977a2165" - integrity sha512-IFuPnFckGmmMDNSy1aEPBF7zF4xxEmgFHFzYvknl6Ljibj1cjk+K6YwXc6RPodUhkOst8splBS2Rjrq6ct0ckw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-route53" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@aws-sdk/xml-builder" "3.310.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-s3@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.410.0.tgz#f1423919449b814460855eb304163027b93d1c72" - integrity sha512-9pInvFl3xgk+CnbHFZVk0wAicZUiokIGQ05e/ZDBHjiWK5ph/XeQ4CCTuh7JxT0yABNhua8/6txsyq/uNXOzoA== - dependencies: - "@aws-crypto/sha1-browser" "3.0.0" - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-bucket-endpoint" "3.410.0" - "@aws-sdk/middleware-expect-continue" "3.410.0" - "@aws-sdk/middleware-flexible-checksums" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-location-constraint" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-s3" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-ssec" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/signature-v4-multi-region" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@aws-sdk/xml-builder" "3.310.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/eventstream-serde-browser" "^2.0.6" - "@smithy/eventstream-serde-config-resolver" "^2.0.6" - "@smithy/eventstream-serde-node" "^2.0.6" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-blob-browser" "^2.0.6" - "@smithy/hash-node" "^2.0.6" - "@smithy/hash-stream-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/md5-js" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-stream" "^2.0.9" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-s3@3.412.0": - version "3.412.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.412.0.tgz#f03c577456951e0e476b2d8ab079b01b2b6a8c93" - integrity sha512-sNrlx9sSBmFUCqMgTznwk9Fee3PJat0nZ3RIDR5Crhsld/eexxrqb6TYKsxzFfBfXTL/oPh+/S5driRV2xsB8A== - dependencies: - "@aws-crypto/sha1-browser" "3.0.0" - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-bucket-endpoint" "3.410.0" - "@aws-sdk/middleware-expect-continue" "3.410.0" - "@aws-sdk/middleware-flexible-checksums" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-location-constraint" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-s3" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-ssec" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/signature-v4-multi-region" "3.412.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@aws-sdk/xml-builder" "3.310.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/eventstream-serde-browser" "^2.0.6" - "@smithy/eventstream-serde-config-resolver" "^2.0.6" - "@smithy/eventstream-serde-node" "^2.0.6" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-blob-browser" "^2.0.6" - "@smithy/hash-node" "^2.0.6" - "@smithy/hash-stream-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/md5-js" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-stream" "^2.0.9" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-secrets-manager@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.410.0.tgz#0c8403c7add53b98bcb39653645e6cf248526664" - integrity sha512-+h+33cU8JpOFp1tfY/oNh6nMzCq0/hUBw/HW1l6nrKQa66+xiGcqaZ6mq/2l/1Fzime0DQMidPy/75ApQRBg2w== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-securityhub@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-securityhub/-/client-securityhub-3.410.0.tgz#40a56d14d85068e03769d43d8ca94191fa85b029" - integrity sha512-3r7xDQwwKfcEgv17SQ2rhnHJzmvHA6YfDCIym9qgS4GRFjJ46G8luk8CIWjpWXIALq+B0I2eTfW0TQ3PmCrfpw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-service-catalog@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-service-catalog/-/client-service-catalog-3.410.0.tgz#fbdcbed275a2ab8ebed06114fe0a30e9497ca49a" - integrity sha512-+dlQo/JyAYqOvanXmXXxG0Z9nBuH+rOtpQYfzBVOYYgUqAfbJAQtn+dDMGa1eOxyqSCrHR9qs5vfnpImgiQ30g== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-service-quotas@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-service-quotas/-/client-service-quotas-3.410.0.tgz#8f0ef3a4c3be1ad65db070ac6cf58fcb8e58bb9f" - integrity sha512-NPpOIJopEloT4ce78evcW/QbltOSkJkR5qSbxkBahTToPMuekW+nk3Hyhly8djlnrD27cS7AYhEsVATl0YVY+A== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-sns@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sns/-/client-sns-3.410.0.tgz#19b7f8ec2896b1859e0fa70ad1088339676792db" - integrity sha512-wiE7VnCof9fOUCgVQxBQCfBw+Ta3eTF72YM/UlqdG9QEdp5Dt81RdqzC089136LaSE5X6O1PiqCk8ssc2tVdcw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-sqs@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sqs/-/client-sqs-3.410.0.tgz#e89acf5dff8a8bfe11ec493dc08ffe7bc7896ccd" - integrity sha512-E2iovaeMiMPM3T6clsuKeKdBAF9vj28nCPj3Lplmxyo42pdRknEO6z4k2/O+UFaPjQ7Mx4tTAefTBOAtBH+y2g== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-sqs" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/md5-js" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-ssm@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-ssm/-/client-ssm-3.410.0.tgz#b3b2ad7de7a51f145627983a4708c5835940adae" - integrity sha512-SbnQxFHU/fm/kMN3/9hie1TKZoBUhwSRV8fMmIsTu0nztr5Kv5Xdi/SbBVA2uBc0VyBbED2nY2PMMh4IbHY37w== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - "@smithy/util-waiter" "^2.0.6" - tslib "^2.5.0" - uuid "^8.3.2" - -"@aws-sdk/client-sso-admin@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-admin/-/client-sso-admin-3.410.0.tgz#6e2f3fe62f0d7bca7b333435837a03dba831f5a0" - integrity sha512-VrT3OCigTZxNWxz9YC8fRrlFVftDlO06yGCennr+x6dm52a3q41xMD/50H1R6OTg+9ePF8PCpdTLkrwckNV6jA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.410.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-sso-oidc@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.556.0.tgz#4c19fccc35361de046d2cd74a7a685d71aa5dd1e" - integrity sha512-AXKd2TB6nNrksu+OfmHl8uI07PdgzOo4o8AxoRO8SHlwoMAGvcT9optDGVSYoVfgOKTymCoE7h8/UoUfPc11wQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/core" "3.556.0" - "@aws-sdk/middleware-host-header" "3.535.0" - "@aws-sdk/middleware-logger" "3.535.0" - "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" - "@aws-sdk/region-config-resolver" "3.535.0" - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" - "@aws-sdk/util-user-agent-browser" "3.535.0" - "@aws-sdk/util-user-agent-node" "3.535.0" - "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/hash-node" "^2.2.0" - "@smithy/invalid-dependency" "^2.2.0" - "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" - "@smithy/middleware-serde" "^2.3.0" - "@smithy/middleware-stack" "^2.2.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/url-parser" "^2.2.0" - "@smithy/util-base64" "^2.3.0" - "@smithy/util-body-length-browser" "^2.2.0" - "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" - "@smithy/util-endpoints" "^1.2.0" - "@smithy/util-middleware" "^2.2.0" - "@smithy/util-retry" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@aws-sdk/client-sso@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.410.0.tgz#e9f08710688dc3e3a739c102571ee7e73bb1474b" - integrity sha512-MC9GrgwtlOuSL2WS3DRM3dQ/5y+49KSMMJRH6JiEcU5vE0dX/OtEcX+VfEwpi73x5pSfIjm7xnzjzOFx+sQBIg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-sso@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz#d52b961efa707b6579821942801145a2e1be8121" - integrity sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.451.0" - "@aws-sdk/middleware-host-header" "3.451.0" - "@aws-sdk/middleware-logger" "3.451.0" - "@aws-sdk/middleware-recursion-detection" "3.451.0" - "@aws-sdk/middleware-user-agent" "3.451.0" - "@aws-sdk/region-config-resolver" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@aws-sdk/util-endpoints" "3.451.0" - "@aws-sdk/util-user-agent-browser" "3.451.0" - "@aws-sdk/util-user-agent-node" "3.451.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/protocol-http" "^3.0.9" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" - "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" - "@smithy/util-utf8" "^2.0.2" - tslib "^2.5.0" - -"@aws-sdk/client-sso@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.556.0.tgz#7beeeebb6a437f09680edefc5c998822292a528a" - integrity sha512-unXdWS7uvHqCcOyC1de+Fr8m3F2vMg2m24GPea0bg7rVGTYmiyn9mhUX11VCt+ozydrw+F50FQwL6OqoqPocmw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.556.0" - "@aws-sdk/middleware-host-header" "3.535.0" - "@aws-sdk/middleware-logger" "3.535.0" - "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" - "@aws-sdk/region-config-resolver" "3.535.0" - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" - "@aws-sdk/util-user-agent-browser" "3.535.0" - "@aws-sdk/util-user-agent-node" "3.535.0" - "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/hash-node" "^2.2.0" - "@smithy/invalid-dependency" "^2.2.0" - "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" - "@smithy/middleware-serde" "^2.3.0" - "@smithy/middleware-stack" "^2.2.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/url-parser" "^2.2.0" - "@smithy/util-base64" "^2.3.0" - "@smithy/util-body-length-browser" "^2.2.0" - "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" - "@smithy/util-endpoints" "^1.2.0" - "@smithy/util-middleware" "^2.2.0" - "@smithy/util-retry" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@aws-sdk/client-sts@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.410.0.tgz#2727e5f9ac3cfc898cdb2a21db57c54cf9244249" - integrity sha512-e6VMrBJtnTxxUXwDmkADGIvyppmDMFf4+cGGA68tVCUm1cFNlCI6M/67bVSIPN/WVKAAfhEL5O2vVXCM7aatYg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/credential-provider-node" "3.410.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-sdk-sts" "3.410.0" - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/protocol-http" "^3.0.2" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-sts@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.451.0.tgz#66423b09621c344b1d41724117e43c7247e4c593" - integrity sha512-48NcIRxWBdP1fom6RSjwn2R2u7SE7eeV3p+c4s7ukEOfrHhBxJfn3EpqBVQMGzdiU55qFImy+Fe81iA2lXq3Jw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.451.0" - "@aws-sdk/credential-provider-node" "3.451.0" - "@aws-sdk/middleware-host-header" "3.451.0" - "@aws-sdk/middleware-logger" "3.451.0" - "@aws-sdk/middleware-recursion-detection" "3.451.0" - "@aws-sdk/middleware-sdk-sts" "3.451.0" - "@aws-sdk/middleware-signing" "3.451.0" - "@aws-sdk/middleware-user-agent" "3.451.0" - "@aws-sdk/region-config-resolver" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@aws-sdk/util-endpoints" "3.451.0" - "@aws-sdk/util-user-agent-browser" "3.451.0" - "@aws-sdk/util-user-agent-node" "3.451.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/protocol-http" "^3.0.9" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" - "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" - "@smithy/util-utf8" "^2.0.2" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/client-sts@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.556.0.tgz#3aa20cca462839f1451f11efada2be119dd36a6b" - integrity sha512-TsK3js7Suh9xEmC886aY+bv0KdLLYtzrcmVt6sJ/W6EnDXYQhBuKYFhp03NrN2+vSvMGpqJwR62DyfKe1G0QzQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.556.0" - "@aws-sdk/middleware-host-header" "3.535.0" - "@aws-sdk/middleware-logger" "3.535.0" - "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" - "@aws-sdk/region-config-resolver" "3.535.0" - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" - "@aws-sdk/util-user-agent-browser" "3.535.0" - "@aws-sdk/util-user-agent-node" "3.535.0" - "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/hash-node" "^2.2.0" - "@smithy/invalid-dependency" "^2.2.0" - "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" - "@smithy/middleware-serde" "^2.3.0" - "@smithy/middleware-stack" "^2.2.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/url-parser" "^2.2.0" - "@smithy/util-base64" "^2.3.0" - "@smithy/util-body-length-browser" "^2.2.0" - "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" - "@smithy/util-endpoints" "^1.2.0" - "@smithy/util-middleware" "^2.2.0" - "@smithy/util-retry" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@aws-sdk/core@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.451.0.tgz#ecd30da40d8e02050a772920485f450ea2a1b804" - integrity sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw== - dependencies: - "@smithy/smithy-client" "^2.1.15" - tslib "^2.5.0" - -"@aws-sdk/core@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.556.0.tgz#d0f4431a72282b71cfbcaedfb803f7f2807cf60b" - integrity sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA== - dependencies: - "@smithy/core" "^1.4.2" - "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - fast-xml-parser "4.2.5" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-env@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.410.0.tgz#ab359804d02c6cf283c468c306ce12e1300048c9" - integrity sha512-c7TB9LbN0PkFOsXI0lcRJnqPNOmc4VBvrHf8jP/BkTDg4YUoKQKOFd4d0SqzODmlZiAyoMQVZTR4ISZo95Zj4Q== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-env@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz#7b7429bd2e3fdebf914a88269274190781aeeab2" - integrity sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-env@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.535.0.tgz#26248e263a8107953d5496cb3760d4e7c877abcf" - integrity sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-http@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.552.0.tgz#ecc88d02cba95621887e6b85b2583e756ad29eb6" - integrity sha512-vsmu7Cz1i45pFEqzVb4JcFmAmVnWFNLsGheZc8SCptlqCO5voETrZZILHYIl4cjKkSDk3pblBOf0PhyjqWW6WQ== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/util-stream" "^2.2.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-ini@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.410.0.tgz#1f3f7605c1a6429b3040cb30757455c2ba2afe8e" - integrity sha512-D8rcr5bRCFD0f42MPQ7K6TWZq5d3pfqrKINL1/bpfkK5BJbvq1BGYmR88UC6CLpTRtZ1LHY2HgYG0fp/2zjjww== - dependencies: - "@aws-sdk/credential-provider-env" "3.410.0" - "@aws-sdk/credential-provider-process" "3.410.0" - "@aws-sdk/credential-provider-sso" "3.410.0" - "@aws-sdk/credential-provider-web-identity" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-ini@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz#e38315611f70700ad9803316d7030e3472c9789c" - integrity sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw== - dependencies: - "@aws-sdk/credential-provider-env" "3.451.0" - "@aws-sdk/credential-provider-process" "3.451.0" - "@aws-sdk/credential-provider-sso" "3.451.0" - "@aws-sdk/credential-provider-web-identity" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-ini@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.556.0.tgz#bf780feb92a7920cc525cd7cb7870ea61b84c125" - integrity sha512-0Nz4ErOlXhe3muxWYMbPwRMgfKmVbBp36BAE2uv/z5wTbfdBkcgUwaflEvlKCLUTdHzuZsQk+BFS/gVyaUeOuA== - dependencies: - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/credential-provider-env" "3.535.0" - "@aws-sdk/credential-provider-process" "3.535.0" - "@aws-sdk/credential-provider-sso" "3.556.0" - "@aws-sdk/credential-provider-web-identity" "3.556.0" - "@aws-sdk/types" "3.535.0" - "@smithy/credential-provider-imds" "^2.3.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-node@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.410.0.tgz#3bb5cfec1ecb1446c82185899b9aa5e25cffb5f9" - integrity sha512-0wmVm33T/j1FS7MZ/j+WsPlgSc0YnCXnpbWSov1Mn6R86SHI2b2JhdIPRRE4XbGfyW2QGNUl2CwoZVaqhXeF5g== - dependencies: - "@aws-sdk/credential-provider-env" "3.410.0" - "@aws-sdk/credential-provider-ini" "3.410.0" - "@aws-sdk/credential-provider-process" "3.410.0" - "@aws-sdk/credential-provider-sso" "3.410.0" - "@aws-sdk/credential-provider-web-identity" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-node@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz#72ccdef2199104379977dc06ea84c8d2a356d545" - integrity sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw== - dependencies: - "@aws-sdk/credential-provider-env" "3.451.0" - "@aws-sdk/credential-provider-ini" "3.451.0" - "@aws-sdk/credential-provider-process" "3.451.0" - "@aws-sdk/credential-provider-sso" "3.451.0" - "@aws-sdk/credential-provider-web-identity" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-node@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.556.0.tgz#51f3dc4506053249f8593765d1ab2cef53732fa3" - integrity sha512-s1xVtKjyGc60O8qcNIzS1X3H+pWEwEfZ7TgNznVDNyuXvLrlNWiAcigPWGl2aAkc8tGcsSG0Qpyw2KYC939LFg== - dependencies: - "@aws-sdk/credential-provider-env" "3.535.0" - "@aws-sdk/credential-provider-http" "3.552.0" - "@aws-sdk/credential-provider-ini" "3.556.0" - "@aws-sdk/credential-provider-process" "3.535.0" - "@aws-sdk/credential-provider-sso" "3.556.0" - "@aws-sdk/credential-provider-web-identity" "3.556.0" - "@aws-sdk/types" "3.535.0" - "@smithy/credential-provider-imds" "^2.3.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-process@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.410.0.tgz#d3b4890bbd4f1cbafa53ddafb68f03f5cd710731" - integrity sha512-BMju1hlDCDNkkSZpKF5SQ8G0WCLRj6/Jvw9QmudLHJuVwYJXEW1r2AsVMg98OZ3hB9G+MAvHruHZIbMiNmUMXQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-process@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz#3dd1d7df235f4eeb99d7e0f16b0e8cd61d555a73" - integrity sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-process@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz#ea1e8a38a32e36bbdc3f75eb03352e6eafa0c659" - integrity sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-sso@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.410.0.tgz#16078d75680c968417978f2efdba432e44ae42d7" - integrity sha512-zEaoY/sY+KYTlQUkp9dvveAHf175b8RIt0DsQkDrRPtrg/RBHR00r5rFvz9+nrwsR8546RaBU7h/zzTaQGhmcA== - dependencies: - "@aws-sdk/client-sso" "3.410.0" - "@aws-sdk/token-providers" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-sso@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz#f2482985a80f1da78e6b50ffaebbf2297d0f366f" - integrity sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg== - dependencies: - "@aws-sdk/client-sso" "3.451.0" - "@aws-sdk/token-providers" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-sso@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.556.0.tgz#26dfdd2c6e034f66e82985d65bd6aa3ae09d5e19" - integrity sha512-ETuBgcnpfxqadEAqhQFWpKoV1C/NAgvs5CbBc5EJbelJ8f4prTdErIHjrRtVT8c02MXj92QwczsiNYd5IoOqyw== - dependencies: - "@aws-sdk/client-sso" "3.556.0" - "@aws-sdk/token-providers" "3.556.0" - "@aws-sdk/types" "3.535.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/credential-provider-web-identity@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.410.0.tgz#6bb6db9c074c240c87bcf0798e105e28ceb51631" - integrity sha512-cE0l8LmEHdWbDkdPNgrfdYSgp4/cIVXrjUKI1QCATA729CrHZ/OQjB/maOBOrMHO9YTiggko887NkslVvwVB7w== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-web-identity@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz#5dc40768869d5887888c6f178c7831dd2c74cfbe" - integrity sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-web-identity@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.556.0.tgz#94cd55eaee6ca96354237569102dfaf6774544f4" - integrity sha512-R/YAL8Uh8i+dzVjzMnbcWLIGeeRi2mioHVGnVF+minmaIkCiQMZg2HPrdlKm49El+RljT28Nl5YHRuiqzEIwMA== - dependencies: - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/types" "3.535.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/endpoint-cache@3.310.0": - version "3.310.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/endpoint-cache/-/endpoint-cache-3.310.0.tgz#e6f84bfcd55462966811390ef797145559bab15a" - integrity sha512-y3wipforet41EDTI0vnzxILqwAGll1KfI5qcdX9pXF/WF1f+3frcOtPiWtQEZQpy4czRogKm3BHo70QBYAZxlQ== - dependencies: - mnemonist "0.38.3" - tslib "^2.5.0" - -"@aws-sdk/lib-dynamodb@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.410.0.tgz#36235589bef48f0a365d5e9612361fa755ce7430" - integrity sha512-YdGd8/DeN9zHXnvBh6zp+iZnFJtQ1JSTAye7OPPcno+xqW8KYoT+cn/vHzljtMoNEBhUE4udmaeqwYPOYSQCGA== - dependencies: - "@aws-sdk/util-dynamodb" "3.410.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-bucket-endpoint@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.410.0.tgz#8ddb3edbaa6e3303bbf8d719d445e8bde4a6b6aa" - integrity sha512-pUGrpFgCKf9fDHu01JJhhw+MUImheS0HFlZwNG37OMubkxUAbCdmYGewGxfTCUvWyZJtx9bVjrSu6gG7w+RARg== - dependencies: - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-arn-parser" "3.310.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - "@smithy/util-config-provider" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-endpoint-discovery@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.410.0.tgz#e759e5e3054e0e965f5b39053436a39e0b56c1ac" - integrity sha512-XHX4IqsyF3yVmh7mgeq9HMg0bpUOMhO5659JF56MdyxZ8JPVZVsgWfJYmQ/hCMG850kvYXS2/8kxCjERrONQFA== - dependencies: - "@aws-sdk/endpoint-cache" "3.310.0" - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-expect-continue@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.410.0.tgz#b91216b598b67563fb65b38783c3e4717a59d376" - integrity sha512-e5YqGCNmW99GZjEPPujJ02RlEZql19U40oORysBhVF7mKz8BBvF3s8l37tvu37oxebDEkh1u/2cm2+ggOXxLjQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-flexible-checksums@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.410.0.tgz#c7014f004aa0eaa6cd2adc3646c23bf669b3c4d1" - integrity sha512-IK7KlvEKtrQVBfmAp/MmGd0wbWLuN2GZwwfAmsU0qFb0f5vOVUbKDsu6tudtDKCBG9uXyTEsx3/QGvoK2zDy+g== - dependencies: - "@aws-crypto/crc32" "3.0.0" - "@aws-crypto/crc32c" "3.0.0" - "@aws-sdk/types" "3.410.0" - "@smithy/is-array-buffer" "^2.0.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-host-header@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.410.0.tgz#174c946b66c96a92d35e37a306c51306ea61eb09" - integrity sha512-ED/OVcyITln5rrxnajZP+V0PN1nug+gSDHJDqdDo/oLy7eiDr/ZWn3nlWW7WcMplQ1/Jnb+hK0UetBp/25XooA== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-host-header@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz#016fcd2b0ec58f26ce62c7ff792174bdf580972b" - integrity sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-host-header@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.535.0.tgz#d5264f813592f5e77df25e5a14bbb0e6441812db" - integrity sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-location-constraint@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.410.0.tgz#9727f3d91a1393202bde9b96b396552382a4f28c" - integrity sha512-jAftSpOpw/5AdpOJ/cGiXCb+Vv22KXR5QZmxmllUDsnlm18672tpRaI2plmu/1d98CVvqhY61eSklFMrIf2c4w== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-logger@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.410.0.tgz#4aa2e1b048f8e7d3e665dd814dbe8aaa4a234c20" - integrity sha512-YtmKYCVtBfScq3/UFJk+aSZOktKJBNZL9DaSc2aPcy/goCVsYDOkGwtHk0jIkC1JRSNCkVTqL7ya60sSr8zaQQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-logger@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz#9ef8ac916199f92ea1bb6c153279727ffa2b0b36" - integrity sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-logger@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.535.0.tgz#1a8ffd6c368edd6cb32e1edf7b1dced95c1820ee" - integrity sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-recursion-detection@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.410.0.tgz#becc7dba617d8fc86df29381ac88d861a28b3987" - integrity sha512-KWaes5FLzRqj28vaIEE4Bimpga2E596WdPF2HaH6zsVMJddoRDsc3ZX9ZhLOGrXzIO1RqBd0QxbLrM0S/B2aOQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-recursion-detection@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz#333a12d4792788bfcc3cab1028868cf37fb17e76" - integrity sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-recursion-detection@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.535.0.tgz#6aa1e1bd1e84730d58a73021b745e20d4341a92d" - integrity sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/middleware-sdk-api-gateway@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-api-gateway/-/middleware-sdk-api-gateway-3.410.0.tgz#766e4d2f37db4d07deb08b39139e4e8c69549cb2" - integrity sha512-flogIKCRwZxoVxsb+R70SkN6w7R+Sx/2zTNU/uwEEst/1mzpRuJZF8see/LdKnCGTIvd8KZgHJB4/BGdTBG03g== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-ec2@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-ec2/-/middleware-sdk-ec2-3.410.0.tgz#fb949290070bccb48649904a6da8d061588e9158" - integrity sha512-aE7OqVT+suWx3AgaP3znfXj1848IgUl84Wez4pJWwt2Uoui+DUB/UZuW8jHAZXNHsTVONdw2YDYgV8EOTDglrg== - dependencies: - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-format-url" "3.410.0" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/protocol-http" "^3.0.2" - "@smithy/signature-v4" "^2.0.0" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-route53@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.410.0.tgz#f1165ff488f30b09888bbf9fe8fa2555dda0f135" - integrity sha512-KGdtjcnO/Gic89ubU4f15oHCkuPwdjprOBSZfnmdl7k3VYk9ndc7WmEag04dQnsGbb4fo7Qbjj0c1339SmQ0yA== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-s3@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.410.0.tgz#15ddf0fb46363366c5dfabaab73bc21fa8ad32a8" - integrity sha512-K2sG2V1ZkezYMCIy3uMt0MwtflcfIwLptwm0iFLaYitiINZQ1tcslk9ggAjyTHg0rslDSI4/zjkhy8VHFOV7HA== - dependencies: - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-arn-parser" "3.310.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-sqs@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.410.0.tgz#ec040da105a7607411bf3e5b413c31b4890ef281" - integrity sha512-s3BwtXxVscCW0uzjyL7q1T1Mr/+Wgt3WxhMs+Edp+MfWoDlNyYtCVW2njvxtM6nRQ/FY9YPK6kiT28fO5hAIyw== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-sts@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.410.0.tgz#8f2b28eab09ec7b068d45196ab284a14bf4425f8" - integrity sha512-YfBpctDocRR4CcROoDueJA7D+aMLBV8nTFfmVNdLLLgyuLZ/AUR11VQSu1lf9gQZKl8IpKE/BLf2fRE/qV1ZuA== - dependencies: - "@aws-sdk/middleware-signing" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-sts@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz#0c70b57523386fe12357b4471cd20b681a27f9aa" - integrity sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg== - dependencies: - "@aws-sdk/middleware-signing" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-signing@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.410.0.tgz#987046066568633af49d691041486a258525fd9c" - integrity sha512-KBAZ/eoAJUSJv5us2HsKwK2OszG2s9FEyKpEhgnHLcbbKzW873zHBH5GcOGEQu4AWArTy2ndzJu3FF+9/J9hJQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.3.0" - "@smithy/util-middleware" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-signing@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz#ed7f5665dd048228e00f8e7e5925db32901a7886" - integrity sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.5.0" - "@smithy/util-middleware" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/middleware-ssec@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.410.0.tgz#d1f01aca5c309e94d1e53800f3bb76ca676472f2" - integrity sha512-DNsjVTXoxIh+PuW9o45CFaMiconbuZRm19MC3NA1yNCaCj3ZxD5OdXAutq6UjQdrx8UG4EjUlCJEEvBKmboITw== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-user-agent@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.410.0.tgz#2d505de9626b1c7709f6a8a1d415ce0c41eedcf4" - integrity sha512-ZayDtLfvCZUohSxQc/49BfoU/y6bDHLfLdyyUJbJ54Sv8zQcrmdyKvCBFUZwE6tHQgAmv9/ZT18xECMl+xiONA== - dependencies: - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-user-agent@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz#33d168e8411be4561eeef69e16c31e41b6f9a0cf" - integrity sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA== - dependencies: - "@aws-sdk/types" "3.451.0" - "@aws-sdk/util-endpoints" "3.451.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-user-agent@3.540.0": - version "3.540.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.540.0.tgz#4981c64c1eeb6b5c453bce02d060b8c71d44994d" - integrity sha512-8Rd6wPeXDnOYzWj1XCmOKcx/Q87L0K1/EHqOBocGjLVbN3gmRxBvpmR1pRTjf7IsWfnnzN5btqtcAkfDPYQUMQ== - dependencies: - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/region-config-resolver@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz#f4de34ebe435832dd6bcdc0a7b9fae14a42fc6de" - integrity sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg== - dependencies: - "@smithy/node-config-provider" "^2.1.5" - "@smithy/types" "^2.5.0" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.6" - tslib "^2.5.0" - -"@aws-sdk/region-config-resolver@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.535.0.tgz#20a30fb5fbbe27ab70f2ed16327bae7e367b5cec" - integrity sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/types" "^2.12.0" - "@smithy/util-config-provider" "^2.3.0" - "@smithy/util-middleware" "^2.2.0" - tslib "^2.6.2" - -"@aws-sdk/signature-v4-multi-region@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.410.0.tgz#a817ec05c2d4b7c2df0a46a6f95dd6876f6e8bef" - integrity sha512-abgcl9/i9frxGUVAfHHWj49UMCFEmzkYwKmV/4kw9MYn6BZ3HKb5M00tBLn9/PcAKfANS7O+qJRiEQT66rmfhg== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/signature-v4-multi-region@3.412.0": - version "3.412.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.412.0.tgz#417cdddd9d345e0a18cb29b282af33dd8ce0c1f2" - integrity sha512-ijxOeYpNDuk2T940S9HYcZ1C+wTP9vqp1Cw37zw9whVY2mKV3Vr7i+44D4FQ5HhWULgdwhjD7IctbNxPIPzUZQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/smithy-client@3.374.0": - version "3.374.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.374.0.tgz#e00e7d9bbf478846c0ac384e22c95159de5eab33" - integrity sha512-YQBdO/Nv5EXBg/qfMF4GgYYLNN3Y/06MyuVBYILC1TKAnMoLy2FV0VOYyediagepAcWPdJqyUq4MCNNBy0CPRg== - dependencies: - "@smithy/smithy-client" "^1.0.3" - tslib "^2.5.0" - -"@aws-sdk/token-providers@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.410.0.tgz#ce809992be1775a521b5f3578639842a953f541a" - integrity sha512-d5Nc0xydkH/X0LA1HDyhGY5sEv4LuADFk+QpDtT8ogLilcre+b1jpdY8Sih/gd1KoGS1H+d1tz2hSGwUHAbUbw== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/middleware-host-header" "3.410.0" - "@aws-sdk/middleware-logger" "3.410.0" - "@aws-sdk/middleware-recursion-detection" "3.410.0" - "@aws-sdk/middleware-user-agent" "3.410.0" - "@aws-sdk/types" "3.410.0" - "@aws-sdk/util-endpoints" "3.410.0" - "@aws-sdk/util-user-agent-browser" "3.410.0" - "@aws-sdk/util-user-agent-node" "3.410.0" - "@smithy/config-resolver" "^2.0.7" - "@smithy/fetch-http-handler" "^2.1.2" - "@smithy/hash-node" "^2.0.6" - "@smithy/invalid-dependency" "^2.0.6" - "@smithy/middleware-content-length" "^2.0.8" - "@smithy/middleware-endpoint" "^2.0.6" - "@smithy/middleware-retry" "^2.0.9" - "@smithy/middleware-serde" "^2.0.6" - "@smithy/middleware-stack" "^2.0.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/node-http-handler" "^2.1.2" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.2" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/smithy-client" "^2.1.3" - "@smithy/types" "^2.3.0" - "@smithy/url-parser" "^2.0.6" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.7" - "@smithy/util-defaults-mode-node" "^2.0.9" - "@smithy/util-retry" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/token-providers@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz#fb80e2fa39bb277fb77040a59c88312a115c35bd" - integrity sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/middleware-host-header" "3.451.0" - "@aws-sdk/middleware-logger" "3.451.0" - "@aws-sdk/middleware-recursion-detection" "3.451.0" - "@aws-sdk/middleware-user-agent" "3.451.0" - "@aws-sdk/region-config-resolver" "3.451.0" - "@aws-sdk/types" "3.451.0" - "@aws-sdk/util-endpoints" "3.451.0" - "@aws-sdk/util-user-agent-browser" "3.451.0" - "@aws-sdk/util-user-agent-node" "3.451.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" - "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" - "@smithy/util-utf8" "^2.0.2" - tslib "^2.5.0" - -"@aws-sdk/token-providers@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.556.0.tgz#96b4dd4fec67ae62f8c98ae8c2f94e4ed050073a" - integrity sha512-tvIiugNF0/+2wfuImMrpKjXMx4nCnFWQjQvouObny+wrif/PGqqQYrybwxPJDvzbd965bu1I+QuSv85/ug7xsg== - dependencies: - "@aws-sdk/client-sso-oidc" "3.556.0" - "@aws-sdk/types" "3.535.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/types@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.410.0.tgz#8c293e3763acb64c68f5752359523c3a40e5eb88" - integrity sha512-D7iaUCszv/v04NDaZUmCmekamy6VD/lKozm/3gS9+dkfU6cC2CsNoUfPV8BlV6dPdw0oWgF91am3I1stdvfVrQ== - dependencies: - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/types@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.451.0.tgz#37ab4b25074c6a36152eb36abb7399b3768c2e7b" - integrity sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/types@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.535.0.tgz#5e6479f31299dd9df170e63f4d10fe739008cf04" - integrity sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/types@^3.222.0": - version "3.398.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.398.0.tgz#8ce02559536670f9188cddfce32e9dd12b4fe965" - integrity sha512-r44fkS+vsEgKCuEuTV+TIk0t0m5ZlXHNjSDYEUvzLStbbfUFiNus/YG4UCa0wOk9R7VuQI67badsvvPeVPCGDQ== - dependencies: - "@smithy/types" "^2.2.2" - tslib "^2.5.0" - -"@aws-sdk/util-arn-parser@3.310.0": - version "3.310.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz#861ff8810851be52a320ec9e4786f15b5fc74fba" - integrity sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA== - dependencies: - tslib "^2.5.0" - -"@aws-sdk/util-dynamodb@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-dynamodb/-/util-dynamodb-3.410.0.tgz#836a5b27db1eb40001652d3439cce91718c39672" - integrity sha512-BHsiggDcotKWUgbv/ZIYGNRzGzkDnek7IRjyZJc/Bl7uA6EnOKAWDS86z+CjnEI9H0vbDnvihLiANdjEcNOa0w== - dependencies: - tslib "^2.5.0" - -"@aws-sdk/util-endpoints@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.410.0.tgz#66bc668e95e376dfcf0ce0774012ca9982d1f899" - integrity sha512-iNiqJyC7N3+8zFwnXUqcWSxrZecVZLToo1iTQQdeYL2af1IcOtRgb7n8jpAI/hmXhBSx2+3RI+Y7pxyFo1vu+w== - dependencies: - "@aws-sdk/types" "3.410.0" - tslib "^2.5.0" - -"@aws-sdk/util-endpoints@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz#8719977c3535c6fec719a2854ffe037e02412ddb" - integrity sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/util-endpoints" "^1.0.4" - tslib "^2.5.0" - -"@aws-sdk/util-endpoints@3.540.0": - version "3.540.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.540.0.tgz#a7fea1d2a5e64623353aaa6ee32dbb86ab9cd3f8" - integrity sha512-1kMyQFAWx6f8alaI6UT65/5YW/7pDWAKAdNwL6vuJLea03KrZRX3PMoONOSJpAS5m3Ot7HlWZvf3wZDNTLELZw== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/types" "^2.12.0" - "@smithy/util-endpoints" "^1.2.0" - tslib "^2.6.2" - -"@aws-sdk/util-format-url@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.410.0.tgz#80600d2c8c17c21c01879d1d74cc7092e1b60199" - integrity sha512-ftxPYq7RBxJMQrOCJARx8+sQccmG+6y7mm9JzfXOHOfS1aWnYQizTitJ7PMA8p90xrUAFQ2CmjT0jaEGWg5VGQ== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/querystring-builder" "^2.0.6" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/util-locate-window@^3.0.0": - version "3.310.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz#b071baf050301adee89051032bd4139bba32cc40" - integrity sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w== - dependencies: - tslib "^2.5.0" - -"@aws-sdk/util-retry@3.374.0": - version "3.374.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-retry/-/util-retry-3.374.0.tgz#7fd819d5857609b65a1bf06c39701fe5de5ad6cd" - integrity sha512-0p/trhYU+Ys8j3vMnWCvAkSOL6JRMooV9dVlQ+o7EHbQs9kDtnyucMUHU09ahHSIPTA/n/013hv7bzIt3MyKQg== - dependencies: - "@smithy/util-retry" "^1.0.3" - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-browser@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.410.0.tgz#c331b0fe3273b1a13e1c09612b3a7a9747415b24" - integrity sha512-i1G/XGpXGMRT2zEiAhi1xucJsfCWk8nNYjk/LbC0sA+7B9Huri96YAzVib12wkHPsJQvZxZC6CpQDIHWm4lXMA== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/types" "^2.3.0" - bowser "^2.11.0" - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-browser@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz#0b511703c3304a5c2fdaa864589246c93ad63dce" - integrity sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/types" "^2.5.0" - bowser "^2.11.0" - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-browser@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.535.0.tgz#d67d72e8b933051620f18ddb1c2be225f79f588f" - integrity sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/types" "^2.12.0" - bowser "^2.11.0" - tslib "^2.6.2" - -"@aws-sdk/util-user-agent-node@3.410.0": - version "3.410.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.410.0.tgz#c880383493663c90c2c13a9361eafbdd21148e84" - integrity sha512-bK70t1jHRl8HrJXd4hEIwc5PBZ7U0w+81AKFnanIVKZwZedd6nLibUXDTK14z/Jp2GFcBqd4zkt2YLGkRt/U4A== - dependencies: - "@aws-sdk/types" "3.410.0" - "@smithy/node-config-provider" "^2.0.9" - "@smithy/types" "^2.3.0" - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-node@3.451.0": - version "3.451.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz#f2af3f0d3f0389a14a7dbbc835dc94c705c0a39a" - integrity sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA== - dependencies: - "@aws-sdk/types" "3.451.0" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-node@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.535.0.tgz#f5c26fb6f3f561d3cf35f96f303b1775afad0a5b" - integrity sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@aws-sdk/util-utf8-browser@^3.0.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz#3275a6f5eb334f96ca76635b961d3c50259fd9ff" - integrity sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw== - dependencies: - tslib "^2.3.1" - -"@aws-sdk/xml-builder@3.310.0": - version "3.310.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz#f0236f2103b438d16117e0939a6305ad69b7ff76" - integrity sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw== - dependencies: - tslib "^2.5.0" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.15.tgz#15d4fd03f478a459015a4b94cfbb3bd42c48d2f4" - integrity sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.22.15" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.22.15" - "@babel/helpers" "^7.22.15" - "@babel/parser" "^7.22.15" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.15" - "@babel/types" "^7.22.15" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.22.15", "@babel/generator@^7.7.2": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" - integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== - dependencies: - "@babel/types" "^7.22.15" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.3.tgz#86e6e83d95903fbe7613f448613b8b319f330a8e" - integrity sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg== - dependencies: - "@babel/types" "^7.23.3" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.15.tgz#40ad2f6950f143900e9c1c72363c0b431a606082" - integrity sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.15" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" - integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== - -"@babel/helpers@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" - integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/highlight@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" - integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== - dependencies: - "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15": - version "7.22.16" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" - integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== - -"@babel/parser@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9" - integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" - integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" - integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/template@^7.22.15", "@babel/template@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.22.15": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.3.tgz#26ee5f252e725aa7aca3474aa5b324eaf7908b5b" - integrity sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.3" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.3" - "@babel/types" "^7.23.3" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.15.tgz#266cb21d2c5fd0b3931e7a91b6dd72d2f617d282" - integrity sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.15" - to-fast-properties "^2.0.0" - -"@babel/types@^7.23.0", "@babel/types@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.3.tgz#d5ea892c07f2ec371ac704420f4dcdb07b5f9598" - integrity sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@balena/dockerignore@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" - integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - -"@esbuild/android-arm64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.10.tgz#ad2ee47dd021035abdfb0c38848ff77a1e1918c4" - integrity sha512-ht1P9CmvrPF5yKDtyC+z43RczVs4rrHpRqrmIuoSvSdn44Fs1n6DGlpZKdK6rM83pFLbVaSUwle8IN+TPmkv7g== - -"@esbuild/android-arm@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.10.tgz#bb5a68af8adeb94b30eadee7307404dc5237d076" - integrity sha512-7YEBfZ5lSem9Tqpsz+tjbdsEshlO9j/REJrfv4DXgKTt1+/MHqGwbtlyxQuaSlMeUZLxUKBaX8wdzlTfHkmnLw== - -"@esbuild/android-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.10.tgz#751d5d8ae9ece1efa9627b689c888eb85b102360" - integrity sha512-CYzrm+hTiY5QICji64aJ/xKdN70IK8XZ6iiyq0tZkd3tfnwwSWTYH1t3m6zyaaBxkuj40kxgMyj1km/NqdjQZA== - -"@esbuild/darwin-arm64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.10.tgz#85601ee7efb2129cd3218d5bcbe8da1173bc1e8b" - integrity sha512-3HaGIowI+nMZlopqyW6+jxYr01KvNaLB5znXfbyyjuo4lE0VZfvFGcguIJapQeQMS4cX/NEispwOekJt3gr5Dg== - -"@esbuild/darwin-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.10.tgz#362c7e988c61fe72d5edef4f717e4b4fc728da98" - integrity sha512-J4MJzGchuCRG5n+B4EHpAMoJmBeAE1L3wGYDIN5oWNqX0tEr7VKOzw0ymSwpoeSpdCa030lagGUfnfhS7OvzrQ== - -"@esbuild/freebsd-arm64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.10.tgz#e8a85a46ede7c3a048a12f16b9d551d25adc8bb1" - integrity sha512-ZkX40Z7qCbugeK4U5/gbzna/UQkM9d9LNV+Fro8r7HA7sRof5Rwxc46SsqeMvB5ZaR0b1/ITQ/8Y1NmV2F0fXQ== - -"@esbuild/freebsd-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.10.tgz#cd0a1b68bffbcb5b65e65b3fd542e8c7c3edd86b" - integrity sha512-0m0YX1IWSLG9hWh7tZa3kdAugFbZFFx9XrvfpaCMMvrswSTvUZypp0NFKriUurHpBA3xsHVE9Qb/0u2Bbi/otg== - -"@esbuild/linux-arm64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.10.tgz#13b183f432512ed9d9281cc89476caeebe9e9123" - integrity sha512-g1EZJR1/c+MmCgVwpdZdKi4QAJ8DCLP5uTgLWSAVd9wlqk9GMscaNMEViG3aE1wS+cNMzXXgdWiW/VX4J+5nTA== - -"@esbuild/linux-arm@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.10.tgz#dd11e0a5faa3ea94dc80278a601c3be7b4fdf1da" - integrity sha512-whRdrrl0X+9D6o5f0sTZtDM9s86Xt4wk1bf7ltx6iQqrIIOH+sre1yjpcCdrVXntQPCNw/G+XqsD4HuxeS+2QA== - -"@esbuild/linux-ia32@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.10.tgz#4d836f87b92807d9292379963c4888270d282405" - integrity sha512-1vKYCjfv/bEwxngHERp7huYfJ4jJzldfxyfaF7hc3216xiDA62xbXJfRlradiMhGZbdNLj2WA1YwYFzs9IWNPw== - -"@esbuild/linux-loong64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.10.tgz#92eb2ee200c17ef12c7fb3b648231948699e7a4c" - integrity sha512-mvwAr75q3Fgc/qz3K6sya3gBmJIYZCgcJ0s7XshpoqIAIBszzfXsqhpRrRdVFAyV1G9VUjj7VopL2HnAS8aHFA== - -"@esbuild/linux-mips64el@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.10.tgz#14f7d50c40fe7f7ee545a9bd07c6f6e4cba5570e" - integrity sha512-XilKPgM2u1zR1YuvCsFQWl9Fc35BqSqktooumOY2zj7CSn5czJn279j9TE1JEqSqz88izJo7yE4x3LSf7oxHzg== - -"@esbuild/linux-ppc64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.10.tgz#1ab5802e93ae511ce9783e1cb95f37df0f84c4af" - integrity sha512-kM4Rmh9l670SwjlGkIe7pYWezk8uxKHX4Lnn5jBZYBNlWpKMBCVfpAgAJqp5doLobhzF3l64VZVrmGeZ8+uKmQ== - -"@esbuild/linux-riscv64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.10.tgz#4fae25201ef7ad868731d16c8b50b0e386c4774a" - integrity sha512-r1m9ZMNJBtOvYYGQVXKy+WvWd0BPvSxMsVq8Hp4GzdMBQvfZRvRr5TtX/1RdN6Va8JMVQGpxqde3O+e8+khNJQ== - -"@esbuild/linux-s390x@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.10.tgz#126254d8335bb3586918b1ca60beb4abb46e6d54" - integrity sha512-LsY7QvOLPw9WRJ+fU5pNB3qrSfA00u32ND5JVDrn/xG5hIQo3kvTxSlWFRP0NJ0+n6HmhPGG0Q4jtQsb6PFoyg== - -"@esbuild/linux-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.10.tgz#7fa4667b2df81ea0538e1b75e607cf04e526ce91" - integrity sha512-zJUfJLebCYzBdIz/Z9vqwFjIA7iSlLCFvVi7glMgnu2MK7XYigwsonXshy9wP9S7szF+nmwrelNaP3WGanstEg== - -"@esbuild/netbsd-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.10.tgz#2d24727ddc2305619685bf237a46d6087a02ee9a" - integrity sha512-lOMkailn4Ok9Vbp/q7uJfgicpDTbZFlXlnKT2DqC8uBijmm5oGtXAJy2ZZVo5hX7IOVXikV9LpCMj2U8cTguWA== - -"@esbuild/openbsd-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.10.tgz#bf3fc38ee6ecf028c1f0cfe11f61d53cc75fef12" - integrity sha512-/VE0Kx6y7eekqZ+ZLU4AjMlB80ov9tEz4H067Y0STwnGOYL8CsNg4J+cCmBznk1tMpxMoUOf0AbWlb1d2Pkbig== - -"@esbuild/sunos-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.10.tgz#8deabd6dfec6256f80bb101bc59d29dbae99c69b" - integrity sha512-ERNO0838OUm8HfUjjsEs71cLjLMu/xt6bhOlxcJ0/1MG3hNqCmbWaS+w/8nFLa0DDjbwZQuGKVtCUJliLmbVgg== - -"@esbuild/win32-arm64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.10.tgz#1ec1ee04c788c4c57a83370b6abf79587b3e4965" - integrity sha512-fXv+L+Bw2AeK+XJHwDAQ9m3NRlNemG6Z6ijLwJAAVdu4cyoFbBWbEtyZzDeL+rpG2lWI51cXeMt70HA8g2MqIg== - -"@esbuild/win32-ia32@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.10.tgz#a362528d7f3ad5d44fa8710a96764677ef92ebe9" - integrity sha512-3s+HADrOdCdGOi5lnh5DMQEzgbsFsd4w57L/eLKKjMnN0CN4AIEP0DCP3F3N14xnxh3ruNc32A0Na9zYe1Z/AQ== - -"@esbuild/win32-x64@0.17.10": - version "0.17.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.10.tgz#ac779220f2da96afd480fb3f3148a292f66e7fc3" - integrity sha512-oP+zFUjYNaMNmjTwlFtWep85hvwUu19cZklB3QsBOcZSs6y7hmH4LNCJ7075bsqzYaNvZFXJlAVaQ2ApITDXtw== - -"@eslint-community/eslint-utils@^4.2.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint/eslintrc@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" - integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.4.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@gar/promisify@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - -"@humanwhocodes/config-array@^0.11.8": - version "0.11.11" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" - integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@hutson/parse-repository-url@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" - integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.4.tgz#a7e2d84516301f986bba0dd55af9d5fe37f46527" - integrity sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.6.3" - jest-util "^29.6.3" - slash "^3.0.0" - -"@jest/core@^29.4.3", "@jest/core@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.4.tgz#265ebee05ec1ff3567757e7a327155c8d6bdb126" - integrity sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg== - dependencies: - "@jest/console" "^29.6.4" - "@jest/reporters" "^29.6.4" - "@jest/test-result" "^29.6.4" - "@jest/transform" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.6.3" - jest-config "^29.6.4" - jest-haste-map "^29.6.4" - jest-message-util "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.6.4" - jest-resolve-dependencies "^29.6.4" - jest-runner "^29.6.4" - jest-runtime "^29.6.4" - jest-snapshot "^29.6.4" - jest-util "^29.6.3" - jest-validate "^29.6.3" - jest-watcher "^29.6.4" - micromatch "^4.0.4" - pretty-format "^29.6.3" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.4.tgz#78ec2c9f8c8829a37616934ff4fea0c028c79f4f" - integrity sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ== - dependencies: - "@jest/fake-timers" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.6.3" - -"@jest/expect-utils@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.4.tgz#17c7dfe6cec106441f218b0aff4b295f98346679" - integrity sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.4.tgz#1d6ae17dc68d906776198389427ab7ce6179dba6" - integrity sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA== - dependencies: - expect "^29.6.4" - jest-snapshot "^29.6.4" - -"@jest/fake-timers@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.4.tgz#45a27f093c43d5d989362a3e7a8c70c83188b4f6" - integrity sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.6.3" - jest-mock "^29.6.3" - jest-util "^29.6.3" - -"@jest/globals@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.4.tgz#4f04f58731b062b44ef23036b79bdb31f40c7f63" - integrity sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA== - dependencies: - "@jest/environment" "^29.6.4" - "@jest/expect" "^29.6.4" - "@jest/types" "^29.6.3" - jest-mock "^29.6.3" - -"@jest/reporters@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.4.tgz#9d6350c8a2761ece91f7946e97ab0dabc06deab7" - integrity sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.6.4" - "@jest/test-result" "^29.6.4" - "@jest/transform" "^29.6.4" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.6.3" - jest-util "^29.6.3" - jest-worker "^29.6.4" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.4.tgz#adf5c79f6e1fb7405ad13d67d9e2b6ff54b54c6b" - integrity sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ== - dependencies: - "@jest/console" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz#86aef66aaa22b181307ed06c26c82802fb836d7b" - integrity sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg== - dependencies: - "@jest/test-result" "^29.6.4" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.4" - slash "^3.0.0" - -"@jest/transform@^29.6.4": - version "29.6.4" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.4.tgz#a6bc799ef597c5d85b2e65a11fd96b6b239bab5a" - integrity sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.4" - jest-regex-util "^29.6.3" - jest-util "^29.6.3" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.4.3", "@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@jsii/check-node@1.75.0": - version "1.75.0" - resolved "https://registry.yarnpkg.com/@jsii/check-node/-/check-node-1.75.0.tgz#1cfc2ab5b461c1e80d9a4c18a6e351a9a13287ce" - integrity sha512-ODorfPJwN0920DJrZ/R/G3x0UHgtuhz9y10s6Xox1BDobVXOQpfUl3XEQjrTrSE7kiCt2FxZvazT4xu1MU0y6Q== - dependencies: - chalk "^4.1.2" - semver "^7.3.8" - -"@jsii/check-node@1.88.0": - version "1.88.0" - resolved "https://registry.yarnpkg.com/@jsii/check-node/-/check-node-1.88.0.tgz#fa20e012230c692ad36976cde29301be1ed28c67" - integrity sha512-AveFyqkJIb8qZvGk5nZal/8mEJB6lWhwqvAQLodHmqE3WzpmZD5+h+aspBVt0El5cEFRJ1k1mrQqhAnJCVpvxg== - dependencies: - chalk "^4.1.2" - semver "^7.5.4" - -"@jsii/spec@1.88.0", "@jsii/spec@^1.75.0", "@jsii/spec@^1.88.0": - version "1.88.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.88.0.tgz#46216d3ca93872b4d878bb81f0cc7b28dc621c28" - integrity sha512-Q6xirxPM06TRW0GcsHa+tzPZLwe9I+mFYx5BaNMimcv21u6bQnxfynZMgNhHqvLYCmP37HWg0SboUYTa5JROzw== - dependencies: - ajv "^8.12.0" - -"@lerna/child-process@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-7.2.0.tgz#42de5b0a4eb7479c2a72b4bf61685616740cf818" - integrity sha512-8cRsYYX8rGZTXL1KcLBv0RHD9PMvphWZay8yg4qf2giX6x86dQyTetSU4SplG2LBGVClilmNHJa/CQwvPQNUFA== - dependencies: - chalk "^4.1.0" - execa "^5.0.0" - strong-log-transformer "^2.1.0" - -"@lerna/create@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-7.2.0.tgz#a8080b419c1f8ab5d575693fcb883a6a0d82c7e0" - integrity sha512-bBypNfwqOQNcfR2nXJ3mWUeIAIoSFpXg8MjuFSf87PzIiyeTEKa3Z57vAa3bDbHQtcB7x6f0rWysK1eQZSH15Q== - dependencies: - "@lerna/child-process" "7.2.0" - "@npmcli/run-script" "6.0.2" - "@nx/devkit" ">=16.5.1 < 17" - "@octokit/plugin-enterprise-rest" "6.0.1" - "@octokit/rest" "19.0.11" - byte-size "8.1.1" - chalk "4.1.0" - clone-deep "4.0.1" - cmd-shim "6.0.1" - columnify "1.6.0" - conventional-changelog-core "5.0.1" - conventional-recommended-bump "7.0.1" - cosmiconfig "^8.2.0" - dedent "0.7.0" - execa "5.0.0" - fs-extra "^11.1.1" - get-stream "6.0.0" - git-url-parse "13.1.0" - glob-parent "5.1.2" - globby "11.1.0" - graceful-fs "4.2.11" - has-unicode "2.0.1" - ini "^1.3.8" - init-package-json "5.0.0" - inquirer "^8.2.4" - is-ci "3.0.1" - is-stream "2.0.0" - js-yaml "4.1.0" - libnpmpublish "7.3.0" - load-json-file "6.2.0" - lodash "^4.17.21" - make-dir "3.1.0" - minimatch "3.0.5" - multimatch "5.0.0" - node-fetch "2.6.7" - npm-package-arg "8.1.1" - npm-packlist "5.1.1" - npm-registry-fetch "^14.0.5" - npmlog "^6.0.2" - nx ">=16.5.1 < 17" - p-map "4.0.0" - p-map-series "2.1.0" - p-queue "6.6.2" - p-reduce "^2.1.0" - pacote "^15.2.0" - pify "5.0.0" - read-cmd-shim "4.0.0" - read-package-json "6.0.4" - resolve-from "5.0.0" - rimraf "^4.4.1" - semver "^7.3.4" - signal-exit "3.0.7" - slash "^3.0.0" - ssri "^9.0.1" - strong-log-transformer "2.1.0" - tar "6.1.11" - temp-dir "1.0.0" - upath "2.0.1" - uuid "^9.0.0" - validate-npm-package-license "^3.0.4" - validate-npm-package-name "5.0.0" - write-file-atomic "5.0.1" - write-pkg "4.0.0" - yargs "16.2.0" - yargs-parser "20.2.4" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@npmcli/fs@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" - integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== - dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" - -"@npmcli/fs@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" - integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== - dependencies: - semver "^7.3.5" - -"@npmcli/git@^4.0.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-4.1.0.tgz#ab0ad3fd82bc4d8c1351b6c62f0fa56e8fe6afa6" - integrity sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ== - dependencies: - "@npmcli/promise-spawn" "^6.0.0" - lru-cache "^7.4.4" - npm-pick-manifest "^8.0.0" - proc-log "^3.0.0" - promise-inflight "^1.0.1" - promise-retry "^2.0.1" - semver "^7.3.5" - which "^3.0.0" - -"@npmcli/installed-package-contents@^2.0.1": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz#bfd817eccd9e8df200919e73f57f9e3d9e4f9e33" - integrity sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ== - dependencies: - npm-bundled "^3.0.0" - npm-normalize-package-bin "^3.0.0" - -"@npmcli/move-file@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" - integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@npmcli/node-gyp@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz#101b2d0490ef1aa20ed460e4c0813f0db560545a" - integrity sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA== - -"@npmcli/promise-spawn@^6.0.0", "@npmcli/promise-spawn@^6.0.1": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz#c8bc4fa2bd0f01cb979d8798ba038f314cfa70f2" - integrity sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg== - dependencies: - which "^3.0.0" - -"@npmcli/run-script@6.0.2", "@npmcli/run-script@^6.0.0": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.2.tgz#a25452d45ee7f7fb8c16dfaf9624423c0c0eb885" - integrity sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA== - dependencies: - "@npmcli/node-gyp" "^3.0.0" - "@npmcli/promise-spawn" "^6.0.0" - node-gyp "^9.0.0" - read-package-json-fast "^3.0.0" - which "^3.0.0" - -"@nrwl/devkit@16.10.0": - version "16.10.0" - resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-16.10.0.tgz#ac8c5b4db00f12c4b817c937be2f7c4eb8f2593c" - integrity sha512-fRloARtsDQoQgQ7HKEy0RJiusg/HSygnmg4gX/0n/Z+SUS+4KoZzvHjXc6T5ZdEiSjvLypJ+HBM8dQzIcVACPQ== - dependencies: - "@nx/devkit" "16.10.0" - -"@nrwl/tao@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-16.8.0.tgz#cec82dae982ac68aeac8add7bc3f6ed76ae64442" - integrity sha512-b8SteybQrq34SBnrkdpsShD6hyjmq2WNi6WY2fLxkWg4IvBtG1U3KC8SdHhmbBqLA8PpBt8bV7paH8IgsiajtA== - dependencies: - nx "16.8.0" - tslib "^2.3.0" - -"@nx/devkit@16.10.0", "@nx/devkit@>=16.5.1 < 17": - version "16.10.0" - resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-16.10.0.tgz#7e466be2dee2dcb1ccaf286786ca2a0a639aa007" - integrity sha512-IvKQqRJFDDiaj33SPfGd3ckNHhHi6ceEoqCbAP4UuMXOPPVOX6H0KVk+9tknkPb48B7jWIw6/AgOeWkBxPRO5w== - dependencies: - "@nrwl/devkit" "16.10.0" - ejs "^3.1.7" - enquirer "~2.3.6" - ignore "^5.0.4" - semver "7.5.3" - tmp "~0.2.1" - tslib "^2.3.0" - -"@nx/nx-darwin-arm64@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.8.0.tgz#2f13a60029c96b80023556f65790565db402c200" - integrity sha512-yNmEJoRKl9JZH/O6oWUvbt5eYbSRHIJtGwvxVCtNRqykyNZNlEzMSLxz0Mx8x3h0QQ3zjZEUmMjac4tb4BPPLw== - -"@nx/nx-darwin-x64@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-16.8.0.tgz#8fc8a8678d03680b7e87e4e80eff46277bad1109" - integrity sha512-YOSLslyMJ682FrhRsmPRl0zJmr2gpSzCHR5ETScPbFaQ+kxGVuTzSUr1PFTw9lAOGiqspFf0CCqxS3ZmY4DjSg== - -"@nx/nx-freebsd-x64@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.8.0.tgz#93846e0385368463f59686cb933b24467fa25b2f" - integrity sha512-puHAmnvDdemUoDt+hO53IqgWXHb6cpCmiIrT7N+sZfBVVoyieb9S9zWCXJO/HCwdkS6ibx7nixNQpYBT7J+1WQ== - -"@nx/nx-linux-arm-gnueabihf@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.8.0.tgz#112c5239eca330b984fda6c99e135027c75b879d" - integrity sha512-wymaeEElhVmXHHB0kC6uL2iE7J2Ze9s0Ws1qp5mQ8275wHEC4/OLAWSnh/B9WsGFsHPlVj/QYoXmevUyjK5etw== - -"@nx/nx-linux-arm64-gnu@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.8.0.tgz#ce94b08d0ed69848f14b00ec43f0ed72f1601d69" - integrity sha512-Hc32BOQ9yw7oS+rHYAlRYZ4rW+YDsGHiUAcKekFh6WtKvj49OpXUfNw8dNzPuCmohi0Nvpm96TAi7rilRS79TQ== - -"@nx/nx-linux-arm64-musl@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.8.0.tgz#8aed46828b89d981e453711c5423933aa4897ac8" - integrity sha512-JsJyZswdPA5Wllh7xdusxogPKmiA/F4LzjFGy01sUy0DV3+5WJasp3VDNbl/o8v4f0BZ2/dfMlVJnF+hLBhK5g== - -"@nx/nx-linux-x64-gnu@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.8.0.tgz#21eb66850f83b396d2260c341dd5e2bba9a91f37" - integrity sha512-WpZ90jcbbTO5g5FtNpZRgpem7HCE3iBKOx0lG2xt7ZWd5JoypzZg9tEjHaDvCm6uNloUqZZigKnVyxZ/ulaq/Q== - -"@nx/nx-linux-x64-musl@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.8.0.tgz#50b31d00b0435a1b9c2bfba90f32fc16c34f8d57" - integrity sha512-iuOT42XEN/NPytNrZLB/V1WHbqletE4q2V3GxY08OFay6jyjA3CZWhUQ0IhiqXs2MXlbeKZM1Y9M8ogXr1bLHA== - -"@nx/nx-win32-arm64-msvc@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.8.0.tgz#da9e7b82e6f8e3f4cbf1b96d9887e28837e52a89" - integrity sha512-zB9EekQAc9DAp8G+2bvO4ko/jJJRDEN9Si+J/xMMCfTNWQE3+JJqqbN9P48Z3UKYZcm2DFRJFPA1Qfy5lv/MNA== - -"@nx/nx-win32-x64-msvc@16.8.0": - version "16.8.0" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.8.0.tgz#9c2689cb83620c3189004ac39c5afe6b4057579e" - integrity sha512-PvF3hvsMFqN90t3GldLAdVW+qEr1J3g18t6QdnRHL39I4+BYCXip1hzWlK+aOP+TxQuZaSbVMMChZQw1eC97LA== - -"@octokit/auth-token@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.4.tgz#70e941ba742bdd2b49bdb7393e821dea8520a3db" - integrity sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ== - -"@octokit/core@^4.2.1": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.2.4.tgz#d8769ec2b43ff37cc3ea89ec4681a20ba58ef907" - integrity sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ== - dependencies: - "@octokit/auth-token" "^3.0.0" - "@octokit/graphql" "^5.0.0" - "@octokit/request" "^6.0.0" - "@octokit/request-error" "^3.0.0" - "@octokit/types" "^9.0.0" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - -"@octokit/endpoint@^7.0.0": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.6.tgz#791f65d3937555141fb6c08f91d618a7d645f1e2" - integrity sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg== - dependencies: - "@octokit/types" "^9.0.0" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/graphql@^5.0.0": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.6.tgz#9eac411ac4353ccc5d3fca7d76736e6888c5d248" - integrity sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw== - dependencies: - "@octokit/request" "^6.0.0" - "@octokit/types" "^9.0.0" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^18.0.0": - version "18.0.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-18.0.0.tgz#f43d765b3c7533fd6fb88f3f25df079c24fccf69" - integrity sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw== - -"@octokit/plugin-enterprise-rest@6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" - integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== - -"@octokit/plugin-paginate-rest@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz#f86456a7a1fe9e58fec6385a85cf1b34072341f8" - integrity sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ== - dependencies: - "@octokit/tsconfig" "^1.0.2" - "@octokit/types" "^9.2.3" - -"@octokit/plugin-request-log@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" - integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== - -"@octokit/plugin-rest-endpoint-methods@^7.1.2": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz#37a84b171a6cb6658816c82c4082ac3512021797" - integrity sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA== - dependencies: - "@octokit/types" "^10.0.0" - -"@octokit/request-error@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.3.tgz#ef3dd08b8e964e53e55d471acfe00baa892b9c69" - integrity sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ== - dependencies: - "@octokit/types" "^9.0.0" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^6.0.0": - version "6.2.8" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.8.tgz#aaf480b32ab2b210e9dadd8271d187c93171d8eb" - integrity sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw== - dependencies: - "@octokit/endpoint" "^7.0.0" - "@octokit/request-error" "^3.0.0" - "@octokit/types" "^9.0.0" - is-plain-object "^5.0.0" - node-fetch "^2.6.7" - universal-user-agent "^6.0.0" - -"@octokit/rest@19.0.11": - version "19.0.11" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.11.tgz#2ae01634fed4bd1fca5b642767205ed3fd36177c" - integrity sha512-m2a9VhaP5/tUw8FwfnW2ICXlXpLPIqxtg3XcAiGMLj/Xhw3RSBfZ8le/466ktO1Gcjr8oXudGnHhxV1TXJgFxw== - dependencies: - "@octokit/core" "^4.2.1" - "@octokit/plugin-paginate-rest" "^6.1.2" - "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^7.1.2" - -"@octokit/tsconfig@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@octokit/tsconfig/-/tsconfig-1.0.2.tgz#59b024d6f3c0ed82f00d08ead5b3750469125af7" - integrity sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA== - -"@octokit/types@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-10.0.0.tgz#7ee19c464ea4ada306c43f1a45d444000f419a4a" - integrity sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg== - dependencies: - "@octokit/openapi-types" "^18.0.0" - -"@octokit/types@^9.0.0", "@octokit/types@^9.2.3": - version "9.3.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-9.3.2.tgz#3f5f89903b69f6a2d196d78ec35f888c0013cac5" - integrity sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA== - dependencies: - "@octokit/openapi-types" "^18.0.0" - -"@parcel/watcher@2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" - integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== - dependencies: - node-addon-api "^3.2.1" - node-gyp-build "^4.3.0" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@pkgr/utils@^2.3.1": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" - -"@sigstore/bundle@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1" - integrity sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog== - dependencies: - "@sigstore/protobuf-specs" "^0.2.0" - -"@sigstore/protobuf-specs@^0.2.0": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz#be9ef4f3c38052c43bd399d3f792c97ff9e2277b" - integrity sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A== - -"@sigstore/sign@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-1.0.0.tgz#6b08ebc2f6c92aa5acb07a49784cb6738796f7b4" - integrity sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA== - dependencies: - "@sigstore/bundle" "^1.1.0" - "@sigstore/protobuf-specs" "^0.2.0" - make-fetch-happen "^11.0.1" - -"@sigstore/tuf@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-1.0.3.tgz#2a65986772ede996485728f027b0514c0b70b160" - integrity sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg== - dependencies: - "@sigstore/protobuf-specs" "^0.2.0" - tuf-js "^1.1.7" - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^1.7.0": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" - integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== - dependencies: - type-detect "4.0.8" - -"@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2", "@sinonjs/fake-timers@^10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@sinonjs/fake-timers@^11.2.2": - version "11.2.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" - integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@sinonjs/fake-timers@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@sinonjs/samsam@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-7.0.1.tgz#5b5fa31c554636f78308439d220986b9523fc51f" - integrity sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw== - dependencies: - "@sinonjs/commons" "^2.0.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" - -"@sinonjs/samsam@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" - integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== - dependencies: - "@sinonjs/commons" "^2.0.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" - -"@sinonjs/text-encoding@^0.7.1", "@sinonjs/text-encoding@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" - integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== - -"@smithy/abort-controller@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-1.1.0.tgz#2da0d73c504b93ca8bb83bdc8d6b8208d73f418b" - integrity sha512-5imgGUlZL4dW4YWdMYAKLmal9ny/tlenM81QZY7xYyb76z9Z/QOg7oM5Ak9HQl8QfFTlGVWwcMXl+54jroRgEQ== - dependencies: - "@smithy/types" "^1.2.0" - tslib "^2.5.0" - -"@smithy/abort-controller@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.0.13.tgz#d050a969bf1a478e548a323ea0f1b83532cbc136" - integrity sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/abort-controller@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.0.7.tgz#e36f6da2f9c14b2abba4c11e42813d4de0755b12" - integrity sha512-rITz65zk8QA3GQ1OeoJ3/Q4+8j/HqubWU8TBqk57BMYTOX+P+LNMoVHPqzLHhE6qKot5muhThNCYvOKNt7ojJA== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/abort-controller@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.2.0.tgz#18983401a5e2154b5c94057730024a7d14cbcd35" - integrity sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/chunked-blob-reader-native@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.0.0.tgz#f6d0eeeb5481026b68b054f45540d924c194d558" - integrity sha512-HM8V2Rp1y8+1343tkZUKZllFhEQPNmpNdgFAncbTsxkZ18/gqjk23XXv3qGyXWp412f3o43ZZ1UZHVcHrpRnCQ== - dependencies: - "@smithy/util-base64" "^2.0.0" - tslib "^2.5.0" - -"@smithy/chunked-blob-reader@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz#c44fe2c780eaf77f9e5381d982ac99a880cce51b" - integrity sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg== - dependencies: - tslib "^2.5.0" - -"@smithy/config-resolver@^2.0.18": - version "2.0.18" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.0.18.tgz#5692b491a423bfb821d12e6eca0eb5f0ca63e789" - integrity sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g== - dependencies: - "@smithy/node-config-provider" "^2.1.5" - "@smithy/types" "^2.5.0" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.6" - tslib "^2.5.0" - -"@smithy/config-resolver@^2.0.7", "@smithy/config-resolver@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.0.8.tgz#bab938d24bada463cea6935d8b98035af07f7a6d" - integrity sha512-e7mwQteHjo9S1GK+TfzP3o7ujE2ZK30d6wkv5brKtabrZF7MBflj9CwUP2XYuOYebdWirHOtv8ZfkMrpcbJfYw== - dependencies: - "@smithy/node-config-provider" "^2.0.10" - "@smithy/types" "^2.3.1" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.0" - tslib "^2.5.0" - -"@smithy/config-resolver@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.2.0.tgz#54f40478bb61709b396960a3535866dba5422757" - integrity sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA== - dependencies: - "@smithy/node-config-provider" "^2.3.0" - "@smithy/types" "^2.12.0" - "@smithy/util-config-provider" "^2.3.0" - "@smithy/util-middleware" "^2.2.0" - tslib "^2.6.2" - -"@smithy/core@^1.4.2": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@smithy/core/-/core-1.4.2.tgz#1c3ed886d403041ce5bd2d816448420c57baa19c" - integrity sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA== - dependencies: - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" - "@smithy/middleware-serde" "^2.3.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/util-middleware" "^2.2.0" - tslib "^2.6.2" - -"@smithy/credential-provider-imds@^2.0.0", "@smithy/credential-provider-imds@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.0.10.tgz#19a95e0ab4fbd71bbf1e3a3e0bd03239c5ba5f63" - integrity sha512-may2/gYlDip2rjlU1Z5fcCEWY0Fu3tSu/HykgZrLfb2/171P6OYuz7dGNKBOCS1W57vP4W5wmUhm0WGehrixig== - dependencies: - "@smithy/node-config-provider" "^2.0.10" - "@smithy/property-provider" "^2.0.8" - "@smithy/types" "^2.3.1" - "@smithy/url-parser" "^2.0.7" - tslib "^2.5.0" - -"@smithy/credential-provider-imds@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz#18607cbfce633ed81a2832889efb660c33a974e9" - integrity sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw== - dependencies: - "@smithy/node-config-provider" "^2.1.5" - "@smithy/property-provider" "^2.0.14" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" - tslib "^2.5.0" - -"@smithy/credential-provider-imds@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz#326ce401b82e53f3c7ee4862a066136959a06166" - integrity sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w== - dependencies: - "@smithy/node-config-provider" "^2.3.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/types" "^2.12.0" - "@smithy/url-parser" "^2.2.0" - tslib "^2.6.2" - -"@smithy/eventstream-codec@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-2.0.7.tgz#564ed3709d89c9cdad62e4f85d07ff926cb2d72b" - integrity sha512-sW3AhXZhmmhh0f11EOotmNNa0rjrKwnMYNKfbp3B/qigdw6foKcmFGX+HF3XGN7w7fFeEFuXr97Ok24gRj92Xg== - dependencies: - "@aws-crypto/crc32" "3.0.0" - "@smithy/types" "^2.3.1" - "@smithy/util-hex-encoding" "^2.0.0" - tslib "^2.5.0" - -"@smithy/eventstream-serde-browser@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.7.tgz#dcd432000e5642d14196ceef4364abdd2435242b" - integrity sha512-5ZKW1tUe+LD1F6dSHs+nC0vRNmMMWDJWCsw44FkhivhOB4MliGfC1ZNeO45AHD749jfJT/zcGGr2ruQT9VbThA== - dependencies: - "@smithy/eventstream-serde-universal" "^2.0.7" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/eventstream-serde-config-resolver@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.7.tgz#df1965e5bee92c964f0024e86db523b6182dee7a" - integrity sha512-0n4LPHZt6/RAHVkwzms6U2xibmizkSYLS9HzlT86WF29X56v7OTCkMF+pUFNYZamN7iRq1Z8PM48mQsBoJPaSA== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/eventstream-serde-node@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.7.tgz#ed83aa983a5e52ddf1fd51daaa477c2c762cfcee" - integrity sha512-ZkBvDIBlJ9eJx/+CC2AY8LxAndGO+Z2FOPPprmNNDbK9/pZzVLHWGwlpsPYnA9Pc0gfOu7isIJM1yPXiK70O3A== - dependencies: - "@smithy/eventstream-serde-universal" "^2.0.7" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/eventstream-serde-universal@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.7.tgz#b9986e3c08d46090391705bd632ba844c6a3c59d" - integrity sha512-CNYEzEPDIGbfvYYN7iajPY6sVZdtGvJzSbvqgH+EvismooFj8ahydGp8IKYPnd5ge5uwTATppJ2t8149tYkS7g== - dependencies: - "@smithy/eventstream-codec" "^2.0.7" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/fetch-http-handler@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-1.1.0.tgz#933694dcc0e1ade205161237a151c1c818479676" - integrity sha512-N22C9R44u5WGlcY+Wuv8EXmCAq62wWwriRAuoczMEwAIjPbvHSthyPSLqI4S7kAST1j6niWg8kwpeJ3ReAv3xg== - dependencies: - "@smithy/protocol-http" "^1.2.0" - "@smithy/querystring-builder" "^1.1.0" - "@smithy/types" "^1.2.0" - "@smithy/util-base64" "^1.1.0" - tslib "^2.5.0" - -"@smithy/fetch-http-handler@^2.1.2", "@smithy/fetch-http-handler@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.1.3.tgz#574a40085aef25edb60607dcdd6873549bd9e4c2" - integrity sha512-kUg+Ey4mJeR/3+Ponuhb1rsmsfZRwjCLvC+WcPgeI+ittretEzuWAPN+9anD0HJEoApVjHpndzxPtlncbCUJDQ== - dependencies: - "@smithy/protocol-http" "^3.0.3" - "@smithy/querystring-builder" "^2.0.7" - "@smithy/types" "^2.3.1" - "@smithy/util-base64" "^2.0.0" - tslib "^2.5.0" - -"@smithy/fetch-http-handler@^2.2.6": - version "2.2.6" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz#c3390c1c0533d024a5e2b1d1e8e778bcdcb66bf4" - integrity sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg== - dependencies: - "@smithy/protocol-http" "^3.0.9" - "@smithy/querystring-builder" "^2.0.13" - "@smithy/types" "^2.5.0" - "@smithy/util-base64" "^2.0.1" - tslib "^2.5.0" - -"@smithy/fetch-http-handler@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz#0b8e1562807fdf91fe7dd5cde620d7a03ddc10ac" - integrity sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw== - dependencies: - "@smithy/protocol-http" "^3.3.0" - "@smithy/querystring-builder" "^2.2.0" - "@smithy/types" "^2.12.0" - "@smithy/util-base64" "^2.3.0" - tslib "^2.6.2" - -"@smithy/hash-blob-browser@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.7.tgz#707a46e83114de4e608a574a98a434411231d1fa" - integrity sha512-egXnfEZRGvovv7Bedkxy31/Pj2x+4FeskHBME32zNfp2/qiAQrDVNyU/7PBGkPIvuAAZYe0Loe8fZX7jhP0u9w== - dependencies: - "@smithy/chunked-blob-reader" "^2.0.0" - "@smithy/chunked-blob-reader-native" "^2.0.0" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/hash-node@^2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.0.15.tgz#fd60ba5dd9a80f14c317bc668813a734f64786fb" - integrity sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ== - dependencies: - "@smithy/types" "^2.5.0" - "@smithy/util-buffer-from" "^2.0.0" - "@smithy/util-utf8" "^2.0.2" - tslib "^2.5.0" - -"@smithy/hash-node@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.0.7.tgz#dee88244153e04d3277ec68a6996e29ace2f4cd5" - integrity sha512-aB5lvIDP1v+ZUUS8ek3XW5xnZ6jUQ86JXqG7a5jMP6AbjAc3439mIbs6+f1EQ5MtYmrQCEtRRyvv5QofvotH0w== - dependencies: - "@smithy/types" "^2.3.1" - "@smithy/util-buffer-from" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/hash-node@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.2.0.tgz#df29e1e64811be905cb3577703b0e2d0b07fc5cc" - integrity sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g== - dependencies: - "@smithy/types" "^2.12.0" - "@smithy/util-buffer-from" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@smithy/hash-stream-node@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-2.0.7.tgz#41a2f43905341ef404fc2a378632b5049646deeb" - integrity sha512-DgTypY0jzDAvYWPDDSngTAnutv/uYokpu82r2g9ZZt9LBw86evTrvo4jo60riU/pPr9naIzMxePiGVl56ldr5w== - dependencies: - "@smithy/types" "^2.3.1" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/invalid-dependency@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz#6f4c5d809906bbb069074c5c11028a2631abed8d" - integrity sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/invalid-dependency@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.0.7.tgz#4ab6aee81a22e332195ae223bb92551ee684ac88" - integrity sha512-qVOZnHFPzQo4BS47/PANHX32Y69c0tJxKBkqTL795D/DKInqBwmBO/m1gS7v0ZQqmtCuoy2l87RflQfRY2xEIw== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/invalid-dependency@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz#ee3d8980022cb5edb514ac187d159b3e773640f0" - integrity sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/is-array-buffer@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-1.1.0.tgz#29948072da2b57575aa9898cda863932e842ab11" - integrity sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ== - dependencies: - tslib "^2.5.0" - -"@smithy/is-array-buffer@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz#8fa9b8040651e7ba0b2f6106e636a91354ff7d34" - integrity sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug== - dependencies: - tslib "^2.5.0" - -"@smithy/is-array-buffer@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" - integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== - dependencies: - tslib "^2.6.2" - -"@smithy/md5-js@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-2.0.7.tgz#4dea27b20b065857f953c74dbaa050003f48a374" - integrity sha512-2i2BpXF9pI5D1xekqUsgQ/ohv5+H//G9FlawJrkOJskV18PgJ8LiNbLiskMeYt07yAsSTZR7qtlcAaa/GQLWww== - dependencies: - "@smithy/types" "^2.3.1" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/middleware-content-length@^2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz#cd419737202f66eb441a233e9e8c8bc6bbd6a6f0" - integrity sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q== - dependencies: - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/middleware-content-length@^2.0.8": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.0.9.tgz#ae8767bac02062fad05f2b218f0e525f4c16a569" - integrity sha512-2XVFsGqswxrIBi0w4Njwzb1zsbte26U513K+WPFm9z6SB/3WR5/VBVjTaTcamrXznTAqBjTwTL0Ysisv1dW0Rw== - dependencies: - "@smithy/protocol-http" "^3.0.3" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/middleware-content-length@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz#a82e97bd83d8deab69e07fea4512563bedb9461a" - integrity sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ== - dependencies: - "@smithy/protocol-http" "^3.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/middleware-endpoint@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.0.7.tgz#1a0ee7526eecdfd46f809755dcbdc372619a868b" - integrity sha512-4/L0wV7PzHEprJB0gazSTIwlW/2cCfwC9EHavUMhoCyl1tLer6CJwDbAMit1IMvwbHkwuKopueb8dFPHfpS2Pw== - dependencies: - "@smithy/middleware-serde" "^2.0.7" - "@smithy/types" "^2.3.1" - "@smithy/url-parser" "^2.0.7" - "@smithy/util-middleware" "^2.0.0" - tslib "^2.5.0" - -"@smithy/middleware-endpoint@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz#b5d065e8459216502adf3d8ccb7a589cfe1ba147" - integrity sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw== - dependencies: - "@smithy/middleware-serde" "^2.0.13" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/shared-ini-file-loader" "^2.2.4" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" - "@smithy/util-middleware" "^2.0.6" - tslib "^2.5.0" - -"@smithy/middleware-endpoint@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz#1333c58304aff4d843e8ef4b85c8cb88975dd5ad" - integrity sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ== - dependencies: - "@smithy/middleware-serde" "^2.3.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - "@smithy/url-parser" "^2.2.0" - "@smithy/util-middleware" "^2.2.0" - tslib "^2.6.2" - -"@smithy/middleware-retry@^2.0.20": - version "2.0.20" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz#19f18ead244f609acc15481219cb8c944fb4620e" - integrity sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg== - dependencies: - "@smithy/node-config-provider" "^2.1.5" - "@smithy/protocol-http" "^3.0.9" - "@smithy/service-error-classification" "^2.0.6" - "@smithy/types" "^2.5.0" - "@smithy/util-middleware" "^2.0.6" - "@smithy/util-retry" "^2.0.6" - tslib "^2.5.0" - uuid "^8.3.2" - -"@smithy/middleware-retry@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.0.10.tgz#64034eaef099bdd8ccd28545afa79b4a9d45b8aa" - integrity sha512-VwAQOR5Rh/y9BzUgb5DzUk7qYBiMZu3pEQa5EwwAf/F7lpMuNildGrAxtDmsXk90490FJwa6LyFknXP3kO5BnA== - dependencies: - "@smithy/node-config-provider" "^2.0.10" - "@smithy/protocol-http" "^3.0.3" - "@smithy/service-error-classification" "^2.0.0" - "@smithy/types" "^2.3.1" - "@smithy/util-middleware" "^2.0.0" - "@smithy/util-retry" "^2.0.0" - tslib "^2.5.0" - uuid "^8.3.2" - -"@smithy/middleware-retry@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz#d6fdce94f2f826642c01b4448e97a509c4556ede" - integrity sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA== - dependencies: - "@smithy/node-config-provider" "^2.3.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/service-error-classification" "^2.1.5" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - "@smithy/util-middleware" "^2.2.0" - "@smithy/util-retry" "^2.2.0" - tslib "^2.6.2" - uuid "^9.0.1" - -"@smithy/middleware-serde@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz#1d105ff5ffee5563c453a8546480182912cd169b" - integrity sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/middleware-serde@^2.0.6", "@smithy/middleware-serde@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.0.7.tgz#007a47ec93cf68b812b33591c49e53238f4d181e" - integrity sha512-tOldis4PUNafdGErLZ+33p9Pf3MmTlLa176X321Z6ZaCf1XNEow9m3T5vXrcHErVAvjPG0mp3l54J94HnPc+rQ== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/middleware-serde@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz#a7615ba646a88b6f695f2d55de13d8158181dd13" - integrity sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/middleware-stack@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-1.1.0.tgz#04edd33b5db48d880b9942c38459f193144fa533" - integrity sha512-XynYiIvXNea2BbLcppvpNK0zu8o2woJqgnmxqYTn4FWagH/Hr2QIk8LOsUz7BIJ4tooFhmx8urHKCdlPbbPDCA== - dependencies: - tslib "^2.5.0" - -"@smithy/middleware-stack@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.0.0.tgz#cd9f442c2788b1ef0ea6b32236d80c76b3c342e9" - integrity sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ== - dependencies: - tslib "^2.5.0" - -"@smithy/middleware-stack@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz#e462bb3b33a9d3a29b80e8a7e13b8ba4726967c9" - integrity sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/middleware-stack@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz#3fb49eae6313f16f6f30fdaf28e11a7321f34d9f" - integrity sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/node-config-provider@^2.0.10", "@smithy/node-config-provider@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.0.10.tgz#08a46f05fd41069f455f620cd41b29d5758c7252" - integrity sha512-e5MiLH5Eu+BbYsmhZIkvUKCzite6JCBPL75PNjlRK2TWvSpfp19hNf2SiJIQbPalcFj5zlyBvtcEkF1sfYIdhg== - dependencies: - "@smithy/property-provider" "^2.0.8" - "@smithy/shared-ini-file-loader" "^2.0.9" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/node-config-provider@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz#f4be47e87c55791bf07c86c8e41383016753153f" - integrity sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw== - dependencies: - "@smithy/property-provider" "^2.0.14" - "@smithy/shared-ini-file-loader" "^2.2.4" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/node-config-provider@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz#9fac0c94a14c5b5b8b8fa37f20c310a844ab9922" - integrity sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg== - dependencies: - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/node-http-handler@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-1.1.0.tgz#887cee930b520e08043c9f41e463f8d8f5dae127" - integrity sha512-d3kRriEgaIiGXLziAM8bjnaLn1fthCJeTLZIwEIpzQqe6yPX0a+yQoLCTyjb2fvdLwkMoG4p7THIIB5cj5lkbg== - dependencies: - "@smithy/abort-controller" "^1.1.0" - "@smithy/protocol-http" "^1.2.0" - "@smithy/querystring-builder" "^1.1.0" - "@smithy/types" "^1.2.0" - tslib "^2.5.0" - -"@smithy/node-http-handler@^2.1.2", "@smithy/node-http-handler@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.1.3.tgz#6b5ecbd6c9e66bd7d9fb760a2fc302ad2da6266e" - integrity sha512-TGkgpx68SqvbspVHaG3iwqP2mKYOT4whiq7Kv2X9v+InngL4MkpH3LQ0Dk7kbloahZr+hAOyb6s8D7T8TXRrzA== - dependencies: - "@smithy/abort-controller" "^2.0.7" - "@smithy/protocol-http" "^3.0.3" - "@smithy/querystring-builder" "^2.0.7" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/node-http-handler@^2.1.9": - version "2.1.9" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz#903c353dcd58990ea46e2793a10160004e2e09e4" - integrity sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA== - dependencies: - "@smithy/abort-controller" "^2.0.13" - "@smithy/protocol-http" "^3.0.9" - "@smithy/querystring-builder" "^2.0.13" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/node-http-handler@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz#7b5e0565dd23d340380489bd5fe4316d2bed32de" - integrity sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA== - dependencies: - "@smithy/abort-controller" "^2.2.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/querystring-builder" "^2.2.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/property-provider@^2.0.0", "@smithy/property-provider@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.0.8.tgz#071a03a03e6e042f521f59fdcf3d4bc95db4f08b" - integrity sha512-oaaP/i7bGG8XbxG9Kx4PZh83iJ2jo/vt8RmJdi9hmc8APBaW1HGDperVXDCyPQdVYXmiqrtxc/rPImyBma1G3A== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/property-provider@^2.0.14": - version "2.0.14" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.0.14.tgz#142e018ee624ae0c966c72886d4fb5d708f086d6" - integrity sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/property-provider@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.2.0.tgz#37e3525a3fa3e11749f86a4f89f0fd7765a6edb0" - integrity sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/protocol-http@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-1.2.0.tgz#a554e4dabb14508f0bc2cdef9c3710e2b294be04" - integrity sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q== - dependencies: - "@smithy/types" "^1.2.0" - tslib "^2.5.0" - -"@smithy/protocol-http@^3.0.2", "@smithy/protocol-http@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.0.3.tgz#4f79cd1354db860b98d1c4f5d6ab180cefe0132d" - integrity sha512-UGfmQNdijlFV+UzgdRyfe05S5vLDdcdkvNcxhGvQ+Er7TjUkZSxjukQB9VXtT8oTHztgOMX74DDlPBsVzZR5Pg== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/protocol-http@^3.0.9": - version "3.0.9" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.0.9.tgz#a1d973394b6da093bc8fd71556b589190352310d" - integrity sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/protocol-http@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.3.0.tgz#a37df7b4bb4960cdda560ce49acfd64c455e4090" - integrity sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/querystring-builder@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-1.1.0.tgz#de6306104640ade34e59be33949db6cc64aa9d7f" - integrity sha512-gDEi4LxIGLbdfjrjiY45QNbuDmpkwh9DX4xzrR2AzjjXpxwGyfSpbJaYhXARw9p17VH0h9UewnNQXNwaQyYMDA== - dependencies: - "@smithy/types" "^1.2.0" - "@smithy/util-uri-escape" "^1.1.0" - tslib "^2.5.0" - -"@smithy/querystring-builder@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz#3eae3ce5a99df9c3c70214ac90b6f3c4ff2a5341" - integrity sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw== - dependencies: - "@smithy/types" "^2.5.0" - "@smithy/util-uri-escape" "^2.0.0" - tslib "^2.5.0" - -"@smithy/querystring-builder@^2.0.6", "@smithy/querystring-builder@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.0.7.tgz#576d0a9fa5a2ae4305cbc38bb6facbcf4243acdc" - integrity sha512-RPHnqt4iH1Kwp1Zbf4gJI88hZiynEZjE5hEWJNBmKqCe1Q6v7HBLtaovTaiuYaMEmPyb2KxOi3lISAdT6uuPqw== - dependencies: - "@smithy/types" "^2.3.1" - "@smithy/util-uri-escape" "^2.0.0" - tslib "^2.5.0" - -"@smithy/querystring-builder@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz#22937e19fcd0aaa1a3e614ef8cb6f8e86756a4ef" - integrity sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A== - dependencies: - "@smithy/types" "^2.12.0" - "@smithy/util-uri-escape" "^2.2.0" - tslib "^2.6.2" - -"@smithy/querystring-parser@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz#9825239eceb2ab6a8906d7a3fa8241d20794b5a7" - integrity sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/querystring-parser@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.0.7.tgz#e61979a498f62f5cc18ab340e8f5d41f57de8f5e" - integrity sha512-Cwi/Hgs73nbLKfgH7dXAxzvDxyTrK+BLrlAd0KXU7xcBR94V132nvxoq39BMWckYAPmnMwxCwq8uusNH4Dnagw== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/querystring-parser@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz#24a5633f4b3806ff2888d4c2f4169e105fdffd79" - integrity sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/service-error-classification@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-1.1.0.tgz#264dd432ae513b3f2ad9fc6f461deda8c516173c" - integrity sha512-OCTEeJ1igatd5kFrS2VDlYbainNNpf7Lj1siFOxnRWqYOP9oNvC5HOJBd3t+Z8MbrmehBtuDJ2QqeBsfeiNkww== - -"@smithy/service-error-classification@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.0.0.tgz#bbce07c9c529d9333d40db881fd4a1795dd84892" - integrity sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw== - -"@smithy/service-error-classification@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz#173c0067c9fce7641c4634e5f2f7e0b6fe11a051" - integrity sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg== - dependencies: - "@smithy/types" "^2.5.0" - -"@smithy/service-error-classification@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz#0568a977cc0db36299d8703a5d8609c1f600c005" - integrity sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ== - dependencies: - "@smithy/types" "^2.12.0" - -"@smithy/shared-ini-file-loader@^2.0.6", "@smithy/shared-ini-file-loader@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.0.9.tgz#9507d9f941a2aa8d34aca51d22158bf02ae41cf2" - integrity sha512-vBLgJI+Qpz1TZ0W2kUBOmG2Q+geVEhiXE99UX02+UFag2WzOQ6frvV6rpadwJu0uwF02GG620NbiKGboqZ19YA== - dependencies: - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/shared-ini-file-loader@^2.2.4": - version "2.2.4" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz#ed86a5afa76025ef827d84f5e07bb757174fe7c8" - integrity sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/shared-ini-file-loader@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz#1636d6eb9bff41e36ac9c60364a37fd2ffcb9947" - integrity sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/signature-v4@^2.0.0": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.0.7.tgz#46ceeb3b9f0496b63a28c555935fcb56d0cdc3ae" - integrity sha512-qNCJpyhRWxT5RWmeSo/Zv+miQ60Y/D2JmPdFw7v2WpPVxVK7JDpqUbvq0QYE+dBGPX/uagAkE3NvJUcn0fTE3A== - dependencies: - "@smithy/eventstream-codec" "^2.0.7" - "@smithy/is-array-buffer" "^2.0.0" - "@smithy/types" "^2.3.1" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-middleware" "^2.0.0" - "@smithy/util-uri-escape" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/signature-v4@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.3.0.tgz#c30dd4028ae50c607db99459981cce8cdab7a3fd" - integrity sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q== - dependencies: - "@smithy/is-array-buffer" "^2.2.0" - "@smithy/types" "^2.12.0" - "@smithy/util-hex-encoding" "^2.2.0" - "@smithy/util-middleware" "^2.2.0" - "@smithy/util-uri-escape" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@smithy/smithy-client@^1.0.3": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-1.1.0.tgz#a546a41cc377c836756b6fa749fc9ae292472985" - integrity sha512-j32SGgVhv2G9nBTmel9u3OXux8KG20ssxuFakJrEeDug3kqbl1qrGzVLCe+Eib402UDtA0Sp1a4NZ2SEXDBxag== - dependencies: - "@smithy/middleware-stack" "^1.1.0" - "@smithy/types" "^1.2.0" - "@smithy/util-stream" "^1.1.0" - tslib "^2.5.0" - -"@smithy/smithy-client@^2.1.15": - version "2.1.15" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.1.15.tgz#8a6e142f52fe253fd7f868eedce0e6d308415098" - integrity sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ== - dependencies: - "@smithy/middleware-stack" "^2.0.7" - "@smithy/types" "^2.5.0" - "@smithy/util-stream" "^2.0.20" - tslib "^2.5.0" - -"@smithy/smithy-client@^2.1.3": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.1.4.tgz#a0d08196ee31e6ed758e60c58a572658f968867e" - integrity sha512-KRQvYYjEGqvmwnKSAZ8EL0hZvPxGQMYbAKS/AMGq2fuRmwAlinSVJ/fkIs65bZp2oYjcskd1ZgKcP+2UDjNPTQ== - dependencies: - "@smithy/middleware-stack" "^2.0.0" - "@smithy/types" "^2.3.1" - "@smithy/util-stream" "^2.0.10" - tslib "^2.5.0" - -"@smithy/smithy-client@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.5.1.tgz#0fd2efff09dc65500d260e590f7541f8a387eae3" - integrity sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ== - dependencies: - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-stack" "^2.2.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/types" "^2.12.0" - "@smithy/util-stream" "^2.2.0" - tslib "^2.6.2" - -"@smithy/types@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-1.2.0.tgz#9dc65767b0ee3d6681704fcc67665d6fc9b6a34e" - integrity sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA== - dependencies: - tslib "^2.5.0" - -"@smithy/types@^2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.12.0.tgz#c44845f8ba07e5e8c88eda5aed7e6a0c462da041" - integrity sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw== - dependencies: - tslib "^2.6.2" - -"@smithy/types@^2.2.2": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.3.0.tgz#a5c3869465f384fd4d811b2f1f37779e069ef06e" - integrity sha512-pJce3rd39MElkV57UTPAoSYAApjQLELUxjU5adHNLYk9gnPvyIGbJNJTZVVFu00BrgZH3W/cQe8QuFcknDyodQ== - dependencies: - tslib "^2.5.0" - -"@smithy/types@^2.3.0", "@smithy/types@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.3.1.tgz#25e8c353ee7a8611488a2cd41811c5a32a9dbcdc" - integrity sha512-cS48e4Yawb6pGakj7DBJUIPFIkqnUWyXTe2ndPRNagD73b6kEJqTc8bhTyfUve0A+sijK256UKE0J1juAfCeDA== - dependencies: - tslib "^2.5.0" - -"@smithy/types@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.5.0.tgz#f1bd5b906e7d3c6fd559b9b4f05e4707c7039180" - integrity sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA== - dependencies: - tslib "^2.5.0" - -"@smithy/url-parser@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.0.13.tgz#1e5f2812c1d5a78ae69fc248487bdd8a8902afc5" - integrity sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA== - dependencies: - "@smithy/querystring-parser" "^2.0.13" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/url-parser@^2.0.6", "@smithy/url-parser@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.0.7.tgz#a744d2a441d608e274f51f6cb0eb6bad6d52bbf6" - integrity sha512-SwMl1Lq3yFR2hzhwWYKg04uJHpfcXWMBPycm4Z8GkLI6Dw7rJNDApEbMtujlYw6pVP2WKbrpaGHjQ9MdP92kMQ== - dependencies: - "@smithy/querystring-parser" "^2.0.7" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/url-parser@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.2.0.tgz#6fcda6116391a4f61fef5580eb540e128359b3c0" - integrity sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ== - dependencies: - "@smithy/querystring-parser" "^2.2.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/util-base64@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-1.1.0.tgz#2b1854013bfd11aefdd0c035eae789d7c4e56a1e" - integrity sha512-FpYmDmVbOXAxqvoVCwqehUN0zXS+lN8V7VS9O7I8MKeVHdSTsZzlwiMEvGoyTNOXWn8luF4CTDYgNHnZViR30g== - dependencies: - "@smithy/util-buffer-from" "^1.1.0" - tslib "^2.5.0" - -"@smithy/util-base64@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.0.0.tgz#1beeabfb155471d1d41c8d0603be1351f883c444" - integrity sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA== - dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-base64@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.0.1.tgz#57f782dafc187eddea7c8a1ff2a7c188ed1a02c4" - integrity sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ== - dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-base64@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.3.0.tgz#312dbb4d73fb94249c7261aee52de4195c2dd8e2" - integrity sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw== - dependencies: - "@smithy/util-buffer-from" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@smithy/util-body-length-browser@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz#5447853003b4c73da3bc5f3c5e82c21d592d1650" - integrity sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg== - dependencies: - tslib "^2.5.0" - -"@smithy/util-body-length-browser@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz#25620645c6b62b42594ef4a93b66e6ab70e27d2c" - integrity sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w== - dependencies: - tslib "^2.6.2" - -"@smithy/util-body-length-node@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz#313a5f7c5017947baf5fa018bfc22628904bbcfa" - integrity sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw== - dependencies: - tslib "^2.5.0" - -"@smithy/util-body-length-node@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz#d065a9b5e305ff899536777bbfe075cdc980136f" - integrity sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw== - dependencies: - tslib "^2.6.2" - -"@smithy/util-buffer-from@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-1.1.0.tgz#a000bd9f95c0e8d5b0edb0112f2a586daa5bed49" - integrity sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw== - dependencies: - "@smithy/is-array-buffer" "^1.1.0" - tslib "^2.5.0" - -"@smithy/util-buffer-from@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz#7eb75d72288b6b3001bc5f75b48b711513091deb" - integrity sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw== - dependencies: - "@smithy/is-array-buffer" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-buffer-from@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" - integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== - dependencies: - "@smithy/is-array-buffer" "^2.2.0" - tslib "^2.6.2" - -"@smithy/util-config-provider@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz#4dd6a793605559d94267312fd06d0f58784b4c38" - integrity sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg== - dependencies: - tslib "^2.5.0" - -"@smithy/util-config-provider@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz#bc79f99562d12a1f8423100ca662a6fb07cde943" - integrity sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ== - dependencies: - tslib "^2.6.2" - -"@smithy/util-defaults-mode-browser@^2.0.19": - version "2.0.19" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz#fe437b62e589812cf97b269e689b18f7bcb1d008" - integrity sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw== - dependencies: - "@smithy/property-provider" "^2.0.14" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - bowser "^2.11.0" - tslib "^2.5.0" - -"@smithy/util-defaults-mode-browser@^2.0.7": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.8.tgz#3067bcb82976be628c737d1318df51ef37af82e4" - integrity sha512-8znx01mkmfKxhiSB2bOF5eMutuCLMd8m2Kh0ulRp8vgzhwRLDJoU6aHSEUoNptbuTAtiFf4u0gpkYC2XfbWwuA== - dependencies: - "@smithy/property-provider" "^2.0.8" - "@smithy/types" "^2.3.1" - bowser "^2.11.0" - tslib "^2.5.0" - -"@smithy/util-defaults-mode-browser@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz#9db31416daf575d2963c502e0528cfe8055f0c4e" - integrity sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw== - dependencies: - "@smithy/property-provider" "^2.2.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - bowser "^2.11.0" - tslib "^2.6.2" - -"@smithy/util-defaults-mode-node@^2.0.25": - version "2.0.25" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz#76a62b8a6602b1414a0af5d0ac11fa1dfdadb308" - integrity sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA== - dependencies: - "@smithy/config-resolver" "^2.0.18" - "@smithy/credential-provider-imds" "^2.1.1" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/property-provider" "^2.0.14" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/util-defaults-mode-node@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.10.tgz#7497a64a052685e9ce00383e1214a84c001c7fe2" - integrity sha512-QUcUckL4ZqDFVwLnh7zStRUnXtTC6hcJZ4FmMqnxlPcL33Rko0sMQwrMDnMdzF3rS3wvqugAaq3zzop1HCluvw== - dependencies: - "@smithy/config-resolver" "^2.0.8" - "@smithy/credential-provider-imds" "^2.0.10" - "@smithy/node-config-provider" "^2.0.10" - "@smithy/property-provider" "^2.0.8" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@smithy/util-defaults-mode-node@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz#4613210a3d107aadb3f85bd80cb71c796dd8bf0a" - integrity sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA== - dependencies: - "@smithy/config-resolver" "^2.2.0" - "@smithy/credential-provider-imds" "^2.3.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/smithy-client" "^2.5.1" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/util-endpoints@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz#2b18aa7175e956e839be7aad5c5f0e0f6016d10d" - integrity sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ== - dependencies: - "@smithy/node-config-provider" "^2.1.5" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/util-endpoints@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz#b8b805f47e8044c158372f69b88337703117665d" - integrity sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ== - dependencies: - "@smithy/node-config-provider" "^2.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/util-hex-encoding@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-1.1.0.tgz#b5ba919aa076a3fd5e93e368e34ae2b732fa2090" - integrity sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg== - dependencies: - tslib "^2.5.0" - -"@smithy/util-hex-encoding@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz#0aa3515acd2b005c6d55675e377080a7c513b59e" - integrity sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA== - dependencies: - tslib "^2.5.0" - -"@smithy/util-hex-encoding@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz#87edb7c88c2f422cfca4bb21f1394ae9602c5085" - integrity sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ== - dependencies: - tslib "^2.6.2" - -"@smithy/util-middleware@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.0.0.tgz#706681d4a1686544a2275f68266304233f372c99" - integrity sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA== - dependencies: - tslib "^2.5.0" - -"@smithy/util-middleware@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.0.6.tgz#fbc23119436baaa1494c11803abaabef8cb3e2c4" - integrity sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA== - dependencies: - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/util-middleware@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.2.0.tgz#80cfad40f6cca9ffe42a5899b5cb6abd53a50006" - integrity sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw== - dependencies: - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/util-retry@^1.0.3": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-1.1.0.tgz#f6e62ec7d7d30f1dd9608991730ba7a86e445047" - integrity sha512-ygQW5HBqYXpR3ua09UciS0sL7UGJzGiktrKkOuEJwARoUuzz40yaEGU6xd9Gs7KBmAaFC8gMfnghHtwZ2nyBCQ== - dependencies: - "@smithy/service-error-classification" "^1.1.0" - tslib "^2.5.0" - -"@smithy/util-retry@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.0.0.tgz#7ac5d5f12383a9d9b2a43f9ff25f3866c8727c24" - integrity sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg== - dependencies: - "@smithy/service-error-classification" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-retry@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.0.6.tgz#c887c2c3e356661c1336efb3f085e32fce777124" - integrity sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw== - dependencies: - "@smithy/service-error-classification" "^2.0.6" - "@smithy/types" "^2.5.0" - tslib "^2.5.0" - -"@smithy/util-retry@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.2.0.tgz#e8e019537ab47ba6b2e87e723ec51ee223422d85" - integrity sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g== - dependencies: - "@smithy/service-error-classification" "^2.1.5" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - -"@smithy/util-stream@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-1.1.0.tgz#3f174223bef33af85aa39261fccb908648e13af9" - integrity sha512-w3lsdGsntaLQIrwDWJkIFKrFscgZXwU/oxsse09aSTNv5TckPhDeYea3LhsDrU5MGAG3vprhVZAKr33S45coVA== - dependencies: - "@smithy/fetch-http-handler" "^1.1.0" - "@smithy/node-http-handler" "^1.1.0" - "@smithy/types" "^1.2.0" - "@smithy/util-base64" "^1.1.0" - "@smithy/util-buffer-from" "^1.1.0" - "@smithy/util-hex-encoding" "^1.1.0" - "@smithy/util-utf8" "^1.1.0" - tslib "^2.5.0" - -"@smithy/util-stream@^2.0.10", "@smithy/util-stream@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.0.10.tgz#3671b107e38b06c2d1a2976424ee4e2272e1c506" - integrity sha512-2EgK5cBiv9OaDmhSXmsZY8ZByBl1dg/Tbc51iBJ5GkLGVYhaA6/1l6vHHV41m4Im3D0XfZV1tmeLlQgmRnYsTQ== - dependencies: - "@smithy/fetch-http-handler" "^2.1.3" - "@smithy/node-http-handler" "^2.1.3" - "@smithy/types" "^2.3.1" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-buffer-from" "^2.0.0" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-stream@^2.0.20": - version "2.0.20" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.0.20.tgz#0dbff46b07856b608512688437e685c638d75431" - integrity sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg== - dependencies: - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/types" "^2.5.0" - "@smithy/util-base64" "^2.0.1" - "@smithy/util-buffer-from" "^2.0.0" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-utf8" "^2.0.2" - tslib "^2.5.0" - -"@smithy/util-stream@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.2.0.tgz#b1279e417992a0f74afa78d7501658f174ed7370" - integrity sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA== - dependencies: - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/types" "^2.12.0" - "@smithy/util-base64" "^2.3.0" - "@smithy/util-buffer-from" "^2.2.0" - "@smithy/util-hex-encoding" "^2.2.0" - "@smithy/util-utf8" "^2.3.0" - tslib "^2.6.2" - -"@smithy/util-uri-escape@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-1.1.0.tgz#a8c5edaf19c0efdb9b51661e840549cf600a1808" - integrity sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w== - dependencies: - tslib "^2.5.0" - -"@smithy/util-uri-escape@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz#19955b1a0f517a87ae77ac729e0e411963dfda95" - integrity sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw== - dependencies: - tslib "^2.5.0" - -"@smithy/util-uri-escape@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz#56f5764051a33b67bc93fdd2a869f971b0635406" - integrity sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA== - dependencies: - tslib "^2.6.2" - -"@smithy/util-utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-1.1.0.tgz#b791ab1e3f694374edfe22811e39dd8424a1be69" - integrity sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A== - dependencies: - "@smithy/util-buffer-from" "^1.1.0" - tslib "^2.5.0" - -"@smithy/util-utf8@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.0.0.tgz#b4da87566ea7757435e153799df9da717262ad42" - integrity sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ== - dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-utf8@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.0.2.tgz#626b3e173ad137208e27ed329d6bea70f4a1a7f7" - integrity sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA== - dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-utf8@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" - integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== - dependencies: - "@smithy/util-buffer-from" "^2.2.0" - tslib "^2.6.2" - -"@smithy/util-waiter@^2.0.6": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-2.0.7.tgz#a0f777265d7177a4a58a968c0c10511484582f74" - integrity sha512-lIY4GOmrSwMiGHhm++1ea0MdKx5y4V39ue4eNg4yxmip1hiuCLxkfXGZVLh0JPxBxAzbQw+E/5TPfY4w/RBkNw== - dependencies: - "@smithy/abort-controller" "^2.0.7" - "@smithy/types" "^2.3.1" - tslib "^2.5.0" - -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - -"@tootallnate/quickjs-emscripten@^0.23.0": - version "0.23.0" - resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" - integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== - -"@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@tufjs/canonical-json@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz#eade9fd1f537993bc1f0949f3aea276ecc4fab31" - integrity sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ== - -"@tufjs/models@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-1.0.4.tgz#5a689630f6b9dbda338d4b208019336562f176ef" - integrity sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A== - dependencies: - "@tufjs/canonical-json" "1.0.0" - minimatch "^9.0.0" - -"@types/babel__core@^7.1.14": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" - integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" - integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== - dependencies: - "@babel/types" "^7.20.7" - -"@types/diff@^5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.3.tgz#1f89e49ff83b5d200d78964fb896c68498ce1828" - integrity sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A== - -"@types/fs-extra@11.0.1": - version "11.0.1" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.1.tgz#f542ec47810532a8a252127e6e105f487e0a6ea5" - integrity sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA== - dependencies: - "@types/jsonfile" "*" - "@types/node" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" - integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@29.4.0": - version "29.4.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.4.0.tgz#a8444ad1704493e84dbf07bb05990b275b3b9206" - integrity sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/jest@^29.4.0": - version "29.5.4" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.4.tgz#9d0a16edaa009a71e6a71a999acd582514dab566" - integrity sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/js-yaml@4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" - integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== - -"@types/json-schema@^7.0.12": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/json-schema@^7.0.9": - version "7.0.12" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" - integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/jsonfile@*": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.1.tgz#ac84e9aefa74a2425a0fb3012bdea44f58970f1b" - integrity sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png== - dependencies: - "@types/node" "*" - -"@types/minimatch@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" - integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== - -"@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== - -"@types/mri@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/mri/-/mri-1.1.1.tgz#bb2fb3f21068998e816fcdd2bcefd4233a711e6e" - integrity sha512-nJOuiTlsvmClSr3+a/trTSx4DTuY/VURsWGKSf/eeavh0LRMqdsK60ti0TlwM5iHiGOK3/Ibkxsbr7i9rzGreA== - -"@types/node@*": - version "20.5.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.9.tgz#a70ec9d8fa0180a314c3ede0e20ea56ff71aed9a" - integrity sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ== - -"@types/node@18.14.0": - version "18.14.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.0.tgz#94c47b9217bbac49d4a67a967fdcdeed89ebb7d0" - integrity sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A== - -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - -"@types/promptly@3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/promptly/-/promptly-3.0.2.tgz#598674d4b78b3dffcb2d756b344f28a2cf7459f8" - integrity sha512-cJFwE7d8GlraY+DJoZ0NhpoJ55slkcbNsGIKMY0H+5h0xaGqXBqXz9zeu+Ey9KfN1UiHQXiIT0GroxyPYMPP/w== - dependencies: - "@types/node" "*" - -"@types/semver@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" - integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== - -"@types/semver@^7.3.12": - version "7.5.1" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367" - integrity sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg== - -"@types/sinon@^10.0.10": - version "10.0.16" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.16.tgz#4bf10313bd9aa8eef1e50ec9f4decd3dd455b4d3" - integrity sha512-j2Du5SYpXZjJVJtXBokASpPRj+e2z+VUhCPHmM6WMfe3dpHu6iVKJMU6AiBcMp/XTAYnEj6Wc1trJUWwZ0QaAQ== - dependencies: - "@types/sinonjs__fake-timers" "*" - -"@types/sinonjs__fake-timers@*": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" - integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/triple-beam@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" - integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== - -"@types/uuid@9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" - integrity sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q== - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz#24b8b4a952f3c615fe070e3c461dd852b5056734" - integrity sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw== - dependencies: - "@typescript-eslint/scope-manager" "5.53.0" - "@typescript-eslint/type-utils" "5.53.0" - "@typescript-eslint/utils" "5.53.0" - debug "^4.3.4" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.53.0.tgz#a1f2b9ae73b83181098747e96683f1b249ecab52" - integrity sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ== - dependencies: - "@typescript-eslint/scope-manager" "5.53.0" - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/typescript-estree" "5.53.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz#42b54f280e33c82939275a42649701024f3fafef" - integrity sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w== - dependencies: - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/visitor-keys" "5.53.0" - -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/type-utils@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz#41665449935ba9b4e6a1ba6e2a3f4b2c31d6cf97" - integrity sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw== - dependencies: - "@typescript-eslint/typescript-estree" "5.53.0" - "@typescript-eslint/utils" "5.53.0" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f" - integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A== - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/typescript-estree@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz#bc651dc28cf18ab248ecd18a4c886c744aebd690" - integrity sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w== - dependencies: - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/visitor-keys" "5.53.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.53.0.tgz#e55eaad9d6fffa120575ffaa530c7e802f13bce8" - integrity sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g== - dependencies: - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.53.0" - "@typescript-eslint/types" "5.53.0" - "@typescript-eslint/typescript-estree" "5.53.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - semver "^7.3.7" - -"@typescript-eslint/utils@^5.10.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.53.0": - version "5.53.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f" - integrity sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w== - dependencies: - "@typescript-eslint/types" "5.53.0" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@xmldom/xmldom@^0.8.10": - version "0.8.10" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" - integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== - -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - -"@yarnpkg/parsers@3.0.0-rc.46": - version "3.0.0-rc.46" - resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz#03f8363111efc0ea670e53b0282cd3ef62de4e01" - integrity sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q== - dependencies: - js-yaml "^3.10.0" - tslib "^2.4.0" - -"@zkochan/js-yaml@0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" - integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== - dependencies: - argparse "^2.0.1" - -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abbrev@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.4.1, acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -add-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" - integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== - -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -agent-base@^7.0.2, agent-base@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" - integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== - dependencies: - debug "^4.3.4" - -agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@8.12.0, ajv@^8.0.1, ajv@^8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-colors@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-escapes@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-5.0.0.tgz#b6a0caf0eef0c41af190e9a749e0c00ec04bb2a6" - integrity sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA== - dependencies: - type-fest "^1.0.2" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-sequence-parser@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" - integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -ansi-styles@^6.0.0, ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -anymatch@^3.0.3, anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - -archiver-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" - integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== - dependencies: - glob "^7.1.4" - graceful-fs "^4.2.0" - lazystream "^1.0.0" - lodash.defaults "^4.2.0" - lodash.difference "^4.5.0" - lodash.flatten "^4.4.0" - lodash.isplainobject "^4.0.6" - lodash.union "^4.6.0" - normalize-path "^3.0.0" - readable-stream "^2.0.0" - -archiver-utils@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" - integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== - dependencies: - glob "^7.2.3" - graceful-fs "^4.2.0" - lazystream "^1.0.0" - lodash.defaults "^4.2.0" - lodash.difference "^4.5.0" - lodash.flatten "^4.4.0" - lodash.isplainobject "^4.0.6" - lodash.union "^4.6.0" - normalize-path "^3.0.0" - readable-stream "^3.6.0" - -archiver@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" - integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== - dependencies: - archiver-utils "^2.1.0" - async "^3.2.4" - buffer-crc32 "^0.2.1" - readable-stream "^3.6.0" - readdir-glob "^1.1.2" - tar-stream "^2.2.0" - zip-stream "^4.1.0" - -are-we-there-yet@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" - integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== - dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" - -array-differ@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" - integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== - -array-ify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" - integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== - -array-includes@^3.1.6: - version "3.1.7" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" - integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-string "^1.0.7" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.flat@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -arraybuffer.prototype.slice@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" - integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" - is-shared-array-buffer "^1.0.2" - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== - -arrify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -ast-types@^0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" - integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== - dependencies: - tslib "^2.0.1" - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -async@^3.2.3, async@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - -aws-cdk-lib@2.93.0: - version "2.93.0" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.93.0.tgz#545bc0072bc0f2e27cb0fecb0c9e54de29b10731" - integrity sha512-kKbcKkts272Ju5xjGKI3pXTOpiJxW4OQbDF8Vmw/NIkkuJLo8GlRCFfeOfoN/hilvlYQgENA67GCgSWccbvu7w== - dependencies: - "@aws-cdk/asset-awscli-v1" "^2.2.200" - "@aws-cdk/asset-kubectl-v20" "^2.1.2" - "@aws-cdk/asset-node-proxy-agent-v6" "^2.0.1" - "@balena/dockerignore" "^1.0.2" - case "1.6.3" - fs-extra "^11.1.1" - ignore "^5.2.4" - jsonschema "^1.4.1" - minimatch "^3.1.2" - punycode "^2.3.0" - semver "^7.5.4" - table "^6.8.1" - yaml "1.10.2" - -aws-cdk@2.93.0: - version "2.93.0" - resolved "https://registry.yarnpkg.com/aws-cdk/-/aws-cdk-2.93.0.tgz#2749dfae6a261b14a48121bcefb90aee5de331a7" - integrity sha512-C0o7rzlXbQ3othvQ9uZamRwr741MSX/9eZ74zNJvpkX5Eitx/XoQYwUHeD+cbb4lKHMi7m2SwJfx3yOEkpu9OQ== - optionalDependencies: - fsevents "2.3.2" - -aws-lambda@1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/aws-lambda/-/aws-lambda-1.0.7.tgz#c6b674df47458b5ecd43ab734899ad2e2d457013" - integrity sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w== - dependencies: - aws-sdk "^2.814.0" - commander "^3.0.2" - js-yaml "^3.14.1" - watchpack "^2.0.0-beta.10" - -aws-sdk-client-mock@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-2.1.0.tgz#58247c0fb651cddedbd093c0c06d6bc0caf82e89" - integrity sha512-JkrPrcEvQ4JwylVdQ0njYQMFVwVaZzoVc557rsCorIMjEtmrFvlVYEfIKWoYw8psU4cLjOMyqpxg65NcAn9fvQ== - dependencies: - "@types/sinon" "^10.0.10" - sinon "^14.0.2" - tslib "^2.1.0" - -aws-sdk-client-mock@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-3.0.1.tgz#f9ecc50ff5190fbe7286155e65ea99ad80f670ff" - integrity sha512-9VAzJLl8mz99KP9HjOm/93d8vznRRUTpJooPBOunRdUAnVYopCe9xmMuu7eVemu8fQ+w6rP7o5bBK1kAFkB2KQ== - dependencies: - "@types/sinon" "^10.0.10" - sinon "^16.1.3" - tslib "^2.1.0" - -aws-sdk@2.1379.0: - version "2.1379.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1379.0.tgz#2d5609a37287d25edda4395fbb89306939f98890" - integrity sha512-kziOtAtJxdgYJwhzY+uhNi/AGPrDEMHd0dEz46YR1AB5bVqjS9/SjOZHemB88QfpW11IVB/FoiIusXlGEvgq9Q== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - util "^0.12.4" - uuid "8.0.0" - xml2js "0.5.0" - -aws-sdk@^2.1442.0: - version "2.1452.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1452.0.tgz#5b91cf8a98d1a0223ddfa911d319bffb33bb1f8f" - integrity sha512-PiCNbl3DPwiQPqQwkugfO+YaDxWz1BZ8m/05R7MSsOQ1VB9eXDNESASKkz9jEdDbM4u/olZIljV6HVZ9e6044g== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - util "^0.12.4" - uuid "8.0.0" - xml2js "0.5.0" - -aws-sdk@^2.814.0: - version "2.1544.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1544.0.tgz#ba93d31d08a77e502378910d639248ce35427786" - integrity sha512-R0C9bonDL0IQ/j0tq6Xaq5weFiaiSOj6KGRseHy+78zdbP1tsG2LZSoN3J5RqjjLHA5/fTMwXO1IuW/4eCNLAg== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - util "^0.12.4" - uuid "8.0.0" - xml2js "0.6.2" - -axios@^1.0.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -babel-jest@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.4.tgz#98dbc45d1c93319c82a8ab4a478b670655dd2585" - integrity sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw== - dependencies: - "@jest/transform" "^29.6.4" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2, base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -basic-ftp@^5.0.2: - version "5.0.3" - resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.3.tgz#b14c0fe8111ce001ec913686434fe0c2fb461228" - integrity sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g== - -before-after-hook@^2.2.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" - integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== - -big-integer@^1.6.44: - version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bl@^4.0.3, bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist@^4.21.9: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@4.9.2: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== - -builtins@^5.0.0, builtins@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" - integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== - dependencies: - semver "^7.0.0" - -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - -byte-size@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.1.tgz#3424608c62d59de5bfda05d31e0313c6174842ae" - integrity sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg== - -cacache@^16.1.0: - version "16.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" - integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== - dependencies: - "@npmcli/fs" "^2.1.0" - "@npmcli/move-file" "^2.0.0" - chownr "^2.0.0" - fs-minipass "^2.1.0" - glob "^8.0.1" - infer-owner "^1.0.4" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^9.0.0" - tar "^6.1.11" - unique-filename "^2.0.0" - -cacache@^17.0.0: - version "17.1.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.1.4.tgz#b3ff381580b47e85c6e64f801101508e26604b35" - integrity sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A== - dependencies: - "@npmcli/fs" "^3.1.0" - fs-minipass "^3.0.0" - glob "^10.2.2" - lru-cache "^7.7.1" - minipass "^7.0.3" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - p-map "^4.0.0" - ssri "^10.0.0" - tar "^6.1.11" - unique-filename "^3.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0, camelcase@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001517: - version "1.0.30001528" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001528.tgz#479972fc705b996f1114336c0032418a215fd0aa" - integrity sha512-0Db4yyjR9QMNlsxh+kKWzQtkyflkG/snYheSzkjmvdEtEXB1+jt7A2HmSEiO6XIJPIbo92lHNGNySvE5pZcs5Q== - -capital-case@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669" - integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - upper-case-first "^2.0.2" - -case@1.6.3, case@^1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" - integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== - -cdk-assets@2.93.0: - version "2.93.0" - resolved "https://registry.yarnpkg.com/cdk-assets/-/cdk-assets-2.93.0.tgz#bd0af27d7fbbeb358cf7ec10a0acdb0f3dc228d3" - integrity sha512-7oPaoQ7YPZL3t1eJu3pqYIAUviVWhzjI2UyUAfJ5xPN0F/iCldQmjIwO5Dl1FSJbC2HmJTo+S6aq487ogVHZ5g== - dependencies: - "@aws-cdk/cloud-assembly-schema" "2.93.0" - "@aws-cdk/cx-api" "2.93.0" - archiver "^5.3.2" - aws-sdk "^2.1442.0" - glob "^7.2.3" - mime "^2.6.0" - yargs "^16.2.0" - -cdk-nag@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/cdk-nag/-/cdk-nag-2.5.2.tgz#b7a03dde19abb92eee5b001f7e5b0a638cfa70ae" - integrity sha512-tIqIunchUtqP8a7efOtgutyfnJzimpTRtMfE8+4CcEm6Ze4CisJ+4qRPoVH1rmOxqHMNvjZh5TSTxrhRCEBwjw== - -chalk@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -change-case@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12" - integrity sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A== - dependencies: - camel-case "^4.1.2" - capital-case "^1.0.4" - constant-case "^3.0.4" - dot-case "^3.0.4" - header-case "^2.0.4" - no-case "^3.0.4" - param-case "^3.0.4" - pascal-case "^3.1.2" - path-case "^3.0.4" - sentence-case "^3.0.4" - snake-case "^3.0.4" - tslib "^2.0.3" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -charenc@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== - -chokidar@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -ci-info@^3.2.0, ci-info@^3.6.1: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -cjs-module-lexer@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" - integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@3.1.0, cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" - integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== - dependencies: - restore-cursor "^4.0.0" - -cli-spinners@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" - integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== - -cli-spinners@^2.5.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.0.tgz#5881d0ad96381e117bbe07ad91f2008fe6ffd8db" - integrity sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g== - -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - -cli-truncate@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" - integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== - dependencies: - slice-ansi "^5.0.0" - string-width "^5.0.0" - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-deep@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -clone@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - -cmd-shim@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" - integrity sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q== - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -codemaker@^1.75.0: - version "1.88.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.88.0.tgz#ef8f1a6b428ba36339147bfe7f3f4a65b1c7a562" - integrity sha512-/7+1mPQCEFmBm9zhf5blMiqirCcXNwulb8dozu2LVsDLgnafPt1h2eg/OwvyrqSMWUnsIFetAssKVP2gE66MPQ== - dependencies: - camelcase "^6.3.0" - decamelize "^5.0.1" - fs-extra "^10.1.0" - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^1.9.0, color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorette@^2.0.19, colorette@^2.0.20: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -colors@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - -columnify@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" - integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== - dependencies: - strip-ansi "^6.0.1" - wcwidth "^1.0.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" - integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== - -commander@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" - integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== - -commander@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" - integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== - -commander@^9.4.1: - version "9.5.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" - integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== - -commonmark@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.30.0.tgz#38811dc7bbf0f59d277ae09054d4d73a332f2e45" - integrity sha512-j1yoUo4gxPND1JWV9xj5ELih0yMv1iCWDG6eEQIPLSWLxzCXiFoyS7kvB+WwU+tZMf4snwJMMtaubV0laFpiBA== - dependencies: - entities "~2.0" - mdurl "~1.0.1" - minimist ">=1.2.2" - string.prototype.repeat "^0.2.0" - -compare-func@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" - integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== - dependencies: - array-ify "^1.0.0" - dot-prop "^5.1.0" - -compress-commons@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" - integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== - dependencies: - buffer-crc32 "^0.2.13" - crc32-stream "^4.0.2" - normalize-path "^3.0.0" - readable-stream "^3.6.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - -constant-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" - integrity sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - upper-case "^2.0.2" - -constructs@10.0.12: - version "10.0.12" - resolved "https://registry.yarnpkg.com/constructs/-/constructs-10.0.12.tgz#64f314d87f7cdf6da127c9eb0bf6a3d990989c4a" - integrity sha512-wVQcQgwwK7b//7yI54/3hundmXAw7RBpuy5f6yIBFNceJr8feTK6Cs2I2f3+gp3/ikszzTouLup9AzxioEEXPQ== - -conventional-changelog-angular@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz#a9a9494c28b7165889144fd5b91573c4aa9ca541" - integrity sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg== - dependencies: - compare-func "^2.0.0" - -conventional-changelog-core@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-5.0.1.tgz#3c331b155d5b9850f47b4760aeddfc983a92ad49" - integrity sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A== - dependencies: - add-stream "^1.0.0" - conventional-changelog-writer "^6.0.0" - conventional-commits-parser "^4.0.0" - dateformat "^3.0.3" - get-pkg-repo "^4.2.1" - git-raw-commits "^3.0.0" - git-remote-origin-url "^2.0.0" - git-semver-tags "^5.0.0" - normalize-package-data "^3.0.3" - read-pkg "^3.0.0" - read-pkg-up "^3.0.0" - -conventional-changelog-preset-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-3.0.0.tgz#14975ef759d22515d6eabae6396c2ae721d4c105" - integrity sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA== - -conventional-changelog-writer@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz#d8d3bb5e1f6230caed969dcc762b1c368a8f7b01" - integrity sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ== - dependencies: - conventional-commits-filter "^3.0.0" - dateformat "^3.0.3" - handlebars "^4.7.7" - json-stringify-safe "^5.0.1" - meow "^8.1.2" - semver "^7.0.0" - split "^1.0.1" - -conventional-commits-filter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz#bf1113266151dd64c49cd269e3eb7d71d7015ee2" - integrity sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q== - dependencies: - lodash.ismatch "^4.4.0" - modify-values "^1.0.1" - -conventional-commits-parser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz#02ae1178a381304839bce7cea9da5f1b549ae505" - integrity sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg== - dependencies: - JSONStream "^1.3.5" - is-text-path "^1.0.1" - meow "^8.1.2" - split2 "^3.2.2" - -conventional-recommended-bump@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-7.0.1.tgz#ec01f6c7f5d0e2491c2d89488b0d757393392424" - integrity sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA== - dependencies: - concat-stream "^2.0.0" - conventional-changelog-preset-loader "^3.0.0" - conventional-commits-filter "^3.0.0" - conventional-commits-parser "^4.0.0" - git-raw-commits "^3.0.0" - git-semver-tags "^5.0.0" - meow "^8.1.2" - -convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^8.2.0: - version "8.3.4" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.4.tgz#ee1356e7f24e248a6bb34ec5d438c3dcebeb410c" - integrity sha512-SF+2P8+o/PTV05rgsAjDzL4OFdVXAulSfC/L19VaeVT7+tpOOSscCt2QLxDZ+CLxF2WOiq6y1K5asvs8qUJT/Q== - dependencies: - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - path-type "^4.0.0" - -crc-32@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" - integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== - -crc32-stream@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" - integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== - dependencies: - crc-32 "^1.2.0" - readable-stream "^3.4.0" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypt@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== - -crypto-random-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-4.0.0.tgz#5a3cc53d7dd86183df5da0312816ceeeb5bb1fc2" - integrity sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA== - dependencies: - type-fest "^1.0.1" - -dargs@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" - integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== - -data-uri-to-buffer@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz#db89a9e279c2ffe74f50637a59a32fb23b3e4d7c" - integrity sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg== - -date-format@^4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" - integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== - -dateformat@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" - integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== - -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -decamelize-keys@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" - integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decamelize@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" - integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== - -dedent@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== - -dedent@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" - integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - -define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -degenerator@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" - integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== - dependencies: - ast-types "^0.13.4" - escodegen "^2.1.0" - esprima "^4.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== - -deprecation@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - -detect-indent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== - -detect-newline@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - integrity sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -diff@^5.0.0, diff@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -dot-prop@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dotenv-expand@~10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" - integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== - -dotenv@~16.3.1: - version "16.3.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" - integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== - -duplexer@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ejs@^3.1.7: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - -electron-to-chromium@^1.4.477: - version "1.4.510" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.510.tgz#446c50d7533c1e71a84b00a3b37ab06dd601d890" - integrity sha512-xPfLIPFcN/WLXBpQ/K4UgE98oUBO5Tia6BD4rkSR0wE7ep/PwBVlgvPJQrIBpmJGVAmUzwPKuDbVt9XV6+uC2g== - -email-validator@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" - integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -encoding@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^5.10.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enquirer@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - -entities@~2.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== - -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -envinfo@7.8.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" - integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== - -err-code@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.20.4, es-abstract@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" - integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.1" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.1" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.10" - is-weakref "^1.0.2" - object-inspect "^1.12.3" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" - safe-array-concat "^1.0.0" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.10" - -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" - -es-shim-unscopables@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== - dependencies: - has "^1.0.3" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -esbuild@0.17.10: - version "0.17.10" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.10.tgz#3be050561b34c5dc05b46978f4e1f326d5cc9437" - integrity sha512-n7V3v29IuZy5qgxx25TKJrEm0FHghAlS6QweUcyIgh/U0zYmQcvogWROitrTyZId1mHSkuhhuyEXtI9OXioq7A== - optionalDependencies: - "@esbuild/android-arm" "0.17.10" - "@esbuild/android-arm64" "0.17.10" - "@esbuild/android-x64" "0.17.10" - "@esbuild/darwin-arm64" "0.17.10" - "@esbuild/darwin-x64" "0.17.10" - "@esbuild/freebsd-arm64" "0.17.10" - "@esbuild/freebsd-x64" "0.17.10" - "@esbuild/linux-arm" "0.17.10" - "@esbuild/linux-arm64" "0.17.10" - "@esbuild/linux-ia32" "0.17.10" - "@esbuild/linux-loong64" "0.17.10" - "@esbuild/linux-mips64el" "0.17.10" - "@esbuild/linux-ppc64" "0.17.10" - "@esbuild/linux-riscv64" "0.17.10" - "@esbuild/linux-s390x" "0.17.10" - "@esbuild/linux-x64" "0.17.10" - "@esbuild/netbsd-x64" "0.17.10" - "@esbuild/openbsd-x64" "0.17.10" - "@esbuild/sunos-x64" "0.17.10" - "@esbuild/win32-arm64" "0.17.10" - "@esbuild/win32-ia32" "0.17.10" - "@esbuild/win32-x64" "0.17.10" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escodegen@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionalDependencies: - source-map "~0.6.1" - -eslint-config-prettier@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207" - integrity sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA== - -eslint-config-standard@17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz#fd5b6cf1dcf6ba8d29f200c461de2e19069888cf" - integrity sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg== - -eslint-import-resolver-node@0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" - integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== - dependencies: - debug "^3.2.7" - is-core-module "^2.11.0" - resolve "^1.22.1" - -eslint-import-resolver-node@^0.3.7: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-import-resolver-typescript@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz#db5ed9e906651b7a59dd84870aaef0e78c663a05" - integrity sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ== - dependencies: - debug "^4.3.4" - enhanced-resolve "^5.10.0" - get-tsconfig "^4.2.0" - globby "^13.1.2" - is-core-module "^2.10.0" - is-glob "^4.0.3" - synckit "^0.8.4" - -eslint-module-utils@^2.7.4: - version "2.8.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" - integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== - dependencies: - debug "^3.2.7" - -eslint-plugin-es@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" - integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" - -eslint-plugin-es@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz#f0822f0c18a535a97c3e714e89f88586a7641ec9" - integrity sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" - -eslint-plugin-import@2.27.5: - version "2.27.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" - integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - array.prototype.flatmap "^1.3.1" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.7" - eslint-module-utils "^2.7.4" - has "^1.0.3" - is-core-module "^2.11.0" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.values "^1.1.6" - resolve "^1.22.1" - semver "^6.3.0" - tsconfig-paths "^3.14.1" - -eslint-plugin-jest@27.2.1: - version "27.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz#b85b4adf41c682ea29f1f01c8b11ccc39b5c672c" - integrity sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg== - dependencies: - "@typescript-eslint/utils" "^5.10.0" - -eslint-plugin-license-header@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-license-header/-/eslint-plugin-license-header-0.6.0.tgz#81b0bab59da5a752d3a129f04bd0ca35bb6b07a2" - integrity sha512-IEywStBWaDBDMkogYoKUAdaOuomZ+YaQmdoSD2vHmXobekM+XuP6SWLlvwUUhIbdocn3MTlb5CUJ8E4VHz1c/w== - dependencies: - requireindex "^1.2.0" - -eslint-plugin-n@15.6.1: - version "15.6.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-15.6.1.tgz#f7e77f24abb92a550115cf11e29695da122c398c" - integrity sha512-R9xw9OtCRxxaxaszTQmQAlPgM+RdGjaL1akWuY/Fv9fRAi8Wj4CUKc6iYVG8QNRjRuo8/BqVYIpfqberJUEacA== - dependencies: - builtins "^5.0.1" - eslint-plugin-es "^4.1.0" - eslint-utils "^3.0.0" - ignore "^5.1.1" - is-core-module "^2.11.0" - minimatch "^3.1.2" - resolve "^1.22.1" - semver "^7.3.8" - -eslint-plugin-node@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" - integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== - dependencies: - eslint-plugin-es "^3.0.0" - eslint-utils "^2.0.0" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - -eslint-plugin-prettier@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== - dependencies: - prettier-linter-helpers "^1.0.0" - -eslint-plugin-promise@6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816" - integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.1.1: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@8.34.0: - version "8.34.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.34.0.tgz#fe0ab0ef478104c1f9ebc5537e303d25a8fb22d6" - integrity sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg== - dependencies: - "@eslint/eslintrc" "^1.4.1" - "@humanwhocodes/config-array" "^0.11.8" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-sdsl "^4.1.4" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - -espree@^9.4.0: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - -events@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== - -execa@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" - integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -execa@7.2.0, execa@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -execa@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" - integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^3.0.1" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.4.tgz#a6e6f66d4613717859b2fe3da98a739437b6f4b8" - integrity sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA== - dependencies: - "@jest/expect-utils" "^29.6.4" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.6.4" - jest-message-util "^29.6.3" - jest-util "^29.6.3" - -exponential-backoff@3.1.1, exponential-backoff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" - integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -fast-glob@3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-xml-parser@4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f" - integrity sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g== - dependencies: - strnum "^1.0.5" - -fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -figures@3.2.0, figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== - dependencies: - locate-path "^2.0.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.1.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" - integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== - dependencies: - flatted "^3.2.7" - keyv "^4.5.3" - rimraf "^3.0.2" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -follow-redirects@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -fp-ts@2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.13.1.tgz#1bf2b24136cca154846af16752dc29e8fa506f2a" - integrity sha512-0eu5ULPS2c/jsa1lGFneEFFEdTbembJv8e4QKXeVJ3lm/5hyve06dlKZrpxmMwJt6rYen7sxmHHK2CLaXvWuWQ== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-extra@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" - integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^11.1.0, fs-extra@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" - integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-minipass@^2.0.0, fs-minipass@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-minipass@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" - integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== - dependencies: - minipass "^7.0.3" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gauge@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" - integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-proto "^1.0.1" - has-symbols "^1.0.3" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-pkg-repo@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" - integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== - dependencies: - "@hutson/parse-repository-url" "^3.0.0" - hosted-git-info "^4.0.0" - through2 "^2.0.0" - yargs "^16.2.0" - -get-port@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" - integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== - -get-stream@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" - integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== - -get-stream@^6.0.0, get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -get-tsconfig@^4.2.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.0.tgz#06ce112a1463e93196aa90320c35df5039147e34" - integrity sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw== - dependencies: - resolve-pkg-maps "^1.0.0" - -get-uri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.1.tgz#cff2ba8d456c3513a04b70c45de4dbcca5b1527c" - integrity sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q== - dependencies: - basic-ftp "^5.0.2" - data-uri-to-buffer "^5.0.1" - debug "^4.3.4" - fs-extra "^8.1.0" - -git-raw-commits@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-3.0.0.tgz#5432f053a9744f67e8db03dbc48add81252cfdeb" - integrity sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw== - dependencies: - dargs "^7.0.0" - meow "^8.1.2" - split2 "^3.2.2" - -git-remote-origin-url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" - integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== - dependencies: - gitconfiglocal "^1.0.0" - pify "^2.3.0" - -git-semver-tags@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-5.0.1.tgz#db748aa0e43d313bf38dcd68624d8443234e1c15" - integrity sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA== - dependencies: - meow "^8.1.2" - semver "^7.0.0" - -git-up@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" - integrity sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ== - dependencies: - is-ssh "^1.4.0" - parse-url "^8.1.0" - -git-url-parse@13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-13.1.0.tgz#07e136b5baa08d59fabdf0e33170de425adf07b4" - integrity sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA== - dependencies: - git-up "^7.0.0" - -gitconfiglocal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" - integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== - dependencies: - ini "^1.3.2" - -glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@10.3.10: - version "10.3.10" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.5" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - -glob@7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^10.2.2: - version "10.3.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.4.tgz#c85c9c7ab98669102b6defda76d35c5b1ef9766f" - integrity sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.0.3" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - -glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.0.1, glob@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^9.2.0: - version "9.3.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" - integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== - dependencies: - fs.realpath "^1.0.0" - minimatch "^8.0.2" - minipass "^4.2.4" - path-scurry "^1.6.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.19.0: - version "13.21.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" - integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - -globby@11.1.0, globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -globby@^13.1.2: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@4.2.11, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -handlebars@^4.7.7: - version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.2" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has-unicode@2.0.1, has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-sum@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" - integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== - -header-case@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" - integrity sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q== - dependencies: - capital-case "^1.0.4" - tslib "^2.0.3" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -hosted-git-info@^3.0.6: - version "3.0.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" - integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== - dependencies: - lru-cache "^6.0.0" - -hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" - integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== - dependencies: - lru-cache "^6.0.0" - -hosted-git-info@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-6.1.1.tgz#629442c7889a69c05de604d52996b74fe6f26d58" - integrity sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w== - dependencies: - lru-cache "^7.5.1" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - -http-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz#e9096c5afd071a3fce56e6252bb321583c124673" - integrity sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ== - dependencies: - agent-base "^7.1.0" - debug "^4.3.4" - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" - integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA== - dependencies: - agent-base "^7.0.2" - debug "4" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -human-signals@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" - integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== - -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - -husky-init@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/husky-init/-/husky-init-8.0.0.tgz#af1773803a44a0625a2d9d415cc892f51847e7d0" - integrity sha512-P9qq7eOInj853p57QhK/QDKahx8Vd+hWWmzA+N+Q3/dsHH+DFUeRurRd6ZSFzF1Ol/6yUS+fa9yMw5Dao0pxxw== - dependencies: - husky "^8.0.0" - -husky@^8.0.0: - version "8.0.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" - integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== - -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ieee754@1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -ieee754@^1.1.13, ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore-walk@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" - integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== - dependencies: - minimatch "^5.0.1" - -ignore-walk@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.3.tgz#0fcdb6decaccda35e308a7b0948645dd9523b7bb" - integrity sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA== - dependencies: - minimatch "^9.0.0" - -ignore@^5.0.4, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@3.1.0, import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@^1.3.2, ini@^1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -init-package-json@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-5.0.0.tgz#030cf0ea9c84cfc1b0dc2e898b45d171393e4b40" - integrity sha512-kBhlSheBfYmq3e0L1ii+VKe3zBTLL5lDCDWR+f9dLmEGSB3MqLlMlsolubSsyI88Bg6EA+BIMlomAnQ1SwgQBw== - dependencies: - npm-package-arg "^10.0.0" - promzard "^1.0.0" - read "^2.0.0" - read-package-json "^6.0.0" - semver "^7.3.5" - validate-npm-package-license "^3.0.4" - validate-npm-package-name "^5.0.0" - -inquirer@^8.2.4: - version "8.2.6" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" - integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.5.5" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - wrap-ansi "^6.0.1" - -internal-slot@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" - integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== - dependencies: - get-intrinsic "^1.2.0" - has "^1.0.3" - side-channel "^1.0.4" - -io-ts-types@0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/io-ts-types/-/io-ts-types-0.5.19.tgz#9c04fa73f15992436605218a5686b610efa7a5d3" - integrity sha512-kQOYYDZG5vKre+INIDZbLeDJe+oM+4zLpUkjXyTMyUfoCpjJNyi29ZLkuEAwcPufaYo3yu/BsemZtbdD+NtRfQ== - -io-ts@2.2.20: - version "2.2.20" - resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.20.tgz#be42b75f6668a2c44f706f72ee6e4c906777c7f5" - integrity sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA== - -ip-address@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" - -ip-num@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/ip-num/-/ip-num-1.5.0.tgz#b97bec67c85daaa44f9596d6cfbf77916ee7f802" - integrity sha512-XPStGtjDEDgaJXZhteWoIYZe2SaLWOzlpqwUmcx2W8UAA3lBY9oTyNNJ3fTnnW9Y4Px9f6Qi1mUqeFb9/dTIwQ== - -is-arguments@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-buffer@~1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-ci@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" - integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== - dependencies: - ci-info "^3.2.0" - -is-core-module@^2.10.0, is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.5.0, is-core-module@^2.8.1: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-fullwidth-code-point@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" - integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-ssh@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" - integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== - dependencies: - protocols "^2.0.1" - -is-stream@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-text-path@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" - integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== - dependencies: - text-extensions "^1.0.0" - -is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== - dependencies: - which-typed-array "^1.1.11" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz#7a8af094cbfff1d5bb280f62ce043695ae8dd5b8" - integrity sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" - integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jackspeak@^2.0.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.3.tgz#95e4cbcc03b3eb357bf6bcce14a903fb3d1151e1" - integrity sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jackspeak@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jake@^10.8.5: - version "10.8.7" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" - integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - -jest-changed-files@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.6.3.tgz#97cfdc93f74fb8af2a1acb0b78f836f1fb40c449" - integrity sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg== - dependencies: - execa "^5.0.0" - jest-util "^29.6.3" - p-limit "^3.1.0" - -jest-circus@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.4.tgz#f074c8d795e0cc0f2ebf0705086b1be6a9a8722f" - integrity sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw== - dependencies: - "@jest/environment" "^29.6.4" - "@jest/expect" "^29.6.4" - "@jest/test-result" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.6.3" - jest-matcher-utils "^29.6.4" - jest-message-util "^29.6.3" - jest-runtime "^29.6.4" - jest-snapshot "^29.6.4" - jest-util "^29.6.3" - p-limit "^3.1.0" - pretty-format "^29.6.3" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.4.3: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.4.tgz#ad52f2dfa1b0291de7ec7f8d7c81ac435521ede0" - integrity sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ== - dependencies: - "@jest/core" "^29.6.4" - "@jest/test-result" "^29.6.4" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^29.6.4" - jest-util "^29.6.3" - jest-validate "^29.6.3" - prompts "^2.0.1" - yargs "^17.3.1" - -jest-config@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.4.tgz#eff958ee41d4e1ee7a6106d02b74ad9fc427d79e" - integrity sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.6.4" - "@jest/types" "^29.6.3" - babel-jest "^29.6.4" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.6.4" - jest-environment-node "^29.6.4" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.6.4" - jest-runner "^29.6.4" - jest-util "^29.6.3" - jest-validate "^29.6.3" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.6.3" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -"jest-diff@>=29.4.3 < 30", jest-diff@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.4.tgz#85aaa6c92a79ae8cd9a54ebae8d5b6d9a513314a" - integrity sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.6.3" - -jest-docblock@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.6.3.tgz#293dca5188846c9f7c0c2b1bb33e5b11f21645f2" - integrity sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.3.tgz#1956f14f5f0cb8ae0b2e7cabc10bb03ec817c142" - integrity sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.6.3" - pretty-format "^29.6.3" - -jest-environment-node@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.4.tgz#4ce311549afd815d3cafb49e60a1e4b25f06d29f" - integrity sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ== - dependencies: - "@jest/environment" "^29.6.4" - "@jest/fake-timers" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.6.3" - jest-util "^29.6.3" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.4.tgz#97143ce833829157ea7025204b08f9ace609b96a" - integrity sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.6.3" - jest-worker "^29.6.4" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-junit@15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-15.0.0.tgz#a47544ab42e9f8fe7ada56306c218e09e52bd690" - integrity sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg== - dependencies: - mkdirp "^1.0.4" - strip-ansi "^6.0.1" - uuid "^8.3.2" - xml "^1.0.1" - -jest-leak-detector@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz#b9661bc3aec8874e59aff361fa0c6d7cd507ea01" - integrity sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.6.3" - -jest-matcher-utils@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz#327db7ababea49455df3b23e5d6109fe0c709d24" - integrity sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ== - dependencies: - chalk "^4.0.0" - jest-diff "^29.6.4" - jest-get-type "^29.6.3" - pretty-format "^29.6.3" - -jest-message-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.3.tgz#bce16050d86801b165f20cfde34dc01d3cf85fbf" - integrity sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.6.3" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.3.tgz#433f3fd528c8ec5a76860177484940628bdf5e0a" - integrity sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.6.3" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz#20156b33c7eacbb6bb77aeba4bed0eab4a3f8734" - integrity sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.6.4" - -jest-resolve@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.4.tgz#e34cb06f2178b429c38455d98d1a07572ac9faa3" - integrity sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.4" - jest-pnp-resolver "^1.2.2" - jest-util "^29.6.3" - jest-validate "^29.6.3" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.4.tgz#b3b8ccb85970fde0fae40c73ee11eb75adccfacf" - integrity sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw== - dependencies: - "@jest/console" "^29.6.4" - "@jest/environment" "^29.6.4" - "@jest/test-result" "^29.6.4" - "@jest/transform" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.6.3" - jest-environment-node "^29.6.4" - jest-haste-map "^29.6.4" - jest-leak-detector "^29.6.3" - jest-message-util "^29.6.3" - jest-resolve "^29.6.4" - jest-runtime "^29.6.4" - jest-util "^29.6.3" - jest-watcher "^29.6.4" - jest-worker "^29.6.4" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.4.tgz#b0bc495c9b6b12a0a7042ac34ca9bb85f8cd0ded" - integrity sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA== - dependencies: - "@jest/environment" "^29.6.4" - "@jest/fake-timers" "^29.6.4" - "@jest/globals" "^29.6.4" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.6.4" - "@jest/transform" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.4" - jest-message-util "^29.6.3" - jest-mock "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.6.4" - jest-snapshot "^29.6.4" - jest-util "^29.6.3" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.4.tgz#9833eb6b66ff1541c7fd8ceaa42d541f407b4876" - integrity sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.6.4" - "@jest/transform" "^29.6.4" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.6.4" - graceful-fs "^4.2.9" - jest-diff "^29.6.4" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.6.4" - jest-message-util "^29.6.3" - jest-util "^29.6.3" - natural-compare "^1.4.0" - pretty-format "^29.6.3" - semver "^7.5.3" - -jest-sonar-reporter@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jest-sonar-reporter/-/jest-sonar-reporter-2.0.0.tgz#faa54a7d2af7198767ee246a82b78c576789cf08" - integrity sha512-ZervDCgEX5gdUbdtWsjdipLN3bKJwpxbvhkYNXTAYvAckCihobSLr9OT/IuyNIRT1EZMDDwR6DroWtrq+IL64w== - dependencies: - xml "^1.0.1" - -jest-util@^29.0.0, jest-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.3.tgz#e15c3eac8716440d1ed076f09bc63ace1aebca63" - integrity sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.3.tgz#a75fca774cfb1c5758c70d035d30a1f9c2784b4d" - integrity sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.6.3" - -jest-watcher@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.4.tgz#633eb515ae284aa67fd6831f1c9d1b534cf0e0ba" - integrity sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ== - dependencies: - "@jest/test-result" "^29.6.4" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.6.3" - string-length "^4.0.1" - -jest-worker@^29.6.4: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.4.tgz#f34279f4afc33c872b470d4af21b281ac616abd3" - integrity sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q== - dependencies: - "@types/node" "*" - jest-util "^29.6.3" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.3.tgz#1b8be541666c6feb99990fd98adac4737e6e6386" - integrity sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA== - dependencies: - "@jest/core" "^29.4.3" - "@jest/types" "^29.4.3" - import-local "^3.0.2" - jest-cli "^29.4.3" - -jmespath@0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" - integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== - -js-sdsl@^4.1.4: - version "4.4.2" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.2.tgz#2e3c031b1f47d3aca8b775532e3ebb0818e7f847" - integrity sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@4.1.0, js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsii-pacmak@1.75.0: - version "1.75.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.75.0.tgz#2ecba3c95d587003c763ee8fc7516d30997cbabf" - integrity sha512-ENH5nNwMjN4CIK9D5mJ8OHDjiwInzQItQQmGwCdPJFLlUN9+9EkhYy2gEPVYPwh7e294c2nJ55DmiOj2CWR17g== - dependencies: - "@jsii/check-node" "1.75.0" - "@jsii/spec" "^1.75.0" - clone "^2.1.2" - codemaker "^1.75.0" - commonmark "^0.30.0" - escape-string-regexp "^4.0.0" - fs-extra "^10.1.0" - jsii-reflect "^1.75.0" - jsii-rosetta "^1.75.0" - semver "^7.3.8" - spdx-license-list "^6.6.0" - xmlbuilder "^15.1.1" - yargs "^16.2.0" - -jsii-reflect@^1.75.0: - version "1.88.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.88.0.tgz#d7f020db2621e8b672c082eea752c47153da5a63" - integrity sha512-YYZTEQpayvwMDtRMCjgNraTFUqsj4/KEOE8ChvDCkpxv6aH89vpZSsAJM5ymhNLDHj4XZ2OW3XE0sNOz31NbvA== - dependencies: - "@jsii/check-node" "1.88.0" - "@jsii/spec" "^1.88.0" - chalk "^4" - fs-extra "^10.1.0" - oo-ascii-tree "^1.88.0" - yargs "^16.2.0" - -jsii-rosetta@^1.75.0: - version "1.88.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.88.0.tgz#1189fb2aa538082d3099b104e2d51daf2cf485e7" - integrity sha512-6xRRkwWUKFqDTnjgCXkB6v9dxA51KUD4Cd7InLB4qirMBDuMtyYhYVNc1yJbHPYs9gkN5/ao0dFk+1CQxt7T7g== - dependencies: - "@jsii/check-node" "1.88.0" - "@jsii/spec" "1.88.0" - "@xmldom/xmldom" "^0.8.10" - commonmark "^0.30.0" - fast-glob "^3.3.1" - jsii "1.88.0" - semver "^7.5.4" - semver-intersect "^1.4.0" - stream-json "^1.8.0" - typescript "~3.9.10" - workerpool "^6.4.2" - yargs "^16.2.0" - -jsii@1.75.0: - version "1.75.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.75.0.tgz#e18fc8cb3ad985da93fb1513fc1e4d8ca191ff98" - integrity sha512-9CWt2IQcM6v5k4XZnaSnsK9epfIJfHWMyB69uOjITZpwYjF0CDzLrh/a8l1RyC3COSpp1K1yTjaebHEivyVr4Q== - dependencies: - "@jsii/check-node" "1.75.0" - "@jsii/spec" "^1.75.0" - case "^1.6.3" - chalk "^4" - fast-deep-equal "^3.1.3" - fs-extra "^10.1.0" - log4js "^6.7.1" - semver "^7.3.8" - semver-intersect "^1.4.0" - sort-json "^2.0.1" - spdx-license-list "^6.6.0" - typescript "~3.9.10" - yargs "^16.2.0" - -jsii@1.88.0: - version "1.88.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.88.0.tgz#f8b56420d47c6230dafb5d78a601bd5696e4f69c" - integrity sha512-WKfwHbcEI/j5OYDPexvkH8KKDcTZR7tIBFNTxu8h1Nh3G8xFT4hh3pObUUSMRCa6rsSF9EHGjS+AKC+TfpFGrQ== - dependencies: - "@jsii/check-node" "1.88.0" - "@jsii/spec" "^1.88.0" - case "^1.6.3" - chalk "^4" - fast-deep-equal "^3.1.3" - fs-extra "^10.1.0" - log4js "^6.9.1" - semver "^7.5.4" - semver-intersect "^1.4.0" - sort-json "^2.0.1" - spdx-license-list "^6.6.0" - typescript "~3.9.10" - yargs "^16.2.0" - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-parse-even-better-errors@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz#2cb2ee33069a78870a0c7e3da560026b89669cf7" - integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonc-parser@3.2.0, jsonc-parser@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" - integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonparse@^1.2.0, jsonparse@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== - -jsonschema@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" - integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== - -just-extend@^4.0.2: - version "4.2.1" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" - integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== - -just-extend@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" - integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== - -keyv@^4.5.3: - version "4.5.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" - integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== - dependencies: - json-buffer "3.0.1" - -kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -lazystream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" - integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== - dependencies: - readable-stream "^2.0.5" - -lerna@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-7.2.0.tgz#55ab3de42032168a75fc7a2ce184a8acb7efc5ea" - integrity sha512-E13iAY4Tdo+86m4ClAe0j0bP7f8QG2neJReglILPOe+gAOoX17TGqEWanmkDELlUXOrTTwnte0ewc6I6/NOqpg== - dependencies: - "@lerna/child-process" "7.2.0" - "@lerna/create" "7.2.0" - "@npmcli/run-script" "6.0.2" - "@nx/devkit" ">=16.5.1 < 17" - "@octokit/plugin-enterprise-rest" "6.0.1" - "@octokit/rest" "19.0.11" - byte-size "8.1.1" - chalk "4.1.0" - clone-deep "4.0.1" - cmd-shim "6.0.1" - columnify "1.6.0" - conventional-changelog-angular "6.0.0" - conventional-changelog-core "5.0.1" - conventional-recommended-bump "7.0.1" - cosmiconfig "^8.2.0" - dedent "0.7.0" - envinfo "7.8.1" - execa "5.0.0" - fs-extra "^11.1.1" - get-port "5.1.1" - get-stream "6.0.0" - git-url-parse "13.1.0" - glob-parent "5.1.2" - globby "11.1.0" - graceful-fs "4.2.11" - has-unicode "2.0.1" - import-local "3.1.0" - ini "^1.3.8" - init-package-json "5.0.0" - inquirer "^8.2.4" - is-ci "3.0.1" - is-stream "2.0.0" - jest-diff ">=29.4.3 < 30" - js-yaml "4.1.0" - libnpmaccess "7.0.2" - libnpmpublish "7.3.0" - load-json-file "6.2.0" - lodash "^4.17.21" - make-dir "3.1.0" - minimatch "3.0.5" - multimatch "5.0.0" - node-fetch "2.6.7" - npm-package-arg "8.1.1" - npm-packlist "5.1.1" - npm-registry-fetch "^14.0.5" - npmlog "^6.0.2" - nx ">=16.5.1 < 17" - p-map "4.0.0" - p-map-series "2.1.0" - p-pipe "3.1.0" - p-queue "6.6.2" - p-reduce "2.1.0" - p-waterfall "2.1.1" - pacote "^15.2.0" - pify "5.0.0" - read-cmd-shim "4.0.0" - read-package-json "6.0.4" - resolve-from "5.0.0" - rimraf "^4.4.1" - semver "^7.3.8" - signal-exit "3.0.7" - slash "3.0.0" - ssri "^9.0.1" - strong-log-transformer "2.1.0" - tar "6.1.11" - temp-dir "1.0.0" - typescript ">=3 < 6" - upath "2.0.1" - uuid "^9.0.0" - validate-npm-package-license "3.0.4" - validate-npm-package-name "5.0.0" - write-file-atomic "5.0.1" - write-pkg "4.0.0" - yargs "16.2.0" - yargs-parser "20.2.4" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -libnpmaccess@7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-7.0.2.tgz#7f056c8c933dd9c8ba771fa6493556b53c5aac52" - integrity sha512-vHBVMw1JFMTgEk15zRsJuSAg7QtGGHpUSEfnbcRL1/gTBag9iEfJbyjpDmdJmwMhvpoLoNBtdAUCdGnaP32hhw== - dependencies: - npm-package-arg "^10.1.0" - npm-registry-fetch "^14.0.3" - -libnpmpublish@7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-7.3.0.tgz#2ceb2b36866d75a6cd7b4aa748808169f4d17e37" - integrity sha512-fHUxw5VJhZCNSls0KLNEG0mCD2PN1i14gH5elGOgiVnU3VgTcRahagYP2LKI1m0tFCJ+XrAm0zVYyF5RCbXzcg== - dependencies: - ci-info "^3.6.1" - normalize-package-data "^5.0.0" - npm-package-arg "^10.1.0" - npm-registry-fetch "^14.0.3" - proc-log "^3.0.0" - semver "^7.3.7" - sigstore "^1.4.0" - ssri "^10.0.1" - -lilconfig@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" - integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== - -lilconfig@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -lines-and-columns@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b" - integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== - -lint-staged@13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.1.2.tgz#443636a0cfd834d5518d57d228130dc04c83d6fb" - integrity sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w== - dependencies: - cli-truncate "^3.1.0" - colorette "^2.0.19" - commander "^9.4.1" - debug "^4.3.4" - execa "^6.1.0" - lilconfig "2.0.6" - listr2 "^5.0.5" - micromatch "^4.0.5" - normalize-path "^3.0.0" - object-inspect "^1.12.2" - pidtree "^0.6.0" - string-argv "^0.3.1" - yaml "^2.1.3" - -lint-staged@^14.0.1: - version "14.0.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-14.0.1.tgz#57dfa3013a3d60762d9af5d9c83bdb51291a6232" - integrity sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw== - dependencies: - chalk "5.3.0" - commander "11.0.0" - debug "4.3.4" - execa "7.2.0" - lilconfig "2.1.0" - listr2 "6.6.1" - micromatch "4.0.5" - pidtree "0.6.0" - string-argv "0.3.2" - yaml "2.3.1" - -listr2@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-6.6.1.tgz#08b2329e7e8ba6298481464937099f4a2cd7f95d" - integrity sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg== - dependencies: - cli-truncate "^3.1.0" - colorette "^2.0.20" - eventemitter3 "^5.0.1" - log-update "^5.0.1" - rfdc "^1.3.0" - wrap-ansi "^8.1.0" - -listr2@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-5.0.8.tgz#a9379ffeb4bd83a68931a65fb223a11510d6ba23" - integrity sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA== - dependencies: - cli-truncate "^2.1.0" - colorette "^2.0.19" - log-update "^4.0.0" - p-map "^4.0.0" - rfdc "^1.3.0" - rxjs "^7.8.0" - through "^2.3.8" - wrap-ansi "^7.0.0" - -load-json-file@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" - integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== - dependencies: - graceful-fs "^4.1.15" - parse-json "^5.0.0" - strip-bom "^4.0.0" - type-fest "^0.6.0" - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.difference@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" - integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - -lodash.ismatch@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" - integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== - -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== - -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log-update@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" - integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== - dependencies: - ansi-escapes "^4.3.0" - cli-cursor "^3.1.0" - slice-ansi "^4.0.0" - wrap-ansi "^6.2.0" - -log-update@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-5.0.1.tgz#9e928bf70cb183c1f0c9e91d9e6b7115d597ce09" - integrity sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw== - dependencies: - ansi-escapes "^5.0.0" - cli-cursor "^4.0.0" - slice-ansi "^5.0.0" - strip-ansi "^7.0.1" - wrap-ansi "^8.0.1" - -log4js@^6.7.1, log4js@^6.9.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" - integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - flatted "^3.2.7" - rfdc "^1.3.0" - streamroller "^3.1.5" - -logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== - dependencies: - "@colors/colors" "1.5.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -lru-cache@^7.14.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - -"lru-cache@^9.1.1 || ^10.0.0": - version "10.0.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" - integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== - -lunr@^2.3.9: - version "2.3.9" - resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" - integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== - -make-dir@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@1.x, make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -make-fetch-happen@^10.0.3: - version "10.2.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" - integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== - dependencies: - agentkeepalive "^4.2.1" - cacache "^16.1.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-fetch "^2.0.3" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.3" - promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" - ssri "^9.0.0" - -make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1, make-fetch-happen@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz#85ceb98079584a9523d4bf71d32996e7e208549f" - integrity sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w== - dependencies: - agentkeepalive "^4.2.1" - cacache "^17.0.0" - http-cache-semantics "^4.1.1" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^5.0.0" - minipass-fetch "^3.0.0" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.3" - promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" - ssri "^10.0.0" - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - -marked@^4.2.12: - version "4.3.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" - integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== - -md5@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" - integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== - dependencies: - charenc "0.0.2" - crypt "0.0.2" - is-buffer "~1.1.6" - -mdurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -meow@^8.1.2: - version "8.1.2" - resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" - integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@4.0.5, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -minimatch@3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" - integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56" - integrity sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1, minimatch@^5.1.0: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^6.1.6: - version "6.2.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.2.0.tgz#2b70fd13294178c69c04dfc05aebdb97a4e79e42" - integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^8.0.2: - version "8.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" - integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.0, minimatch@^9.0.1: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - -minimist@>=1.2.2, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-fetch@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" - integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== - dependencies: - minipass "^3.1.6" - minipass-sized "^1.0.3" - minizlib "^2.1.2" - optionalDependencies: - encoding "^0.1.13" - -minipass-fetch@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.4.tgz#4d4d9b9f34053af6c6e597a64be8e66e42bf45b7" - integrity sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg== - dependencies: - minipass "^7.0.3" - minipass-sized "^1.0.3" - minizlib "^2.1.2" - optionalDependencies: - encoding "^0.1.13" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-json-stream@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" - integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== - dependencies: - jsonparse "^1.3.1" - minipass "^3.0.0" - -minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" - integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" - integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== - -minizlib@^2.1.1, minizlib@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -mnemonist@0.38.3: - version "0.38.3" - resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.3.tgz#35ec79c1c1f4357cfda2fe264659c2775ccd7d9d" - integrity sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw== - dependencies: - obliterator "^1.6.1" - -modify-values@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" - integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== - -monocle-ts@2.3.13: - version "2.3.13" - resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-2.3.13.tgz#7c8af489613cd5175df2ea3c02c57c6151995e5d" - integrity sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ== - -mri@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" - integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.0.0, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multimatch@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" - integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== - dependencies: - "@types/minimatch" "^3.0.3" - array-differ "^3.0.0" - array-union "^2.1.0" - arrify "^2.0.1" - minimatch "^3.0.4" - -mute-stream@0.0.8, mute-stream@~0.0.4: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -mute-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" - integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== - -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -netmask@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" - integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== - -newtype-ts@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/newtype-ts/-/newtype-ts-0.3.5.tgz#5c113d34a04938d9e90061437638260ea965c490" - integrity sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g== - -nise@^5.1.2: - version "5.1.4" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0" - integrity sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^10.0.2" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" - -nise@^5.1.4: - version "5.1.9" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.9.tgz#0cb73b5e4499d738231a473cd89bd8afbb618139" - integrity sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww== - dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers" "^11.2.2" - "@sinonjs/text-encoding" "^0.7.2" - just-extend "^6.2.0" - path-to-regexp "^6.2.1" - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-addon-api@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" - integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-gyp-build@^4.3.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.1.tgz#24b6d075e5e391b8d5539d98c7fc5c210cac8a3e" - integrity sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ== - -node-gyp@^9.0.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" - integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ== - dependencies: - env-paths "^2.2.0" - exponential-backoff "^3.1.1" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^10.0.3" - nopt "^6.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-machine-id@1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" - integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== - -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -nopt@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" - integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== - dependencies: - abbrev "^1.0.0" - -normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0, normalize-package-data@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-5.0.0.tgz#abcb8d7e724c40d88462b84982f7cbf6859b4588" - integrity sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q== - dependencies: - hosted-git-info "^6.0.0" - is-core-module "^2.8.1" - semver "^7.3.5" - validate-npm-package-license "^3.0.4" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-bundled@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" - integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-bundled@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-3.0.0.tgz#7e8e2f8bb26b794265028491be60321a25a39db7" - integrity sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ== - dependencies: - npm-normalize-package-bin "^3.0.0" - -npm-install-checks@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.2.0.tgz#fae55b9967b03ac309695ec96629492d5cedf371" - integrity sha512-744wat5wAAHsxa4590mWO0tJ8PKxR8ORZsH9wGpQc3nWTzozMAgBN/XyqYw7mg3yqLM8dLwEnwSfKMmXAjF69g== - dependencies: - semver "^7.1.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-normalize-package-bin@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832" - integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== - -npm-package-arg@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.1.tgz#00ebf16ac395c63318e67ce66780a06db6df1b04" - integrity sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg== - dependencies: - hosted-git-info "^3.0.6" - semver "^7.0.0" - validate-npm-package-name "^3.0.0" - -npm-package-arg@^10.0.0, npm-package-arg@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-10.1.0.tgz#827d1260a683806685d17193073cc152d3c7e9b1" - integrity sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA== - dependencies: - hosted-git-info "^6.0.0" - proc-log "^3.0.0" - semver "^7.3.5" - validate-npm-package-name "^5.0.0" - -npm-packlist@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.1.tgz#79bcaf22a26b6c30aa4dd66b976d69cc286800e0" - integrity sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw== - dependencies: - glob "^8.0.1" - ignore-walk "^5.0.1" - npm-bundled "^1.1.2" - npm-normalize-package-bin "^1.0.1" - -npm-packlist@^7.0.0: - version "7.0.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-7.0.4.tgz#033bf74110eb74daf2910dc75144411999c5ff32" - integrity sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q== - dependencies: - ignore-walk "^6.0.0" - -npm-pick-manifest@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz#2159778d9c7360420c925c1a2287b5a884c713aa" - integrity sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg== - dependencies: - npm-install-checks "^6.0.0" - npm-normalize-package-bin "^3.0.0" - npm-package-arg "^10.0.0" - semver "^7.3.5" - -npm-registry-fetch@^14.0.0, npm-registry-fetch@^14.0.3, npm-registry-fetch@^14.0.5: - version "14.0.5" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz#fe7169957ba4986a4853a650278ee02e568d115d" - integrity sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA== - dependencies: - make-fetch-happen "^11.0.0" - minipass "^5.0.0" - minipass-fetch "^3.0.0" - minipass-json-stream "^1.0.1" - minizlib "^2.1.2" - npm-package-arg "^10.0.0" - proc-log "^3.0.0" - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -npm-run-path@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" - integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== - dependencies: - path-key "^4.0.0" - -npmlog@^6.0.0, npmlog@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.3" - set-blocking "^2.0.0" - -nx@16.8.0, "nx@>=16.5.1 < 17": - version "16.8.0" - resolved "https://registry.yarnpkg.com/nx/-/nx-16.8.0.tgz#05466e6d979524cdbc4770d92c3722a29bb9d5c0" - integrity sha512-n32nk+y0h5iBuyxBvZJ4YJlFzs/ovT1q9mEXBkXWYgZp74t8nFu5qkLD/ndIOvw/9bvyw4/894vzb6uNDp8jBQ== - dependencies: - "@nrwl/tao" "16.8.0" - "@parcel/watcher" "2.0.4" - "@yarnpkg/lockfile" "^1.1.0" - "@yarnpkg/parsers" "3.0.0-rc.46" - "@zkochan/js-yaml" "0.0.6" - axios "^1.0.0" - chalk "^4.1.0" - cli-cursor "3.1.0" - cli-spinners "2.6.1" - cliui "^7.0.2" - dotenv "~16.3.1" - dotenv-expand "~10.0.0" - enquirer "~2.3.6" - fast-glob "3.2.7" - figures "3.2.0" - flat "^5.0.2" - fs-extra "^11.1.0" - glob "7.1.4" - ignore "^5.0.4" - js-yaml "4.1.0" - jsonc-parser "3.2.0" - lines-and-columns "~2.0.3" - minimatch "3.0.5" - node-machine-id "1.1.12" - npm-run-path "^4.0.1" - open "^8.4.0" - semver "7.5.3" - string-width "^4.2.3" - strong-log-transformer "^2.1.0" - tar-stream "~2.2.0" - tmp "~0.2.1" - tsconfig-paths "^4.1.2" - tslib "^2.3.0" - v8-compile-cache "2.3.0" - yargs "^17.6.2" - yargs-parser "21.1.1" - optionalDependencies: - "@nx/nx-darwin-arm64" "16.8.0" - "@nx/nx-darwin-x64" "16.8.0" - "@nx/nx-freebsd-x64" "16.8.0" - "@nx/nx-linux-arm-gnueabihf" "16.8.0" - "@nx/nx-linux-arm64-gnu" "16.8.0" - "@nx/nx-linux-arm64-musl" "16.8.0" - "@nx/nx-linux-x64-gnu" "16.8.0" - "@nx/nx-linux-x64-musl" "16.8.0" - "@nx/nx-win32-arm64-msvc" "16.8.0" - "@nx/nx-win32-x64-msvc" "16.8.0" - -object-inspect@^1.12.2, object-inspect@^1.12.3, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.values@^1.1.6: - version "1.1.7" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" - integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -obliterator@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" - integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig== - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -oo-ascii-tree@^1.88.0: - version "1.88.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.88.0.tgz#3ed84cdcaab9e5b7970fcfc2d086c2c069db65b7" - integrity sha512-A7m3z7XlUD3DnXSYxWmAdKQTIY6+1JzWS0lhaqgPGhj6g7a/odCsV1ctaRnjJljCB3zQBrbp2QHdYTUsD9AXcQ== - -open@^8.4.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -open@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - -optionator@^0.9.1: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - -ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== - dependencies: - p-limit "^1.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map-series@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" - integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== - -p-map@4.0.0, p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-pipe@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" - integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== - -p-queue@6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-reduce@2.1.0, p-reduce@^2.0.0, p-reduce@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" - integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -p-waterfall@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" - integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== - dependencies: - p-reduce "^2.0.0" - -pac-proxy-agent@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75" - integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A== - dependencies: - "@tootallnate/quickjs-emscripten" "^0.23.0" - agent-base "^7.0.2" - debug "^4.3.4" - get-uri "^6.0.1" - http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.2" - pac-resolver "^7.0.0" - socks-proxy-agent "^8.0.2" - -pac-resolver@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" - integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== - dependencies: - degenerator "^5.0.0" - netmask "^2.0.2" - -pacote@^15.2.0: - version "15.2.0" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-15.2.0.tgz#0f0dfcc3e60c7b39121b2ac612bf8596e95344d3" - integrity sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA== - dependencies: - "@npmcli/git" "^4.0.0" - "@npmcli/installed-package-contents" "^2.0.1" - "@npmcli/promise-spawn" "^6.0.1" - "@npmcli/run-script" "^6.0.0" - cacache "^17.0.0" - fs-minipass "^3.0.0" - minipass "^5.0.0" - npm-package-arg "^10.0.0" - npm-packlist "^7.0.0" - npm-pick-manifest "^8.0.0" - npm-registry-fetch "^14.0.0" - proc-log "^3.0.0" - promise-retry "^2.0.1" - read-package-json "^6.0.0" - read-package-json-fast "^3.0.0" - sigstore "^1.3.0" - ssri "^10.0.0" - tar "^6.1.11" - -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0, parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-path@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" - integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== - dependencies: - protocols "^2.0.0" - -parse-url@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" - integrity sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w== - dependencies: - parse-path "^7.0.0" - -pascal-case@3.1.2, pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" - integrity sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.10.1, path-scurry@^1.6.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== - dependencies: - lru-cache "^9.1.1 || ^10.0.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-to-regexp@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" - integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pidtree@0.6.0, pidtree@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" - integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== - -pify@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@2.8.4: - version "2.8.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" - integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== - -pretty-format@^29.0.0, pretty-format@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" - integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -proc-log@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" - integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== - -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" - integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== - dependencies: - err-code "^2.0.2" - retry "^0.12.0" - -promptly@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/promptly/-/promptly-3.2.0.tgz#a5517fbbf59bd31c1751d4e1d9bef1714f42b9d8" - integrity sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug== - dependencies: - read "^1.0.4" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -promzard@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/promzard/-/promzard-1.0.0.tgz#3246f8e6c9895a77c0549cefb65828ac0f6c006b" - integrity sha512-KQVDEubSUHGSt5xLakaToDFrSoZhStB8dXLzk2xvwR67gJktrHFvpR63oZgHyK19WKbHFLXJqCPXdVR3aBP8Ig== - dependencies: - read "^2.0.0" - -protocols@^2.0.0, protocols@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" - integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== - -proxy-agent@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.3.0.tgz#72f7bb20eb06049db79f7f86c49342c34f9ba08d" - integrity sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og== - dependencies: - agent-base "^7.0.2" - debug "^4.3.4" - http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.0" - lru-cache "^7.14.1" - pac-proxy-agent "^7.0.0" - proxy-from-env "^1.1.0" - socks-proxy-agent "^8.0.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== - -punycode@^2.1.0, punycode@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -pure-rand@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.3.tgz#3c9e6b53c09e52ac3cedffc85ab7c1c7094b38cb" - integrity sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w== - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -read-cmd-shim@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz#640a08b473a49043e394ae0c7a34dd822c73b9bb" - integrity sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q== - -read-package-json-fast@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz#394908a9725dc7a5f14e70c8e7556dff1d2b1049" - integrity sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw== - dependencies: - json-parse-even-better-errors "^3.0.0" - npm-normalize-package-bin "^3.0.0" - -read-package-json@6.0.4, read-package-json@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.4.tgz#90318824ec456c287437ea79595f4c2854708836" - integrity sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw== - dependencies: - glob "^10.2.2" - json-parse-even-better-errors "^3.0.0" - normalize-package-data "^5.0.0" - npm-normalize-package-bin "^3.0.0" - -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - integrity sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw== - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -read@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" - integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== - dependencies: - mute-stream "~0.0.4" - -read@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/read/-/read-2.1.0.tgz#69409372c54fe3381092bc363a00650b6ac37218" - integrity sha512-bvxi1QLJHcaywCAEsAk4DG3nVoqiY2Csps3qzWalhj5hFqRn1d/OixkFXtLO1PrgHUcAP0FNaSY/5GYNfENFFQ== - dependencies: - mute-stream "~1.0.0" - -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdir-glob@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" - integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== - dependencies: - minimatch "^5.1.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - -regexp.prototype.flags@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" - integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - functions-have-names "^1.2.3" - -regexpp@^3.0.0, regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requireindex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@5.0.0, resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== - -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.4: - version "1.22.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -restore-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" - integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rimraf@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" - integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== - dependencies: - glob "^9.2.0" - -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^7.5.5, rxjs@^7.8.0: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -safe-array-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-regex "^1.1.4" - -safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" - integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver-intersect@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/semver-intersect/-/semver-intersect-1.4.0.tgz#bdd9c06bedcdd2fedb8cd352c3c43ee8c61321f3" - integrity sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ== - dependencies: - semver "^5.0.0" - -"semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.6.0: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@7.5.2: - version "7.5.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" - integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== - dependencies: - lru-cache "^6.0.0" - -semver@7.5.3: - version "7.5.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== - dependencies: - lru-cache "^6.0.0" - -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -sentence-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" - integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - upper-case-first "^2.0.2" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shiki@^0.14.1: - version "0.14.4" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.4.tgz#2454969b466a5f75067d0f2fa0d7426d32881b20" - integrity sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ== - dependencies: - ansi-sequence-parser "^1.1.0" - jsonc-parser "^3.2.0" - vscode-oniguruma "^1.7.0" - vscode-textmate "^8.0.0" - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@3.0.7, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -sigstore@^1.3.0, sigstore@^1.4.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.9.0.tgz#1e7ad8933aa99b75c6898ddd0eeebc3eb0d59875" - integrity sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A== - dependencies: - "@sigstore/bundle" "^1.1.0" - "@sigstore/protobuf-specs" "^0.2.0" - "@sigstore/sign" "^1.0.0" - "@sigstore/tuf" "^1.0.3" - make-fetch-happen "^11.0.1" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sinon@^14.0.2: - version "14.0.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-14.0.2.tgz#585a81a3c7b22cf950762ac4e7c28eb8b151c46f" - integrity sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^9.1.2" - "@sinonjs/samsam" "^7.0.1" - diff "^5.0.0" - nise "^5.1.2" - supports-color "^7.2.0" - -sinon@^16.1.3: - version "16.1.3" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.1.3.tgz#b760ddafe785356e2847502657b4a0da5501fba8" - integrity sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA== - dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers" "^10.3.0" - "@sinonjs/samsam" "^8.0.0" - diff "^5.1.0" - nise "^5.1.4" - supports-color "^7.2.0" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@3.0.0, slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -slice-ansi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slice-ansi@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" - integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== - dependencies: - ansi-styles "^6.0.0" - is-fullwidth-code-point "^4.0.0" - -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -snake-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" - integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -socks-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" - integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks-proxy-agent@^8.0.1, socks-proxy-agent@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz#5acbd7be7baf18c46a3f293a840109a430a640ad" - integrity sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g== - dependencies: - agent-base "^7.0.2" - debug "^4.3.4" - socks "^2.7.1" - -socks@^2.6.2, socks@^2.7.1: - version "2.8.3" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" - integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== - dependencies: - ip-address "^9.0.5" - smart-buffer "^4.2.0" - -sort-json@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/sort-json/-/sort-json-2.0.1.tgz#7338783bef807185dc37d5b02e3afd905d537cfb" - integrity sha512-s8cs2bcsQCzo/P2T/uoU6Js4dS/jnX8+4xunziNoq9qmSpZNCrRIAIvp4avsz0ST18HycV4z/7myJ7jsHWB2XQ== - dependencies: - detect-indent "^5.0.0" - detect-newline "^2.1.0" - minimist "^1.2.0" - -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== - dependencies: - is-plain-obj "^1.0.0" - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdx-correct@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" - integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.13" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" - integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== - -spdx-license-list@^6.6.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/spdx-license-list/-/spdx-license-list-6.7.0.tgz#dd8c6aba00e7a3f9549e473d0be2b83c4cae081f" - integrity sha512-NFqavuJxNsHdwSy/0PjmUpcc76XwlmHQRPjVVtE62qmSLhKJUnzSvJCkU9nrY6TsChfGU1xqGePriBkNtNRMiA== - -split2@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" - integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== - dependencies: - readable-stream "^3.0.0" - -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -sprintf-js@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -ssri@^10.0.0, ssri@^10.0.1: - version "10.0.5" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.5.tgz#e49efcd6e36385196cb515d3a2ad6c3f0265ef8c" - integrity sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A== - dependencies: - minipass "^7.0.3" - -ssri@^9.0.0, ssri@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" - integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== - dependencies: - minipass "^3.1.1" - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -stream-chain@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" - integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== - -stream-json@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.8.0.tgz#53f486b2e3b4496c506131f8d7260ba42def151c" - integrity sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw== - dependencies: - stream-chain "^2.2.5" - -streamroller@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" - integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - fs-extra "^8.1.0" - -string-argv@0.3.2, string-argv@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" - integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string.prototype.repeat@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" - integrity sha512-1BH+X+1hSthZFW+X+JaUkjkkUPwIlLEMJBLANN3hOob3RhEk5snLWNECDnYbgn/m5c5JV7Ersu1Yubaf+05cIA== - -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimstart@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - -strong-log-transformer@2.1.0, strong-log-transformer@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" - integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== - dependencies: - duplexer "^0.1.1" - minimist "^1.2.0" - through "^2.3.4" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0, supports-color@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -synckit@^0.8.4: - version "0.8.5" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" - integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== - dependencies: - "@pkgr/utils" "^2.3.1" - tslib "^2.5.0" - -table@^6.8.1: - version "6.8.1" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" - integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - -tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -tar-stream@^2.2.0, tar-stream@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@6.1.11: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.1.11, tar@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -temp-dir@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== - -temp-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" - integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== - -tempy@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tempy/-/tempy-3.0.0.tgz#a6c0a15f5534a820e92c3e1369f1c1e87ebd6b68" - integrity sha512-B2I9X7+o2wOaW4r/CWMkpOO9mdiTRCxXNgob6iGvPmfPWgH/KyUD6Uy5crtWBxIBe3YrNZKR2lSzv1JJKWD4vA== - dependencies: - is-stream "^3.0.0" - temp-dir "^2.0.0" - type-fest "^2.12.2" - unique-string "^3.0.0" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-extensions@^1.0.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" - integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmp@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -ts-jest@29.0.5: - version "29.0.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.5.tgz#c5557dcec8fe434fcb8b70c3e21c6b143bfce066" - integrity sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "^21.0.1" - -ts-json-schema-generator@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/ts-json-schema-generator/-/ts-json-schema-generator-1.4.1.tgz#83a72dace79c8684b6d0fcad1c7d49c656e985d2" - integrity sha512-wnhPMtskk9KvsTuU8AYx0TNdm1YrLVUEontT22+jL12JIPqPXdaoxPgsYBhlqDXsR9R9Nm2bJgH5r4IrTMbTSg== - dependencies: - "@types/json-schema" "^7.0.12" - commander "^11.0.0" - glob "^8.0.3" - json5 "^2.2.3" - normalize-path "^3.0.0" - safe-stable-stringify "^2.4.3" - typescript "~5.3.2" - -ts-node@10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" - integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - yn "3.1.1" - -ts-node@10.9.1, ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tsconfig-paths@^3.14.1: - version "3.14.2" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" - integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tsconfig-paths@^4.1.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@^1.11.1, tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.0, tslib@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tuf-js@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-1.1.7.tgz#21b7ae92a9373015be77dfe0cb282a80ec3bbe43" - integrity sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg== - dependencies: - "@tufjs/models" "1.0.4" - debug "^4.3.4" - make-fetch-happen "^11.1.1" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8, type-detect@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" - integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-fest@^1.0.1, type-fest@^1.0.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" - integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== - -type-fest@^2.12.2: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -typed-array-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" - integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-typed-array "^1.1.10" - -typed-array-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" - integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" - -typed-array-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" - integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" - -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - is-typed-array "^1.1.9" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -typedoc@0.23.25: - version "0.23.25" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.23.25.tgz#5f8f1850fd044c4d15d453117affddf11a265610" - integrity sha512-O1he153qVyoCgJYSvIyY3bPP1wAJTegZfa6tL3APinSZhJOf8CSd8F/21M6ex8pUY/fuY6n0jAsT4fIuMGA6sA== - dependencies: - lunr "^2.3.9" - marked "^4.2.12" - minimatch "^6.1.6" - shiki "^0.14.1" - -typescript@4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" - integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== - -typescript@4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== - -"typescript@>=3 < 6": - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== - -typescript@~3.9.10: - version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== - -typescript@~5.3.2: - version "5.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== - -uglify-js@^3.1.4: - version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" - integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unique-filename@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" - integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== - dependencies: - unique-slug "^3.0.0" - -unique-filename@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" - integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g== - dependencies: - unique-slug "^4.0.0" - -unique-slug@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" - integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== - dependencies: - imurmurhash "^0.1.4" - -unique-slug@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" - integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ== - dependencies: - imurmurhash "^0.1.4" - -unique-string@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-3.0.0.tgz#84a1c377aff5fd7a8bc6b55d8244b2bd90d75b9a" - integrity sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ== - dependencies: - crypto-random-string "^4.0.0" - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - -upath@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" - integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== - -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -upper-case-first@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" - integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg== - dependencies: - tslib "^2.0.3" - -upper-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a" - integrity sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg== - dependencies: - tslib "^2.0.3" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" - integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util@^0.12.4: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - -uuid@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" - integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -uuid@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== - -uuid@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-compile-cache@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - -v8-to-istanbul@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" - integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - -validate-npm-package-license@3.0.4, validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -validate-npm-package-name@5.0.0, validate-npm-package-name@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz#f16afd48318e6f90a1ec101377fa0384cfc8c713" - integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ== - dependencies: - builtins "^5.0.0" - -validate-npm-package-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw== - dependencies: - builtins "^1.0.3" - -vscode-oniguruma@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" - integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== - -vscode-textmate@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" - integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -watchpack@^2.0.0-beta.10: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wcwidth@^1.0.0, wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.2: - version "1.1.11" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" - integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -which@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/which/-/which-3.0.1.tgz#89f1cd0c23f629a8105ffe69b8172791c87b4be1" - integrity sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - -winston@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.8.2.tgz#56e16b34022eb4cff2638196d9646d7430fdad50" - integrity sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew== - dependencies: - "@colors/colors" "1.5.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -workerpool@^6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.2.tgz#5d086f6fef89adbc4300ca24fcafb7082330e960" - integrity sha512-MrDWwemtC4xNV22kbbZDQQQmxNX+yLm790sgYl2wVD3CWnK7LJY1youI/11wHorAjHjK+GEjUxUh74XoPU71uQ== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" - integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - -write-file-atomic@^2.4.2: - version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -write-json-file@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" - integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== - dependencies: - detect-indent "^5.0.0" - graceful-fs "^4.1.15" - make-dir "^2.1.0" - pify "^4.0.1" - sort-keys "^2.0.0" - write-file-atomic "^2.4.2" - -write-pkg@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" - integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== - dependencies: - sort-keys "^2.0.0" - type-fest "^0.4.1" - write-json-file "^3.2.0" - -xml2js@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" - integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xml2js@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xml@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" - integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== - -xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yaml@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== - -yaml@^2.1.3: - version "2.3.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" - integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs-parser@^20.2.2, yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@16.2.0, yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@17.7.1: - version "17.7.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" - integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yargs@^17.3.1, yargs@^17.6.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zip-stream@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" - integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== - dependencies: - archiver-utils "^3.0.4" - compress-commons "^4.1.2" - readable-stream "^3.6.0" diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - From b8d4a9d06b8eb459da5c8f6725ecdeefefe774c6 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 11:53:49 +0100 Subject: [PATCH 06/18] unrequired workflows --- .github/ISSUE_TEMPLATE/bug_report.md | 41 ------------------- .github/ISSUE_TEMPLATE/feature_request.md | 17 -------- .github/PULL_REQUEST_TEMPLATE.md | 5 --- .github/workflows/automated-tests.yml | 35 ---------------- .github/workflows/docs.yml | 50 ----------------------- 5 files changed, 148 deletions(-) delete mode 100755 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100755 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100755 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/workflows/automated-tests.yml delete mode 100644 .github/workflows/docs.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100755 index a915341..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "" -labels: bug -assignees: "" ---- - - - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Please complete the following information about the solution:** - -- [ ] Version: [e.g. v1.5.1] - -> To get the version of the solution, you can look at the description of the created AWS CloudFormation stack used to install the LZA (AWSAccelerator-InstallerStack). For example, "__(SO0199) Landing Zone Accelerator on AWS. **Version 1.5.1.**__". If the description does not contain the version information, you can look at the Parameters of the stack for the **RepositoryBranchName** as that should contain the version number. - -- [ ] Region: [e.g. us-east-1] -- [ ] Was the solution modified from the version published on this repository? -- [ ] If the answer to the previous question was yes, are the changes available on GitHub? -- [ ] Have you checked your [service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) for the services this solution uses? -- [ ] Were there any errors in the CloudWatch Logs? - -**Screenshots** -If applicable, add screenshots to help explain your problem (please **DO NOT include sensitive information**). - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100755 index d3d209f..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this solution -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the feature you'd like** -A clear and concise description of what you want to happen. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100755 index de50e4d..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,5 +0,0 @@ -*Issue #, if available:* - -*Description of changes:* - -By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml deleted file mode 100644 index cfe958d..0000000 --- a/.github/workflows/automated-tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Automated Tests -on: - pull_request_review: - types: [submitted, edited, dismissed] - pull_request_target: - types: - - edited - - opened - - synchronize - - reopened - -jobs: - test: - permissions: - contents: read - pull-requests: read - statuses: write - issues: read - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18.x - cache: 'yarn' - - name: 'Build and Test' - run: | - cd source - yarn install - yarn lerna run precommit --stream - yarn build - yarn test - env: - ACCELERATOR_PREFIX: AWSAccelerator diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index a3374d1..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,50 +0,0 @@ -## This workflow runs when a new release is published in the Landing Zone Accelerator -## GitHub repository. The workflow pulls the latest versioned TypeDocs from a private -## S3 bucket, then builds and versions the documentation using mike. -on: - release: - types: [published] - -jobs: - build-mkdocs: - name: Build mkdocs - runs-on: ubuntu-latest - env: - TYPEDOCS_DIR: ./source/mkdocs/docs/typedocs - # These permissions are needed to interact with GitHub's OIDC Token endpoint. - permissions: - id-token: write - contents: write - environment: release-docs - steps: - - uses: actions/checkout@v4 - - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.IAM_ROLE_ARN }} - aws-region: us-east-1 - - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - name: Copy TypeDocs from S3 - id: s3 - run: | - commit_tag=$(echo ${GITHUB_REF} | tr -d 'refs/tags/') - aws s3 cp s3://${{ secrets.BUCKET_NAME }}/${commit_tag}/typedocs.zip . - echo "commit_tag=$commit_tag" >> $GITHUB_OUTPUT - - name: Unzip TypeDocs - run: unzip -q ./typedocs.zip -d ${TYPEDOCS_DIR} - - name: Install Python dependencies - working-directory: ./source/mkdocs - run: pip install mkdocs==1.5.3 mkdocs-material==9.5.3 mike==2.0.0 - - name: Deploy docs - working-directory: ./source/mkdocs - run: | - git fetch origin gh-pages --depth=1 - git config user.name github-actions - git config user.email github-actions@github.com - mike deploy --push --update-aliases ${{ steps.s3.outputs.commit_tag }} latest - mike set-default --push latest - - - - \ No newline at end of file From 66073f31e726c602e61eae3336e84cce6e77ee1a Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 11:58:12 +0100 Subject: [PATCH 07/18] updating pull request trigger --- .github/workflows/docker-build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index c5a0b2b..6fc6d9f 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,7 +1,11 @@ name: Build and Publish Docker Image on: - pull_request + pull_request: + paths: + - .github/workflows/docker-build.yml + - build.sh + - Dockerfile env: REGISTRY: ghcr.io From dbec440cdb367c56c3a5f15864c2a60ec2fd62cd Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 12:18:27 +0100 Subject: [PATCH 08/18] removing docker actions --- .github/workflows/docker-build.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 6fc6d9f..53eee24 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -23,16 +23,6 @@ jobs: - name: checkout uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2.1.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Run build script run: | bash build.sh From 575e51b45a6424a85c0f83ccd743e9453f7fe850 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 12:32:48 +0100 Subject: [PATCH 09/18] list image after build --- .github/workflows/docker-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 53eee24..52d5382 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -26,6 +26,7 @@ jobs: - name: Run build script run: | bash build.sh + docker images From 02ad156b22b498d6d89f553736acdd15bd596b4a Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 14:33:05 +0100 Subject: [PATCH 10/18] updating dockerfile cmd and adding steps to login and tag image --- .github/workflows/docker-build.yml | 17 +++++++++++++++-- Dockerfile | 11 ++++------- build.sh | 3 +-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 52d5382..6166974 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -27,7 +27,20 @@ jobs: run: | bash build.sh docker images - + - name: Login to ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - + - name: Publish Docker Image + run: | + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + + [ "$VERSION" == "main" ] && VERSION=latest + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION diff --git a/Dockerfile b/Dockerfile index 33c736b..7ca7e9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,14 @@ FROM node:18-alpine3.20 -WORKDIR /lza -COPY landing-zone-accelerator-on-aws . +WORKDIR /source +COPY landing-zone-accelerator-on-aws/source . # COPY lza-validator.sh ./lza-validator.sh -RUN mkdir config -RUN VERSION=$(echo $release | tr -cd [0-9]) -RUN cd source \ - && export NODE_OPTIONS=--max_old_space_size=8192 \ +RUN export NODE_OPTIONS=--max_old_space_size=8192 \ && yarn install \ && yarn build \ && yarn cache clean # ENTRYPOINT ["/lza/lza-validator.sh"] # CMD ["/lza/config/"] -CMD yarn validate config ../config +CMD yarn validate-config ../config diff --git a/build.sh b/build.sh index e831c74..75de6a3 100644 --- a/build.sh +++ b/build.sh @@ -12,5 +12,4 @@ release=$(git describe --tags --abbrev=0) git -c advice.detachedHead=false checkout $release cd .. echo $release -docker buildx build --platform linux/amd64 --tag cc-lza-validator:$release . -docker images +docker build --platform linux/amd64 --tag cc-lza-validator:$release . From 80453a582c537a858ca0bbc31f046ecf738744aa Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 15:49:41 +0100 Subject: [PATCH 11/18] updating dockerfile cmd and updated script to tag image --- .github/workflows/docker-build.yml | 13 +------------ Dockerfile | 5 +---- build.sh | 7 ++++++- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 6166974..1958973 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -26,21 +26,10 @@ jobs: - name: Run build script run: | bash build.sh - docker images - name: Login to ghcr run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Publish Docker Image run: | - IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME - IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') - - VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') - - [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - - [ "$VERSION" == "main" ] && VERSION=latest - echo IMAGE_ID=$IMAGE_ID - echo VERSION=$VERSION - docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker images diff --git a/Dockerfile b/Dockerfile index 7ca7e9e..06e755b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,11 @@ FROM node:18-alpine3.20 WORKDIR /source COPY landing-zone-accelerator-on-aws/source . -# COPY lza-validator.sh ./lza-validator.sh RUN export NODE_OPTIONS=--max_old_space_size=8192 \ && yarn install \ && yarn build \ && yarn cache clean -# ENTRYPOINT ["/lza/lza-validator.sh"] -# CMD ["/lza/config/"] -CMD yarn validate-config ../config +CMD ["yarn", "validate-config", "../config"] diff --git a/build.sh b/build.sh index 75de6a3..4d5405b 100644 --- a/build.sh +++ b/build.sh @@ -12,4 +12,9 @@ release=$(git describe --tags --abbrev=0) git -c advice.detachedHead=false checkout $release cd .. echo $release -docker build --platform linux/amd64 --tag cc-lza-validator:$release . +image_name=cc-lza-validator +image_id=ghcr.io/UKHomeOffice/$image_name +image_id=$(echo $image_id | tr '[A-Z]' '[a-z]') + +docker build --platform linux/amd64 --tag $image_id:$release . +docker tag $image_id:$release $image_id:latest \ No newline at end of file From 536d3db4ae03cee67f12c33e75ffe20e49c5d4e2 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 16:16:57 +0100 Subject: [PATCH 12/18] update workflow to get image tags --- .github/workflows/docker-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 1958973..f6e27b7 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -32,4 +32,5 @@ jobs: - name: Publish Docker Image run: | - docker images + tags=$(ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') + echo $tags \ No newline at end of file From f911e19eb2f867a00c9b4b38f7ec9840985d733e Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 16:24:59 +0100 Subject: [PATCH 13/18] update workflow to get image tags --- .github/workflows/docker-build.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f6e27b7..96de59a 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -32,5 +32,8 @@ jobs: - name: Publish Docker Image run: | - tags=$(ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') - echo $tags \ No newline at end of file + tags=$(docker image inspect ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') + echo $tags + # for tag in $tags: do + # docker push $tag + # done \ No newline at end of file From a4535aa043637d82cf7b806add35903a0fe16f44 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 16:32:02 +0100 Subject: [PATCH 14/18] push images --- .github/workflows/docker-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 96de59a..fa82cd5 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -34,6 +34,6 @@ jobs: run: | tags=$(docker image inspect ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') echo $tags - # for tag in $tags: do - # docker push $tag - # done \ No newline at end of file + for tag in $tags: do + docker push $tag + done \ No newline at end of file From 39b03e5540ccc8d0b8d7f9555e27d6d7487a4c60 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 16:39:44 +0100 Subject: [PATCH 15/18] fix syntax error in for loop --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index fa82cd5..941a417 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -34,6 +34,6 @@ jobs: run: | tags=$(docker image inspect ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') echo $tags - for tag in $tags: do + for tag in $tags; do docker push $tag done \ No newline at end of file From 4fae4b2ab85f73f5653679aefd3a1d42873c1655 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Fri, 26 Jul 2024 18:10:04 +0100 Subject: [PATCH 16/18] push all tags --- .github/workflows/docker-build.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 941a417..bd904dd 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -32,8 +32,10 @@ jobs: - name: Publish Docker Image run: | - tags=$(docker image inspect ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') - echo $tags - for tag in $tags; do - docker push $tag - done \ No newline at end of file + # tags=$(docker image inspect ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') + # echo $tags + # for tag in $tags; do + # docker push $tag + # done + + docker push -a ghcr.io/ukhomeoffice/cc-lza-validator \ No newline at end of file From 630d1370a5ee7826f39ccd271c50be27612d05e3 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Wed, 31 Jul 2024 10:18:36 +0100 Subject: [PATCH 17/18] changing trigger to be merge to main --- .github/workflows/docker-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index bd904dd..be33264 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,7 +1,9 @@ name: Build and Publish Docker Image on: - pull_request: + push: + branches: + - main paths: - .github/workflows/docker-build.yml - build.sh From ba5bbc4b72dd10b6e25ad67446be702bd4325fb2 Mon Sep 17 00:00:00 2001 From: Ed Bent Date: Wed, 31 Jul 2024 10:54:32 +0100 Subject: [PATCH 18/18] cleaning up docker push step --- .github/workflows/docker-build.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index be33264..77a7230 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -33,11 +33,4 @@ jobs: run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Publish Docker Image - run: | - # tags=$(docker image inspect ghcr.io/ukhomeoffice/cc-lza-validator:latest | jq '.[0].RepoTags') - # echo $tags - # for tag in $tags; do - # docker push $tag - # done - - docker push -a ghcr.io/ukhomeoffice/cc-lza-validator \ No newline at end of file + run: docker push -a ghcr.io/ukhomeoffice/cc-lza-validator \ No newline at end of file

Xe^C*8H)(Y0X4`(Y9nOJ5I=7K4^L_>9Kbg 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 diff --git a/source/mkdocs/docs/sample-configurations/standard/images/scp_inheritance.jpg b/source/mkdocs/docs/sample-configurations/standard/images/scp_inheritance.jpg deleted file mode 100644 index eec38554ff593b979f9c7f26fb23b8f00734caf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57112 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 K!Ig3!1OE>-_#jLG diff --git a/source/mkdocs/docs/sample-configurations/standard/images/standard_network.jpg b/source/mkdocs/docs/sample-configurations/standard/images/standard_network.jpg deleted file mode 100644 index 1899f5841b36c6aa5b26239d7408694b28e22311..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 320395 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%0Am6e5$;nx*T9|@=t74;~llO$c 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 >* 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 z($ple

;?Cxo!OM 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 @%42_h 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Ŏ)fWO9v*#!j%%Zl@dj1=k;dta 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≶*CNnl5v2Zv#uOSdMF%WfftHkUte;eQb zG!Cv!>w#^Ht~JN3dL`{bESbv;f}?OY#+Pi~H_GSNS^#*XJH?jn?h1|2H7rqS%q>+L z?sTLn=Kp0fylyB=^o2Cn)-r+-H@t62?1d>layP0bulhpVcc{2ZMzf3Tt_O zi>7d*0u;=DOY99@8+mFELx_JN6+o9b-3pUvt*T&h~SGC}+<(2r>y)GD>1A zuFezq%ZN@hgPaubk|MX4miFI<3Gqn+oXa6aMJEKh>TcM)L4B~)RXt2Uoc-xpNgqXYce$qT(VOWPLo`kZCb{E0?|-; ztz1Abf}f)F@o(?wT&Yru{o3A%owm}#ikVvg5LA1))v$ovlV`Cui@_Cpc7A&2aN5Bp zQ{*4O{1oA|bK{K4y#t@d`U0~z=kcGgZ+j>tyFWR_wQ1|HaU0(3c}w#8vHET0wz0{8 z9+ob$^WP-U+gb`B#Am(omiZ!JlD)NMO5>$HuN$kx*c}Cy`F_lOIieNv$;PFC{T^o7 zi+*I3_0XqQ81DPf;PFJJLMONvTs_RoU?}wC4}2SpRuWoS!odCPF~uhS^yl&m7KK%F z2m(>Ko^khgHZ+{ClTcLBx7_%me%fHFua?m+f}lOyA_&2eNY1C5NilKaOP{tOcnGt( zMHIy8bFneY+v63r?yB5x2>54rims@c>FqXhWzC_25pzP zsl;yJeff#xfJDWUM6J~QGvrGjqP)W;E3cvJF5?ujS2u({@B*`PO^26&s!Ue$a+(%A zOo#!i1?QEJxeC!K!6&Q?UT#GC;!s@fE_m0B%IAxjjk~^F)fdrk#)LmC#MLns9u{V6 zUvd)0R&)-a|0f;3-{uLk)A9k#kuoeAK<(XN-f?n`sV9_dBR9Z}>Qf31W!b8e+WnIZokQUu1SDHVLerA((D5Kv%t}4+jOnFd9M~X!>|K+ zM+H2<9z4$Ahbxr2niYZOc2*QHA&-BSlKsWgT@gj{6jR~%6zc7tlUW7wjYZ}z2*2*j zCgmT1OdV1EC?Z8Z_=Ctu*Ie|)G@R*1v~&MZ>dmt)gQF1|XbiIh1%xRX0 z+)`Ak5$hn2ShEw}pX>1kY3#eRb-NEKtPk|wbRtSF{q*APVaD;#8o-yXVFth4jd06HeQo3}4h9t5$_lLS(ow z_|V?msqH}Chj(hi<{4;ukI5KdO)()~7OfF^2)cQMQi_FnFFys%A}Y4n1ls<6Sr6ym zZvU&TdS#nD@ky~i{)7Ef9_DoE9l?kZ zts_}zVb3vCy4tr{S1(UQHAy|fbT?7(!;X{`LeES z*UTg`T0i1-l4GXz23nF~PEh*1euCL07<=AgaxbEM0m6#B#?>7L6}!>n@vPVpEI>TS zIlkn%i--+z{3P(UBRJoe!itot9%~D7bo5slISN1(0`xw4PB66qbxlh;uG%LgB-GbR z1 z%1_PIO-TZHlJN2gPRm90*@1j%KJ?*twr$y0QtA=$8XQuhTb7S@DwEvD9Lcihw@MIn zfg?Vuo@QQsLj6Cir_*fo|vjy)E?u+^4$GDb}9z@>YoesS*_q;hP2G_Da z#^13F)T=`H4%@YCmUL#s0>VV^LP4J$s&NRsBKGeu_gfl487XaOl7tJyTbhDEvi2gs3hmQJ;S+ufsO&*N3{IG zq&`9V$HD8O2b4Fg8J|Gh-nH?-$DV)hTcwS-D?otqD(AtagE2E+`TX@PfT{|`fdfxO zb>)0McrHwz$uta5lj>qI<`L?Af`o{}-!OWyn2$YKu;dbc3{>M+zkw*&qtLwtSMJAO z9hA4gdN`>#6wd|caTplQb1WzWAtHIT^01I+=Obe7knVvvzQ1CzXYal=SatgerX&MS z*RIxqCp#q0t@}Vm;X-tN^d@oKHuP;3{wRT>G=~skD**zsN=AYW)1fMe(zQ@WHXGU3 zp2|KM`==rTQLEI7F-#?`-|oBmWyUFAza|O7o9fUD*lcA4ZpoYlmNFq*l#(jRde|3? zLdvx5u+jMrLapbMLz5{bQq1RL*91=Ul4L+ZyAOS(?|75q5;&SC_We-spLg==?p!zs zjpt{Eu;OVQAr?5AFbX#-7QoURvDIb|Rf=oOU(8SUa{m>f(cb?`Q=Jd>hmw>SBV##D zx79t>q!erKr)*$OY_iDV_=Exuc!F0_A)&RF7yLQhO0N6IL=+HJ zXu$x5=GO93i&>yu!i37q|H;Mvr=;m)qFCN}3~fx#6?3-#sLMgjsdv+;Nuf+f(Eu$W z?n;Hi+_DNY;2xn$Qv#LxTxgv|tBc!yC&!8N&sx_nR)#+^)cmz$pmASE-0oP-*>0b2 z=ktDhsxgslZ^ZmL?srKhe(JZm@v^C^@S0fD_LoiLDp5py827Z3acm0eCDTaIC{*oq zGvYQL^y2m})1Xp-=I6QYW0@Wmw0j%3nG2^GF=k5`B}UOfAmIJneRRUQ!>#J2T8}HS z!Qu)jjIpW>s02wWK$l}ODKn#oGLG8L zNHCOK5xC8K*qL1AEHD+jms6b>9o~4Z0|u%(>*xprCw7YI6}x{B=RkP}ZWm|L&= z=8isvi$h#WRnEoTn@DtPx;JQhwZs{tC<`q>pUAj`=7dvF8PVabgaJm9X^VVoi<;yJ+{qr`wYJz1@uV-ko7`i(MOh8%A}f!KXf_imD+zxG5F+O z9I}kgJVRc*!M61jeXDkU=1r}#(P^gEDxv$uAQnXU#dw>IBi(n)ckI#S<&<11DNibc zf?fc*^VYU2M{v{kg|*%|W&cl!uY1GmG-0yf+_tMmMxe6XdgjKkEL)(doORL*Y%|v~z zevh6)B-X5yK4sZVvqqa7d!)yCBoc@G{yHoxf*xO8cpb&q=SW}Cu)W)ovrbW7kmWwk zk#-hX#K<*^V&GALkMQte&N?`DP*bjpFbl0E8^w7bdy1Q4(a$njvwkwlc^>mIS>B*w z>*kpW6csM&8Mn}sLx5AVBZsy6-A7}-1VeLNRmW}4Wgju3&GGFv`i`px2)^I&9xzrk zAI-2SNW6mr`Iwt-ou*^2ujY^CWcy0go@$HXnZ`;X7Y${fYFvWyMLBuzGe}LgwY?W{ z8Pstey*$S$xpdL_c%PfyogOc``xO8<#a(zWG5iRcmuM5F4qk1xXfJ<+lJBk^NTK_Q zIIXb1eRv-?E@fa@S>t{uL_>9Cl&UMtg%(4?7K};yFnCYmg_`QKYS}6+XkFxZ{3A;b z5-<5;0*9i1$kk!8T+ZHqGFJQbUAc+ipkcEkm>m9N_x5v1H1)b&FieXxfXA@ybB^qN z!4UTf^xYdA=#G!>gV~>{dyQM2zFZJ&#{5{GA|wF(Xg+IcYqMWrHmDDeBURlnb;^Z4 z42-7$VrMjB`-_{M;vZ8lUAxK>0v>LK3#QC@Y|+Shp2q$z zcU7pLbYVC+k7X@mQ0C|TIQb$la%7nGrGNNlAGOtXFzD&I!GZ|dO#ejCJ*(5y_TLsy zM?>Qt9pQnIiUg(i@exr~La0wPD=SeX9W^n2N9x2+CZ%a!3^dO%DLnXR#OLEt^6~yX zz%?6H`zzn$EX{n3#dwOc04T=*gepI3PUa&49^GWFZUL?q**EX0_;@lsQ3j!rI+spF z`~uAv%i7a@m7A^Gmkfn!uxhd2#Hd%?7&gTkE`jFSQlHWT9#<+j%}RXj*XukPVM!^+ zeqF!jH=6nP#*+ppDW$ozi31#+2wzGpxGmYc3Xd>)cxZWs)zE2FXtlWP)tqK`@J~Km z@?8KvMgTZ7-B-^4+K0ok`7C_wZ$GC9ApdM88)R@?dGyrZCvn$-r|`ml)~7#e*6s)0 zuT(WF{5eBIo?NZdX<+x~ePxM%Z=m=F9ss+T43_fz36oX=ae z5&2sQdxX{?hDd|e8jFGN5DF>3?SA3F7H#Jnc%nNx1HFcGkwzwwBfCdUO3C_RW~NOU z4@RmHA4&a5f$(WOQnKpLy@aLfbHBJ95#4EeUrKN0w~cAyjmxtXS^GP+Spr%Kkz{Nt z;`?6;l9uqTOW=a_jy+@1$+-c^5d9@I@Pb?1Yd7WNYl83S-JmG1UOo{v955|PQ922| zV7|QT;Y=DoRp9J>`Q{UQXLmNS!)B?NSZa>-yuo-owc6?^b-Ml@=w#vCk>x_R0!uNzA92^Wb=4YC>S@PEjT> z`DyC{$E1l$UB^hY9B>5Oa4y7r0A@n>U%Zt9vSOHhFzu`&ThBz>X zo&*(0_KJuAKF2&&xiii7^Q)=|MOaew@hW7+y%yquWvk|CWcxhh7FadPG_Pf@37Aj$ zd7{i;5CG%Qy3b)M;Jq>E?U=RIzXt4<7b#1v3l134K=9#x6h4B z%%!B+p37S-n1m|1(;ZWlc^r{xWg9*W2Y;;jLJH6Y>bxG!!pppKmPe5J_i*{;Q8ArP zgB%i5u^BVo(psBa)LcU2%=x#2D|{3~$bL8^JdBr1jFWIr8SAl3HVzbhoLo5j=DF6c zr@INxGd_NKD+MRBnm80q>{dbL0V1@Wa2TC(P8^uRYB9{Fnm_K;*@wuiA9a^kg*C|{ z$0MIw}pJ;P)~H!@}7d1}+>1YQS7KePvhH(dgDt1BBSnlS2HazaTq+ zL!O_pG{O8(s!aM&=flb5o+?&*d>do=Xn0DQpuV4rD?t0yAZmE9p9SdTyy8{q+_@XS%efF+|LrZug6_$-M9{qlN%5v zefmZYR_HHFN-j+iG~S8%8nqV3@7!UE{xCQ^6`UrR2pnVLC=m-A(=W1oH_Fe~R2V1C zP*hpJOH|qE!UA!YyMtFgE84bdM*I+5A%W6W;4TQkqol_!1k4S7yCl+K*v2YF{P&Sb z`7?py-jTw9I35Dh9W5aK9Tvb|c6f;2gbqC#yPvx={|0+-U?Pnr+)2s=e|uv+-&=VY zpAiNV_JmRKvy*WGgHaH-v3zEAQuF4scBFsg$9&NyD3%QtGX8R7K)H@6O=jbrPLj~X z*2pQd)&6hm?tW4I)${AmgPHia#}cfKrU^8KTn{tId1Z!2SH&EDD}#S8D*_-SnuI2O_Fh_%g@G0DFQ{o=c2T1?mvhxRBu^4F6OesLEUqp;2Tig2+6J2b4AY! zL(Hr2G8rR)2nIF8$Y=N@wYBiJlh!rvdhf>zGrhG9BS+kKN9ZelO&c^VEmZ_1kC|8NZ0W{;piMk1%fg|W;#^LgypcGDIgpo(;0 z?l61`^jS{Yg}Im&%A9K78p{kQ7(=;6Pza727d`-U0oBP#in>eZKbu=q!H zT4PiqIp&_epBy!zN5K1tp9x9o$p}j1iSDuQxCk-m+ad8_%rTqEb%&RJGKy8OSa39w z!~Npc&W4nR+4^m;GQdQK`$f)7SIf^2yaL+E>a$-y}Mr#&#g90Obb-)ACp_?d)bqh}I@VI(# zp07T|Wj5%(0+Zd5G0z{QB|*G@(%Os+-p@3;7(tkqPNIi2HBaOKyl?Ho@n`5S3!Ij+ zv$vmAo19101N?PxlrQ%8-gpOJ>c+w}eXlX0@P5=&SF;mQ0!XCIWER~?LO)aV!)w6{ z)H{oAcw`|lQG$HbqQ7jYgNWG?EzFRFFGNV4a5}Rz&@X_gFK?R{7bG>1FBILFC>hWI z8iLf8Elj{WQkWf{+ou1&_lH^0IG{Qk3I&l3;7$Vs@odQ7dlSRjX+k3RF-Cg5@6iLl z_0IX8q_Ptw0r7;r6*_{?ASrunTH7(veVcu~nlFlbIXz@Iy=^0FWXn|k56)Lz$SY}m z*Mx6g4%uIjbFEtw;w;7R=I18+8v?NbU!ae$Q3%6u9cx0LS7ZtGh&o3Wmdllwk%gfc z+W5Jgw?`eMJg>T)+wDp=)t)W`*+ipkA3Fzxy>cfxkdl;LNqRV6P-o$wzL;v$9u)D8 z%nZ>9kNPH`XXcE4N{vG|rom3qsS}%SK=wNv8gXl_ma$CuL^b6O)D9(%934C-Zv%H) zS^#b>Zt0dL!JtT>fzlJ7hr6qF4k}Yaxnmr<`JV59A`8XCwRlJ~KlM|?>xxF6i4dz@ zOIU3mrY$0GKW;q|ACAl|wI}|J#b@<}5CP}htN|m(CQ}%+By}Q42!E%714YS0zTc5# z=h6y4XLD=@Ob%oJYln(LL%|R=E&@+21!$AHPXAYhfI3#}-&2XTYMh74_@$qy-$9>`P9ztdrW39S zC6mR^&^Qh)(lD+cQJ4qejug-81-u>e6>=^!8|`9!X;JjiXauGs#Bv?_5<^nD59&AX z&E228IUIK4Yn8OmOHt^*dzKzr1;K}&F8&-bOmCRi!XWKN)I&k?WHi0VNA$K< zT4NcWZF7?h^Wa_1A{=0W0hMrOE`(>5))Z0GW~LMIXB3`CUQSnd&dwTBVjG3%kBl&V z#QQGxXTfxj-6vD2_@u*>nmi;A?_(&gdBcWwyMA72l}(FzTBnpg)Sn&oOp3Vmlc9F{#mGqq37Emu$P&+4kb zhlB*p`Wz3n_``fVvbJnuA*&xLMpw99SIf7HZDa;DMMUbKhy)_7X8JanM#5wZ>-1U# zp6$B`rEXIO%CqL-TPWb)XY34ziHqwZW%15EZ2&sejn^XGE3Gb22BCwd-@?G-FOft1 zIc35ryLE{5`j}xhd1|ww+>Xme+mtOQ>U*-^Lgt}&>q?!QQv>SDR|ocp&)sBVIgVMq zUf%j{bRjc;tt7b#MumzgyYcP_o7XCI=sC)L5X8-EuTIlKyaq~^<$7oU+9-64#`~th zdp|P&w&|6Li&g`B+}(3=R8iI({RRmtkO`Ogo4VGU7id|Yot!g6RXIB>3@xhb4F?KD zcP1F6`ZH8LCLG#~+Y0X-yKqK1&CL+`-e0w1b1;_5aeo4cZrvu{k0*9D0~AVk$FFzm zSe>qH;e3S-%Tuk#O(aLv`rvGol=#rzT?&4Tgdaa)JOXM)=7G9K9sk8>Izw))D74>H06y2F#RhdfvcIqD^S!J{iG~5 zspr-gS3IsY!~Uyqm-DFPuG>Wqsfu zy<06N9kps=V1de$6L1Kf)>j0qFsvpY`A)1{uM)0Ik-SV><>IQ-a51XtB$Ka%U^gk% zzDjKnbaB}h-L&d0epP(mBO#8zwwc6AlG&ybgEy6`$o zeYAL<)(9b@+Emg9ogX~kUJ7QxE&vRm5PVoE^I{A2BHFr0KMmb6;;dK?jP`%n{K_Yf zQ@+sI`QDQg`6p{^NWkJJY;2LXX}9+n=c7IPQ6c;P{*a~z*%b8V(W)W*hzojB7tI@H z$N7N#C8lf{NJxl>VtG;HV2fXycYZEgd*@EQsUlexCeMTx{Pxsfk$XdFr71U!1m>)C zs$nsSYhOk_i)rryB^~>+{oc!`wUuI-0**y;r!gB~olS)h+UfQ<$+MlCEpnKw-9Vt+ zp(&|LWx97Yx=KF7N6;20BK-O^49>ZO5wl!D7_ znAC*&8rOca+5dSi?s6~^(_SoqO;XV*NO#cqnVS5sAzdiBi~_G(f2aG|&nnMFJ9~Qt zn95zagwCyCFVz|cf??=9+g74HIfqJwp@VVpjcz*;$$QHwF}LM~UM?O%^9c`}O*&`+ zCX`01Xp>Q{VdTyc%TVy(o2q`CjukI^9-X{?umMtM@D2LMqLWuAM!*|E#Xu-x_nF%= z7vhQjhj2869rZ9O3pPTYf(#1A=C9DicnO@#+>g_6Z9{=83I18F2f_@NMq4whK7G$q zPo0hiDpTn1X^EKP6$SCM{?Fr4^@YH}7>Zvhj{IukWgK{w-wCT8ZM{ArQr86E;z`F9 z@!L>4Wpi{M;s9?B+6Mv$_iTdfXPx{M`v*V9x_$9;YM5DG`fEuAN}a@}hRm#yWC%pE zL*nxHCu%x(=#&;2B!r64pQM~A-7@p$T9wm=A)g5561&2FO3NVZhClzSJ!1N;d!C~@b9;S&nj2T_O_E4X`?L{SC>KW6HT*5wGZXC{hNryQjh5l zVwXcCPBNsJssSmsLn^`C@{4Qt{baoY707l8jTSK~#-=A~;{ zg!(}wXRs44j6hEJc&)#EP1n78x_@OQ^TzFGMbyS?ujx~P3hrX}a>z%p@P13r`|Gp? z-sInjA@@bqZ3M5yr3qYHyTe%GRsY#%LeBzic*}}Bxcks=5yFd2=Ng+zpHJM(v79pR zZhVk@_v^l=ISb zk}9{FVx|Y01p&4H_&7|GVx(Wk4-MA$;{f*mjwcDXu0FJ;Or4w-st-ub_Y9w}T3Zqf z-9R_8DUBs@q^!sZDm${m5JgmszHX-`P(_3>@KR;5UqkB3?^K-EH%s85WyE5n6W!-b zZv@@`=Z@CL5(0|G@BcD!ovpFAA%}zT2A-qtW_tLb z^LQ}VD`}kTs->XTZ*Ac1lNo%O`?(Pyi9QXIrSfMg|93L}{0U&eg~l7uiqO)l>cH?d z6_dbFQI|N}kW4wU4bg#_ernPN1v-IgSrQ^?4OHc2G7JN}rZPgdwim`K)V&_bo`>T;T=aU1R72Nj#0T=c=bUg=yvG7nfJ6;O^=R|h{hApQ{n$kBd zEtp!&v}R}kZX=n@6JJcNH)q8V7LxU<@AUNEoSo4KZy4aBHK%9(@y$xHx>S;&wE*Wx zNNu&-VfmJCbn*T5rN6bVwy|yf)&ghS(T*uGAdj`l<}a=(LR0GP#=6(`xx+`ckz{Wq ze}px4dlj$D86;%am`T3<=;QKU!CAD`mphtV7Z;cI@iN83jv{tGz!~A2Fz;-fw*%KY z{V-oYFKLP>N-J%3;#nAI5&d3dWB2{--jfTD=o=Bp`Cy^|i?FY8?iVmJu9;I{wTB*--8Y>`4KNv7S2VzT_HnJ6)d1r;%DW89A`j<&qMVzdQv&p!j(z zdVigR4%d!%!o=cw%IVI0Vz#QwhJL6Y{`qo$m_kiT$_nTOpm=_(af)?UNd%zr;F(AV z1C6z>o<%=co6$awMI!R6yt4%TTz!3Doo@7}%`k@0N3z21GV#n=A5?S7Uy$}X$RFp`Ft`kP`J)3yyj|{1`_-AI;oblM<=-3K#`gQ>_OtiNp<6!x zP9Cx!YlQdvqRt01izlH!5iFuFtNQcDV-HJkacU{5G0m_zt%0XNvM6F`0;j&>`0b3} z14mrc4PJNX3>{@P?EpTw7uBzLI@kM(X9D*Z^T2@3=w)&+n=jCG^aITDZ+=3CsFQYk z5T1P$@b4DD=NsmFM|4wl2FJQbgY+B}=Z7tsRg{+GL}X`S=eDWub0bbjK)kjmI&TXH z51+B@mWAtCNg7s`$ zGE6et{7!GzdzLz0(Q?)rrU>(hm46$D0Q0j=PpGsLiUd5w;gisw{MEn|VOIU~{a=3m zNvL!3Pk|-({`n@ULtS^9-RF+{S|e>5#S@jMMvzpf*ANW)j>lRSTl-XJCLe`Q+Fb@h4AfA6e2_FvTk$BJ6spwC;kX z-wzuRnOnt_8-b6{E9i{GgKi+pHD zORUURDAO-qRvKbQ2=AZFm-Nx5A;&m_$!l--_rB&dcbf^v7VcJ0}^?oO*Vt z=`|bz(!a2K|LDcbKEhIW;4sFM@-87Jl79DKxK3nmwrCn*-bg}K z)NI?RTi$8M_U65ka#3+7Hiyw-K+bKJ?9OO9_H!QH@?zw>`3*kw#vAKJy zmFNeU<_b}I@@)Tw14DPcw^TVbby>gFtYP=C%Cr&xHH9+t+i%z(kBRj?O#_GP3@pU% z(8p5#Q(?{>w%O0Et~>(dPwBHd;HG1{m2|9$O?>j1?>`V%jKdBZ@*`cAb9IyDDznX0 zSamE)lNWe0d2TqkF3L{d&h-_qGs7z>J-u1MCfm}yoaznWz>@6eXHj(r8(h~LyNTEd zZp)Kq;;hsjIdMLVVyqkiJ74;USBsLYtPYf_waI9AEe(w(&3tKH2ZfHHrW4lNlFRLs zhNZ}i<+()5SrD1P#{YVm zbQ7DruwS!YW(W=sVI6uaWI=|!*Qvf}d;nWb*xBf4xg@I}J9V_x8~(g8(C>)hXv--D zu)k&?Uu!qMyi`_k&LH%aSrl$~(8-hES9TR9e&p;e7*EVj*i$wAe@wjvSDfLttQ*|j zgS$%z!5Vk>KyZQt2<{HS-Q6KL1PJaB+}+)w(Z-=08V+mkyUw|Pp~p9R&YEx4Q@3a7 zIsVXC*+10eGEPrXi$l)MW=gm8Bucqn51w4^BnHm?xlm()>N79)ml!}7l0+nn)(v+l zz`8AyUcdb+V!lFi`{-~RdPTa2#V+>KOuEd)5Y2=*=dil|mcFXepsi1tkGRtK#c&d= z+p7M&w7~{QFp{4SG@vGi{oy^oG=p<`M*78Ajd-1!E|?Te4}0~{rOhC~;0tqgFR+UD z6M#;u{P!ZC9yk8{?;LBVB6f2aWt4FeYK)3JzEG~z*-sY>KGOH!(qJ%sa~EN8(D=G) z2zgwR_d+Y@u!F=dOO-)+>aC7ay@Pxny6i4}m27uhL=`r5GKVJ?!PJH=ZX5PBgcoZS z=5-o!lM{5$QH)j6`4c7`GgytDi^fiZB?115%-95Kks=CYxX3tnXIlU;EcGjHPfepL zcAqHw;!vX)G7BnV_9;u#Z8v*2e5_)$@mTPWy?owC9$$sP=@?k5JgZ6F`m7jYmb)%; z;}1ogQ;~togX_Rs)Q_~;5|1ON!rNkpk}Zq?6H3u<=w*hpj~-v#EkjWQoq{RHM*yQq zzF4w@Ol*RzP)MAooG%kl@||)w5kku;e`XZdto-_IN5%+dGQ`885E?V~p@4N@%%?4> z57Ju1#eXw>D`vrT6m*RP@*0lkaZCw)CPQSAxDe~tuKv35uYbG(_zm?Ziabscy4504 zY{SR+B5L%(O{KU!XI;f>8>XiFro#asO#wz=(%fRHHH2VBiB~Kt(Kya$jA_ecB+*T< zJSc~d%71teLd*v~%6sw3FSopr$fO?}{{@pt4z8^{HW}$y%b@1jSJHkA#!au7%f~EEJQxVvs#$mGRqJb%ZnJiJK;i(SQ(IIj#rYZ3)P zuQLNDB_6%}k|Uuvnn~hFHe44F?*z?7hM(N8zzT9NeOwy6cIT%|{v|%xB!)vB!ux$7 zGYNErs(KciyIf84@=z{0)H?2}PwmI>9q+bh?+=)m&qDDmocGdT?mgWa;_r@w<#bCP z1u)#%tpr+}hh^)DI1vXwX4nS>f5vn3OHydJlaq}87)%%##Hp_occ`WEH0<`Bwcc2S zm>o34#n|Ah)fdE#B^9D_B-`TeGLA|;<7a!iPNUi@M7mkh>^UGuTZ{bf`7kM>F*Tj~dsvcgJ=ZVb* z8q3MHsc)Bbt00R|fw37ch_xuJbO|JgZcVM$66#cC!H#;YP!l!ywelmwQ(18Y#g*yt zXYl}Ts+tO8V*Z|J%hY`8ds-XCEY`7+@YY>gh~+7&Nl5paG9eh&o27QofRk~${ob3d zmRhusvZU6;4iRf=J-GCS`5+%Q`PSw zrM#le&pD9TbMwYj|Fiz9GtW#Q6fMZeK;6C$L%x_lm3m!dri=*+zVPz)UwfV3 zT0S48^|`2PBeL)mba-n9P=zn}~}o<1S51g?L5Ib=)D?y#FJ- z|L=@WPli=*i|*^HF3K_>obN;1@2*YTF8Vg|;oQF4_cUl6&Ef)E*m39AMSv6|~r6qo59*dWhAD8*)j=tUAdN3j=DY+)h z0(@!9j7ljHgea)pE$Gye06%~JynnNjHD_6NHMEyC%-8+gPTHA*X7;M|k?EV$Om~%c zA;qblf!VB$!jKG;-7_?@y4|F^i}#yT2IO$?>|JXAIPOFb`tc3~cvlV0e_{3FFq_jh zkfmNmMk+;(+kHnpoFAI9x*WDe*1IK#s1(M>%Y_mspID0!DAQ@BWpDb0Pn_wEzs#n9 zVIw}gAg=ers_e_G=x$gc@lfedJP(K#OgXZBaHXrgn$3Cwjis`>KOfnZEyqbJPURQu zCe%)TjnhXX#gNPP2+=o~xkji=FPtk+xBOnX8>UT$@~`F@xB~-J@D8z54`sFZox_&0 z6YevHl!YrNcCm-yz{gko0ZH7_+<6Npm?4ZYM|CMhRHszuHfVU88M1Z7ze0ZoM7(fR z8r|r143QKUl2|jvDf=V@N3Kmb-hdZiVZ8;m_I~?4pL|1m)}j^gsmG{PsF3H!8JA)%bVwdOJmP0T(vE{j3;QyQ_+GNHd>i*L=rdHN zS8@vFMa1TxqwlVEUBvcc5Q6$f0G_&Z+T>`P`T7QZ=ZCGv-T#fC(886eexfqJqR>+_y6N|0vt*+MIZ=ZQ%XD9kyfoUF zNFT{MLPW{HMQ>5oGeiUmy^a+H{t++I-oJz-*MKNykO88eVBh__WEeQ`FxB`thG>7~evf*>*3Y+M3~$M|3JOfrE)UkOgD$yDQs zsOJHIw;(=JD!*5jj~d-P+xefoX*XV|E-*2 z&5Pp`#tA$av%Nzo0}C1=WBWLtwoBwP4*4;E%*h@I&!q}S9dRW*0(mCP{<7ed@;dC|1Dis~iciS{at5vroyeUK#IHEZ9% z3wzP{u~(vWkSXxtUbMJsx$qR>U29uvk5q5Sn$hdRfnAhZwJO7S>7s?L#Ip=OJq1zJ zL9a3W4;2LO;I0H4cVv5DD_Opav6SC4^ z9ccTjhO1cEpY?0;4k)LLhOJ#p_Hd(JJumIkmG_jl4);Ic9*}imjC`Ig<{{daJ;>om#-0Yce`~yRGSumxFrFJ z%$ov}N;oj%A7Z4V-O#SXn>YFsylIyS3G4eQS=f<p~v+ z!eG~$E>uwKcKSXibfZTk;eDa{xhtDbKP|svPD7G`Kph_#%NoQIY>jV9oH1MJ+$LxXAWv&GWObPBdTT zyaMa0ZW5F8p1nf6Qb0YFX>A~mYnSz6!>y%hUzBQS(2??8h>-6T5Mj?KSvK!+3yTl! zBEFxpt(`nMY(3kQT%Ed=6E;lW1S3Z5Q1Fd7b#)`|Qx>O)@_DRf&hRGr^W0l}UUGej zYa!5I{5qyw$!D1MA){_C0|?_W_;9VZNk)dKyarOpY&Pk59NAPfjXZTOWkqz3q2k=H z;~JZWL3CF7t{AUuyR?cx5nw#R+bXs{HVzRa@QoV<*XXLaNB<7v-u@#}XEh^Ud(IQQ z|AOnrx)7dl9+F1!u0z61ZB_0b|2_I{Wh9LXOWi*QiNCZP?Qt@J-6g%>GFln-T>ya` zf12QoD?Vl4Bo!xD6grzW4qh7&4>%i_YmUXJOW`L#CSAtu6>=UFAWfV3 z*ES8U2Nzb~pdS^6)$bW&<~Ivxx)nBY&T7#tXgdls%z2VBuRzeT#hEQcyscLyDD`gi zDahJG!_T|3_El7Xnb&#)jUJz8&F45 zz`}j!@TK$h&PP+F_B>S90;pCWtXi$Bt1z9q_htMm-MC86k9Fx7FI9tR^EMjS+k&ypZR;$|~V1svBmc5+C{lc_b9*0UQ zc$KA0ZoBfb`2EmW#;}9O{CtT+dz9UBD!1;aVjD-b0&D8enleUpDkyhpZzRPosb?-k zEwWEym5S7TEU0l^CmHm}}qjIqekIO+;bRJhRjviTD=;$yn&`BLg1yMO~6 zo6W}Hd{cW@TF@Y`3*YW*>twnq&8irj`=N!e=Qht60s zAxn2>4ZyKaGk5oS!rQ8|l>_N9sO9D~Vv*4ZXUtjU)0maCwh=ceGR+_qv~qJ-8M^__ zGV}ynoEdCak)MYD;rLYrCNmnYLHGf1yux-3ZT))uoS5K<0%*94w*ohS1Se`GL&NhqQ zB~<_1GG?Ysy%ApBUC+ftQ$Cd1A^=(cYbqRTpSJ|}sQ~!F32wu+t0Pkw`huKrtfT0g z;tBedvLBmy-Dg>DxBpJwdXc8R@T*ar=~)#Hs#N6xl&{BwYxNDoRJ@dtNv2))I1eJ$ zn!7n?s2GnMeO{JUD+%~8^*SD!u`_u?Ubrq~LP*FDMGPuYP;{fQl298gNQh8JsaU;& zrNiw#dcW$oo&SBxLO?p;X7_DQq-}!Jb50(|Ik}qyKes%nriAamaNyF6g_@E+h1A6( zRMt>_VKRJ2lvzzo4thzTKV8?1G~aa)6FN&$$tf70&H8bVbQbtLZI!S#WeBuxhYr6|jX{I};LL6d`%qR-{?%-k#HA&!h=2ZHjAr7tECfa`O$6|ICfZ z4$sPuR3vPSNar-h?{#p6gA|WOKi;H5!kl~F`AXNydTum~@-zDR6wwv9|jdq6B^gP^cPa}X+o@Mzz8ZFs=y`KQcR9dj%b=ai<`H1k@u=NILTqF|kwT-pa; zd%S(L6Z+1YzXOLAc!{q_{>9K8P1k?HbT)GZ>|%UNgAssu3Pl$s&ulNo$mo|FrcCv_ z@Y+JqrnRO=D-|V6k5mLg5}BXQ+#Nyc=fqPvL}y?30B-lyxk+4DxVnF|=J=e6%aS7C zQqMgI8yKbcspD|VxOO1g%$_=osG8b$WdC_x$YPi&+8ypE>r|WUZYR3;k11)&xiiJ> zKUSSsZuz^aIuP?!hPE@G4j7b(cC~NPGP&@5Gd<&^*C$_%ENs4*`_k`I#^kd|egU{T z=4XMzx}Zy(Uu3nqLEJU8qEs-F`+p!0YIt;{V`n5gs&fGj!exBS2qH+RDcQT8{l1)J zb=~TFbTrh6mD}I{{Is!;c^;nNvvagC@i|()2t=az`{o6jJ}2E~UT@*(`p((YX**qG z#Zl{Zrol_K?4FF56NM6F+EVOq5UoT|eeCq&His`7j$0;Q`u`cp-_Hv^YE&v$*LzW) zbFZVY$An>7g300NlbN?@#fC<&g?}rHLZk3aG++!fSN^gF=hpSoh$7RS^Mq{Wq(hhV z9dymahslsvEfqSdR@sHP$)>(7yRLXdY_xV?0_^hwUOT&P##kTzc{sakp-Cej4J51k zIa`D&Zl3O$YstRYi`dAq?PdUeJF?WNWuN`G)_ ztHatYLHTVrFq$HJ0~2s1*+sxp!63wQ&yO#1Z~+JF7ibKogVemtUgqXyv1!z__QMTQ zTy92g8wOss)7us^k9keL!M#~?qxx;}8FsRod!r!~khp5l^NSZ)s=>Ga{I~w+)~j#% z8jx@q63l0o>n+SemHO=Pa;KMx=q$}nq6GFk5CH92utQWOQsbFW5z_g`htS2xhZykq$4a=o9mu%fm* zQ%)N1bJ$2Cu?9%uyxaZ#CKBcHAp4RWd<$!zAO7PUzRc0BCo=Y(jU!7ssYK@-8~lsc zP%`L+pCJ6PnanAX(tV;H@YB|IEK%b1W%_L+QPn-~n+trQEhC%BT``rLbZH;$FkSmY zNA$BzPdJA%If}SIp5KG+^y7@}dg9mL-&y=fk(u$ja$jHPYyw~JqE%F9iQDmz09i~W;?QQ!r^&PgeX&y#l^c!@`e`BnVD$v0Is)t1;`=8t ze?wln{78dAzMGY=B24>Nfuwo0o#-ArsgQ!h>liB}RyoPfsYM(;xR_b$n84K3PIz40 z&d_LIW85H2R|;)#;d4aXeX9#2F-9`w7?!DWMSyR#`#@K7^AXSfHzYM{LVU|Vl4`yM zMAI`7o+y&OWPlIbH?fwooEj8FgoHv$8a|-;!r`o6kxr-icC9;i{_o@Mbq*h^_fdwaP{4TkCATUm0(h@ z*Mvywg+&RF#D>cbNPW(J9v8H~B5*|gpA4Ht>4#hS zqe78mE%CLio_-L!oqgf<#drdiE`_MUF8Dvj?oySWA`IoP0M~WYVg9@7idbwAW*1sXFGXw$nPL`N6q8<=m*vj#s!x$TzAwS{jk5~icnDZVMLuRz!oIma~C**?dn<(hn z>qSE0R$*A}`z_4%nwPuPYW_J5zpcvXH77w=ior%2J{O*w$>Dn-3r!Q%ofCyi88bZn z;AiYT><*f>HC<3gFdy7+)7|0$k7v6fDbF?ZLmjRg>b^wTrJ z7VHhiwUlO_c^St>Urpzc8h?^XnIgd0?YU|y9>=L7 zVRI`*{_w-cY2{^UE+U!7mqD3j0C30jDUDsr6hg`ms%sJe4loHq+{VuaMM ziusfBP&C~#ZZ{4Azxq6N#IKngb*_Q3+&=PM)QOg_s2NTI|Rmbs3KTf|lfJj)G$zE7N<@EudXE^Ufi!|x^v{surpGaeJ>g#p` zCkY--{ycu>-SC=t?ovT2Lc~r`5R9uZqdmRt`^^O{aE#5;Ad5|o8zPNmUNul0cC}mE zTT2Pp=5-m1e_&bC?iA5qR39~FZKgKYgjCKk{^4w6z0D4XeZF=eabVFT!0o<|FRaFa_kFT8w>YnMlSgg9+_gF~3r>3{~ zZFNUk1Y$j+Q~>**j`;Mu7>Y_N{Ip5Z_7pmvqBpaSJMw2$%>6*fVB`keOj@s06E1j! zhE4LkS4eh|#NPL7za3vLk%piB+-fa;A0G!rC6V%4gq!r;FNpwOw5>adyNbBC)A(J+ z!{UHXZL8zEt3^tTox7((coyGSNn?2dcr8G$$uk#R)R8 zwHp`^v6!rOgO$?7Ft;rfi^GSjO2%-wjvu}Qr2WVAy z-RC?zq=_}2tSWu)BSdqs!LMObCIS7VGphBlJ6XaM)&A3XVX@9T+i)>_WGK?!&c(8y zQ?(Y#f9A@J<`#$wG}nqxS|_0ID>AA()4s9qIKR=-aem3Q`9dn-C?4i7@x0ni7ULqt zDC%BQpj|`Wizaa~=1NCv>3WxGwRYibo^^y=;M>#rs(xLv{r`fIAMnLIf(tAz4K^kKm0Cmg1y`6YoczFfPCJ6jBbCF;sCIYuA^6RsK7Tx3y5Yb{+RarT*x7l)t*TBEGO_zD^G1_=qf%T6+nN_(9{WQ5^6nY~1%cmh zAQ#!&FQus-UFXRIrSNe~fA%?-kRCNPHF;lvm@QiYUxCUAtl?5Ft4b{mmh)xgjwA4 z=#g=5uMK!c>JFmV<2kPDI6K{jsBCxU!zNzNta&NFoL^{s9RM6}j;t>j-^8)n6&Rob zUT?Xvi(dJs7{hiydOZJ~8498qp}mv!Vh`djwB}px@AG0151L`&>csrR{5pbXO|Wnj z(Zd)l@!sZ61$y685;S)DnZ-zB_eHw7G`yZKS}qtx1&-~`GG-uR=h+*JZjvGPAa|?u zpKng*)Joe#qmltTe$8uCOgvvmeo^&T12Z2Yu1yloAaQ!QYQ96w5Wh7pufED(zJ>UO zJ=U|O_ZV~uCnwf`>lGNRx4i~o+0Wo!EdTHSsn4^uqKm=1IfwoWPk&jywNR{47nOc> zfqbH%H!L03YjN0a9{1pA-EiI6RWnkv0lw{d9`Y#HPXPAzvJ}}&x~Atl@__i{uE9o+ zn;!YGgdB+NV^=|s+zyTqcYLJpW`dt6_6qEyD&au!il;7L1MZfEEZumLN%zmb(T%N4 zg%=5pGJ5r^Pk9>S-C*w0PG4=#FQ#7A`HM&OCsMw+(@ziFcnfwp9^em=cE*E;YL8_B z+X=?Y>pDWOFGKCLD6y$%4KI_^1}-a zSjIOSPi>6)KAYhI1tUos=kwdpndB+L4^qKkuf>bk>g(vvWcT9qc%R>O^Rix{_KheB zyzAuTnk?N`?xe5Xt1DNJ#pBOzhZuMHTi!c~5?(8Oss~#B=#gRrH0)zC{!_Vt;|MuT zT9mP$&gL*yfER8_pU&JS`)>2>bzu5g3zywSDVs>p|c!6gY9-Z;tKjzJ+QHZoyo~#8P?P=fy2|!7=y>oA>E0h zW9MM}o@BmECLbXwRIK-7I>pKTXM`SiX-X@3-~TMW>i7}+jJ_&9y}k47=~+VynE8ku`D(RH0Qsev&Z5O^*EwKpN8-+NqQ2teP5EuQr~Tm`2+~R z*@D@oH&7|e{ZT;IivJ0qe@1vca(ErWVBU3jYOji|n%D>2&ulO8SX#zqu_H7-|A>zL zD$V-S63^uzny7Z2p8D@SWskM0&?oyhu;^Q1kz*XHI>bmMft7!~HC-IZ| zW*!tolMeI6jZ!60Nvd7W{nduPU-%IhJ~^jZ--S)QZ1npT8;NHZDqM6HQ9@V=#8@l00vtx72KKAS@#=APA;w2OXhC?I5aiO_U zHtuM3T7~~K^zM}$!j;O#iaAK`JP$?S5I^$-j`(-kW-zrVzQcdP1?;^ll*@jibx*u4 zz}JS&S!@pZ4ys0a2L9Eh#&kXa@q)-V-LnHb!BPp9mSY*?M7Cy{0O@wuVqXJT3;_PP$+bZ|b+GgKx+f8yAIJmgc zd?d_EsdDuy^c*PQh?CPs{ho_sf*!RGr>QnumRj1DTD)<=Brg7EE;Ee=O+>(T`2=Ug zNP6u;QYouY$AD{9(KH>jSpLY1-O5k}gBo%|nEKGO*FV9AFCX_u=_1ri;Cq>?oc9G! zmmLikDLL|q1u@&#p}Oo7wP{oyc7fguuCd6XaD}Wp+=BWmh8*JW8j^Tjo=qnyzuGET+oRd137*E+GNNY`=>*q zB0pCr_xmgF!Tp-H&q%p{eP+~5pjO#le-Oi`)fxtcAW8Q1;DQTzl!x-{VQ4uG!z6=&*9&Ie0TxD8pJHq|fOgHE&1HX~TC6CI8an zVfCR7VoxV%DC34fY(u&}CBM~pvwCM9A(SAtQs0xrCfX2Ltc=+CSOxjVt^57PR~sYEfcAFH}7rf0W2`*M47Z2dsYR*Yy^M{7K=fu=L|p z5C~^2LXC}Hl#0{Md%0R0n?>;!%kEHNTZPDKXH zuP=Ja3nVQk5*x%JS}r<3EJ+2pp)(ubD4qWfx^~f^+ic#NG?XAq+Gz6cp&WFzp=HXV zcnOQCkpd|+07cEucH_|Fvha921%3uM9|#g6utcwYE!}T$bypT)g;O4NCjPO&IpNil zO#BJ*ZSRq_6rjhOKF~K9uXkCpQ{AFqho>LwrBn1Uc~qno&|eq@1h2t=W^W z`9geJX|6r$BBVL}Q8;#AaH_Fr_md9m`U8@2<7S74B(~^jmB%esb^m2s7?)$Q5-D5w zwZ+M4Cp!v(mFW`!^&Yxywr5(=&`)9Vc$TYfiN^#=fZN#yrcSGyUhhuhe9!6qeDZG0 z{9|0o+()?Eww6@0xcIDVXT6|uz8M0kOaq(v+sC86D9TlByrlV~3y<@E{v>=TPGvqP zq)SGt?CnuVT5nTlV?_6>sY;=?lbp1ICT!Ua7NnIt?AvQLlNe30V^P)70c)xAdE35r z9b9*5gS3LBE$osROX>i(o5t9t?&Bz#@y#1Z#bcE{T|-{i#clQPxINknrH9r{Pea+L zF1HP^#kv%KT$Lcn!By`T+ENwA$|5+JgVW+O_Xo|^bCZ$SCj-pnPsZZDk{xXB57x(~ zL)!K^@2xQFY%RBG7WT%3(|`25%AVXWTG@eCO-iz)A50cC5*$7!PP?y@9Qm1WC+A&6dU3KF{*YJ0_xvzn#8r-#9#aVV*8NiBP%6l$3ZqZVWa*UtM zgLCMe>0GeLWD#!WN5{Bn7G4x7>LtJ@XmdS!P&$e%&rc z7wRA@o4dsrl-6o5aZjc9WZD11O8#?W)}T6_na!2bP4O{j%hOEP*l8&KAcOV?rq^bK zhyQX{oVUjW|9_GL+NkCkHVQ_dB^h54qXnU!5|9003~GV#5e=7#pgTlA@~>FiSl0ft z4o+;HPd)tKiArkCg2&$OPeqCXgW|>bPp}%lB9Y*XO2*cjiE?-wijE(C-PVjhNa@mBy^7+_!b z&|Vo;w7;Q;I;?BJN|xC-0(Ouw+a5_@&D}!g#-idjz9DyzBjW+DtFC$9|IS&{(6nop z{pw=)3oJ9_r2E4-8DJ`0#-7D3O38d4FYjYGJ;EfwxILYBe<{`3thfLGo8*=DVd zC-ti{!A9=0+hgg!JYk>wK zgZo)I;hG1}uQ7PZa(l+}ooSx>ZmP{SbhNoU(96wFu?6Z5`p#|(x`XI>+M@3McA*+G zhY%)H?iXEs{f48Ph)4Zg88Mj%WZb;o{{`ZJ3P0?#kF`X-e*ZNI-eX$t20w4#Ik9OP zJU$jyD?TJqCPdm0sj^dNb2X>BsjkO&e9#?W%q`v983Vo3K^jhDle*rE z7#_x09aiU6MK7EZYp2&|b;OOw6#QjmgLfVDp-isjC@?ru8O`X#1+0g?4sF`^H^ZNz zJRQF)VfonsJ&JaxJNAW~u44W9(>s9oUW)b0b8|_H%eazhiMrVt$ivcm>BdT|a}&3} zg_0jiZ=|3mje|buW+=fp81JL`_k;Nk=}Z)?0>%hGe_vpPxIw`;9uNw|z+l%Kkz)JF^8;;8hi#;-WZCF*;sR8e5rclzT=e{J0*7e%n({p7$I6q6V1(y6q zF>l!3f1=0I90;}7A8Y?aNbxb-jdDAkNPtT@j1RE%pOAEzj^T0gx&o0%>7TrSLoA{b z9MC4ePNUwpZ^&V~V**yP2u(hHdrxVj=Qqb4$Ide}f>vVQO$Li6v_n^_@7_ENjG0N0 z+hA|>d=kkBrOfvU<0s63$lwhuFRIDH-QPu@m{XSdxT&p|@bd)!r11YF0$<&NB!%`* z^4|eZJDQ&9{umT}sf()MuA2OzS?v>|jL-A6IX#}EV4^KX<%FUCKQBUr1A$mGAv__I zSo4Y6=5sSV_;`OP1ER`&Ri*jiRZ=7z)EbVp3wXjhm}#ZBsv6FKC^26R7JjG)Kv{+2 zI10MmhmW(pyS}_xY*9UUY)!jNbWwO$E9buvhGa~?1Q|L_SiXVtjhh-I`k|nnta+4= zSZgzpj2C61(2C6rtXuSD>GBJByIG=eOly_@W@vU=;$T5K1&H(xT0q+KPhvrcfgkvR zXL<=V>|WC*z+@c5B3Oak9A6&1!$wjfz7Yn%Hj+5qJ3p-%TM_kZVecJp<9e3iQ^y9^ z-o4KDY~OH*@Ksk5JArXyq~2e)DY@Ado!k^>T$uxB!Z=6ZAt#b_@mKm*#gkPB8ywY{ zFj_YvD~h*E`;PmT32}webbFMtm)B2$eg@a*7&gamWUm`(b zMThCx6_nws`W+sgjUq&RN{Z-u{9wQDvsjp9mN$*;eMHALuNnt>k^eUd!6S$aDB*$tAtPf&Z2kyKm98Nf)9vY2os zA~MG9jP$3Oxvy<~?Wa8^>sUyy*6invv54gg-^EMIASF|5VXhoc4X-CN->vfN#dj{P~y)LZE7uD41V#9m#~BH2DrbW-*1mmVPM`G_F^w zaG>2MArOD7c$Go!l_+}MJO!L&{x zG8*_Fb)=y`L1~XwgIo4)-nyh?myJed3(kXphlP1neZOaUJ|3pyJ0pdzsFX3b`sJTN z^@%=E2h6pju~N10upk<#h#e#JH9b|E(|qP|SqJE_v;(##;H2bH9f&Lok~W$TG;&D)P#O6#_I77hyLt+EH~)O0 zE{i1eSo_KvKMowr(-es2?b0vjm5^rrBVvBPPER8D$zSf_>)WtgsF18q?q~?6k?>z2 zZZ}avZ0Ji7hb~13zk2eVd<1_Gq%Kt7NBZ#-DP`_G!b!~xDdQu>Y-?GbD)^L$w>o`$ zhF>R{jK++Eye)%3LhU;h@y}TGYNI8V!KCU1kCm~Pzp>}S6`c>~Mov4Kwq5?COFUb5f{zIPRqz6Z zntp>nFhxyP>tlk=$r|dZ1*$XZCJ!Z-w)lSzo=|@CYPIiAwyCk2YQoYh>aG+rY?hRb zTLI<~=&TujZF^2iZ5X2zJ*8s(NKJV?e#)ePeMM!Za;Mk9Y@U-Tf2P!B4T!IVS{Z~< zE)#0SOU$#Pv|)Pz7!R^K&2Nm1oD;0d+$T*fC~6x65(qT&-eW>TKfL`agHp02Wt9@i_eaPnPp+ z?AFF-+@;z?RFeKrwGEytyfxFh{+INmtF+!n1N+<(Ket!jjc4-IH1opT_yn!(KI4Elg7iqvV~om&?KNw*8!q>^M2=r z)kbqDAB)Q4#*WCeWtroY0yiX6=&y;{v$<94r+vhJQbm9kr64ML?4MKaXd%u8d3geP z@SLIoj5^g=Y~)7X?3Z*!HRt=b-~Qq5n3d(X);Vu}5_^Hj(buXn){TqCJn&7!rm%6x zj*lp`s=+1yWe_^BBh0kzC$t$DSJa_F1x5e6*K^}`hQ|aug-lE1ZS_<29be^JAkMYE zyR5_Tfj@(NOW0g@AxQ*+dX#f?0<}%i0KvlF<&Og7U%J?|X6kD_6tL#I_iw{&b-e{n zM3+j?NUbD<*`P>%_v`&V4i0R0A@+X5n3N*6Z{xHM>u;}&!{?9FF9SyYx^hDjpYkfL z`=OTKN_kHIyWz_*Uqv$i+BuHmu1M3Jfsxr_q}BM_M(;^YOeJMT`7*$j&p*qvLx@1b zj)cq7I?R$;v^dec{tlfhHtbu5`fYqM#9t7Nl7)9W{muMMBE&7m)v+$GSoqQMGzoMr z^E$-W{e9Z^Eebv*-R|6kYe(=NBEN07X;cM~7@nf_BFDid94rqQhCXI=X9VmW5U*_t ze?Q8s=L7VLjWuS3jyLyzNf$b{P?Hqs!l1*m+iQCU8`0-4IU$@g)uKCi=VD`eW0~iO zQ*;A4=0L$WBm_7{6se&%e~BadWA=5Jx1XLoS9-VG_5seap+O&RQqEO#R48;I$CAvB zvcnd?Outsg36KMRJ};m(9uu8~?OU}Db6OI6bg=s4$E@Z&mu>A#YB%gI#}O`XeEHI= zE}As^o8>w-W}=mPeUenOJD4&APV}SP$<)Ne$`l-uQ0$V)CedGkso-4k{=>@0Pkpf0 zXYUiZQ+SEf*J^eSiy6WHjQQU;xJtRwyjib3qD92AiwJZImejK?73<{2X@BEkmr)0G z+1kB#vJbk!C%caF2lT$)4yeSGI^R;6Ie$U(x&y)hXB-qRaMG0y|1u`d|F_>~ZQKBr zA^H(QY3gTe9U2+i`^7uqyU&w9gmpipUc;YMb!J8Ly(>TUsew>OH zmTGep!rlw-muyRn+!=UE42Sij!}IJom}D3R;OBu*90upj8M)r^Ov9~x5I6UMSlDXK z{#IvqsudfOPCgnP2{pydh!3`p7}rPEuzj z)g*?~a<$bO$N0|2*V2}s7k3e5L5=y8&NY+ic@&BsGVv$ts%U#luK?pASGzIa_JJfI zE<({D5e`j|CEn)b=nrX-hxtm2vKx1`6Q1nLi0H+`r(?yv5JnWq=gn3UUL&>mg~(O6 zTG=(GMp_|o+>MTuN#8)G=f_Dd&YcYXxS;hfjI<^s#NSOOA6X?hZ@5`F_M9jdeIUqN z$%r7+fM5B*)@^T<%ljcH;m=;NAiP<30q%C zwy+RCqvXT;@A>^Fl82A5gSXam+xV3BZ~%>tVc+Z;@1`VJMDj0FEpRBfz~;ON;T3zP zQl@_j+?`ARe2kGvjh`G4OGKjb}te?6@qqYAj{D6=wn!&=Wmg-5`?)3o#BO-(qYBmrvWo*fbG zFmbPgvm9H#x601}+urltkj2dhU~q%THhHbFo1f&*r+ssUxBr{RBT9rt_p}i2ZBSE7 zkjO)kOW(BBaBjNCAR@3?6TFD}o<~Bg*?UF%lSlk1(cs!B9Sd-YbbguUr~zCu__xw2 z$S9RULYmcW@+j;pA)z29F&ijMA(Bm$6lorxV=XP|F3`4D3Qu_PaTG)G1WQ5n*O7xw z8LwN4Lwu{6L*{(5W$o(J=tZ~5?F|+MIwG+y{*@l~d=S+8$6`&+T1{wFpx>bnEpJ5; zllLMT2NR0QFt~6VElSmH9rM06_g^o4G&hQAl;xDDuO1Flrl)xj3>F&3Wj&01NtNB$ zxe^dn7pAc?W-1J5gXFIR77PGKeKGb~>%yw1_U#!rMST$hEqA(eU?FOz5#{ba@5%8K z(pf%N&(92${XN1f$lKfZ;iV)*eIERay;9L_cY(qVTbbL4fR{NQiV!RDUfa27GadGz zF=OwkTptCNH{KaN(CO-KkU;!Rj&WiI7cyR>o{uYmCjM_?v>@2ELJ@w&paz?Ym9HsU z-!64?wkAirYrmE;gzg2?`#Ta2vNT10LQ-d!U{8S>GT^3xoVuAQXEh2Nfo18u+B&@L z^c&$dmGa#fpRam#{(4Dq2vvzl5HHqmRYYPx-2prYb!*&s(}z3#wlAt4r`=vv>24E4 zkj5>WUhln>#RtRYLuta%5SAGn+~CvdbPQLL)L)pn?WqW=Wz29=dz?(-a~F2!$Y@?& z2&zcMlzkopl)j+oR|=!2FG9!9fg*?S)U2%LWG|LUwDN-Be?)MpM1mIWg;0DXNtI=o zcW$O76I*9msO>%&$F9Id78$!Z`Kw4|3#;VkQ|V_*q3*n=1tJW{?Q1tDJn9~66@~B? zy!=R#KMjparkbx~bh@nV^nt%V36XOjRFL7K$)co48cg;i^wT0L+FuVT!Lv>F7}BiU z`J<^R-0o!T_Y4+c=te2;%2J~1*m3Hy|1^=*bF9;JZ6wyH@n@HD3g0X7vkNfeg)8yO zYw%ff<4wCAIz_W>lPUN*ppJYvPQSWdIlA1i!{rFY`0p9Y8nQRr&mbwUW=h`t4{k#N zras-K?yslrq8~$f$2=V1^xjDu_X+5nMrr@KBW?4bn&Z*c8;Oe7$iq!9{X7Ln(WA2;xM=(Ij?c}YobW{p1x@VyK_MI=KC3a57*BW`3nsruI`XEtG4I`Ruhm9xgWG&kV}` z4m{+@+zn=OUG0hXHQR<_Dv{(qTunFD57s%hQ#hcG%G+7oWFWDQHjn6(z4P`Q<4 z<)WZ*F*PB)!T1a`Ryi|?Rxyl%oJO=kLlUvet5KV+-Bt}q{j;Wk{3k#*&IM9Z{{La? zErZ$$yKqt5wLo!q5ALpoKyfJ+9E!WU755U{tx&wUyL-_>aCdhu-#O@bh3BRM^X^IiCN48c3-0u7+u?@(at6w6h z|5IU}NuJ>bTXj~Ib~??L?zhuBqXv)0kGsFuLVvoF%1l(<^!$*ZSB-R9Yw}FSI)-Jo zVi7N9>O=ln>{fv&ZBn=1;Oeq@uIyWWbuA_SLrE^h$}OVJ58pVH(bmB#WXf+x$L-2Y}Nru_h;F@!BCnX z#tL5N0zx#VCH8l;Qw}97xcXNE*K|-Z4ko;^x{Jc zxfjF4=LxbJHp3uI$DBV#k&I_EZDr*aIIK|ezp8WWesP_?$XQHv*JYweJ6Z%c3N4OX zkmCgxzwFbz|7Np2_|as*Yo%m8?(n2z8%B(RHd^=)w|y4cfK-<}-YA{c>W0%VsRXwp z_rm0V=_}v4Eu(l!!QKFnNYvG=i3wR9IvGWkYCP?1j~yPSZ`$^{EZyOp5F>b|6=xQ( zv#>gKZ`qvK=g<)@Xw@Judf@ynR5W);cTM&c$o>JBZNM@FoQsvRP|2vW_^&~itafEDdph?h6qT{Y&cM4$GQXtdqijxgt7M)wo_%F7*8TFMK?bz zO*a%ml5Y;wBk@&F?iH=LC_EQozc@)Lm$HTM=M(#}HM^$IzZfGwG1f&1*%+PspSSJY z`%DVR)MZ3VD-_A+NUeJ}UFYn2EZhrEJ&KS)#|O%75Jy0T4JuP82Jzs}K5Pq!$sasO z^deJLufY&Zc|qFbq`HldxzAdT)05BomMC9jL|+cl%;k$&>!mV+%R1Lx%=_17*?jAe zYSF}=Pq>u$J-Xg6$=_>VMsi;wUuMW(6H$zteI6a&?;JEiQ>`2B!kqe@W8_nsmpe0c z@52r_VkL>zCjRMcZ=P>5B#^@GDo2x@D>+(X!-_`!mEelwnCQtzQ(+9tpZMoE?Aml} zzE7J1@fOA(DSVbX?Q}LMxz}0et-hB6r1&8+$BKYWb)#G0O4FcZ^*K}6PkK99%Eaj;Nt@wUqwn7$-PXgl4$B+P41XgpJ>R7`Z=Z*agxw2)kT zq1|HTVf^)nz?7!ICu4xo&Gu}!2Ls6BTRK7n<6KX(j=S%qO{b5KC{KdV%Vyj0anln0 z(jcijhS*-_zOStB4@5%LESE#Gte2U~;f85;h@~)j02(wc35N-Z8D@c4NoEWhkneh= zb=2SqL#nfn|6%);S-Z+=sXv-U!dA~W-k3u_=X&U&;~GhL3aR;SxUtVE<222#|IY-X zu~fjz8X3ma@|}KLml45D-~BS#GJdmD*m;eg*zl#_;1B?8zc^L<)!6gvO6^yV4{ZQL zZx~bP$9siNLagcSB~X#)e=vLB1-d6V(CQo4y!G z$CRgT*D$YC37NK`Z~QnvTJ$#4+#~=@pN0^-K^8E4(JPv3eC5~qksQenzE4jle0;f{ zy8ZW8ajWmzWqOa2lbv?%Bv~lJe4IcZi_U*Zx3&S)~cR%$%^*s%E_qBdp zcTPTQov+#KE|WC|{K9p9b+G`^M#hi#{f~cYH}wYg1(GM+$IBpx&oD2a^IU9TUdTN5 zeFw?ANFUa^sk7Uv@$Vn|93L>c9s?f7j7E*#cG)R}o_!pSjzz(ba{uc#7dlwYEYsM+|FX?4e@67}L9#pLxu~{{lAM-xw!2)}tsf_KO`L zA;8I2@`H^uIlO!Sb#x#XeY89v&fv@dEDj|hfy^D5ZvN@0INfz+X^k{t2$PR<&*aj zf!4`9OE;P2*^7PHJH-b{q9^XwrggflPOwBWBOj?!@rz#TpQ*pnJva7+^G@aq4Bv)( z2y7X1dr~A-e5Xen=EM=>6_|RX|Gy22>J6G)u?Ap5zGlgnp2C4C)Px9_xG0UB05MGvf zg*wcsAD&OZY--wkafOEYB;ivOzJNNCZ`$eiW+{A52oL6SkNV2YB{k(7Y22Fo-;_AY0z;dlCM4nV!OxyCfNfz5Q zmyR$eE2D*C*`Oas&RfF1by0Gcx%R|^7c-F+)Q$d^%ci61GSn*ms5v=jk`pTI7N^eJ z8t6{mRteL035%{|v=pG!2fM_uF8x#-A9#sM}ik_Wt>ap@sAZNw^!%Ft`o53c6bl$B1Dv}h23)T z6KmK8{nSP1hEj;l63TnsImoP4MSi7P!L<^ncuFw_^q19draW1^&4fqNwVDW0nAfG^ z;rD1Ea0!_u^!;O(Bev*To#BnDB{!af2a)7QcWcNms_$XPpdCD8XKUDyF59wDv9?fPyU4ik<%Y8JQ|f8GcfI(F!10 zla50|nSYpIk)MR!@-SSaFP_9Juo`0t$A`*2>OZfF;-J6hNL z-{&Sr)JzrY%2;A!6-bTKP{5#pN$8PP0e1}y1=xEBUzoZN#zG;Z{$6Q^K*=r6(cqII zl&wJS4htvTorHrz--PLS8o!ep;cVO6luLZ~=fX;fU|U<)6WHRydV56L_A=i?NbXySJBOx&Na}{%5@HroQ+~eGvh9t9|LQ z5lixTe+igJZ}>e+u+UON*zBy)B{y~|kv)WeBHU3N)ACTV--_7T(H5lpzCqL^IOdy; zzdz2il=^5;gCwB8Fk)7F-zt3!ihnz*-;%odpS>75zPpfm!@b4{I~w&)e|jsJ55K*c z`49Y|Adi{X1ZjQTWw3vTk1HI%^#*1Ef`P_PmA;8V1tGLTfn*LKujuM%_`>N`xW?)=8i7S$s+(xi@0GU~A49!N1oLbP;`S>u4T{pN{S!t(DDeEaT-Sa*#i;8{N&ipKbZjl6OZvh8@+3 zkFC?2uPi*D^F29LeN}a#y;-OMl^e9`aj-mIhs9}mo-SV?%&8?B67lEis_fxdM;|#d zpVH8xfpF@!u4voCiPW5izB02iwJU zD0GAv$I~Gs#{}@XJUenuMLXzmkHjJ6%``1#D57g;30p$p%?G*7jzhx7OwVZuqdlpl z={D}t8uIbOTB(9_OWUYsCcQNPNL@e|?wadj74T7deD!Rt-m4JZ3hoCKJ3+-e<$A(P|W zwuuEo3W$`+?6S4#zk%AyE}@z|C|U0#Gae|`ud*iDF79aE7O8dS_VY{=8%JHnl2!dY z4(aa?)ej%RT`x7w@Dh{ECMG4739utTVbFK?w|77Ua-B!uT5W^P!&Rwz5~qG}tm00k zrxAE!<19|<&<~3PK*#)j7P|KrGktY%Bu~DlF~)@ihrW8x(fqu(EK&i3HOsO}tw7PU zB)5Pu>I;2}Zr<^`Zujv!i6I|bdQ4}Mu7jnY1hO%W2PBZMju3tqSHB>kE5AF3$=a`u zuL!Hcct&J9Spj5(pSR7)4`%Q3?z<-Ufzff7^29^tK?S&TU~%kV&)>g$Lh)XtA2?pi zndI|`L$(0br2Z$XI-<}QJiBJN8T#*v!iTwXaoL2pY6K22VVH}HiO6A5V(k{WBLJ=k z;}?zg{B40;b;{m(XxB1F0U-74OgkOKoIfU7ESCP%`eiz4=H5iUJ)KN&P>RbDjc)+v zbDx`e^6cGblHBy0-b%V&kLDS;e;>aK z6z>3e67CIsTL=UbIQmO;0hxz74X3rTthA!5LVc|Sbi_0~7q8&nVz@$(MML6yQv3-& z$KMRK_Fv=DJs+?K1sF<&eSDCKen~(X&w*b_*m=3GUM8Z_u*jPg*$*!N80I*j0Dw)j z&=lMscG+m>+gx$ERqPY$d(1mGbdDC@$A92Cd0}EjC&PiXlE*V%$fvB8 z0PG=vs~>z$i$B(GxClg1t_#U~J^p{)#TOk(XEQO87^KmruHMIL>l8vn=nsP=OvTwI z4)wQ(jW3d^)p1Nh6)*54aM%VT&wk^D=+hezE?uuA=O1TjbM}?41=v>dM0&5+6Ja3J zo;Wxdm#!_mMh{=8bi^sSU-pqIJ|EJwG-0Y~C${HizF{prRi>Cdew@T-1^ zZlTmH*-J6Wi!u}Wd#Z7Yz^xznnqgAG?NsmTGCiJF=jEsV+ik+l5uXMn%@JXE-oXme zMb#HxM@@u05U1aF*jn4+qd(`rsAg88uq%~F4AlnmSKa~=kW>0rO1mOlrNj|?NRd#A z!kS&F1n-}J@k@mVJ$=ZMra-+b3EU1r2GD!eyj|?MGdpsYDtO#?Bx2P#)0@a2j&-Ao zvI#t$$0Zu!Ge3JCYMQmu9u*y|JDInyGvY2g&X0TVZZ5M`KmzniI}s&vB-?7@T1YPx z3R;x7GOFTVm;Gi%pKx6%8X}2l)vQm$Y8-IT;0jlAaSg$#na&^YIXU3W!GF{K z{u_D7ky&$JJJlR!r8$dED-sXOqWIMFV=~hOgxM-+3P1rPKPr# zE7JykKe^}0(xQi-n^0c%IO6)kah%J)o@$<+x={Rz)vam%&RD}I!zZ2x753|1G|~4p zkO>1lm;ZN^+}}t6!qqmqOj1(}8&8N#e=U^_UmT;*EqKgrA#oy>42HAMk}9tUhkzjBJlEfOxDY1d*1oiv?S!c=p=N+EFOM_?JDTpa>_; zJnzrfJqxTR^98~a{GtV^+_)#nWm}!}9?TdKP>4LXJ;9VUfALBk6CXMrrRRXXuP(!= zyhj9gh`>(ni9L5QRAba1jv5{k@z-q~=pN502DDU^5(7Ng`2Ya+&7KjWBJ>i6Bq#4? z2oj9iofL^S*q+b6W@3G_G4^X7ee-s0*9DTWCQ_iWqQUgM)EU9Mz6kv_=AN4Id|D6@ zuBw)7H?ZEQcSeSE%FL#n3r!40mQZkrfs)fI9YqrH@rpVBKN5-W&*cxv)lDGMl^6A4c z^mnWZ(%uMzMJd9{mAYQE3c}ImXd-V(O3l*DibC90WHzz&`ep@<)(~1#_+g z^h{Y{P!jAi&xS50KX}W4J?Pdv&B<72WGNVBGA^k=ehcksS7Qr2F$}e4i5Mfk5#Fp$)pv>P@{@K?Uf)q<@_Mro!5w{}yu>v1+BR@uNqE@DaLG#awm`L=1P0 z;~K}w3Ag2MF-F-GW!gNlB>`0eVRWBp@6^~4u0b6DnOZbx6ny#&Os2BmtB+`vY>xsD z)shs{sY;`;&S7$ID>XMt^zr{PEY-UF@@X88&<-V>F%+jZAOzCxkU~@N??y@hHv)valxcdBB2CQTucY1Z|OAMh4%BrJ4F zxSOk0kssxvWpp)}*4eKqzSe{0aMZKzl0!|rT4)yM0{ugGOkP1YdZq*rsH#(W#nKV0#+l9eTOB?}6 zA|Pu@ck|#sxh3XA8}Z+tZDLcD^w+@!$#xiO$CL{IOP54J=85Odn>j$>dHeM}=ldm( z0TQcmSW0kq@BZ~wA^EF(c)6;ANEUK|u<~yncfYr8*bDvsBmHmEgQ_2#rBvFNy)IdRq}?0OkRFnv zx5koQPWsH#rt15U&SpN;2c*&H^`T|M9*>o8LTM5XZ!SZ$9opJW6{sBP^yn2g>V{Ief|4J!qHpz!fbCf~>_jn?;l+J`*Wo{7@mx3N9rupRA8oSoGyCRuI7 zG}r7_=0ZSDr}C?S6@1d?T!a**j}T3k%w3+2ecXWWKl8fNpMEW3TI;!QfU%u z<1afZ8yh~t^kh-+gLNbd0_}T3Cmx3lXiy!@sAP|92Gy3h1@lGN&+6*ZPcd&APH_I# zpJSakfZ<9*`~8aohHdE45N zY1Q?oNprAeS!~UouIri2DQ~B}B+R38fPHwQA4r zn&oy`8Fd68^|2eKTwEJ`|B!(AG|z2jWFa|I=5!0Uiq}|HCZ{W%Cz$tXKP!HlI{>Jt z&kc0dMWH3=$EP9nlN}&4q_@S9cfcQ=aHTGW?qh+W<(o4^gEc`4b2UL()jZ0L;rA6| z(P+rRVN&eFbBsT9JTw+E5iq`WtPq_hzZMVpMbcOJI~Vim$s}vkH#k*cYpRA2e=0q` zpA(;n`14kNF!A}ls%*@4H2;&SXH*xf_Tc8(wQx`{t^o;Q7k8;_#8fP6={dDRIQ+Ab z_dBu&xt{fhdj}A1$>MP=&uYlqkc~i=dBJi)$X?zPSMDc!9jI=~#AtIRnHV9ElnJvp{dYeicN*z&OWnc2)BML5eG>DqsjS?Ka)G8h? zW6b7#DPj;fB-!k7FB^#Z=Bkp)nbW-?fghY6&W4DCt{{Gd+Gc(z=zc+G^KeUHU(tub z=>B&l>=M9(!ZP{UI)-${RrNiOt@%CT-GfX4Sxr z=n6(gVR%i*rj<;(ve(6<`Sx0Z!a`(sk|HfKoL~EIfavvN(rrXBEg$5lGZ{Q#daeox zj1kv`5-L%|b|*$uar{zCyljDbO@CaO$3S6(oyGGAVk6r-Z@f5B1~KC)*ug{r7X zUdzBhqg4`ai01W~ID#{>k5tWOlg-09D3!_@b_&p};W5Z*I#6*fNk9Y{clV;PJXm^IK84DHoTDgy%m30Y7GCI) zJNuMO^C?#X+suh38|(_HhY67 zlvo|#QZn$Z!WB)bfzKxa<1gSZ6weDnFRNjmYj)2=4a5vwtL6O4i?t7QGMD0IOpwwO zd5@29+iXpr>Umi_o@LfgBn#uZ7`lTDE?mQ;@S$)t{LK$tfEAK#L8GXp&SitkdvQXn zS`Hh7cSG@s!9u12uEC>HyKq8T(~QGW z{f2cjQoa`|OZ-4I))Q@Exm8eX-G`){3421Ru(vF&$KNSJ4Hl*wwHNHp$O%|vuW|C< zA&&~C-nvvPG^22RYtH3Mxbsa5ja6ZL+g2XjNO73X_SV02URAx<7Xrz7O>( zqr}`1n#tHHPL?>k>y#C+DVZ2milk1ii!PcPMB`sa4~-~m_UuEOFu_Cv1yE?{M@iT& z4d`NK@6ZU@R)^pQ3lGI-e&C-|7-l&Xv2%ex4GaZ`Ys*O$j#mlQOfXisu8A@FG^URn87vg7bOib3dtt zaKpALi&C+IjmiUW1IdB3NLu__DM4S98~NuM<7jCcf^c66etcGu0~ZUW$}lG97sgBp z#dp_7iT`*@WSattwGQY+@RqZElW3FdLJV(`qK9d@d$fN%*`^inUSeQGaL>VNzT{2^ zobOs}Ogxo94_Nu&D&Ms#)s#w-bPeI$EoS~{jq{KJTu7`*p)g>Us5_fxc47zChF?1o z%J8>nOn-IZV6rDvuS5r6+^VH8;)Y6Q)c1=G=2BGq6-60ol&(>!fXZ8(}%m5`=fF%Nbq+ajt76HwnHax0oA=h?Qm#<^Cl*_@B1(jv^* zL@Qmcv=RAXsve(Yj}Fn57Xjl?Z{X4wM%jdV5jX|eaV1ZAqPsXKJ5Rg>p~xQD3dtzd zrqRYs(3ayK{UVr=@iP-}B8yT+?o7az&R+5_`r-cj6bfkS4OH@(;+n&R13eXvvEo>F z)#Sa^Ehu-82R}ydCKmdC`iWF2$+5S)!#(S)koij=-=Z($9cw%*hJLYy_CkgfeVXj5 zQ|HnjX(@bA!5iO_U~yV+Gc#&*->l4}V=Pa;48!eEm1Kg~d2a6O`__~u zM9UFzZF?_Pio?_P;7nIGnT^M>a_=R!Icl6Kl#~&+fm(%b}m=z0CoF6Hdv2hOA z+pF)l|Apkb`B0(R2Gvx$$vrA(=kf6Z8F2H&)8c~R*Lb2nlDg-z3kXgc-T#)ezwaAk zZJ4~7pN!|eE?b6U%eq&{I>QC!>FPA##QVTa-jAr&*VGiTPzPKfLbTw+$$$h2lsn{^ z1`JRWA1M#40w1h%Wi__%r7ZU5>^mJ6>R_Jsf>_*yjZQcos@3@%-V{_`+@s=>+5`F`McH{ z;HbGhD=hq>qPmmG56@)h+xrLx*AfZuLvW(3M?k?^nRrMY|AS^eDIBLQ*UO(koMj!^ zuQ~IcpfT}6t6mx;j<^DNB`&<LlYBjtmh5$b62Q&Z8Gp(l6LCX$phrySM90AV#E#!O5h@fUgLn;$kvXJ z^c85?V~EQEW-gM|t2~7pz1-I#Y?aqwR|>O!k*)5A`m<}%zKvQ%XzI~c=w0T$0N5VD z>kG$}Z^W%YFe9nxR9gvK0(|6@%M!un^JD-T%Dc9q7ko+EI=~!Zlhz;rszf3o98FTF zBzB2|)eMU&`BQ4Z-8qCkm6V=mGV`{RX%tS0>+kt3niN86|L}Mp>8-7YwW;K56Fyz) zj4AqO_J(z<%KJAdU_&+rZi=1NR?+h2ctgvRMEjp{75u%uJ?_`vnn&@>cm-77Ji69aradQ(UgX6>-KDVQ;hoFoDlIGJDsB3uF5z8pA*T|Fftxdz8Dd-T zsnCA8_vwiDF>$d^IFjM$2wx%bL1OCp1yo1Zf{oJv*+qgS&ZeuU8p}XO? zpwp<$8Y7CrrCQ`ZdBj^`zgNR3G3v*tyZ7SR)<%btp;uR6i_wnfpyzUDn>_5C2-a6C zv1L{wiw1gDLi4U-0(Am3U(d-KwH|S&h^;u$q4uCSee)yCjPZV6nYShFOVU%se2QK` zH1>Etr7%x?G+?H08V4-bKm@t;X zsS84T`8l_Xq1>$ypR<*nnwrn5era7Ns>WrkpZ7hEY~ObB%j8nwy* zm3re5{uG!uEg^7Si(oR9 zS^GwZO}k+&UvBzg+KLP}G~n#NWBdQykp5>yAWR4MU^j8D;!m#<6hP|C!KofUafe6B<){#MH=p~&ILKVe`k89qVy5_-vlL3B)FP(+jUN7 zJC;15R85!6HtUvVJQELA#9O7sK{2#5^ zAkXWJ>E0cAFol16d46LGz#+Rj{UGfCx-=?o$D_FK7mND+5Nk2sO9cIGN7KSTp3ez?sboyHyf1CYu$RNZr(ob3Bh`ZLaw78ha# zyV2Sd&+0lTb`ozEk`B{J&rfNlV5&|ij}v|2~{VGN3wo=N*VXg>f!5%%Yf=s`GX zrSrM=xe+nr z7nWoV01V{=PY__TlYV%n+Kj|nT4gNHFw_ON4#lO=odVnQiTnAFLD<-eXvGL>QqU`f zq^J3Or zlqRrXdfphod6O`CR9(#ZqmkE1={v)F`5%dVXI;+wXCeo}aa32`@3t7F@P#!$saT!R zUIY=%{$!7JKB32U`8Kc}=Ne9P3VSuoijKQVA6X(; zLBIhDaK4lvUs8eUUdB^A69XeFiIz-(Nz7f4ApQ=|1iMmASs(%oa2xoN_X&`uB@qV$ z`d}A+Wrpzs>`$vY1+g{V3Lv2+BiI5>I^hTZAellFpc=kADSZ&}{8_u+#O}tv`d@|P zV)#~`ibQ&Qx2EKMR1wBphRKVZ<($nH-hB z^&mNAN`2diWvw2njl(b{N$tkDL5$&p>ciS+*Nky$kDFE&ppOxh`|G+3>GFd%qiNf@ zuo3G175>t=SB&z$LCUo$Qr-m=+QJ~EMwtt0uZ0vEGg*axAJu2lqzqFt^E{Gzq@8~R0L8!sAXdPA{fE$81$0rWKhXHWG#~C;RErwyzcrC;(mA$J0VF?YQW5Ee**Oe?bsv?iwC-oI`VLIv(lL2IV5bNJaT;( zI+8#w?6VZa=i9b{-_CWSldgj;@T!pHEaa};vf8ii$7Dry+Q>w$7GFb7#gVvzzMMCK zmdals4M*#0CNBjut3|H)PZ|FN75ur4l6^XO0D}vMPmV_P2qP$r4G+jBK zaQ7SDjB_l`z>cth$7unhDly`dg7!lIW*%2@@QSUSr+K3<7_W7FVn3ZtaD1XMh3f|` zM{hh(%T_^_8}7n^v5Q1FiBr{Jl$Bgh;o&Wvpc*!c(|3m zDC|Z(QBotnI({n&=0$oq_$NuJcZ$5NSki*QC zO+tk>9>x9d`88kbhIpHP_rsDV)#d!dcl>1w|LO{)`lqcFKe2-+bMn5gN(Rz9{ASFHD46@D~aiuE&Lc zu^Kk#V&%URra}~!Eyf%)wd*ee=r6TsV7l*VT=q;jiEb8;qn6%E68N=Kf)@84ScLGu zZMPHB%L-ZkWK*(~jQu6V|B3+Ajbo(Sv(5B9yDGDr72Th751*6bH>*h(b(Rhf56^2i z1}d^lO_i7b!T=hm!1@m{i&xb8zHxxX5zUulk4iRXByiRRrt!kn)ha1p+f4Hy1=hr^H8`?PlH9(`26r0!hT*j^C^6l?G`#h0F*eh9CM|RUU_o$6DW?qS?P^fP7R$ z-v|`c?B>XC^LqvXx}D$r-%t-l51!?w1YvAZCu>E{{3E z>nXL8eZg$8${@qtZV_+pe*G>=?EgJFYljCupCO&Rbbcp{EFT{hU{7TtA2`$r=g4p| zB6Y}L8*zyXAR+Ez+aI*s+w^k(WrhkxH8vboEr}Y@6UJ2if-ob1oucgn_^T5f*>4X4 z14_24JpT9O`X6e24jo*UYj|ehL9_I4xwTl6$ybOEb@v};Ei7le$?TK4HPd*;y&>OXZFvI+WI#F4{kSeLuAGde6q; z6mXOe|7!ca)oK)KPOWD5WWlk}lk^rr0YTY(E&RzGbE9=eyOFL_3pNuCZm@P;awJFy zDqjOfg0?eIrdK!|Mu{VNuaWdv*B1P_cl<;HqlSwMu3n9g3kCbjX#WJ^dged7M@b9p8Sf3umZ$%^v0Q zYC8p&O*hSXrahl|vte;N)l~yK`cYDxaV_E)F6__AE{>+uor2HOC?y)7 zU7`KTN04$b$CI^sCLaPyvz7RkZ45x@8CN$Pz#J7s3OVgUN~YpS87-*%<>_j!IU(aD zWswhu6Es5w7&m+8cK?n!u2AQtAo{og%g91DgY2%s8za4D_AOOY7Bhf?z1otO(&02EY%{*3h$7Dzrjdkloo? zXjcSs{XHSzIij9ii<@xLNlVJm z%L$+&4CAjuP3ca+1<|uSyCVcdxSWoj-dLq+23fuif|=L`qzni%adjQW<@MnmV0ahY zL9*4D15k0))`ucI*kH_wAQ3nxdc;8HA>;-wmhpN_u5H9w@qZ&Xj(q#>S^rMcyY{g? zkFWsOjFZp@3^fJY+|j2A{ciu#HONjzM;uNEIx<@+F5hyq14pW(NQ4^{l>+`E#7eqD zg;TKUhk|$u#|(U&P8S9WWrzNb8N6E=IW5xfS>T`kJQa-iqHq`Q1gLtQzg^DY%*xJu zC3tyU+{^5x!_0FVe|7U0G6;&Vp5Wt*?0q0~|L@%9La&h* zzv(64Hi+eI`woGBW9*nodVg~}RuJnw{rf;b1*BdC_w&B`nGfN=h$$v+QKSE(kO~(f z^_w||E1vo$k%+p+sqcxsWWMVzs`eOFJJZ?T4~)hygB7_}c0e6{%U<)py^cx0au~T? zYq`7>KmfbbAFD&E_tpcYGKwTokfkJ-bq;0;V-cD_L9Uv zkB=IeI~Xg)`cAA~JO>M0M~N9>F`Q5QFWMs+JB*pl$8LK}OZW~zkQ5a1s|xpqzmc3x zvV4P{8%3O{$iMxIACsy2rXc*24owjZyi;L!oC0e9O@!D>J{l%wAB(yaAwUgGvE^6S zad80V_(0$G=z{095He9x4Lt*FZGrghpdtBh0Bp@5UMP5X8Nij}q6tQ_oY~gWztCkU zCjM^Vr+7?f{R9mQLa<)}HFXbWzys`K^!szt+0FZ_lnM&;G18p){(?ZdoXdHRI`HFRp9t zMRYR(->Q7ew;AL;x-;x}-V|XS3<03&>2kf?f@Ms(@EMR4N)Ki2ga2vf>hQH%?BQ7_ zT~o41W(=N)rxLc33z+-Nk?pID;yF0>kX=Z=r}w%68+3jxFxN+3oz6z4$g9sOf3j3- zT9H#b2~ZC2aoI~Gxc-JW3n~C!4vIW6gN*gZh(@){vNJyVY6;k5FqK-QvhPD>SlF9D zHbT|sgPjsSjO%n-ncr%pr>8!^%z427nRzcB2psR5pHB&IYPMjwCyot|3C@muv$VA2 zndq0(4ZXmqwGh?+5DS)+hGge!bPL>fN=$9`yZ1X2=2TieKRpPJ*9GnFAraVu*PZ!U zKO&T+S|@@zb))w7_s!2yv7BLX-tKz)7i-gElE%dS#XYbZ5MeapaAO0)(+rWO9MRK%P7@A$6MPQC;Y?D?xv5zsCIqA1;wel_gA$Rva#|t0}(0wNO@A$-q)Ql``1ut!{${Zny ziT{-!aAjEWR|^^!ejm5$S`=U7AY$rqyI?*9PR`>?uZtTqoQoWS1R;%2LlVuvWJ0CH zk51=8jDZkSPNz!Bo{p=>#<~;-qT*Z?L|98rtbl=;D#8In!S_g(LH+#ENd%qiYF#z()qE+f*>R*ySwJg&7#FHqwUUXEX`p$B)h!~ zvoEPE-#FVpVk%8}rm!&6j7{fd82;iGCUGvN6hy_KE6JbBaFRJ~+0up!9Ut+ z%87NF>V21wrYxyi`u}kCjg6TE-L|o9b7I?=*v`a!!ijC$6Wewswr$(C&6}6^obv;^ zyShHq-nI4uQ#E-x^@E!6=NxjXyMtU=VP5<5;n3)JV5_hjNW{V$!h?~gyKb)|9t=5b zZRb76>4;pIc$FbBsxee>2FPeF;s8DM5m?S^m}1DSCy)$}es~yUHv${Im097P8*(^@ zWxiLToZPxnW*=qQ&Ew)t!4G*G|!Piq< zMa4k))w8;W^JR(DA`nIiq$F+|;&gH_kV@jS!q&=^)Sx%ks6S(25q(e`pUT2^hA+9@~*nZ>) z`Jeq=3H5hnw1UKCoqpc1UUCp(bkuNY3Lz6%hNlyY2&hZ^I{lx=$gW99%Vk^6c(uQ} z&1)lsZDES|YCnwzRLud_7>NC~cR9m^pG)BP5n=1?GYavsvPg@f#|$8mpyuKH=;M}y z8#)6J9j{)BG8y_81fBjBRbJj17xT_*F`5e3rK6L{rcUe2cD^Qf0trCjf6k5}r9k)e zHM>*bojD&ZuqrQ+Zvm|xL2Xes(oCn9H>3+Lyta2aJl3zU|L{71CDH%p!KHXKvV1hZ zxZnFezJjCfbnq2ISuVhG$T#$bwVfLvOa#4RMiaybgy{Ww+WbHtPr9Q@4Zpeg2z@zR zmT4SCu|zHAq;8|_#C%)t_a)_bzKk?@b9FeHYtub(VZJ?xZt|G5@msUubvau54&D3C zeh|hqs0=)#{s4l}cy2s43_$H-fn8pn|B0PwK(fN~Pm$WcR2tt>WsmRXsLc#AsuAjNSD#6 z-Po}^mE1S>_V}+Mwck*nFH9y=H<^03k{ASS(oAg7A%(z8n8?A;mxpSf%BSY(#3FrM zznV}fP-@OaiqEMIB%)5o{s_$>N24CfMCUO-P)OUW`r3F_+w7^_uH0x?Z|&&#KCIVq)wGGwI*K*8k+%bH_59DvLXwHf52V8yomWfcv za9FP^1f}rI`Lo*2-ecj-^){zs5@=AoM9?VA`jUv7gN(}B%KfR)xd|cfd){nt@G_3( z@@3w6n}6j0&Ul0ys{XyqOK~Tin?fTj>vL2<+cU2T@(Z^S7p3@5ZmzbQBE3%Qj|W{z z3{1>IC==YG0IW`^#gv>j`Kh)O4Oz@YUlS7(q@$l5SEsOZf)kEKQEoEb4MJVSw<8w& z!{>qnsXg6{r~9sx_Q3{p6QF&252K^u)VHeTfPYNRs4lp2opKp%Rp;a}QZ*~dy`GJA zoYZfJ-b(u0srOIwPA6FHl@_CypcEo1Nn5mff zJhLm*jH*|7W@enl8%dcFkmUz{Uq=?DyVBCq3Z4W_iBxH~C;;FD_b(W!CGda9;Fb+7D^XU-!REBrByLcw$1S$E+PLl}9sxsB#? zUm>eKyRE~fIlYV4+`^;yMKT&#<%c16-6(noLIiHwCc>*KwP_=x36mEeWnk@<>v3Xy z8`F>rl3AK|%h{yJF>ZaK^64DQ*&wQ0{icQ)mJk*UUp9NH6y-6)8> zJ6p@lM%{`Y{FpMBP}j%T8S5W69(R|h+hE0Yczsgy?DRSQEd8%0>mNQ2LovPpd*`tN zYXHiGg4>fm*!pV=;ChDxWNB{;hnh0CXU(Ro^48yE=a095EyBnH&8Q4zd1&+EBhn_FK0VJ4h8Op%Z zR!f(i0-Hia!!@Yz$sZNaIm8K9cySG9rahMqibX|waqT!G_Fg(L!aPnl)W>X&On>Ke ze~CuPvFBaQiJ1dco-_LD;~S&y_f3BUlR#ndKIel7spR#_D)yZyNQC;-+=CawL4Dn( z6-bJ{QYA1d91>rqOU7pA`BI5w&b=eh#!K4(-S`nOX&;eGoqOWN%buTSug@hKS}87C zFBaTOiE6+k#Gss0mynexmq>rRyjwcKMuu=XoQy;X;T&7bFk7i>GpmY%7&C|IXFDmq z>idV?#wAd>j?3`pty5AeI2bqtYAvGHkKhBz`r2Y0|D%n&S z2yVW~9_dY0#P*!w6|k)*m&SDAdl-{$Wn=V0}0Fn2$R;%l4PN`+?QXC9zHkY zakEvUN>j1sz=13VzRQ+YFCL6?`R$5fR|r}4JoZAO%Pyb!cdYHz%pU%=SX-+-8z#e@`E>k&RUV%zYunoCnNIWwgE1@egC*tHqWNmHc&ngChU-^R3u_c+R++0I1N5pw?mf3(C!q&083!UD6}ss=&q$zB8$A&&yAzM>HSSWkULWyMIt6o3&L9~1z|$>a+NeH^%*i2uoF zq9DOC>A5YMluL0Y92@2TFraMaBbHhMJ5WpsYywrBZE4mid=E-nLPRCWR1#L7^9Rg8 z;*XF9R2SN~jGZEMRvH05j`{c?yEj4plc6}04U69O`zsIq*8d0Fk2T)h{u|1 zK)Hnfx5@fsC7u^)qS4BxxH%pT=J^$%h+3-BQbpSG~7B+0VOmCFc-S`zT;ns1Ym|C@vhxR*4c zxp;_2*EZo--9;pvv$q`s+WYJ39s>#CUR^-=4b;;)3g;dUlu-*r(7ov*!ok> z2DGfQoY77$3Bu8o(aR1Ku&TVT8%^^)0hO-tjtJT3V^ddtcxBhivod_lO-mGS42mE2 z+|>d#0olIk-Uiw^pQCnl&KioR!9_q7)S_Wp2c@rK(iVCl348Unhcy&o?T0shV4fBs z*6G&Q&3|Zc{}krSiW3FChfY>v8Z<~$Z7j!Ot11WeSBh0*qB8|f zq@U{^G7qeq_ZC}2HA1|)YvVl5N;&NTr)PII-%31y{qr&GpQDPAFUOk{Ya^>9XZ;oG z*j%_@vNaG((U67MhEX`Kw~#Z(!dxsCUL|TD(*MP(o?nr2{)nZT)bRI$v%sWtD(tFI zpcvuX`DauA+JI^ul?)4s!K?QB z3&GqJ0bXo3hw_uq!Go{QMgwksAl=(3#vM5l7lgGcXZLcq7fX?FGQ*!9HM%|oS@|SP zfSS|INAAj&FU&p%cS8+ayH40@GBx4So(S!4qy14AF z(s3a>#-h(Rd@Y+d1rnhSla@Mb#bLSNEXxC7S)1I9-vTXv?N#UENp;euvdn!df9c%l z1kV20qFZ!7wQ&H5T_x9k6s`MaHlnJ@`$6wTloSL4HaW<_mCj<+k;Rk9#$uv}l%9)< zFh|&SPEh+G23}ik5xai*{M>6hWoSAu?bx{MwkJU_!pQ^Zi3aD+6*HLg2C&`n2JRBs z!7J6KFjeoRd3W|PE^jer+rcS&Hcm1UO7L}jv%6gBk*6DDoG00PoyXaGJcwUCbn7~< zQ`-mu{N0ac>G`i6gtq6x1OrUEs1OM-Ou=BzqM@S_GR2Y+k{o+8{K|LwSWrxV+>ZW3 z`SPpA1CEa6)*DP2<|4-*Dy-bghxft3I4X~p4c)`_d2ihI6o6FDc9F*b4sB-zes{KX zc7N?y3VH!#M)0l_ye$781qlJmw-Xgn`PWtZbM5F_m39}*7|7BA&OVEErPR{OPRtfc z`w(dt8E1CF{b%IL(`dBXdkZG>XXk%J<&{vJvIb4cqoNQk)pl6>lquFtt-p4TlENt6 zbEd-rD0~jrY3o}C*l@!I)=`0}C9zpVGGx&FNWslARFH(_Sda0t+^oT@fYE^!*bOvU zVbhmeunbKB{`mK-XXuo52rJ4tfc{uuWx8fi9MX%5CqKP;F)cpe{ zG~9`#k=|V>i;S^=60_@2| zv3>QxPnbR<3N3K5SU2YZh>?unjL<%c01$kQl;$v|%g%x&lyt`+zg%hx7C0DSIF>iE z5S`|{ly^m>a@o`iw1+|zS?Ta2aILC-FjXlSXh7e~En?uvAp&r#q3^9fO*KgAkahMqAQae%8kE9L(JN+ig)p>MiXu!b9_ zAKjmc*Cz;%y}S{wi=W)!6sfvv&;r6DI7RilM?8Q!zpNgXw2V(<3AADi?%)Tjk-U%j zX~!MRCQAe`K-N-4^pAdCOep>wZjV1QA|jbAMngQqZqDT+-Q*J@+Ud0@DS1b)Y35VNaH+qwA3;RB(?ybUXMm8B8s5V(aJ|s0+QM`+px@S5Z z9d@?8U5htWU0)OPu5diGVM%U3#n=^zY;gNZXI44ocqS|)-u=jEYd}95BK%*+2QNI- zzBAwQ1i+?pvDJ;7-QQlftb8RQ!1j)x4oLq4+(E5Q)0?JPU6vWMRnX(?v+{wI7NM`@ zWO32+(YacF=H+s~^?bEUn%_EL=jiV2_Vbn>8s;q)w9cR*P5*|l3g5u1O#vA2U|f1= zMTP8A(O{gUp~B}CCLr&+awc6c@9}SPf!`4faFefSFt%}puRRRr3?=0b3EGt+(k|XN zwp`M@JGsH)^KJ!}(VebtXBY1Jt6a5@B%fz5iUbL>blnjBhMea+LencLKx^3$u8AS~ zj;nfuFSsY0h9`?>=sVisv$MnjwyXm2z2zEDII0|%KFT^bcIkT<@NbIGKU_LoQJ`#f z`?+9`%6Yfe*XLW1uUZI?s592Cxe^gMo9ynYC}K?ri^aFye)?KXd+{hx#T?0bUrzMF%k^E$hyevDSrfjB!7a=2YS#j zJo4XgOv!fjJfK%)+Opt|jr?yEY6=G=<*;n8J#>+)d9o);me*VLZmSrdh52DF4U~@E zmVSzh_bBZB@x67OF6?9pJpgg(zxY3*9}opJ9-OjSlhUzbw?8zbm>dbG-7y(HpM7pb z1l2s9Gi;r<_TA+tRDnJ_G;BBYI3M!$=^&=D{!rQck#15;IVm$gjsc2NQiGiEsCsiC zg}z)`4|D?LxKJYdI0D6AFm`}RQ1m#TEmf){V_4)_eCsg;KpAHW2g^Ec zF-65?aU0T-MM5L^;*SxnQnG5Q3}c$79U1A}Zey1yM9&f5b&W zLgd_gpU%$JjWfy3ZN56(_@Rh?Y#Hv?jw6LZ>-9Rlt?&ELbWA!|8-w*SH@%{!XA`kR zETH&(LeYZ+#M(i`bTfYdVRhhZ9A*j93d{dai4(#VXfB%9%Sv+$nkmu^F>>}Dqoe<6 zR7HAL4K#@96z$&2#IfSola$OqdGXO7Ok*_Z7om)rwdRK8?#x@PJ(5=`$1ClGBu#KgRE-Ov0}((D%3cSdq+uIoAeoY_=VS+74Sgk6X? z7n;^7)nMEXPQJ=3_9cXVOBd1dZLDb)FuKHKB@)dgv30oHRLSwU+7L6`ke(%5Ec>+> z98(EVe?dCj$nf~&=m#((sYBMbK2t-gI&m^PlTJu=Y<^uA?XoRi!0*-k zQReW`jB9H7o_fX+kz*Xt7bzO!|E-e(^OK*SDImti9MK)pt;(@zLG8x_nI#kw}VSQtiWdX+32rP z@=KN)im{}5x4P4Q+IXdmeRE;*vE7oteB0W_Qy=Uk;75!gx;i6M*9)ULW}uuemB`l` zf+$C>sY<&-NBmz#XMV-tesD?H$4bUmJ21g7IrbQEY3f6iiQ%(-+z#qJ!FCm0VhjG3 z^&F~In1aEMZELTeN~co1#8DmGb@XS8#pe?go!)!1H#I}4qf}fj2Y83Ue*z3De^=P4 z+NEB>mu1u;r#}3RHBzN!JR3NJr~ouSSODECD6XxX3U{38-TvoR*S!?%y9S-9Z~_Ty zT%c>i9_8go{Tk?}2$okH^BWD*P!(vG?W6p5cqys1SU|rH%bc<#Gx+9v`aAwXp8PG` zA6*_(mkEqhHO|RXC-6*U zhHb|b&JBq;cEf}XD+KemA7SxSWLCX-=-d{8ik3a7dw0 zG~}N&1^3%}1zC0u*dn#Gc~{w8VLx=Y`-o^NxKd37U}A>*VQMyjew*AT4Uh>t^bFQMh$lU zPS!+TY)$o<;wIam+0Q#vBY*l1=|DVcn4ua*S&yr0Rn&y$bKbDF{GwuTONR2csAV(q zjZPAOy>-1caf|;L=@}dhhLe#ydA~j*`EaYRj%JAcdA0E01KUNoyTnid@GLhZ34*Df znG+yIpnVKev%MOu@B@?$470hpTZ|YQQ#pZnn7)1kJFROqS`Z0WG?f|4(A|sj46fej z$PZX@H&{EZjk21SP!S?im$4d#T?4$RqoyRqfEhF31F~QjC3l(`xDLSu(*&OOPsq0; za&oLsfMPgSCwRj6-nKY*ngz#jhw31*Y`mT_Cj3U6RoTM4nyuh8vIlSSF2QcYjfSUAYK>Nw9{NHlN6cAoNgxgz|_>|qUcoP2*p9hT~6GpTmY*r z97B+3ykI$Y_8)WTsu=s62--Q>p3CR)^E=(1ZS1ky*Z6s6B;!LqDo*lTD>&9F^1Y>E-?FA<49t60yUAWB|3v$0~``?eKCL>Nm9TY z2DQa011w#qb+PZ7+u3Q6nNTHLM0H+sBPxnKu4Wuuweo^NZJ`VeT6GjM9u(V&PVze*6MIL0DQ1pQlp?Pq8J{!EsD#s} z4|<^i56nQk*l-c-ygPJpB5=Om{`rutpRw1L;^-wxb<^`rNNSQu? zJ>oCZhbpa7E}YkU(o_hKpHwW2VhQc&jjxobH7Q)oJNwfm6HyJm>05^{FK%07si%>@`-b3M054CSWwpYLuh-W|jt z2>5Y|Db#K04sBPLkK_Mi81s!8Tsx8EjPj*GDpHQWNE6;MaKl)|;Bk>`4Mj6jzkMfD z2=XJt(7c>=eOhd2 z>#ttcg-1>;srdnQmA2TO90`22j89FS;XrVZ&&aT!ZRS&dGQy<^Q_$d@Q4c|W{0jLK zvJ5?e(?9lEkqOry6vS%%C-% ztpj8o^ajPa)#ar#j?r(z+{ovrlbmu$ii_IKfjY{2=PG2&tjS3`WQo8hWPI2s=MWQ; zM0JGs`{UJV%)E9wo8R~nS;eFz;RCT_B+&3rsK#WX=kW1vu0gOdk@|u6I=1+RktR3> zInpc{RY#5bD)AxP_=6<3p+g2ZI8^D)E%WyQ3-|HcpM1e#Gf-FPhxe_R*kaivGFda| zg@r@EjCwC127UgTrkE;GaMr`I$3>TaRlYPD+y0qn-bkoAbr_)~jeKCp4I5&juc5~> zedj77T^h~wN#3HY=QJYsY5~Okh2#ENuRoiJYl1a&AJ=N}mj{wIKqN_noODbk9#iJo z-1;_F3}T6uaM8A}d;4^Bis=xn)c6=0o)qbK%}CN%7d(Z$?2CluscBNG?Ev##VB>rL zVgM0iV{8)^3*KG~V75gSh`e*1nI`B_xtn>v`PM-Oe-|qlM2lbzn;s-SW_|bk9gRH}SNAUd~otl^{JnKKJTdf8T+q)f04rddD|D+$R4Ka*$V_35Z zj1`!?(Jjxmle*kA+R<;FKIwefw3}*0+BSaGPH=nn<6nLFpFI6#lOl!De!pCee#@0E zp0Lq>ds}B(+=wk7a*6$sI@x5%$Y5VJ%7q*w;9uMG)b-`1`fM=E^<)Kz#`SApEJ9&F zWOcOe+t`!-8#6+BeNMc6$U|>h)kS0{4z=S2l+I^__=d2$|B{NG+QFHBfmEM&iSFY` zkKFv#UbE0^LUz9-JqF9{aYBOTSoWHk?#~r&f!-mWA;KEP?wiQ4l}fSdi9E%2rH z;06;^twfas#?w9l&*0A__}ukBJ4QMv5$8+}JA1G(w9H+tF#2JG)SS8auGfgKf#IO- zm@Q?4sZlnNJ}h{E{nAId!2Z+EYS=-! zips=t(BJ&6D~C@#UsIduK<>X(Tt1JQIvbvXD*a6Kz>q~mYz-V(DNm16noU@7D5AL) zyECxeiJOTeOJx_;Id^v^Yd-jMaLwZ!HdcpxQw3B~uGzqz#6i71YKDXU>30glfq zWYP)_8T7p-p8((|E(*4Yn8&(_VB5DLANp`@+1D|5HBMSIq9v=gh|t#Db9#oXC180Lop`royy8wfg<9n;S+-rkbR z^TU{CIwj4isYVAQZ{)G&nTmU-S@hxUmFeiSp~cW_MO*349nhc7f0=tr(qBKVI~gG!a&CwrkyBO^&{Z>ERwUM(NSYya9^1;ddv*wM{I1XpBs#J9WTz~=_10H!yqV!lC`>EnM9Bx| zj;YIq9G~K|{{>TRaz9#dAg~REQ_e!|0W(i>=T{_e_2Q5T)vC?8ETC*C+nH@GAO(8f z3Qrzl^9c)t2*5}+dxBfmhzeNlk@gevVzx2inEjLTyWnWrH-hE5{%@HA6)lodE8fM` z{oDw~^n-4r(G)cILjpgo0BpbnR@jE;+(_?`L`uFX>oW@8tcmh4j2|l_nI(s^C!LN7 ztqKghfGv=e2UgjeopUcHp=cXdZ%9uD3Op5=?f4O;zAPC&w<9A{Co04gXLBt+agRNe=a>?E< zE1+&E$zFS1LB{5Q(3>3gLuU-?loqcJUIML#dBv!1@ynrZJx4S3XNPI|-x7mr?kTGB zj|vI}*#HB)mA-rtaY!={2_TU#6~mk(I~7(VYd2u=&OzI7lcQcm6$uwnAMsvSWazOo z(q15Mw?@e>QpYljgvq(bWxSr&vXwt5Lj?>8nm^WDy-MpONp^*Vs>385&0h%InZ6b4 zO!VRIfOippiY3+6rC!Chlt!@+WgCu=N*}vExwk|z=LJ=lWj>CykF~V3^X&nOm0II5 z%aL|aFVf$wZrP-?FAOULE^9+qM+frz1G*pcg~3z!Lp{-H1gGYx94uaYa_ z5A4Y(g?qkUYxaAhx~7IWhF~78=SZP%=R4#cwa_sTzhi|MhMsDN5=<)eJ{K)3l6a5z z#c_{+jEnjd4=CE)xiGqTk7uFMdbIzQdmKgu4=&DjjXuk;M{+3Qn3;}j`fY8~EwGdy z7z9ZuodnDRCgPurJ~d`Fq|t=Ed96KXZPZHMRJ|fQwPR3TZ8IDlDYQk#!0B~%(|49>LN_g)E1<#+aRZ}Elr&HAme|ey|rM1>HJt8X-FTU3&A-;b2!V6|Eun+ z?8)W<1lwB92U&%s*B~Qq!?6j_7Gq=D+-qqz;l-RfkPY+eAOuZ3=b}C2iEyLgJ=%XM z%!vzsMw^RxIU-g-em{O3alU9PYr1Q1(dkjNKJ1Hlp&~nf*d~Q`;PdLnF9LJ>IAz{? z)+k~s@bydMIQ{x|Zthxpy4;FAIQwJ$NK;%qP=)JNT81_k zW`lXaP8c3~ri3tWV$bv#%*S)x2c(kaOqBrh#t=b-<+{juz<{GiE@1R~!8v$1_xD+5&Z) zw)Y%V)(Kq8E>$NP)$CI#QhyC!2t8S%~@Rffw z;;LvEnZaHp@kJ#oRHd31S@f8hL5fN0E}osQe!V4xZuX>wIU?r#_oT2l3s1*bM`F?g zmIK+TnFQ~7?VXDFWf;3t3C=84&FL$fn=bmj(Jh$!61%QUms%*gxbOV=%XNLwQECJ^%PEj(5??3jIu=PW5#BiiRwu&GE-5_uKu zY;V`EhEu9-y2BHd7{)ZAQOw&lJg&32ong=4uG3L;Roeojs-Ai* zd#oK{4=pGJt{w7VqLUikU4YNTOtg^Fw55}00+KK7R>u9fzxXr#;OlzA) zFMx{phxV)Kk!ul8BbaAHzUWs&{9yZWNl?Jc-#-pTM`n5ns0WY)v%zS8!8et{Te3V% zH;=qG<#ZXp)wC@%`}g3RjrcCFn@!N*o({93d0fv?$NmK4FMC|q*eBr@vGj&0cub?D z&M|tRgbtls&({8z5q5l_HrqDjMeQ_9|EPuCq&waxn&tEwOAsNg0v;fvMpLBLBaQQ$q?5WP zu9R>RC&duib3(brA+s?tS|7*qDuF21#K&zj1jVz|+sG<2KL?A3DRvlYP%EhOYH z+J?;Z1AAxb%&^49}q~s(07@*{ZOwR4^Cg>AH`P^pI zh#+z(3?U7sb_eQ``S{`&84$ zM6@cpsHF~l*wC!xA#l_s-+N)c#s_} zgNajguI5-ZQ*S%Zk4+O4S=?urx=I*lsKDxyUR$?5Jm!?k zRO!s)0^Lbc%li!W4%tB`_*ur1W|d_b2L7W1XCGbnE|jga7WeI-o)G?8rdv z^BGRqu4!9!Jk-OtbnZP$pX<`a!(4g(LfWHiCyGJ9pfi7#86h&DQ<*rDSL%6&Byfs7 z3%h|Av)MAg8_RzFx}-we9x4sC2$x)h-1@GZ+y{J%~ms&hsX6#!l2JMVbK$wJRu%k+u^{nL$gD5s)XgEm1 z*Rxb^+#D#dx+%=^8^$8y^V5#^v0EgXBP^M1qIp3f@C<(89q&<*7YB+hU^}0W3JeA8 zbX({wI%;JZ%=%Y4x`mvdZF7H5uVJ^cf3oL9Qbpo4Nl4?X3S0x z1$(oDk4kNkb0uohr-m?ny^;*-@@sM zDk7mf0a`xli=b_BiOsZY*1+N)oQ6?lI689@8)2%km;q$Jf9us8O|Zh1@UZmQ!Aoai z#bB>T{sJ03xqE9|=SJ3C=~H5}sNr}3MyAHj?Z}|tE^%Q2nn8S7olIiDw&Z>M0R$3R zvt2XE%iv@3UU&-wd$2CQlS&m??~dK+p6iwtPUS=B0Vffv4A`ABkqB~Q)!?N7C!$Mu zjfcG;@T&0GVkFM2GIq6vo98y~q@!)WZJjo^yp7D9G%07Lc0Q&8b>0pOSsfmfTx+(= zkFwS@1&kM?uPx?}2P{1{r_kvZk&deK;-2XcEnh~hBph05OwyoXF#Xj9 zKS4!RmwFU=*|6Rpd_~6K$DTGM@W=zw?Nv0~w(A$1W?9pK;Qs;l6Pwqlm-**+dyfIi z94Jpbx7Q;Mk&9Qor+FTTj=eXb>n(dhMeEyVkL5Y6$^Oi91KfFYe6w@3Kf|GeTaR)b zT9jBLeqSD~p4pr3yV(HSc{j84m&akUP`MYRX4}W%z+vtJttf+u7i+3ujqlD$eKiCU z*fV5OWkg4vF*sxZyG5)!eo%JWAZWmQ+JY(i)@2U1u}wtao7tYKrUc zB)D!{Bq}(9;G>1>bn00c!>zd6&BA2lI@VK5=kA-_MZ_Z*!7kS*{FFEXm_RK3HTsQ1 zwcnMQT6>JQ&+;A_^JP(a0{Rcn1r7lwKTGInaUo4A;1+#njs-1@HWz&&UaJ0f%y0Ys zrXLkQhVSGNIY`*RI+VOm40BX$?0LC1BV5RG4gqW&&i%h}UL^3j!f5)tpMIH8&74c& zx5cSR2{23a|A=j{)C!>O!6xt#3KMsd+%LYW`~_ORu2$zCwJ!+czIcCh)%+W#u%YUi zG&V*6mBPpKG+7Sv%>FvhC5;VS9KMBZvQ45NIo%!=QE#LBjsTL*m!lA+ztl#By^vcb zj`VqcY+)>Y&mbW%#sbNRz%X;aRS~ z9`6w~y=W^5xivE$IB)7D?P8`xa`oTn!~0TklR_%qha#K^_XRG3yv|`%`_uKy^=WlA z^BM}c7uWr83s>#vBMCiT$aaT))`2%qRM)5%!?!17RNFy^RxwL5kISt`y1rI31$UM^ z_5F-^va!B`heLxkN2aX2tp_JJ*HGso5yV@qY2g`%8Jk#&AxK@oy3vdGG$PA|^b1Ir zqLf|Km67R!Xs-n3l>D@XKFuQGgfa}z8+~@p1Z5BPzlv=<0_etKJ-j`N3g%R%)xKaK z>|vj&bFH1=@IHY@egEYWK+j$y4=KgrDHITro9-i1K4Tpddm8{kQ3yH;s9-iJJWnuQ9U+eGyk+qq^IjK1 z5o8w&3)=Q@g0LI@vVaB&ErUq%aq$nI9S{B`TnsOo1Dma8Wdk>4MW>ygNIFriPG_DwQi#o8j+Ga0KaGn79$!&ijg zs-Du-AJ0iad5gp?2-OTYjBdccYj(Dut7w<=FDls;)qlw30-eGbKA$h8{MqgUk08!j z&R0lEO#TKw{#`EqQ9n!=kyy)J*=8CpKOkEhqwxf@o}HmqxGsg;GnXSs^-m7m3>T)_SlXs5sFb- zjqQ~SVO!uspwba@!k1Rc>?~5CumbHPulG^{>++uwl@L0tV8l(*m^rZepK!>(|0pC1 z_~b_@$237YTo?I_tO8!b-@DYdh{J^6BG}Hrpg~1;*HEG&^a-wisnNFt2WADsshYhm zTTz^yX5g;(uC;j7}iY>^ADeRO+E{>q_e#D4}^Xm^}8ux1jzv< zkhrhZNl_d{uvo%6Q;_sJR(>ad_d5L1CkZSj^w}=RVKZ@gT=*b8+mJ<{^X$C9im zKIRqR8S)1)P}u98(`%IY%YVE8S_Ss#mXu)w_OHq@{Qb=IGD`WHvP!Dk`KjT? zGlPEw&InN&#A5t(@}LdY{L!Ev3`7OJ6z61uvN?X5(QCM`=+`i10t0toUR_fh#{|5XBsIylD>H`3vow47E6k2AYZB+4qn+FnF4QvdpCJv?LCTql@G>t06nn zh<1ffCQ=M&`ee^F#FR}Bu_;H!U}1*&Q#~)iNl!nld8CcJ93t^MoaAM4n9B`vL7SeY z-;oFehh%4JhE9!O90U!ex$3BEn|d=1&aFF(c>(<8dDJxkF>o`qPKU6?ls=`l_ljC|*W*_A{GBfD^(P}Hi&GNWL&ei1y`j(N$<0jCQM z+rH!OYLHk_JrCE(ZxeSOQQNhOkLFYmI3^^MsA|*4(QF2)vm9uYhQSizzmREZa{W|# zrLJ`jqW{QYKKLnr3yglYoGY<}OCsw}R%l}?zso%-eb9_ocFmh$QtsPBvn(u7!tOTV z*L3a4%|d{xzeDA1;w_7+MzNn3ZPh??_+~}H&WH*A!p2+Ta{H>}8VLPbihHj-%3jAW z`f|cuM9_v@C?Gwv4Fg1^9E7l37=h`lvPqBHz zP2n{?Qx|`J&A*zdJJ^bU0ABx>UksV$goL~}@^kna?L(&<^+9yRVS91_-Ien8!j*t( z791<)8pnR{_)3UgZ9aVmzQtp^CC6fxGr}1nQ^C<9m_n0h&Zl%;C+G{A{fSAQV9x_0 zW@ek$=Fkttm8!E3q$`F?!r1S>s4yer^q8ZGz@|V0M0im~T7R8o9ilsiFmUwW4Mobu zHvA4xEIWsix{%m>ncHDTppJbw5Si3+-R)SxP4j)=lIUQyHs+C3>p9Bq<935q(4FjT z{)MTIam=iJVAdT7*h0$Bj)zLz^Uc5Tmm{XYt{=OY$*aK9Gtt83BH}*{WcD;2_V)JF z?TmsE29jZZY0_O&2P@0u(LjYpquaDM<*Tn#?}5cMFi!WP`)pUxOR4F(@Wi$oYNPuD zJ65d$^b>+OHqI=224n<|&1CIGQUZ01DVb_0m~yUEdzUc6)pM`&qfjyfxHvh@KtWxv zcXiE$ILmqM?<%f3;M#(LcT{0~Yw=_jkD)MA9at&OH0S#3V0Ur@O_^*iDqwmFK9eC? zdNfT*h1^&n{#oY!2c+&wVkpQO@vO*Xm8Uk4G?~sZHE3Y6v6i>_F9e;>9~e6+H2F`H zGSxo(1LCs_cdOI<3-Np16>{^a)Dr@n*}DY2-TTcDj~?{^;lKmSl>i2BnGp%K9&c-O z-Wg%q)dQuj&)=Pnp&Imd&TxC+czt9K`pvCGZ)c#4{zNGjEijG5IAXV2%DUk~r#g*= zzbV76il;LkZf`fQLcqN&s&TW3T!mP^pNTw+G5#hI5h_Ka5<~vP0D3f|IBku$lPXG; z%N(%RQH%du7rXnqy9Y4!EIBqDll68LC39 zkWC1>{*e3|)LZYsN+3Q^C-m+3P24{w)Gy3FQ7$tvHHGSZVLZjt!}W_ix!~#H&7rjd zO({E{D?Z}Dwkm<1mYsw@KovHIhRbJX24dZHm>|SR<5#7cW$T;c8nbhhRSj{p+}?8a zYeJL^p=svg!y<>TH|)ZR#F+<2=j`XcJU>A1<RAJ zgF>PKZ*AqyIGSkks6HcyyZk(Pb83NI>Yf3(rm1dF(LXOm1pyY2bIB)b->b#`mvXahZ~=5w*uz7KdW4hC@fwYF;z(H#^a?d+ z^6@O(a)w%%?L*?^efTx=-Wd%E|8ji|+d+F__U;Q%iX74;AiOwdg7#_!GjVODK9#=d z*$D|Daa&gEW>SIK*`ekI5%n1gnR9C3Yo&WIX^gzbKWoCn)1M@9KuuqI^%x*;0{tg5sveZ zLi;^9RA|JeKk-=vi|aAo*M_nm=b=D{KwnPSzb&Lh&&(wo$)Tdo?+jY2JvVB zH&1s5z7bTK_~KEbQrj!J^FmHad|c{v&;~8NTjM)%QEALy31L&%B}KL;`HtD4q9CH0k`u~`E3$D7lE?5(Hch>|B5Q2M7a0u=Y9D=(Y+}+*X-Q5Z9?r?BBxI4|; z-@QHhFYG<`ntRQvddj9@6`@N1snK{0k65SSJ3afqh#Cn}j_c;?(N5}$aI1uN`N90&QmZF*4mM-kmokf5yGLOnD{FU!ahLXJZ5aOw z(gpY}%#m_>S>w6ip)_HA66HY|cN-aCPZT&z{)oKb4@7%ZGVe`&ywtfOE%G}k3O}S1 zy1qCsPhjX*lj5;4MOc|*r!gIt{F1StQVf&0JreHQy8EN6I^=@*s2-6YySa9Kf&0Yt zOwT%I=>c!Z)6C;cYMMZqaEQbqgS@v>zn)+HAM)Yj_{4=2E_&JnWv~#oFo*jnCK@}s zY1r6Y{uB*$zNLLnJ35MO0=EIN?lqly8)q769@@V2HTeWvDGK3pNhNMYe&KEYN@-LH z+PojWX7ryXK)|TEeuXma#@?5qO3L$v43%C}N38u$^TJR?jBDy^>0A?{sVi)3r6$|l zz5I&X0+kI88f$u(yPW9Rtnu@~?+^kKA$NtJwI(C0N{^B^j!f?CC{r|<25(YO%R66u zzrlxH5tC5_qN4*I_Mf`fq_1;z?t92akXoIK!t*XjYNc@YagaU9K3em7{`L^>5AqUR zihI;r8@1%U<~>5O#8NL--;#iie|MSMN#VEN%FyGy(4^{oTg69o@Bo1J9(`!z1~|;q z%`Tem#JpiL6l7z&ASm`3`MdY-9E)BF$BWu-9Dr5m&s^SC3O3umv=GcyJl?U@z^f2z zHwEvAZ~{62djAsn9nLY<2F9)x91CB$;A-|Jd=k&?6!qtAROj~Lw^=Op5DW?A((k^Z zyD9?s+Uht97XHG4pW~N7!8H(dM~5MF)&Jj!Zqds@K3iGB$mK)K)NF*j#WK>HMQ1vN z(+4c^4^Ho$Jq)$X*R>F$^k~nEBueB$M2Tf#JwFq33UY=HrP04!imJJ3vSN1MN+++? zZ~Dq24V5+w)jY;qvf^O>mGZFP2t}s8omR&ZUhZ{AU-xG-!Ab~INSH}X5txbiJhd`C zKVJS~zbZWrkkX%rykYsTi~9VtTmIssp?qMgU)Jw#hygFd*JQn6p8fI4`?81MeF!x7 zMc!uIdpLWXWuu)Qc09!~OQ`DQ#zwDAaKzVXSxq8^;oT1%jr%pf8Ip!|@Tm`BnVUI? zAd%MjoKsHQ%CYrlyB?$-0q)nBn%8%v=&&lD4iGOKS$yS_cs_X)%fd_;lV3}4Fsar@ zuT?xLk%b3>^Ct3P<=&q308aesN2Y?wcLmjmK#IHzSd$2U_%W@n|83O&zv+F7h!WC; zSxLn!*VgMacQ?B^&bQRpk2 za{%NB1G^WyYQON9a4r8$fWc(cHGdrURlBCJ&UIWekW}|EIs8rYaza1mQ3A;mWiF^f zO!TBHH2I~pG)dd*Y5)Gr#(S;md1cCKqs@u)a}k9KBqAegQj+Bv(0LkFTfND0fwMzQ zVf4Pmez{KWJ_YZ=;{VYz;Ridak%c+c2a@P7XqTVb2ws`#%AxZB13TZWx&iM-HO2M? zJ_UETHXrvkRtp;s&u%Z>?_1uyU1fC6>rM{>_pMVpMxfUNE~}d&g=%Tg2LPVb$RyJ< zsB{xM%RT}6i`-8yBXy|iSEm2}W&i)!9%{a0kY)2tA2Y)nOexx3zf)%cqz>Du19*GR z>0U+@{yP5N%tna>8?-Buxy`#@um3t6b&pI5lCq*CLppDUkEl;X@Z5-O`fI?$l|Fq3 z&_M7KT`|($qI4&bM1O@7UiuPignlA$&x$Gx5?={ z)caIUeXUMO@*dAOUh6v1rox1PbUxe{Fw!n?kPLF$I!YGY%Ag{QH_bxQwRry)CB2H$R_l)|8SzFC#? z?-G*LrPM7`M){dc2@~T7O_>US-ttVp($xedQlEk7X4-reT$HtArOez92y2=A+C#A% z_HPe?&-vj8ZoFDVIf!?H3e9Rf(sqk3UmO;#1G@wxD6CK}^&)u=7 z!)zjLk*{M)wd+77Cq4-cW#~F z@NtQhiu&c?f$ms)joHV;8R_l!iK8{_wGDAL2$CGyk5{|r( zys6u!|E)bYOvAa+3RWDwtMO*k=c%tJVS_mPE{dwAudShN^4Rchu2Tu7Rvq4@n=FWC ztNpAi6cT}@U$A~z;8rOy@1=FrAP~81OnJSNwP+gjLa^s(x+~7%p09oFdZlNrVKMk= zC^Y!Ge$jIX4C>yosDNEn$B5iL=DA^?X?KNM#*bBMD(&xKHqg`C;V$+k?)*3F2JM}D zQMQtD{dk7+!PYs(u+I^E7^e+m7;Sv<_t|jpn&S$yw^>${Y-HLNBB$d2Fu!_!!Gcms6g_@HE$v z&Do;?L4qd7=!a`CJr*pB#eZT_NSjEGygAq+n(LRCxaqPT39Hr6oV2C}$jY;S1rpfK z>>OzgLBkY9L%T*&c(n~2aMp!8Gv_nVWKp31)DZ+K@HqV;;rjzDmFHuylw$1uYc;r`47UPnoWvgPVO{HSVI?Hs%T6f^@oK8%*=FtZmFhw2^AIC{LMM5OfQ^NlSk94!1) z=tqk;U@?9nWsCkLVgzZ^JcZ|#c(Enod-5|=7CCJbR~I>i|Ih;0Ts>Fk@zQ6Rjg8cx z=D3MpGd(_kfE|iXb<69J^NO+BGBM~;%>9thTUeuVqT{(*_l?LwV#B&~AOHT82tB@YS((~~Tzp(TU2{D2 z6U=0kvKG`9@JQ^ItdiZhIwpi_-&m0HGh{Au?U%E5pSapmY!mjj?TSMwL{A-SZZx2^ zU5e0``Rl;5)l7LO9e3&IEbU*3d#+8`QzZ#kX9o^#rw;DN>8X%va?MK%ZpIEFIY+wn zu8o2#jjsKg_s<_!;LK=PEL4uz3c)p(&ux^3$|sc^@YfI6U>VJ&V`CQa<4(eg4bb)2F>RUtMV1?{mWXP;R z0C8)^Cb+U%DZ(NW+f`akC}h$O_?kYGm;IdUmkbFT>9Vi}Q(ZO%E)ZIs=ICqxBI71k z{q@yI5O5+z+==4VDUHUUy$Ih$Z-=X^27Wy5j` z7C%J7&Luw`f5Wj&VrAFD3(+96UkHY@ViXH9nVwUl(|zU6fid71d>Z3S+IX5pB&sVVlZBl%JVII57t z1!T@en={%XexZdZRl^MxQ+lV4;`WO^Fz;9xS8n`xe~G_8z=1qHvrECIe~J0){8DuJ zyg}3TEeA$DL71Btt~i@R_k<-5tsl$6*X2&M zt&1{Y+@TlUi&Me$p9dFzprms$}@}9UHoGPhUCkkeO z#8p~A`D$g(l_56Wn?y7GS4Qy!IM&G9x>l7HDYe{FJVU&9Fu*^-OS$55-cr@lFF!oM zP=mB|9CEPSn%5qBys}MdiIqfbdu8oLz!Y|W?0*_NzZ8loYCMLBA5~>B7EY)_a^g0j zNyaufbrU>wD1LJFBvC3w%qwp zac|N&=R!#|c#JVa{<+$cH_!_vm=1bvT+*?~C-&Y3p_V~qRYm6)eLeE7V|+(xif)*h zQAh`IQ|z)>S@x48Jit1Kta(x@@@JSvw*t=8VrryzMI0k`8m{spi_l)v54KaHIq<`) zX5gd}N?o3BP|2e_W?$}a6Atm^PL34}z&eBr4Y;gOMA(za5~x%YkN&;_gmW0zicANkZ8GXOp`uScTsu0Z z?|}Cdcm@=#zgOrmhu!prdK(rftoX?RE_|nVd54j2zfr>%p6Oxcwz=)8JrkWN>Rhe) zVhb$wgjQwZrT_VJSL}hy>E@_{8pVZ@^r~S6PxdD2)|-dwv`}3~ik`50>?8U%4JYJw zP~1~}jmY`wrF5lM)x68+BbvMz8{Acn~7< z4B1_tJVahOmi`zrq6;RwiZ~d>aead13k9P`>!%&EU;Z2s8x=oP*Mi>9K=+YJ+uN@9 zv&miapMI97b5Dsk7xFM6Q2Q+?bBgZ7J%j%p!8`52ka>Zmf?`U+#uKb{_8Iv^d-!U5KVDXsSRD8li6nc)8RHLj)y2P1;AJJM?W!YbYtjaQL*~*HDi$OPI1X8K>M)F} zT-y9DJ}LGp?bqT7>c7}OSgDmFaKE?8TAXk#zyln=fIIxp2hVG-4YeBaj&6&M?Q#gD;1AU zNakuCj}>1gT)M}u-sCh=i!D}ZbDDg*5;=&QjfpWEMzC>?3;f8#Di~hFY|6a=^7B=6<@EKa=-5#s~_gu zaBQ;cnth{>oTIClF70SwM!s-MdZmBnzAinBBJQN^uRq7FcEd@~_4X6r)wozaeJQa< zAd$~a^~9Tzzh0+e~be`zk-A=oz1Th`=P;o@NEw=~Am;+nnv0=e~i!Qe>7 z(Kt*M9Y9iExG4+rk8cUr4g^a1!F~-96C%@b%*M6cA`v0IX!Kk^h46t3jH~Y@ zBC^n`o!?#lfzH7$HGGj3?KlB-a54;4(M4w>NM`ehV6NWh%G1z1nSEiwsnt{i>tiU& z`HwYP=GG<%`j-|Wl{}iBWeq6H;RyW<9b^C})EjJgUs0Uexz#^bN!}7mnfFEsN#OMR z!50nlay=hKQFugHM}N=EcjuA-5<2%ql8mlI726O%5+;B<8Dia266L_@L@LMbxwBtr zT7VS5E~wL>I4sA3G3<7lqajFdOw-mMZA;yWV{;-SXuH$q+7q&=f3nx2JFZ4E^-U7X;ek9wTqCV`CSbt$D9B9#gn1o* z_>?3~JGl``3Eg^rxbfX?p+dl17r=-K;I0Og%(!Ai5@%X~a#;%4=Tc=+6r*du+Q?fI zQZ092iZnRSNi&BJ8)7v@&3t^?ss`s!o{&Sz^eu2KiX z+wCn7Zob_tIKO{VPO!3^67`fJNtv98$udo`GzEme$2o@G$zXlL56zJjA-WDP}glVFlsDx{{KcCyt!y+$cLj12b;cq+9b&abu!+vtPB`nd!8G@Z2uijc3 z5R{Nw@!e)w-p8*uX9e-uNiYl*p;QoGzg-C({6lcdLYwl&)X|v6buNg`EHp z-MBf6I#&&40{uws#%G)-rm6>5AF!JHljUuoh-6%)JGsTRpOpR^n<*fHfi`0CO7Fdr zFCM|x$y>RC9AR9tC0ERNSTNiL3EgQ&dfY1B%tt=A&ebn6O^0l+zu0pWG3yaQKE>qp zZ|qx3jj3B5F*p8_fZW{(-M!U^DoT&p46X5h0ROplI5PMorauTm{9BHr(BS4h8C)RO zBSVXEcD)4;dOg?FD`kkhE2%9#qtQ&}brF?Z+;h5le#k#Q7XJoq;2rcb1zFsK{$qW+ zDs5}%fmJ97409Nnuh7{7+|xfsNU$jJQ|K($A9*Mt z6lz8rG2~~)^VTBfe-xv!!DNW1OHnM%#cJNO2|y=L!NGXIwtuQ9?g9_h*|(?y zBlrPx3`vr>oF?r+k`fCgX04-U`P0%%T4_gD`j|oipV}lzeFTq@*-Z;6sJ^aEg8eJ2 zzj8kf+HqGepS4%lVTu(u?V^8AM_G-|VsbF?qmI!PEg6#hlm7bHiX^lJFeLf_G7RpK4; ziECzW-8>LZll#@Qq_xZ0VcKfLZLLquy)WnZf|4C~G|1hV`=dX z%r`^Re8Bi-CNOiyipWe3IR1){w|+J`7L~v!cU7|aMKSYRf$a<9iR6sng%=IZ^_i-bFQkJl5TGkZKm= zm}9A)2sY1AyZnnD{C)Y8h|x`TdO5aleunYVZED`Vf7JhMQA@$=JN?_wg_`7PkXpTo z;BINtbAuC=cVxLx2r(6T@A8e8k#}N?T&rtWlHIh zV5p^B##3=M?MOv}ALbZmHv-_y(GyBW@|`s^xu})chOHo)9Zn*nE?XkeVspemn<&*)i&xAMtuHu4zqEEAxug4_&L~lC_U& zkT!1%*9#*@@LF&>R@Y)Eblj0-(OEg+av??8=6ytWMUFF*^hh{J|H>kLR{8M_Fy7L& z`z>8^x})a}nRB?(N?9e~Qz#l#tohxiC67{9_dPzQFxGYKtLdI2tzW!XK5W%`qqKS} zR#fF|&Fi23fP!SB$45LzLLd|Owe0Os-o$XHyl{P3JBxwF-yI1Tz`SQSXSa@-z%KUg zXiCk_5yrw{yK+A{PuNA5d*O$;jQY41a|e-oA&6FE4i00CdhsR#q?Cz-tZ*vOn+z4| z{tmy8$tGUS(c^mGD=#J9CHF(cL^8@0WXv0BpM5n zpgh_sQ<)Y3C`Tm&+MM}B)B)U~ywzyvTaYGx?CZiu!{y;%^xSF6yk$gDhJOfsVw}0U z{_P8{5h$30I~F1qy>fs{9#m1tFJi0+VfIB?G2Ee9@p;I`?1$1);=Hi?q2JxVA&d1g z1O;hb5#@=Byq+YUBp8grjQb+6eQ-)p|ASx^%97}hWG^06=mTIL8MA2VPkE2%=j4Y^ z-=8R3Ku(A&ZPxH@UDnh}C$w$UmNUJbg zJ?1`{X8m>$N!t-ImR1O!Ce5_xS9>lqU3Ixa6TzK7)`sF$_VO2ubI~%JbZ>_AzpM~y zj6{xr*Id)t@qeYGRFMqYJH$Pq=GTY!c4TF3fH-43HOEzMPS zLGtZ-zGRLN8j)t6+qO`~b;B*DqaoT3!E4*MVwHt)xL~S~*O-TavU2qZ)&ipBk9?$T zyyW3YuU}BeC8AKQ&#K5@N)-qMF9ahM1fe7Sv>yvBil$ik5GIK_3Elq_n}89l6qLjK z0T^0hVySTq_kQ>tJ+gdf>;VcKx*+qk4g|Mi*M^~jBel2^Xw@AcwShG3@n^WCNkneaiSBjJpJ^-^8$XP>Jl7uL9iHYPb`ZimgD zyzdTewum`AW+7zbdL< zuKL(``wqV0ed}w#Hj5JP9-bzyRcI62H_aG?i^G{#u0jz^(DAMlQR96xn^QD%N8o5E zlE4(Z8xgMEq{h2+%713Ct#~VM=#z`c#$I z{QijSBb^sYClk=-yd+ZtaRQKo;b|6keI)JX)Snr3(yha)^+KzGp2=-Uf2UZ^HH>Rq zY2O>l>U$AK!M<&s+RWULocy%o;<5_Ditl^CH#fhf^&brrk7f-GgR=ytRU%|?_zaJIoj{ViZ4TW zJgPdL(=1uuaUQC=rd=2J_iP41da}KHLbM!YKcX2ampn;=hU32u?@r-dcjQO${s{g3 z)Dn6P!P^smz*?`?d7?s6dFMJ5-SKjCOH|u>J$+*XdUR3Cic?EV?mT^BHc^HYdK!`I zAek^>@z_Q_%lrHe|0_jvBTxVtaEc69@;X=bi7}Il0moySJn}PKDjX26eA^1e#A#GiZq5D zKcYl9AFxDO{aLVMwES&K`rkj~jxz?O$ICi8HGo}TcyqS9wurPl#iJfEC<&RMvP3SSJyTIMM(DfFq&u!O#ht26O!SpI02j_`}EwP^{I38!Ov1pMP z0mE4sDADP+A%EDVviB#l;ydCp;sE}LMv``kfk1*G&T0#p8R>9pC$Lsup{m6ht>2Of z^RN6W8Ly%UMIB%>e8l0iQQQxfUYFBdfX@#Ru3Ez_N{_?fVx|vBx)xZ*h z({x)XOC9ol{>4Rt;M_m|{{D@qf#8}ultd2*2Jpk#rTW;GhEt~W?MN>oB+SLSdE$3W z-@~5imrMK7P#ci2d`HD`D8dVIJzD0!B1xDb>BQdY3>ED)Miarbfy7B~o*Kl@m;E6gZQH+@mqdg%qlwf}CsN zK!O$1Xn_byZo7ha=OPtxmgB*j);(*6uu7#MR|XM6f!qA{{O)Q_jnw$J&(Ppsa+6st ztPm{=X}b(|>+&gCvw|?GLS1*jdC{(b72GQ++3lOm@O`^$l=4o z$`_rCXLviU2m^enJCqXWOOJrHd0&wnF7PdFE)6PkFM9rdcEC5PfxuSPKr><%KgGN|6NvZfA-4hvNeg#U%IKhu6Ws-mT|cdUcb$u zfz8XK+gujvq}IoL55M?}elJiADFvqW@-D~quk)p(3a0H-KvU%!B=wqxd59K~U=7c_H>a!T(B1j|d`F@CkJoW*^Y z_C`_(@WIYgYVm$Po@z+9ZW~dp)Z8Bm4{5@vY~RJW>0_Q0;$!ZJU}CLN)5}>&>5GT& zpa9FjPrUJB?G=$D$H3f={1TBFarDuOsIfk{)8${(A^mZ-jM^nYaP}MIqIH?wKO!L_ zC6u`e2lAX}^a(~}IukG^~{JDC~ss;0L`x84_V0_ri z1CFJCD$E-UNMHC;Lg9()RRgc2iR+CL%-i!ffb;LY@Rdo*kD=$o+@B3(W2clL)01BO zD67HtUZ^j4Bny$pkz*`Irg^BWh{GZ`W~xgPRx$=2!sKm!m=>a$&y2TD=)(AofGH$w zU{jo;yj&mrkEWdB2ExEFBr@1#y+kn#3kg(SChiy}tu1oQPie4owyX;pPu23tFVOi$ zn^;@6rwLz&mQy46-n8_@zCi*yt5b$&bm@zt#hN3nMvvN6Oa%qHV%(Clukfo4DJ%u7 zYYX-!hisuG8!9Q|SDG&ro9}q`KJ#1%sEsROHG^C(ZSg%#%XmI>GFlpp;+)jb$F-20 zQ_=K<*~>e}&$;pCmMB9rEZ1SVUKaTf?7cL*dfg}?@)e`Rj?g5yEIdlM7NiSnz5g1X zb1NgcuG?JEr@X$TB~-}&#G-lW@tsv8g!{J9-dT?6@Mz@;Mw*Q`6*j!ZSe5WPo&Kzwm;6e-+Fur6Kcj8yGOH&>cM=)W4xQxgl(- z8p)1gYsLhxdE>pA>^2?Si*t-oT0xt-qShTaDO5FG&&JNrq>_{I#6PtvJ?)ii_8h0W*uVc2L$cKyz&gJws*2R4LVdeyYx_*vM`NvFZ@B*D`HH;wW{0T*&vOx!r^~LzBG4>>Ewkb5J^rD5$U9DEI>anA| z<*(^VFDo|C`~o~vN7y#3cvf1i8`Q_*gc^m%gX1GA=~2%Px_8r*9VVUR5a}eUyKe`4vS19(I2Dw;^;_LRlgh~866}lX7ssVuCKG=H4wlOOC&0@z8ByZkaFdS{sR@iOxO_p6 z&m(O$niC-Ru2@JlC z6X#^Zd^a5JE*3!a&~W+Remtw6fcw4$+w!>X?Tj{Ar*9VNYp>?lHbB-7i&Mn2Z#b_P z`G0(_tyd<)Sa!2735Ej-Ie+35Q6%9GQeK{Sx&LaTN66Frle2)vH*Pu5>n{rdfCIMi zh6!Fb>?B2^@RqPCU|N=h_{6Ct_SgKvb}RWA!x*{F0kR8T$;^mG(bgX#Ggj}P2-b`1 zi7a>*CIwK^KV$$Hpcdl++M%sj8O@*;JSx(^mW3;$iKZ#YzellI`O=55QmE))^Bu+c zNzE9)Gx(twI!2Ql1gMFShKBnh#2NcArqA1I$mp15@d@kf`%a6$C@;YbNp08-xwYF& z@CIL{7%m8YQBptP8P~XE+D?HtOK`YN{)Uz7wv9|dBC4K$M8#c9*|u1jUgz{MBknl! zLzbEE^gEh|aQEO$6yqST@i1x|f|5vDC{vPQQjLE&e8`6u%sV2)65T&jE*4In~AeZ+{svg`-baH_g z0lDOd3C{#cb65EMr)6Z{wt2TFMV<{Op;B14WQx;j)v_66 z-DGz|kam4LkXx5WiD}%1l4N6$URw_Je_j1U7|>X4!#^a$gyh^cc{io)m}t~0l~x00 zJyyO>9!_+G6uC1wIp8e}VKmj1d(`FQp)h4| z_}M`STu7lN#)gbHPGqgl9R2Ewt+`VCC@QFeKOVW=X7i9_VuRq(=e zoAtdaufad~p2WA2e08oL&!p=xtQseacZ;Zd_-f~tjbHLbJvcXno9z;UJ+7PJfo zKVWyBlaOVBp^OfLE4nXEkb>!J6*&l*0Yn7HKJ0xUW`i=+0 z`m>6)pi&5Y)3}oOTEhPsG8XZcq?B|jK^bBMUOC~5eiML4VNe1);=}1)ZaOUy8KI6@ z>gmKaU#|W;SxEQDh=^(=)m>F*{`69G;zB7g{@6k&KQ_Im0ZtP^dJioWmt$+JP@AA~4U##zV zn~XECXd{)CT=;z(Ol4edrP;1BIXxXy46<1zz9yzP?`~maJWf zZSW^gWRg==d|u)GVXXT$+r4a|b7OvK<$T_HxT>rB_%x43NI#`x(Yi#$?^9c`>U7IG z)GGc7deAblSgLANz)6`WM`f>QAVXd{k)uCGz<*wh63*$u+~4E4ooH*+QtFNszoU!1 zR#Avqd<*ke$hF- zJaY++`x{u-rn+V8)Zlj^E6p?OUG~YWLY4U(wgAE)C1>jiiP`s&s}f$U`w1Jb`nQJ% zk7a*>*2+84q#SfeeKGT88G71FBZ7RyBv&E){S0eE2-9?KM|c zLeJ(|QRi6u)F+8QE~-B2I-eKtS1QZJY>_mZbp@wRJuhCOo6HWNu1U4v)ycW>7Ypg69o97bmdAXq|k5boqjO^W4|FJ2;T3xB^ zRyXTiLPJRD*7@UK({@!nB>p`^4Y1ZN1X( zJEvFE@qjOaE($}O)1(&u);n#MPYIouDGX=s#iZ>=hN`;LCNliD>_$xnbZ-#4(L#wr zvo3MinV`3o(EHRWk6tgJrx5U{o^=v=QLlt^kg{*4XA&*DEPm|GzuM(o966J-d-Bw& z+C7TN`qk%F9qV_|ds9@*PkepX;^{sWSKKpKaNcl$e4S$OTx>m=EuCO-cc5Nms7R!Z z{icnBZR^{qNq5c1!`Dc-3YfxN==htH!<5eL*K$ie;i(|~nj!^+_{6^x>@>AK6||81 z3t*6S_Uj()SrT7#fL-^7SPhLlyaiA?+2f4>p4%EW82ArO=t0BPB-YbOuXBa_Ty$o8xSfl4yVb7i#PqYayMG9t#;HEE--0HQ z2nJ0Az$h<0d_2W2y--@SFSWb5s*=q8X01X)Q_4=UY*;Rl0;B`wPWRm(C)PM#i)&Ws zIi&^ixj*~z#E6kojUzyj_esqi14-=6cE}PWOE2z^lIRns8VE|$IJrT|`uN{5$O-&+ z0l!$QrM6|U!{7=(CMIz9_b-gk!PlCeM_pufg{O z4KhxAz>xRV-tu5fl~Nn2(mL9P%U9zSEM4&F>2&ygt#2L%_~exRGm0C4t;xcRAQ&z9 zY+5l*gE7f^NQq%K#=!C$mDM{#ATJzM;{%UOE@Z6j^CqT&a+E%k?bl&={HQ{&A^qKb zo>hDeA2aLMN{bI`C2svzreQ2USjJV$U`sOC_p_`MnZLpDd&u-Ev#3&F8NaVV>#6t? z1J&Zv>&EdBJbFETAx^#8+akkvG9*|0>SR=22K6w;hN8J3KxBR*|JJd%l+f8C)Mjn$ z3_=%m=jM_Z$CMNSF6DV+ZVqbhfV!V>@AB{geUx?&Bg;PndT|e%^|~aUl&gLZ#_}5? zkAv@7z+`)vU8;4c2|3FOtKMbtbdmU1NlYW1sHLt+1t4n-q`b zb9Wfty`7Jkei+A1jn{wucWppnnA>C%Q%!7DtHB$#&zp37JMp@y3SuYL=@{oizC#6L zlYxF5oN3lt=Gx8j<&LpxY7^_kvL}%BI9wb| zia;}H`}J`fp6MSnByl^ICWad@CZ=Qi(jWh+mN?&KJvbPWpWu;jqvJuj-rKLr+qTQj zIqJHrzvsNqhcm$ir{H_8>y|sHa~>DV==)0lclk~H*&@q`UDACoZoAR!MQdhJ1k-B< z?f#3V^v9njy*X_Sq1CQGr9;le#b`Wr+@?MrgwBG9t$)_C8Yk>Tb6#rwX*^($u|To` z9E#WehkayxFZoS>P2xTeXu{*UJ)>))JFCxfYGdk`UPq+O)Z?u?84~Kkg3YQ|!2ABP z9bMJqY&UyY`S}9^FtA&+U!;3Hjeg7Su6gKs=ycwXt^zP?HKtp)ta;h3nVe#K-w~?) z1kQv{ItAu1+$u)?7U+mT=pqtlJD<#H{N}d@ambFNj>o)p zBtmL3@MoOJd31!95_GmgH%hAedgxsT@&>(Ba4a^OWZ2ieoes8l2r1tq(n%e!UDTQU zUlxFo51bU&63DiZ*v0v>k9t27?mCXE;~4dR{g8B*SmCkRL{?niq~SbF(fg@%sue4# zX&!WAZWI9Kj9S_~jyP^)>WonvSvz zk`F=7bJVU0#qFoT<3jm)&4m}3_`Xf8dv@vHp}?WWeQG$fvnvo5)-dvdA-jKpcKEi?jP`- zVrM@FY&<}aVT;1|GG;iQjNEs>FSUDQemo9+Y^FjS& zgG(3Ks^6czVi6xO_qKcZ9@JWBRqZ;rmYYa(<-dFg3Oztg*7z}Dirv^fQ~Q?8a^K17 zxO2Li{jvU#sbl*Ndk!fc=BbF6?N>76@kK5`Ztr`9-zGaK{vM6P>m~T*?1)-z)!Xf9 z>@-~WmFk9WbB>_+}I)K)rs5xX_?tL6Xn)MZ-M?}71&ybgBa;W%@4 z`70OT1cbkpnW5sq({w>$6~TMsiG)AA@UC)eKN3ePy$h+ zaj1k^p;gF^BhA)L<=zJOSq~^{(v!v=p)I~Z|1#MpARgwnn?Q$R*Tj)_NUPfieAgbC zI_E%8EdqQbRY+?iF(+W0eq-p=ko<>Q!B!+2Rht_|&wM_w%7#1W#hU_SV$p>Q=QyE% zs$;0|p}Fg;bYyou=&dt8qvl*6QPqM*h8y$!T_2)7WP~Vf9LbLl!P-d8T4h@75w>&p z3=1$+dc$W4%I^MfwgC+~Z^{?(JO0gp&~9$BO0H6P5wy&Hrfx)sDaY*Dh7oE=xGlP+ zo09gv!sl88d80O$HmGnXOz4Sbxgt!Te~+vayA`eeNiw87Hs6s0e%{N3t0NQ++6ec$#$M&MWY zw4?pMq}vT2UkpognYfr1KMFC2hJ!DaR%e#Iq&ExJ5V_y#`g?SOArXym9Z|{`fu3x2 zF&Lz|Hj;k&ovz<$z&E;DV@yzWm}XVl_WS!)JgHUv8Npoj zw;soOROCghY0zl(cv4);j&Ed>K31bIL_tGYfx8rpz>2;19dJ0Y0mh)JjxNF_;CS~R zDv}_Hy)_*`k}~%4;RGBJJ?_to8>GOp6}#JxBRVPgeAbbK$-w&Vi#T^$Ko@7o-M_Y=p~!$JzOpC zVm@`HBghBLdMnG2L60KeT`Huq7T9?(uM>>}e?joBXe2gm2 z83|rV@wy7v9mmws>O6)g_bAEyOuvo;URr-m9}TB$0Cywla{e6N1Ro%gCga?*9A-E~ zyOVL6} zr(SFhXIp;8G}RKFo5>#92ZI9LM=e#7>|#zHDJM#uWxK-OuK@mIlswbfd*B2(ZUTMl z>8&3-x)I&;2>leA{87p1z~#}sx719F7jDY)oUn(Ehf@ltrO)LF4ig%hj=5Ie?9Y)L zc*o|w`Qz^ta8OAARKz6k@~G=MFL0pjLp`djSy@Tla&uv#?KbI#%KyJB|S~) zbN0h@WTla_dzZA^*M5p)xLs(c1xuj)x+d}d{h>R1x2!CPZe@u!5%iN)A}iK0Vu4Zj z$^L=vcAv$2WhiHHr8wKM?sGR?J%HUMODF-V-uf)%$#p-?BfauF%t>V;abU@ z3yFK+?9`D6el;KR>F0ZCEPgpz>N@96&mwX1Uv!uhoLgE#T&3jJ;@yDk5WGp1tA|E^ z&A#6Ry$Kf!i#T{*`ja<6E8r{wOY93$O^xW8!_Ge^_U>8q`%(0%xAzwH?OUc!&}#4; z`KALq0Vr>Mi`D?B4iK5$W!36s5aiq#FbTq`Rbh$dT^u8W03T zy1QfO?i#vt=w6=v?RWRJ|HFOmbDdATFO_lB?)bwgzJCt4#4$10!uwgj(}IY^lA^bx z4_O@UdG=(=8#g(}i`of&CzEhRpVfO1gZMl9VfLhmt#FuR^Av~D{9-yNqlqh`68P|L{XB#%PLeXu<(haDP&pxC6j~kx%=tR0s6HJJ951YK9H8#ebqaT^veTjUV?0PQ%M_G zZU}kdC(s&aE6f1(VFnzKUxEK<=&PZQ06gKIkLDdGq z{h=P;cOa;Ug~QV`$I|U5q4Eo@wRW5#%Fa=f9P$ApmfQAmN;8nh;3zK!9IE+Gra!IX ze>D=&Mn*gTI$v9tu(T}^Rv(y|BQ2+#Z~nSDiHcp>s+)OzTWuZ8K1$M$3J-IYxNl>H zJn(n7BdgiCXEQvikT zp64AWS#7KO^IC%?weRQI%PsDYuW9vUqBR2$F4p+gHO6zdz7ex=+1u|1KS4K^Aj_Pm ze(VuZ#rM}%#1t!JZa^O1zLAy8Ngt$DS4f#fh1XV)?^QlqbrIDqsCDI@r(K;#kut!l z7rz0OUz)Lp)ySy4K#xaPk4S3jKuxE#3njuSbyhjqp{@Jr?jH02azFpAa^aFm)~fY{ zpCU7Md{G6-(@82I!^pD}2Xi-sFMCtcBY*qb#89Q}7`h`EST}F($aUl9_t%CjAkczE z8(CXqlz~fNZ=LS!^SSn0Wzv=Fz~kDU2#{-!8)Anfb#sj82zh+}vQn-(CRtIV&KYzr z`#y*jEx25SFi{XKC9C*LtpKUo?~M6tFEB)?t=8{xSmrX{3)^1yZd)App>V8eTkYMv zpgz+B2VC{)xi7bys6ULlALjn=1eJ%mQS$aL7Cu9=If^s3r>7vrH zQ_)p(M>> z_CiM`a4cx#0q~u?yu8-)(1yNX8>U^2a4L5!NjwP*VQiYgU$LEFX!r-uVT~5IZcl@% zE;#Fl)brKK_yDBO6dgPZYd^C{qZz1bg3rS|cz2iaq2B>>H_W}~EjR9v1Zd zBF}|-D~4ER24RvRG{HJ6+-5K3U|hCs``AhOnKw z#D2|+^WPi|DkD{(6nsftbVUVSKL!tCeh&~(5*Ke^w)ro2i zRjiQEwj%M#SlskSS)LH+ysrxkl|iRqb`07ps7tJGYU(|D9i*UZuM$RBQPGaNMOuS4 zTC8Mb;-v^-c#37Hu9TTI50E_)zu-T8Wlb!!eT#Nkz}Imhqw-CB*cC9F<@Fp6Wq?PV z60E98;S5S=`G5d~Fs?UOy?oX>{&L)t)0sc&(51CAyY6&Oedwd7X@p(+oBQ`; zNx8lq+ha0Z%mjH2UW~Sd;k}pIgwF#C)5>Gk!(%gwOlr%b7y5mH{z zV<$Y;f{|TGw(BwHoQX0j42f#Sto7gc;eUdJN!tA{?-B1y zGJ|mO30z%4_F&lP@G;-(V~zyCnV#4t`{rw*_tvR~+4WpKQ#HBMy4+jYc3$nq2j4BO zXy!K;%@^-%9PcBLXJqzkR?&Nj(5>>HH4dtJ-09we> zJ;x0mhP2jaW8H{}9iA55Zmx$bq^sPLMRwBi_5An#@pWH2V9>GqIl`+Ia=ff%U~}RRf4oVhXL%+yDf`yRd}y6M{s(%E zuq$ILq?*Ff&U1JJ$h7tlR`I(0`fTve@2BBYkwde|g0^MOF{Rhfi4zClYUGJG^obXY z4!NMcVsc+|8Fx)vI!5w&NF;LkzGhbs>y`Q}*fy1d#ac%I6!$u48uw7)aPa9FK4Hgi zFZ4>}?LyoxdObZVH}aU6dOohJ@GYZoyYnGSgF2jfT^_MhgRstjoAvXpXypVbix1lT z!yKJ+(4}r{t`kN7O%_H?^rI&G2EaI4z`MBd9ci}<(^U%IQwPIAI**XG<5=f$Y`%TV zb!6DklTv1cqW8z?5?aoy?c+yzGlnb>e-vvu?np_-2AkL*mCm%c)Fyg3K^SP zVZh?cTdwr-5Y{3cp})$}Ksp&0y^%YQyG!@eHNmq(o)BG|HatOv3{>r@6=OAyy0QsB z?s?)3CZ!Tl_Dzo@C-d9kQH% z6Z7ij|Gevc$GV7DBB4@uVaD4@d`!(G?uPNPY2;DTc&F}v7bv&DZyax@)q?u+BRxae+$!X{m@mY#>89!kBhUswhAbO)#iHAPGHgg08b>A`Czanz4`U#S zbO1;8ucK>w{z|8m8K`rMGET(@VTw*vuH0eqIvfOfzkhg=$|MO`N(u-!VK#ZUPdXqY zZc|P4aj-Zwas*lK_u?B!@YotS1$5t4&e$9WJbrDMOZ(b+Rlw#uYTu;#56vh&`zOvl&d9=esvsY>!IS)BZK8{)dd8CtF5tFTB{<-<_wcX_r zlg@;c_*)~-^jTrEMv?Mt8Qx)*dgSm%I;Y2)YwcMiv|V(mpwm+5@@Dl+RonZ~X=AzB zO>neF{pPSF0~nUo6^iESj(QM>RB$zbtA^`Ek#S6NYx_;oa&y{zHOAy#YhM;P`s7xP-ltcn`d zvocADR@wDUaB40I=pqvF{f_nT%W87a&7vulx2)I5b4yPmEQN)vyJlMmP?CQxm2E9c zmw@w2^kH(mfC2PxWRo`pK>84vKpJ@x!sgLs+?>p@r(6H-8_5>kyyGMrsr-{ePMrsk zSkJqs@T%=hU%V+y6FrGjcXMUrrK96cN_;|_)YsG(bKThrwG-j10kA7r@^KJ6a%jd8?*Qigl`riYpcZa$~~f=c=``z9pm|uN{ zcAG_a>8TbR*n+zoA?9?{;A5z~`87XNaB@?aNQ+U8Ns(Z4P%4J@L>N8HrDM|0wSbGV zjS7Iz${}qk*Z{J~@8G_=zRy+~BZ7UqsS}Rf*{wv@!i3ry5Z%M}VhAKM&W@9!dzi0k zPlyZ}XjLcoN!t+PXHVDC&c3&2I5+OJM0_{O?fmNR{~jIz(ftGblt9B_+^O8@pU7w5 zee}3#eyP({)RTkmTnp62`VjJ?8)X+$M#dg5U@5-pAe_~m<5>pV4RWm^$Rb0TOe?-qf%(yYmCg)VlW{-Xo>zJ@4raxW& zMBA9gHkBCRa3rPb&3cwLXv<2`*$sY=>v>N@vv^gr+QD)Bb8oXL--1XLf-;gaDmQ8; zy(48M^i(y2@;0+RDrkj8ImmQ0qd1I_lYJqojA_nwG#2)$sJ>cb)RDB8ea_ECm*=wO zITYvZs!pf3sc{?rzHXi;2v~1FD6F%-hwT)P)SGV>*|g^WI_8#W@*B(eKz_H`^I+Ne8+Z(-b;>zz-O2Dax)J${{QO|}=RG{A zm^e%z5er?YNtOe`;W4vdD!5w%6%lz-Rnjcf0&Lc6`Bh*-oPI0Ca&u+>YpBS2yqH$>Lygl`f9<&FWA6_!0y1DyihzAo%n3mV|p`yu zeUBciZRjtKn>R8?qUc>a2r(qMv!0oj?RmZRC{MqBwQTOsb@$c+PDjel_@3BnSIJ(R zMpRZ;bY;fblc8R+C@enq&xiJ)^|;7w6o;umiDp@ZrwQ;hV127K z1@v!g=M;F+awC}Ik&ky83?et(RL{Ee^?9>tOpC&?x?Jj}*SycN!IxQke=2-WY|dLq z{GzdE=I1H+n~C!BtI>>gz!=PLiqU***dFKcNDa3^V)#%HFT}NgR>o zY-6|x1>s-!Ma!#<0)xG}GnsKms*5KtMy4qt-$^M|dc6O*QV%6Q-^v}F87558#paF3 zi5HK(5YJaLn+&O^&Q51i23L&@FGd_>Xm-y54)0#Z8K%_@TY`d07B+zLGBpvfk z4`wQ3zLYu{G42u4VH*`rn149~v8miH;~{$NMsY9mrM!$M zUU1&I2jJKi45JsbVgez3NB2nrSmw*wN;P(RSCtv%JfE&E6k2i{1y8kW4zky)Y}Ox- zxhiNG3ru5|LT~hQSaU3BaKG}#-M{*|Xn6H)EHnh>xD!bOo(pQtH_xx04WF2mmU!2* zV;x;y16!u~UJ@&l>dt5gq6!FE9d7{^)FL*Z)>VVTZ@HFUd@0u_S6K{C=ph-O6=^@( zDZ0_Rq+v(bn0~ZzQ5qLYz(G^oDQ9+NjlrjR;uUxAV6>O5~`}p~FMAg`+wVNd$9zGsU~z zfb+4TCY8PI%{xWzMTQg&zkK~5)5Y^7g5lW5KQ2m}^0M3Ax|qSlw>?}i(qB^HQR!aM zZG-(@yc`>o?IdmF!`6Oy!=ShNd1~Esq(Rd8T_gX8K$dl6q}Z!%t&MyCuL5h~PnTT| z^;6aPm?{cCp{PJh$1f3swJm_4*alw{Qym4NZwg4l6(TDvr^xrBmI(7DPD;f^5~o@U zGF0CbEQP5H_S%@L3o?lb8Nfmr2F`LkUnIv%D*j+2TBAsDA}X$W@qUpD7Nqqqx@&P^ zHv;`^waED0p@W>Hv5SkK5Dd|i<33{Ke~w2>qm($ncUTj8-NupX#+~})E_~{&lHeZf ziN}^@ktu5g1ahazaDDs`K#Le!wsjf;%bJOw1Ib*iV&}GZa2@{>QwQ ze)@!#avO-5k+k#q<@6n5)WYViU6N=|LPJ>Tr!BMx)eKeYsCaZIjCP2RhRJraQ3O-j zBzI)JbK#KRiAP+$FHy%evuT!dGl_wx7}a_Ub<-I27}-Z|=`o7?lvJ-Eua4l&k?;L= zt4GLE5I3$;4S~EXPM&XR>D2{O%lobj=Ueq>+&Cy4FgM{P4QGfObCp*}5xKa({1hU3 zx_lk$q5T$yRj>bxPIl$?ba80eSBnfTNzGoX=!S3RG9R!MKL0<59u*y*_>KjAn-X$J zN-O7DKt=M*$tb1EfO&;I)RrJTO}(shV5sWv$k)$TzVoxDK^?pV>bV(7R}T*%UT{tKd+6R@ulXoSi3gioOF1%Y$X-p z#Cg)5ABAUp0l6#v^FCvSHLidy6OJX~BAYD-Y>6tEMe1 z%RXEdE7Buf;g6ok{+E`R)^JXQDiX?V-o5v%rZhI0-~x%kK4oU+MWC^c?2fS zL=eY!Je_h=G_y=pE`V^Cq6Va6j71B|Oqs=H4IjOKecC zzV5%b&*=b~1w!|iNWV#1P4`~Pj{y%t@TX(-IKhDfE9uxtF4l!lPV8c{e_9PKKmk{s?esQJ;6Cc62Hw`V)X@kSVEDzrwLjy+x!P{1s9=WY5 z(Dh%Sh4C0yGA@od4`X}^&9ZqvVCZ$CGp$42>rHOy{o3AUSL<&vp?E@~lWvqNuSG;b#%*B_cF;`MTH^AD}z(FnI< zRsv@~l~Li12HR;R&mZakjupMGt(l(AxoEAm5;I(~8(iz0NVHg2S-W~@z!9_1{#mL` zhI7lLf$@zP^mMaigMt}|SBxddknPZ|^8I(R_eoP)$q4(s5cEaR(+OXK(&MRi!aT=k zO76gu4TvhN!kf}l2^o;=Oe{h*E$nJolG-M9bCG8AInIv_*t;+w>Q?|nUuYHriWvGi zG=%`@W81!zms+v%T0R#^$IHK?;BVI8_w;C~)m{go0$fVmk72bE-zn~jmX24h{A2w@ zM7Apfd)RNM3=Pxe;p=K0@5`h7lUN(iA6yAAJApU-xBL-n&&QYLateem^icux<;^r0 zr`4a6ACOFu0&=okRGU-&`j<_;U$=0=_1#4GS^NUHGk(jv7)s;+u~_mYF@JrUveIn> z`|b(O2>xx}*oxX5%k;UG=A~z3gNky!KS>W~Y3|b^!kivU77Pk)?`f@d*y`s%_vu#y zm@2mqUswZfbK#v|u8vAN(Rj%ug|_{o2S&;v15kcw?K(rXtFB?MTj-g_-qxAN$DiRc zO_X~cJC72#m&i5^3q(bfGoJL{Em=Hb z*SO#R^&2+BaTEL6Nc|R<_5vgs4-^L6!+fLZ!idu~#NjX%M7n3ZgHMOWRbd0I&*!0h z1uVc)sp@aA);$rlK$=?v1nD2yvFsEsBOJ$ct)X-Ph4Rq^v%S(=R=G)046miy3hBp~ z;h@yXk$s32t>yt|7tad}xb99c!)0TDW z+|3ORqud81jER8x9p6VKtkgQ2CZukrwiW8O`%$)@Au1w;1aBM*O7zxuYJ_YCQApj- zQ`iy4%dE=!-vAeqs(ln?(VzQ0@Yml<3ngyn6&_F-JljV3rMUK+TC^G)p0zkQc=dBp zMzc#7#+dm%Uth?(23l@Hb9UYk;q+@%N2bi-v0YuP=yNH-d_Nfmq5dp9aAZCTYy2YQoOq;qI0XlQD4W_496i~POJu`067*H60qRRJ1hwb7Q9A{WtiW68M`_tu~I42P2)A{=3*TW^aZ zFj0N^l+~;Gu_vtJ_$8QmAV--qwsL3ih}>`4A-#L|ISVUpZnrW1cuDz4{e^q{B=HOe z(iJ@#KJb)o@q`Kjoz_}kxE&gaBr+J6tDuN`+?Xmbc>V!MK*9Bna6Hx#YI(P1@ETo0RYiuwKh_>&YP`*~^Z4~K6H%tAU%s4H0f>ODfu?yRlB+Lr54{YcICr=*4QpODOO;Lv!t_SRe$Fpr50q&qKmB}6`LLzu<@Py?OTh8<`4&s$VdC5F zgxjo!R-ez2PcxxZInMcUBs`Wddw=9T1wgv1>Fv%iU31G~HEb8wF#|PyoFO(2hFzFJ zwGF_~+u29KT}9ytGpN9vFKbo|!XtPZ{gdNc{-V@|#Zbqd5>kE7^^i1a1PQqIf^wUM z0TPzgNl*HcI+!rHp~+e%*od+iu4BX2vEy$!E)tyFR=*u4LpjMj4+!wFo9M;RYdZ5q z^l;2N(R|5*jI$uUgKT5m zD}$ZYJ+iS{&)9<%Mv-^~@5dmGq=RX{pR&J2X5UBywydnAunw(oQk>zSu8M9ssb8+I znw($7J6qgq-(myYroHyYnj^%%L)R$nYjuxWdKHN3_84Yq=oNtuZd?oZbU?lO8}7k_ zg5){(z^KgIDZowJWiG$th_~?djQVszsv+e(CS<;Km&e#>hnN7eDXr(p{Qr*7D=x%w zg*#XB&AY-UdZ!MS1DcV-c%;#eWL}vP1#SXU`U&^^-zV$1TB3^dLS5YNe ziYwQm*#&56PFCvfSiH7$RdA6OdvJ8u-zDrB%pv0l&&f*cZ{x^X&o$+L5sBcFy5> z;h~-@rW_baSBpU50r!e0>XUXV9E&x-&)&@jNpqvfL^#3CkZIX*Y1AecYit&>pM~5( z=EIQ&+<@%!ZKZ`{ANUV6zXoi#Cg-Fb7u?2*57Jc>5A+Qw5U6ItX=WF6jtT>@jlvu; z*S<=*SvM03{Na^a(0Li{seV3x4%?myjmMwNW?$+NioHpep?l4Hkv=SFjb~40EW+U9 zB0OyAY%AAFP?!eNr|ouDN*yAX&ETi>?nvwu8@>ldGfI9 z$gyTT`2BI$oZlnjH=^JCkWPMqgYJ;Dq8QUVUFM$@Tot_1M8uyIsAO!5?8YU5AHvWs zn5n1m(A_gR=C?YXh-0~@SY6nRI#!avFLyNc@us%M>HLu%Jq23pe;RjitgYu~<$a4Y zJ&Lnf+h#mR$2u!PAyt*s|JHwiBFCHZL zdB`Wab-+9K?EpQ=5lKg?_h)5$4r@aY<$#UXlb7+oLm{kH#Lr*)I0{! z-}7vevAK_8P02+lToK|vqnZhE%1Nh*@dO!*;?48y%Z9_`k6#T^D!DF*JqMbVQnW5& zX~OP?R_bT&rKF5f7=}}29>P=axO9h-zTNY9Y;srdMRKf{p^HlE2|IYSLt91W7HyaM z72~1wORLOpcZsmFhC5ZI56-xH^TR`?nn!_9bj#0yx*azTcWeD5rn3LEG-We0)sYG^yMn*w)&*v= zF4tl*HxK72lv3iIKV|`0#eHXtgr%|4HST`&TdAZC-zJ)dd z&QWpH0_SGv9L_#`4H0t((;Ox9FGZxPH8LME*1K4K2}#sbEc7aKLK%^2nf?VZ`i`vR z`w$h5FXIydzJ)h4s~qCTEMH44|D0i8o!jbN=Tvble`&DxAy1f^n-l_fJ$4?3wmAnmXe(H-WL zYuWx(mtI<=466MVna9}_BbeuNUbez;8u{I5oec^89(c{PcWGwelErtA?27(l%R92-n-#T_Hr-!FQt&RkPSF8%T)iHtf zaVytYq215*Jk1Htg|v*r5||?N*Aj_3o5VlTt*0fh0L;EG!-fjigHF4%(!_r$y#@l) z8*2K4{E(Yo$QW|c#{?cn!m^z1-nMDcND!sAzz?6-VVRtp`P0B2n@s90k?lQILFjwgh zO;7&_4sb40{`8?CPPcZQF7OWByYFDwn6~BW84CgXEga5_29h^h(DWVImW?tFx$Fg$ zK2S!9^KliCk<35&`ihLqFQ)P3)LI;+aKLU5ea|EYkA1+CTn{p&@9bu8j*o3FWZzEk zd-GMp`r)4g&iJ?21E-D*ZkucWn(A?4jMIj5{1T0w{x+_ccaWP(XfDYJTV67sWWSR% z)Fh?Mr^c-7RtLWuK8ui?o9N& z46p1(5-a_UQ{IFKC#yu#_UDBAMr@hLm*LO-r1sZa6eqVN&BJ4Q7WnKr!8IK5q$N=b zPD^=P7WxW~2Ek%Z!WyQfaN^~+Z(Rbq@Pp+9FIo^R>175k-KrK2aNy#1 zIlJ6BbZ8o6Mk2Vr^N_o|)_g*CY2acZyj!-&8;?h##dBo_Q_Q(Po_8z+d9>F19IRZy zh=lp-77+eqp4DNT1&1)Xz}m~!H%tHbtT>fMcb%XA&~_N0r}?{zYodf#SmeS7KH_J6 zCl=tn%X_Tag8nr{$@?6I4+hgeQkO5E4i5N=2XbEgF8W2H;4tKzPye(u4s(jt~)I!Q9g+ z!yiv-kIFy0__&kt1jdV*R&7T*S*#d)~nFGN|6Xs^-qqcUfdy;rOxnqmrV#BNGFf z4)i=kKR>nwWsr3VgRig*D*>(3LomeSI;pCJ_F`=sJSTvpIb6PA1tHXprH_uVWxT^p zvD&IMH)VFNEt)YwA@WGLb z6@)m7`kgWVnqq%kF%+%%QMfm?>wBY};12brncPGST+^3r-?9e0^K zxn5~hTF%p--REa^^(w=gLBa_X*JV~}oz zuen~Vv{a@2<+8N8ipm!wY`7t&Tcb?NW`128+&~s+m2#rp;hAMs3ZjaV zIV|98n_MbOJ+^Yd1BmZHM`!wme??@JG&h=ztM097#MkPkKBj!ujsbFef?tDHFaM7e z{0o{Uw`lQ(a9H5oeO7+PJhg?jB)}gpSO{)MX63Bqn72;zjPIfz9*N)`=qjIzg1~Tb7N)u>AFMgjL9eGk(I(Fu@Xh706LEKIhMCM!7?5=kmdzhUX6MJK&L|5 z>Doyi8ypW4-fNfOT#WVxsNZ7&gXl%LD%d=uw~bv}SvgfCRTQ`lYIX3usoDW1A(jZS z%Sr!vOS0yTO?<0g{o`5DxVd|^dIk=Ud3r4Xz3hRMY{FO*XBQ`XE?<`F^2J(8r#VZt zTII%r2KPfTqD+FiEK``CM=t2mQx3-W+CARDm|j9_hE2{Nw`?VvU;3jD{*}yQYgjSs zgPyM?o1IiA{7Q=$-l&%G&}td!^SQ#(tU}a4^GL2U&+i};{k7Eji;{4bO2I~a#0{>B zEm06dPK8fJelr&ES@Xmj^6RWPbJAjxH_~RKdIW_$Q4#Dt?))4sr(}M9JAdoou3yzPwX;pj0xOq3m{2fyb#}r$h1SEZSjrQ` zjVQ>Aay*QN_faCBxv@<+?Sg-K$i|J9J7_-1qu}yfRTqT}PjTwlbc)}*SGkUgP<(jT zixBMmJmAW1onNPMKnVQjcN^6Ah?;+S4lWtAL||Y0<$nk^0sx1{h$~sa8jC{2Lh;B$ zi9=|5!4P9~Hl8G@)gR0qtVWc^uM*yWb>+ayK+F3{m@Sg|6! z+sc+b=FDlsApf|uq|$^)A^(|Dj93-ndk9XlC1ET)^X%i^2-Y z{nVN-rDB~glNx-hKSt|Et|v?1o|YsMlygPn@KazHBRHLo77o}k*51MCPW3$Kci^&T z(8q4vg&&(996fmT2cj{(g$2p191(>ew$da4`Cs8=l10Lzv3W!$@X(*@7>N!7hDONq zXp^Rlhoz_TB(E~<*Ynm=MuSLHL|TvU`ZT_-UB>NP3Dpcs3rmtCZQ*R1(KqufX$e*-to%z>Skc*&c*SFe|hI;Dr4wSfTpFKs34I4iD zXHOj&KS_pGp&{$pkZ9{teNd-cFZjFfOK*oMtThn4vfSGfvk{;o2KJB|R=N*90G#w7Q^>M|JFXPGar?tCQbAe1 z?xKsg=Y&5hm43JtW7@ZsEu}^VxX=E8y+KA(h9t5nDST9~<^v9G6V@nv!g;Xyyj^QD zEr$bvHH5>cm-6->)D8|UAdY=-i+RoJFa)OJaU{#LT4|hsKz~QtijiIwsHu7W;TV!Q z7W6&l)D!IBS=)H#SpmZ3BLLyDW(`jbhQIVgforb1OO@}?=)V%k6 z-5%=Umw7=ga#YN2|8%qBz^BHG`IGZgVd>IwRtLcyf_g!ASF&NlMH1i6`8gcKtzf#G z*C+XXXX))d6h6NmGKG5a0ns$REYvG&?`GLl4hSc2>CkA*sKn_m@muSJ_(Hn4FXw;G z#Ao(6<6IUFx*b=}mLuq~XyLWkSyJEi6dT?bH!IdUleq+EgI=?i z1c!NbNO$pmSe{~sh?Pv=?JJT>rdS60*P5}0h~n+6HxM!#BoU5P6l7}FiI;7_U zyJOLuO$T~NrRjaV3>CrU0_62E(COF`)5#!mV7lX?Kga#|aR$rHQKJdI8pg-!ea5+A z$%`TaAkp3&a*=tj+Et=-+xhL-E6_&prE$wtA%lRgBFrxHjg72LynEaLUE|H-px1#w9!;90?phV0%X_WT^ zGSvjrsvP5}fKP~utz!x)+vq;yAM+bs#X?y-p5{9}B?XChYOZ>%__g%C^)sT7MI=*9 zyk(;+SUYz6!}tLs-(O0Lw)(<71eD~7UJp6@k=!dijmPP?!b{i%G0`ATuw{GqU-c{*Vn*Xxt>c$(cR1+9d7zw z!@6q;yvkJz%e z%^&;ragX-IYnpS+ni}Y7S}c{c%(T4T}D|5ii?pVO8oL?V3& zOouP}&TaSP4`sIyZh~pB?QTgoTP`QShX2cvN}!C+Nc)n$xm19YL5U^BsTJrM(^2 z|Hi4@&d${+|D0?;XFQl?*1LxPqsKycf-9e)B~Iq@$nZ;z&_ElIuO$(ELg^9*{sm67DujXB<35Ejd-be9zD^ zDO#rWw?XW#m$EyF_g8+1S@D#^ku0Hw0zW5taqBR;#J|toH!FDS6$9sG@2)0e!AUX8 zRI$DKRujb0rrxEfdbb|!mo^{jnR6neKh+OMyKA%@kTLiJX#Llii|=a1^oFk(-@U=! zj~k>I1KAP4_mOH3%N@_UU{H|aidJfwD4^%NaWzlb!`kz zXo$(cJ7gBGAFwga;P=Ul@yXSkkszoo_Kv&*ZnUqs)}sHL`@fG5)0L?K+u`wz306Pz zD;hL2rd-F^zZmlELc3Ga;Y_0Z7O}88Ln7yIk?o+6UP#x`Vh(XY9+8In__B5ZL)2E96pQ~`e z5EoYX5uvziRMF$j`QkyoxK#Ysw|uJUZTv{NA8o@}e~|--lzTqM6B<$1*i($Bfi5|G zwMII|_RxsNN0-!--AAhHs?nF`4O^Rx7xP+_y|L}rH?;cRCR2;C_t4f z({!~~d$txi{Q)v-^TRb`%*_5PiU9zK2t=BmEBo=n{QYw;F&}!8eAgLjqN{Er!zbzK z6rBAhY-P$o{e#@+)JmTBuq@7x=JKB!Y!_qZs$qG;r6XCFWK&w^J#X6+`Ut1GSt!l) zh3%Jw3E~lg%KH8EH(6AcbM|h9ha_a7tDJogQ4#Dt?z<7QfzKZaAKlSbblbar&EzX? z?&mqSSd13dqW9#Jq6u7fUA7vd`wUo?nXPJeRNpb{RuP|=#EOJ)gveWuHiH++O;47h z_sI!+g<4Jp`B?OX`5fb~-#w6$2OcM8to_LTpp)Oh(hx0d@2z0V#aPZjidXdp|A}Xc zLN%%lmWP((5ac1X(_f+23T(Z($dR&TEF}=t7cSyZxY^3L*(p ze?x+aT&*{Tu^tFhtnleJ)GTkr5m(d zrx>ict(`Of6^WdL6O)Q^tUI>Xp_hEz7jm&SLlgODISC@}dJ&+9EF^0gkc0rhC67~B z-+g1ZBr>Li_{lF%1_n}UP5KU9&%p}fp>v(65GxVsG}R(rjQDuXGW`}p0ljDYZtA0r z18;z^5PBZBdmU!9ZPa0TezpxtyF(CF4xPKnI*kBtfUjW^baYOseQ#m!SG;)rY-{Dq zTcTYa3$!mWC5CjO;`!yd2k<2EP~_1nvaN-o`G=_6%#)G@@>vFn!u4Azyzp()xYF6{ z9fSCuUYW%tJk@0yBxoZlX;D@UMlX#wMh-vhH16Ku)Qb~>XwbsuH5u7W46&iZT!MfN zeXnN^(n6^|_}Ub_G3m4q-f$9MUA?SB z{2O(YTdf=U`T0k_tA_;bSu!zs?^lR)tL4`T>Rlx|=~5SxF=;Vv>=Os~MTFkJ|}R}SKTO&u9nYEDmIqD#+)rS z{JXNw$WczuFe@4r6!#_nG39jvg!L?=bjq}0Ldczj_^Ui-F&TwBrbtc$>|Z&&Sk57 z%VsyqxH;MRLGZTFVTJO~bD6ECQkvQ2wf?6&^}?*#tZS~Uid!Q&+eSEyo}x-sm5si* z0y{uL@lq)Ji~(=4HGK9(cl9`^mPjSweNk;d1Hd(){jpbi?slMEfYOzeqA82Ls#N+O z`F$g&L?4wZ^DChC0-NS6ib_ybxmn05VNCV{qW+vIe_~P!=c_<}Q^n1|-3Gd!Q1x8~ z--A(}`O9Bh4WX`2gC6Qb~RK+?KPe^mNrF@5NL(M zq?LZG!T;mo5smvJ5@XP}-muYL7NX8^pl|@nSJqU-UHE4S2H&-Y9jCxw?7<>;UA}A9 z1hr`IruZdlNKcRB)46QFgCAdGFG`U9z!sR-^(@;R_bbgXwrX;tj0ymkhA9Gjy>x#& zH{4RnOznW;j3AQb8Y&y4>62XI;s1yTawcQIsTYOdWP$Gk^_U2q4siaZ(envyVz;Z1 zV(;yEq;vPK^J3HaLVU6FkH|p$COqPVTbwh+KxD4Cq?@fXliDQp@6~rGe*Ikr{*l?) z;2W{54BVLS3V&3k)3_*s1EUd<20kRmoOp!B##pA}{`lnRZcuYDT1_B&y%I4nZt@n2$4;- zup%k^QklB*x`qDC4q8bb>pc) zeVh&cGirZ?fh!`G6^w$#gvHR98g_Ac44mJ#`9kfcVr-CK|g#%5q#8 zlta zH2aqU-WyHKJ$SHL#pie9kL0w-L~R+n?fd~QH317{>bd(J*xH#1JL%T4l7GbXc@ZPA zU5yUVa0*9iMUVc|-)`+?($zNwa)wrJwc1QNxu$>p>R-<`&S&`XnM!sd$LG%szW|WE0netuL@=?hv`|*Tiep!`yxc3sF1)4&pAJL#zTj zLkWL|hb?D$r8!Pc2I`S>%p&bq+ek&eCn1QxylN8G?kw64D-g#2RnaA~zJO9R?yFjs zuJu5#7f=rUi65J=8gq)B5O1Voq)#;}1-l94H0$bfc&K{P1@FXhp#g<#6GE+)Zz9oi zQ>lId`I~#0?c56fRqiB@S(d}sVEN$1C98w3y{dsQ^**i})sOFKbIO4Pa1{Rj(rAY| z!?5x4Y}d7TJT&8$A3+;-l)659crwd&<2)5I$aTKMh2d!>aZ9Pu!mA`Ij_k#m-6u2z z$)o&@-)R#^Svo{t;S}NZx)dx1x&IA#&wf>Ms+fohz*Bf5@)zbfrk#%$jW*sUyH;8IRMK1aD+@#IkI1GB9-atDz;0l~Nz) z1AX6LuFfxWvg3T_u|q6GcdmdMo}kTA$AH&Wf^=2-R$iYsQ}TXKdQtY$OTgv)yfk5# z1MAEsgC*XM-&MX`k(|iEi`%t%D%`usP9*g-y%?5W0sR-yG!+p!Wc2h8wPIv8QTpoj zt4IzKp*zagQvQ$I%DG3zxyy@C{4R!qRj}#7xeq*MUR6q_waUSSBQ0Y+7JfdQI97`w zZSfpA7m+W`b_Zmf6Tyh-%71(2upn^zfl~Z;S6lqkyyKHHBHZD#4v1b{1A9Dd`IZdu zcCoX6>3bzjGkAOqNL3X-?^>cd9g!o637YKL3ZQ#R7-#E2k~$B%Yi0*9ckkeWicM{f zS?#PJVv|&3VTa*K8w?4M?h7d3&+Zysr)XDZrC&B4)zrX{ta?AF^E>jp_rsp6eGV$3 zp}@02c7SsR=%)#npzVT6=`C9uk%l2s_ax4{$n<8a$EvM?&*Z7$pDD4+ECO~lfrD#n zF97?KbOa>%bQUic`Wt0J3`$bXWJ8z98v}mOA4%lTAyM^D z^QB_EqMy%du~mj|JFU(~AF4+lB&D*HH_w}Ili!X%lY8!pFaO@M#nk!5$qI|^GpZ?o z`4si@hcz&pN}DeWt&sqkPhaYY&8X;k!M~3fi!s;|3r=gBC~u8-bax6dz5iNkR1C$R z1Llz9d^NUyP&+%rnf<2lu0N-1Ez5uU_MhjgCp=sUua%L~ok244jYN{Zte6BpXCu?W z>FjqySx}_%TCib7k-PLFkipXhK;Wrro8AMBSW_qe$N;y8_peOim#dN$A=ICrT&G+T z>5`!sD_#giVj?v3L~R$~^!QUKukgLiooT}{5{-Sba{(2PQjFrCd`gbRW)XM|h=+Fb zr#R)@^JiCuno>+{5@<@`4~v9&*;XUOc}TDKmt?W$(Mv2z?vUJz)uFCYtAA% z*xQHEm_=?Itg%V)WF3wOS!oxTcHzh&0Xk8{DUrF%w=cP-+anQTETVV$_^z^8=`oE0 zvkE^Xe+zxAEyFGUYA?fsmrB2IRZ-TmrwF>0=Xm zlfz3{qv$BBUfzEwEr-6wL~M{KzfB1IybG;eq?qJdWsu?ZV*2m7_KX0oh;gcwNh`ax z7=1QO=&@LCmj!Cd81rjbp5IqO+SuoHAmC!H-7B$iN&4RjmT`!d8xg9S;H^#^WgI@c zK^mS3eI~P0YfQ^r?CG#~fB-8{X=4Otc656?ziCqhk56#3t_Ig{^uza@{C>Tz1JkV# z($FF!1WFmyC>VlEaMcE@0=)335*Jy!M@ycv9%;Pn>HNa2^;@r%lQoTB{3%4y+t_Qo zTA!Z`PQsml=$Vw7gH2zEoz+N5ebqd}%-Q+h%S>5sSnI+?C%bB=+uc<7C0{$B&Zs}@ zJ7HD`{MJdHb-uufNWAj@?Fyb6fr085Mfh~q1-Bi;zCwseZf#IO*K0?tx~+`M)$!;5ROz;|6(Ai8&~{DMz@~pdM5JypZV`vt zPG-dJnb${F>+GS+o({y=MF&;y*O_45%VchGpJ8rq1ailbJw3mtbD$!ILyXuAw}D+cEZHm*mN(hHLQ$1qTM$;U zrR|F6F^3zh)JpZY>G=Dtdf}m*oc%Z;-^*PzTL(k;7GXTv+o`5aQ{HdfLeXWP5I6>K zf*nb>j5I5Sg_BBLmW{qk0HwvQ*2l#CmA_gi`{RA{GrOUo8Ob;y`546bek=5n9Vw8m z+9rOIz25^Wwx0!mG5s9)*|UDPW@!nBBYVx6IB}rMXICg5hkUEp%5T4tEcV#WUaHx9 z;ynWoPvQiD*+-7EVu4DTd~X?P@IO36+IMiVd(g?uAlY*~1J$^1ZhYYfeb!at9ae`U z9uRB$YyhF+U&1ORYr#U6?}s<0=#~Gauc6Pd@J51-)-ajRQOGPb`Bk2>xT-$gYK#?G z8{f*97rjYb_*_dPIcO&z zcU{u)x&WV)+*F5}l6@%%d4oH@a=KB=V7GKC7oe5z2f#N$fR1)%t zO@1Om;!9*5C3UH-s%F_KDbBt9nC|65hMP;a@B6Un2)O%9%Jp(P`pwpUw%Cu{!rAv> z+M{xcw|L-A_dx8-GJ>x?Os;q^db=$4x{+&cX3DfXcb56)Es!(Ak-O-`)%gxK6|6nI z#al~(Evp;}R)s*fTJ4eCek})G?v&LVrFd>B&ZY=_vG7G$wXyal zK`eS#R|u8L5&JIM*3otAvJd_1q@a%(9!RTS+CM@uME8mN{4Br0j=O$?CHYatj+`k4 z6v7P9`WVmgBq++!Cg>D647n#KgHn;)W9aNye1EA2&dG^=6wb^3t}lkRwk6Qcheq|Y z$MSdE;D}E4xOTASA#HP1{JbwoM$>yxSo!res?flj%*j|Z*T_hv*aCja1>RAgP|wbs zxO~XBqY9%IGiZK zFJBwQ>u{?Q+qZ zqUGN+CIDMwf}LFBo`YVO1K3RCzxT)4E_?$rRj88FX`~YxqQo|#yQD7Cpt(c}D@GcQ z6)c(DuX6Np13V$1nW)yr7LL16Vx!2ZJCJ+K49HP9c0IB#oO|6xKY81D0A>B}(NSu-WSGf6yZ z<;^~;mgtmpv3E)3%U#KspVVRq%ES4^>*uj2XS!05`Q~2(*Zu3u+KlIvS$wv8Q|feQ zTli7g^nXvMhAnm%M)o0;J1#@$;*M#S#~iDUr=E`qJ_=tUWj*srfh?k&mgty@!dq_) zbR&~FJacexLQ3mBhzj&CM$`vfRm1<S!AJQ-SpN}>K84S#S?#L_>`eA{5Z|2y* z-%&!*fzl0)v27X@^sRGN?#6ncFPE~~7!_Mal;SpIr(8F&<)J}JQ5{Y{M}P+J$UHnQoql-wo-6f*&)8HogwAcv(5SQI60!DD|01BRv;Kjge%a+RcNoG){(? zzf$MJMW=D*FtkUp(T#msG&S3Wz;5qH5EW&E`}W|KYn)G!T?0^)eePVxzmb z<$ZmIByd75o)3S9=y6oO{=Djpw8EVL-=jFceY7VY01OE`Q`v9Ww=K7JDGs0d?;gkA zQ=5RB()H2tcKukuUN(*D$%OazNR&Y<)Vb3N3@qnmd4%glwWP2KmZjKOi&?{&YP+ER z80H$|CwjA&Fo)3eUPP6idz&oRqn7MLbbFLdcBQN=-CTKTE-oBg>so_Q3G(~lmgbl6QpnG?zRid&-S89) zMH+qI-LWR;qM$~`rdSUHaI~Bt?;$+M>W_Da4Ub35P*#{h*LCbCz38DJNMTrH-3@w2c4R)R#P%8dck?jocl@VmKqdHBG%wd=Z>8*bZt^} z32q!a==h$V+$E-Vg)&G)4|u^QPI>;Bt##}W%ywVUK90$KBgtqAybsPHFunA9l;e03 zq!hjS7^f~!y~A^>gIs@`r&s-(?<&;2jamz*CZNo|%G}J>M&J{>@tVSV+r@?PRa1a?gi2ZC76hVl}L!6aE~a#ofz`B*Yf%-h=IHS`w#T_);+RJr&?`F?ihZ!%;2Z zu_6E>=NYjj=;bNs=7+R0cvvhR4L!|T9);ha`dz<@?fq2vQ&{v?TTMyLXSUD@2L7j! zqv`Lnr>B1ODFs7tw{8vv^R?&^J82T!H-EGAxwy^AA-iX|0UES_6wH9`TVqOWwVYmt z_>oR+PGoE=aN`!r^FsAv*sj5lhJ{uR;XuIKU-sgF*VX_f(}=B`Y=zwGheVc47U`{~7ivU9(8Tiqs^1^xVNLrz4Ze%<=m(tM zTX*?#SRv02qCrB#vd-p0!)>_-8-Kk7hd@n;*I3NTo5;d-OUv;>_2IA#xGw%7;<^vw zib;8Gr;E&750fVu_z$eqQ7EyZu<#tFtcK=~KQuSHVa2>psWPRT(NvxpF9r%Gq^Cn; z6J}86jP|ufT$nx-O7v+Hp-BH`{{|x~w$Ngh=!F}Ww_GPwin5w`aB+tB_HkoA(KP=J z-Aed9+lT3}i|$+V;zbDN$+fMxFll^eETV6o)m=eY@zd|zhq{`jPS3|4^kVCr`bO=A zPet~cpHjHr;-i#!` zth9$doVDnpYu}MgBZIY&b^08bI74#~WH|Z=xa((=#S|8sVbW#WM94`OtY@s>MXFhGdNg>#{GioRy|DyqhRvRn#|8~>jgwRZ~%Bq7mNYHlln8-ov-P3rY z^$nJ`h;yvy7_KeP&#`eWvmGh~3pY1i7kD{Q(O;=5tb!r>IoiRKVn*xrmuhM0wckDn z$o4#K<>iWASDV4+?^G=a#~~XkjdJf5Gj`GctnsRfnAySzo2P_|Tpg~hiYQ|N%BTGG z>xh2xjqBt{1i{P=D#IB0m6ijyisr4+sYgA*f0cm^Rfggz2aV@LUEbZHHEQ#{46(Ju zsIXXZk^R{AO9H>(4_D^r#Bl`5`F)_l!MS6HHfN+3OD>V!@IzMAu-rQjjPo@HxcULX z@d0BNGYccU0ObF~bwMc5Y*=kh6xmlx-!G^IBZi@db0NC~Nd}6%%H+5Y=qYU#^Fj=$ zc@(Jg6KBv?cWNn(=@pl#K(5h=2nT269wZbL=V;VbZee@g}_4g z@$uUpP8(Meou5A+?Y(Mvw4U-rQ;uC(yb45&V{M6;SWxkDl~V}Koi6;77JU|canRz4 z9@6+*y18<9Xh*rE)F9rTU)g4xoZI;WSYECHcHSx+ZYvale*pq1PY;e()+MgnW|EmKx z+uBqu06n^Ed0aN#%6Q8x{VmEzsCwjtCIC&Xv||UHV>Sqb>}4J9z3tpIAA!DJx7c*H z^l8&2Y2P+Nqcy~mRuCO42QW;0eJ{7 zewVP6Xo&?xI25OjS`9ea1c9SZZqLQ382#@+$dh&0L(+fPuIyZH;z<`#^f_rg59RT& zD#ZOzvd|EIBvNSSLl}cyNd(^+$Zc6ned!YLCp9C$=2)3}!5|Wm)N3NQAim9Szs*ajKs+_s4dIJr_HjEluT~AxUoON9A;p@D;L3IP z(;)rUNRo{IF%t+x^>%ORathW7^$tC9eH*2!Y0^gUJrv$dwUN7T2A1~Y*>BL12uUY`W@s}1bkw(xEDjd(IOvFAOoHIu#=HX3dY z>{v-(sL>{zNwToy?vA+ASuvn)==MRSM)tpO%~gs{J^KYPSJ3ITa6O{!aGGcq)z^20 zYJI4cjXQtvdI8ghb9+AiB|-JBGbsK&Ur@>1eO5B`k=(2Oejw-TfB1eG5b#2_ zZ70n@wY_z||8%o4EcQ@;7lmGn-1hP^UHvsxmUW8$e`@`vdh;(YrX==5T52M{bXKn; zEHH=nmMwF$rvy$aUpF!;7qP|SO1O4$cvrYL zEG$#A0b#EOV}Mh|jAA(VP4E8_{TIW5calE)By8Obn0Euu%f7QI&(ODX{RGJ5(_LcK zI?YV_9rc)}5Y}GSSCGcqozO;ygXC{vn=Ue@$n3^~aRL1PBR@{~(vX!4^_qeEk(JmkpDaAnjLRMg>Un zzjpGh)qn?u9OcM$gj3=ORym(P*w-Mc-<8u~^mW0+{3b+6=NK0x&2R==qJUpz)wyB8 zV@)Kx62G%IM>hvoVTGX`4hm%*m;GkQx{3}0lX%T1vPBHa&D;n@-!K+9n}VfQ@m6pa z`^ySqOS2|u0KN&esTagt++O@f(}VoYb9h^e(CSiE_@dq{qL)v^bG;(jSN!XtY{N>u zt@yAOv8t6H830ILZcVvv+TBuRQ$0p!?-RpvcSpqnuJP7T( z2+}v9ub0cYKWnn&1?Z$-hCHLl?zT+7UG`a15}-^kRb(sQ<%VK87tw!{1l&|sMwut$ z!h6j}^iS9D2SZa_$HwMq}z@tC%R_#~9vVu}f|kqgIY1f29}aWF{d5B6*=mh@=dzu72A{;bP9Lv$x< zvM~fRV&DK?Nk#*mT{ zZcVZKXEZZS2anj@kyn%cc0nd}6al29v<5%1kHa%d3N~IQ5M<>$X|tNO7)~k4G2g6m z_g`%1YS4FSd=Yh>8qB|dvYOmAq^Jjkc1ii6+5q4&R;OH~5s#|8yYoqk zQTrDO=j7#d^a8g_obvy+GZj$e=ZXSwt^WM-0{wWJPbfAqKH|%_q7O>Hj_<8>S|&5* zfeLA`NQz{|9ub$#a@J*c2=&{L)r#?;7+3I&0b;vlyVlzN`nG4eo5?~x(C`uo7l$|Y z_%R_q&OWC8Fu=4Rg97H{bi|=a)W&fc5Xz=w))2Y1swDRj79c<6+-LL-zGL|$l_Z4Z ztq;M^GYk*v7al4zg)&h{&9TbeXi@G+v*4z)uh_X|vuH>lqo0k?%YE742!i5oM0(d) zjBCY9L;C(RP{9iV?+2m_^-cACYt)31A*f?ICbeBXd{7E}UIyS9HTneV{v-tjIGf%8 z5d9EK)v-FXa7?`U3#55Rs{oOmeRQB*cYGWC*bu$16Hjl1q`LB=(u8?%1j#YAT#6Xr zI*=1hAr%$;lEXlJ$PJb~b7dwJyw71Y1mCxDBM)QDC!eb)&18ld{;KQ}%)liMb7^BQ zL5)8n-7RYCu~$#4V(VhFYE!j#xx=?vYQ698I~{#}qCr7=EN_!De#A5m)@B;zm!PmU zx=k;Z4x2qJLtC@ueDRczO!q_0WzUb*FpDcY>^gujP~T@*aPThY(?{*4G~k8)h-fHf z>Rd2wLfE~)bsi%zYf-ly2n8~Y=CF=o1m%)9cD_xnBrP_cdmM%?pL?`6+_;<(FbW<} zyEHrNG(JmCN3wBQA3KXL~6+r zRKqXzV7D<&rpbhli9C9172a78dymM`4^4B5A#O=)qRc!ow^gFTjyTcejuah9p)RU0J8}ba`kgJGIc%MxV!x8Wzc)(T^QpG5ED=%jyIEUtMmQJeNLZ!-%>9-owt#@2xl=UKiP_&fo_ME3@2R7z) zwYcG}s4{~o@ZsM+zlhrt?0c&b0R!cz1&-5q)$pmGp^-+zJ8bESxI`v+UhDsHz|^bl zX=hxanxi-jX){~fvdAsp>&ngkTdbxFCckQn?0bRC4rW>v44)Mi2@Uha&hT0vN@(%` z(pF*L8y$sFDmudIpsT-<%^1pWU1 zu7Mu4>jHwjGeBYqM3!KB>XL0U)X%vSyO|Xu=1z6D*KfM~Fl0(1u6`&e6Qd}W4L_>- z_DSE5B}=Jl7mMtg*?-im0_5J10hQeE)9RjSUqs(%FEzsDgy32w1CMqfv$ z;a7prF+71nrkc>tUtd$qR=X@A$cM2toda~=TXR1<4mriN#PdP(uM@$$Y*$D8EXpcZ zmt96~)l5xt?>~~9MPR#!z$cGT16#N0y@B*o?sx-F!u|BCW@Jfa&e6l#Ir(aER@Hm`bF`l@B=oGy zJ!3ya$06+Jw*D3rPB>RdzfI=Q2+t^mbx}W5($CGS6`U>TKbjmKTo-G|bl|)fTM{jG z09f)caMXV`zN9mws(MAHjU@&c@HooPIt*vY%JJO5)dE>UL7Eb3Qp$z6EJj_5BPk{%R$C^NIk=@bulW_t{6&>&>C7`rv)03I5n|CJ9dxuKA_#u!Ut zo{$V|jZ)fmUjD*CVYA1@c|Lp+F$(2Oj_HUQw($krBpDW~oC?W5W%IA{!?Da{RMfb86eo5HPq)~W=0hoGl@mDrXZxG zlsbhx7=AbScEiOabhk2xt-{_l;hWoBg{Cq`J%Lr^6phcW41U3gmv@>~4w#ktHdVOy zSOwf`9+O8^zO_9Lb$pJ?aUU=N+*UX>sD48Q?q?m|5V_!Y1s!^4S}qODXA}*#C-PP@ z7VCQ~)1HHqwcw%6`wa3N*pL0-t>)uC$y+&F8_?~u+Psk})_Bq7zo7kJBHz>Pz?bQ? zI22r$@L(bZp`^KdUn~>jL_S|01>ZLXT@j>SM$ndM;DGTPdRgzP0828^Kk z@jv_RG#@@b6sd=D%~u-SSEOlX35(t3f6|SOF?fyY)Xu2o+;~sfwpy(%GcE?%QWWL~ z{nDfEB6)2xox_wP!Q@(MSocJ3zov{3OFJ(m!{^HJnNe0;TgLdMqs&eZ6 zS?^Lk4M1Ha5S}+U2+QvgG%m0@$=XxJ{+EE_c^zdG6vv{I*fs zbLb5Ky(Aa9KHVPnq#ll2rw=QjNZz=-hbwy%SFO@0A2chkvH@HK{1-d^B(aWj-LyPS zJiZMQAvyW*#Di%-ga;qlmE&QNM|I-JnG0^bP&`!?t-|X{|Rm=8)gZl^n9sf9smA!es zVd8Ca{?D&?n%w>%tsS!8zOua0cec3Ef29s!n`i}pR>eW!cFL>z(&aPtTZ+B@K=xzz zV^_=6`S|#?eb3wSC1a1*e;;p>R(QjkqsV-BO;w!ttf&@WY?_zD$LltkwE)gcK>JOu`Qo-L9E7ESEjU!VA=NFX7Y6%@{p7@lE zT(^thl8OaRKp;bIW&yy%2t~Mb(H4AI2QNc*VIwf<6p;D`+xgcM%NV zx}`;z>4zx{pYKEMq4+^4+XFeszsodUK&cZ-AaUwqX15v2O(eMCBtuv8m)X(>CGf*f z&G$!5pH*bNEe_MHH@`SEC*z{2&nRH1nv0Afrk+ULBP#Al!cBt9DL8jN z_#=8wDfiDTe9t7WlU)M_(^DlsUChWvB3a8(KnN#Ujh>vpNZL8@x&F`3g}5l01?JkE zB!{l+p}X)>!vxNUwl4@e-J1DCG7LcAH8J#BLv&LJ!N?G*=!@?rNc1$#$b@8Q7A@pEwo>|E#lIW(KM1OXEKIG!eGBC&s;G)%>qlL#wXd>^c72))q>dSN_~rM6<_iDA6%|Uk3_%;(|+q9~V#zl77&QSwFGD zLL(X~i2YBk=YJs!SC$cw*2!Z}dWf|e!(nwF+xfr8lP5iFR)#L!Y!G$Y^kYRdH#l9w zqKR<3uI?ipu@iMbS8RJe_$)*i>c*_CTk&l*yU#2$)kOV45BC_HdGDXgzSs;(@s(5D zV7UTqYQ>mN@vKFq*=U2^veuArgEM;c`p{CtHiQG7X~}UA_ncD)yg-CS<}olo(_zJw zJ3NSx8;+<>w2jlguHR_9lEqkw3oFsyKE+I-xcOmhpLNss_H#Y*^=7|vY^9-stC7j! z-}vRLNw>3UCs;gzn(gAy*!N99-^}Qipe3a1ENr2};$PaCyJS(~S%F205w790WM$1k z@2@!rMMsS$lQ=kYVf|`sc4xLYSHU9S3fl3ac|9D1c{iL7Xu)m8z}AKZ1BJ)Zr-%S-QoHPSo(d;2&MEI zew{F&`%Fw)*OlkeFWl#QMSj1ffdaX>wPlmXW4aniY?t+RX}!U2o`L@n*zGpkC?K1s zC1&hMKYMK{1<1K?e)^60FSCrC@B1t3`m>}9IxB03yFMi)Spg8P=Ozp9a}EQw1qE#+ zF=R|rwsKrWTQ9FmXrabq7Ww=qH_zYPALni5S|$ky0mar{A;g;lzBp8mG#ai@iqn>y z0XTu0&_k@Sq_kEZV>nv8NYnU7pcwkQSM|tuzvDLUlK_*;JLn-{Uz9?x3Uwq7H!)jw zG24I5J*PG#n<9tYEp68L+5@ttGJRrlK;o1)$>1` z9B-IP)-SpX&U_AQI2Pz*SZs=jI&&HK==oa4BvYcA0%j^&o7t%hq@s+^f7);;%H0NVvR0lopv$R6!L= zy!0H9wW{F4xm179`40;qvE0+$P=odZ#&185uh6#Mj1lovTj@ZP z;bQbG-`G70SL-Tx0S!eQk1`9ZGD+BGoE;$mT}``>rHIcjfH3h^fhBH zQ6Wt-W{-$e``1cYCjM70^)VBCGtQDiV#uv1oK)r)C&w`piPBh&WUI)v1mJ!~sbJb# zts8U@3+~;w2)3qrm{>Y9iQezaKCX=x3RbyQiYoT=1_>u9ln4JI33WT6*%h?rrZ%v-%lkLx ze@tV6#8#s<3#{+!)3gBNI3xf6C18u=b^IgM+F_NjCE(?t&h#+O>HgrHXuOw5Y|%SG z-?XE1h3(_u=4;YFaTdXgRi~)f$4vYOMUQ;%djY)j6lVFX^OW1k3(Huj5y76feO+Mf zr;z6j5s2>Ki=Vm9a-JDQPS9$^U#;*frBB;Q)ey(^F$FJ979mlzKK!@T&zp#Cs&2i2 z?lh#);*aB8yn)<}yZ)>Cct*X9LrjEc{~Nq(l=mRYpmekdGRp`hbE}AP_S!I8LnrID z*9%7^YK<|s@r`*%^Vz|?~JEcfHe`xP>CWaW>JGSBiR^jS`yX8*79>%y& z_*b!VGe+_l(&)h;p@SOz&Vgt{7MXN*Wj4bJ92i;LelurA>&!g$vI2K#ix7fr4%V;KKb8%ovD z$xz~PGQ%E$pu$QMgzr-okvp>S9TCKDs27!{uUcQ+K5c0Ne zEmh9ME18c59--vZ7AZL@|0c-*KiRLe?tLA7Pmg9S+}KDpv1whAjv3@blys>7y-H;v zLn(-U+DEobQf~03QBQjvq+1Tq;yW(llBr`vf3>%;kW~~XO4iU5%zlDg>0#5Yv?G4N{DhYq8V@9S59joC-sKUwKMzNBmBILzm|m9qm!# z&%0`p`c-GXL@{}RoV*Fpms-e3PW+8zFUc4dFImVSU$ADhwQp&lJZC(>B+doDXyoKdxq3F`ud(+iW<&E!j3Jn=n7r z+LdL_uC_c{-;hBs<>_DRjiyq@nTF>ulu-LRfl^8Gn|&R|H6{BOOtGo@376BA#>Fh;Lc`TnvI%uTvqJEPPU=Mc*t?;G-zvSkZ&SCAV+ z?7bE4#r^;WJgI0)eXd@$=01I2YPQw%Iz@F%&bn67cuSx0qW7-V-W0q|R=>A8`*riM zeN`b5b-a_g=_lfMx0{O)aE%{;nX}Rfy6Of4sO?Cf!P;_xF>Ot6h(h~%G|{T|uzG-N z`@fY{KVh9fLiy)`A%{`3bjuZ&R&QMwt)BFHh@IJ=%$Sg$L1>m-2Cn9~+ihbjIO0W5 z$Gd{3^Cd7-d*bZNA{XvqgDYc2Yddd*h%h-2rsBued>jFtEeZ~A)G^Fk4YIQRK$gGE zWY(*CC44ZO90?Av9D^9emTk0atcKfz*!y}f#IMv_w&v92j7*%L&fVdlgb6v4L^!#< zxh*N8o+yAjU^k_?G0oH?H!jL&$t2RYJJqZeYOQ2E^giM#soCM({xxv9$$IQ4e3maIyTt7W z!Ty)$lqM3xVw-aaq9n4hm3e4)MS>#&^E*vPSyC28ale7qCu z)Og@yT;&=rFjFEpKp`I{TzvPesEiP>tyWD3Md`t7A!EvWQb6f+eZl-gbhV&I2A7ri z4R(sP$u4+fev^5e4MrLBzQ?Hz5Ab=*fnhQm!cu~;G6+za>1Dd`0eji`olp3VfHA%(-w(sUv z5Rkd3NoQn><`ET>li*CukP!2>Os7Rtv6|hYv=|~cehHU;v)+=HK(fAHbrzVxB*XD* zyY)0EF%?QG2sr~S>3529#wHIdkMxPiSlBCit1#wDS#=_#MSEl`aE@p!*eI7Tz~!QU z9&55Ae!jnwV}~fd2%jYbl|LD+lPb%GBGb{5@lYs5R6dHRCtqHBw2%Wn(Qmg~172CT zeREy`oMDQMg-M>l8+(+#3&Jc53?5B>G|!dCOB3$wNS%KOc>11H>9q^I9uFDw2_|yV zB!gxoKoxkqYBbS8u1TD0O-?;W582zf0gKo=ILFBa$!w!xXKZ9zz}#k z;AKD=-p0B{zsx1Y`1dd6FSSZ-?bIWZf3`Xei3UF3Owq$)g>;s;D})%cB%TWBA@kF= z3Tj*wo`lMIo-1Fne7H5%q5|~&<*yCR!_kN^Xa%f)3Df)1;uG07 zBT6j)rb|>seP1Q_rC9s$q+GiWO~kf;NQlZ2e}%`~giKMh0;&j}^nSKj0W}LwIaaO4 zE2R1DfIG=})~xR6k!zV)`8X=@8r)TpPziw>Y%(*n<<2@V+v@r$&35$#WK)u*qh2fG zRdeD(*QU{L3}r`0MF_vbL!*#OX-AKN%y!mdU?J^z>xfC;C|X_)iyzM%RHyDP3=+Nj ze49CKC7X{;6Bh2Qms1=v8TF5p&(jouh;+H{2DO)Pl%ZJlDMF^;lEgXAO_NwSqHlbp z7Y9#l@Gdus*hzcN#!N77(wAojYbAPMMgw3@p4f#qd!G9v>Y@U%9*e!+UzewQePQ$y zp3G+mMlUOdsf7oKH;EdN7LrUfAj8+vfn_3E@qCppsus#z(?;|EarKVTk+oabaBOz$ zq&v24+qP}n=-9T+PCB-2vto3tN;>#*-{+j?9pf8ojH(~Is`igvYh6ooPR9aU=`%LB zq~}Hcs0k%X3weRu()$ z0Zn9NfqTccx4X%~W4Q^5TMo!foOI$E6}JH}3i9E(U$kN0D@ltIWQ&VF z2w_kDO1-$0ed24itdpq_s~F}gf`r8rW`piqppO00>gTkt#^Y-wj+l#BJciO1U=nbO z!un9xy@`^Z@b*KGla#yvZg0ESK&TBdZGHE%+OWL|_|1?h9?}Pvh?+v7zBYMVFp>7U z`4U3KSX(Vl%n(gfDZ+jb-!{{nWOy)288a+-y5{0dgrx|-`7D|CMVr)%-qMc`f@}eo zZ`|&Mt~3Oo0AW|52$L-lmV8ty+hSHa!2D|xIn-?d&k^o184|)o|IpsI?2hdXW9!BB z25+Ty04JY5fc74Ht<&o?p8wlgHUMpV!nH^yJOcraoYQN$Uqm78MO(@yLc?BK?__2; zup;jpK~?jhTSMdauZFLVj$0i_dla9Ukt4eFEoq4XMwLM#qq0zas(gcvVC~>nH-(xQ z4GN{kFT=!ov5UgdfB4qv`S#>&-yjDWTVb~sP+w~h6>pkcN0Ild@FX_>Eu{k6YTxMo z-nT^X+|in%c#jS|tXKH}(EYqc(DT$%9|oIyQ(d@43BQ?IL!VK3KL3;AMc?>H#qL)b z8>owfTohr9L|fxJ4^quX(4qwsO=bkDE!*f~4Z@09d`O%!fpJ7O} z4fpyBMv;AngDOX{Mhm*h=i^-u^dkK1Qx*d!{P$;$t9ngOwGVBt z7bl<7v!Q=`+|!X@u8;-(F7_5MshSWDt}a-az{Xh|6w|anc5NsXr>NyfQEg8NVBj|G ztGJTwuHeT!B3t$*l+8!-JF6LyXQBk^V$>oO$H5(9CaAEW+QHU6Re2Q;N3=KBH?qXNDyuSWRQjtT(~YYmE`?*M#y1;` zY%6Y>K%3ENwB!rl1;PI4!F&yd;sBw{SV~Ck!B3H=g;y(- zk-b)$UYmp0JM4$sj2Tu%C~cfe7W>|wT#EE8E@+j(fv#J!^}QWJh$|I&zJ^n~7#Zai zerg~uCHI2bRVOX@0vFo*8(1Kk%_HZvn3j+!^Y@v#JXb1hj$6D$gSl7z#7^q?!ljI8 z{17naBac!Luy>kqjJZOc2DtgI-9$4Vt;7B#RAO+BM8=I>kQnhfQHGIK%lfkIR5n37 zYFA2lL}7nia|rGm1aoUN<%wWRgsjIgg~nBaG31vsRXZYp3+ znL(}4^Xc#T?Rjt{G;?l+v9jicKcdA42IoMmx`~Na7%44mKqDW6kpk7!2bA*1+bO>X zsHo!A(k(S7j(Q4h^z|uB_=Jj<8+lV{1wSv(RO}Z1G1G*i3=3FAKPpI9(%~HHDe$%Y z87%n>M0$^d=4;_OElxDZ#c81CMAG`a98<>eeG)De$?`s*z?*wcrS{UJ~s1;%Uy6~Rh#=CB+Vr5I5z7sg@Y9u0X4%%#%h`KkEqY}g^k|#Zd ze=#BaO)om`wP0mx;Nk%LO?#9qfro+TTbe9ng${07cgG#EIZ zs^j1_83y=#c{6Q$e~NHyhVbM5qOTw@M*+=oU?5|C7sC9UWPDU;_BkE5cgyU--GQR6Y?+tw)eeu% zftIl;vjaa_LbyVl$i{bgr@RZ%=;EdDwuQ%XJ5_xEJUs`vi;BAp`13;ou6qqNw||f| z5Aj#p&xN3kUDa0p4rZI>O7&wxRx`E#_(&3fj}OB3+bKkhQfS)6!9$7*c7kHy$~KS@ zp69}3kAn8<;%!Exwp;w2`4xzONVy9ymp4EHxa0}*M6^4$+?!LGFB0a06;x3heHjam5^&s2qm^klB!5Ip=vi7@# zkTmQ)!YGzt_F%>F-A4izQD89y{@g)MQF7np_nOVDpMDU1{hr}Ts&!E`1-2#81H(or zQ=A1{V~%~9U|C-jm7stblA%cKE&2zH(1jqJK$}6c->>OfQ&yhp7cn$COw`uNU^XMz zK{OkFow+#F1f&C~fkAkNt#5h0&iS#hG0-zMMzA@a1h6f(GwUb{P)QtKe^Qy7rbU>f zR7gn~ZZI~{%vo1$H-8ULQMeSvHE0?f5`Sl*FJF}$<#5Vm@}xivg!a4(bN&G=dJ*V$ z5>NAc!m9`QKx+6=a$>T1BaM!nkpI8_!bKd|l*nb>$^v`qbhv`wxe6fQZ9Ks0$WWiD zM*|J(8ebW3CXg+;q8G_BkO_7X0(N6|6Px;)ujM{RPtB|ORcfFWf|zvF;+2pI@?`z0 zj&ViIGc|0c%azkb1>zowS>vMc4oQ zosV$sz1lfYYw(%`@IA@(kdE`~JZ`7{v(?Vhbyvy&yQ=SF=Xu-OC8!asXT>bK64j%m zZ-EL~F+~YNfVf4_PSji}r4QEt^(CkT?V9oM;doJNkTm?xFiHJ4SzH7izA``Hsbe+V z;BD9dot_1N*64MxNi4b6h5vMIPAAz|D}ymQzG6gOQOO;ZmFrm<1fT9fheTDr7i3 zhXt|UZ4g`-yIWCF8YY>oqZL491}}u4WRa)dR}F^Y)jGTz3Mje| zdC3+VO;}`FO(AP38TBjNl=BKzRbLBN|HR(S7gHOkK4ztDpuVP2}1b3d!3 z(=;C5>Lx{iPYx0|#l?;sVR$X&qW;{ZMDDVm#a0zA>D1~Ziq325on(I$FZM(sY<|jl z_nJ;_bKAv9DVBB?py8`n=1RdASBlY=Z?^n!e~jZRd``(pW&f+5oobq#OeYOZnBC-d zQlh%l#i0A#RCyD*Yj;6ig~I=LIRjamK+rFk$7K^B(Mu8P(c_}h*7j6-lW|r0D*S+C zSLz2iW}tE+DHqe$p|QNls@z7UPaVJ?F$=6+_-wP>CsKYnB%Wlk9GPOhg4+!JNZk-M zjVuX41GF!COny&jZff_arbA3QQ{|@;v{UwXiQoog(J`BSw6PO%9TmLQDZi8eMf;xK#LsLSi^5jXcelqO3KTshb z)rocfl4vTl8~QI`h6m{^s#1@7Cj{NZQ!Kl(>!S0N+C*r495I)0L)DM$ZAeE1_=5_; ziV!gZx=Fv}ZToxdk{NB+XTKmCV=2^xUYE{dKLT6hO%r_nQY7@*or|?#8j@n%Yg*DS=P<^aAU!#=i=oZ{IsYvUNX@o1FMqwSQ7oHErvrqEQwQvzfjFBSm%U>KP&=ayMVvAi}QaD@3OlL=8dCWjb$j4D8 z=W0S$0DV30`z#Ik<&8dQ+T&uLFKj#}g70Ps92b1t(<^h!2AdrA$UJO?mtDruxltxt zb4HRO6!{x?)-DQ`U|W@cFUeT9E5?QMco;VAX@}pgEa^AQVdbXx;aF#-Z^bdc0J&E$ z-cJW�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�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